karajan-code 4.49.2 → 4.49.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -289,10 +289,12 @@ export function createStreamJsonFilter(onOutput) {
|
|
|
289
289
|
* sessions. The spawned `claude -p` is a separate non-interactive
|
|
290
290
|
* invocation, not a true nested session.
|
|
291
291
|
*
|
|
292
|
-
* 2.
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
292
|
+
* 2. Never inherit stdin — When launched from Node.js (which is how Claude
|
|
293
|
+
* Code / Karajan MCP runs), an inherited stdin made `claude -p` block
|
|
294
|
+
* reading what the parent was consuming. KJC-BUG-0344: the prompt now
|
|
295
|
+
* travels ON stdin (input), which also closes it, so there is nothing to
|
|
296
|
+
* wait on, and a large prompt no longer hits the kernel's per-argument
|
|
297
|
+
* limit (E2BIG).
|
|
296
298
|
*
|
|
297
299
|
* 3. Claude Code 2.x writes all structured output (stream-json, json) to
|
|
298
300
|
* stderr, NOT stdout. The agent must read from stderr for the actual
|
|
@@ -303,7 +305,7 @@ function cleanExecaOpts(extra = {}) {
|
|
|
303
305
|
// PAR-H (KJC-TSK-0631): extra.env ADDS to the inherited env (lane slot
|
|
304
306
|
// vars) instead of replacing it wholesale.
|
|
305
307
|
const { env: extraEnv, ...rest } = extra;
|
|
306
|
-
return { env: extraEnv ? { ...env, ...extraEnv } : env,
|
|
308
|
+
return { env: extraEnv ? { ...env, ...extraEnv } : env, ...rest };
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
/**
|
|
@@ -332,11 +334,12 @@ const ALLOWED_TOOLS = [
|
|
|
332
334
|
* the volatile block goes in the -p message. Without the split, legacy
|
|
333
335
|
* single-prompt behavior is unchanged.
|
|
334
336
|
*/
|
|
337
|
+
// KJC-BUG-0344: the -p message goes on stdin (`input`), never as an argument.
|
|
335
338
|
function buildPromptArgs(task) {
|
|
336
339
|
if (task.stablePrompt && task.volatilePrompt) {
|
|
337
|
-
return ["-p",
|
|
340
|
+
return { args: ["-p", "--append-system-prompt", task.stablePrompt, "--allowedTools", ...ALLOWED_TOOLS], input: task.volatilePrompt };
|
|
338
341
|
}
|
|
339
|
-
return ["-p",
|
|
342
|
+
return { args: ["-p", "--allowedTools", ...ALLOWED_TOOLS], input: String(task.prompt ?? "") };
|
|
340
343
|
}
|
|
341
344
|
|
|
342
345
|
export class ClaudeAgent extends BaseAgent {
|
|
@@ -353,7 +356,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
async _runTaskExec(task, model, _role) {
|
|
356
|
-
const args = buildPromptArgs(task);
|
|
359
|
+
const { args, input } = buildPromptArgs(task);
|
|
357
360
|
if (model) args.push("--model", model);
|
|
358
361
|
// PL-C (KJC-TSK-0735): el rol del subproceso viaja en KJ_POLICY_ROLE —
|
|
359
362
|
// si su harness corre hooks (claude -p), el tier A evalúa la policy con
|
|
@@ -366,6 +369,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
366
369
|
args.push("--output-format", "stream-json", "--verbose");
|
|
367
370
|
const streamFilter = createStreamJsonFilter(task.onOutput);
|
|
368
371
|
const res = await this.runCommand(resolveBin("claude"), args, cleanExecaOpts({
|
|
372
|
+
input,
|
|
369
373
|
onOutput: streamFilter,
|
|
370
374
|
silenceTimeoutMs: task.silenceTimeoutMs,
|
|
371
375
|
timeout: task.timeoutMs,
|
|
@@ -380,7 +384,7 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
380
384
|
|
|
381
385
|
// Without streaming, use json output to get structured response via stderr
|
|
382
386
|
args.push("--output-format", "json");
|
|
383
|
-
const res = await this.runCommand(resolveBin("claude"), args, cleanExecaOpts({ env: roleEnv, cwd: task.cwd }));
|
|
387
|
+
const res = await this.runCommand(resolveBin("claude"), args, cleanExecaOpts({ input, env: roleEnv, cwd: task.cwd }));
|
|
384
388
|
const raw = pickOutput(res);
|
|
385
389
|
const output = extractTextFromStreamJson(raw);
|
|
386
390
|
const usage = extractUsageFromStreamJson(raw);
|
|
@@ -388,9 +392,11 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
async _reviewTaskExec(task, model) {
|
|
391
|
-
const
|
|
395
|
+
const prompt = buildPromptArgs(task);
|
|
396
|
+
const args = [...prompt.args, "--output-format", "stream-json", "--verbose"];
|
|
392
397
|
if (model) args.push("--model", model);
|
|
393
398
|
const res = await this.runCommand(resolveBin("claude"), args, cleanExecaOpts({
|
|
399
|
+
input: prompt.input,
|
|
394
400
|
onOutput: task.onOutput,
|
|
395
401
|
silenceTimeoutMs: task.silenceTimeoutMs,
|
|
396
402
|
timeout: task.timeoutMs,
|
|
@@ -32,6 +32,9 @@ export const CONTRACT_PATHS = [
|
|
|
32
32
|
".karajan/supervisor-signers.json",
|
|
33
33
|
// KJC-TSK-0995 (HUM-D, ADR 0018): the security level the whole team inherits.
|
|
34
34
|
".karajan/security-level.json",
|
|
35
|
+
// KJC-BUG-0346: kj env install wires kj_rag_query as a native tool here; a clone
|
|
36
|
+
// without it loses the tool. Only committed when kj changed it in this run.
|
|
37
|
+
".mcp.json",
|
|
35
38
|
];
|
|
36
39
|
|
|
37
40
|
const FRESH_MESSAGE = "chore(bootstrap): el contrato del método, para que quien clone lo herede";
|
package/src/review/loc-budget.js
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* unbounded growth there dilutes the signal the agent receives.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Generated or vendored: nobody typed it. KJC-BUG-0347: also what the review and
|
|
23
|
+
// Solomon prompts summarize instead of pasting whole.
|
|
24
|
+
const GENERATED = [
|
|
24
25
|
/(^|\/)dist\//,
|
|
25
26
|
/(^|\/)build\//,
|
|
26
27
|
/(^|\/)coverage\//,
|
|
@@ -35,6 +36,10 @@ const EXEMPT = [
|
|
|
35
36
|
/(^|\/)tests\/_diet\//,
|
|
36
37
|
// KJC-BUG-0318: the plan `kj plan share` writes, at the project root only.
|
|
37
38
|
/^\.karajan-shared\/plans\/[^/]+\.json$/,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const EXEMPT = [
|
|
42
|
+
...GENERATED,
|
|
38
43
|
// Human documentation, wherever the project keeps it. The landing's docs
|
|
39
44
|
// moved into the monorepo (MONO-3) and the old root-anchored `docs/` rule
|
|
40
45
|
// stopped matching them, which is how documenting started costing budget.
|
|
@@ -46,6 +51,11 @@ const EXEMPT = [
|
|
|
46
51
|
// The exceptions to the exception: these ARE the agent's context.
|
|
47
52
|
const AI_RULES = [/^CLAUDE\.md$/, /^AGENTS\.md$/, /^GEMINI\.md$/, /(^|\/)templates\//];
|
|
48
53
|
|
|
54
|
+
/** Whether nobody typed this file (lockfile, build output, snapshot...). */
|
|
55
|
+
export function isGenerated(file) {
|
|
56
|
+
return Boolean(file) && GENERATED.some((re) => re.test(file));
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
/** @param {string} file @returns {boolean} */
|
|
50
60
|
export function countsTowardBudget(file) {
|
|
51
61
|
if (!file) return false;
|
|
@@ -15,6 +15,7 @@ import { resolveReviewProfile } from "./profiles.js";
|
|
|
15
15
|
import { parseMaybeJsonString, normalizeReviewPayload } from "./parser.js";
|
|
16
16
|
import { detectAvailableAgents, detectHostAgent } from "../utils/agent-detect.js";
|
|
17
17
|
import { saveVerdict, diffHash } from "./verdict-store.js";
|
|
18
|
+
import { summarizeGeneratedFiles } from "./prompt-diff.js";
|
|
18
19
|
import { reportUnparseableVerdict } from "./unparseable-verdict.js";
|
|
19
20
|
import { detectWorkspace } from "./workspace.js";
|
|
20
21
|
import { isQuotaExhausted, candidateStatus, pickQuotaFallback, formatCandidateMenu } from "./reviewer-fallback.js";
|
|
@@ -69,7 +70,8 @@ export async function runOneShotReview({
|
|
|
69
70
|
const attempt = async (who, cfg = config) => {
|
|
70
71
|
const prompt = await buildReviewerPrompt({
|
|
71
72
|
task: task || "Review the following diff for correctness, security and maintainability.",
|
|
72
|
-
|
|
73
|
+
// KJC-BUG-0347: generated files summarized; the verdict still binds to the full diff.
|
|
74
|
+
diff: summarizeGeneratedFiles(diff), reviewRules: rules, mode: "standard", provider: who, projectDir,
|
|
73
75
|
});
|
|
74
76
|
logger?.info?.(`kj review: host=${hostAgent || "none"} → reviewer=${who} (cross-AI)`);
|
|
75
77
|
return createAgentFn(who, cfg, logger).reviewTask({ prompt, role: "reviewer" });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-BUG-0347 (#2101): the diff a reviewer or Solomon READS. A generated file
|
|
3
|
+
* (lockfile, build output, snapshot) enters as one summary line instead of its
|
|
4
|
+
* thousands of lines: the reviewer kept losing the real hunks behind a 4800-line
|
|
5
|
+
* package-lock.json, and Solomon's prompt blew the argument limit (E2BIG).
|
|
6
|
+
* The verdict still binds to the full diff; this only shapes the prompt.
|
|
7
|
+
*/
|
|
8
|
+
import { isGenerated } from "./loc-budget.js";
|
|
9
|
+
|
|
10
|
+
const HEADER = /^diff --git a\/(.+?) b\/(.+)$/;
|
|
11
|
+
|
|
12
|
+
/** @param {string} diff unified diff @returns {string} */
|
|
13
|
+
export function summarizeGeneratedFiles(diff) {
|
|
14
|
+
const sections = String(diff).split(/(?=^diff --git )/m);
|
|
15
|
+
let changed = false;
|
|
16
|
+
const out = sections.map((sec) => {
|
|
17
|
+
const file = sec.split("\n", 1)[0].match(HEADER)?.[2];
|
|
18
|
+
if (!file || !isGenerated(file)) return sec;
|
|
19
|
+
changed = true;
|
|
20
|
+
const lines = sec.split("\n");
|
|
21
|
+
const added = lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).length;
|
|
22
|
+
const removed = lines.filter((l) => l.startsWith("-") && !l.startsWith("---")).length;
|
|
23
|
+
return `diff --git a/${file} b/${file}\n# ${file}: generated file, +${added} -${removed} lines, contents left out of this prompt\n`;
|
|
24
|
+
});
|
|
25
|
+
return changed ? out.join("") : diff;
|
|
26
|
+
}
|
|
@@ -16,6 +16,7 @@ import { createAgent } from "../agents/index.js";
|
|
|
16
16
|
import { parseMaybeJsonString } from "./parser.js";
|
|
17
17
|
import { detectAvailableAgents, detectHostAgent } from "../utils/agent-detect.js";
|
|
18
18
|
import { diffHash, loadVerdict, saveVerdict } from "./verdict-store.js";
|
|
19
|
+
import { summarizeGeneratedFiles } from "./prompt-diff.js";
|
|
19
20
|
|
|
20
21
|
const SECURITY_PATTERN = /injection|xss|csrf|secret|credential|password|token leak|auth(entication|orization)?|crypt|session|sanitiz|traversal|rce\b/i;
|
|
21
22
|
|
|
@@ -75,7 +76,8 @@ export async function runSolomonArbitration({
|
|
|
75
76
|
"", "## Reviewer's blocking issues",
|
|
76
77
|
JSON.stringify(verdict.issues || [], null, 2),
|
|
77
78
|
"", "## Brain's position", position,
|
|
78
|
-
|
|
79
|
+
// KJC-BUG-0347: generated files summarized; the ruling still binds to the full diff.
|
|
80
|
+
"", "## The diff under dispute", summarizeGeneratedFiles(diff),
|
|
79
81
|
].join("\n");
|
|
80
82
|
|
|
81
83
|
logger?.info?.(`kj solomon: brain=${hostAgent || "?"} vs reviewer=${verdict.reviewer} → arbiter=${solomon}`);
|