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 +1 -1
- package/src/cli.js +1 -0
- package/src/commands/init.js +51 -0
- package/src/commands/run.js +6 -0
- package/src/config.js +6 -4
- package/src/guards/intent-guard.js +13 -1
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +8 -1
- package/src/mcp/tools.js +1 -0
- package/src/orchestrator/post-loop-stages.js +51 -0
- package/src/orchestrator/pre-loop-stages.js +1 -1
- package/src/orchestrator.js +19 -5
- package/src/roles/impeccable-role.js +121 -0
- package/src/roles/triage-role.js +1 -1
- package/src/session-cleanup.js +51 -27
- package/templates/roles/impeccable.md +125 -0
- package/templates/roles/triage.md +8 -1
- package/templates/skills/kj-architect.md +45 -0
- package/templates/skills/kj-code.md +51 -0
- package/templates/skills/kj-discover.md +24 -0
- package/templates/skills/kj-review.md +47 -0
- package/templates/skills/kj-run.md +69 -0
- package/templates/skills/kj-security.md +49 -0
- package/templates/skills/kj-sonar.md +41 -0
- package/templates/skills/kj-test.md +40 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -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
|
}
|
package/src/commands/run.js
CHANGED
|
@@ -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
|
-
|
|
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 = {};
|
package/src/orchestrator.js
CHANGED
|
@@ -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
|
+
}
|
package/src/roles/triage-role.js
CHANGED
|
@@ -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) {
|
package/src/session-cleanup.js
CHANGED
|
@@ -1,48 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Automatic cleanup of expired sessions.
|
|
3
|
-
*
|
|
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
|
|
17
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
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
|