karajan-code 1.59.1 → 2.0.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 +2 -0
- package/package.json +1 -1
- package/scripts/install.js +18 -15
- package/src/agents/aider-agent.js +0 -7
- package/src/agents/claude-agent.js +1 -4
- package/src/agents/codex-agent.js +2 -6
- package/src/agents/gemini-agent.js +0 -4
- package/src/agents/opencode-agent.js +0 -4
- package/src/{becaria → ci}/dispatch.js +13 -13
- package/src/{becaria → ci}/pr-diff.js +1 -1
- package/src/cli.js +2 -2
- package/src/commands/agents.js +0 -1
- package/src/commands/board.js +8 -1
- package/src/commands/doctor.js +22 -40
- package/src/commands/init.js +8 -8
- package/src/commands/report.js +0 -13
- package/src/commands/review.js +17 -17
- package/src/commands/run.js +1 -2
- package/src/commands/status.js +1 -7
- package/src/config.js +17 -52
- package/src/git/automation.js +9 -9
- package/src/guards/output-guard.js +0 -17
- package/src/mcp/handlers/direct-handlers.js +1 -1
- package/src/mcp/handlers/run-handler.js +1 -2
- package/src/mcp/sovereignty-guard.js +1 -1
- package/src/mcp/tools.js +1 -1
- package/src/orchestrator/brain-coordinator.js +165 -0
- package/src/orchestrator/ci-integration.js +139 -0
- package/src/orchestrator/config-init.js +435 -0
- package/src/orchestrator/direct-actions.js +186 -0
- package/src/orchestrator/feedback-enrichment.js +140 -0
- package/src/orchestrator/feedback-queue.js +152 -0
- package/src/orchestrator/flow-control.js +159 -0
- package/src/orchestrator/pipeline-context.js +3 -0
- package/src/orchestrator/post-loop-stages.js +20 -15
- package/src/orchestrator/preflight-checks.js +7 -3
- package/src/orchestrator/role-output-compressor.js +190 -0
- package/src/orchestrator/session-journal.js +344 -0
- package/src/orchestrator/solomon-escalation.js +38 -5
- package/src/orchestrator/solomon-rules.js +32 -33
- package/src/orchestrator/stages/coder-stage.js +49 -4
- package/src/orchestrator/stages/reviewer-stage.js +52 -9
- package/src/orchestrator/stages/sonar-stage.js +24 -1
- package/src/orchestrator/verification-gate.js +146 -0
- package/src/orchestrator.js +481 -672
- package/src/planning-game/client.js +2 -2
- package/src/planning-game/pipeline-adapter.js +1 -1
- package/src/prompts/coder.js +17 -3
- package/src/prompts/reviewer.js +2 -2
- package/src/prompts/rtk-snippet.js +3 -5
- package/src/review/diff-generator.js +66 -17
- package/src/review/snapshot-diff.js +122 -0
- package/src/review/tdd-policy.js +4 -2
- package/src/roles/agent-role.js +135 -0
- package/src/roles/architect-role.js +61 -108
- package/src/roles/audit-role.js +25 -71
- package/src/roles/coder-role.js +32 -57
- package/src/roles/discover-role.js +30 -71
- package/src/roles/hu-reviewer-role.js +51 -99
- package/src/roles/impeccable-role.js +33 -88
- package/src/roles/index.js +2 -0
- package/src/roles/karajan-brain-role.js +134 -0
- package/src/roles/planner-role.js +40 -75
- package/src/roles/refactorer-role.js +22 -64
- package/src/roles/researcher-role.js +51 -115
- package/src/roles/reviewer-role.js +68 -125
- package/src/roles/security-role.js +38 -102
- package/src/roles/solomon-role.js +69 -11
- package/src/roles/tester-role.js +121 -95
- package/src/roles/triage-role.js +50 -111
- package/src/session-store.js +1 -1
- package/src/skills/skill-detector.js +11 -0
- package/src/sonar/config-resolver.js +20 -1
- package/src/sonar/credentials.js +17 -0
- package/src/sonar/project-key.js +1 -1
- package/src/sonar/scanner.js +1 -1
- package/src/utils/display.js +33 -14
- package/src/utils/injection-guard.js +23 -8
- package/src/utils/logger.js +6 -1
- package/src/utils/process.js +3 -4
- package/src/utils/role-assigner.js +131 -0
- package/src/utils/update-check.js +5 -1
- package/src/webperf/devtools-detect.js +70 -23
- package/templates/roles/coder.md +7 -0
- package/templates/roles/karajan-brain.md +95 -0
- package/templates/roles/solomon.md +67 -106
- package/templates/workflows/automerge.yml +4 -4
- package/templates/workflows/houston-override.yml +4 -4
- package/templates/workflows/{becaria-gateway.yml → kj-ci-gateway.yml} +12 -12
- package/src/proxy/adapters/anthropic.js +0 -131
- package/src/proxy/adapters/gemini.js +0 -106
- package/src/proxy/adapters/index.js +0 -51
- package/src/proxy/adapters/openai.js +0 -71
- package/src/proxy/compression/ai-compressor.js +0 -209
- package/src/proxy/compression/dedup-cache.js +0 -100
- package/src/proxy/compression/deterministic/bash-build.js +0 -60
- package/src/proxy/compression/deterministic/bash-git.js +0 -101
- package/src/proxy/compression/deterministic/bash-infra.js +0 -64
- package/src/proxy/compression/deterministic/bash-misc.js +0 -53
- package/src/proxy/compression/deterministic/bash-pkg.js +0 -49
- package/src/proxy/compression/deterministic/bash-test.js +0 -61
- package/src/proxy/compression/deterministic/glob.js +0 -26
- package/src/proxy/compression/deterministic/grep.js +0 -53
- package/src/proxy/compression/deterministic/index.js +0 -58
- package/src/proxy/compression/deterministic/read.js +0 -22
- package/src/proxy/compression/deterministic/utils.js +0 -33
- package/src/proxy/compression/pipeline.js +0 -270
- package/src/proxy/middleware/prompt-injector.js +0 -165
- package/src/proxy/middleware/response-interceptor.js +0 -223
- package/src/proxy/progress-monitor.js +0 -98
- package/src/proxy/proxy-lifecycle.js +0 -201
- package/src/proxy/proxy-server.js +0 -239
- package/src/proxy/security.js +0 -45
- /package/src/{becaria → ci}/index.js +0 -0
- /package/src/{becaria → ci}/repo.js +0 -0
package/src/config.js
CHANGED
|
@@ -34,8 +34,15 @@ const DEFAULTS = {
|
|
|
34
34
|
discover: { enabled: false },
|
|
35
35
|
architect: { enabled: false },
|
|
36
36
|
hu_reviewer: { enabled: false },
|
|
37
|
+
brain: { enabled: true },
|
|
37
38
|
auto_simplify: true
|
|
38
39
|
},
|
|
40
|
+
brain: {
|
|
41
|
+
enabled: true,
|
|
42
|
+
provider: "claude",
|
|
43
|
+
bypass_solomon_on_correctness: true,
|
|
44
|
+
max_consecutive_verification_failures: 2
|
|
45
|
+
},
|
|
39
46
|
review_mode: "standard",
|
|
40
47
|
max_iterations: 5,
|
|
41
48
|
max_budget_usd: null,
|
|
@@ -49,7 +56,7 @@ const DEFAULTS = {
|
|
|
49
56
|
model: null,
|
|
50
57
|
deterministic: true,
|
|
51
58
|
retries: 1,
|
|
52
|
-
fallback_reviewer:
|
|
59
|
+
fallback_reviewer: null
|
|
53
60
|
},
|
|
54
61
|
development: {
|
|
55
62
|
methodology: "tdd",
|
|
@@ -128,7 +135,7 @@ const DEFAULTS = {
|
|
|
128
135
|
policies: {},
|
|
129
136
|
serena: { enabled: false },
|
|
130
137
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
131
|
-
|
|
138
|
+
ci: { enabled: false, review_event: "kj-review", comment_event: "kj-comment", comment_prefix: true },
|
|
132
139
|
git: { auto_commit: false, auto_push: false, auto_pr: false, auto_rebase: true, branch_prefix: "feat/" },
|
|
133
140
|
output: { report_dir: "./.reviews", log_level: "info", quiet: true },
|
|
134
141
|
budget: {
|
|
@@ -159,50 +166,12 @@ const DEFAULTS = {
|
|
|
159
166
|
failFast: {
|
|
160
167
|
repeatThreshold: 2
|
|
161
168
|
},
|
|
162
|
-
retry: {
|
|
163
|
-
max_attempts: 3,
|
|
164
|
-
initial_backoff_ms: 1000,
|
|
165
|
-
max_backoff_ms: 30000,
|
|
166
|
-
backoff_multiplier: 2,
|
|
167
|
-
jitter_factor: 0.1
|
|
168
|
-
},
|
|
169
169
|
webperf: {
|
|
170
170
|
enabled: true,
|
|
171
171
|
devtools_mcp: false,
|
|
172
172
|
thresholds: { lcp: 2500, cls: 0.1, inp: 200 }
|
|
173
173
|
},
|
|
174
174
|
telemetry: true,
|
|
175
|
-
proxy: {
|
|
176
|
-
enabled: true,
|
|
177
|
-
port: "auto",
|
|
178
|
-
compression: {
|
|
179
|
-
enabled: true,
|
|
180
|
-
ai_compression: false,
|
|
181
|
-
ai_model: "haiku",
|
|
182
|
-
ai_provider: "anthropic",
|
|
183
|
-
layers: {
|
|
184
|
-
git: true,
|
|
185
|
-
tests: true,
|
|
186
|
-
build: true,
|
|
187
|
-
infra: true,
|
|
188
|
-
packages: true,
|
|
189
|
-
read_dedup: true,
|
|
190
|
-
glob_truncate: true,
|
|
191
|
-
grep_collapse: true,
|
|
192
|
-
},
|
|
193
|
-
pressure_thresholds: {
|
|
194
|
-
low: 0.5,
|
|
195
|
-
medium: 0.8,
|
|
196
|
-
high: 0.9,
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
cache: {
|
|
200
|
-
persist_to_disk: true,
|
|
201
|
-
flush_interval_ms: 5000,
|
|
202
|
-
},
|
|
203
|
-
inject_prompts: true,
|
|
204
|
-
monitor: true,
|
|
205
|
-
},
|
|
206
175
|
guards: {
|
|
207
176
|
output: {
|
|
208
177
|
enabled: true,
|
|
@@ -398,12 +367,12 @@ function applyMethodologyOverride(out, flags) {
|
|
|
398
367
|
out.development.require_test_changes = methodology === "tdd";
|
|
399
368
|
}
|
|
400
369
|
|
|
401
|
-
function
|
|
402
|
-
out.
|
|
403
|
-
if (flags.
|
|
404
|
-
out.
|
|
405
|
-
//
|
|
406
|
-
if (out.
|
|
370
|
+
function applyCiOverride(out, flags) {
|
|
371
|
+
out.ci = out.ci || { enabled: false };
|
|
372
|
+
if (flags.enableCi === undefined) return;
|
|
373
|
+
out.ci.enabled = Boolean(flags.enableCi);
|
|
374
|
+
// CI requires git automation (commit + push + PR)
|
|
375
|
+
if (out.ci.enabled) {
|
|
407
376
|
out.git.auto_commit = true;
|
|
408
377
|
out.git.auto_push = true;
|
|
409
378
|
out.git.auto_pr = true;
|
|
@@ -432,10 +401,6 @@ function applyMiscOverrides(out, flags) {
|
|
|
432
401
|
if (flags.pgTask) out.planning_game.enabled = true;
|
|
433
402
|
if (flags.pgProject) out.planning_game.project_id = flags.pgProject;
|
|
434
403
|
|
|
435
|
-
out.proxy = out.proxy || {};
|
|
436
|
-
if (flags.noProxy) out.proxy.enabled = false;
|
|
437
|
-
if (flags.proxyPort) out.proxy.port = flags.proxyPort;
|
|
438
|
-
|
|
439
404
|
out.model_selection = out.model_selection || { enabled: true, tiers: {}, role_overrides: {} };
|
|
440
405
|
if (flags.smartModels === true) out.model_selection.enabled = true;
|
|
441
406
|
if (flags.smartModels === false || flags.noSmartModels === true) out.model_selection.enabled = false;
|
|
@@ -461,7 +426,7 @@ export function applyRunOverrides(config, flags) {
|
|
|
461
426
|
applyPipelineOverrides(out, flags);
|
|
462
427
|
applyScalarAndBooleanOverrides(out, flags);
|
|
463
428
|
applyMethodologyOverride(out, flags);
|
|
464
|
-
|
|
429
|
+
applyCiOverride(out, flags);
|
|
465
430
|
applyMiscOverrides(out, flags);
|
|
466
431
|
applyOutputModeOverrides(out, flags);
|
|
467
432
|
|
|
@@ -479,7 +444,7 @@ const AGENT_MODEL_SIGNATURES = {
|
|
|
479
444
|
gemini: ["gemini", "flash-"]
|
|
480
445
|
};
|
|
481
446
|
|
|
482
|
-
|
|
447
|
+
function isModelCompatible(agent, model) {
|
|
483
448
|
if (!model || !agent) return true;
|
|
484
449
|
const lower = model.toLowerCase();
|
|
485
450
|
|
package/src/git/automation.js
CHANGED
|
@@ -110,7 +110,7 @@ export function buildPrBody({ task, stageResults }) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Create an early PR after the first coder iteration (
|
|
113
|
+
* Create an early PR after the first coder iteration (Karajan CI flow).
|
|
114
114
|
* Commits, pushes, and creates a PR before the reviewer runs.
|
|
115
115
|
* Returns { prNumber, prUrl, commits } or null if nothing to commit.
|
|
116
116
|
*/
|
|
@@ -125,10 +125,10 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
128
|
-
await addCheckpoint(session, { stage: "
|
|
128
|
+
await addCheckpoint(session, { stage: "ci-commit", committed: true });
|
|
129
129
|
|
|
130
130
|
await pushBranch(gitCtx.branch);
|
|
131
|
-
await addCheckpoint(session, { stage: "
|
|
131
|
+
await addCheckpoint(session, { stage: "ci-push", branch: gitCtx.branch });
|
|
132
132
|
logger.info(`Pushed branch for early PR: ${gitCtx.branch}`);
|
|
133
133
|
|
|
134
134
|
const body = buildPrBody({ task, stageResults });
|
|
@@ -138,7 +138,7 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
138
138
|
title: commitMessageFromTask(task),
|
|
139
139
|
body
|
|
140
140
|
});
|
|
141
|
-
await addCheckpoint(session, { stage: "
|
|
141
|
+
await addCheckpoint(session, { stage: "ci-pr", branch: gitCtx.branch, pr: prUrl });
|
|
142
142
|
logger.info(`Early PR created: ${prUrl}`);
|
|
143
143
|
|
|
144
144
|
// Extract PR number from URL (e.g. https://github.com/owner/repo/pull/42)
|
|
@@ -147,7 +147,7 @@ export async function earlyPrCreation({ gitCtx, task, logger, session, stageResu
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
|
-
* Incremental push after each coder iteration (
|
|
150
|
+
* Incremental push after each coder iteration (Karajan CI flow).
|
|
151
151
|
* Commits and pushes without creating a new PR.
|
|
152
152
|
*/
|
|
153
153
|
export async function incrementalPush({ gitCtx, task, logger, session }) {
|
|
@@ -161,10 +161,10 @@ export async function incrementalPush({ gitCtx, task, logger, session }) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
const commits = commitResult.commit ? [commitResult.commit] : [];
|
|
164
|
-
await addCheckpoint(session, { stage: "
|
|
164
|
+
await addCheckpoint(session, { stage: "ci-incremental-commit", committed: true });
|
|
165
165
|
|
|
166
166
|
await pushBranch(gitCtx.branch);
|
|
167
|
-
await addCheckpoint(session, { stage: "
|
|
167
|
+
await addCheckpoint(session, { stage: "ci-incremental-push", branch: gitCtx.branch });
|
|
168
168
|
logger.info(`Incremental push: ${gitCtx.branch}`);
|
|
169
169
|
|
|
170
170
|
return { commits };
|
|
@@ -202,7 +202,7 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
202
202
|
logger.info(`Pushed branch: ${gitCtx.branch}`);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
let prUrl = session.
|
|
205
|
+
let prUrl = session.ci_pr_url || null;
|
|
206
206
|
if (config.git.auto_pr && !prUrl) {
|
|
207
207
|
const body = buildPrBody({ task, stageResults });
|
|
208
208
|
prUrl = await createPullRequest({
|
|
@@ -214,7 +214,7 @@ export async function finalizeGitAutomation({ config, gitCtx, task, logger, sess
|
|
|
214
214
|
await addCheckpoint(session, { stage: "git-pr", branch: gitCtx.branch, pr: prUrl });
|
|
215
215
|
logger.info("Pull request created");
|
|
216
216
|
} else if (prUrl) {
|
|
217
|
-
logger.info(`PR already exists (
|
|
217
|
+
logger.info(`PR already exists (CI flow): ${prUrl}`);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
return { committed, branch: gitCtx.branch, prUrl, pr: prUrl, commits };
|
|
@@ -148,21 +148,4 @@ export function scanDiff(diff, config = {}) {
|
|
|
148
148
|
return { pass: !hasCritical, violations };
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/**
|
|
152
|
-
* Run output guard on the current git diff.
|
|
153
|
-
* This is the main entry point for the pipeline integration.
|
|
154
|
-
*/
|
|
155
|
-
export async function runOutputGuard(config = {}, baseBranch = "main") {
|
|
156
|
-
const diffResult = await runCommand("git", ["diff", `origin/${baseBranch}...HEAD`]);
|
|
157
|
-
if (diffResult.exitCode !== 0) {
|
|
158
|
-
// Fallback: diff against HEAD~1
|
|
159
|
-
const fallback = await runCommand("git", ["diff", "HEAD~1"]);
|
|
160
|
-
if (fallback.exitCode !== 0) {
|
|
161
|
-
return { pass: true, violations: [], error: "Could not generate diff" };
|
|
162
|
-
}
|
|
163
|
-
return scanDiff(fallback.stdout, config);
|
|
164
|
-
}
|
|
165
|
-
return scanDiff(diffResult.stdout, config);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
151
|
export { DESTRUCTIVE_PATTERNS, CREDENTIAL_PATTERNS, DEFAULT_PROTECTED_FILES };
|
|
@@ -258,7 +258,7 @@ export async function handleReviewDirect(a, server, extra) {
|
|
|
258
258
|
const logger = createLogger(config.output.log_level, "mcp");
|
|
259
259
|
|
|
260
260
|
const reviewerRole = resolveRole(config, "reviewer");
|
|
261
|
-
await assertAgentsAvailable([reviewerRole.provider
|
|
261
|
+
await assertAgentsAvailable([reviewerRole.provider]);
|
|
262
262
|
|
|
263
263
|
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
264
264
|
const runLog = createRunLog(projectDir);
|
|
@@ -98,8 +98,7 @@ const PIPELINE_PROVIDER_ROLES = [
|
|
|
98
98
|
|
|
99
99
|
function collectRequiredProviders(config) {
|
|
100
100
|
const providers = [
|
|
101
|
-
resolveRole(config, "coder").provider
|
|
102
|
-
config.reviewer_options?.fallback_reviewer
|
|
101
|
+
resolveRole(config, "coder").provider
|
|
103
102
|
];
|
|
104
103
|
if (config.pipeline?.reviewer?.enabled !== false) {
|
|
105
104
|
providers.push(resolveRole(config, "reviewer").provider);
|
|
@@ -17,7 +17,7 @@ const ALLOWED_PARAMS = new Set([
|
|
|
17
17
|
"enableTester", "enableSecurity", "enableImpeccable",
|
|
18
18
|
"enableDiscover", "enableArchitect",
|
|
19
19
|
"architectModel", "huFile",
|
|
20
|
-
"enableSerena", "
|
|
20
|
+
"enableSerena", "enableCi",
|
|
21
21
|
"reviewerFallback", "reviewerRetries",
|
|
22
22
|
"mode", "maxIterations", "maxIterationMinutes", "maxTotalMinutes",
|
|
23
23
|
"baseBranch", "baseRef", "methodology",
|
package/src/mcp/tools.js
CHANGED
|
@@ -79,7 +79,7 @@ export const tools = [
|
|
|
79
79
|
architectModel: { type: "string" },
|
|
80
80
|
huFile: { type: "string", description: "Path to YAML file with HU stories to certify before coding" },
|
|
81
81
|
enableSerena: { type: "boolean" },
|
|
82
|
-
|
|
82
|
+
enableCi: { type: "boolean", description: "Enable Karajan CI (early PR + dispatch comments/reviews)" },
|
|
83
83
|
reviewerFallback: { type: "string" },
|
|
84
84
|
reviewerRetries: { type: "number" },
|
|
85
85
|
mode: { type: "string" },
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Brain Coordinator: integrates Karajan Brain + 5 support modules into the pipeline.
|
|
2
|
+
// Replaces ad-hoc role-to-role communication with a central intelligence.
|
|
3
|
+
|
|
4
|
+
import * as queue from "./feedback-queue.js";
|
|
5
|
+
import * as enrich from "./feedback-enrichment.js";
|
|
6
|
+
import { verifyCoderOutput, VerificationTracker } from "./verification-gate.js";
|
|
7
|
+
import { executeActions } from "./direct-actions.js";
|
|
8
|
+
import { compressRoleOutput, measureCompression } from "./role-output-compressor.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} BrainContext
|
|
12
|
+
* @property {Object} feedbackQueue - structured feedback queue
|
|
13
|
+
* @property {VerificationTracker} verificationTracker - tracks coder changes
|
|
14
|
+
* @property {Object} compressionStats - token savings per role
|
|
15
|
+
* @property {boolean} enabled - whether brain coordination is active
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a new brain context for a pipeline session.
|
|
20
|
+
*/
|
|
21
|
+
export function createBrainContext({ enabled = false } = {}) {
|
|
22
|
+
return {
|
|
23
|
+
feedbackQueue: queue.createQueue(),
|
|
24
|
+
verificationTracker: new VerificationTracker(),
|
|
25
|
+
compressionStats: { totalSaved: 0, perRole: {} },
|
|
26
|
+
enabled
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if brain coordination is enabled for this config.
|
|
32
|
+
*/
|
|
33
|
+
export function isBrainEnabled(config) {
|
|
34
|
+
return Boolean(config?.brain?.enabled) || Boolean(config?.pipeline?.brain?.enabled);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* After a role completes, push its feedback to the queue and compress.
|
|
39
|
+
* Returns the compressed output for passing to next role.
|
|
40
|
+
*/
|
|
41
|
+
export function processRoleOutput(ctx, { roleName, output, iteration }) {
|
|
42
|
+
if (!ctx.enabled) return output;
|
|
43
|
+
|
|
44
|
+
// Compress for next-role context
|
|
45
|
+
const compressed = compressRoleOutput(roleName, output);
|
|
46
|
+
const stats = measureCompression(output, compressed);
|
|
47
|
+
ctx.compressionStats.totalSaved += stats.savedTokens;
|
|
48
|
+
ctx.compressionStats.perRole[roleName] = stats;
|
|
49
|
+
|
|
50
|
+
// Extract feedback entries from specific role outputs
|
|
51
|
+
const entries = extractFeedbackEntries(roleName, output, iteration);
|
|
52
|
+
if (entries.length > 0) {
|
|
53
|
+
queue.addEntries(ctx.feedbackQueue, entries);
|
|
54
|
+
enrich.enrichQueue(ctx.feedbackQueue);
|
|
55
|
+
queue.deduplicate(ctx.feedbackQueue);
|
|
56
|
+
queue.prioritize(ctx.feedbackQueue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return compressed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract structured feedback entries from a role's output.
|
|
64
|
+
*/
|
|
65
|
+
function extractFeedbackEntries(roleName, output, iteration) {
|
|
66
|
+
if (!output || typeof output !== "object") return [];
|
|
67
|
+
|
|
68
|
+
const entries = [];
|
|
69
|
+
if (roleName === "reviewer" && output.blocking_issues) {
|
|
70
|
+
for (const issue of output.blocking_issues) {
|
|
71
|
+
entries.push({
|
|
72
|
+
source: "reviewer",
|
|
73
|
+
severity: issue.severity || "medium",
|
|
74
|
+
description: issue.description || "",
|
|
75
|
+
file: issue.file || null,
|
|
76
|
+
line: issue.line || null,
|
|
77
|
+
suggestedFix: issue.suggested_fix || null,
|
|
78
|
+
id: issue.id || null,
|
|
79
|
+
iteration
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} else if (roleName === "tester" && output.verdict === "fail") {
|
|
83
|
+
const missing = output.missing_scenarios || [];
|
|
84
|
+
for (const scenario of missing) {
|
|
85
|
+
entries.push({
|
|
86
|
+
source: "tester",
|
|
87
|
+
severity: "high",
|
|
88
|
+
category: "tests",
|
|
89
|
+
description: `Missing scenario: ${scenario}`,
|
|
90
|
+
iteration
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (output.coverage?.overall != null && output.coverage.overall < 80) {
|
|
94
|
+
entries.push({
|
|
95
|
+
source: "tester",
|
|
96
|
+
severity: "high",
|
|
97
|
+
category: "tests",
|
|
98
|
+
description: `Coverage below 80% (${output.coverage.overall}%)`,
|
|
99
|
+
iteration
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else if (roleName === "security" && output.verdict === "fail") {
|
|
103
|
+
for (const vuln of output.vulnerabilities || []) {
|
|
104
|
+
entries.push({
|
|
105
|
+
source: "security",
|
|
106
|
+
severity: vuln.severity || "high",
|
|
107
|
+
category: "security",
|
|
108
|
+
description: vuln.description || "",
|
|
109
|
+
file: vuln.file || null,
|
|
110
|
+
line: vuln.line || null,
|
|
111
|
+
suggestedFix: vuln.fix_suggestion || null,
|
|
112
|
+
iteration
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Before coder runs, format the feedback queue as an actionable prompt.
|
|
121
|
+
*/
|
|
122
|
+
export function buildCoderFeedbackPrompt(ctx) {
|
|
123
|
+
if (!ctx.enabled || ctx.feedbackQueue.entries.length === 0) return null;
|
|
124
|
+
return enrich.formatEnrichedForCoder(ctx.feedbackQueue.entries);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* After coder runs, verify changes were made. Returns verification result.
|
|
129
|
+
*/
|
|
130
|
+
export function verifyCoderRan(ctx, { baseRef, projectDir }) {
|
|
131
|
+
if (!ctx.enabled) return { passed: true, filesChanged: 0, reason: "brain disabled" };
|
|
132
|
+
const result = verifyCoderOutput({ baseRef, projectDir });
|
|
133
|
+
ctx.verificationTracker.record(result);
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Execute direct actions (npm install, gitignore updates, etc.).
|
|
139
|
+
*/
|
|
140
|
+
export async function runDirectActions(ctx, actions, { cwd } = {}) {
|
|
141
|
+
if (!ctx.enabled || !actions?.length) return [];
|
|
142
|
+
return executeActions(actions, { cwd });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Clear feedback queue after coder addresses it.
|
|
147
|
+
*/
|
|
148
|
+
export function clearFeedback(ctx) {
|
|
149
|
+
if (!ctx.enabled) return;
|
|
150
|
+
queue.clear(ctx.feedbackQueue);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get a summary of brain activity for the session.
|
|
155
|
+
*/
|
|
156
|
+
export function summarize(ctx) {
|
|
157
|
+
if (!ctx.enabled) return null;
|
|
158
|
+
return {
|
|
159
|
+
feedbackQueueSize: ctx.feedbackQueue.entries.length,
|
|
160
|
+
queueCategories: queue.countByCategory(ctx.feedbackQueue),
|
|
161
|
+
consecutiveVerificationFailures: ctx.verificationTracker.consecutiveFailures,
|
|
162
|
+
compressionSaved: ctx.compressionStats.totalSaved,
|
|
163
|
+
compressionPerRole: ctx.compressionStats.perRole
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI/CD integration — early PR creation, incremental push, review dispatch.
|
|
3
|
+
* Extracted from orchestrator.js — self-contained feature, only active when ci.enabled.
|
|
4
|
+
*/
|
|
5
|
+
import { saveSession } from "../session-store.js";
|
|
6
|
+
import { earlyPrCreation, incrementalPush } from "../git/automation.js";
|
|
7
|
+
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
8
|
+
|
|
9
|
+
export async function tryCiComment({ config, session, logger, agent, body }) {
|
|
10
|
+
if (!config.ci?.enabled || !session.ci_pr_number) return;
|
|
11
|
+
try {
|
|
12
|
+
const { dispatchComment } = await import("../ci/dispatch.js");
|
|
13
|
+
const { detectRepo } = await import("../ci/repo.js");
|
|
14
|
+
const repo = await detectRepo();
|
|
15
|
+
if (repo) {
|
|
16
|
+
await dispatchComment({
|
|
17
|
+
repo, prNumber: session.ci_pr_number, agent,
|
|
18
|
+
body, ciConfig: config.ci
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
} catch { /* non-blocking */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function formatCommitList(commits) {
|
|
25
|
+
return commits.map((c) => `- \`${c.hash.slice(0, 7)}\` ${c.message}`).join("\n");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function ciIncrementalPush({ config, session, gitCtx, task, logger, repo, dispatchComment }) {
|
|
29
|
+
const pushResult = await incrementalPush({ gitCtx, task, logger, session });
|
|
30
|
+
if (!pushResult) return;
|
|
31
|
+
|
|
32
|
+
const { accumulateCommit } = await import("../planning-game/pipeline-adapter.js");
|
|
33
|
+
for (const c of pushResult.commits) accumulateCommit(session, c);
|
|
34
|
+
|
|
35
|
+
session.ci_commits = [...(session.ci_commits ?? []), ...pushResult.commits];
|
|
36
|
+
await saveSession(session);
|
|
37
|
+
|
|
38
|
+
if (!repo) return;
|
|
39
|
+
const feedback = session.last_reviewer_feedback || "N/A";
|
|
40
|
+
await dispatchComment({
|
|
41
|
+
repo, prNumber: session.ci_pr_number, agent: "Coder",
|
|
42
|
+
body: `Issues fixed:\n${feedback}\n\nCommits:\n${formatCommitList(pushResult.commits)}`,
|
|
43
|
+
ciConfig: config.ci
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function ciCreateEarlyPr({ config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i, repo, dispatchComment }) {
|
|
48
|
+
const earlyPr = await earlyPrCreation({ gitCtx, task, logger, session, stageResults });
|
|
49
|
+
if (!earlyPr) return;
|
|
50
|
+
|
|
51
|
+
const { accumulateCommit } = await import("../planning-game/pipeline-adapter.js");
|
|
52
|
+
for (const c of earlyPr.commits) accumulateCommit(session, c);
|
|
53
|
+
|
|
54
|
+
session.ci_pr_number = earlyPr.prNumber;
|
|
55
|
+
session.ci_pr_url = earlyPr.prUrl;
|
|
56
|
+
session.ci_commits = earlyPr.commits;
|
|
57
|
+
await saveSession(session);
|
|
58
|
+
emitProgress(emitter, makeEvent("ci:pr-created", { ...eventBase, stage: "ci" }, {
|
|
59
|
+
message: `Early PR created: #${earlyPr.prNumber}`,
|
|
60
|
+
detail: { prNumber: earlyPr.prNumber, prUrl: earlyPr.prUrl }
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
if (!repo) return;
|
|
64
|
+
await dispatchComment({
|
|
65
|
+
repo, prNumber: earlyPr.prNumber, agent: "Coder",
|
|
66
|
+
body: `Iteration ${i} completed.\n\nCommits:\n${formatCommitList(earlyPr.commits)}`,
|
|
67
|
+
ciConfig: config.ci
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function handleCiEarlyPrOrPush({ ciEnabled, config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i }) {
|
|
72
|
+
if (!ciEnabled) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const { dispatchComment } = await import("../ci/dispatch.js");
|
|
76
|
+
const { detectRepo } = await import("../ci/repo.js");
|
|
77
|
+
const repo = await detectRepo();
|
|
78
|
+
|
|
79
|
+
if (session.ci_pr_number) {
|
|
80
|
+
await ciIncrementalPush({ config, session, gitCtx, task, logger, repo, dispatchComment });
|
|
81
|
+
} else {
|
|
82
|
+
await ciCreateEarlyPr({ config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i, repo, dispatchComment });
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
logger.warn(`CI early PR/push failed (non-blocking): ${err.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function formatBlockingIssues(issues) {
|
|
90
|
+
return issues?.map((x) => `- ${x.id || "ISSUE"} [${x.severity || ""}] ${x.description}`).join("\n") || "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatSuggestions(suggestions) {
|
|
94
|
+
return suggestions?.map((s) => {
|
|
95
|
+
const detail = typeof s === "string" ? s : `${s.id || ""} ${s.description || s}`;
|
|
96
|
+
return `- ${detail}`;
|
|
97
|
+
}).join("\n") || "";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildReviewCommentBody(review, i) {
|
|
101
|
+
const status = review.approved ? "APPROVED" : "REQUEST_CHANGES";
|
|
102
|
+
const blocking = formatBlockingIssues(review.blocking_issues);
|
|
103
|
+
const suggestions = formatSuggestions(review.non_blocking_suggestions);
|
|
104
|
+
let body = `Review iteration ${i}: ${status}`;
|
|
105
|
+
if (blocking) body += `\n\n**Blocking:**\n${blocking}`;
|
|
106
|
+
if (suggestions) body += `\n\n**Suggestions:**\n${suggestions}`;
|
|
107
|
+
return body;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function handleCiReviewDispatch({ ciEnabled, config, session, review, i, logger }) {
|
|
111
|
+
if (!ciEnabled || !session.ci_pr_number) return;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const { dispatchReview, dispatchComment } = await import("../ci/dispatch.js");
|
|
115
|
+
const { detectRepo } = await import("../ci/repo.js");
|
|
116
|
+
const repo = await detectRepo();
|
|
117
|
+
if (!repo) return;
|
|
118
|
+
|
|
119
|
+
const bc = config.ci;
|
|
120
|
+
const reviewEvent = review.approved ? "APPROVE" : "REQUEST_CHANGES";
|
|
121
|
+
const reviewBody = review.approved
|
|
122
|
+
? (review.summary || "Approved")
|
|
123
|
+
: (formatBlockingIssues(review.blocking_issues) || review.summary || "Changes requested");
|
|
124
|
+
|
|
125
|
+
await dispatchReview({
|
|
126
|
+
repo, prNumber: session.ci_pr_number,
|
|
127
|
+
event: reviewEvent, body: reviewBody, agent: "Reviewer", ciConfig: bc
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await dispatchComment({
|
|
131
|
+
repo, prNumber: session.ci_pr_number, agent: "Reviewer",
|
|
132
|
+
body: buildReviewCommentBody(review, i), ciConfig: bc
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
logger.info(`CI: dispatched review for PR #${session.ci_pr_number}`);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
logger.warn(`CI dispatch failed (non-blocking): ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|