kairn-cli 2.2.1 → 2.2.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/dist/cli.js +76 -10
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1237,22 +1237,29 @@ function classifyError(err, provider) {
|
|
|
1237
1237
|
}
|
|
1238
1238
|
async function callLLM(config, userMessage, options) {
|
|
1239
1239
|
const maxTokens = options.maxTokens ?? 8192;
|
|
1240
|
-
const systemPrompt = options
|
|
1240
|
+
const { systemPrompt } = options;
|
|
1241
|
+
const jsonMode = options.jsonMode ?? false;
|
|
1241
1242
|
const providerName = getProviderName(config.provider);
|
|
1242
1243
|
if (config.provider === "anthropic") {
|
|
1243
1244
|
const client2 = new Anthropic2({ apiKey: config.api_key });
|
|
1245
|
+
const messages = [
|
|
1246
|
+
{ role: "user", content: userMessage }
|
|
1247
|
+
];
|
|
1248
|
+
if (jsonMode) {
|
|
1249
|
+
messages.push({ role: "assistant", content: "{" });
|
|
1250
|
+
}
|
|
1244
1251
|
try {
|
|
1245
1252
|
const response = await client2.messages.create({
|
|
1246
1253
|
model: config.model,
|
|
1247
1254
|
max_tokens: maxTokens,
|
|
1248
1255
|
system: systemPrompt,
|
|
1249
|
-
messages
|
|
1256
|
+
messages
|
|
1250
1257
|
});
|
|
1251
1258
|
const textBlock = response.content.find((block) => block.type === "text");
|
|
1252
1259
|
if (!textBlock || textBlock.type !== "text") {
|
|
1253
1260
|
throw new Error("No text response from compiler LLM");
|
|
1254
1261
|
}
|
|
1255
|
-
return textBlock.text;
|
|
1262
|
+
return jsonMode ? `{${textBlock.text}` : textBlock.text;
|
|
1256
1263
|
} catch (err) {
|
|
1257
1264
|
throw new Error(classifyError(err, providerName));
|
|
1258
1265
|
}
|
|
@@ -1268,7 +1275,8 @@ async function callLLM(config, userMessage, options) {
|
|
|
1268
1275
|
messages: [
|
|
1269
1276
|
{ role: "system", content: systemPrompt },
|
|
1270
1277
|
{ role: "user", content: userMessage }
|
|
1271
|
-
]
|
|
1278
|
+
],
|
|
1279
|
+
...jsonMode ? { response_format: { type: "json_object" } } : {}
|
|
1272
1280
|
});
|
|
1273
1281
|
const text = response.choices[0]?.message?.content;
|
|
1274
1282
|
if (!text) {
|
|
@@ -4023,6 +4031,13 @@ async function snapshotBaseline(projectRoot, workspacePath) {
|
|
|
4023
4031
|
}
|
|
4024
4032
|
await copyDir(claudeDir, baselineDir);
|
|
4025
4033
|
await copyDir(claudeDir, iter0Dir);
|
|
4034
|
+
const mcpJsonPath = path16.join(projectRoot, ".mcp.json");
|
|
4035
|
+
try {
|
|
4036
|
+
await fs16.access(mcpJsonPath);
|
|
4037
|
+
await fs16.copyFile(mcpJsonPath, path16.join(baselineDir, ".mcp.json"));
|
|
4038
|
+
await fs16.copyFile(mcpJsonPath, path16.join(iter0Dir, ".mcp.json"));
|
|
4039
|
+
} catch {
|
|
4040
|
+
}
|
|
4026
4041
|
}
|
|
4027
4042
|
async function copyDir(src, dest) {
|
|
4028
4043
|
await fs16.mkdir(dest, { recursive: true });
|
|
@@ -4345,6 +4360,11 @@ async function scoreTask(task, workspacePath, stdout, stderr, config) {
|
|
|
4345
4360
|
// src/evolve/runner.ts
|
|
4346
4361
|
var execAsync2 = promisify2(exec2);
|
|
4347
4362
|
var COPY_SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".kairn-evolve", ".claude"]);
|
|
4363
|
+
async function deployMcpJson(harnessPath, workDir) {
|
|
4364
|
+
const src = path18.join(harnessPath, ".mcp.json");
|
|
4365
|
+
await fs18.copyFile(src, path18.join(workDir, ".mcp.json")).catch(() => {
|
|
4366
|
+
});
|
|
4367
|
+
}
|
|
4348
4368
|
async function createIsolatedWorkspace(projectRoot, harnessPath) {
|
|
4349
4369
|
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
4350
4370
|
try {
|
|
@@ -4359,6 +4379,7 @@ async function createIsolatedWorkspace(projectRoot, harnessPath) {
|
|
|
4359
4379
|
});
|
|
4360
4380
|
await fs18.rm(path18.join(tmpDir2, ".claude"), { recursive: true, force: true });
|
|
4361
4381
|
await copyDir(harnessPath, path18.join(tmpDir2, ".claude"));
|
|
4382
|
+
await deployMcpJson(harnessPath, tmpDir2);
|
|
4362
4383
|
return { workDir: tmpDir2, isWorktree: true };
|
|
4363
4384
|
} catch {
|
|
4364
4385
|
}
|
|
@@ -4366,6 +4387,7 @@ async function createIsolatedWorkspace(projectRoot, harnessPath) {
|
|
|
4366
4387
|
await copyProjectDir(projectRoot, tmpDir);
|
|
4367
4388
|
await fs18.rm(path18.join(tmpDir, ".claude"), { recursive: true, force: true });
|
|
4368
4389
|
await copyDir(harnessPath, path18.join(tmpDir, ".claude"));
|
|
4390
|
+
await deployMcpJson(harnessPath, tmpDir);
|
|
4369
4391
|
return { workDir: tmpDir, isWorktree: false };
|
|
4370
4392
|
}
|
|
4371
4393
|
async function copyProjectDir(src, dest) {
|
|
@@ -4615,23 +4637,37 @@ minimal changes to the harness files that will fix those failures.
|
|
|
4615
4637
|
|
|
4616
4638
|
3. Check history for counterfactual evidence
|
|
4617
4639
|
|
|
4640
|
+
## Available Mutation Actions
|
|
4641
|
+
1. **replace** \u2014 Replace old_text with new_text in a file: { "file": "...", "action": "replace", "old_text": "...", "new_text": "...", "rationale": "..." }
|
|
4642
|
+
2. **add_section** \u2014 Append new content to a file (or create it): { "file": "...", "action": "add_section", "new_text": "...", "rationale": "..." }
|
|
4643
|
+
3. **create_file** \u2014 Create a new file: { "file": "...", "action": "create_file", "new_text": "...", "rationale": "..." }
|
|
4644
|
+
4. **delete_section** \u2014 Remove specific text from a file: { "file": "...", "action": "delete_section", "old_text": "...", "rationale": "..." }
|
|
4645
|
+
5. **delete_file** \u2014 Delete an entire file: { "file": "...", "action": "delete_file", "rationale": "..." }
|
|
4646
|
+
|
|
4618
4647
|
## Output Format
|
|
4619
4648
|
Return a JSON object:
|
|
4620
4649
|
{
|
|
4621
4650
|
"reasoning": "Your full causal analysis...",
|
|
4622
4651
|
"mutations": [
|
|
4623
4652
|
{ "file": "CLAUDE.md", "action": "replace", "old_text": "...", "new_text": "...", "rationale": "..." },
|
|
4624
|
-
{ "file": "commands/develop.md", "action": "add_section", "new_text": "...", "rationale": "..." }
|
|
4653
|
+
{ "file": "commands/develop.md", "action": "add_section", "new_text": "...", "rationale": "..." },
|
|
4654
|
+
{ "file": "rules/obsolete.md", "action": "delete_file", "rationale": "..." }
|
|
4625
4655
|
],
|
|
4626
4656
|
"expected_impact": { "task-id": "+15% \u2014 explanation" }
|
|
4627
4657
|
}
|
|
4628
4658
|
|
|
4659
|
+
## MCP Configuration
|
|
4660
|
+
You can also mutate .mcp.json to add, remove, or reconfigure MCP servers.
|
|
4661
|
+
Treat .mcp.json like any other harness file \u2014 propose changes when traces show
|
|
4662
|
+
the agent lacks a tool it needs, or has tools that add noise without benefit.
|
|
4663
|
+
|
|
4629
4664
|
## Rules
|
|
4630
4665
|
- MINIMAL changes only. Don't rewrite the entire CLAUDE.md.
|
|
4631
4666
|
- Each mutation must have a clear rationale tied to a specific trace observation.
|
|
4632
4667
|
- Never remove something that's working for another task.
|
|
4633
4668
|
- If a previous iteration's change caused a regression, REVERT it.
|
|
4634
|
-
-
|
|
4669
|
+
- Consider both additions AND removals. Remove sections that add noise without improving task performance.
|
|
4670
|
+
- Bloated harnesses hurt performance \u2014 trim what isn't earning its keep.
|
|
4635
4671
|
|
|
4636
4672
|
Return ONLY valid JSON.`;
|
|
4637
4673
|
var STDOUT_TRUNCATION_LIMIT = 1e3;
|
|
@@ -4785,7 +4821,18 @@ function parseProposerResponse(raw) {
|
|
|
4785
4821
|
try {
|
|
4786
4822
|
parsed = JSON.parse(cleaned);
|
|
4787
4823
|
} catch {
|
|
4788
|
-
|
|
4824
|
+
const firstBrace = cleaned.indexOf("{");
|
|
4825
|
+
const lastBrace = cleaned.lastIndexOf("}");
|
|
4826
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
4827
|
+
const extracted = cleaned.slice(firstBrace, lastBrace + 1);
|
|
4828
|
+
try {
|
|
4829
|
+
parsed = JSON.parse(extracted);
|
|
4830
|
+
} catch {
|
|
4831
|
+
throw new Error(`Proposer returned invalid JSON: ${cleaned.slice(0, 200)}`);
|
|
4832
|
+
}
|
|
4833
|
+
} else {
|
|
4834
|
+
throw new Error(`Proposer returned invalid JSON: ${cleaned.slice(0, 200)}`);
|
|
4835
|
+
}
|
|
4789
4836
|
}
|
|
4790
4837
|
if (typeof parsed !== "object" || parsed === null) {
|
|
4791
4838
|
throw new Error("Proposer response is not a JSON object");
|
|
@@ -4811,10 +4858,11 @@ function parseProposerResponse(raw) {
|
|
|
4811
4858
|
if (file.includes("..")) {
|
|
4812
4859
|
continue;
|
|
4813
4860
|
}
|
|
4814
|
-
|
|
4861
|
+
const validActions = /* @__PURE__ */ new Set(["replace", "add_section", "create_file", "delete_section", "delete_file"]);
|
|
4862
|
+
if (!validActions.has(action)) {
|
|
4815
4863
|
continue;
|
|
4816
4864
|
}
|
|
4817
|
-
if (action === "replace" && !oldText) {
|
|
4865
|
+
if ((action === "replace" || action === "delete_section") && !oldText) {
|
|
4818
4866
|
continue;
|
|
4819
4867
|
}
|
|
4820
4868
|
const mutation = {
|
|
@@ -4848,7 +4896,8 @@ async function propose(iteration, workspacePath, harnessPath, history, tasks, co
|
|
|
4848
4896
|
const proposerConfig = { ...config, model: proposerModel };
|
|
4849
4897
|
const response = await callLLM(proposerConfig, userMessage, {
|
|
4850
4898
|
systemPrompt: PROPOSER_SYSTEM_PROMPT,
|
|
4851
|
-
maxTokens: 8192
|
|
4899
|
+
maxTokens: 8192,
|
|
4900
|
+
jsonMode: true
|
|
4852
4901
|
});
|
|
4853
4902
|
return parseProposerResponse(response);
|
|
4854
4903
|
}
|
|
@@ -4892,6 +4941,23 @@ async function applyMutations(currentHarnessPath, nextIterationDir, mutations) {
|
|
|
4892
4941
|
} else if (mutation.action === "create_file") {
|
|
4893
4942
|
await fs20.mkdir(path20.dirname(filePath), { recursive: true });
|
|
4894
4943
|
await fs20.writeFile(filePath, mutation.newText, "utf-8");
|
|
4944
|
+
} else if (mutation.action === "delete_section") {
|
|
4945
|
+
if (!mutation.oldText) {
|
|
4946
|
+
continue;
|
|
4947
|
+
}
|
|
4948
|
+
let sectionContent;
|
|
4949
|
+
try {
|
|
4950
|
+
sectionContent = await fs20.readFile(filePath, "utf-8");
|
|
4951
|
+
} catch {
|
|
4952
|
+
continue;
|
|
4953
|
+
}
|
|
4954
|
+
if (!sectionContent.includes(mutation.oldText)) {
|
|
4955
|
+
continue;
|
|
4956
|
+
}
|
|
4957
|
+
await fs20.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
|
|
4958
|
+
} else if (mutation.action === "delete_file") {
|
|
4959
|
+
await fs20.unlink(filePath).catch(() => {
|
|
4960
|
+
});
|
|
4895
4961
|
}
|
|
4896
4962
|
}
|
|
4897
4963
|
const diffPatch = await generateDiff2(currentHarnessPath, newHarnessPath);
|