kairn-cli 2.7.0 → 2.7.2

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 CHANGED
@@ -1831,6 +1831,16 @@ async function parseAgents(harnessPath) {
1831
1831
  if (Array.isArray(disallowedTools)) {
1832
1832
  node.disallowedTools = disallowedTools;
1833
1833
  }
1834
+ const knownKeys = /* @__PURE__ */ new Set(["name", "model", "disallowedTools"]);
1835
+ const extra = {};
1836
+ for (const [key, value] of Object.entries(frontmatter)) {
1837
+ if (!knownKeys.has(key)) {
1838
+ extra[key] = value;
1839
+ }
1840
+ }
1841
+ if (Object.keys(extra).length > 0) {
1842
+ node.extraFrontmatter = extra;
1843
+ }
1834
1844
  nodes.push(node);
1835
1845
  }
1836
1846
  return nodes;
@@ -1847,8 +1857,10 @@ async function parseSkills(harnessPath) {
1847
1857
  const name = entry.replace(/\.md$/, "");
1848
1858
  nodes.push({ name, content });
1849
1859
  } else if (await isDirectory(entryPath)) {
1850
- const skillMdPath = path21.join(entryPath, "skill.md");
1851
- const content = await readFileSafe2(skillMdPath);
1860
+ let content = await readFileSafe2(path21.join(entryPath, "skill.md"));
1861
+ if (content === null) {
1862
+ content = await readFileSafe2(path21.join(entryPath, "SKILL.md"));
1863
+ }
1852
1864
  if (content === null) continue;
1853
1865
  nodes.push({ name: entry, content });
1854
1866
  }
@@ -2445,7 +2457,8 @@ function renderRuleWithFrontmatter(rule) {
2445
2457
  function renderAgentWithFrontmatter(agent) {
2446
2458
  const hasModel = agent.model !== void 0;
2447
2459
  const hasDisallowed = agent.disallowedTools !== void 0 && agent.disallowedTools.length > 0;
2448
- if (!hasModel && !hasDisallowed) {
2460
+ const hasExtra = agent.extraFrontmatter !== void 0 && Object.keys(agent.extraFrontmatter).length > 0;
2461
+ if (!hasModel && !hasDisallowed && !hasExtra) {
2449
2462
  return agent.content;
2450
2463
  }
2451
2464
  const yamlLines = ["---"];
@@ -2458,6 +2471,23 @@ function renderAgentWithFrontmatter(agent) {
2458
2471
  yamlLines.push(` - ${tool}`);
2459
2472
  }
2460
2473
  }
2474
+ if (hasExtra) {
2475
+ for (const [key, value] of Object.entries(agent.extraFrontmatter)) {
2476
+ if (Array.isArray(value)) {
2477
+ yamlLines.push(`${key}:`);
2478
+ for (const item of value) {
2479
+ yamlLines.push(` - ${String(item)}`);
2480
+ }
2481
+ } else if (typeof value === "object" && value !== null) {
2482
+ yamlLines.push(`${key}:`);
2483
+ for (const [subKey, subVal] of Object.entries(value)) {
2484
+ yamlLines.push(` ${subKey}: ${String(subVal)}`);
2485
+ }
2486
+ } else {
2487
+ yamlLines.push(`${key}: ${String(value)}`);
2488
+ }
2489
+ }
2490
+ }
2461
2491
  yamlLines.push("---");
2462
2492
  return yamlLines.join("\n") + "\n\n" + agent.content;
2463
2493
  }
@@ -2874,6 +2904,14 @@ async function renderAffectedFiles(ir, targetDir, touchedCategories) {
2874
2904
  if (touchedCategories.has("agents")) {
2875
2905
  await deleteOrphanedFiles(targetDir, "agents", fileMap);
2876
2906
  }
2907
+ if (touchedCategories.has("mcp") && !fileMap.has(".mcp.json")) {
2908
+ await fs22.unlink(path22.join(targetDir, ".mcp.json")).catch(() => {
2909
+ });
2910
+ }
2911
+ if (touchedCategories.has("settings") && !fileMap.has("settings.json")) {
2912
+ await fs22.unlink(path22.join(targetDir, "settings.json")).catch(() => {
2913
+ });
2914
+ }
2877
2915
  }
2878
2916
  function getFileCategory(relativePath) {
2879
2917
  if (relativePath === "CLAUDE.md") return "claude_md";
@@ -3344,7 +3382,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
3344
3382
  }
3345
3383
  }
