karajan-code 1.36.0 → 1.37.0
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/README.md +156 -39
- package/bin/kj-tail +294 -41
- package/docs/README.es.md +150 -47
- package/package.json +2 -1
- package/src/orchestrator/iteration-stages.js +50 -15
- package/src/orchestrator/post-loop-stages.js +25 -11
- package/src/orchestrator/pre-loop-stages.js +28 -20
- package/src/orchestrator/preflight-checks.js +3 -3
- package/src/orchestrator/solomon-escalation.js +3 -2
- package/src/orchestrator.js +6 -5
- package/src/utils/display.js +133 -23
- package/src/utils/injection-guard.js +171 -0
package/docs/README.es.md
CHANGED
|
@@ -23,39 +23,40 @@
|
|
|
23
23
|
|
|
24
24
|
## Que es Karajan Code?
|
|
25
25
|
|
|
26
|
-
Karajan Code (`kj`) orquesta multiples agentes de IA a traves de un pipeline automatizado: generacion de codigo, analisis estatico, revision de codigo, testing y auditorias de seguridad
|
|
26
|
+
Karajan Code (`kj`) orquesta multiples agentes de IA a traves de un pipeline automatizado: generacion de codigo, analisis estatico, revision de codigo, testing y auditorias de seguridad. Todo en un solo comando.
|
|
27
27
|
|
|
28
28
|
En lugar de ejecutar un agente de IA y revisar manualmente su output, `kj` encadena agentes con quality gates. El coder escribe codigo, SonarQube lo analiza, el reviewer lo revisa, y si hay problemas, el coder recibe otra oportunidad. Este bucle se repite hasta que el codigo es aprobado o se alcanza el limite de iteraciones.
|
|
29
29
|
|
|
30
30
|
**Caracteristicas principales:**
|
|
31
31
|
- **Pipeline multi-agente** con 11 roles configurables
|
|
32
32
|
- **5 agentes de IA soportados**: Claude, Codex, Gemini, Aider, OpenCode
|
|
33
|
-
- **Servidor MCP** con 20 herramientas
|
|
34
|
-
- **Bootstrap obligatorio
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
|
|
53
|
-
|
|
33
|
+
- **Servidor MCP** con 20 herramientas. Usa `kj` desde Claude, Codex o cualquier host compatible con MCP sin salir de tu agente. [Ver configuracion MCP](#servidor-mcp)
|
|
34
|
+
- **Bootstrap obligatorio.** Valida prerequisitos del entorno (git, remote, config, agentes, SonarQube) antes de cada ejecucion. Si algo falta, para con instrucciones claras
|
|
35
|
+
- **Guardia anti-inyeccion.** Escanea diffs antes de pasarlos a la IA: detecta directivas de override, Unicode invisible y payloads ocultos en comentarios. Tambien como GitHub Action en cada PR
|
|
36
|
+
- **TDD obligatorio.** Se exigen cambios en tests cuando se modifican ficheros fuente
|
|
37
|
+
- **Integracion con SonarQube.** Analisis estatico con quality gates (requiere [Docker](#requisitos))
|
|
38
|
+
- **Perfiles de revision.** standard, strict, relaxed, paranoid
|
|
39
|
+
- **Tracking de presupuesto.** Monitorizacion de tokens y costes por sesion con `--trace`
|
|
40
|
+
- **Automatizacion Git.** Auto-commit, auto-push, auto-PR tras aprobacion
|
|
41
|
+
- **Gestion de sesiones.** Pausa/reanudacion con deteccion fail-fast y limpieza automatica de sesiones expiradas
|
|
42
|
+
- **Sistema de plugins.** Extiende con agentes custom via `.karajan/plugins/`
|
|
43
|
+
- **Checkpoints interactivos.** En lugar de matar tareas largas, pausa cada 5 minutos con un informe de progreso y te deja decidir: continuar, parar o ajustar el tiempo
|
|
44
|
+
- **Descomposicion de tareas.** Triage detecta cuando una tarea debe dividirse y recomienda subtareas; con integracion Planning Game, crea cards vinculadas con bloqueo secuencial
|
|
45
|
+
- **Retry con backoff.** Recuperacion automatica ante errores transitorios de API (429, 5xx) con backoff exponencial y jitter
|
|
46
|
+
- **Pipeline stage tracker.** Vista de progreso acumulativo durante `kj_run` mostrando que stages estan completadas, en ejecucion o pendientes, tanto en CLI como via eventos MCP para renderizado en tiempo real en el host
|
|
47
|
+
- **Guardarrailes de observabilidad del planner.** Telemetria continua de heartbeat/stall, proteccion configurable por silencio maximo (`session.max_agent_silence_minutes`) y limite duro de ejecucion (`session.max_planner_minutes`) para evitar bloqueos prolongados en `kj_plan`/planner
|
|
48
|
+
- **Standby por rate-limit.** Cuando un agente alcanza limites de uso, Karajan parsea el tiempo de espera, espera con backoff exponencial y reanuda automaticamente en vez de fallar
|
|
49
|
+
- **Preflight handshake.** `kj_preflight` requiere confirmacion humana de la configuracion de agentes antes de ejecutar, previniendo que la IA cambie asignaciones silenciosamente
|
|
50
|
+
- **Config de 3 niveles.** Sesion > proyecto > global con scoping de `kj_agents`
|
|
51
|
+
- **Mediacion inteligente del reviewer.** El scope filter difiere automaticamente issues del reviewer fuera de scope (ficheros no presentes en el diff) como deuda tecnica rastreada en vez de bloquear; Solomon media reviews estancados; el contexto diferido se inyecta en el prompt del coder
|
|
52
|
+
- **Integracion con Planning Game.** Combina opcionalmente con [Planning Game](https://github.com/AgenteIA-Geniova/planning-game) para gestion agil de proyectos (tareas, sprints, estimacion). Como Jira, pero open-source y nativo XP
|
|
53
|
+
|
|
54
|
+
> **Mejor con MCP.** Karajan Code esta disenado para usarse como servidor MCP dentro de tu agente de IA (Claude, Codex, etc.). El agente envia tareas a `kj_run`, recibe notificaciones de progreso en tiempo real, y obtiene resultados estructurados. Sin copiar y pegar.
|
|
54
55
|
|
|
55
56
|
## Requisitos
|
|
56
57
|
|
|
57
58
|
- **Node.js** >= 18
|
|
58
|
-
- **Docker**
|
|
59
|
+
- **Docker** (necesario para SonarQube). Si no lo necesitas, desactivalo con `--no-sonar` o `sonarqube.enabled: false`
|
|
59
60
|
- Al menos un agente de IA instalado: Claude, Codex, Gemini o Aider
|
|
60
61
|
|
|
61
62
|
## Pipeline
|
|
@@ -66,7 +67,7 @@ triage? ─> researcher? ─> planner? ─> coder ─> refactorer? ─> sonar?
|
|
|
66
67
|
|
|
67
68
|
| Rol | Descripcion | Por defecto |
|
|
68
69
|
|-----|-------------|-------------|
|
|
69
|
-
| **triage** | Director de pipeline
|
|
70
|
+
| **triage** | Director de pipeline: analiza la complejidad y activa roles dinamicamente | **On** |
|
|
70
71
|
| **researcher** | Investiga el contexto del codebase antes de planificar | Off |
|
|
71
72
|
| **planner** | Genera planes de implementacion estructurados | Off |
|
|
72
73
|
| **coder** | Escribe codigo y tests siguiendo metodologia TDD | **Siempre activo** |
|
|
@@ -75,7 +76,7 @@ triage? ─> researcher? ─> planner? ─> coder ─> refactorer? ─> sonar?
|
|
|
75
76
|
| **reviewer** | Revision de codigo con perfiles de exigencia configurables | **Siempre activo** |
|
|
76
77
|
| **tester** | Quality gate de tests y verificacion de cobertura | **On** |
|
|
77
78
|
| **security** | Auditoria de seguridad OWASP | **On** |
|
|
78
|
-
| **solomon** | Supervisor de sesion
|
|
79
|
+
| **solomon** | Supervisor de sesion: monitoriza salud de iteraciones con 5 reglas (incl. reviewer overreach), media reviews estancados, escala ante anomalias | **On** |
|
|
79
80
|
| **commiter** | Automatizacion de git commit, push y PR tras aprobacion | Off |
|
|
80
81
|
|
|
81
82
|
Los roles marcados con `?` son opcionales y se pueden activar por ejecucion o via config.
|
|
@@ -129,33 +130,135 @@ Guias completas: [`docs/multi-instance.md`](multi-instance.md) | [`docs/install-
|
|
|
129
130
|
|
|
130
131
|
`kj init` auto-detecta los agentes instalados. Si solo hay uno disponible, se asigna a todos los roles automaticamente.
|
|
131
132
|
|
|
132
|
-
##
|
|
133
|
+
## Tres formas de usar Karajan
|
|
134
|
+
|
|
135
|
+
Karajan instala **tres comandos**: `kj`, `kj-tail` y `karajan-mcp`.
|
|
136
|
+
|
|
137
|
+
### 1. CLI: directamente desde terminal
|
|
133
138
|
|
|
134
139
|
```bash
|
|
135
|
-
# Ejecutar una tarea con defaults (claude=coder, codex=reviewer, TDD)
|
|
136
140
|
kj run "Implementar autenticacion de usuario con JWT"
|
|
137
|
-
|
|
138
|
-
# Solo coder (sin revision)
|
|
139
141
|
kj code "Anadir validacion de inputs al formulario de registro"
|
|
140
|
-
|
|
141
|
-
# Solo reviewer (revisar diff actual)
|
|
142
142
|
kj review "Revisar los cambios de autenticacion"
|
|
143
|
+
kj plan "Refactorizar la capa de base de datos"
|
|
144
|
+
```
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
kj plan "Refactorizar la capa de base de datos para usar connection pooling"
|
|
146
|
+
### 2. MCP: dentro de tu agente de IA
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
148
|
+
El caso de uso principal. Karajan corre como servidor MCP dentro de Claude Code, Codex o Gemini. El agente tiene acceso a 20 herramientas (`kj_run`, `kj_code`, `kj_review`, etc.) y delega el trabajo pesado al pipeline de Karajan.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Tu → Claude Code → kj_run (via MCP) → triage → coder → sonar → reviewer → tester → security
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**El problema**: cuando Karajan corre dentro de un agente de IA, pierdes visibilidad. El agente te muestra el resultado final, pero no las etapas del pipeline, iteraciones o decisiones de Solomon en tiempo real.
|
|
155
|
+
|
|
156
|
+
### 3. kj-tail: monitorizar desde otro terminal
|
|
157
|
+
|
|
158
|
+
**La herramienta companera.** Abre un segundo terminal en el **mismo directorio del proyecto** donde esta trabajando tu agente de IA:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
kj-tail
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Veras la salida del pipeline en vivo (etapas, resultados, iteraciones, errores) tal como ocurren.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
kj-tail # Seguir pipeline en tiempo real (por defecto)
|
|
168
|
+
kj-tail -v # Verbose: incluir heartbeats de agente y presupuesto
|
|
169
|
+
kj-tail -t # Mostrar timestamps
|
|
170
|
+
kj-tail -s # Snapshot: mostrar log actual y salir
|
|
171
|
+
kj-tail -n 50 # Mostrar ultimas 50 lineas y seguir
|
|
172
|
+
kj-tail --help # Todas las opciones
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
> **Importante**: `kj-tail` debe ejecutarse desde el mismo directorio donde el agente de IA esta trabajando. Lee `<proyecto>/.kj/run.log`, que se crea cuando Karajan arranca un pipeline via MCP.
|
|
176
|
+
|
|
177
|
+
**Flujo tipico:**
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
┌──────────────────────────┐ ┌──────────────────────────┐
|
|
181
|
+
│ Terminal 1 │ │ Terminal 2 │
|
|
182
|
+
│ │ │ │
|
|
183
|
+
│ $ claude │ │ $ kj-tail │
|
|
184
|
+
│ > implementa la │ │ │
|
|
185
|
+
│ siguiente tarea │ │ ├─ 📋 Triage: medium │
|
|
186
|
+
│ prioritaria │ │ ├─ 🔬 Researcher ✅ │
|
|
187
|
+
│ │ │ ├─ 🧠 Planner ✅ │
|
|
188
|
+
│ (Claude llama a kj_run │ │ ├─ 🔨 Coder ✅ │
|
|
189
|
+
│ via MCP, solo ves │ │ ├─ 🔍 Sonar: OK │
|
|
190
|
+
│ el resultado final) │ │ ├─ 👁️ Reviewer ❌ │
|
|
191
|
+
│ │ │ ├─ ⚖️ Solomon: 2 cond. │
|
|
192
|
+
│ │ │ ├─ 🔨 Coder (iter 2) ✅ │
|
|
193
|
+
│ │ │ ├─ ✅ Review: APPROVED │
|
|
194
|
+
│ │ │ ├─ 🧪 Tester: passed │
|
|
195
|
+
│ │ │ └─ 🏁 Result: APPROVED │
|
|
196
|
+
└──────────────────────────┘ └──────────────────────────┘
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Ejemplo con pipeline completo**, tarea compleja con todos los roles:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
┌─ Terminal 1 ─────────────────────────────────────────────────────────────────┐
|
|
203
|
+
│ │
|
|
204
|
+
│ $ claude │
|
|
205
|
+
│ │
|
|
206
|
+
│ > Construye una API REST para un sistema de reservas. Requisitos: │
|
|
207
|
+
│ > - Express + TypeScript con validacion Zod en cada endpoint │
|
|
208
|
+
│ > - Endpoints: POST /bookings, GET /bookings/:id, │
|
|
209
|
+
│ > PATCH /bookings/:id/cancel │
|
|
210
|
+
│ > - Una reserva tiene: id, guestName, roomType (standard|suite|penthouse), │
|
|
211
|
+
│ > checkIn, checkOut, status (confirmed|cancelled) │
|
|
212
|
+
│ > - Validar: checkOut posterior a checkIn, sin fechas pasadas, │
|
|
213
|
+
│ > roomType debe ser un valor valido del enum │
|
|
214
|
+
│ > - Cancelar devuelve 409 si ya esta cancelada │
|
|
215
|
+
│ > - Usa TDD. Ejecutalo con Karajan con architect y planner activos, │
|
|
216
|
+
│ > modo paranoid. Coder claude, reviewer codex. │
|
|
217
|
+
│ │
|
|
218
|
+
│ Claude llama a kj_run via MCP con: │
|
|
219
|
+
│ --enable-architect --enable-researcher --enable-planner --mode paranoid │
|
|
220
|
+
│ │
|
|
221
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
222
|
+
|
|
223
|
+
┌─ Terminal 2: kj-tail ────────────────────────────────────────────────────────┐
|
|
224
|
+
│ │
|
|
225
|
+
│ kj-tail v1.37.0 — .kj/run.log │
|
|
226
|
+
│ │
|
|
227
|
+
│ ├─ 📋 Triage: medium (sw) — activando researcher, architect, planner │
|
|
228
|
+
│ ├─ ⚙️ Preflight passed — all checks OK │
|
|
229
|
+
│ ├─ 🔬 Researcher: 8 ficheros, 3 patrones, 5 restricciones │
|
|
230
|
+
│ ├─ 🏗️ Architect: diseno 3 capas (routes → service → validators) │
|
|
231
|
+
│ ├─ 🧠 Planner: 6 pasos — tests primero, luego rutas, servicio, validadores │
|
|
232
|
+
│ │ │
|
|
233
|
+
│ ▶ Iteracion 1/5 │
|
|
234
|
+
│ ├─ 🔨 Coder (claude): 3 endpoints + 18 tests │
|
|
235
|
+
│ ├─ 📋 TDD: PASS (3 src, 2 test) │
|
|
236
|
+
│ ├─ 🔍 Sonar: Quality gate OK │
|
|
237
|
+
│ ├─ 👁️ Reviewer (codex): REJECTED (2 blocking) │
|
|
238
|
+
│ │ "Falta 404 para GET booking inexistente" │
|
|
239
|
+
│ │ "Endpoint cancel sin test de idempotencia" │
|
|
240
|
+
│ ├─ ⚖️ Solomon: approve_with_conditions (2 condiciones) │
|
|
241
|
+
│ │ "Anadir respuesta 404 y test para GET /bookings/:id con id desconocido" │
|
|
242
|
+
│ │ "Anadir test: cancelar reserva ya cancelada devuelve 409, no 500" │
|
|
243
|
+
│ │ │
|
|
244
|
+
│ ▶ Iteracion 2/5 │
|
|
245
|
+
│ ├─ 🔨 Coder (claude): corregido — 22 tests │
|
|
246
|
+
│ ├─ 📋 TDD: PASS │
|
|
247
|
+
│ ├─ 🔍 Sonar: OK │
|
|
248
|
+
│ ├─ 👁️ Reviewer (codex): APPROVED │
|
|
249
|
+
│ ├─ 🧪 Tester: passed — cobertura 94%, 22 tests │
|
|
250
|
+
│ ├─ 🔒 Security: passed — 0 criticos, 1 bajo (helmet recomendado) │
|
|
251
|
+
│ ├─ 📊 Audit: CERTIFIED (3 advertencias) │
|
|
252
|
+
│ │ │
|
|
253
|
+
│ 🏁 Resultado: APPROVED │
|
|
254
|
+
│ 🔬 Investigacion: 8 ficheros, 3 patrones │
|
|
255
|
+
│ 🗺 Plan: 6 pasos (tests primero) │
|
|
256
|
+
│ 🧪 Cobertura: 94%, 22 tests │
|
|
257
|
+
│ 🔒 Seguridad: OK │
|
|
258
|
+
│ 🔍 Sonar: OK │
|
|
259
|
+
│ 💰 Presupuesto: $0.42 (claude: $0.38, codex: $0.04) │
|
|
260
|
+
│ │
|
|
261
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
159
262
|
```
|
|
160
263
|
|
|
161
264
|
## Comandos CLI
|
|
@@ -230,7 +333,7 @@ Cada rol tiene un template `.md` con instrucciones que el agente de IA sigue. Lo
|
|
|
230
333
|
|
|
231
334
|
Usa `kj roles show <rol>` para inspeccionar cualquier template. Crea un override de proyecto para personalizar el comportamiento por proyecto.
|
|
232
335
|
|
|
233
|
-
**Variantes de revision**: `reviewer-strict`, `reviewer-relaxed`, `reviewer-paranoid
|
|
336
|
+
**Variantes de revision**: `reviewer-strict`, `reviewer-relaxed`, `reviewer-paranoid`, seleccionables via flag `--mode` o config `review_mode`.
|
|
234
337
|
|
|
235
338
|
## Contribuir
|
|
236
339
|
|
|
@@ -238,7 +341,7 @@ Usa `kj roles show <rol>` para inspeccionar cualquier template. Crea un override
|
|
|
238
341
|
git clone https://github.com/manufosela/karajan-code.git
|
|
239
342
|
cd karajan-code
|
|
240
343
|
npm install
|
|
241
|
-
npm test # Ejecutar
|
|
344
|
+
npm test # Ejecutar 2044 tests con Vitest
|
|
242
345
|
npm run test:watch # Modo watch
|
|
243
346
|
npm run validate # Lint + test
|
|
244
347
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "karajan-code",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"bin": {
|
|
40
40
|
"kj": "bin/kj.js",
|
|
41
|
+
"kj-tail": "bin/kj-tail",
|
|
41
42
|
"karajan-mcp": "bin/karajan-mcp.js"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
@@ -21,7 +21,7 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
21
21
|
emitter,
|
|
22
22
|
makeEvent("coder:start", { ...eventBase, stage: "coder" }, {
|
|
23
23
|
message: `Coder (${coderRole.provider}) running`,
|
|
24
|
-
detail: { coder: coderRole.provider }
|
|
24
|
+
detail: { coder: coderRole.provider, provider: coderRole.provider, executorType: "agent" }
|
|
25
25
|
})
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -89,7 +89,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
89
89
|
emitProgress(
|
|
90
90
|
emitter,
|
|
91
91
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
92
|
-
message: `Coder completed (fallback: ${fallbackCoder})
|
|
92
|
+
message: `Coder completed (fallback: ${fallbackCoder})`,
|
|
93
|
+
detail: { provider: fallbackCoder, executorType: "agent" }
|
|
93
94
|
})
|
|
94
95
|
);
|
|
95
96
|
return;
|
|
@@ -114,7 +115,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
114
115
|
emitter,
|
|
115
116
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
116
117
|
status: "fail",
|
|
117
|
-
message: `Coder failed: ${details}
|
|
118
|
+
message: `Coder failed: ${details}`,
|
|
119
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
118
120
|
})
|
|
119
121
|
);
|
|
120
122
|
throw new Error(`Coder failed: ${details}`);
|
|
@@ -124,7 +126,8 @@ export async function runCoderStage({ coderRoleInstance, coderRole, config, logg
|
|
|
124
126
|
emitProgress(
|
|
125
127
|
emitter,
|
|
126
128
|
makeEvent("coder:end", { ...eventBase, stage: "coder" }, {
|
|
127
|
-
message: "Coder completed"
|
|
129
|
+
message: "Coder completed",
|
|
130
|
+
detail: { provider: coderRole.provider, executorType: "agent" }
|
|
128
131
|
})
|
|
129
132
|
);
|
|
130
133
|
}
|
|
@@ -135,7 +138,7 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
135
138
|
emitter,
|
|
136
139
|
makeEvent("refactorer:start", { ...eventBase, stage: "refactorer" }, {
|
|
137
140
|
message: `Refactorer (${refactorerRole.provider}) running`,
|
|
138
|
-
detail: { refactorer: refactorerRole.provider }
|
|
141
|
+
detail: { refactorer: refactorerRole.provider, provider: refactorerRole.provider, executorType: "agent" }
|
|
139
142
|
})
|
|
140
143
|
);
|
|
141
144
|
const refactorerOnOutput = ({ stream, line }) => {
|
|
@@ -184,7 +187,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
184
187
|
emitter,
|
|
185
188
|
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
186
189
|
status: "fail",
|
|
187
|
-
message: `Refactorer failed: ${details}
|
|
190
|
+
message: `Refactorer failed: ${details}`,
|
|
191
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
188
192
|
})
|
|
189
193
|
);
|
|
190
194
|
throw new Error(`Refactorer failed: ${details}`);
|
|
@@ -193,7 +197,8 @@ export async function runRefactorerStage({ refactorerRole, config, logger, emitt
|
|
|
193
197
|
emitProgress(
|
|
194
198
|
emitter,
|
|
195
199
|
makeEvent("refactorer:end", { ...eventBase, stage: "refactorer" }, {
|
|
196
|
-
message: "Refactorer completed"
|
|
200
|
+
message: "Refactorer completed",
|
|
201
|
+
detail: { provider: refactorerRole.provider, executorType: "agent" }
|
|
197
202
|
})
|
|
198
203
|
);
|
|
199
204
|
}
|
|
@@ -287,7 +292,8 @@ export async function runTddCheckStage({ config, logger, emitter, eventBase, ses
|
|
|
287
292
|
ok: tddEval.ok,
|
|
288
293
|
reason: tddEval.reason,
|
|
289
294
|
sourceFiles: tddEval.sourceFiles?.length || 0,
|
|
290
|
-
testFiles: tddEval.testFiles?.length || 0
|
|
295
|
+
testFiles: tddEval.testFiles?.length || 0,
|
|
296
|
+
executorType: "local"
|
|
291
297
|
}
|
|
292
298
|
})
|
|
293
299
|
);
|
|
@@ -386,7 +392,8 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
386
392
|
emitProgress(
|
|
387
393
|
emitter,
|
|
388
394
|
makeEvent("sonar:start", { ...eventBase, stage: "sonar" }, {
|
|
389
|
-
message: "SonarQube scanning"
|
|
395
|
+
message: "SonarQube scanning",
|
|
396
|
+
detail: { provider: "sonarqube", executorType: "local" }
|
|
390
397
|
})
|
|
391
398
|
);
|
|
392
399
|
|
|
@@ -470,7 +477,7 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
470
477
|
makeEvent("sonar:end", { ...eventBase, stage: "sonar" }, {
|
|
471
478
|
status: sonarResult.blocking ? "fail" : "ok",
|
|
472
479
|
message: `Quality gate: ${sonarResult.gateStatus}`,
|
|
473
|
-
detail: { projectKey: sonarResult.projectKey, gateStatus: sonarResult.gateStatus, openIssues: sonarResult.openIssuesTotal }
|
|
480
|
+
detail: { projectKey: sonarResult.projectKey, gateStatus: sonarResult.gateStatus, openIssues: sonarResult.openIssuesTotal, provider: "sonarqube", executorType: "local" }
|
|
474
481
|
})
|
|
475
482
|
);
|
|
476
483
|
|
|
@@ -498,7 +505,8 @@ export async function runSonarCloudStage({ config, logger, emitter, eventBase, s
|
|
|
498
505
|
emitProgress(
|
|
499
506
|
emitter,
|
|
500
507
|
makeEvent("sonarcloud:start", { ...eventBase, stage: "sonarcloud" }, {
|
|
501
|
-
message: "SonarCloud scanning"
|
|
508
|
+
message: "SonarCloud scanning",
|
|
509
|
+
detail: { provider: "sonarcloud", executorType: "local" }
|
|
502
510
|
})
|
|
503
511
|
);
|
|
504
512
|
|
|
@@ -532,7 +540,7 @@ export async function runSonarCloudStage({ config, logger, emitter, eventBase, s
|
|
|
532
540
|
makeEvent("sonarcloud:end", { ...eventBase, stage: "sonarcloud" }, {
|
|
533
541
|
status,
|
|
534
542
|
message,
|
|
535
|
-
detail: { projectKey: result.projectKey, exitCode: result.exitCode }
|
|
543
|
+
detail: { projectKey: result.projectKey, exitCode: result.exitCode, provider: "sonarcloud", executorType: "local" }
|
|
536
544
|
})
|
|
537
545
|
);
|
|
538
546
|
|
|
@@ -681,7 +689,7 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
681
689
|
emitter,
|
|
682
690
|
makeEvent("reviewer:start", { ...eventBase, stage: "reviewer" }, {
|
|
683
691
|
message: `Reviewer (${reviewerRole.provider}) running`,
|
|
684
|
-
detail: { reviewer: reviewerRole.provider }
|
|
692
|
+
detail: { reviewer: reviewerRole.provider, provider: reviewerRole.provider, executorType: "agent" }
|
|
685
693
|
})
|
|
686
694
|
);
|
|
687
695
|
|
|
@@ -692,6 +700,30 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
692
700
|
logger.warn(`Review diff generation failed: ${err.message}`);
|
|
693
701
|
return { approved: false, blocking_issues: [{ description: `Diff generation failed: ${err.message}` }], non_blocking_suggestions: [], summary: `Reviewer failed: cannot generate diff — ${err.message}`, confidence: 0 };
|
|
694
702
|
}
|
|
703
|
+
|
|
704
|
+
// Injection guard: scan diff before sending to AI reviewer
|
|
705
|
+
const { scanDiff } = await import("../utils/injection-guard.js");
|
|
706
|
+
const guardResult = scanDiff(diff);
|
|
707
|
+
if (!guardResult.clean) {
|
|
708
|
+
logger.warn(`Injection guard: ${guardResult.summary}`);
|
|
709
|
+
emitProgress(emitter, makeEvent("guard:injection", { ...eventBase, stage: "reviewer" }, {
|
|
710
|
+
message: `Injection guard blocked review: ${guardResult.summary}`,
|
|
711
|
+
detail: { findings: guardResult.findings, summary: guardResult.summary }
|
|
712
|
+
}));
|
|
713
|
+
return {
|
|
714
|
+
approved: false,
|
|
715
|
+
blocking_issues: guardResult.findings.map((f) => ({
|
|
716
|
+
id: `INJECTION_${f.type.toUpperCase()}`,
|
|
717
|
+
severity: "critical",
|
|
718
|
+
description: `Potential prompt injection (${f.type}): ${f.snippet}`,
|
|
719
|
+
line: f.line,
|
|
720
|
+
})),
|
|
721
|
+
non_blocking_suggestions: [],
|
|
722
|
+
summary: `Review blocked by injection guard: ${guardResult.summary}`,
|
|
723
|
+
confidence: 1,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
695
727
|
const reviewerOnOutput = ({ stream, line }) => {
|
|
696
728
|
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "reviewer" }, {
|
|
697
729
|
message: line,
|
|
@@ -754,7 +786,8 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
754
786
|
emitter,
|
|
755
787
|
makeEvent("reviewer:end", { ...eventBase, stage: "reviewer" }, {
|
|
756
788
|
status: "fail",
|
|
757
|
-
message: `Reviewer failed: ${details}
|
|
789
|
+
message: `Reviewer failed: ${details}`,
|
|
790
|
+
detail: { provider: reviewerRole.provider, executorType: "agent" }
|
|
758
791
|
})
|
|
759
792
|
);
|
|
760
793
|
throw new Error(`Reviewer failed: ${details}`);
|
|
@@ -836,7 +869,9 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
836
869
|
blockingCount: review.blocking_issues.length,
|
|
837
870
|
issues: review.blocking_issues.map(
|
|
838
871
|
(x) => `${x.id || "ISSUE"}: ${x.description || "Missing description"}`
|
|
839
|
-
)
|
|
872
|
+
),
|
|
873
|
+
provider: reviewerRole.provider,
|
|
874
|
+
executorType: "agent"
|
|
840
875
|
}
|
|
841
876
|
})
|
|
842
877
|
);
|
|
@@ -98,7 +98,8 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
98
98
|
emitProgress(
|
|
99
99
|
emitter,
|
|
100
100
|
makeEvent("tester:start", { ...eventBase, stage: "tester" }, {
|
|
101
|
-
message: "Tester evaluating test quality"
|
|
101
|
+
message: "Tester evaluating test quality",
|
|
102
|
+
detail: { provider: config?.roles?.tester?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
102
103
|
})
|
|
103
104
|
);
|
|
104
105
|
|
|
@@ -126,11 +127,13 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
126
127
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
const testerProvider = provider || coderRole.provider;
|
|
129
131
|
emitProgress(
|
|
130
132
|
emitter,
|
|
131
133
|
makeEvent("tester:end", { ...eventBase, stage: "tester" }, {
|
|
132
134
|
status: testerOutput.ok ? "ok" : "fail",
|
|
133
|
-
message: testerOutput.ok ? "Tester passed" : `Tester: ${testerOutput.summary}
|
|
135
|
+
message: testerOutput.ok ? "Tester passed" : `Tester: ${testerOutput.summary}`,
|
|
136
|
+
detail: { ok: testerOutput.ok, summary: testerOutput.summary, provider: testerProvider, executorType: "agent" }
|
|
134
137
|
})
|
|
135
138
|
);
|
|
136
139
|
|
|
@@ -143,7 +146,7 @@ export async function runTesterStage({ config, logger, emitter, eventBase, sessi
|
|
|
143
146
|
makeEvent("tester:auto-continue", { ...eventBase, stage: "tester" }, {
|
|
144
147
|
status: "warn",
|
|
145
148
|
message: `Tester issues are advisory (reviewer approved), continuing: ${testerOutput.summary}`,
|
|
146
|
-
detail: { summary: testerOutput.summary, auto_continued: true }
|
|
149
|
+
detail: { summary: testerOutput.summary, auto_continued: true, provider: testerProvider, executorType: "agent" }
|
|
147
150
|
})
|
|
148
151
|
);
|
|
149
152
|
return { action: "ok", stageResult: { ok: false, summary: testerOutput.summary || "Tester issues (advisory)", auto_continued: true } };
|
|
@@ -158,7 +161,8 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
158
161
|
emitProgress(
|
|
159
162
|
emitter,
|
|
160
163
|
makeEvent("security:start", { ...eventBase, stage: "security" }, {
|
|
161
|
-
message: "Security auditing code"
|
|
164
|
+
message: "Security auditing code",
|
|
165
|
+
detail: { provider: config?.roles?.security?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
162
166
|
})
|
|
163
167
|
);
|
|
164
168
|
|
|
@@ -186,11 +190,13 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
186
190
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
187
191
|
});
|
|
188
192
|
|
|
193
|
+
const securityProvider = provider || coderRole.provider;
|
|
189
194
|
emitProgress(
|
|
190
195
|
emitter,
|
|
191
196
|
makeEvent("security:end", { ...eventBase, stage: "security" }, {
|
|
192
197
|
status: securityOutput.ok ? "ok" : "fail",
|
|
193
|
-
message: securityOutput.ok ? "Security audit passed" : `Security: ${securityOutput.summary}
|
|
198
|
+
message: securityOutput.ok ? "Security audit passed" : `Security: ${securityOutput.summary}`,
|
|
199
|
+
detail: { ok: securityOutput.ok, summary: securityOutput.summary, provider: securityProvider, executorType: "agent" }
|
|
194
200
|
})
|
|
195
201
|
);
|
|
196
202
|
|
|
@@ -246,7 +252,8 @@ export async function runImpeccableStage({ config, logger, emitter, eventBase, s
|
|
|
246
252
|
emitProgress(
|
|
247
253
|
emitter,
|
|
248
254
|
makeEvent("impeccable:start", { ...eventBase, stage: "impeccable" }, {
|
|
249
|
-
message: "Impeccable auditing frontend design quality"
|
|
255
|
+
message: "Impeccable auditing frontend design quality",
|
|
256
|
+
detail: { provider: config?.roles?.impeccable?.provider || coderRole.provider, executorType: "agent" }
|
|
250
257
|
})
|
|
251
258
|
);
|
|
252
259
|
|
|
@@ -277,13 +284,15 @@ export async function runImpeccableStage({ config, logger, emitter, eventBase, s
|
|
|
277
284
|
});
|
|
278
285
|
|
|
279
286
|
const verdict = impeccableOutput.result?.verdict || "APPROVED";
|
|
287
|
+
const impeccableProvider = config?.roles?.impeccable?.provider || coderRole.provider;
|
|
280
288
|
emitProgress(
|
|
281
289
|
emitter,
|
|
282
290
|
makeEvent("impeccable:end", { ...eventBase, stage: "impeccable" }, {
|
|
283
291
|
status: impeccableOutput.ok ? "ok" : "fail",
|
|
284
292
|
message: impeccableOutput.ok
|
|
285
293
|
? (verdict === "IMPROVED" ? "Impeccable applied design fixes" : "Impeccable audit passed")
|
|
286
|
-
: `Impeccable: ${impeccableOutput.summary}
|
|
294
|
+
: `Impeccable: ${impeccableOutput.summary}`,
|
|
295
|
+
detail: { provider: impeccableProvider, executorType: "agent" }
|
|
287
296
|
})
|
|
288
297
|
);
|
|
289
298
|
|
|
@@ -296,7 +305,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
296
305
|
emitProgress(
|
|
297
306
|
emitter,
|
|
298
307
|
makeEvent("audit:start", { ...eventBase, stage: "audit" }, {
|
|
299
|
-
message: "Final audit — verifying code quality"
|
|
308
|
+
message: "Final audit — verifying code quality",
|
|
309
|
+
detail: { provider: config?.roles?.audit?.provider || config?.roles?.coder?.provider || config?.coder || "claude", executorType: "agent" }
|
|
300
310
|
})
|
|
301
311
|
);
|
|
302
312
|
|
|
@@ -324,6 +334,7 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
324
334
|
attempts: attempts.length > 1 ? attempts : undefined
|
|
325
335
|
});
|
|
326
336
|
|
|
337
|
+
const auditProvider = provider || coderRole.provider;
|
|
327
338
|
if (!auditOutput.ok) {
|
|
328
339
|
// Audit agent failed to run — treat as advisory, don't block pipeline
|
|
329
340
|
logger.warn(`Audit agent error (advisory): ${auditOutput.summary}`);
|
|
@@ -331,7 +342,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
331
342
|
emitter,
|
|
332
343
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
333
344
|
status: "warn",
|
|
334
|
-
message: `Audit: agent error (advisory), continuing — ${auditOutput.summary}
|
|
345
|
+
message: `Audit: agent error (advisory), continuing — ${auditOutput.summary}`,
|
|
346
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
335
347
|
})
|
|
336
348
|
);
|
|
337
349
|
return { action: "ok", stageResult: { ok: false, summary: auditOutput.summary || "Audit agent error (advisory)", auto_continued: true } };
|
|
@@ -374,7 +386,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
374
386
|
emitter,
|
|
375
387
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
376
388
|
status: "fail",
|
|
377
|
-
message: `Audit: ${criticalCount + highCount} issue(s) found, sending back to coder
|
|
389
|
+
message: `Audit: ${criticalCount + highCount} issue(s) found, sending back to coder`,
|
|
390
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
378
391
|
})
|
|
379
392
|
);
|
|
380
393
|
|
|
@@ -392,7 +405,8 @@ export async function runFinalAuditStage({ config, logger, emitter, eventBase, s
|
|
|
392
405
|
emitter,
|
|
393
406
|
makeEvent("audit:end", { ...eventBase, stage: "audit" }, {
|
|
394
407
|
status: "ok",
|
|
395
|
-
message: certifiedMsg
|
|
408
|
+
message: certifiedMsg,
|
|
409
|
+
detail: { provider: auditProvider, executorType: "agent" }
|
|
396
410
|
})
|
|
397
411
|
);
|
|
398
412
|
|