atlas-workflow 0.8.2 → 0.8.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 (36) hide show
  1. package/README.md +2 -2
  2. package/VERSION +1 -1
  3. package/build/bump-version.mjs +74 -0
  4. package/hosts/opencode/.opencode/atlas/VERSION +1 -1
  5. package/hosts/opencode/.opencode/atlas/orchestrator/README.md +9 -2
  6. package/hosts/opencode/.opencode/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
  7. package/hosts/opencode/.opencode/atlas/packages/mcp-server/README.md +3 -2
  8. package/hosts/opencode/.opencode/atlas/packages/mcp-server/package.json +1 -1
  9. package/hosts/opencode/.opencode/atlas/packages/mcp-server/server.js +297 -4
  10. package/hosts/opencode/.opencode/skills/atlas-direct-execute/SKILL.md +31 -0
  11. package/hosts/opencode/.opencode/skills/atlas-plan-execute/SKILL.md +32 -0
  12. package/hosts/opencode/.opencode/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
  13. package/hosts/pi/.pi/agents/atlas-direct-execute.md +31 -0
  14. package/hosts/pi/.pi/agents/atlas-plan-execute.md +32 -0
  15. package/hosts/pi/atlas/VERSION +1 -1
  16. package/hosts/pi/atlas/orchestrator/README.md +9 -2
  17. package/hosts/pi/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
  18. package/hosts/pi/atlas/packages/mcp-server/README.md +3 -2
  19. package/hosts/pi/atlas/packages/mcp-server/package.json +1 -1
  20. package/hosts/pi/atlas/packages/mcp-server/server.js +297 -4
  21. package/hosts/pi/skills/atlas-direct-execute/SKILL.md +31 -0
  22. package/hosts/pi/skills/atlas-plan-execute/SKILL.md +32 -0
  23. package/hosts/pi/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
  24. package/package.json +1 -1
  25. package/plugins/atlas-workflow-orchestrator/.codex-plugin/plugin.json +1 -1
  26. package/plugins/atlas-workflow-orchestrator/VERSION +1 -1
  27. package/plugins/atlas-workflow-orchestrator/orchestrator/README.md +9 -2
  28. package/plugins/atlas-workflow-orchestrator/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
  29. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/README.md +3 -2
  30. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/package.json +1 -1
  31. package/plugins/atlas-workflow-orchestrator/packages/mcp-server/server.js +297 -4
  32. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-direct-execute/SKILL.md +31 -0
  33. package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-execute/SKILL.md +32 -0
  34. package/plugins/atlas-workflow-orchestrator/skills/atlas-direct-execute/SKILL.md +31 -0
  35. package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-execute/SKILL.md +32 -0
  36. package/plugins/atlas-workflow-orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +4 -3
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Atlas Workflow
2
2
 
3
- Plugin **Atlas Workflow Orchestrator** v0.8.2 — pipeline determinístico (PRD → plano → execução → validação) com skills `atlas-*`, templates e MCP. Um pacote, cinco hosts: **Claude Code**, **Cursor**, **Codex App**, **OpenCode** e **Pi CLI**.
3
+ Plugin **Atlas Workflow Orchestrator** v0.8.3 — pipeline determinístico (PRD → plano → execução → validação) com skills `atlas-*`, templates e MCP. Um pacote, cinco hosts: **Claude Code**, **Cursor**, **Codex App**, **OpenCode** e **Pi CLI**.
4
4
 
5
- **Versão:** [`VERSION`](VERSION) (`0.8.2`) · **Repo:** https://github.com/pauloborini/atlas-workflow
5
+ **Versão:** [`VERSION`](VERSION) (`0.8.3`) · **Repo:** https://github.com/pauloborini/atlas-workflow
6
6
 
7
7
  ## Hosts
