ai-dev-workflow-cli 0.0.0-stage → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.continue/prompts/analisar-projeto.md +7 -0
- package/.continue/prompts/documentar-task.md +7 -0
- package/.continue/prompts/planejar-task.md +9 -0
- package/.continue/prompts/revisar-codigo.md +7 -0
- package/.continue/rules/workflow.md +12 -0
- package/.github/pull_request_template.md +10 -0
- package/.gitignore +28 -0
- package/AGENTS.md +38 -0
- package/CLAUDE.md +7 -0
- package/GEMINI.md +7 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/agents/architect.md +5 -0
- package/agents/developer.md +5 -0
- package/agents/discovery.md +5 -0
- package/agents/documentation.md +5 -0
- package/agents/environment.md +3 -0
- package/agents/reviewer.md +5 -0
- package/bin/ai-dev-workflow.js +10 -0
- package/catalog/central-v0.2.json +197 -0
- package/catalog/legacy-v1.json +158 -0
- package/catalog/template.json +207 -0
- package/docs/CLI.md +224 -0
- package/docs/DISCOVERY_CLI.md +83 -0
- package/docs/LAYOUT.md +13 -0
- package/docs/MULTI_AI.md +36 -0
- package/docs/PERSISTENCE.md +80 -0
- package/docs/QUICKSTART.md +219 -0
- package/docs/WORKFLOW.md +58 -0
- package/integrations/codex/README.md +14 -0
- package/integrations/continue/README.md +19 -0
- package/package.json +45 -4
- package/payload/central/.ai-dev-workflow/agents/architect.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/developer.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/discovery.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/documentation.md +5 -0
- package/payload/central/.ai-dev-workflow/agents/environment.md +3 -0
- package/payload/central/.ai-dev-workflow/agents/reviewer.md +5 -0
- package/payload/central/.ai-dev-workflow/docs/WORKFLOW.md +58 -0
- package/payload/central/.ai-dev-workflow/integrations/codex/README.md +14 -0
- package/payload/central/.ai-dev-workflow/integrations/continue/README.md +19 -0
- package/payload/central/.ai-dev-workflow/rules/cost-control.md +6 -0
- package/payload/central/.ai-dev-workflow/rules/database.md +46 -0
- package/payload/central/.ai-dev-workflow/rules/general.md +7 -0
- package/payload/central/.ai-dev-workflow/rules/git-workflow.md +16 -0
- package/payload/central/.ai-dev-workflow/rules/security.md +11 -0
- package/payload/central/.ai-dev-workflow/rules/testing.md +6 -0
- package/payload/central/.ai-dev-workflow/stacks/nodejs.md +3 -0
- package/payload/central/.ai-dev-workflow/stacks/php-laravel.md +6 -0
- package/payload/central/.ai-dev-workflow/stacks/php-native.md +3 -0
- package/payload/central/.ai-dev-workflow/stacks/wordpress.md +4 -0
- package/payload/central/.ai-dev-workflow/templates/ARCHITECTURE.md +8 -0
- package/payload/central/.ai-dev-workflow/templates/BASELINE.md +16 -0
- package/payload/central/.ai-dev-workflow/templates/CURRENT_STATE.md +15 -0
- package/payload/central/.ai-dev-workflow/templates/DATABASE_CHANGE.md +31 -0
- package/payload/central/.ai-dev-workflow/templates/DECISIONS.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/DISCOVERY.md +13 -0
- package/payload/central/.ai-dev-workflow/templates/ENVIRONMENT.md +20 -0
- package/payload/central/.ai-dev-workflow/templates/PR_CHECKLIST.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/REQUIREMENTS.md +9 -0
- package/payload/central/.ai-dev-workflow/templates/TASK.md +26 -0
- package/payload/central/.ai-dev-workflow/templates/TASK_REPORT.md +20 -0
- package/payload/central/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/central/.continue/prompts/documentar-task.md +7 -0
- package/payload/central/.continue/prompts/planejar-task.md +9 -0
- package/payload/central/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/central/.continue/rules/workflow.md +12 -0
- package/payload/central/.github/pull_request_template.md +10 -0
- package/payload/central/.gitignore.txt +28 -0
- package/payload/central/AGENTS.md +38 -0
- package/payload/central/CLAUDE.md +7 -0
- package/payload/central/GEMINI.md +7 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/architect.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/developer.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/discovery.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/documentation.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/environment.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/agents/reviewer.md +5 -0
- package/payload/central-v0.2/.ai-dev-workflow/docs/WORKFLOW.md +58 -0
- package/payload/central-v0.2/.ai-dev-workflow/integrations/codex/README.md +14 -0
- package/payload/central-v0.2/.ai-dev-workflow/integrations/continue/README.md +19 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/cost-control.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/database.md +46 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/general.md +7 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/git-workflow.md +16 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/security.md +11 -0
- package/payload/central-v0.2/.ai-dev-workflow/rules/testing.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/nodejs.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/php-laravel.md +6 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/php-native.md +3 -0
- package/payload/central-v0.2/.ai-dev-workflow/stacks/wordpress.md +4 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/ARCHITECTURE.md +8 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/BASELINE.md +16 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/CURRENT_STATE.md +15 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DATABASE_CHANGE.md +31 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DECISIONS.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/DISCOVERY.md +13 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/ENVIRONMENT.md +20 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/PR_CHECKLIST.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/REQUIREMENTS.md +9 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/TASK.md +26 -0
- package/payload/central-v0.2/.ai-dev-workflow/templates/TASK_REPORT.md +20 -0
- package/payload/central-v0.2/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/central-v0.2/.continue/prompts/documentar-task.md +7 -0
- package/payload/central-v0.2/.continue/prompts/planejar-task.md +9 -0
- package/payload/central-v0.2/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/central-v0.2/.continue/rules/workflow.md +12 -0
- package/payload/central-v0.2/.github/pull_request_template.md +10 -0
- package/payload/central-v0.2/.gitignore.txt +28 -0
- package/payload/central-v0.2/AGENTS.md +38 -0
- package/payload/legacy-v1/.continue/prompts/analisar-projeto.md +7 -0
- package/payload/legacy-v1/.continue/prompts/documentar-task.md +7 -0
- package/payload/legacy-v1/.continue/prompts/planejar-task.md +9 -0
- package/payload/legacy-v1/.continue/prompts/revisar-codigo.md +7 -0
- package/payload/legacy-v1/.continue/rules/workflow.md +12 -0
- package/payload/legacy-v1/.github/pull_request_template.md +10 -0
- package/payload/legacy-v1/.gitignore.txt +28 -0
- package/payload/legacy-v1/AGENTS.md +38 -0
- package/payload/legacy-v1/agents/architect.md +5 -0
- package/payload/legacy-v1/agents/developer.md +5 -0
- package/payload/legacy-v1/agents/discovery.md +5 -0
- package/payload/legacy-v1/agents/documentation.md +5 -0
- package/payload/legacy-v1/agents/environment.md +3 -0
- package/payload/legacy-v1/agents/reviewer.md +5 -0
- package/payload/legacy-v1/docs/WORKFLOW.md +58 -0
- package/payload/legacy-v1/integrations/codex/README.md +14 -0
- package/payload/legacy-v1/integrations/continue/README.md +19 -0
- package/payload/legacy-v1/rules/cost-control.md +6 -0
- package/payload/legacy-v1/rules/database.md +46 -0
- package/payload/legacy-v1/rules/general.md +7 -0
- package/payload/legacy-v1/rules/git-workflow.md +16 -0
- package/payload/legacy-v1/rules/security.md +11 -0
- package/payload/legacy-v1/rules/testing.md +6 -0
- package/payload/legacy-v1/stacks/nodejs.md +3 -0
- package/payload/legacy-v1/stacks/php-laravel.md +6 -0
- package/payload/legacy-v1/stacks/php-native.md +3 -0
- package/payload/legacy-v1/stacks/wordpress.md +4 -0
- package/payload/legacy-v1/templates/ARCHITECTURE.md +8 -0
- package/payload/legacy-v1/templates/BASELINE.md +16 -0
- package/payload/legacy-v1/templates/CURRENT_STATE.md +15 -0
- package/payload/legacy-v1/templates/DATABASE_CHANGE.md +31 -0
- package/payload/legacy-v1/templates/DECISIONS.md +9 -0
- package/payload/legacy-v1/templates/DISCOVERY.md +13 -0
- package/payload/legacy-v1/templates/ENVIRONMENT.md +20 -0
- package/payload/legacy-v1/templates/PR_CHECKLIST.md +9 -0
- package/payload/legacy-v1/templates/REQUIREMENTS.md +9 -0
- package/payload/legacy-v1/templates/TASK.md +26 -0
- package/payload/legacy-v1/templates/TASK_REPORT.md +20 -0
- package/rules/cost-control.md +6 -0
- package/rules/database.md +46 -0
- package/rules/general.md +7 -0
- package/rules/git-workflow.md +16 -0
- package/rules/security.md +11 -0
- package/rules/testing.md +6 -0
- package/scripts/check-catalog.js +11 -0
- package/src/cli.js +88 -0
- package/src/commands/discover.js +2 -0
- package/src/commands/doctor.js +1 -0
- package/src/commands/init.js +2 -0
- package/src/commands/status.js +1 -0
- package/src/commands/validate.js +1 -0
- package/src/core/adapters.js +9 -0
- package/src/core/catalog.js +52 -0
- package/src/core/diagnostics.js +82 -0
- package/src/core/errors.js +10 -0
- package/src/core/install-plan.js +49 -0
- package/src/core/installer.js +72 -0
- package/src/core/journal.js +28 -0
- package/src/core/manifest.js +55 -0
- package/src/discovery/detectors.js +156 -0
- package/src/discovery/evidence.js +12 -0
- package/src/discovery/reader.js +47 -0
- package/src/discovery/reconcile.js +18 -0
- package/src/discovery/report.js +24 -0
- package/src/discovery/scanner.js +60 -0
- package/src/interaction/init.js +52 -0
- package/src/output/json.js +1 -0
- package/src/output/text.js +18 -0
- package/src/persistence/history.js +23 -0
- package/src/persistence/markdown.js +25 -0
- package/src/persistence/record.js +90 -0
- package/src/persistence/storage.js +47 -0
- package/src/platform/filesystem.js +71 -0
- package/src/platform/git.js +123 -0
- package/stacks/nodejs.md +3 -0
- package/stacks/php-laravel.md +6 -0
- package/stacks/php-native.md +3 -0
- package/stacks/wordpress.md +4 -0
- package/templates/ARCHITECTURE.md +8 -0
- package/templates/BASELINE.md +16 -0
- package/templates/CURRENT_STATE.md +15 -0
- package/templates/DATABASE_CHANGE.md +31 -0
- package/templates/DECISIONS.md +9 -0
- package/templates/DISCOVERY.md +13 -0
- package/templates/ENVIRONMENT.md +20 -0
- package/templates/PR_CHECKLIST.md +9 -0
- package/templates/REQUIREMENTS.md +9 -0
- package/templates/TASK.md +26 -0
- package/templates/TASK_REPORT.md +20 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Discovery — [projeto]
|
|
2
|
+
Data: [AAAA-MM-DD] | Trilha: existente | Responsável: [...] | Status G1: pendente
|
|
3
|
+
## Escopo/limitações
|
|
4
|
+
## Evidências (caminho:linha ou comando seguro)
|
|
5
|
+
## Stack (declarada / confirmada)
|
|
6
|
+
| Camada | Tecnologia | Versão declarada | Versão confirmada | Evidência |
|
|
7
|
+
|---|---|---|---|---|
|
|
8
|
+
## Mapa de módulos e fluxos
|
|
9
|
+
## Banco de dados (sem dados reais)
|
|
10
|
+
## Dependências e pontos críticos
|
|
11
|
+
## Riscos e lacunas
|
|
12
|
+
## Próximas ações propostas
|
|
13
|
+
## Aprovação G1 (quem, quando, escopo)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Environment — [projeto]
|
|
2
|
+
Status G1: [...] | Status G2: pendente
|
|
3
|
+
## Pré-requisitos e versões compatíveis
|
|
4
|
+
## SO / arquitetura / Docker
|
|
5
|
+
## Serviços, portas e volumes
|
|
6
|
+
## Variáveis de ambiente (somente nomes, sem valores secretos)
|
|
7
|
+
## Comandos propostos (alvo e riscos)
|
|
8
|
+
## Isolamento do banco de teste
|
|
9
|
+
## Reprodução / troubleshooting
|
|
10
|
+
## Testes de inicialização e evidências
|
|
11
|
+
## Aprovação G2 da configuração (ID em DECISIONS)
|
|
12
|
+
G2 não autoriza inicialização, migrations ou seeders.
|
|
13
|
+
## Modo de operação de banco do projeto
|
|
14
|
+
Modo: MANUAL_SQL (padrão até escolha humana explícita)
|
|
15
|
+
Escolha DB_MODE (ID): [...] | Motor/versão real e evidência: [...]
|
|
16
|
+
Alvos autorizados sem credenciais: [...]
|
|
17
|
+
DB_CHANGE e DB_EXEC são separados e específicos por task/execução; ver `rules/database.md`.
|
|
18
|
+
## Entrada e bootstrap
|
|
19
|
+
Discovery (existente) ou REQUIREMENTS/ARCHITECTURE (novo), G1: [...]
|
|
20
|
+
Projeto novo: não criar aplicação antes da task de bootstrap com G4.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Checklist de PR
|
|
2
|
+
- [ ] Task e G4 documentados
|
|
3
|
+
- [ ] Branch isolada; diff revisado
|
|
4
|
+
- [ ] Sem credenciais, dumps nem dados reais
|
|
5
|
+
- [ ] Testes relevantes executados e evidências anexadas
|
|
6
|
+
- [ ] Compatibilidade validada; migrations/rollback planejados
|
|
7
|
+
- [ ] Docs/CURRENT_STATE atualizados
|
|
8
|
+
- [ ] Revisão G5 realizada
|
|
9
|
+
- [ ] Deploy separado sujeito a G6
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Requirements — projeto novo
|
|
2
|
+
## Problema / usuários / objetivo
|
|
3
|
+
## Funcionalidades do MVP e fora do escopo
|
|
4
|
+
## Requisitos funcionais e não funcionais
|
|
5
|
+
## Dados e privacidade
|
|
6
|
+
## Alternativas de stack e trade-offs
|
|
7
|
+
## Critérios de aceitação
|
|
8
|
+
## Riscos / perguntas abertas
|
|
9
|
+
## Aprovação G1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# TASK-XXX — [título]
|
|
2
|
+
Status: proposta | Branch: [feature/TASK-XXX-...] | Responsável: [...] | Aprovação G4: pendente
|
|
3
|
+
## Contexto e objetivo
|
|
4
|
+
## Fora do escopo
|
|
5
|
+
## Requisitos e critérios de aceitação (verificáveis)
|
|
6
|
+
## Arquivos/áreas possivelmente impactados
|
|
7
|
+
## Dados, segurança e riscos
|
|
8
|
+
## Plano de implementação (curto)
|
|
9
|
+
## Plano de testes e ambiente
|
|
10
|
+
## Rollback / reversão
|
|
11
|
+
## Dependências e decisões abertas
|
|
12
|
+
## Aprovação explícita G4
|
|
13
|
+
|
|
14
|
+
## Isolamento e aprovações
|
|
15
|
+
Raiz Git confirmada: [...] | Git-dir/common-dir: [...] | Branch efetiva: [...]
|
|
16
|
+
IDs em `docs/project/DECISIONS.md`: [...] | Revisão dos artefatos: [...]
|
|
17
|
+
Estados: pendente/aprovado/rejeitado/revogado/revalidação pendente. Escopo alterado exige revalidação.
|
|
18
|
+
|
|
19
|
+
## Controle de banco (obrigatório, inclusive quando não aplicável)
|
|
20
|
+
Banco envolvido: [sim/não/desconhecido; evidência]
|
|
21
|
+
Modo do projeto: [MANUAL_SQL por padrão; herdar escolha humana vigente] | Modo efetivo da task: [...]
|
|
22
|
+
Escolha humana/override DB_MODE (ID): [...] | Motor e versão real/evidência: [...]
|
|
23
|
+
Alvo/ambiente sem credenciais: [...] | Executor: [...]
|
|
24
|
+
G2 ambiente (ID): [...] | DB_CHANGE plano (ID): [...] | DB_EXEC execução concreta (ID): [...] | G6 produção, se aplicável: [...]
|
|
25
|
+
**G4 não autoriza modificar banco. Sem DB_EXEC específico, não executar migrations, seeders, SQL de escrita ou scripts/testes que alterem banco. MANUAL_SQL proíbe execução pelo agente.**
|
|
26
|
+
Entrega SQL/roteiro: `docs/tasks/TASK-XXX/sql/` conforme `templates/DATABASE_CHANGE.md`, ou não aplicável com justificativa.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# TASK-XXX — Relatório de execução
|
|
2
|
+
Data: [...] | Branch / commit: [...] | Status: [...]
|
|
3
|
+
## Resumo da alteração
|
|
4
|
+
## Arquivos criados/alterados
|
|
5
|
+
## Testes executados
|
|
6
|
+
| Comando | Ambiente | Resultado | Evidência/observação |
|
|
7
|
+
|---|---|---|---|
|
|
8
|
+
## Testes não executados e motivo
|
|
9
|
+
## Segurança/compatibilidade e riscos restantes
|
|
10
|
+
## Como reverter
|
|
11
|
+
## Checklist para revisão G5
|
|
12
|
+
## Próximas tarefas / CURRENT_STATE atualizado?
|
|
13
|
+
|
|
14
|
+
## Controles de execução
|
|
15
|
+
Raiz Git e branch verificadas: [...]
|
|
16
|
+
IDs/revisões em DECISIONS e revalidações necessárias: [...]
|
|
17
|
+
Modo de banco do projeto/task: MANUAL_SQL / [...]
|
|
18
|
+
DB_CHANGE/DB_EXEC/G2/G6 aplicáveis: [...] | Executor e alvo: [...]
|
|
19
|
+
SQL/roteiro entregues: [...] | Execução real e evidências: [...]
|
|
20
|
+
Em MANUAL_SQL marcar: nenhuma escrita de banco executada pelo agente.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Controle de custo da IA
|
|
2
|
+
- Dar tarefa curta, com caminhos e critérios explícitos; evitar anexar repositório inteiro.
|
|
3
|
+
- Reusar CURRENT_STATE e task para não repetir análise.
|
|
4
|
+
- Escolher modelos baratos/gratuitos para perguntas genéricas; modelo principal para mudanças críticas.
|
|
5
|
+
- Cotas `:free` têm limites e podem mudar; modelos online podem processar contexto por terceiros.
|
|
6
|
+
- Desligar ferramentas/autocomplete automático quando não necessários; verificar consumo do provedor.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Banco de dados — controle obrigatório
|
|
2
|
+
|
|
3
|
+
## Modo por projeto e por tarefa
|
|
4
|
+
Registrar o modo do projeto em `docs/project/ENVIRONMENT.md` e a seleção efetiva em cada task. Sem escolha humana explícita, usar **MANUAL_SQL**. A task herda o modo do projeto; qualquer substituição exige escolha humana registrada em DECISIONS. Escolher um modo não autoriza executar operações.
|
|
5
|
+
|
|
6
|
+
| Modo | Preparação | Execução pelo agente |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| MANUAL_SQL | SQL versionado por task e roteiro para execução humana no phpMyAdmin | Proibida qualquer alteração de banco, mesmo local |
|
|
9
|
+
| AUTOMATIC_LOCAL | Plano, scripts/migrations e verificações | Somente local ou ambiente isolado confirmado, com G2, DB_CHANGE e DB_EXEC aprovados |
|
|
10
|
+
| PRODUCTION_CONTROLLED | Plano específico de produção, backup validado, validação e rollback | Somente com G2 do alvo, DB_CHANGE, DB_EXEC e G6 específicos aprovados; respeitar permissões da ferramenta |
|
|
11
|
+
|
|
12
|
+
MANUAL_SQL é o padrão inclusive quando a stack oferece migrations. Não executar migrations, seeders, SQL de escrita, inicialização de bases, testes ou scripts que modifiquem banco sem autorização específica. Em MANUAL_SQL o agente apenas prepara os artefatos; para executar deve haver mudança explícita de modo e autorização separada. Testes com escrita também contam como execução de banco.
|
|
13
|
+
|
|
14
|
+
## Aprovações cumulativas e independentes
|
|
15
|
+
- **G2:** aprova configuração, isolamento e destino do ambiente; não autoriza escrita.
|
|
16
|
+
- **G4:** aprova a implementação da task; não autoriza modificar banco.
|
|
17
|
+
- **DB_CHANGE:** aprova o plano da mudança de schema/dados, scripts, riscos, backup, validação e reversão. Não autoriza executá-lo.
|
|
18
|
+
- **DB_EXEC:** autoriza uma execução concreta pelo executor identificado (humano ou agente), sobre alvo e artefato/revisão identificados, com janela, comandos e limites. Para execução manual humana, registrar também DB_CHANGE e DB_EXEC antes da execução; isso não concede execução ao agente.
|
|
19
|
+
- **G6:** autoriza operação de produção concreta, cumulativa com os controles de banco quando aplicáveis.
|
|
20
|
+
|
|
21
|
+
Registrar os IDs e estados conforme `docs/WORKFLOW.md`. Um mesmo pedido humano pode conceder várias aprovações apenas se explicitar cada ação e alvo. Nunca inferir DB_EXEC de G2, G4, G5, G6 ou da escolha de modo. Após mudança de script, alvo ou escopo, suspender execução até revalidação. DB_EXEC vale para a execução registrada; repetição ou retry exige autorização específica, salvo se já delimitado explicitamente.
|
|
22
|
+
|
|
23
|
+
## Preparação e verificação do alvo
|
|
24
|
+
Inspecionar schema, FKs, índices e migrations antes de propor mudança. Confirmar motor, versão real, host/ambiente, banco, privilégios e isolamento sem registrar credenciais. Não usar somente a versão declarada em dependências. Se essas evidências faltarem, solicitar metadados sanitizados e bloquear o SQL executável dependente delas. Separar conexões por ambiente; não usar produção como fixture sem anonimização aprovada. Dados reais continuam sujeitos às regras de privacidade.
|
|
25
|
+
|
|
26
|
+
## Entrega MANUAL_SQL
|
|
27
|
+
Usar `templates/DATABASE_CHANGE.md` e `docs/tasks/TASK-XXX/sql/`:
|
|
28
|
+
- `01-precheck.sql`: consultas de leitura sobre versão, schema e pré-condições, sem dados pessoais.
|
|
29
|
+
- `02-change.sql`: alteração revisável, compatível com a versão real do MySQL ou MariaDB, charset/collation, engine e SQL mode confirmados.
|
|
30
|
+
- `03-postcheck.sql`: consultas de leitura com resultados esperados e critérios de falha.
|
|
31
|
+
- `04-rollback.sql`: somente quando tecnicamente seguro; caso contrário documentar irreversibilidade e restauração/compensação necessária.
|
|
32
|
+
- `README.md`: ordem e seleção do banco no phpMyAdmin, conferência do alvo e versão, backup necessário e validação de restauração, autorizações, execução manual por etapas, resultados esperados, riscos e condições de parada. Não colar credenciais ou resultados sensíveis.
|
|
33
|
+
|
|
34
|
+
Os SQL são artefatos da task; nenhum é executado automaticamente. Não prometer rollback transacional de DDL nem reversão de perda de dados; avaliar suporte e commits implícitos na versão confirmada. Registrar locks, impacto, dependências e risco de repetição. Idempotência somente quando demonstrável. Se phpMyAdmin/motor MySQL ou MariaDB não se aplicar, preservar MANUAL_SQL sem execução e acordar interface e dialeto reais antes de preparar a entrega; nunca presumir stack.
|
|
35
|
+
|
|
36
|
+
## Restrições
|
|
37
|
+
`DROP`, `TRUNCATE`, `DELETE` sem filtro, `migrate:fresh`, `db:wipe` e equivalentes continuam proibidos sem autorização específica da ação destrutiva. Um plano de rollback destrutivo também precisa de autorização para execução. Não executar rollback automaticamente. Migrations são uma opção de implementação versionada, nunca uma autorização implícita.
|
|
38
|
+
|
|
39
|
+
Antes da entrega, verificar se os SQL revisados da task não estão ignorados pelo Git. Scripts de precheck, alteração aprovada, postcheck e reversão podem ser versionados em `docs/tasks/TASK-XXX/sql/`; backups, dumps e arquivos sensíveis devem permanecer nos locais ignorados e fora da entrega. Sinalizar qualquer exclusão inesperada, sem forçar staging ou alterar proteções implicitamente.
|
|
40
|
+
|
|
41
|
+
O Git e o `.gitignore` não garantem ausência de dados sensíveis: arquivos já rastreados e SQL fora dos diretórios protegidos exigem inspeção. Todo SQL deve passar por revisão humana de conteúdo, dados, alvo e riscos antes de ser versionado. Não incluir dumps, credenciais, dados pessoais ou dados reais; scripts de dados devem usar conteúdo sintético ou parâmetros sem valores sensíveis. Poder versionar um arquivo não autoriza executar seu SQL nem fazer commit/push.
|
|
42
|
+
|
|
43
|
+
## Bloqueio padrão de produção e ferramentas
|
|
44
|
+
Produção permanece bloqueada por padrão. Operações de produção exigem as autorizações humanas específicas do workflow, incluindo G6 para a ação e o ambiente concretos. Operações de banco exigem também G2, DB_CHANGE e DB_EXEC aplicáveis, com executor, alvo e revisão dos scripts identificados; produção exige backup validado, plano de execução, validação e rollback. G4, G5 ou G6 não implicam autorização automática para executar SQL.
|
|
45
|
+
|
|
46
|
+
MANUAL_SQL permanece como modo padrão: o agente apenas prepara os arquivos SQL e as instruções; a execução é responsabilidade humana, sujeita às autorizações aplicáveis. Nenhuma ferramenta deve executar comandos de escrita no banco sem autorização explícita para a operação e o ambiente. Para execução pelo agente, exigir também modo permitido escolhido pelo humano conforme `rules/database.md`; DB_EXEC humano não autoriza execução pelo agente. Essas instruções não substituem permissões técnicas.
|
package/rules/general.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Regras gerais
|
|
2
|
+
- Confirmar trilha e fase.
|
|
3
|
+
- Basear afirmações em arquivos/comandos reais e citar caminhos.
|
|
4
|
+
- Alterações mínimas e reversíveis; evitar escopo ampliado.
|
|
5
|
+
- Separar fatos, hipóteses e recomendações.
|
|
6
|
+
- Declarar impedimentos, não esconder testes não executados.
|
|
7
|
+
- Resumo final: objetivo, diff, testes, riscos, próximos passos.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Git workflow
|
|
2
|
+
- Inspecionar `git status` antes de iniciar; não sobrescrever alterações preexistentes.
|
|
3
|
+
- Trabalhar em `feature/TASK-xxx-descricao`, `fix/TASK-xxx-descricao`, `docs/TASK-xxx-descricao` conforme o caso.
|
|
4
|
+
- Branch protegida (`main`/`master`); nunca push/merge sem autorização.
|
|
5
|
+
- Commit pequeno e descritivo `feat:`, `fix:`, `docs:`, `test:`, `chore:`.
|
|
6
|
+
- Revisar diff, conflitos, segredos e arquivos não rastreados; PR inclui testes, risco e rollback.
|
|
7
|
+
|
|
8
|
+
## Verificação de isolamento antes de alterações
|
|
9
|
+
Executar no diretório autorizado: `pwd`, `git rev-parse --show-toplevel`, `git rev-parse --absolute-git-dir`, `git rev-parse --git-common-dir`, `git status --short --branch` e `git symbolic-ref --short HEAD`. Confirmar caminhos reais, inclusive symlinks, e comparar a raiz com o projeto autorizado. Repositórios aninhados, submódulos ou worktrees com metadados externos exigem confirmação do escopo antes de qualquer escrita Git. HEAD destacado exige branch de task antes de editar.
|
|
10
|
+
|
|
11
|
+
Se a raiz for um diretório pai ou outro projeto, interromper alterações. Não executar `git add`, criar branch ou inicializar repositório nesse pai; não apagar/mover seu `.git`. Pedir definição explícita do limite correto. Não sobrescrever alterações preexistentes; arquivos não rastreados também são trabalho existente.
|
|
12
|
+
|
|
13
|
+
## Inicialização segura
|
|
14
|
+
Inicializar somente mediante autorização explícita para o diretório concreto, após confirmar caminho real e ausência de `.git` local. Se um pai já for repositório, informar que será criado um repositório aninhado e obter autorização específica. Não modificar o repositório pai nem aplicações vizinhas.
|
|
15
|
+
|
|
16
|
+
No diretório confirmado, `git init` cria apenas os metadados locais; conferir novamente a raiz e o git-dir. Criar branch dedicada com `git switch -c docs/TASK-XXX-descricao` (ou feature/fix conforme tarefa), inclusive sem commits, e verificar a branch efetiva antes de editar. Se falhar por permissões ou versão da ferramenta, parar e resolver sem alterar em main. Não usar `git add .`, commit inicial, remote ou push como parte implícita da inicialização. Branch de task não autoriza commit/push/merge.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Segurança e privacidade
|
|
2
|
+
- Autorização G0 para transmitir código/dados a IA externa; respeitar contrato/política da organização.
|
|
3
|
+
- Não abrir/transmitir `.env`, tokens, dumps reais, dados pessoais ou segredos; não versionar chaves de API.
|
|
4
|
+
- Não executar ações destrutivas/produção sem aprovação explícita e específica; conferir alvo `APP_ENV`, host, banco e branch.
|
|
5
|
+
- Proteção por permissões de ferramenta, ambiente e revisão humana; prompts não são barreiras de segurança.
|
|
6
|
+
- Para acesso à nuvem, aplicar princípio do menor privilégio.
|
|
7
|
+
- Redigir evidências com dados sintéticos, nunca credenciais.
|
|
8
|
+
|
|
9
|
+
- Para banco, G2/G4 não autorizam escrita: aplicar modo efetivo, DB_CHANGE e DB_EXEC de `rules/database.md`; produção exige G6 específico e backup validado.
|
|
10
|
+
|
|
11
|
+
G4, G5 e G6 não substituem DB_EXEC. Produção permanece bloqueada por padrão; qualquer escrita por ferramentas exige autorização explícita da operação, ambiente e executor, em modo permitido. Em MANUAL_SQL a execução é humana; o agente apenas prepara os artefatos. Git e .gitignore não garantem ausência de dados sensíveis: revisar todo SQL antes de versionar e manter dumps/backups/segredos fora da entrega.
|
package/rules/testing.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Testes e evidências
|
|
2
|
+
- Baseline antes de refatorar legado.
|
|
3
|
+
- Preferir testes automatizados; smoke HTTP, autenticação, autorização, dados inválidos e regressões.
|
|
4
|
+
- Registrar comando, ambiente, data, resultado e limitações.
|
|
5
|
+
- Testes que alteram banco exigem base de teste isolada confirmada, G2, DB_CHANGE e DB_EXEC específicos, em AUTOMATIC_LOCAL. MANUAL_SQL permite apenas preparar o procedimento; não executar testes com escrita, mesmo com rollback. Seguir `rules/database.md`.
|
|
6
|
+
- Não reportar PASS sem execução.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { loadCatalog, contextEntries } from '../src/core/catalog.js';
|
|
2
|
+
import { validationFindings } from '../src/core/diagnostics.js';
|
|
3
|
+
for (const [layout, version] of [['central-v2','0.3.0'],['central-v2','0.2.0'],['legacy-v1','0.1.0']]) {
|
|
4
|
+
const catalog = await loadCatalog(layout, version);
|
|
5
|
+
const allowed = new Set([...catalog.entries, ...contextEntries('new', layout)].map(e => e.path));
|
|
6
|
+
const findings = catalog.entries.flatMap(e => e.path.endsWith('.md') ? validationFindings(e.path, e.content, allowed) : []);
|
|
7
|
+
if (findings.length) {
|
|
8
|
+
console.error(JSON.stringify(findings, null, 2));
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
} else console.log(`OK ${layout}/${version}: ${catalog.entries.length} arquivos explícitos, hashes e referências do catálogo.`);
|
|
11
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { resolveTarget } from './platform/filesystem.js';
|
|
3
|
+
import { packageVersion } from './core/catalog.js';
|
|
4
|
+
import { WorkflowError, fail } from './core/errors.js';
|
|
5
|
+
import { run as init } from './commands/init.js';
|
|
6
|
+
import { run as doctor } from './commands/doctor.js';
|
|
7
|
+
import { run as status } from './commands/status.js';
|
|
8
|
+
import { run as discover } from './commands/discover.js';
|
|
9
|
+
import { run as validate } from './commands/validate.js';
|
|
10
|
+
import { preflight, persist } from './persistence/record.js';
|
|
11
|
+
import { prepareInit } from './interaction/init.js';
|
|
12
|
+
import { renderText } from './output/text.js';
|
|
13
|
+
import { renderJson } from './output/json.js';
|
|
14
|
+
const help = `ai-dev-workflow <init|doctor|status|validate|discover> [--target <diretorio>]
|
|
15
|
+
--target omitido usa process.cwd(); não procura outra raiz nem sobe diretórios.
|
|
16
|
+
Git 2.28+ é necessário para init/doctor; Git nunca é inicializado automaticamente.
|
|
17
|
+
init em TTY sem flags de automação oferece plano e confirmação padrão N; fora desse modo requer --track new|existing. discover admite --allow-no-git somente para ausência real de Git. Opções: --dry-run --json --help --version. --save grava registros locais opt-in; incompatível com --dry-run.
|
|
18
|
+
Node.js 24 somente para a CLI; nenhum comando acessa banco, rede ou runtime da aplicação.
|
|
19
|
+
`;
|
|
20
|
+
export async function main(args, io = { out: text => process.stdout.write(text), err: text => process.stderr.write(text) }) {
|
|
21
|
+
let json = args.includes('--json');
|
|
22
|
+
try {
|
|
23
|
+
if (Number(process.versions.node.split('.')[0]) !== 24) fail('NODE_VERSION', 'Execute a CLI com Node.js 24 isolado da aplicação.', 1);
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = parseArgs({ args, allowPositionals: true, options: {
|
|
27
|
+
target: { type: 'string' }, track: { type: 'string' }, 'dry-run': { type: 'boolean' },
|
|
28
|
+
'allow-no-git': { type: 'boolean' }, save: { type: 'boolean' }, json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' }
|
|
29
|
+
} });
|
|
30
|
+
} catch { fail('ARGUMENTS', 'Argumentos inválidos. Use --help.', 3); }
|
|
31
|
+
const { values, positionals } = parsed;
|
|
32
|
+
json = values.json ?? false;
|
|
33
|
+
if (values.save && values['dry-run']) fail('ARGUMENTS', '--save e --dry-run são incompatíveis; nenhuma escrita.', 3);
|
|
34
|
+
if (values.help || values.version) {
|
|
35
|
+
const result = { ok: true, message: values.version ? await packageVersion() : help.trim() };
|
|
36
|
+
io.out(json ? renderJson(result) : result.message + '\n'); return 0;
|
|
37
|
+
}
|
|
38
|
+
const command = positionals[0];
|
|
39
|
+
if (positionals.length !== 1 || !['init', 'doctor', 'status', 'validate', 'discover'].includes(command) ||
|
|
40
|
+
values.target === '' || (command === 'init' && values.track !== undefined && !['new', 'existing'].includes(values.track)) ||
|
|
41
|
+
(command !== 'init' && values.track !== undefined) ||
|
|
42
|
+
(command !== 'discover' && values['allow-no-git'] !== undefined))
|
|
43
|
+
fail('ARGUMENTS', 'Informe comando válido e, para init, --track new|existing. --target é opcional, mas não pode ser vazio. Use --help.', 3);
|
|
44
|
+
const interactiveInit = !process.env.CI && command === 'init' && values.track === undefined && !json && !values['dry-run'] &&
|
|
45
|
+
!!(io.input ?? process.stdin).isTTY && !!(io.output ?? process.stdout).isTTY;
|
|
46
|
+
if (command === 'init' && values.track === undefined && !interactiveInit)
|
|
47
|
+
fail('ARGUMENTS', 'Init sem interação requer --track new|existing. Use um terminal TTY para o assistente; CI, --json e --dry-run não fazem perguntas.', 3);
|
|
48
|
+
let input = values.target;
|
|
49
|
+
if (input === undefined) {
|
|
50
|
+
try { input = process.cwd(); }
|
|
51
|
+
catch { fail('CWD_UNAVAILABLE', 'Diretório atual indisponível. Use um diretório acessível ou --target absoluto explícito.', 1); }
|
|
52
|
+
}
|
|
53
|
+
const target = await resolveTarget(input);
|
|
54
|
+
if ((command === 'init' && !values['dry-run']) || values.save) io.err(`Destino canonicalizado: ${target}\n`);
|
|
55
|
+
if (values.save) await preflight(target,command,!!values['allow-no-git']);
|
|
56
|
+
if (interactiveInit) {
|
|
57
|
+
const prepared = await prepareInit(target,io);
|
|
58
|
+
if (prepared.result) {
|
|
59
|
+
io.out(renderText({ command, canonicalTarget:target, ...prepared.result }));
|
|
60
|
+
return prepared.result.exitCode ?? (prepared.result.ok ? 0 : 2);
|
|
61
|
+
}
|
|
62
|
+
values.track = prepared.track;
|
|
63
|
+
}
|
|
64
|
+
const execute = async () => command === 'discover' ? await discover({ target, allowNoGit: !!values['allow-no-git'] }) : command === 'init' ? await init({ target, track: values.track, dryRun: !!values['dry-run'] }) :
|
|
65
|
+
await ({ doctor, status, validate })[command](target);
|
|
66
|
+
let result;
|
|
67
|
+
if (values.save) {
|
|
68
|
+
try { result = await execute(); }
|
|
69
|
+
catch (e) { result = {ok:false,exitCode:e instanceof WorkflowError?e.exitCode:1,code:e instanceof WorkflowError?e.code:'IO_ERROR',message:e instanceof WorkflowError?e.message:'Falha operacional local; nenhum detalhe sensível exibido.'}; }
|
|
70
|
+
} else result = await execute();
|
|
71
|
+
const response = { command, canonicalTarget: target, ...result, ...(values['dry-run'] ? { dryRun: true } : {}) };
|
|
72
|
+
if (values.save) {
|
|
73
|
+
response.operationExitCode=result.exitCode??(result.ok?0:2);
|
|
74
|
+
try { response.persistence=await persist(target,command,result,!!values['allow-no-git']); }
|
|
75
|
+
catch(e) { response.persistence={ok:false,writeState:'incomplete-or-unknown',code:e instanceof WorkflowError?e.code:'IO_ERROR',message:'Persistência não concluída; preservar evidências e revisar sem repetir init automaticamente.'};response.ok=false;response.exitCode=e.exitCode??1; }
|
|
76
|
+
}
|
|
77
|
+
io.out(json ? renderJson(response) : renderText(response));
|
|
78
|
+
if (interactiveInit && response.ok) io.out('Próximos passos neste projeto: npx ai-dev-workflow-cli discover, npx ai-dev-workflow-cli doctor e npx ai-dev-workflow-cli status.\nA CLI não instala automaticamente Codex, Continue, Claude Code ou Gemini CLI.\n');
|
|
79
|
+
if (!json && command==='discover' && !values.save && result.ok) io.out('Para salvar este diagnóstico, use discover --save, mantendo o mesmo destino.\n');
|
|
80
|
+
return response.exitCode??result.exitCode ?? (result.ok ? 0 : 2);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const known = error instanceof WorkflowError;
|
|
83
|
+
const result = { ok: false, code: known ? error.code : 'IO_ERROR',
|
|
84
|
+
message: known ? error.message : 'Falha operacional local. Confira permissões e integridade; nenhum detalhe sensível foi exibido.' };
|
|
85
|
+
(json ? io.out : io.err)(json ? renderJson(result) : renderText(result));
|
|
86
|
+
return known ? error.exitCode : 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { doctor as run } from '../core/diagnostics.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { status as run } from '../core/diagnostics.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { validateInstallation as run } from '../core/diagnostics.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { checkedPath, stat, readKnown } from '../platform/filesystem.js';
|
|
2
|
+
import { fail } from './errors.js';
|
|
3
|
+
export async function readInstalledEntry(root, relative) {
|
|
4
|
+
if (['CLAUDE.md','GEMINI.md'].includes(relative)) {
|
|
5
|
+
const info = await stat(await checkedPath(root, relative));
|
|
6
|
+
if (info && info.nlink !== 1) fail('UNSAFE_PATH', 'Hardlink em adaptador: isolamento não comprovado.');
|
|
7
|
+
}
|
|
8
|
+
return readKnown(root, relative);
|
|
9
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { hash, readKnown, safeRelative } from '../platform/filesystem.js';
|
|
4
|
+
import { fail } from './errors.js';
|
|
5
|
+
export const packageRoot = fileURLToPath(new URL('../../', import.meta.url));
|
|
6
|
+
export const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
7
|
+
export const sha256 = /^[a-f0-9]{64}$/;
|
|
8
|
+
export const manifestPath = '.ai-dev-workflow/install.json';
|
|
9
|
+
export const manifestVersion = '2.0.0';
|
|
10
|
+
const allowedLegacy = /^(AGENTS\.md|rules\/[^/]+\.md|agents\/[^/]+\.md|templates\/[^/]+\.md|stacks\/[^/]+\.md|docs\/WORKFLOW\.md|integrations\/[^/]+\/README\.md|\.continue\/(rules|prompts)\/[^/]+\.md|\.github\/pull_request_template\.md|\.gitignore)$/;
|
|
11
|
+
const centralTarget = value => ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', '.gitignore', '.github/pull_request_template.md'].includes(value) ||
|
|
12
|
+
/^\.continue\/(rules|prompts)\/[^/]+\.md$/.test(value) ||
|
|
13
|
+
(value.startsWith('.ai-dev-workflow/') && /^(agents|rules|stacks|templates|integrations|docs)\//.test(value.slice(17)) && allowedLegacy.test(value.slice(17)));
|
|
14
|
+
export async function loadCatalog(layout = 'central-v2', templateVersion = layout === 'legacy-v1' ? '0.1.0' : '0.3.0') {
|
|
15
|
+
if (!['central-v2', 'legacy-v1'].includes(layout)) fail('UNSUPPORTED_LAYOUT', 'Layout não suportado.');
|
|
16
|
+
const legacy = layout === 'legacy-v1';
|
|
17
|
+
if (!(legacy ? templateVersion === '0.1.0' : ['0.2.0','0.3.0'].includes(templateVersion))) fail('VERSION_PLAN_REQUIRED', 'Template não suportado; nenhuma migração será executada.');
|
|
18
|
+
const prior = !legacy && templateVersion === '0.2.0';
|
|
19
|
+
const file = legacy ? 'legacy-v1.json' : prior ? 'central-v0.2.json' : 'template.json';
|
|
20
|
+
const catalog = JSON.parse(await fs.readFile(new URL('../../catalog/' + file, import.meta.url), 'utf8'));
|
|
21
|
+
if (catalog.templateVersion !== templateVersion || catalog.catalogVersion !== (legacy ? '1.0.0' : '2.0.0') ||
|
|
22
|
+
(!legacy && catalog.layoutVersion !== layout) || !Array.isArray(catalog.files)) fail('INVALID_CATALOG', 'Catálogo inválido.');
|
|
23
|
+
const seen = new Set();
|
|
24
|
+
const entries = [];
|
|
25
|
+
for (const item of catalog.files) {
|
|
26
|
+
const target = legacy ? item.path : item.target;
|
|
27
|
+
const source = legacy ? item.path : item.source;
|
|
28
|
+
const asset = legacy ? 'payload/legacy-v1/' + target + (target === '.gitignore' ? '.txt' : '') : prior ? source.replace('payload/central/', 'payload/central-v0.2/') : source;
|
|
29
|
+
if (!safeRelative(target) || !safeRelative(source) || seen.has(target.toLowerCase()) || !sha256.test(item.sha256) ||
|
|
30
|
+
(legacy ? !allowedLegacy.test(target) : (prior && ['CLAUDE.md','GEMINI.md'].includes(target)) || !centralTarget(target) || source !== 'payload/central/' + target + (target === '.gitignore' ? '.txt' : '')))
|
|
31
|
+
fail('INVALID_CATALOG', 'Entrada fora da lista permitida do catálogo.');
|
|
32
|
+
if ([...seen].some(p => p.startsWith(target.toLowerCase() + '/') || target.toLowerCase().startsWith(p + '/')))
|
|
33
|
+
fail('INVALID_CATALOG', 'Colisão de arquivo e diretório no catálogo.');
|
|
34
|
+
const content = await readKnown(packageRoot, asset);
|
|
35
|
+
if (!content || hash(content) !== item.sha256) fail('CATALOG_INTEGRITY', `Integridade do template inválida: ${target}`);
|
|
36
|
+
seen.add(target.toLowerCase());
|
|
37
|
+
entries.push({ path: target, kind: target === '.gitignore' || (!legacy && !prior && ['CLAUDE.md','GEMINI.md'].includes(target)) ? 'custom' : 'managed', source, content });
|
|
38
|
+
}
|
|
39
|
+
if (entries.length !== (legacy || prior ? 38 : 40)) fail('INVALID_CATALOG', 'Inventário de fontes incompleto.');
|
|
40
|
+
return { ...catalog, readOnly: legacy || prior, layoutVersion: layout, manifestVersion: legacy ? '1.0.0' : '2.0.0', entries };
|
|
41
|
+
}
|
|
42
|
+
export async function packageVersion() {
|
|
43
|
+
return JSON.parse(await fs.readFile(new URL('../../package.json', import.meta.url), 'utf8')).version;
|
|
44
|
+
}
|
|
45
|
+
export function contextEntries(track, layout = 'central-v2') {
|
|
46
|
+
const phase = track === 'new' ? 'P00 Requirements & Architecture' : 'P01 Discovery';
|
|
47
|
+
return [
|
|
48
|
+
{ path: 'docs/project/CURRENT_STATE.md', content: `# Estado atual\n\n## Trilha e fase inicial\nTrilha: ${track}\nFase inicial: ${phase}\n\n## Stack e ambiente\nNão verificados. O runtime Node.js 24 pertence somente à CLI.\n\n## Banco\nMANUAL_SQL (padrão). Nenhuma execução de banco autorizada.\n\n## Aprovações\nG0–G6, DB_MODE, DB_CHANGE e DB_EXEC pendentes quando aplicáveis.\nFonte canônica: docs/project/DECISIONS.md. Instalação não aprova fases.\n\n## Próximo passo\nRealizar a fase inicial autorizada e preencher evidências sem presumir resultados.\n` },
|
|
49
|
+
{ path: 'docs/project/ENVIRONMENT.md', content: '# Ambiente do projeto\n\nModo de banco: MANUAL_SQL (padrão).\nMotor, versão, stack e ambiente: não verificados.\nG2: pendente. DB_CHANGE e DB_EXEC: pendentes quando aplicáveis.\nNenhuma execução autorizada; preencher a partir de templates/ENVIRONMENT.md.\n' },
|
|
50
|
+
{ path: 'docs/project/DECISIONS.md', content: '# Registro canônico de decisões e aprovações\n\nNenhuma aprovação humana registrada pela instalação.\nG0–G6, DB_MODE, DB_CHANGE e DB_EXEC pendentes quando aplicáveis.\nUsar templates/DECISIONS.md; registrar manifestação humana, ação, alvo, executor e revisão.\n' }
|
|
51
|
+
].map(e => ({ ...e, kind: 'custom', source: `generated:${track}`, content: Buffer.from(layout === 'legacy-v1' ? e.content : e.content.replaceAll('templates/', '.ai-dev-workflow/templates/')) }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readKnown, hash } from '../platform/filesystem.js';
|
|
2
|
+
import { inspectGit, assertGitMetadataIsolation } from '../platform/git.js';
|
|
3
|
+
import { readInstalledEntry } from './adapters.js';
|
|
4
|
+
import { safeRelative } from '../platform/filesystem.js';
|
|
5
|
+
import { installedProfile } from './manifest.js';
|
|
6
|
+
import { assertReconciled, checkContext } from './journal.js';
|
|
7
|
+
import { fail, WorkflowError } from './errors.js';
|
|
8
|
+
export async function inspectInstallation(root) {
|
|
9
|
+
await assertGitMetadataIsolation(root);
|
|
10
|
+
await assertReconciled(root);
|
|
11
|
+
await checkContext(root);
|
|
12
|
+
const profile = await installedProfile(root);
|
|
13
|
+
if (!profile) fail('NOT_INSTALLED', 'Manifesto ausente; execute init --dry-run para planejar instalação.');
|
|
14
|
+
const { catalog, manifest } = profile;
|
|
15
|
+
const files = [];
|
|
16
|
+
for (const entry of manifest.files) {
|
|
17
|
+
const current = await readInstalledEntry(root, entry.path);
|
|
18
|
+
const state = !current ? 'missing' : hash(current) === entry.installedHash ? 'unchanged' : entry.kind === 'custom' ? 'customized' : 'modified';
|
|
19
|
+
files.push({ path: entry.path, kind: entry.kind, state });
|
|
20
|
+
}
|
|
21
|
+
return { manifest, files, catalog };
|
|
22
|
+
}
|
|
23
|
+
export function validationFindings(path, content, allowedPaths) {
|
|
24
|
+
const findings = [];
|
|
25
|
+
const text = content.toString('utf8');
|
|
26
|
+
if (path.startsWith('.continue/') && path.endsWith('.md')) {
|
|
27
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
28
|
+
const field = key => new RegExp(`^${key}:\\s*(.+)$`, 'm').exec(fm?.[1] ?? '')?.[1].trim();
|
|
29
|
+
if (!fm || !field('name') || (path.includes('/prompts/') && field('invokable') !== 'true') ||
|
|
30
|
+
(path === '.continue/rules/workflow.md' && field('alwaysApply') !== 'true'))
|
|
31
|
+
findings.push({ code: 'FRONTMATTER', path });
|
|
32
|
+
}
|
|
33
|
+
// Caminhos concretos de instrução; saídas de tasks e placeholders não são pré-requisitos.
|
|
34
|
+
for (const match of text.matchAll(/`((?:\.ai-dev-workflow|rules|agents|templates|integrations|stacks|docs|\.continue)\/[^`\n]+)`/g)) {
|
|
35
|
+
const ref = match[1];
|
|
36
|
+
if (/[<>*]/.test(ref) || /TASK-[Xx]+/.test(ref) || /\/$/.test(ref)) continue;
|
|
37
|
+
if (!allowedPaths.has(ref)) findings.push({ code: 'REFERENCE_MISSING', path, reference: ref });
|
|
38
|
+
}
|
|
39
|
+
if (['CLAUDE.md','GEMINI.md'].includes(path)) for (const match of text.matchAll(/^@([^\r\n]+)$/gm)) {
|
|
40
|
+
const ref = match[1].replace(/^\.\//, '');
|
|
41
|
+
if (!safeRelative(ref) || !allowedPaths.has(ref)) findings.push({ code: 'IMPORT_INVALID', path, reference: ref });
|
|
42
|
+
}
|
|
43
|
+
return findings;
|
|
44
|
+
}
|
|
45
|
+
export async function validateInstallation(root) {
|
|
46
|
+
const state = await inspectInstallation(root);
|
|
47
|
+
const findings = [];
|
|
48
|
+
const allowed = new Set(state.files.filter(f => f.state !== 'missing').map(f => f.path));
|
|
49
|
+
for (const file of state.files) {
|
|
50
|
+
if (['missing', 'modified'].includes(file.state)) findings.push({ code: file.state.toUpperCase(), path: file.path });
|
|
51
|
+
if (file.kind === 'managed' && file.state !== 'missing' && file.path.endsWith('.md')) {
|
|
52
|
+
findings.push(...validationFindings(file.path, await readKnown(root, file.path), allowed));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { ok: !findings.length, exitCode: findings.length ? 2 : 0, findings, files: state.files,
|
|
56
|
+
layoutVersion: state.catalog.layoutVersion, readOnly: state.catalog.readOnly,
|
|
57
|
+
templateVersion: state.manifest.templateVersion, manifestVersion: state.manifest.manifestVersion,
|
|
58
|
+
message: (state.catalog.readOnly ? 'Perfil anterior somente leitura; init bloqueado. ' : '') + (findings.length ? 'Validação encontrou divergências; nenhum arquivo alterado.' : 'Estrutura, hashes e referências validados; aprovações humanas não verificadas automaticamente.') };
|
|
59
|
+
}
|
|
60
|
+
export async function status(root) {
|
|
61
|
+
const { manifest, files, catalog } = await inspectInstallation(root);
|
|
62
|
+
return { ok: true, layoutVersion: catalog.layoutVersion, readOnly: catalog.readOnly, templateVersion: manifest.templateVersion, cliVersion: manifest.cliVersion,
|
|
63
|
+
manifestVersion: manifest.manifestVersion, installedTrack: manifest.track, files,
|
|
64
|
+
approvals: 'Consultar docs/project/DECISIONS.md; a CLI não aprova gates.',
|
|
65
|
+
message: 'Contexto customizado é preservado; trilha exibida é a selecionada na instalação.' };
|
|
66
|
+
}
|
|
67
|
+
export async function doctor(root) {
|
|
68
|
+
const checks = [];
|
|
69
|
+
try { const git = await inspectGit(root); checks.push({ ...git, ok: git.safe }); }
|
|
70
|
+
catch (e) { checks.push({ code: e.code ?? 'IO_ERROR', ok: false, exitCode: e.exitCode ?? 1, message: 'Falha operacional na consulta Git.' }); }
|
|
71
|
+
try {
|
|
72
|
+
const validation = await validateInstallation(root);
|
|
73
|
+
checks.push({ code: 'INSTALLATION', layoutVersion: validation.layoutVersion, readOnly: validation.readOnly, ok: validation.ok, exitCode: validation.exitCode, message: validation.message, findings: validation.findings });
|
|
74
|
+
} catch (e) {
|
|
75
|
+
checks.push({ code: e instanceof WorkflowError ? e.code : 'IO_ERROR', ok: false, exitCode: e.exitCode ?? 1,
|
|
76
|
+
message: e instanceof WorkflowError ? e.message : 'Falha operacional na leitura da instalação.' });
|
|
77
|
+
}
|
|
78
|
+
const ok = checks.every(c => c.ok);
|
|
79
|
+
return { ok, checks, layoutVersion: checks.find(c => c.code === 'INSTALLATION')?.layoutVersion,
|
|
80
|
+
readOnly: checks.find(c => c.code === 'INSTALLATION')?.readOnly, exitCode: checks.some(c => c.exitCode === 1) ? 1 : ok ? 0 : 2,
|
|
81
|
+
message: 'Diagnóstico somente leitura; nenhum reparo, banco ou acesso externo executado.' };
|
|
82
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export class WorkflowError extends Error {
|
|
2
|
+
constructor(code, message, exitCode = 2) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.code = code;
|
|
5
|
+
this.exitCode = exitCode;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function fail(code, message, exitCode) {
|
|
9
|
+
throw new WorkflowError(code, message, exitCode);
|
|
10
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { hash, checkedPath, stat } from '../platform/filesystem.js';
|
|
4
|
+
import { contextEntries, loadCatalog, packageVersion } from './catalog.js';
|
|
5
|
+
import { makeManifest, installedProfile } from './manifest.js';
|
|
6
|
+
import { assertReconciled, checkContext } from './journal.js';
|
|
7
|
+
import { readInstalledEntry } from './adapters.js';
|
|
8
|
+
import { fail } from './errors.js';
|
|
9
|
+
export async function planInstallation(root, track) {
|
|
10
|
+
await assertReconciled(root);
|
|
11
|
+
await checkContext(root);
|
|
12
|
+
const installed = await installedProfile(root);
|
|
13
|
+
if (installed?.catalog.layoutVersion === 'legacy-v1') fail('LEGACY_READ_ONLY', 'Instalação legada: somente doctor/status/validate. Init não migra nem repara; revisão humana necessária.');
|
|
14
|
+
if (installed?.catalog.readOnly) fail('TEMPLATE_READ_ONLY', 'Template anterior: somente inspeção; init não atualiza nem repara instalações existentes.');
|
|
15
|
+
if (!installed) await checkEmptyNamespace(root);
|
|
16
|
+
const catalog = await loadCatalog();
|
|
17
|
+
const entries = [...catalog.entries, ...contextEntries(track)];
|
|
18
|
+
const previous = installed?.manifest ?? null;
|
|
19
|
+
if (previous && previous.track !== track) fail('TRACK_CONFLICT', 'Trilha já instalada difere; alteração requer plano humano.');
|
|
20
|
+
const plan = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const current = await readInstalledEntry(root, entry.path);
|
|
23
|
+
const initialHash = hash(entry.content);
|
|
24
|
+
const prior = previous?.files.find(f => f.path === entry.path);
|
|
25
|
+
let action = 'create';
|
|
26
|
+
if (current) {
|
|
27
|
+
action = hash(current) === initialHash ? 'unchanged' : entry.kind === 'custom' ? 'preserve-custom' : 'conflict';
|
|
28
|
+
} else if (prior?.kind === 'custom') {
|
|
29
|
+
action = 'conflict'; // não recriar contexto perdido com defaults que apaguem decisões
|
|
30
|
+
}
|
|
31
|
+
plan.push({ ...entry, action, installedHash: prior?.installedHash ?? (current ? hash(current) : initialHash) });
|
|
32
|
+
}
|
|
33
|
+
return { catalog, previous, plan, manifest: makeManifest(catalog, await packageVersion(), track, plan) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function checkEmptyNamespace(root) {
|
|
37
|
+
const directory = await checkedPath(root, '.ai-dev-workflow/install.json');
|
|
38
|
+
const parent = path.dirname(directory);
|
|
39
|
+
if (!await stat(parent)) return;
|
|
40
|
+
const names = await fs.readdir(parent);
|
|
41
|
+
for (const name of names) {
|
|
42
|
+
const info = await stat(path.join(parent, name));
|
|
43
|
+
if (info?.isSymbolicLink() || (info && !info.isDirectory() && !info.isFile())) fail('UNSAFE_PATH', 'Link ou arquivo especial no namespace central.');
|
|
44
|
+
if (['agents', 'rules', 'templates', 'stacks', 'integrations', 'docs'].includes(name) && !info?.isDirectory())
|
|
45
|
+
fail('UNSAFE_PATH', 'Diretório central ocupado por arquivo.');
|
|
46
|
+
}
|
|
47
|
+
if (names.some(name => !['install.lock', 'install-journal.jsonl'].includes(name)))
|
|
48
|
+
fail('NAMESPACE_CONFLICT', 'Namespace .ai-dev-workflow preexistente sem manifesto reconhecido; reconciliação humana necessária.');
|
|
49
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createExclusive, checkedPath, readKnown, hash } from '../platform/filesystem.js';
|
|
4
|
+
import { manifestPath } from './catalog.js';
|
|
5
|
+
import { inspectGit } from '../platform/git.js';
|
|
6
|
+
import { planInstallation } from './install-plan.js';
|
|
7
|
+
import { startJournal, journalPath, checkContext } from './journal.js';
|
|
8
|
+
import { fail, WorkflowError } from './errors.js';
|
|
9
|
+
const lockPath = '.ai-dev-workflow/install.lock';
|
|
10
|
+
export async function install(root, track, dryRun = false) {
|
|
11
|
+
const git = await inspectGit(root);
|
|
12
|
+
if (!git.safe) fail(git.code, git.message, git.exitCode);
|
|
13
|
+
const result = await planInstallation(root, track);
|
|
14
|
+
const operations = [...result.plan.map(({ path, kind, action }) => ({ path, kind, action })),
|
|
15
|
+
{ path: manifestPath, kind: 'metadata', action: result.previous ? 'unchanged' : 'create' }];
|
|
16
|
+
if (result.plan.some(e => e.action === 'conflict'))
|
|
17
|
+
return { ok: false, exitCode: 2, code: 'CONFLICT', dryRun, operations, message: 'Conflitos detectados; nenhum arquivo escrito.' };
|
|
18
|
+
if (dryRun) return { ok: true, dryRun: true, operations, message: 'Plano somente leitura; journal e manifesto não foram criados.' };
|
|
19
|
+
let lock;
|
|
20
|
+
const created = [];
|
|
21
|
+
try {
|
|
22
|
+
lock = await checkedPath(root, lockPath);
|
|
23
|
+
await fs.mkdir(path.dirname(lock), { recursive: true });
|
|
24
|
+
await checkedPath(root, lockPath);
|
|
25
|
+
await fs.writeFile(lock, '', { flag: 'wx' });
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e instanceof WorkflowError) throw e;
|
|
28
|
+
return { ok: false, exitCode: e.code === 'EEXIST' ? 2 : 1, code: e.code === 'EEXIST' ? 'INSTALL_LOCKED' : 'IO_ERROR',
|
|
29
|
+
message: 'Não foi possível obter lock local; nenhum arquivo do template escrito.', operations };
|
|
30
|
+
}
|
|
31
|
+
let response;
|
|
32
|
+
let journal;
|
|
33
|
+
try {
|
|
34
|
+
const check = await inspectGit(root);
|
|
35
|
+
if (!check.safe) fail(check.code, check.message, check.exitCode);
|
|
36
|
+
const fresh = await planInstallation(root, track);
|
|
37
|
+
if (fresh.plan.some(e => e.action === 'conflict')) fail('CONFLICT', 'Destino mudou desde o plano; execução interrompida.');
|
|
38
|
+
const pending = fresh.plan.filter(e => e.action === 'create');
|
|
39
|
+
if (!fresh.previous) pending.push({ path: manifestPath, kind: 'metadata', action: 'create',
|
|
40
|
+
content: Buffer.from(JSON.stringify(fresh.manifest, null, 2) + '\n') });
|
|
41
|
+
if (pending.length) {
|
|
42
|
+
// Intenção completa durável ANTES de qualquer escrita do template/contexto/manifesto.
|
|
43
|
+
const manifestEntry = pending.find(e => e.path === manifestPath);
|
|
44
|
+
journal = await startJournal(root, [...fresh.plan, ...(manifestEntry ? [manifestEntry] : [])], track, fresh.catalog.templateVersion, fresh.manifest);
|
|
45
|
+
for (const entry of pending) {
|
|
46
|
+
await createExclusive(root, entry.path, entry.content);
|
|
47
|
+
created.push(entry.path);
|
|
48
|
+
await journal.append({ event: 'created', path: entry.path, hash: hash(entry.content) });
|
|
49
|
+
}
|
|
50
|
+
for (const entry of pending) {
|
|
51
|
+
const actual = await readKnown(root, entry.path);
|
|
52
|
+
if (!actual || hash(actual) !== hash(entry.content)) fail('INSTALL_INCONSISTENT', 'Conteúdo criado diverge do plano; reconciliação humana necessária.');
|
|
53
|
+
}
|
|
54
|
+
await checkContext(root);
|
|
55
|
+
await journal.append({ event: 'completed' });
|
|
56
|
+
await journal.close();
|
|
57
|
+
// Somente esta execução bem-sucedida remove seu próprio journal.
|
|
58
|
+
await fs.unlink(journal.file);
|
|
59
|
+
}
|
|
60
|
+
response = { ok: true, operations, created, message: created.length ? 'Instalação concluída; nenhuma aprovação de workflow ou banco concedida.' : 'Instalação já equivalente; nenhum arquivo persistente alterado.' };
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (e.createdPath && !created.includes(e.createdPath)) created.push(e.createdPath);
|
|
63
|
+
response = { ok: false, exitCode: e.exitCode ?? 1, code: e.code ?? 'IO_ERROR', created, journal: journalPath,
|
|
64
|
+
message: 'Instalação interrompida. Journal e arquivos foram preservados; init/status/validate bloqueados até reconciliação humana segura.', operations };
|
|
65
|
+
} finally {
|
|
66
|
+
if (journal) await journal.close().catch(() => {});
|
|
67
|
+
try { await fs.unlink(lock); }
|
|
68
|
+
catch { response = { ...response, ok: false, exitCode: 1, code: 'LOCK_CLEANUP',
|
|
69
|
+
message: 'Não foi possível remover o lock desta execução; revisar lock e journal manualmente. Arquivos preservados.' }; }
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
}
|