karajan-code 1.59.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/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 +10 -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/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/reviewer-stage.js +16 -5
- package/src/orchestrator/stages/sonar-stage.js +24 -1
- package/src/orchestrator/verification-gate.js +146 -0
- package/src/orchestrator.js +428 -664
- 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
|
@@ -49,7 +49,7 @@ const DEFAULTS = {
|
|
|
49
49
|
model: null,
|
|
50
50
|
deterministic: true,
|
|
51
51
|
retries: 1,
|
|
52
|
-
fallback_reviewer:
|
|
52
|
+
fallback_reviewer: null
|
|
53
53
|
},
|
|
54
54
|
development: {
|
|
55
55
|
methodology: "tdd",
|
|
@@ -128,7 +128,7 @@ const DEFAULTS = {
|
|
|
128
128
|
policies: {},
|
|
129
129
|
serena: { enabled: false },
|
|
130
130
|
planning_game: { enabled: false, project_id: null, codeveloper: null },
|
|
131
|
-
|
|
131
|
+
ci: { enabled: false, review_event: "kj-review", comment_event: "kj-comment", comment_prefix: true },
|
|
132
132
|
git: { auto_commit: false, auto_push: false, auto_pr: false, auto_rebase: true, branch_prefix: "feat/" },
|
|
133
133
|
output: { report_dir: "./.reviews", log_level: "info", quiet: true },
|
|
134
134
|
budget: {
|
|
@@ -159,50 +159,12 @@ const DEFAULTS = {
|
|
|
159
159
|
failFast: {
|
|
160
160
|
repeatThreshold: 2
|
|
161
161
|
},
|
|
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
162
|
webperf: {
|
|
170
163
|
enabled: true,
|
|
171
164
|
devtools_mcp: false,
|
|
172
165
|
thresholds: { lcp: 2500, cls: 0.1, inp: 200 }
|
|
173
166
|
},
|
|
174
167
|
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
168
|
guards: {
|
|
207
169
|
output: {
|
|
208
170
|
enabled: true,
|
|
@@ -398,12 +360,12 @@ function applyMethodologyOverride(out, flags) {
|
|
|
398
360
|
out.development.require_test_changes = methodology === "tdd";
|
|
399
361
|
}
|
|
400
362
|
|
|
401
|
-
function
|
|
402
|
-
out.
|
|
403
|
-
if (flags.
|
|
404
|
-
out.
|
|
405
|
-
//
|
|
406
|
-
if (out.
|
|
363
|
+
function applyCiOverride(out, flags) {
|
|
364
|
+
out.ci = out.ci || { enabled: false };
|
|
365
|
+
if (flags.enableCi === undefined) return;
|
|
366
|
+
out.ci.enabled = Boolean(flags.enableCi);
|
|
367
|
+
// CI requires git automation (commit + push + PR)
|
|
368
|
+
if (out.ci.enabled) {
|
|
407
369
|
out.git.auto_commit = true;
|
|
408
370
|
out.git.auto_push = true;
|
|
409
371
|
out.git.auto_pr = true;
|
|
@@ -432,10 +394,6 @@ function applyMiscOverrides(out, flags) {
|
|
|
432
394
|
if (flags.pgTask) out.planning_game.enabled = true;
|
|
433
395
|
if (flags.pgProject) out.planning_game.project_id = flags.pgProject;
|
|
434
396
|
|
|
435
|
-
out.proxy = out.proxy || {};
|
|
436
|
-
if (flags.noProxy) out.proxy.enabled = false;
|
|
437
|
-
if (flags.proxyPort) out.proxy.port = flags.proxyPort;
|
|
438
|
-
|
|
439
397
|
out.model_selection = out.model_selection || { enabled: true, tiers: {}, role_overrides: {} };
|
|
440
398
|
if (flags.smartModels === true) out.model_selection.enabled = true;
|
|
441
399
|
if (flags.smartModels === false || flags.noSmartModels === true) out.model_selection.enabled = false;
|
|
@@ -461,7 +419,7 @@ export function applyRunOverrides(config, flags) {
|
|
|
461
419
|
applyPipelineOverrides(out, flags);
|
|
462
420
|
applyScalarAndBooleanOverrides(out, flags);
|
|
463
421
|
applyMethodologyOverride(out, flags);
|
|
464
|
-
|
|
422
|
+
applyCiOverride(out, flags);
|
|
465
423
|
applyMiscOverrides(out, flags);
|
|
466
424
|
applyOutputModeOverrides(out, flags);
|
|
467
425
|
|
|
@@ -479,7 +437,7 @@ const AGENT_MODEL_SIGNATURES = {
|
|
|
479
437
|
gemini: ["gemini", "flash-"]
|
|
480
438
|
};
|
|
481
439
|
|
|
482
|
-
|
|
440
|
+
function isModelCompatible(agent, model) {
|
|
483
441
|
if (!model || !agent) return true;
|
|
484
442
|
const lower = model.toLowerCase();
|
|
485
443
|
|
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
|
+
}
|