karajan-code 1.16.0 → 1.17.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/package.json +1 -1
- package/src/activity-log.js +13 -13
- package/src/agents/availability.js +2 -3
- package/src/agents/claude-agent.js +42 -21
- package/src/agents/model-registry.js +1 -1
- package/src/becaria/dispatch.js +1 -1
- package/src/becaria/repo.js +3 -3
- package/src/cli.js +5 -2
- package/src/commands/doctor.js +154 -108
- package/src/commands/init.js +101 -90
- package/src/commands/plan.js +1 -1
- package/src/commands/report.js +77 -71
- package/src/commands/roles.js +0 -1
- package/src/commands/run.js +2 -3
- package/src/config.js +155 -93
- package/src/git/automation.js +3 -4
- package/src/guards/policy-resolver.js +3 -3
- package/src/mcp/orphan-guard.js +1 -2
- package/src/mcp/progress.js +4 -3
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +242 -253
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +2 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +200 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +820 -748
- package/src/planning-game/adapter.js +23 -20
- package/src/planning-game/architect-adrs.js +45 -0
- package/src/planning-game/client.js +15 -1
- package/src/planning-game/decomposition.js +7 -5
- package/src/prompts/architect.js +88 -0
- package/src/prompts/discover.js +54 -53
- package/src/prompts/planner.js +53 -33
- package/src/prompts/triage.js +8 -16
- package/src/review/parser.js +18 -19
- package/src/review/profiles.js +2 -2
- package/src/review/schema.js +3 -3
- package/src/review/scope-filter.js +3 -4
- package/src/roles/architect-role.js +122 -0
- package/src/roles/commiter-role.js +2 -2
- package/src/roles/discover-role.js +59 -67
- package/src/roles/index.js +1 -0
- package/src/roles/planner-role.js +54 -38
- package/src/roles/refactorer-role.js +8 -7
- package/src/roles/researcher-role.js +6 -7
- package/src/roles/reviewer-role.js +4 -5
- package/src/roles/security-role.js +3 -4
- package/src/roles/solomon-role.js +6 -18
- package/src/roles/sonar-role.js +5 -1
- package/src/roles/tester-role.js +8 -5
- package/src/roles/triage-role.js +2 -2
- package/src/session-cleanup.js +29 -24
- package/src/session-store.js +1 -1
- package/src/sonar/api.js +1 -1
- package/src/sonar/manager.js +1 -1
- package/src/sonar/project-key.js +5 -5
- package/src/sonar/scanner.js +34 -65
- package/src/utils/display.js +312 -272
- package/src/utils/git.js +3 -3
- package/src/utils/logger.js +6 -1
- package/src/utils/model-selector.js +5 -5
- package/src/utils/process.js +80 -102
- package/src/utils/rate-limit-detector.js +13 -13
- package/src/utils/run-log.js +55 -52
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
package/src/review/profiles.js
CHANGED
|
@@ -10,7 +10,7 @@ import path from "node:path";
|
|
|
10
10
|
import { loadFirstExisting } from "../roles/base-role.js";
|
|
11
11
|
import { getKarajanHome } from "../utils/paths.js";
|
|
12
12
|
|
|
13
|
-
const KNOWN_MODES = ["paranoid", "strict", "standard", "relaxed"];
|
|
13
|
+
const KNOWN_MODES = new Set(["paranoid", "strict", "standard", "relaxed"]);
|
|
14
14
|
|
|
15
15
|
const DEFAULT_RULES = "Focus on critical issues: security vulnerabilities, logic errors, and broken tests.";
|
|
16
16
|
|
|
@@ -38,7 +38,7 @@ function buildCandidates(fileName, projectDir) {
|
|
|
38
38
|
|
|
39
39
|
export async function resolveReviewProfile({ mode = "standard", projectDir } = {}) {
|
|
40
40
|
// Known modes: try mode-specific file first, then base reviewer.md
|
|
41
|
-
if (KNOWN_MODES.
|
|
41
|
+
if (KNOWN_MODES.has(mode)) {
|
|
42
42
|
const modePaths = buildCandidates(`reviewer-${mode}.md`, projectDir);
|
|
43
43
|
const modeRules = await loadFirstExisting(modePaths);
|
|
44
44
|
if (modeRules) {
|
package/src/review/schema.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export function validateReviewResult(reviewResult) {
|
|
2
2
|
if (!reviewResult || typeof reviewResult !== "object") {
|
|
3
|
-
throw new
|
|
3
|
+
throw new TypeError("Reviewer output must be a JSON object");
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
if (typeof reviewResult.approved !== "boolean") {
|
|
7
|
-
throw new
|
|
7
|
+
throw new TypeError("Reviewer output missing boolean field: approved");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
if (!Array.isArray(reviewResult.blocking_issues)) {
|
|
11
|
-
throw new
|
|
11
|
+
throw new TypeError("Reviewer output missing array field: blocking_issues");
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
if (!Array.isArray(reviewResult.non_blocking_suggestions)) {
|
|
@@ -17,7 +17,7 @@ export function extractDiffFiles(diff) {
|
|
|
17
17
|
const files = new Set();
|
|
18
18
|
for (const line of (diff || "").split("\n")) {
|
|
19
19
|
// Match "+++ b/path" lines in unified diff
|
|
20
|
-
const m =
|
|
20
|
+
const m = /^\+\+\+ b\/(.+)/.exec(line);
|
|
21
21
|
if (m) files.add(m[1]);
|
|
22
22
|
}
|
|
23
23
|
return files;
|
|
@@ -50,7 +50,7 @@ export function isIssueInScope(issue, diffFiles, diffContent) {
|
|
|
50
50
|
|
|
51
51
|
// Check if the file path appears anywhere in the diff content
|
|
52
52
|
// (covers cases where the file is referenced in imports/requires)
|
|
53
|
-
if (diffContent
|
|
53
|
+
if (diffContent?.includes(file)) return true;
|
|
54
54
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
@@ -146,8 +146,7 @@ export function buildDeferredContext(deferredIssues) {
|
|
|
146
146
|
lines.push(`- [${issue.severity}] ${file}: ${issue.description}${fix}`);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
lines.push("");
|
|
150
|
-
lines.push("If your current changes naturally address any of these, great. Otherwise, they will be tracked for future resolution.");
|
|
149
|
+
lines.push("", "If your current changes naturally address any of these, great. Otherwise, they will be tracked for future resolution.");
|
|
151
150
|
|
|
152
151
|
return lines.join("\n");
|
|
153
152
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { BaseRole } from "./base-role.js";
|
|
2
|
+
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
3
|
+
import { buildArchitectPrompt, parseArchitectOutput } from "../prompts/architect.js";
|
|
4
|
+
|
|
5
|
+
function resolveProvider(config) {
|
|
6
|
+
return (
|
|
7
|
+
config?.roles?.architect?.provider ||
|
|
8
|
+
config?.roles?.coder?.provider ||
|
|
9
|
+
"claude"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildSummary(parsed) {
|
|
14
|
+
const parts = [];
|
|
15
|
+
const arch = parsed.architecture;
|
|
16
|
+
|
|
17
|
+
if (arch.type) parts.push(arch.type);
|
|
18
|
+
|
|
19
|
+
const layers = arch.layers?.length || 0;
|
|
20
|
+
if (layers) parts.push(`${layers} layer${layers === 1 ? "" : "s"}`);
|
|
21
|
+
|
|
22
|
+
const patterns = arch.patterns?.length || 0;
|
|
23
|
+
if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
|
|
24
|
+
|
|
25
|
+
const entities = arch.dataModel?.entities?.length || 0;
|
|
26
|
+
if (entities) parts.push(`${entities} entit${entities === 1 ? "y" : "ies"}`);
|
|
27
|
+
|
|
28
|
+
const questions = parsed.questions?.length || 0;
|
|
29
|
+
if (questions) parts.push(`${questions} question${questions === 1 ? "" : "s"}`);
|
|
30
|
+
|
|
31
|
+
return parts.length
|
|
32
|
+
? `Architecture complete: ${parts.join(", ")} (verdict: ${parsed.verdict})`
|
|
33
|
+
: "Architecture complete";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const EMPTY_ARCHITECTURE = {
|
|
37
|
+
type: "",
|
|
38
|
+
layers: [],
|
|
39
|
+
patterns: [],
|
|
40
|
+
dataModel: { entities: [] },
|
|
41
|
+
apiContracts: [],
|
|
42
|
+
dependencies: [],
|
|
43
|
+
tradeoffs: []
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export class ArchitectRole extends BaseRole {
|
|
47
|
+
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
48
|
+
super({ name: "architect", config, logger, emitter });
|
|
49
|
+
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async execute(input) {
|
|
53
|
+
const task = typeof input === "string"
|
|
54
|
+
? input
|
|
55
|
+
: input?.task || this.context?.task || "";
|
|
56
|
+
const onOutput = typeof input === "string" ? null : input?.onOutput || null;
|
|
57
|
+
const researchContext = typeof input === "object" ? input?.researchContext || null : null;
|
|
58
|
+
|
|
59
|
+
const provider = resolveProvider(this.config);
|
|
60
|
+
const agent = this._createAgent(provider, this.config, this.logger);
|
|
61
|
+
|
|
62
|
+
const prompt = buildArchitectPrompt({ task, instructions: this.instructions, researchContext });
|
|
63
|
+
const runArgs = { prompt, role: "architect" };
|
|
64
|
+
if (onOutput) runArgs.onOutput = onOutput;
|
|
65
|
+
const result = await agent.runTask(runArgs);
|
|
66
|
+
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
result: {
|
|
71
|
+
error: result.error || result.output || "Architect failed",
|
|
72
|
+
provider
|
|
73
|
+
},
|
|
74
|
+
summary: `Architect failed: ${result.error || "unknown error"}`,
|
|
75
|
+
usage: result.usage
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const parsed = parseArchitectOutput(result.output);
|
|
81
|
+
if (!parsed) {
|
|
82
|
+
return {
|
|
83
|
+
ok: true,
|
|
84
|
+
result: {
|
|
85
|
+
verdict: "needs_clarification",
|
|
86
|
+
architecture: { ...EMPTY_ARCHITECTURE },
|
|
87
|
+
questions: [],
|
|
88
|
+
raw: result.output,
|
|
89
|
+
provider
|
|
90
|
+
},
|
|
91
|
+
summary: "Architecture complete (unstructured output)",
|
|
92
|
+
usage: result.usage
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
result: {
|
|
99
|
+
verdict: parsed.verdict,
|
|
100
|
+
architecture: parsed.architecture,
|
|
101
|
+
questions: parsed.questions,
|
|
102
|
+
provider
|
|
103
|
+
},
|
|
104
|
+
summary: buildSummary(parsed),
|
|
105
|
+
usage: result.usage
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
result: {
|
|
111
|
+
verdict: "needs_clarification",
|
|
112
|
+
architecture: { ...EMPTY_ARCHITECTURE },
|
|
113
|
+
questions: [],
|
|
114
|
+
raw: result.output,
|
|
115
|
+
provider
|
|
116
|
+
},
|
|
117
|
+
summary: "Architecture complete (unstructured output)",
|
|
118
|
+
usage: result.usage
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
function buildCommitMessage(task) {
|
|
15
15
|
const clean = String(task || "")
|
|
16
|
-
.
|
|
16
|
+
.replaceAll(/\s+/g, " ")
|
|
17
17
|
.trim();
|
|
18
18
|
const prefix = "feat: ";
|
|
19
19
|
const maxBody = 72 - prefix.length;
|
|
@@ -22,7 +22,7 @@ function buildCommitMessage(task) {
|
|
|
22
22
|
|
|
23
23
|
function buildPrTitle(task) {
|
|
24
24
|
const clean = String(task || "")
|
|
25
|
-
.
|
|
25
|
+
.replaceAll(/\s+/g, " ")
|
|
26
26
|
.trim();
|
|
27
27
|
return clean.slice(0, 70) || "Karajan update";
|
|
28
28
|
}
|
|
@@ -10,31 +10,76 @@ function resolveProvider(config) {
|
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function pluralize(count, singular, plural) {
|
|
14
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function collectModeParts(parsed, mode, gapCount) {
|
|
16
18
|
const parts = [];
|
|
17
|
-
if (gapCount > 0) parts.push(`${gapCount
|
|
19
|
+
if (gapCount > 0) parts.push(`${pluralize(gapCount, "gap", "gaps")} found`);
|
|
20
|
+
|
|
18
21
|
if (mode === "momtest") {
|
|
19
22
|
const qCount = parsed.momTestQuestions?.length || 0;
|
|
20
|
-
if (qCount > 0) parts.push(`${qCount
|
|
23
|
+
if (qCount > 0) parts.push(`${pluralize(qCount, "Mom Test question", "Mom Test questions")}`);
|
|
21
24
|
}
|
|
22
25
|
if (mode === "wendel") {
|
|
23
26
|
const failCount = (parsed.wendelChecklist || []).filter(c => c.status === "fail").length;
|
|
24
|
-
if (failCount > 0) parts.push(`${failCount
|
|
25
|
-
else if (gapCount === 0) return "Discovery complete: task is ready";
|
|
27
|
+
if (failCount > 0) parts.push(`${pluralize(failCount, "Wendel condition", "Wendel conditions")} failed`);
|
|
26
28
|
}
|
|
27
29
|
if (mode === "classify" && parsed.classification) {
|
|
28
30
|
parts.push(`type: ${parsed.classification.type}, risk: ${parsed.classification.adoptionRisk}`);
|
|
29
31
|
}
|
|
30
32
|
if (mode === "jtbd") {
|
|
31
33
|
const jCount = parsed.jtbds?.length || 0;
|
|
32
|
-
if (jCount > 0) parts.push(`${jCount
|
|
33
|
-
else if (gapCount === 0) return "Discovery complete: task is ready";
|
|
34
|
+
if (jCount > 0) parts.push(`${pluralize(jCount, "JTBD", "JTBDs")} generated`);
|
|
34
35
|
}
|
|
36
|
+
return parts;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildSummary(parsed, mode) {
|
|
40
|
+
const gapCount = parsed.gaps?.length || 0;
|
|
41
|
+
const parts = collectModeParts(parsed, mode, gapCount);
|
|
42
|
+
if (parts.length === 0) return "Discovery complete: task is ready";
|
|
35
43
|
return `Discovery complete: ${parts.join(", ")} (verdict: ${parsed.verdict})`;
|
|
36
44
|
}
|
|
37
45
|
|
|
46
|
+
function resolveInput(input, context) {
|
|
47
|
+
if (typeof input === "string") {
|
|
48
|
+
return { task: input, onOutput: null, mode: "gaps", context: null };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
task: input?.task || context?.task || "",
|
|
52
|
+
onOutput: input?.onOutput || null,
|
|
53
|
+
mode: input?.mode || "gaps",
|
|
54
|
+
context: input?.context || null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildUnstructuredResult(output, mode, provider, usage) {
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
result: { verdict: "ready", gaps: [], mode, raw: output, provider },
|
|
62
|
+
summary: "Discovery complete (unstructured output)",
|
|
63
|
+
usage
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const MODE_FIELDS = {
|
|
68
|
+
momtest: { key: "momTestQuestions", fallback: [] },
|
|
69
|
+
wendel: { key: "wendelChecklist", fallback: [] },
|
|
70
|
+
classify: { key: "classification", fallback: null },
|
|
71
|
+
jtbd: { key: "jtbds", fallback: [] }
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function buildResultFromParsed(parsed, mode, provider) {
|
|
75
|
+
const resultObj = { verdict: parsed.verdict, gaps: parsed.gaps, mode, provider };
|
|
76
|
+
const fieldDef = MODE_FIELDS[mode];
|
|
77
|
+
if (fieldDef) {
|
|
78
|
+
resultObj[fieldDef.key] = parsed[fieldDef.key] ?? fieldDef.fallback;
|
|
79
|
+
}
|
|
80
|
+
return resultObj;
|
|
81
|
+
}
|
|
82
|
+
|
|
38
83
|
export class DiscoverRole extends BaseRole {
|
|
39
84
|
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
40
85
|
super({ name: "discover", config, logger, emitter });
|
|
@@ -42,13 +87,7 @@ export class DiscoverRole extends BaseRole {
|
|
|
42
87
|
}
|
|
43
88
|
|
|
44
89
|
async execute(input) {
|
|
45
|
-
const task =
|
|
46
|
-
? input
|
|
47
|
-
: input?.task || this.context?.task || "";
|
|
48
|
-
const onOutput = typeof input === "string" ? null : input?.onOutput || null;
|
|
49
|
-
const mode = (typeof input === "object" ? input?.mode : null) || "gaps";
|
|
50
|
-
const context = typeof input === "object" ? input?.context || null : null;
|
|
51
|
-
|
|
90
|
+
const { task, onOutput, mode, context } = resolveInput(input, this.context);
|
|
52
91
|
const provider = resolveProvider(this.config);
|
|
53
92
|
const agent = this._createAgent(provider, this.config, this.logger);
|
|
54
93
|
|
|
@@ -60,11 +99,7 @@ export class DiscoverRole extends BaseRole {
|
|
|
60
99
|
if (!result.ok) {
|
|
61
100
|
return {
|
|
62
101
|
ok: false,
|
|
63
|
-
result: {
|
|
64
|
-
error: result.error || result.output || "Discovery failed",
|
|
65
|
-
provider,
|
|
66
|
-
mode
|
|
67
|
-
},
|
|
102
|
+
result: { error: result.error || result.output || "Discovery failed", provider, mode },
|
|
68
103
|
summary: `Discovery failed: ${result.error || "unknown error"}`,
|
|
69
104
|
usage: result.usage
|
|
70
105
|
};
|
|
@@ -72,59 +107,16 @@ export class DiscoverRole extends BaseRole {
|
|
|
72
107
|
|
|
73
108
|
try {
|
|
74
109
|
const parsed = parseDiscoverOutput(result.output);
|
|
75
|
-
if (!parsed)
|
|
76
|
-
return {
|
|
77
|
-
ok: true,
|
|
78
|
-
result: {
|
|
79
|
-
verdict: "ready",
|
|
80
|
-
gaps: [],
|
|
81
|
-
mode,
|
|
82
|
-
raw: result.output,
|
|
83
|
-
provider
|
|
84
|
-
},
|
|
85
|
-
summary: "Discovery complete (unstructured output)",
|
|
86
|
-
usage: result.usage
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const resultObj = {
|
|
91
|
-
verdict: parsed.verdict,
|
|
92
|
-
gaps: parsed.gaps,
|
|
93
|
-
mode,
|
|
94
|
-
provider
|
|
95
|
-
};
|
|
96
|
-
if (mode === "momtest") {
|
|
97
|
-
resultObj.momTestQuestions = parsed.momTestQuestions || [];
|
|
98
|
-
}
|
|
99
|
-
if (mode === "wendel") {
|
|
100
|
-
resultObj.wendelChecklist = parsed.wendelChecklist || [];
|
|
101
|
-
}
|
|
102
|
-
if (mode === "classify") {
|
|
103
|
-
resultObj.classification = parsed.classification || null;
|
|
104
|
-
}
|
|
105
|
-
if (mode === "jtbd") {
|
|
106
|
-
resultObj.jtbds = parsed.jtbds || [];
|
|
107
|
-
}
|
|
110
|
+
if (!parsed) return buildUnstructuredResult(result.output, mode, provider, result.usage);
|
|
108
111
|
|
|
109
112
|
return {
|
|
110
113
|
ok: true,
|
|
111
|
-
result:
|
|
114
|
+
result: buildResultFromParsed(parsed, mode, provider),
|
|
112
115
|
summary: buildSummary(parsed, mode),
|
|
113
116
|
usage: result.usage
|
|
114
117
|
};
|
|
115
118
|
} catch {
|
|
116
|
-
return
|
|
117
|
-
ok: true,
|
|
118
|
-
result: {
|
|
119
|
-
verdict: "ready",
|
|
120
|
-
gaps: [],
|
|
121
|
-
mode,
|
|
122
|
-
raw: result.output,
|
|
123
|
-
provider
|
|
124
|
-
},
|
|
125
|
-
summary: "Discovery complete (unstructured output)",
|
|
126
|
-
usage: result.usage
|
|
127
|
-
};
|
|
119
|
+
return buildUnstructuredResult(result.output, mode, provider, result.usage);
|
|
128
120
|
}
|
|
129
121
|
}
|
|
130
122
|
}
|
package/src/roles/index.js
CHANGED
|
@@ -21,51 +21,66 @@ function resolvePlannerRuntimeTimeoutMs(config) {
|
|
|
21
21
|
return Math.round(minutes * 60 * 1000);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
|
|
24
|
+
function appendDecompositionSection(sections, triageDecomposition) {
|
|
25
|
+
if (!triageDecomposition?.length) return;
|
|
26
|
+
sections.push("## Triage decomposition recommendation", "The triage stage determined this task should be decomposed. Suggested subtasks:");
|
|
27
|
+
for (let i = 0; i < triageDecomposition.length; i++) {
|
|
28
|
+
sections.push(`${i + 1}. ${triageDecomposition[i]}`);
|
|
29
|
+
}
|
|
30
|
+
sections.push("", "Focus your plan on the FIRST subtask only. List the remaining subtasks as 'pending_subtasks' in your output for documentation.", "");
|
|
31
|
+
}
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
const RESEARCH_FIELDS = [
|
|
34
|
+
{ key: "affected_files", label: "Affected files" },
|
|
35
|
+
{ key: "patterns", label: "Patterns" },
|
|
36
|
+
{ key: "constraints", label: "Constraints" },
|
|
37
|
+
{ key: "risks", label: "Risks" },
|
|
38
|
+
{ key: "prior_decisions", label: "Prior decisions" }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function appendResearchSection(sections, research) {
|
|
42
|
+
if (!research) return;
|
|
43
|
+
sections.push("## Research findings");
|
|
44
|
+
for (const { key, label } of RESEARCH_FIELDS) {
|
|
45
|
+
if (research[key]?.length) {
|
|
46
|
+
sections.push(`${label}: ${research[key].join(", ")}`);
|
|
47
|
+
}
|
|
30
48
|
}
|
|
49
|
+
sections.push("");
|
|
50
|
+
}
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
function appendArchitectSection(sections, architectContext) {
|
|
53
|
+
if (!architectContext) return;
|
|
54
|
+
const arch = architectContext.architecture || {};
|
|
55
|
+
sections.push("## Architecture context");
|
|
56
|
+
if (arch.type) sections.push(`Type: ${arch.type}`);
|
|
57
|
+
if (arch.layers?.length) sections.push(`Layers: ${arch.layers.join(", ")}`);
|
|
58
|
+
if (arch.patterns?.length) sections.push(`Patterns: ${arch.patterns.join(", ")}`);
|
|
59
|
+
if (arch.dataModel?.entities?.length) sections.push(`Data model entities: ${arch.dataModel.entities.join(", ")}`);
|
|
60
|
+
if (arch.apiContracts?.length) sections.push(`API contracts: ${arch.apiContracts.join(", ")}`);
|
|
61
|
+
if (arch.tradeoffs?.length) sections.push(`Tradeoffs: ${arch.tradeoffs.join(", ")}`);
|
|
62
|
+
if (architectContext.summary) sections.push(`Summary: ${architectContext.summary}`);
|
|
34
63
|
sections.push("");
|
|
64
|
+
}
|
|
35
65
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
sections.push("The triage stage determined this task should be decomposed. Suggested subtasks:");
|
|
39
|
-
for (let i = 0; i < triageDecomposition.length; i++) {
|
|
40
|
-
sections.push(`${i + 1}. ${triageDecomposition[i]}`);
|
|
41
|
-
}
|
|
42
|
-
sections.push("");
|
|
43
|
-
sections.push("Focus your plan on the FIRST subtask only. List the remaining subtasks as 'pending_subtasks' in your output for documentation.");
|
|
44
|
-
sections.push("");
|
|
45
|
-
}
|
|
66
|
+
function buildPrompt({ task, instructions, research, triageDecomposition, architectContext }) {
|
|
67
|
+
const sections = [];
|
|
46
68
|
|
|
47
|
-
if (
|
|
48
|
-
sections.push(
|
|
49
|
-
if (research.affected_files?.length) {
|
|
50
|
-
sections.push(`Affected files: ${research.affected_files.join(", ")}`);
|
|
51
|
-
}
|
|
52
|
-
if (research.patterns?.length) {
|
|
53
|
-
sections.push(`Patterns: ${research.patterns.join(", ")}`);
|
|
54
|
-
}
|
|
55
|
-
if (research.constraints?.length) {
|
|
56
|
-
sections.push(`Constraints: ${research.constraints.join(", ")}`);
|
|
57
|
-
}
|
|
58
|
-
if (research.risks?.length) {
|
|
59
|
-
sections.push(`Risks: ${research.risks.join(", ")}`);
|
|
60
|
-
}
|
|
61
|
-
if (research.prior_decisions?.length) {
|
|
62
|
-
sections.push(`Prior decisions: ${research.prior_decisions.join(", ")}`);
|
|
63
|
-
}
|
|
64
|
-
sections.push("");
|
|
69
|
+
if (instructions) {
|
|
70
|
+
sections.push(instructions, "");
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
sections.push(
|
|
68
|
-
|
|
73
|
+
sections.push(
|
|
74
|
+
"Create an implementation plan for this task.",
|
|
75
|
+
"Return concise numbered steps focused on execution order and risk.",
|
|
76
|
+
""
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
appendDecompositionSection(sections, triageDecomposition);
|
|
80
|
+
appendArchitectSection(sections, architectContext);
|
|
81
|
+
appendResearchSection(sections, research);
|
|
82
|
+
|
|
83
|
+
sections.push("## Task", task);
|
|
69
84
|
|
|
70
85
|
return sections.join("\n");
|
|
71
86
|
}
|
|
@@ -83,10 +98,11 @@ export class PlannerRole extends BaseRole {
|
|
|
83
98
|
const taskStr = task || this.context?.task || "";
|
|
84
99
|
const research = this.context?.research || null;
|
|
85
100
|
const triageDecomposition = this.context?.triageDecomposition || null;
|
|
101
|
+
const architectContext = this.context?.architecture || null;
|
|
86
102
|
const provider = resolveProvider(this.config);
|
|
87
103
|
|
|
88
104
|
const agent = this._createAgent(provider, this.config, this.logger);
|
|
89
|
-
const prompt = buildPrompt({ task: taskStr, instructions: this.instructions, research, triageDecomposition });
|
|
105
|
+
const prompt = buildPrompt({ task: taskStr, instructions: this.instructions, research, triageDecomposition, architectContext });
|
|
90
106
|
|
|
91
107
|
const runArgs = { prompt, role: "planner" };
|
|
92
108
|
if (onOutput) runArgs.onOutput = onOutput;
|
|
@@ -13,15 +13,16 @@ function buildPrompt({ task, instructions }) {
|
|
|
13
13
|
const sections = [];
|
|
14
14
|
|
|
15
15
|
if (instructions) {
|
|
16
|
-
sections.push(instructions);
|
|
17
|
-
sections.push("");
|
|
16
|
+
sections.push(instructions, "");
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
sections.push(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
sections.push(
|
|
20
|
+
"Refactor the current changes for clarity and maintainability without changing behavior.",
|
|
21
|
+
"Do not expand scope and keep tests green.",
|
|
22
|
+
"",
|
|
23
|
+
"## Task context",
|
|
24
|
+
task
|
|
25
|
+
);
|
|
25
26
|
|
|
26
27
|
return sections.join("\n");
|
|
27
28
|
}
|
|
@@ -26,17 +26,16 @@ function buildPrompt({ task, instructions }) {
|
|
|
26
26
|
"Investigate the codebase for the following task.",
|
|
27
27
|
"Identify affected files, patterns, constraints, prior decisions, risks, and test coverage.",
|
|
28
28
|
"Return a single valid JSON object with your findings and nothing else.",
|
|
29
|
-
'JSON schema: {"affected_files":[string],"patterns":[string],"constraints":[string],"prior_decisions":[string],"risks":[string],"test_coverage":string}'
|
|
29
|
+
'JSON schema: {"affected_files":[string],"patterns":[string],"constraints":[string],"prior_decisions":[string],"risks":[string],"test_coverage":string}',
|
|
30
|
+
`## Task\n${task}`
|
|
30
31
|
);
|
|
31
32
|
|
|
32
|
-
sections.push(`## Task\n${task}`);
|
|
33
|
-
|
|
34
33
|
return sections.join("\n\n");
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function parseResearchOutput(raw) {
|
|
38
37
|
const text = raw?.trim() || "";
|
|
39
|
-
const jsonMatch =
|
|
38
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
40
39
|
if (!jsonMatch) return null;
|
|
41
40
|
return JSON.parse(jsonMatch[0]);
|
|
42
41
|
}
|
|
@@ -46,9 +45,9 @@ function buildSummary(parsed) {
|
|
|
46
45
|
const risks = parsed.risks?.length || 0;
|
|
47
46
|
const patterns = parsed.patterns?.length || 0;
|
|
48
47
|
const parts = [];
|
|
49
|
-
if (files) parts.push(`${files} file${files
|
|
50
|
-
if (risks) parts.push(`${risks} risk${risks
|
|
51
|
-
if (patterns) parts.push(`${patterns} pattern${patterns
|
|
48
|
+
if (files) parts.push(`${files} file${files === 1 ? "" : "s"}`);
|
|
49
|
+
if (risks) parts.push(`${risks} risk${risks === 1 ? "" : "s"}`);
|
|
50
|
+
if (patterns) parts.push(`${patterns} pattern${patterns === 1 ? "" : "s"}`);
|
|
52
51
|
return parts.length
|
|
53
52
|
? `Research complete: ${parts.join(", ")} identified`
|
|
54
53
|
: "Research complete";
|
|
@@ -33,15 +33,14 @@ function buildPrompt({ task, diff, reviewRules, reviewMode, instructions }) {
|
|
|
33
33
|
sections.push(instructions);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
sections.push(`You are a code reviewer in ${reviewMode || "standard"} mode.`);
|
|
37
36
|
sections.push(
|
|
37
|
+
`You are a code reviewer in ${reviewMode || "standard"} mode.`,
|
|
38
38
|
"Return only one valid JSON object and nothing else.",
|
|
39
39
|
'JSON schema:',
|
|
40
|
-
'{"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}'
|
|
40
|
+
'{"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}',
|
|
41
|
+
`Task context:\n${task}`
|
|
41
42
|
);
|
|
42
43
|
|
|
43
|
-
sections.push(`Task context:\n${task}`);
|
|
44
|
-
|
|
45
44
|
if (reviewRules) {
|
|
46
45
|
sections.push(`Review rules:\n${reviewRules}`);
|
|
47
46
|
}
|
|
@@ -53,7 +52,7 @@ function buildPrompt({ task, diff, reviewRules, reviewMode, instructions }) {
|
|
|
53
52
|
|
|
54
53
|
function parseReviewOutput(raw) {
|
|
55
54
|
const text = raw?.trim() || "";
|
|
56
|
-
const jsonMatch =
|
|
55
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
57
56
|
if (!jsonMatch) {
|
|
58
57
|
throw new Error(`Failed to parse reviewer output: no JSON found`);
|
|
59
58
|
}
|
|
@@ -26,11 +26,10 @@ function buildPrompt({ task, diff, instructions }) {
|
|
|
26
26
|
"You are a security auditor. Analyze the code changes for vulnerabilities.",
|
|
27
27
|
"Check for: OWASP top 10, exposed secrets/API keys, hardcoded credentials, command injection, XSS, SQL injection, path traversal, prototype pollution, insecure dependencies.",
|
|
28
28
|
"Return a single valid JSON object with your findings and nothing else.",
|
|
29
|
-
'JSON schema: {"vulnerabilities":[{"severity":"critical|high|medium|low","category":string,"file":string,"line":number,"description":string,"fix_suggestion":string}],"verdict":"pass"|"fail"}'
|
|
29
|
+
'JSON schema: {"vulnerabilities":[{"severity":"critical|high|medium|low","category":string,"file":string,"line":number,"description":string,"fix_suggestion":string}],"verdict":"pass"|"fail"}',
|
|
30
|
+
`## Task\n${task}`
|
|
30
31
|
);
|
|
31
32
|
|
|
32
|
-
sections.push(`## Task\n${task}`);
|
|
33
|
-
|
|
34
33
|
if (diff) {
|
|
35
34
|
sections.push(`## Git diff to audit\n${diff}`);
|
|
36
35
|
}
|
|
@@ -40,7 +39,7 @@ function buildPrompt({ task, diff, instructions }) {
|
|
|
40
39
|
|
|
41
40
|
function parseSecurityOutput(raw) {
|
|
42
41
|
const text = raw?.trim() || "";
|
|
43
|
-
const jsonMatch =
|
|
42
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
44
43
|
if (!jsonMatch) return null;
|
|
45
44
|
return JSON.parse(jsonMatch[0]);
|
|
46
45
|
}
|