karajan-code 1.27.0 → 1.28.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/cli.js +13 -0
- package/src/commands/audit.js +99 -0
- package/src/config.js +1 -1
- package/src/mcp/run-kj.js +5 -2
- package/src/mcp/server-handlers.js +86 -16
- package/src/mcp/tools.js +21 -0
- package/src/prompts/audit.js +210 -0
- package/src/roles/audit-role.js +105 -0
- package/src/roles/index.js +1 -0
- package/templates/roles/audit.md +68 -0
- package/templates/skills/kj-audit.md +63 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import { discoverCommand } from "./commands/discover.js";
|
|
|
22
22
|
import { triageCommand } from "./commands/triage.js";
|
|
23
23
|
import { researcherCommand } from "./commands/researcher.js";
|
|
24
24
|
import { architectCommand } from "./commands/architect.js";
|
|
25
|
+
import { auditCommand } from "./commands/audit.js";
|
|
25
26
|
|
|
26
27
|
const PKG_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
27
28
|
const PKG_VERSION = JSON.parse(readFileSync(PKG_PATH, "utf8")).version;
|
|
@@ -253,6 +254,18 @@ program
|
|
|
253
254
|
});
|
|
254
255
|
});
|
|
255
256
|
|
|
257
|
+
program
|
|
258
|
+
.command("audit")
|
|
259
|
+
.description("Analyze codebase health (read-only)")
|
|
260
|
+
.argument("[task]")
|
|
261
|
+
.option("--dimensions <list>", "Comma-separated: security,quality,performance,architecture,testing", "all")
|
|
262
|
+
.option("--json", "Output raw JSON")
|
|
263
|
+
.action(async (task, flags) => {
|
|
264
|
+
await withConfig("audit", flags, async ({ config, logger }) => {
|
|
265
|
+
await auditCommand({ task: task || "Analyze the full codebase", config, logger, dimensions: flags.dimensions, json: flags.json });
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
256
269
|
program
|
|
257
270
|
.command("resume")
|
|
258
271
|
.description("Resume a paused session")
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createAgent } from "../agents/index.js";
|
|
2
|
+
import { assertAgentsAvailable } from "../agents/availability.js";
|
|
3
|
+
import { resolveRole } from "../config.js";
|
|
4
|
+
import { buildAuditPrompt, parseAuditOutput, AUDIT_DIMENSIONS } from "../prompts/audit.js";
|
|
5
|
+
|
|
6
|
+
function formatFindings(findings) {
|
|
7
|
+
const lines = [];
|
|
8
|
+
for (const f of findings) {
|
|
9
|
+
const loc = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : "";
|
|
10
|
+
const rule = f.rule ? ` [${f.rule}]` : "";
|
|
11
|
+
lines.push(` - [${f.severity.toUpperCase()}] ${loc}${rule}`);
|
|
12
|
+
lines.push(` ${f.description}`);
|
|
13
|
+
if (f.recommendation) lines.push(` Fix: ${f.recommendation}`);
|
|
14
|
+
}
|
|
15
|
+
return lines;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatDimension(name, dim) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
lines.push(`### ${name} — Score: ${dim.score}`);
|
|
21
|
+
if (dim.findings.length === 0) {
|
|
22
|
+
lines.push(" No issues found.");
|
|
23
|
+
} else {
|
|
24
|
+
lines.push(...formatFindings(dim.findings));
|
|
25
|
+
}
|
|
26
|
+
lines.push("");
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatRecommendations(recs) {
|
|
31
|
+
const lines = ["## Top Recommendations", ""];
|
|
32
|
+
for (const r of recs) {
|
|
33
|
+
lines.push(`${r.priority}. [${r.dimension}] ${r.action} (impact: ${r.impact}, effort: ${r.effort})`);
|
|
34
|
+
}
|
|
35
|
+
lines.push("");
|
|
36
|
+
return lines;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DIMENSION_LABELS = {
|
|
40
|
+
security: "Security",
|
|
41
|
+
codeQuality: "Code Quality",
|
|
42
|
+
performance: "Performance",
|
|
43
|
+
architecture: "Architecture",
|
|
44
|
+
testing: "Testing"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function formatAudit(parsed) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push("## Codebase Health Report");
|
|
50
|
+
lines.push(`**Overall Health:** ${parsed.summary.overallHealth}`);
|
|
51
|
+
lines.push(`**Total Findings:** ${parsed.summary.totalFindings} (${parsed.summary.critical} critical, ${parsed.summary.high} high, ${parsed.summary.medium} medium, ${parsed.summary.low} low)`);
|
|
52
|
+
lines.push("");
|
|
53
|
+
|
|
54
|
+
for (const dim of AUDIT_DIMENSIONS) {
|
|
55
|
+
if (parsed.dimensions[dim]) {
|
|
56
|
+
lines.push(...formatDimension(DIMENSION_LABELS[dim] || dim, parsed.dimensions[dim]));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (parsed.topRecommendations?.length) {
|
|
61
|
+
lines.push(...formatRecommendations(parsed.topRecommendations));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (parsed.textSummary) lines.push(`---\n${parsed.textSummary}`);
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function auditCommand({ task, config, logger, dimensions, json }) {
|
|
69
|
+
const auditRole = resolveRole(config, "audit");
|
|
70
|
+
await assertAgentsAvailable([auditRole.provider]);
|
|
71
|
+
logger.info(`Audit (${auditRole.provider}) starting...`);
|
|
72
|
+
|
|
73
|
+
const agent = createAgent(auditRole.provider, config, logger);
|
|
74
|
+
const dimList = dimensions && dimensions !== "all"
|
|
75
|
+
? dimensions.split(",").map(d => d.trim().toLowerCase()).map(d => d === "quality" ? "codeQuality" : d).filter(d => AUDIT_DIMENSIONS.includes(d))
|
|
76
|
+
: null;
|
|
77
|
+
|
|
78
|
+
const prompt = buildAuditPrompt({ task: task || "Analyze the full codebase", dimensions: dimList });
|
|
79
|
+
const onOutput = ({ line }) => process.stdout.write(`${line}\n`);
|
|
80
|
+
const result = await agent.runTask({ prompt, onOutput, role: "audit" });
|
|
81
|
+
|
|
82
|
+
if (!result.ok) {
|
|
83
|
+
throw new Error(result.error || result.output || "Audit failed");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const parsed = parseAuditOutput(result.output);
|
|
87
|
+
|
|
88
|
+
if (json) {
|
|
89
|
+
console.log(JSON.stringify(parsed || result.output, null, 2));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parsed?.summary) {
|
|
94
|
+
console.log(formatAudit(parsed));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(result.output);
|
|
97
|
+
}
|
|
98
|
+
logger.info("Audit completed.");
|
|
99
|
+
}
|
package/src/config.js
CHANGED
|
@@ -414,7 +414,7 @@ export function resolveRole(config, role) {
|
|
|
414
414
|
let provider = roleConfig.provider ?? null;
|
|
415
415
|
if (!provider && role === "coder") provider = legacyCoder;
|
|
416
416
|
if (!provider && role === "reviewer") provider = legacyReviewer;
|
|
417
|
-
if (!provider && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "impeccable" || role === "triage" || role === "discover" || role === "architect")) {
|
|
417
|
+
if (!provider && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "impeccable" || role === "triage" || role === "discover" || role === "architect" || role === "audit")) {
|
|
418
418
|
provider = roles.coder?.provider || legacyCoder;
|
|
419
419
|
}
|
|
420
420
|
|
package/src/mcp/run-kj.js
CHANGED
|
@@ -76,11 +76,14 @@ export async function runKjCommand({ command, commandArgs = [], options = {}, en
|
|
|
76
76
|
|
|
77
77
|
const timeout = options.timeoutMs ? Number(options.timeoutMs) : undefined;
|
|
78
78
|
|
|
79
|
-
const
|
|
79
|
+
const execOpts = {
|
|
80
80
|
env: runEnv,
|
|
81
81
|
reject: false,
|
|
82
82
|
timeout
|
|
83
|
-
}
|
|
83
|
+
};
|
|
84
|
+
if (options.projectDir) execOpts.cwd = options.projectDir;
|
|
85
|
+
|
|
86
|
+
const result = await execa("node", args, execOpts);
|
|
84
87
|
|
|
85
88
|
const ok = result.exitCode === 0;
|
|
86
89
|
const payload = {
|
|
@@ -25,20 +25,37 @@ import { currentBranch } from "../utils/git.js";
|
|
|
25
25
|
import { isPreflightAcked, ackPreflight, getSessionOverrides } from "./preflight.js";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Resolve the user's project directory
|
|
29
|
-
*
|
|
28
|
+
* Resolve the user's project directory.
|
|
29
|
+
* Priority: 1) explicit projectDir param, 2) MCP roots, 3) error with instructions.
|
|
30
30
|
*/
|
|
31
|
-
async function resolveProjectDir(server) {
|
|
31
|
+
async function resolveProjectDir(server, explicitProjectDir) {
|
|
32
|
+
// 1. Explicit projectDir from tool parameter — always wins
|
|
33
|
+
if (explicitProjectDir) return explicitProjectDir;
|
|
34
|
+
|
|
35
|
+
// 2. MCP roots (host-provided workspace directory)
|
|
32
36
|
try {
|
|
33
37
|
const { roots } = await server.listRoots();
|
|
34
38
|
if (roots?.length > 0) {
|
|
35
39
|
const uri = roots[0].uri;
|
|
36
|
-
// MCP roots use file:// URIs
|
|
37
40
|
if (uri.startsWith("file://")) return new URL(uri).pathname;
|
|
38
41
|
return uri;
|
|
39
42
|
}
|
|
40
43
|
} catch { /* client may not support roots */ }
|
|
41
|
-
|
|
44
|
+
|
|
45
|
+
// 3. Check if process.cwd() looks like a real project (has package.json or .git)
|
|
46
|
+
const cwd = process.cwd();
|
|
47
|
+
try {
|
|
48
|
+
const hasGit = await fs.access(`${cwd}/.git`).then(() => true).catch(() => false);
|
|
49
|
+
const hasPkg = await fs.access(`${cwd}/package.json`).then(() => true).catch(() => false);
|
|
50
|
+
if (hasGit || hasPkg) return cwd;
|
|
51
|
+
} catch { /* ignore */ }
|
|
52
|
+
|
|
53
|
+
// 4. No valid project directory — fail with clear instructions
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Cannot determine project directory. The MCP server is running from "${cwd}" which does not appear to be your project. ` +
|
|
56
|
+
`Fix: pass the "projectDir" parameter with the absolute path to your project (e.g., projectDir: "/home/user/my-project"), ` +
|
|
57
|
+
`or run "kj init" inside your project directory.`
|
|
58
|
+
);
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
export function asObject(value) {
|
|
@@ -260,7 +277,7 @@ export async function handleRunDirect(a, server, extra) {
|
|
|
260
277
|
if (config.pipeline?.security?.enabled) requiredProviders.push(resolveRole(config, "security").provider);
|
|
261
278
|
await assertAgentsAvailable(requiredProviders);
|
|
262
279
|
|
|
263
|
-
const projectDir = await resolveProjectDir(server);
|
|
280
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
264
281
|
const runLog = createRunLog(projectDir);
|
|
265
282
|
runLog.logText(`[kj_run] started — task="${a.task.slice(0, 80)}..."`);
|
|
266
283
|
|
|
@@ -293,7 +310,7 @@ export async function handleResumeDirect(a, server, extra) {
|
|
|
293
310
|
const config = await buildConfig(a);
|
|
294
311
|
const logger = createLogger(config.output.log_level, "mcp");
|
|
295
312
|
|
|
296
|
-
const projectDir = await resolveProjectDir(server);
|
|
313
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
297
314
|
const runLog = createRunLog(projectDir);
|
|
298
315
|
runLog.logText(`[kj_resume] started — session="${a.sessionId}"`);
|
|
299
316
|
|
|
@@ -349,7 +366,7 @@ export async function handlePlanDirect(a, server, extra) {
|
|
|
349
366
|
const plannerRole = resolveRole(config, "planner");
|
|
350
367
|
await assertAgentsAvailable([plannerRole.provider]);
|
|
351
368
|
|
|
352
|
-
const projectDir = await resolveProjectDir(server);
|
|
369
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
353
370
|
const runLog = createRunLog(projectDir);
|
|
354
371
|
const silenceTimeoutMs = Number(config?.session?.max_agent_silence_minutes) > 0
|
|
355
372
|
? Math.round(Number(config.session.max_agent_silence_minutes) * 60 * 1000)
|
|
@@ -416,7 +433,7 @@ export async function handleCodeDirect(a, server, extra) {
|
|
|
416
433
|
const coderRole = resolveRole(config, "coder");
|
|
417
434
|
await assertAgentsAvailable([coderRole.provider]);
|
|
418
435
|
|
|
419
|
-
const projectDir = await resolveProjectDir(server);
|
|
436
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
420
437
|
const runLog = createRunLog(projectDir);
|
|
421
438
|
runLog.logText(`[kj_code] started — provider=${coderRole.provider}`);
|
|
422
439
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -465,7 +482,7 @@ export async function handleReviewDirect(a, server, extra) {
|
|
|
465
482
|
const reviewerRole = resolveRole(config, "reviewer");
|
|
466
483
|
await assertAgentsAvailable([reviewerRole.provider, config.reviewer_options?.fallback_reviewer]);
|
|
467
484
|
|
|
468
|
-
const projectDir = await resolveProjectDir(server);
|
|
485
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
469
486
|
const runLog = createRunLog(projectDir);
|
|
470
487
|
runLog.logText(`[kj_review] started — provider=${reviewerRole.provider}`);
|
|
471
488
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -512,7 +529,7 @@ export async function handleDiscoverDirect(a, server, extra) {
|
|
|
512
529
|
const discoverRole = resolveRole(config, "discover");
|
|
513
530
|
await assertAgentsAvailable([discoverRole.provider]);
|
|
514
531
|
|
|
515
|
-
const projectDir = await resolveProjectDir(server);
|
|
532
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
516
533
|
const runLog = createRunLog(projectDir);
|
|
517
534
|
runLog.logText(`[kj_discover] started — mode=${a.mode || "gaps"}`);
|
|
518
535
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -565,7 +582,7 @@ export async function handleTriageDirect(a, server, extra) {
|
|
|
565
582
|
const triageRole = resolveRole(config, "triage");
|
|
566
583
|
await assertAgentsAvailable([triageRole.provider]);
|
|
567
584
|
|
|
568
|
-
const projectDir = await resolveProjectDir(server);
|
|
585
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
569
586
|
const runLog = createRunLog(projectDir);
|
|
570
587
|
runLog.logText(`[kj_triage] started`);
|
|
571
588
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -609,7 +626,7 @@ export async function handleResearcherDirect(a, server, extra) {
|
|
|
609
626
|
const researcherRole = resolveRole(config, "researcher");
|
|
610
627
|
await assertAgentsAvailable([researcherRole.provider]);
|
|
611
628
|
|
|
612
|
-
const projectDir = await resolveProjectDir(server);
|
|
629
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
613
630
|
const runLog = createRunLog(projectDir);
|
|
614
631
|
runLog.logText(`[kj_researcher] started`);
|
|
615
632
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -646,6 +663,54 @@ export async function handleResearcherDirect(a, server, extra) {
|
|
|
646
663
|
return { ok: true, ...result.result, summary: result.summary };
|
|
647
664
|
}
|
|
648
665
|
|
|
666
|
+
export async function handleAuditDirect(a, server, extra) {
|
|
667
|
+
const config = await buildConfig(a, "audit");
|
|
668
|
+
const logger = createLogger(config.output.log_level, "mcp");
|
|
669
|
+
|
|
670
|
+
const auditRole = resolveRole(config, "audit");
|
|
671
|
+
await assertAgentsAvailable([auditRole.provider]);
|
|
672
|
+
|
|
673
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
674
|
+
const runLog = createRunLog(projectDir);
|
|
675
|
+
runLog.logText(`[kj_audit] started — dimensions=${a.dimensions || "all"}`);
|
|
676
|
+
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
677
|
+
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
678
|
+
const onOutput = ({ stream, line }) => {
|
|
679
|
+
emitter.emit("progress", { type: "agent:output", stage: "audit", message: line, detail: { stream, agent: auditRole.provider } });
|
|
680
|
+
};
|
|
681
|
+
const stallDetector = createStallDetector({
|
|
682
|
+
onOutput, emitter, eventBase, stage: "audit", provider: auditRole.provider
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
const { AuditRole } = await import("../roles/audit-role.js");
|
|
686
|
+
const audit = new AuditRole({ config, logger, emitter });
|
|
687
|
+
await audit.init({ task: a.task || "Analyze the full codebase" });
|
|
688
|
+
|
|
689
|
+
sendTrackerLog(server, "audit", "running", auditRole.provider);
|
|
690
|
+
runLog.logText(`[audit] agent launched, waiting for response...`);
|
|
691
|
+
let result;
|
|
692
|
+
try {
|
|
693
|
+
result = await audit.run({
|
|
694
|
+
task: a.task || "Analyze the full codebase",
|
|
695
|
+
dimensions: a.dimensions || null,
|
|
696
|
+
onOutput: stallDetector.onOutput
|
|
697
|
+
});
|
|
698
|
+
} finally {
|
|
699
|
+
stallDetector.stop();
|
|
700
|
+
const stats = stallDetector.stats();
|
|
701
|
+
runLog.logText(`[audit] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
702
|
+
runLog.close();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (!result.ok) {
|
|
706
|
+
sendTrackerLog(server, "audit", "failed");
|
|
707
|
+
throw new Error(result.result?.error || result.summary || "Audit failed");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
sendTrackerLog(server, "audit", "done");
|
|
711
|
+
return { ok: true, ...result.result, summary: result.summary };
|
|
712
|
+
}
|
|
713
|
+
|
|
649
714
|
export async function handleArchitectDirect(a, server, extra) {
|
|
650
715
|
const config = await buildConfig(a, "architect");
|
|
651
716
|
const logger = createLogger(config.output.log_level, "mcp");
|
|
@@ -653,7 +718,7 @@ export async function handleArchitectDirect(a, server, extra) {
|
|
|
653
718
|
const architectRole = resolveRole(config, "architect");
|
|
654
719
|
await assertAgentsAvailable([architectRole.provider]);
|
|
655
720
|
|
|
656
|
-
const projectDir = await resolveProjectDir(server);
|
|
721
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
657
722
|
const runLog = createRunLog(projectDir);
|
|
658
723
|
runLog.logText(`[kj_architect] started`);
|
|
659
724
|
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
@@ -780,7 +845,7 @@ function buildReportArgs(a) {
|
|
|
780
845
|
|
|
781
846
|
async function handleStatus(a, server) {
|
|
782
847
|
const maxLines = a.lines || 50;
|
|
783
|
-
const projectDir = await resolveProjectDir(server);
|
|
848
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
784
849
|
return readRunLog(projectDir, maxLines);
|
|
785
850
|
}
|
|
786
851
|
|
|
@@ -938,6 +1003,10 @@ async function handleArchitect(a, server, extra) {
|
|
|
938
1003
|
return handleArchitectDirect(a, server, extra);
|
|
939
1004
|
}
|
|
940
1005
|
|
|
1006
|
+
async function handleAudit(a, server, extra) {
|
|
1007
|
+
return handleAuditDirect(a, server, extra);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
941
1010
|
/* ── Handler dispatch map ─────────────────────────────────────────── */
|
|
942
1011
|
|
|
943
1012
|
const toolHandlers = {
|
|
@@ -958,7 +1027,8 @@ const toolHandlers = {
|
|
|
958
1027
|
kj_discover: (a, server, extra) => handleDiscover(a, server, extra),
|
|
959
1028
|
kj_triage: (a, server, extra) => handleTriage(a, server, extra),
|
|
960
1029
|
kj_researcher: (a, server, extra) => handleResearcher(a, server, extra),
|
|
961
|
-
kj_architect: (a, server, extra) => handleArchitect(a, server, extra)
|
|
1030
|
+
kj_architect: (a, server, extra) => handleArchitect(a, server, extra),
|
|
1031
|
+
kj_audit: (a, server, extra) => handleAudit(a, server, extra)
|
|
962
1032
|
};
|
|
963
1033
|
|
|
964
1034
|
export async function handleToolCall(name, args, server, extra) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -53,6 +53,7 @@ export const tools = [
|
|
|
53
53
|
required: ["task"],
|
|
54
54
|
properties: {
|
|
55
55
|
task: { type: "string", description: "Task description for the coder (can include a Planning Game card ID like KJC-TSK-0042)" },
|
|
56
|
+
projectDir: { type: "string", description: "Absolute path to the project directory. Required when KJ MCP server runs from a different directory than the target project." },
|
|
56
57
|
pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as task context and updates card status on completion." },
|
|
57
58
|
pgProject: { type: "string", description: "Planning Game project ID (e.g., 'Karajan Code'). Required when pgTask is used." },
|
|
58
59
|
planner: { type: "string" },
|
|
@@ -112,6 +113,7 @@ export const tools = [
|
|
|
112
113
|
properties: {
|
|
113
114
|
sessionId: { type: "string", description: "Session ID to resume" },
|
|
114
115
|
answer: { type: "string", description: "Answer to the question that caused the pause" },
|
|
116
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
115
117
|
kjHome: { type: "string" }
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -185,6 +187,7 @@ export const tools = [
|
|
|
185
187
|
task: { type: "string" },
|
|
186
188
|
coder: { type: "string" },
|
|
187
189
|
coderModel: { type: "string" },
|
|
190
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
188
191
|
kjHome: { type: "string" }
|
|
189
192
|
}
|
|
190
193
|
}
|
|
@@ -200,6 +203,7 @@ export const tools = [
|
|
|
200
203
|
reviewer: { type: "string" },
|
|
201
204
|
reviewerModel: { type: "string" },
|
|
202
205
|
baseRef: { type: "string" },
|
|
206
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
203
207
|
kjHome: { type: "string" }
|
|
204
208
|
}
|
|
205
209
|
}
|
|
@@ -226,6 +230,7 @@ export const tools = [
|
|
|
226
230
|
plannerModel: { type: "string" },
|
|
227
231
|
coder: { type: "string", description: "Legacy alias for planner" },
|
|
228
232
|
coderModel: { type: "string", description: "Legacy alias for plannerModel" },
|
|
233
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
229
234
|
kjHome: { type: "string" }
|
|
230
235
|
}
|
|
231
236
|
}
|
|
@@ -242,6 +247,7 @@ export const tools = [
|
|
|
242
247
|
context: { type: "string", description: "Additional context for the analysis (e.g., research output)" },
|
|
243
248
|
pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as additional context." },
|
|
244
249
|
pgProject: { type: "string", description: "Planning Game project ID. Required when pgTask is used." },
|
|
250
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
245
251
|
kjHome: { type: "string" }
|
|
246
252
|
}
|
|
247
253
|
}
|
|
@@ -254,6 +260,7 @@ export const tools = [
|
|
|
254
260
|
required: ["task"],
|
|
255
261
|
properties: {
|
|
256
262
|
task: { type: "string", description: "Task description to classify" },
|
|
263
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
257
264
|
kjHome: { type: "string" }
|
|
258
265
|
}
|
|
259
266
|
}
|
|
@@ -266,6 +273,7 @@ export const tools = [
|
|
|
266
273
|
required: ["task"],
|
|
267
274
|
properties: {
|
|
268
275
|
task: { type: "string", description: "Task description to research" },
|
|
276
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
269
277
|
kjHome: { type: "string" }
|
|
270
278
|
}
|
|
271
279
|
}
|
|
@@ -279,6 +287,19 @@ export const tools = [
|
|
|
279
287
|
properties: {
|
|
280
288
|
task: { type: "string", description: "Task description to architect" },
|
|
281
289
|
context: { type: "string", description: "Additional context (e.g., researcher output)" },
|
|
290
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
291
|
+
kjHome: { type: "string" }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "kj_audit",
|
|
297
|
+
description: "Analyze codebase health without modifying files. Returns findings across security, code quality, performance, architecture, and testing dimensions.",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
projectDir: { type: "string", description: "Absolute path to the project to audit" },
|
|
302
|
+
dimensions: { type: "string", description: "Comma-separated dimensions to analyze: security,codeQuality,performance,architecture,testing (default: all)" },
|
|
282
303
|
kjHome: { type: "string" }
|
|
283
304
|
}
|
|
284
305
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
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 auditing the codebase for health, quality, and risks.",
|
|
5
|
+
"DO NOT modify any files — this is a read-only analysis. Only use: Read, Grep, Glob, Bash (for analysis commands like wc, find, git log, du, npm ls)."
|
|
6
|
+
].join(" ");
|
|
7
|
+
|
|
8
|
+
export const AUDIT_DIMENSIONS = ["security", "codeQuality", "performance", "architecture", "testing"];
|
|
9
|
+
|
|
10
|
+
const VALID_HEALTH = new Set(["good", "fair", "poor", "critical"]);
|
|
11
|
+
const VALID_SCORES = new Set(["A", "B", "C", "D", "F"]);
|
|
12
|
+
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low"]);
|
|
13
|
+
const VALID_IMPACT = new Set(["high", "medium", "low"]);
|
|
14
|
+
|
|
15
|
+
export function buildAuditPrompt({ task, instructions, dimensions = null, context = null }) {
|
|
16
|
+
const sections = [SUBAGENT_PREAMBLE];
|
|
17
|
+
|
|
18
|
+
if (instructions) {
|
|
19
|
+
sections.push(instructions);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
sections.push(
|
|
23
|
+
"You are a codebase auditor for Karajan Code, a multi-agent coding orchestrator.",
|
|
24
|
+
"Analyze the project across multiple dimensions and produce a comprehensive health report.",
|
|
25
|
+
"DO NOT modify any files. This is a READ-ONLY analysis."
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const activeDimensions = dimensions
|
|
29
|
+
? dimensions.filter(d => AUDIT_DIMENSIONS.includes(d))
|
|
30
|
+
: AUDIT_DIMENSIONS;
|
|
31
|
+
|
|
32
|
+
if (activeDimensions.includes("security")) {
|
|
33
|
+
sections.push(
|
|
34
|
+
"## Security Analysis",
|
|
35
|
+
[
|
|
36
|
+
"- Hardcoded secrets, API keys, tokens in source code",
|
|
37
|
+
"- SQL/NoSQL injection vectors",
|
|
38
|
+
"- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML, eval)",
|
|
39
|
+
"- Command injection (exec, spawn with user input)",
|
|
40
|
+
"- Insecure dependencies (check package.json for known vulnerable packages)",
|
|
41
|
+
"- Missing input validation at system boundaries",
|
|
42
|
+
"- Authentication/authorization gaps"
|
|
43
|
+
].join("\n")
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (activeDimensions.includes("codeQuality")) {
|
|
48
|
+
sections.push(
|
|
49
|
+
"## Code Quality Analysis (SOLID, DRY, KISS, YAGNI)",
|
|
50
|
+
[
|
|
51
|
+
"- Functions/methods longer than 50 lines",
|
|
52
|
+
"- Files longer than 500 lines",
|
|
53
|
+
"- Duplicated code blocks (same logic in multiple places)",
|
|
54
|
+
"- God classes/modules (too many responsibilities)",
|
|
55
|
+
"- Deep nesting (>4 levels)",
|
|
56
|
+
"- Dead code (unused exports, unreachable branches)",
|
|
57
|
+
"- Missing error handling (uncaught promises, empty catches)",
|
|
58
|
+
"- Over-engineering (abstractions for single use)"
|
|
59
|
+
].join("\n")
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (activeDimensions.includes("performance")) {
|
|
64
|
+
sections.push(
|
|
65
|
+
"## Performance Analysis",
|
|
66
|
+
[
|
|
67
|
+
"- N+1 query patterns",
|
|
68
|
+
"- Synchronous file I/O in request handlers",
|
|
69
|
+
"- Missing pagination on list endpoints",
|
|
70
|
+
"- Large bundle imports (importing entire libraries for one function)",
|
|
71
|
+
"- Missing lazy loading",
|
|
72
|
+
"- Expensive operations in loops",
|
|
73
|
+
"- Missing caching opportunities"
|
|
74
|
+
].join("\n")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (activeDimensions.includes("architecture")) {
|
|
79
|
+
sections.push(
|
|
80
|
+
"## Architecture Analysis",
|
|
81
|
+
[
|
|
82
|
+
"- Circular dependencies",
|
|
83
|
+
"- Layer violations (UI importing from data layer directly)",
|
|
84
|
+
"- Coupling between modules (shared mutable state)",
|
|
85
|
+
"- Missing dependency injection",
|
|
86
|
+
"- Inconsistent patterns across the codebase",
|
|
87
|
+
"- Missing or outdated documentation",
|
|
88
|
+
"- Configuration scattered vs centralized"
|
|
89
|
+
].join("\n")
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (activeDimensions.includes("testing")) {
|
|
94
|
+
sections.push(
|
|
95
|
+
"## Testing Analysis",
|
|
96
|
+
[
|
|
97
|
+
"- Test coverage gaps (source files without corresponding tests)",
|
|
98
|
+
"- Test quality (assertions per test, meaningful test names)",
|
|
99
|
+
"- Missing edge case coverage",
|
|
100
|
+
"- Test isolation (shared state between tests)",
|
|
101
|
+
"- Flaky test indicators (timeouts, sleep, retries)"
|
|
102
|
+
].join("\n")
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sections.push(
|
|
107
|
+
"Return a single valid JSON object and nothing else.",
|
|
108
|
+
'JSON schema: {"ok":true,"result":{"summary":{"overallHealth":"good|fair|poor|critical","totalFindings":number,"critical":number,"high":number,"medium":number,"low":number},"dimensions":{"security":{"score":"A|B|C|D|F","findings":[]},"codeQuality":{"score":"A|B|C|D|F","findings":[]},"performance":{"score":"A|B|C|D|F","findings":[]},"architecture":{"score":"A|B|C|D|F","findings":[]},"testing":{"score":"A|B|C|D|F","findings":[]}},"topRecommendations":[{"priority":number,"dimension":string,"action":string,"impact":"high|medium|low","effort":"high|medium|low"}]},"summary":string}',
|
|
109
|
+
'Each finding: {"severity":"critical|high|medium|low","file":string,"line":number,"rule":string,"description":string,"recommendation":string}',
|
|
110
|
+
`Only include dimensions you were asked to analyze: ${activeDimensions.join(", ")}`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (context) {
|
|
114
|
+
sections.push(`## Context\n${context}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sections.push(`## Task\n${task}`);
|
|
118
|
+
|
|
119
|
+
return sections.join("\n\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseFinding(raw) {
|
|
123
|
+
if (!raw || typeof raw !== "object") return null;
|
|
124
|
+
const severity = String(raw.severity || "").toLowerCase();
|
|
125
|
+
if (!VALID_SEVERITIES.has(severity)) return null;
|
|
126
|
+
return {
|
|
127
|
+
severity,
|
|
128
|
+
file: raw.file || "",
|
|
129
|
+
line: typeof raw.line === "number" ? raw.line : 0,
|
|
130
|
+
rule: raw.rule || "",
|
|
131
|
+
description: raw.description || "",
|
|
132
|
+
recommendation: raw.recommendation || ""
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseDimension(raw) {
|
|
137
|
+
if (!raw || typeof raw !== "object") return { score: "F", findings: [] };
|
|
138
|
+
const score = VALID_SCORES.has(raw.score) ? raw.score : "F";
|
|
139
|
+
const findings = (Array.isArray(raw.findings) ? raw.findings : [])
|
|
140
|
+
.map(parseFinding)
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
return { score, findings };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseRecommendation(raw) {
|
|
146
|
+
if (!raw || typeof raw !== "object") return null;
|
|
147
|
+
return {
|
|
148
|
+
priority: typeof raw.priority === "number" ? raw.priority : 99,
|
|
149
|
+
dimension: raw.dimension || "",
|
|
150
|
+
action: raw.action || "",
|
|
151
|
+
impact: VALID_IMPACT.has(raw.impact) ? raw.impact : "medium",
|
|
152
|
+
effort: VALID_IMPACT.has(raw.effort) ? raw.effort : "medium"
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function parseAuditOutput(raw) {
|
|
157
|
+
const text = raw?.trim() || "";
|
|
158
|
+
const jsonMatch = /\{[\s\S]*\}/.exec(text);
|
|
159
|
+
if (!jsonMatch) return null;
|
|
160
|
+
|
|
161
|
+
let parsed;
|
|
162
|
+
try {
|
|
163
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Handle both wrapped (result.summary) and flat structures
|
|
169
|
+
const resultObj = parsed.result || parsed;
|
|
170
|
+
const summaryObj = resultObj.summary || {};
|
|
171
|
+
|
|
172
|
+
const overallHealth = VALID_HEALTH.has(summaryObj.overallHealth)
|
|
173
|
+
? summaryObj.overallHealth
|
|
174
|
+
: "poor";
|
|
175
|
+
|
|
176
|
+
const dims = resultObj.dimensions || {};
|
|
177
|
+
const dimensions = {};
|
|
178
|
+
for (const d of AUDIT_DIMENSIONS) {
|
|
179
|
+
dimensions[d] = parseDimension(dims[d]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const totalFindings = Object.values(dimensions)
|
|
183
|
+
.reduce((sum, d) => sum + d.findings.length, 0);
|
|
184
|
+
|
|
185
|
+
const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
186
|
+
for (const d of Object.values(dimensions)) {
|
|
187
|
+
for (const f of d.findings) {
|
|
188
|
+
bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const topRecommendations = (Array.isArray(resultObj.topRecommendations) ? resultObj.topRecommendations : [])
|
|
193
|
+
.map(parseRecommendation)
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.sort((a, b) => a.priority - b.priority);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
summary: {
|
|
199
|
+
overallHealth,
|
|
200
|
+
totalFindings,
|
|
201
|
+
critical: bySeverity.critical,
|
|
202
|
+
high: bySeverity.high,
|
|
203
|
+
medium: bySeverity.medium,
|
|
204
|
+
low: bySeverity.low
|
|
205
|
+
},
|
|
206
|
+
dimensions,
|
|
207
|
+
topRecommendations,
|
|
208
|
+
textSummary: parsed.summary || resultObj.textSummary || ""
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { BaseRole } from "./base-role.js";
|
|
2
|
+
import { createAgent as defaultCreateAgent } from "../agents/index.js";
|
|
3
|
+
import { buildAuditPrompt, parseAuditOutput, AUDIT_DIMENSIONS } from "../prompts/audit.js";
|
|
4
|
+
|
|
5
|
+
function resolveProvider(config) {
|
|
6
|
+
return (
|
|
7
|
+
config?.roles?.audit?.provider ||
|
|
8
|
+
config?.roles?.coder?.provider ||
|
|
9
|
+
"claude"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildSummary(parsed) {
|
|
14
|
+
const { summary } = parsed;
|
|
15
|
+
const parts = [];
|
|
16
|
+
if (summary.critical > 0) parts.push(`${summary.critical} critical`);
|
|
17
|
+
if (summary.high > 0) parts.push(`${summary.high} high`);
|
|
18
|
+
if (summary.medium > 0) parts.push(`${summary.medium} medium`);
|
|
19
|
+
if (summary.low > 0) parts.push(`${summary.low} low`);
|
|
20
|
+
|
|
21
|
+
const findingsStr = parts.length > 0 ? parts.join(", ") : "no issues";
|
|
22
|
+
return `Overall health: ${summary.overallHealth}. ${summary.totalFindings} findings (${findingsStr})`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveInput(input, context) {
|
|
26
|
+
if (typeof input === "string") {
|
|
27
|
+
return { task: input, onOutput: null, dimensions: null, context: null };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
task: input?.task || context?.task || "",
|
|
31
|
+
onOutput: input?.onOutput || null,
|
|
32
|
+
dimensions: input?.dimensions || null,
|
|
33
|
+
context: input?.context || null
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseDimensions(dimensionsStr) {
|
|
38
|
+
if (!dimensionsStr || dimensionsStr === "all") return null;
|
|
39
|
+
const requested = dimensionsStr.split(",").map(d => d.trim().toLowerCase());
|
|
40
|
+
// Map "quality" shorthand to "codeQuality"
|
|
41
|
+
const mapped = requested.map(d => d === "quality" ? "codeQuality" : d);
|
|
42
|
+
const valid = mapped.filter(d => AUDIT_DIMENSIONS.includes(d));
|
|
43
|
+
return valid.length > 0 ? valid : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class AuditRole extends BaseRole {
|
|
47
|
+
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
48
|
+
super({ name: "audit", config, logger, emitter });
|
|
49
|
+
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async execute(input) {
|
|
53
|
+
const { task, onOutput, dimensions: rawDimensions, context } = resolveInput(input, this.context);
|
|
54
|
+
const dimensions = typeof rawDimensions === "string"
|
|
55
|
+
? parseDimensions(rawDimensions)
|
|
56
|
+
: rawDimensions;
|
|
57
|
+
const provider = resolveProvider(this.config);
|
|
58
|
+
const agent = this._createAgent(provider, this.config, this.logger);
|
|
59
|
+
|
|
60
|
+
const prompt = buildAuditPrompt({ task, instructions: this.instructions, dimensions, context });
|
|
61
|
+
const runArgs = { prompt, role: "audit" };
|
|
62
|
+
if (onOutput) runArgs.onOutput = onOutput;
|
|
63
|
+
const result = await agent.runTask(runArgs);
|
|
64
|
+
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
result: { error: result.error || result.output || "Audit failed", provider },
|
|
69
|
+
summary: `Audit failed: ${result.error || "unknown error"}`,
|
|
70
|
+
usage: result.usage
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const parsed = parseAuditOutput(result.output);
|
|
76
|
+
if (!parsed) {
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
result: { raw: result.output, provider },
|
|
80
|
+
summary: "Audit complete (unstructured output)",
|
|
81
|
+
usage: result.usage
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
result: {
|
|
88
|
+
summary: parsed.summary,
|
|
89
|
+
dimensions: parsed.dimensions,
|
|
90
|
+
topRecommendations: parsed.topRecommendations,
|
|
91
|
+
provider
|
|
92
|
+
},
|
|
93
|
+
summary: buildSummary(parsed),
|
|
94
|
+
usage: result.usage
|
|
95
|
+
};
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
result: { raw: result.output, provider },
|
|
100
|
+
summary: "Audit complete (unstructured output)",
|
|
101
|
+
usage: result.usage
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/roles/index.js
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Audit Role
|
|
2
|
+
|
|
3
|
+
You are the **Codebase Auditor**. Analyze the project for health, quality, and risks. DO NOT modify any files — this is a read-only analysis.
|
|
4
|
+
|
|
5
|
+
## Tools allowed
|
|
6
|
+
ONLY use: Read, Grep, Glob, Bash (for analysis commands only — `wc`, `find`, `git log`, `du`, `npm ls`). DO NOT use Edit or Write.
|
|
7
|
+
|
|
8
|
+
## Analysis dimensions
|
|
9
|
+
|
|
10
|
+
### 1. Security
|
|
11
|
+
- Hardcoded secrets, API keys, tokens in source code
|
|
12
|
+
- SQL/NoSQL injection vectors
|
|
13
|
+
- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML, eval)
|
|
14
|
+
- Command injection (exec, spawn with user input)
|
|
15
|
+
- Insecure dependencies (check package.json for known vulnerable packages)
|
|
16
|
+
- Missing input validation at system boundaries
|
|
17
|
+
- Authentication/authorization gaps
|
|
18
|
+
|
|
19
|
+
### 2. Code Quality (SOLID, DRY, KISS, YAGNI)
|
|
20
|
+
- Functions/methods longer than 50 lines
|
|
21
|
+
- Files longer than 500 lines
|
|
22
|
+
- Duplicated code blocks (same logic in multiple places)
|
|
23
|
+
- God classes/modules (too many responsibilities)
|
|
24
|
+
- Deep nesting (>4 levels)
|
|
25
|
+
- Dead code (unused exports, unreachable branches)
|
|
26
|
+
- Missing error handling (uncaught promises, empty catches)
|
|
27
|
+
- Over-engineering (abstractions for single use)
|
|
28
|
+
|
|
29
|
+
### 3. Performance
|
|
30
|
+
- N+1 query patterns
|
|
31
|
+
- Synchronous file I/O in request handlers
|
|
32
|
+
- Missing pagination on list endpoints
|
|
33
|
+
- Large bundle imports (importing entire libraries for one function)
|
|
34
|
+
- Missing lazy loading
|
|
35
|
+
- Expensive operations in loops
|
|
36
|
+
- Missing caching opportunities
|
|
37
|
+
|
|
38
|
+
### 4. Architecture
|
|
39
|
+
- Circular dependencies
|
|
40
|
+
- Layer violations (UI importing from data layer directly)
|
|
41
|
+
- Coupling between modules (shared mutable state)
|
|
42
|
+
- Missing dependency injection
|
|
43
|
+
- Inconsistent patterns across the codebase
|
|
44
|
+
- Missing or outdated documentation
|
|
45
|
+
- Configuration scattered vs centralized
|
|
46
|
+
|
|
47
|
+
### 5. Testing
|
|
48
|
+
- Test coverage gaps (source files without corresponding tests)
|
|
49
|
+
- Test quality (assertions per test, meaningful test names)
|
|
50
|
+
- Missing edge case coverage
|
|
51
|
+
- Test isolation (shared state between tests)
|
|
52
|
+
- Flaky test indicators (timeouts, sleep, retries)
|
|
53
|
+
|
|
54
|
+
## Task
|
|
55
|
+
|
|
56
|
+
{{task}}
|
|
57
|
+
|
|
58
|
+
## Context
|
|
59
|
+
|
|
60
|
+
{{context}}
|
|
61
|
+
|
|
62
|
+
## Output format
|
|
63
|
+
|
|
64
|
+
Return a single valid JSON object and nothing else.
|
|
65
|
+
|
|
66
|
+
JSON schema: {"ok":true,"result":{"summary":{"overallHealth":"good|fair|poor|critical","totalFindings":number,"critical":number,"high":number,"medium":number,"low":number},"dimensions":{"security":{"score":"A|B|C|D|F","findings":[]},"codeQuality":{"score":"A|B|C|D|F","findings":[]},"performance":{"score":"A|B|C|D|F","findings":[]},"architecture":{"score":"A|B|C|D|F","findings":[]},"testing":{"score":"A|B|C|D|F","findings":[]}},"topRecommendations":[{"priority":number,"dimension":string,"action":string,"impact":"high|medium|low","effort":"high|medium|low"}]},"summary":string}
|
|
67
|
+
|
|
68
|
+
Each finding: {"severity":"critical|high|medium|low","file":string,"line":number,"rule":string,"description":string,"recommendation":string}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# kj-audit — Codebase Health Audit
|
|
2
|
+
|
|
3
|
+
Analyze the current codebase for security, code quality, performance, architecture, and testing issues. This is a READ-ONLY analysis — do not modify any files.
|
|
4
|
+
|
|
5
|
+
## Your task
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS
|
|
8
|
+
|
|
9
|
+
## Dimensions to analyze
|
|
10
|
+
|
|
11
|
+
### 1. Security
|
|
12
|
+
- Hardcoded secrets, API keys, tokens in source code
|
|
13
|
+
- SQL/NoSQL injection vectors (string concatenation in queries)
|
|
14
|
+
- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML, eval)
|
|
15
|
+
- Command injection (exec, spawn with user input)
|
|
16
|
+
- Insecure dependencies (check package.json for known vulnerable packages)
|
|
17
|
+
- Missing input validation at system boundaries
|
|
18
|
+
- Authentication/authorization gaps
|
|
19
|
+
|
|
20
|
+
### 2. Code Quality (SOLID, DRY, KISS, YAGNI)
|
|
21
|
+
- Functions/methods longer than 50 lines
|
|
22
|
+
- Files longer than 500 lines
|
|
23
|
+
- Duplicated code blocks (same logic in multiple places)
|
|
24
|
+
- God classes/modules (too many responsibilities)
|
|
25
|
+
- Deep nesting (>4 levels)
|
|
26
|
+
- Dead code (unused exports, unreachable branches)
|
|
27
|
+
- Missing error handling (uncaught promises, empty catches)
|
|
28
|
+
- Over-engineering (abstractions for single use)
|
|
29
|
+
|
|
30
|
+
### 3. Performance
|
|
31
|
+
- N+1 query patterns
|
|
32
|
+
- Synchronous file I/O in request handlers
|
|
33
|
+
- Missing pagination on list endpoints
|
|
34
|
+
- Large bundle imports (importing entire libraries for one function)
|
|
35
|
+
- Missing lazy loading
|
|
36
|
+
- Expensive operations in loops
|
|
37
|
+
- Missing caching opportunities
|
|
38
|
+
|
|
39
|
+
### 4. Architecture
|
|
40
|
+
- Circular dependencies
|
|
41
|
+
- Layer violations (UI importing from data layer directly)
|
|
42
|
+
- Coupling between modules (shared mutable state)
|
|
43
|
+
- Missing dependency injection
|
|
44
|
+
- Inconsistent patterns across the codebase
|
|
45
|
+
- Missing or outdated documentation
|
|
46
|
+
- Configuration scattered vs centralized
|
|
47
|
+
|
|
48
|
+
### 5. Testing
|
|
49
|
+
- Test coverage gaps (source files without corresponding tests)
|
|
50
|
+
- Test quality (assertions per test, meaningful test names)
|
|
51
|
+
- Missing edge case coverage
|
|
52
|
+
- Test isolation (shared state between tests)
|
|
53
|
+
- Flaky test indicators (timeouts, sleep, retries)
|
|
54
|
+
|
|
55
|
+
## Output
|
|
56
|
+
|
|
57
|
+
For each dimension, provide:
|
|
58
|
+
- **Score**: A (excellent) to F (failing)
|
|
59
|
+
- **Findings**: severity, file, line, rule, description, recommendation
|
|
60
|
+
|
|
61
|
+
End with:
|
|
62
|
+
- **Overall Health**: good / fair / poor / critical
|
|
63
|
+
- **Top Recommendations**: prioritized list of actions with impact and effort estimates
|