karajan-code 1.53.1 → 1.55.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/README.md +1 -1
- package/docs/README.es.md +1 -1
- package/package.json +1 -1
- package/src/bootstrap.js +3 -2
- package/src/cli.js +13 -0
- package/src/commands/doctor.js +6 -5
- package/src/commands/undo.js +62 -0
- package/src/mcp/handlers/management-handlers.js +12 -0
- package/src/mcp/run-kj.js +1 -0
- package/src/mcp/server-handlers.js +11 -9
- package/src/mcp/tools.js +12 -0
- package/src/orchestrator/post-loop-stages.js +8 -4
- package/src/orchestrator/preflight-checks.js +4 -3
- package/src/orchestrator.js +8 -1
- package/src/roles/impeccable-role.js +4 -2
- package/src/utils/doc-links.js +27 -0
- package/templates/roles/impeccable-design.md +87 -0
package/README.md
CHANGED
|
@@ -258,7 +258,7 @@ Not nostalgia, not stubbornness. I've been using JavaScript since 1997, when Bre
|
|
|
258
258
|
git clone https://github.com/manufosela/karajan-code.git
|
|
259
259
|
cd karajan-code
|
|
260
260
|
npm install
|
|
261
|
-
npm test # Run ~
|
|
261
|
+
npm test # Run ~2599 tests with Vitest
|
|
262
262
|
npm run validate # Lint + test
|
|
263
263
|
```
|
|
264
264
|
|
package/docs/README.es.md
CHANGED
|
@@ -231,7 +231,7 @@ No es nostalgia ni cabezoneria. Es que llevo usando JavaScript desde 1997, cuand
|
|
|
231
231
|
git clone https://github.com/manufosela/karajan-code.git
|
|
232
232
|
cd karajan-code
|
|
233
233
|
npm install
|
|
234
|
-
npm test # Ejecutar ~
|
|
234
|
+
npm test # Ejecutar ~2599 tests con Vitest
|
|
235
235
|
npm run validate # Lint + test
|
|
236
236
|
```
|
|
237
237
|
|
package/package.json
CHANGED
package/src/bootstrap.js
CHANGED
|
@@ -22,6 +22,7 @@ import { checkBinary } from "./utils/agent-detect.js";
|
|
|
22
22
|
import { exists } from "./utils/fs.js";
|
|
23
23
|
import { getConfigPath, resolveRole } from "./config.js";
|
|
24
24
|
import { isSonarReachable, sonarUp } from "./sonar/manager.js";
|
|
25
|
+
import { withDocLink } from "./utils/doc-links.js";
|
|
25
26
|
|
|
26
27
|
const BOOTSTRAP_VERSION = 1;
|
|
27
28
|
const BOOTSTRAP_TTL_HOURS = 24;
|
|
@@ -185,14 +186,14 @@ function formatBootstrapFailure(failures) {
|
|
|
185
186
|
const lines = failures.map(f =>
|
|
186
187
|
` FAIL ${f.name}: ${f.detail}\n Fix: ${f.fix}`
|
|
187
188
|
);
|
|
188
|
-
return [
|
|
189
|
+
return withDocLink([
|
|
189
190
|
"BOOTSTRAP FAILED — Environment not ready for Karajan Code.\n",
|
|
190
191
|
"The following prerequisite(s) are not met:\n",
|
|
191
192
|
...lines,
|
|
192
193
|
"",
|
|
193
194
|
"Run 'kj_doctor' for a complete environment diagnostic.",
|
|
194
195
|
"Do NOT work around these issues — fix them properly."
|
|
195
|
-
].join("\n");
|
|
196
|
+
].join("\n"), "bootstrap_failed");
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// ── Public API ───────────────────────────────────────────────────────
|
package/src/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import { researcherCommand } from "./commands/researcher.js";
|
|
|
24
24
|
import { architectCommand } from "./commands/architect.js";
|
|
25
25
|
import { auditCommand } from "./commands/audit.js";
|
|
26
26
|
import { boardCommand } from "./commands/board.js";
|
|
27
|
+
import { undoCommand } from "./commands/undo.js";
|
|
27
28
|
|
|
28
29
|
import { printUpdateNotice } from "./utils/update-check.js";
|
|
29
30
|
|
|
@@ -117,6 +118,7 @@ program
|
|
|
117
118
|
.option("--no-auto-simplify", "Force full pipeline regardless of triage level")
|
|
118
119
|
.option("--smart-models", "Enable smart model selection based on triage complexity")
|
|
119
120
|
.option("--no-smart-models", "Disable smart model selection")
|
|
121
|
+
.option("--design", "Activate design refactoring mode (impeccable role applies design changes)")
|
|
120
122
|
.option("--dry-run", "Show what would be executed without running anything")
|
|
121
123
|
.option("--json", "Output JSON only (no styled display)")
|
|
122
124
|
.option("-q, --quiet", "Show only stage status lines, suppress raw agent output (default)")
|
|
@@ -323,6 +325,17 @@ program
|
|
|
323
325
|
});
|
|
324
326
|
});
|
|
325
327
|
|
|
328
|
+
program
|
|
329
|
+
.command("undo")
|
|
330
|
+
.description("Revert last pipeline run")
|
|
331
|
+
.option("--hard", "Discard all changes (default: soft reset, keeps changes staged)")
|
|
332
|
+
.action(async (flags) => {
|
|
333
|
+
await withConfig("undo", flags, async ({ logger }) => {
|
|
334
|
+
const result = await undoCommand({ hard: !!flags.hard, logger });
|
|
335
|
+
if (!result.ok) process.exit(1);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
326
339
|
const sonar = program.command("sonar").description("Manage SonarQube container");
|
|
327
340
|
sonar.command("status").action(async () => sonarCommand({ action: "status" }));
|
|
328
341
|
sonar.command("start").action(async () => sonarCommand({ action: "start" }));
|
package/src/commands/doctor.js
CHANGED
|
@@ -11,6 +11,7 @@ import { resolveRoleMdPath, loadFirstExisting } from "../roles/base-role.js";
|
|
|
11
11
|
import { ensureGitRepo } from "../utils/git.js";
|
|
12
12
|
import { checkBinary, KNOWN_AGENTS } from "../utils/agent-detect.js";
|
|
13
13
|
import { getInstallCommand } from "../utils/os-detect.js";
|
|
14
|
+
import { withDocLink } from "../utils/doc-links.js";
|
|
14
15
|
|
|
15
16
|
function getPackageVersion() {
|
|
16
17
|
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../package.json");
|
|
@@ -36,7 +37,7 @@ async function checkConfigFile() {
|
|
|
36
37
|
label: "Config file",
|
|
37
38
|
ok: configExists,
|
|
38
39
|
detail: configExists ? configPath : "Not found",
|
|
39
|
-
fix: configExists ? null : "Run 'kj init' to create the config file."
|
|
40
|
+
fix: configExists ? null : withDocLink("Run 'kj init' to create the config file.", "config_missing")
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -63,7 +64,7 @@ async function checkDocker() {
|
|
|
63
64
|
label: "Docker",
|
|
64
65
|
ok: docker.ok,
|
|
65
66
|
detail: docker.ok ? docker.version : "Not found",
|
|
66
|
-
fix: docker.ok ? null : "Install Docker: https://docs.docker.com/get-docker/"
|
|
67
|
+
fix: docker.ok ? null : withDocLink("Install Docker: https://docs.docker.com/get-docker/", "sonar_docker")
|
|
67
68
|
};
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -91,7 +92,7 @@ async function checkSonarQube(config) {
|
|
|
91
92
|
detail: sonarDetail(config, sonarOk, sonarHost),
|
|
92
93
|
fix: isOkOrDisabled
|
|
93
94
|
? null
|
|
94
|
-
: "Run 'kj sonar start' or 'docker start karajan-sonarqube'. Use --no-sonar to skip."
|
|
95
|
+
: withDocLink("Run 'kj sonar start' or 'docker start karajan-sonarqube'. Use --no-sonar to skip.", "sonar_docker")
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -104,7 +105,7 @@ async function checkAgentCLIs() {
|
|
|
104
105
|
label: `Agent: ${agent.name}`,
|
|
105
106
|
ok: result.ok,
|
|
106
107
|
detail: result.ok ? `${result.version} (${result.path})` : "Not found",
|
|
107
|
-
fix: result.ok ? null : `Install: ${agent.install}
|
|
108
|
+
fix: result.ok ? null : withDocLink(`Install: ${agent.install}`, "agent_not_found")
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
111
|
return checks;
|
|
@@ -220,7 +221,7 @@ async function checkRtk() {
|
|
|
220
221
|
label: "RTK (Rust Token Killer)",
|
|
221
222
|
ok: false,
|
|
222
223
|
detail: "Not found — 60-90% token savings available",
|
|
223
|
-
fix: `Install: ${installCmd}
|
|
224
|
+
fix: withDocLink(`Install: ${installCmd}`, "rtk_install")
|
|
224
225
|
};
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { loadMostRecentSession } from "../session-store.js";
|
|
2
|
+
import { runCommand } from "../utils/process.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Revert the last pipeline run by resetting to the pre-pipeline commit.
|
|
6
|
+
* Default: soft reset (keeps changes staged). With --hard: discards all changes.
|
|
7
|
+
*/
|
|
8
|
+
export async function undoCommand({ hard = false, logger } = {}) {
|
|
9
|
+
const session = await loadMostRecentSession();
|
|
10
|
+
if (!session) {
|
|
11
|
+
const msg = "No session to undo. No sessions found in the sessions directory.";
|
|
12
|
+
if (logger) logger.error(msg);
|
|
13
|
+
return { ok: false, error: msg };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sha = session.session_start_sha;
|
|
17
|
+
if (!sha) {
|
|
18
|
+
const msg = `Session ${session.id} has no session_start_sha. Cannot determine the pre-pipeline commit.`;
|
|
19
|
+
if (logger) logger.error(msg);
|
|
20
|
+
return { ok: false, error: msg };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Verify the SHA exists in git history
|
|
24
|
+
const verifyRes = await runCommand("git", ["cat-file", "-t", sha]);
|
|
25
|
+
if (verifyRes.exitCode !== 0 || verifyRes.stdout.trim() !== "commit") {
|
|
26
|
+
const msg = `Commit ${sha} not found in git history. It may have been garbage-collected or belong to a different repository.`;
|
|
27
|
+
if (logger) logger.error(msg);
|
|
28
|
+
return { ok: false, error: msg };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check there are commits after the SHA (HEAD is ahead)
|
|
32
|
+
const logRes = await runCommand("git", ["log", "--oneline", `${sha}..HEAD`]);
|
|
33
|
+
if (logRes.exitCode !== 0 || !logRes.stdout.trim()) {
|
|
34
|
+
const msg = `No commits to undo. HEAD is already at or before ${sha.slice(0, 8)}.`;
|
|
35
|
+
if (logger) logger.warn(msg);
|
|
36
|
+
return { ok: false, error: msg };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const mode = hard ? "--hard" : "--soft";
|
|
40
|
+
const resetRes = await runCommand("git", ["reset", mode, sha]);
|
|
41
|
+
if (resetRes.exitCode !== 0) {
|
|
42
|
+
const msg = `git reset ${mode} ${sha} failed: ${resetRes.stderr || resetRes.stdout}`;
|
|
43
|
+
if (logger) logger.error(msg);
|
|
44
|
+
return { ok: false, error: msg };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const shortSha = sha.slice(0, 8);
|
|
48
|
+
const message = hard
|
|
49
|
+
? `Reverted to pre-pipeline state (commit ${shortSha}). All changes discarded.`
|
|
50
|
+
: `Reverted to pre-pipeline state (commit ${shortSha}). Changes are staged. Use \`git diff --cached\` to review.`;
|
|
51
|
+
|
|
52
|
+
if (logger) logger.info(message);
|
|
53
|
+
console.log(message);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
message,
|
|
58
|
+
sessionId: session.id,
|
|
59
|
+
sha,
|
|
60
|
+
mode: hard ? "hard" : "soft"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -163,6 +163,18 @@ export async function handleBoard(a) {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
export async function handleUndo(a, server) {
|
|
167
|
+
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
168
|
+
const origCwd = process.cwd();
|
|
169
|
+
try {
|
|
170
|
+
process.chdir(projectDir);
|
|
171
|
+
const { undoCommand } = await import("../../commands/undo.js");
|
|
172
|
+
return undoCommand({ hard: !!a.hard });
|
|
173
|
+
} finally {
|
|
174
|
+
process.chdir(origCwd);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
166
178
|
export async function buildPreflightRequiredResponse(toolName) {
|
|
167
179
|
const { config } = await loadConfig();
|
|
168
180
|
const { listAgents } = await import("../../commands/agents.js");
|
package/src/mcp/run-kj.js
CHANGED
|
@@ -43,6 +43,7 @@ export async function runKjCommand({ command, commandArgs = [], options = {}, en
|
|
|
43
43
|
normalizeBoolFlag(options.enableTester, "--enable-tester", args);
|
|
44
44
|
normalizeBoolFlag(options.enableSecurity, "--enable-security", args);
|
|
45
45
|
normalizeBoolFlag(options.enableImpeccable, "--enable-impeccable", args);
|
|
46
|
+
normalizeBoolFlag(options.design, "--design", args);
|
|
46
47
|
normalizeBoolFlag(options.enableTriage, "--enable-triage", args);
|
|
47
48
|
normalizeBoolFlag(options.enableDiscover, "--enable-discover", args);
|
|
48
49
|
normalizeBoolFlag(options.enableArchitect, "--enable-architect", args);
|
|
@@ -10,11 +10,12 @@ import { loadConfig, applyRunOverrides, validateConfig } from "../config.js";
|
|
|
10
10
|
import { currentBranch } from "../utils/git.js";
|
|
11
11
|
import { buildProgressNotifier } from "./progress.js";
|
|
12
12
|
import { compressResponse, compactStringify } from "./response-compressor.js";
|
|
13
|
+
import { withDocLink } from "../utils/doc-links.js";
|
|
13
14
|
|
|
14
15
|
// ── Sub-module re-exports ────────────────────────────────────────────
|
|
15
16
|
export { handleRunDirect, handleResumeDirect, validateResumeAnswer, handleRun, handleResume } from "./handlers/run-handler.js";
|
|
16
17
|
export { handlePlanDirect, handleCodeDirect, handleReviewDirect, handleDiscoverDirect, handleTriageDirect, handleResearcherDirect, handleAuditDirect, handleArchitectDirect, handleCode, handleReview, handlePlan, handleDiscover, handleTriage, handleResearcher, handleArchitect, handleAudit } from "./handlers/direct-handlers.js";
|
|
17
|
-
export { handleStatus, handleAgents, handlePreflight, handleRoles, handleReport, handleInit, handleDoctor, handleConfig, handleScan, handleBoard, buildPreflightRequiredResponse } from "./handlers/management-handlers.js";
|
|
18
|
+
export { handleStatus, handleAgents, handlePreflight, handleRoles, handleReport, handleInit, handleDoctor, handleConfig, handleScan, handleBoard, handleUndo, buildPreflightRequiredResponse } from "./handlers/management-handlers.js";
|
|
18
19
|
export { handleHu, handleSkills, handleSuggest } from "./handlers/hu-handlers.js";
|
|
19
20
|
|
|
20
21
|
// ── Shared helpers (used by sub-modules and external consumers) ──────
|
|
@@ -82,22 +83,22 @@ const ERROR_CLASSIFIERS = [
|
|
|
82
83
|
{
|
|
83
84
|
test: (lower) => lower.includes("sonar") && (lower.includes("connect") || lower.includes("econnrefused") || lower.includes("not available") || lower.includes("not running")),
|
|
84
85
|
category: "sonar_unavailable",
|
|
85
|
-
suggestion: "SonarQube is not reachable. Try: kj_init to set up SonarQube, or run 'docker start sonarqube' if already installed. Use --no-sonar to skip SonarQube."
|
|
86
|
+
suggestion: withDocLink("SonarQube is not reachable. Try: kj_init to set up SonarQube, or run 'docker start sonarqube' if already installed. Use --no-sonar to skip SonarQube.", "sonar_docker")
|
|
86
87
|
},
|
|
87
88
|
{
|
|
88
89
|
test: (lower) => lower.includes("401") || lower.includes("unauthorized") || lower.includes("invalid token"),
|
|
89
90
|
category: "auth_error",
|
|
90
|
-
suggestion: "Authentication failed. Regenerate the SonarQube token and update it via kj_init or in ~/.karajan/kj.config.yml under sonarqube.token."
|
|
91
|
+
suggestion: withDocLink("Authentication failed. Regenerate the SonarQube token and update it via kj_init or in ~/.karajan/kj.config.yml under sonarqube.token.", "sonar_token")
|
|
91
92
|
},
|
|
92
93
|
{
|
|
93
94
|
test: (lower) => lower.includes("config") && (lower.includes("missing") || lower.includes("not found") || lower.includes("invalid")),
|
|
94
95
|
category: "config_error",
|
|
95
|
-
suggestion: "Configuration issue detected. Run kj_doctor to diagnose, or kj_init to create a fresh config."
|
|
96
|
+
suggestion: withDocLink("Configuration issue detected. Run kj_doctor to diagnose, or kj_init to create a fresh config.", "config_missing")
|
|
96
97
|
},
|
|
97
98
|
{
|
|
98
99
|
test: (lower) => lower.includes("missing provider") || lower.includes("not found") && (lower.includes("claude") || lower.includes("codex") || lower.includes("gemini") || lower.includes("aider")),
|
|
99
100
|
category: "agent_missing",
|
|
100
|
-
suggestion: "Required agent CLI not found. Run kj_doctor to check which agents are installed and get installation instructions."
|
|
101
|
+
suggestion: withDocLink("Required agent CLI not found. Run kj_doctor to check which agents are installed and get installation instructions.", "agent_not_found")
|
|
101
102
|
},
|
|
102
103
|
{
|
|
103
104
|
test: (lower) => lower.includes("timed out") || lower.includes("timeout"),
|
|
@@ -107,7 +108,7 @@ const ERROR_CLASSIFIERS = [
|
|
|
107
108
|
{
|
|
108
109
|
test: (lower) => lower.includes("you are on the base branch"),
|
|
109
110
|
category: "branch_error",
|
|
110
|
-
suggestion: "Create a feature branch before running Karajan. Use 'git checkout -b feat/<task-description>' and then retry. Do NOT run kj_code directly on the base branch."
|
|
111
|
+
suggestion: withDocLink("Create a feature branch before running Karajan. Use 'git checkout -b feat/<task-description>' and then retry. Do NOT run kj_code directly on the base branch.", "branch_error")
|
|
111
112
|
},
|
|
112
113
|
{
|
|
113
114
|
test: (lower) => lower.includes("not a git repository"),
|
|
@@ -117,7 +118,7 @@ const ERROR_CLASSIFIERS = [
|
|
|
117
118
|
{
|
|
118
119
|
test: (lower) => lower.includes("bootstrap failed"),
|
|
119
120
|
category: "bootstrap_error",
|
|
120
|
-
suggestion: "Environment prerequisites not met. Run kj_doctor for diagnostics, then fix the issues listed. Do NOT work around these — fix them properly."
|
|
121
|
+
suggestion: withDocLink("Environment prerequisites not met. Run kj_doctor for diagnostics, then fix the issues listed. Do NOT work around these — fix them properly.", "bootstrap_failed")
|
|
121
122
|
}
|
|
122
123
|
];
|
|
123
124
|
|
|
@@ -218,7 +219,7 @@ import {
|
|
|
218
219
|
import {
|
|
219
220
|
handleStatus, handleAgents, handlePreflight, handleRoles,
|
|
220
221
|
handleReport, handleInit, handleDoctor, handleConfig,
|
|
221
|
-
handleScan, handleBoard
|
|
222
|
+
handleScan, handleBoard, handleUndo
|
|
222
223
|
} from "./handlers/management-handlers.js";
|
|
223
224
|
import { handleHu, handleSkills, handleSuggest } from "./handlers/hu-handlers.js";
|
|
224
225
|
|
|
@@ -245,7 +246,8 @@ const toolHandlers = {
|
|
|
245
246
|
kj_board: (a) => handleBoard(a),
|
|
246
247
|
kj_hu: (a, server) => handleHu(a, server),
|
|
247
248
|
kj_suggest: (a) => handleSuggest(a),
|
|
248
|
-
kj_skills: (a) => handleSkills(a)
|
|
249
|
+
kj_skills: (a) => handleSkills(a),
|
|
250
|
+
kj_undo: (a, server) => handleUndo(a, server)
|
|
249
251
|
};
|
|
250
252
|
|
|
251
253
|
export async function handleToolCall(name, args, server, extra) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -100,6 +100,7 @@ export const tools = [
|
|
|
100
100
|
taskType: { type: "string", enum: ["sw", "infra", "doc", "add-tests", "refactor"], description: "Explicit task type for policy resolution. Overrides triage classification." },
|
|
101
101
|
quiet: { type: "boolean", description: "Suppress raw agent output lines, show only stage status (default: true). Set false for verbose output." },
|
|
102
102
|
noSonar: { type: "boolean" },
|
|
103
|
+
design: { type: "boolean", description: "Activate design refactoring mode. Impeccable role applies design changes instead of just auditing." },
|
|
103
104
|
enableSonarcloud: { type: "boolean", description: "Enable SonarCloud scan (complementary to SonarQube)" },
|
|
104
105
|
kjHome: { type: "string" },
|
|
105
106
|
sonarToken: { type: "string" },
|
|
@@ -363,5 +364,16 @@ export const tools = [
|
|
|
363
364
|
},
|
|
364
365
|
required: ["action"]
|
|
365
366
|
}
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "kj_undo",
|
|
370
|
+
description: "Revert the last pipeline run by resetting to the pre-pipeline commit. Default: soft reset (keeps changes staged). Use hard=true to discard all changes.",
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: "object",
|
|
373
|
+
properties: {
|
|
374
|
+
hard: { type: "boolean", description: "If true, discard all changes (git reset --hard). Default: false (soft reset, keeps changes staged)." },
|
|
375
|
+
projectDir: { type: "string", description: "Absolute path to the project directory" }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
366
378
|
}
|
|
367
379
|
];
|
|
@@ -247,17 +247,21 @@ export async function runSecurityStage({ config, logger, emitter, eventBase, ses
|
|
|
247
247
|
return { action: "ok", stageResult: { ok: true, summary: securityOutput.summary || "No vulnerabilities found" } };
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
export async function runImpeccableStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff }) {
|
|
250
|
+
export async function runImpeccableStage({ config, logger, emitter, eventBase, session, coderRole, trackBudget, iteration, task, diff, mode = "audit" }) {
|
|
251
251
|
logger.setContext({ iteration, stage: "impeccable" });
|
|
252
|
+
const isRefactoring = mode === "refactoring";
|
|
253
|
+
const startMessage = isRefactoring
|
|
254
|
+
? "Impeccable applying design refactoring"
|
|
255
|
+
: "Impeccable auditing frontend design quality";
|
|
252
256
|
emitProgress(
|
|
253
257
|
emitter,
|
|
254
258
|
makeEvent("impeccable:start", { ...eventBase, stage: "impeccable" }, {
|
|
255
|
-
message:
|
|
256
|
-
detail: { provider: config?.roles?.impeccable?.provider || coderRole.provider, executorType: "agent" }
|
|
259
|
+
message: startMessage,
|
|
260
|
+
detail: { provider: config?.roles?.impeccable?.provider || coderRole.provider, executorType: "agent", mode }
|
|
257
261
|
})
|
|
258
262
|
);
|
|
259
263
|
|
|
260
|
-
const impeccable = new ImpeccableRole({ config, logger, emitter });
|
|
264
|
+
const impeccable = new ImpeccableRole({ config, logger, emitter, mode });
|
|
261
265
|
await impeccable.init({ task, iteration });
|
|
262
266
|
const impeccableStart = Date.now();
|
|
263
267
|
let impeccableOutput;
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
resolveSonarToken,
|
|
20
20
|
resolveSonarCredentials,
|
|
21
21
|
} from "../sonar/config-resolver.js";
|
|
22
|
+
import { withDocLink } from "../utils/doc-links.js";
|
|
22
23
|
|
|
23
24
|
function parseJsonSafe(text) {
|
|
24
25
|
try {
|
|
@@ -188,7 +189,7 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
188
189
|
result.errors.push({
|
|
189
190
|
check: "docker",
|
|
190
191
|
message: "Docker not available but SonarQube is enabled.",
|
|
191
|
-
fix: "Start Docker, or disable SonarQube: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar."
|
|
192
|
+
fix: withDocLink("Start Docker, or disable SonarQube: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
|
|
192
193
|
});
|
|
193
194
|
logger.error("Preflight: Docker not found — SonarQube requires Docker");
|
|
194
195
|
|
|
@@ -222,7 +223,7 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
222
223
|
result.errors.push({
|
|
223
224
|
check: "sonar-reachable",
|
|
224
225
|
message: `SonarQube not reachable at ${sonarHost}.`,
|
|
225
|
-
fix: "Start SonarQube: 'docker start karajan-sonarqube', or disable it: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar."
|
|
226
|
+
fix: withDocLink("Start SonarQube: 'docker start karajan-sonarqube', or disable it: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
|
|
226
227
|
});
|
|
227
228
|
logger.error("Preflight: SonarQube not reachable after remediation attempt");
|
|
228
229
|
}
|
|
@@ -248,7 +249,7 @@ export async function runPreflightChecks({ config, logger, emitter, eventBase, r
|
|
|
248
249
|
result.errors.push({
|
|
249
250
|
check: "sonar-auth",
|
|
250
251
|
message: "SonarQube is running but no authentication token is configured.",
|
|
251
|
-
fix: "Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to ~/.karajan/kj.config.yml."
|
|
252
|
+
fix: withDocLink("Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to ~/.karajan/kj.config.yml.", "sonar_token")
|
|
252
253
|
});
|
|
253
254
|
logger.error("Preflight: Sonar auth failed");
|
|
254
255
|
}
|
package/src/orchestrator.js
CHANGED
|
@@ -262,6 +262,12 @@ function applyFlagOverrides(pipelineFlags, flags) {
|
|
|
262
262
|
if (flags.enableTester !== undefined) pipelineFlags.testerEnabled = Boolean(flags.enableTester);
|
|
263
263
|
if (flags.enableSecurity !== undefined) pipelineFlags.securityEnabled = Boolean(flags.enableSecurity);
|
|
264
264
|
if (flags.enableImpeccable !== undefined) pipelineFlags.impeccableEnabled = Boolean(flags.enableImpeccable);
|
|
265
|
+
|
|
266
|
+
// --design flag: force-enable impeccable in refactoring mode
|
|
267
|
+
if (flags.design) {
|
|
268
|
+
pipelineFlags.impeccableEnabled = true;
|
|
269
|
+
pipelineFlags.impeccableMode = "refactoring";
|
|
270
|
+
}
|
|
265
271
|
}
|
|
266
272
|
|
|
267
273
|
function resolvePipelinePolicies({ flags, config, stageResults, emitter, eventBase, session, pipelineFlags }) {
|
|
@@ -1149,9 +1155,10 @@ async function runQualityGateStages({ config, logger, emitter, eventBase, sessio
|
|
|
1149
1155
|
|
|
1150
1156
|
if (pipelineFlags?.impeccableEnabled) {
|
|
1151
1157
|
const diff = await generateDiff({ baseRef: session.session_start_sha });
|
|
1158
|
+
const impeccableMode = pipelineFlags?.impeccableMode || "audit";
|
|
1152
1159
|
const impeccableResult = await runImpeccableStage({
|
|
1153
1160
|
config, logger, emitter, eventBase, session, coderRole, trackBudget,
|
|
1154
|
-
iteration: i, task, diff
|
|
1161
|
+
iteration: i, task, diff, mode: impeccableMode
|
|
1155
1162
|
});
|
|
1156
1163
|
if (impeccableResult.stageResult) {
|
|
1157
1164
|
stageResults.impeccable = impeccableResult.stageResult;
|
|
@@ -59,9 +59,11 @@ function buildSummary(parsed) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export class ImpeccableRole extends BaseRole {
|
|
62
|
-
constructor({ config, logger, emitter = null, createAgentFn = null }) {
|
|
63
|
-
|
|
62
|
+
constructor({ config, logger, emitter = null, createAgentFn = null, mode = "audit" }) {
|
|
63
|
+
const roleName = mode === "refactoring" ? "impeccable-design" : "impeccable";
|
|
64
|
+
super({ name: roleName, config, logger, emitter });
|
|
64
65
|
this._createAgent = createAgentFn || defaultCreateAgent;
|
|
66
|
+
this.mode = mode;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
async execute(input) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation links for error messages.
|
|
3
|
+
*
|
|
4
|
+
* Maps error types to their relevant documentation page so that
|
|
5
|
+
* every actionable error includes a "See: <url>" pointer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const BASE_URL = "https://karajancode.com/docs";
|
|
9
|
+
|
|
10
|
+
const ERROR_DOCS = {
|
|
11
|
+
sonar_token: `${BASE_URL}/guides/configuration/#sonarqube`,
|
|
12
|
+
sonar_docker: `${BASE_URL}/getting-started/installation/#docker`,
|
|
13
|
+
agent_not_found: `${BASE_URL}/getting-started/installation/#agents`,
|
|
14
|
+
bootstrap_failed: `${BASE_URL}/guides/troubleshooting/`,
|
|
15
|
+
config_missing: `${BASE_URL}/getting-started/quick-start/`,
|
|
16
|
+
branch_error: `${BASE_URL}/guides/pipeline/#git-workflow`,
|
|
17
|
+
rtk_install: `${BASE_URL}/guides/configuration/#rtk`,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function docLink(errorType) {
|
|
21
|
+
return ERROR_DOCS[errorType] || `${BASE_URL}/guides/troubleshooting/`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function withDocLink(message, errorType) {
|
|
25
|
+
const link = docLink(errorType);
|
|
26
|
+
return `${message}\n See: ${link}`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Impeccable Design (Refactoring Mode)
|
|
2
|
+
|
|
3
|
+
You are refactoring the frontend design. Apply ALL of these improvements:
|
|
4
|
+
|
|
5
|
+
## Scope constraint
|
|
6
|
+
|
|
7
|
+
- **ONLY modify 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
|
+
## Visual Hierarchy
|
|
17
|
+
- Establish clear heading levels (size, weight, color)
|
|
18
|
+
- Group related elements with consistent spacing
|
|
19
|
+
- Use whitespace to separate sections
|
|
20
|
+
|
|
21
|
+
## Spacing & Alignment
|
|
22
|
+
- Consistent padding/margin using a spacing scale (4px, 8px, 16px, 24px, 32px)
|
|
23
|
+
- Align elements to a grid
|
|
24
|
+
- Remove arbitrary pixel values
|
|
25
|
+
|
|
26
|
+
## Responsive
|
|
27
|
+
- Mobile-first approach
|
|
28
|
+
- Fluid layouts with relative units (rem, %, vw)
|
|
29
|
+
- Breakpoints at 640px, 768px, 1024px, 1280px
|
|
30
|
+
|
|
31
|
+
## Accessibility
|
|
32
|
+
- Color contrast ratio >= 4.5:1 for text
|
|
33
|
+
- All interactive elements keyboard-focusable
|
|
34
|
+
- Labels on all form inputs
|
|
35
|
+
- Alt text on all images
|
|
36
|
+
|
|
37
|
+
## Micro-interactions
|
|
38
|
+
- Hover/focus states on all interactive elements
|
|
39
|
+
- Smooth transitions (150-300ms)
|
|
40
|
+
- Loading states for async operations
|
|
41
|
+
|
|
42
|
+
## Theming
|
|
43
|
+
- CSS custom properties for colors, fonts, spacing
|
|
44
|
+
- Dark mode support if applicable
|
|
45
|
+
- Consistent color palette (max 5 primary colors)
|
|
46
|
+
|
|
47
|
+
## Rules
|
|
48
|
+
- Each fix must be minimal and targeted (Edit, not Write)
|
|
49
|
+
- Only use Read, Edit, Grep, Glob, and Bash tools
|
|
50
|
+
- Verify each fix with `git diff` to confirm only intended lines changed
|
|
51
|
+
- Apply changes directly. Do not just list issues.
|
|
52
|
+
|
|
53
|
+
## Report
|
|
54
|
+
|
|
55
|
+
Output a strict JSON object:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"ok": true,
|
|
60
|
+
"result": {
|
|
61
|
+
"verdict": "IMPROVED",
|
|
62
|
+
"issuesFound": 3,
|
|
63
|
+
"issuesFixed": 3,
|
|
64
|
+
"categories": {
|
|
65
|
+
"visualHierarchy": 0,
|
|
66
|
+
"spacing": 1,
|
|
67
|
+
"responsive": 1,
|
|
68
|
+
"a11y": 1,
|
|
69
|
+
"microInteractions": 0,
|
|
70
|
+
"theming": 0
|
|
71
|
+
},
|
|
72
|
+
"changes": [
|
|
73
|
+
{
|
|
74
|
+
"file": "src/components/Button.astro",
|
|
75
|
+
"issue": "Arbitrary padding values",
|
|
76
|
+
"fix": "Replaced with spacing scale (16px)",
|
|
77
|
+
"category": "spacing"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"summary": "3 design issues found and fixed (1 spacing, 1 responsive, 1 a11y)"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Verdict rules
|
|
86
|
+
- **APPROVED** — No frontend design issues found (issuesFound === 0)
|
|
87
|
+
- **IMPROVED** — Issues were found and fixes were applied (issuesFixed > 0)
|