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
|
@@ -1,72 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
config?.roles?.coder?.provider ||
|
|
8
|
-
"claude"
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function buildPrompt({ task, instructions }) {
|
|
13
|
-
const sections = [];
|
|
14
|
-
|
|
15
|
-
if (instructions) {
|
|
16
|
-
sections.push(instructions, "");
|
|
3
|
+
export class RefactorerRole extends AgentRole {
|
|
4
|
+
constructor(opts) {
|
|
5
|
+
super({ ...opts, name: "refactorer" });
|
|
17
6
|
}
|
|
18
7
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"Do not expand scope and keep tests green.",
|
|
22
|
-
"",
|
|
23
|
-
"## Task context",
|
|
24
|
-
task
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
return sections.join("\n");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class RefactorerRole extends BaseRole {
|
|
31
|
-
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
32
|
-
super({ name: "refactorer", config, logger, emitter });
|
|
33
|
-
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
8
|
+
resolveProvider() {
|
|
9
|
+
return this.config?.roles?.refactorer?.provider || this.config?.roles?.coder?.provider || "claude";
|
|
34
10
|
}
|
|
35
11
|
|
|
36
|
-
async
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const result = await agent.runTask(runArgs);
|
|
49
|
-
|
|
50
|
-
if (!result.ok) {
|
|
51
|
-
return {
|
|
52
|
-
ok: false,
|
|
53
|
-
result: {
|
|
54
|
-
...result,
|
|
55
|
-
error: result.error || result.output || "Refactorer failed",
|
|
56
|
-
provider
|
|
57
|
-
},
|
|
58
|
-
summary: `Refactorer failed: ${result.error || "unknown error"}`
|
|
59
|
-
};
|
|
60
|
-
}
|
|
12
|
+
async buildPrompt({ task }) {
|
|
13
|
+
const sections = [];
|
|
14
|
+
if (this.instructions) sections.push(this.instructions, "");
|
|
15
|
+
sections.push(
|
|
16
|
+
"Refactor the current changes for clarity and maintainability without changing behavior.",
|
|
17
|
+
"Do not expand scope and keep tests green.",
|
|
18
|
+
"",
|
|
19
|
+
"## Task context",
|
|
20
|
+
task
|
|
21
|
+
);
|
|
22
|
+
return { prompt: sections.join("\n") };
|
|
23
|
+
}
|
|
61
24
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
result: {
|
|
65
|
-
...result,
|
|
66
|
-
output: result.output?.trim() || "",
|
|
67
|
-
provider
|
|
68
|
-
},
|
|
69
|
-
summary: "Refactoring applied"
|
|
70
|
-
};
|
|
25
|
+
buildSuccessResult(parsed, provider, agentResult) {
|
|
26
|
+
return { ...agentResult, output: (agentResult.output || "").trim(), provider };
|
|
71
27
|
}
|
|
28
|
+
|
|
29
|
+
buildSummary() { return "Refactoring applied"; }
|
|
72
30
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
import { extractFirstJson } from "../utils/json-extract.js";
|
|
4
3
|
|
|
5
4
|
const SUBAGENT_PREAMBLE = [
|
|
@@ -8,127 +7,64 @@ const SUBAGENT_PREAMBLE = [
|
|
|
8
7
|
"Do NOT use any MCP tools. Focus only on researching the codebase."
|
|
9
8
|
].join(" ");
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"claude"
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function buildPrompt({ task, instructions }) {
|
|
20
|
-
const sections = [SUBAGENT_PREAMBLE];
|
|
10
|
+
const EMPTY_RESEARCH = {
|
|
11
|
+
affected_files: [], patterns: [], constraints: [],
|
|
12
|
+
prior_decisions: [], risks: [], test_coverage: ""
|
|
13
|
+
};
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
export class ResearcherRole extends AgentRole {
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
super({ ...opts, name: "researcher" });
|
|
24
18
|
}
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
function parseResearchOutput(raw) {
|
|
38
|
-
return extractFirstJson(raw);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function buildSummary(parsed) {
|
|
42
|
-
const files = parsed.affected_files?.length || 0;
|
|
43
|
-
const risks = parsed.risks?.length || 0;
|
|
44
|
-
const patterns = parsed.patterns?.length || 0;
|
|
45
|
-
const parts = [];
|
|
46
|
-
if (files) parts.push(`${files} file${files === 1 ? "" : "s"}`);
|
|
47
|
-
if (risks) parts.push(`${risks} risk${risks === 1 ? "" : "s"}`);
|
|
48
|
-
if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
|
|
49
|
-
return parts.length
|
|
50
|
-
? `Research complete: ${parts.join(", ")} identified`
|
|
51
|
-
: "Research complete";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class ResearcherRole extends BaseRole {
|
|
55
|
-
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
56
|
-
super({ name: "researcher", config, logger, emitter });
|
|
57
|
-
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
20
|
+
async buildPrompt({ task }) {
|
|
21
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
22
|
+
if (this.instructions) sections.push(this.instructions);
|
|
23
|
+
sections.push(
|
|
24
|
+
"Investigate the codebase for the following task.",
|
|
25
|
+
"Identify affected files, patterns, constraints, prior decisions, risks, and test coverage.",
|
|
26
|
+
"Return a single valid JSON object with your findings and nothing else.",
|
|
27
|
+
'{"affected_files":[string],"patterns":[string],"constraints":[string],"prior_decisions":[string],"risks":[string],"test_coverage":string}',
|
|
28
|
+
`## Task\n${task}`
|
|
29
|
+
);
|
|
30
|
+
return { prompt: sections.join("\n\n") };
|
|
58
31
|
}
|
|
59
32
|
|
|
60
|
-
|
|
61
|
-
const task = typeof input === "string"
|
|
62
|
-
? input
|
|
63
|
-
: input?.task || this.context?.task || "";
|
|
64
|
-
const onOutput = typeof input === "string" ? null : input?.onOutput || null;
|
|
65
|
-
|
|
66
|
-
const provider = resolveProvider(this.config);
|
|
67
|
-
const agent = this._createAgent(provider, this.config, this.logger);
|
|
33
|
+
parseOutput(raw) { return extractFirstJson(raw); }
|
|
68
34
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
35
|
+
buildSuccessResult(parsed, provider) {
|
|
36
|
+
return {
|
|
37
|
+
affected_files: parsed.affected_files || [],
|
|
38
|
+
patterns: parsed.patterns || [],
|
|
39
|
+
constraints: parsed.constraints || [],
|
|
40
|
+
prior_decisions: parsed.prior_decisions || [],
|
|
41
|
+
risks: parsed.risks || [],
|
|
42
|
+
test_coverage: parsed.test_coverage || "",
|
|
43
|
+
provider
|
|
44
|
+
};
|
|
45
|
+
}
|
|
73
46
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
47
|
+
buildSummary(parsed) {
|
|
48
|
+
const files = parsed.affected_files?.length || 0;
|
|
49
|
+
const risks = parsed.risks?.length || 0;
|
|
50
|
+
const patterns = parsed.patterns?.length || 0;
|
|
51
|
+
const parts = [];
|
|
52
|
+
if (files) parts.push(`${files} file${files === 1 ? "" : "s"}`);
|
|
53
|
+
if (risks) parts.push(`${risks} risk${risks === 1 ? "" : "s"}`);
|
|
54
|
+
if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
|
|
55
|
+
return parts.length ? `Research complete: ${parts.join(", ")} identified` : "Research complete";
|
|
56
|
+
}
|
|
84
57
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
constraints: [],
|
|
94
|
-
prior_decisions: [],
|
|
95
|
-
risks: [],
|
|
96
|
-
test_coverage: "",
|
|
97
|
-
raw: result.output,
|
|
98
|
-
provider
|
|
99
|
-
},
|
|
100
|
-
summary: "Research complete (unstructured output)"
|
|
101
|
-
};
|
|
102
|
-
}
|
|
58
|
+
handleParseNull(agentResult, provider) {
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
result: { ...EMPTY_RESEARCH, raw: agentResult.output, provider },
|
|
62
|
+
summary: "Research complete (unstructured output)",
|
|
63
|
+
usage: agentResult.usage
|
|
64
|
+
};
|
|
65
|
+
}
|
|
103
66
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
result: {
|
|
107
|
-
affected_files: parsed.affected_files || [],
|
|
108
|
-
patterns: parsed.patterns || [],
|
|
109
|
-
constraints: parsed.constraints || [],
|
|
110
|
-
prior_decisions: parsed.prior_decisions || [],
|
|
111
|
-
risks: parsed.risks || [],
|
|
112
|
-
test_coverage: parsed.test_coverage || "",
|
|
113
|
-
provider
|
|
114
|
-
},
|
|
115
|
-
summary: buildSummary(parsed)
|
|
116
|
-
};
|
|
117
|
-
} catch { /* agent output is not structured JSON */
|
|
118
|
-
return {
|
|
119
|
-
ok: true,
|
|
120
|
-
result: {
|
|
121
|
-
affected_files: [],
|
|
122
|
-
patterns: [],
|
|
123
|
-
constraints: [],
|
|
124
|
-
prior_decisions: [],
|
|
125
|
-
risks: [],
|
|
126
|
-
test_coverage: "",
|
|
127
|
-
raw: result.output,
|
|
128
|
-
provider
|
|
129
|
-
},
|
|
130
|
-
summary: "Research complete (unstructured output)"
|
|
131
|
-
};
|
|
132
|
-
}
|
|
67
|
+
handleParseError(_err, agentResult, provider) {
|
|
68
|
+
return this.handleParseNull(agentResult, provider);
|
|
133
69
|
}
|
|
134
70
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
import { buildRtkInstructions } from "../prompts/rtk-snippet.js";
|
|
4
3
|
import { extractFirstJson } from "../utils/json-extract.js";
|
|
5
4
|
|
|
@@ -11,146 +10,90 @@ const SUBAGENT_PREAMBLE = [
|
|
|
11
10
|
"Do NOT use any MCP tools. Focus only on reviewing the code."
|
|
12
11
|
].join(" ");
|
|
13
12
|
|
|
14
|
-
function resolveProvider(config) {
|
|
15
|
-
return (
|
|
16
|
-
config?.roles?.reviewer?.provider ||
|
|
17
|
-
config?.roles?.coder?.provider ||
|
|
18
|
-
"claude"
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
function truncateDiff(diff) {
|
|
23
14
|
if (!diff) return "";
|
|
24
|
-
return diff.length > MAX_DIFF_LENGTH
|
|
25
|
-
? `${diff.slice(0, MAX_DIFF_LENGTH)}\n\n[TRUNCATED]`
|
|
26
|
-
: diff;
|
|
15
|
+
return diff.length > MAX_DIFF_LENGTH ? `${diff.slice(0, MAX_DIFF_LENGTH)}\n\n[TRUNCATED]` : diff;
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sections.push(SUBAGENT_PREAMBLE);
|
|
33
|
-
|
|
34
|
-
if (instructions) {
|
|
35
|
-
sections.push(instructions);
|
|
18
|
+
export class ReviewerRole extends AgentRole {
|
|
19
|
+
constructor(opts) {
|
|
20
|
+
super({ ...opts, name: "reviewer" });
|
|
36
21
|
}
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
`You are a code reviewer in ${reviewMode || "standard"} mode.`,
|
|
40
|
-
"Return only one valid JSON object and nothing else.",
|
|
41
|
-
'JSON schema:',
|
|
42
|
-
'{"approved":boolean,"blocking_issues":[{"id":string,"severity":"critical|high|medium|low","file":string,"line":number,"description":string,"suggested_fix":string}],"non_blocking_suggestions":[string],"summary":string,"confidence":number}',
|
|
43
|
-
`Task context:\n${task}`
|
|
44
|
-
);
|
|
23
|
+
get agentMethod() { return "reviewTask"; }
|
|
45
24
|
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
extractInput(input) {
|
|
26
|
+
if (typeof input === "string") return { task: input, diff: "", reviewRules: null, onOutput: null };
|
|
27
|
+
return {
|
|
28
|
+
task: input?.task || this.context?.task || "",
|
|
29
|
+
diff: input?.diff || "",
|
|
30
|
+
reviewRules: input?.reviewRules || null,
|
|
31
|
+
onOutput: input?.onOutput || null
|
|
32
|
+
};
|
|
48
33
|
}
|
|
49
34
|
|
|
50
|
-
|
|
51
|
-
sections
|
|
52
|
-
|
|
35
|
+
async buildPrompt({ task, diff, reviewRules }) {
|
|
36
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
37
|
+
if (this.instructions) sections.push(this.instructions);
|
|
38
|
+
sections.push(
|
|
39
|
+
`You are a code reviewer in ${this.config?.review_mode || "standard"} mode.`,
|
|
40
|
+
"Return only one valid JSON object and nothing else.",
|
|
41
|
+
"JSON schema:",
|
|
42
|
+
'{"approved":boolean,"blocking_issues":[{"id":string,"severity":"critical|high|medium|low","file":string,"line":number,"description":string,"suggested_fix":string}],"non_blocking_suggestions":[string],"summary":string,"confidence":number}',
|
|
43
|
+
`Task context:\n${task}`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const pc = this.config?.productContext;
|
|
47
|
+
if (pc) sections.push(`## Product Context\n${pc}`);
|
|
48
|
+
const dc = this.config?.domainContext;
|
|
49
|
+
if (dc) sections.push(`## Domain Context\n${dc}`);
|
|
50
|
+
|
|
51
|
+
const rtkSnippet = buildRtkInstructions({
|
|
52
|
+
rtkAvailable: Boolean(this.config?.rtk?.available)
|
|
53
|
+
});
|
|
54
|
+
if (rtkSnippet) sections.push(rtkSnippet);
|
|
55
|
+
if (reviewRules) sections.push(`Review rules:\n${reviewRules}`);
|
|
56
|
+
sections.push(`Git diff:\n${truncateDiff(diff)}`);
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
if (rtkSnippet) {
|
|
56
|
-
sections.push(rtkSnippet);
|
|
58
|
+
return { prompt: sections.join("\n\n") };
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
parseOutput(raw) {
|
|
62
|
+
const parsed = extractFirstJson(raw);
|
|
63
|
+
if (!parsed) throw new Error("Failed to parse reviewer output: no JSON found");
|
|
64
|
+
return parsed;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
buildSuccessResult(parsed, provider, agentResult) {
|
|
68
|
+
return {
|
|
69
|
+
...agentResult,
|
|
70
|
+
approved: parsed.approved,
|
|
71
|
+
blocking_issues: parsed.blocking_issues || [],
|
|
72
|
+
non_blocking_suggestions: parsed.non_blocking_suggestions || [],
|
|
73
|
+
confidence: parsed.confidence ?? null,
|
|
74
|
+
raw_summary: parsed.summary || ""
|
|
75
|
+
};
|
|
72
76
|
}
|
|
73
|
-
return parsed;
|
|
74
|
-
}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
buildSummary(parsed) {
|
|
79
|
+
const blockingIssues = parsed.blocking_issues || [];
|
|
80
|
+
return parsed.approved
|
|
81
|
+
? `Approved: ${parsed.summary || "no issues found"}`
|
|
82
|
+
: `Rejected: ${blockingIssues.length} blocking issue(s) — ${parsed.summary || ""}`;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
instructions: this.instructions,
|
|
96
|
-
rtkAvailable: Boolean(this.config?.rtk?.available),
|
|
97
|
-
proxyEnabled: Boolean(this.config?.proxy?.enabled),
|
|
98
|
-
productContext: this.config?.productContext || null,
|
|
99
|
-
domainContext: this.config?.domainContext || null
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const reviewArgs = { prompt, role: "reviewer" };
|
|
103
|
-
if (onOutput) reviewArgs.onOutput = onOutput;
|
|
104
|
-
|
|
105
|
-
const result = await agent.reviewTask(reviewArgs);
|
|
106
|
-
|
|
107
|
-
if (!result.ok) {
|
|
108
|
-
return {
|
|
109
|
-
ok: false,
|
|
110
|
-
result: {
|
|
111
|
-
error: result.error || result.output || "Reviewer agent failed",
|
|
112
|
-
approved: false,
|
|
113
|
-
blocking_issues: []
|
|
114
|
-
},
|
|
115
|
-
summary: `Reviewer failed: ${result.error || "unknown error"}`
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const parsed = parseReviewOutput(result.output);
|
|
121
|
-
const blockingIssues = parsed.blocking_issues || [];
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
ok: true,
|
|
125
|
-
result: {
|
|
126
|
-
...result,
|
|
127
|
-
approved: parsed.approved,
|
|
128
|
-
blocking_issues: blockingIssues,
|
|
129
|
-
non_blocking_suggestions: parsed.non_blocking_suggestions || [],
|
|
130
|
-
confidence: parsed.confidence ?? null,
|
|
131
|
-
raw_summary: parsed.summary || ""
|
|
132
|
-
},
|
|
133
|
-
summary: parsed.approved
|
|
134
|
-
? `Approved: ${parsed.summary || "no issues found"}`
|
|
135
|
-
: `Rejected: ${blockingIssues.length} blocking issue(s) — ${parsed.summary || ""}`
|
|
136
|
-
};
|
|
137
|
-
} catch (err) {
|
|
138
|
-
return {
|
|
139
|
-
ok: true,
|
|
140
|
-
result: {
|
|
141
|
-
...result,
|
|
142
|
-
approved: false,
|
|
143
|
-
blocking_issues: [{
|
|
144
|
-
id: "PARSE_ERROR",
|
|
145
|
-
severity: "high",
|
|
146
|
-
description: `Reviewer output could not be parsed: ${err.message}`
|
|
147
|
-
}],
|
|
148
|
-
non_blocking_suggestions: [],
|
|
149
|
-
confidence: 0,
|
|
150
|
-
raw_summary: `Parse error: ${err.message}`
|
|
151
|
-
},
|
|
152
|
-
summary: `Reviewer output parse error: ${err.message}`
|
|
153
|
-
};
|
|
154
|
-
}
|
|
85
|
+
handleParseError(err, agentResult, _provider) {
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
result: {
|
|
89
|
+
...agentResult,
|
|
90
|
+
approved: false,
|
|
91
|
+
blocking_issues: [{ id: "PARSE_ERROR", severity: "high", description: `Reviewer output could not be parsed: ${err.message}` }],
|
|
92
|
+
non_blocking_suggestions: [],
|
|
93
|
+
confidence: 0,
|
|
94
|
+
raw_summary: `Parse error: ${err.message}`
|
|
95
|
+
},
|
|
96
|
+
summary: `Reviewer output parse error: ${err.message}`
|
|
97
|
+
};
|
|
155
98
|
}
|
|
156
99
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
1
|
+
import { AgentRole } from "./agent-role.js";
|
|
3
2
|
import { extractFirstJson } from "../utils/json-extract.js";
|
|
4
3
|
|
|
5
4
|
const SUBAGENT_PREAMBLE = [
|
|
@@ -8,118 +7,55 @@ const SUBAGENT_PREAMBLE = [
|
|
|
8
7
|
"Do NOT use any MCP tools. Focus only on auditing code for security vulnerabilities."
|
|
9
8
|
].join(" ");
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
config?.roles?.coder?.provider ||
|
|
15
|
-
"claude"
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function buildPrompt({ task, diff, instructions }) {
|
|
20
|
-
const sections = [SUBAGENT_PREAMBLE];
|
|
21
|
-
|
|
22
|
-
if (instructions) {
|
|
23
|
-
sections.push(instructions);
|
|
10
|
+
export class SecurityRole extends AgentRole {
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
super({ ...opts, name: "security" });
|
|
24
13
|
}
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
15
|
+
async buildPrompt({ task, diff }) {
|
|
16
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
17
|
+
if (this.instructions) sections.push(this.instructions);
|
|
18
|
+
sections.push(
|
|
19
|
+
"You are a security auditor. Analyze the code changes for vulnerabilities.",
|
|
20
|
+
"Check for: OWASP top 10, exposed secrets/API keys, hardcoded credentials, command injection, XSS, SQL injection, path traversal, prototype pollution, insecure dependencies.",
|
|
21
|
+
"Return a single valid JSON object with your findings and nothing else.",
|
|
22
|
+
'{"vulnerabilities":[{"severity":"critical|high|medium|low","category":string,"file":string,"line":number,"description":string,"fix_suggestion":string}],"verdict":"pass"|"fail"}',
|
|
23
|
+
`## Task\n${task}`
|
|
24
|
+
);
|
|
25
|
+
if (diff) sections.push(`## Git diff to audit\n${diff}`);
|
|
26
|
+
return { prompt: sections.join("\n\n") };
|
|
36
27
|
}
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseSecurityOutput(raw) {
|
|
42
|
-
return extractFirstJson(raw);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function buildSummary(parsed) {
|
|
46
|
-
const vulns = parsed.vulnerabilities || [];
|
|
47
|
-
if (vulns.length === 0) {
|
|
48
|
-
return `Verdict: ${parsed.verdict || "pass"}; No vulnerabilities found`;
|
|
29
|
+
extractInput(input) {
|
|
30
|
+
if (typeof input === "string") return { task: input, diff: null };
|
|
31
|
+
return { task: input?.task || this.context?.task || "", diff: input?.diff || null, onOutput: input?.onOutput || null };
|
|
49
32
|
}
|
|
50
33
|
|
|
51
|
-
|
|
52
|
-
for (const v of vulns) {
|
|
53
|
-
const sev = v.severity || "unknown";
|
|
54
|
-
bySeverity[sev] = (bySeverity[sev] || 0) + 1;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const parts = Object.entries(bySeverity)
|
|
58
|
-
.sort(([a], [b]) => {
|
|
59
|
-
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
60
|
-
return (order[a] ?? 4) - (order[b] ?? 4);
|
|
61
|
-
})
|
|
62
|
-
.map(([sev, count]) => `${count} ${sev}`);
|
|
63
|
-
|
|
64
|
-
return `Verdict: ${parsed.verdict || "fail"}; ${parts.join(", ")}`;
|
|
65
|
-
}
|
|
34
|
+
parseOutput(raw) { return extractFirstJson(raw); }
|
|
66
35
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
36
|
+
isSuccessful(parsed) {
|
|
37
|
+
const verdict = parsed.verdict || (parsed.vulnerabilities?.length ? "fail" : "pass");
|
|
38
|
+
return verdict === "pass";
|
|
71
39
|
}
|
|
72
40
|
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const provider = resolveProvider(this.config);
|
|
79
|
-
const agent = this._createAgent(provider, this.config, this.logger);
|
|
80
|
-
|
|
81
|
-
const prompt = buildPrompt({ task, diff, instructions: this.instructions });
|
|
82
|
-
const result = await agent.runTask({ prompt, role: "security" });
|
|
41
|
+
buildSuccessResult(parsed, provider) {
|
|
42
|
+
const verdict = parsed.verdict || (parsed.vulnerabilities?.length ? "fail" : "pass");
|
|
43
|
+
return { vulnerabilities: parsed.vulnerabilities || [], verdict, provider };
|
|
44
|
+
}
|
|
83
45
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error: result.error || result.output || "Security audit failed",
|
|
89
|
-
provider
|
|
90
|
-
},
|
|
91
|
-
summary: `Security audit failed: ${result.error || "unknown error"}`
|
|
92
|
-
};
|
|
93
|
-
}
|
|
46
|
+
buildSummary(parsed) {
|
|
47
|
+
const verdict = parsed.verdict || (parsed.vulnerabilities?.length ? "fail" : "pass");
|
|
48
|
+
const vulns = parsed.vulnerabilities || [];
|
|
49
|
+
if (!vulns.length) return `Verdict: ${verdict}; No vulnerabilities found`;
|
|
94
50
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!parsed) {
|
|
98
|
-
return {
|
|
99
|
-
ok: false,
|
|
100
|
-
result: { error: "Failed to parse security output: no JSON found", provider },
|
|
101
|
-
summary: "Security output parse error: no JSON found"
|
|
102
|
-
};
|
|
103
|
-
}
|
|
51
|
+
const bySeverity = {};
|
|
52
|
+
for (const v of vulns) bySeverity[v.severity || "unknown"] = (bySeverity[v.severity || "unknown"] || 0) + 1;
|
|
104
53
|
|
|
105
|
-
|
|
106
|
-
|
|
54
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
55
|
+
const parts = Object.entries(bySeverity)
|
|
56
|
+
.sort(([a], [b]) => (order[a] ?? 4) - (order[b] ?? 4))
|
|
57
|
+
.map(([sev, count]) => `${count} ${sev}`);
|
|
107
58
|
|
|
108
|
-
|
|
109
|
-
ok,
|
|
110
|
-
result: {
|
|
111
|
-
vulnerabilities: parsed.vulnerabilities || [],
|
|
112
|
-
verdict,
|
|
113
|
-
provider
|
|
114
|
-
},
|
|
115
|
-
summary: buildSummary({ ...parsed, verdict })
|
|
116
|
-
};
|
|
117
|
-
} catch (err) {
|
|
118
|
-
return {
|
|
119
|
-
ok: false,
|
|
120
|
-
result: { error: `Failed to parse security output: ${err.message}`, provider },
|
|
121
|
-
summary: `Security output parse error: ${err.message}`
|
|
122
|
-
};
|
|
123
|
-
}
|
|
59
|
+
return `Verdict: ${verdict}; ${parts.join(", ")}`;
|
|
124
60
|
}
|
|
125
61
|
}
|