8
8
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.2
1
+ 0.8.3
@@ -0,0 +1,74 @@
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.
5
+ //
6
+ // Uso: node build/bump-version.mjs <nova-versao> (ex.: 0.8.3)
7
+ //
8
+ // Não toca CHANGELOG.md nem PATCH_PROCEDURE.md (prosa/exemplos históricos), nem
9
+ // a seção "Novidades vX" do orchestrator README (changelog embutido). Esses são
10
+ // passos narrativos manuais, listados no final.
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { execFileSync } from 'node:child_process';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
17
+ const next = (process.argv[2] || '').trim();
18
+
19
+ function die(msg) { console.error(`bump-version: ${msg}`); process.exit(1); }
20
+
21
+ if (!/^\d+\.\d+\.\d+$/.test(next)) {
22
+ die(`versão inválida "${process.argv[2] || ''}" — use SemVer X.Y.Z (ex.: 0.8.3)`);
23
+ }
24
+
25
+ const versionPath = path.join(ROOT, 'VERSION');
26
+ const current = fs.readFileSync(versionPath, 'utf8').trim();
27
+ if (current === next) die(`VERSION já é ${next} — nada a bumpar`);
28
+
29
+ const esc = current.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+
31
+ // [arquivo, transform]. transform recebe o conteúdo e devolve o novo.
32
+ const edits = [
33
+ ['VERSION', () => `${next}\n`],
34
+ ['package.json', (t) => replaceOnce(t, `"version": "${current}"`, `"version": "${next}"`, 'package.json')],
35
+ ['packages/mcp-server/package.json', (t) => replaceOnce(t, `"version": "${current}"`, `"version": "${next}"`, 'mcp-server/package.json')],
36
+ ['.claude-plugin/plugin.json', (t) => replaceOnce(t, `"version": "${current}"`, `"version": "${next}"`, '.claude-plugin/plugin.json')],
37
+ // Prosa de versão atual — replace-all é seguro (sem changelog embutido).
38
+ ['README.md', (t) => replaceAll(t, esc, next, 'README.md')],
39
+ ['COMMANDS.md', (t) => replaceAll(t, esc, next, 'COMMANDS.md')],
40
+ ['packages/mcp-server/README.md', (t) => replaceAll(t, esc, next, 'mcp-server/README.md')],
41
+ // Orchestrator README tem "Novidades vX" (histórico) — só a linha Plugin version.
42
+ ['packages/orchestrator/README.md', (t) => replaceOnce(t, `**Plugin version:** ${current}`, `**Plugin version:** ${next}`, 'orchestrator/README.md (Plugin version)')],
43
+ ];
44
+
45
+ function replaceOnce(text, from, to, label) {
46
+ if (!text.includes(from)) die(`âncora não encontrada em ${label}: "${from}"`);
47
+ return text.replace(from, to);
48
+ }
49
+ function replaceAll(text, escFrom, to, label) {
50
+ const re = new RegExp(escFrom, 'g');
51
+ if (!re.test(text)) die(`versão ${current} não encontrada em ${label}`);
52
+ return text.replace(new RegExp(escFrom, 'g'), to);
53
+ }
54
+
55
+ for (const [rel, fn] of edits) {
56
+ const p = path.join(ROOT, rel);
57
+ const before = fs.readFileSync(p, 'utf8');
58
+ fs.writeFileSync(p, fn(before));
59
+ console.log(` bump ${rel} ${current} -> ${next}`);
60
+ }
61
+
62
+ console.log(`\nRegenerando bundles/catálogos (build-plugins.sh)…`);
63
+ execFileSync('bash', [path.join(ROOT, 'build', 'build-plugins.sh')], { cwd: ROOT, stdio: 'inherit' });
64
+
65
+ console.log(`Rodando check-consistency…`);
66
+ execFileSync('node', [path.join(ROOT, 'build', 'check-consistency.mjs')], { cwd: ROOT, stdio: 'inherit' });
67
+
68
+ console.log(`\nbump-version: ${current} -> ${next} OK.
69
+
70
+ Passos narrativos manuais (não automatizáveis):
71
+ 1. CHANGELOG.md — adicionar entrada "## ${next} - YYYY-MM-DD".
72
+ 2. packages/orchestrator/README.md — adicionar seção "### Novidades v${next}" e "Last updated".
73
+ 3. Revisar 'git status', commitar e dar push na main.
74
+ => VERSION novo na main dispara Release (tag + npm + GitHub release) sozinho.`);
@@ -1 +1 @@
1
- 0.8.2
1
+ 0.8.3
@@ -220,9 +220,16 @@ Veja este README, `packages/mcp-server/README.md` e os SKILL.md `atlas-*` para o
220
220
 
221
221
  ---
222
222
 
223
- **Plugin version:** 0.8.2
223
+ **Plugin version:** 0.8.3
224
224
  **Author:** Paulo Borini
225
- **Last updated:** 2026-06-15
225
+ **Last updated:** 2026-06-16
226
+
227
+ ### Novidades v0.8.3 — liveness do executor (Gate G12)
228
+
229
+ - `plan_execute` agora tem liveness explícito: `atlas_lock_dispatch(start)` cria deadline de bootstrap e o executor precisa emitir checkpoints materiais.
230
+ - `atlas-plan-execute` deve reportar `executor_started`, `skill_loaded`, `plan_loaded`, `handoff_accepted`, `task_started`, `first_write` e `state_path_created` conforme avança.
231
+ - Se o sub-agent não retornar/progredir, o orquestrador consulta `atlas_lock_dispatch(status)`; bootstrap vencido vira `executor_bootstrap_timeout`, checkpoint antigo sem avanço vira `executor_progress_timeout`; ambos persistem `stalled`, liberam retry e não podem ser tratados como execução em andamento.
232
+ - `atlas_lock_validator(start)` só abre o validator depois de `state_path_created` para o mesmo `state_path`; checkpoint final sem arquivo legível é bloqueado.
226
233
 
227
234
  ### Novidades v0.8.2 — release/npm e procedimento de bump
228
235
 
@@ -177,6 +177,7 @@ Regras inegociáveis. Violação = parar, não contornar.
177
177
  | TC | **Conformidade de template via MCP.** PRD e PLAN só avançam como artefatos documentais se `atlas_verify_template_conformance` retornar `passed` e `pending_count: 0`. Pendência bloqueia com `next_action`. | PRD + plano |
178
178
  | G6 | **Status verificado, não auto-reportado.** O ✅ de cada item no output só pode ser marcado após confirmar o artefato em disco. Faltou artefato exigido pelo modo → status final `incomplete`, nunca `completed`. | output |
179
179
  | G7 | **Execução de código roda SEMPRE como sub-agent despachado (verbo nativo do host, lido de `atlas_capabilities`), nunca no contexto do orquestrador.** A **autoria** do `PLAN_*.md` pode ser feita pelo orquestrador no fio principal **enquanto o plano não foi validado** (autoria documental, PRD D10) — mas o plano só vira confiável após `atlas_verify_artifact` + TC `passed`. A **execução do plano** (`plan_execute`) e qualquer mutação de código vão obrigatoriamente a sub-agent. Antes de iniciar/concluir fase de execução, usar `atlas_lock_dispatch`; fase fora de ordem ou paralela bloqueia. Depois do plano validado, o orquestrador não edita mais o plano (mãos atadas fortes). | plano + execução |