3346
3384
  }
3347
- let rngState = 42;
3385
+ let rngState = evolveConfig.rngSeed ?? 42;
3348
3386
  const rng = () => {
3349
3387
  rngState = rngState * 1664525 + 1013904223 & 4294967295;
3350
3388
  return (rngState >>> 0) / 4294967296;
@@ -3458,7 +3496,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
3458
3496
  }
3459
3497
  if (useThompson) {
3460
3498
  const scoreMap = {};
3461
- for (const [taskId, score] of Object.entries(results)) {
3499
+ for (const [taskId, score] of Object.entries(evalResults)) {
3462
3500
  scoreMap[taskId] = score.score ?? (score.pass ? 100 : 0);
3463
3501
  }
3464
3502
  beliefs = updateBeliefs(beliefs, scoreMap);
@@ -3981,7 +4019,9 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
3981
4019
  const branchEvolveConfig = {
3982
4020
  ...evolveConfig,
3983
4021
  // Disable principal for individual branches — synthesis replaces it
3984
- usePrincipal: false
4022
+ usePrincipal: false,
4023
+ // Each branch gets its own RNG seed for Thompson Sampling diversity
4024
+ rngSeed: branchConfig.seed
3985
4025
  };
3986
4026
  const branchProgress = onProgress ? (event) => {
3987
4027
  onProgress({ ...event, branchId: branchConfig.branchId });
@@ -8687,7 +8727,81 @@ async function listFilesRecursive(dir) {
8687
8727
  await walk(dir);
8688
8728
  return results;
8689
8729
  }
8690
- async function applyEvolution(workspacePath, projectRoot, targetIteration) {
8730
+ async function findBestPBTHarness(workspacePath) {
8731
+ const branchesDir = path27.join(workspacePath, "branches");
8732
+ let branchEntries;
8733
+ try {
8734
+ branchEntries = await fs27.readdir(branchesDir);
8735
+ } catch {
8736
+ return null;
8737
+ }
8738
+ let bestScore = -Infinity;
8739
+ let bestPath = "";
8740
+ let bestLabel = "";
8741
+ for (const branchId of branchEntries) {
8742
+ const branchPath = path27.join(branchesDir, branchId);
8743
+ const branchIterations = await listIterations(branchPath);
8744
+ if (branchIterations.length === 0) continue;
8745
+ const bestIter = await findBestIteration(branchPath, branchIterations);
8746
+ const log = await loadIterationLog(branchPath, bestIter);
8747
+ const score = log?.score ?? 0;
8748
+ if (score > bestScore) {
8749
+ bestScore = score;
8750
+ bestPath = path27.join(branchPath, "iterations", bestIter.toString(), "harness");
8751
+ bestLabel = `branch ${branchId}, iteration ${bestIter} (${score.toFixed(1)}%)`;
8752
+ }
8753
+ }
8754
+ const synthesisHarness = path27.join(workspacePath, "synthesis", "harness");
8755
+ try {
8756
+ await fs27.access(synthesisHarness);
8757
+ const synthesisLog = await loadIterationLog(workspacePath, 999);
8758
+ const synthScore = synthesisLog?.score ?? 0;
8759
+ if (synthScore > bestScore) {
8760
+ bestScore = synthScore;
8761
+ bestPath = synthesisHarness;
8762
+ bestLabel = `Meta-Principal synthesis (${synthScore.toFixed(1)}%)`;
8763
+ }
8764
+ } catch {
8765
+ }
8766
+ if (!bestPath) return null;
8767
+ return { harnessPath: bestPath, label: bestLabel };
8768
+ }
8769
+ async function applyEvolution(workspacePath, projectRoot, targetIteration, pbt) {
8770
+ if (pbt) {
8771
+ const pbtResult = await findBestPBTHarness(workspacePath);
8772
+ if (!pbtResult) {
8773
+ throw new Error("No PBT results found. Run `kairn evolve pbt` first.");
8774
+ }
8775
+ const claudeDir2 = path27.join(projectRoot, ".claude");
8776
+ const diffPreview2 = await generateDiff2(claudeDir2, pbtResult.harnessPath);
8777
+ const currentFiles2 = await listFilesRecursive(claudeDir2);
8778
+ const targetFiles2 = await listFilesRecursive(pbtResult.harnessPath);
8779
+ const allPaths2 = /* @__PURE__ */ new Set([...currentFiles2, ...targetFiles2]);
8780
+ const filesChanged2 = [];
8781
+ for (const filePath of allPaths2) {
8782
+ const currentContent = await fs27.readFile(path27.join(claudeDir2, filePath), "utf-8").catch(() => null);
8783
+ const targetContent = await fs27.readFile(path27.join(pbtResult.harnessPath, filePath), "utf-8").catch(() => null);
8784
+ if (currentContent !== targetContent) {
8785
+ filesChanged2.push(filePath);
8786
+ }
8787
+ }
8788
+ await fs27.rm(claudeDir2, { recursive: true, force: true });
8789
+ await copyDir(pbtResult.harnessPath, claudeDir2);
8790
+ const harnessMcpJson2 = path27.join(pbtResult.harnessPath, ".mcp.json");
8791
+ const projectMcpJson2 = path27.join(projectRoot, ".mcp.json");
8792
+ try {
8793
+ await fs27.access(harnessMcpJson2);
8794
+ await fs27.copyFile(harnessMcpJson2, projectMcpJson2);
8795
+ if (!filesChanged2.includes(".mcp.json")) filesChanged2.push(".mcp.json");
8796
+ } catch {
8797
+ }
8798
+ return {
8799
+ iteration: -1,
8800
+ // signals PBT source
8801
+ filesChanged: filesChanged2,
8802
+ diffPreview: diffPreview2
8803
+ };
8804
+ }
8691
8805
  const iterations = await listIterations(workspacePath);
8692
8806
  if (iterations.length === 0) {
8693
8807
  throw new Error("No iterations found in workspace. Run `kairn evolve run` first.");
@@ -9258,7 +9372,7 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
9258
9372
  process.exit(1);
9259
9373
  }
9260
9374
  });
9261
- 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) => {
9375
+ evolveCommand.command("apply").description("Apply the best evolved harness to your project").option("--iter <n>", "Apply a specific iteration instead of the best").option("--pbt", "Apply best PBT result (branch winner or synthesis)").option("--force", "Apply even if git working tree is dirty").option("--no-commit", "Skip automatic git commit after applying").action(async (options) => {
9262
9376
  try {
9263
9377
  const projectRoot = process.cwd();
9264
9378
  const workspace = path30.join(projectRoot, ".kairn-evolve");
@@ -9277,7 +9391,7 @@ evolveCommand.command("apply").description("Apply the best evolved harness to yo
9277
9391
  process.exit(1);
9278
9392
  }
9279
9393
  }
9280
- const result = await applyEvolution(workspace, projectRoot, targetIteration);
9394
+ const result = await applyEvolution(workspace, projectRoot, targetIteration, options.pbt);
9281
9395
  if (result.diffPreview) {
9282
9396
  console.log(ui.section("Changes"));
9283
9397
  for (const line of result.diffPreview.split("\n")) {