karajan-code 2.4.1 → 2.6.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 +20 -4
- package/package.json +5 -2
- package/src/agents/aider-agent.js +1 -2
- package/src/agents/base-agent.js +63 -1
- package/src/agents/claude-agent.js +3 -4
- package/src/agents/codex-agent.js +1 -2
- package/src/agents/gemini-agent.js +1 -2
- package/src/agents/index.js +11 -2
- package/src/agents/opencode-agent.js +1 -2
- package/src/brain/decision-tracker.js +123 -0
- package/src/brain/decisor.js +243 -0
- package/src/brain/solomon-consult.js +163 -0
- package/src/budget/comparison.js +96 -0
- package/src/checks/binaries.js +123 -0
- package/src/checks/ci.js +105 -0
- package/src/checks/config-files.js +217 -0
- package/src/checks/dir-setup.js +63 -0
- package/src/checks/mcp-health.js +157 -0
- package/src/checks/node.js +61 -0
- package/src/checks/ports.js +110 -0
- package/src/checks/rtk.js +36 -0
- package/src/checks/runner.js +267 -0
- package/src/checks/skills.js +49 -0
- package/src/checks/sonar.js +67 -0
- package/src/checks/system.js +53 -0
- package/src/checks/tokens.js +96 -0
- package/src/checks/types.js +105 -0
- package/src/cli.js +90 -7
- package/src/commands/board.js +23 -0
- package/src/commands/doctor.js +130 -362
- package/src/commands/plan.js +221 -37
- package/src/commands/resume.js +1 -1
- package/src/commands/run.js +2 -1
- package/src/commands/skills.js +49 -0
- package/src/config/schema.js +222 -0
- package/src/config.js +32 -3
- package/src/guards/policy-resolver.js +8 -0
- package/src/hu/store.js +9 -0
- package/src/infrastructure/command-runner.js +44 -0
- package/src/infrastructure/environment.js +38 -0
- package/src/infrastructure/file-system-service.js +63 -0
- package/src/infrastructure/mocks.js +182 -0
- package/src/mcp/handlers/management-handlers.js +7 -2
- package/src/mcp/tools.js +10 -3
- package/src/orchestrator/config-init.js +16 -1
- package/src/orchestrator/flow-runner.js +2085 -0
- package/src/orchestrator/pipeline-context.js +17 -0
- package/src/orchestrator/preflight-checks.js +78 -4
- package/src/orchestrator/solomon-escalation.js +21 -0
- package/src/orchestrator/stages/stage-executor.js +155 -0
- package/src/orchestrator.js +19 -1884
- package/src/plan/plan-executor.js +69 -0
- package/src/plan/plan-hu-ops.js +134 -0
- package/src/plan/plan-id.js +25 -0
- package/src/plan/plan-schema.js +139 -0
- package/src/plan/plan-store.js +58 -25
- package/src/prompts/architect.js +2 -2
- package/src/prompts/coder.js +2 -2
- package/src/prompts/prompt-resolver.js +127 -0
- package/src/prompts/reviewer.js +2 -2
- package/src/roles/architect-role.js +3 -1
- package/src/roles/base-role.js +23 -2
- package/src/roles/coder-role.js +3 -1
- package/src/roles/security-role.js +12 -0
- package/src/roles/tester-role.js +11 -0
- package/src/session/journal/decisions-writer.js +200 -0
- package/src/session/journal/iteration-logger.js +194 -0
- package/src/session/journal/summary-writer.js +176 -0
- package/src/session/journal/tree-writer.js +164 -0
- package/src/skills/semantic-detector.js +147 -0
- package/src/skills/skill-detector.js +186 -8
- package/src/skills/skill-loader.js +131 -1
- package/src/skills/skills-cache.js +128 -0
- package/src/types/agent.js +55 -0
- package/src/types/config.js +114 -0
- package/src/types/hu.js +47 -0
- package/src/types/index.js +34 -0
- package/src/types/policy.js +43 -0
- package/src/types/session.js +74 -0
- package/src/types/stage.js +68 -0
- package/src/utils/display/event-handlers.js +389 -0
- package/src/utils/display/formatters.js +132 -0
- package/src/utils/display/header.js +48 -0
- package/src/utils/display/session-summary.js +108 -0
- package/src/utils/display/solomon.js +42 -0
- package/src/utils/port-check.js +45 -0
- package/src/utils/port-occupant.js +95 -0
- package/src/utils/provider-env.js +116 -0
- package/src/utils/with-timeout.js +42 -0
- package/templates/roles/architect/anthropic.md +65 -0
- package/templates/roles/architect/default.md +63 -0
- package/templates/roles/architect/google.md +52 -0
- package/templates/roles/architect/openai.md +51 -0
- package/templates/roles/architect/opencode.md +50 -0
- package/templates/roles/coder/anthropic.md +72 -0
- package/templates/roles/coder/default.md +69 -0
- package/templates/roles/coder/google.md +78 -0
- package/templates/roles/coder/openai.md +59 -0
- package/templates/roles/coder/opencode.md +49 -0
- package/templates/roles/planner/anthropic.md +50 -0
- package/templates/roles/planner/default.md +48 -0
- package/templates/roles/planner/google.md +66 -0
- package/templates/roles/planner/openai.md +46 -0
- package/templates/roles/planner/opencode.md +53 -0
- package/templates/roles/reviewer/anthropic.md +61 -0
- package/templates/roles/reviewer/default.md +62 -0
- package/templates/roles/reviewer/google.md +63 -0
- package/templates/roles/reviewer/openai.md +57 -0
- package/templates/roles/reviewer/opencode.md +42 -0
- package/templates/roles/solomon/anthropic.md +71 -0
- package/templates/roles/solomon/default.md +83 -0
- package/templates/roles/solomon/google.md +61 -0
- package/templates/roles/solomon/openai.md +53 -0
- package/templates/roles/solomon/opencode.md +55 -0
- package/src/utils/display.js +0 -669
package/src/hu/store.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HU (Historia de Usuario / User Story) storage. Persists HU batches and
|
|
3
|
+
* their stories to the session directory.
|
|
4
|
+
*
|
|
5
|
+
* @typedef {import("../types/hu.js").HUStory} HUStory
|
|
6
|
+
* @typedef {import("../types/hu.js").HUBatch} HUBatch
|
|
7
|
+
* @typedef {import("../types/hu.js").HUStatus} HUStatus
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import fs from "node:fs/promises";
|
|
2
11
|
import path from "node:path";
|
|
3
12
|
import { execFile } from "node:child_process";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommandRunner — DI-friendly adapter over execa / runCommand.
|
|
3
|
+
*
|
|
4
|
+
* Why: agent adapters (claude, codex, gemini, ...) all end up invoking
|
|
5
|
+
* src/utils/process.js -> runCommand. That makes unit-testing the agent layer
|
|
6
|
+
* slow and flaky — every test either spins up a real child process or stubs
|
|
7
|
+
* execa deep inside Vitest's module graph.
|
|
8
|
+
*
|
|
9
|
+
* This adapter preserves the runCommand API exactly, so current consumers
|
|
10
|
+
* can opt-in to DI without behavioural changes. A matching MockCommandRunner
|
|
11
|
+
* lives in src/infrastructure/mocks.js and lets tests script outputs per
|
|
12
|
+
* invocation.
|
|
13
|
+
*
|
|
14
|
+
* @typedef {Object} CommandRunResult
|
|
15
|
+
* @property {number} exitCode
|
|
16
|
+
* @property {string} stdout
|
|
17
|
+
* @property {string} stderr
|
|
18
|
+
* @property {boolean} [timedOut]
|
|
19
|
+
* @property {string|null} [signal]
|
|
20
|
+
*
|
|
21
|
+
* @typedef {Object} CommandRunOptions
|
|
22
|
+
* @property {number} [timeout]
|
|
23
|
+
* @property {(evt: {stream: string, line: string}) => void} [onOutput]
|
|
24
|
+
* @property {number} [silenceTimeoutMs]
|
|
25
|
+
* @property {number} [partialOutputFlushMs]
|
|
26
|
+
* @property {string|Buffer} [input]
|
|
27
|
+
* @property {string} [cwd]
|
|
28
|
+
* @property {Record<string,string>} [env]
|
|
29
|
+
*
|
|
30
|
+
* @typedef {Object} CommandRunner
|
|
31
|
+
* @property {(command: string, args?: string[], options?: CommandRunOptions) => Promise<CommandRunResult>} run
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { runCommand } from "../utils/process.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default implementation — delegates to runCommand.
|
|
38
|
+
* @type {CommandRunner}
|
|
39
|
+
*/
|
|
40
|
+
export const defaultCommandRunner = {
|
|
41
|
+
async run(command, args = [], options = {}) {
|
|
42
|
+
return runCommand(command, args, options);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment — the DI bundle passed to services that need filesystem + command
|
|
3
|
+
* execution. Grouping them keeps call sites small: `new BaseAgent(..., env)`
|
|
4
|
+
* instead of `new BaseAgent(..., fs, runner)`.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("./file-system-service.js").FileSystemService} FileSystemService
|
|
7
|
+
* @typedef {import("./command-runner.js").CommandRunner} CommandRunner
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Object} Environment
|
|
10
|
+
* @property {FileSystemService} fs
|
|
11
|
+
* @property {CommandRunner} runner
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { defaultFileSystem } from "./file-system-service.js";
|
|
15
|
+
import { defaultCommandRunner } from "./command-runner.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default environment uses native fs + real execa-based runner.
|
|
19
|
+
* Tests should inject a custom environment via buildEnvironment() below.
|
|
20
|
+
* @type {Environment}
|
|
21
|
+
*/
|
|
22
|
+
export const defaultEnvironment = Object.freeze({
|
|
23
|
+
fs: defaultFileSystem,
|
|
24
|
+
runner: defaultCommandRunner
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Merge overrides on top of the default environment. Missing properties fall
|
|
29
|
+
* back to defaults, so callers can replace only the pieces they care about.
|
|
30
|
+
* @param {Partial<Environment>} [overrides]
|
|
31
|
+
* @returns {Environment}
|
|
32
|
+
*/
|
|
33
|
+
export function buildEnvironment(overrides = {}) {
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
fs: overrides.fs ?? defaultFileSystem,
|
|
36
|
+
runner: overrides.runner ?? defaultCommandRunner
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystemService — thin DI-friendly adapter over node:fs/promises.
|
|
3
|
+
*
|
|
4
|
+
* Why: existing code uses node:fs directly (see src/utils/fs.js and consumers),
|
|
5
|
+
* which makes unit-testing orchestrator/agent paths slow and flaky — every test
|
|
6
|
+
* that exercises a code path touching fs has to stage real files on disk.
|
|
7
|
+
*
|
|
8
|
+
* This adapter preserves the exact native API shapes for the handful of
|
|
9
|
+
* operations actually used in the codebase, so consumers can opt-in to DI
|
|
10
|
+
* without rewriting call sites. A matching MockFileSystem lives in
|
|
11
|
+
* src/infrastructure/mocks.js.
|
|
12
|
+
*
|
|
13
|
+
* Scope for TSK-0316: intentionally minimal. Only the operations already used
|
|
14
|
+
* by orchestrator + agents. Additional methods added on demand.
|
|
15
|
+
*
|
|
16
|
+
* @typedef {Object} FileSystemService
|
|
17
|
+
* @property {(path: string, encoding?: string) => Promise<string|Buffer>} readFile
|
|
18
|
+
* @property {(path: string, data: string|Buffer, encoding?: string) => Promise<void>} writeFile
|
|
19
|
+
* @property {(path: string, data: string|Buffer, encoding?: string) => Promise<void>} appendFile
|
|
20
|
+
* @property {(path: string) => Promise<boolean>} exists
|
|
21
|
+
* @property {(path: string) => Promise<void>} ensureDir
|
|
22
|
+
* @property {(path: string) => Promise<string[]>} readdir
|
|
23
|
+
* @property {(src: string, dest: string) => Promise<void>} copyFile
|
|
24
|
+
* @property {(path: string) => Promise<{size: number, isDirectory: () => boolean, isFile: () => boolean}>} stat
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import fs from "node:fs/promises";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default implementation backed by node:fs/promises.
|
|
31
|
+
* @type {FileSystemService}
|
|
32
|
+
*/
|
|
33
|
+
export const defaultFileSystem = {
|
|
34
|
+
async readFile(path, encoding = "utf8") {
|
|
35
|
+
return fs.readFile(path, encoding);
|
|
36
|
+
},
|
|
37
|
+
async writeFile(path, data, encoding = "utf8") {
|
|
38
|
+
await fs.writeFile(path, data, encoding);
|
|
39
|
+
},
|
|
40
|
+
async appendFile(path, data, encoding = "utf8") {
|
|
41
|
+
await fs.appendFile(path, data, encoding);
|
|
42
|
+
},
|
|
43
|
+
async exists(path) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(path);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async ensureDir(path) {
|
|
52
|
+
await fs.mkdir(path, { recursive: true });
|
|
53
|
+
},
|
|
54
|
+
async readdir(path) {
|
|
55
|
+
return fs.readdir(path);
|
|
56
|
+
},
|
|
57
|
+
async copyFile(src, dest) {
|
|
58
|
+
await fs.copyFile(src, dest);
|
|
59
|
+
},
|
|
60
|
+
async stat(path) {
|
|
61
|
+
return fs.stat(path);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test doubles for the DI surfaces. Intentionally dumb — tests stage inputs
|
|
3
|
+
* through simple setters and assert on recorded calls.
|
|
4
|
+
*
|
|
5
|
+
* @typedef {import("./file-system-service.js").FileSystemService} FileSystemService
|
|
6
|
+
* @typedef {import("./command-runner.js").CommandRunner} CommandRunner
|
|
7
|
+
* @typedef {import("./command-runner.js").CommandRunResult} CommandRunResult
|
|
8
|
+
* @typedef {import("./command-runner.js").CommandRunOptions} CommandRunOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* In-memory file system. Paths are stored as-is (no normalization) to keep
|
|
13
|
+
* behaviour predictable in tests.
|
|
14
|
+
*/
|
|
15
|
+
export class MockFileSystem {
|
|
16
|
+
constructor(initial = {}) {
|
|
17
|
+
/** @type {Map<string, string|Buffer>} */
|
|
18
|
+
this.files = new Map(Object.entries(initial));
|
|
19
|
+
/** @type {Set<string>} */
|
|
20
|
+
this.directories = new Set();
|
|
21
|
+
/** @type {Array<{op: string, path: string, args?: unknown}>} */
|
|
22
|
+
this.calls = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_record(op, path, args) {
|
|
26
|
+
this.calls.push({ op, path, args });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async readFile(path, encoding = "utf8") {
|
|
30
|
+
this._record("readFile", path, { encoding });
|
|
31
|
+
if (!this.files.has(path)) {
|
|
32
|
+
const err = new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
33
|
+
err.code = "ENOENT";
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
const data = this.files.get(path);
|
|
37
|
+
if (Buffer.isBuffer(data) && encoding) return data.toString(encoding);
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async writeFile(path, data, encoding = "utf8") {
|
|
42
|
+
this._record("writeFile", path, { encoding });
|
|
43
|
+
this.files.set(path, data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async appendFile(path, data, encoding = "utf8") {
|
|
47
|
+
this._record("appendFile", path, { encoding });
|
|
48
|
+
const prev = this.files.get(path) ?? "";
|
|
49
|
+
this.files.set(path, String(prev) + String(data));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async exists(path) {
|
|
53
|
+
this._record("exists", path);
|
|
54
|
+
return this.files.has(path) || this.directories.has(path);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async ensureDir(path) {
|
|
58
|
+
this._record("ensureDir", path);
|
|
59
|
+
this.directories.add(path);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async readdir(path) {
|
|
63
|
+
this._record("readdir", path);
|
|
64
|
+
const prefix = path.endsWith("/") ? path : path + "/";
|
|
65
|
+
const entries = new Set();
|
|
66
|
+
for (const p of this.files.keys()) {
|
|
67
|
+
if (p.startsWith(prefix)) {
|
|
68
|
+
const tail = p.slice(prefix.length).split("/")[0];
|
|
69
|
+
if (tail) entries.add(tail);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!entries.size && !this.directories.has(path)) {
|
|
73
|
+
const err = new Error(`ENOENT: no such file or directory, scandir '${path}'`);
|
|
74
|
+
err.code = "ENOENT";
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
return [...entries];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async copyFile(src, dest) {
|
|
81
|
+
this._record("copyFile", src, { dest });
|
|
82
|
+
if (!this.files.has(src)) {
|
|
83
|
+
const err = new Error(`ENOENT: no such file or directory, copyfile '${src}' -> '${dest}'`);
|
|
84
|
+
err.code = "ENOENT";
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
this.files.set(dest, this.files.get(src));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async stat(path) {
|
|
91
|
+
this._record("stat", path);
|
|
92
|
+
if (this.files.has(path)) {
|
|
93
|
+
const data = this.files.get(path);
|
|
94
|
+
const size = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(String(data));
|
|
95
|
+
return {
|
|
96
|
+
size,
|
|
97
|
+
isDirectory: () => false,
|
|
98
|
+
isFile: () => true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (this.directories.has(path)) {
|
|
102
|
+
return {
|
|
103
|
+
size: 0,
|
|
104
|
+
isDirectory: () => true,
|
|
105
|
+
isFile: () => false
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const err = new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
109
|
+
err.code = "ENOENT";
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Scripted command runner. Tests call enqueue() to stage responses in order,
|
|
116
|
+
* or setResponder() for a per-invocation decision. Every run() is recorded.
|
|
117
|
+
*/
|
|
118
|
+
export class MockCommandRunner {
|
|
119
|
+
constructor() {
|
|
120
|
+
/** @type {Array<CommandRunResult>} */
|
|
121
|
+
this.queue = [];
|
|
122
|
+
/** @type {Array<{command: string, args: string[], options: CommandRunOptions}>} */
|
|
123
|
+
this.calls = [];
|
|
124
|
+
/** @type {null | ((args: {command: string, args: string[], options: CommandRunOptions}) => CommandRunResult|Promise<CommandRunResult>)} */
|
|
125
|
+
this.responder = null;
|
|
126
|
+
/** @type {CommandRunResult} */
|
|
127
|
+
this.defaultResponse = { exitCode: 0, stdout: "", stderr: "" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Queue a response. FIFO — first enqueued is returned on the first run().
|
|
132
|
+
* @param {Partial<CommandRunResult>} response
|
|
133
|
+
*/
|
|
134
|
+
enqueue(response) {
|
|
135
|
+
this.queue.push({ exitCode: 0, stdout: "", stderr: "", ...response });
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Replace FIFO queue with a function that decides per-call.
|
|
141
|
+
* @param {(args: {command: string, args: string[], options: CommandRunOptions}) => CommandRunResult|Promise<CommandRunResult>} fn
|
|
142
|
+
*/
|
|
143
|
+
setResponder(fn) {
|
|
144
|
+
this.responder = fn;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {string} command
|
|
150
|
+
* @param {string[]} args
|
|
151
|
+
* @param {CommandRunOptions} options
|
|
152
|
+
* @returns {Promise<CommandRunResult>}
|
|
153
|
+
*/
|
|
154
|
+
async run(command, args = [], options = {}) {
|
|
155
|
+
this.calls.push({ command, args: [...args], options: { ...options } });
|
|
156
|
+
if (this.responder) return this.responder({ command, args, options });
|
|
157
|
+
if (this.queue.length > 0) return this.queue.shift();
|
|
158
|
+
return { ...this.defaultResponse };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Convenience: last recorded invocation, or null.
|
|
163
|
+
*/
|
|
164
|
+
get lastCall() {
|
|
165
|
+
return this.calls.at(-1) ?? null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Build an Environment backed by the mocks above. Useful shorthand in tests:
|
|
171
|
+
*
|
|
172
|
+
* const { env, fs, runner } = buildMockEnvironment();
|
|
173
|
+
* const agent = new BaseAgent(..., env);
|
|
174
|
+
*
|
|
175
|
+
* @param {{files?: Record<string, string|Buffer>}} [opts]
|
|
176
|
+
*/
|
|
177
|
+
export function buildMockEnvironment({ files } = {}) {
|
|
178
|
+
const fs = new MockFileSystem(files);
|
|
179
|
+
const runner = new MockCommandRunner();
|
|
180
|
+
const env = Object.freeze({ fs, runner });
|
|
181
|
+
return { env, fs, runner };
|
|
182
|
+
}
|
|
@@ -157,8 +157,13 @@ export function handleInit(a) {
|
|
|
157
157
|
return runKjCommand({ command: "init", options: a });
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
export function handleDoctor(a) {
|
|
161
|
-
|
|
160
|
+
export function handleDoctor(a = {}) {
|
|
161
|
+
const commandArgs = [];
|
|
162
|
+
if (a.checkOnly) commandArgs.push("--check-only");
|
|
163
|
+
if (a.yes) commandArgs.push("--yes");
|
|
164
|
+
if (a.json) commandArgs.push("--json");
|
|
165
|
+
if (a.verbose) commandArgs.push("--verbose");
|
|
166
|
+
return runKjCommand({ command: "doctor", commandArgs, options: a });
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
export function handleConfig(a) {
|
package/src/mcp/tools.js
CHANGED
|
@@ -12,12 +12,16 @@ export const tools = [
|
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
name: "kj_doctor",
|
|
15
|
-
description: "Check system dependencies and
|
|
15
|
+
description: "Check system dependencies, agent CLIs, ports, tokens and MCP health — auto-remediates when possible",
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: "object",
|
|
18
18
|
properties: {
|
|
19
19
|
kjHome: { type: "string" },
|
|
20
|
-
timeoutMs: { type: "number" }
|
|
20
|
+
timeoutMs: { type: "number" },
|
|
21
|
+
checkOnly: { type: "boolean", description: "Detect only, do not apply fixes" },
|
|
22
|
+
yes: { type: "boolean", description: "Auto-confirm prompts for invasive remediations" },
|
|
23
|
+
json: { type: "boolean", description: "Emit machine-readable JSON output" },
|
|
24
|
+
verbose: { type: "boolean", description: "Include fix hints and timing per check" }
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
},
|
|
@@ -105,7 +109,10 @@ export const tools = [
|
|
|
105
109
|
kjHome: { type: "string" },
|
|
106
110
|
sonarToken: { type: "string" },
|
|
107
111
|
timeoutMs: { type: "number" },
|
|
108
|
-
domain: { type: "string", description: "Domain knowledge: inline text describing the project domain, or absolute path to a .md file. Auto-saved to .karajan/domains/ for the curator." }
|
|
112
|
+
domain: { type: "string", description: "Domain knowledge: inline text describing the project domain, or absolute path to a .md file. Auto-saved to .karajan/domains/ for the curator." },
|
|
113
|
+
brain: { type: "string", enum: ["on", "off"], description: "Brain decisor: on|off. Controls whether intent-driven routing overrides pipeline flags." },
|
|
114
|
+
forceRole: { type: "array", items: { type: "string" }, description: "Force roles ON regardless of triage (e.g. [\"security\"])" },
|
|
115
|
+
skipRole: { type: "array", items: { type: "string" }, description: "Force roles OFF regardless of triage (e.g. [\"tester\"])" }
|
|
109
116
|
}
|
|
110
117
|
}
|
|
111
118
|
},
|
|
@@ -10,6 +10,7 @@ import { buildReviewerPrompt } from "../prompts/reviewer.js";
|
|
|
10
10
|
import { resolveRole } from "../config.js";
|
|
11
11
|
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
12
12
|
import { BudgetTracker, extractUsageMetrics } from "../utils/budget.js";
|
|
13
|
+
import { computeKjComparison } from "../budget/comparison.js";
|
|
13
14
|
import { resolveRoleMdPath, loadFirstExisting } from "../roles/base-role.js";
|
|
14
15
|
import { applyPolicies } from "../guards/policy-resolver.js";
|
|
15
16
|
import { resolveReviewProfile } from "../review/profiles.js";
|
|
@@ -280,7 +281,7 @@ export async function handleDryRun({ task, config, flags, emitter, pipelineFlags
|
|
|
280
281
|
return summary;
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
export function createBudgetManager({ config, emitter, eventBase }) {
|
|
284
|
+
export function createBudgetManager({ config, emitter, eventBase, getCompressionStats = null }) {
|
|
284
285
|
const budgetTracker = new BudgetTracker({ pricing: config?.budget?.pricing });
|
|
285
286
|
const budgetLimit = Number(config?.max_budget_usd);
|
|
286
287
|
const hasBudgetLimit = Number.isFinite(budgetLimit) && budgetLimit >= 0;
|
|
@@ -290,6 +291,20 @@ export function createBudgetManager({ config, emitter, eventBase }) {
|
|
|
290
291
|
function budgetSummary() {
|
|
291
292
|
const s = budgetTracker.summary();
|
|
292
293
|
s.trace = budgetTracker.trace();
|
|
294
|
+
// Attach the "With KJ vs Without KJ" comparison when compression data
|
|
295
|
+
// is available (TSK-0274). hasCompression: false means no savings to
|
|
296
|
+
// report; the display falls back to the plain cost line.
|
|
297
|
+
if (typeof getCompressionStats === "function") {
|
|
298
|
+
try {
|
|
299
|
+
const stats = getCompressionStats() || {};
|
|
300
|
+
s.kj_comparison = computeKjComparison({
|
|
301
|
+
realTokens: s.total_tokens,
|
|
302
|
+
realCost: s.total_cost_usd,
|
|
303
|
+
rtkSavings: stats.rtkSavings,
|
|
304
|
+
brainCtx: stats.brainCtx,
|
|
305
|
+
});
|
|
306
|
+
} catch { /* best-effort — omit comparison on failure */ }
|
|
307
|
+
}
|
|
293
308
|
return s;
|
|
294
309
|
}
|
|
295
310
|
|