180
+ | G12 | **Executor vivo precisa provar progresso.** Ao iniciar `plan_execute`, `atlas_lock_dispatch(start)` cria liveness de bootstrap/progresso. O executor precisa emitir `atlas_lock_dispatch(checkpoint, phase=plan_execute, event=...)` cedo, começando por `executor_started`/`skill_loaded`, depois `plan_loaded`, `handoff_accepted`, `task_started`, `first_write` e `state_path_created` conforme avança. `state_path_created` exige `state_path` legível/parseável, e `atlas_lock_validator(start)` só abre validator se o último checkpoint for `state_path_created` para exatamente o mesmo `state_path`. Se o sub-agent não retornar, travar, ficar sem primeiro checkpoint, ou ficar com checkpoint antigo sem avanço, o orquestrador chama `atlas_lock_dispatch(action=status, phase=plan_execute)`: `executor_bootstrap_timeout`/`executor_progress_timeout` viram `stalled`, o lock é liberado para `retry_plan_execute`, e a execução não pode ser declarada completa. Sem checkpoint/progresso não há "em andamento" confiável. | execução |
180
181
  | G8 | **Ordem fixa de validação: `task-validator` ANTES, `slice-review` POR ÚLTIMO. Nunca em paralelo.** Conclusão de `plan_execute` usa `atlas_lock_dispatch` com `validator_status: passed`; review só inicia após execução concluída. | validação + review |
181
182
  | PREREQ | **Pré-requisitos de determinismo (hard-fail, DEC-004).** `atlas_preflight` verifica, **antes de tudo**, se o host tem subagente + MCP (essenciais). Ausente (ex.: pi sem `pi-mcp-adapter`/`pi-subagents`, host MCP-only sem subagente) → aborta em `ready` com `missing_prerequisites`/`next_action`. Sem degradação, sem validator inline, qualquer tamanho. `todo` não-essencial segue sem mirror. | roteamento |
182
183
  | DEP | **Dependência de backlog não satisfeita = hard-fail determinístico.** Se o input é `backlog-item` e o item declara `Dependências` (ex.: S40 dep S39) cujo status, lido no mesmo backlog/registro de onde o item veio, **não** é `done`, abortar em `ready` com `unmet_dependencies`, causa e `next_action` (executar a dependência primeiro). Sem improviso e sem pergunta: ou a dep está `done` e segue, ou bloqueia com causa. Não confundir com decisão em aberto (que não bloqueia). | roteamento (backlog-item) |
