karajan-code 1.16.0 → 1.18.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 +174 -93
- package/src/git/automation.js +3 -4
- package/src/guards/intent-guard.js +123 -0
- package/src/guards/output-guard.js +158 -0
- package/src/guards/perf-guard.js +126 -0
- 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 +902 -746
- 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/kj.config.yml +33 -0
- package/templates/roles/architect.md +62 -0
- package/templates/roles/planner.md +1 -0
|
@@ -7,31 +7,27 @@ const CARD_ID_PATTERN = /[A-Z0-9]{2,5}-(?:TSK|BUG|PCS|PRP|SPR|QA)-\d{4}/;
|
|
|
7
7
|
|
|
8
8
|
export function parseCardId(text) {
|
|
9
9
|
if (!text) return null;
|
|
10
|
-
const match = String(text)
|
|
10
|
+
const match = CARD_ID_PATTERN.exec(String(text));
|
|
11
11
|
return match ? match[0] : null;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
const parts = [`## ${card.cardId}: ${card.title}`];
|
|
16
|
-
|
|
14
|
+
function appendDescriptionSection(parts, card) {
|
|
17
15
|
if (card.descriptionStructured?.length) {
|
|
18
16
|
parts.push("", "### User Story");
|
|
19
17
|
for (const s of card.descriptionStructured) {
|
|
20
|
-
parts.push(`- **Como** ${s.role}`);
|
|
21
|
-
parts.push(` **Quiero** ${s.goal}`);
|
|
22
|
-
parts.push(` **Para** ${s.benefit}`);
|
|
18
|
+
parts.push(`- **Como** ${s.role}`, ` **Quiero** ${s.goal}`, ` **Para** ${s.benefit}`);
|
|
23
19
|
}
|
|
24
20
|
} else if (card.description) {
|
|
25
21
|
parts.push("", "### Description", card.description);
|
|
26
22
|
}
|
|
23
|
+
}
|
|
27
24
|
|
|
25
|
+
function appendAcceptanceCriteriaSection(parts, card) {
|
|
28
26
|
if (card.acceptanceCriteriaStructured?.length) {
|
|
29
27
|
parts.push("", "### Acceptance Criteria");
|
|
30
28
|
for (const ac of card.acceptanceCriteriaStructured) {
|
|
31
29
|
if (ac.given && ac.when && ac.then) {
|
|
32
|
-
parts.push(`- **Given** ${ac.given}`);
|
|
33
|
-
parts.push(` **When** ${ac.when}`);
|
|
34
|
-
parts.push(` **Then** ${ac.then}`);
|
|
30
|
+
parts.push(`- **Given** ${ac.given}`, ` **When** ${ac.when}`, ` **Then** ${ac.then}`);
|
|
35
31
|
} else if (ac.raw) {
|
|
36
32
|
parts.push(`- ${ac.raw}`);
|
|
37
33
|
}
|
|
@@ -39,24 +35,31 @@ export function buildTaskFromCard(card) {
|
|
|
39
35
|
} else if (card.acceptanceCriteria) {
|
|
40
36
|
parts.push("", "### Acceptance Criteria", card.acceptanceCriteria);
|
|
41
37
|
}
|
|
38
|
+
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
40
|
+
function appendImplementationPlanSection(parts, card) {
|
|
41
|
+
if (!card.implementationPlan) return;
|
|
42
|
+
const plan = card.implementationPlan;
|
|
43
|
+
parts.push("", "### Implementation Plan");
|
|
44
|
+
if (plan.approach) parts.push(`**Approach:** ${plan.approach}`);
|
|
45
|
+
if (plan.steps?.length) {
|
|
46
|
+
parts.push("**Steps:**");
|
|
47
|
+
for (const step of plan.steps) {
|
|
48
|
+
parts.push(`1. ${step.description}`);
|
|
52
49
|
}
|
|
53
50
|
}
|
|
51
|
+
}
|
|
54
52
|
|
|
53
|
+
export function buildTaskFromCard(card) {
|
|
54
|
+
const parts = [`## ${card.cardId}: ${card.title}`];
|
|
55
|
+
appendDescriptionSection(parts, card);
|
|
56
|
+
appendAcceptanceCriteriaSection(parts, card);
|
|
57
|
+
appendImplementationPlanSection(parts, card);
|
|
55
58
|
return parts.join("\n");
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export function buildCommitsPayload(gitLog) {
|
|
59
|
-
if (!gitLog
|
|
62
|
+
if (!gitLog?.length) return [];
|
|
60
63
|
return gitLog.map((entry) => ({
|
|
61
64
|
hash: entry.hash,
|
|
62
65
|
message: entry.message,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic ADR generation from architect tradeoffs.
|
|
3
|
+
* Creates ADRs in Planning Game when PG is linked, or returns suggestions otherwise.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function buildAdr({ tradeoff, taskTitle }) {
|
|
7
|
+
return {
|
|
8
|
+
title: tradeoff,
|
|
9
|
+
status: "accepted",
|
|
10
|
+
context: `Architecture decision for task: ${taskTitle}`,
|
|
11
|
+
decision: tradeoff
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function createArchitectADRs({ tradeoffs, pgTaskId, pgProject, taskTitle, mcpClient }) {
|
|
16
|
+
if (!tradeoffs?.length) {
|
|
17
|
+
return { created: 0, adrs: [] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hasPg = Boolean(pgTaskId && pgProject && mcpClient);
|
|
21
|
+
|
|
22
|
+
if (!hasPg) {
|
|
23
|
+
const adrs = tradeoffs.map(tradeoff => ({
|
|
24
|
+
...buildAdr({ tradeoff, taskTitle }),
|
|
25
|
+
suggestion: true
|
|
26
|
+
}));
|
|
27
|
+
return { created: 0, adrs };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const adrs = [];
|
|
31
|
+
let created = 0;
|
|
32
|
+
|
|
33
|
+
for (const tradeoff of tradeoffs) {
|
|
34
|
+
const adr = buildAdr({ tradeoff, taskTitle });
|
|
35
|
+
try {
|
|
36
|
+
await mcpClient.createAdr({ projectId: pgProject, adr });
|
|
37
|
+
adrs.push(adr);
|
|
38
|
+
created++;
|
|
39
|
+
} catch {
|
|
40
|
+
// Log warning but don't block pipeline for a single ADR failure
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { created, adrs };
|
|
45
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Requires planning_game.api_url in config or PG_API_URL env var.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { withRetry
|
|
12
|
+
import { withRetry } from "../utils/retry.js";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_API_URL = "http://localhost:3000/api";
|
|
15
15
|
const DEFAULT_TIMEOUT_MS = 10000;
|
|
@@ -104,6 +104,20 @@ export async function createCard({ projectId, card, timeoutMs = DEFAULT_TIMEOUT_
|
|
|
104
104
|
return parseJsonResponse(response);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
export async function createAdr({ projectId, adr, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
108
|
+
const url = `${getApiUrl()}/projects/${encodeURIComponent(projectId)}/adrs`;
|
|
109
|
+
const response = await fetchWithRetry(
|
|
110
|
+
url,
|
|
111
|
+
{
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify(adr)
|
|
115
|
+
},
|
|
116
|
+
timeoutMs
|
|
117
|
+
);
|
|
118
|
+
return parseJsonResponse(response);
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
export async function relateCards({ projectId, sourceCardId, targetCardId, relationType, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
108
122
|
const url = `${getApiUrl()}/projects/${encodeURIComponent(projectId)}/cards/relate`;
|
|
109
123
|
const response = await fetchWithRetry(
|
|
@@ -74,10 +74,12 @@ export function buildDecompositionQuestion(subtasks, parentCardId) {
|
|
|
74
74
|
for (let i = 0; i < subtasks.length; i++) {
|
|
75
75
|
lines.push(`${i + 1}. ${subtasks[i]}`);
|
|
76
76
|
}
|
|
77
|
-
lines.push(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
lines.push(
|
|
78
|
+
"",
|
|
79
|
+
`Create these as linked cards in Planning Game (parent: ${parentCardId})?`,
|
|
80
|
+
"Each subtask will block the next one (sequential chain).",
|
|
81
|
+
"",
|
|
82
|
+
"Reply: yes / no"
|
|
83
|
+
);
|
|
82
84
|
return lines.join("\n");
|
|
83
85
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const SUBAGENT_PREAMBLE = [
|
|
2
|
+
"IMPORTANT: You are running as a Karajan sub-agent.",
|
|
3
|
+
"Do NOT ask about using Karajan, do NOT mention Karajan, do NOT suggest orchestration.",
|
|
4
|
+
"Do NOT use any MCP tools. Focus only on designing the architecture for the task."
|
|
5
|
+
].join(" ");
|
|
6
|
+
|
|
7
|
+
export const VALID_VERDICTS = new Set(["ready", "needs_clarification"]);
|
|
8
|
+
|
|
9
|
+
export function buildArchitectPrompt({ task, instructions, researchContext = null }) {
|
|
10
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
11
|
+
|
|
12
|
+
if (instructions) {
|
|
13
|
+
sections.push(instructions);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
sections.push(
|
|
17
|
+
"You are the architect in a multi-role AI pipeline.",
|
|
18
|
+
"Analyze the task and produce a concrete architecture design including layers, patterns, data model, API contracts, dependencies, and tradeoffs.",
|
|
19
|
+
"## Architecture Guidelines",
|
|
20
|
+
[
|
|
21
|
+
"- Identify the architecture type (layered, microservices, event-driven, etc.)",
|
|
22
|
+
"- Define the layers and their responsibilities",
|
|
23
|
+
"- Identify design patterns to apply",
|
|
24
|
+
"- Define the data model with entities",
|
|
25
|
+
"- Specify API contracts (endpoints, events, interfaces)",
|
|
26
|
+
"- List external and internal dependencies",
|
|
27
|
+
"- Document tradeoffs and their rationale",
|
|
28
|
+
"- If critical decisions cannot be made without more information, list clarifying questions"
|
|
29
|
+
].join("\n"),
|
|
30
|
+
"Return a single valid JSON object and nothing else.",
|
|
31
|
+
'JSON schema: {"verdict":"ready|needs_clarification","architecture":{"type":string,"layers":[string],"patterns":[string],"dataModel":{"entities":[string]},"apiContracts":[string],"dependencies":[string],"tradeoffs":[string]},"questions":[string],"summary":string}'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (researchContext) {
|
|
35
|
+
sections.push(`## Research Context\n${researchContext}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sections.push(`## Task\n${task}`);
|
|
39
|
+
|
|
40
|
+
return sections.join("\n\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function filterStrings(arr) {
|
|
44
|
+
if (!Array.isArray(arr)) return [];
|
|
45
|
+
return arr.filter((item) => typeof item === "string");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseArchitectOutput(raw) {
|
|
49
|
+
const text = raw?.trim() || "";
|
|
50
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
51
|
+
if (!jsonMatch) return null;
|
|
52
|
+
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const verdict = VALID_VERDICTS.has(parsed.verdict)
|
|
61
|
+
? parsed.verdict
|
|
62
|
+
: "needs_clarification";
|
|
63
|
+
|
|
64
|
+
const arch = parsed.architecture && typeof parsed.architecture === "object"
|
|
65
|
+
? parsed.architecture
|
|
66
|
+
: {};
|
|
67
|
+
|
|
68
|
+
const dataModel = arch.dataModel && typeof arch.dataModel === "object"
|
|
69
|
+
? arch.dataModel
|
|
70
|
+
: {};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
verdict,
|
|
74
|
+
architecture: {
|
|
75
|
+
type: typeof arch.type === "string" ? arch.type : "",
|
|
76
|
+
layers: filterStrings(arch.layers),
|
|
77
|
+
patterns: filterStrings(arch.patterns),
|
|
78
|
+
dataModel: {
|
|
79
|
+
entities: filterStrings(dataModel.entities)
|
|
80
|
+
},
|
|
81
|
+
apiContracts: filterStrings(arch.apiContracts),
|
|
82
|
+
dependencies: filterStrings(arch.dependencies),
|
|
83
|
+
tradeoffs: filterStrings(arch.tradeoffs)
|
|
84
|
+
},
|
|
85
|
+
questions: filterStrings(parsed.questions),
|
|
86
|
+
summary: parsed.summary || ""
|
|
87
|
+
};
|
|
88
|
+
}
|
package/src/prompts/discover.js
CHANGED
|
@@ -6,11 +6,11 @@ const SUBAGENT_PREAMBLE = [
|
|
|
6
6
|
|
|
7
7
|
export const DISCOVER_MODES = ["gaps", "momtest", "wendel", "classify", "jtbd"];
|
|
8
8
|
|
|
9
|
-
const VALID_VERDICTS = ["ready", "needs_validation"];
|
|
10
|
-
const VALID_SEVERITIES = ["critical", "major", "minor"];
|
|
11
|
-
const VALID_WENDEL_STATUSES = ["pass", "fail", "unknown", "not_applicable"];
|
|
12
|
-
const VALID_CLASSIFY_TYPES = ["START", "STOP", "DIFFERENT", "not_applicable"];
|
|
13
|
-
const VALID_ADOPTION_RISKS = ["none", "low", "medium", "high"];
|
|
9
|
+
const VALID_VERDICTS = new Set(["ready", "needs_validation"]);
|
|
10
|
+
const VALID_SEVERITIES = new Set(["critical", "major", "minor"]);
|
|
11
|
+
const VALID_WENDEL_STATUSES = new Set(["pass", "fail", "unknown", "not_applicable"]);
|
|
12
|
+
const VALID_CLASSIFY_TYPES = new Set(["START", "STOP", "DIFFERENT", "not_applicable"]);
|
|
13
|
+
const VALID_ADOPTION_RISKS = new Set(["none", "low", "medium", "high"]);
|
|
14
14
|
|
|
15
15
|
export function buildDiscoverPrompt({ task, instructions, mode = "gaps", context = null }) {
|
|
16
16
|
const sections = [SUBAGENT_PREAMBLE];
|
|
@@ -21,10 +21,7 @@ export function buildDiscoverPrompt({ task, instructions, mode = "gaps", context
|
|
|
21
21
|
|
|
22
22
|
sections.push(
|
|
23
23
|
"You are a task discovery agent for Karajan Code, a multi-agent coding orchestrator.",
|
|
24
|
-
"Analyze the following task and identify gaps, ambiguities, missing information, and implicit assumptions."
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
sections.push(
|
|
24
|
+
"Analyze the following task and identify gaps, ambiguities, missing information, and implicit assumptions.",
|
|
28
25
|
"## Gap Detection Guidelines",
|
|
29
26
|
[
|
|
30
27
|
"- Look for missing acceptance criteria or requirements",
|
|
@@ -143,56 +140,60 @@ export function buildDiscoverPrompt({ task, instructions, mode = "gaps", context
|
|
|
143
140
|
return sections.join("\n\n");
|
|
144
141
|
}
|
|
145
142
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
function parseClassification(raw) {
|
|
144
|
+
if (!raw || typeof raw !== "object") return null;
|
|
145
|
+
const rawType = String(raw.type || "").toUpperCase();
|
|
146
|
+
let type;
|
|
147
|
+
if (rawType === "NOT_APPLICABLE") type = "not_applicable";
|
|
148
|
+
else if (VALID_CLASSIFY_TYPES.has(rawType)) type = rawType;
|
|
149
|
+
else type = "not_applicable";
|
|
150
|
+
const rawRisk = String(raw.adoptionRisk || "").toLowerCase();
|
|
151
|
+
return {
|
|
152
|
+
type,
|
|
153
|
+
adoptionRisk: VALID_ADOPTION_RISKS.has(rawRisk) ? rawRisk : "medium",
|
|
154
|
+
frictionEstimate: raw.frictionEstimate || ""
|
|
155
|
+
};
|
|
156
|
+
}
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.filter((g) => g
|
|
158
|
+
function parseGaps(rawGaps) {
|
|
159
|
+
return (Array.isArray(rawGaps) ? rawGaps : [])
|
|
160
|
+
.filter((g) => g?.id && g.description && g.suggestedQuestion)
|
|
163
161
|
.map((g) => ({
|
|
164
162
|
id: g.id,
|
|
165
163
|
description: g.description,
|
|
166
|
-
severity: VALID_SEVERITIES.
|
|
164
|
+
severity: VALID_SEVERITIES.has(String(g.severity).toLowerCase())
|
|
167
165
|
? String(g.severity).toLowerCase()
|
|
168
166
|
: "major",
|
|
169
167
|
suggestedQuestion: g.suggestedQuestion
|
|
170
168
|
}));
|
|
169
|
+
}
|
|
171
170
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.filter((q) => q
|
|
171
|
+
function parseMomTestQuestions(rawQuestions) {
|
|
172
|
+
return (Array.isArray(rawQuestions) ? rawQuestions : [])
|
|
173
|
+
.filter((q) => q?.gapId && q.question && q.targetRole && q.rationale)
|
|
175
174
|
.map((q) => ({
|
|
176
175
|
gapId: q.gapId,
|
|
177
176
|
question: q.question,
|
|
178
177
|
targetRole: q.targetRole,
|
|
179
178
|
rationale: q.rationale
|
|
180
179
|
}));
|
|
180
|
+
}
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.filter((c) => c
|
|
182
|
+
function parseWendelChecklist(rawChecklist) {
|
|
183
|
+
return (Array.isArray(rawChecklist) ? rawChecklist : [])
|
|
184
|
+
.filter((c) => c?.condition && c.justification && c.status)
|
|
185
185
|
.map((c) => ({
|
|
186
186
|
condition: c.condition,
|
|
187
|
-
status: VALID_WENDEL_STATUSES.
|
|
187
|
+
status: VALID_WENDEL_STATUSES.has(String(c.status).toLowerCase())
|
|
188
188
|
? String(c.status).toLowerCase()
|
|
189
189
|
: "unknown",
|
|
190
190
|
justification: c.justification
|
|
191
191
|
}));
|
|
192
|
+
}
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
.filter((j) => j
|
|
194
|
+
function parseJtbds(rawJtbds) {
|
|
195
|
+
return (Array.isArray(rawJtbds) ? rawJtbds : [])
|
|
196
|
+
.filter((j) => j?.id && j.functional && j.emotionalPersonal && j.emotionalSocial && j.behaviorChange && j.evidence)
|
|
196
197
|
.map((j) => ({
|
|
197
198
|
id: j.id,
|
|
198
199
|
functional: j.functional,
|
|
@@ -201,27 +202,27 @@ export function parseDiscoverOutput(raw) {
|
|
|
201
202
|
behaviorChange: j.behaviorChange,
|
|
202
203
|
evidence: j.evidence
|
|
203
204
|
}));
|
|
205
|
+
}
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
207
|
+
export function parseDiscoverOutput(raw) {
|
|
208
|
+
const text = raw?.trim() || "";
|
|
209
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
210
|
+
if (!jsonMatch) return null;
|
|
211
|
+
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
return {
|
|
219
|
-
verdict,
|
|
220
|
-
gaps,
|
|
221
|
-
momTestQuestions,
|
|
222
|
-
wendelChecklist,
|
|
223
|
-
classification,
|
|
224
|
-
jtbds,
|
|
220
|
+
verdict: VALID_VERDICTS.has(parsed.verdict) ? parsed.verdict : "ready",
|
|
221
|
+
gaps: parseGaps(parsed.gaps),
|
|
222
|
+
momTestQuestions: parseMomTestQuestions(parsed.momTestQuestions),
|
|
223
|
+
wendelChecklist: parseWendelChecklist(parsed.wendelChecklist),
|
|
224
|
+
classification: parseClassification(parsed.classification),
|
|
225
|
+
jtbds: parseJtbds(parsed.jtbds),
|
|
225
226
|
summary: parsed.summary || ""
|
|
226
227
|
};
|
|
227
228
|
}
|
package/src/prompts/planner.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
function extractStepText(line) {
|
|
2
|
+
const numberedStep = /^\d+[).:-]\s*(.+)$/.exec(line);
|
|
3
|
+
if (numberedStep) return numberedStep[1].trim();
|
|
4
|
+
const bulletStep = /^[-*]\s+(.+)$/.exec(line);
|
|
5
|
+
if (bulletStep) return bulletStep[1].trim();
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function classifyLine(line, state) {
|
|
10
|
+
if (!state.title) {
|
|
11
|
+
const titleMatch = /^title\s*:\s*(.+)$/i.exec(line);
|
|
12
|
+
if (titleMatch) return { type: "title", value: titleMatch[1].trim() };
|
|
13
|
+
}
|
|
14
|
+
if (!state.approach) {
|
|
15
|
+
const approachMatch = /^(approach|strategy)\s*:\s*(.+)$/i.exec(line);
|
|
16
|
+
if (approachMatch) return { type: "approach", value: approachMatch[2].trim() };
|
|
17
|
+
}
|
|
18
|
+
const stepText = extractStepText(line);
|
|
19
|
+
if (stepText) return { type: "step", value: stepText };
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
export function parsePlannerOutput(output) {
|
|
2
24
|
const text = String(output || "").trim();
|
|
3
25
|
if (!text) return null;
|
|
@@ -7,49 +29,42 @@ export function parsePlannerOutput(output) {
|
|
|
7
29
|
.map((line) => line.trim())
|
|
8
30
|
.filter(Boolean);
|
|
9
31
|
|
|
10
|
-
|
|
11
|
-
let approach = null;
|
|
32
|
+
const state = { title: null, approach: null };
|
|
12
33
|
const steps = [];
|
|
13
34
|
|
|
14
35
|
for (const line of lines) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
36
|
+
const classified = classifyLine(line, state);
|
|
37
|
+
if (!classified) continue;
|
|
38
|
+
if (classified.type === "title") state.title = classified.value;
|
|
39
|
+
else if (classified.type === "approach") state.approach = classified.value;
|
|
40
|
+
else if (classified.type === "step") steps.push(classified.value);
|
|
41
|
+
}
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
43
|
+
if (!state.title) {
|
|
44
|
+
const firstFreeLine = lines.find((line) => !/^(approach|strategy)\s*:/i.test(line) && !/^\d+[).:-]\s*/.test(line));
|
|
45
|
+
state.title = firstFreeLine || null;
|
|
46
|
+
}
|
|
30
47
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
steps.push(numberedStep[1].trim());
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
48
|
+
return { title: state.title, approach: state.approach, steps };
|
|
49
|
+
}
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
}
|
|
51
|
+
function formatArchitectContext(architectContext) {
|
|
52
|
+
if (!architectContext) return null;
|
|
53
|
+
const arch = architectContext.architecture || {};
|
|
54
|
+
const lines = ["## Architecture Context"];
|
|
43
55
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
56
|
+
if (arch.type) lines.push(`**Type:** ${arch.type}`);
|
|
57
|
+
if (arch.layers?.length) lines.push(`**Layers:** ${arch.layers.join(", ")}`);
|
|
58
|
+
if (arch.patterns?.length) lines.push(`**Patterns:** ${arch.patterns.join(", ")}`);
|
|
59
|
+
if (arch.dataModel?.entities?.length) lines.push(`**Data model entities:** ${arch.dataModel.entities.join(", ")}`);
|
|
60
|
+
if (arch.apiContracts?.length) lines.push(`**API contracts:** ${arch.apiContracts.join(", ")}`);
|
|
61
|
+
if (arch.tradeoffs?.length) lines.push(`**Tradeoffs:** ${arch.tradeoffs.join(", ")}`);
|
|
62
|
+
if (architectContext.summary) lines.push(`**Summary:** ${architectContext.summary}`);
|
|
48
63
|
|
|
49
|
-
return
|
|
64
|
+
return lines.length > 1 ? lines.join("\n") : null;
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
export function buildPlannerPrompt({ task, context }) {
|
|
67
|
+
export function buildPlannerPrompt({ task, context, architectContext }) {
|
|
53
68
|
const parts = [
|
|
54
69
|
"You are an expert software architect. Create an implementation plan for the following task.",
|
|
55
70
|
"",
|
|
@@ -62,6 +77,11 @@ export function buildPlannerPrompt({ task, context }) {
|
|
|
62
77
|
parts.push("## Context", context, "");
|
|
63
78
|
}
|
|
64
79
|
|
|
80
|
+
const archSection = formatArchitectContext(architectContext);
|
|
81
|
+
if (archSection) {
|
|
82
|
+
parts.push(archSection, "");
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
parts.push(
|
|
66
86
|
"## Output format",
|
|
67
87
|
"Respond with a JSON object containing:",
|
package/src/prompts/triage.js
CHANGED
|
@@ -10,7 +10,8 @@ const ROLE_DESCRIPTIONS = [
|
|
|
10
10
|
{ role: "tester", description: "Runs dedicated testing pass after coding. Ensures tests exist and pass." },
|
|
11
11
|
{ role: "security", description: "Audits code for security vulnerabilities. Checks auth, input validation, injection risks." },
|
|
12
12
|
{ role: "refactorer", description: "Cleans up and refactors code after the main implementation." },
|
|
13
|
-
{ role: "reviewer", description: "Reviews the code diff for quality issues. Standard quality gate." }
|
|
13
|
+
{ role: "reviewer", description: "Reviews the code diff for quality issues. Standard quality gate." },
|
|
14
|
+
{ role: "architect", description: "Designs solution architecture — layers, patterns, data model, API contracts, tradeoffs. Activate when task creates new module/app, affects data model/APIs, complexity is medium/complex, or design is ambiguous." }
|
|
14
15
|
];
|
|
15
16
|
|
|
16
17
|
export function buildTriagePrompt({ task, instructions, availableRoles }) {
|
|
@@ -24,15 +25,9 @@ export function buildTriagePrompt({ task, instructions, availableRoles }) {
|
|
|
24
25
|
|
|
25
26
|
sections.push(
|
|
26
27
|
"You are a task triage agent for Karajan Code, a multi-agent coding orchestrator.",
|
|
27
|
-
"Analyze the following task and determine which pipeline roles should be activated."
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
sections.push(
|
|
28
|
+
"Analyze the following task and determine which pipeline roles should be activated.",
|
|
31
29
|
"## Available Roles",
|
|
32
|
-
roles.map((r) => `- **${r.role}**: ${r.description}`).join("\n")
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
sections.push(
|
|
30
|
+
roles.map((r) => `- **${r.role}**: ${r.description}`).join("\n"),
|
|
36
31
|
"## Decision Guidelines",
|
|
37
32
|
[
|
|
38
33
|
"- **planner**: Enable for complex tasks (multi-file, architectural changes, data model changes). Disable for simple fixes.",
|
|
@@ -41,20 +36,17 @@ export function buildTriagePrompt({ task, instructions, availableRoles }) {
|
|
|
41
36
|
"- **security**: Enable for authentication, APIs, user input handling, data access, external integrations. Disable for UI-only or doc changes.",
|
|
42
37
|
"- **refactorer**: Enable only when explicitly requested or when the task is a refactoring task.",
|
|
43
38
|
"- **reviewer**: Enable for most tasks as a quality gate. Disable only for trivial, single-line changes.",
|
|
39
|
+
"- **architect**: Enable when creating new modules/apps, changing data models or APIs, medium/complex tasks, or when design approach is ambiguous. Disable for simple fixes, doc-only, or CSS-only changes.",
|
|
44
40
|
"",
|
|
45
41
|
"Note: coder is ALWAYS active — you don't need to decide on it."
|
|
46
|
-
].join("\n")
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
sections.push(
|
|
42
|
+
].join("\n"),
|
|
50
43
|
"Classify the task complexity, determine its taskType, recommend only the necessary pipeline roles, and assess whether the task should be decomposed into smaller subtasks.",
|
|
51
44
|
"Keep the reasoning short and practical.",
|
|
52
45
|
"Return a single valid JSON object and nothing else.",
|
|
53
|
-
'JSON schema: {"level":"trivial|simple|medium|complex","roles":["planner|researcher|refactorer|reviewer|tester|security"],"taskType":"sw|infra|doc|add-tests|refactor","reasoning":string,"shouldDecompose":boolean,"subtasks":string[]}'
|
|
46
|
+
'JSON schema: {"level":"trivial|simple|medium|complex","roles":["planner|researcher|refactorer|reviewer|tester|security|architect"],"taskType":"sw|infra|doc|add-tests|refactor","reasoning":string,"shouldDecompose":boolean,"subtasks":string[]}',
|
|
47
|
+
`## Task\n${task}`
|
|
54
48
|
);
|
|
55
49
|
|
|
56
|
-
sections.push(`## Task\n${task}`);
|
|
57
|
-
|
|
58
50
|
return sections.join("\n\n");
|
|
59
51
|
}
|
|
60
52
|
|
package/src/review/parser.js
CHANGED
|
@@ -22,33 +22,32 @@ export function parseMaybeJsonString(value) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (payload.approved !== undefined && payload.blocking_issues !== undefined) {
|
|
29
|
-
return payload;
|
|
30
|
-
}
|
|
25
|
+
function isReviewPayload(obj) {
|
|
26
|
+
return obj?.approved !== undefined && obj?.blocking_issues !== undefined;
|
|
27
|
+
}
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return item;
|
|
37
|
-
}
|
|
29
|
+
function findReviewInArray(arr) {
|
|
30
|
+
for (let i = arr.length - 1; i >= 0; i -= 1) {
|
|
31
|
+
const item = arr[i];
|
|
32
|
+
if (isReviewPayload(item)) return item;
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
34
|
+
const nested = item?.result || item?.message?.content?.[0]?.text;
|
|
35
|
+
if (typeof nested === "string") {
|
|
36
|
+
const parsedNested = parseMaybeJsonString(nested);
|
|
37
|
+
if (parsedNested?.approved !== undefined) return parsedNested;
|
|
44
38
|
}
|
|
45
|
-
return null;
|
|
46
39
|
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function normalizeReviewPayload(payload) {
|
|
44
|
+
if (!payload) return null;
|
|
45
|
+
if (isReviewPayload(payload)) return payload;
|
|
46
|
+
if (Array.isArray(payload)) return findReviewInArray(payload);
|
|
47
47
|
|
|
48
48
|
if (typeof payload.result === "string") {
|
|
49
49
|
const parsedResult = parseMaybeJsonString(payload.result);
|
|
50
50
|
if (parsedResult?.approved !== undefined) return parsedResult;
|
|
51
|
-
return null;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
return null;
|