regista 0.6.0

🎬 AI agent director — multi-provider, stack-agnostic pipeline
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# 🏗️ regista — Diseño

🎬 AI agent director multi-provider. Independiente del proyecto: no sabe nada
de Rust, ni de qué construyen los agentes. Solo sabe tres cosas:

1. **Dónde están las historias** y cómo leer su estado
2. **Qué provider y qué instrucciones de rol** usar para cada rol del workflow
3. **La máquina de estados fija** que gobierna las transiciones

El proyecto anfitrión se configura mediante un archivo `.regista/config.toml` en su raíz.
El provider se puede elegir vía config (`agents.provider`) o flag CLI (`--provider`).

---

## 1. Sistema de providers

### 1.1 Trait `AgentProvider`

```rust
pub trait AgentProvider {
    fn binary(&self) -> &str;                                      // "pi", "claude", "codex", "opencode"
    fn build_args(&self, instruction_path: &Path, prompt: &str) -> Vec<String>;
    fn display_name(&self) -> &str;                                // "pi", "Claude Code", "Codex", "OpenCode"
    fn instruction_name(&self) -> &str;                            // "skill", "agent", "command"
    fn instruction_dir(&self, role: &str) -> String;               // ".pi/skills/po/SKILL.md"
}
```

El trait devuelve `Vec<String>` (args de CLI), no un `Command`, para ser
compatible con ejecución síncrona y asíncrona (paralelismo #01).

### 1.2 Providers implementados

| Provider | Binario | Argumentos no-interactivo | Directorio de instrucciones |
|----------|---------|--------------------------|----------------------------|
| `PiProvider` | `pi` | `-p "..." --skill <path> --no-session` | `.pi/skills/<rol>/SKILL.md` |
| `ClaudeCodeProvider` | `claude` | `-p "..." --append-system-prompt-file <path> --permission-mode bypassPermissions` | `.claude/agents/<rol>.md` |
| `CodexProvider` | `codex` | `exec --sandbox workspace-write "..."` | `.agents/skills/<rol>/SKILL.md` |
| `OpenCodeProvider` | `opencode` | `-p "..." -q` | `.opencode/commands/<rol>.md` |

### 1.3 Resolución de provider

`AgentsConfig` tiene un `provider` global (default `"pi"`) y cada rol puede
sobreescribirlo vía `AgentRoleConfig`. La CLI puede sobreescribir el global
con `--provider`.

```rust
impl AgentsConfig {
    pub fn provider_for_role(&self, role: &str) -> String { ... }
    pub fn skill_for_role(&self, role: &str) -> String { ... }
}
```

---

## 2. Configuración (`.regista/config.toml`)

```toml
[project]
stories_dir    = ".regista/stories"
story_pattern  = "STORY-*.md"
epics_dir      = ".regista/epics"
decisions_dir  = ".regista/decisions"
log_dir        = ".regista/logs"

[agents]
provider = "pi"                        # provider global (pi, claude, codex, opencode)

[agents.product_owner]                 # opcional: sobreescribir por rol
# provider = "claude"
# skill = ".claude/agents/po-custom.md"

[limits]
max_iterations            = 0   # 0 = auto: nº historias × 6 (mín 10)
max_retries_per_step      = 5
max_reject_cycles         = 3
agent_timeout_seconds     = 1800
max_wall_time_seconds     = 28800
retry_delay_base_seconds  = 10
plan_max_iterations      = 5       # bucle plan→validate→corregir
inject_feedback_on_retry  = true    # inyectar stderr en reintentos

[hooks]
post_qa       = "cargo check --tests"
post_dev      = "cargo build && cargo test && cargo clippy -- -D warnings"
post_reviewer = "cargo test && cargo clippy -- -D warnings"

[git]
enabled = true

[stack]
# Comandos del stack. Opcionales: si no se definen, el prompt usa
# instrucciones genéricas y el skill del agente interpreta el stack.
build_command = "npm run build"
test_command  = "npm test"
lint_command  = "eslint ."
fmt_command   = "prettier --check ."
src_dir       = "src/"
```

### 2.1 StackConfig

```rust
pub struct StackConfig {
    pub build_command: Option<String>,
    pub test_command: Option<String>,
    pub lint_command: Option<String>,
    pub fmt_command: Option<String>,
    pub src_dir: Option<String>,
}
```

`StackConfig::render()` genera el bloque de comandos para el prompt.
Si no hay comandos definidos, devuelve instrucción genérica.

---

## 3. Máquina de Estados

### 3.1 Diagrama

```
                    ┌──────────┐
                    │  Draft   │  ← Historia creada, pendiente de refinamiento
                    └────┬─────┘
                         │ PO (plan)
                    ┌────▼─────┐
              ┌─────│  Ready   │  ← Refinada, lista para QA
              │     └────┬─────┘
              │ QA       │ QA (tests escritos)
              │ (rollback│
              │ si no es  ┌────▼──────┐
              │ testeable)│Tests Ready│  ← Tests existen, pendiente Dev
              │           └────┬──────┘
              │                │ Dev (implementa)
              │           ┌────▼──────┐
              │           │ In Review │  ← Implementación lista, pendiente Reviewer
              │           └────┬──────┘
              │                │ Reviewer
              │           ┌────▼──────────┐
              │           │Business Review│  ← DoD técnico OK, pendiente PO
              │           └────┬──────────┘
              │                │ PO (validate)
              │           ┌────▼──────┐
              │           │    Done   │  ← ¡Finalizada!
              │           └───────────┘
              │   ┌──────────────────────────────────────────┐
              └───┤  Rechazos (retrocesos)                   │
                  │                                          │
                  │  In Review ──Reviewer──→ In Progress     │
                  │  Business Review ──PO──→ In Review       │
                  │  Business Review ──PO──→ In Progress     │
                  │  In Progress ──Dev(fix)──→ In Review     │
                  │                                          │
                  │  Estados terminales de fallo:            │
                  │  * ──max reject cycles──→ Failed         │
                  │                                          │
                  │  Bloqueo por dependencias:               │
                  │  * ──bloqueadores no Done──→ Blocked     │
                  │  Blocked ──bloqueadores Done──→ Ready    │
                  └──────────────────────────────────────────┘
```

### 3.2 Tabla canónica de transiciones

| # | De | A | Actor | Condición |
|---|---|---|---|---|
| 1 | `Draft` | `Ready` | **PO** (plan) | Historia cumple DoR |
| 2 | `Ready` | `Tests Ready` | **QA** | Tests escritos para todos los CAs |
| 3 | `Ready` | `Draft` | **QA** (rollback) | Historia no es testeable → PO debe re-refinar |
| 4 | `Tests Ready` | `In Review` | **Dev** | Implementación completa, tests pasan |
| 5 | `Tests Ready` | `Tests Ready` | **QA** (corregir) | Dev reportó que tests no compilan o son incorrectos |
| 6 | `In Progress` | `In Review` | **Dev** (fix) | Corrección aplicada tras rechazo |
| 7 | `In Review` | `Business Review` | **Reviewer** | DoD técnico OK, todos los CAs cubiertos |
| 8 | `In Review` | `In Progress` | **Reviewer** | Rechazo técnico con detalles concretos |
| 9 | `Business Review` | `Done` | **PO** (validate) | Validación de negocio OK |
| 10 | `Business Review` | `In Review` | **PO** (reject) | Rechazo leve: falta detalle, base técnica sólida |
| 11 | `Business Review` | `In Progress` | **PO** (reject) | Rechazo grave: no cumple valor de negocio |
| 12 | `*` | `Blocked` | **Orquestador** | Tiene dependencias en estado ≠ `Done` |
| 13 | `Blocked` | `Ready` | **Orquestador** | Todas las dependencias están en `Done` |
| 14 | `*` | `Failed` | **Orquestador** | Superado `max_reject_cycles` |

> Las transiciones 12, 13, 14 son **automáticas**: las ejecuta el propio orquestador
> sin invocar agentes.

### 3.3 Tipo en Rust

```rust
pub enum Status {
    Draft, Ready, TestsReady, InProgress, InReview,
    BusinessReview, Done, Blocked, Failed,
}

pub enum Actor {
    ProductOwner, QaEngineer, Developer, Reviewer, Orchestrator,
}
```

---

## 4. Detección de bloqueos (deadlock)

Si no hay historias accionables, el orquestador analiza el grafo de dependencias
y dispara al PO para desatascar la historia que más bloqueos resuelve.

### 4.1 Algoritmo

```
Para cada historia no terminal:
  1. Si status == Draft         → "stuck": necesita PO (plan)
  2. Si status == Blocked:
     a. Si algún bloqueador está en Draft → "stuck": PO debe planificar el Draft
     b. Si hay ciclo de dependencias     → "stuck": PO debe romper el ciclo
     c. Si todos los bloqueadores Done   → automático → Ready
  3. Resto → el loop normal lo maneja

Si ninguna accionable Y hay stuck → disparar PO para la de mayor prioridad.
Si ninguna accionable Y sin stuck  → Pipeline Complete.
```

### 4.2 Prioridad de desbloqueo

La historia que **desbloquea más historias** (conteo de referencias inversas).
En empate, ID numérico más bajo.

---

## 5. Arquitectura del crate

```
regista/
├── Cargo.toml
├── README.md
├── DESIGN.md                  ← este documento
├── AGENTS.md                  ← guía para agentes de codificación
├── src/
│   ├── main.rs                ← CLI (clap), subcomandos, JSON output, exit codes
│   ├── config.rs              ← Config, AgentsConfig + AgentRoleConfig, carga TOML
│   ├── state.rs               ← Status, Actor, Transition, can_transition_to()
│   ├── story.rs               ← Story, parseo .md, set_status(), advance_status_in_memory()
│   ├── dependency_graph.rs    ← Grafo, ciclo DFS, has_any_cycle(), blocks_count()
│   ├── deadlock.rs            ← analyze(), DeadlockResolution, priorización
│   ├── providers.rs           ← trait AgentProvider + 4 implementaciones + factory
│   ├── agent.rs               ← invoke_with_retry(), AgentOptions, feedback rico
│   ├── prompts.rs             ← PromptContext, 7 prompts stack-agnósticos, StackConfig::render()
│   ├── orchestrator.rs        ← run(), run_real(), run_dry(), process_story()
│   ├── checkpoint.rs          ← OrchestratorState: save/load/remove (.regista/state.toml)
│   ├── validator.rs           ← validate(): chequeo pre-vuelo de proyecto
│   ├── init.rs                ← init(): scaffolding multi-provider
│   ├── plan.rs               ← run(): generación de backlog desde spec
│   ├── hooks.rs               ← run_hook(): comandos post-fase
│   ├── git.rs                 ← snapshot(), rollback()
│   └── daemon.rs              ← detach(), status(), kill(), follow()
├── roadmap/                   ← Ideas y features futuras
└── tests/fixtures/            ← Archivos .md de ejemplo
```

---

## 6. Formato de historia esperado (contrato fijo)

```markdown
# STORY-NNN: Título

## Status
**Draft**   ← uno de los 9 estados

## Epic
EPIC-XXX

## Descripción
...

## Criterios de aceptación
- [ ] CA1
- [ ] CA2

## Dependencias       ← opcional
- Bloqueado por: STORY-XXX, STORY-YYY

## Activity Log       ← obligatorio
- YYYY-MM-DD | Actor | descripción
```

---

## 7. CLI

### Comandos

| Comando | Descripción |
|---------|-------------|
| `regista [DIR]` | Pipeline completo (default) |
| `regista validate [DIR]` | Chequeo pre-vuelo de integridad |
| `regista init [DIR]` | Scaffolding de proyecto nuevo |
| `regista plan <SPEC>` | Generar backlog desde spec |
| `regista help` | Mostrar todos los comandos y flags |

### Flags principales

| Flag | Descripción |
|------|-------------|
| `--provider <NAME>` | Provider a usar (pi, claude, codex, opencode) |
| `--once` | Una sola iteración |
| `--json` | Salida JSON a stdout |
| `--quiet` | Suprimir logs de progreso |
| `--dry-run` | Simular sin ejecutar agentes |
| `--resume` | Reanudar desde checkpoint |
| `--clean-state` | Borrar checkpoint |
| `--story <ID>` | Filtrar por historia |
| `--epic <ID>` | Filtrar por épica |
| `--epics <RANGE>` | Filtrar por rango de épicas |
| `--config <FILE>` | Configuración alternativa |
| `--log-file <FILE>` | Archivo de log |
| `--detach` | Modo daemon |
| `--follow` | Ver log del daemon |
| `--status` | Estado del daemon |
| `--kill` | Detener daemon |

### Exit codes

| Código | Significado |
|--------|-------------|
| 0 | Pipeline completo, 0 `Failed` |
| 2 | Pipeline completo, ≥1 `Failed` |
| 3 | Parada temprana por límite (`max_iterations` o `max_wall_time`) |

---

## 8. Checkpoint / Resume

Tras cada `process_story()` exitoso, el orquestador guarda su estado en
`<project_dir>/.regista/state.toml`:

```toml
iteration = 7

[reject_cycles]
"STORY-013" = 2

[story_iterations]
"STORY-001" = 4

[story_errors]
"STORY-015" = "max_reject_cycles alcanzado"
```

Al reanudar con `--resume`, se restauran los contadores y se continúa desde
la iteración guardada. El checkpoint se limpia automáticamente al llegar a
`PipelineComplete`.

---

## 9. Feedback rico de agentes

Cuando `inject_feedback_on_retry = true` (default):

1. En cada intento fallido, se guarda stdout/stderr en
   `.regista/decisions/<STORY>-<actor>-<timestamp>.md`.
2. En el reintento, el prompt se modifica:
   ```
   ⚠️ Tu intento anterior falló. Esto fue lo ocurrido:
     [stderr del intento anterior]
   Corrige el error e inténtalo de nuevo.
   ---
   [prompt original]
   ```
3. El `AgentResult` incluye `attempts: Vec<AttemptTrace>` con la traza completa.

---

## 10. Dry-run

`--dry-run` simula el pipeline en memoria sin invocar agentes ni modificar
archivos. Usa `Story::advance_status_in_memory()` para mutar estados sin
escribir a disco. Muestra qué transiciones se harían, qué historias se
desbloquearían, y estima el tiempo total. Compatible con `--json`.

---

## 11. Groom — Generación automática de backlog

`regista plan <spec.md>` invoca al PO para descomponer una spec en historias
y épicas. Tras generar, ejecuta un **bucle de validación**:

```
plan → generate → validate dependencias
  ├── OK → terminar
  └── errores → feedback al PO → corregir → validate → ...
```

Máximo de iteraciones configurable: `plan_max_iterations` (default 5).

---

## 12. Plan de implementación (histórico)

| Fase | Qué | Resultado |
|------|-----|-----------|
| F1–F12 | Crate base, CLI, máquina de estados, pipeline, daemon, tests | 82 tests ✅ |
| F13 | Salida JSON + CI/CD, dry-run | `--json`, `--dry-run`|
| F14 | `regista validate`, `regista init` | Subcomandos ✅ |
| F15 | `regista plan` | Generación de backlog ✅ |
| F16 | Checkpoint/resume + feedback rico | `--resume`, feedback en retry ✅ |
| F17 | **Multi-provider (#20)** | pi, Claude Code, Codex, OpenCode ✅ |