karajan-code 1.27.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.27.1",
3
+ "version": "1.28.0",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
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
 
@@ -663,6 +663,54 @@ export async function handleResearcherDirect(a, server, extra) {
663
663
  return { ok: true, ...result.result, summary: result.summary };
664
664
  }
665
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
+
666
714
  export async function handleArchitectDirect(a, server, extra) {
667
715
  const config = await buildConfig(a, "architect");
668
716
  const logger = createLogger(config.output.log_level, "mcp");
@@ -955,6 +1003,10 @@ async function handleArchitect(a, server, extra) {
955
1003
  return handleArchitectDirect(a, server, extra);
956
1004
  }
957
1005
 
1006
+ async function handleAudit(a, server, extra) {
1007
+ return handleAuditDirect(a, server, extra);
1008
+ }
1009
+
958
1010
  /* ── Handler dispatch map ─────────────────────────────────────────── */
959
1011
 
960
1012
  const toolHandlers = {
@@ -975,7 +1027,8 @@ const toolHandlers = {
975
1027
  kj_discover: (a, server, extra) => handleDiscover(a, server, extra),
976
1028
  kj_triage: (a, server, extra) => handleTriage(a, server, extra),
977
1029
  kj_researcher: (a, server, extra) => handleResearcher(a, server, extra),
978
- 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)
979
1032
  };
980
1033
 
981
1034
  export async function handleToolCall(name, args, server, extra) {
package/src/mcp/tools.js CHANGED
@@ -291,5 +291,17 @@ export const tools = [
291
291
  kjHome: { type: "string" }
292
292
  }
293
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)" },
303
+ kjHome: { type: "string" }
304
+ }
305
+ }
294
306
  }
295
307
  ];
@@ -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
+ }
@@ -12,3 +12,4 @@ export { SecurityRole } from "./security-role.js";
12
12
  export { SolomonRole } from "./solomon-role.js";
13
13
  export { DiscoverRole } from "./discover-role.js";
14
14
  export { ArchitectRole } from "./architect-role.js";
15
+ export { AuditRole } from "./audit-role.js";
@@ -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