@jaimevalasek/aioson 1.36.0 → 1.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/docs/en/1-understand/ecosystem-map.md +1 -1
- package/docs/en/1-understand/glossary.md +1 -1
- package/docs/en/2-start/first-project.md +1 -1
- package/docs/en/2-start/initial-decisions.md +4 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +8 -1
- package/docs/en/3-recipes/full-feature-with-sheldon.md +3 -1
- package/docs/en/4-agents/README.md +6 -3
- package/docs/en/4-agents/briefing-refiner.md +146 -0
- package/docs/en/5-reference/README.md +1 -0
- package/docs/en/5-reference/autopilot-handoff.md +286 -0
- package/docs/en/5-reference/cli-reference.md +6 -0
- package/docs/pt/1-entender/glossario.md +1 -1
- package/docs/pt/1-entender/mapa-do-ecossistema.md +1 -1
- package/docs/pt/2-comecar/decisoes-iniciais.md +4 -2
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +3 -1
- package/docs/pt/4-agentes/README.md +13 -11
- package/docs/pt/4-agentes/briefing-refiner.md +64 -40
- package/docs/pt/4-agentes/briefing.md +6 -1
- package/docs/pt/4-agentes/dev.md +19 -1
- package/docs/pt/4-agentes/deyvin.md +4 -0
- package/docs/pt/4-agentes/discover.md +4 -0
- package/docs/pt/4-agentes/neo.md +4 -0
- package/docs/pt/4-agentes/orache.md +6 -0
- package/docs/pt/4-agentes/orchestrator.md +12 -0
- package/docs/pt/4-agentes/pentester.md +6 -0
- package/docs/pt/4-agentes/product.md +19 -1
- package/docs/pt/4-agentes/qa.md +10 -2
- package/docs/pt/4-agentes/setup.md +3 -1
- package/docs/pt/4-agentes/sheldon.md +12 -0
- package/docs/pt/4-agentes/tester.md +6 -0
- package/docs/pt/4-agentes/ux-ui.md +2 -1
- package/docs/pt/5-referencia/README.md +1 -1
- package/docs/pt/5-referencia/agent-chain-continuity.md +1 -1
- package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
- package/docs/pt/5-referencia/comandos-cli.md +16 -7
- package/docs/pt/5-referencia/skills.md +2 -0
- package/docs/pt/agentes.md +3 -1
- package/package.json +1 -1
- package/src/agent-execution/adapters/base.js +15 -0
- package/src/agent-execution/adapters/claude.js +3 -0
- package/src/agent-execution/adapters/codex.js +3 -0
- package/src/agent-execution/adapters/opencode.js +3 -0
- package/src/agent-execution/capabilities.js +9 -0
- package/src/agent-execution/dispatcher.js +72 -0
- package/src/agent-execution/executable-resolver.js +7 -0
- package/src/agent-execution/manifest.js +62 -0
- package/src/agent-execution/model-catalog.js +80 -0
- package/src/agent-execution/model-resolver.js +132 -0
- package/src/agent-execution/reports.js +9 -0
- package/src/agent-execution/schema.js +48 -0
- package/src/agent-execution/telemetry-bridge.js +15 -0
- package/src/agents.js +1 -1
- package/src/artifact-kinds.js +2 -1
- package/src/autopilot-signal.js +71 -0
- package/src/cli.js +31 -5
- package/src/commands/agent-execution.js +36 -0
- package/src/commands/agents.js +18 -2
- package/src/commands/briefing.js +337 -1
- package/src/commands/feature-close.js +136 -43
- package/src/commands/live.js +47 -11
- package/src/commands/op-capture.js +33 -3
- package/src/commands/op-reinforce.js +10 -22
- package/src/commands/update.js +5 -1
- package/src/commands/verification-plan.js +56 -12
- package/src/commands/verify-artifact.js +64 -1
- package/src/commands/workflow-execute.js +168 -36
- package/src/commands/workflow-next.js +60 -16
- package/src/doctor.js +4 -2
- package/src/harness/criteria-runner.js +4 -1
- package/src/i18n/messages/en.js +2 -1
- package/src/i18n/messages/es.js +2 -1
- package/src/i18n/messages/fr.js +2 -1
- package/src/i18n/messages/pt-BR.js +2 -1
- package/src/lib/briefing-refiner/apply-feedback.js +18 -4
- package/src/lib/briefing-refiner/feedback-schema.js +73 -4
- package/src/lib/briefing-refiner/refinement-report.js +11 -0
- package/src/lib/briefing-refiner/review-html.js +388 -68
- package/src/operator-memory/decision.js +41 -0
- package/src/parser.js +6 -0
- package/src/runtime-store.js +167 -8
- package/template/.aioson/agents/briefing-refiner.md +87 -47
- package/template/.aioson/agents/briefing.md +4 -0
- package/template/.aioson/agents/dev.md +9 -2
- package/template/.aioson/agents/deyvin.md +4 -0
- package/template/.aioson/agents/discover.md +4 -0
- package/template/.aioson/agents/neo.md +4 -0
- package/template/.aioson/agents/orache.md +4 -0
- package/template/.aioson/agents/orchestrator.md +16 -0
- package/template/.aioson/agents/pentester.md +4 -0
- package/template/.aioson/agents/product.md +26 -1
- package/template/.aioson/agents/qa.md +5 -1
- package/template/.aioson/agents/sheldon.md +9 -1
- package/template/.aioson/agents/tester.md +4 -0
- package/template/.aioson/agents/ux-ui.md +1 -1
- package/template/.aioson/docs/agent-help.md +126 -0
- package/template/.aioson/docs/autopilot-handoff.md +32 -16
- package/template/.aioson/docs/dev/phase-loop.md +9 -7
- package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
- package/template/.aioson/schemas/agent-execution.schema.json +28 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
- package/template/AGENTS.md +36 -36
- package/template/CLAUDE.md +1 -1
|
@@ -1,96 +1,172 @@
|
|
|
1
|
-
# Autopilot Handoff —
|
|
1
|
+
# Autopilot Handoff — o full-feature autopilot
|
|
2
2
|
|
|
3
|
-
> Protocolo opt-in que
|
|
3
|
+
> Protocolo opt-in que remove as confirmações **mecânicas** de handoff em toda a cadeia de uma feature — do `@product` até a recomendação de `aioson feature:close`. Decisões humanas genuínas (escopo, sizing) continuam acontecendo dentro dos próprios agentes; o autopilot só remove o "roda o próximo passo" depois que o trabalho do agente atual já está resolvido. `feature:close`/publish **nunca** roda sozinho — é sempre gate humano.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Dois segmentos
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Segmento 1: @product → @sheldon (SMALL) / @orchestrator (MEDIUM) → @dev
|
|
11
|
+
Segmento 2: @dev → @qa (hub) → @tester / @pentester (quando o trigger dispara) → @validator → STOP
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
1. **Cadeia spec → dev** — cada agente de spec, uma vez resolvidas as próprias decisões (sem `AskUserQuestion` aberta, gates que ele possui aprovados), semeia o esquema agêntico e invoca o próximo estágio em vez de parar. A travessia pré-dev acontece pelo pacote `dev-state.md`, não carregando o chat bruto adiante.
|
|
15
|
+
2. **Ciclo de revisão pós-dev** — `@dev` e os agentes de revisão se encadeiam automaticamente até a feature estar pronta para fechar. `@qa` é o hub: possui o roteamento para os agentes especializados e o ciclo de correções.
|
|
16
|
+
|
|
17
|
+
Historicamente o Segmento 1 sempre parava no humano (os agentes de spec terminavam em decisões humanas); hoje o autopilot atravessa esse segmento também — mas só mecanicamente. Uma decisão real de produto/escopo/sizing sempre pausa antes de qualquer auto-invocação.
|
|
6
18
|
|
|
7
19
|
---
|
|
8
20
|
|
|
9
21
|
## Ativação
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
Autopilot está ativo quando as duas primeiras condições valem, com a terceira como gate:
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
1. **Sinal de autopilot armado** — `auto_handoff: true` no `project.context.md` (flag do projeto), **ou** `.aioson/context/workflow-execute.json` existe com `agentic_policy.enabled: true` **e `feature` igual ao slug atual** (esquema semeado — um esquema deixado por outra feature/já fechada **não conta**, para nenhum agente da cadeia). **O desarme por feature vence a flag:** um esquema para o slug atual com `agentic_policy.enabled: false` (escrito por `aioson workflow:execute . --feature={slug} --seed --step`) desliga o autopilot **só para essa feature**, mesmo com `auto_handoff: true` no projeto.
|
|
26
|
+
2. Um workflow de feature está ativo (slug conhecido).
|
|
27
|
+
3. O gate/veredito do agente atual passou **e** não há decisão humana genuína em aberto (ver condições de parada).
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
### Token inline de modo de execução (maior precedência)
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
1. O workflow de feature deve estar ativo (slug conhecido, classificação SMALL ou MEDIUM).
|
|
21
|
-
2. O gate/verdict do agente atual deve ter passado.
|
|
31
|
+
Um `--auto` ou `--step` isolado nos argumentos de ativação do `@product` (kickoff) ou do `@dev` (entrada tardia/override) **é** a decisão de modo de execução — o agente remove o token do texto da tarefa e nunca pergunta:
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
| Token | Onde | Efeito |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `/product --auto <tarefa>` | Kickoff da feature | Pula a pergunta na tela; semeia o esquema e arma toda a cadeia a partir dali |
|
|
36
|
+
| `/product --step <tarefa>` | Kickoff da feature | Pula a pergunta; grava o esquema desarmado (`agentic_policy.enabled: false`) — handoff manual |
|
|
37
|
+
| `/dev --auto` | Entrada em `@dev` | Arma o autopilot a partir daqui mesmo sem flag/esquema prévio — implementação + ciclo de revisão pós-dev rodam sozinhos |
|
|
38
|
+
| `/dev --step` | Entrada em `@dev` | Desarma o autopilot **só para esta feature** — para no handoff `@dev → @qa` mesmo em projeto com `auto_handoff: true` (o desarme por feature sempre vence a flag do projeto) |
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
Agentes downstream (`@qa`/`@tester`/`@pentester`/`@validator`) não parseiam tokens — eles só leem a flag/esquema já decidido. Só `@product` pergunta; os demais nunca re-perguntam.
|
|
41
|
+
|
|
42
|
+
### Sem token: a pergunta acontece uma única vez, no handoff do `@product`
|
|
43
|
+
|
|
44
|
+
Quando não há token inline nem escolha padrão registrada, `@product` pergunta on-screen ao fechar o PRD (`AskUserQuestion`, localizado, com marcador de recomendação):
|
|
26
45
|
|
|
27
|
-
|
|
46
|
+
- **Autopilot — roda tudo até `feature:close`** → roda as ações de autopilot só para esta feature (não persiste default).
|
|
47
|
+
- **Passo a passo — eu conduzo cada etapa** → apresenta o handoff manual e para.
|
|
48
|
+
- **Sempre autopilot neste projeto** → grava `auto_handoff: true` no `project.context.md` (cria a linha se ausente) e roda as ações de autopilot.
|
|
49
|
+
|
|
50
|
+
Precedência completa, da mais forte para a mais fraca:
|
|
28
51
|
|
|
29
52
|
```
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
1. Token inline (--auto / --step)
|
|
54
|
+
2. Esquema por feature (.aioson/context/workflow-execute.json com feature={slug}) — armado ou desarmado
|
|
55
|
+
3. Flag do projeto (auto_handoff: true/false em project.context.md)
|
|
56
|
+
4. Pergunta on-screen do @product (quando nada dos 3 acima está definido)
|
|
34
57
|
```
|
|
35
58
|
|
|
36
|
-
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Semeando o esquema agêntico
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
O primeiro agente de spec a terminar sob autopilot semeia o contrato do run — o "esquema" que toda a cadeia segue, e o que torna uma feature construída do jeito normal (`@product → @sheldon`/`@orchestrator` → …) rodar até `feature:close` sem você precisar invocar nada:
|
|
39
64
|
|
|
40
|
-
|
|
65
|
+
```bash
|
|
66
|
+
aioson workflow:execute . --feature={slug} --seed --tool=<tool>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`--seed` grava `.aioson/context/workflow-execute.json` (com `agentic_policy.enabled: true` — caps do ciclo de revisão, `feature_close: human_gate`, condições de parada) mais `.aioson/context/workflow.state.json`. É **seed-only**: registra a política que os agentes interativos seguem, mas **não** dirige as transições de estágio sozinho (quem faz isso são os próprios agentes, via `Skill(aioson:agent:<próximo>)` + `aioson workflow:next . --complete=<agente>`). Re-semear o mesmo slug é idempotente.
|
|
70
|
+
|
|
71
|
+
**Falha ao semear é condição de parada.** O agente que semeia checa o resultado do comando: uma falha `different_active_feature` significa que outra feature genuinamente ativa segura o `workflow.state.json` — expõe isso ao usuário (fechar/pausar essa feature, ou `aioson feature:sweep .`) e para com o handoff manual. Nunca continue a cadeia como se o autopilot estivesse armado quando o seed falhou.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Roteamento — determinístico, nunca escolhido pelo modelo
|
|
76
|
+
|
|
77
|
+
O próximo agente vem da máquina de estados do workflow e da evidência em disco, nunca de julgamento do LLM:
|
|
78
|
+
|
|
79
|
+
- CLI disponível: rode `aioson workflow:next .` (modo inspeção) e use o estágio reportado, ou o campo `next` de `.aioson/context/workflow.state.json`.
|
|
80
|
+
- CLI ausente: siga a sequência de classificação em `.aioson/config.md` e as tabelas de roteamento abaixo, exatamente.
|
|
81
|
+
|
|
82
|
+
Nunca pule um estágio, reordene, ou escolha um agente que a máquina de estados/tabela não nomeou.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Segmento 1 — cadeia spec → dev
|
|
87
|
+
|
|
88
|
+
**SMALL (lane lean, padrão):** `@product` → `@sheldon` → `@dev`. Sob autopilot: `@product`, com o PRD resolvido, semeia o esquema e invoca `@sheldon`; `@sheldon`, com sizing/enriquecimento confirmados e o pacote lean + `dev-state.md` gravados, completa o próprio estágio (`aioson workflow:next . --complete=sheldon`) e invoca `@dev`. O detour opt-in full-merged do SMALL encadeia `@analyst` → `@architect` → `@dev` quando ativado (com `@scope-check`/`@discovery-design-doc` só se a sequência os inclui).
|
|
89
|
+
|
|
90
|
+
**MEDIUM (lane maestro, padrão):** `@product` → `@orchestrator` → `@dev`. Sob autopilot: `@product` semeia e invoca `@orchestrator`; `@orchestrator`, com o pacote de spec com gates aprovados (Gates A/B/C, readiness pronta) + `dev-state.md` gravados, invoca `@dev`. O fan-out para `@analyst`/`@architect`/`@pm` acontece como sub-agentes, não como estágios do workflow — eles só encadeiam como estágios sob um detour opt-in full-chain.
|
|
91
|
+
|
|
92
|
+
A travessia para `@dev` passa pelo pacote `dev-state.md` que o agente de spec grava — o protocolo de início de sessão do `@dev` carrega só esse pacote mínimo, então `@dev` não herda o chat pesado anterior; auto-compact transparente cuida do resto. É por isso que a travessia é segura sem um `/compact` manual. O agente de spec ainda para com o handoff manual normal se tiver uma decisão de produto/escopo/sizing em aberto, ou um gate que ele possui não aprovado. Recomende `/clear` só para reset forte, troca de feature, contexto poluído ou reset sensível a segurança.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Segmento 2 — ciclo de revisão pós-dev (hub = `@qa`)
|
|
97
|
+
|
|
98
|
+
Uma vez que um humano inicia `@dev` e ele termina, a cadeia retoma automaticamente. `@qa` é o hub; cada agente especializado volta para ele.
|
|
41
99
|
|
|
42
100
|
| Agente atual | Condição | Próximo invocado |
|
|
43
101
|
|---|---|---|
|
|
44
|
-
| `@dev` (1ª passagem) | testes ok, sem correções
|
|
102
|
+
| `@dev` (1ª passagem) | testes ok, gates limpos, sem ciclo de correções aberto | `@qa` |
|
|
45
103
|
| `@dev` (correções) | correções aplicadas, testes ok | `@qa` (re-verificação) |
|
|
46
|
-
| `@qa` | FAIL (Critical/High) | `@dev` via ciclo de correções (cap
|
|
47
|
-
| `@qa` | PASS + trigger `@tester` não
|
|
48
|
-
| `@qa` | PASS + trigger `@pentester` não
|
|
49
|
-
| `@qa` | PASS + contrato harness presente
|
|
50
|
-
| `@qa` | PASS +
|
|
51
|
-
| `@tester` |
|
|
52
|
-
| `@tester` | sem
|
|
53
|
-
| `@pentester` | findings abertos
|
|
54
|
-
| `@pentester` | sem findings do dev | `@qa` (
|
|
104
|
+
| `@qa` | veredito **FAIL** (Critical/High) | `@dev` via ciclo de correções (cap 3, gate de segurança) |
|
|
105
|
+
| `@qa` | veredito **PASS** + trigger `@tester` dispara e ainda não rodou limpo | `@tester` |
|
|
106
|
+
| `@qa` | veredito **PASS** + trigger `@pentester` dispara e ainda não rodou limpo | `@pentester` |
|
|
107
|
+
| `@qa` | veredito **PASS** + contrato harness presente e `@validator` ainda não PASS | `@validator` |
|
|
108
|
+
| `@qa` | veredito **PASS** + nenhum trigger/contrato pendente | **STOP** — recomenda `aioson feature:close . --feature={slug}` |
|
|
109
|
+
| `@tester` | gaps bloqueantes de propriedade do dev | `@dev` |
|
|
110
|
+
| `@tester` | sem gaps bloqueantes | `@qa` (reavaliação/sign-off) |
|
|
111
|
+
| `@pentester` | findings abertos com `recommended_owner = dev` | `@dev` |
|
|
112
|
+
| `@pentester` | sem findings abertos do dev | `@qa` (reavaliação/sign-off) |
|
|
55
113
|
| `@validator` | PASS | **STOP** — recomenda `aioson feature:close` |
|
|
56
114
|
| `@validator` | FAIL | `@dev` |
|
|
57
115
|
|
|
58
|
-
|
|
116
|
+
**Origem do trigger para `@tester`/`@pentester`:** a lógica de trigger do `@qa` já existente (gaps de cobertura → `@tester`; superfície sensível auth/secrets/data/upload/URL externa/supply-chain → `@pentester`). Os quatro agentes estão **sempre** ligados na cadeia, mas `@tester`/`@pentester` só **executam** quando o trigger dispara — do contrário `@qa` pula direto para a próxima linha de roteamento.
|
|
117
|
+
|
|
118
|
+
**Guarda de reentrada (sem loop infinito):** antes de auto-invocar um agente especializado, `@qa` checa evidência em disco de que ele já rodou limpo neste ciclo (`security-findings-{slug}.json` limpo → `@pentester` feito; artefato de cobertura do tester presente sem gap novo → `@tester` feito; `progress.json.ready_for_done_gate`/PASS do validador registrado → `@validator` feito). Um agente que já retornou limpo não é reinvocado.
|
|
59
119
|
|
|
60
|
-
|
|
120
|
+
**`@validator` roda em contexto fresco:** ao rotear para `@validator` com contrato de harness presente, não rode inline na sessão atual — o histórico de implementação enviesa o veredito. A sequência: `harness:check` (checks determinísticos) → `harness:validate` (gera `validator-prompt.txt` autocontido: critérios + resultados do check + diff vs. base) → execução em **subagente isolado** (Task tool, sem contexto de conversa) que grava o veredito em `last-validator-output.json` → `harness:validate` de novo, para consumir o veredito pelo circuit breaker. Clientes sem suporte a subagente caem para `Skill(aioson:agent:validator)` numa sessão fresca, como antes.
|
|
121
|
+
|
|
122
|
+
---
|
|
61
123
|
|
|
62
|
-
|
|
124
|
+
## Condições de parada — quebram a cadeia e emitem o handoff manual normal
|
|
125
|
+
|
|
126
|
+
1. **`feature:close`/publish** — SEMPRE o gate humano. Quando `@qa` (PASS, nada pendente) ou `@validator` (PASS) é o último passo limpo, STOP e recomenda `aioson feature:close . --feature={slug}`. Nunca roda `feature:close`, `feature:archive`, `npm publish`, ou qualquer ação de publish/close sozinho.
|
|
127
|
+
2. **Decisão humana genuína em aberto** — um agente de spec com pergunta de produto/escopo/sizing não resolvida (uma `AskUserQuestion` aberta, ou um gate que ele possui ainda não aprovado) resolve essa decisão com o humano antes de qualquer auto-invocação, e para com o handoff manual normal. Autopilot remove paradas mecânicas, nunca decisões reais.
|
|
128
|
+
3. **Cap de correções atingido** — ciclos de revisão são limitados por `agentic_policy.review_cycle` (default **3**); quando `review-cycle:advance` retorna `stop_cycle_limit`, para e escala para o humano.
|
|
129
|
+
4. **Finding crítico de segurança** — o gate de segurança de correções do `@qa` (keywords auth/secret/credential/session/password/token/PII/encryption) bloqueia o auto-loop; para e exige intervenção humana.
|
|
130
|
+
5. **Veredito não limpo / gate ou readiness bloqueado** — o pacote de spec maestro do `@orchestrator` sem Gates A/B/C aprovados ou com readiness `blocked`, `@validator` FAIL sem caminho seguro de correção (e, quando presentes como detours, `@architect` Gate B/readiness modo-merged `blocked`, `@pm` Gate C bloqueado, `@scope-check` não `approved`/`patched`, ou `@discovery-design-doc` readiness `blocked`): para e roteia ao dono manualmente.
|
|
131
|
+
6. **Orçamento de contexto** — uso estimado ≥ `context_warning_threshold` (`.aioson/config.md`): grava o checkpoint de compactação em `.aioson/context/last-handoff.json`, para, e recomenda `/compact` para continuar a mesma feature. O workflow retoma de `.aioson/context/workflow.state.json` — a próxima sessão reentra no autopilot automaticamente. Recomende `/clear` só para reset forte, troca de feature, contexto poluído ou reset sensível a segurança.
|
|
132
|
+
7. **Ambiguidade** — estado do workflow indisponível e roteamento ambíguo, ou qualquer decisão real exige input do usuário: para e pergunta, manualmente.
|
|
133
|
+
|
|
134
|
+
Você pode interromper a qualquer momento com Ctrl+C; o autopilot nunca retenta uma invocação interrompida.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Opção `--help` nos 13 agentes mais usados
|
|
139
|
+
|
|
140
|
+
Uma ativação com `--help` isolado (`/<agente> --help`) faz o agente imprimir um bloco de ajuda rápido — o que faz / quando usar / opções / chamada típica / o que produz / próximo agente —, localizado no seu idioma, e parar sem fazer nenhum trabalho. Isso vale para os 13 agentes mais usados, fonte única em `.aioson/docs/agent-help.md`:
|
|
63
141
|
|
|
64
142
|
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
harness:validate → gera validator-prompt.txt com REVIEW PAYLOAD autocontido:
|
|
68
|
-
(a) resultados do harness:check
|
|
69
|
-
(b) lista de arquivos alterados (untracked incluídos, .aioson/** filtrado)
|
|
70
|
-
(c) diff unificado vs base resolvida (--base > baseline.json > merge-base > HEAD)
|
|
71
|
-
↓
|
|
72
|
-
execução isolada em subagente → @validator julga só os critérios sem verification, em contexto limpo
|
|
73
|
-
↓
|
|
74
|
-
harness:validate (de novo) → consome o veredito pelo circuit breaker (waiting_validation/apply-validation)
|
|
143
|
+
@product · @briefing · @briefing-refiner · @dev · @deyvin · @discover ·
|
|
144
|
+
@neo · @orache · @orchestrator · @tester · @pentester · @qa · @sheldon
|
|
75
145
|
```
|
|
76
146
|
|
|
77
|
-
|
|
147
|
+
Cada agente imprime **só a própria seção**, nunca o arquivo inteiro. Fichas individuais em [`4-agentes/`](../4-agentes/README.md) linkam para essa referência quando relevante.
|
|
78
148
|
|
|
79
149
|
---
|
|
80
150
|
|
|
81
|
-
##
|
|
151
|
+
## Confiabilidade
|
|
152
|
+
|
|
153
|
+
Três ajustes de confiabilidade que afetam diretamente quem usa o autopilot:
|
|
154
|
+
|
|
155
|
+
- **Estado de workflow obsoleto não bloqueia mais a próxima feature.** Um `workflow.state.json` deixado por uma feature já fechada/abandonada é descartado e re-semeado automaticamente — só uma feature *genuinamente* ativa e diferente (aparece como `in_progress` em `features.md`) segue gerando o refuso `different_active_feature`. Nesse caso, a orientação é fechar/pausar a feature ativa ou rodar:
|
|
82
156
|
|
|
83
|
-
|
|
157
|
+
```bash
|
|
158
|
+
aioson feature:sweep .
|
|
159
|
+
```
|
|
84
160
|
|
|
85
|
-
|
|
86
|
-
2. **Primeira entrada em `@dev`** — a cadeia pré-dev para aqui.
|
|
87
|
-
3. **Cap de correções atingido** — ciclo `@qa` ↔ `@dev` limitado a 2 rounds.
|
|
88
|
-
4. **Finding crítico de segurança** — keywords auth/secret/credential/session/password/token/PII detectados pelo gate de segurança do `@qa` → STOP, requer intervenção humana.
|
|
89
|
-
5. **Verdict não passou** — `@scope-check` não aprovado, `@architect` Gate B bloqueado, `@discovery-design-doc` readiness bloqueado, `@pm` Gate C bloqueado, `@validator` FAIL sem caminho seguro → STOP, roteamento manual.
|
|
90
|
-
6. **Orçamento de contexto** — uso ≥ `context_warning_threshold`: grava checkpoint em `last-handoff.json`, STOP, recomenda `/compact` para continuidade na mesma feature. A próxima sessão reentra no autopilot automaticamente. Use `/clear` apenas para reset forte, troca de feature, contexto poluído ou reset sensível a segurança.
|
|
91
|
-
7. **Ambiguidade** — estado do workflow indisponível ou qualquer decisão real requer input humano → STOP.
|
|
161
|
+
- **`aioson update` agora informa exatamente qual template chegou**, incluindo o build exato de uma instalação `npm link`:
|
|
92
162
|
|
|
93
|
-
|
|
163
|
+
```
|
|
164
|
+
Template version applied: 1.36.0 (a1b2c3d, 2026-07-01)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
(o `(sha, data)` só aparece quando a instalação vem de um checkout git — ex.: dogfooding via `npm link`; instalações normais via npm mostram só a versão semântica.)
|
|
168
|
+
|
|
169
|
+
- **A lane lean não regride mais para `@sheldon` depois da implementação.** Antes, nada resolvia o estágio `sheldon` na máquina de estados, então `aioson workflow:next --complete=dev` reativava `@sheldon` (uma ativação para trás). O estágio `sheldon` agora é reconhecido como concluído junto com o resto da cadeia — completar um estágio posterior nunca deixa `next` apontando para um estágio anterior já resolvido.
|
|
94
170
|
|
|
95
171
|
---
|
|
96
172
|
|
|
@@ -98,11 +174,10 @@ O usuário pode interromper a qualquer momento com Ctrl+C. O autopilot nunca ret
|
|
|
98
174
|
|
|
99
175
|
Quando autopilot está ativo e nenhuma condição de parada aplica:
|
|
100
176
|
|
|
101
|
-
1. O agente termina suas responsabilidades (artefatos em disco, gate, dossier, `
|
|
102
|
-
2.
|
|
103
|
-
3.
|
|
104
|
-
|
|
105
|
-
O roteamento é **determinístico** — nunca escolhido pelo modelo. O próximo agente vem do state machine do workflow (`workflow.state.json` ou `aioson workflow:status .`), não de julgamento do LLM.
|
|
177
|
+
1. O agente termina suas responsabilidades de fechamento primeiro (artefatos em disco, registro de gate, dossier/spec atualizados, `agent:epilogue`/`agent:done`).
|
|
178
|
+
2. Se o checkpoint do runtime contém `agentic_policy.enabled=true`, deixa o gateway continuar a partir de `.aioson/context/workflow-execute.json` — não pergunta ao usuário para confirmar o próximo estágio determinístico.
|
|
179
|
+
3. Sem gateway de runtime disponível, emite um aviso de transição de uma linha: `Autopilot: @<atual> done → invocando @<próximo> (Ctrl+C para interromper)`.
|
|
180
|
+
4. Invoca `Skill(aioson:agent:<próximo>)` com a tarefa `"continue feature {slug} — autopilot handoff from @<atual>"`. Sem pergunta ao usuário — Ctrl+C interrompe.
|
|
106
181
|
|
|
107
182
|
---
|
|
108
183
|
|
|
@@ -112,33 +187,73 @@ O roteamento é **determinístico** — nunca escolhido pelo modelo. O próximo
|
|
|
112
187
|
# Ver estado atual do workflow (qual agente está ativo)
|
|
113
188
|
aioson workflow:status .
|
|
114
189
|
|
|
190
|
+
# Semear o esquema agêntico sem avançar (agentes de spec fazem isso ao terminar)
|
|
191
|
+
aioson workflow:execute . --feature=checkout --seed --tool=claude
|
|
192
|
+
|
|
193
|
+
# Semear já desarmado (equivalente ao token --step)
|
|
194
|
+
aioson workflow:execute . --feature=checkout --seed --step --tool=claude
|
|
195
|
+
|
|
115
196
|
# Avançar manualmente (quando autopilot está desligado)
|
|
116
197
|
aioson workflow:next .
|
|
117
198
|
|
|
199
|
+
# Limpar workflow.state.json obsoleto de features já fechadas/abandonadas
|
|
200
|
+
aioson feature:sweep .
|
|
201
|
+
|
|
118
202
|
# Ver handoff preparado pelo agente anterior
|
|
119
203
|
cat .aioson/context/last-handoff.json
|
|
120
204
|
```
|
|
121
205
|
|
|
122
206
|
---
|
|
123
207
|
|
|
124
|
-
## Exemplo: feature
|
|
208
|
+
## Exemplo: feature SMALL com autopilot ativo do início ao fim
|
|
125
209
|
|
|
126
210
|
```
|
|
127
|
-
Você > /
|
|
211
|
+
Você > /product --auto build email notifications
|
|
212
|
+
|
|
213
|
+
@product > PRD fechado. Modo Autopilot (token inline) — sem pergunta.
|
|
214
|
+
Semeando esquema... invocando @sheldon.
|
|
215
|
+
|
|
216
|
+
@sheldon > Gates A/B/C aprovados. dev-state.md gravado.
|
|
217
|
+
Autopilot: @sheldon done → invocando @dev.
|
|
218
|
+
|
|
219
|
+
@dev > Loop de fases: 3 fases implementadas em sequência, sem parar.
|
|
220
|
+
Testes verdes. Autopilot: @dev done → invocando @qa.
|
|
221
|
+
|
|
222
|
+
@qa > PASS. Nenhum trigger de @tester/@pentester disparou. Sem harness-contract pendente.
|
|
223
|
+
STOP — "Recomendo: aioson feature:close . --feature=email-notifications"
|
|
224
|
+
|
|
225
|
+
Você > aioson feature:close . --feature=email-notifications
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Exemplo: feature MEDIUM, escolha feita on-screen no handoff do `@product`
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
Você > /product
|
|
234
|
+
|
|
235
|
+
@product > [conduz o PRD...] PRD fechado. Classificação: MEDIUM.
|
|
236
|
+
Como você quer rodar esta feature?
|
|
237
|
+
1. Autopilot — rodar tudo até feature:close (recomendado)
|
|
238
|
+
2. Passo a passo — eu conduzo cada etapa
|
|
239
|
+
3. Sempre autopilot neste projeto
|
|
240
|
+
|
|
241
|
+
Você > 3
|
|
242
|
+
|
|
243
|
+
@product > Gravado auto_handoff: true em project.context.md.
|
|
244
|
+
Semeando esquema... invocando @orchestrator.
|
|
245
|
+
|
|
246
|
+
@orchestrator > Fan-out @analyst + @architect + @pm (+ @ux-ui). Gates A/B/C aprovados.
|
|
247
|
+
Autopilot: @orchestrator done → invocando @dev.
|
|
128
248
|
|
|
129
|
-
@
|
|
130
|
-
@scope-check → pre-dev check → autopilot: invocando @architect
|
|
131
|
-
@architect → Gate B PASS → autopilot: invocando @discovery-design-doc
|
|
132
|
-
@discovery-design-doc → readiness ok → autopilot: invocando @pm (MEDIUM)
|
|
133
|
-
@pm → Gate C PASS → STOP — "Recomendo /compact e /dev"
|
|
249
|
+
@dev > Implementado. Autopilot: @dev done → invocando @pentester (inline no MEDIUM).
|
|
134
250
|
|
|
135
|
-
|
|
136
|
-
Você > /dev (implementação manual — contexto limpo)
|
|
251
|
+
@pentester > Sem findings HIGH/CRITICAL. Autopilot: @pentester done → invocando @qa.
|
|
137
252
|
|
|
138
|
-
@
|
|
139
|
-
@qa → PASS + nenhum trigger → STOP — "Execute: aioson feature:close . --feature=..."
|
|
253
|
+
@qa > PASS. Harness-contract presente, @validator ainda não PASS → invocando @validator.
|
|
140
254
|
|
|
141
|
-
|
|
255
|
+
@validator > Contexto fresco e isolado. harness:check + julgamento LLM dos critérios sem verification.
|
|
256
|
+
PASS. STOP — "Recomendo: aioson feature:close . --feature=..."
|
|
142
257
|
```
|
|
143
258
|
|
|
144
259
|
---
|
|
@@ -159,6 +274,8 @@ Veja [SDD Automation Scripts — forge:compile](./sdd-automation-scripts.md#forg
|
|
|
159
274
|
|
|
160
275
|
## Próximos passos
|
|
161
276
|
|
|
162
|
-
- [SDD Framework](./sdd-framework.md) — sequência completa MICRO/SMALL/MEDIUM
|
|
163
|
-
- [Comandos CLI](./comandos-cli.md) — `workflow:next`, `workflow:
|
|
277
|
+
- [SDD Framework](./sdd-framework.md) — sequência completa MICRO/SMALL/MEDIUM e as lanes lean/maestro
|
|
278
|
+
- [Comandos CLI](./comandos-cli.md) — `workflow:next`, `workflow:execute`, `feature:sweep`
|
|
164
279
|
- [Motor Hardening](./motor-hardening.md) — gates técnicos e auto-cura
|
|
280
|
+
- [Ficha do @product](../4-agentes/product.md) — onde o modo de execução é decidido
|
|
281
|
+
- [Ficha do @dev](../4-agentes/dev.md) — `--auto`/`--step` na entrada da implementação
|
|
@@ -226,10 +226,10 @@ Scripts determinísticos que movem verificações de estado, validação de arte
|
|
|
226
226
|
| `feature:archive` | Move artefatos de uma feature `done` para `.aioson/context/done/{slug}/` e atualiza o manifest | Chamado pelo `feature:close` automaticamente; também disponível para retroativo com `--dry-run` e `--restore` |
|
|
227
227
|
| `feature:export` | **Copia** todos os artefatos de uma feature para um `--out` limpo, sem mexer na origem; gera `INDEX.md` | Exportar specs para analisar fora, entregar a cliente, ou usar o AIOSON só como gerador de specs. Veja [feature-export.md](./feature-export.md) |
|
|
228
228
|
| `gate:check` | Valida pré-requisitos e artefatos de um phase gate (A/B/C/D); retorna PASS ou BLOCKED | Antes de avançar para o próximo agente |
|
|
229
|
-
| `artifact:validate` | Verifica a cadeia completa de artefatos de uma feature (PRD → spec → plano → conformance) | A qualquer momento para checar completude |
|
|
230
|
-
| `ac:test-audit` | Mapeia cada `AC-*` declarado em PRD/requisitos/conformance para evidência em testes ou critério executável do harness | Antes do Gate D; falha quando algum AC não tem prova de teste |
|
|
231
|
-
| `sdd:benchmark` | Gera score determinístico de SDD usando cadeia de artefatos, `spec:analyze` e `ac:test-audit`; salva relatório em `.aioson/context/retro/` | Para medir a qualidade do harness sem depender de julgamento solto |
|
|
232
|
-
| `spec:analyze` | Irmão de **conteúdo** do `artifact:validate`: consistência cruzada entre os artefatos (rastreabilidade REQ/AC, staleness, readiness, sanidade do contrato, vínculo AC→contrato, overlap de waves) antes do gate de execução | No preflight do `@scope-check` — errors viram blockers, warnings viram evidência de drift |
|
|
229
|
+
| `artifact:validate` | Verifica a cadeia completa de artefatos de uma feature (PRD → spec → plano → conformance) | A qualquer momento para checar completude |
|
|
230
|
+
| `ac:test-audit` | Mapeia cada `AC-*` declarado em PRD/requisitos/conformance para evidência em testes ou critério executável do harness | Antes do Gate D; falha quando algum AC não tem prova de teste |
|
|
231
|
+
| `sdd:benchmark` | Gera score determinístico de SDD usando cadeia de artefatos, `spec:analyze` e `ac:test-audit`; salva relatório em `.aioson/context/retro/` | Para medir a qualidade do harness sem depender de julgamento solto |
|
|
232
|
+
| `spec:analyze` | Irmão de **conteúdo** do `artifact:validate`: consistência cruzada entre os artefatos (rastreabilidade REQ/AC, staleness, readiness, sanidade do contrato, vínculo AC→contrato, overlap de waves) antes do gate de execução | No preflight do `@scope-check` — errors viram blockers, warnings viram evidência de drift |
|
|
233
233
|
| `forge:compile` | **Lane B:** compila os artefatos de uma feature MEDIUM num `forge-run.workflow.js` auditável e versionável (parallel por Wave → convergência no `harness:check` → revisão adversarial → validador fresh-context) | Quando quer execução compilada e reproduzível via `@forge-run`; nunca roda `feature:close`/publish |
|
|
234
234
|
| `workflow:execute` | Monta e executa o plano de agentes baseado na classificação; aceita `--dry-run` e `--start-from` | Para orquestrar features sem o dashboard |
|
|
235
235
|
| `runner:run` | Executa uma tarefa ou worker diretamente pelo runner | Quando quer executar fora do loop principal de sessão |
|
|
@@ -375,7 +375,7 @@ aioson update .
|
|
|
375
375
|
aioson doctor . --fix
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
-
Use depois de atualizar a versão do pacote. O `update` mexe só nos arquivos gerenciados e o `doctor --fix` recoloca o que estiver faltando.
|
|
378
|
+
Use depois de atualizar a versão do pacote. O `update` mexe só nos arquivos gerenciados e o `doctor --fix` recoloca o que estiver faltando. Ao terminar, `update` imprime `Template version applied: <versão>` (e `(<sha>, <data>)` quando a instalação vem de um checkout git, como um `npm link` de dogfooding) — assim dá para conferir exatamente qual template chegou.
|
|
379
379
|
|
|
380
380
|
### 4. Ver e ajustar configurações globais
|
|
381
381
|
|
|
@@ -989,7 +989,7 @@ O manifest em `.aioson/context/done/MANIFEST.md` registra todas as features arqu
|
|
|
989
989
|
# Verificar se está no safe zone (< 60%), warning (60–80%) ou critical (≥ 80%)
|
|
990
990
|
aioson context:monitor . --budget=80000 --tokens=52000
|
|
991
991
|
# ⚠ Context: 52,000 tokens (65%) — WARNING
|
|
992
|
-
# Suggestion: /compact before next agent activation; use /clear only for a hard reset
|
|
992
|
+
# Suggestion: /compact before next agent activation; use /clear only for a hard reset
|
|
993
993
|
|
|
994
994
|
# Verificar com output JSON para integrar em scripts
|
|
995
995
|
aioson context:monitor . --budget=80000 --tokens=67000 --json
|
|
@@ -1742,8 +1742,17 @@ aioson workflow:execute . \
|
|
|
1742
1742
|
--feature=checkout \
|
|
1743
1743
|
--tool=claude \
|
|
1744
1744
|
--start-from=dev
|
|
1745
|
+
|
|
1746
|
+
# Semear o esquema agêntico do full-feature autopilot sem avançar estágio
|
|
1747
|
+
# (é isso que @product/@sheldon/@orchestrator rodam ao terminar sob autopilot)
|
|
1748
|
+
aioson workflow:execute . --feature=checkout --seed --tool=claude
|
|
1749
|
+
|
|
1750
|
+
# Semear já desarmado — equivalente ao token inline /product --step
|
|
1751
|
+
aioson workflow:execute . --feature=checkout --seed --step --tool=claude
|
|
1745
1752
|
```
|
|
1746
1753
|
|
|
1754
|
+
Um `workflow.state.json` deixado por uma feature já fechada/abandonada é descartado e re-semeado automaticamente — só uma feature *diferente e genuinamente ativa* (`in_progress` em `features.md`) gera o refuso `different_active_feature`. Nesse caso, feche/pause a feature ativa ou rode `aioson feature:sweep .` para limpar o estado obsoleto. Veja [Autopilot Handoff](./autopilot-handoff.md) para a cadeia completa, os tokens `--auto`/`--step` e as condições de parada.
|
|
1755
|
+
|
|
1747
1756
|
### 52. Enfileirar fases do plano no runner
|
|
1748
1757
|
|
|
1749
1758
|
```bash
|
|
@@ -1877,7 +1886,7 @@ aioson harness:retro . --last=5
|
|
|
1877
1886
|
aioson harness:retro . --feature=checkout --json
|
|
1878
1887
|
```
|
|
1879
1888
|
|
|
1880
|
-
Saída em `.aioson/context/retro/checkout.md`. Fontes mineradas: QA reports, reports de verificação de implementação (apenas findings não confirmatórios do `Machine Report`), planos de correção, trilha FAIL→PASS do dossier, eventos de execução, tentativas, assinaturas de falha e devlogs. Raw auditor output, stderr, prompts e texto de evidence não são minerados. Operação de leitura — arquivos-fonte nunca são alterados.
|
|
1889
|
+
Saída em `.aioson/context/retro/checkout.md`. Fontes mineradas: QA reports, reports de verificação de implementação (apenas findings não confirmatórios do `Machine Report`), planos de correção, trilha FAIL→PASS do dossier, eventos de execução, tentativas, assinaturas de falha e devlogs. Raw auditor output, stderr, prompts e texto de evidence não são minerados. Operação de leitura — arquivos-fonte nunca são alterados.
|
|
1881
1890
|
|
|
1882
1891
|
```bash
|
|
1883
1892
|
# Exibir prévia de um artefato com truncação segura
|
|
@@ -158,6 +158,8 @@ Skills de design disponíveis:
|
|
|
158
158
|
- `interface-design` — Design de interfaces (dashboards, apps, ferramentas)
|
|
159
159
|
- `premium-command-center-ui` — UI premium para command centers
|
|
160
160
|
|
|
161
|
+
A skill `interface-design` é um **motor**: antes de desenhar, ela resolve um `identity.md` — primeiro `.aioson/briefings/{slug}/identity.md`, depois `.aioson/context/identity.md` — e aplica essa identidade em tudo que produz; sem `identity.md`, roda intent-first. O `identity.md` é extraído **uma única vez** das suas imagens de referência pela skill de processo `reference-identity-extract`, e todo consumidor do motor (`@dev`, `@ux-ui`, protótipos) herda essa resolução. Ele é *input* do motor — não um segundo design skill.
|
|
162
|
+
|
|
161
163
|
## Skills de processo
|
|
162
164
|
|
|
163
165
|
Skills de processo ensinam os agentes **como as fases do AIOSON se conectam** — não o que implementar, mas como sequenciar, quando aprofundar e como fazer handoff limpo entre agentes.
|
package/docs/pt/agentes.md
CHANGED
|
@@ -56,9 +56,11 @@ O AIOSON tem agentes oficiais de projeto e também pode criar agentes de squad.
|
|
|
56
56
|
Quando o projeto ja existe e voce roda `scan:project`, o handoff correto agora e:
|
|
57
57
|
|
|
58
58
|
```text
|
|
59
|
-
scan:project -> @analyst -> @scope-check -> @architect
|
|
59
|
+
scan:project -> @analyst -> @scope-check -> @dev (ou @architect quando o full-merged opt-in do SMALL estiver ativo)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
> Desde a lane lean (v1.35.0), `@analyst`/`@architect` nao sao mais hops obrigatorios do fluxo padrao apos o scan. A classificacao decide a lane: SMALL segue `@sheldon` como autoridade unica de spec (`@product -> @sheldon -> @dev`), MEDIUM segue o maestro `@orchestrator` (que dispara `@analyst`/`@architect`/`@pm` como sub-agentes do fan-out). Um `@analyst` isolado logo apos o scan continua valido como detour opt-in para mapear o dominio existente antes de abrir a primeira feature.
|
|
63
|
+
|
|
62
64
|
Regras do fluxo:
|
|
63
65
|
- os artefatos locais do scan (`scan-index.md`, `scan-folders.md`, `scan-<pasta>.md`, `scan-aioson.md`) servem como mapas brutos do codigo
|
|
64
66
|
- `discovery.md` continua sendo a memoria comprimida que os agentes usam para entender o sistema sem reler tudo
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
const { capabilities, requiredCapability } = require('../capabilities');
|
|
4
|
+
const { resolveExecutable } = require('../executable-resolver');
|
|
5
|
+
function redact(text) { return String(text||'').replace(/(authorization\s*[:=]\s*(?:bearer\s+)?|api[_-]?key\s*[:=]\s*|token\s*[:=]\s*|password\s*[:=]\s*)[^\s,;]+/gi,'$1[REDACTED]'); }
|
|
6
|
+
function normalizeError(error) { const text=String(error?.message||error||'').toLowerCase(); if(/unexpected argument|invalid (?:argument|option)|unknown option/.test(text))return'invalid_arguments';if(/capacity|overloaded|rate limit/.test(text)) return 'capacity'; if(/model.*invalid|unknown model/.test(text)) return 'invalid_model'; if(/unauthorized|authentication failed|not authenticated|forbidden/.test(text)) return 'auth'; if(/timeout|timed out/.test(text)) return 'timeout'; return 'crash'; }
|
|
7
|
+
function createAdapter(host, buildArgs) {
|
|
8
|
+
return {
|
|
9
|
+
host,
|
|
10
|
+
probe() { return capabilities(host); },
|
|
11
|
+
build(input) { const cap=this.probe(); const required=requiredCapability(input.mode); if(required && !cap[required]) return {ok:false,reason:'unsupported_capability',capability:required,host};if(input.reasoning_effort&&!cap.reasoning_effort)return{ok:false,reason:'unsupported_reasoning_effort',capability:'reasoning_effort',host};if(input.writable_roots?.length&&!cap.additional_workspaces)return{ok:false,reason:'host_capability_missing',capability:'additional_workspaces',host};const command=buildArgs(input);const args=Array.isArray(command)?command:command.args;return {ok:true,executable:cap.executable,args,stdin:Boolean(command.stdin),options:{cwd:input.cwd,shell:false,stdio:'pipe'}}; },
|
|
12
|
+
async execute(input) { const built=this.build(input); if(!built.ok) return built;let resolved;try{resolved=await resolveExecutable(built.executable,input.resolverOptions)}catch(error){return{ok:false,reason:'executable_not_found',error:redact(error.message)}}return new Promise(resolve=>{ const child=spawn(resolved.executable,[...resolved.prefixArgs,...built.args],built.options); input.onSpawn?.(child.pid,new Date().toISOString());let stderr='';let timedOut=false;let settled=false;child.stdout.on('data',d=>input.onStdout?.(d));child.stderr.on('data',d=>{input.onStderr?.(d);stderr+=d;if(stderr.length>4000)stderr=stderr.slice(-4000)});if(built.stdin)child.stdin.end(String(input.prompt_text||''));else child.stdin.end();const finish=result=>{if(settled)return;settled=true;if(timer)clearTimeout(timer);child.stdout.destroy();child.stderr.destroy();input.onExit?.(result);resolve(result)};const timer=input.timeout?setTimeout(()=>{timedOut=true;child.kill()},input.timeout):null;child.on('error',e=>finish({ok:false,reason:normalizeError(e),error:redact(e.message)}));child.on('exit',code=>finish(code===0&&!timedOut?{ok:true,code,resolver_source:resolved.source}:{ok:false,code,reason:timedOut?'timeout':normalizeError(stderr),error:redact(stderr).slice(0,1000),resolver_source:resolved.source})); }); }
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
module.exports={createAdapter,normalizeError,redact};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createAdapter } = require('./base');
|
|
3
|
+
module.exports=createAdapter('codex',i=>({args:['exec',...(i.model==='configured-default'?[]:['--model',i.model]),...(i.reasoning_effort?['-c',`model_reasoning_effort="${i.reasoning_effort}"`]:[]),...(i.writable_roots||[]).flatMap(root=>['--add-dir',root]),'-'],stdin:true}));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const MATRIX = {
|
|
3
|
+
claude: { native_subagent: false, fresh_session: false, external_process: true, additional_workspaces: true, model_catalog: false, reasoning_effort: false, executable: 'claude' },
|
|
4
|
+
codex: { native_subagent: false, fresh_session: false, external_process: true, additional_workspaces: true, model_catalog: true, reasoning_effort: true, executable: 'codex' },
|
|
5
|
+
opencode: { native_subagent: false, fresh_session: false, external_process: true, additional_workspaces: false, model_catalog: false, reasoning_effort: false, executable: 'opencode' }
|
|
6
|
+
};
|
|
7
|
+
function capabilities(host) { return { ...(MATRIX[host] || {}), source: 'registered_adapter' }; }
|
|
8
|
+
function requiredCapability(mode) { return mode === 'subagent' ? 'native_subagent' : mode === 'fresh-session' ? 'fresh_session' : mode === 'external' ? 'external_process' : null; }
|
|
9
|
+
module.exports = { MATRIX, capabilities, requiredCapability };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('node:fs/promises');const path=require('node:path');const crypto=require('node:crypto');
|
|
3
|
+
const adapters={claude:require('./adapters/claude'),codex:require('./adapters/codex'),opencode:require('./adapters/opencode')};
|
|
4
|
+
const {loadManifest,resolveAgentExecution,resolveExecutionEntry}=require('./manifest');
|
|
5
|
+
const {safeReportPath,validateReport}=require('./reports');
|
|
6
|
+
const {createTelemetryBridge}=require('./telemetry-bridge');
|
|
7
|
+
const TERMINAL=['approved','failed','cancelled'];const LEASE_MS=30000;
|
|
8
|
+
function statePath(projectDir,feature){return path.join(projectDir,'.aioson','context',`agent-execution-state-${feature}.json`)}
|
|
9
|
+
function leasePath(projectDir,feature){return `${statePath(projectDir,feature)}.lock`}
|
|
10
|
+
async function atomic(file,value){await fs.mkdir(path.dirname(file),{recursive:true});const tmp=`${file}.${process.pid}.${crypto.randomUUID()}.tmp`;await fs.writeFile(tmp,`${JSON.stringify(value,null,2)}\n`);await fs.rename(tmp,file)}
|
|
11
|
+
async function readState(projectDir,feature){try{return JSON.parse(await fs.readFile(statePath(projectDir,feature),'utf8'))}catch(e){if(e.code==='ENOENT')return null;throw e}}
|
|
12
|
+
async function acquireLease(projectDir,feature){const file=leasePath(projectDir,feature);await fs.mkdir(path.dirname(file),{recursive:true});const owner=crypto.randomUUID();for(let pass=0;pass<2;pass++){try{const h=await fs.open(file,'wx');await h.writeFile(JSON.stringify({owner,expires_at:Date.now()+LEASE_MS}));await h.close();return{file,owner}}catch(e){if(e.code!=='EEXIST')throw e;try{const current=JSON.parse(await fs.readFile(file,'utf8'));if(current.expires_at>Date.now())return null;await fs.unlink(file)}catch(readError){if(readError.code!=='ENOENT'&&readError.name!=='SyntaxError')throw readError}}}return null}
|
|
13
|
+
async function releaseLease(lease){if(!lease)return;try{const current=JSON.parse(await fs.readFile(lease.file,'utf8'));if(current.owner===lease.owner)await fs.unlink(lease.file)}catch(e){if(e.code!=='ENOENT')throw e}}
|
|
14
|
+
async function safePromptPath(projectDir,promptPath){const root=await fs.realpath(projectDir);const candidate=path.resolve(root,promptPath||'.aioson/context/dev-state.md');const real=await fs.realpath(candidate);if(real!==root&&!real.startsWith(root+path.sep))throw new Error('prompt_path escapes project workspace');const stat=await fs.stat(real);if(!stat.isFile())throw new Error('prompt_path must be a regular file');return real}
|
|
15
|
+
async function resolveWritableRoots(projectDir,roots=[]){const resolved=[];for(const configured of roots){if(typeof configured!=='string'||!configured.trim())throw new Error('writable_root must be a non-empty path');if(configured.split(/[\\/]+/).includes('..'))throw new Error(`writable_root traversal is forbidden: ${configured}`);const candidate=path.resolve(projectDir,configured);let real;try{real=await fs.realpath(candidate)}catch{throw new Error(`writable_root does not exist: ${configured}`)}if(!(await fs.stat(real)).isDirectory())throw new Error(`writable_root is not a directory: ${configured}`);if(!resolved.includes(real))resolved.push(real)}return resolved}
|
|
16
|
+
function capacitySequence(manifest,resolved){const policy=manifest.capacity_policy;if(policy.strategy!=='fallback')return[];return resolved.fallbacks.filter(item=>policy.allow_cross_host===true||item.host===resolved.host)}
|
|
17
|
+
function applyCapacityPolicy(manifest,resolved,attemptCount=0){const p=manifest.capacity_policy;if(attemptCount>=p.max_attempts)return{action:'pause',reason:'capacity_limit'};if(p.strategy==='fallback'){const sequence=capacitySequence(manifest,resolved);return sequence.length?{action:'fallback',sequence}:{action:'pause',reason:'no_authorized_fallback'}}if(p.strategy==='retry'||p.strategy==='wait')return{action:p.strategy,backoff_ms:p.backoff_ms};return{action:'pause'}}
|
|
18
|
+
async function executeWithCapacityPolicy({manifest,resolved,input,adapterRegistry=adapters,catalogLoader,wait=(ms)=>new Promise(resolve=>setTimeout(resolve,ms))}) {
|
|
19
|
+
const history=[];
|
|
20
|
+
const primary=resolved.ok?resolved:await resolveExecutionEntry(resolved,{catalogLoader});
|
|
21
|
+
if(!primary.ok)return{ok:false,reason:primary.reason,candidates:primary.candidates||[],history};
|
|
22
|
+
const candidates=[primary,...capacitySequence(manifest,resolved).map(item=>({...resolved,ok:false,host:item.host,model:item.model,model_requested:undefined}))];
|
|
23
|
+
let index=0;
|
|
24
|
+
for(let count=0;count<manifest.capacity_policy.max_attempts;count++){
|
|
25
|
+
let candidate=candidates[index];
|
|
26
|
+
if(!candidate.ok){
|
|
27
|
+
candidate=await resolveExecutionEntry(candidate,{catalogLoader});
|
|
28
|
+
candidates[index]=candidate;
|
|
29
|
+
if(!candidate.ok){
|
|
30
|
+
history.push({attempt:count+1,host:candidate.host,model_requested:candidate.model_requested||candidate.model,model:null,reason:candidate.reason});
|
|
31
|
+
return{ok:false,reason:candidate.reason,candidates:candidate.candidates||[],history};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const adapter=adapterRegistry[candidate.host];
|
|
35
|
+
if(!adapter)return{ok:false,reason:'unsupported_host',history};
|
|
36
|
+
const prompt_text=String(input.prompt_text||'')
|
|
37
|
+
.replace(/host=[^,\n]+/,`host=${candidate.host}`)
|
|
38
|
+
.replace(/model_resolved=[^,\n]+/,`model_resolved=${candidate.model}`)
|
|
39
|
+
.replace(/model_resolution_strategy=[^,\n]+/,`model_resolution_strategy=${candidate.model_resolution_strategy}`);
|
|
40
|
+
const result=await adapter.execute({...input,prompt_text,mode:candidate.mode,model:candidate.model,reasoning_effort:candidate.reasoning_effort});
|
|
41
|
+
history.push({attempt:count+1,host:candidate.host,model_requested:candidate.model_requested,model:candidate.model,model_resolution_strategy:candidate.model_resolution_strategy,reasoning_effort:candidate.reasoning_effort,reason:result.reason||null});
|
|
42
|
+
if(result.ok)return{...result,host:candidate.host,model:candidate.model,model_requested:candidate.model_requested,model_resolution_strategy:candidate.model_resolution_strategy,reasoning_effort:candidate.reasoning_effort,history};
|
|
43
|
+
if(result.reason!=='capacity')return{...result,history};
|
|
44
|
+
const decision=applyCapacityPolicy(manifest,resolved,count);
|
|
45
|
+
if(decision.action==='pause')return{ok:false,reason:decision.reason||'capacity',history};
|
|
46
|
+
if(decision.action==='fallback'){
|
|
47
|
+
if(index+1>=candidates.length)return{ok:false,reason:'fallback_exhausted',history};
|
|
48
|
+
index+=1;
|
|
49
|
+
}else if(decision.action==='wait'||decision.action==='retry'){
|
|
50
|
+
if(decision.backoff_ms)await wait(decision.backoff_ms);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return{ok:false,reason:'capacity_limit',history};
|
|
54
|
+
}
|
|
55
|
+
function buildExecutionPrompt(promptText,expected,reportPath){return `${promptText}\n\nAIOSON EXECUTION CONTRACT\nComplete the requested agent work, then write exactly one JSON report to: ${reportPath}\nThe report must include: version=1, feature=${expected.feature}, run_id=${expected.run_id}, attempt_id=${expected.attempt_id}, agent=${expected.agent}, host=${expected.host}, model_requested=${expected.model_requested}, model_resolved=${expected.model_resolved}, model_resolution_strategy=${expected.model_resolution_strategy}, reasoning_effort=${expected.reasoning_effort||'null'}, manifest_digest=${expected.manifest_digest}, writable_roots=${JSON.stringify(expected.writable_roots)}, started_at, finished_at, verdict (PASS|FAIL|BLOCKED), findings[], evidence[]. Do not report PASS unless the work and verification completed.`}
|
|
56
|
+
async function readBoundReport(projectDir,relative,expected){const file=safeReportPath(projectDir,relative);let report;try{report=JSON.parse(await fs.readFile(file,'utf8'))}catch(error){return{ok:false,reason:error.code==='ENOENT'?'report_missing':'report_invalid_json',error:error.message}}const validation=validateReport(report,expected);return validation.ok?{ok:true,file,report}:{ok:false,reason:'report_binding_invalid',errors:validation.errors}}
|
|
57
|
+
async function dispatch({projectDir,feature,agent='dev',runId,promptPath,adapterRegistry=adapters,catalogLoader,timeout=600000}){
|
|
58
|
+
const lease=await acquireLease(projectDir,feature);if(!lease)return{ok:false,status:'paused',reason:'lease_held',resume_command:`aioson agent:execution:resume . --feature=${feature}`};
|
|
59
|
+
try{const loaded=await loadManifest(projectDir,feature);if(!loaded.exists)return{ok:false,status:'paused',reason:'manifest_missing',resume_command:`aioson agent:execution:resume . --feature=${feature}`};if(!loaded.ok)return{ok:false,status:'paused',reason:'manifest_invalid',errors:loaded.errors};
|
|
60
|
+
let state=await readState(projectDir,feature);if(state&&state.manifest_digest!==loaded.digest&&!TERMINAL.includes(state.status))return{ok:false,status:'paused',reason:'manifest_digest_changed',expected:state.manifest_digest,actual:loaded.digest};if(state?.completed_agents?.includes(agent))return{ok:true,status:state.status,idempotent:true,state};
|
|
61
|
+
let safePrompt;try{safePrompt=await safePromptPath(projectDir,promptPath)}catch(error){return{ok:false,status:'paused',reason:'invalid_prompt_path',error:error.message}}
|
|
62
|
+
const resolved=await resolveAgentExecution(loaded.manifest,agent,{}, {catalogLoader});if(!resolved.ok)return{ok:false,status:'paused',reason:resolved.reason,candidates:resolved.candidates||[],supported:resolved.supported||[]};const adapter=adapterRegistry[resolved.host];const run_id=runId||state?.run_id||crypto.randomUUID();const attempt_id=crypto.randomUUID();if(!adapter)return{ok:false,status:'paused',reason:'unsupported_host'};let writableRoots;try{writableRoots=await resolveWritableRoots(projectDir,resolved.writable_roots)}catch(error){return{ok:false,status:'paused',reason:'invalid_writable_root',error:error.message}}const reportRelative=resolved.report.replace(/\{run_id\}/g,run_id);const reportFile=safeReportPath(projectDir,reportRelative);const expected={feature,run_id,attempt_id,agent,host:resolved.host,model_requested:resolved.model_requested,model_resolved:resolved.model,model_resolution_strategy:resolved.model_resolution_strategy,reasoning_effort:resolved.reasoning_effort,manifest_digest:loaded.digest,writable_roots:writableRoots,status:'running'};const promptText=await fs.readFile(safePrompt,'utf8');const executionInput={mode:resolved.mode,model:resolved.model,reasoning_effort:resolved.reasoning_effort,writable_roots:writableRoots,cwd:projectDir,prompt_text:buildExecutionPrompt(promptText,expected,path.relative(projectDir,reportFile)),timeout};const built=adapter.build(executionInput);
|
|
63
|
+
const correlation={feature,agent,dispatcher_run_id:run_id,attempt_id,host:resolved.host,model:resolved.model,model_requested:resolved.model_requested,model_resolved:resolved.model,reasoning_effort:resolved.reasoning_effort,model_resolution_strategy:resolved.model_resolution_strategy,catalog_source:resolved.catalog_source};const telemetry=await createTelemetryBridge(projectDir,correlation);
|
|
64
|
+
const attempt={...expected,telemetry_run_id:telemetry.run.telemetry_run_id,status:built.ok?'running':'blocked',reason:built.reason||null,report:reportRelative};state={version:1,feature,run_id,manifest_path:path.relative(projectDir,loaded.path),manifest_digest:loaded.digest,status:built.ok?'running':'paused',reason:built.reason||null,attempts:[...(state?.attempts||[]),attempt],completed_agents:state?.completed_agents||[],updated_at:new Date().toISOString()};await atomic(statePath(projectDir,feature),state);if(!built.ok){telemetry.transition('paused',built.reason);telemetry.close();return{ok:false,status:'paused',reason:built.reason,capability:built.capability,resume_command:`aioson agent:execution:resume . --feature=${feature}`,state}};
|
|
65
|
+
Object.assign(executionInput,{onSpawn:(pid,at)=>telemetry.onSpawn(pid,at),onStdout:d=>telemetry.output('stdout',d),onStderr:d=>telemetry.output('stderr',d)});
|
|
66
|
+
const execution=await executeWithCapacityPolicy({manifest:loaded.manifest,resolved,input:executionInput,adapterRegistry,catalogLoader});attempt.execution_history=execution.history||[];for(const item of attempt.execution_history){if(item.attempt>1)telemetry.event(item.host===resolved.host?'retry':'fallback',`${item.host}/${item.model}`,{attempt:item.attempt,reason:item.reason})}attempt.model_resolved=execution.model||resolved.model;attempt.model_resolution_strategy=execution.model_resolution_strategy||resolved.model_resolution_strategy;attempt.reasoning_effort=execution.reasoning_effort||resolved.reasoning_effort;attempt.host_resolved=execution.host||resolved.host;if(!execution.ok){attempt.status='blocked';attempt.reason=execution.reason;state.status='paused';state.reason=execution.reason;state.updated_at=new Date().toISOString();await atomic(statePath(projectDir,feature),state);if(execution.reason==='timeout')telemetry.event('timeout','Execution timeout');telemetry.transition('paused',execution.reason);telemetry.close();return{ok:false,status:'paused',reason:execution.reason,candidates:execution.candidates||[],execution,state,resume_command:`aioson agent:execution:resume . --feature=${feature}`}}
|
|
67
|
+
telemetry.transition('waiting_report');
|
|
68
|
+
const boundExpected={...expected,host:attempt.host_resolved,model_resolved:attempt.model_resolved,model_resolution_strategy:attempt.model_resolution_strategy,reasoning_effort:attempt.reasoning_effort,status:'running'};const reportResult=await readBoundReport(projectDir,reportRelative,boundExpected);if(!reportResult.ok){attempt.status='blocked';attempt.reason=reportResult.reason;state.status='paused';state.reason=reportResult.reason;state.updated_at=new Date().toISOString();await atomic(statePath(projectDir,feature),state);telemetry.transition('paused',reportResult.reason);telemetry.close();return{ok:false,status:'paused',reason:reportResult.reason,report:reportResult,state,resume_command:`aioson agent:execution:resume . --feature=${feature}`}}
|
|
69
|
+
const digest=crypto.createHash('sha256').update(JSON.stringify(reportResult.report)).digest('hex');telemetry.report(reportResult.report,reportRelative,digest);attempt.status='completed';attempt.verdict=reportResult.report.verdict;state.status=reportResult.report.verdict==='PASS'?'verification_planned':'correcting';telemetry.transition(reportResult.report.verdict==='PASS'?'passed':reportResult.report.verdict==='FAIL'?'failed':'correcting');telemetry.close();if(reportResult.report.verdict==='PASS'&&!state.completed_agents.includes(agent))state.completed_agents.push(agent);state.updated_at=new Date().toISOString();await atomic(statePath(projectDir,feature),state);return{ok:true,status:state.status,run_id,attempt_id,execution,report:reportResult.report,state};
|
|
70
|
+
}finally{await releaseLease(lease)}
|
|
71
|
+
}
|
|
72
|
+
module.exports={acquireLease,applyCapacityPolicy,buildExecutionPrompt,capacitySequence,dispatch,executeWithCapacityPolicy,leasePath,readBoundReport,readState,releaseLease,resolveWritableRoots,safePromptPath,statePath};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs=require('node:fs/promises');const path=require('node:path');
|
|
3
|
+
async function isFile(file){try{return(await fs.stat(file)).isFile()}catch{return false}}
|
|
4
|
+
async function resolveNpmCmdShim(cmdPath){const text=await fs.readFile(cmdPath,'utf8');const dir=path.dirname(cmdPath);const matches=[...text.matchAll(/["'](%~dp0|%dp0%)[\\/]?([^"']+\.(?:js|exe))["']/gi)];const targets=matches.map(match=>path.resolve(dir,match[2].replace(/[\\/]+/g,path.sep))).filter(file=>!/[\\/]node\.exe$/i.test(file));for(const target of targets.reverse()){if(!(await isFile(target)))continue;const real=await fs.realpath(target);return path.extname(real).toLowerCase()==='.js'?{executable:process.execPath,prefixArgs:[real],source:'npm_cmd_js'}:{executable:real,prefixArgs:[],source:'npm_cmd_exe'}}throw new Error(`Unsupported or invalid npm command shim: ${cmdPath}`)}
|
|
5
|
+
async function resolveExecutable(command,{env=process.env,platform=process.platform}={}){if(typeof command!=='string'||!command.trim())throw new Error('Executable is required');const raw=command.trim();if(path.isAbsolute(raw)){if(!(await isFile(raw)))throw new Error(`Executable not found: ${raw}`);return platform==='win32'&&path.extname(raw).toLowerCase()==='.cmd'?resolveNpmCmdShim(raw):{executable:await fs.realpath(raw),prefixArgs:[],source:'absolute'}}if(platform!=='win32')return{executable:raw,prefixArgs:[],source:'path'};const dirs=String(env.PATH||env.Path||'').split(';').filter(Boolean);for(const dir of dirs){for(const ext of ['.exe','.cmd','']){const candidate=path.join(dir,`${raw}${ext}`);if(!(await isFile(candidate)))continue;if(ext==='.cmd')return resolveNpmCmdShim(candidate);if(ext==='.exe')return{executable:await fs.realpath(candidate),prefixArgs:[],source:'windows_exe'};}}
|
|
6
|
+
throw new Error(`Executable not found on PATH: ${raw}`)}
|
|
7
|
+
module.exports={resolveExecutable,resolveNpmCmdShim};
|