@jaimevalasek/aioson 1.23.0 → 1.23.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/docs/en/5-reference/cli-reference.md +85 -0
- package/docs/pt/4-agentes/pm.md +31 -4
- package/docs/pt/5-referencia/README.md +3 -0
- package/docs/pt/5-referencia/autopilot-handoff.md +131 -0
- package/docs/pt/5-referencia/comandos-cli.md +72 -6
- package/docs/pt/5-referencia/harness-retro.md +133 -0
- package/docs/pt/5-referencia/loop-guardrails.md +225 -0
- package/docs/pt/5-referencia/sdd-automation-scripts.md +25 -13
- package/package.json +1 -1
- package/src/cli.js +54 -29
- package/src/commands/agent-epilogue.js +186 -0
- package/src/commands/context-select.js +33 -0
- package/src/commands/preflight-context.js +13 -9
- package/src/commands/review-cycle.js +328 -0
- package/src/commands/runtime.js +4 -4
- package/src/commands/state-save.js +2 -0
- package/src/commands/workflow-execute.js +138 -28
- package/src/commands/workflow-next.js +3 -2
- package/src/commands/workflow-status.js +30 -10
- package/src/constants.js +15 -13
- package/src/context-memory.js +50 -25
- package/src/context-selector.js +394 -0
- package/src/i18n/messages/en.js +13 -7
- package/src/i18n/messages/es.js +13 -7
- package/src/i18n/messages/fr.js +13 -7
- package/src/i18n/messages/pt-BR.js +13 -7
- package/src/parser.js +1 -1
- package/src/squad/preflight-context.js +26 -27
- package/template/.aioson/agents/analyst.md +41 -46
- package/template/.aioson/agents/architect.md +33 -46
- package/template/.aioson/agents/briefing.md +76 -67
- package/template/.aioson/agents/dev.md +66 -59
- package/template/.aioson/agents/deyvin.md +55 -50
- package/template/.aioson/agents/discovery-design-doc.md +35 -22
- package/template/.aioson/agents/manifests/architect.manifest.json +11 -1
- package/template/.aioson/agents/manifests/dev.manifest.json +15 -0
- package/template/.aioson/agents/manifests/pm.manifest.json +20 -0
- package/template/.aioson/agents/orchestrator.md +31 -18
- package/template/.aioson/agents/pentester.md +7 -7
- package/template/.aioson/agents/pm.md +41 -35
- package/template/.aioson/agents/product.md +116 -165
- package/template/.aioson/agents/qa.md +21 -14
- package/template/.aioson/agents/scope-check.md +46 -24
- package/template/.aioson/agents/tester.md +12 -6
- package/template/.aioson/agents/ux-ui.md +36 -31
- package/template/.aioson/agents/validator.md +3 -3
- package/template/.aioson/config/autonomy-protocol.json +7 -0
- package/template/.aioson/design-docs/code-reuse.md +10 -5
- package/template/.aioson/design-docs/componentization.md +10 -5
- package/template/.aioson/design-docs/file-size.md +10 -5
- package/template/.aioson/design-docs/folder-structure.md +10 -5
- package/template/.aioson/design-docs/naming.md +10 -5
- package/template/.aioson/docs/autonomy-protocol.md +2 -2
- package/template/.aioson/docs/autopilot-handoff.md +32 -21
- package/template/.aioson/docs/briefing/briefing-craft.md +9 -3
- package/template/.aioson/docs/deyvin/continuity-recovery.md +18 -22
- package/template/.aioson/docs/product/conversation-playbook.md +8 -3
- package/template/.aioson/docs/product/prd-contract.md +8 -3
- package/template/.aioson/docs/product/quality-lens.md +8 -3
- package/template/.aioson/docs/product/research-loop.md +8 -3
- package/template/.aioson/docs/ux-ui/accessibility-audit.md +7 -2
- package/template/.aioson/docs/ux-ui/audit-mode.md +7 -2
- package/template/.aioson/docs/ux-ui/component-map.md +7 -2
- package/template/.aioson/docs/ux-ui/design-execution.md +7 -2
- package/template/.aioson/docs/ux-ui/design-gate.md +7 -2
- package/template/.aioson/docs/ux-ui/research-mode.md +7 -2
- package/template/.aioson/docs/ux-ui/site-delivery.md +7 -2
- package/template/.aioson/docs/ux-ui/token-contract.md +7 -2
- package/template/.aioson/rules/aioson-context-boundary.md +1 -1
- package/template/.aioson/rules/disk-first-artifacts.md +1 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +1 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +3 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +21 -9
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +2 -1
- package/template/.aioson/skills/static/web-research-cache.md +29 -8
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Loop Guardrails — Contrato verificável para o self:loop
|
|
2
|
+
|
|
3
|
+
> Controla o loop autônomo com fronteira de arquivos, orçamento aplicado, gates humanos e critérios avaliados — sem criar subsistemas novos.
|
|
4
|
+
|
|
5
|
+
Introduzido na v1.22.0. O `self:loop` existente ganhava cap de iterações e circuit-breaker, mas nada impedia o agente de alterar arquivos fora do escopo, o `cost_ceiling_tokens` existia no schema e nunca era aplicado, e não havia aprovação humana no meio do loop. Os guardrails resolvem isso de forma retrocompatível: contratos antigos continuam válidos.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## O que são os guardrails
|
|
10
|
+
|
|
11
|
+
Quatro camadas que o preflight do `self:loop` ativa automaticamente quando o `harness-contract.json` as declara (ou usa os defaults):
|
|
12
|
+
|
|
13
|
+
| Camada | O que faz |
|
|
14
|
+
|---|---|
|
|
15
|
+
| **Scope guard** | Valida os arquivos alterados em cada iteração contra globs. Violação pausa o loop. |
|
|
16
|
+
| **Budget enforcement** | Aplica `cost_ceiling_tokens` e `max_runtime_minutes`. 80% → warning; 100% → pausa. |
|
|
17
|
+
| **Human gates** | Detecta alterações em áreas sensíveis (pagamentos, auth, migrations) e pausa o loop até decisão humana. |
|
|
18
|
+
| **Criteria evaluation** | Executa comandos de verificação por critério; mesma assinatura de falha 2× → escala para humano. |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Campos novos no harness-contract.json
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"feature": "minha-feature",
|
|
27
|
+
"contract_mode": "safe",
|
|
28
|
+
"governor": {
|
|
29
|
+
"max_steps": 10,
|
|
30
|
+
"cost_ceiling_tokens": 200000,
|
|
31
|
+
"max_runtime_minutes": 30,
|
|
32
|
+
"max_changed_files": 20,
|
|
33
|
+
"max_diff_lines": 1500
|
|
34
|
+
},
|
|
35
|
+
"allowed_files": ["src/modules/checkout/**", "tests/checkout/**"],
|
|
36
|
+
"forbidden_files": ["src/modules/auth/**"],
|
|
37
|
+
"human_gate": {
|
|
38
|
+
"required_for": ["payment_logic_change", "database_destructive_change"]
|
|
39
|
+
},
|
|
40
|
+
"criteria": [
|
|
41
|
+
{
|
|
42
|
+
"id": "tests-pass",
|
|
43
|
+
"description": "Suíte de testes passa sem erros",
|
|
44
|
+
"binary": true,
|
|
45
|
+
"verification": { "command": "npm test", "timeout_ms": 60000 }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Campos novos são **todos opcionais**. Contratos existentes sem eles continuam válidos: o scope guard aplica só os defaults proibidos, sem gates e sem orçamento.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Presets de contract_mode
|
|
56
|
+
|
|
57
|
+
Em vez de configurar cada campo do `governor` manualmente, use um preset:
|
|
58
|
+
|
|
59
|
+
| Modo | max_steps | cost_ceiling_tokens | max_runtime_minutes | max_changed_files |
|
|
60
|
+
|---|---|---|---|---|
|
|
61
|
+
| `balanced` (padrão) | — | — | — | — |
|
|
62
|
+
| `safe` | 10 | 200.000 | 30 min | 20 |
|
|
63
|
+
| `builder` | 30 | 1.000.000 | 120 min | 60 |
|
|
64
|
+
| `autopilot` | 50 | 3.000.000 | 360 min | — |
|
|
65
|
+
|
|
66
|
+
Valor explícito no `governor` sempre vence o preset. `balanced` mantém o comportamento anterior.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Scope guard
|
|
71
|
+
|
|
72
|
+
### Globs proibidos por padrão (não-removíveis)
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
.env* *.pem *.key secrets/**
|
|
76
|
+
.git/** node_modules/** package-lock.json
|
|
77
|
+
yarn.lock pnpm-lock.yaml npm-shrinkwrap.json bun.lockb
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Esses defaults são aplicados mesmo quando `forbidden_files` está ausente no contrato.
|
|
81
|
+
|
|
82
|
+
### Campos
|
|
83
|
+
|
|
84
|
+
- `allowed_files[]` — globs de arquivos que o loop pode alterar. Quando ausente, qualquer arquivo fora dos defaults proibidos é permitido.
|
|
85
|
+
- `forbidden_files[]` — globs proibidos adicionais (mergeados com os defaults).
|
|
86
|
+
|
|
87
|
+
### O que acontece numa violação
|
|
88
|
+
|
|
89
|
+
1. Loop pausa com evento `scope_violation` listando os arquivos fora dos globs.
|
|
90
|
+
2. A próxima iteração recebe instrução de reverter e refazer dentro do escopo.
|
|
91
|
+
3. Reincidência: circuito abre e escala para humano.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Human gates
|
|
96
|
+
|
|
97
|
+
### Temas disponíveis
|
|
98
|
+
|
|
99
|
+
| Tema | Paths monitorados (override possível) |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `payment_logic_change` | `**/billing/**`, `**/payment/**` |
|
|
102
|
+
| `auth_permission_change` | `**/auth/**` |
|
|
103
|
+
| `database_destructive_change` | `**/migrations/**` |
|
|
104
|
+
| `publish` | (gate de comando — detectado em `feature:close`, não por diff) |
|
|
105
|
+
|
|
106
|
+
### Ciclo de gate
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Loop detecta diff em migrations/** → HUMAN_GATE
|
|
110
|
+
↓
|
|
111
|
+
process.exitCode = persistido em .aioson/plans/{slug}/gates/{id}.json
|
|
112
|
+
↓
|
|
113
|
+
aioson harness:approve . --slug=billing --gate=migration-1
|
|
114
|
+
↓ (ou harness:reject --reason="reverter mudança")
|
|
115
|
+
Loop retoma da iteração atual
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Comandos
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Aprovar gate pendente
|
|
122
|
+
aioson harness:approve . --slug=<feature> --gate=<id>
|
|
123
|
+
|
|
124
|
+
# Rejeitar (encerra a tentativa, requer motivo)
|
|
125
|
+
aioson harness:reject . --slug=<feature> --gate=<id> --reason="..."
|
|
126
|
+
|
|
127
|
+
# Listar gates pendentes / estado do loop
|
|
128
|
+
aioson harness:status . --slug=<feature>
|
|
129
|
+
aioson harness:status . --slug=<feature> --json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Budget enforcement
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
"governor": {
|
|
138
|
+
"cost_ceiling_tokens": 500000,
|
|
139
|
+
"max_runtime_minutes": 60
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- 80% dos tokens → warning em evento, loop continua.
|
|
144
|
+
- 100% → pausa com resumo do que foi feito e do que falta. Edite o contrato e reexecute.
|
|
145
|
+
- `max_runtime_minutes` → pausa quando o wall-clock ultrapassa o limite.
|
|
146
|
+
|
|
147
|
+
A estimativa de tokens é best-effort (heurística chars/4 sobre o output do agente, erro típico 5–15%). É uma guarda de parada, não contabilidade exata.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Criteria evaluation
|
|
152
|
+
|
|
153
|
+
Cada critério pode declarar um comando de verificação:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"id": "lint",
|
|
158
|
+
"description": "Sem erros de lint",
|
|
159
|
+
"verification": { "command": "npm run lint", "timeout_ms": 30000 }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- O comando roda via `sandbox:exec` (timeout, kill de process tree) após cada iteração.
|
|
164
|
+
- Logs gravados em `.aioson/plans/{slug}/attempts/{n}/checks/{id}.log`.
|
|
165
|
+
- **Mesma assinatura de falha por 2 tentativas consecutivas** → circuito abre e escala para humano (detecção de loop estéril).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Artefatos por tentativa
|
|
170
|
+
|
|
171
|
+
Cada iteração produz:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
.aioson/plans/{slug}/attempts/{n}/
|
|
175
|
+
changed-files.json # arquivos alterados nesta tentativa
|
|
176
|
+
diff.patch # patch da tentativa (arquivos rastreados)
|
|
177
|
+
checks/
|
|
178
|
+
{criterion-id}.log # stdout/stderr + exit_code de cada verificação
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Esses artefatos são o insumo do `@qa` e do `@validator` — sem precisar reconstruir o que aconteceu.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## harness:status — visão do loop
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
aioson harness:status . --slug=<feature>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Exibe:
|
|
192
|
+
- Estado do circuito (open / closed / half-open)
|
|
193
|
+
- Iteração atual / máximo
|
|
194
|
+
- Budget: tokens estimados, tempo decorrido
|
|
195
|
+
- Checks da última tentativa: passados / falhos
|
|
196
|
+
- Última assinatura de falha
|
|
197
|
+
- Gates pendentes
|
|
198
|
+
- Próxima ação recomendada
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## git:guard (camada 2)
|
|
203
|
+
|
|
204
|
+
O `git:guard` lê os `forbidden_files` do contrato ativo e os aplica como política de pre-commit. Uma segunda barreira: mesmo que o scope guard não capture algo (ex: commit manual fora do loop), o hook bloqueia.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
aioson git:guard . # inspecionar política ativa
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Retrocompatibilidade
|
|
213
|
+
|
|
214
|
+
- Contratos sem os novos campos: scope guard aplica defaults, sem orçamento, sem gates. ✅
|
|
215
|
+
- Nenhum comando existente mudou de assinatura. ✅
|
|
216
|
+
- Contratos novos com agentes mais antigos que não conhecem os guardrails: campos ignorados silenciosamente pelo agente, mas o CLI ainda aplica as checagens no preflight. ✅
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Próximos passos
|
|
221
|
+
|
|
222
|
+
- [Referência de todos os comandos](./comandos-cli.md#harness)
|
|
223
|
+
- [Motor Hardening](./motor-hardening.md) — gates técnicos e auto-cura
|
|
224
|
+
- [Sandbox de Execução](./sandbox.md) — como os comandos de verificação rodam
|
|
225
|
+
- [Retrospectiva de loop](./harness-retro.md) — minerar o histórico de falhas de uma feature
|
|
@@ -349,7 +349,7 @@ project.context.md
|
|
|
349
349
|
## workflow:execute
|
|
350
350
|
|
|
351
351
|
```
|
|
352
|
-
aioson workflow:execute [path] --feature=<slug> [--tool=<tool>] [--classification=<tier>] [--dry-run] [--start-from=<agent>] [--json]
|
|
352
|
+
aioson workflow:execute [path] --feature=<slug> [--tool=<tool>] [--classification=<tier>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--start-from=<agent>] [--json]
|
|
353
353
|
```
|
|
354
354
|
|
|
355
355
|
Monta e executa o plano de agentes para uma feature com base na classificação.
|
|
@@ -367,27 +367,39 @@ Monta e executa o plano de agentes para uma feature com base na classificação.
|
|
|
367
367
|
| Flag | Descrição |
|
|
368
368
|
|---|---|
|
|
369
369
|
| `--tool=<tool>` | Ferramenta a usar (`claude`, `codex`, `opencode`) |
|
|
370
|
-
| `--classification=<tier>` | Override manual da classificação |
|
|
371
|
-
| `--
|
|
372
|
-
| `--
|
|
370
|
+
| `--classification=<tier>` | Override manual da classificação |
|
|
371
|
+
| `--agentic` | Emite/persiste `agentic_policy` para o gateway continuar handoffs determinísticos |
|
|
372
|
+
| `--max-dev-qa-cycles=<n>` | Limite do loop `@dev` ↔ `@qa` no modo agentic (padrão: 3) |
|
|
373
|
+
| `--max-tester-cycles=<n>` | Limite de correções após `@tester` no modo agentic (padrão: 3) |
|
|
374
|
+
| `--max-pentester-cycles=<n>` | Limite de correções após `@pentester` no modo agentic (padrão: 3) |
|
|
375
|
+
| `--dry-run` | Mostra o plano sem executar |
|
|
376
|
+
| `--start-from=<agent>` | Pula agentes anteriores ao agente dado |
|
|
373
377
|
|
|
374
378
|
**Exemplo dry-run:**
|
|
375
379
|
|
|
376
380
|
```bash
|
|
377
|
-
aioson workflow:execute . \
|
|
378
|
-
--feature=checkout \
|
|
379
|
-
--classification=SMALL \
|
|
380
|
-
--
|
|
381
|
-
--
|
|
382
|
-
|
|
381
|
+
aioson workflow:execute . \
|
|
382
|
+
--feature=checkout \
|
|
383
|
+
--classification=SMALL \
|
|
384
|
+
--agentic \
|
|
385
|
+
--dry-run \
|
|
386
|
+
--json
|
|
387
|
+
```
|
|
383
388
|
|
|
384
389
|
```json
|
|
385
390
|
{
|
|
386
391
|
"ok": true,
|
|
387
392
|
"dry_run": true,
|
|
388
|
-
"feature": "checkout",
|
|
389
|
-
"classification": "SMALL",
|
|
390
|
-
"
|
|
393
|
+
"feature": "checkout",
|
|
394
|
+
"classification": "SMALL",
|
|
395
|
+
"agentic_policy": {
|
|
396
|
+
"enabled": true,
|
|
397
|
+
"review_cycle": {
|
|
398
|
+
"max_dev_qa_cycles": 3,
|
|
399
|
+
"feature_close": "human_gate"
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"steps": [
|
|
391
403
|
{ "agent": "product", "skip": false, "reason": "prd not found" },
|
|
392
404
|
{ "agent": "analyst", "skip": false },
|
|
393
405
|
{ "agent": "dev", "skip": false },
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -11,9 +11,10 @@ const { runInfo } = require('./commands/info');
|
|
|
11
11
|
const { runDoctorCommand } = require('./commands/doctor');
|
|
12
12
|
const { runI18nAdd } = require('./commands/i18n-add');
|
|
13
13
|
const { runAgentsList, runAgentPrompt } = require('./commands/agents');
|
|
14
|
-
const { runContextValidate } = require('./commands/context-validate');
|
|
15
|
-
const { runContextPack } = require('./commands/context-pack');
|
|
16
|
-
const {
|
|
14
|
+
const { runContextValidate } = require('./commands/context-validate');
|
|
15
|
+
const { runContextPack } = require('./commands/context-pack');
|
|
16
|
+
const { runContextSelect } = require('./commands/context-select');
|
|
17
|
+
const { runContextLoad } = require('./commands/context-load');
|
|
17
18
|
const { runChainAudit } = require('./commands/chain-audit');
|
|
18
19
|
const { runMemorySearch } = require('./commands/memory-search');
|
|
19
20
|
const { runMemoryArchive } = require('./commands/memory-archive');
|
|
@@ -182,8 +183,9 @@ const { runPreflight } = require('./commands/preflight');
|
|
|
182
183
|
const { runClassify } = require('./commands/classify');
|
|
183
184
|
const { runSizing } = require('./commands/sizing');
|
|
184
185
|
const { runDetectTestRunner } = require('./commands/detect-test-runner');
|
|
185
|
-
const { runPulseUpdate } = require('./commands/pulse-update');
|
|
186
|
-
const {
|
|
186
|
+
const { runPulseUpdate } = require('./commands/pulse-update');
|
|
187
|
+
const { runAgentEpilogue } = require('./commands/agent-epilogue');
|
|
188
|
+
const { runStateSave, runStateReset } = require('./commands/state-save');
|
|
187
189
|
const { runOpIdentity } = require('./commands/op-identity');
|
|
188
190
|
const { runOpCapture } = require('./commands/op-capture');
|
|
189
191
|
const { runOpPromote } = require('./commands/op-promote');
|
|
@@ -203,7 +205,8 @@ const { runRevisionOpen, runRevisionList, runRevisionResolve } = require('./comm
|
|
|
203
205
|
const { runGateCheck } = require('./commands/gate-check');
|
|
204
206
|
const { runGateApprove } = require('./commands/gate-approve');
|
|
205
207
|
const { runArtifactValidate } = require('./commands/artifact-validate');
|
|
206
|
-
const { runWorkflowExecute } = require('./commands/workflow-execute');
|
|
208
|
+
const { runWorkflowExecute } = require('./commands/workflow-execute');
|
|
209
|
+
const { runReviewCycle } = require('./commands/review-cycle');
|
|
207
210
|
const { runRunnerQueueFromPlan } = require('./commands/runner-queue-from-plan');
|
|
208
211
|
const { runLearningAutoPromote } = require('./commands/learning-auto-promote');
|
|
209
212
|
const { runBriefValidate } = require('./commands/brief-validate');
|
|
@@ -238,8 +241,10 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
238
241
|
'agent-prompt',
|
|
239
242
|
'agent:help',
|
|
240
243
|
'agent-help',
|
|
241
|
-
'agent:invoke',
|
|
242
|
-
'agent-invoke',
|
|
244
|
+
'agent:invoke',
|
|
245
|
+
'agent-invoke',
|
|
246
|
+
'agent:epilogue',
|
|
247
|
+
'agent-epilogue',
|
|
243
248
|
'setup:context',
|
|
244
249
|
'setup-context',
|
|
245
250
|
'locale:apply',
|
|
@@ -248,9 +253,11 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
248
253
|
'doctor',
|
|
249
254
|
'context:validate',
|
|
250
255
|
'context-validate',
|
|
251
|
-
'context:pack',
|
|
252
|
-
'context-pack',
|
|
253
|
-
'context:
|
|
256
|
+
'context:pack',
|
|
257
|
+
'context-pack',
|
|
258
|
+
'context:select',
|
|
259
|
+
'context-select',
|
|
260
|
+
'context:load',
|
|
254
261
|
'context-load',
|
|
255
262
|
'chain:audit',
|
|
256
263
|
'chain-audit',
|
|
@@ -687,8 +694,16 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
687
694
|
'gate-approve',
|
|
688
695
|
'artifact:validate',
|
|
689
696
|
'artifact-validate',
|
|
690
|
-
'workflow:execute',
|
|
691
|
-
'workflow-execute',
|
|
697
|
+
'workflow:execute',
|
|
698
|
+
'workflow-execute',
|
|
699
|
+
'review-cycle:status',
|
|
700
|
+
'review-cycle-status',
|
|
701
|
+
'review-cycle:advance',
|
|
702
|
+
'review-cycle-advance',
|
|
703
|
+
'review-cycle:resolve',
|
|
704
|
+
'review-cycle-resolve',
|
|
705
|
+
'review-cycle:reset',
|
|
706
|
+
'review-cycle-reset',
|
|
692
707
|
'runner:queue:from-plan',
|
|
693
708
|
'runner-queue-from-plan',
|
|
694
709
|
'learning:auto-promote',
|
|
@@ -791,11 +806,13 @@ function printHelp(t, logger) {
|
|
|
791
806
|
logHelpLine(t, logger, 'cli.help_i18n_add');
|
|
792
807
|
logHelpLine(t, logger, 'cli.help_agents');
|
|
793
808
|
logHelpLine(t, logger, 'cli.help_agent_prompt');
|
|
794
|
-
logHelpLine(t, logger, 'cli.help_agent_help');
|
|
795
|
-
logHelpLine(t, logger, 'cli.help_agent_invoke');
|
|
796
|
-
logHelpLine(t, logger, 'cli.
|
|
797
|
-
logHelpLine(t, logger, 'cli.
|
|
798
|
-
logHelpLine(t, logger, 'cli.
|
|
809
|
+
logHelpLine(t, logger, 'cli.help_agent_help');
|
|
810
|
+
logHelpLine(t, logger, 'cli.help_agent_invoke');
|
|
811
|
+
logHelpLine(t, logger, 'cli.help_agent_epilogue');
|
|
812
|
+
logHelpLine(t, logger, 'cli.help_context_validate');
|
|
813
|
+
logHelpLine(t, logger, 'cli.help_context_pack');
|
|
814
|
+
logHelpLine(t, logger, 'cli.help_context_select');
|
|
815
|
+
logHelpLine(t, logger, 'cli.help_context_load');
|
|
799
816
|
logHelpLine(t, logger, 'cli.help_memory_status');
|
|
800
817
|
logHelpLine(t, logger, 'cli.help_memory_summary');
|
|
801
818
|
logHelpLine(t, logger, 'cli.help_memory_search');
|
|
@@ -810,8 +827,9 @@ function printHelp(t, logger) {
|
|
|
810
827
|
logHelpLine(t, logger, 'cli.help_test_package');
|
|
811
828
|
logHelpLine(t, logger, 'cli.help_workflow_plan');
|
|
812
829
|
logHelpLine(t, logger, 'cli.help_workflow_next');
|
|
813
|
-
logHelpLine(t, logger, 'cli.help_workflow_status');
|
|
814
|
-
logHelpLine(t, logger, 'cli.help_workflow_execute');
|
|
830
|
+
logHelpLine(t, logger, 'cli.help_workflow_status');
|
|
831
|
+
logHelpLine(t, logger, 'cli.help_workflow_execute');
|
|
832
|
+
logHelpLine(t, logger, 'cli.help_review_cycle');
|
|
815
833
|
logHelpLine(t, logger, 'cli.help_parallel_init');
|
|
816
834
|
logHelpLine(t, logger, 'cli.help_parallel_doctor');
|
|
817
835
|
logHelpLine(t, logger, 'cli.help_parallel_assign');
|
|
@@ -1076,10 +1094,12 @@ async function main() {
|
|
|
1076
1094
|
result = await runAgentPrompt({ args, options, logger: commandLogger, t });
|
|
1077
1095
|
} else if (command === 'context:validate' || command === 'context-validate') {
|
|
1078
1096
|
result = await runContextValidate({ args, options, logger: commandLogger, t });
|
|
1079
|
-
} else if (command === 'context:pack' || command === 'context-pack') {
|
|
1080
|
-
result = await runContextPack({ args, options, logger: commandLogger, t });
|
|
1081
|
-
} else if (command === 'context:
|
|
1082
|
-
result = await
|
|
1097
|
+
} else if (command === 'context:pack' || command === 'context-pack') {
|
|
1098
|
+
result = await runContextPack({ args, options, logger: commandLogger, t });
|
|
1099
|
+
} else if (command === 'context:select' || command === 'context-select') {
|
|
1100
|
+
result = await runContextSelect({ args, options, logger: commandLogger, t });
|
|
1101
|
+
} else if (command === 'context:load' || command === 'context-load') {
|
|
1102
|
+
result = await runContextLoad({ args, options, logger: commandLogger, t });
|
|
1083
1103
|
} else if (command === 'chain:audit' || command === 'chain-audit') {
|
|
1084
1104
|
result = await runChainAudit({ args, options, logger: commandLogger, t });
|
|
1085
1105
|
} else if (command === 'setup:context' || command === 'setup-context') {
|
|
@@ -1520,8 +1540,10 @@ async function main() {
|
|
|
1520
1540
|
result = await runSizing({ args, options, logger: commandLogger });
|
|
1521
1541
|
} else if (command === 'detect:test-runner' || command === 'detect-test-runner') {
|
|
1522
1542
|
result = await runDetectTestRunner({ args, options, logger: commandLogger });
|
|
1523
|
-
} else if (command === 'pulse:update' || command === 'pulse-update') {
|
|
1524
|
-
result = await runPulseUpdate({ args, options, logger: commandLogger });
|
|
1543
|
+
} else if (command === 'pulse:update' || command === 'pulse-update') {
|
|
1544
|
+
result = await runPulseUpdate({ args, options, logger: commandLogger });
|
|
1545
|
+
} else if (command === 'agent:epilogue' || command === 'agent-epilogue') {
|
|
1546
|
+
result = await runAgentEpilogue({ args, options, logger: commandLogger, t });
|
|
1525
1547
|
} else if (
|
|
1526
1548
|
command === 'state:save' ||
|
|
1527
1549
|
command === 'state-save' ||
|
|
@@ -1589,9 +1611,12 @@ async function main() {
|
|
|
1589
1611
|
result = await runGateApprove({ args, options, logger: commandLogger });
|
|
1590
1612
|
} else if (command === 'artifact:validate' || command === 'artifact-validate') {
|
|
1591
1613
|
result = await runArtifactValidate({ args, options, logger: commandLogger });
|
|
1592
|
-
} else if (command === 'workflow:execute' || command === 'workflow-execute') {
|
|
1593
|
-
result = await runWorkflowExecute({ args, options, logger: commandLogger });
|
|
1594
|
-
} else if (command
|
|
1614
|
+
} else if (command === 'workflow:execute' || command === 'workflow-execute') {
|
|
1615
|
+
result = await runWorkflowExecute({ args, options, logger: commandLogger });
|
|
1616
|
+
} else if (command.startsWith('review-cycle:') || command.startsWith('review-cycle-')) {
|
|
1617
|
+
const sub = command.replace(/^review-cycle[:-]/, '');
|
|
1618
|
+
result = await runReviewCycle({ args, options: { ...options, sub }, logger: commandLogger, t });
|
|
1619
|
+
} else if (command === 'runner:queue:from-plan' || command === 'runner-queue-from-plan') {
|
|
1595
1620
|
result = await runRunnerQueueFromPlan({ args, options, logger: commandLogger });
|
|
1596
1621
|
} else if (command === 'learning:auto-promote' || command === 'learning-auto-promote') {
|
|
1597
1622
|
result = await runLearningAutoPromote({ args, options, logger: commandLogger });
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { runPulseUpdate } = require('./pulse-update');
|
|
5
|
+
const { runDossierAddFinding } = require('./dossier');
|
|
6
|
+
const { runGateApprove } = require('./gate-approve');
|
|
7
|
+
const { runAgentDone } = require('./runtime');
|
|
8
|
+
|
|
9
|
+
function resolveTargetDir(args) {
|
|
10
|
+
return path.resolve(process.cwd(), args[0] || '.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeAgent(value) {
|
|
14
|
+
return String(value || '').trim().replace(/^@/, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeList(value) {
|
|
18
|
+
if (!value) return [];
|
|
19
|
+
if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
|
|
20
|
+
return String(value).split(',').map((item) => item.trim()).filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeSilentLogger() {
|
|
24
|
+
return { log() {}, error() {}, warn() {} };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pushStep(steps, name, result) {
|
|
28
|
+
steps.push({
|
|
29
|
+
name,
|
|
30
|
+
ok: Boolean(result && result.ok),
|
|
31
|
+
skipped: Boolean(result && result.skipped),
|
|
32
|
+
reason: result && (result.reason || result.error || null)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatAutoAdvance(autoAdvance) {
|
|
37
|
+
if (!autoAdvance) return null;
|
|
38
|
+
if (autoAdvance.advanced) {
|
|
39
|
+
const nextStage = autoAdvance.result && (autoAdvance.result.next || autoAdvance.result.nextStage);
|
|
40
|
+
return `workflow auto-advanced${nextStage ? ` -> ${nextStage}` : ''}`;
|
|
41
|
+
}
|
|
42
|
+
return `workflow skip: ${autoAdvance.skipped || 'not_advanced'}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runAgentEpilogue({ args, options = {}, logger, t }) {
|
|
46
|
+
const targetDir = resolveTargetDir(args);
|
|
47
|
+
const agent = normalizeAgent(options.agent);
|
|
48
|
+
const summary = String(options.summary || options.message || '').trim();
|
|
49
|
+
const feature = options.feature ? String(options.feature).trim() : null;
|
|
50
|
+
const action = options.action ? String(options.action).trim() : summary;
|
|
51
|
+
const next = options.next ? String(options.next).trim() : null;
|
|
52
|
+
const gate = options.gate ? String(options.gate).trim() : null;
|
|
53
|
+
const approveGate = options['approve-gate'] || options.approveGate
|
|
54
|
+
? String(options['approve-gate'] || options.approveGate).trim().toUpperCase()
|
|
55
|
+
: null;
|
|
56
|
+
const verdict = options.verdict ? String(options.verdict).trim().toUpperCase() : null;
|
|
57
|
+
const artifacts = normalizeList(options.artifacts);
|
|
58
|
+
const strict = Boolean(options.strict);
|
|
59
|
+
const steps = [];
|
|
60
|
+
const errors = [];
|
|
61
|
+
const silentLogger = makeSilentLogger();
|
|
62
|
+
|
|
63
|
+
if (!agent) {
|
|
64
|
+
const failure = { ok: false, reason: 'missing_agent' };
|
|
65
|
+
if (options.json) return failure;
|
|
66
|
+
logger.error('--agent=<agent> is required.');
|
|
67
|
+
return failure;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!summary) {
|
|
71
|
+
const failure = { ok: false, reason: 'missing_summary' };
|
|
72
|
+
if (options.json) return failure;
|
|
73
|
+
logger.error('--summary="<summary>" is required.');
|
|
74
|
+
return failure;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (approveGate) {
|
|
78
|
+
const gateResult = await runGateApprove({
|
|
79
|
+
args: [targetDir],
|
|
80
|
+
options: {
|
|
81
|
+
feature,
|
|
82
|
+
gate: approveGate,
|
|
83
|
+
agent,
|
|
84
|
+
json: true
|
|
85
|
+
},
|
|
86
|
+
logger: silentLogger
|
|
87
|
+
});
|
|
88
|
+
pushStep(steps, 'gate:approve', gateResult);
|
|
89
|
+
if (!gateResult.ok) {
|
|
90
|
+
errors.push({ step: 'gate:approve', reason: gateResult.reason || 'gate_failed', result: gateResult });
|
|
91
|
+
if (strict) {
|
|
92
|
+
const failure = { ok: false, reason: 'gate_approve_failed', steps, errors };
|
|
93
|
+
if (options.json) return failure;
|
|
94
|
+
logger.error(`agent:epilogue blocked: gate ${approveGate} approval failed.`);
|
|
95
|
+
return failure;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!options['no-pulse'] && !options.noPulse) {
|
|
101
|
+
const pulseResult = await runPulseUpdate({
|
|
102
|
+
args: [targetDir],
|
|
103
|
+
options: {
|
|
104
|
+
agent,
|
|
105
|
+
...(feature ? { feature } : {}),
|
|
106
|
+
...(gate || approveGate ? { gate: gate || `Gate ${approveGate}: approved` } : {}),
|
|
107
|
+
...(action ? { action } : {}),
|
|
108
|
+
...(next ? { next } : {}),
|
|
109
|
+
...(verdict ? { verdict } : {}),
|
|
110
|
+
json: true
|
|
111
|
+
},
|
|
112
|
+
logger: silentLogger
|
|
113
|
+
});
|
|
114
|
+
pushStep(steps, 'pulse:update', pulseResult);
|
|
115
|
+
if (!pulseResult.ok) {
|
|
116
|
+
errors.push({ step: 'pulse:update', reason: pulseResult.reason || 'pulse_failed', result: pulseResult });
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
pushStep(steps, 'pulse:update', { ok: true, skipped: true, reason: 'disabled' });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (feature && !options['no-dossier'] && !options.noDossier) {
|
|
123
|
+
const dossierResult = await runDossierAddFinding({
|
|
124
|
+
args: [targetDir],
|
|
125
|
+
options: {
|
|
126
|
+
slug: feature,
|
|
127
|
+
agent,
|
|
128
|
+
section: options.section ? String(options.section) : 'Agent Trail',
|
|
129
|
+
content: options.content ? String(options.content) : summary,
|
|
130
|
+
json: true
|
|
131
|
+
},
|
|
132
|
+
logger: silentLogger
|
|
133
|
+
});
|
|
134
|
+
pushStep(steps, 'dossier:add-finding', dossierResult);
|
|
135
|
+
if (!dossierResult.ok) {
|
|
136
|
+
errors.push({ step: 'dossier:add-finding', reason: dossierResult.reason || 'dossier_failed', result: dossierResult });
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
pushStep(steps, 'dossier:add-finding', { ok: true, skipped: true, reason: feature ? 'disabled' : 'missing_feature' });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const doneResult = await runAgentDone({
|
|
143
|
+
args: [targetDir],
|
|
144
|
+
options: {
|
|
145
|
+
agent,
|
|
146
|
+
summary,
|
|
147
|
+
...(feature ? { feature } : {}),
|
|
148
|
+
...(verdict ? { verdict } : {}),
|
|
149
|
+
...(artifacts.length > 0 ? { artifacts: artifacts.join(',') } : {}),
|
|
150
|
+
json: true
|
|
151
|
+
},
|
|
152
|
+
logger: silentLogger,
|
|
153
|
+
t
|
|
154
|
+
});
|
|
155
|
+
pushStep(steps, 'agent:done', doneResult);
|
|
156
|
+
if (!doneResult.ok) {
|
|
157
|
+
errors.push({ step: 'agent:done', reason: doneResult.reason || doneResult.error || 'agent_done_failed', result: doneResult });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ok = doneResult.ok && (strict ? errors.length === 0 : !errors.some((error) => error.step === 'agent:done'));
|
|
161
|
+
const result = {
|
|
162
|
+
ok,
|
|
163
|
+
targetDir,
|
|
164
|
+
agent: `@${agent}`,
|
|
165
|
+
feature,
|
|
166
|
+
summary,
|
|
167
|
+
steps,
|
|
168
|
+
errors,
|
|
169
|
+
agent_done: doneResult
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (options.json) return result;
|
|
173
|
+
|
|
174
|
+
logger.log(`agent:epilogue — @${agent} (${ok ? 'ok' : 'issues'})`);
|
|
175
|
+
for (const step of steps) {
|
|
176
|
+
const marker = step.skipped ? 'skip' : step.ok ? 'ok' : 'fail';
|
|
177
|
+
logger.log(` ${marker} ${step.name}${step.reason ? ` (${step.reason})` : ''}`);
|
|
178
|
+
}
|
|
179
|
+
const autoAdvanceMessage = formatAutoAdvance(doneResult.auto_advance);
|
|
180
|
+
if (autoAdvanceMessage) logger.log(` ${autoAdvanceMessage}`);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
runAgentEpilogue
|
|
186
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { selectContext } = require('../context-selector');
|
|
5
|
+
|
|
6
|
+
async function runContextSelect({ args, options = {}, logger }) {
|
|
7
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
8
|
+
const result = await selectContext(targetDir, {
|
|
9
|
+
agent: options.agent || options.a || 'dev',
|
|
10
|
+
mode: options.mode || 'planning',
|
|
11
|
+
task: options.task || options.goal || '',
|
|
12
|
+
paths: options.paths || options.path || '',
|
|
13
|
+
feature: options.feature || options.slug || ''
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (options.json) return result;
|
|
17
|
+
|
|
18
|
+
logger.log(`Context selection for @${result.agent} (${result.mode})`);
|
|
19
|
+
if (result.task) logger.log(`Task: ${result.task}`);
|
|
20
|
+
if (result.paths.length > 0) logger.log(`Paths: ${result.paths.join(', ')}`);
|
|
21
|
+
if (result.selected.length === 0) {
|
|
22
|
+
logger.log('No context files selected.');
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const item of result.selected) {
|
|
27
|
+
logger.log(`- ${item.path} [${item.surface}; ${item.load_tier}] ${item.reason}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { runContextSelect };
|