karajan-code 1.48.0 → 1.50.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/agents/resolve-bin.js +1 -1
- package/src/audit/basal-cost.js +26 -23
- package/src/bootstrap.js +2 -2
- package/src/commands/agents.js +1 -1
- package/src/commands/board.js +3 -3
- package/src/commands/code.js +1 -1
- package/src/commands/doctor.js +4 -4
- package/src/commands/init.js +2 -2
- package/src/commands/report.js +1 -1
- package/src/commands/review.js +1 -1
- package/src/commands/roles.js +1 -1
- package/src/hu/store.js +9 -5
- package/src/mcp/handlers/direct-handlers.js +393 -0
- package/src/mcp/handlers/hu-handlers.js +93 -0
- package/src/mcp/handlers/management-handlers.js +182 -0
- package/src/mcp/handlers/run-handler.js +283 -0
- package/src/mcp/orphan-guard.js +1 -1
- package/src/mcp/server-handlers.js +41 -871
- package/src/mcp/suggest-handler.js +2 -2
- package/src/orchestrator/hu-sub-pipeline.js +1 -1
- package/src/orchestrator/iteration-stages.js +9 -890
- package/src/orchestrator/pre-loop-stages.js +8 -906
- package/src/orchestrator/preflight-checks.js +13 -19
- package/src/orchestrator/stages/architect-stage.js +176 -0
- package/src/orchestrator/stages/coder-stage.js +307 -0
- package/src/orchestrator/stages/hu-reviewer-stage.js +467 -0
- package/src/orchestrator/stages/planner-stage.js +90 -0
- package/src/orchestrator/stages/research-stage.js +74 -0
- package/src/orchestrator/stages/reviewer-stage.js +350 -0
- package/src/orchestrator/stages/sonar-stage.js +267 -0
- package/src/orchestrator/stages/triage-stage.js +137 -0
- package/src/plugins/loader.js +1 -1
- package/src/prompts/architect.js +1 -1
- package/src/review/parser.js +3 -3
- package/src/roles/architect-role.js +1 -1
- package/src/roles/audit-role.js +3 -3
- package/src/roles/base-role.js +1 -1
- package/src/roles/discover-role.js +1 -1
- package/src/roles/hu-reviewer-role.js +1 -1
- package/src/roles/researcher-role.js +1 -1
- package/src/roles/triage-role.js +1 -1
- package/src/session-cleanup.js +1 -1
- package/src/session-store.js +2 -2
- package/src/skills/openskills-client.js +1 -1
- package/src/skills/skill-detector.js +2 -2
- package/src/sonar/api.js +4 -3
- package/src/sonar/config-resolver.js +69 -0
- package/src/sonar/credentials.js +1 -1
- package/src/sonar/project-key.js +1 -1
- package/src/sonar/scanner.js +13 -21
- package/src/utils/fs.js +1 -1
- package/src/utils/json-extract.js +1 -1
- package/src/utils/project-detect.js +1 -1
- package/src/utils/rtk-detect.js +1 -1
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ function getNvmDirs() {
|
|
|
26
26
|
const versionsDir = path.join(nvmDir, "versions", "node");
|
|
27
27
|
try {
|
|
28
28
|
return readdirSync(versionsDir).map(v => path.join(versionsDir, v, "bin"));
|
|
29
|
-
} catch {
|
|
29
|
+
} catch { /* nvm versions dir may not exist */
|
|
30
30
|
return [];
|
|
31
31
|
}
|
|
32
32
|
}
|
package/src/audit/basal-cost.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { execFile
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
4
5
|
import { getKarajanHome } from "../utils/paths.js";
|
|
5
6
|
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
|
|
6
9
|
const SOURCE_EXTENSIONS = new Set([
|
|
7
10
|
".js", ".mjs", ".cjs", ".ts", ".mts", ".cts", ".tsx", ".jsx",
|
|
8
11
|
".py", ".rb", ".go", ".rs", ".java", ".kt", ".swift", ".c", ".cpp", ".h",
|
|
@@ -18,11 +21,11 @@ function slugify(projectDir) {
|
|
|
18
21
|
return path.basename(projectDir).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
function walkSourceFiles(dir) {
|
|
24
|
+
async function walkSourceFiles(dir) {
|
|
22
25
|
// Use git ls-files for speed (instant vs recursive walk)
|
|
23
26
|
try {
|
|
24
|
-
const
|
|
25
|
-
return
|
|
27
|
+
const { stdout } = await execFileAsync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { cwd: dir, encoding: "utf8", timeout: 5000 });
|
|
28
|
+
return stdout.trim().split("\n")
|
|
26
29
|
.filter(f => f && SOURCE_EXTENSIONS.has(path.extname(f)) && !EXCLUDE_DIRS.has(f.split("/")[0]))
|
|
27
30
|
.map(f => path.join(dir, f));
|
|
28
31
|
} catch {
|
|
@@ -30,15 +33,15 @@ function walkSourceFiles(dir) {
|
|
|
30
33
|
const files = [];
|
|
31
34
|
let entries;
|
|
32
35
|
try {
|
|
33
|
-
entries = fs.
|
|
34
|
-
} catch {
|
|
36
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
37
|
+
} catch { /* directory not readable */
|
|
35
38
|
return files;
|
|
36
39
|
}
|
|
37
40
|
for (const entry of entries) {
|
|
38
41
|
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
39
42
|
const full = path.join(dir, entry.name);
|
|
40
43
|
if (entry.isDirectory()) {
|
|
41
|
-
files.push(...walkSourceFiles(full));
|
|
44
|
+
files.push(...await walkSourceFiles(full));
|
|
42
45
|
} else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
43
46
|
files.push(full);
|
|
44
47
|
}
|
|
@@ -47,19 +50,19 @@ function walkSourceFiles(dir) {
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function countLines(filePath) {
|
|
53
|
+
async function countLines(filePath) {
|
|
51
54
|
try {
|
|
52
|
-
const content = fs.
|
|
55
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
53
56
|
return content.split("\n").length;
|
|
54
57
|
} catch {
|
|
55
58
|
return 0;
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
function countDependencies(projectDir) {
|
|
62
|
+
async function countDependencies(projectDir) {
|
|
60
63
|
const pkgPath = path.join(projectDir, "package.json");
|
|
61
64
|
try {
|
|
62
|
-
const pkg = JSON.parse(fs.
|
|
65
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
63
66
|
const deps = Object.keys(pkg.dependencies || {});
|
|
64
67
|
const devDeps = Object.keys(pkg.devDependencies || {});
|
|
65
68
|
return { dependencies: deps.length, devDependencies: devDeps.length, total: deps.length + devDeps.length };
|
|
@@ -89,7 +92,7 @@ function runDepcheck(projectDir) {
|
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
function findDeadExports(sourceFiles) {
|
|
95
|
+
async function findDeadExports(sourceFiles) {
|
|
93
96
|
const exportMap = new Map(); // file -> [exportName]
|
|
94
97
|
const importedNames = new Set();
|
|
95
98
|
|
|
@@ -101,7 +104,7 @@ function findDeadExports(sourceFiles) {
|
|
|
101
104
|
for (const file of sourceFiles) {
|
|
102
105
|
let content;
|
|
103
106
|
try {
|
|
104
|
-
content = fs.
|
|
107
|
+
content = await fs.readFile(file, "utf8");
|
|
105
108
|
} catch {
|
|
106
109
|
continue;
|
|
107
110
|
}
|
|
@@ -139,15 +142,15 @@ function findDeadExports(sourceFiles) {
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
export async function measureBasalCost(projectDir) {
|
|
142
|
-
const sourceFiles = walkSourceFiles(projectDir);
|
|
145
|
+
const sourceFiles = await walkSourceFiles(projectDir);
|
|
143
146
|
let totalLines = 0;
|
|
144
147
|
for (const f of sourceFiles) {
|
|
145
|
-
totalLines += countLines(f);
|
|
148
|
+
totalLines += await countLines(f);
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
const depInfo = countDependencies(projectDir);
|
|
151
|
+
const depInfo = await countDependencies(projectDir);
|
|
149
152
|
const depcheck = await runDepcheck(projectDir);
|
|
150
|
-
const deadExports = findDeadExports(sourceFiles);
|
|
153
|
+
const deadExports = await findDeadExports(sourceFiles);
|
|
151
154
|
|
|
152
155
|
return {
|
|
153
156
|
totalLines,
|
|
@@ -162,20 +165,20 @@ function auditDir() {
|
|
|
162
165
|
return path.join(getKarajanHome(), "audits");
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
export function loadPreviousAudit(projectDir) {
|
|
168
|
+
export async function loadPreviousAudit(projectDir) {
|
|
166
169
|
const file = path.join(auditDir(), slugify(projectDir), "latest.json");
|
|
167
170
|
try {
|
|
168
|
-
return JSON.parse(fs.
|
|
171
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
169
172
|
} catch {
|
|
170
173
|
return null;
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
export function saveAuditSnapshot(projectDir, metrics) {
|
|
177
|
+
export async function saveAuditSnapshot(projectDir, metrics) {
|
|
175
178
|
const dir = path.join(auditDir(), slugify(projectDir));
|
|
176
|
-
fs.
|
|
179
|
+
await fs.mkdir(dir, { recursive: true });
|
|
177
180
|
const snapshot = { ...metrics, timestamp: new Date().toISOString() };
|
|
178
|
-
fs.
|
|
181
|
+
await fs.writeFile(path.join(dir, "latest.json"), JSON.stringify(snapshot, null, 2));
|
|
179
182
|
return snapshot;
|
|
180
183
|
}
|
|
181
184
|
|
package/src/bootstrap.js
CHANGED
|
@@ -38,7 +38,7 @@ async function checkGitRepo() {
|
|
|
38
38
|
let ok = false;
|
|
39
39
|
try {
|
|
40
40
|
ok = await ensureGitRepo();
|
|
41
|
-
} catch {
|
|
41
|
+
} catch { /* git may not be installed */
|
|
42
42
|
ok = false;
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
@@ -156,7 +156,7 @@ async function readBootstrapFile(projectDir) {
|
|
|
156
156
|
try {
|
|
157
157
|
const raw = await fs.readFile(bootstrapPath(projectDir), "utf8");
|
|
158
158
|
return JSON.parse(raw);
|
|
159
|
-
} catch {
|
|
159
|
+
} catch { /* bootstrap file may not exist or contain invalid JSON */
|
|
160
160
|
return null;
|
|
161
161
|
}
|
|
162
162
|
}
|
package/src/commands/agents.js
CHANGED
|
@@ -51,7 +51,7 @@ export async function setAgent(role, provider, { global: isGlobal = false } = {}
|
|
|
51
51
|
const { setSessionOverride } = await import("../mcp/preflight.js");
|
|
52
52
|
setSessionOverride(role, provider);
|
|
53
53
|
return { role, provider, scope: "session" };
|
|
54
|
-
} catch {
|
|
54
|
+
} catch { /* preflight module not available in CLI mode */
|
|
55
55
|
// preflight module not available (CLI mode) — write to project config
|
|
56
56
|
const projectConfigPath = getProjectConfigPath();
|
|
57
57
|
const projectConfig = (await loadProjectConfig()) || {};
|
package/src/commands/board.js
CHANGED
|
@@ -10,7 +10,7 @@ function readPid() {
|
|
|
10
10
|
try {
|
|
11
11
|
const pid = Number.parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
12
12
|
return Number.isFinite(pid) ? pid : null;
|
|
13
|
-
} catch {
|
|
13
|
+
} catch { /* PID file does not exist */
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -19,7 +19,7 @@ function isProcessAlive(pid) {
|
|
|
19
19
|
try {
|
|
20
20
|
process.kill(pid, 0);
|
|
21
21
|
return true;
|
|
22
|
-
} catch {
|
|
22
|
+
} catch { /* process does not exist */
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -55,7 +55,7 @@ export async function stopBoard() {
|
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
57
|
process.kill(pid, "SIGTERM");
|
|
58
|
-
} catch {
|
|
58
|
+
} catch { /* process already dead */
|
|
59
59
|
// already dead
|
|
60
60
|
}
|
|
61
61
|
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
|
package/src/commands/code.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function codeCommand({ task, config, logger }) {
|
|
|
13
13
|
if (config.coder_rules) {
|
|
14
14
|
try {
|
|
15
15
|
coderRules = await fs.readFile(config.coder_rules, "utf8");
|
|
16
|
-
} catch {
|
|
16
|
+
} catch { /* configured coder_rules path not found — try fallback */
|
|
17
17
|
try { coderRules = await fs.readFile("coder-rules.md", "utf8"); } catch { /* no coder rules file */ }
|
|
18
18
|
}
|
|
19
19
|
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -44,7 +44,7 @@ async function checkGitRepo() {
|
|
|
44
44
|
let gitOk = false;
|
|
45
45
|
try {
|
|
46
46
|
gitOk = await ensureGitRepo();
|
|
47
|
-
} catch {
|
|
47
|
+
} catch { /* git check failed */
|
|
48
48
|
gitOk = false;
|
|
49
49
|
}
|
|
50
50
|
return {
|
|
@@ -79,7 +79,7 @@ async function checkSonarQube(config) {
|
|
|
79
79
|
if (config.sonarqube?.enabled !== false) {
|
|
80
80
|
try {
|
|
81
81
|
sonarOk = await isSonarReachable(sonarHost);
|
|
82
|
-
} catch {
|
|
82
|
+
} catch { /* sonar check failed */
|
|
83
83
|
sonarOk = false;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -130,7 +130,7 @@ async function checkSerena() {
|
|
|
130
130
|
try {
|
|
131
131
|
const serenaCheck = await runCommand("serena", ["--version"]);
|
|
132
132
|
serenaOk = serenaCheck.exitCode === 0;
|
|
133
|
-
} catch {
|
|
133
|
+
} catch { /* serena not installed */
|
|
134
134
|
serenaOk = false;
|
|
135
135
|
}
|
|
136
136
|
return {
|
|
@@ -181,7 +181,7 @@ async function checkBecariaSecrets() {
|
|
|
181
181
|
detail: secretsOk ? "BECARIA_APP_ID + BECARIA_APP_PRIVATE_KEY found" : `Missing: ${missing}`,
|
|
182
182
|
fix: secretsOk ? null : "Add BECARIA_APP_ID and BECARIA_APP_PRIVATE_KEY as GitHub repository secrets"
|
|
183
183
|
};
|
|
184
|
-
} catch {
|
|
184
|
+
} catch { /* GitHub API error */
|
|
185
185
|
return null;
|
|
186
186
|
}
|
|
187
187
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -163,7 +163,7 @@ async function ensureCoderRules(coderRulesPath, logger) {
|
|
|
163
163
|
let content;
|
|
164
164
|
try {
|
|
165
165
|
content = await fs.readFile(templatePath, "utf8");
|
|
166
|
-
} catch {
|
|
166
|
+
} catch { /* template not found, use inline default */
|
|
167
167
|
content = [
|
|
168
168
|
"# Coder Rules",
|
|
169
169
|
"",
|
|
@@ -333,7 +333,7 @@ export async function initCommand({ logger, flags = {} }) {
|
|
|
333
333
|
try {
|
|
334
334
|
const rtkRes = await runCommand("rtk", ["--version"]);
|
|
335
335
|
hasRtk = rtkRes.exitCode === 0;
|
|
336
|
-
} catch {
|
|
336
|
+
} catch { /* rtk not installed */
|
|
337
337
|
hasRtk = false;
|
|
338
338
|
}
|
|
339
339
|
if (!hasRtk) {
|
package/src/commands/report.js
CHANGED
package/src/commands/review.js
CHANGED
package/src/commands/roles.js
CHANGED
package/src/hu/store.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
4
7
|
import { getKarajanHome } from "../utils/paths.js";
|
|
5
8
|
|
|
6
9
|
// FUTURE: hu-storage adapter for PG/Trello/etc — currently local files only
|
|
@@ -237,13 +240,14 @@ export function addSplittingMetadata(batch, huId, metadata) {
|
|
|
237
240
|
/**
|
|
238
241
|
* Detect project info from the given directory.
|
|
239
242
|
* @param {string} cwd - Directory to detect project from.
|
|
240
|
-
* @returns {{ name: string, remoteUrl: string|null }}
|
|
243
|
+
* @returns {Promise<{ name: string, remoteUrl: string|null }>}
|
|
241
244
|
*/
|
|
242
|
-
export function detectProject(cwd) {
|
|
245
|
+
export async function detectProject(cwd) {
|
|
243
246
|
const name = path.basename(cwd);
|
|
244
247
|
let remoteUrl = null;
|
|
245
248
|
try {
|
|
246
|
-
|
|
249
|
+
const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
250
|
+
remoteUrl = stdout.trim();
|
|
247
251
|
} catch { /* not a git repo or no remote */ }
|
|
248
252
|
return { name, remoteUrl };
|
|
249
253
|
}
|
|
@@ -276,7 +280,7 @@ async function loadProjectBatch(projectDir) {
|
|
|
276
280
|
try {
|
|
277
281
|
const raw = await fs.readFile(filePath, "utf8");
|
|
278
282
|
return JSON.parse(raw);
|
|
279
|
-
} catch {
|
|
283
|
+
} catch { /* no saved batch for this project */
|
|
280
284
|
return {
|
|
281
285
|
project: projectSlug(projectDir),
|
|
282
286
|
created_at: new Date().toISOString(),
|