karajan-code 1.12.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/docs/README.es.md +2 -1
- package/package.json +1 -1
- package/src/agents/claude-agent.js +2 -2
- package/src/becaria/dispatch.js +99 -0
- package/src/becaria/index.js +3 -0
- package/src/becaria/pr-diff.js +26 -0
- package/src/becaria/repo.js +45 -0
- package/src/cli.js +2 -0
- package/src/commands/doctor.js +56 -1
- package/src/commands/init.js +33 -0
- package/src/commands/review.js +54 -2
- package/src/config.js +11 -0
- package/src/git/automation.js +65 -2
- package/src/mcp/tools.js +1 -0
- package/src/orchestrator/iteration-stages.js +8 -1
- package/src/orchestrator.js +185 -1
- package/templates/roles/coder.md +11 -7
- package/templates/roles/planner.md +2 -0
- package/templates/roles/refactorer.md +1 -1
- package/templates/roles/reviewer.md +11 -4
- package/templates/workflows/automerge.yml +30 -0
- package/templates/workflows/becaria-gateway.yml +58 -0
- package/templates/workflows/houston-override.yml +46 -0
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ Instead of running one AI agent and manually reviewing its output, `kj` chains a
|
|
|
47
47
|
- **Rate-limit standby** — when agents hit rate limits, Karajan parses cooldown times, waits with exponential backoff, and auto-resumes instead of failing
|
|
48
48
|
- **Preflight handshake** — `kj_preflight` requires human confirmation of agent assignments before execution, preventing AI from silently overriding your config
|
|
49
49
|
- **3-tier config** — session > project > global config layering with `kj_agents` scoping
|
|
50
|
+
- **Intelligent reviewer mediation** — scope filter auto-defers out-of-scope reviewer issues (files not in the diff) as tracked tech debt instead of stalling; Solomon mediates stalled reviews; deferred context injected into coder prompt
|
|
50
51
|
- **Planning Game integration** — optionally pair with [Planning Game](https://github.com/AgenteIA-Geniova/planning-game) for agile project management (tasks, sprints, estimation) — like Jira, but open-source and XP-native
|
|
51
52
|
|
|
52
53
|
> **Best with MCP** — Karajan Code is designed to be used as an MCP server inside your AI agent (Claude, Codex, etc.). The agent sends tasks to `kj_run`, gets real-time progress notifications, and receives structured results — no copy-pasting needed.
|
|
@@ -74,7 +75,7 @@ triage? ─> researcher? ─> planner? ─> coder ─> refactorer? ─> sonar?
|
|
|
74
75
|
| **reviewer** | Code review with configurable strictness profiles | **Always on** |
|
|
75
76
|
| **tester** | Test quality gate and coverage verification | **On** |
|
|
76
77
|
| **security** | OWASP security audit | **On** |
|
|
77
|
-
| **solomon** | Session supervisor — monitors iteration health with
|
|
78
|
+
| **solomon** | Session supervisor — monitors iteration health with 5 rules (incl. reviewer overreach), mediates stalled reviews, escalates on anomalies | **On** |
|
|
78
79
|
| **commiter** | Git commit, push, and PR automation after approval | Off |
|
|
79
80
|
|
|
80
81
|
Roles marked with `?` are optional and can be enabled per-run or via config.
|
|
@@ -477,7 +478,7 @@ Use `kj roles show <role>` to inspect any template. Create a project override to
|
|
|
477
478
|
git clone https://github.com/manufosela/karajan-code.git
|
|
478
479
|
cd karajan-code
|
|
479
480
|
npm install
|
|
480
|
-
npm test # Run
|
|
481
|
+
npm test # Run 1190+ tests with Vitest
|
|
481
482
|
npm run test:watch # Watch mode
|
|
482
483
|
npm run validate # Lint + test
|
|
483
484
|
```
|
package/docs/README.es.md
CHANGED
|
@@ -46,6 +46,7 @@ En lugar de ejecutar un agente de IA y revisar manualmente su output, `kj` encad
|
|
|
46
46
|
- **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
|
|
47
47
|
- **Preflight handshake** — `kj_preflight` requiere confirmacion humana de la configuracion de agentes antes de ejecutar, previniendo que la IA cambie asignaciones silenciosamente
|
|
48
48
|
- **Config de 3 niveles** — sesion > proyecto > global con scoping de `kj_agents`
|
|
49
|
+
- **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
|
|
49
50
|
- **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
|
|
50
51
|
|
|
51
52
|
> **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.
|
|
@@ -73,7 +74,7 @@ triage? ─> researcher? ─> planner? ─> coder ─> refactorer? ─> sonar?
|
|
|
73
74
|
| **reviewer** | Revision de codigo con perfiles de exigencia configurables | **Siempre activo** |
|
|
74
75
|
| **tester** | Quality gate de tests y verificacion de cobertura | **On** |
|
|
75
76
|
| **security** | Auditoria de seguridad OWASP | **On** |
|
|
76
|
-
| **solomon** | Supervisor de sesion — monitoriza salud de iteraciones con
|
|
77
|
+
| **solomon** | Supervisor de sesion — monitoriza salud de iteraciones con 5 reglas (incl. reviewer overreach), media reviews estancados, escala ante anomalias | **On** |
|
|
77
78
|
| **commiter** | Automatizacion de git commit, push y PR tras aprobacion | Off |
|
|
78
79
|
|
|
79
80
|
Los roles marcados con `?` son opcionales y se pueden activar por ejecucion o via config.
|
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
120
120
|
|
|
121
121
|
// Use stream-json when onOutput is provided to get real-time feedback
|
|
122
122
|
if (task.onOutput) {
|
|
123
|
-
args.push("--output-format", "stream-json");
|
|
123
|
+
args.push("--output-format", "stream-json", "--verbose");
|
|
124
124
|
const streamFilter = createStreamJsonFilter(task.onOutput);
|
|
125
125
|
const res = await runCommand(resolveBin("claude"), args, cleanExecaOpts({
|
|
126
126
|
onOutput: streamFilter,
|
|
@@ -141,7 +141,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
async reviewTask(task) {
|
|
144
|
-
const args = ["-p", task.prompt, "--allowedTools", ...ALLOWED_TOOLS, "--output-format", "stream-json"];
|
|
144
|
+
const args = ["-p", task.prompt, "--allowedTools", ...ALLOWED_TOOLS, "--output-format", "stream-json", "--verbose"];
|
|
145
145
|
const model = this.getRoleModel(task.role || "reviewer");
|
|
146
146
|
if (model) args.push("--model", model);
|
|
147
147
|
const res = await runCommand(resolveBin("claude"), args, cleanExecaOpts({
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BecarIA dispatch client — sends repository_dispatch events via gh CLI
|
|
3
|
+
* so the BecarIA Gateway can publish comments and reviews on PRs.
|
|
4
|
+
*
|
|
5
|
+
* Event types are configurable via becaria config:
|
|
6
|
+
* - comment_event (default: "becaria-comment")
|
|
7
|
+
* - review_event (default: "becaria-review")
|
|
8
|
+
*
|
|
9
|
+
* Only active when becaria.enabled: true.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { runCommand } from "../utils/process.js";
|
|
13
|
+
|
|
14
|
+
export const VALID_AGENTS = [
|
|
15
|
+
"Coder",
|
|
16
|
+
"Reviewer",
|
|
17
|
+
"Solomon",
|
|
18
|
+
"Sonar",
|
|
19
|
+
"Tester",
|
|
20
|
+
"Security",
|
|
21
|
+
"Planner"
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const VALID_REVIEW_EVENTS = ["APPROVE", "REQUEST_CHANGES"];
|
|
25
|
+
|
|
26
|
+
function validateCommon({ repo, prNumber }) {
|
|
27
|
+
if (!repo) throw new Error("repo is required (e.g. 'owner/repo')");
|
|
28
|
+
if (!prNumber) throw new Error("prNumber is required (positive integer)");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function validateAgent(agent) {
|
|
32
|
+
if (!VALID_AGENTS.includes(agent)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Invalid agent "${agent}". Must be one of: ${VALID_AGENTS.join(", ")}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function sendDispatch(repo, payload) {
|
|
40
|
+
const res = await runCommand(
|
|
41
|
+
"gh",
|
|
42
|
+
["api", `repos/${repo}/dispatches`, "--method", "POST", "--input", "-"],
|
|
43
|
+
{ input: JSON.stringify(payload) }
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (res.exitCode === 127) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"gh CLI not found. Install GitHub CLI: https://cli.github.com/"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (res.exitCode !== 0) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Dispatch failed (exit ${res.exitCode}): ${res.stderr || res.stdout}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Send a comment event so the gateway posts a PR comment.
|
|
61
|
+
* @param {object} opts
|
|
62
|
+
* @param {object} [opts.becariaConfig] - becaria config section (optional)
|
|
63
|
+
*/
|
|
64
|
+
export async function dispatchComment({ repo, prNumber, agent, body, becariaConfig }) {
|
|
65
|
+
validateCommon({ repo, prNumber });
|
|
66
|
+
validateAgent(agent);
|
|
67
|
+
if (!body) throw new Error("body is required (comment text)");
|
|
68
|
+
|
|
69
|
+
const prefix = becariaConfig?.comment_prefix !== false ? `[${agent}] ` : "";
|
|
70
|
+
const eventType = becariaConfig?.comment_event || "becaria-comment";
|
|
71
|
+
|
|
72
|
+
await sendDispatch(repo, {
|
|
73
|
+
event_type: eventType,
|
|
74
|
+
client_payload: { pr_number: prNumber, agent, body: `${prefix}${body}` }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Send a review event so the gateway submits a formal PR review.
|
|
80
|
+
* @param {object} opts
|
|
81
|
+
* @param {object} [opts.becariaConfig] - becaria config section (optional)
|
|
82
|
+
*/
|
|
83
|
+
export async function dispatchReview({ repo, prNumber, event, body, agent, becariaConfig }) {
|
|
84
|
+
validateCommon({ repo, prNumber });
|
|
85
|
+
validateAgent(agent);
|
|
86
|
+
if (!VALID_REVIEW_EVENTS.includes(event)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`event must be one of: ${VALID_REVIEW_EVENTS.join(", ")} (got "${event}")`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (!body) throw new Error("body is required (review text)");
|
|
92
|
+
|
|
93
|
+
const eventType = becariaConfig?.review_event || "becaria-review";
|
|
94
|
+
|
|
95
|
+
await sendDispatch(repo, {
|
|
96
|
+
event_type: eventType,
|
|
97
|
+
client_payload: { pr_number: prNumber, event, body, agent }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read PR diff via gh CLI for BecarIA Gateway flow.
|
|
3
|
+
* The reviewer reads the PR diff instead of local git diff.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { runCommand } from "../utils/process.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the diff of a PR via `gh pr diff <number>`.
|
|
10
|
+
* Returns the diff string or throws on failure.
|
|
11
|
+
*/
|
|
12
|
+
export async function getPrDiff(prNumber) {
|
|
13
|
+
if (!prNumber) throw new Error("prNumber is required");
|
|
14
|
+
|
|
15
|
+
const res = await runCommand("gh", [
|
|
16
|
+
"pr",
|
|
17
|
+
"diff",
|
|
18
|
+
String(prNumber)
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
if (res.exitCode !== 0) {
|
|
22
|
+
throw new Error(`gh pr diff ${prNumber} failed: ${res.stderr || res.stdout}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return res.stdout;
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect GitHub repo and PR number from local git context.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { runCommand } from "../utils/process.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detect owner/repo from the git remote URL (origin).
|
|
9
|
+
* Supports HTTPS, SSH, and custom SSH aliases (e.g. github.com-user).
|
|
10
|
+
* Returns null if not a GitHub repo or not a git repo.
|
|
11
|
+
*/
|
|
12
|
+
export async function detectRepo() {
|
|
13
|
+
const res = await runCommand("git", [
|
|
14
|
+
"remote",
|
|
15
|
+
"get-url",
|
|
16
|
+
"origin"
|
|
17
|
+
]);
|
|
18
|
+
if (res.exitCode !== 0) return null;
|
|
19
|
+
|
|
20
|
+
const url = res.stdout.trim();
|
|
21
|
+
// SSH: git@github.com:owner/repo.git or git@github.com-alias:owner/repo.git
|
|
22
|
+
const sshMatch = url.match(/github\.com[^:]*:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
23
|
+
if (sshMatch) return sshMatch[1];
|
|
24
|
+
|
|
25
|
+
// HTTPS: https://github.com/owner/repo.git
|
|
26
|
+
const httpsMatch = url.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
27
|
+
if (httpsMatch) return httpsMatch[1];
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect the PR number for a given branch using gh CLI.
|
|
34
|
+
* Returns null if no PR exists for the branch.
|
|
35
|
+
*/
|
|
36
|
+
export async function detectPrNumber(branch) {
|
|
37
|
+
const args = ["pr", "view"];
|
|
38
|
+
if (branch) args.push(branch);
|
|
39
|
+
args.push("--json", "number", "--jq", ".number");
|
|
40
|
+
const res = await runCommand("gh", args);
|
|
41
|
+
if (res.exitCode !== 0) return null;
|
|
42
|
+
|
|
43
|
+
const num = parseInt(res.stdout.trim(), 10);
|
|
44
|
+
return Number.isFinite(num) ? num : null;
|
|
45
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -37,6 +37,7 @@ program
|
|
|
37
37
|
.command("init")
|
|
38
38
|
.description("Initialize config, review rules and SonarQube")
|
|
39
39
|
.option("--no-interactive", "Skip wizard, use defaults (for CI/scripts)")
|
|
40
|
+
.option("--scaffold-becaria", "Scaffold BecarIA Gateway workflow files")
|
|
40
41
|
.action(async (flags) => {
|
|
41
42
|
await withConfig("init", flags, async ({ config, logger }) => {
|
|
42
43
|
await initCommand({ logger, flags });
|
|
@@ -84,6 +85,7 @@ program
|
|
|
84
85
|
.option("--auto-commit")
|
|
85
86
|
.option("--auto-push")
|
|
86
87
|
.option("--auto-pr")
|
|
88
|
+
.option("--enable-becaria", "Enable BecarIA Gateway (early PR + dispatch comments/reviews)")
|
|
87
89
|
.option("--branch-prefix <prefix>")
|
|
88
90
|
.option("--methodology <name>")
|
|
89
91
|
.option("--no-auto-rebase")
|
package/src/commands/doctor.js
CHANGED
|
@@ -129,7 +129,62 @@ export async function runChecks({ config }) {
|
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
// 8.
|
|
132
|
+
// 8. BecarIA Gateway infrastructure
|
|
133
|
+
if (config.becaria?.enabled) {
|
|
134
|
+
const projectDir = config.projectDir || process.cwd();
|
|
135
|
+
|
|
136
|
+
// Workflow files
|
|
137
|
+
const workflowDir = path.join(projectDir, ".github", "workflows");
|
|
138
|
+
const requiredWorkflows = ["becaria-gateway.yml", "automerge.yml", "houston-override.yml"];
|
|
139
|
+
for (const wf of requiredWorkflows) {
|
|
140
|
+
const wfPath = path.join(workflowDir, wf);
|
|
141
|
+
const wfExists = await exists(wfPath);
|
|
142
|
+
checks.push({
|
|
143
|
+
name: `becaria:workflow:${wf}`,
|
|
144
|
+
label: `BecarIA workflow: ${wf}`,
|
|
145
|
+
ok: wfExists,
|
|
146
|
+
detail: wfExists ? "Found" : "Not found",
|
|
147
|
+
fix: wfExists ? null : `Run 'kj init --scaffold-becaria' or copy from karajan-code/templates/workflows/${wf}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// gh CLI
|
|
152
|
+
const ghCheck = await checkBinary("gh");
|
|
153
|
+
checks.push({
|
|
154
|
+
name: "becaria:gh",
|
|
155
|
+
label: "BecarIA: gh CLI",
|
|
156
|
+
ok: ghCheck.ok,
|
|
157
|
+
detail: ghCheck.ok ? ghCheck.version : "Not found",
|
|
158
|
+
fix: ghCheck.ok ? null : "Install GitHub CLI: https://cli.github.com/"
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Secrets check via gh api (best effort — only works if user has admin access)
|
|
162
|
+
let secretsOk = false;
|
|
163
|
+
try {
|
|
164
|
+
const { detectRepo } = await import("../becaria/repo.js");
|
|
165
|
+
const repo = await detectRepo();
|
|
166
|
+
if (repo) {
|
|
167
|
+
const secretsRes = await runCommand("gh", ["api", `repos/${repo}/actions/secrets`, "--jq", ".secrets[].name"]);
|
|
168
|
+
if (secretsRes.exitCode === 0) {
|
|
169
|
+
const names = secretsRes.stdout.split("\n").map((s) => s.trim());
|
|
170
|
+
const hasAppId = names.includes("BECARIA_APP_ID");
|
|
171
|
+
const hasKey = names.includes("BECARIA_APP_PRIVATE_KEY");
|
|
172
|
+
secretsOk = hasAppId && hasKey;
|
|
173
|
+
checks.push({
|
|
174
|
+
name: "becaria:secrets",
|
|
175
|
+
label: "BecarIA: GitHub secrets",
|
|
176
|
+
ok: secretsOk,
|
|
177
|
+
detail: secretsOk ? "BECARIA_APP_ID + BECARIA_APP_PRIVATE_KEY found" : `Missing: ${!hasAppId ? "BECARIA_APP_ID " : ""}${!hasKey ? "BECARIA_APP_PRIVATE_KEY" : ""}`.trim(),
|
|
178
|
+
fix: secretsOk ? null : "Add BECARIA_APP_ID and BECARIA_APP_PRIVATE_KEY as GitHub repository secrets"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// Skip secrets check if we can't access the API
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 9. Review rules / Coder rules
|
|
133
188
|
const projectDir = config.projectDir || process.cwd();
|
|
134
189
|
const reviewRules = await loadFirstExisting(resolveRoleMdPath("reviewer", projectDir));
|
|
135
190
|
const coderRules = await loadFirstExisting(resolveRoleMdPath("coder", projectDir));
|
package/src/commands/init.js
CHANGED
|
@@ -177,4 +177,37 @@ export async function initCommand({ logger, flags = {} }) {
|
|
|
177
177
|
} else {
|
|
178
178
|
logger.info("SonarQube disabled — skipping container setup.");
|
|
179
179
|
}
|
|
180
|
+
|
|
181
|
+
// --- BecarIA Gateway scaffolding ---
|
|
182
|
+
if (config.becaria?.enabled || flags.scaffoldBecaria) {
|
|
183
|
+
const projectDir = process.cwd();
|
|
184
|
+
const workflowDir = path.join(projectDir, ".github", "workflows");
|
|
185
|
+
await ensureDir(workflowDir);
|
|
186
|
+
|
|
187
|
+
const templatesDir = path.resolve(import.meta.dirname, "../../templates/workflows");
|
|
188
|
+
const workflows = ["becaria-gateway.yml", "automerge.yml", "houston-override.yml"];
|
|
189
|
+
|
|
190
|
+
for (const wf of workflows) {
|
|
191
|
+
const destPath = path.join(workflowDir, wf);
|
|
192
|
+
if (!(await exists(destPath))) {
|
|
193
|
+
const srcPath = path.join(templatesDir, wf);
|
|
194
|
+
try {
|
|
195
|
+
const content = await fs.readFile(srcPath, "utf8");
|
|
196
|
+
await fs.writeFile(destPath, content, "utf8");
|
|
197
|
+
logger.info(`Created ${path.relative(projectDir, destPath)}`);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
logger.warn(`Could not scaffold ${wf}: ${err.message}`);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
logger.info(`${wf} already exists — skipping`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
logger.info("");
|
|
207
|
+
logger.info("BecarIA Gateway scaffolded. Next steps:");
|
|
208
|
+
logger.info(" 1. Create a GitHub App named 'becaria-reviewer' with pull_request write permissions");
|
|
209
|
+
logger.info(" 2. Install the App on your repository");
|
|
210
|
+
logger.info(" 3. Add secrets: BECARIA_APP_ID and BECARIA_APP_PRIVATE_KEY");
|
|
211
|
+
logger.info(" 4. Push the workflow files and enable 'kj run --enable-becaria'");
|
|
212
|
+
}
|
|
180
213
|
}
|
package/src/commands/review.js
CHANGED
|
@@ -10,8 +10,26 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
10
10
|
await assertAgentsAvailable([reviewerRole.provider, config.reviewer_options?.fallback_reviewer]);
|
|
11
11
|
logger.info(`Reviewer (${reviewerRole.provider}) starting...`);
|
|
12
12
|
const reviewer = createAgent(reviewerRole.provider, config, logger);
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
let diff;
|
|
15
|
+
if (config.becaria?.enabled) {
|
|
16
|
+
// BecarIA mode: read diff from open PR
|
|
17
|
+
const { detectRepo, detectPrNumber } = await import("../becaria/repo.js");
|
|
18
|
+
const { getPrDiff } = await import("../becaria/pr-diff.js");
|
|
19
|
+
const repo = await detectRepo();
|
|
20
|
+
const prNumber = await detectPrNumber();
|
|
21
|
+
if (!prNumber) {
|
|
22
|
+
throw new Error("BecarIA enabled but no open PR found for current branch. Create a PR first or disable BecarIA.");
|
|
23
|
+
}
|
|
24
|
+
logger.info(`BecarIA: reading PR diff #${prNumber}`);
|
|
25
|
+
diff = await getPrDiff(prNumber);
|
|
26
|
+
// Store for dispatch later
|
|
27
|
+
config._becaria_pr = { repo, prNumber };
|
|
28
|
+
} else {
|
|
29
|
+
const resolvedBase = await computeBaseRef({ baseBranch: config.base_branch, baseRef });
|
|
30
|
+
diff = await generateDiff({ baseRef: resolvedBase });
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
|
|
16
34
|
|
|
17
35
|
const prompt = buildReviewerPrompt({ task, diff, reviewRules: rules, mode: config.review_mode });
|
|
@@ -23,4 +41,38 @@ export async function reviewCommand({ task, config, logger, baseRef }) {
|
|
|
23
41
|
}
|
|
24
42
|
console.log(result.output);
|
|
25
43
|
logger.info(`Reviewer completed (exit ${result.exitCode})`);
|
|
44
|
+
|
|
45
|
+
// BecarIA: dispatch review result
|
|
46
|
+
if (config.becaria?.enabled && config._becaria_pr) {
|
|
47
|
+
try {
|
|
48
|
+
const { dispatchReview, dispatchComment } = await import("../becaria/dispatch.js");
|
|
49
|
+
const { repo, prNumber } = config._becaria_pr;
|
|
50
|
+
const bc = config.becaria;
|
|
51
|
+
|
|
52
|
+
// Try to parse structured review from output
|
|
53
|
+
let review;
|
|
54
|
+
try {
|
|
55
|
+
review = JSON.parse(result.output);
|
|
56
|
+
} catch {
|
|
57
|
+
review = { approved: true, summary: result.output };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const event = review.approved ? "APPROVE" : "REQUEST_CHANGES";
|
|
61
|
+
await dispatchReview({
|
|
62
|
+
repo, prNumber, event,
|
|
63
|
+
body: review.summary || result.output.slice(0, 500),
|
|
64
|
+
agent: "Reviewer", becariaConfig: bc
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await dispatchComment({
|
|
68
|
+
repo, prNumber, agent: "Reviewer",
|
|
69
|
+
body: `Standalone review: ${event}\n\n${review.summary || result.output.slice(0, 1000)}`,
|
|
70
|
+
becariaConfig: bc
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
logger.info(`BecarIA: dispatched review for PR #${prNumber}`);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.warn(`BecarIA dispatch failed (non-blocking): ${err.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
26
78
|
}
|
package/src/config.js
CHANGED
|
@@ -99,6 +99,7 @@ const DEFAULTS = {
|
|
|
99
99
|
},
|
|
100
100
|
serena: { enabled: false },
|
|
101
101
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
102
|
+
becaria: { enabled: false, review_event: "becaria-review", comment_event: "becaria-comment", comment_prefix: true },
|
|
102
103
|
git: { auto_commit: false, auto_push: false, auto_pr: false, auto_rebase: true, branch_prefix: "feat/" },
|
|
103
104
|
output: { report_dir: "./.reviews", log_level: "info" },
|
|
104
105
|
budget: {
|
|
@@ -287,6 +288,16 @@ export function applyRunOverrides(config, flags) {
|
|
|
287
288
|
if (flags.noSonar || flags.sonar === false) out.sonarqube.enabled = false;
|
|
288
289
|
out.serena = out.serena || { enabled: false };
|
|
289
290
|
if (flags.enableSerena !== undefined) out.serena.enabled = Boolean(flags.enableSerena);
|
|
291
|
+
out.becaria = out.becaria || { enabled: false };
|
|
292
|
+
if (flags.enableBecaria !== undefined) {
|
|
293
|
+
out.becaria.enabled = Boolean(flags.enableBecaria);
|
|
294
|
+
// BecarIA requires git automation (commit + push + PR)
|
|
295
|
+
if (out.becaria.enabled) {
|
|
296
|
+
out.git.auto_commit = true;
|
|
297
|
+
out.git.auto_push = true;
|
|
298
|
+
out.git.auto_pr = true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
290
301
|
out.planning_game = out.planning_game || {};
|
|
291
302
|
if (flags.pgTask) out.planning_game.enabled = true;
|
|
292
303
|
if (flags.pgProject) out.planning_game.project_id = flags.pgProject;
|
package/src/git/automation.js
CHANGED
|
@@ -82,6 +82,67 @@ export function buildPrBody({ task, stageResults }) {
|
|
|
82
82
|
return sections.join("\n");
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Create an early PR after the first coder iteration (BecarIA Gateway flow).
|
|
87
|
+
* Commits, pushes, and creates a PR before the reviewer runs.
|
|
88
|
+
* Returns { prNumber, prUrl, commits } or null if nothing to commit.
|
|
89
|
+
*/
|
|
90
|
+
export async function earlyPrCreation({ gitCtx, task, logger, session, stageResults = null }) {
|
|
91
|
+
if (!gitCtx?.enabled) return null;
|
|
92
|
+
|
|
93
|
+
const commitMsg = commitMessageFromTask(task);
|
|
94
|
+
const commitResult = await commitAll(commitMsg);
|
|
95
|
+
if (!commitResult.committed) {
|
|
96
|
+
logger.info("earlyPrCreation: no changes to commit");
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
101
|
+
await addCheckpoint(session, { stage: "becaria-commit", committed: true });
|
|
102
|
+
|
|
103
|
+
await pushBranch(gitCtx.branch);
|
|
104
|
+
await addCheckpoint(session, { stage: "becaria-push", branch: gitCtx.branch });
|
|
105
|
+
logger.info(`Pushed branch for early PR: ${gitCtx.branch}`);
|
|
106
|
+
|
|
107
|
+
const body = buildPrBody({ task, stageResults });
|
|
108
|
+
const prUrl = await createPullRequest({
|
|
109
|
+
baseBranch: gitCtx.baseBranch,
|
|
110
|
+
branch: gitCtx.branch,
|
|
111
|
+
title: commitMessageFromTask(task),
|
|
112
|
+
body
|
|
113
|
+
});
|
|
114
|
+
await addCheckpoint(session, { stage: "becaria-pr", branch: gitCtx.branch, pr: prUrl });
|
|
115
|
+
logger.info(`Early PR created: ${prUrl}`);
|
|
116
|
+
|
|
117
|
+
// Extract PR number from URL (e.g. https://github.com/owner/repo/pull/42)
|
|
118
|
+
const prNumber = parseInt(prUrl.split("/").pop(), 10) || null;
|
|
119
|
+
return { prNumber, prUrl, commits };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Incremental push after each coder iteration (BecarIA Gateway flow).
|
|
124
|
+
* Commits and pushes without creating a new PR.
|
|
125
|
+
*/
|
|
126
|
+
export async function incrementalPush({ gitCtx, task, logger, session }) {
|
|
127
|
+
if (!gitCtx?.enabled) return null;
|
|
128
|
+
|
|
129
|
+
const commitMsg = commitMessageFromTask(task);
|
|
130
|
+
const commitResult = await commitAll(commitMsg);
|
|
131
|
+
if (!commitResult.committed) {
|
|
132
|
+
logger.info("incrementalPush: no changes to commit");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
137
|
+
await addCheckpoint(session, { stage: "becaria-incremental-commit", committed: true });
|
|
138
|
+
|
|
139
|
+
await pushBranch(gitCtx.branch);
|
|
140
|
+
await addCheckpoint(session, { stage: "becaria-incremental-push", branch: gitCtx.branch });
|
|
141
|
+
logger.info(`Incremental push: ${gitCtx.branch}`);
|
|
142
|
+
|
|
143
|
+
return { commits };
|
|
144
|
+
}
|
|
145
|
+
|
|
85
146
|
export async function finalizeGitAutomation({ config, gitCtx, task, logger, session, stageResults = null }) {
|
|
86
147
|
if (!gitCtx?.enabled) return { git: "disabled", commits: [] };
|
|
87
148
|
|
|
@@ -114,8 +175,8 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
114
175
|
logger.info(`Pushed branch: ${gitCtx.branch}`);
|
|
115
176
|
}
|
|
116
177
|
|
|
117
|
-
let prUrl = null;
|
|
118
|
-
if (config.git.auto_pr) {
|
|
178
|
+
let prUrl = session.becaria_pr_url || null;
|
|
179
|
+
if (config.git.auto_pr && !prUrl) {
|
|
119
180
|
const body = buildPrBody({ task, stageResults });
|
|
120
181
|
prUrl = await createPullRequest({
|
|
121
182
|
baseBranch: gitCtx.baseBranch,
|
|
@@ -125,6 +186,8 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
125
186
|
});
|
|
126
187
|
await addCheckpoint(session, { stage: "git-pr", branch: gitCtx.branch, pr: prUrl });
|
|
127
188
|
logger.info("Pull request created");
|
|
189
|
+
} else if (prUrl) {
|
|
190
|
+
logger.info(`PR already exists (BecarIA flow): ${prUrl}`);
|
|
128
191
|
}
|
|
129
192
|
|
|
130
193
|
return { committed, branch: gitCtx.branch, prUrl, pr: prUrl, commits };
|
package/src/mcp/tools.js
CHANGED
|
@@ -71,6 +71,7 @@ export const tools = [
|
|
|
71
71
|
enableSecurity: { type: "boolean" },
|
|
72
72
|
enableTriage: { type: "boolean" },
|
|
73
73
|
enableSerena: { type: "boolean" },
|
|
74
|
+
enableBecaria: { type: "boolean", description: "Enable BecarIA Gateway (early PR + dispatch comments/reviews)" },
|
|
74
75
|
reviewerFallback: { type: "string" },
|
|
75
76
|
reviewerRetries: { type: "number" },
|
|
76
77
|
mode: { type: "string" },
|
|
@@ -402,7 +402,14 @@ export async function runReviewerStage({ reviewerRole, config, logger, emitter,
|
|
|
402
402
|
})
|
|
403
403
|
);
|
|
404
404
|
|
|
405
|
-
|
|
405
|
+
let diff;
|
|
406
|
+
if (session.becaria_pr_number) {
|
|
407
|
+
const { getPrDiff } = await import("../becaria/pr-diff.js");
|
|
408
|
+
diff = await getPrDiff(session.becaria_pr_number);
|
|
409
|
+
logger.info(`Reviewer reading PR diff #${session.becaria_pr_number}`);
|
|
410
|
+
} else {
|
|
411
|
+
diff = await generateDiff({ baseRef: session.session_start_sha });
|
|
412
|
+
}
|
|
406
413
|
const reviewerOnOutput = ({ stream, line }) => {
|
|
407
414
|
emitProgress(emitter, makeEvent("agent:output", { ...eventBase, stage: "reviewer" }, {
|
|
408
415
|
message: line,
|
package/src/orchestrator.js
CHANGED
|
@@ -17,7 +17,9 @@ import { emitProgress, makeEvent } from "./utils/events.js";
|
|
|
17
17
|
import { BudgetTracker, extractUsageMetrics } from "./utils/budget.js";
|
|
18
18
|
import {
|
|
19
19
|
prepareGitAutomation,
|
|
20
|
-
finalizeGitAutomation
|
|
20
|
+
finalizeGitAutomation,
|
|
21
|
+
earlyPrCreation,
|
|
22
|
+
incrementalPush
|
|
21
23
|
} from "./git/automation.js";
|
|
22
24
|
import { resolveRoleMdPath, loadFirstExisting } from "./roles/base-role.js";
|
|
23
25
|
import { resolveReviewProfile } from "./review/profiles.js";
|
|
@@ -288,6 +290,23 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
288
290
|
const plannerResult = await runPlannerStage({ config, logger, emitter, eventBase, session, plannerRole, researchContext, triageDecomposition, trackBudget });
|
|
289
291
|
plannedTask = plannerResult.plannedTask;
|
|
290
292
|
stageResults.planner = plannerResult.stageResult;
|
|
293
|
+
|
|
294
|
+
// BecarIA: dispatch planner comment (only on resume where PR already exists)
|
|
295
|
+
if (Boolean(config.becaria?.enabled) && session.becaria_pr_number) {
|
|
296
|
+
try {
|
|
297
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
298
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
299
|
+
const repo = await detectRepo();
|
|
300
|
+
if (repo) {
|
|
301
|
+
const p = plannerResult.stageResult;
|
|
302
|
+
await dispatchComment({
|
|
303
|
+
repo, prNumber: session.becaria_pr_number, agent: "Planner",
|
|
304
|
+
body: `Plan: ${p?.summary || plannedTask}`,
|
|
305
|
+
becariaConfig: config.becaria
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
} catch { /* non-blocking */ }
|
|
309
|
+
}
|
|
291
310
|
}
|
|
292
311
|
|
|
293
312
|
const gitCtx = await prepareGitAutomation({ config, task, logger, session });
|
|
@@ -393,6 +412,7 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
393
412
|
|
|
394
413
|
eventBase.iteration = i;
|
|
395
414
|
const iterStart = Date.now();
|
|
415
|
+
const becariaEnabled = Boolean(config.becaria?.enabled) && gitCtx?.enabled;
|
|
396
416
|
logger.setContext({ iteration: i, stage: "iteration" });
|
|
397
417
|
|
|
398
418
|
emitProgress(
|
|
@@ -483,6 +503,75 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
483
503
|
}
|
|
484
504
|
if (sonarResult.stageResult) {
|
|
485
505
|
stageResults.sonar = sonarResult.stageResult;
|
|
506
|
+
// BecarIA: dispatch sonar comment
|
|
507
|
+
if (becariaEnabled && session.becaria_pr_number) {
|
|
508
|
+
try {
|
|
509
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
510
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
511
|
+
const repo = await detectRepo();
|
|
512
|
+
if (repo) {
|
|
513
|
+
const s = sonarResult.stageResult;
|
|
514
|
+
await dispatchComment({
|
|
515
|
+
repo, prNumber: session.becaria_pr_number, agent: "Sonar",
|
|
516
|
+
body: `SonarQube scan: ${s.summary || "completed"}`,
|
|
517
|
+
becariaConfig: config.becaria
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
} catch { /* non-blocking */ }
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// --- BecarIA Gateway: early PR or incremental push ---
|
|
526
|
+
if (becariaEnabled) {
|
|
527
|
+
try {
|
|
528
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
529
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
530
|
+
const repo = await detectRepo();
|
|
531
|
+
|
|
532
|
+
if (!session.becaria_pr_number) {
|
|
533
|
+
// First iteration: commit + push + create PR
|
|
534
|
+
const earlyPr = await earlyPrCreation({ gitCtx, task, logger, session, stageResults });
|
|
535
|
+
if (earlyPr) {
|
|
536
|
+
session.becaria_pr_number = earlyPr.prNumber;
|
|
537
|
+
session.becaria_pr_url = earlyPr.prUrl;
|
|
538
|
+
session.becaria_commits = earlyPr.commits;
|
|
539
|
+
await saveSession(session);
|
|
540
|
+
emitProgress(emitter, makeEvent("becaria:pr-created", { ...eventBase, stage: "becaria" }, {
|
|
541
|
+
message: `Early PR created: #${earlyPr.prNumber}`,
|
|
542
|
+
detail: { prNumber: earlyPr.prNumber, prUrl: earlyPr.prUrl }
|
|
543
|
+
}));
|
|
544
|
+
|
|
545
|
+
// Post coder comment on new PR
|
|
546
|
+
if (repo) {
|
|
547
|
+
const commitList = earlyPr.commits.map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
548
|
+
await dispatchComment({
|
|
549
|
+
repo, prNumber: earlyPr.prNumber, agent: "Coder",
|
|
550
|
+
body: `Iteración ${i} completada.\n\nCommits:\n${commitList}`,
|
|
551
|
+
becariaConfig: config.becaria
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
// Subsequent iterations: incremental push + comment
|
|
557
|
+
const pushResult = await incrementalPush({ gitCtx, task, logger, session });
|
|
558
|
+
if (pushResult) {
|
|
559
|
+
session.becaria_commits = [...(session.becaria_commits || []), ...pushResult.commits];
|
|
560
|
+
await saveSession(session);
|
|
561
|
+
|
|
562
|
+
if (repo) {
|
|
563
|
+
const feedback = session.last_reviewer_feedback || "N/A";
|
|
564
|
+
const commitList = pushResult.commits.map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
565
|
+
await dispatchComment({
|
|
566
|
+
repo, prNumber: session.becaria_pr_number, agent: "Coder",
|
|
567
|
+
body: `Issues corregidos:\n${feedback}\n\nCommits:\n${commitList}`,
|
|
568
|
+
becariaConfig: config.becaria
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} catch (err) {
|
|
574
|
+
logger.warn(`BecarIA early PR/push failed (non-blocking): ${err.message}`);
|
|
486
575
|
}
|
|
487
576
|
}
|
|
488
577
|
|
|
@@ -576,11 +665,74 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
576
665
|
}
|
|
577
666
|
}
|
|
578
667
|
}
|
|
668
|
+
|
|
669
|
+
// BecarIA: dispatch solomon comment
|
|
670
|
+
if (becariaEnabled && session.becaria_pr_number) {
|
|
671
|
+
try {
|
|
672
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
673
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
674
|
+
const repo = await detectRepo();
|
|
675
|
+
if (repo) {
|
|
676
|
+
const alerts = rulesResult.alerts || [];
|
|
677
|
+
const alertMsg = alerts.length > 0
|
|
678
|
+
? alerts.map(a => `- [${a.severity}] ${a.message}`).join("\n")
|
|
679
|
+
: "No anomalies detected";
|
|
680
|
+
await dispatchComment({
|
|
681
|
+
repo, prNumber: session.becaria_pr_number, agent: "Solomon",
|
|
682
|
+
body: `Supervisor check iteración ${i}: ${alertMsg}`,
|
|
683
|
+
becariaConfig: config.becaria
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
} catch { /* non-blocking */ }
|
|
687
|
+
}
|
|
579
688
|
} catch (err) {
|
|
580
689
|
logger.warn(`Solomon rules evaluation failed: ${err.message}`);
|
|
581
690
|
}
|
|
582
691
|
}
|
|
583
692
|
|
|
693
|
+
// --- BecarIA Gateway: dispatch review result ---
|
|
694
|
+
if (becariaEnabled && session.becaria_pr_number) {
|
|
695
|
+
try {
|
|
696
|
+
const { dispatchReview, dispatchComment } = await import("./becaria/dispatch.js");
|
|
697
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
698
|
+
const repo = await detectRepo();
|
|
699
|
+
if (repo) {
|
|
700
|
+
const bc = config.becaria;
|
|
701
|
+
// Formal review (APPROVE / REQUEST_CHANGES)
|
|
702
|
+
if (review.approved) {
|
|
703
|
+
await dispatchReview({
|
|
704
|
+
repo, prNumber: session.becaria_pr_number,
|
|
705
|
+
event: "APPROVE", body: review.summary || "Approved", agent: "Reviewer", becariaConfig: bc
|
|
706
|
+
});
|
|
707
|
+
} else {
|
|
708
|
+
const blocking = review.blocking_issues?.map((x) => `- ${x.id || "ISSUE"} [${x.severity || ""}] ${x.description}`).join("\n") || "";
|
|
709
|
+
await dispatchReview({
|
|
710
|
+
repo, prNumber: session.becaria_pr_number,
|
|
711
|
+
event: "REQUEST_CHANGES",
|
|
712
|
+
body: blocking || review.summary || "Changes requested",
|
|
713
|
+
agent: "Reviewer", becariaConfig: bc
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Detailed comment
|
|
718
|
+
const status = review.approved ? "APPROVED" : "REQUEST_CHANGES";
|
|
719
|
+
const blocking = review.blocking_issues?.map((x) => `- ${x.id || "ISSUE"} [${x.severity || ""}] ${x.description}`).join("\n") || "";
|
|
720
|
+
const suggestions = review.non_blocking_suggestions?.map((s) => `- ${typeof s === "string" ? s : `${s.id || ""} ${s.description || s}`}`).join("\n") || "";
|
|
721
|
+
let reviewBody = `Review iteración ${i}: ${status}`;
|
|
722
|
+
if (blocking) reviewBody += `\n\n**Blocking:**\n${blocking}`;
|
|
723
|
+
if (suggestions) reviewBody += `\n\n**Suggestions:**\n${suggestions}`;
|
|
724
|
+
await dispatchComment({
|
|
725
|
+
repo, prNumber: session.becaria_pr_number, agent: "Reviewer",
|
|
726
|
+
body: reviewBody, becariaConfig: bc
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
logger.info(`BecarIA: dispatched review for PR #${session.becaria_pr_number}`);
|
|
730
|
+
}
|
|
731
|
+
} catch (err) {
|
|
732
|
+
logger.warn(`BecarIA dispatch failed (non-blocking): ${err.message}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
584
736
|
if (review.approved) {
|
|
585
737
|
session.reviewer_retry_count = 0;
|
|
586
738
|
|
|
@@ -600,6 +752,22 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
600
752
|
}
|
|
601
753
|
if (testerResult.stageResult) {
|
|
602
754
|
stageResults.tester = testerResult.stageResult;
|
|
755
|
+
// BecarIA: dispatch tester comment
|
|
756
|
+
if (becariaEnabled && session.becaria_pr_number) {
|
|
757
|
+
try {
|
|
758
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
759
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
760
|
+
const repo = await detectRepo();
|
|
761
|
+
if (repo) {
|
|
762
|
+
const t = testerResult.stageResult;
|
|
763
|
+
await dispatchComment({
|
|
764
|
+
repo, prNumber: session.becaria_pr_number, agent: "Tester",
|
|
765
|
+
body: `Tests: ${t.summary || "completed"}`,
|
|
766
|
+
becariaConfig: config.becaria
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
} catch { /* non-blocking */ }
|
|
770
|
+
}
|
|
603
771
|
}
|
|
604
772
|
}
|
|
605
773
|
|
|
@@ -616,6 +784,22 @@ export async function runFlow({ task, config, logger, flags = {}, emitter = null
|
|
|
616
784
|
}
|
|
617
785
|
if (securityResult.stageResult) {
|
|
618
786
|
stageResults.security = securityResult.stageResult;
|
|
787
|
+
// BecarIA: dispatch security comment
|
|
788
|
+
if (becariaEnabled && session.becaria_pr_number) {
|
|
789
|
+
try {
|
|
790
|
+
const { dispatchComment } = await import("./becaria/dispatch.js");
|
|
791
|
+
const { detectRepo } = await import("./becaria/repo.js");
|
|
792
|
+
const repo = await detectRepo();
|
|
793
|
+
if (repo) {
|
|
794
|
+
const s = securityResult.stageResult;
|
|
795
|
+
await dispatchComment({
|
|
796
|
+
repo, prNumber: session.becaria_pr_number, agent: "Security",
|
|
797
|
+
body: `Security scan: ${s.summary || "completed"}`,
|
|
798
|
+
becariaConfig: config.becaria
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
} catch { /* non-blocking */ }
|
|
802
|
+
}
|
|
619
803
|
}
|
|
620
804
|
}
|
|
621
805
|
|
package/templates/roles/coder.md
CHANGED
|
@@ -7,9 +7,20 @@ You are the **Coder** in a multi-role AI pipeline. Your job is to write code and
|
|
|
7
7
|
- Follow TDD methodology when `methodology=tdd` is configured.
|
|
8
8
|
- Write tests BEFORE implementation when using TDD.
|
|
9
9
|
- Keep changes minimal and focused on the task.
|
|
10
|
+
- "Minimal" means no unnecessary changes — it does NOT mean avoiding new files. If the task requires creating new files (pages, components, modules, tests), you MUST create them. Updating references/links without creating the actual files is an incomplete implementation.
|
|
10
11
|
- Do not modify code unrelated to the task.
|
|
12
|
+
- Before creating a new utility or helper, check if a similar one already exists in the codebase. Reuse existing code over creating duplicates.
|
|
11
13
|
- Follow existing code conventions and patterns in the repository.
|
|
12
14
|
|
|
15
|
+
## Task completeness
|
|
16
|
+
|
|
17
|
+
Before reporting done, verify that ALL parts of the task are addressed:
|
|
18
|
+
- Re-read the task description and acceptance criteria.
|
|
19
|
+
- Check every requirement — if the task says "create pages X and Y", both must exist.
|
|
20
|
+
- If the task lists multiple deliverables, each one must be implemented, not just some.
|
|
21
|
+
- Run the test suite after implementation to verify nothing is broken.
|
|
22
|
+
- An incomplete implementation is worse than an error — never report success if parts are missing.
|
|
23
|
+
|
|
13
24
|
## File modification safety
|
|
14
25
|
|
|
15
26
|
- NEVER overwrite existing files entirely. Always make targeted, minimal edits.
|
|
@@ -18,13 +29,6 @@ You are the **Coder** in a multi-role AI pipeline. Your job is to write code and
|
|
|
18
29
|
- If unintended changes are detected, revert immediately with `git checkout -- <file>`.
|
|
19
30
|
- Pay special attention to CSS, HTML, and config files where full rewrites destroy prior work.
|
|
20
31
|
|
|
21
|
-
## Multi-agent environment
|
|
22
|
-
|
|
23
|
-
- Multiple developers and AI agents may be committing and modifying code simultaneously.
|
|
24
|
-
- ALWAYS run `git fetch origin main` and check recent commits before starting work.
|
|
25
|
-
- Before pushing or merging, rebase on the latest main: `git rebase origin/main`.
|
|
26
|
-
- Create a dedicated branch per task and merge via PR, never push directly to main.
|
|
27
|
-
|
|
28
32
|
## Output format
|
|
29
33
|
|
|
30
34
|
Return a JSON object:
|
|
@@ -20,6 +20,8 @@ You are the **Planner** in a multi-role AI pipeline. Your job is to create an im
|
|
|
20
20
|
## Rules
|
|
21
21
|
|
|
22
22
|
- Each step should be small and independently verifiable.
|
|
23
|
+
- Steps must list ALL files involved: both files to modify AND new files to create. If a step requires creating a new file, list it explicitly in the `files` array.
|
|
24
|
+
- The plan must cover ALL requirements from the task. Re-read the task description before finalizing — if something is mentioned in the task, it must appear in a step.
|
|
23
25
|
- Identify the testing strategy (unit, integration, E2E).
|
|
24
26
|
- Consider backward compatibility.
|
|
25
27
|
- Reference research findings when available.
|
|
@@ -5,7 +5,7 @@ You are the **Refactorer** in a multi-role AI pipeline. Your job is to improve c
|
|
|
5
5
|
## Constraints
|
|
6
6
|
|
|
7
7
|
- Do NOT change any observable behavior or API contracts.
|
|
8
|
-
-
|
|
8
|
+
- Focus on the files that were already modified in this session. You may create new files when extracting code (e.g., extracting a helper to a new module), but do not refactor unrelated parts of the codebase.
|
|
9
9
|
- Keep all existing tests passing — run tests after every change.
|
|
10
10
|
- Follow existing code conventions and patterns in the repository.
|
|
11
11
|
- Do NOT add new features or fix unrelated bugs.
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
You are the **Reviewer** in a multi-role AI pipeline. Your job is to review code changes against task requirements and quality standards.
|
|
4
4
|
|
|
5
|
+
## Scope constraint
|
|
6
|
+
|
|
7
|
+
- **ONLY review files present in the diff.** Do not flag issues in files that were not changed.
|
|
8
|
+
- If you notice problems in untouched files, mention them as `non_blocking_suggestions` with a note that they are outside the current scope — never as `blocking_issues`.
|
|
9
|
+
- Your job is to review THIS change, not audit the entire codebase.
|
|
10
|
+
|
|
5
11
|
## Review priorities (in order)
|
|
6
12
|
|
|
7
13
|
1. **Security** — vulnerabilities, exposed secrets, injection vectors
|
|
@@ -13,9 +19,10 @@ You are the **Reviewer** in a multi-role AI pipeline. Your job is to review code
|
|
|
13
19
|
## Rules
|
|
14
20
|
|
|
15
21
|
- Focus on security, correctness, and tests first.
|
|
16
|
-
- Only raise blocking issues for concrete production risks.
|
|
22
|
+
- Only raise blocking issues for concrete production risks in the changed files.
|
|
17
23
|
- Keep non-blocking suggestions separate.
|
|
18
24
|
- Style preferences NEVER block approval.
|
|
25
|
+
- Confidence threshold: reject only if < 0.70.
|
|
19
26
|
|
|
20
27
|
## File overwrite detection (BLOCKING)
|
|
21
28
|
|
|
@@ -31,7 +38,7 @@ Return a strict JSON object:
|
|
|
31
38
|
"result": {
|
|
32
39
|
"approved": true,
|
|
33
40
|
"blocking_issues": [],
|
|
34
|
-
"
|
|
41
|
+
"non_blocking_suggestions": ["Optional improvement ideas"],
|
|
35
42
|
"confidence": 0.95
|
|
36
43
|
},
|
|
37
44
|
"summary": "Approved: all changes look correct and well-tested"
|
|
@@ -45,9 +52,9 @@ When rejecting:
|
|
|
45
52
|
"result": {
|
|
46
53
|
"approved": false,
|
|
47
54
|
"blocking_issues": [
|
|
48
|
-
{ "file": "src/foo.js", "line": 42, "severity": "critical", "
|
|
55
|
+
{ "id": "R-1", "file": "src/foo.js", "line": 42, "severity": "critical", "description": "SQL injection vulnerability", "suggested_fix": "Use parameterized queries instead of string concatenation" }
|
|
49
56
|
],
|
|
50
|
-
"
|
|
57
|
+
"non_blocking_suggestions": [],
|
|
51
58
|
"confidence": 0.9
|
|
52
59
|
},
|
|
53
60
|
"summary": "Rejected: 1 critical security issue found"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Auto-merge PRs approved by BecarIA reviewer.
|
|
2
|
+
# Merges when becaria-reviewer[bot] approves and all checks pass.
|
|
3
|
+
|
|
4
|
+
name: Auto-merge (BecarIA)
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
pull_request_review:
|
|
8
|
+
types: [submitted]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
pull-requests: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
automerge:
|
|
16
|
+
if: >
|
|
17
|
+
github.event.review.state == 'approved' &&
|
|
18
|
+
contains(github.event.review.user.login, 'becaria-reviewer')
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- name: Enable auto-merge
|
|
22
|
+
uses: actions/github-script@v7
|
|
23
|
+
with:
|
|
24
|
+
script: |
|
|
25
|
+
await github.rest.pulls.merge({
|
|
26
|
+
owner: context.repo.owner,
|
|
27
|
+
repo: context.repo.repo,
|
|
28
|
+
pull_number: context.payload.pull_request.number,
|
|
29
|
+
merge_method: 'squash'
|
|
30
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# BecarIA Gateway — publishes PR comments and reviews from repository_dispatch events.
|
|
2
|
+
# Triggered by Karajan Code pipeline via `gh api repos/{owner}/{repo}/dispatches`.
|
|
3
|
+
#
|
|
4
|
+
# Required secrets:
|
|
5
|
+
# BECARIA_APP_ID — GitHub App ID for becaria-reviewer
|
|
6
|
+
# BECARIA_APP_PRIVATE_KEY — PEM private key for the GitHub App
|
|
7
|
+
|
|
8
|
+
name: BecarIA Gateway
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
repository_dispatch:
|
|
12
|
+
types:
|
|
13
|
+
- becaria-comment
|
|
14
|
+
- becaria-review
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
pull-requests: write
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
gateway:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- name: Generate App Token
|
|
25
|
+
id: app-token
|
|
26
|
+
uses: actions/create-github-app-token@v1
|
|
27
|
+
with:
|
|
28
|
+
app-id: ${{ secrets.BECARIA_APP_ID }}
|
|
29
|
+
private-key: ${{ secrets.BECARIA_APP_PRIVATE_KEY }}
|
|
30
|
+
|
|
31
|
+
- name: Post Comment
|
|
32
|
+
if: github.event.action == 'becaria-comment'
|
|
33
|
+
uses: actions/github-script@v7
|
|
34
|
+
with:
|
|
35
|
+
github-token: ${{ steps.app-token.outputs.token }}
|
|
36
|
+
script: |
|
|
37
|
+
const { pr_number, agent, body } = context.payload.client_payload;
|
|
38
|
+
await github.rest.issues.createComment({
|
|
39
|
+
owner: context.repo.owner,
|
|
40
|
+
repo: context.repo.repo,
|
|
41
|
+
issue_number: pr_number,
|
|
42
|
+
body: `**${agent}** (via BecarIA)\n\n${body}`
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
- name: Submit Review
|
|
46
|
+
if: github.event.action == 'becaria-review'
|
|
47
|
+
uses: actions/github-script@v7
|
|
48
|
+
with:
|
|
49
|
+
github-token: ${{ steps.app-token.outputs.token }}
|
|
50
|
+
script: |
|
|
51
|
+
const { pr_number, event, body, agent } = context.payload.client_payload;
|
|
52
|
+
await github.rest.pulls.createReview({
|
|
53
|
+
owner: context.repo.owner,
|
|
54
|
+
repo: context.repo.repo,
|
|
55
|
+
pull_number: pr_number,
|
|
56
|
+
event: event,
|
|
57
|
+
body: `**${agent}** (via BecarIA)\n\n${body}`
|
|
58
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Houston Override — allows a human to force-approve a PR by commenting "/houston approve".
|
|
2
|
+
# Bypasses BecarIA's REQUEST_CHANGES review.
|
|
3
|
+
|
|
4
|
+
name: Houston Override
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
issue_comment:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
pull-requests: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
override:
|
|
15
|
+
if: >
|
|
16
|
+
github.event.issue.pull_request &&
|
|
17
|
+
contains(github.event.comment.body, '/houston approve')
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: Dismiss BecarIA review and approve
|
|
21
|
+
uses: actions/github-script@v7
|
|
22
|
+
with:
|
|
23
|
+
script: |
|
|
24
|
+
const { owner, repo } = context.repo;
|
|
25
|
+
const pr_number = context.payload.issue.number;
|
|
26
|
+
|
|
27
|
+
// Find and dismiss becaria-reviewer reviews
|
|
28
|
+
const reviews = await github.rest.pulls.listReviews({
|
|
29
|
+
owner, repo, pull_number: pr_number
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
for (const review of reviews.data) {
|
|
33
|
+
if (review.user.login.includes('becaria-reviewer') && review.state === 'CHANGES_REQUESTED') {
|
|
34
|
+
await github.rest.pulls.dismissReview({
|
|
35
|
+
owner, repo, pull_number: pr_number, review_id: review.id,
|
|
36
|
+
message: `Dismissed by Houston Override (${context.payload.comment.user.login})`
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Approve the PR
|
|
42
|
+
await github.rest.pulls.createReview({
|
|
43
|
+
owner, repo, pull_number: pr_number,
|
|
44
|
+
event: 'APPROVE',
|
|
45
|
+
body: `Houston Override by @${context.payload.comment.user.login}`
|
|
46
|
+
});
|