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.
Files changed (169) hide show
  1. package/README.md +5 -2
  2. package/VERSION +1 -1
  3. package/build/bump-version.mjs +6 -21
  4. package/build/cli/atlas-init.mjs +92 -5
  5. package/build/tests/classify-findings.test.mjs +20 -0
  6. package/build/tests/etapa3.test.mjs +161 -0
  7. package/build/tests/test_classify_findings.py +79 -0
  8. package/hosts/opencode/.opencode/agents/atlas-findings-repair.md +4 -0
  9. package/hosts/opencode/.opencode/agents/atlas-task-validator.md +18 -1
  10. package/hosts/opencode/.opencode/atlas/VERSION +1 -1
  11. package/hosts/opencode/.opencode/atlas/orchestrator/README.md +7 -5
  12. package/hosts/opencode/.opencode/atlas/orchestrator/commands/workflow.md +1 -1
  13. package/hosts/opencode/.opencode/atlas/orchestrator/references/host-adapters.md +13 -12
  14. package/hosts/opencode/.opencode/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  15. package/hosts/opencode/.opencode/atlas/packages/mcp-server/README.md +1 -1
  16. package/hosts/opencode/.opencode/atlas/packages/mcp-server/package.json +1 -1
  17. package/hosts/opencode/.opencode/atlas/packages/mcp-server/server.js +514 -20
  18. package/hosts/opencode/.opencode/atlas/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
  19. package/hosts/opencode/.opencode/atlas/packages/templates/PRD_TEMPLATE.md +2 -1
  20. package/hosts/opencode/.opencode/atlas/packages/templates/STATE_FILE_SCHEMA.md +25 -1
  21. package/hosts/opencode/.opencode/skills/_shared/references/stack-profiles.md +36 -0
  22. package/hosts/opencode/.opencode/skills/_shared/scripts/document_quality.mjs +252 -0
  23. package/hosts/opencode/.opencode/skills/atlas-backlog-generator/SKILL.md +7 -2
  24. package/hosts/opencode/.opencode/skills/atlas-direct-execute/SKILL.md +6 -2
  25. package/hosts/opencode/.opencode/skills/atlas-findings-repair/SKILL.md +11 -1
  26. package/hosts/opencode/.opencode/skills/atlas-plan-execute/SKILL.md +16 -2
  27. package/hosts/opencode/.opencode/skills/atlas-plan-handoff/SKILL.md +6 -4
  28. package/hosts/opencode/.opencode/skills/atlas-prd-interview/SKILL.md +7 -2
  29. package/hosts/opencode/.opencode/skills/atlas-slice-review/SKILL.md +37 -2
  30. package/hosts/opencode/.opencode/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
  31. package/hosts/opencode/.opencode/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
  32. package/hosts/opencode/.opencode/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
  33. package/hosts/opencode/.opencode/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
  34. package/hosts/opencode/.opencode/skills/atlas-task-validator/SKILL.md +29 -14
  35. package/hosts/opencode/.opencode/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  36. package/hosts/pi/.pi/agents/atlas-direct-execute.md +6 -2
  37. package/hosts/pi/.pi/agents/atlas-findings-repair.md +15 -1
  38. package/hosts/pi/.pi/agents/atlas-plan-execute.md +16 -2
  39. package/hosts/pi/.pi/agents/atlas-slice-review.md +37 -2
  40. package/hosts/pi/.pi/agents/atlas-task-validator.md +18 -1
  41. package/hosts/pi/atlas/VERSION +1 -1
  42. package/hosts/pi/atlas/orchestrator/README.md +7 -5
  43. package/hosts/pi/atlas/orchestrator/commands/workflow.md +1 -1
  44. package/hosts/pi/atlas/orchestrator/references/host-adapters.md +13 -12
  45. package/hosts/pi/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  46. package/hosts/pi/atlas/packages/mcp-server/README.md +1 -1
  47. package/hosts/pi/atlas/packages/mcp-server/package.json +1 -1
  48. package/hosts/pi/atlas/packages/mcp-server/server.js +514 -20
  49. package/hosts/pi/atlas/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
  50. package/hosts/pi/atlas/packages/templates/PRD_TEMPLATE.md +2 -1
  51. package/hosts/pi/atlas/packages/templates/STATE_FILE_SCHEMA.md +25 -1
  52. package/hosts/pi/skills/_shared/references/stack-profiles.md +36 -0
  53. package/hosts/pi/skills/_shared/scripts/document_quality.mjs +252 -0
  54. package/hosts/pi/skills/atlas-backlog-generator/SKILL.md +7 -2
  55. package/hosts/pi/skills/atlas-direct-execute/SKILL.md +6 -2
  56. package/hosts/pi/skills/atlas-findings-repair/SKILL.md +11 -1
  57. package/hosts/pi/skills/atlas-plan-execute/SKILL.md +16 -2
  58. package/hosts/pi/skills/atlas-plan-handoff/SKILL.md +6 -4
  59. package/hosts/pi/skills/atlas-prd-interview/SKILL.md +7 -2
  60. package/hosts/pi/skills/atlas-slice-review/SKILL.md +37 -2
  61. package/hosts/pi/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
  62. package/hosts/pi/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
  63. package/hosts/pi/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
  64. package/hosts/pi/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
  65. package/hosts/pi/skills/atlas-task-validator/SKILL.md +29 -14
  66. package/hosts/pi/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  67. package/hosts/zcode/.zcode-plugin/plugin.json +27 -0
  68. package/hosts/zcode/agents/atlas-direct-execute.md +31 -0
  69. package/hosts/zcode/agents/atlas-findings-repair.md +39 -0
  70. package/hosts/zcode/agents/atlas-plan-execute.md +33 -0
  71. package/hosts/zcode/agents/atlas-slice-review.md +27 -0
  72. package/hosts/zcode/agents/atlas-task-validator.md +138 -0
  73. package/hosts/zcode/packages/mcp-server/README.md +29 -0
  74. package/hosts/zcode/packages/mcp-server/VERSION +1 -0
  75. package/hosts/zcode/packages/mcp-server/package.json +15 -0
  76. package/hosts/zcode/packages/mcp-server/server.js +3897 -0
  77. package/hosts/zcode/packages/orchestrator/README.md +270 -0
  78. package/hosts/zcode/packages/orchestrator/commands/workflow.md +37 -0
  79. package/hosts/zcode/packages/orchestrator/defaults/paths.md +21 -0
  80. package/hosts/zcode/packages/orchestrator/references/host-adapters.md +106 -0
  81. package/hosts/zcode/packages/orchestrator/references/qa_s13_matrix.md +141 -0
  82. package/hosts/zcode/packages/orchestrator/references/subagent_dispatch.md +42 -0
  83. package/hosts/zcode/packages/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +391 -0
  84. package/hosts/zcode/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +855 -0
  85. package/hosts/zcode/packages/templates/BOUNDARY_PRD_PLAN.md +93 -0
  86. package/hosts/zcode/packages/templates/PERGUNTAS_EM_ABERTO_TEMPLATE.md +139 -0
  87. package/hosts/zcode/packages/templates/PLAN_TEMPLATE.md +146 -0
  88. package/hosts/zcode/packages/templates/PRD_TEMPLATE.md +150 -0
  89. package/hosts/zcode/packages/templates/STATE_FILE_SCHEMA.md +56 -0
  90. package/hosts/zcode/skills/_shared/references/stack-profiles.md +36 -0
  91. package/hosts/zcode/skills/_shared/scripts/document_quality.mjs +252 -0
  92. package/hosts/zcode/skills/atlas-backlog-generator/SKILL.md +93 -0
  93. package/hosts/zcode/skills/atlas-backlog-generator/agents/openai.yaml +4 -0
  94. package/hosts/zcode/skills/atlas-direct-execute/SKILL.md +221 -0
  95. package/hosts/zcode/skills/atlas-direct-execute/agents/openai.yaml +7 -0
  96. package/hosts/zcode/skills/atlas-findings-repair/SKILL.md +158 -0
  97. package/hosts/zcode/skills/atlas-findings-repair/agents/openai.yaml +7 -0
  98. package/hosts/zcode/skills/atlas-plan-execute/SKILL.md +175 -0
  99. package/hosts/zcode/skills/atlas-plan-execute/agents/openai.yaml +7 -0
  100. package/hosts/zcode/skills/atlas-plan-execute/references/plan-contract.md +88 -0
  101. package/hosts/zcode/skills/atlas-plan-execute/references/quality-gates.md +60 -0
  102. package/hosts/zcode/skills/atlas-plan-execute/scripts/check_budget_state.py +96 -0
  103. package/hosts/zcode/skills/atlas-plan-execute/scripts/extract_plan_contract.py +191 -0
  104. package/hosts/zcode/skills/atlas-plan-execute/scripts/validate_gate_result.py +56 -0
  105. package/hosts/zcode/skills/atlas-plan-handoff/SKILL.md +183 -0
  106. package/hosts/zcode/skills/atlas-plan-handoff/agents/openai.yaml +7 -0
  107. package/hosts/zcode/skills/atlas-prd-interview/SKILL.md +82 -0
  108. package/hosts/zcode/skills/atlas-prd-interview/agents/openai.yaml +7 -0
  109. package/hosts/zcode/skills/atlas-slice-review/SKILL.md +156 -0
  110. package/hosts/zcode/skills/atlas-slice-review/agents/openai.yaml +4 -0
  111. package/hosts/zcode/skills/atlas-slice-review/references/review-contract.md +58 -0
  112. package/hosts/zcode/skills/atlas-slice-review/references/scenario-lenses.md +57 -0
  113. package/hosts/zcode/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
  114. package/hosts/zcode/skills/atlas-slice-review/scripts/classify_findings.py +24 -0
  115. package/hosts/zcode/skills/atlas-slice-review/scripts/extract_review_slice.py +158 -0
  116. package/hosts/zcode/skills/atlas-sprint-prd-generator/SKILL.md +77 -0
  117. package/hosts/zcode/skills/atlas-sprint-prd-generator/agents/openai.yaml +7 -0
  118. package/hosts/zcode/skills/atlas-task-validator/SKILL.md +173 -0
  119. package/hosts/zcode/skills/atlas-task-validator/agents/openai.yaml +7 -0
  120. package/hosts/zcode/skills/atlas-workflow-orchestrator/SKILL.md +391 -0
  121. package/package.json +1 -1
  122. package/plugins/atlas-workflow-orchestrator/.codex/agents/atlas-findings-repair.toml +1 -1
  123. package/plugins/atlas-workflow-orchestrator/.codex/agents/atlas-task-validator.toml +1 -1
  124. package/plugins/atlas-workflow-orchestrator/.codex-plugin/plugin.json +1 -1
  125. package/plugins/atlas-workflow-orchestrator/VERSION +1 -1
  126. package/plugins/atlas-workflow-orchestrator/agents/atlas-findings-repair.md +4 -0
  127. package/plugins/atlas-workflow-orchestrator/agents/atlas-task-validator.md +18 -1
  128. package/plugins/atlas-workflow-orchestrator/orchestrator/README.md +7 -5
  129. package/plugins/atlas-workflow-orchestrator/orchestrator/commands/workflow.md +1 -1
  130. package/plugins/atlas-workflow-orchestrator/orchestrator/references/host-adapters.md +13 -12
  131. package/plugins/atlas-workflow-orchestrator/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  132. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/README.md +1 -1
  133. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/package.json +1 -1
  134. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/server.js +514 -20
  135. package/plugins/atlas-workflow-orchestrator/packages/skills/_shared/references/stack-profiles.md +36 -0
  136. package/plugins/atlas-workflow-orchestrator/packages/skills/_shared/scripts/document_quality.mjs +252 -0
  137. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-backlog-generator/SKILL.md +7 -2
  138. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-direct-execute/SKILL.md +6 -2
  139. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-findings-repair/SKILL.md +11 -1
  140. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-execute/SKILL.md +16 -2
  141. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-handoff/SKILL.md +6 -4
  142. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-prd-interview/SKILL.md +7 -2
  143. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/SKILL.md +37 -2
  144. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
  145. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
  146. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
  147. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
  148. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-task-validator/SKILL.md +29 -14
  149. package/plugins/atlas-workflow-orchestrator/packages/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
  150. package/plugins/atlas-workflow-orchestrator/packages/templates/PRD_TEMPLATE.md +2 -1
  151. package/plugins/atlas-workflow-orchestrator/packages/templates/STATE_FILE_SCHEMA.md +25 -1
  152. package/plugins/atlas-workflow-orchestrator/skills/_shared/references/stack-profiles.md +36 -0
  153. package/plugins/atlas-workflow-orchestrator/skills/_shared/scripts/document_quality.mjs +252 -0
  154. package/plugins/atlas-workflow-orchestrator/skills/atlas-backlog-generator/SKILL.md +7 -2
  155. package/plugins/atlas-workflow-orchestrator/skills/atlas-direct-execute/SKILL.md +6 -2
  156. package/plugins/atlas-workflow-orchestrator/skills/atlas-findings-repair/SKILL.md +11 -1
  157. package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-execute/SKILL.md +16 -2
  158. package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-handoff/SKILL.md +6 -4
  159. package/plugins/atlas-workflow-orchestrator/skills/atlas-prd-interview/SKILL.md +7 -2
  160. package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/SKILL.md +37 -2
  161. package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/references/scenario-lenses.md +8 -0
  162. package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/scripts/classify_findings.mjs +60 -0
  163. package/plugins/atlas-workflow-orchestrator/skills/atlas-slice-review/scripts/classify_findings.py +9 -41
  164. package/plugins/atlas-workflow-orchestrator/skills/atlas-sprint-prd-generator/SKILL.md +7 -4
  165. package/plugins/atlas-workflow-orchestrator/skills/atlas-task-validator/SKILL.md +29 -14
  166. package/plugins/atlas-workflow-orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +24 -17
  167. package/plugins/atlas-workflow-orchestrator/templates/BACKLOG_MESTRE_TEMPLATE.md +14 -3
  168. package/plugins/atlas-workflow-orchestrator/templates/PRD_TEMPLATE.md +2 -1
  169. 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.1 — pipeline determinístico (PRD → plano → execução → validação) com skills `atlas-*`, templates e MCP. Um pacote, seis hosts: **Claude Code**, **Cursor**, **Codex App**, **Antigravity (Gemini)**, **OpenCode** e **Pi CLI**.
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.1`) · **Repo:** https://github.com/pauloborini/atlas-workflow
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
1
+ 0.9.3
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // Bump determinístico de versão. Sincroniza os arquivos com versão concreta,
3
- // regenera bundles/catálogos e roda check-consistency. NÃO cria tag nem commita
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 (incluir tag):
112
- git push origin main v${next}
113
- => CI Release detecta tag existente e publica sem re-criar.`);
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}.`);
@@ -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
- { "severity": "P0|P1|P2|P3", "file": "string", "line": 0, "msg": "string" }
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
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. Entrevista direta (sem PRD anterior)
130
+ 1. Cria draft mínimo pelo `PRD_TEMPLATE.md` quando a entrada é brainstorm
131
131
 
132
- 2. Output (PRD esboço + decisões)
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.1
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); plano/execução/review como sub-agents reais com `atlas_lock_dispatch` (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 `plan_execute` após plano (G11); PRD/PLAN exigem `atlas_verify_template_conformance` passed com `pending_count: 0` (TC).
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.