@jaimevalasek/aioson 1.23.0 → 1.23.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/docs/en/5-reference/cli-reference.md +85 -0
  2. package/docs/pt/4-agentes/pm.md +31 -4
  3. package/docs/pt/5-referencia/README.md +3 -0
  4. package/docs/pt/5-referencia/autopilot-handoff.md +131 -0
  5. package/docs/pt/5-referencia/comandos-cli.md +72 -6
  6. package/docs/pt/5-referencia/harness-retro.md +133 -0
  7. package/docs/pt/5-referencia/loop-guardrails.md +225 -0
  8. package/docs/pt/5-referencia/sdd-automation-scripts.md +25 -13
  9. package/package.json +1 -1
  10. package/src/cli.js +54 -29
  11. package/src/commands/agent-epilogue.js +186 -0
  12. package/src/commands/context-select.js +34 -0
  13. package/src/commands/preflight-context.js +13 -9
  14. package/src/commands/review-cycle.js +328 -0
  15. package/src/commands/runtime.js +4 -4
  16. package/src/commands/state-save.js +2 -0
  17. package/src/commands/workflow-execute.js +138 -28
  18. package/src/commands/workflow-next.js +3 -2
  19. package/src/commands/workflow-status.js +30 -10
  20. package/src/constants.js +15 -13
  21. package/src/context-memory.js +50 -25
  22. package/src/context-selector.js +420 -0
  23. package/src/gateway-pointer-merge.js +25 -4
  24. package/src/i18n/messages/en.js +13 -7
  25. package/src/i18n/messages/es.js +13 -7
  26. package/src/i18n/messages/fr.js +13 -7
  27. package/src/i18n/messages/pt-BR.js +13 -7
  28. package/src/parser.js +1 -1
  29. package/src/squad/preflight-context.js +26 -27
  30. package/template/.aioson/agents/analyst.md +41 -46
  31. package/template/.aioson/agents/architect.md +33 -46
  32. package/template/.aioson/agents/briefing.md +76 -67
  33. package/template/.aioson/agents/dev.md +66 -59
  34. package/template/.aioson/agents/deyvin.md +124 -114
  35. package/template/.aioson/agents/discovery-design-doc.md +35 -22
  36. package/template/.aioson/agents/manifests/architect.manifest.json +11 -1
  37. package/template/.aioson/agents/manifests/dev.manifest.json +15 -0
  38. package/template/.aioson/agents/manifests/pm.manifest.json +20 -0
  39. package/template/.aioson/agents/orchestrator.md +31 -18
  40. package/template/.aioson/agents/pentester.md +7 -7
  41. package/template/.aioson/agents/pm.md +41 -35
  42. package/template/.aioson/agents/product.md +116 -165
  43. package/template/.aioson/agents/qa.md +21 -14
  44. package/template/.aioson/agents/scope-check.md +46 -24
  45. package/template/.aioson/agents/tester.md +12 -6
  46. package/template/.aioson/agents/ux-ui.md +36 -31
  47. package/template/.aioson/agents/validator.md +3 -3
  48. package/template/.aioson/config/autonomy-protocol.json +7 -0
  49. package/template/.aioson/design-docs/code-reuse.md +10 -5
  50. package/template/.aioson/design-docs/componentization.md +10 -5
  51. package/template/.aioson/design-docs/file-size.md +10 -5
  52. package/template/.aioson/design-docs/folder-structure.md +10 -5
  53. package/template/.aioson/design-docs/naming.md +10 -5
  54. package/template/.aioson/docs/autonomy-protocol.md +2 -2
  55. package/template/.aioson/docs/autopilot-handoff.md +32 -21
  56. package/template/.aioson/docs/briefing/briefing-craft.md +9 -3
  57. package/template/.aioson/docs/deyvin/continuity-recovery.md +18 -22
  58. package/template/.aioson/docs/product/conversation-playbook.md +8 -3
  59. package/template/.aioson/docs/product/prd-contract.md +8 -3
  60. package/template/.aioson/docs/product/quality-lens.md +8 -3
  61. package/template/.aioson/docs/product/research-loop.md +8 -3
  62. package/template/.aioson/docs/ux-ui/accessibility-audit.md +7 -2
  63. package/template/.aioson/docs/ux-ui/audit-mode.md +7 -2
  64. package/template/.aioson/docs/ux-ui/component-map.md +7 -2
  65. package/template/.aioson/docs/ux-ui/design-execution.md +7 -2
  66. package/template/.aioson/docs/ux-ui/design-gate.md +7 -2
  67. package/template/.aioson/docs/ux-ui/research-mode.md +7 -2
  68. package/template/.aioson/docs/ux-ui/site-delivery.md +7 -2
  69. package/template/.aioson/docs/ux-ui/token-contract.md +7 -2
  70. package/template/.aioson/rules/aioson-context-boundary.md +1 -1
  71. package/template/.aioson/rules/disk-first-artifacts.md +1 -1
  72. package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +9 -7
  73. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +1 -1
  74. package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +3 -2
  75. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +21 -9
  76. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -1
  77. package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +19 -15
  78. package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +2 -1
  79. package/template/.aioson/skills/static/web-research-cache.md +29 -8
  80. package/template/AGENTS.md +13 -13
  81. package/template/CLAUDE.md +9 -9
  82. package/template/OPENCODE.md +3 -2
@@ -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
- | `--dry-run` | Mostra o plano sem executar |
372
- | `--start-from=<agent>` | Pula agentes anteriores ao agente dado |
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
- --dry-run \
381
- --json
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
- "steps": [
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaimevalasek/aioson",
3
- "version": "1.23.0",
3
+ "version": "1.23.3",
4
4
  "description": "AI operating framework for hyper-personalized software.",
5
5
  "keywords": [
6
6
  "ai",
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 { runContextLoad } = require('./commands/context-load');
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 { runStateSave, runStateReset } = require('./commands/state-save');
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:load',
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.help_context_validate');
797
- logHelpLine(t, logger, 'cli.help_context_pack');
798
- logHelpLine(t, logger, 'cli.help_context_load');
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:load' || command === 'context-load') {
1082
- result = await runContextLoad({ args, options, logger: commandLogger, t });
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 === 'runner:queue:from-plan' || command === 'runner-queue-from-plan') {
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,34 @@
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
+ logger.log('Boundary: load only the selected files until the task, mode, feature, or touched paths change.');
22
+ if (result.selected.length === 0) {
23
+ logger.log('No context files selected.');
24
+ return result;
25
+ }
26
+
27
+ for (const item of result.selected) {
28
+ logger.log(`- ${item.path} [${item.surface}; ${item.load_tier}] ${item.reason}`);
29
+ }
30
+
31
+ return result;
32
+ }
33
+
34
+ module.exports = { runContextSelect };