karajan-code 1.15.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 +6 -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 +157 -89
- 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 +2 -0
- package/src/mcp/server-handlers.js +294 -241
- package/src/mcp/server.js +4 -3
- package/src/mcp/tools.js +19 -0
- package/src/orchestrator/agent-fallback.js +1 -3
- package/src/orchestrator/iteration-stages.js +206 -170
- package/src/orchestrator/pre-loop-stages.js +266 -34
- package/src/orchestrator/solomon-rules.js +2 -2
- package/src/orchestrator.js +820 -739
- 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 +228 -0
- 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 +122 -0
- package/src/roles/index.js +2 -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/discover.md +167 -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
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
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 discovering gaps in the task specification."
|
|
5
|
+
].join(" ");
|
|
6
|
+
|
|
7
|
+
export const DISCOVER_MODES = ["gaps", "momtest", "wendel", "classify", "jtbd"];
|
|
8
|
+
|
|
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
|
+
|
|
15
|
+
export function buildDiscoverPrompt({ task, instructions, mode = "gaps", context = null }) {
|
|
16
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
17
|
+
|
|
18
|
+
if (instructions) {
|
|
19
|
+
sections.push(instructions);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
sections.push(
|
|
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
|
+
"## Gap Detection Guidelines",
|
|
26
|
+
[
|
|
27
|
+
"- Look for missing acceptance criteria or requirements",
|
|
28
|
+
"- Identify implicit assumptions that need explicit confirmation",
|
|
29
|
+
"- Find ambiguities where multiple interpretations exist",
|
|
30
|
+
"- Check for contradictions between different parts of the spec",
|
|
31
|
+
"- Consider edge cases and error scenarios not addressed",
|
|
32
|
+
"- Classify each gap by severity: critical (blocks implementation), major (could cause rework), minor (reasonable default exists)"
|
|
33
|
+
].join("\n")
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (mode === "momtest") {
|
|
37
|
+
sections.push(
|
|
38
|
+
"## Mom Test Rules",
|
|
39
|
+
[
|
|
40
|
+
"For each gap, generate questions that follow The Mom Test principles:",
|
|
41
|
+
"- ALWAYS ask about past behavior and real experiences, never hypothetical scenarios",
|
|
42
|
+
"- NEVER ask 'Would you...?', 'Do you think...?', 'Would it be useful if...?'",
|
|
43
|
+
"- ALWAYS ask 'When was the last time...?', 'How do you currently...?', 'What happened when...?'",
|
|
44
|
+
"- Ask about specifics, not generalities",
|
|
45
|
+
"- Each question must have a targetRole (who to ask) and rationale (why this matters)",
|
|
46
|
+
"",
|
|
47
|
+
"Examples of BAD questions (hypothetical/opinion):",
|
|
48
|
+
" - 'Would you use this feature?' -> opinion, not data",
|
|
49
|
+
" - 'Do you think users need this?' -> speculation",
|
|
50
|
+
"",
|
|
51
|
+
"Examples of GOOD questions (past behavior):",
|
|
52
|
+
" - 'When was the last time you had to do X manually?' -> real experience",
|
|
53
|
+
" - 'How are you currently handling Y?' -> current behavior",
|
|
54
|
+
" - 'What happened the last time Z failed?' -> real consequence"
|
|
55
|
+
].join("\n")
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (mode === "wendel") {
|
|
60
|
+
sections.push(
|
|
61
|
+
"## Wendel Behavior Change Checklist",
|
|
62
|
+
[
|
|
63
|
+
"Evaluate whether the task implies a user behavior change. If it does, assess these 5 conditions:",
|
|
64
|
+
"",
|
|
65
|
+
"1. **CUE** — Is there a clear trigger that will prompt the user to take the new action?",
|
|
66
|
+
"2. **REACTION** — Will the user have a positive emotional reaction when they encounter the cue?",
|
|
67
|
+
"3. **EVALUATION** — Can the user quickly understand the value of the new behavior?",
|
|
68
|
+
"4. **ABILITY** — Does the user have the skill and resources to perform the new behavior?",
|
|
69
|
+
"5. **TIMING** — Is this the right moment to introduce this change?",
|
|
70
|
+
"",
|
|
71
|
+
"For each condition, set status to: pass, fail, unknown, or not_applicable",
|
|
72
|
+
"If the task does NOT imply behavior change (e.g., internal refactor, backend optimization), set ALL conditions to 'not_applicable'",
|
|
73
|
+
"If ANY condition is 'fail', set verdict to 'needs_validation'"
|
|
74
|
+
].join("\n")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (mode === "classify") {
|
|
79
|
+
sections.push(
|
|
80
|
+
"## Behavior Change Classification",
|
|
81
|
+
[
|
|
82
|
+
"Classify the task by its impact on user behavior:",
|
|
83
|
+
"",
|
|
84
|
+
"- **START**: User must adopt a completely new behavior or workflow",
|
|
85
|
+
"- **STOP**: User must stop doing something they currently do (highest resistance risk)",
|
|
86
|
+
"- **DIFFERENT**: User must do something they already do, but differently",
|
|
87
|
+
"- **not_applicable**: Task has no user behavior impact (internal refactor, backend, infra)",
|
|
88
|
+
"",
|
|
89
|
+
"Assess adoption risk: none (no user impact), low, medium, high",
|
|
90
|
+
"STOP changes carry the highest risk of resistance — always flag them",
|
|
91
|
+
"Provide a frictionEstimate explaining the expected friction"
|
|
92
|
+
].join("\n")
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (mode === "jtbd") {
|
|
97
|
+
sections.push(
|
|
98
|
+
"## Jobs-to-be-Done Framework",
|
|
99
|
+
[
|
|
100
|
+
"Generate reinforced Jobs-to-be-Done from the task and any provided context (interview notes, field observations).",
|
|
101
|
+
"Each JTBD must include 5 layers:",
|
|
102
|
+
"",
|
|
103
|
+
"- **functional**: The practical job the user is trying to accomplish",
|
|
104
|
+
"- **emotionalPersonal**: How the user wants to feel personally",
|
|
105
|
+
"- **emotionalSocial**: How the user wants to be perceived by others",
|
|
106
|
+
"- **behaviorChange**: Type of change: START, STOP, DIFFERENT, or not_applicable",
|
|
107
|
+
"- **evidence**: Direct quotes or specific references from the context. If no context provided, set to 'not_available' and suggest what context is needed",
|
|
108
|
+
"",
|
|
109
|
+
"CRITICAL: evidence must contain real quotes or references from the provided context, NEVER invented assumptions",
|
|
110
|
+
"If no context is provided, mark evidence as 'not_available'"
|
|
111
|
+
].join("\n")
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const baseSchema = '{"verdict":"ready|needs_validation","gaps":[{"id":string,"description":string,"severity":"critical|major|minor","suggestedQuestion":string}]';
|
|
116
|
+
const momtestSchema = mode === "momtest"
|
|
117
|
+
? ',"momTestQuestions":[{"gapId":string,"question":string,"targetRole":string,"rationale":string}]'
|
|
118
|
+
: "";
|
|
119
|
+
const wendelSchema = mode === "wendel"
|
|
120
|
+
? ',"wendelChecklist":[{"condition":"CUE|REACTION|EVALUATION|ABILITY|TIMING","status":"pass|fail|unknown|not_applicable","justification":string}]'
|
|
121
|
+
: "";
|
|
122
|
+
const classifySchema = mode === "classify"
|
|
123
|
+
? ',"classification":{"type":"START|STOP|DIFFERENT|not_applicable","adoptionRisk":"none|low|medium|high","frictionEstimate":string}'
|
|
124
|
+
: "";
|
|
125
|
+
const jtbdSchema = mode === "jtbd"
|
|
126
|
+
? ',"jtbds":[{"id":string,"functional":string,"emotionalPersonal":string,"emotionalSocial":string,"behaviorChange":"START|STOP|DIFFERENT|not_applicable","evidence":string}]'
|
|
127
|
+
: "";
|
|
128
|
+
|
|
129
|
+
sections.push(
|
|
130
|
+
"Return a single valid JSON object and nothing else.",
|
|
131
|
+
`JSON schema: ${baseSchema}${momtestSchema}${wendelSchema}${classifySchema}${jtbdSchema},"summary":string}`
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (context) {
|
|
135
|
+
sections.push(`## Context\n${context}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
sections.push(`## Task\n${task}`);
|
|
139
|
+
|
|
140
|
+
return sections.join("\n\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
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
|
+
}
|
|
157
|
+
|
|
158
|
+
function parseGaps(rawGaps) {
|
|
159
|
+
return (Array.isArray(rawGaps) ? rawGaps : [])
|
|
160
|
+
.filter((g) => g?.id && g.description && g.suggestedQuestion)
|
|
161
|
+
.map((g) => ({
|
|
162
|
+
id: g.id,
|
|
163
|
+
description: g.description,
|
|
164
|
+
severity: VALID_SEVERITIES.has(String(g.severity).toLowerCase())
|
|
165
|
+
? String(g.severity).toLowerCase()
|
|
166
|
+
: "major",
|
|
167
|
+
suggestedQuestion: g.suggestedQuestion
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseMomTestQuestions(rawQuestions) {
|
|
172
|
+
return (Array.isArray(rawQuestions) ? rawQuestions : [])
|
|
173
|
+
.filter((q) => q?.gapId && q.question && q.targetRole && q.rationale)
|
|
174
|
+
.map((q) => ({
|
|
175
|
+
gapId: q.gapId,
|
|
176
|
+
question: q.question,
|
|
177
|
+
targetRole: q.targetRole,
|
|
178
|
+
rationale: q.rationale
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parseWendelChecklist(rawChecklist) {
|
|
183
|
+
return (Array.isArray(rawChecklist) ? rawChecklist : [])
|
|
184
|
+
.filter((c) => c?.condition && c.justification && c.status)
|
|
185
|
+
.map((c) => ({
|
|
186
|
+
condition: c.condition,
|
|
187
|
+
status: VALID_WENDEL_STATUSES.has(String(c.status).toLowerCase())
|
|
188
|
+
? String(c.status).toLowerCase()
|
|
189
|
+
: "unknown",
|
|
190
|
+
justification: c.justification
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
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)
|
|
197
|
+
.map((j) => ({
|
|
198
|
+
id: j.id,
|
|
199
|
+
functional: j.functional,
|
|
200
|
+
emotionalPersonal: j.emotionalPersonal,
|
|
201
|
+
emotionalSocial: j.emotionalSocial,
|
|
202
|
+
behaviorChange: j.behaviorChange,
|
|
203
|
+
evidence: j.evidence
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
|
|
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;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
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),
|
|
226
|
+
summary: parsed.summary || ""
|
|
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
|
|