karajan-code 1.22.0 → 1.24.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.22.0",
3
+ "version": "1.24.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
@@ -75,6 +75,7 @@ program
75
75
  .option("--enable-researcher")
76
76
  .option("--enable-tester")
77
77
  .option("--enable-security")
78
+ .option("--enable-impeccable")
78
79
  .option("--enable-triage")
79
80
  .option("--enable-discover")
80
81
  .option("--enable-architect")
@@ -204,6 +204,56 @@ async function scaffoldBecariaGateway(config, flags, logger) {
204
204
  logger.info(" 4. Push the workflow files and enable 'kj run --enable-becaria'");
205
205
  }
206
206
 
207
+ async function installSkills(logger, interactive) {
208
+ const projectDir = process.cwd();
209
+ const commandsDir = path.join(projectDir, ".claude", "commands");
210
+ const skillsTemplateDir = path.resolve(import.meta.dirname, "../../templates/skills");
211
+
212
+ let doInstall = true;
213
+ if (interactive) {
214
+ const wizard = createWizard();
215
+ try {
216
+ doInstall = await wizard.confirm("Install Karajan skills as slash commands (/kj-code, /kj-review, etc.)?", true);
217
+ } finally {
218
+ wizard.close();
219
+ }
220
+ }
221
+
222
+ if (!doInstall) {
223
+ logger.info("Skills installation skipped.");
224
+ return;
225
+ }
226
+
227
+ await ensureDir(commandsDir);
228
+
229
+ let installed = 0;
230
+ try {
231
+ const files = await fs.readdir(skillsTemplateDir);
232
+ for (const file of files) {
233
+ if (!file.endsWith(".md")) continue;
234
+ const src = path.join(skillsTemplateDir, file);
235
+ const dest = path.join(commandsDir, file);
236
+ if (await exists(dest)) {
237
+ logger.info(` ${file} already exists — skipping`);
238
+ continue;
239
+ }
240
+ const content = await fs.readFile(src, "utf8");
241
+ await fs.writeFile(dest, content, "utf8");
242
+ installed += 1;
243
+ }
244
+ } catch (err) {
245
+ logger.warn(`Could not install skills: ${err.message}`);
246
+ return;
247
+ }
248
+
249
+ if (installed > 0) {
250
+ logger.info(`Installed ${installed} Karajan skill(s) in .claude/commands/`);
251
+ logger.info("Available as slash commands: /kj-run, /kj-code, /kj-review, /kj-test, /kj-security, /kj-discover, /kj-architect, /kj-sonar");
252
+ } else {
253
+ logger.info("All skills already installed.");
254
+ }
255
+ }
256
+
207
257
  export async function initCommand({ logger, flags = {} }) {
208
258
  const karajanHome = getKarajanHome();
209
259
  await ensureDir(karajanHome);
@@ -219,6 +269,7 @@ export async function initCommand({ logger, flags = {} }) {
219
269
  await handleConfigSetup({ config, configExists, interactive, configPath, logger });
220
270
  await ensureReviewRules(reviewRulesPath, logger);
221
271
  await ensureCoderRules(coderRulesPath, logger);
272
+ await installSkills(logger, interactive);
222
273
  await setupSonarQube(config, logger);
223
274
  await scaffoldBecariaGateway(config, flags, logger);
224
275
  }
@@ -7,6 +7,12 @@ import { resolveRole } from "../config.js";
7
7
  import { parseCardId } from "../planning-game/adapter.js";
8
8
 
9
9
  export async function runCommandHandler({ task, config, logger, flags }) {
10
+ // Best-effort session cleanup before starting
11
+ try {
12
+ const { cleanupExpiredSessions } = await import("../session-cleanup.js");
13
+ await cleanupExpiredSessions({ logger });
14
+ } catch { /* non-blocking */ }
15
+
10
16
  const requiredProviders = [
11
17
  resolveRole(config, "coder").provider,
12
18
  config.reviewer_options?.fallback_reviewer
package/src/config.js CHANGED
@@ -16,6 +16,7 @@ const DEFAULTS = {
16
16
  researcher: { provider: null, model: null },
17
17
  tester: { provider: null, model: null },
18
18
  security: { provider: null, model: null },
19
+ impeccable: { provider: null, model: null },
19
20
  triage: { provider: null, model: null },
20
21
  discover: { provider: null, model: null },
21
22
  architect: { provider: null, model: null }
@@ -27,6 +28,7 @@ const DEFAULTS = {
27
28
  researcher: { enabled: false },
28
29
  tester: { enabled: true },
29
30
  security: { enabled: true },
31
+ impeccable: { enabled: false },
30
32
  triage: { enabled: true },
31
33
  discover: { enabled: false },
32
34
  architect: { enabled: false }
@@ -276,7 +278,7 @@ const ROLE_MODEL_FLAGS = [
276
278
  const PIPELINE_ENABLE_FLAGS = [
277
279
  ["enablePlanner", "planner"], ["enableRefactorer", "refactorer"],
278
280
  ["enableSolomon", "solomon"], ["enableResearcher", "researcher"],
279
- ["enableTester", "tester"], ["enableSecurity", "security"],
281
+ ["enableTester", "tester"], ["enableSecurity", "security"], ["enableImpeccable", "impeccable"],
280
282
  ["enableTriage", "triage"], ["enableDiscover", "discover"],
281
283
  ["enableArchitect", "architect"]
282
284
  ];
@@ -408,14 +410,14 @@ export function resolveRole(config, role) {
408
410
  let provider = roleConfig.provider ?? null;
409
411
  if (!provider && role === "coder") provider = legacyCoder;
410
412
  if (!provider && role === "reviewer") provider = legacyReviewer;
411
- if (!provider && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "triage" || role === "discover" || role === "architect")) {
413
+ if (!provider && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "impeccable" || role === "triage" || role === "discover" || role === "architect")) {
412
414
  provider = roles.coder?.provider || legacyCoder;
413
415
  }
414
416
 
415
417
  let model = roleConfig.model ?? null;
416
418
  if (!model && role === "coder") model = config?.coder_options?.model ?? null;
417
419
  if (!model && role === "reviewer") model = config?.reviewer_options?.model ?? null;
418
- if (!model && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "triage" || role === "discover" || role === "architect")) {
420
+ if (!model && (role === "planner" || role === "refactorer" || role === "solomon" || role === "researcher" || role === "tester" || role === "security" || role === "impeccable" || role === "triage" || role === "discover" || role === "architect")) {
419
421
  model = config?.coder_options?.model ?? null;
420
422
  }
421
423
 
@@ -426,7 +428,7 @@ export function resolveRole(config, role) {
426
428
  const RUN_PIPELINE_ROLES = [
427
429
  ["reviewer", "reviewer"], ["triage", "triage"], ["planner", "planner"],
428
430
  ["refactorer", "refactorer"], ["researcher", "researcher"],
429
- ["tester", "tester"], ["security", "security"]
431
+ ["tester", "tester"], ["security", "security"], ["impeccable", "impeccable"]
430
432
  ];
431
433
 
432
434
  // Direct command-to-role mapping for non-"run" commands
@@ -51,6 +51,16 @@ const INTENT_PATTERNS = [
51
51
  confidence: 0.9,
52
52
  message: "Trivial fix detected",
53
53
  },
54
+ // Frontend / UI tasks (sets hasFrontend flag for impeccable role activation)
55
+ {
56
+ id: "frontend-ui",
57
+ keywords: ["html", "css", "ui", "landing", "component", "responsive", "accessibility", "a11y", "frontend", "design", "layout", "styling", "dark mode", "animation"],
58
+ taskType: "sw",
59
+ level: "simple",
60
+ confidence: 0.8,
61
+ message: "Frontend/UI task detected",
62
+ hasFrontend: true,
63
+ },
54
64
  ];
55
65
 
56
66
  /**
@@ -106,7 +116,7 @@ export function classifyIntent(task, config = {}) {
106
116
  if (!matchesKeywords(task, pattern.keywords)) continue;
107
117
 
108
118
  if (pattern.confidence >= threshold) {
109
- return {
119
+ const result = {
110
120
  classified: true,
111
121
  taskType: pattern.taskType,
112
122
  level: pattern.level,
@@ -114,6 +124,8 @@ export function classifyIntent(task, config = {}) {
114
124
  patternId: pattern.id,
115
125
  message: pattern.message,
116
126
  };
127
+ if (pattern.hasFrontend) result.hasFrontend = true;
128
+ return result;
117
129
  }
118
130
  }
119
131
 
package/src/mcp/run-kj.js CHANGED
@@ -42,6 +42,7 @@ export async function runKjCommand({ command, commandArgs = [], options = {}, en
42
42
  normalizeBoolFlag(options.enableResearcher, "--enable-researcher", args);
43
43
  normalizeBoolFlag(options.enableTester, "--enable-tester", args);
44
44
  normalizeBoolFlag(options.enableSecurity, "--enable-security", args);
45
+ normalizeBoolFlag(options.enableImpeccable, "--enable-impeccable", args);
45
46
  normalizeBoolFlag(options.enableTriage, "--enable-triage", args);
46
47
  normalizeBoolFlag(options.enableDiscover, "--enable-discover", args);
47
48
  normalizeBoolFlag(options.enableArchitect, "--enable-architect", args);
@@ -239,6 +239,12 @@ export async function handleRunDirect(a, server, extra) {
239
239
  await assertNotOnBaseBranch(config);
240
240
  const logger = createLogger(config.output.log_level, "mcp");
241
241
 
242
+ // Best-effort session cleanup before starting
243
+ try {
244
+ const { cleanupExpiredSessions } = await import("../session-cleanup.js");
245
+ await cleanupExpiredSessions({ logger });
246
+ } catch { /* non-blocking */ }
247
+
242
248
  const requiredProviders = [
243
249
  resolveRole(config, "coder").provider,
244
250
  config.reviewer_options?.fallback_reviewer
@@ -821,6 +827,7 @@ async function handleResume(a, server, extra) {
821
827
  if (!a.sessionId) {
822
828
  return failPayload("Missing required field: sessionId");
823
829
  }
830
+ applySessionOverrides(a, ["coder", "reviewer", "tester", "security", "solomon", "enableTester", "enableSecurity", "enableImpeccable"]);
824
831
  return handleResumeDirect(a, server, extra);
825
832
  }
826
833
 
@@ -837,7 +844,7 @@ async function handleRun(a, server, extra) {
837
844
  if (!isPreflightAcked()) {
838
845
  return buildPreflightRequiredResponse("kj_run");
839
846
  }
840
- applySessionOverrides(a, ["coder", "reviewer", "tester", "security", "solomon", "enableTester", "enableSecurity"]);
847
+ applySessionOverrides(a, ["coder", "reviewer", "tester", "security", "solomon", "enableTester", "enableSecurity", "enableImpeccable"]);
841
848
  return handleRunDirect(a, server, extra);
842
849
  }
843
850
 
package/src/mcp/tools.js CHANGED
@@ -69,6 +69,7 @@ export const tools = [
69
69
  enableResearcher: { type: "boolean" },
70
70
  enableTester: { type: "boolean" },
71
71
  enableSecurity: { type: "boolean" },
72
+ enableImpeccable: { type: "boolean" },
72
73
  enableTriage: { type: "boolean" },
73
74
  enableDiscover: { type: "boolean" },
74
75
  enableArchitect: { type: "boolean" },
@@ -1,5 +1,6 @@
1
1
  import { TesterRole } from "../roles/tester-role.js";
2
2
  import { SecurityRole } from "../roles/security-role.js";
3
+ import { ImpeccableRole } from "../roles/impeccable-role.js";
3
4
  import { addCheckpoint, saveSession } from "../session-store.js";
4
5
  import { emitProgress, makeEvent } from "../utils/events.js";
5
6
  import { invokeSolomon } from "./solomon-escalation.js";
@@ -163,3 +164,53 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
163
164
  session.security_retry_count = 0;
164
165
  return { action: "ok", stageResult: { ok: true, summary: securityOutput.summary || "No vulnerabilities found" } };
165
166
  }
167
+
168
+ export async function runImpeccableStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff }) {
169
+ logger.setContext({ iteration, stage: "impeccable" });
170
+ emitProgress(
171
+ emitter,
172
+ makeEvent("impeccable:start", { ...eventBase, stage: "impeccable" }, {
173
+ message: "Impeccable auditing frontend design quality"
174
+ })
175
+ );
176
+
177
+ const impeccable = new ImpeccableRole({ config, logger, emitter });
178
+ await impeccable.init({ task, iteration });
179
+ const impeccableStart = Date.now();
180
+ let impeccableOutput;
181
+ try {
182
+ impeccableOutput = await impeccable.run({ task, diff });
183
+ } catch (err) {
184
+ logger.warn(`Impeccable threw: ${err.message}`);
185
+ impeccableOutput = { ok: false, summary: `Impeccable error: ${err.message}`, result: { error: err.message } };
186
+ }
187
+ trackBudget({
188
+ role: "impeccable",
189
+ provider: config?.roles?.impeccable?.provider || coderRole.provider,
190
+ model: config?.roles?.impeccable?.model || coderRole.model,
191
+ result: impeccableOutput,
192
+ duration_ms: Date.now() - impeccableStart
193
+ });
194
+
195
+ await addCheckpoint(session, {
196
+ stage: "impeccable",
197
+ iteration,
198
+ ok: impeccableOutput.ok,
199
+ provider: config?.roles?.impeccable?.provider || coderRole.provider,
200
+ model: config?.roles?.impeccable?.model || coderRole.model || null
201
+ });
202
+
203
+ const verdict = impeccableOutput.result?.verdict || "APPROVED";
204
+ emitProgress(
205
+ emitter,
206
+ makeEvent("impeccable:end", { ...eventBase, stage: "impeccable" }, {
207
+ status: impeccableOutput.ok ? "ok" : "fail",
208
+ message: impeccableOutput.ok
209
+ ? (verdict === "IMPROVED" ? "Impeccable applied design fixes" : "Impeccable audit passed")
210
+ : `Impeccable: ${impeccableOutput.summary}`
211
+ })
212
+ );
213
+
214
+ // Impeccable is advisory — failures do not block the pipeline
215
+ return { action: "ok", stageResult: { ok: impeccableOutput.ok, verdict, summary: impeccableOutput.summary || "No frontend design issues found" } };
216
+ }
@@ -11,7 +11,7 @@ import { parsePlannerOutput } from "../prompts/planner.js";
11
11
  import { selectModelsForRoles } from "../utils/model-selector.js";
12
12
  import { createStallDetector } from "../utils/stall-detector.js";
13
13
 
14
- const ROLE_NAMES = ["planner", "researcher", "architect", "refactorer", "reviewer", "tester", "security"];
14
+ const ROLE_NAMES = ["planner", "researcher", "architect", "refactorer", "reviewer", "tester", "security", "impeccable"];
15
15
 
16
16
  function buildRoleOverrides(recommendedRoles, pipelineConfig) {
17
17
  const overrides = {};
@@ -31,7 +31,7 @@ import { CoderRole } from "./roles/coder-role.js";
31
31
  import { invokeSolomon } from "./orchestrator/solomon-escalation.js";
32
32
  import { runTriageStage, runResearcherStage, runArchitectStage, runPlannerStage, runDiscoverStage } from "./orchestrator/pre-loop-stages.js";
33
33
  import { runCoderStage, runRefactorerStage, runTddCheckStage, runSonarStage, runSonarCloudStage, runReviewerStage } from "./orchestrator/iteration-stages.js";
34
- import { runTesterStage, runSecurityStage } from "./orchestrator/post-loop-stages.js";
34
+ import { runTesterStage, runSecurityStage, runImpeccableStage } from "./orchestrator/post-loop-stages.js";
35
35
  import { waitForCooldown, MAX_STANDBY_RETRIES } from "./orchestrator/standby.js";
36
36
 
37
37
 
@@ -44,6 +44,7 @@ function resolvePipelineFlags(config) {
44
44
  researcherEnabled: Boolean(config.pipeline?.researcher?.enabled),
45
45
  testerEnabled: Boolean(config.pipeline?.tester?.enabled),
46
46
  securityEnabled: Boolean(config.pipeline?.security?.enabled),
47
+ impeccableEnabled: Boolean(config.pipeline?.impeccable?.enabled),
47
48
  reviewerEnabled: config.pipeline?.reviewer?.enabled !== false,
48
49
  discoverEnabled: Boolean(config.pipeline?.discover?.enabled),
49
50
  architectEnabled: Boolean(config.pipeline?.architect?.enabled),
@@ -51,7 +52,7 @@ function resolvePipelineFlags(config) {
51
52
  }
52
53
 
53
54
  async function handleDryRun({ task, config, flags, emitter, pipelineFlags }) {
54
- const { plannerEnabled, refactorerEnabled, researcherEnabled, testerEnabled, securityEnabled, reviewerEnabled, discoverEnabled, architectEnabled } = pipelineFlags;
55
+ const { plannerEnabled, refactorerEnabled, researcherEnabled, testerEnabled, securityEnabled, impeccableEnabled, reviewerEnabled, discoverEnabled, architectEnabled } = pipelineFlags;
55
56
  const plannerRole = resolveRole(config, "planner");
56
57
  const coderRole = resolveRole(config, "coder");
57
58
  const reviewerRole = resolveRole(config, "reviewer");
@@ -84,6 +85,7 @@ async function handleDryRun({ task, config, flags, emitter, pipelineFlags }) {
84
85
  researcher_enabled: researcherEnabled,
85
86
  tester_enabled: testerEnabled,
86
87
  security_enabled: securityEnabled,
88
+ impeccable_enabled: impeccableEnabled,
87
89
  solomon_enabled: Boolean(config.pipeline?.solomon?.enabled)
88
90
  },
89
91
  limits: {
@@ -203,7 +205,7 @@ async function markPgCardInProgress({ pgTaskId, pgProject, config, logger }) {
203
205
  }
204
206
 
205
207
  function applyTriageOverrides(pipelineFlags, roleOverrides) {
206
- const keys = ["plannerEnabled", "researcherEnabled", "architectEnabled", "refactorerEnabled", "reviewerEnabled", "testerEnabled", "securityEnabled"];
208
+ const keys = ["plannerEnabled", "researcherEnabled", "architectEnabled", "refactorerEnabled", "reviewerEnabled", "testerEnabled", "securityEnabled", "impeccableEnabled"];
207
209
  for (const key of keys) {
208
210
  if (roleOverrides[key] !== undefined) {
209
211
  pipelineFlags[key] = roleOverrides[key];
@@ -271,6 +273,7 @@ function applyFlagOverrides(pipelineFlags, flags) {
271
273
  if (flags.enableReviewer !== undefined) pipelineFlags.reviewerEnabled = Boolean(flags.enableReviewer);
272
274
  if (flags.enableTester !== undefined) pipelineFlags.testerEnabled = Boolean(flags.enableTester);
273
275
  if (flags.enableSecurity !== undefined) pipelineFlags.securityEnabled = Boolean(flags.enableSecurity);
276
+ if (flags.enableImpeccable !== undefined) pipelineFlags.impeccableEnabled = Boolean(flags.enableImpeccable);
274
277
  }
275
278
 
276
279
  function resolvePipelinePolicies({ flags, config, stageResults, emitter, eventBase, session, pipelineFlags }) {
@@ -892,7 +895,7 @@ async function runGuardStages({ config, logger, emitter, eventBase, session, ite
892
895
  return { action: "ok" };
893
896
  }
894
897
 
895
- async function runQualityGateStages({ config, logger, emitter, eventBase, session, trackBudget, i, askQuestion, repeatDetector, budgetSummary, sonarState, task, stageResults }) {
898
+ async function runQualityGateStages({ config, logger, emitter, eventBase, session, trackBudget, i, askQuestion, repeatDetector, budgetSummary, sonarState, task, stageResults, coderRole, pipelineFlags }) {
896
899
  const tddResult = await runTddCheckStage({ config, logger, emitter, eventBase, session, trackBudget, iteration: i, askQuestion });
897
900
  if (tddResult.action === "pause") return { action: "return", result: tddResult.result };
898
901
  if (tddResult.action === "continue") return { action: "continue" };
@@ -919,6 +922,17 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
919
922
  }
920
923
  }
921
924
 
925
+ if (pipelineFlags?.impeccableEnabled) {
926
+ const diff = await generateDiff({ baseRef: session.session_start_sha });
927
+ const impeccableResult = await runImpeccableStage({
928
+ config, logger, emitter, eventBase, session, coderRole, trackBudget,
929
+ iteration: i, task, diff
930
+ });
931
+ if (impeccableResult.stageResult) {
932
+ stageResults.impeccable = impeccableResult.stageResult;
933
+ }
934
+ }
935
+
922
936
  return { action: "ok" };
923
937
  }
924
938
 
@@ -1071,7 +1085,7 @@ async function runSingleIteration(ctx) {
1071
1085
  const guardResult = await runGuardStages({ config, logger, emitter, eventBase, session, iteration: i });
1072
1086
  if (guardResult.action === "return") return guardResult;
1073
1087
 
1074
- const qgResult = await runQualityGateStages({ config, logger, emitter, eventBase, session, trackBudget, i, askQuestion, repeatDetector, budgetSummary, sonarState, task, stageResults });
1088
+ const qgResult = await runQualityGateStages({ config, logger, emitter, eventBase, session, trackBudget, i, askQuestion, repeatDetector, budgetSummary, sonarState, task, stageResults, coderRole, pipelineFlags });
1075
1089
  if (qgResult.action === "return" || qgResult.action === "continue") return qgResult;
1076
1090
 
1077
1091
  await handleBecariaEarlyPrOrPush({ becariaEnabled, config, session, emitter, eventBase, gitCtx, task, logger, stageResults, i });
@@ -0,0 +1,121 @@
1
+ import { BaseRole } from "./base-role.js";
2
+ import { createAgent as defaultCreateAgent } from "../agents/index.js";
3
+
4
+ const SUBAGENT_PREAMBLE = [
5
+ "IMPORTANT: You are running as a Karajan sub-agent.",
6
+ "Do NOT ask about using Karajan, do NOT mention Karajan, do NOT suggest orchestration.",
7
+ "Do NOT use any MCP tools. Focus only on auditing frontend/UI code for design quality."
8
+ ].join(" ");
9
+
10
+ function resolveProvider(config) {
11
+ return (
12
+ config?.roles?.impeccable?.provider ||
13
+ config?.roles?.coder?.provider ||
14
+ "claude"
15
+ );
16
+ }
17
+
18
+ function buildPrompt({ task, diff, instructions }) {
19
+ const sections = [SUBAGENT_PREAMBLE];
20
+
21
+ if (instructions) {
22
+ sections.push(instructions);
23
+ }
24
+
25
+ sections.push(
26
+ `## Task\n${task}`
27
+ );
28
+
29
+ if (diff) {
30
+ sections.push(`## Git diff to audit\n${diff}`);
31
+ }
32
+
33
+ return sections.join("\n\n");
34
+ }
35
+
36
+ function parseImpeccableOutput(raw) {
37
+ const text = raw?.trim() || "";
38
+ const jsonMatch = /\{[\s\S]*\}/.exec(text);
39
+ if (!jsonMatch) return null;
40
+ return JSON.parse(jsonMatch[0]);
41
+ }
42
+
43
+ function buildSummary(parsed) {
44
+ const verdict = parsed.verdict || "APPROVED";
45
+ const found = parsed.issuesFound || 0;
46
+ const fixed = parsed.issuesFixed || 0;
47
+
48
+ if (verdict === "APPROVED" || found === 0) {
49
+ return `Verdict: APPROVED; No frontend design issues found`;
50
+ }
51
+
52
+ const cats = parsed.categories || {};
53
+ const parts = Object.entries(cats)
54
+ .filter(([, count]) => count > 0)
55
+ .map(([cat, count]) => `${count} ${cat}`);
56
+
57
+ return `Verdict: ${verdict}; ${found} issue(s) found, ${fixed} fixed (${parts.join(", ")})`;
58
+ }
59
+
60
+ export class ImpeccableRole extends BaseRole {
61
+ constructor({ config, logger, emitter = null, createAgentFn = null }) {
62
+ super({ name: "impeccable", config, logger, emitter });
63
+ this._createAgent = createAgentFn || defaultCreateAgent;
64
+ }
65
+
66
+ async execute(input) {
67
+ const { task, diff } = typeof input === "string"
68
+ ? { task: input, diff: null }
69
+ : { task: input?.task || this.context?.task || "", diff: input?.diff || null };
70
+
71
+ const provider = resolveProvider(this.config);
72
+ const agent = this._createAgent(provider, this.config, this.logger);
73
+
74
+ const prompt = buildPrompt({ task, diff, instructions: this.instructions });
75
+ const result = await agent.runTask({ prompt, role: "impeccable" });
76
+
77
+ if (!result.ok) {
78
+ return {
79
+ ok: false,
80
+ result: {
81
+ error: result.error || result.output || "Impeccable audit failed",
82
+ provider
83
+ },
84
+ summary: `Impeccable audit failed: ${result.error || "unknown error"}`
85
+ };
86
+ }
87
+
88
+ try {
89
+ const parsed = parseImpeccableOutput(result.output);
90
+ if (!parsed) {
91
+ return {
92
+ ok: false,
93
+ result: { error: "Failed to parse impeccable output: no JSON found", provider },
94
+ summary: "Impeccable output parse error: no JSON found"
95
+ };
96
+ }
97
+
98
+ const verdict = parsed.verdict || (parsed.issuesFound > 0 ? "IMPROVED" : "APPROVED");
99
+ const ok = verdict === "APPROVED" || verdict === "IMPROVED";
100
+
101
+ return {
102
+ ok,
103
+ result: {
104
+ verdict,
105
+ issuesFound: parsed.issuesFound || 0,
106
+ issuesFixed: parsed.issuesFixed || 0,
107
+ categories: parsed.categories || {},
108
+ changes: parsed.changes || [],
109
+ provider
110
+ },
111
+ summary: buildSummary({ ...parsed, verdict })
112
+ };
113
+ } catch (err) {
114
+ return {
115
+ ok: false,
116
+ result: { error: `Failed to parse impeccable output: ${err.message}`, provider },
117
+ summary: `Impeccable output parse error: ${err.message}`
118
+ };
119
+ }
120
+ }
121
+ }
@@ -4,7 +4,7 @@ import { buildTriagePrompt } from "../prompts/triage.js";
4
4
  import { VALID_TASK_TYPES } from "../guards/policy-resolver.js";
5
5
 
6
6
  const VALID_LEVELS = new Set(["trivial", "simple", "medium", "complex"]);
7
- const VALID_ROLES = new Set(["planner", "researcher", "refactorer", "reviewer", "tester", "security"]);
7
+ const VALID_ROLES = new Set(["planner", "researcher", "refactorer", "reviewer", "tester", "security", "impeccable"]);
8
8
  const FALLBACK_TASK_TYPE = "sw";
9
9
 
10
10
  function resolveProvider(config) {
@@ -1,48 +1,72 @@
1
1
  /**
2
2
  * Automatic cleanup of expired sessions.
3
- * Removes session directories older than session.expiry_days (default: 30).
3
+ *
4
+ * Policy (by status):
5
+ * - failed / stopped: removed after 1 day
6
+ * - approved: removed after 7 days
7
+ * - running (stale): marked failed + removed after 1 day (crash without cleanup)
8
+ * - paused: kept (user may want to resume)
9
+ *
10
+ * Runs automatically at the start of every kj_run (best-effort, non-blocking).
4
11
  */
5
12
 
6
13
  import fs from "node:fs/promises";
7
14
  import path from "node:path";
8
15
  import { getSessionRoot } from "./utils/paths.js";
9
16
 
10
- const DEFAULT_EXPIRY_DAYS = 30;
17
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
11
18
 
12
- async function tryRemoveOrphan({ sessionDir, dirName, cutoff, removed, errors, logger }) {
13
- const stat = await fs.stat(sessionDir).catch(() => null);
14
- if (!stat || stat.mtimeMs >= cutoff) return;
15
- try {
16
- await fs.rm(sessionDir, { recursive: true, force: true });
17
- removed.push(dirName);
18
- logger?.debug?.(`Orphan session dir removed: ${dirName}`);
19
- } catch (error_) {
20
- errors.push({ session: dirName, error: error_.message });
21
- }
19
+ const POLICY = {
20
+ failed: { expiryMs: ONE_DAY_MS },
21
+ stopped: { expiryMs: ONE_DAY_MS },
22
+ running: { expiryMs: ONE_DAY_MS }, // stale — crashed without marking failed
23
+ approved: { expiryMs: 7 * ONE_DAY_MS },
24
+ paused: null // never auto-delete
25
+ };
26
+
27
+ function shouldRemove(session) {
28
+ const status = session.status || "unknown";
29
+ const policy = POLICY[status];
30
+ if (!policy) return false;
31
+
32
+ const updatedAt = new Date(session.updated_at || session.created_at).getTime();
33
+ return Date.now() - updatedAt > policy.expiryMs;
22
34
  }
23
35
 
24
- async function tryCleanupSession({ sessionDir, dirName, cutoff, removed, errors, logger }) {
36
+ async function tryCleanupSession({ sessionDir, dirName, removed, errors, logger }) {
25
37
  const sessionFile = path.join(sessionDir, "session.json");
38
+ let session;
26
39
  try {
27
40
  const raw = await fs.readFile(sessionFile, "utf8");
28
- const session = JSON.parse(raw);
29
- const updatedAt = new Date(session.updated_at || session.created_at).getTime();
30
- if (updatedAt < cutoff) {
31
- await fs.rm(sessionDir, { recursive: true, force: true });
32
- removed.push(dirName);
33
- logger?.debug?.(`Session expired and removed: ${dirName}`);
34
- }
41
+ session = JSON.parse(raw);
35
42
  } catch {
36
- await tryRemoveOrphan({ sessionDir, dirName, cutoff, removed, errors, logger });
43
+ // Orphan dir without valid session.json remove if older than 1 day
44
+ const stat = await fs.stat(sessionDir).catch(() => null);
45
+ if (stat && Date.now() - stat.mtimeMs > ONE_DAY_MS) {
46
+ try {
47
+ await fs.rm(sessionDir, { recursive: true, force: true });
48
+ removed.push(dirName);
49
+ logger?.debug?.(`Orphan session dir removed: ${dirName}`);
50
+ } catch (err) {
51
+ errors.push({ session: dirName, error: err.message });
52
+ }
53
+ }
54
+ return;
37
55
  }
38
- }
39
56
 
40
- export async function cleanupExpiredSessions({ config, logger } = {}) {
41
- const expiryDays = config?.session?.expiry_days ?? DEFAULT_EXPIRY_DAYS;
42
- if (expiryDays <= 0) return { removed: 0, errors: [] };
57
+ if (!shouldRemove(session)) return;
58
+
59
+ try {
60
+ await fs.rm(sessionDir, { recursive: true, force: true });
61
+ removed.push(dirName);
62
+ logger?.debug?.(`Session cleaned up: ${dirName} (status: ${session.status})`);
63
+ } catch (err) {
64
+ errors.push({ session: dirName, error: err.message });
65
+ }
66
+ }
43
67
 
68
+ export async function cleanupExpiredSessions({ logger } = {}) {
44
69
  const sessionRoot = getSessionRoot();
45
- const cutoff = Date.now() - expiryDays * 24 * 60 * 60 * 1000;
46
70
 
47
71
  let entries;
48
72
  try {
@@ -57,7 +81,7 @@ export async function cleanupExpiredSessions({ config, logger } = {}) {
57
81
 
58
82
  for (const dir of dirs) {
59
83
  const sessionDir = path.join(sessionRoot, dir.name);
60
- await tryCleanupSession({ sessionDir, dirName: dir.name, cutoff, removed, errors, logger });
84
+ await tryCleanupSession({ sessionDir, dirName: dir.name, removed, errors, logger });
61
85
  }
62
86
 
63
87
  if (removed.length > 0) {
@@ -0,0 +1,125 @@
1
+ # Impeccable Role
2
+
3
+ You are the **Impeccable Design Auditor** in a multi-role AI pipeline. You run after SonarQube and before the reviewer. Your job is to audit changed UI/frontend files for design quality issues and apply fixes automatically.
4
+
5
+ ## Scope constraint
6
+
7
+ - **ONLY audit and fix files present in the diff.** Do not touch files that were not changed.
8
+ - If no frontend files (.html, .css, .astro, .jsx, .tsx, .vue, .svelte, .lit, .js with DOM manipulation) are in the diff, report APPROVED immediately with 0 issues.
9
+
10
+ ## Input
11
+
12
+ - **Task**: {{task}}
13
+ - **Diff**: {{diff}}
14
+ - **Context**: {{context}}
15
+
16
+ ## Phase 1 — Audit
17
+
18
+ Analyze all changed files in the diff that are frontend-related. Run these checks systematically:
19
+
20
+ ### 1. Accessibility (a11y)
21
+ - Missing ARIA labels on interactive elements
22
+ - No `focus-visible` styles on focusable elements
23
+ - Missing `alt` text on images
24
+ - Non-semantic HTML (e.g. `<div>` used as buttons instead of `<button>`)
25
+ - Missing skip links for navigation
26
+ - Keyboard traps (focus cannot leave a component)
27
+ - Insufficient color contrast
28
+
29
+ ### 2. Performance
30
+ - Render-blocking resources (synchronous scripts in `<head>`)
31
+ - Missing `loading="lazy"` on below-fold images
32
+ - Animating layout properties (`width`, `height`, `top`, `left`) instead of `transform`/`opacity`
33
+ - Missing image dimensions (`width`/`height` attributes) causing CLS
34
+ - No `prefers-reduced-motion` support for animations
35
+
36
+ ### 3. Theming
37
+ - Hard-coded colors not using design tokens or CSS custom properties
38
+ - Broken dark mode (elements invisible or unreadable in dark theme)
39
+ - Inconsistent token usage across the same component
40
+
41
+ ### 4. Responsive
42
+ - Fixed widths (`width: 500px`) that break on mobile viewports
43
+ - Touch targets smaller than 44×44px
44
+ - Horizontal scroll on narrow viewports (< 375px)
45
+ - Text that does not scale with user font-size preferences
46
+
47
+ ### 5. Anti-patterns
48
+ - AI slop tells: gratuitous gradient text, excessive card grids, bounce animations, glassmorphism overuse
49
+ - Gray text on colored backgrounds (poor readability)
50
+ - Deeply nested cards (card inside card inside card)
51
+ - Generic fallback fonts without a proper font stack
52
+
53
+ ## Phase 2 — Fix
54
+
55
+ For each issue found in Phase 1, apply the fix directly. Use the **Edit** tool for targeted changes — never use Write to overwrite entire files.
56
+
57
+ ### Priority order
58
+ 1. **Critical a11y** — keyboard accessibility, ARIA attributes, semantic HTML
59
+ 2. **Performance** — CLS fixes, render-blocking resources
60
+ 3. **Theming** — design token consistency, dark mode
61
+ 4. **Responsive** — viewport, touch targets, scaling
62
+ 5. **Anti-pattern cleanup** — slop removal, readability
63
+
64
+ ### Rules
65
+ - Each fix must be minimal and targeted (Edit, not Write)
66
+ - Only use Read, Edit, Grep, Glob, and Bash tools
67
+ - Verify each fix with `git diff` to confirm only intended lines changed
68
+ - If a fix would require changes outside the diff, skip it and note it in the report
69
+
70
+ ## Phase 3 — Report
71
+
72
+ Output a strict JSON object:
73
+
74
+ ```json
75
+ {
76
+ "ok": true,
77
+ "result": {
78
+ "verdict": "APPROVED",
79
+ "issuesFound": 0,
80
+ "issuesFixed": 0,
81
+ "categories": {
82
+ "a11y": 0,
83
+ "performance": 0,
84
+ "theming": 0,
85
+ "responsive": 0,
86
+ "antiPatterns": 0
87
+ },
88
+ "changes": []
89
+ },
90
+ "summary": "No frontend design issues found"
91
+ }
92
+ ```
93
+
94
+ When issues are found and fixed:
95
+
96
+ ```json
97
+ {
98
+ "ok": true,
99
+ "result": {
100
+ "verdict": "IMPROVED",
101
+ "issuesFound": 3,
102
+ "issuesFixed": 3,
103
+ "categories": {
104
+ "a11y": 2,
105
+ "performance": 1,
106
+ "theming": 0,
107
+ "responsive": 0,
108
+ "antiPatterns": 0
109
+ },
110
+ "changes": [
111
+ {
112
+ "file": "src/components/Button.astro",
113
+ "issue": "Non-semantic div used as button",
114
+ "fix": "Replaced <div onclick> with <button>",
115
+ "category": "a11y"
116
+ }
117
+ ]
118
+ },
119
+ "summary": "3 design issues found and fixed (2 a11y, 1 performance)"
120
+ }
121
+ ```
122
+
123
+ ### Verdict rules
124
+ - **APPROVED** — No frontend design issues found (issuesFound === 0)
125
+ - **IMPROVED** — Issues were found and fixes were applied (issuesFixed > 0)
@@ -9,7 +9,7 @@ Return a single valid JSON object and nothing else:
9
9
  {
10
10
  "level": "trivial|simple|medium|complex",
11
11
  "taskType": "sw|infra|doc|add-tests|refactor",
12
- "roles": ["planner", "researcher", "refactorer", "reviewer", "tester", "security"],
12
+ "roles": ["planner", "researcher", "refactorer", "reviewer", "tester", "security", "impeccable"],
13
13
  "reasoning": "brief practical justification",
14
14
  "shouldDecompose": false,
15
15
  "subtasks": []
@@ -41,6 +41,13 @@ When `shouldDecompose` is true, provide `subtasks`: an array of 2-5 short string
41
41
 
42
42
  When `shouldDecompose` is false, `subtasks` must be an empty array.
43
43
 
44
+ ## Frontend detection
45
+ If the task involves frontend/UI work, include `"impeccable"` in `roles`. Detect frontend tasks by:
46
+ - **File extensions**: .html, .css, .astro, .jsx, .tsx, .vue, .svelte
47
+ - **Keywords in description**: UI, landing, component, responsive, accessibility, a11y, frontend, design, layout, styling, dark mode, animation, CSS, HTML
48
+
49
+ The `impeccable` role audits and fixes frontend design quality (a11y, performance, theming, responsive, anti-patterns).
50
+
44
51
  ## Rules
45
52
  - Keep `reasoning` short.
46
53
  - Recommend only roles that add clear value.
@@ -0,0 +1,45 @@
1
+ # kj-architect — Architecture Design
2
+
3
+ Analyze the task and propose an architecture before implementation.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps
10
+
11
+ 1. Read the task and understand the requirements
12
+ 2. Explore the existing codebase structure (`ls`, `find`, read key files)
13
+ 3. Identify the appropriate architectural approach
14
+ 4. Propose a design with tradeoffs
15
+
16
+ ## What to deliver
17
+
18
+ ### Architecture overview
19
+ - Architecture type (layered, hexagonal, event-driven, etc.)
20
+ - Key components/layers and their responsibilities
21
+ - Data flow between components
22
+
23
+ ### API contracts (if applicable)
24
+ - Endpoints with method, path, request/response schema
25
+ - Error handling strategy
26
+
27
+ ### Data model changes (if applicable)
28
+ - New entities/collections
29
+ - Modified fields
30
+ - Migration strategy
31
+
32
+ ### Tradeoffs
33
+ - For each design decision: what was chosen, why, and what alternatives were considered
34
+ - Constraints that influenced the design
35
+
36
+ ### Clarification questions
37
+ - Any ambiguities that could affect the architecture
38
+ - Decisions that need stakeholder input
39
+
40
+ ## Constraints
41
+
42
+ - Follow existing patterns in the codebase — don't introduce a new architecture without justification
43
+ - Keep it simple — the right amount of complexity is the minimum needed
44
+ - Consider testability in every design decision
45
+ - Do NOT start coding — this is design only
@@ -0,0 +1,51 @@
1
+ # kj-code — Coder with Guardrails
2
+
3
+ Implement the task with TDD methodology and built-in quality checks.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Methodology
10
+
11
+ 1. **Tests first**: Write or update tests BEFORE implementation
12
+ 2. **Implement**: Write minimal, focused code to pass the tests
13
+ 3. **Verify**: Run the test suite (`npm test` or project equivalent)
14
+ 4. **Check diff**: Run `git diff` and verify ONLY intended lines changed
15
+
16
+ ## Guardrails (MANDATORY)
17
+
18
+ After writing code, verify ALL of these before reporting done:
19
+
20
+ ### Security check
21
+ - [ ] No hardcoded credentials, API keys, or secrets in the diff
22
+ - [ ] No `eval()`, `innerHTML` with user input, or SQL string concatenation
23
+ - [ ] User input is validated/sanitized at system boundaries
24
+
25
+ ### Destructive operation check
26
+ - [ ] No `rm -rf /`, `DROP TABLE`, `git push --force`, or similar in the diff
27
+ - [ ] No `fs.rmSync` or `fs.rm` on paths derived from user input
28
+ - [ ] No `process.exit()` in library code
29
+
30
+ ### Performance check
31
+ - [ ] No synchronous file I/O (`readFileSync`, `writeFileSync`) in request handlers
32
+ - [ ] No `document.write()` or layout thrashing patterns
33
+ - [ ] No unbounded loops or missing pagination
34
+
35
+ ### TDD check
36
+ - [ ] Source changes have corresponding test changes
37
+ - [ ] Tests actually run and pass
38
+
39
+ ## File modification safety
40
+
41
+ - NEVER overwrite existing files entirely — make targeted edits
42
+ - After each edit, verify with `git diff` that ONLY intended lines changed
43
+ - If unintended changes detected, revert immediately with `git checkout -- <file>`
44
+
45
+ ## Completeness check
46
+
47
+ Before reporting done:
48
+ - Re-read the task description
49
+ - Check every requirement is addressed
50
+ - Run the test suite
51
+ - Verify no regressions
@@ -0,0 +1,24 @@
1
+ # kj-discover — Gap Detection
2
+
3
+ Analyze the task for gaps, ambiguities, and missing information BEFORE coding.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## What to do
10
+
11
+ 1. Read the task description carefully
12
+ 2. Identify gaps: missing requirements, implicit assumptions, ambiguities, contradictions
13
+ 3. Classify each gap: **critical** (blocks implementation), **major** (risks rework), **minor** (reasonable default exists)
14
+ 4. For each gap, suggest a specific question to resolve it
15
+ 5. Give a verdict: **ready** (no gaps) or **needs_validation** (gaps found)
16
+
17
+ ## Output
18
+
19
+ Present findings clearly:
20
+ - List each gap with severity and suggested question
21
+ - Give your verdict at the end
22
+ - If ready, say so and suggest proceeding to implementation
23
+
24
+ Do NOT start coding. This is analysis only.
@@ -0,0 +1,47 @@
1
+ # kj-review — Code Review with Quality Gates
2
+
3
+ Review the current changes against task requirements and quality standards.
4
+
5
+ ## Your task
6
+
7
+ Review the changes in the current branch: $ARGUMENTS
8
+
9
+ ## Steps
10
+
11
+ 1. Run `git diff main...HEAD` (or appropriate base branch) to see all changes
12
+ 2. Review each changed file against the priorities below
13
+ 3. Report findings clearly
14
+
15
+ ## Review priorities (in order)
16
+
17
+ 1. **Security** — vulnerabilities, exposed secrets, injection vectors
18
+ 2. **Correctness** — logic errors, edge cases, broken tests
19
+ 3. **Tests** — adequate coverage, meaningful assertions
20
+ 4. **Architecture** — patterns, maintainability, SOLID principles
21
+ 5. **Style** — naming, formatting (only flag if egregious)
22
+
23
+ ## Scope constraint
24
+
25
+ - **ONLY review files present in the diff** — do not flag issues in untouched files
26
+ - Out-of-scope issues go as suggestions, never as blocking
27
+
28
+ ## Guardrails (auto-check)
29
+
30
+ Flag as BLOCKING if any of these are detected in the diff:
31
+ - [ ] Hardcoded credentials, API keys, or secrets
32
+ - [ ] Entire file replaced (massive deletions + additions instead of targeted edits)
33
+ - [ ] `eval()`, `innerHTML` with user input, SQL string concatenation
34
+ - [ ] Missing test changes when source files changed (TDD violation)
35
+ - [ ] `rm -rf`, `DROP TABLE`, `git push --force` or similar destructive operations
36
+
37
+ ## Output
38
+
39
+ For each issue found:
40
+ - **File and line** where the issue is
41
+ - **Severity**: critical / major / minor
42
+ - **Description**: what's wrong
43
+ - **Suggested fix**: how to fix it
44
+
45
+ End with a clear verdict:
46
+ - **APPROVED** — no blocking issues found
47
+ - **REQUEST_CHANGES** — blocking issues listed above must be fixed
@@ -0,0 +1,69 @@
1
+ # kj-run — Full Pipeline (Skills Mode)
2
+
3
+ Execute the complete Karajan pipeline as sequential skills.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Pipeline steps (execute in order)
10
+
11
+ ### Step 1 — Discover (optional but recommended)
12
+ Analyze the task for gaps before coding:
13
+ - Identify missing requirements, ambiguities, contradictions
14
+ - If critical gaps found, STOP and ask the user before proceeding
15
+ - If ready, continue
16
+
17
+ ### Step 2 — Code (with guardrails)
18
+ Implement the task:
19
+ 1. **Tests first** (TDD): write/update tests before implementation
20
+ 2. **Implement**: minimal, focused code to fulfill the task
21
+ 3. **Verify**: run the test suite
22
+ 4. **Security check**: no hardcoded secrets, no injection vectors, no destructive ops in the diff
23
+ 5. **Diff check**: run `git diff` and verify only intended lines changed
24
+ 6. If any guardrail fails, fix before proceeding
25
+
26
+ ### Step 3 — Review (self-review)
27
+ Review your own changes against quality standards:
28
+ 1. Run `git diff main...HEAD` (or base branch)
29
+ 2. Check: security, correctness, tests, architecture, style (in that order)
30
+ 3. Flag blocking issues:
31
+ - Hardcoded credentials or secrets
32
+ - Entire files overwritten instead of targeted edits
33
+ - Missing tests for new code
34
+ - SQL injection, XSS, command injection
35
+ - Destructive operations
36
+ 4. If blocking issues found, fix them and re-review
37
+ 5. If clean, proceed
38
+
39
+ ### Step 4 — Test audit
40
+ Verify test quality:
41
+ 1. Every changed source file has corresponding tests
42
+ 2. Run `npm test` (or equivalent) — all must pass
43
+ 3. No skipped tests for changed code
44
+ 4. If tests fail, fix before proceeding
45
+
46
+ ### Step 5 — Security scan
47
+ Quick security audit on the diff:
48
+ 1. Scan for OWASP top 10 in changed files
49
+ 2. Check for leaked secrets, injection vectors, missing auth
50
+ 3. If critical/high findings, fix before proceeding
51
+
52
+ ### Step 6 — Sonar (if available)
53
+ If SonarQube is running (`docker ps | grep sonarqube`):
54
+ 1. Run `npx @sonar/scan`
55
+ 2. Check quality gate
56
+ 3. Fix blockers and critical issues
57
+
58
+ ### Step 7 — Commit
59
+ If all steps pass:
60
+ 1. Stage changed files: `git add <specific files>`
61
+ 2. Commit with conventional commit message: `feat:`, `fix:`, `refactor:`, etc.
62
+ 3. Do NOT push unless the user explicitly asks
63
+
64
+ ## Important rules
65
+
66
+ - **Never skip steps** — execute all applicable steps in order
67
+ - **Fix before proceeding** — if a step finds issues, fix them before moving to the next
68
+ - **Report progress** — after each step, briefly state what was done and the result
69
+ - **Stop on critical** — if a critical security or correctness issue can't be fixed, stop and report
@@ -0,0 +1,49 @@
1
+ # kj-security — Security Audit
2
+
3
+ Perform a security audit on the current changes.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps
10
+
11
+ 1. Run `git diff main...HEAD` to see all changes
12
+ 2. Scan for each vulnerability category below
13
+ 3. Report findings with severity and remediation
14
+
15
+ ## Vulnerability categories
16
+
17
+ ### Critical
18
+ - [ ] Hardcoded secrets (API keys, passwords, tokens, connection strings)
19
+ - [ ] SQL injection (string concatenation in queries)
20
+ - [ ] Command injection (`exec`, `spawn` with unsanitized input)
21
+ - [ ] Path traversal (file operations with user-controlled paths)
22
+
23
+ ### High
24
+ - [ ] XSS (Cross-Site Scripting) — `innerHTML`, `dangerouslySetInnerHTML` with user input
25
+ - [ ] Missing authentication/authorization checks on new endpoints
26
+ - [ ] Insecure deserialization
27
+ - [ ] SSRF (Server-Side Request Forgery) — fetch/request with user-controlled URLs
28
+
29
+ ### Medium
30
+ - [ ] Missing input validation at system boundaries
31
+ - [ ] Verbose error messages that leak internal details
32
+ - [ ] Missing CSRF protection on state-changing endpoints
33
+ - [ ] Insecure random number generation for security purposes
34
+
35
+ ### Low
36
+ - [ ] Missing security headers
37
+ - [ ] Dependencies with known vulnerabilities (check `npm audit`)
38
+ - [ ] Console.log with sensitive data
39
+
40
+ ## Output
41
+
42
+ For each finding:
43
+ - **Severity**: critical / high / medium / low
44
+ - **File and line**: where the issue is
45
+ - **Category**: which vulnerability type
46
+ - **Description**: what's wrong
47
+ - **Remediation**: specific fix
48
+
49
+ End with a summary: total findings by severity, and whether the code is safe to ship.
@@ -0,0 +1,41 @@
1
+ # kj-sonar — Static Analysis
2
+
3
+ Run SonarQube/SonarCloud analysis and fix any issues found.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps
10
+
11
+ 1. Check if SonarQube is running: `docker ps | grep sonarqube`
12
+ 2. If running, execute scan:
13
+ ```bash
14
+ npx @sonar/scan -Dsonar.host.url=http://localhost:9000 -Dsonar.projectKey=<project-key>
15
+ ```
16
+ 3. Check quality gate status:
17
+ ```bash
18
+ curl -s -u admin:admin "http://localhost:9000/api/qualitygates/project_status?projectKey=<project-key>"
19
+ ```
20
+ 4. List issues:
21
+ ```bash
22
+ curl -s -u admin:admin "http://localhost:9000/api/issues/search?projectKeys=<project-key>&statuses=OPEN&ps=50"
23
+ ```
24
+
25
+ ## If SonarQube is not available
26
+
27
+ Perform manual static analysis checks:
28
+ - [ ] Cognitive complexity — functions over 15 should be refactored
29
+ - [ ] Duplicated code blocks (3+ lines repeated)
30
+ - [ ] Unused imports and variables
31
+ - [ ] Empty catch blocks without comments
32
+ - [ ] Nested ternary operations
33
+ - [ ] `console.log` left in production code
34
+
35
+ ## Output
36
+
37
+ Report:
38
+ - Quality gate status (passed/failed)
39
+ - Issues found by severity (blocker, critical, major, minor)
40
+ - For each issue: file, line, rule, and suggested fix
41
+ - Fix critical and blocker issues before proceeding
@@ -0,0 +1,40 @@
1
+ # kj-test — Test Quality Audit
2
+
3
+ Evaluate test coverage and quality for the current changes.
4
+
5
+ ## Your task
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps
10
+
11
+ 1. Run `git diff main...HEAD` to identify changed source files
12
+ 2. For each changed source file, find the corresponding test file
13
+ 3. Run the test suite and check results
14
+ 4. Evaluate test quality
15
+
16
+ ## Checks
17
+
18
+ ### Coverage
19
+ - [ ] Every changed source file has a corresponding test file
20
+ - [ ] New functions/methods have at least one test
21
+ - [ ] Edge cases are covered (null, empty, boundary values)
22
+
23
+ ### Quality
24
+ - [ ] Tests have meaningful assertions (not just "no error thrown")
25
+ - [ ] Test descriptions clearly state what is being tested
26
+ - [ ] No tests that always pass (e.g., empty test body, `expect(true).toBe(true)`)
27
+ - [ ] Mocks are minimal — prefer real implementations where feasible
28
+
29
+ ### Execution
30
+ - [ ] Run `npm test` (or project equivalent) and report results
31
+ - [ ] All tests pass
32
+ - [ ] No skipped tests (`.skip`) for the changed code
33
+
34
+ ## Output
35
+
36
+ Report:
37
+ - Test files found/missing for each changed source file
38
+ - Test execution results (pass/fail count)
39
+ - Quality issues found
40
+ - Suggestions for improving coverage