atlas-workflow 0.9.1 → 0.9.3
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/README.md +5 -2
- package/VERSION +1 -1
- package/build/bump-version.mjs +6 -21
- package/build/cli/atlas-init.mjs +92 -5
- package/build/tests/classify-findings.test.mjs +20 -0
- package/build/tests/etapa3.test.mjs +161 -0
- package/build/tests/test_classify_findings.py +79 -0
- package/hosts/opencode/.opencode/agents/atlas-findings-repair.md +4 -0
- package/hosts/opencode/.opencode/agents/atlas-task-validator.md +18 -1
- package/hosts/opencode/.opencode/atlas/VERSION +1 -1
- package/hosts/opencode/.opencode/atlas/orchestrator/README.md +7 -5
- package/hosts/opencode/.opencode/atlas/orchestrator/commands/workflow.md +1 -1
- package/hosts/opencode/.opencode/atlas/orchestrator/references/host-adapters.md +13 -12
- package/hosts/opencode/.opencode/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/README.md +1 -1
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/package.json +1 -1
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/server.js +514 -20
- package/hosts/opencode/.opencode/atlas/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
- package/hosts/opencode/.opencode/atlas/packages/templates/PRD_TEMPLATE.md +2 -1
- package/hosts/opencode/.opencode/atlas/packages/templates/STATE_FILE_SCHEMA.md +25 -1
- package/hosts/opencode/.opencode/skills/_shared/references/stack-profiles.md +36 -0
- package/hosts/opencode/.opencode/skills/_shared/scripts/document_quality.mjs +252 -0
- package/hosts/opencode/.opencode/skills/atlas-backlog-generator/SKILL.md +7 -2
- package/hosts/opencode/.opencode/skills/atlas-direct-execute/SKILL.md +6 -2
- package/hosts/opencode/.opencode/skills/atlas-findings-repair/SKILL.md +11 -1
- package/hosts/opencode/.opencode/skills/atlas-plan-execute/SKILL.md +16 -2
- package/hosts/opencode/.opencode/skills/atlas-plan-handoff/SKILL.md +6 -4
- package/hosts/opencode/.opencode/skills/atlas-prd-interview/SKILL.md +7 -2
- package/hosts/opencode/.opencode/skills/atlas-slice-review/SKILL.md +37 -2
- package/hosts/opencode/.opencode/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
- package/hosts/opencode/.opencode/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
- package/hosts/opencode/.opencode/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
- package/hosts/opencode/.opencode/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
- package/hosts/opencode/.opencode/skills/atlas-task-validator/SKILL.md +29 -14
- package/hosts/opencode/.opencode/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/hosts/pi/.pi/agents/atlas-direct-execute.md +6 -2
- package/hosts/pi/.pi/agents/atlas-findings-repair.md +15 -1
- package/hosts/pi/.pi/agents/atlas-plan-execute.md +16 -2
- package/hosts/pi/.pi/agents/atlas-slice-review.md +37 -2
- package/hosts/pi/.pi/agents/atlas-task-validator.md +18 -1
- package/hosts/pi/atlas/VERSION +1 -1
- package/hosts/pi/atlas/orchestrator/README.md +7 -5
- package/hosts/pi/atlas/orchestrator/commands/workflow.md +1 -1
- package/hosts/pi/atlas/orchestrator/references/host-adapters.md +13 -12
- package/hosts/pi/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/hosts/pi/atlas/packages/mcp-server/README.md +1 -1
- package/hosts/pi/atlas/packages/mcp-server/package.json +1 -1
- package/hosts/pi/atlas/packages/mcp-server/server.js +514 -20
- package/hosts/pi/atlas/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
- package/hosts/pi/atlas/packages/templates/PRD_TEMPLATE.md +2 -1
- package/hosts/pi/atlas/packages/templates/STATE_FILE_SCHEMA.md +25 -1
- package/hosts/pi/skills/_shared/references/stack-profiles.md +36 -0
- package/hosts/pi/skills/_shared/scripts/document_quality.mjs +252 -0
- package/hosts/pi/skills/atlas-backlog-generator/SKILL.md +7 -2
- package/hosts/pi/skills/atlas-direct-execute/SKILL.md +6 -2
- package/hosts/pi/skills/atlas-findings-repair/SKILL.md +11 -1
- package/hosts/pi/skills/atlas-plan-execute/SKILL.md +16 -2
- package/hosts/pi/skills/atlas-plan-handoff/SKILL.md +6 -4
- package/hosts/pi/skills/atlas-prd-interview/SKILL.md +7 -2
- package/hosts/pi/skills/atlas-slice-review/SKILL.md +37 -2
- package/hosts/pi/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
- package/hosts/pi/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
- package/hosts/pi/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
- package/hosts/pi/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
- package/hosts/pi/skills/atlas-task-validator/SKILL.md +29 -14
- package/hosts/pi/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/hosts/zcode/.zcode-plugin/plugin.json +27 -0
- package/hosts/zcode/agents/atlas-direct-execute.md +31 -0
- package/hosts/zcode/agents/atlas-findings-repair.md +39 -0
- package/hosts/zcode/agents/atlas-plan-execute.md +33 -0
- package/hosts/zcode/agents/atlas-slice-review.md +27 -0
- package/hosts/zcode/agents/atlas-task-validator.md +138 -0
- package/hosts/zcode/packages/mcp-server/README.md +29 -0
- package/hosts/zcode/packages/mcp-server/VERSION +1 -0
- package/hosts/zcode/packages/mcp-server/package.json +15 -0
- package/hosts/zcode/packages/mcp-server/server.js +3897 -0
- package/hosts/zcode/packages/orchestrator/README.md +270 -0
- package/hosts/zcode/packages/orchestrator/commands/workflow.md +37 -0
- package/hosts/zcode/packages/orchestrator/defaults/paths.md +21 -0
- package/hosts/zcode/packages/orchestrator/references/host-adapters.md +106 -0
- package/hosts/zcode/packages/orchestrator/references/qa_s13_matrix.md +141 -0
- package/hosts/zcode/packages/orchestrator/references/subagent_dispatch.md +42 -0
- package/hosts/zcode/packages/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +391 -0
- package/hosts/zcode/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +855 -0
- package/hosts/zcode/packages/templates/BOUNDARY_PRD_PLAN.md +93 -0
- package/hosts/zcode/packages/templates/PERGUNTAS_EM_ABERTO_TEMPLATE.md +139 -0
- package/hosts/zcode/packages/templates/PLAN_TEMPLATE.md +146 -0
- package/hosts/zcode/packages/templates/PRD_TEMPLATE.md +150 -0
- package/hosts/zcode/packages/templates/STATE_FILE_SCHEMA.md +56 -0
- package/hosts/zcode/skills/_shared/references/stack-profiles.md +36 -0
- package/hosts/zcode/skills/_shared/scripts/document_quality.mjs +252 -0
- package/hosts/zcode/skills/atlas-backlog-generator/SKILL.md +93 -0
- package/hosts/zcode/skills/atlas-backlog-generator/agents/openai.yaml +4 -0
- package/hosts/zcode/skills/atlas-direct-execute/SKILL.md +221 -0
- package/hosts/zcode/skills/atlas-direct-execute/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-findings-repair/SKILL.md +158 -0
- package/hosts/zcode/skills/atlas-findings-repair/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-plan-execute/SKILL.md +175 -0
- package/hosts/zcode/skills/atlas-plan-execute/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-plan-execute/references/plan-contract.md +88 -0
- package/hosts/zcode/skills/atlas-plan-execute/references/quality-gates.md +60 -0
- package/hosts/zcode/skills/atlas-plan-execute/scripts/check_budget_state.py +96 -0
- package/hosts/zcode/skills/atlas-plan-execute/scripts/extract_plan_contract.py +191 -0
- package/hosts/zcode/skills/atlas-plan-execute/scripts/validate_gate_result.py +56 -0
- package/hosts/zcode/skills/atlas-plan-handoff/SKILL.md +183 -0
- package/hosts/zcode/skills/atlas-plan-handoff/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-prd-interview/SKILL.md +82 -0
- package/hosts/zcode/skills/atlas-prd-interview/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-slice-review/SKILL.md +156 -0
- package/hosts/zcode/skills/atlas-slice-review/agents/openai.yaml +4 -0
- package/hosts/zcode/skills/atlas-slice-review/references/review-contract.md +58 -0
- package/hosts/zcode/skills/atlas-slice-review/references/scenario-lenses.md +57 -0
- package/hosts/zcode/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
- package/hosts/zcode/skills/atlas-slice-review/scripts/classify_findings.py +24 -0
- package/hosts/zcode/skills/atlas-slice-review/scripts/extract_review_slice.py +158 -0
- package/hosts/zcode/skills/atlas-sprint-prd-generator/SKILL.md +77 -0
- package/hosts/zcode/skills/atlas-sprint-prd-generator/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-task-validator/SKILL.md +173 -0
- package/hosts/zcode/skills/atlas-task-validator/agents/openai.yaml +7 -0
- package/hosts/zcode/skills/atlas-workflow-orchestrator/SKILL.md +391 -0
- package/package.json +1 -1
- package/plugins/atlas-workflow-orchestrator/.codex/agents/atlas-findings-repair.toml +1 -1
- package/plugins/atlas-workflow-orchestrator/.codex/agents/atlas-task-validator.toml +1 -1
- package/plugins/atlas-workflow-orchestrator/.codex-plugin/plugin.json +1 -1
- package/plugins/atlas-workflow-orchestrator/VERSION +1 -1
- package/plugins/atlas-workflow-orchestrator/agents/atlas-findings-repair.md +4 -0
- package/plugins/atlas-workflow-orchestrator/agents/atlas-task-validator.md +18 -1
- package/plugins/atlas-workflow-orchestrator/orchestrator/README.md +7 -5
- package/plugins/atlas-workflow-orchestrator/orchestrator/commands/workflow.md +1 -1
- package/plugins/atlas-workflow-orchestrator/orchestrator/references/host-adapters.md +13 -12
- package/plugins/atlas-workflow-orchestrator/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/README.md +1 -1
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/package.json +1 -1
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/server.js +514 -20
- package/plugins/atlas-workflow-orchestrator/packages/skills/_shared/references/stack-profiles.md +36 -0
- package/plugins/atlas-workflow-orchestrator/packages/skills/_shared/scripts/document_quality.mjs +252 -0
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-backlog-generator/SKILL.md +7 -2
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-direct-execute/SKILL.md +6 -2
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-findings-repair/SKILL.md +11 -1
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-execute/SKILL.md +16 -2
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-handoff/SKILL.md +6 -4
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-prd-interview/SKILL.md +7 -2
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/SKILL.md +37 -2
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-task-validator/SKILL.md +29 -14
- package/plugins/atlas-workflow-orchestrator/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
- package/plugins/atlas-workflow-orchestrator/packages/templates/PRD_TEMPLATE.md +2 -1
- package/plugins/atlas-workflow-orchestrator/packages/templates/STATE_FILE_SCHEMA.md +25 -1
- package/plugins/atlas-workflow-orchestrator/skills/_shared/references/stack-profiles.md +36 -0
- package/plugins/atlas-workflow-orchestrator/skills/_shared/scripts/document_quality.mjs +252 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-backlog-generator/SKILL.md +7 -2
- package/plugins/atlas-workflow-orchestrator/skills/atlas-direct-execute/SKILL.md +6 -2
- package/plugins/atlas-workflow-orchestrator/skills/atlas-findings-repair/SKILL.md +11 -1
- package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-execute/SKILL.md +16 -2
- package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-handoff/SKILL.md +6 -4
- package/plugins/atlas-workflow-orchestrator/skills/atlas-prd-interview/SKILL.md +7 -2
- package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/SKILL.md +37 -2
- package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
- package/plugins/atlas-workflow-orchestrator/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
- package/plugins/atlas-workflow-orchestrator/skills/atlas-task-validator/SKILL.md +29 -14
- package/plugins/atlas-workflow-orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
- package/plugins/atlas-workflow-orchestrator/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
- package/plugins/atlas-workflow-orchestrator/templates/PRD_TEMPLATE.md +2 -1
- package/plugins/atlas-workflow-orchestrator/templates/STATE_FILE_SCHEMA.md +25 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Atlas Workflow
|
|
2
2
|
|
|
3
|
-
Plugin **Atlas Workflow Orchestrator** v0.9.
|
|
3
|
+
Plugin **Atlas Workflow Orchestrator** v0.9.3 — pipeline determinístico (PRD → plano → execução → validação) com skills `atlas-*`, templates e MCP. Um pacote, sete hosts: **Claude Code**, **Cursor**, **Codex App**, **Antigravity (Gemini)**, **ZCode**, **OpenCode** e **Pi CLI**.
|
|
4
4
|
|
|
5
|
-
**Versão:** [`VERSION`](VERSION) (`0.9.
|
|
5
|
+
**Versão:** [`VERSION`](VERSION) (`0.9.3`) · **Repo:** https://github.com/pauloborini/atlas-workflow
|
|
6
6
|
|
|
7
7
|
## Hosts
|
|
8
8
|
|
|
@@ -12,6 +12,7 @@ Plugin **Atlas Workflow Orchestrator** v0.9.1 — pipeline determinístico (PRD
|
|
|
12
12
|
| Cursor | **Igual ao Claude Code** (ver nota abaixo) | `atlas-workflow-claude.plugin` | — |
|
|
13
13
|
| Codex App | Marketplace GitHub | `atlas-workflow-codex.plugin` | — |
|
|
14
14
|
| Antigravity (Gemini) | Instalador from-source (`init antigravity`) → `~/.gemini/config/` | — (cópia direta, sem artefato `.plugin`) | — |
|
|
15
|
+
| ZCode | Instalador cache-based (`init zcode`) → `~/.zcode/cli/plugins/cache/` | `atlas-workflow-zcode.plugin` | — |
|
|
15
16
|
| Opencode | Catálogo from-source `hosts/opencode/` | `atlas-workflow-opencode.plugin` | — |
|
|
16
17
|
| Pi CLI | Catálogo from-source `hosts/pi/` | `atlas-workflow-pi.plugin` | **`pi-mcp-adapter` + `pi-subagents`** |
|
|
17
18
|
|
|
@@ -31,6 +32,7 @@ Um instalador único cobre os hosts de forma **global** (recomendado para valer
|
|
|
31
32
|
npx github:pauloborini/atlas-workflow init claudecode # ou: cursor
|
|
32
33
|
npx github:pauloborini/atlas-workflow init codex
|
|
33
34
|
npx github:pauloborini/atlas-workflow init antigravity
|
|
35
|
+
npx github:pauloborini/atlas-workflow init zcode
|
|
34
36
|
npx github:pauloborini/atlas-workflow init opencode --global
|
|
35
37
|
npx github:pauloborini/atlas-workflow init pi --global --yes # --yes auto-instala as 2 deps
|
|
36
38
|
```
|
|
@@ -38,6 +40,7 @@ npx github:pauloborini/atlas-workflow init pi --global --yes # --yes auto-insta
|
|
|
38
40
|
- **claudecode/cursor**: o instalador roda o `marketplace add` + `install` nativos da CLI por você. Já são globais por natureza.
|
|
39
41
|
- **codex**: o instalador roda `marketplace add` + `plugin add` e também copia os custom agents Atlas para `CODEX_HOME/agents` (`~/.codex/agents` se `CODEX_HOME` não estiver definido). Este é o caminho garantido para `spawn_agent(agent_type: "atlas-*")`.
|
|
40
42
|
- **antigravity**: o instalador registra o Atlas como um plugin em `~/.gemini/config/plugins/` e adiciona o MCP correspondente em `mcp_config.json`.
|
|
43
|
+
- **zcode**: o instalador copia o catálogo from-source `hosts/zcode/` para `~/.zcode/cli/plugins/cache/pauloborini/atlas-workflow-orchestrator/<version>/` e registra o plugin no `marketplace.json` do ZCode. Ative no host via `/plugins enable atlas-workflow-orchestrator`. ZCode é Claude Agent SDK (clone estrutural do Claude Code): `Agent(subagent_type)` + `TodoWrite` + MCP stdio nativos — perfil `self_evident`, sem dependências externas.
|
|
41
44
|
- **opencode**: com `--global`, instala globalmente em `~/.config/opencode/` (o MCP é registrado com caminho absoluto, funcionando em todos os projetos).
|
|
42
45
|
- **pi**: com `--global`, instala globalmente em `~/.pi/agent/` (honra `PI_CODING_AGENT_DIR`), registra o MCP em `mcp.json` global e checa/instala as deps `pi-mcp-adapter` + `pi-subagents`.
|
|
43
46
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.3
|
package/build/bump-version.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Bump
|
|
3
|
-
// regenera bundles/
|
|
4
|
-
// — quem publica é o workflow Release ao detectar VERSION novo na main.
|
|
2
|
+
// Bump deterministico de versao. Sincroniza os arquivos com versao concreta,
|
|
3
|
+
// regenera bundles/catalogos e roda check-consistency. NAO cria tag nem commita
|
|
4
|
+
// — quem publica e cria a tag é o workflow Release ao detectar VERSION novo na main.
|
|
5
5
|
//
|
|
6
6
|
// Uso: node build/bump-version.mjs <nova-versao> (ex.: 0.8.3)
|
|
7
7
|
//
|
|
@@ -88,26 +88,11 @@ execFileSync('bash', [path.join(ROOT, 'build', 'build-plugins.sh')], { cwd: ROOT
|
|
|
88
88
|
console.log(`Rodando check-consistency…`);
|
|
89
89
|
execFileSync('node', [path.join(ROOT, 'build', 'check-consistency.mjs')], { cwd: ROOT, stdio: 'inherit' });
|
|
90
90
|
|
|
91
|
-
// Criar tag git local (marketplace resolve por tag, não por HEAD).
|
|
92
|
-
// Tag só é criada — push fica pro passo manual junto com o commit.
|
|
93
|
-
// CI Release é idempotente: se tag já existe, pula criação.
|
|
94
|
-
const tag = `v${next}`;
|
|
95
|
-
try {
|
|
96
|
-
execFileSync('git', ['tag', tag], { cwd: ROOT, stdio: 'pipe' });
|
|
97
|
-
console.log(`\n tag ${tag} (local — push com: git push origin ${tag})`);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
if (e.stderr?.toString().includes('already exists')) {
|
|
100
|
-
console.log(`\n tag ${tag} já existe (ok)`);
|
|
101
|
-
} else {
|
|
102
|
-
throw e;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
91
|
console.log(`\nbump-version: ${current} -> ${next} OK.
|
|
107
92
|
|
|
108
93
|
Passos narrativos manuais (não automatizáveis):
|
|
109
94
|
1. CHANGELOG.md — adicionar entrada "## ${next} - YYYY-MM-DD".
|
|
110
95
|
2. packages/orchestrator/README.md — adicionar seção "### Novidades v${next}" e "Last updated".
|
|
111
|
-
3. Revisar 'git status', commitar e dar push na main
|
|
112
|
-
git push origin main
|
|
113
|
-
=> CI Release
|
|
96
|
+
3. Revisar 'git status', commitar e dar push na main:
|
|
97
|
+
git push origin main
|
|
98
|
+
=> CI Release publica e cria a tag v${next}.`);
|
package/build/cli/atlas-init.mjs
CHANGED
|
@@ -31,6 +31,7 @@ const HOST_ALIASES = {
|
|
|
31
31
|
codex: 'codex',
|
|
32
32
|
opencode: 'opencode',
|
|
33
33
|
pi: 'pi',
|
|
34
|
+
zcode: 'zcode', zai: 'zcode',
|
|
34
35
|
antigravity: 'antigravity', gemini: 'antigravity', antigravitycode: 'antigravity',
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -571,6 +572,91 @@ function uninstallAntigravity(opts) {
|
|
|
571
572
|
log('ok — artefatos globais do Atlas para Antigravity removidos.');
|
|
572
573
|
}
|
|
573
574
|
|
|
575
|
+
// --- ZCode (cache-based install) ----------------------------------------------
|
|
576
|
+
// ZCode só descobre plugins no escopo `zcode-plugins-official` (verificado
|
|
577
|
+
// empiricamente no bundle zcode.cjs: `G2="zcode-plugins-official"` é hardcoded e o
|
|
578
|
+
// scan de cache é restrito a `cache/zcode-plugins-official/<plugin>/<version>/`).
|
|
579
|
+
// Por isso o installer copia para esse path — não para um marketplace custom.
|
|
580
|
+
// O ZCode também regenera `marketplaces/zcode-plugins-official/marketplace.json` no
|
|
581
|
+
// boot a partir do scan; mantemos essa entry sincronizada para visualização imediata.
|
|
582
|
+
|
|
583
|
+
const ZCODE_MARKETPLACE = 'zcode-plugins-official';
|
|
584
|
+
const ZCODE_PLUGIN_NAME = 'atlas-workflow-orchestrator';
|
|
585
|
+
|
|
586
|
+
function zcodeCacheDir() {
|
|
587
|
+
return path.join(homedir(), '.zcode', 'cli', 'plugins', 'cache', ZCODE_MARKETPLACE, ZCODE_PLUGIN_NAME, VERSION);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function zcodeMarketplaceCacheFile() {
|
|
591
|
+
return path.join(homedir(), '.zcode', 'cli', 'plugins', 'marketplaces', ZCODE_MARKETPLACE, 'marketplace.json');
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function updateZcodeMarketplaceCacheEntry(cacheDir) {
|
|
595
|
+
const file = zcodeMarketplaceCacheFile();
|
|
596
|
+
let cfg = { name: ZCODE_MARKETPLACE, plugins: [], version: 1 };
|
|
597
|
+
if (fs.existsSync(file)) {
|
|
598
|
+
try { cfg = JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
599
|
+
catch { log(` aviso: ${path.basename(file)} é JSON inválido — reescrevendo do zero`); }
|
|
600
|
+
}
|
|
601
|
+
cfg.name = ZCODE_MARKETPLACE;
|
|
602
|
+
cfg.plugins = (cfg.plugins ?? []).filter((p) => p.name !== ZCODE_PLUGIN_NAME);
|
|
603
|
+
cfg.plugins.push({ cachePath: cacheDir, name: ZCODE_PLUGIN_NAME, source: 'filesystem', version: VERSION });
|
|
604
|
+
cfg.version = 1;
|
|
605
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
606
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function removeZcodeMarketplaceCacheEntry() {
|
|
610
|
+
const file = zcodeMarketplaceCacheFile();
|
|
611
|
+
if (!fs.existsSync(file)) return;
|
|
612
|
+
try {
|
|
613
|
+
const cfg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
614
|
+
cfg.plugins = (cfg.plugins ?? []).filter((p) => p.name !== ZCODE_PLUGIN_NAME);
|
|
615
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
616
|
+
} catch { log(` aviso: ${path.basename(file)} é JSON inválido — não mexi`); }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function installZcode(opts) {
|
|
620
|
+
const cacheDir = zcodeCacheDir();
|
|
621
|
+
const catalogSrc = path.join(ROOT, 'hosts/zcode');
|
|
622
|
+
log(`instalando Atlas (zcode v${VERSION}) GLOBAL em ${cacheDir}`);
|
|
623
|
+
if (!fs.existsSync(catalogSrc)) fail(`catálogo zcode ausente: hosts/zcode/ (rode build/build-plugins.sh)`);
|
|
624
|
+
if (opts.dryRun) {
|
|
625
|
+
log(` [dry-run] copiaria hosts/zcode/ → ${cacheDir}`);
|
|
626
|
+
log(` [dry-run] atualizaria ${zcodeMarketplaceCacheFile()}`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
// Limpa instalação anterior (pode haver versão stale)
|
|
630
|
+
const parentDir = path.dirname(cacheDir);
|
|
631
|
+
if (fs.existsSync(parentDir)) fs.rmSync(parentDir, { recursive: true, force: true });
|
|
632
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
633
|
+
fs.cpSync(catalogSrc, cacheDir, { recursive: true });
|
|
634
|
+
// Gera o seed file no formato que o ZCode espera
|
|
635
|
+
const seed = {
|
|
636
|
+
hash: '',
|
|
637
|
+
marketplace: ZCODE_MARKETPLACE,
|
|
638
|
+
plugin: ZCODE_PLUGIN_NAME,
|
|
639
|
+
pluginVersion: VERSION,
|
|
640
|
+
source: 'filesystem',
|
|
641
|
+
version: 1,
|
|
642
|
+
};
|
|
643
|
+
fs.writeFileSync(path.join(cacheDir, '.zcode-plugin-seed.json'), JSON.stringify(seed, null, 2) + '\n');
|
|
644
|
+
// Sincroniza a entry do marketplace cache (o ZCode regenera no boot, mas
|
|
645
|
+
// mantemos sincronizado para visualização imediata no `/plugins`).
|
|
646
|
+
updateZcodeMarketplaceCacheEntry(cacheDir);
|
|
647
|
+
log('ok — ZCode instalado no cache oficial.');
|
|
648
|
+
log('próximo: abra o ZCode e ative via /plugins enable atlas-workflow-orchestrator');
|
|
649
|
+
log(' confirme com a tool MCP atlas_ping (host=zcode, status=alive).');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function uninstallZcode(opts) {
|
|
653
|
+
const cacheParent = path.join(homedir(), '.zcode', 'cli', 'plugins', 'cache', ZCODE_MARKETPLACE, ZCODE_PLUGIN_NAME);
|
|
654
|
+
log(`removendo Atlas (zcode) GLOBAL de ${cacheParent}`);
|
|
655
|
+
rmIfExists(cacheParent, opts);
|
|
656
|
+
removeZcodeMarketplaceCacheEntry();
|
|
657
|
+
log('ok — ZCode: cache e registry removidos.');
|
|
658
|
+
}
|
|
659
|
+
|
|
574
660
|
function usage() {
|
|
575
661
|
log(`atlas-workflow v${VERSION} — instalador multi-host
|
|
576
662
|
|
|
@@ -582,6 +668,7 @@ hosts:
|
|
|
582
668
|
claudecode | cursor via \`claude plugin\` (marketplace from-source; já global)
|
|
583
669
|
codex via \`codex plugin\` + custom agents em CODEX_HOME/agents
|
|
584
670
|
antigravity via plugin nativo em ~/.gemini/config/ (já global)
|
|
671
|
+
zcode via cache ~/.zcode/cli/plugins/cache/ (já global; /plugins enable)
|
|
585
672
|
opencode por-projeto: .opencode/ + opencode.json no [dir]
|
|
586
673
|
--global: ~/.config/opencode/ (vale em todos os projetos)
|
|
587
674
|
pi por-projeto: .mcp.json + .pi/agents/ no [dir] + deps
|
|
@@ -636,23 +723,23 @@ function main() {
|
|
|
636
723
|
fail(`comando desconhecido: ${cmd} (use \`init <host>\` ou \`uninstall <host>\`)`, 2);
|
|
637
724
|
}
|
|
638
725
|
|
|
639
|
-
if (!rawHost) fail('informe o host: claudecode | cursor | codex | antigravity | opencode | pi', 2);
|
|
726
|
+
if (!rawHost) fail('informe o host: claudecode | cursor | codex | antigravity | zcode | opencode | pi', 2);
|
|
640
727
|
if (extra.length) fail(`argumentos extras não suportados: ${extra.join(' ')}`, 2);
|
|
641
728
|
const host = HOST_ALIASES[rawHost.toLowerCase()];
|
|
642
|
-
if (!host) fail(`host inválido: ${rawHost} (use claudecode|cursor|codex|antigravity|opencode|pi)`, 2);
|
|
729
|
+
if (!host) fail(`host inválido: ${rawHost} (use claudecode|cursor|codex|antigravity|zcode|opencode|pi)`, 2);
|
|
643
730
|
|
|
644
731
|
const opts = parsed.opts;
|
|
645
732
|
const targetDir = path.resolve(opts.dir || rawDir || process.cwd());
|
|
646
733
|
const actions = {
|
|
647
|
-
init: { claude: installClaude, codex: installCodex, antigravity: installAntigravity, opencode: installOpencode, pi: installPi },
|
|
648
|
-
uninstall: { claude: uninstallClaude, codex: uninstallCodex, antigravity: uninstallAntigravity, opencode: uninstallOpencode, pi: uninstallPi },
|
|
734
|
+
init: { claude: installClaude, codex: installCodex, antigravity: installAntigravity, zcode: installZcode, opencode: installOpencode, pi: installPi },
|
|
735
|
+
uninstall: { claude: uninstallClaude, codex: uninstallCodex, antigravity: uninstallAntigravity, zcode: uninstallZcode, opencode: uninstallOpencode, pi: uninstallPi },
|
|
649
736
|
};
|
|
650
737
|
const globalActions = {
|
|
651
738
|
init: { opencode: installOpencodeGlobal, pi: installPiGlobal },
|
|
652
739
|
uninstall: { opencode: uninstallOpencodeGlobal, pi: uninstallPiGlobal },
|
|
653
740
|
};
|
|
654
741
|
|
|
655
|
-
if (host === 'claude' || host === 'codex' || host === 'antigravity') {
|
|
742
|
+
if (host === 'claude' || host === 'codex' || host === 'antigravity' || host === 'zcode') {
|
|
656
743
|
if (opts.global && (host === 'claude' || host === 'codex')) log('nota: claude/codex já são globais por natureza (registro da CLI) — --global ignorado.');
|
|
657
744
|
actions[cmd][host](opts);
|
|
658
745
|
} else if (opts.global) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { classifyFindings } from '../../packages/skills/atlas-slice-review/scripts/classify_findings.mjs';
|
|
4
|
+
|
|
5
|
+
const valid = () => ({
|
|
6
|
+
severity: 'P1', task_id: 'T01', title: 'Finding', file: 'a.js', line: 1,
|
|
7
|
+
failure_mode: 'falha', evidence: 'evidência', recommendation: 'corrigir', fix_validation: 'testar',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('classificador aceita finding completo e array vazio', () => {
|
|
11
|
+
assert.equal(classifyFindings([valid()])[0].recommendation, 'corrigir');
|
|
12
|
+
assert.deepEqual(classifyFindings([]), []);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('classificador rejeita severidade, linha e campos inválidos', () => {
|
|
16
|
+
assert.throws(() => classifyFindings([{ ...valid(), severity: 'high' }]), /invalid severity/);
|
|
17
|
+
assert.throws(() => classifyFindings([{ ...valid(), line: 0 }]), /invalid line/);
|
|
18
|
+
const missing = valid(); delete missing.recommendation;
|
|
19
|
+
assert.throws(() => classifyFindings([missing]), /recommendation/);
|
|
20
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import {
|
|
8
|
+
closedDecisionIds,
|
|
9
|
+
detectStackProfiles,
|
|
10
|
+
pendingInterviewQuestions,
|
|
11
|
+
persistInterviewRound,
|
|
12
|
+
resolveSprintAuthority,
|
|
13
|
+
validateBacklogUpdate,
|
|
14
|
+
} from '../../packages/skills/_shared/scripts/document_quality.mjs';
|
|
15
|
+
|
|
16
|
+
const ROOT = path.resolve(import.meta.dirname, '../..');
|
|
17
|
+
const CLASSIFIER = path.join(ROOT, 'packages/skills/atlas-slice-review/scripts/classify_findings.mjs');
|
|
18
|
+
|
|
19
|
+
const finding = {
|
|
20
|
+
severity: 'P1', task_id: 'T01', title: 'Falha', file: 'src/a.js', line: 3,
|
|
21
|
+
failure_mode: 'Falha alcançável.', evidence: 'Guard ausente.',
|
|
22
|
+
recommendation: 'Restabelecer guard.', fix_validation: 'Teste negativo.',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
test('review: gate canônico executa diretamente com Node, sem Python no PATH', () => {
|
|
26
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'atlas-review-'));
|
|
27
|
+
const input = path.join(dir, 'findings.json');
|
|
28
|
+
fs.writeFileSync(input, JSON.stringify([finding]));
|
|
29
|
+
const output = execFileSync(process.execPath, [CLASSIFIER, input], { env: { ...process.env, PATH: dir }, encoding: 'utf8' });
|
|
30
|
+
assert.equal(JSON.parse(output)[0].title, 'Falha');
|
|
31
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('perfis: Flutter, Node e Python ativam só regras aplicáveis', () => {
|
|
35
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'atlas-stack-'));
|
|
36
|
+
const fixture = (name, files, commands = []) => {
|
|
37
|
+
const dir = path.join(root, name); fs.mkdirSync(dir);
|
|
38
|
+
for (const [file, content] of Object.entries(files)) fs.writeFileSync(path.join(dir, file), content);
|
|
39
|
+
return detectStackProfiles(dir, commands);
|
|
40
|
+
};
|
|
41
|
+
assert.deepEqual(fixture('node', { 'package.json': '{"scripts":{"test":"node --test"}}' }).boundaries[0], {
|
|
42
|
+
boundary: '.', universal: true, flutter_dart: false, node_typescript: true, python: false, getx: false,
|
|
43
|
+
});
|
|
44
|
+
assert.deepEqual(fixture('flutter', { 'pubspec.yaml': 'name: fixture\ndependencies:\n flutter:\n sdk: flutter\n' }).boundaries[0], {
|
|
45
|
+
boundary: '.', universal: true, flutter_dart: true, node_typescript: false, python: false, getx: false,
|
|
46
|
+
});
|
|
47
|
+
assert.deepEqual(fixture('python', { 'pyproject.toml': '[project]\nname="fixture"\n' }).boundaries[0], {
|
|
48
|
+
boundary: '.', universal: true, flutter_dart: false, node_typescript: false, python: true, getx: false,
|
|
49
|
+
});
|
|
50
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('perfis: monorepo restringe stack por boundary e GetX exige evidência', () => {
|
|
54
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'atlas-monorepo-'));
|
|
55
|
+
fs.mkdirSync(path.join(root, 'packages/node'), { recursive: true });
|
|
56
|
+
fs.mkdirSync(path.join(root, 'apps/flutter'), { recursive: true });
|
|
57
|
+
fs.mkdirSync(path.join(root, 'apps/getx'), { recursive: true });
|
|
58
|
+
fs.writeFileSync(path.join(root, 'packages/node/package.json'), '{}');
|
|
59
|
+
fs.writeFileSync(path.join(root, 'apps/flutter/pubspec.yaml'), 'name: plain\ndependencies:\n flutter:\n sdk: flutter\n');
|
|
60
|
+
fs.writeFileSync(path.join(root, 'apps/getx/pubspec.yaml'), 'name: getx\ndependencies:\n flutter:\n sdk: flutter\n get: ^4.7.0\n');
|
|
61
|
+
const profiles = detectStackProfiles(root, [], ['packages/node', 'apps/flutter', 'apps/getx']);
|
|
62
|
+
assert.deepEqual(profiles.boundaries.map(({ boundary, node_typescript, flutter_dart, getx }) => (
|
|
63
|
+
{ boundary, node_typescript, flutter_dart, getx }
|
|
64
|
+
)), [
|
|
65
|
+
{ boundary: 'packages/node', node_typescript: true, flutter_dart: false, getx: false },
|
|
66
|
+
{ boundary: 'apps/flutter', node_typescript: false, flutter_dart: true, getx: false },
|
|
67
|
+
{ boundary: 'apps/getx', node_typescript: false, flutter_dart: true, getx: true },
|
|
68
|
+
]);
|
|
69
|
+
assert.throws(() => detectStackProfiles(root, [], ['../outside']), /BOUNDARY_OUTSIDE_PROJECT/);
|
|
70
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
function backlog(rows, decisions = '| D1 | Contrato fechado | S02 | Produto | decidido |', changelog = '- 2026-06-22 — baseline.') {
|
|
74
|
+
return `# Backlog\n\n### Decisões bloqueantes\n\n| ID | Decisão | Bloqueia | Dono | Status |\n|---|---|---|---|---|\n${decisions}\n\n## 7. Registro de sprints\n\n| ID | Sprint | Fase-fonte | Objetivo (1 linha) | MoSCoW | Ganho | Esforço | Prioridade | PRD | Depende de | Estado | Gate |\n|---|---|---|---|---|---|---|---|---|---|---|---|\n${rows.join('\n')}\n${changelog ? `\n## Registro de alterações\n\n${changelog}` : ''}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const done = '| S01 | Base | F0 | Fechar base | Must | Alto | Baixo | P0 | `PRD_S01_base.md` | — | done | ✅ |';
|
|
78
|
+
const todo = '| S02 | Próxima | F1 | Entregar próxima | Must | Alto | Médio | P0 | `PRD_S02_proxima.md` | S01 | backlog | — |';
|
|
79
|
+
|
|
80
|
+
test('backlog update: preserva sprint done, decisão fechada e itens não relacionados', () => {
|
|
81
|
+
const before = backlog([done, todo]);
|
|
82
|
+
const after = backlog(
|
|
83
|
+
[done, todo, '| S03 | Extra | F2 | Entregar extra | Should | Médio | Baixo | P1 | `PRD_S03_extra.md` | S01 | backlog | — |'],
|
|
84
|
+
undefined,
|
|
85
|
+
'- 2026-06-22 — baseline.\n- 2026-06-22 — S03 adicionada.',
|
|
86
|
+
);
|
|
87
|
+
assert.deepEqual(validateBacklogUpdate(before, after), { valid: true, errors: [] });
|
|
88
|
+
const destructive = backlog([done.replace('Base', 'Base reescrita'), todo]);
|
|
89
|
+
assert.ok(validateBacklogUpdate(before, destructive).errors.includes('DONE_SPRINT_CHANGED:S01'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('backlog update: bloqueia dependência cíclica', () => {
|
|
93
|
+
const before = backlog([done, todo]);
|
|
94
|
+
const cyclic = backlog([
|
|
95
|
+
'| S01 | Base | F0 | Fechar base | Must | Alto | Baixo | P0 | `PRD_S01_base.md` | S02 | backlog | — |',
|
|
96
|
+
'| S02 | Próxima | F1 | Entregar próxima | Must | Alto | Médio | P0 | `PRD_S02_proxima.md` | S01 | backlog | — |',
|
|
97
|
+
]);
|
|
98
|
+
assert.ok(validateBacklogUpdate(before, cyclic).errors.some((error) => error.startsWith('DEPENDENCY_CYCLE:')));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('backlog update: bloqueia dependência inexistente, mudança não autorizada e histórico reescrito', () => {
|
|
102
|
+
const before = backlog([done, todo]);
|
|
103
|
+
const missing = backlog(
|
|
104
|
+
[done, todo.replace('S01 | backlog', 'S99 | backlog')], undefined,
|
|
105
|
+
'- 2026-06-22 — baseline.\n- 2026-06-22 — dependência alterada.',
|
|
106
|
+
);
|
|
107
|
+
assert.ok(validateBacklogUpdate(before, missing, { authorizedIds: ['S02'] }).errors.includes('DEPENDENCY_NOT_FOUND:S02:S99'));
|
|
108
|
+
const unauthorized = backlog(
|
|
109
|
+
[done, todo.replace('Próxima', 'Reescrita')], undefined,
|
|
110
|
+
'- 2026-06-22 — baseline.\n- 2026-06-22 — S02 alterada.',
|
|
111
|
+
);
|
|
112
|
+
assert.ok(validateBacklogUpdate(before, unauthorized).errors.includes('UNAUTHORIZED_SPRINT_CHANGED:S02'));
|
|
113
|
+
const rewritten = backlog(
|
|
114
|
+
[done, todo, '| S03 | Extra | F2 | Entregar extra | Should | Médio | Baixo | P1 | p | S01 | backlog | — |'],
|
|
115
|
+
undefined,
|
|
116
|
+
'- 2026-06-22 — histórico substituído.',
|
|
117
|
+
);
|
|
118
|
+
assert.ok(validateBacklogUpdate(before, rewritten).errors.includes('CHANGELOG_REWRITTEN'));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('Sprint PRD: múltiplos backlogs conflitantes bloqueiam autoridade', () => {
|
|
122
|
+
assert.throws(() => resolveSprintAuthority({
|
|
123
|
+
sprintId: 'S03', candidates: [
|
|
124
|
+
{ path: '/repo/a/BACKLOG_MESTRE.md', sprints: ['S03'] },
|
|
125
|
+
{ path: '/repo/b/BACKLOG_MESTRE.md', sprints: ['S03'] },
|
|
126
|
+
],
|
|
127
|
+
}), /AMBIGUOUS_BACKLOG_AUTHORITY/);
|
|
128
|
+
assert.equal(resolveSprintAuthority({
|
|
129
|
+
sprintId: 'S03', explicitPath: '/repo/b/BACKLOG_MESTRE.md', candidates: [
|
|
130
|
+
{ path: '/repo/a/BACKLOG_MESTRE.md', sprints: ['S03'] },
|
|
131
|
+
{ path: '/repo/b/BACKLOG_MESTRE.md', sprints: ['S03'] },
|
|
132
|
+
],
|
|
133
|
+
}).path, path.resolve('/repo/b/BACKLOG_MESTRE.md'));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('interview: persiste resposta e não repete decisão fechada', () => {
|
|
137
|
+
const prd = '## 3. Decisões de produto (fechadas)\n\n| ID | Decisão |\n|---|---|\n| D1 | Escolha anterior |\n\n## 4. Fluxos\n';
|
|
138
|
+
const questions = [{ decision_id: 'D1' }, { decision_id: 'D2' }];
|
|
139
|
+
assert.deepEqual(pendingInterviewQuestions(prd, questions), [{ decision_id: 'D2' }]);
|
|
140
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'atlas-interview-'));
|
|
141
|
+
const prdPath = path.join(dir, 'PRD.md');
|
|
142
|
+
fs.writeFileSync(prdPath, prd);
|
|
143
|
+
const updated = persistInterviewRound(prdPath, [{ decision_id: 'D2', value: 'Nova escolha' }], '2026-06-22');
|
|
144
|
+
assert.equal(fs.readFileSync(prdPath, 'utf8'), updated);
|
|
145
|
+
assert.deepEqual([...closedDecisionIds(updated)].sort(), ['D1', 'D2']);
|
|
146
|
+
assert.deepEqual(pendingInterviewQuestions(updated, questions), []);
|
|
147
|
+
assert.match(updated, /entrevista: D2 persistida/);
|
|
148
|
+
const moduleUrl = new URL('../../packages/skills/_shared/scripts/document_quality.mjs', import.meta.url).href;
|
|
149
|
+
const freshProcess = execFileSync(process.execPath, ['--input-type=module', '-e', `
|
|
150
|
+
import fs from 'node:fs';
|
|
151
|
+
import { pendingInterviewQuestions } from ${JSON.stringify(moduleUrl)};
|
|
152
|
+
process.stdout.write(JSON.stringify(pendingInterviewQuestions(fs.readFileSync(process.argv[1], 'utf8'), [{ decision_id: 'D2' }])));
|
|
153
|
+
`, prdPath], { encoding: 'utf8' });
|
|
154
|
+
assert.deepEqual(JSON.parse(freshProcess), []);
|
|
155
|
+
const invalidPath = path.join(dir, 'INVALID.md');
|
|
156
|
+
fs.writeFileSync(invalidPath, '# PRD sem tabela de decisões\n');
|
|
157
|
+
assert.throws(() => persistInterviewRound(invalidPath, [{ decision_id: 'D3', value: 'x' }]), /DECISION_NOT_MATERIALIZED/);
|
|
158
|
+
assert.equal(fs.readFileSync(invalidPath, 'utf8'), '# PRD sem tabela de decisões\n');
|
|
159
|
+
assert.throws(() => persistInterviewRound(path.join(dir, 'missing', 'PRD.md'), [{ decision_id: 'D3', value: 'x' }]), /INTERVIEW_PERSISTENCE_FAILED/);
|
|
160
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
161
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Testes do gate determinístico de findings da atlas-slice-review."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import pathlib
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|
15
|
+
SCRIPT = ROOT / "packages/skills/atlas-slice-review/scripts/classify_findings.py"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def valid_finding() -> dict[str, object]:
|
|
19
|
+
return {
|
|
20
|
+
"severity": "P1",
|
|
21
|
+
"task_id": "T01",
|
|
22
|
+
"title": "Finding confirmado",
|
|
23
|
+
"file": "packages/example.py",
|
|
24
|
+
"line": 12,
|
|
25
|
+
"failure_mode": "Entrada inválida alcança estado inconsistente.",
|
|
26
|
+
"evidence": "Guard ausente na linha indicada.",
|
|
27
|
+
"recommendation": "Restabelecer o guard no proprietário do invariante.",
|
|
28
|
+
"fix_validation": "Teste negativo deve manter o estado anterior.",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_gate(payload: object) -> subprocess.CompletedProcess[str]:
|
|
33
|
+
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", suffix=".json") as handle:
|
|
34
|
+
json.dump(payload, handle)
|
|
35
|
+
handle.flush()
|
|
36
|
+
return subprocess.run(
|
|
37
|
+
[sys.executable, str(SCRIPT), handle.name],
|
|
38
|
+
check=False,
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ClassifyFindingsTest(unittest.TestCase):
|
|
45
|
+
def test_accepts_complete_finding(self) -> None:
|
|
46
|
+
result = run_gate([valid_finding()])
|
|
47
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
48
|
+
normalized = json.loads(result.stdout)
|
|
49
|
+
self.assertEqual(normalized[0]["recommendation"], valid_finding()["recommendation"])
|
|
50
|
+
|
|
51
|
+
def test_accepts_empty_findings(self) -> None:
|
|
52
|
+
result = run_gate([])
|
|
53
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
54
|
+
self.assertEqual(json.loads(result.stdout), [])
|
|
55
|
+
|
|
56
|
+
def test_rejects_missing_recommendation(self) -> None:
|
|
57
|
+
finding = valid_finding()
|
|
58
|
+
del finding["recommendation"]
|
|
59
|
+
result = run_gate([finding])
|
|
60
|
+
self.assertNotEqual(result.returncode, 0)
|
|
61
|
+
self.assertIn("recommendation", result.stderr)
|
|
62
|
+
|
|
63
|
+
def test_rejects_invalid_severity(self) -> None:
|
|
64
|
+
finding = valid_finding()
|
|
65
|
+
finding["severity"] = "high"
|
|
66
|
+
result = run_gate([finding])
|
|
67
|
+
self.assertNotEqual(result.returncode, 0)
|
|
68
|
+
self.assertIn("invalid severity", result.stderr)
|
|
69
|
+
|
|
70
|
+
def test_rejects_invalid_line(self) -> None:
|
|
71
|
+
finding = valid_finding()
|
|
72
|
+
finding["line"] = 0
|
|
73
|
+
result = run_gate([finding])
|
|
74
|
+
self.assertNotEqual(result.returncode, 0)
|
|
75
|
+
self.assertIn("invalid line", result.stderr)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
unittest.main()
|
|
@@ -32,4 +32,8 @@ O orquestrador passa obrigatoriamente `state_path`, findings estruturados, `vali
|
|
|
32
32
|
- Não replanejar
|
|
33
33
|
- Não ampliar escopo
|
|
34
34
|
- Atualizar o `state_path` original em lugar; não trocar o boundary para outro arquivo
|
|
35
|
+
- Consumir IDs/recommendations estruturadas; persistir correlação em `repair_evidence`
|
|
36
|
+
- Preservar `worktree_baseline`, recapturar `worktree_final` e incluir exatamente todo arquivo tocado em `files_changed`; recomputar `head_sha` e `diff_stat`
|
|
37
|
+
- Aceitar somente IDs recebidos; cada arquivo tocado deve estar atribuído a um finding recebido, sem IDs/arquivos extras ou duplicados
|
|
38
|
+
- Devolver `repairs[]` com `finding_id`, arquivos, checks e status
|
|
35
39
|
- Ao terminar, devolver `repair_complete` ou `blocked`
|
|
@@ -27,9 +27,14 @@ Leia o JSON em `.atlas/state/<run_id>/<slice>.json` usando o schema em `packages
|
|
|
27
27
|
2. **Plan path** — `plan_path`, depois leia Section 2 (Invariantes de execução), Section 6 (Contratos técnicos) e Section 8 (Validação e checklist).
|
|
28
28
|
3. **Executed task ids** — `tasks`.
|
|
29
29
|
4. **Boundary refs** — `boundary_refs`.
|
|
30
|
+
5. **Deterministic boundary** — `base_sha`, `head_sha`, `contract_kind` e arrays de evidence/probes.
|
|
31
|
+
6. **Working-tree delta** — confronte `worktree_baseline`, `worktree_final` e árvore atual; dirty preexistente intacto fica fora, mutação posterior entra.
|
|
32
|
+
7. **Repair correlation** — no attempt 2, correlacione findings por ID com `repair_evidence` no mesmo state path.
|
|
30
33
|
|
|
31
34
|
Não aceite contrato inline, diff colado ou listas de tasks coladas como boundary de validação. Se `state_path` estiver ausente, ilegível, ou faltar qualquer campo obrigatório, retorne JSON com `verdict: "fail"` e um finding P1 `Input insuficiente: <missing item>`.
|
|
32
35
|
|
|
36
|
+
Compatibilidade: state legado mínimo sem `contract_kind` só é aceito para `atlas-plan-execute`. `atlas-direct-execute` exige extensão completa e `obligations` não vazio. Compare `base_sha...head_sha`, `HEAD` atual e arquivos evidenciados no working tree com `files_changed`; nunca infira base pelo nome da branch. Divergência gera boundary violation + P1.
|
|
37
|
+
|
|
33
38
|
## State persistence
|
|
34
39
|
|
|
35
40
|
Use `atlas_run_state` como fonte primária de metadados da run e estado de gate. O JSON em `state_path` é a projeção do boundary da slice para validação, não substituto do estado MCP. Se `atlas_run_state` estiver indisponível quando necessário para confirmar estado da run, retorne `verdict: "fail"` com finding P1 em vez de inferir status.
|
|
@@ -88,7 +93,17 @@ Retorne JSON estrito como output final. Não envolva em Markdown e não anteceda
|
|
|
88
93
|
"challenge_response": "string (sha256 hex do challenge.file; null se sem challenge)",
|
|
89
94
|
"verdict": "pass | fail | pass_with_observations",
|
|
90
95
|
"findings": [
|
|
91
|
-
{
|
|
96
|
+
{
|
|
97
|
+
"id": "F-001",
|
|
98
|
+
"severity": "P0|P1|P2|P3",
|
|
99
|
+
"file": "string",
|
|
100
|
+
"line": 1,
|
|
101
|
+
"failure_mode": "string",
|
|
102
|
+
"evidence": "string",
|
|
103
|
+
"recommendation": "string",
|
|
104
|
+
"fix_validation": "string",
|
|
105
|
+
"msg": "string (deprecated; derivado por uma release)"
|
|
106
|
+
}
|
|
92
107
|
],
|
|
93
108
|
"observations": [
|
|
94
109
|
{ "file": "string", "line": 0, "msg": "string" }
|
|
@@ -101,6 +116,8 @@ Retorne JSON estrito como output final. Não envolva em Markdown e não anteceda
|
|
|
101
116
|
|
|
102
117
|
`dispatch_token` deve ser exatamente `validator_recovery.expected_dispatch_token`. `findings`, `observations` e `boundary_violations` são sempre arrays. Use arrays vazios quando não houver itens.
|
|
103
118
|
|
|
119
|
+
IDs são únicos, obrigatórios no formato `F-NNN` e estáveis nos dois ciclos; severity é estritamente `P0|P1|P2|P3`. No segundo, devolva `repaired_finding_ids` e confirme que cada ID alvo possui `repair_evidence` com arquivos, checks e `status: resolved`. O MCP rejeita shape incompleto e `pass`/`pass_with_observations` com P0/P1.
|
|
120
|
+
|
|
104
121
|
## Severity Model
|
|
105
122
|
|
|
106
123
|
Escala alinhada com `atlas-slice-review` (`P0/P1/P2/P3`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.3
|
|
@@ -117,7 +117,7 @@ Status:
|
|
|
117
117
|
↓
|
|
118
118
|
2. Validate PRD + Interview (condicional)
|
|
119
119
|
↓
|
|
120
|
-
3. Execute
|
|
120
|
+
3. Execute (`atlas-direct-execute`, mantendo `phase: plan_execute`)
|
|
121
121
|
↓
|
|
122
122
|
4. Review (se --review)
|
|
123
123
|
↓
|
|
@@ -127,9 +127,11 @@ Status:
|
|
|
127
127
|
### Interview-Only Mode
|
|
128
128
|
|
|
129
129
|
```
|
|
130
|
-
1.
|
|
130
|
+
1. Cria draft mínimo pelo `PRD_TEMPLATE.md` quando a entrada é brainstorm
|
|
131
131
|
↓
|
|
132
|
-
2.
|
|
132
|
+
2. Entrevista `atlas-prd-interview` com `prd_path` válido
|
|
133
|
+
↓
|
|
134
|
+
3. Output (PRD esboço + decisões)
|
|
133
135
|
```
|
|
134
136
|
|
|
135
137
|
## Sequências canônicas
|
|
@@ -140,7 +142,7 @@ Atlas é família única. Cliente (Claude Code, Cursor, Codex App) é apenas o h
|
|
|
140
142
|
|------|-----------|
|
|
141
143
|
| `full` | `atlas-sprint-prd-generator` → `atlas-prd-interview` quando necessário → `atlas-plan-handoff` → `atlas-plan-execute` → `atlas-task-validator` → `atlas-findings-repair` (no `fail`) → `atlas-slice-review` somente com `--review` |
|
|
142
144
|
| `direct` | PRD/spec existente → `atlas-direct-execute` → `atlas-task-validator` → `atlas-findings-repair` (no `fail`) → `atlas-slice-review` somente com `--review` |
|
|
143
|
-
| `interview-only` | `atlas-prd-interview` |
|
|
145
|
+
| `interview-only` | draft PRD mínimo (se brainstorm) → `atlas-prd-interview` |
|
|
144
146
|
|
|
145
147
|
## Validação automática
|
|
146
148
|
|
|
@@ -220,7 +222,7 @@ Veja este README, `packages/mcp-server/README.md` e os SKILL.md `atlas-*` para o
|
|
|
220
222
|
|
|
221
223
|
---
|
|
222
224
|
|
|
223
|
-
**Plugin version:** 0.9.
|
|
225
|
+
**Plugin version:** 0.9.3
|
|
224
226
|
**Author:** Paulo Borini
|
|
225
227
|
**Last updated:** 2026-06-16
|
|
226
228
|
|
|
@@ -34,4 +34,4 @@ Exemplos:
|
|
|
34
34
|
|
|
35
35
|
Não improvise comportamento fora do `SKILL.md`. **Pipeline é fire-and-continue**: uma vez iniciado, avança fase a fase sem pedir permissão entre gates; só para em gate duro `blocked` ou blockage de ambiente real (ver "Princípio de continuação automática"). Nunca invente "Modo Discussão" ou peça "quer que eu gere/continue?". Decisão em aberto não para — dispara entrevista, propaga e segue. Em caso de erro real, siga "Error handling".
|
|
36
36
|
|
|
37
|
-
**Gates duros (v0.3):** o pipeline é orientado a artefato e MCP. Antes de iniciar, rode a **Fase 0 (Pré-flight)** com `atlas_ping` e `atlas_preflight`; use ids `atlas-*`; garanta que cada sub-agent carregue o `SKILL.md` real antes de agir. Se MCP não responder, resultado exigido estiver ausente ou status for bloqueante, **aborte; nunca use fallback narrativo**. Respeite os Gates G1–G11 + TC da SKILL: `atlas_verify_artifact` antes de avançar (G1); em `full`, nenhum código antes de `PLAN_*.md` validado (G2); skills invocadas de verdade (G3); validador frio como sub-agent separado (G4); `atlas_scan_prd` determinístico e logado (G5); status verificado contra disco e MCP (G6);
|
|
37
|
+
**Gates duros (v0.3):** o pipeline é orientado a artefato e MCP. Antes de iniciar, rode a **Fase 0 (Pré-flight)** com `atlas_ping` e `atlas_preflight`; use ids `atlas-*`; garanta que cada sub-agent carregue o `SKILL.md` real antes de agir. Se MCP não responder, resultado exigido estiver ausente ou status for bloqueante, **aborte; nunca use fallback narrativo**. Respeite os Gates G1–G11 + TC da SKILL: `atlas_verify_artifact` antes de avançar (G1); em `full`, nenhum código antes de `PLAN_*.md` validado (G2); skills invocadas de verdade (G3); validador frio como sub-agent separado (G4); `atlas_scan_prd` determinístico e logado (G5); status verificado contra disco e MCP (G6); execução/review como sub-agents reais com `atlas_lock_dispatch`, enquanto PRD/entrevista/plano são autoria documental no pai (G7/G8); orquestrador de mãos atadas e dispatch blocking (G9); família única atlas-* via `atlas_preflight` (G10); em `full`, `atlas_assert_after_plan` exige `atlas-plan-execute` após plano (G11); `direct` usa `atlas-direct-execute`; ambos mantêm `phase: plan_execute`; PRD/PLAN exigem `atlas_verify_template_conformance` passed com `pending_count: 0` (TC). Em `interview-only brainstorm`, crie draft mínimo pelo template antes de invocar `atlas-prd-interview` com `prd_path` válido.
|