kairn-cli 2.2.3 → 2.2.4
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 +265 -52
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -221,7 +221,7 @@ var ui = {
|
|
|
221
221
|
// Key-value pairs
|
|
222
222
|
kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
|
|
223
223
|
// File list
|
|
224
|
-
file: (
|
|
224
|
+
file: (path25) => chalk.dim(` ${path25}`),
|
|
225
225
|
// Tool display
|
|
226
226
|
tool: (name, reason) => ` ${warmStone("\u25CF")} ${chalk.bold(name)}
|
|
227
227
|
${chalk.dim(reason)}`,
|
|
@@ -3702,8 +3702,8 @@ var keysCommand = new Command10("keys").description("Add or update API keys for
|
|
|
3702
3702
|
import { Command as Command11 } from "commander";
|
|
3703
3703
|
import chalk14 from "chalk";
|
|
3704
3704
|
import ora2 from "ora";
|
|
3705
|
-
import
|
|
3706
|
-
import
|
|
3705
|
+
import fs24 from "fs/promises";
|
|
3706
|
+
import path24 from "path";
|
|
3707
3707
|
import { parse as yamlParse2 } from "yaml";
|
|
3708
3708
|
import { confirm as confirm3, select as select4 } from "@inquirer/prompts";
|
|
3709
3709
|
|
|
@@ -4569,31 +4569,77 @@ function parseToolCalls(stdout) {
|
|
|
4569
4569
|
return [];
|
|
4570
4570
|
}
|
|
4571
4571
|
}
|
|
4572
|
-
|
|
4572
|
+
function computeStddev(values, mean) {
|
|
4573
|
+
if (values.length <= 1) return 0;
|
|
4574
|
+
const sumSqDiffs = values.reduce((sum, v) => sum + (v - mean) ** 2, 0);
|
|
4575
|
+
return Math.sqrt(sumSqDiffs / values.length);
|
|
4576
|
+
}
|
|
4577
|
+
async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config, onProgress, runsPerTask = 1) {
|
|
4573
4578
|
const results = {};
|
|
4574
4579
|
const projectRoot = path18.resolve(workspacePath, "..");
|
|
4580
|
+
const effectiveRuns = Math.max(1, runsPerTask);
|
|
4575
4581
|
for (const task of tasks) {
|
|
4576
|
-
const traceDir = path18.join(
|
|
4577
|
-
workspacePath,
|
|
4578
|
-
"traces",
|
|
4579
|
-
iteration.toString(),
|
|
4580
|
-
task.id
|
|
4581
|
-
);
|
|
4582
4582
|
onProgress?.({ type: "task-start", iteration, taskId: task.id });
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4583
|
+
if (effectiveRuns > 1 && config) {
|
|
4584
|
+
const runScores = [];
|
|
4585
|
+
let passCount = 0;
|
|
4586
|
+
for (let run = 0; run < effectiveRuns; run++) {
|
|
4587
|
+
const traceDir = path18.join(
|
|
4588
|
+
workspacePath,
|
|
4589
|
+
"traces",
|
|
4590
|
+
iteration.toString(),
|
|
4591
|
+
`${task.id}_run${run}`
|
|
4592
|
+
);
|
|
4593
|
+
onProgress?.({
|
|
4594
|
+
type: "task-run",
|
|
4595
|
+
iteration,
|
|
4596
|
+
taskId: task.id,
|
|
4597
|
+
message: `Run ${run + 1}/${effectiveRuns} of ${task.id}`
|
|
4598
|
+
});
|
|
4599
|
+
await runTask(task, harnessPath, traceDir, iteration, projectRoot);
|
|
4600
|
+
const stdout = await fs18.readFile(path18.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
|
|
4601
|
+
const stderr = await fs18.readFile(path18.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
|
|
4602
|
+
const score = await scoreTask(task, traceDir, stdout, stderr, config);
|
|
4603
|
+
await writeScore(traceDir, score);
|
|
4604
|
+
runScores.push(score.score ?? (score.pass ? 100 : 0));
|
|
4605
|
+
if (score.pass) passCount++;
|
|
4606
|
+
}
|
|
4607
|
+
const mean = runScores.reduce((a, b) => a + b, 0) / runScores.length;
|
|
4608
|
+
const stddev = computeStddev(runScores, mean);
|
|
4609
|
+
results[task.id] = {
|
|
4610
|
+
pass: passCount > effectiveRuns / 2,
|
|
4611
|
+
score: mean,
|
|
4612
|
+
details: `Mean of ${effectiveRuns} runs`,
|
|
4613
|
+
variance: {
|
|
4614
|
+
runs: effectiveRuns,
|
|
4615
|
+
scores: runScores,
|
|
4616
|
+
mean,
|
|
4617
|
+
stddev
|
|
4618
|
+
}
|
|
4619
|
+
};
|
|
4620
|
+
} else {
|
|
4621
|
+
const traceDir = path18.join(
|
|
4622
|
+
workspacePath,
|
|
4623
|
+
"traces",
|
|
4624
|
+
iteration.toString(),
|
|
4625
|
+
task.id
|
|
4626
|
+
);
|
|
4627
|
+
const taskResult = await runTask(task, harnessPath, traceDir, iteration, projectRoot);
|
|
4628
|
+
let score = taskResult.score;
|
|
4629
|
+
if (config) {
|
|
4630
|
+
const stdout = await fs18.readFile(path18.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
|
|
4631
|
+
const stderr = await fs18.readFile(path18.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
|
|
4632
|
+
score = await scoreTask(task, traceDir, stdout, stderr, config);
|
|
4633
|
+
await writeScore(traceDir, score);
|
|
4634
|
+
}
|
|
4635
|
+
results[task.id] = score;
|
|
4636
|
+
}
|
|
4637
|
+
const finalScore = results[task.id];
|
|
4592
4638
|
onProgress?.({
|
|
4593
4639
|
type: "task-scored",
|
|
4594
4640
|
iteration,
|
|
4595
4641
|
taskId: task.id,
|
|
4596
|
-
score:
|
|
4642
|
+
score: finalScore.score ?? (finalScore.pass ? 100 : 0)
|
|
4597
4643
|
});
|
|
4598
4644
|
}
|
|
4599
4645
|
const scores = Object.values(results);
|
|
@@ -5052,7 +5098,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
5052
5098
|
workspacePath,
|
|
5053
5099
|
iter,
|
|
5054
5100
|
kairnConfig,
|
|
5055
|
-
onProgress
|
|
5101
|
+
onProgress,
|
|
5102
|
+
evolveConfig.runsPerTask
|
|
5056
5103
|
);
|
|
5057
5104
|
onProgress?.({ type: "iteration-scored", iteration: iter, score: aggregate });
|
|
5058
5105
|
if (iter === 0) baselineScore = aggregate;
|
|
@@ -5409,24 +5456,128 @@ async function generateJsonReport(workspacePath) {
|
|
|
5409
5456
|
};
|
|
5410
5457
|
}
|
|
5411
5458
|
|
|
5459
|
+
// src/evolve/apply.ts
|
|
5460
|
+
import fs23 from "fs/promises";
|
|
5461
|
+
import path23 from "path";
|
|
5462
|
+
async function listIterations(workspacePath) {
|
|
5463
|
+
const iterationsDir = path23.join(workspacePath, "iterations");
|
|
5464
|
+
let entries;
|
|
5465
|
+
try {
|
|
5466
|
+
entries = await fs23.readdir(iterationsDir);
|
|
5467
|
+
} catch {
|
|
5468
|
+
return [];
|
|
5469
|
+
}
|
|
5470
|
+
const nums = [];
|
|
5471
|
+
for (const entry of entries) {
|
|
5472
|
+
const n = parseInt(entry, 10);
|
|
5473
|
+
if (!isNaN(n)) {
|
|
5474
|
+
try {
|
|
5475
|
+
await fs23.access(path23.join(iterationsDir, entry, "harness"));
|
|
5476
|
+
nums.push(n);
|
|
5477
|
+
} catch {
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
return nums.sort((a, b) => a - b);
|
|
5482
|
+
}
|
|
5483
|
+
async function findBestIteration(workspacePath, iterations) {
|
|
5484
|
+
let bestIter = iterations[0];
|
|
5485
|
+
let bestScore = -Infinity;
|
|
5486
|
+
for (const iter of iterations) {
|
|
5487
|
+
const log = await loadIterationLog(workspacePath, iter);
|
|
5488
|
+
const score = log?.score ?? 0;
|
|
5489
|
+
if (score > bestScore) {
|
|
5490
|
+
bestScore = score;
|
|
5491
|
+
bestIter = iter;
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
return bestIter;
|
|
5495
|
+
}
|
|
5496
|
+
async function listFilesRecursive(dir) {
|
|
5497
|
+
const results = [];
|
|
5498
|
+
async function walk(current) {
|
|
5499
|
+
let entries;
|
|
5500
|
+
try {
|
|
5501
|
+
entries = await fs23.readdir(current, { withFileTypes: true });
|
|
5502
|
+
} catch {
|
|
5503
|
+
return;
|
|
5504
|
+
}
|
|
5505
|
+
for (const entry of entries) {
|
|
5506
|
+
const fullPath = path23.join(current, entry.name);
|
|
5507
|
+
if (entry.isDirectory()) {
|
|
5508
|
+
await walk(fullPath);
|
|
5509
|
+
} else {
|
|
5510
|
+
results.push(path23.relative(dir, fullPath));
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
await walk(dir);
|
|
5515
|
+
return results;
|
|
5516
|
+
}
|
|
5517
|
+
async function applyEvolution(workspacePath, projectRoot, targetIteration) {
|
|
5518
|
+
const iterations = await listIterations(workspacePath);
|
|
5519
|
+
if (iterations.length === 0) {
|
|
5520
|
+
throw new Error("No iterations found in workspace. Run `kairn evolve run` first.");
|
|
5521
|
+
}
|
|
5522
|
+
let iter;
|
|
5523
|
+
if (targetIteration !== void 0) {
|
|
5524
|
+
if (!iterations.includes(targetIteration)) {
|
|
5525
|
+
throw new Error(
|
|
5526
|
+
`Iteration ${targetIteration} not found. Available: ${iterations.join(", ")}`
|
|
5527
|
+
);
|
|
5528
|
+
}
|
|
5529
|
+
iter = targetIteration;
|
|
5530
|
+
} else {
|
|
5531
|
+
iter = await findBestIteration(workspacePath, iterations);
|
|
5532
|
+
}
|
|
5533
|
+
const harnessPath = path23.join(
|
|
5534
|
+
workspacePath,
|
|
5535
|
+
"iterations",
|
|
5536
|
+
iter.toString(),
|
|
5537
|
+
"harness"
|
|
5538
|
+
);
|
|
5539
|
+
const claudeDir = path23.join(projectRoot, ".claude");
|
|
5540
|
+
const diffPreview = await generateDiff2(claudeDir, harnessPath);
|
|
5541
|
+
const currentFiles = await listFilesRecursive(claudeDir);
|
|
5542
|
+
const targetFiles = await listFilesRecursive(harnessPath);
|
|
5543
|
+
const allPaths = /* @__PURE__ */ new Set([...currentFiles, ...targetFiles]);
|
|
5544
|
+
const filesChanged = [];
|
|
5545
|
+
for (const filePath of allPaths) {
|
|
5546
|
+
const currentContent = await fs23.readFile(path23.join(claudeDir, filePath), "utf-8").catch(() => null);
|
|
5547
|
+
const targetContent = await fs23.readFile(path23.join(harnessPath, filePath), "utf-8").catch(() => null);
|
|
5548
|
+
if (currentContent !== targetContent) {
|
|
5549
|
+
filesChanged.push(filePath);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
await fs23.rm(claudeDir, { recursive: true, force: true });
|
|
5553
|
+
await copyDir(harnessPath, claudeDir);
|
|
5554
|
+
return {
|
|
5555
|
+
iteration: iter,
|
|
5556
|
+
filesChanged,
|
|
5557
|
+
diffPreview
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
|
|
5412
5561
|
// src/commands/evolve.ts
|
|
5413
5562
|
var DEFAULT_CONFIG = {
|
|
5414
5563
|
model: "claude-sonnet-4-6",
|
|
5415
5564
|
proposerModel: "claude-opus-4-6",
|
|
5416
5565
|
scorer: "pass-fail",
|
|
5417
5566
|
maxIterations: 5,
|
|
5418
|
-
parallelTasks: 1
|
|
5567
|
+
parallelTasks: 1,
|
|
5568
|
+
runsPerTask: 1
|
|
5419
5569
|
};
|
|
5420
5570
|
async function loadEvolveConfigFromWorkspace(workspacePath) {
|
|
5421
5571
|
try {
|
|
5422
|
-
const configStr = await
|
|
5572
|
+
const configStr = await fs24.readFile(path24.join(workspacePath, "config.yaml"), "utf-8");
|
|
5423
5573
|
const parsed = yamlParse2(configStr);
|
|
5424
5574
|
return {
|
|
5425
5575
|
model: parsed.model ?? DEFAULT_CONFIG.model,
|
|
5426
5576
|
proposerModel: parsed.proposer_model ?? DEFAULT_CONFIG.proposerModel,
|
|
5427
5577
|
scorer: parsed.scorer ?? DEFAULT_CONFIG.scorer,
|
|
5428
5578
|
maxIterations: parsed.max_iterations ?? DEFAULT_CONFIG.maxIterations,
|
|
5429
|
-
parallelTasks: parsed.parallel_tasks ?? DEFAULT_CONFIG.parallelTasks
|
|
5579
|
+
parallelTasks: parsed.parallel_tasks ?? DEFAULT_CONFIG.parallelTasks,
|
|
5580
|
+
runsPerTask: parsed.runs_per_task ?? DEFAULT_CONFIG.runsPerTask
|
|
5430
5581
|
};
|
|
5431
5582
|
} catch {
|
|
5432
5583
|
return { ...DEFAULT_CONFIG };
|
|
@@ -5437,9 +5588,9 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
5437
5588
|
try {
|
|
5438
5589
|
const projectRoot = process.cwd();
|
|
5439
5590
|
console.log(ui.section("Evolve Init"));
|
|
5440
|
-
const claudeDir =
|
|
5591
|
+
const claudeDir = path24.join(projectRoot, ".claude");
|
|
5441
5592
|
try {
|
|
5442
|
-
await
|
|
5593
|
+
await fs24.access(claudeDir);
|
|
5443
5594
|
} catch {
|
|
5444
5595
|
console.log(ui.error("No .claude/ directory found. Run kairn describe first."));
|
|
5445
5596
|
process.exit(1);
|
|
@@ -5489,7 +5640,7 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
5489
5640
|
if (config) {
|
|
5490
5641
|
let claudeMd = "";
|
|
5491
5642
|
try {
|
|
5492
|
-
claudeMd = await
|
|
5643
|
+
claudeMd = await fs24.readFile(path24.join(claudeDir, "CLAUDE.md"), "utf-8");
|
|
5493
5644
|
} catch {
|
|
5494
5645
|
}
|
|
5495
5646
|
const profile = await buildProjectProfile(projectRoot);
|
|
@@ -5520,16 +5671,16 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
5520
5671
|
evolveCommand.command("baseline").description("Snapshot current .claude/ directory as baseline").action(async () => {
|
|
5521
5672
|
try {
|
|
5522
5673
|
const projectRoot = process.cwd();
|
|
5523
|
-
const workspace =
|
|
5674
|
+
const workspace = path24.join(projectRoot, ".kairn-evolve");
|
|
5524
5675
|
console.log(ui.section("Evolve Baseline"));
|
|
5525
5676
|
try {
|
|
5526
|
-
await
|
|
5677
|
+
await fs24.access(workspace);
|
|
5527
5678
|
} catch {
|
|
5528
5679
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
5529
5680
|
process.exit(1);
|
|
5530
5681
|
}
|
|
5531
5682
|
await snapshotBaseline(projectRoot, workspace);
|
|
5532
|
-
const baselineDir =
|
|
5683
|
+
const baselineDir = path24.join(workspace, "baseline");
|
|
5533
5684
|
const fileCount = await countFiles(baselineDir);
|
|
5534
5685
|
console.log(ui.success(`Baseline snapshot created (${fileCount} files)`));
|
|
5535
5686
|
} catch (err) {
|
|
@@ -5538,21 +5689,21 @@ evolveCommand.command("baseline").description("Snapshot current .claude/ directo
|
|
|
5538
5689
|
process.exit(1);
|
|
5539
5690
|
}
|
|
5540
5691
|
});
|
|
5541
|
-
evolveCommand.command("run").description("Run tasks against the current harness").option("--task <id>", "Run a specific task by ID").option("--iterations <n>", "Number of evolution iterations", "5").action(async (options) => {
|
|
5692
|
+
evolveCommand.command("run").description("Run tasks against the current harness").option("--task <id>", "Run a specific task by ID").option("--iterations <n>", "Number of evolution iterations", "5").option("--runs <n>", "Run each task N times for variance measurement", "1").action(async (options) => {
|
|
5542
5693
|
try {
|
|
5543
5694
|
const projectRoot = process.cwd();
|
|
5544
|
-
const workspace =
|
|
5695
|
+
const workspace = path24.join(projectRoot, ".kairn-evolve");
|
|
5545
5696
|
console.log(ui.section("Evolve Run"));
|
|
5546
5697
|
try {
|
|
5547
|
-
await
|
|
5698
|
+
await fs24.access(workspace);
|
|
5548
5699
|
} catch {
|
|
5549
5700
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
5550
5701
|
process.exit(1);
|
|
5551
5702
|
}
|
|
5552
|
-
const tasksPath =
|
|
5703
|
+
const tasksPath = path24.join(workspace, "tasks.yaml");
|
|
5553
5704
|
let tasksContent;
|
|
5554
5705
|
try {
|
|
5555
|
-
tasksContent = await
|
|
5706
|
+
tasksContent = await fs24.readFile(tasksPath, "utf-8");
|
|
5556
5707
|
} catch {
|
|
5557
5708
|
console.log(ui.error("No tasks.yaml found. Run kairn evolve init first."));
|
|
5558
5709
|
process.exit(1);
|
|
@@ -5571,15 +5722,15 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5571
5722
|
console.log(ui.info(`Running ${tasksToRun.length} task(s)...`));
|
|
5572
5723
|
console.log("");
|
|
5573
5724
|
const config = await loadConfig();
|
|
5574
|
-
const harnessPath =
|
|
5725
|
+
const harnessPath = path24.join(projectRoot, ".claude");
|
|
5575
5726
|
const results = [];
|
|
5576
5727
|
for (const task of tasksToRun) {
|
|
5577
|
-
const traceDir =
|
|
5728
|
+
const traceDir = path24.join(workspace, "traces", "0", task.id);
|
|
5578
5729
|
const spinner = ora2(`Running: ${task.id}`).start();
|
|
5579
5730
|
const result = await runTask(task, harnessPath, traceDir, 0);
|
|
5580
5731
|
if (config) {
|
|
5581
|
-
const stdout = await
|
|
5582
|
-
const stderr = await
|
|
5732
|
+
const stdout = await fs24.readFile(path24.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
|
|
5733
|
+
const stderr = await fs24.readFile(path24.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
|
|
5583
5734
|
const score = await scoreTask(task, traceDir, stdout, stderr, config);
|
|
5584
5735
|
result.score = score;
|
|
5585
5736
|
await writeScore(traceDir, score);
|
|
@@ -5607,8 +5758,14 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5607
5758
|
process.exit(1);
|
|
5608
5759
|
}
|
|
5609
5760
|
evolveConfig.maxIterations = iterations;
|
|
5761
|
+
const runs = parseInt(options.runs ?? "1", 10);
|
|
5762
|
+
if (isNaN(runs) || runs < 1) {
|
|
5763
|
+
console.log(ui.error("--runs must be a positive integer"));
|
|
5764
|
+
process.exit(1);
|
|
5765
|
+
}
|
|
5766
|
+
evolveConfig.runsPerTask = runs;
|
|
5610
5767
|
try {
|
|
5611
|
-
await
|
|
5768
|
+
await fs24.access(path24.join(workspace, "iterations", "0", "harness"));
|
|
5612
5769
|
} catch {
|
|
5613
5770
|
console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
|
|
5614
5771
|
process.exit(1);
|
|
@@ -5641,6 +5798,9 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5641
5798
|
case "task-start":
|
|
5642
5799
|
console.log(chalk14.dim(` Running: ${event.taskId ?? "unknown"}...`));
|
|
5643
5800
|
break;
|
|
5801
|
+
case "task-run":
|
|
5802
|
+
console.log(chalk14.dim(` ${event.message ?? ""}`));
|
|
5803
|
+
break;
|
|
5644
5804
|
case "task-scored": {
|
|
5645
5805
|
const taskScore = event.score ?? 0;
|
|
5646
5806
|
const taskStatus = taskScore >= 100 ? chalk14.green("PASS") : taskScore >= 60 ? chalk14.yellow("PARTIAL") : chalk14.red("FAIL");
|
|
@@ -5662,9 +5822,18 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5662
5822
|
console.log(` Improvement: ${improvement.toFixed(1)} points`);
|
|
5663
5823
|
}
|
|
5664
5824
|
console.log("");
|
|
5665
|
-
|
|
5825
|
+
const showVariance = runs > 1;
|
|
5826
|
+
console.log(showVariance ? " Iter Score Mutations Status" : " Iter Score Mutations Status");
|
|
5666
5827
|
for (const iter of result.iterations) {
|
|
5667
|
-
|
|
5828
|
+
let scoreDisplay;
|
|
5829
|
+
if (showVariance) {
|
|
5830
|
+
const taskScores = Object.values(iter.taskResults);
|
|
5831
|
+
const stddevs = taskScores.map((s) => s.variance?.stddev).filter((v) => v !== void 0);
|
|
5832
|
+
const avgStddev = stddevs.length > 0 ? stddevs.reduce((a, b) => a + b, 0) / stddevs.length : 0;
|
|
5833
|
+
scoreDisplay = `${iter.score.toFixed(1).padStart(6)}% \xB1${avgStddev.toFixed(1)}`;
|
|
5834
|
+
} else {
|
|
5835
|
+
scoreDisplay = iter.score.toFixed(1).padStart(6) + "%";
|
|
5836
|
+
}
|
|
5668
5837
|
const mutations = iter.proposal?.mutations.length ?? 0;
|
|
5669
5838
|
const mutStr = mutations > 0 ? mutations.toString() : "-";
|
|
5670
5839
|
let status = "evaluated";
|
|
@@ -5672,7 +5841,7 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5672
5841
|
else if (!iter.proposal && !iter.diffPatch) status = "rollback";
|
|
5673
5842
|
else if (iter.score >= 100) status = "perfect";
|
|
5674
5843
|
else if (iter.iteration === result.bestIteration) status = "best";
|
|
5675
|
-
console.log(` ${iter.iteration.toString().padStart(4)} ${
|
|
5844
|
+
console.log(` ${iter.iteration.toString().padStart(4)} ${scoreDisplay} ${mutStr.padStart(9)} ${status}`);
|
|
5676
5845
|
}
|
|
5677
5846
|
}
|
|
5678
5847
|
} catch (err) {
|
|
@@ -5681,12 +5850,56 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
5681
5850
|
process.exit(1);
|
|
5682
5851
|
}
|
|
5683
5852
|
});
|
|
5853
|
+
evolveCommand.command("apply").description("Apply the best evolved harness to your project").option("--iter <n>", "Apply a specific iteration instead of the best").option("--force", "Apply even if git working tree is dirty").option("--no-commit", "Skip automatic git commit after applying").action(async (options) => {
|
|
5854
|
+
try {
|
|
5855
|
+
const projectRoot = process.cwd();
|
|
5856
|
+
const workspace = path24.join(projectRoot, ".kairn-evolve");
|
|
5857
|
+
console.log(ui.section("Evolve Apply"));
|
|
5858
|
+
try {
|
|
5859
|
+
await fs24.access(workspace);
|
|
5860
|
+
} catch {
|
|
5861
|
+
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
5862
|
+
process.exit(1);
|
|
5863
|
+
}
|
|
5864
|
+
let targetIteration;
|
|
5865
|
+
if (options.iter) {
|
|
5866
|
+
targetIteration = parseInt(options.iter, 10);
|
|
5867
|
+
if (isNaN(targetIteration)) {
|
|
5868
|
+
console.log(ui.error("--iter must be a number"));
|
|
5869
|
+
process.exit(1);
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
const result = await applyEvolution(workspace, projectRoot, targetIteration);
|
|
5873
|
+
if (result.diffPreview) {
|
|
5874
|
+
console.log(ui.section("Changes"));
|
|
5875
|
+
for (const line of result.diffPreview.split("\n")) {
|
|
5876
|
+
if (line.startsWith("---") || line.startsWith("+++")) {
|
|
5877
|
+
console.log(chalk14.bold(line));
|
|
5878
|
+
} else if (line.startsWith("+")) {
|
|
5879
|
+
console.log(chalk14.green(line));
|
|
5880
|
+
} else if (line.startsWith("-")) {
|
|
5881
|
+
console.log(chalk14.red(line));
|
|
5882
|
+
} else {
|
|
5883
|
+
console.log(line);
|
|
5884
|
+
}
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
console.log("");
|
|
5888
|
+
console.log(ui.success(
|
|
5889
|
+
`Applied iteration ${result.iteration} harness (${result.filesChanged.length} files)`
|
|
5890
|
+
));
|
|
5891
|
+
} catch (err) {
|
|
5892
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5893
|
+
console.log(ui.error(msg));
|
|
5894
|
+
process.exit(1);
|
|
5895
|
+
}
|
|
5896
|
+
});
|
|
5684
5897
|
evolveCommand.command("report").description("Generate a summary report of the evolution run").option("--json", "Output machine-readable JSON instead of Markdown").action(async (options) => {
|
|
5685
5898
|
try {
|
|
5686
5899
|
const projectRoot = process.cwd();
|
|
5687
|
-
const workspace =
|
|
5900
|
+
const workspace = path24.join(projectRoot, ".kairn-evolve");
|
|
5688
5901
|
try {
|
|
5689
|
-
await
|
|
5902
|
+
await fs24.access(workspace);
|
|
5690
5903
|
} catch {
|
|
5691
5904
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
5692
5905
|
process.exit(1);
|
|
@@ -5707,23 +5920,23 @@ evolveCommand.command("report").description("Generate a summary report of the ev
|
|
|
5707
5920
|
evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes between two iterations").action(async (iter1Str, iter2Str) => {
|
|
5708
5921
|
try {
|
|
5709
5922
|
const projectRoot = process.cwd();
|
|
5710
|
-
const workspace =
|
|
5923
|
+
const workspace = path24.join(projectRoot, ".kairn-evolve");
|
|
5711
5924
|
const iter1 = parseInt(iter1Str, 10);
|
|
5712
5925
|
const iter2 = parseInt(iter2Str, 10);
|
|
5713
5926
|
if (isNaN(iter1) || isNaN(iter2)) {
|
|
5714
5927
|
console.log(ui.error("Both arguments must be integers (iteration numbers)"));
|
|
5715
5928
|
process.exit(1);
|
|
5716
5929
|
}
|
|
5717
|
-
const harness1 =
|
|
5718
|
-
const harness2 =
|
|
5930
|
+
const harness1 = path24.join(workspace, "iterations", iter1.toString(), "harness");
|
|
5931
|
+
const harness2 = path24.join(workspace, "iterations", iter2.toString(), "harness");
|
|
5719
5932
|
try {
|
|
5720
|
-
await
|
|
5933
|
+
await fs24.access(harness1);
|
|
5721
5934
|
} catch {
|
|
5722
5935
|
console.log(ui.error(`Iteration ${iter1} harness not found at ${harness1}`));
|
|
5723
5936
|
process.exit(1);
|
|
5724
5937
|
}
|
|
5725
5938
|
try {
|
|
5726
|
-
await
|
|
5939
|
+
await fs24.access(harness2);
|
|
5727
5940
|
} catch {
|
|
5728
5941
|
console.log(ui.error(`Iteration ${iter2} harness not found at ${harness2}`));
|
|
5729
5942
|
process.exit(1);
|
|
@@ -5778,10 +5991,10 @@ evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes
|
|
|
5778
5991
|
async function countFiles(dir) {
|
|
5779
5992
|
let count = 0;
|
|
5780
5993
|
try {
|
|
5781
|
-
const entries = await
|
|
5994
|
+
const entries = await fs24.readdir(dir, { withFileTypes: true });
|
|
5782
5995
|
for (const entry of entries) {
|
|
5783
5996
|
if (entry.isDirectory()) {
|
|
5784
|
-
count += await countFiles(
|
|
5997
|
+
count += await countFiles(path24.join(dir, entry.name));
|
|
5785
5998
|
} else {
|
|
5786
5999
|
count++;
|
|
5787
6000
|
}
|