diff --git a/.github/workflows/verify-invocation.yml b/.github/workflows/verify-invocation.yml index 1bfb3d6a..42235bcd 100644 --- a/.github/workflows/verify-invocation.yml +++ b/.github/workflows/verify-invocation.yml @@ -11,12 +11,14 @@ on: - 'agents/**' - 'scripts/verify-invocation.py' - 'scripts/test-installer-transport.mjs' + - 'scripts/test-code-express-compat.mjs' - '.github/workflows/verify-invocation.yml' pull_request: paths: - 'agents/**' - 'scripts/verify-invocation.py' - 'scripts/test-installer-transport.mjs' + - 'scripts/test-code-express-compat.mjs' workflow_dispatch: jobs: @@ -34,3 +36,5 @@ jobs: run: python3 scripts/verify-invocation.py - name: Smoke test de transporte do installer run: node scripts/test-installer-transport.mjs + - name: Compatibilidade do /reversa-code-express + run: node scripts/test-code-express-compat.mjs diff --git a/agents/reversa-add/SKILL.md b/agents/reversa-add/SKILL.md index 318c41f3..739ae671 100644 --- a/agents/reversa-add/SKILL.md +++ b/agents/reversa-add/SKILL.md @@ -119,3 +119,13 @@ Termine com: **Nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto além do necessário para a emenda aprovada.** Nos artefatos do `_reversa_forward/` este skill é estritamente aditivo: acrescenta seção, linha de tabela e linha de log. Nunca reescreve corpo de `requirements.md`, nunca reordena `actions.md`, nunca regrava `legacy-impact.md` inteiro. Os artefatos da extração em `_reversa_sdd/` são somente leitura aqui, converger é trabalho do `/reversa-sync`. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer parte da emenda que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a emenda no código do projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (o registro da emenda nos artefatos de `_reversa_forward/` pode ser feito normalmente). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-code-express/SKILL.md b/agents/reversa-code-express/SKILL.md new file mode 100644 index 00000000..493d6390 --- /dev/null +++ b/agents/reversa-code-express/SKILL.md @@ -0,0 +1,252 @@ +--- +name: reversa-code-express +description: 'Passagem única do ciclo forward: entrevista curta, spec fina, código e rastros de impacto num só passo, sem parada intermediária. Para delta pequeno em base já conhecida (legado extraído ou greenfield). Recusa e devolve ao pipeline completo quando o delta cresce.' +disable-model-invocation: true +license: MIT +compatibility: Claude Code, Codex, Cursor, Gemini CLI e demais agentes compatíveis com Agent Skills. +metadata: + author: sandeco + version: "1.0.0" + framework: reversa + phase: forward + stage: express +--- + +Você é a passagem única do ciclo forward. O pipeline completo (`requirements`, `clarify`, `plan`, `to-do`, `audit`, `quality`, `coding`) custa sete a nove invocações e doze artefatos. Para um delta pequeno numa base já conhecida, esse custo é maior que o próprio delta, e o usuário acaba codando direto no chat, o que deixa a spec atrás do código. + +Sua missão é fechar esse intervalo: entrevistar uma vez, escrever a spec fina, executar o código e deixar os rastros de impacto, tudo numa passada só. + +Você não é o pipeline forward em versão barata. Você é o caminho para delta pequeno, e recusar o que não cabe faz parte do trabalho. + +## Exceção declarada ao padrão de handoff + +Todo agente do Reversa termina sugerindo o próximo e pedindo CONTINUAR. Este skill é a única exceção deliberada a esse padrão, e a exceção vale só para o meio do fluxo: entre a spec e o código você NÃO para, não pede confirmação, não oferece revisar o plano antes de executar. É exatamente isso que ele existe para fazer, um handoff no meio devolve o custo que o skill veio eliminar. + +O padrão continua valendo na saída: você encerra sugerindo o próximo passo e pedindo CONTINUAR, uma vez, no fim. + +## Antes de começar + +1. Leia `.reversa/state.json` para resolver `output_folder`, `forward_folder` e `user_name` +2. Use os valores reais nos lugares onde o texto mencionar `_reversa_sdd/` ou `_reversa_forward/` + +## Âncora de contexto: legado ou greenfield + +Este skill EXIGE uma âncora em `_reversa_sdd/`, pelo mesmo motivo do `/reversa-coding`: sem ela, `legacy-impact.md` e `regression-watch.md` perdem o valor e o express vira um gerador de código genérico. Duas âncoras são válidas: + +1. **Legado:** `_reversa_sdd/` contém `architecture.md` E `domain.md` +2. **Greenfield:** `_reversa_sdd/` contém `prd.md` E pelo menos uma spec em `_reversa_sdd/sdd/` + +Se as duas existirem, use a de legado como principal e as specs SDD como complemento. + +Se NENHUMA existir, aborte sem escrever nada: + +> 🛑 `/reversa-code-express` exige uma âncora de contexto em `_reversa_sdd/` e não encontrei nenhuma: +> +> - **Legado:** `architecture.md` + `domain.md` (gere com `/reversa`) +> - **Greenfield:** `prd.md` + specs em `sdd/` (gere com `/reversa-new`) +> +> O express é rápido porque a base já foi entendida uma vez. Sem isso, ele seria só um chute veloz. + +## Verificações Iniciais + +1. Aplique `before-code-express` da forma padrão (leia `.reversa/hooks.yml`, filtre `enabled: false`, ganchos `optional: true` viram link, `optional: false` viram `EXECUTAR: `; nunca avalie a chave `condition`) +2. Leia `.reversa/active-requirements.json` e detecte o estágio físico da feature anterior pelas MESMAS regras do `/reversa-requirements` (artefato físico manda, não o campo `current-stage`) + 2.1. Sem arquivo, JSON inválido, `feature-dir` inexistente, estágio `done` ou `vazio`: siga em frente + 2.2. Estágio `requirements`, `plan` ou `coding-em-progresso`: há feature em andamento, apresente o menu abaixo junto com a entrevista, na MESMA mensagem, para não gastar um turno só com isso + +> Já existe uma feature em andamento: `-`, estágio ``. +> +> **[1] Pausar a anterior e seguir com o express**, ela vai para `paused-features` e pode ser retomada com `/reversa-resume`. +> **[2] Abortar o express**, você retoma a anterior pelo pipeline normal. +> **[3] Outro**, descreva o que prefere. + +A opção 1 segue as mesmas regras de pausa do `/reversa-requirements` (copiar os campos para `paused-features`, jamais apagar a pasta antiga). A opção 2 encerra sem escrever nada. + +## Trava de escopo + +Avalie ANTES de escrever qualquer coisa. Basta um item para recusar. + +**Recuse se o delta exigir qualquer um destes:** + +- tocar mais de **três** módulos ou pastas de primeiro nível do código +- migração de dados, ou mudança de schema sobre dados que já existem +- mudar contrato público **já existente**: endpoint publicado, assinatura pública, formato de payload já consumido por terceiro +- alterar caminho de autenticação, permissão ou pagamento **já existente** +- mais de **doze** ações atômicas para ficar de pé + +**Não recuse por estes, eles são o caso normal do express:** + +- superfície pública nova: comando novo, endpoint novo, tela nova, evento novo +- dependência nova +- arquivo novo, pasta nova, plugin novo + +A fronteira é o **tamanho do delta**, não o ineditismo dele. Criar coisa nova numa base conhecida é barato. Mexer no que já está de pé, com gente consumindo, é que é caro. + +Ao recusar, diga qual item falhou e por quê, e encerre com: + +> Isso é feature de ciclo completo, não express. Rode `/reversa-requirements` para abrir o pipeline. + +Não escreva nada em disco depois de recusar. Não ofereça fazer "só a parte pequena". + +## Entrevista única + +Uma rodada só, três perguntas, numa única mensagem: + +1. O que essa feature entrega, em uma ou duas frases +2. Onde ela encosta no código atual (componente, pasta, arquivo). Se não souber, diga, e eu infiro do `_reversa_sdd/` +3. Como você sabe que funcionou, um critério observável + +Regras: + +- O que o argumento livre passado ao skill já responder, NÃO repergunte +- Resposta parcial não abre segunda rodada. Siga com o que tem e marque `[DÚVIDA]` no `requirements.md`, no máximo dois marcadores +- NUNCA faça uma segunda rodada de perguntas. Se depois da primeira ainda faltar informação essencial para decidir o escopo, isso é sinal de delta grande: volte à trava e recuse + +## Leitura de contexto, fatiada + +O que faz o express ser rápido é o que ele NÃO lê. Não carregue o `_reversa_sdd/` inteiro. + +**Cenário legado**, nesta ordem, pulando o que não existir: + +1. `_reversa_sdd/inventory.md`, só para localizar os componentes citados na entrevista +2. `_reversa_sdd/architecture.md`, SOMENTE as seções desses componentes +3. `_reversa_sdd/domain.md`, SOMENTE as regras desses componentes +4. `_reversa_sdd/addenda/*.md` vigentes que citem esses componentes (seção Vigência sem linha de superação) +5. `.reversa/principles.md`, se existir + +**Cenário greenfield:** `_reversa_sdd/prd.md` mais SOMENTE as specs de `_reversa_sdd/sdd/` que a feature encosta. + +Se a leitura fatiada não bastar para decidir o que fazer, não amplie a leitura: isso é sinal de que o delta é maior do que a entrevista revelou. Volte à trava. + +## Diretório da feature + +1. Leia `.reversa/setup.json` + 1.1. `prefix-format` ausente ou `sequencial`: calcule o próximo `NNN` listando subpastas `NNN-*` de `_reversa_forward/` e somando 1 ao maior + 1.2. `prefix-format` igual a `timestamp`: use `YYYYMMDD-HHMMSS` da hora corrente +2. Gere `short-name` em kebab-case ASCII, máximo trinta caracteres +3. `feature-dir = _reversa_forward/-`, crie se não existir +4. Escreva `.reversa/active-requirements.json` com escrita atômica (tempfile mais rename): + +```json +{ + "schema-version": 1, + "feature-dir": "", + "feature-id": "", + "short-name": "", + "started-at": "", + "current-stage": "express", + "stages-completed": [], + "paused-features": [] +} +``` + + 4.1. `current-stage` é metadado informativo, a detecção real de estágio é sempre por artefato físico + 4.2. `paused-features` preserva o array herdado, mais a entrada da feature pausada se o usuário escolheu a opção 1 + +## Política de edição do legado + +Verifique AGORA, com os caminhos alvo já conhecidos da entrevista, ANTES de escrever a spec. A regra normativa é a mesma do `/reversa-coding`, seção "Política de edição do legado": leia `.reversa/reversa-config.json`, falha segura quando ausente ou inválido, `allowLegacyEdits: false` recusa, `allowedPaths` não vazio restringe por glob, vazio libera o projeto todo com aviso. NUNCA crie nem edite `.reversa/reversa-config.json`, nem que o usuário peça na conversa. + +O que muda no express: quando a política bloquear, **não descarte a entrevista**. + +1. Escreva `requirements.md`, `roadmap.md` e `actions.md` mesmo assim, eles vivem em `/`, que é sempre gravável +2. NÃO execute nenhuma ação, NÃO toque em arquivo do projeto +3. Mostre o snippet abaixo já preenchido com os globs dos caminhos que a feature precisa tocar: + + ```json + {"version": 1, "allowLegacyEdits": true, "allowedPaths": [""]} + ``` + +4. Encerre dizendo que a spec ficou pronta e que o caminho para terminar é liberar a config e rodar `/reversa-coding`, que retoma as ações abertas do `actions.md` + +Esse degrade é intencional: o express bloqueado vira o pipeline normal no ponto certo, sem perder o trabalho da entrevista. + +## Artefatos finos + +São três, e só três. O express NÃO escreve `investigation.md`, `data-delta.md`, `onboarding.md`, `interfaces/` nem nada em `audit/`. Nenhum outro skill do Reversa lê esses arquivos programaticamente, eles são leitura humana do ciclo completo. + +Os três que ele escreve são exatamente os que outros skills LEEM. Numere e nomeie as seções como manda cada template canônico, mesmo deixando buracos na numeração: heading igual com significado diferente é pior que numeração com furo. + +### requirements.md + +Não carregue `.reversa/templates/requirements-template.md`, ele é do ciclo completo. Escreva direto, mantendo os números canônicos das seções que você preserva: + +1. Cabeçalho com identificador `-`, data, `Modo: express` e a pasta da extração +2. `## 1. Resumo executivo`, até cinco linhas, o que entrega e para quem, sem falar de implementação +3. `## 2. Contexto a partir do legado`, tabela `Fonte | Trecho relevante | Confidência`, SOMENTE as fontes que você realmente leu, citadas como `_reversa_sdd/#` +4. `## 4. Regras de negócio novas ou alteradas`, itens `RN-01`, `RN-02`, cada um com tipo (nova, alterada, removida) e confidência 🟢 🟡 🔴 +5. `## 7. Critérios de Aceitação`, derivados da terceira pergunta da entrevista, verificáveis +6. `## Emendas`, vazia, reservada ao `/reversa-add` + +As seções 3, 5, 6, 8, 9, 10 e 11 do template canônico ficam de fora. Os números 4 e 7 são mantidos de propósito, são os mesmos números que essas seções têm no ciclo completo. + +A linha `Modo: express` no cabeçalho não é enfeite: é como uma leitura futura sabe que essa spec é fina de propósito, e não uma spec completa mal preenchida. + +### roadmap.md + +Aqui está a razão pela qual o express escreve um roadmap, mesmo sendo express: a **detecção de estágio físico** do Reversa (usada por `/reversa-forward`, `/reversa-requirements` e `/reversa-resume`) classifica como estágio `requirements` toda pasta que tenha `requirements.md` e não tenha `roadmap.md`. Sem esse arquivo, uma feature express CONCLUÍDA seria lida como feature parada no começo, e o `/reversa-forward` mandaria o usuário para `/reversa-plan` de uma coisa já entregue. + +Então escreva um roadmap magro, duas seções, com os números canônicos: + +1. Cabeçalho com identificador, data e `Modo: express` +2. `## 1. Resumo da abordagem`, até cinco linhas, como a feature vai ser feita +3. `## 5. Delta arquitetural`, tabela `Componente | Tipo | Arquivo alvo` com o que vai ser criado ou tocado + +Nada além disso. Sem princípios, sem premissas, sem riscos, sem plano de migração: se a feature precisasse dessas seções, ela não teria passado na trava de escopo. + +### actions.md + +Mantenha o formato canônico do `actions-template.md`, sete colunas: `ID | Descrição | Dependências | Paralelismo | Arquivo alvo | Confidência | Status`. + +- IDs `T001`, `T002`, zero-padded, nunca reciclados +- Máximo doze ações, cada uma atômica +- Use SOMENTE nomes de fase canônicos, e no máximo dois: `## Fase 1, Preparação` (só se houver setup real, como dependência nova) e `## Fase 3, Núcleo` (todo o resto) +- Ações nascem `[ ]` e viram `[X]` conforme a execução, nunca nascem fechadas: uma execução parcial precisa ficar visível para o `/reversa-sync` +- Inclua `## Notas de execução` e `## Histórico de alterações`, com a linha inicial creditando `/reversa-code-express` + +O nome de fase canônico não é formalidade: se o express parar no meio, o `/reversa-coding` retoma as ações abertas sem tradução nenhuma. + +## Execução + +Sem parada, sem confirmação, sem pedir CONTINUAR. Execute `## Fase 1, Preparação` antes de `## Fase 3, Núcleo`, respeitando dependências e o marcador `[//]`. + +Para cada ação concluída: + +1. Atualize `feature-dir/actions.md` de `[ ]` para `[X]` +2. Faça append em `feature-dir/progress.jsonl`, uma linha por ação, append-only, jamais reescrevendo linha anterior: + +```json +{"ts":"2026-05-05T16:30:00Z","action":"T003","status":"done","files":["src/x/y.js"]} +``` + +Se uma ação falhar: mantenha `[ ]`, registre `status: failed` no progress, pare e relate. + +**Trava em tempo de execução.** Se no meio da execução aparecer necessidade que estouraria a trava de escopo (um quarto módulo, uma migração, um contrato existente que precisa mudar), pare ali. Registre o motivo em `## Notas de execução` do `actions.md`, deixe as ações restantes em `[ ]` e relate. Não force a barra para terminar, e não amplie o escopo por conta própria. + +## Rastros derivados do diff + +Depois de executar, mesmo que parcialmente, gere `legacy-impact.md` e `regression-watch.md` seguindo exatamente as regras do `/reversa-coding` (seções "Geração do legacy-impact.md" e "Geração do regression-watch.md"), incluindo as adaptações do cenário greenfield. + +Duas diferenças de cabeçalho: registre `Modo: express` e o estado da política de edição do legado no momento da execução (`allowLegacyEdits` e os caminhos que `allowedPaths` liberou). + +Esses dois arquivos são a razão de o express ainda ser Reversa e não código cru: eles nascem do diff real, não de previsão. + +## Ganchos Pós-execução + +Aplique `after-code-express` da forma padrão. + +## Relatório final + +Esta é a única parada do skill. + +1. Quantas ações executadas, quantas falharam +2. Caminhos absolutos de `requirements.md`, `roadmap.md`, `actions.md`, `progress.jsonl`, `legacy-impact.md`, `regression-watch.md` +3. Quantos watch items foram criados +4. Uma linha declarando o que o express não produziu de propósito: `investigation.md`, `data-delta.md`, `onboarding.md`, `interfaces/` e `audit/`. Se o delta crescer daqui para frente, o caminho é abrir feature nova com `/reversa-requirements`, não emendar o express +5. Se a execução foi parcial, diga qual ação está pendente e que `/reversa-coding` retoma dali + +Termine com: + +> Digite **CONTINUAR** para prosseguir com `/reversa-sync` (convergência da entrega na extração) ou outra ação que você quiser. + +NUNCA dispare a re-extração `/reversa` sozinho, isso é decisão do usuário. diff --git a/agents/reversa-code-express/agents/openai.yaml b/agents/reversa-code-express/agents/openai.yaml new file mode 100644 index 00000000..5fa14783 --- /dev/null +++ b/agents/reversa-code-express/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Reversa Code Express" + short_description: "Passagem única do ciclo forward: entrevista curta, spec fina…" +policy: + allow_implicit_invocation: false diff --git a/agents/reversa-coding/SKILL.md b/agents/reversa-coding/SKILL.md index c5479d8f..17dd6a5d 100644 --- a/agents/reversa-coding/SKILL.md +++ b/agents/reversa-coding/SKILL.md @@ -50,7 +50,28 @@ A verificação continua estrita quando NENHUMA âncora existe: o skill aborta c 3.4. No caso do passo 3.3, NÃO crie `legacy-impact.md`, NÃO crie `regression-watch.md`, NÃO toque em `actions.md`, NÃO escreva `progress.jsonl`. Apenas relate e encerre. -4. Aplique `before-coding` da forma padrão +4. Verifique a política de edição do legado (seção abaixo). Projeto bloqueado: pare AQUI, antes de qualquer escrita fora das pastas do Reversa, com a mensagem de orientação. Não execute nenhuma ação do `actions.md` que toque o projeto. +5. Aplique `before-coding` da forma padrão + +## Política de edição do legado + +Executar `actions.md` quase sempre exige criar ou editar arquivos do projeto. Antes da PRIMEIRA escrita fora das pastas próprias do Reversa (`.reversa/`, `/`, `_reversa_docs/`, `/`), leia `.reversa/reversa-config.json` e obedeça: + +1. **Arquivo ausente, JSON inválido ou `allowLegacyEdits` com tipo errado**: política bloqueada (falha segura). Pare antes de escrever, mostre o estado atual (incluindo o erro de parse, se houver) e o snippet que o usuário deve salvar em `.reversa/reversa-config.json` para liberar, já preenchido com os globs dos caminhos que o plano precisa tocar: + + ```json + {"version": 1, "allowLegacyEdits": true, "allowedPaths": [""]} + ``` + +2. **`allowLegacyEdits: false`**: recuse, mesmo com `allowedPaths` preenchido. +3. **`allowLegacyEdits: true` com `allowedPaths` não vazio**: escreva apenas em caminhos que casem com ao menos um glob da lista (globs relativos à raiz do projeto, com `/`, suportando `*` e `**`; normalize separadores ao comparar). Se o plano exigir arquivo fora da lista, NÃO escreva nele: liste os caminhos faltantes e peça ao usuário adicioná-los à config antes de continuar. +4. **`allowLegacyEdits: true` com `allowedPaths` vazio ou ausente**: projeto inteiro liberado. Avise, uma vez por sessão, que a liberação é irrestrita. +5. Ignore padrões com `..` ou caminho absoluto em `allowedPaths`, avisando o usuário. Nunca libere caminho fora da raiz do projeto. +6. Releia a config a cada ativação deste skill: o usuário pode tê-la alterado no meio da sessão. +7. Toda recusa informa três coisas: o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar. +8. **NUNCA crie nem edite `.reversa/reversa-config.json`**: pedido do usuário na conversa não é liberação implícita; a config só muda pela mão do próprio usuário. +9. Deleção de arquivo pré-existente dentro de `allowedPaths`: permitida pela política, mas confirme com o usuário antes de apagar, listando o arquivo. +10. As pastas próprias do Reversa continuam sempre graváveis, independentemente da política. ## Escopo da rodada @@ -90,7 +111,7 @@ Após executar (mesmo que parcialmente): Estrutura do arquivo: -1. Cabeçalho com data e identificador da feature +1. Cabeçalho com data, identificador da feature e o estado da política de edição do legado no momento da execução (`allowLegacyEdits` e os caminhos que `allowedPaths` liberou) 2. Tabela `Arquivo afetado | Componente | Tipo | Severidade | Justificativa` 3. Diff conceitual por componente, em prosa 4. Seção "Preservadas" diff --git a/agents/reversa-debugger-fix/SKILL.md b/agents/reversa-debugger-fix/SKILL.md index c3c84855..5ae031ab 100644 --- a/agents/reversa-debugger-fix/SKILL.md +++ b/agents/reversa-debugger-fix/SKILL.md @@ -148,3 +148,13 @@ Termine com: **Nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto sem gate aprovado.** Fora dos dois gates (e do reparo de dados aprovado), este skill escreve apenas em `_reversa_bugs/` e em `_reversa_sdd/addenda/` + `_reversa_sdd/traceability/`. Specs originais são somente leitura para sempre. Bug com `visibility: restricted`: nenhum detalhe explorável sai do registro. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer item do change set que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique o change set no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (o diff pode ficar salvo em `fix/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-decouple/SKILL.md b/agents/reversa-decouple/SKILL.md index 94f89b28..a984b37f 100644 --- a/agents/reversa-decouple/SKILL.md +++ b/agents/reversa-decouple/SKILL.md @@ -66,3 +66,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve só em `_reversa_refactor/`. Comportamento observável nunca muda; redução de acoplamento sem número comprovado não é aceita. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-migrate/SKILL.md b/agents/reversa-migrate/SKILL.md index 2b066f93..d3a9438b 100644 --- a/agents/reversa-migrate/SKILL.md +++ b/agents/reversa-migrate/SKILL.md @@ -234,6 +234,16 @@ Este agente faz parte do Time de Migração e escreve exclusivamente em `_revers - Modo padrão é interativo. `--auto` é explícito e exibe os defaults antes de aplicar. - Cada pausa apresenta resumo + decisões pendentes; nunca prossegue silenciosamente. +## Política de edição do legado + +Este orquestrador escreve só em `_reversa_sdd/migration/`, mas se qualquer passo exigir escrita fora das pastas próprias do Reversa, leia antes `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: recuse a escrita informando o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar. +- `allowLegacyEdits: true` com `allowedPaths` não vazio: escreva apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. + ## Saída ``` diff --git a/agents/reversa-modularize/SKILL.md b/agents/reversa-modularize/SKILL.md index 5f8c596c..b88ebc3e 100644 --- a/agents/reversa-modularize/SKILL.md +++ b/agents/reversa-modularize/SKILL.md @@ -65,3 +65,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve só em `_reversa_refactor/`. Comportamento observável nunca muda. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-optimize/SKILL.md b/agents/reversa-optimize/SKILL.md index 641a7ab2..4fba2b7c 100644 --- a/agents/reversa-optimize/SKILL.md +++ b/agents/reversa-optimize/SKILL.md @@ -68,3 +68,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve só em `_reversa_refactor/`. Saída para os mesmos inputs nunca muda; otimização sem ganho medido não é aplicada. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-prune/SKILL.md b/agents/reversa-prune/SKILL.md index a90bd346..2b739b96 100644 --- a/agents/reversa-prune/SKILL.md +++ b/agents/reversa-prune/SKILL.md @@ -73,3 +73,13 @@ Termine com: ## Regra absoluta **Nunca remova código sem gate aprovado e sem prova de morte anexada.** Fora do gate, escreve só em `_reversa_refactor/`. Na dúvida, não remove: sinaliza como órfão suspeito. Regra de negócio confirmada nunca é tratada como morta. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-refactor/SKILL.md b/agents/reversa-refactor/SKILL.md index 21188525..1eaf479e 100644 --- a/agents/reversa-refactor/SKILL.md +++ b/agents/reversa-refactor/SKILL.md @@ -98,3 +98,15 @@ Termine com: **Nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto.** Este skill escreve APENAS em `_reversa_refactor/`. Código do projeto, specs e alma são somente leitura aqui. Este skill NUNCA aplica transformação: ele inventaria, prioriza e roteia. + +## Política de edição do legado + +A transformação que o especialista vai aplicar toca código do projeto, e gate aprovado não substitui a política. Antes de qualquer escrita fora das pastas próprias do Reversa (sua ou de um especialista roteado por você), leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: recuse a escrita informando o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar. +- `allowLegacyEdits: true` com `allowedPaths` não vazio: escreva apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. + +Ao rotear uma oportunidade cujo alvo está fora dos caminhos liberados, avise o usuário no menu: o especialista vai travar no gate até a config liberar o caminho. diff --git a/agents/reversa-restructure/SKILL.md b/agents/reversa-restructure/SKILL.md index d40d350a..2945f600 100644 --- a/agents/reversa-restructure/SKILL.md +++ b/agents/reversa-restructure/SKILL.md @@ -66,3 +66,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve apenas em `_reversa_refactor/`. Comportamento observável nunca muda; o que não provar preservação para no gate. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-simplify/SKILL.md b/agents/reversa-simplify/SKILL.md index bfd45c8f..ca81b71d 100644 --- a/agents/reversa-simplify/SKILL.md +++ b/agents/reversa-simplify/SKILL.md @@ -67,3 +67,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve só em `_reversa_refactor/`. O resultado nunca muda; complexidade essencial exigida por regra confirmada não é removida. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/agents/reversa-standardize/SKILL.md b/agents/reversa-standardize/SKILL.md index 0dcb3a79..729c9fa0 100644 --- a/agents/reversa-standardize/SKILL.md +++ b/agents/reversa-standardize/SKILL.md @@ -64,3 +64,13 @@ Termine com: ## Regra absoluta **Nunca apague, modifique ou sobrescreva código do projeto sem gate aprovado.** Fora do gate, escreve só em `_reversa_refactor/`. Nenhuma mudança semântica: se um passo mudaria comportamento, ele não pertence aqui, pertence ao especialista certo. + +## Política de edição do legado + +Gate aprovado não substitui a política: antes de aplicar qualquer transformação que toque arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça (releia a cada ativação): + +- Ausente, inválido ou `allowLegacyEdits: false`: NÃO aplique a transformação no projeto. Informe o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar (a oportunidade e o plano podem ficar registrados em `_reversa_refactor/` aguardando). +- `allowLegacyEdits: true` com `allowedPaths` não vazio: aplique apenas em caminhos que casem com algum glob da lista (relativos à raiz, com `/`); fora da lista, recuse e peça o glob. +- `allowLegacyEdits: true` sem `allowedPaths`: liberado; avise uma vez por sessão que a liberação é irrestrita. +- NUNCA crie ou edite `.reversa/reversa-config.json`: aprovação do gate ou pedido na conversa não é liberação; a config só muda pela mão do usuário. +- Deleção de arquivo pré-existente liberado: confirme com o usuário antes, listando o arquivo. diff --git a/evolucao/permissao-de-edicao-codigo-legado/legacy-code-edition.md b/evolucao/permissao-de-edicao-codigo-legado/legacy-code-edition.md new file mode 100644 index 00000000..e1aad311 --- /dev/null +++ b/evolucao/permissao-de-edicao-codigo-legado/legacy-code-edition.md @@ -0,0 +1,250 @@ +# Spec: Configuração de permissão de edição do legado (reversa-config.json) + +**Versão:** 1.3 +**Status:** Implementada (Reversa v1.2.59) +**Autor:** Sandeco (com apoio do Claude) +**Data:** 2026-08-21 +**Reviewers:** N/A + +--- + +## 1. Resumo + +O Reversa passa a ler um arquivo de configuração, `.reversa/reversa-config.json`, com uma política que por padrão proíbe qualquer alteração no código do sistema legado e que o usuário pode liberar de forma explícita, global ou restrita a caminhos específicos. Todos os agentes que operam o Reversa (Claude Code, Cursor, Codex ou outros) consultam essa política antes de criar, modificar ou apagar arquivos fora das pastas próprias do Reversa. + +--- + +## 2. Contexto e Motivação + +**Problema:** +A regra atual do Reversa é fixa e não-negociável: "Nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto legado. O Reversa escreve apenas em `.reversa/`, `_reversa_sdd/`, `_reversa_docs/` e `_reversa_forward/`". Essa regra protege o legado durante a análise, mas conflita com o ciclo `/reversa-forward`, cujo propósito é implementar código. Lida ao pé da letra, ela proíbe até a criação de arquivos novos dentro do repositório legado, e um agente pode travar no meio do pipeline citando a regra. + +**Evidências:** +Caso real no projeto deepseek-harness: o forward 001 (plugin de reconhecimento de voz) precisa criar dois pacotes novos dentro de `DEEPSEEK-HARNESS/packages/` e editar uma linha de um arquivo pré-existente (`packages/api/remotes/src/client/index.ts`). A exceção precisou ser registrada manualmente no requirements.md (RN-05) e ainda assim conflita com a cláusula "escreve apenas em" do CLAUDE.md do projeto, criando risco de recusa pelo agente durante o coding. + +**Por que agora:** +O ciclo forward está entrando em uso real. Sem uma política configurável, cada projeto exige remendos manuais no CLAUDE.md, e a proteção do legado passa a depender de texto improvisado em vez de um mecanismo padronizado do framework. + +--- + +## 3. Goals (Objetivos) + +- [ ] G-01: O usuário controla, por arquivo de configuração, se o Reversa pode alterar o legado, sem editar instruções de agente manualmente. +- [ ] G-02: O comportamento padrão (arquivo ausente ou recém-instalado) é idêntico ao atual: legado intocável. +- [ ] G-03: A liberação pode ser restrita a caminhos específicos, evitando o "tudo ou nada". +- [ ] G-04: A política vale para qualquer agente que opere o Reversa, porque é referenciada nos arquivos de instrução (CLAUDE.md, AGENTS.md, CURSOR.md) e nos SKILL.md dos fluxos que escrevem código. + +**Métricas de sucesso:** +| Métrica | Baseline atual | Target | Prazo | +|---------|---------------|--------|-------| +| Pipeline forward concluído sem recusa do agente por conflito com a regra do legado | Recusas possíveis (exceções manuais no CLAUDE.md) | 0 recusas em projeto com config liberando os caminhos da feature | 1ª release com a feature | +| Escritas fora dos caminhos permitidos em projeto com `allowLegacyEdits: false` | Sem mecanismo de medição | 0 ocorrências em teste manual do fluxo | 1ª release com a feature | + +--- + +## 4. Non-Goals (Fora do Escopo) + +- NG-01: Permissões por agente ou por usuário (a política é única por projeto). +- NG-02: Interface gráfica ou comando interativo para editar a configuração (edição manual do JSON nesta versão). +- NG-03: Integração com git (pre-commit, proteção de branch); a política atua no momento da escrita pelo agente. +- NG-04: Migração de esquema do JSON entre versões futuras (o campo `version` existe, mas só a versão 1 é definida aqui). +- NG-05: Enforcement duro em agentes que não suportam hooks (Cursor, Codex); nesses, a política é guardrail por instrução. + +--- + +## 5. Usuários e Personas + +**Usuário primário:** Desenvolvedor usando o Reversa em um projeto legado, com nível técnico suficiente para editar um JSON simples. +**Usuário secundário:** Alunos e equipes que recebem um projeto com Reversa instalado e não devem conseguir alterar o legado por acidente. + +**Jornada atual (sem a feature):** +1. O usuário roda `/reversa-forward` e o pipeline planeja criar código no repositório legado. +2. O agente encontra a regra não-negociável no CLAUDE.md e recusa ou hesita. +3. O usuário edita o CLAUDE.md do projeto à mão, escrevendo uma exceção improvisada. + +**Jornada futura (com a feature):** +1. O usuário abre `.reversa/reversa-config.json` e muda `allowLegacyEdits` para `true`, listando em `allowedPaths` os caminhos da feature. +2. O agente, instruído pelos arquivos de instrução e pelo SKILL.md, lê a configuração antes de escrever. +3. O pipeline forward cria e edita apenas os arquivos permitidos, sem recusas e sem remendos manuais. + +--- + +## 6. Requisitos Funcionais + +### 6.1 Requisitos Principais + +| ID | Requisito | Prioridade | Critério de Aceite | +|----|-----------|-----------|-------------------| +| RF-01 | O Reversa deve ler a política de edição do legado de `.reversa/reversa-config.json`, relativo à raiz do projeto onde o Reversa está instalado | Must | Com o arquivo presente e válido, o agente cita a política lida antes de qualquer escrita fora das pastas do Reversa | +| RF-02 | Com o arquivo ausente, o comportamento deve ser idêntico a `allowLegacyEdits: false` | Must | Em projeto sem o arquivo, o agente recusa escrita fora das pastas próprias do Reversa e informa como criar a configuração | +| RF-03 | Com `allowLegacyEdits: false`, o agente deve recusar criar, modificar ou apagar qualquer arquivo fora das pastas próprias do Reversa: `.reversa/`, `_reversa_sdd/`, `_reversa_docs/`, `_reversa_forward/`, `_reversa_bugs/` e `_reversa_refactor/`, mesmo que `allowedPaths` esteja preenchido | Must | Pedido de escrita em `src/main.py` com `allow=false` resulta em recusa com mensagem orientando a habilitar a config | +| RF-04 | Com `allowLegacyEdits: true` e `allowedPaths` não vazio, o agente deve escrever apenas em caminhos que casem com ao menos um padrão da lista; os demais continuam proibidos | Must | Com `allowedPaths: ["packages/voice/**"]`, escrita em `packages/voice/voice/src/index.ts` é aceita e em `packages/core/x.ts` é recusada | +| RF-05 | Com `allowLegacyEdits: true` e `allowedPaths` vazio ou ausente, o agente deve tratar o projeto inteiro como liberado, e deve avisar o usuário uma vez por sessão que a liberação é irrestrita | Must | Sessão com config `{"allowLegacyEdits": true}` mostra o aviso na primeira escrita fora das pastas do Reversa | +| RF-06 | Os padrões de `allowedPaths` devem ser globs relativos à raiz do projeto, com barras normais (`/`), suportando `*` e `**` | Must | `packages/client/ui-voice/**` casa com qualquer arquivo sob essa pasta em Windows e Linux | +| RF-07 | As pastas próprias do Reversa (as seis listadas em RF-03) devem permanecer sempre graváveis, independentemente da política | Must | Com `allow=false`, escrita em `_reversa_forward/001-x/plan.md` ou `_reversa_bugs/ctx/bugs/b1/bug.md` continua aceita | +| RF-08 | Os arquivos de instrução gerados na instalação do Reversa (CLAUDE.md do projeto, AGENTS.md, CURSOR.md quando existirem) devem conter um parágrafo padrão mandando o agente ler `.reversa/reversa-config.json` antes de qualquer escrita fora das pastas do Reversa e obedecer ao resultado | Must | O texto padrão consta dos templates de instalação e aparece em projeto recém-instalado | +| RF-09 | Os SKILL.md dos fluxos do Reversa que escrevem ou alteram código devem incluir o passo de verificação da política antes da primeira escrita: forward/coding, add, migrate, debugger-fix, refactor e os 7 especialistas que aplicam transformação via gate (restructure, modularize, decouple, optimize, simplify, standardize, prune) | Must | Cada SKILL.md listado contém a instrução; o fluxo forward em projeto bloqueado para no passo de verificação com mensagem clara; nos fluxos com gate, aprovação do gate não substitui a política | +| RF-10 | Ao recusar uma escrita por política, o agente deve informar o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar | Must | Mensagem de recusa contém os três elementos | +| RF-11 | O Reversa deve fornecer um hook PreToolUse para Claude Code (script instalável) que bloqueia Write/Edit fora dos caminhos permitidos quando `allow=false` ou fora de `allowedPaths` quando `allow=true`, como enforcement duro opcional | Should | Com o hook instalado e `allow=false`, uma chamada Write em arquivo do legado é negada pelo hook antes de executar | +| RF-12 | O agente não deve criar nem editar `.reversa/reversa-config.json` por iniciativa própria; alterações no arquivo só ocorrem por pedido explícito do usuário na conversa | Must | Pedido "implemente X no legado" com config bloqueada resulta em recusa e orientação, nunca em auto-edição da config | +| RF-13 | A atualização do Reversa em repositório que já tem o Reversa instalado deve configurar a feature: criar `.reversa/reversa-config.json` com o default seguro (`allowLegacyEdits: false`) quando ausente, adicionar o parágrafo padrão aos arquivos de instrução existentes (CLAUDE.md, AGENTS.md, CURSOR.md) sem apagar conteúdo do usuário, e atualizar os SKILL.md dos fluxos de RF-09. A atualização informa o usuário que a feature existe e como liberá-la | Must | Após atualizar o Reversa num projeto pré-existente, o arquivo de config existe com `allowLegacyEdits: false`, os arquivos de instrução contêm o parágrafo padrão e o conteúdo anterior deles está preservado | + +### 6.2 Fluxo Principal (Happy Path) + +1. O usuário edita `.reversa/reversa-config.json` deixando `allowLegacyEdits: true` e `allowedPaths` com os globs da feature. +2. O usuário roda `/reversa-forward` e o pipeline chega ao passo de implementação. +3. O agente lê a configuração, confirma que os arquivos-alvo casam com `allowedPaths` e registra no documento do forward quais caminhos a política liberou. +4. O agente cria e edita apenas os arquivos permitidos. +5. Resultado: feature implementada, `git status` do legado mostra apenas mudanças dentro dos caminhos liberados. + +### 6.3 Fluxos Alternativos + +**Fluxo Alternativo A, projeto bloqueado:** +1. O agente chega ao passo de implementação com `allowLegacyEdits: false` (ou arquivo ausente). +2. O agente para antes da primeira escrita, exibe o estado da política e o snippet de JSON que o usuário deve salvar para liberar, e encerra o passo sem escrever. + +**Fluxo Alternativo B, caminho fora da lista:** +1. Durante a implementação, o plano exige tocar um arquivo que não casa com `allowedPaths`. +2. O agente não escreve nesse arquivo; lista os caminhos faltantes e pede que o usuário os adicione à config antes de continuar. + +--- + +## 7. Requisitos Não-Funcionais + +| ID | Requisito | Valor alvo | Observação | +|----|-----------|-----------|------------| +| RNF-01 | Compatibilidade retroativa | Projetos sem o arquivo comportam-se exatamente como hoje | Nenhuma migração exigida | +| RNF-02 | Portabilidade | Globs funcionam em Windows e Unix | Sempre `/` no JSON; o agente normaliza separadores ao comparar | +| RNF-03 | Legibilidade | JSON de no máximo 3 campos na versão 1 | Editável à mão por iniciante | +| RNF-04 | Falha segura | Qualquer erro de leitura ou parse resulta em política bloqueada | Ver EC-01 e EC-02 | + +--- + +## 8. Design e Interface + +**Componentes afetados:** templates de instalação do Reversa (CLAUDE.md do projeto, AGENTS.md, CURSOR.md), SKILL.md dos fluxos que escrevem ou alteram código listados em RF-09 (12 skills), e um novo script de hook opcional para Claude Code. + +**Comportamento esperado:** +Não há UI. A interface é o próprio JSON e as mensagens do agente. Texto padrão de recusa (referência): "A política do Reversa (`.reversa/reversa-config.json`) proíbe alterar ``. Estado atual: allowLegacyEdits=, allowedPaths=. Para liberar, edite o arquivo e adicione o caminho desejado." + +**Estados da UI:** +- Estado vazio: arquivo ausente; agente trata como bloqueado e informa como criar. +- Estado de carregamento: não aplicável. +- Estado de erro: JSON inválido; agente trata como bloqueado e mostra o erro de parse. +- Estado de sucesso: escrita realizada; o documento do forward registra os caminhos usados sob a política. + +--- + +## 9. Modelo de Dados + +**Entidades novas ou modificadas:** + +``` +reversa-config.json (versão 1) { + version: number // fixo em 1 nesta versão + allowLegacyEdits: bool // default false; false bloqueia todo o legado + allowedPaths: string[] // opcional; globs relativos à raiz, só considerados quando allowLegacyEdits=true +} +``` + +Exemplo mínimo bloqueado: `{"version": 1, "allowLegacyEdits": false}` +Exemplo liberado restrito: `{"version": 1, "allowLegacyEdits": true, "allowedPaths": ["DEEPSEEK-HARNESS/packages/voice/**", "DEEPSEEK-HARNESS/packages/api/remotes/src/client/index.ts"]}` + +**Migrações necessárias:** Não. Arquivo novo, ausência equivale ao comportamento atual. + +--- + +## 10. Integrações e Dependências + +| Dependência | Tipo | Impacto se indisponível | +|-------------|------|------------------------| +| Arquivos de instrução do agente (CLAUDE.md, AGENTS.md, CURSOR.md) | Obrigatória | Sem o parágrafo padrão, o agente não sabe da política; instalação do Reversa deve incluí-lo | +| Hooks PreToolUse do Claude Code | Opcional | Sem o hook, a política vale como guardrail por instrução (comportamento igual aos demais agentes) | + +--- + +## 11. Edge Cases e Tratamento de Erros + +| Cenário | Trigger | Comportamento esperado | +|---------|---------|----------------------| +| EC-01: Arquivo ausente | Projeto sem `.reversa/reversa-config.json` | Tratar como `allowLegacyEdits: false`; informar como criar o arquivo ao recusar uma escrita | +| EC-02: JSON malformado ou campo com tipo errado | Erro de parse ou `allowLegacyEdits` não booleano | Tratar como bloqueado (falha segura); mostrar o erro ao usuário | +| EC-03: Glob que escapa da raiz | Padrão com `..` ou caminho absoluto em `allowedPaths` | Ignorar o padrão, avisar o usuário; nunca liberar caminho fora da raiz do projeto | +| EC-04: Config alterada no meio da sessão | Usuário edita o JSON após o início do trabalho | O agente relê o arquivo antes de cada novo bloco de escritas no legado (no mínimo, a cada ativação de skill) | +| EC-05: `allowedPaths` cobre as pastas do Reversa | Ex.: `_reversa_sdd/**` na lista | Redundante e inofensivo; as pastas do Reversa são sempre graváveis (RF-07) | +| EC-06: Pedido do usuário conflita com a config | Usuário manda editar o legado com política bloqueada | Recusar a escrita e orientar a editar a config; não tratar o pedido na conversa como liberação implícita (RF-12) | +| EC-07: Agente tenta se autoliberar | Qualquer fluxo tenta editar `reversa-config.json` sem pedido explícito | Proibido por RF-12; o hook (quando instalado) também nega escrita nesse arquivo | +| EC-08: Deleção de arquivo pré-existente | Plano exige apagar arquivo do legado dentro de `allowedPaths` | Permitido pela política, mas o agente confirma com o usuário antes de apagar, listando o arquivo | +| EC-09: Atualização sobre instalação com exceções manuais | Repositório atualizado já tem exceções improvisadas escritas no CLAUDE.md (caso do forward 001 deste projeto) | A atualização preserva o texto manual e sugere ao usuário migrar as exceções para `allowedPaths`, sem migrá-las automaticamente (RF-12) | + +--- + +## 12. Segurança e Privacidade + +- **Autenticação:** não aplicável; a política vale para qualquer operador do projeto. +- **Autorização:** a autoridade é o conteúdo do JSON no disco, editado pelo usuário. O risco central é autoautorização pelo agente, coberto por RF-12 e EC-07. +- **Dados sensíveis:** nenhum; o arquivo contém apenas política de caminhos. +- **Auditoria:** os documentos do ciclo forward registram, por feature, quais caminhos foram escritos sob qual estado da política (RF de rastreabilidade no passo 3 do fluxo principal). + +--- + +## 13. Plano de Rollout + +- **Estratégia:** big bang na próxima versão do Reversa. Instalações novas já nascem com a config; instalações existentes recebem a feature no fluxo de atualização (RF-13), com o default seguro tornando a mudança invisível até o usuário optar por liberar. +- **Como reverter (rollback):** apagar `.reversa/reversa-config.json` (volta ao comportamento atual) e remover o parágrafo padrão dos arquivos de instrução. +- **Monitoramento pós-deploy:** nos primeiros usos do forward, revisar `git status` do legado após cada implementação e conferir que só os caminhos liberados aparecem. + +--- + +## 14. Open Questions + +| # | Pergunta | Impacto | Dono | Prazo | +|---|---------|---------|------|-------| +| OQ-01 | O hook PreToolUse deve ser instalado por padrão pelo instalador do Reversa ou ficar como passo manual documentado? | Médio | Sandeco | Antes da release | +| OQ-02 | O `/reversa-forward` deve oferecer, ao detectar bloqueio, a geração do snippet de config já preenchido com os caminhos do plano (sem aplicá-lo, respeitando RF-12)? | Baixo | Sandeco | Antes da release | + +--- + +## 15. Decisões Tomadas (Decision Log) + +| Decisão | Alternativas consideradas | Racional | +|---------|--------------------------|---------| +| Arquivo em `.reversa/reversa-config.json` | JSON solto na raiz do projeto | `.reversa/` já é território do framework; evita poluir a raiz do legado | +| Booleano + lista de globs | Só booleano | O caso real quase nunca é "libera tudo"; a lista evita o 8 ou 80 | +| Falha segura (erro = bloqueado) | Erro = liberado ou perguntar | Proteger o legado é o propósito da regra; na dúvida, bloquear | +| Guardrail por instrução com hook opcional | Só hook obrigatório | O Reversa roda em agentes sem suporte a hooks (Cursor, Codex); a instrução é o denominador comum e o hook endurece onde possível | +| Agente proibido de editar a config | Permitir com confirmação | Autoautorização anula a política; a edição é ato exclusivo do usuário | + +--- + +## Apêndice + +### Referências +- Regra atual não-negociável: CLAUDE.md do projeto deepseek-harness (seção "Regra não-negociável"). +- Caso motivador: `_reversa_forward/001-plugin-reconhecimento-de-voz/requirements.md` (RN-05 e RNF Composição). +- Hooks do Claude Code (PreToolUse): documentação oficial do Claude Code. + +### Histórico de Revisões +| Versão | Data | Autor | Mudanças | +|--------|------|-------|---------| +| 1.0 | 2026-08-21 | Sandeco | Criação inicial | +| 1.1 | 2026-08-21 | Sandeco | RF-13 e EC-09: caminho de atualização em instalação pré-existente configura a feature | +| 1.2 | 2026-08-21 | Sandeco (implementação com Claude) | Feature implementada na v1.2.59. OQ-01 resolvida: o hook é instalado por padrão em `.reversa/hooks/`, mas o wiring no `.claude/settings.json` é passo manual documentado no `README.md` da pasta (o Reversa não toca settings do usuário). OQ-02 resolvida: o `reversa-coding`, ao detectar bloqueio, mostra o snippet de config pré-preenchido com os globs do plano, sem aplicá-lo (RF-12 respeitado). Nota de implementação: as pastas sempre graváveis incluem também `_reversa_bugs/` e `_reversa_refactor/`, territórios do Reversa criados depois da lista original de quatro pastas | +| 1.3 | 2026-08-21 | Sandeco | A spec absorve a nota de implementação: RF-03 e RF-07 passam a listar as seis pastas próprias do Reversa (incluindo `_reversa_bugs/` e `_reversa_refactor/`). RF-09 ampliado para cobrir todos os fluxos que escrevem código: entram `reversa-add` e os 7 especialistas do time Refactor, todos com o bloco da política no próprio SKILL.md e a regra de que gate aprovado não substitui a política | + +### Relatório de Avaliação (spec_scorer) + +``` +SCORE TOTAL: 88.0/100 — ✅ Boa — Pronta com ajustes menores + +Dimensão Score Peso Contribuição +Completude 100% 30% 30.0 +Testabilidade 72% 25% 18.0 +Clareza 75% 20% 15.0 +Escopo 100% 15% 15.0 +Edge Cases 100% 10% 10.0 + +Melhorias recomendadas: +- Métricas de sucesso sem valores numéricos (as metas usam "0 recusas"/"0 ocorrências"; heurística do scorer) +- Possível contradição entre requisitos (RF-03 vs RF-05: não há conflito, as condições allow=false e allow=true são mutuamente exclusivas) +``` diff --git a/lib/commands/update.js b/lib/commands/update.js index 1707713d..d6807db1 100644 --- a/lib/commands/update.js +++ b/lib/commands/update.js @@ -1,6 +1,7 @@ -import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs'; import { join, resolve } from 'path'; import { checkExistingInstallation } from '../installer/validator.js'; +import { renderLegacyEditPolicyParagraph } from '../installer/policy.js'; import { loadManifest, saveManifest, buildManifest, fileStatus } from '../installer/manifest.js'; import { Writer } from '../installer/writer.js'; import { ENGINES } from '../installer/detector.js'; @@ -110,6 +111,8 @@ export default async function update(args) { } const writer = new Writer(projectRoot); + let configCreated = false; + const appendedTo = []; const updateSpinner = ora({ text: 'Updating agents...', color: 'cyan' }).start(); try { @@ -161,6 +164,28 @@ export default async function update(args) { } } + updateSpinner.text = 'Configuring legacy-edit policy...'; + + // Política de edição do legado: config com default seguro quando ausente + hook opcional + configCreated = writer.ensureReversaConfig(); + writer.installPolicyHooks(); + + // Entry files modificados pelo usuário recebem o parágrafo padrão por append, + // sem apagar conteúdo (os intactos já foram reescritos com o template novo acima) + for (const engine of installedEngines) { + if (!engine.entryFile) continue; + const entryPath = join(projectRoot, engine.entryFile); + if (!existsSync(entryPath)) continue; + const content = readFileSync(entryPath, 'utf8'); + if (content.includes('reversa-config.json')) continue; + appendFileSync( + entryPath, + '\n\n## Política de edição do legado (Reversa)\n\n' + renderLegacyEditPolicyParagraph() + '\n', + 'utf8' + ); + appendedTo.push(engine.entryFile); + } + updateSpinner.text = 'Updating version...'; if (latestVersion && semver.valid(latestVersion)) { @@ -190,5 +215,18 @@ export default async function update(args) { if (modified.length > 0) { console.log(chalk.yellow(`\n ${modified.length} file(s) kept (modified by you).`)); } + + console.log(chalk.bold('\n Legacy-edit policy:')); + if (configCreated) { + console.log(` ${chalk.hex('#ffa203')('+')} .reversa/reversa-config.json created with the safe default (allowLegacyEdits: false).`); + } else { + console.log(chalk.gray(' .reversa/reversa-config.json already exists and was kept as is.')); + } + if (appendedTo.length > 0) { + console.log(` ${chalk.hex('#ffa203')('+')} Policy paragraph appended to: ${appendedTo.join(', ')} (your content was preserved).`); + } + console.log(' To let Reversa write in the legacy code, edit the file: allowLegacyEdits: true + allowedPaths with the desired globs.'); + console.log(' If your entry files carry manual exceptions, consider migrating them to allowedPaths.'); + console.log(' Optional hard enforcement for Claude Code: see .reversa/hooks/README.md'); console.log(''); } diff --git a/lib/installer/policy.js b/lib/installer/policy.js index 43710a70..c3a1344a 100644 --- a/lib/installer/policy.js +++ b/lib/installer/policy.js @@ -8,18 +8,46 @@ // Exportada também para uso futuro por updateGitignore/uninstall, que hoje // só conhecem `.reversa/` + output_folder e divergem da regra global. +// `_reversa_bugs/` e `_reversa_refactor/` entraram na v1.2.59 (spec +// legacy-code-edition v1.3): territórios dos times Bugs e Code Quality. export function getWritableFolders({ outputFolder = '_reversa_sdd', forwardFolder = '_reversa_forward', } = {}) { - return ['.reversa/', `${outputFolder}/`, '_reversa_docs/', `${forwardFolder}/`]; + return [ + '.reversa/', + `${outputFolder}/`, + '_reversa_docs/', + `${forwardFolder}/`, + '_reversa_bugs/', + '_reversa_refactor/', + ]; +} + +// Parágrafo padrão da política configurável de edição do legado (spec +// legacy-code-edition, RF-08). Entra em todos os entry files via +// renderPolicyBlock e é anexado a entry files modificados durante o update. +export function renderLegacyEditPolicyParagraph() { + return [ + 'Antes de criar, modificar ou apagar qualquer arquivo fora das pastas próprias do Reversa, leia `.reversa/reversa-config.json` e obedeça ao resultado:', + '', + '- Arquivo ausente, JSON inválido ou campo com tipo errado: trate como `allowLegacyEdits: false` (falha segura, nenhuma escrita fora das pastas do Reversa).', + '- `allowLegacyEdits: false`: recuse a escrita, informando o caminho recusado, o estado atual da config e o que o usuário deve editar para liberar.', + '- `allowLegacyEdits: true` com `allowedPaths` não vazio: escreva apenas em caminhos que casem com algum glob da lista (globs relativos à raiz do projeto, com `/`, suportando `*` e `**`).', + '- `allowLegacyEdits: true` com `allowedPaths` vazio ou ausente: projeto liberado; avise uma vez por sessão que a liberação é irrestrita.', + '', + 'Nunca crie nem edite `.reversa/reversa-config.json` por iniciativa própria: pedido na conversa não é liberação implícita, alterações nesse arquivo são ato exclusivo do usuário.', + ].join('\n'); } export function renderPolicyBlock(opts = {}) { const folders = getWritableFolders(opts).map((f) => `\`${f}\``); const list = `${folders.slice(0, -1).join(', ')} e ${folders[folders.length - 1]}`; return [ - 'Nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto legado.', - `O Reversa escreve apenas em ${list}.`, + 'Por padrão, nunca apague, modifique ou sobrescreva arquivos pré-existentes do projeto legado:', + `o Reversa escreve apenas em ${list}.`, + 'A única exceção é a política configurável abaixo, controlada exclusivamente pelo usuário.', + '', + renderLegacyEditPolicyParagraph(), ].join('\n'); } diff --git a/lib/installer/prompts.js b/lib/installer/prompts.js index af547d68..6f8552d8 100644 --- a/lib/installer/prompts.js +++ b/lib/installer/prompts.js @@ -49,6 +49,7 @@ const FORWARD_TEAM = [ 'reversa-audit', 'reversa-quality', 'reversa-coding', + 'reversa-code-express', 'reversa-add', 'reversa-sync', 'reversa-principles', diff --git a/lib/installer/writer.js b/lib/installer/writer.js index 95e98bd7..90705ab3 100644 --- a/lib/installer/writer.js +++ b/lib/installer/writer.js @@ -137,6 +137,10 @@ export class Writer { // Estrutura forward (templates de corpo, scripts, hooks, setup) this._installForwardAssets(reversaDir, answers, version); + // Política de edição do legado: config com default seguro + hook opcional + this.ensureReversaConfig(); + this.installPolicyHooks(); + // state.json const stateTemplate = readFileSync(join(TEMPLATES_DIR, 'state.json'), 'utf8'); const state = JSON.parse(stateTemplate.replace('{{VERSION}}', version)); @@ -248,6 +252,29 @@ export class Writer { } } + // Cria .reversa/reversa-config.json com o default seguro (allowLegacyEdits: false). + // Só cria se ausente: o conteúdo pertence ao usuário e jamais é sobrescrito (RF-12). + // Retorna true se o arquivo foi criado nesta chamada. + ensureReversaConfig() { + const src = join(TEMPLATES_DIR, 'reversa-config.json'); + if (!existsSync(src)) return false; + const dest = join(this.projectRoot, '.reversa', 'reversa-config.json'); + return this._writeNew(dest, readFileSync(src, 'utf8')); + } + + // Instala os hooks da política (script PreToolUse do Claude Code + README de + // instalação) em .reversa/hooks/. Enforcement duro opcional: o wiring no + // .claude/settings.json é passo manual documentado no README. + installPolicyHooks() { + const srcDir = join(TEMPLATES_DIR, 'hooks'); + if (!existsSync(srcDir)) return; + for (const file of readdirSync(srcDir)) { + const srcFile = join(srcDir, file); + if (!statSync(srcFile).isFile()) continue; + this._writeNew(join(this.projectRoot, '.reversa', 'hooks', file), readFileSync(srcFile, 'utf8')); + } + } + // Refresca body templates, scripts e hooks.yml em .reversa/ a partir do pacote npm, // pulando arquivos que o usuário modificou. setup.json é sempre preservado por carregar // dados específicos do projeto (project-name, installed-at, prefix-format do usuário). diff --git a/package.json b/package.json index d08a1dad..1f7ce23b 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "reversa", - "version": "1.2.58", + "version": "1.2.60", "description": "Transform legacy systems into executable specifications for AI coding agents", "bin": { "reversa": "bin/reversa.js" }, "type": "module", "scripts": { - "verify": "python3 scripts/verify-invocation.py && node scripts/test-installer-transport.mjs" + "verify": "python3 scripts/verify-invocation.py && node scripts/test-installer-transport.mjs && node scripts/test-code-express-compat.mjs" }, "keywords": [ "legacy", diff --git a/scripts/test-code-express-compat.mjs b/scripts/test-code-express-compat.mjs new file mode 100644 index 00000000..841b70c8 --- /dev/null +++ b/scripts/test-code-express-compat.mjs @@ -0,0 +1,338 @@ +#!/usr/bin/env node +// Teste de compatibilidade do /reversa-code-express. +// +// O express corta sete artefatos do ciclo forward. A aposta central do agente é +// que a pasta que ele deixa continua legível pelos skills que vêm depois. Este +// teste materializa uma feature no formato que o SKILL.md do express prescreve e +// checa três coisas, todas derivadas de texto normativo de outros skills: +// +// 1. A detecção de estágio físico (tabela replicada em reversa-forward, +// reversa-requirements e reversa-resume) classifica a feature como `done`. +// 2. As pré-condições declaradas de /reversa-sync, /reversa-add e +// /reversa-coding estão satisfeitas pelos artefatos que o express escreve. +// 3. As duas marcas do eixo de invocação atravessam o cpSync do installer. +// +// O caso negativo (fixture sem roadmap.md) existe para provar que a detecção de +// estágio de fato depende desse arquivo. Se ele passar a dar `done`, a razão de +// o express escrever um roadmap magro deixou de existir e a seção correspondente +// do SKILL.md pode ser revista. +import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); + +let failures = 0; +const check = (cond, msg) => { + if (cond) console.log(` ✓ ${msg}`); + else { console.error(` ✗ ${msg}`); failures++; } +}; +const eq = (actual, expected, msg) => check(actual === expected, `${msg} (esperado \`${expected}\`, obtido \`${actual}\`)`); + +// --------------------------------------------------------------------------- +// Detecção de estágio físico, transcrita da tabela canônica. +// Fonte: agents/reversa-forward/SKILL.md, seção "Detecção do estágio físico". +// A ordem das condições é a ordem das linhas da tabela. +// --------------------------------------------------------------------------- +const ACTION_ROW = /^\|\s*T\d{3}\s*\|/; +const OPEN_STATUS = /\|\s*`?\[ \]`?\s*\|\s*$/; +const CLOSED_STATUS = /\|\s*`?\[X\]`?\s*\|\s*$/; + +function actionRows(actionsPath) { + return readFileSync(actionsPath, 'utf8') + .split(/\r?\n/) + .filter(line => ACTION_ROW.test(line)); +} + +function detectStage(featureDir) { + if (!featureDir || !existsSync(featureDir)) return 'sem-feature-ativa'; + if (!existsSync(join(featureDir, 'requirements.md'))) return 'vazio'; + if (!existsSync(join(featureDir, 'roadmap.md'))) return 'requirements'; + if (!existsSync(join(featureDir, 'actions.md'))) return 'plan'; + const rows = actionRows(join(featureDir, 'actions.md')); + if (rows.length === 0) return 'plan'; + if (rows.some(r => OPEN_STATUS.test(r))) return 'coding-em-progresso'; + if (rows.every(r => CLOSED_STATUS.test(r))) return 'done'; + return 'indeterminado'; +} + +// Matriz de roteamento do /reversa-forward, apenas as linhas que o express alcança. +function routeFrom(stage, { hasAddendum = false } = {}) { + switch (stage) { + case 'requirements': return '/reversa-plan'; + case 'plan': return '/reversa-to-do'; + case 'coding-em-progresso': return '/reversa-coding'; + case 'done': return hasAddendum ? 'conclusao' : '/reversa-sync'; + default: return '/reversa-requirements'; + } +} + +// --------------------------------------------------------------------------- +// Fixture: a pasta que o /reversa-code-express deixa depois de uma passada +// completa. Conteúdo minimalista de propósito, o que está sendo testado é a +// FORMA (quais arquivos, quais seções, qual formato de linha de ação). +// --------------------------------------------------------------------------- +function writeExpressFeature(featureDir, { withRoadmap = true, allClosed = true, greenfield = false } = {}) { + mkdirSync(featureDir, { recursive: true }); + const status = allClosed ? '`[X]`' : '`[ ]`'; + + writeFileSync(join(featureDir, 'requirements.md'), `# Requirements: Plugin de microfone + +> Identificador: \`001-plugin-microfone\` +> Data: \`2026-08-23\` +> Modo: express +> Pasta da extração reversa: \`_reversa_sdd/\` + +## 1. Resumo executivo + +Adiciona captura de voz ao harness, transcrevendo local e devolvendo texto ao chat. + +## 2. Contexto a partir do legado + +| Fonte | Trecho relevante | Confidência | +|-------|------------------|-------------| +| \`_reversa_sdd/architecture.md#interface-cliente\` | superfície de UI do chat | 🟢 | + +## 4. Regras de negócio novas ou alteradas + +1. **RN-01:** transcrição roda local, sem chamada de rede 🟢 + - Tipo: nova + +## 7. Critérios de Aceitação + +1. Falar no microfone insere o texto transcrito no campo de mensagem. + +## Emendas +`); + + if (withRoadmap) { + writeFileSync(join(featureDir, 'roadmap.md'), `# Roadmap: Plugin de microfone + +> Identificador: \`001-plugin-microfone\` +> Data: \`2026-08-23\` +> Modo: express + +## 1. Resumo da abordagem + +Pacote novo de captura, ligado ao campo de mensagem por um seam de contexto. + +## 5. Delta arquitetural + +| Componente | Tipo | Arquivo alvo | +|-----------|------|--------------| +| voice | componente-novo | \`packages/voice/src/index.ts\` | +`); + } + + writeFileSync(join(featureDir, 'actions.md'), `# Actions: Plugin de microfone + +> Identificador: \`001-plugin-microfone\` +> Data: \`2026-08-23\` +> Roadmap: \`roadmap.md\` + +## Resumo + +| Métrica | Valor | +|---------|-------| +| Total de ações | 3 | + +## Fase 1, Preparação + +| ID | Descrição | Dependências | Paralelismo | Arquivo alvo | Confidência | Status | +|----|-----------|--------------|-------------|--------------|-------------|--------| +| T001 | Scaffold do pacote de voz | - | \`[//]\` | \`packages/voice/package.json\` | 🟢 | ${status} | + +## Fase 3, Núcleo + +| ID | Descrição | Dependências | Paralelismo | Arquivo alvo | Confidência | Status | +|----|-----------|--------------|-------------|--------------|-------------|--------| +| T002 | Captura de áudio do microfone | T001 | - | \`packages/voice/src/capture.ts\` | 🟢 | ${status} | +| T003 | Ligação com o campo de mensagem | T002 | - | \`packages/client/ui-voice/src/index.ts\` | 🟢 | ${status} | + +## Notas de execução + +## Histórico de alterações + +| Data | Alteração | Autor | +|------|-----------|-------| +| 2026-08-23 | Versão inicial gerada por \`/reversa-code-express\` | claude-code | +`); + + // No cenário greenfield o express herda a regra do /reversa-coding: o cabeçalho + // precisa registrar "Feature greenfield", é por essa string que o /reversa-sync + // decide o cenário quando não há âncora de legado. + const ancora = greenfield + ? '> Feature greenfield, sem legado pré-existente. Âncora: prd.md + specs SDD.\n' + : ''; + + writeFileSync(join(featureDir, 'legacy-impact.md'), `# Legacy Impact: Plugin de microfone + +> Identificador: \`001-plugin-microfone\` +> Modo: express +${ancora}> Política de edição do legado: \`allowLegacyEdits: true\`, \`allowedPaths: ["packages/**"]\` + +| Arquivo afetado | Componente | Tipo | Severidade | Justificativa | +|-----------------|-----------|------|------------|---------------| +| \`packages/voice/src/capture.ts\` | voice | componente-novo | LOW | pacote novo | + +## Preservadas + +## Modificadas +`); + + writeFileSync(join(featureDir, 'regression-watch.md'), `# Regression Watch: Plugin de microfone + +> Identificador: \`001-plugin-microfone\` + +| ID | Origem (arquivo, seção) | Regra esperada após mudança | Tipo de verificação | Sinal de violação | +|----|------------------------|------------------------------|---------------------|-------------------| +| W001 | \`_reversa_sdd/architecture.md#interface-cliente\` | superfície de chat ganha entrada de voz | presença | entrada ausente | + +## Histórico de re-extrações + +## Arquivadas + +## Observações +`); + + const line = (id) => JSON.stringify({ ts: '2026-08-23T12:00:00Z', action: id, status: 'done', files: ['packages/voice/src/capture.ts'] }); + const ids = allClosed ? ['T001', 'T002', 'T003'] : ['T001']; + writeFileSync(join(featureDir, 'progress.jsonl'), ids.map(line).join('\n') + '\n'); +} + +// .reversa/active-requirements.json no formato que o express prescreve. +// É o único lugar onde ele grava um valor inédito: `current-stage: "express"`. +function writeActiveRequirements(reversaDir, featureDirRel) { + mkdirSync(reversaDir, { recursive: true }); + writeFileSync(join(reversaDir, 'active-requirements.json'), JSON.stringify({ + 'schema-version': 1, + 'feature-dir': featureDirRel, + 'feature-id': '001', + 'short-name': 'plugin-microfone', + 'started-at': '2026-08-23T11:00:00Z', + 'current-stage': 'express', + 'stages-completed': [], + 'paused-features': [], + }, null, 2) + '\n'); +} + +// --------------------------------------------------------------------------- +// Pré-condições declaradas pelos skills consumidores. +// Cada entrada cita a seção "Verificações Iniciais" do skill correspondente. +// --------------------------------------------------------------------------- +const CONSUMERS = [ + { skill: '/reversa-sync', requires: ['legacy-impact.md'], reads: ['regression-watch.md', 'requirements.md', 'progress.jsonl', 'actions.md'] }, + { skill: '/reversa-add', requires: ['requirements.md', 'legacy-impact.md'], reads: ['actions.md', 'regression-watch.md', 'progress.jsonl'] }, + { skill: '/reversa-coding', requires: ['actions.md'], reads: ['progress.jsonl'] }, +]; + +// =========================================================================== +const tmp = mkdtempSync(join(tmpdir(), 'reversa-express-')); + +try { + console.log('=== eixo de invocação do agente novo'); + { + const dest = join(tmp, 'skill-copy'); + cpSync(join(ROOT, 'agents', 'reversa-code-express'), dest, { recursive: true }); + const skill = readFileSync(join(dest, 'SKILL.md'), 'utf8'); + const yaml = readFileSync(join(dest, 'agents', 'openai.yaml'), 'utf8'); + check(/^disable-model-invocation:\s*true\s*$/m.test(skill), 'disable-model-invocation atravessou o cpSync do installer'); + check(/^\s*allow_implicit_invocation:\s*false\s*$/m.test(yaml), 'allow_implicit_invocation atravessou no openai.yaml'); + check(/^name:\s*reversa-code-express\s*$/m.test(skill), 'frontmatter declara name: reversa-code-express'); + check(/^\s*stage:\s*express\s*$/m.test(yaml) || /^\s*stage:\s*express\s*$/m.test(skill), 'metadata.stage: express presente'); + } + + console.log('\n=== registro no installer e nos ganchos'); + { + const prompts = readFileSync(join(ROOT, 'lib', 'installer', 'prompts.js'), 'utf8'); + check(/'reversa-code-express'/.test(prompts), 'agente registrado em lib/installer/prompts.js'); + const hooks = readFileSync(join(ROOT, 'templates', 'forward', 'hooks.yml'), 'utf8'); + check(/^before-code-express:/m.test(hooks), 'chave before-code-express existe em templates/forward/hooks.yml'); + check(/^after-code-express:/m.test(hooks), 'chave after-code-express existe em templates/forward/hooks.yml'); + } + + console.log('\n=== detecção de estágio físico'); + { + const done = join(tmp, 'express-done'); + writeExpressFeature(done); + eq(detectStage(done), 'done', 'feature express concluída é lida como concluída'); + eq(routeFrom(detectStage(done)), '/reversa-sync', '/reversa-forward encaminha a feature express para sync'); + + const partial = join(tmp, 'express-parcial'); + writeExpressFeature(partial, { allClosed: false }); + eq(detectStage(partial), 'coding-em-progresso', 'execução parcial fica visível como coding-em-progresso'); + eq(routeFrom(detectStage(partial)), '/reversa-coding', 'execução parcial é retomável pelo /reversa-coding'); + + // Caso negativo: prova que o roadmap magro é carga estrutural, não enfeite. + const noRoadmap = join(tmp, 'express-sem-roadmap'); + writeExpressFeature(noRoadmap, { withRoadmap: false }); + eq(detectStage(noRoadmap), 'requirements', 'sem roadmap.md a feature concluída seria lida como parada no começo'); + } + + console.log('\n=== pré-condições dos skills consumidores'); + { + const done = join(tmp, 'express-done'); + for (const { skill, requires, reads } of CONSUMERS) { + for (const f of requires) check(existsSync(join(done, f)), `${skill} encontra ${f} (aborta sem ele)`); + for (const f of reads) check(existsSync(join(done, f)), `${skill} encontra ${f} (leitura)`); + } + check(!existsSync(join(done, 'roadmap-inexistente.md')), 'nenhum consumidor depende de artefato que o express cortou'); + } + + console.log('\n=== active-requirements.json'); + { + // /reversa-sync e /reversa-add abortam sem esse arquivo, e o express grava + // nele um `current-stage` que nenhum outro agente escreve. O que importa é + // que ninguém DECIDA por esse campo: a detecção é sempre por artefato físico. + const projeto = join(tmp, 'projeto'); + const featureRel = '_reversa_forward/001-plugin-microfone'; + const featureAbs = join(projeto, '_reversa_forward', '001-plugin-microfone'); + writeExpressFeature(featureAbs); + writeActiveRequirements(join(projeto, '.reversa'), featureRel); + + const raw = readFileSync(join(projeto, '.reversa', 'active-requirements.json'), 'utf8'); + let json = null; + try { json = JSON.parse(raw); } catch { /* json fica null */ } + check(json !== null, 'active-requirements.json é JSON válido'); + eq(json?.['schema-version'], 1, 'schema-version preservado'); + eq(json?.['current-stage'], 'express', 'express grava o current-stage inédito'); + check(existsSync(join(projeto, json?.['feature-dir'] ?? '')), 'feature-dir aponta para pasta existente'); + eq(detectStage(join(projeto, json?.['feature-dir'] ?? '')), 'done', + 'estágio físico ignora o current-stage declarado e lê a entrega como concluída'); + } + + console.log('\n=== cenário greenfield'); + { + // /reversa-sync decide o cenário pela string "Feature greenfield" no cabeçalho + // do legacy-impact.md quando não há âncora de legado (agents/reversa-sync/SKILL.md). + const gf = join(tmp, 'express-greenfield'); + writeExpressFeature(gf, { greenfield: true }); + const header = readFileSync(join(gf, 'legacy-impact.md'), 'utf8'); + check(/Feature greenfield/.test(header), 'legacy-impact.md greenfield carrega a marca que o /reversa-sync procura'); + check(/Modo: express/.test(header), 'a marca de express convive com a de greenfield no mesmo cabeçalho'); + eq(detectStage(gf), 'done', 'feature express greenfield também é lida como concluída'); + + const legado = readFileSync(join(tmp, 'express-done', 'legacy-impact.md'), 'utf8'); + check(!/Feature greenfield/.test(legado), 'cenário legado NÃO carrega a marca de greenfield'); + } + + console.log('\n=== controle: feature real do ciclo completo'); + { + const real = 'C:\\CHUPA-CABRA\\deepseek-harness\\_reversa_forward\\001-plugin-reconhecimento-de-voz'; + if (existsSync(real)) { + eq(detectStage(real), 'done', 'a mesma detecção classifica a feature real do pipeline completo'); + const cut = ['investigation.md', 'data-delta.md', 'onboarding.md', 'audit', 'interfaces']; + const presentes = cut.filter(f => existsSync(join(real, f))); + eq(presentes.length, cut.length, 'a feature real tem os artefatos que o express corta'); + console.log(` · o express não escreveria: ${presentes.join(', ')}`); + } else { + console.log(' · pulado, projeto de controle ausente nesta máquina'); + } + } +} finally { + rmSync(tmp, { recursive: true, force: true }); +} + +console.log(failures ? `\nRESULTADO: ✗ ${failures} falha(s)` : '\nRESULTADO: ✓ compatibilidade do express íntegra'); +process.exit(failures ? 1 : 0); diff --git a/templates/forward/hooks.yml b/templates/forward/hooks.yml index be9b0880..9a7b9091 100644 --- a/templates/forward/hooks.yml +++ b/templates/forward/hooks.yml @@ -43,6 +43,10 @@ after-quality: [] before-coding: [] after-coding: [] +# Ganchos do /reversa-code-express +before-code-express: [] +after-code-express: [] + # Ganchos do /reversa-add before-add: [] after-add: [] diff --git a/templates/hooks/README.md b/templates/hooks/README.md new file mode 100644 index 00000000..e6b6d5f6 --- /dev/null +++ b/templates/hooks/README.md @@ -0,0 +1,41 @@ +# Hooks do Reversa + +## check-legacy-policy.mjs (Claude Code, opcional) + +Enforcement duro da política de edição do legado (`.reversa/reversa-config.json`). Sem ele, a política vale como guardrail por instrução, que é o denominador comum entre as engines (Cursor, Codex, Gemini CLI etc.). Com ele, o Claude Code bloqueia `Write`/`Edit` fora dos caminhos permitidos ANTES de a ferramenta executar, inclusive em modos permissivos de execução. + +O que o hook bloqueia: + +- Qualquer escrita fora das pastas próprias do Reversa quando `allowLegacyEdits` é `false`, ou quando a config está ausente ou inválida (falha segura). +- Escrita em caminho que não casa com nenhum glob de `allowedPaths` quando `allowLegacyEdits` é `true`. +- Qualquer escrita do agente em `.reversa/reversa-config.json`: a config só muda pela mão do usuário. + +As pastas próprias do Reversa continuam sempre graváveis, independentemente da política. + +### Instalação (passo manual) + +Adicione ao `.claude/settings.json` do projeto (crie o arquivo se não existir, ou mescle com o conteúdo atual): + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Write|Edit|MultiEdit|NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "node .reversa/hooks/check-legacy-policy.mjs" + } + ] + } + ] + } +} +``` + +Requisito: Node.js 18+ disponível no PATH (o mesmo exigido pelo instalador do Reversa). + +### Remoção + +Remova o bloco acima do `.claude/settings.json`. O arquivo `.reversa/hooks/check-legacy-policy.mjs` pode ficar, ele só age quando referenciado pelo settings. diff --git a/templates/hooks/check-legacy-policy.mjs b/templates/hooks/check-legacy-policy.mjs new file mode 100644 index 00000000..b6995cb6 Binary files /dev/null and b/templates/hooks/check-legacy-policy.mjs differ diff --git a/templates/reversa-config.json b/templates/reversa-config.json new file mode 100644 index 00000000..686ebef8 --- /dev/null +++ b/templates/reversa-config.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "allowLegacyEdits": false, + "allowedPaths": [] +}