@@ -199,7 +200,7 @@ Artefatos esperados (em ordem): `PRD_*.md` → (`PRD_*.md` atualizado) → `PLAN
199
200
  5. **Plan** — `atlas_lock_dispatch(action=start, phase=plan_handoff)`, carregar/invocar `plan_handoff` no fio principal para redigir `PLAN_*.md`, depois chamar `atlas_verify_artifact` e `atlas_verify_template_conformance(artifact_type=plan)`. Concluir a fase com `atlas_lock_dispatch(action=complete, phase=plan_handoff)`. **Nenhuma linha de código pode ter sido escrita até aqui.**
200
201
  - **G11:** se `PLAN_*.md` foi validado, chamar `atlas_assert_after_plan`. Se a próxima ação não for `dispatch_plan_execute_blocking`, abortar.
201
202
  6. **Validate plan** — se há gaps → dispara entrevista, propaga e continua (ver "Decisão em aberto ≠ parada"). Não para pra pedir permissão.
202
- 7. **Execute** — `atlas_lock_dispatch(action=start, phase=plan_execute)`, despachar `plan_execute` como sub-agent lendo o `PLAN_*.md`. O executor retorna `validator_handoff_required` com `state_path`; o orquestrador abre um slot via `atlas_lock_validator(action=start)`, despacha **um** `task_validator`, exige no output o `dispatch_token` do slot e fecha com `validator_run_id` + `dispatch_token`. Se o veredito for `fail`, chama `repair_start`, despacha `atlas-findings-repair` com `{state_path, findings, validator_attempt, repair_run_id, repair_budget: 1}`, exige atualização do mesmo `state_path`, fecha com `repair_run_id` e só então roda o **2º e último** validator. Ao obter `passed` ou `passed_with_observations`, conclui `plan_execute` com o terminal. Status diferente bloqueia review e output completed.
203
+ 7. **Execute** — `atlas_lock_dispatch(action=start, phase=plan_execute)`, despachar `plan_execute` como sub-agent lendo o `PLAN_*.md`. O executor precisa emitir checkpoints G12; se o dispatch não retornar ou não produzir primeiro checkpoint/progresso, chamar `atlas_lock_dispatch(action=status, phase=plan_execute)` e tratar `executor_bootstrap_timeout`/`executor_progress_timeout` como `stalled`/retry, nunca como execução em andamento. O executor retorna `validator_handoff_required` com `state_path`; antes de abrir validator, o MCP exige checkpoint `state_path_created` para esse mesmo `state_path`. O orquestrador abre um slot via `atlas_lock_validator(action=start)`, despacha **um** `task_validator`, exige no output o `dispatch_token` do slot e fecha com `validator_run_id` + `dispatch_token`. Se o veredito for `fail`, chama `repair_start`, despacha `atlas-findings-repair` com `{state_path, findings, validator_attempt, repair_run_id, repair_budget: 1}`, exige atualização do mesmo `state_path`, fecha com `repair_run_id` e só então roda o **2º e último** validator. Ao obter `passed` ou `passed_with_observations`, conclui `plan_execute` com o terminal. Status diferente bloqueia review e output completed.
203
204
  8. **Review (condicional)** — somente após execução concluída e se `--review` → `atlas_lock_dispatch(action=start, phase=slice_review)`, despachar `slice_review`, depois `atlas_lock_dispatch(action=complete, phase=slice_review)`.
204
205
  9. **Output** — ledger verificado com fonte MCP por gate/fase (ver "Output") + próximos passos.
205
206
 
@@ -209,7 +210,7 @@ Artefatos esperados: `PRD_*.md` → (atualizado) → diff de código → relató
209
210
 
210
211
  1. Parse / Generate PRD (se necessário) + `atlas_verify_artifact`.
211
212
  2. Validate PRD → `atlas_scan_prd` + `atlas_verify_template_conformance`; entrevista condicional reexecuta os gates.
212
- 3. **Execute** — `atlas_lock_dispatch(action=start, phase=plan_execute)`; despachar `plan_execute` como sub-agent blocking a partir do PRD. O executor retorna `validator_handoff_required` com `state_path`; o orquestrador abre o slot, despacha o validator e fecha usando `validator_run_id` + `dispatch_token`. Em `fail`, abre repair, passa `{state_path, findings, validator_attempt, repair_run_id, repair_budget: 1}` ao `atlas-findings-repair`, mantém o mesmo `state_path`, fecha o repair e roda o **2º e último** validator. `passed` e `passed_with_observations` são terminais aprovados.
213
+ 3. **Execute** — `atlas_lock_dispatch(action=start, phase=plan_execute)`; despachar `plan_execute` como sub-agent blocking a partir do PRD. Exigir checkpoints G12; sem retorno/progresso, chamar `atlas_lock_dispatch(action=status, phase=plan_execute)` e bloquear/retry em `executor_bootstrap_timeout`/`executor_progress_timeout`. O executor retorna `validator_handoff_required` com `state_path`; o MCP abre o slot após `state_path_created` para o mesmo `state_path`; então o orquestrador despacha o validator e fecha usando `validator_run_id` + `dispatch_token`. Em `fail`, abre repair, passa `{state_path, findings, validator_attempt, repair_run_id, repair_budget: 1}` ao `atlas-findings-repair`, mantém o mesmo `state_path`, fecha o repair e roda o **2º e último** validator. `passed` e `passed_with_observations` são terminais aprovados.
213
214
  4. Review (condicional) — só após executor retornar 100% e dispatch MCP permitir.
214
215
  5. Output (ledger verificado).
215
216
 
@@ -221,7 +222,7 @@ Entrada: um **`PLAN_*.md` pronto**. Artefatos esperados: (plano já existe) →
221
222
 
222
223
  1. **Parse / classify** — `atlas_ping` → `atlas_capabilities` → **`atlas_classify_input`** no input (PRD D3/D6: o tipo é fato e precisa ser conhecido antes de travar o modo) → **`atlas_preflight(<modo efetivo>)`** (PREREQ hard-fail intacto). A classificação determina o tipo: se for plano, o modo efetivo é `execute` e o preflight trava `execute`; se o input não for plano, auto-rotear (ver Fase 0, passo 2b) e o preflight trava o modo roteado. **`classify_input` sempre precede `preflight`** (o preflight trava o modo efetivo, não o pedido).
223
224
  2. **Reverificar o plano na entrada** — `atlas_verify_artifact` no `PLAN_*.md` (G1) + `atlas_verify_template_conformance(artifact_type=plan)` (TC). Plano velho/manual/inválido **trava aqui** com `next_action` em linguagem de produto (PRD D11 — "autoria é livre, execução é gateada"). Sem reverificação válida não há dispatch.
224
- 3. **Executar** — `atlas_lock_dispatch(action=start, phase=plan_execute)`; despachar `plan_execute` como sub-agent blocking lendo o `PLAN_*.md`. A validação é sempre **sibling**: o executor escreve `state_path` e para; o orquestrador despacha o validator via `atlas_lock_validator` e fecha o slot somente com `validator_run_id` + `dispatch_token`. Ao obter `passed` ou `passed_with_observations`, concluir `plan_execute` com o terminal correspondente. `plan_execute` é aceito como **primeira fase** em `execute` (sem fase nova; PRD D13).
225
+ 3. **Executar** — `atlas_lock_dispatch(action=start, phase=plan_execute)`; despachar `plan_execute` como sub-agent blocking lendo o `PLAN_*.md`. Exigir checkpoints G12; se o executor não devolver retorno/progresso, chamar `atlas_lock_dispatch(action=status, phase=plan_execute)` e bloquear/retry em `executor_bootstrap_timeout`/`executor_progress_timeout`. A validação é sempre **sibling**: o executor escreve `state_path`, emite `state_path_created` e para; o MCP só abre validator para esse mesmo `state_path`; o orquestrador despacha o validator via `atlas_lock_validator` e fecha o slot somente com `validator_run_id` + `dispatch_token`. Ao obter `passed` ou `passed_with_observations`, concluir `plan_execute` com o terminal correspondente. `plan_execute` é aceito como **primeira fase** em `execute` (sem fase nova; PRD D13).
225
226
  4. **Review (condicional)** — só após execução concluída e se `--review` → `atlas_lock_dispatch(action=start, phase=slice_review)`, despachar `slice_review`, depois `complete`.
226
227
  5. **Output** — ledger verificado; `guarantee_level` = `full_pipeline` (PRD D12).
227
228
 
@@ -1,6 +1,6 @@
1
1
  # Atlas Workflow MCP Server
2
2
 
3
- Servidor MCP do plugin Atlas Workflow v0.8.2.
3
+ Servidor MCP do plugin Atlas Workflow v0.8.3.
4
4
 
5
5
  ## Tools
6
6
 
@@ -12,7 +12,7 @@ Servidor MCP do plugin Atlas Workflow v0.8.2.
12
12
  - `atlas_verify_template_conformance`: Gate TC; PRD/PLAN só avançam com template conforme e `pending_count: 0`.
13
13
  - `atlas_scan_prd`: Gate G5; escaneia PRD por padrões determinísticos de ambiguidade bloqueante.
14
14
  - `atlas_preflight`: Gate G10; valida modo, versão, lock ativo e mapa oficial de skills atlas-*.
15
- - `atlas_lock_dispatch`: Gates G7/G8; controla fase ativa, ordem de dispatch e validator antes de review.
15
+ - `atlas_lock_dispatch`: Gates G7/G8/G12; controla fase ativa, checkpoints de liveness do executor, ordem de dispatch e validator antes de review (`state_path_created` exige `state_path` legível).
16
16
  - `atlas_lock_validator`: Gate G4 sibling; um validator por vez, `dispatch_token` obrigatório, máximo de 2 attempts, repair obrigatório entre fail e retry, proof-of-work (challenge sha256 do boundary recomputado no complete; re-dispatch bounded → `challenge_exhausted`).
17
17
  - `atlas_assert_after_plan`: Gate G11; bloqueia encerramento prematuro do modo full após plano validado.
18
18
 
@@ -25,4 +25,5 @@ Servidor MCP do plugin Atlas Workflow v0.8.2.
25
25
  - Gates: resultados persistidos em `data.gates`.
26
26
  - Roteamento: lock persistido em `data.routing`.
27
27
  - Dispatch: fase ativa, próxima ação e histórico persistidos em `data.dispatch`.
28
+ - Liveness: `plan_execute` persiste `data.dispatch.active.liveness`; bootstrap vencido sem checkpoint ou checkpoint antigo sem progresso vira `executor_liveness.status = stalled` e `next_action: retry_plan_execute`; `atlas_lock_validator(start)` bloqueia até `state_path_created` corresponder ao mesmo `state_path`.
28
29
  - Erro bloqueante: entradas inválidas, run inexistente ou falha de estado retornam erro JSON-RPC; gate bloqueado retorna `status: "blocked"` e `next_action`.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlas-workflow/mcp-server",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "bin": {
@@ -78,6 +78,17 @@ const VALIDATOR_MAX_ATTEMPTS = 2;
78
78
  // bloqueia com causa explícita em vez de re-despachar indefinidamente.
79
79
  const VALIDATOR_CHALLENGE_MAX_FAILURES = 2;
80
80
  const VALIDATOR_PASSED_STATUSES = new Set(['passed', 'passed_with_observations']);
81
+ const EXECUTOR_BOOTSTRAP_TIMEOUT_MS = 120_000;
82
+ const EXECUTOR_PROGRESS_TIMEOUT_MS = 300_000;
83
+ const EXECUTOR_CHECKPOINT_EVENTS = new Set([
84
+ 'executor_started',
85
+ 'skill_loaded',
86
+ 'plan_loaded',
87
+ 'handoff_accepted',
88
+ 'task_started',
89
+ 'first_write',
90
+ 'state_path_created',
91
+ ]);
81
92
 
82
93
  function validatorRunId(runId, attempt, timestamp) {
83
94
  return `${runId}:validator:${attempt}:${timestamp}`;
@@ -584,6 +595,11 @@ function nowIso() {
584
595
  return new Date().toISOString();
585
596
  }
586
597
 
598
+ function isoPlusMs(iso, ms) {
599
+ const base = Date.parse(iso);
600
+ return new Date((Number.isFinite(base) ? base : Date.now()) + ms).toISOString();
601
+ }
602
+
587
603
  function redact(value) {
588
604
  if (Array.isArray(value)) return value.map(redact);
589
605
  if (!value || typeof value !== 'object') return value;
@@ -882,6 +898,7 @@ function patchDispatchResult(runId, result, args = {}) {
882
898
  timestamp: result.timestamp,
883
899
  phase: result.phase ?? null,
884
900
  action: result.action ?? null,
901
+ event: result.event ?? null,
885
902
  status: result.status,
886
903
  next_action: result.next_action ?? null,
887
904
  error: result.error ?? null,
@@ -1693,6 +1710,27 @@ function expectedNextPhase(routing, dispatch) {
1693
1710
  return 'prd_interview';
1694
1711
  }
1695
1712
 
1713
+ function initialExecutorLiveness(timestamp) {
1714
+ return {
1715
+ status: 'spawned',
1716
+ bootstrap_timeout_ms: EXECUTOR_BOOTSTRAP_TIMEOUT_MS,
1717
+ progress_timeout_ms: EXECUTOR_PROGRESS_TIMEOUT_MS,
1718
+ bootstrap_deadline_at: isoPlusMs(timestamp, EXECUTOR_BOOTSTRAP_TIMEOUT_MS),
1719
+ next_progress_deadline_at: null,
1720
+ required_first_checkpoint: 'executor_started',
1721
+ last_checkpoint: null,
1722
+ last_progress_at: null,
1723
+ checkpoints: [],
1724
+ };
1725
+ }
1726
+
1727
+ function checkpointStatus(event) {
1728
+ if (event === 'state_path_created') return 'handoff_ready';
1729
+ if (event === 'task_started' || event === 'first_write') return 'executing';
1730
+ if (event === 'plan_loaded' || event === 'handoff_accepted') return 'ready';
1731
+ return 'booting';
1732
+ }
1733
+
1696
1734
  function startDispatch(args, context) {
1697
1735
  const phase = requiredString(args, 'phase');
1698
1736
  if (Object.prototype.hasOwnProperty.call(args, LEGACY_ROUTE_KEY)) {
@@ -1752,7 +1790,11 @@ function startDispatch(args, context) {
1752
1790
  current_phase: phase,
1753
1791
  expected_phase: expected,
1754
1792
  dispatch: {
1755
- active: { phase, started_at: timestamp },
1793
+ active: {
1794
+ phase,
1795
+ started_at: timestamp,
1796
+ ...(phase === 'plan_execute' ? { liveness: initialExecutorLiveness(timestamp) } : {}),
1797
+ },
1756
1798
  previous_phase: context.dispatch.previous_phase ?? null,
1757
1799
  next_phase: null,
1758
1800
  next_action: `complete_${phase}`,
@@ -1761,6 +1803,220 @@ function startDispatch(args, context) {
1761
1803
  };
1762
1804
  }
1763
1805
 
1806
+ function checkpointDispatch(args, context) {
1807
+ const phase = requiredString(args, 'phase');
1808
+ const event = requiredString(args, 'event');
1809
+ const timestamp = nowIso();
1810
+ const active = context.dispatch.active;
1811
+
1812
+ if (phase !== 'plan_execute') {
1813
+ return {
1814
+ gate: 'G12',
1815
+ action: 'checkpoint',
1816
+ phase,
1817
+ event,
1818
+ status: 'blocked',
1819
+ timestamp,
1820
+ error: 'Checkpoint de liveness só se aplica a plan_execute',
1821
+ current_phase: active?.phase ?? null,
1822
+ expected_phase: 'plan_execute',
1823
+ next_action: 'corrigir_fase_do_checkpoint',
1824
+ };
1825
+ }
1826
+ if (!active || active.phase !== phase) {
1827
+ return {
1828
+ gate: 'G12',
1829
+ action: 'checkpoint',
1830
+ phase,
1831
+ event,
1832
+ status: 'blocked',
1833
+ timestamp,
1834
+ error: `Checkpoint fora de ordem: fase ativa ${active?.phase ?? 'nenhuma'}, recebido ${phase}`,
1835
+ current_phase: active?.phase ?? null,
1836
+ expected_phase: active?.phase ?? expectedNextPhase(context.routing, context.dispatch),
1837
+ next_action: active ? `checkpoint_${active.phase}` : `dispatch_${expectedNextPhase(context.routing, context.dispatch)}`,
1838
+ };
1839
+ }
1840
+ if (!EXECUTOR_CHECKPOINT_EVENTS.has(event)) {
1841
+ return {
1842
+ gate: 'G12',
1843
+ action: 'checkpoint',
1844
+ phase,
1845
+ event,
1846
+ status: 'blocked',
1847
+ timestamp,
1848
+ error: `Checkpoint desconhecido: ${event}`,
1849
+ current_phase: phase,
1850
+ expected_phase: phase,
1851
+ next_action: 'emitir_checkpoint_executor_valido',
1852
+ };
1853
+ }
1854
+
1855
+ const planPath = optionalString(args, 'plan_path');
1856
+ const statePathValue = optionalString(args, 'state_path');
1857
+ const detail = optionalString(args, 'detail');
1858
+ if (event === 'state_path_created') {
1859
+ if (!statePathValue || statePathValue.trim() === '') {
1860
+ return {
1861
+ gate: 'G12',
1862
+ action: 'checkpoint',
1863
+ phase,
1864
+ event,
1865
+ status: 'blocked',
1866
+ timestamp,
1867
+ error: 'state_path obrigatório para checkpoint state_path_created',
1868
+ current_phase: phase,
1869
+ expected_phase: phase,
1870
+ next_action: 'emitir_state_path_created_com_state_path',
1871
+ };
1872
+ }
1873
+ try {
1874
+ JSON.parse(fs.readFileSync(resolveConsumerPath(statePathValue, args), 'utf8'));
1875
+ } catch (error) {
1876
+ return {
1877
+ gate: 'G12',
1878
+ action: 'checkpoint',
1879
+ phase,
1880
+ event,
1881
+ status: 'blocked',
1882
+ timestamp,
1883
+ error: `state_path inválido ou ilegível para checkpoint state_path_created: ${error.message}`,
1884
+ current_phase: phase,
1885
+ expected_phase: phase,
1886
+ next_action: 'corrigir_state_path_antes_do_handoff',
1887
+ };
1888
+ }
1889
+ }
1890
+ const liveness = active.liveness && typeof active.liveness === 'object'
1891
+ ? active.liveness
1892
+ : initialExecutorLiveness(active.started_at ?? timestamp);
1893
+ const checkpoint = {
1894
+ event,
1895
+ timestamp,
1896
+ ...(planPath ? { plan_path: planPath } : {}),
1897
+ ...(statePathValue ? { state_path: statePathValue } : {}),
1898
+ ...(detail ? { detail } : {}),
1899
+ };
1900
+ const nextLiveness = {
1901
+ ...liveness,
1902
+ status: checkpointStatus(event),
1903
+ last_checkpoint: event,
1904
+ last_progress_at: timestamp,
1905
+ next_progress_deadline_at: isoPlusMs(timestamp, EXECUTOR_PROGRESS_TIMEOUT_MS),
1906
+ checkpoints: [
1907
+ ...(Array.isArray(liveness.checkpoints) ? liveness.checkpoints : []),
1908
+ checkpoint,
1909
+ ],
1910
+ };
1911
+
1912
+ return {
1913
+ gate: 'G12',
1914
+ action: 'checkpoint',
1915
+ phase,
1916
+ event,
1917
+ status: 'passed',
1918
+ timestamp,
1919
+ executor_liveness: nextLiveness.status,
1920
+ current_phase: phase,
1921
+ expected_phase: phase,
1922
+ dispatch: {
1923
+ active: {
1924
+ ...active,
1925
+ liveness: nextLiveness,
1926
+ },
1927
+ executor_liveness: nextLiveness,
1928
+ next_action: `complete_${phase}`,
1929
+ },
1930
+ next_action: `continue_${phase}`,
1931
+ };
1932
+ }
1933
+
1934
+ function statusDispatch(args, context) {
1935
+ const phase = requiredString(args, 'phase');
1936
+ const timestamp = nowIso();
1937
+ const active = context.dispatch.active;
1938
+
1939
+ if (!active || active.phase !== phase) {
1940
+ return {
1941
+ gate: 'G12',
1942
+ action: 'status',
1943
+ phase,
1944
+ status: 'blocked',
1945
+ timestamp,
1946
+ error: `Status fora de ordem: fase ativa ${active?.phase ?? 'nenhuma'}, recebido ${phase}`,
1947
+ current_phase: active?.phase ?? null,
1948
+ expected_phase: active?.phase ?? expectedNextPhase(context.routing, context.dispatch),
1949
+ next_action: active ? `status_${active.phase}` : `dispatch_${expectedNextPhase(context.routing, context.dispatch)}`,
1950
+ };
1951
+ }
1952
+
1953
+ const liveness = active.liveness && typeof active.liveness === 'object'
1954
+ ? active.liveness
1955
+ : (phase === 'plan_execute' ? initialExecutorLiveness(active.started_at ?? timestamp) : null);
1956
+ const checkpoints = Array.isArray(liveness?.checkpoints) ? liveness.checkpoints : [];
1957
+ const deadline = Date.parse(liveness?.bootstrap_deadline_at ?? '');
1958
+ const now = Date.parse(timestamp);
1959
+ const bootstrapExpired = phase === 'plan_execute'
1960
+ && checkpoints.length === 0
1961
+ && Number.isFinite(deadline)
1962
+ && Number.isFinite(now)
1963
+ && now > deadline;
1964
+ const progressDeadline = Date.parse(liveness?.next_progress_deadline_at ?? '');
1965
+ const progressExpired = phase === 'plan_execute'
1966
+ && checkpoints.length > 0
1967
+ && Number.isFinite(progressDeadline)
1968
+ && Number.isFinite(now)
1969
+ && now > progressDeadline;
1970
+
1971
+ if (bootstrapExpired || progressExpired) {
1972
+ const cause = bootstrapExpired ? 'executor_bootstrap_timeout' : 'executor_progress_timeout';
1973
+ const stalledLiveness = {
1974
+ ...liveness,
1975
+ status: 'stalled',
1976
+ stalled_at: timestamp,
1977
+ cause,
1978
+ };
1979
+ return {
1980
+ gate: 'G12',
1981
+ action: 'status',
1982
+ phase,
1983
+ status: 'blocked',
1984
+ timestamp,
1985
+ cause,
1986
+ error: bootstrapExpired
1987
+ ? `Executor sem checkpoint até ${liveness.bootstrap_deadline_at}`
1988
+ : `Executor sem progresso desde ${liveness.last_progress_at}`,
1989
+ current_phase: phase,
1990
+ expected_phase: phase,
1991
+ dispatch: {
1992
+ active: null,
1993
+ previous_phase: phase,
1994
+ next_phase: phase,
1995
+ next_action: `retry_${phase}`,
1996
+ executor_liveness: stalledLiveness,
1997
+ },
1998
+ next_action: `retry_${phase}`,
1999
+ };
2000
+ }
2001
+
2002
+ return {
2003
+ gate: 'G12',
2004
+ action: 'status',
2005
+ phase,
2006
+ status: 'passed',
2007
+ timestamp,
2008
+ executor_liveness: liveness?.status ?? 'not_tracked',
2009
+ current_phase: phase,
2010
+ expected_phase: phase,
2011
+ dispatch: {
2012
+ active: liveness ? { ...active, liveness } : active,
2013
+ executor_liveness: liveness,
2014
+ next_action: `complete_${phase}`,
2015
+ },
2016
+ next_action: `complete_${phase}`,
2017
+ };
2018
+ }
2019
+
1764
2020
  function completeDispatch(args, context) {
1765
2021
  const phase = requiredString(args, 'phase');
1766
2022
  const timestamp = nowIso();
@@ -1896,13 +2152,15 @@ function lockDispatch(args = {}) {
1896
2152
  throw rpcError(-32602, `unknown_property: ${LEGACY_ROUTE_KEY}`);
1897
2153
  }
1898
2154
  const action = args.action ?? 'start';
1899
- if (!['start', 'complete', 'abort'].includes(action)) {
2155
+ if (!['start', 'checkpoint', 'status', 'complete', 'abort'].includes(action)) {
1900
2156
  throw rpcError(-32602, `Ação inválida para atlas_lock_dispatch: ${action}`);
1901
2157
  }
1902
2158
 
1903
2159
  const context = getDispatchState(runId, args);
1904
2160
  const result =
1905
2161
  action === 'start' ? startDispatch(args, context) :
2162
+ action === 'checkpoint' ? checkpointDispatch(args, context) :
2163
+ action === 'status' ? statusDispatch(args, context) :
1906
2164
  action === 'complete' ? completeDispatch(args, context) :
1907
2165
  abortDispatch(args, context);
1908
2166
 
@@ -1987,6 +2245,33 @@ function validatorStart(args, context) {
1987
2245
  };
1988
2246
  }
1989
2247
 
2248
+ const liveness = context.dispatch.active.liveness && typeof context.dispatch.active.liveness === 'object'
2249
+ ? context.dispatch.active.liveness
2250
+ : null;
2251
+ const checkpoints = Array.isArray(liveness?.checkpoints) ? liveness.checkpoints : [];
2252
+ const lastCheckpoint = checkpoints.at(-1);
2253
+ if (
2254
+ liveness?.status !== 'handoff_ready'
2255
+ || liveness.last_checkpoint !== 'state_path_created'
2256
+ || lastCheckpoint?.event !== 'state_path_created'
2257
+ || lastCheckpoint?.state_path !== statePathValue
2258
+ ) {
2259
+ return {
2260
+ gate: 'G12',
2261
+ action: 'start',
2262
+ status: 'blocked',
2263
+ timestamp,
2264
+ error: 'Validator bloqueado: executor não comprovou state_path_created para este state_path',
2265
+ current_phase: 'plan_execute',
2266
+ executor_liveness: liveness?.status ?? 'not_tracked',
2267
+ expected_checkpoint: 'state_path_created',
2268
+ state_path: statePathValue,
2269
+ last_checkpoint: liveness?.last_checkpoint ?? null,
2270
+ last_state_path: lastCheckpoint?.state_path ?? null,
2271
+ next_action: 'aguardar_state_path_created_antes_do_validator',
2272
+ };
2273
+ }
2274
+
1990
2275
  if (cycle.active) {
1991
2276
  return {
1992
2277
  gate: 'G4',
@@ -2876,7 +3161,7 @@ function toolsList() {
2876
3161
  },
2877
3162
  {
2878
3163
  name: 'atlas_lock_dispatch',
2879
- description: 'Gates G7/G8: controla fase ativa, transições de dispatch, validator antes de review e concorrência 1.',
3164
+ description: 'Gates G7/G8/G12: controla fase ativa, checkpoints de liveness do executor, transições de dispatch, validator antes de review e concorrência 1.',
2880
3165
  inputSchema: {
2881
3166
  type: 'object',
2882
3167
  additionalProperties: false,
@@ -2884,8 +3169,16 @@ function toolsList() {
2884
3169
  properties: {
2885
3170
  run_id: { type: 'string', minLength: 1 },
2886
3171
  project_root: { type: 'string', minLength: 1 },
2887
- action: { type: 'string', enum: ['start', 'complete', 'abort'], default: 'start' },
3172
+ action: { type: 'string', enum: ['start', 'checkpoint', 'status', 'complete', 'abort'], default: 'start' },
2888
3173
  phase: { type: 'string', enum: ['plan_handoff', 'plan_execute', 'slice_review'] },
3174
+ event: {
3175
+ type: 'string',
3176
+ enum: [...EXECUTOR_CHECKPOINT_EVENTS],
3177
+ description: 'Checkpoint G12 emitido pelo executor durante plan_execute.',
3178
+ },
3179
+ plan_path: { type: 'string' },
3180
+ state_path: { type: 'string' },
3181
+ detail: { type: 'string' },
2889
3182
  validator_status: { type: 'string' },
2890
3183
  },
2891
3184
  },
@@ -11,6 +11,29 @@ Execute directly from a PRD/spec/task while preserving execution quality: explic
11
11
 
12
12
  This is not planless execution. Replace the visible markdown plan with a compact operational contract held in the current turn and passed to validation.
13
13
 
14
+ ## Executor liveness checkpoints
15
+
16
+ Depois de carregar esta skill e antes de qualquer discovery longo, emita um checkpoint MCP:
17
+
18
+ ```json
19
+ atlas_lock_dispatch({
20
+ "action": "checkpoint",
21
+ "phase": "plan_execute",
22
+ "event": "executor_started"
23
+ })
24
+ ```
25
+
26
+ Em seguida, emita checkpoints materiais conforme avança:
27
+
28
+ - `skill_loaded` — skill carregada e contrato reconhecido.
29
+ - `plan_loaded` — PRD/spec/task de entrada lido.
30
+ - `handoff_accepted` — boundary, obligations, `state_path` alvo e contrato direto aceitos.
31
+ - `task_started` — primeira task começou.
32
+ - `first_write` — primeira mutação de workspace feita.
33
+ - `state_path_created` — state file escrito antes de devolver `validator_handoff_required`.
34
+
35
+ Se não conseguir emitir checkpoint por MCP, retorne `blocked`: liveness não é comprovável. Sem `state_path_created` com o mesmo `state_path`, `atlas_lock_validator(start)` bloqueia em G12 e o orquestrador não pode despachar o validador frio.
36
+
14
37
  ## Use Criteria
15
38
 
16
39
  Use when all are true:
@@ -40,6 +63,8 @@ Ask at most 1-3 blocking questions only when a reasonable assumption could chang
40
63
 
41
64
  ### 1. Load inputs
42
65
 
66
+ First, emit `executor_started`, then `skill_loaded`, before doing any long scan.
67
+
43
68
  Read the user-provided PRD/spec/task and any directly referenced files needed to resolve scope. If the input names repo artifacts, verify those artifacts exist before editing.
44
69
 
45
70
  Extract only execution-relevant items:
@@ -57,6 +82,8 @@ Extract only execution-relevant items:
57
82
 
58
83
  If the PRD references another PRD or code contract as dependency, inspect enough to confirm the dependency shape and required bridge. Do not satisfy a dependency by creating parallel synthetic contracts unless the PRD explicitly allows it.
59
84
 
85
+ After the input is loaded, emit `plan_loaded`. After validating the execution boundary, obligations, and `state_path` target, emit `handoff_accepted`.
86
+
60
87
  ### 2. Build Compact Execution Contract
61
88
 
62
89
  Before editing, write a compact contract in the working response or internal task state. Size follows complexity: terse for simple tasks, denser only where needed to preserve scope, invariants, and validator quality.
@@ -122,6 +149,8 @@ For each task, keep a tiny task contract:
122
149
 
123
150
  Do not widen scope for opportunistic cleanup.
124
151
 
152
+ Before the first concrete task, emit `task_started`. After the first workspace mutation, emit `first_write`.
153
+
125
154
  ### 4. Gate each task
126
155
 
127
156
  Run focused checks appropriate to the diff:
@@ -148,6 +177,8 @@ For direct execution, the state file is still the only validator input. Use the
148
177
 
149
178
  The state file is the only validator input. Validation is always **sibling**, on every host: this executor **never** dispatches `atlas-task-validator` itself and never validates its own work in the same context. After tasks and local gates pass and the state file is written, this executor **stops mutation** and returns `validator_handoff_required` with the `state_path`. The orchestrator then dispatches `atlas-task-validator` as the next isolated sibling phase, locks it via `atlas_lock_validator`, and — if the verdict is `fail` — dispatches `atlas-findings-repair` (not this executor) before the **2nd and last** validator.
150
179
 
180
+ After writing the state file and before returning, emit `state_path_created` with the same `state_path`.
181
+
151
182
  Do not paste the compact contract, diff, obligation ledger, local checks, or closure analysis packet into the state file's handoff. Those belong in the state file and referenced artifacts.
152
183
 
153
184
  **Finish all local work before the handoff — then stop idle.** Finish every local gate (lint, analyze, tests, `git diff --check`, diff-stat) and write the state file **before** returning the handoff. After returning `validator_handoff_required`, do nothing: no diff hygiene checks, no extra reads, no opportunistic edits, no parallel work. The orchestrator now owns the slice; any mutation here would change what the sibling validator reads and breaks determinism (same failure class as the orchestrator's G9).