karajan-code 1.38.1 → 1.38.2
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 +6 -6
- package/docs/README.es.md +13 -351
- package/package.json +1 -1
- package/src/guards/output-guard.js +12 -2
- package/src/hu/store.js +136 -0
- package/src/mcp/server-handlers.js +40 -1
- package/src/mcp/tools.js +21 -4
- package/src/orchestrator/iteration-stages.js +8 -3
- package/src/orchestrator/preflight-checks.js +2 -2
- package/src/orchestrator/solomon-escalation.js +63 -2
- package/src/review/diff-generator.js +6 -2
- package/src/review/tdd-policy.js +5 -3
- package/src/sonar/scanner.js +1 -1
- package/src/utils/display.js +5 -1
- package/src/utils/project-detect.js +75 -11
- package/templates/roles/coder.md +9 -0
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ This is the primary use case. Karajan runs as an MCP server inside Claude Code,
|
|
|
82
82
|
You → Claude Code → kj_run (via MCP) → triage → coder → sonar → reviewer → tester → security
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
The MCP server auto-registers during `npm install`. Your AI agent sees
|
|
85
|
+
The MCP server auto-registers during `npm install`. Your AI agent sees 21 tools (`kj_run`, `kj_code`, `kj_review`, etc.) and uses them as needed.
|
|
86
86
|
|
|
87
87
|
**The problem**: when Karajan runs inside an AI agent, you lose visibility. The agent shows you the final result, but not the pipeline stages, iterations, or Solomon decisions happening in real time.
|
|
88
88
|
|
|
@@ -154,7 +154,7 @@ kj-tail --help # Full options
|
|
|
154
154
|
|
|
155
155
|
┌─ Terminal 2: kj-tail ────────────────────────────────────────────────────────┐
|
|
156
156
|
│ │
|
|
157
|
-
│ kj-tail v1.38.
|
|
157
|
+
│ kj-tail v1.38.1 — .kj/run.log │
|
|
158
158
|
│ │
|
|
159
159
|
│ ├─ 📋 Triage: medium (sw) — enabling researcher, architect, planner │
|
|
160
160
|
│ ├─ ⚙️ Preflight passed — all checks OK │
|
|
@@ -233,7 +233,7 @@ hu-reviewer? → triage → discover? → architect? → planner? → coder →
|
|
|
233
233
|
|
|
234
234
|
Mix and match. Use Claude as coder and Codex as reviewer. Karajan auto-detects installed agents during `kj init`.
|
|
235
235
|
|
|
236
|
-
## MCP server (
|
|
236
|
+
## MCP server (21 tools)
|
|
237
237
|
|
|
238
238
|
After `npm install -g karajan-code`, the MCP server auto-registers in Claude and Codex. Manual config if needed:
|
|
239
239
|
|
|
@@ -245,7 +245,7 @@ After `npm install -g karajan-code`, the MCP server auto-registers in Claude and
|
|
|
245
245
|
# command = "karajan-mcp"
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
**
|
|
248
|
+
**21 tools** available: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_impeccable`, `kj_hu`.
|
|
249
249
|
|
|
250
250
|
Use `kj-tail` in a separate terminal to see what the pipeline is doing in real time (see [Three ways to use Karajan](#three-ways-to-use-karajan)).
|
|
251
251
|
|
|
@@ -267,7 +267,7 @@ Use `kj roles show <role>` to inspect any template.
|
|
|
267
267
|
|
|
268
268
|
Karajan auto-detects and auto-configures everything it can:
|
|
269
269
|
|
|
270
|
-
- **TDD**: Detects test framework (vitest, jest,
|
|
270
|
+
- **TDD**: Detects test framework for 12 languages (vitest, jest, JUnit, pytest, go test, cargo test, and more). Auto-enables TDD for code tasks, skips for doc/infra
|
|
271
271
|
- **Bootstrap gate**: Validates all prerequisites (git repo, remote, config, agents, SonarQube) before any tool runs. Fails hard with actionable fix instructions, never silently degrades
|
|
272
272
|
- **Injection guard**: Scans diffs for prompt injection before AI review. Detects directive overrides, invisible Unicode, oversized comment payloads. Also runs as a GitHub Action on every PR
|
|
273
273
|
- **SonarQube**: Auto-starts Docker container, generates config if missing
|
|
@@ -299,7 +299,7 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
|
|
|
299
299
|
git clone https://github.com/manufosela/karajan-code.git
|
|
300
300
|
cd karajan-code
|
|
301
301
|
npm install
|
|
302
|
-
npm test # Run
|
|
302
|
+
npm test # Run 2142 tests with Vitest
|
|
303
303
|
npm run validate # Lint + test
|
|
304
304
|
```
|
|
305
305
|
|
package/docs/README.es.md
CHANGED
|
@@ -1,151 +1,27 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<<<<<<< HEAD
|
|
3
|
-
<img src="karajan-code-logo-small.png" alt="Karajan Code" width="200">
|
|
4
|
-
=======
|
|
5
2
|
<img src="karajan-code-logo-small.png" alt="Karajan Code" width="180">
|
|
6
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
7
3
|
</p>
|
|
8
4
|
|
|
9
5
|
<h1 align="center">Karajan Code</h1>
|
|
10
6
|
|
|
11
7
|
<p align="center">
|
|
12
|
-
<<<<<<< HEAD
|
|
13
|
-
Orquestador local multi-agente con TDD, SonarQube y revision de codigo automatizada.
|
|
14
|
-
=======
|
|
15
8
|
Orquestador local multi-agente. TDD-first, basado en MCP, JavaScript vanilla.
|
|
16
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
17
9
|
</p>
|
|
18
10
|
|
|
19
11
|
<p align="center">
|
|
20
12
|
<a href="https://www.npmjs.com/package/karajan-code"><img src="https://img.shields.io/npm/v/karajan-code.svg" alt="npm version"></a>
|
|
21
|
-
<<<<<<< HEAD
|
|
22
|
-
=======
|
|
23
13
|
<a href="https://www.npmjs.com/package/karajan-code"><img src="https://img.shields.io/npm/dw/karajan-code.svg" alt="npm downloads"></a>
|
|
24
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
25
14
|
<a href="https://github.com/manufosela/karajan-code/actions"><img src="https://github.com/manufosela/karajan-code/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
26
15
|
<a href="https://www.gnu.org/licenses/agpl-3.0"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue.svg" alt="License"></a>
|
|
27
16
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node.js"></a>
|
|
28
17
|
</p>
|
|
29
18
|
|
|
30
19
|
<p align="center">
|
|
31
|
-
<<<<<<< HEAD
|
|
32
|
-
<a href="../README.md">Read in English</a>
|
|
33
|
-
=======
|
|
34
20
|
<a href="../README.md">Read in English</a> · <a href="https://karajancode.com">Documentacion</a>
|
|
35
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
36
21
|
</p>
|
|
37
22
|
|
|
38
23
|
---
|
|
39
24
|
|
|
40
|
-
<<<<<<< HEAD
|
|
41
|
-
## Que es Karajan Code?
|
|
42
|
-
|
|
43
|
-
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.
|
|
44
|
-
|
|
45
|
-
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.
|
|
46
|
-
|
|
47
|
-
**Caracteristicas principales:**
|
|
48
|
-
- **Pipeline multi-agente** con 11 roles configurables
|
|
49
|
-
- **5 agentes de IA soportados**: Claude, Codex, Gemini, Aider, OpenCode
|
|
50
|
-
- **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)
|
|
51
|
-
- **Bootstrap obligatorio** — valida prerequisitos del entorno (git, remote, config, agentes, SonarQube) antes de cada ejecucion. Si algo falta, para con instrucciones claras
|
|
52
|
-
- **TDD obligatorio** — se exigen cambios en tests cuando se modifican ficheros fuente
|
|
53
|
-
- **Integracion con SonarQube** — analisis estatico con quality gates (requiere [Docker](#requisitos))
|
|
54
|
-
- **Perfiles de revision** — standard, strict, relaxed, paranoid
|
|
55
|
-
- **Tracking de presupuesto** — monitorizacion de tokens y costes por sesion con `--trace`
|
|
56
|
-
- **Automatizacion Git** — auto-commit, auto-push, auto-PR tras aprobacion
|
|
57
|
-
- **Gestion de sesiones** — pausa/reanudacion con deteccion fail-fast y limpieza automatica de sesiones expiradas
|
|
58
|
-
- **Sistema de plugins** — extiende con agentes custom via `.karajan/plugins/`
|
|
59
|
-
- **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
|
|
60
|
-
- **Descomposicion de tareas** — triage detecta cuando una tarea debe dividirse y recomienda subtareas; con integracion Planning Game, crea cards vinculadas con bloqueo secuencial
|
|
61
|
-
- **Retry con backoff** — recuperacion automatica ante errores transitorios de API (429, 5xx) con backoff exponencial y jitter
|
|
62
|
-
- **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
|
|
63
|
-
- **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
|
|
64
|
-
- **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
|
|
65
|
-
- **Preflight handshake** — `kj_preflight` requiere confirmacion humana de la configuracion de agentes antes de ejecutar, previniendo que la IA cambie asignaciones silenciosamente
|
|
66
|
-
- **Config de 3 niveles** — sesion > proyecto > global con scoping de `kj_agents`
|
|
67
|
-
- **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
|
|
68
|
-
- **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
|
|
69
|
-
|
|
70
|
-
> **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.
|
|
71
|
-
|
|
72
|
-
## Requisitos
|
|
73
|
-
|
|
74
|
-
- **Node.js** >= 18
|
|
75
|
-
- **Docker** — necesario para el analisis estatico con SonarQube. Si no tienes Docker o no necesitas SonarQube, desactivalo con `--no-sonar` o `sonarqube.enabled: false` en la config
|
|
76
|
-
- Al menos un agente de IA instalado: Claude, Codex, Gemini o Aider
|
|
77
|
-
|
|
78
|
-
## Pipeline
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
triage? ─> researcher? ─> planner? ─> coder ─> refactorer? ─> sonar? ─> reviewer ─> tester? ─> security? ─> commiter?
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
| Rol | Descripcion | Por defecto |
|
|
85
|
-
|-----|-------------|-------------|
|
|
86
|
-
| **triage** | Director de pipeline — analiza la complejidad y activa roles dinamicamente | **On** |
|
|
87
|
-
| **researcher** | Investiga el contexto del codebase antes de planificar | Off |
|
|
88
|
-
| **planner** | Genera planes de implementacion estructurados | Off |
|
|
89
|
-
| **coder** | Escribe codigo y tests siguiendo metodologia TDD | **Siempre activo** |
|
|
90
|
-
| **refactorer** | Mejora la claridad del codigo sin cambiar comportamiento | Off |
|
|
91
|
-
| **sonar** | Ejecuta analisis estatico SonarQube y quality gates | On (si configurado) |
|
|
92
|
-
| **reviewer** | Revision de codigo con perfiles de exigencia configurables | **Siempre activo** |
|
|
93
|
-
| **tester** | Quality gate de tests y verificacion de cobertura | **On** |
|
|
94
|
-
| **security** | Auditoria de seguridad OWASP | **On** |
|
|
95
|
-
| **solomon** | Supervisor de sesion — monitoriza salud de iteraciones con 5 reglas (incl. reviewer overreach), media reviews estancados, escala ante anomalias | **On** |
|
|
96
|
-
| **commiter** | Automatizacion de git commit, push y PR tras aprobacion | Off |
|
|
97
|
-
|
|
98
|
-
Los roles marcados con `?` son opcionales y se pueden activar por ejecucion o via config.
|
|
99
|
-
|
|
100
|
-
## Instalacion
|
|
101
|
-
|
|
102
|
-
### Desde npm (recomendado)
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
npm install -g karajan-code
|
|
106
|
-
kj init
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Desde codigo fuente
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
git clone https://github.com/manufosela/karajan-code.git
|
|
113
|
-
cd karajan-code
|
|
114
|
-
./scripts/install.sh
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Setup no interactivo (CI/automatizacion)
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
./scripts/install.sh \
|
|
121
|
-
--non-interactive \
|
|
122
|
-
--kj-home /ruta/a/.karajan \
|
|
123
|
-
--sonar-host http://localhost:9000 \
|
|
124
|
-
--sonar-token "$KJ_SONAR_TOKEN" \
|
|
125
|
-
--coder claude \
|
|
126
|
-
--reviewer codex \
|
|
127
|
-
--run-doctor true
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Setup multi-instancia
|
|
131
|
-
|
|
132
|
-
Guias completas: [`docs/multi-instance.md`](multi-instance.md) | [`docs/install-two-instances.md`](install-two-instances.md)
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
./scripts/setup-multi-instance.sh
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Agentes soportados
|
|
139
|
-
|
|
140
|
-
| Agente | CLI | Instalacion |
|
|
141
|
-
|--------|-----|-------------|
|
|
142
|
-
| **Claude** | `claude` | `npm install -g @anthropic-ai/claude-code` |
|
|
143
|
-
| **Codex** | `codex` | `npm install -g @openai/codex` |
|
|
144
|
-
| **Gemini** | `gemini` | Ver [Gemini CLI docs](https://github.com/google-gemini/gemini-cli) |
|
|
145
|
-
| **Aider** | `aider` | `pipx install aider-chat` (o `pip3 install aider-chat`) |
|
|
146
|
-
|
|
147
|
-
`kj init` auto-detecta los agentes instalados. Si solo hay uno disponible, se asigna a todos los roles automaticamente.
|
|
148
|
-
=======
|
|
149
25
|
Tu describes lo que quieres construir. Karajan orquesta multiples agentes de IA para planificarlo, implementarlo, testearlo, revisarlo con SonarQube e iterar. Sin que tengas que supervisar cada paso.
|
|
150
26
|
|
|
151
27
|
## Que es Karajan?
|
|
@@ -162,6 +38,7 @@ Claude Code es excelente. Usalo para codificacion interactiva basada en sesiones
|
|
|
162
38
|
|
|
163
39
|
Usa Karajan cuando quieras:
|
|
164
40
|
|
|
41
|
+
- **Un pipeline repetible y documentado** que corre igual cada vez
|
|
165
42
|
- **TDD por defecto.** Los tests se escriben antes de la implementacion, no despues
|
|
166
43
|
- **Integracion con SonarQube.** Quality gates como parte del flujo, no como algo secundario
|
|
167
44
|
- **Solomon como jefe del pipeline.** Cada rechazo del reviewer es evaluado por un supervisor que decide si es valido o solo ruido de estilo
|
|
@@ -171,7 +48,7 @@ Usa Karajan cuando quieras:
|
|
|
171
48
|
- **Local-first.** Tu codigo, tus claves, tu maquina. Ningun dato sale salvo que tu lo digas
|
|
172
49
|
- **Zero costes de API.** Karajan usa CLIs de agentes de IA (Claude Code, Codex, Gemini CLI), no APIs. Pagas tu suscripcion existente (Claude Pro, ChatGPT Plus), no tarifas por token
|
|
173
50
|
|
|
174
|
-
Si Claude Code es un programador de pares inteligente, Karajan es el pipeline CI/CD para desarrollo asistido por IA. Funcionan genial juntos: Karajan esta
|
|
51
|
+
Si Claude Code es un programador de pares inteligente, Karajan es el pipeline CI/CD para desarrollo asistido por IA. Funcionan genial juntos: Karajan esta diseñado para usarse como servidor MCP dentro de Claude Code.
|
|
175
52
|
|
|
176
53
|
## Instalacion
|
|
177
54
|
|
|
@@ -180,26 +57,11 @@ npm install -g karajan-code
|
|
|
180
57
|
```
|
|
181
58
|
|
|
182
59
|
Eso es todo. No requiere Docker (SonarQube usa Docker, pero Karajan lo gestiona automaticamente). Sin ficheros de configuracion que copiar. `kj init` auto-detecta tus agentes instalados.
|
|
183
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
184
60
|
|
|
185
61
|
## Tres formas de usar Karajan
|
|
186
62
|
|
|
187
63
|
Karajan instala **tres comandos**: `kj`, `kj-tail` y `karajan-mcp`.
|
|
188
64
|
|
|
189
|
-
<<<<<<< HEAD
|
|
190
|
-
### 1. CLI — Directamente desde terminal
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
kj run "Implementar autenticacion de usuario con JWT"
|
|
194
|
-
kj code "Anadir validacion de inputs al formulario de registro"
|
|
195
|
-
kj review "Revisar los cambios de autenticacion"
|
|
196
|
-
kj plan "Refactorizar la capa de base de datos"
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### 2. MCP — Dentro de tu agente de IA
|
|
200
|
-
|
|
201
|
-
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.
|
|
202
|
-
=======
|
|
203
65
|
### 1. CLI: directamente desde terminal
|
|
204
66
|
|
|
205
67
|
Ejecuta Karajan directamente. Ves la salida completa del pipeline en tiempo real.
|
|
@@ -215,41 +77,26 @@ kj plan "Refactorizar la capa de base de datos" # Planificar
|
|
|
215
77
|
### 2. MCP: dentro de tu agente de IA
|
|
216
78
|
|
|
217
79
|
Este es el caso de uso principal. Karajan corre como servidor MCP dentro de Claude Code, Codex o Gemini. Le pides algo a tu agente de IA y el delega el trabajo pesado al pipeline de Karajan.
|
|
218
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
219
80
|
|
|
220
81
|
```
|
|
221
82
|
Tu → Claude Code → kj_run (via MCP) → triage → coder → sonar → reviewer → tester → security
|
|
222
83
|
```
|
|
223
84
|
|
|
224
|
-
|
|
225
|
-
**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.
|
|
226
|
-
|
|
227
|
-
### 3. kj-tail — Monitorizar desde otro terminal
|
|
228
|
-
|
|
229
|
-
**La herramienta companera.** Abre un segundo terminal en el **mismo directorio del proyecto** donde esta trabajando tu agente de IA:
|
|
230
|
-
=======
|
|
231
|
-
El servidor MCP se auto-registra durante `npm install`. Tu agente de IA ve 20 herramientas (`kj_run`, `kj_code`, `kj_review`, etc.) y las usa segun necesite.
|
|
85
|
+
El servidor MCP se auto-registra durante `npm install`. Tu agente de IA ve 21 herramientas (`kj_run`, `kj_code`, `kj_review`, `kj_hu`, etc.) y las usa segun necesite.
|
|
232
86
|
|
|
233
87
|
**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.
|
|
234
88
|
|
|
235
89
|
### 3. kj-tail: monitorizar desde otro terminal
|
|
236
90
|
|
|
237
91
|
**La herramienta compañera.** Abre un segundo terminal en el **mismo directorio del proyecto** donde esta trabajando tu agente de IA:
|
|
238
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
239
92
|
|
|
240
93
|
```bash
|
|
241
94
|
kj-tail
|
|
242
95
|
```
|
|
243
96
|
|
|
244
|
-
<<<<<<< HEAD
|
|
245
|
-
Veras la salida del pipeline en vivo — etapas, resultados, iteraciones, errores — tal como ocurren.
|
|
246
|
-
|
|
247
|
-
```bash
|
|
248
|
-
=======
|
|
249
97
|
Veras la salida del pipeline en vivo (etapas, resultados, iteraciones, errores) tal como ocurren. La misma vista que ejecutar `kj run` directamente.
|
|
250
98
|
|
|
251
99
|
```
|
|
252
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
253
100
|
kj-tail # Seguir pipeline en tiempo real (por defecto)
|
|
254
101
|
kj-tail -v # Verbose: incluir heartbeats de agente y presupuesto
|
|
255
102
|
kj-tail -t # Mostrar timestamps
|
|
@@ -260,172 +107,6 @@ kj-tail --help # Todas las opciones
|
|
|
260
107
|
|
|
261
108
|
> **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.
|
|
262
109
|
|
|
263
|
-
**Flujo tipico:**
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
┌──────────────────────────┐ ┌──────────────────────────┐
|
|
267
|
-
│ Terminal 1 │ │ Terminal 2 │
|
|
268
|
-
│ │ │ │
|
|
269
|
-
│ $ claude │ │ $ kj-tail │
|
|
270
|
-
│ > implementa la │ │ │
|
|
271
|
-
│ siguiente tarea │ │ ├─ 📋 Triage: medium │
|
|
272
|
-
│ prioritaria │ │ ├─ 🔬 Researcher ✅ │
|
|
273
|
-
│ │ │ ├─ 🧠 Planner ✅ │
|
|
274
|
-
│ (Claude llama a kj_run │ │ ├─ 🔨 Coder ✅ │
|
|
275
|
-
<<<<<<< HEAD
|
|
276
|
-
│ via MCP — solo ves │ │ ├─ 🔍 Sonar: OK │
|
|
277
|
-
=======
|
|
278
|
-
│ via MCP, solo ves │ │ ├─ 🔍 Sonar: OK │
|
|
279
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
280
|
-
│ el resultado final) │ │ ├─ 👁️ Reviewer ❌ │
|
|
281
|
-
│ │ │ ├─ ⚖️ Solomon: 2 cond. │
|
|
282
|
-
│ │ │ ├─ 🔨 Coder (iter 2) ✅ │
|
|
283
|
-
│ │ │ ├─ ✅ Review: APPROVED │
|
|
284
|
-
│ │ │ ├─ 🧪 Tester: passed │
|
|
285
|
-
│ │ │ └─ 🏁 Result: APPROVED │
|
|
286
|
-
└──────────────────────────┘ └──────────────────────────┘
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
<<<<<<< HEAD
|
|
290
|
-
**Ejemplo con pipeline completo** — tarea compleja con todos los roles:
|
|
291
|
-
|
|
292
|
-
```
|
|
293
|
-
┌─ Terminal 1 ─────────────────────────────────────────────────────────────────┐
|
|
294
|
-
│ │
|
|
295
|
-
│ $ claude │
|
|
296
|
-
│ │
|
|
297
|
-
│ > Construye una API REST para un sistema de reservas. Requisitos: │
|
|
298
|
-
│ > - Express + TypeScript con validacion Zod en cada endpoint │
|
|
299
|
-
│ > - Endpoints: POST /bookings, GET /bookings/:id, │
|
|
300
|
-
│ > PATCH /bookings/:id/cancel │
|
|
301
|
-
│ > - Una reserva tiene: id, guestName, roomType (standard|suite|penthouse), │
|
|
302
|
-
│ > checkIn, checkOut, status (confirmed|cancelled) │
|
|
303
|
-
│ > - Validar: checkOut posterior a checkIn, sin fechas pasadas, │
|
|
304
|
-
│ > roomType debe ser un valor valido del enum │
|
|
305
|
-
│ > - Cancelar devuelve 409 si ya esta cancelada │
|
|
306
|
-
│ > - Usa TDD. Ejecutalo con Karajan con architect y planner activos, │
|
|
307
|
-
│ > modo paranoid. Coder claude, reviewer codex. │
|
|
308
|
-
│ │
|
|
309
|
-
│ Claude llama a kj_run via MCP con: │
|
|
310
|
-
│ --enable-architect --enable-researcher --enable-planner --mode paranoid │
|
|
311
|
-
│ │
|
|
312
|
-
└──────────────────────────────────────────────────────────────────────────────┘
|
|
313
|
-
|
|
314
|
-
┌─ Terminal 2: kj-tail ────────────────────────────────────────────────────────┐
|
|
315
|
-
│ │
|
|
316
|
-
│ kj-tail v1.36.1 — .kj/run.log │
|
|
317
|
-
│ │
|
|
318
|
-
│ ├─ 📋 Triage: medium (sw) — activando researcher, architect, planner │
|
|
319
|
-
│ ├─ ⚙️ Preflight passed — all checks OK │
|
|
320
|
-
│ ├─ 🔬 Researcher: 8 ficheros, 3 patrones, 5 restricciones │
|
|
321
|
-
│ ├─ 🏗️ Architect: diseno 3 capas (routes → service → validators) │
|
|
322
|
-
│ ├─ 🧠 Planner: 6 pasos — tests primero, luego rutas, servicio, validadores │
|
|
323
|
-
│ │ │
|
|
324
|
-
│ ▶ Iteracion 1/5 │
|
|
325
|
-
│ ├─ 🔨 Coder (claude): 3 endpoints + 18 tests │
|
|
326
|
-
│ ├─ 📋 TDD: PASS (3 src, 2 test) │
|
|
327
|
-
│ ├─ 🔍 Sonar: Quality gate OK │
|
|
328
|
-
│ ├─ 👁️ Reviewer (codex): REJECTED (2 blocking) │
|
|
329
|
-
│ │ "Falta 404 para GET booking inexistente" │
|
|
330
|
-
│ │ "Endpoint cancel sin test de idempotencia" │
|
|
331
|
-
│ ├─ ⚖️ Solomon: approve_with_conditions (2 condiciones) │
|
|
332
|
-
│ │ "Anadir respuesta 404 y test para GET /bookings/:id con id desconocido" │
|
|
333
|
-
│ │ "Anadir test: cancelar reserva ya cancelada devuelve 409, no 500" │
|
|
334
|
-
│ │ │
|
|
335
|
-
│ ▶ Iteracion 2/5 │
|
|
336
|
-
│ ├─ 🔨 Coder (claude): corregido — 22 tests │
|
|
337
|
-
│ ├─ 📋 TDD: PASS │
|
|
338
|
-
│ ├─ 🔍 Sonar: OK │
|
|
339
|
-
│ ├─ 👁️ Reviewer (codex): APPROVED │
|
|
340
|
-
│ ├─ 🧪 Tester: passed — cobertura 94%, 22 tests │
|
|
341
|
-
│ ├─ 🔒 Security: passed — 0 criticos, 1 bajo (helmet recomendado) │
|
|
342
|
-
│ ├─ 📊 Audit: CERTIFIED (3 advertencias) │
|
|
343
|
-
│ │ │
|
|
344
|
-
│ 🏁 Resultado: APPROVED │
|
|
345
|
-
│ 🔬 Investigacion: 8 ficheros, 3 patrones │
|
|
346
|
-
│ 🗺 Plan: 6 pasos (tests primero) │
|
|
347
|
-
│ 🧪 Cobertura: 94%, 22 tests │
|
|
348
|
-
│ 🔒 Seguridad: OK │
|
|
349
|
-
│ 🔍 Sonar: OK │
|
|
350
|
-
│ 💰 Presupuesto: $0.42 (claude: $0.38, codex: $0.04) │
|
|
351
|
-
│ │
|
|
352
|
-
└──────────────────────────────────────────────────────────────────────────────┘
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
## Comandos CLI
|
|
356
|
-
|
|
357
|
-
Consulta la [documentacion completa de comandos](../README.md#cli-commands) en el README principal (ingles). Resumen:
|
|
358
|
-
|
|
359
|
-
| Comando | Descripcion |
|
|
360
|
-
|---------|-------------|
|
|
361
|
-
| `kj init` | Wizard interactivo de configuracion |
|
|
362
|
-
| `kj run <task>` | Pipeline completo: coder → sonar → reviewer |
|
|
363
|
-
| `kj code <task>` | Solo coder (sin revision) |
|
|
364
|
-
| `kj review <task>` | Solo reviewer (sobre diff actual) |
|
|
365
|
-
| `kj plan <task>` | Generar plan de implementacion |
|
|
366
|
-
| `kj scan` | Ejecutar analisis SonarQube |
|
|
367
|
-
| `kj doctor` | Verificar entorno (git, Docker, agentes, SonarQube) |
|
|
368
|
-
| `kj config` | Mostrar configuracion actual |
|
|
369
|
-
| `kj report` | Informes de sesion con tracking de presupuesto |
|
|
370
|
-
| `kj resume <id>` | Reanudar sesion pausada |
|
|
371
|
-
| `kj roles` | Inspeccionar roles y templates del pipeline |
|
|
372
|
-
| `kj sonar` | Gestionar contenedor Docker de SonarQube |
|
|
373
|
-
|
|
374
|
-
## Servidor MCP
|
|
375
|
-
|
|
376
|
-
Karajan Code expone un servidor MCP para integracion con cualquier host compatible (Claude, Codex, agentes custom).
|
|
377
|
-
|
|
378
|
-
Tras `npm install -g karajan-code`, el servidor MCP se auto-registra en las configs de Claude y Codex. Config manual:
|
|
379
|
-
|
|
380
|
-
```json
|
|
381
|
-
{
|
|
382
|
-
"mcpServers": {
|
|
383
|
-
"karajan-mcp": {
|
|
384
|
-
"command": "karajan-mcp"
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Herramientas MCP
|
|
391
|
-
|
|
392
|
-
| Herramienta | Descripcion |
|
|
393
|
-
|-------------|-------------|
|
|
394
|
-
| `kj_init` | Inicializar config y SonarQube |
|
|
395
|
-
| `kj_doctor` | Verificar dependencias del sistema |
|
|
396
|
-
| `kj_config` | Mostrar configuracion |
|
|
397
|
-
| `kj_scan` | Ejecutar analisis SonarQube |
|
|
398
|
-
| `kj_run` | Ejecutar pipeline completo (con notificaciones de progreso en tiempo real) |
|
|
399
|
-
| `kj_resume` | Reanudar sesion pausada |
|
|
400
|
-
| `kj_report` | Leer informes de sesion (soporta `--trace`) |
|
|
401
|
-
| `kj_roles` | Listar roles o mostrar templates |
|
|
402
|
-
| `kj_code` | Modo solo coder |
|
|
403
|
-
| `kj_review` | Modo solo reviewer |
|
|
404
|
-
| `kj_plan` | Generar plan de implementacion con telemetria heartbeat/stall y diagnostico mas claro |
|
|
405
|
-
|
|
406
|
-
### MCPs complementarios recomendados
|
|
407
|
-
|
|
408
|
-
Karajan Code funciona perfectamente solo, pero combinarlo con estos servidores MCP le da a tu agente un entorno de desarrollo completo:
|
|
409
|
-
|
|
410
|
-
| MCP | Para que | Caso de uso |
|
|
411
|
-
|-----|----------|-------------|
|
|
412
|
-
| [**Planning Game MCP**](https://github.com/AgenteIA-Geniova/planning-game-mcp) | Puente MCP para [Planning Game](https://github.com/AgenteIA-Geniova/planning-game), gestor agil open-source (tareas, sprints, estimacion, XP). Solo necesario si usas Planning Game | `kj_run` con `--pg-task` obtiene contexto completo de la tarea y actualiza el estado al completar |
|
|
413
|
-
| [**GitHub MCP**](https://github.com/modelcontextprotocol/servers/tree/main/src/github) | Crear PRs, gestionar issues, leer repos desde el agente | Combinar con `--auto-push` para flujo completo: codigo → revision → push → PR |
|
|
414
|
-
| [**Serena**](https://github.com/oramasearch/serena) | Navegacion a nivel de simbolo (find references, go-to-definition) para proyectos JS/TS | Activar con `--enable-serena` para inyectar contexto de simbolos en prompts de coder/reviewer |
|
|
415
|
-
| [**Chrome DevTools MCP**](https://github.com/anthropics/anthropic-quickstarts/tree/main/chrome-devtools-mcp) | Automatizacion de navegador, screenshots, inspeccion de consola/red | Verificar cambios de UI visualmente tras modificar codigo frontend |
|
|
416
|
-
|
|
417
|
-
## Templates de roles
|
|
418
|
-
|
|
419
|
-
Cada rol tiene un template `.md` con instrucciones que el agente de IA sigue. Los templates se resuelven en orden de prioridad:
|
|
420
|
-
|
|
421
|
-
1. **Override de proyecto**: `.karajan/roles/<rol>.md` (en la raiz del proyecto)
|
|
422
|
-
2. **Override de usuario**: `$KJ_HOME/roles/<rol>.md`
|
|
423
|
-
3. **Built-in**: `templates/roles/<rol>.md` (incluido en el paquete)
|
|
424
|
-
|
|
425
|
-
Usa `kj roles show <rol>` para inspeccionar cualquier template. Crea un override de proyecto para personalizar el comportamiento por proyecto.
|
|
426
|
-
|
|
427
|
-
**Variantes de revision**: `reviewer-strict`, `reviewer-relaxed`, `reviewer-paranoid` — seleccionables via flag `--mode` o config `review_mode`.
|
|
428
|
-
=======
|
|
429
110
|
[**Ver la demo completa del pipeline**](https://karajancode.com#demo): triage, arquitectura, TDD, SonarQube, code review, arbitraje de Solomon, auditoria de seguridad.
|
|
430
111
|
|
|
431
112
|
## El pipeline
|
|
@@ -441,7 +122,7 @@ hu-reviewer? → triage → discover? → architect? → planner? → coder →
|
|
|
441
122
|
| **hu-reviewer** | Certifica historias de usuario antes de codificar (6 dimensiones, 7 antipatrones) | Auto (media/compleja) |
|
|
442
123
|
| **triage** | Clasifica complejidad, activa roles, auto-simplifica para tareas triviales | **On** |
|
|
443
124
|
| **discover** | Detecta huecos en requisitos (Mom Test, Wendel, JTBD) | Off |
|
|
444
|
-
| **architect** |
|
|
125
|
+
| **architect** | Diseña la arquitectura de la solucion antes de planificar | Off |
|
|
445
126
|
| **planner** | Genera planes de implementacion estructurados | Off |
|
|
446
127
|
| **coder** | Escribe codigo y tests siguiendo metodologia TDD | **Siempre on** |
|
|
447
128
|
| **refactorer** | Mejora la claridad del codigo sin cambiar comportamiento | Off |
|
|
@@ -466,19 +147,19 @@ hu-reviewer? → triage → discover? → architect? → planner? → coder →
|
|
|
466
147
|
|
|
467
148
|
Mezcla y combina. Usa Claude como coder y Codex como reviewer. Karajan auto-detecta agentes instalados durante `kj init`.
|
|
468
149
|
|
|
469
|
-
## Servidor MCP (
|
|
150
|
+
## Servidor MCP (21 herramientas)
|
|
470
151
|
|
|
471
152
|
Tras `npm install -g karajan-code`, el servidor MCP se auto-registra en Claude y Codex. Config manual si es necesario:
|
|
472
153
|
|
|
473
154
|
```bash
|
|
474
|
-
# Claude:
|
|
155
|
+
# Claude: añadir a ~/.claude.json → "mcpServers":
|
|
475
156
|
# { "karajan-mcp": { "command": "karajan-mcp" } }
|
|
476
157
|
|
|
477
|
-
# Codex:
|
|
158
|
+
# Codex: añadir a ~/.codex/config.toml → [mcp_servers."karajan-mcp"]
|
|
478
159
|
# command = "karajan-mcp"
|
|
479
160
|
```
|
|
480
161
|
|
|
481
|
-
**
|
|
162
|
+
**21 herramientas** disponibles: `kj_run`, `kj_code`, `kj_review`, `kj_plan`, `kj_audit`, `kj_scan`, `kj_doctor`, `kj_config`, `kj_report`, `kj_resume`, `kj_roles`, `kj_agents`, `kj_preflight`, `kj_status`, `kj_init`, `kj_discover`, `kj_triage`, `kj_researcher`, `kj_architect`, `kj_impeccable`, `kj_hu`.
|
|
482
163
|
|
|
483
164
|
Usa `kj-tail` en un terminal separado para ver lo que el pipeline esta haciendo en tiempo real (ver [Tres formas de usar Karajan](#tres-formas-de-usar-karajan)).
|
|
484
165
|
|
|
@@ -500,7 +181,7 @@ Usa `kj roles show <rol>` para inspeccionar cualquier template.
|
|
|
500
181
|
|
|
501
182
|
Karajan auto-detecta y auto-configura todo lo que puede:
|
|
502
183
|
|
|
503
|
-
- **TDD**: Detecta framework de tests (vitest, jest,
|
|
184
|
+
- **TDD**: Detecta framework de tests para 12 lenguajes (vitest, jest, JUnit, pytest, go test, cargo test, y mas). Auto-activa TDD para tareas de codigo, lo salta para doc/infra
|
|
504
185
|
- **Bootstrap gate**: Valida todos los prerequisitos (repo git, remote, config, agentes, SonarQube) antes de ejecutar. Falla con instrucciones claras, nunca degrada silenciosamente
|
|
505
186
|
- **Injection guard**: Escanea diffs en busca de prompt injection antes del review de IA. Detecta directivas de override, Unicode invisible, payloads en comentarios sobredimensionados. Tambien como GitHub Action en cada PR
|
|
506
187
|
- **SonarQube**: Auto-arranca contenedor Docker, genera config si falta
|
|
@@ -511,11 +192,11 @@ Karajan auto-detecta y auto-configura todo lo que puede:
|
|
|
511
192
|
|
|
512
193
|
Sin configuracion por proyecto requerida. Si quieres personalizar, la config se apila: sesion > proyecto > global.
|
|
513
194
|
|
|
514
|
-
## Por
|
|
195
|
+
## Por que JavaScript vanilla?
|
|
515
196
|
|
|
516
|
-
No es nostalgia ni
|
|
197
|
+
No es nostalgia ni cabezoneria. Es que llevo usando JavaScript desde 1997, cuando Brendan Eich lo creo en una semana y nos cambio la vida a los que haciamos webs. Conozco sus tripas, sus bugs, sus rarezas. Y se que quien conoce JS de verdad convierte esos bugs en features. TypeScript existe para que developers acostumbrados a lenguajes fuertemente tipados no entren en panico al ver JS. Respeto eso. Pero yo no lo necesito. Los tests son mi seguridad de tipos. JSDoc y un buen IDE son mi intellisense. Y no tener un compilador entre el codigo y yo es lo que me permite moverme a 57 releases en 45 dias sin miedo.
|
|
517
198
|
|
|
518
|
-
[Por
|
|
199
|
+
[Por que JavaScript vanilla: la version larga](why-vanilla-js.md)
|
|
519
200
|
|
|
520
201
|
## Compañeros recomendados
|
|
521
202
|
|
|
@@ -525,7 +206,6 @@ No es nostalgia ni cabezonería. Es que llevo usando JavaScript desde 1997, cuan
|
|
|
525
206
|
| [**Planning Game MCP**](https://github.com/AgenteIA-Geniova/planning-game-mcp) | Gestion agil de proyectos (tareas, sprints, estimacion), nativo XP |
|
|
526
207
|
| [**GitHub MCP**](https://github.com/modelcontextprotocol/servers/tree/main/src/github) | Crear PRs, gestionar issues directamente desde el agente |
|
|
527
208
|
| [**Chrome DevTools MCP**](https://github.com/anthropics/anthropic-quickstarts/tree/main/chrome-devtools-mcp) | Verificar cambios de UI visualmente tras modificar frontend |
|
|
528
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
529
209
|
|
|
530
210
|
## Contribuir
|
|
531
211
|
|
|
@@ -533,32 +213,15 @@ No es nostalgia ni cabezonería. Es que llevo usando JavaScript desde 1997, cuan
|
|
|
533
213
|
git clone https://github.com/manufosela/karajan-code.git
|
|
534
214
|
cd karajan-code
|
|
535
215
|
npm install
|
|
536
|
-
|
|
537
|
-
npm test # Ejecutar 1040+ tests con Vitest
|
|
538
|
-
npm run test:watch # Modo watch
|
|
539
|
-
npm run validate # Lint + test
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
- Tests: [Vitest](https://vitest.dev/)
|
|
543
|
-
- Commits: [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `refactor:`, `test:`, `chore:`)
|
|
544
|
-
- PRs: un solo proposito por PR, < 300 lineas cambiadas
|
|
545
|
-
=======
|
|
546
|
-
npm test # Ejecutar 2093 tests con Vitest
|
|
216
|
+
npm test # Ejecutar 2142 tests con Vitest
|
|
547
217
|
npm run validate # Lint + test
|
|
548
218
|
```
|
|
549
219
|
|
|
550
220
|
Issues y pull requests bienvenidos. Si algo no funciona como esta documentado, [abre un issue](https://github.com/manufosela/karajan-code/issues). Es la contribucion mas util en esta fase.
|
|
551
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
|
552
221
|
|
|
553
222
|
## Enlaces
|
|
554
223
|
|
|
555
224
|
- [Web](https://karajancode.com) (tambien [kj-code.com](https://kj-code.com))
|
|
556
|
-
<<<<<<< HEAD
|
|
557
|
-
- [Changelog](../CHANGELOG.md)
|
|
558
|
-
- [Politica de seguridad](../SECURITY.md)
|
|
559
|
-
- [Licencia (AGPL-3.0)](../LICENSE)
|
|
560
|
-
- [Issues](https://github.com/manufosela/karajan-code/issues)
|
|
561
|
-
=======
|
|
562
225
|
- [Documentacion completa](https://karajancode.com/docs/)
|
|
563
226
|
- [Changelog](../CHANGELOG.md)
|
|
564
227
|
- [Politica de seguridad](../SECURITY.md)
|
|
@@ -567,4 +230,3 @@ Issues y pull requests bienvenidos. Si algo no funciona como esta documentado, [
|
|
|
567
230
|
---
|
|
568
231
|
|
|
569
232
|
Construido por [@manufosela](https://github.com/manufosela). Head of Engineering en Geniova Technologies, co-organizador de NodeJS Madrid, autor de [Liderazgo Afectivo](https://www.liderazgoafectivo.com). 90+ paquetes npm publicados.
|
|
570
|
-
>>>>>>> 8792e49efcdc75995e024d81339b100c7b253920
|
package/package.json
CHANGED
|
@@ -10,13 +10,23 @@ const DESTRUCTIVE_PATTERNS = [
|
|
|
10
10
|
{ id: "format-disk", pattern: /mkfs\.|fdisk|dd\s+if=/, severity: "critical", message: "Disk format operation detected" },
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
-
// Built-in credential patterns
|
|
13
|
+
// Built-in credential patterns (ALL critical — secrets must NEVER be committed)
|
|
14
14
|
const CREDENTIAL_PATTERNS = [
|
|
15
15
|
{ id: "aws-key", pattern: /AKIA[0-9A-Z]{16}/, severity: "critical", message: "AWS access key exposed" },
|
|
16
16
|
{ id: "private-key", pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/, severity: "critical", message: "Private key exposed" },
|
|
17
|
-
{ id: "generic-secret", pattern: /(password|secret|token|api_key|apikey)\s*[:=]\s*["'][^"']{8,}["']/i, severity: "
|
|
17
|
+
{ id: "generic-secret", pattern: /(password|secret|token|api_key|apikey)\s*[:=]\s*["'][^"']{8,}["']/i, severity: "critical", message: "Hardcoded secret detected. Use .env file + process.env instead" },
|
|
18
18
|
{ id: "github-token", pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/, severity: "critical", message: "GitHub token exposed" },
|
|
19
19
|
{ id: "npm-token", pattern: /npm_[A-Za-z0-9]{36,}/, severity: "critical", message: "npm token exposed" },
|
|
20
|
+
{ id: "openai-key", pattern: /sk-[A-Za-z0-9]{20,}/, severity: "critical", message: "OpenAI API key exposed" },
|
|
21
|
+
{ id: "anthropic-key", pattern: /sk-ant-[A-Za-z0-9_-]{20,}/, severity: "critical", message: "Anthropic API key exposed" },
|
|
22
|
+
{ id: "stripe-key", pattern: /sk_live_[A-Za-z0-9]{20,}/, severity: "critical", message: "Stripe secret key exposed" },
|
|
23
|
+
{ id: "stripe-test", pattern: /sk_test_[A-Za-z0-9]{20,}/, severity: "critical", message: "Stripe test key exposed. Use .env even for test keys" },
|
|
24
|
+
{ id: "google-api-key", pattern: /AIza[A-Za-z0-9_-]{35}/, severity: "critical", message: "Google API key exposed" },
|
|
25
|
+
{ id: "firebase-key", pattern: /"apiKey"\s*:\s*"AIza[A-Za-z0-9_-]{35}"/, severity: "critical", message: "Firebase API key hardcoded. Use .env file" },
|
|
26
|
+
{ id: "slack-token", pattern: /xox[bpors]-[A-Za-z0-9-]{10,}/, severity: "critical", message: "Slack token exposed" },
|
|
27
|
+
{ id: "jwt-secret", pattern: /jwt[_-]?secret\s*[:=]\s*["'][^"']{8,}["']/i, severity: "critical", message: "JWT secret hardcoded. Use .env file" },
|
|
28
|
+
{ id: "database-url", pattern: /(mongodb|postgres|mysql|redis):\/\/[^"'\s]+:[^"'\s]+@/i, severity: "critical", message: "Database URL with credentials exposed. Use .env file" },
|
|
29
|
+
{ id: "hardcoded-key-assignment", pattern: /(?:const|let|var)\s+\w*(?:key|secret|token|password)\w*\s*=\s*["'][A-Za-z0-9_-]{16,}["']/i, severity: "critical", message: "Hardcoded key in variable. Use process.env or .env file" },
|
|
20
30
|
];
|
|
21
31
|
|
|
22
32
|
// Default protected files (block if these appear in added/modified lines)
|
package/src/hu/store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
3
4
|
import { getKarajanHome } from "../utils/paths.js";
|
|
4
5
|
|
|
5
6
|
// FUTURE: hu-storage adapter for PG/Trello/etc — currently local files only
|
|
@@ -208,3 +209,138 @@ export async function createHistoryRecord(sessionId, { task, result, approved, s
|
|
|
208
209
|
await fs.writeFile(path.join(dir, "batch.json"), JSON.stringify(batch, null, 2));
|
|
209
210
|
return batch;
|
|
210
211
|
}
|
|
212
|
+
|
|
213
|
+
/* ── Manual HU management ─────────────────────────────────────────── */
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Detect project info from the given directory.
|
|
217
|
+
* @param {string} cwd - Directory to detect project from.
|
|
218
|
+
* @returns {{ name: string, remoteUrl: string|null }}
|
|
219
|
+
*/
|
|
220
|
+
export function detectProject(cwd) {
|
|
221
|
+
const name = path.basename(cwd);
|
|
222
|
+
let remoteUrl = null;
|
|
223
|
+
try {
|
|
224
|
+
remoteUrl = execSync("git remote get-url origin", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
225
|
+
} catch { /* not a git repo or no remote */ }
|
|
226
|
+
return { name, remoteUrl };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Derive a project slug from a directory path.
|
|
231
|
+
* @param {string} projectDir - Absolute path to the project.
|
|
232
|
+
* @returns {string}
|
|
233
|
+
*/
|
|
234
|
+
function projectSlug(projectDir) {
|
|
235
|
+
return path.basename(projectDir);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get the batch file path for a project.
|
|
240
|
+
* @param {string} projectDir
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
243
|
+
function projectBatchPath(projectDir) {
|
|
244
|
+
return path.join(getHuDir(), projectSlug(projectDir), "batch.json");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Load the project batch, or return a fresh empty batch if none exists.
|
|
249
|
+
* @param {string} projectDir
|
|
250
|
+
* @returns {Promise<object>}
|
|
251
|
+
*/
|
|
252
|
+
async function loadProjectBatch(projectDir) {
|
|
253
|
+
const filePath = projectBatchPath(projectDir);
|
|
254
|
+
try {
|
|
255
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
256
|
+
return JSON.parse(raw);
|
|
257
|
+
} catch {
|
|
258
|
+
return {
|
|
259
|
+
project: projectSlug(projectDir),
|
|
260
|
+
created_at: new Date().toISOString(),
|
|
261
|
+
stories: []
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Save a project batch to disk (auto-creates directory).
|
|
268
|
+
* @param {string} projectDir
|
|
269
|
+
* @param {object} batch
|
|
270
|
+
*/
|
|
271
|
+
async function saveProjectBatch(projectDir, batch) {
|
|
272
|
+
const filePath = projectBatchPath(projectDir);
|
|
273
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
274
|
+
batch.updated_at = new Date().toISOString();
|
|
275
|
+
await fs.writeFile(filePath, JSON.stringify(batch, null, 2));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create a manual HU (user story) for a project.
|
|
280
|
+
* Auto-creates the batch file if it doesn't exist.
|
|
281
|
+
* @param {string} projectDir
|
|
282
|
+
* @param {{ title: string, description?: string, status?: string, acceptanceCriteria?: string }} data
|
|
283
|
+
* @returns {Promise<object>} The created HU.
|
|
284
|
+
*/
|
|
285
|
+
export async function createManualHu(projectDir, { title, description, status, acceptanceCriteria }) {
|
|
286
|
+
if (!title) throw new Error("title is required to create a HU");
|
|
287
|
+
const batch = await loadProjectBatch(projectDir);
|
|
288
|
+
const now = new Date().toISOString();
|
|
289
|
+
const hu = {
|
|
290
|
+
id: `HU-${Date.now()}-${batch.stories.length}`,
|
|
291
|
+
title,
|
|
292
|
+
description: description || "",
|
|
293
|
+
status: status || HU_STATUS.PENDING,
|
|
294
|
+
acceptanceCriteria: acceptanceCriteria || "",
|
|
295
|
+
createdAt: now,
|
|
296
|
+
updatedAt: now
|
|
297
|
+
};
|
|
298
|
+
batch.stories.push(hu);
|
|
299
|
+
await saveProjectBatch(projectDir, batch);
|
|
300
|
+
return hu;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* List all HUs for a project.
|
|
305
|
+
* @param {string} projectDir
|
|
306
|
+
* @returns {Promise<Array<{ id: string, title: string, status: string, createdAt: string }>>}
|
|
307
|
+
*/
|
|
308
|
+
export async function listHus(projectDir) {
|
|
309
|
+
const batch = await loadProjectBatch(projectDir);
|
|
310
|
+
return batch.stories.map(s => ({
|
|
311
|
+
id: s.id,
|
|
312
|
+
title: s.title,
|
|
313
|
+
status: s.status,
|
|
314
|
+
createdAt: s.createdAt
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Update the status of a specific HU.
|
|
320
|
+
* @param {string} projectDir
|
|
321
|
+
* @param {string} huId
|
|
322
|
+
* @param {string} status
|
|
323
|
+
* @returns {Promise<object>} The updated HU.
|
|
324
|
+
*/
|
|
325
|
+
export async function updateHuStatus(projectDir, huId, status) {
|
|
326
|
+
const batch = await loadProjectBatch(projectDir);
|
|
327
|
+
const hu = batch.stories.find(s => s.id === huId);
|
|
328
|
+
if (!hu) throw new Error(`HU ${huId} not found`);
|
|
329
|
+
hu.status = status;
|
|
330
|
+
hu.updatedAt = new Date().toISOString();
|
|
331
|
+
await saveProjectBatch(projectDir, batch);
|
|
332
|
+
return hu;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get a single HU by id.
|
|
337
|
+
* @param {string} projectDir
|
|
338
|
+
* @param {string} huId
|
|
339
|
+
* @returns {Promise<object>}
|
|
340
|
+
*/
|
|
341
|
+
export async function getHu(projectDir, huId) {
|
|
342
|
+
const batch = await loadProjectBatch(projectDir);
|
|
343
|
+
const hu = batch.stories.find(s => s.id === huId);
|
|
344
|
+
if (!hu) throw new Error(`HU ${huId} not found`);
|
|
345
|
+
return hu;
|
|
346
|
+
}
|
|
@@ -906,6 +906,44 @@ async function handleAudit(a, server, extra) {
|
|
|
906
906
|
return handleAuditDirect(a, server, extra);
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
+
async function handleHu(a, server) {
|
|
910
|
+
const action = a.action;
|
|
911
|
+
if (!action) return failPayload("Missing required field: action");
|
|
912
|
+
|
|
913
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
914
|
+
const { createManualHu, listHus, getHu, updateHuStatus } = await import("../hu/store.js");
|
|
915
|
+
|
|
916
|
+
switch (action) {
|
|
917
|
+
case "create": {
|
|
918
|
+
if (!a.title) return failPayload("Missing required field: title (required for create)");
|
|
919
|
+
const hu = await createManualHu(projectDir, {
|
|
920
|
+
title: a.title,
|
|
921
|
+
description: a.description,
|
|
922
|
+
status: a.status,
|
|
923
|
+
acceptanceCriteria: a.acceptanceCriteria
|
|
924
|
+
});
|
|
925
|
+
return { ok: true, hu };
|
|
926
|
+
}
|
|
927
|
+
case "list": {
|
|
928
|
+
const hus = await listHus(projectDir);
|
|
929
|
+
return { ok: true, hus };
|
|
930
|
+
}
|
|
931
|
+
case "get": {
|
|
932
|
+
if (!a.huId) return failPayload("Missing required field: huId (required for get)");
|
|
933
|
+
const hu = await getHu(projectDir, a.huId);
|
|
934
|
+
return { ok: true, hu };
|
|
935
|
+
}
|
|
936
|
+
case "update": {
|
|
937
|
+
if (!a.huId) return failPayload("Missing required field: huId (required for update)");
|
|
938
|
+
if (!a.status) return failPayload("Missing required field: status (required for update)");
|
|
939
|
+
const hu = await updateHuStatus(projectDir, a.huId, a.status);
|
|
940
|
+
return { ok: true, hu };
|
|
941
|
+
}
|
|
942
|
+
default:
|
|
943
|
+
return failPayload(`Unknown hu action: ${action}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
909
947
|
async function handleBoard(a) {
|
|
910
948
|
const action = a.action || "status";
|
|
911
949
|
const { loadConfig: lc } = await import("../config.js");
|
|
@@ -947,7 +985,8 @@ const toolHandlers = {
|
|
|
947
985
|
kj_researcher: (a, server, extra) => handleResearcher(a, server, extra),
|
|
948
986
|
kj_architect: (a, server, extra) => handleArchitect(a, server, extra),
|
|
949
987
|
kj_audit: (a, server, extra) => handleAudit(a, server, extra),
|
|
950
|
-
kj_board: (a) => handleBoard(a)
|
|
988
|
+
kj_board: (a) => handleBoard(a),
|
|
989
|
+
kj_hu: (a, server) => handleHu(a, server)
|
|
951
990
|
};
|
|
952
991
|
|
|
953
992
|
export async function handleToolCall(name, args, server, extra) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -47,7 +47,7 @@ export const tools = [
|
|
|
47
47
|
{
|
|
48
48
|
name: "kj_run",
|
|
49
49
|
description:
|
|
50
|
-
"Run full
|
|
50
|
+
"Run the full Karajan pipeline. IMPORTANT: Pass the user's task description exactly as they wrote it. Do NOT group, split, reorder, or modify tasks yourself. Karajan handles decomposition, role activation, iteration, and quality gates internally. Do NOT override pipeline parameters (enableHuReviewer, mode, methodology) unless the user explicitly requested it. If you have observations about the task, use kj_suggest instead.",
|
|
51
51
|
inputSchema: {
|
|
52
52
|
type: "object",
|
|
53
53
|
required: ["task"],
|
|
@@ -182,7 +182,7 @@ export const tools = [
|
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
name: "kj_code",
|
|
185
|
-
description: "Run coder-only mode",
|
|
185
|
+
description: "Run coder-only mode. Pass the user's task exactly as described. Do NOT split or reinterpret the task.",
|
|
186
186
|
inputSchema: {
|
|
187
187
|
type: "object",
|
|
188
188
|
required: ["task"],
|
|
@@ -197,7 +197,7 @@ export const tools = [
|
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
name: "kj_review",
|
|
200
|
-
description: "Run reviewer-only mode against current diff",
|
|
200
|
+
description: "Run reviewer-only mode against current diff. Do NOT filter or pre-process the diff before passing it.",
|
|
201
201
|
inputSchema: {
|
|
202
202
|
type: "object",
|
|
203
203
|
required: ["task"],
|
|
@@ -223,7 +223,7 @@ export const tools = [
|
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
name: "kj_plan",
|
|
226
|
-
description: "Generate implementation plan for a task",
|
|
226
|
+
description: "Generate implementation plan for a task. Pass the task as the user described it. Karajan's planner decides the approach.",
|
|
227
227
|
inputSchema: {
|
|
228
228
|
type: "object",
|
|
229
229
|
required: ["task"],
|
|
@@ -317,5 +317,22 @@ export const tools = [
|
|
|
317
317
|
port: { type: "number", description: "Port (default: 4000)" }
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "kj_hu",
|
|
323
|
+
description: "Manage user stories (HUs) in the local board",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
required: ["action"],
|
|
327
|
+
properties: {
|
|
328
|
+
action: { type: "string", enum: ["create", "update", "list", "get"], description: "Action to perform" },
|
|
329
|
+
title: { type: "string", description: "HU title (required for create)" },
|
|
330
|
+
description: { type: "string", description: "HU description" },
|
|
331
|
+
acceptanceCriteria: { type: "string", description: "Acceptance criteria" },
|
|
332
|
+
huId: { type: "string", description: "HU ID (required for update/get)" },
|
|
333
|
+
status: { type: "string", description: "HU status (for create/update)", enum: ["pending", "coding", "reviewing", "done", "failed", "blocked"] },
|
|
334
|
+
projectDir: { type: "string", description: "Project directory (defaults to cwd)" }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
320
337
|
}
|
|
321
338
|
];
|
|
@@ -444,15 +444,20 @@ export async function runSonarStage({ config, logger, emitter, eventBase, sessio
|
|
|
444
444
|
const sonarResult = sonarOutput.result;
|
|
445
445
|
|
|
446
446
|
if (!sonarResult.gateStatus && sonarResult.error) {
|
|
447
|
+
const isTokenError = /unable to resolve sonar token/i.test(sonarResult.error);
|
|
448
|
+
const errorMessage = isTokenError
|
|
449
|
+
? "SonarQube is running but no authentication token is configured. Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to ~/.karajan/kj.config.yml."
|
|
450
|
+
: `Sonar scan failed: ${sonarResult.error}`;
|
|
451
|
+
|
|
447
452
|
await markSessionStatus(session, "failed");
|
|
448
453
|
emitProgress(
|
|
449
454
|
emitter,
|
|
450
455
|
makeEvent("sonar:end", { ...eventBase, stage: "sonar" }, {
|
|
451
456
|
status: "fail",
|
|
452
|
-
message:
|
|
457
|
+
message: errorMessage
|
|
453
458
|
})
|
|
454
459
|
);
|
|
455
|
-
throw new Error(
|
|
460
|
+
throw new Error(errorMessage);
|
|
456
461
|
}
|
|
457
462
|
|
|
458
463
|
session.last_sonar_summary = sonarOutput.summary;
|
|
@@ -680,7 +685,7 @@ async function fetchReviewDiff(session, logger) {
|
|
|
680
685
|
logger.info(`Reviewer reading PR diff #${session.becaria_pr_number}`);
|
|
681
686
|
return diff;
|
|
682
687
|
}
|
|
683
|
-
return generateDiff({ baseRef: session.session_start_sha });
|
|
688
|
+
return generateDiff({ baseRef: session.session_start_sha, stageNewFiles: true });
|
|
684
689
|
}
|
|
685
690
|
|
|
686
691
|
export async function runReviewerStage({ reviewerRole, config, logger, emitter, eventBase, session, trackBudget, iteration, reviewRules, task, repeatDetector, budgetSummary, askQuestion }) {
|
|
@@ -252,8 +252,8 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
252
252
|
result.ok = false;
|
|
253
253
|
result.errors.push({
|
|
254
254
|
check: "sonar-auth",
|
|
255
|
-
message: "SonarQube authentication
|
|
256
|
-
fix: "
|
|
255
|
+
message: "SonarQube is running but no authentication token is configured.",
|
|
256
|
+
fix: "Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to ~/.karajan/kj.config.yml."
|
|
257
257
|
});
|
|
258
258
|
logger.error("Preflight: Sonar auth failed");
|
|
259
259
|
}
|
|
@@ -2,6 +2,68 @@ import { SolomonRole } from "../roles/solomon-role.js";
|
|
|
2
2
|
import { addCheckpoint, pauseSession } from "../session-store.js";
|
|
3
3
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Build a human-readable escalation message from raw Solomon conflict data.
|
|
7
|
+
* Replaces the raw JSON dump with structured, plain-text output.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} solomonResult - The conflict object passed to escalateToHuman
|
|
10
|
+
* @param {object} [session] - Current session (optional, used for extra context)
|
|
11
|
+
* @returns {string} Formatted message suitable for display to the user
|
|
12
|
+
*/
|
|
13
|
+
export function formatEscalationMessage(solomonResult, session) {
|
|
14
|
+
const stage = solomonResult?.stage || "unknown";
|
|
15
|
+
const solomonReason = solomonResult?.solomonReason || null;
|
|
16
|
+
const history = solomonResult?.history || [];
|
|
17
|
+
|
|
18
|
+
const lines = [];
|
|
19
|
+
lines.push(`--- Conflict: ${stage} ---`);
|
|
20
|
+
lines.push("");
|
|
21
|
+
|
|
22
|
+
// Extract reviewer/agent feedback from history
|
|
23
|
+
const feedbackEntries = history.filter(entry => entry && typeof entry === "object");
|
|
24
|
+
if (feedbackEntries.length > 0) {
|
|
25
|
+
lines.push("Reviewer feedback:");
|
|
26
|
+
for (const entry of feedbackEntries) {
|
|
27
|
+
const agent = entry.agent || "unknown";
|
|
28
|
+
const feedback = entry.feedback || "No feedback provided";
|
|
29
|
+
// Split multi-line feedback (e.g. "R-1: ...\nR-2: ...") into bullet points
|
|
30
|
+
const feedbackLines = String(feedback).split("\n").filter(l => l.trim());
|
|
31
|
+
if (feedbackLines.length === 1) {
|
|
32
|
+
lines.push(` [${agent}] ${feedbackLines[0]}`);
|
|
33
|
+
} else {
|
|
34
|
+
lines.push(` [${agent}]`);
|
|
35
|
+
for (const fl of feedbackLines) {
|
|
36
|
+
lines.push(` - ${fl}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
lines.push("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Solomon's reason (if it tried and failed)
|
|
44
|
+
if (solomonReason) {
|
|
45
|
+
lines.push(`Solomon could not resolve the conflict: ${solomonReason}`);
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Iteration context
|
|
50
|
+
const iterationCount = solomonResult?.iterationCount;
|
|
51
|
+
const maxIterations = solomonResult?.maxIterations;
|
|
52
|
+
if (iterationCount !== undefined && maxIterations !== undefined) {
|
|
53
|
+
lines.push(`Iterations: ${iterationCount}/${maxIterations}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
lines.push("Options:");
|
|
58
|
+
lines.push(" 1. Accept coder's work as-is");
|
|
59
|
+
lines.push(" 2. Retry with reviewer's feedback");
|
|
60
|
+
lines.push(" 3. Stop the session");
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push("How should we proceed?");
|
|
63
|
+
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
5
67
|
export async function invokeSolomon({ config, logger, emitter, eventBase, stage, conflict, askQuestion, session, iteration }) {
|
|
6
68
|
const solomonEnabled = Boolean(config.pipeline?.solomon?.enabled);
|
|
7
69
|
|
|
@@ -86,8 +148,7 @@ export async function invokeSolomon({ config, logger, emitter, eventBase, stage,
|
|
|
86
148
|
}
|
|
87
149
|
|
|
88
150
|
export async function escalateToHuman({ askQuestion, session, emitter, eventBase, stage, conflict, iteration }) {
|
|
89
|
-
const
|
|
90
|
-
const question = `${stage} conflict requires human intervention: ${reason}\nDetails: ${JSON.stringify(conflict?.history?.slice(-2) || [], null, 2)}\n\nHow should we proceed?`;
|
|
151
|
+
const question = formatEscalationMessage({ ...conflict, stage }, session);
|
|
91
152
|
|
|
92
153
|
if (askQuestion) {
|
|
93
154
|
const answer = await askQuestion(question, { iteration, stage });
|
|
@@ -13,8 +13,12 @@ export async function computeBaseRef({ baseBranch = "main", baseRef = null }) {
|
|
|
13
13
|
return mergeBase.stdout.trim();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export async function generateDiff({ baseRef }) {
|
|
17
|
-
|
|
16
|
+
export async function generateDiff({ baseRef, stageNewFiles = false }) {
|
|
17
|
+
// Stage untracked files so they appear in the diff (coder creates files but doesn't git add)
|
|
18
|
+
if (stageNewFiles) {
|
|
19
|
+
await runCommand("git", ["add", "-A"]);
|
|
20
|
+
}
|
|
21
|
+
const result = await runCommand("git", ["diff", stageNewFiles ? "--cached" : "", `${baseRef}`].filter(Boolean));
|
|
18
22
|
if (result.exitCode !== 0) {
|
|
19
23
|
throw new Error(`git diff failed: ${result.stderr || result.stdout}`);
|
|
20
24
|
}
|
package/src/review/tdd-policy.js
CHANGED
|
@@ -21,15 +21,17 @@ function isSourceFile(file, extensions = []) {
|
|
|
21
21
|
|
|
22
22
|
const SKIP_TDD_TASK_TYPES = new Set(["doc", "infra"]);
|
|
23
23
|
|
|
24
|
+
const DEFAULT_TEST_PATTERNS = ["/tests/", "/__tests__/", ".test.", ".spec.", "/src/test/", "/test/", "Test.java", "Test.kt", "test_", "_test.py", "_test.go", "Test.cs", "_spec.rb", "Test.php", "_test.dart", "Tests.swift"];
|
|
25
|
+
const DEFAULT_SOURCE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".kt", ".rb", ".php", ".cs", ".rs", ".swift", ".dart"];
|
|
26
|
+
|
|
24
27
|
export function evaluateTddPolicy(diff, developmentConfig = {}, untrackedFiles = [], taskType = null) {
|
|
25
28
|
if (taskType && SKIP_TDD_TASK_TYPES.has(taskType)) {
|
|
26
29
|
return { ok: true, reason: "tdd_not_applicable_for_task_type", sourceFiles: [], testFiles: [] };
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
const requireTestChanges = developmentConfig.require_test_changes !== false;
|
|
30
|
-
const patterns = developmentConfig.test_file_patterns ||
|
|
31
|
-
const extensions =
|
|
32
|
-
developmentConfig.source_file_extensions || [".js", ".jsx", ".ts", ".tsx", ".py", ".go", ".java", ".rb", ".php", ".cs"];
|
|
33
|
+
const patterns = developmentConfig.test_file_patterns || DEFAULT_TEST_PATTERNS;
|
|
34
|
+
const extensions = developmentConfig.source_file_extensions || DEFAULT_SOURCE_EXTENSIONS;
|
|
33
35
|
|
|
34
36
|
const diffFiles = extractChangedFiles(diff);
|
|
35
37
|
const extra = Array.isArray(untrackedFiles) ? untrackedFiles : [];
|
package/src/sonar/scanner.js
CHANGED
|
@@ -225,7 +225,7 @@ export async function runSonarScan(config, projectKey = null) {
|
|
|
225
225
|
ok: false,
|
|
226
226
|
stdout: "",
|
|
227
227
|
stderr:
|
|
228
|
-
"Unable to resolve Sonar token.
|
|
228
|
+
"Unable to resolve Sonar token. Fix: (1) run 'kj init' to configure it, (2) set KJ_SONAR_TOKEN env var, or (3) add sonarqube.token to ~/.karajan/kj.config.yml.",
|
|
229
229
|
exitCode: 1
|
|
230
230
|
};
|
|
231
231
|
}
|
package/src/utils/display.js
CHANGED
|
@@ -452,7 +452,11 @@ const EVENT_HANDLERS = {
|
|
|
452
452
|
question: (event, icon) => {
|
|
453
453
|
console.log();
|
|
454
454
|
console.log(`${ANSI.bold}${ANSI.yellow}${icon} Paused - question:${ANSI.reset}`);
|
|
455
|
-
|
|
455
|
+
const questionText = event.detail?.question || event.message || "";
|
|
456
|
+
const questionLines = questionText.split("\n");
|
|
457
|
+
for (const line of questionLines) {
|
|
458
|
+
console.log(` ${line}`);
|
|
459
|
+
}
|
|
456
460
|
console.log(`${ANSI.dim}Resume with: kj resume ${event.sessionId} --answer "<response>"${ANSI.reset}`);
|
|
457
461
|
},
|
|
458
462
|
|
|
@@ -1,15 +1,62 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
4
|
+
// --- JS/TS ---
|
|
5
|
+
const JS_FRAMEWORKS = ["vitest", "jest", "@jest/core", "mocha", "ava", "tap", "playwright", "@playwright/test"];
|
|
6
|
+
const JS_CONFIG_FILES = [
|
|
7
7
|
"vitest.config.js", "vitest.config.ts",
|
|
8
8
|
"jest.config.js", "jest.config.ts", "jest.config.mjs",
|
|
9
9
|
".mocharc.yml", ".mocharc.json",
|
|
10
10
|
"playwright.config.js", "playwright.config.ts"
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
// --- Multi-language project markers ---
|
|
14
|
+
const LANGUAGE_MARKERS = [
|
|
15
|
+
// Java / Kotlin (Maven)
|
|
16
|
+
{ file: "pom.xml", framework: "junit", language: "java" },
|
|
17
|
+
// Java / Kotlin (Gradle)
|
|
18
|
+
{ file: "build.gradle", framework: "junit", language: "java" },
|
|
19
|
+
{ file: "build.gradle.kts", framework: "junit", language: "kotlin" },
|
|
20
|
+
// Python
|
|
21
|
+
{ file: "pytest.ini", framework: "pytest", language: "python" },
|
|
22
|
+
{ file: "pyproject.toml", framework: "pytest", language: "python" },
|
|
23
|
+
{ file: "setup.py", framework: "pytest", language: "python" },
|
|
24
|
+
{ file: "setup.cfg", framework: "pytest", language: "python" },
|
|
25
|
+
{ file: "tox.ini", framework: "pytest", language: "python" },
|
|
26
|
+
// Go
|
|
27
|
+
{ file: "go.mod", framework: "go-test", language: "go" },
|
|
28
|
+
// Rust
|
|
29
|
+
{ file: "Cargo.toml", framework: "cargo-test", language: "rust" },
|
|
30
|
+
// C# / .NET
|
|
31
|
+
{ file: "*.csproj", glob: true, framework: "dotnet-test", language: "csharp" },
|
|
32
|
+
{ file: "*.sln", glob: true, framework: "dotnet-test", language: "csharp" },
|
|
33
|
+
// Ruby
|
|
34
|
+
{ file: "Gemfile", framework: "rspec", language: "ruby" },
|
|
35
|
+
{ file: ".rspec", framework: "rspec", language: "ruby" },
|
|
36
|
+
// PHP
|
|
37
|
+
{ file: "phpunit.xml", framework: "phpunit", language: "php" },
|
|
38
|
+
{ file: "phpunit.xml.dist", framework: "phpunit", language: "php" },
|
|
39
|
+
// Swift
|
|
40
|
+
{ file: "Package.swift", framework: "xctest", language: "swift" },
|
|
41
|
+
// Dart / Flutter
|
|
42
|
+
{ file: "pubspec.yaml", framework: "dart-test", language: "dart" },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** Test file patterns per language (used by TDD policy). */
|
|
46
|
+
export const TEST_PATTERNS_BY_LANGUAGE = {
|
|
47
|
+
javascript: ["/tests/", "/__tests__/", ".test.", ".spec."],
|
|
48
|
+
java: ["/src/test/", "/test/", "Test.java", "Tests.java", "IT.java"],
|
|
49
|
+
kotlin: ["/src/test/", "/test/", "Test.kt", "Tests.kt", "/src/androidTest/"],
|
|
50
|
+
python: ["/tests/", "/test/", "test_", "_test.py", "tests.py"],
|
|
51
|
+
go: ["_test.go"],
|
|
52
|
+
rust: ["/tests/", "#[test]", "#[cfg(test)]"],
|
|
53
|
+
csharp: ["/Tests/", "/Test/", "Tests.cs", "Test.cs", ".Tests/"],
|
|
54
|
+
ruby: ["/spec/", "/test/", "_spec.rb", "_test.rb"],
|
|
55
|
+
php: ["/tests/", "/test/", "Test.php"],
|
|
56
|
+
swift: ["/Tests/", "Tests.swift"],
|
|
57
|
+
dart: ["/test/", "_test.dart"],
|
|
58
|
+
};
|
|
59
|
+
|
|
13
60
|
function frameworkFromConfig(filename) {
|
|
14
61
|
if (filename.startsWith("vitest")) return "vitest";
|
|
15
62
|
if (filename.startsWith("jest")) return "jest";
|
|
@@ -19,32 +66,49 @@ function frameworkFromConfig(filename) {
|
|
|
19
66
|
|
|
20
67
|
/**
|
|
21
68
|
* Detect if the project has a test framework configured.
|
|
22
|
-
* Checks
|
|
23
|
-
* Also checks for config files (vitest.config, jest.config, .mocharc, playwright.config).
|
|
69
|
+
* Checks JS (package.json), then multi-language project markers.
|
|
24
70
|
* @param {string} cwd - Project root
|
|
25
|
-
* @returns {Promise<{hasTests: boolean, framework: string|null}>}
|
|
71
|
+
* @returns {Promise<{hasTests: boolean, framework: string|null, language: string|null}>}
|
|
26
72
|
*/
|
|
27
73
|
export async function detectTestFramework(cwd = process.cwd()) {
|
|
74
|
+
// 1. JS/TS: check package.json
|
|
28
75
|
try {
|
|
29
76
|
const pkgRaw = await fs.readFile(path.join(cwd, "package.json"), "utf8");
|
|
30
77
|
const pkg = JSON.parse(pkgRaw);
|
|
31
78
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
32
79
|
|
|
33
|
-
for (const fw of
|
|
80
|
+
for (const fw of JS_FRAMEWORKS) {
|
|
34
81
|
if (allDeps[fw]) {
|
|
35
|
-
return { hasTests: true, framework: fw };
|
|
82
|
+
return { hasTests: true, framework: fw, language: "javascript" };
|
|
36
83
|
}
|
|
37
84
|
}
|
|
38
85
|
} catch { /* no package.json or parse error */ }
|
|
39
86
|
|
|
40
|
-
|
|
87
|
+
// 2. JS/TS: check config files
|
|
88
|
+
for (const cf of JS_CONFIG_FILES) {
|
|
41
89
|
try {
|
|
42
90
|
await fs.access(path.join(cwd, cf));
|
|
43
|
-
return { hasTests: true, framework: frameworkFromConfig(cf) };
|
|
91
|
+
return { hasTests: true, framework: frameworkFromConfig(cf), language: "javascript" };
|
|
92
|
+
} catch { /* not found */ }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 3. Multi-language: check project markers
|
|
96
|
+
for (const marker of LANGUAGE_MARKERS) {
|
|
97
|
+
try {
|
|
98
|
+
if (marker.glob) {
|
|
99
|
+
const entries = await fs.readdir(cwd);
|
|
100
|
+
const ext = marker.file.replace("*", "");
|
|
101
|
+
if (entries.some((e) => e.endsWith(ext))) {
|
|
102
|
+
return { hasTests: true, framework: marker.framework, language: marker.language };
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
await fs.access(path.join(cwd, marker.file));
|
|
106
|
+
return { hasTests: true, framework: marker.framework, language: marker.language };
|
|
107
|
+
}
|
|
44
108
|
} catch { /* not found */ }
|
|
45
109
|
}
|
|
46
110
|
|
|
47
|
-
return { hasTests: false, framework: null };
|
|
111
|
+
return { hasTests: false, framework: null, language: null };
|
|
48
112
|
}
|
|
49
113
|
|
|
50
114
|
/**
|
package/templates/roles/coder.md
CHANGED
|
@@ -28,6 +28,15 @@ Before reporting done, verify that ALL parts of the task are addressed:
|
|
|
28
28
|
- If you write tests first (TDD), the implementation MUST make those tests pass.
|
|
29
29
|
- Do NOT commit code that doesn't compile or doesn't pass tests.
|
|
30
30
|
|
|
31
|
+
## Secrets and environment variables
|
|
32
|
+
|
|
33
|
+
- NEVER hardcode API keys, tokens, passwords, secrets, or credentials in source code. No exceptions, not even for public keys or test keys.
|
|
34
|
+
- ALWAYS use environment variables via `process.env` (Node.js), `os.environ` (Python), or the equivalent for the project's language.
|
|
35
|
+
- If the project needs API keys, create a `.env.example` file with placeholder values and add `.env` to `.gitignore`.
|
|
36
|
+
- If `.gitignore` does not already include `.env`, add it.
|
|
37
|
+
- If the task requires Firebase, Stripe, OpenAI, or any third-party API: use `.env` for the keys and document required variables in `.env.example` or README.
|
|
38
|
+
- Database connection strings with credentials MUST use environment variables.
|
|
39
|
+
|
|
31
40
|
## File modification safety
|
|
32
41
|
|
|
33
42
|
- NEVER overwrite existing files entirely. Always make targeted, minimal edits.
|