kairn-cli 2.14.0 → 2.15.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/dist/cli.js CHANGED
@@ -411,7 +411,7 @@ var init_ui = __esm({
411
411
  // Key-value pairs
412
412
  kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
413
413
  // File list
414
- file: (path37) => chalk.dim(` ${path37}`),
414
+ file: (path39) => chalk.dim(` ${path39}`),
415
415
  // Tool display
416
416
  tool: (name, reason) => ` ${warmStone("\u25CF")} ${chalk.bold(name)}
417
417
  ${chalk.dim(reason)}`,
@@ -3811,12 +3811,31 @@ var init_secrets = __esm({
3811
3811
  }
3812
3812
  });
3813
3813
 
3814
- // src/scanner/scan.ts
3814
+ // src/compiler/persist.ts
3815
3815
  import fs9 from "fs/promises";
3816
3816
  import path9 from "path";
3817
+ async function persistHarnessIR(targetDir, ir) {
3818
+ const kairnDir = path9.join(targetDir, KAIRN_DIR2);
3819
+ await fs9.mkdir(kairnDir, { recursive: true });
3820
+ const filePath = path9.join(kairnDir, HARNESS_IR_FILENAME);
3821
+ await fs9.writeFile(filePath, JSON.stringify(ir, null, 2), "utf-8");
3822
+ return filePath;
3823
+ }
3824
+ var KAIRN_DIR2, HARNESS_IR_FILENAME;
3825
+ var init_persist = __esm({
3826
+ "src/compiler/persist.ts"() {
3827
+ "use strict";
3828
+ KAIRN_DIR2 = ".kairn";
3829
+ HARNESS_IR_FILENAME = "harness-ir.json";
3830
+ }
3831
+ });
3832
+
3833
+ // src/scanner/scan.ts
3834
+ import fs10 from "fs/promises";
3835
+ import path10 from "path";
3817
3836
  async function fileExists(p) {
3818
3837
  try {
3819
- await fs9.access(p);
3838
+ await fs10.access(p);
3820
3839
  return true;
3821
3840
  } catch {
3822
3841
  return false;
@@ -3824,7 +3843,7 @@ async function fileExists(p) {
3824
3843
  }
3825
3844
  async function readJsonSafe(p) {
3826
3845
  try {
3827
- const data = await fs9.readFile(p, "utf-8");
3846
+ const data = await fs10.readFile(p, "utf-8");
3828
3847
  return JSON.parse(data);
3829
3848
  } catch {
3830
3849
  return null;
@@ -3832,14 +3851,14 @@ async function readJsonSafe(p) {
3832
3851
  }
3833
3852
  async function readFileSafe(p) {
3834
3853
  try {
3835
- return await fs9.readFile(p, "utf-8");
3854
+ return await fs10.readFile(p, "utf-8");
3836
3855
  } catch {
3837
3856
  return null;
3838
3857
  }
3839
3858
  }
3840
3859
  async function listDirSafe(p) {
3841
3860
  try {
3842
- const entries = await fs9.readdir(p);
3861
+ const entries = await fs10.readdir(p);
3843
3862
  return entries.filter((e) => !e.startsWith("."));
3844
3863
  } catch {
3845
3864
  return [];
@@ -3891,7 +3910,7 @@ function extractEnvKeys(content) {
3891
3910
  return keys;
3892
3911
  }
3893
3912
  async function scanProject(dir) {
3894
- const pkg2 = await readJsonSafe(path9.join(dir, "package.json"));
3913
+ const pkg2 = await readJsonSafe(path10.join(dir, "package.json"));
3895
3914
  const deps = pkg2?.dependencies ? Object.keys(pkg2.dependencies) : [];
3896
3915
  const devDeps = pkg2?.devDependencies ? Object.keys(pkg2.devDependencies) : [];
3897
3916
  const allDeps = [...deps, ...devDeps];
@@ -3919,19 +3938,19 @@ async function scanProject(dir) {
3919
3938
  const framework = detectFramework(allDeps);
3920
3939
  const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
3921
3940
  const testCommand = scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1' ? scripts.test : null;
3922
- const hasTests = testCommand !== null || await fileExists(path9.join(dir, "tests")) || await fileExists(path9.join(dir, "__tests__")) || await fileExists(path9.join(dir, "test"));
3941
+ const hasTests = testCommand !== null || await fileExists(path10.join(dir, "tests")) || await fileExists(path10.join(dir, "__tests__")) || await fileExists(path10.join(dir, "test"));
3923
3942
  const buildCommand = scripts.build || null;
3924
3943
  const lintCommand = scripts.lint || null;
3925
- const hasSrc = await fileExists(path9.join(dir, "src"));
3926
- const hasDocker = await fileExists(path9.join(dir, "docker-compose.yml")) || await fileExists(path9.join(dir, "Dockerfile"));
3927
- const hasCi = await fileExists(path9.join(dir, ".github/workflows"));
3928
- const hasEnvFile = await fileExists(path9.join(dir, ".env")) || await fileExists(path9.join(dir, ".env.example"));
3944
+ const hasSrc = await fileExists(path10.join(dir, "src"));
3945
+ const hasDocker = await fileExists(path10.join(dir, "docker-compose.yml")) || await fileExists(path10.join(dir, "Dockerfile"));
3946
+ const hasCi = await fileExists(path10.join(dir, ".github/workflows"));
3947
+ const hasEnvFile = await fileExists(path10.join(dir, ".env")) || await fileExists(path10.join(dir, ".env.example"));
3929
3948
  let envKeys = [];
3930
- const envExample = await readFileSafe(path9.join(dir, ".env.example"));
3949
+ const envExample = await readFileSafe(path10.join(dir, ".env.example"));
3931
3950
  if (envExample) {
3932
3951
  envKeys = extractEnvKeys(envExample);
3933
3952
  }
3934
- const claudeDir = path9.join(dir, ".claude");
3953
+ const claudeDir = path10.join(dir, ".claude");
3935
3954
  const hasClaudeDir = await fileExists(claudeDir);
3936
3955
  let existingClaudeMd = null;
3937
3956
  let existingSettings = null;
@@ -3943,21 +3962,21 @@ async function scanProject(dir) {
3943
3962
  let mcpServerCount = 0;
3944
3963
  let claudeMdLineCount = 0;
3945
3964
  if (hasClaudeDir) {
3946
- existingClaudeMd = await readFileSafe(path9.join(claudeDir, "CLAUDE.md"));
3965
+ existingClaudeMd = await readFileSafe(path10.join(claudeDir, "CLAUDE.md"));
3947
3966
  if (existingClaudeMd) {
3948
3967
  claudeMdLineCount = existingClaudeMd.split("\n").length;
3949
3968
  }
3950
- existingSettings = await readJsonSafe(path9.join(claudeDir, "settings.json"));
3951
- existingMcpConfig = await readJsonSafe(path9.join(dir, ".mcp.json"));
3969
+ existingSettings = await readJsonSafe(path10.join(claudeDir, "settings.json"));
3970
+ existingMcpConfig = await readJsonSafe(path10.join(dir, ".mcp.json"));
3952
3971
  if (existingMcpConfig?.mcpServers) {
3953
3972
  mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
3954
3973
  }
3955
- existingCommands = (await listDirSafe(path9.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3956
- existingRules = (await listDirSafe(path9.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3957
- existingSkills = await listDirSafe(path9.join(claudeDir, "skills"));
3958
- existingAgents = (await listDirSafe(path9.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3974
+ existingCommands = (await listDirSafe(path10.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3975
+ existingRules = (await listDirSafe(path10.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3976
+ existingSkills = await listDirSafe(path10.join(claudeDir, "skills"));
3977
+ existingAgents = (await listDirSafe(path10.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3959
3978
  }
3960
- const name = pkg2?.name || path9.basename(dir);
3979
+ const name = pkg2?.name || path10.basename(dir);
3961
3980
  const description = pkg2?.description || "";
3962
3981
  return {
3963
3982
  name,
@@ -4014,8 +4033,8 @@ var init_types3 = __esm({
4014
4033
  });
4015
4034
 
4016
4035
  // src/analyzer/patterns.ts
4017
- import fs10 from "fs/promises";
4018
- import path10 from "path";
4036
+ import fs11 from "fs/promises";
4037
+ import path11 from "path";
4019
4038
  function getStrategy(language) {
4020
4039
  if (language === null) {
4021
4040
  return null;
@@ -4080,7 +4099,7 @@ function extractPathsFromScripts(scripts) {
4080
4099
  async function resolveNodeEntryPoints(dir) {
4081
4100
  const paths = [];
4082
4101
  try {
4083
- const raw = await fs10.readFile(path10.join(dir, "package.json"), "utf-8");
4102
+ const raw = await fs11.readFile(path11.join(dir, "package.json"), "utf-8");
4084
4103
  const pkg2 = JSON.parse(raw);
4085
4104
  if (typeof pkg2.main === "string") {
4086
4105
  paths.push(pkg2.main);
@@ -4119,7 +4138,7 @@ async function resolveNodeEntryPoints(dir) {
4119
4138
  async function resolvePythonEntryPoints(dir) {
4120
4139
  const paths = [];
4121
4140
  try {
4122
- const raw = await fs10.readFile(path10.join(dir, "pyproject.toml"), "utf-8");
4141
+ const raw = await fs11.readFile(path11.join(dir, "pyproject.toml"), "utf-8");
4123
4142
  const scriptMatches = raw.matchAll(/^\s*\w+\s*=\s*"([^":]+)/gm);
4124
4143
  for (const m of scriptMatches) {
4125
4144
  const modulePath = m[1].replace(/\./g, "/") + ".py";
@@ -4130,17 +4149,17 @@ async function resolvePythonEntryPoints(dir) {
4130
4149
  }
4131
4150
  for (const f of ["manage.py", "wsgi.py", "asgi.py"]) {
4132
4151
  try {
4133
- await fs10.access(path10.join(dir, f));
4152
+ await fs11.access(path11.join(dir, f));
4134
4153
  paths.push(f);
4135
4154
  } catch {
4136
4155
  }
4137
4156
  }
4138
4157
  try {
4139
- const entries = await fs10.readdir(path10.join(dir, "src"), { withFileTypes: true });
4158
+ const entries = await fs11.readdir(path11.join(dir, "src"), { withFileTypes: true });
4140
4159
  for (const entry of entries) {
4141
4160
  if (entry.isDirectory()) {
4142
4161
  try {
4143
- await fs10.access(path10.join(dir, "src", entry.name, "__main__.py"));
4162
+ await fs11.access(path11.join(dir, "src", entry.name, "__main__.py"));
4144
4163
  paths.push(`src/${entry.name}/__main__.py`);
4145
4164
  paths.push(`src/${entry.name}/__init__.py`);
4146
4165
  } catch {
@@ -4154,7 +4173,7 @@ async function resolvePythonEntryPoints(dir) {
4154
4173
  async function resolveRustEntryPoints(dir) {
4155
4174
  const paths = [];
4156
4175
  try {
4157
- const raw = await fs10.readFile(path10.join(dir, "Cargo.toml"), "utf-8");
4176
+ const raw = await fs11.readFile(path11.join(dir, "Cargo.toml"), "utf-8");
4158
4177
  const binPaths = raw.matchAll(/path\s*=\s*"([^"]+\.rs)"/g);
4159
4178
  for (const m of binPaths) paths.push(m[1]);
4160
4179
  } catch {
@@ -4164,7 +4183,7 @@ async function resolveRustEntryPoints(dir) {
4164
4183
  async function resolveGoEntryPoints(dir) {
4165
4184
  const paths = [];
4166
4185
  try {
4167
- const cmdEntries = await fs10.readdir(path10.join(dir, "cmd"), { withFileTypes: true });
4186
+ const cmdEntries = await fs11.readdir(path11.join(dir, "cmd"), { withFileTypes: true });
4168
4187
  for (const entry of cmdEntries) {
4169
4188
  if (entry.isDirectory()) {
4170
4189
  paths.push(`cmd/${entry.name}/main.go`);
@@ -4330,9 +4349,9 @@ var init_patterns = __esm({
4330
4349
  });
4331
4350
 
4332
4351
  // src/analyzer/repomix-adapter.ts
4333
- import path11 from "path";
4352
+ import path12 from "path";
4334
4353
  import os3 from "os";
4335
- import fs11 from "fs/promises";
4354
+ import fs12 from "fs/promises";
4336
4355
  import {
4337
4356
  pack,
4338
4357
  mergeConfigs,
@@ -4342,7 +4361,7 @@ import {
4342
4361
  } from "repomix";
4343
4362
  async function packCodebase(dir, options) {
4344
4363
  setLogLevel(0);
4345
- const tempOutputFile = path11.join(
4364
+ const tempOutputFile = path12.join(
4346
4365
  os3.tmpdir(),
4347
4366
  `kairn-repomix-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`
4348
4367
  );
@@ -4413,7 +4432,7 @@ ${f.content}`).join("\n\n");
4413
4432
  message
4414
4433
  );
4415
4434
  } finally {
4416
- await fs11.rm(tempOutputFile, { force: true }).catch(() => {
4435
+ await fs12.rm(tempOutputFile, { force: true }).catch(() => {
4417
4436
  });
4418
4437
  }
4419
4438
  }
@@ -4425,50 +4444,64 @@ var init_repomix_adapter = __esm({
4425
4444
  });
4426
4445
 
4427
4446
  // src/analyzer/cache.ts
4428
- import fs12 from "fs/promises";
4447
+ import fs13 from "fs/promises";
4429
4448
  import fsSync from "fs";
4430
- import path12 from "path";
4449
+ import path13 from "path";
4431
4450
  import { createHash } from "crypto";
4432
4451
  import { fileURLToPath as fileURLToPath3 } from "url";
4433
4452
  function getKairnVersion() {
4434
- let dir = path12.dirname(fileURLToPath3(import.meta.url));
4453
+ let dir = path13.dirname(fileURLToPath3(import.meta.url));
4435
4454
  for (let i = 0; i < 5; i++) {
4436
4455
  try {
4437
- const pkgPath = path12.join(dir, "package.json");
4456
+ const pkgPath = path13.join(dir, "package.json");
4438
4457
  const content = fsSync.readFileSync(pkgPath, "utf-8");
4439
4458
  const parsed = JSON.parse(content);
4440
4459
  if (parsed.name === "kairn-cli") return parsed.version ?? "0.0.0";
4441
4460
  } catch {
4442
4461
  }
4443
- dir = path12.dirname(dir);
4462
+ dir = path13.dirname(dir);
4444
4463
  }
4445
4464
  return "0.0.0";
4446
4465
  }
4447
4466
  async function readCache(dir) {
4448
- const filePath = path12.join(dir, CACHE_FILENAME);
4467
+ const filePath = path13.join(dir, CACHE_FILENAME);
4449
4468
  try {
4450
- const raw = await fs12.readFile(filePath, "utf-8");
4469
+ const raw = await fs13.readFile(filePath, "utf-8");
4451
4470
  const parsed = JSON.parse(raw);
4452
- return parsed;
4471
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.analysis !== "object" || parsed.analysis === null) {
4472
+ return null;
4473
+ }
4474
+ const cache = parsed;
4475
+ let packedSource = null;
4476
+ try {
4477
+ const packedPath = path13.join(dir, PACKED_SOURCE_FILENAME);
4478
+ packedSource = await fs13.readFile(packedPath, "utf-8");
4479
+ } catch {
4480
+ }
4481
+ return { ...cache, packedSource };
4453
4482
  } catch {
4454
4483
  return null;
4455
4484
  }
4456
4485
  }
4457
- async function writeCache(dir, analysis) {
4458
- const filePath = path12.join(dir, CACHE_FILENAME);
4486
+ async function writeCache(dir, analysis, packedSource) {
4487
+ const filePath = path13.join(dir, CACHE_FILENAME);
4459
4488
  const cache = {
4460
4489
  analysis,
4461
4490
  content_hash: analysis.content_hash,
4462
4491
  kairn_version: KAIRN_VERSION
4463
4492
  };
4464
- await fs12.writeFile(filePath, JSON.stringify(cache, null, 2), "utf-8");
4493
+ await fs13.writeFile(filePath, JSON.stringify(cache, null, 2), "utf-8");
4494
+ if (packedSource !== void 0) {
4495
+ const packedPath = path13.join(dir, PACKED_SOURCE_FILENAME);
4496
+ await fs13.writeFile(packedPath, packedSource, "utf-8");
4497
+ }
4465
4498
  }
4466
4499
  async function computeContentHash(filePaths, dir) {
4467
4500
  const hash = createHash("sha256");
4468
4501
  for (const filePath of filePaths) {
4469
4502
  try {
4470
- const fullPath = path12.join(dir, filePath);
4471
- const content = await fs12.readFile(fullPath, "utf-8");
4503
+ const fullPath = path13.join(dir, filePath);
4504
+ const content = await fs13.readFile(fullPath, "utf-8");
4472
4505
  hash.update(content);
4473
4506
  } catch {
4474
4507
  }
@@ -4478,12 +4511,13 @@ async function computeContentHash(filePaths, dir) {
4478
4511
  function isCacheValid(cache, currentHash) {
4479
4512
  return cache.content_hash === currentHash && cache.kairn_version === KAIRN_VERSION;
4480
4513
  }
4481
- var KAIRN_VERSION, CACHE_FILENAME;
4514
+ var KAIRN_VERSION, CACHE_FILENAME, PACKED_SOURCE_FILENAME;
4482
4515
  var init_cache = __esm({
4483
4516
  "src/analyzer/cache.ts"() {
4484
4517
  "use strict";
4485
4518
  KAIRN_VERSION = getKairnVersion();
4486
4519
  CACHE_FILENAME = ".kairn-analysis.json";
4520
+ PACKED_SOURCE_FILENAME = ".kairn-packed-source.txt";
4487
4521
  }
4488
4522
  });
4489
4523
 
@@ -4516,7 +4550,10 @@ async function analyzeProject(dir, profile, config, options) {
4516
4550
  dir
4517
4551
  );
4518
4552
  if (isCacheValid(cache, currentHash)) {
4519
- return cache.analysis;
4553
+ return {
4554
+ analysis: cache.analysis,
4555
+ packedSource: cache.packedSource ?? ""
4556
+ };
4520
4557
  }
4521
4558
  }
4522
4559
  }
@@ -4582,8 +4619,8 @@ async function analyzeProject(dir, profile, config, options) {
4582
4619
  content_hash: contentHash,
4583
4620
  analyzed_at: (/* @__PURE__ */ new Date()).toISOString()
4584
4621
  };
4585
- await writeCache(dir, analysis);
4586
- return analysis;
4622
+ await writeCache(dir, analysis, packed.content);
4623
+ return { analysis, packedSource: packed.content };
4587
4624
  }
4588
4625
  function buildUserMessage6(language, profile, packedContent) {
4589
4626
  return [
@@ -4656,8 +4693,8 @@ import { Command as Command2 } from "commander";
4656
4693
  import { confirm as confirm2 } from "@inquirer/prompts";
4657
4694
  import chalk5 from "chalk";
4658
4695
  import ora from "ora";
4659
- import fs13 from "fs/promises";
4660
- import path13 from "path";
4696
+ import fs14 from "fs/promises";
4697
+ import path14 from "path";
4661
4698
  function simpleDiff(oldContent, newContent) {
4662
4699
  const oldLines = oldContent.split("\n");
4663
4700
  const newLines = newContent.split("\n");
@@ -4681,10 +4718,10 @@ async function generateDiff(spec, targetDir) {
4681
4718
  const fileMap = buildFileMap(spec);
4682
4719
  const results = [];
4683
4720
  for (const [relativePath, newContent] of fileMap) {
4684
- const absolutePath = path13.join(targetDir, relativePath);
4721
+ const absolutePath = path14.join(targetDir, relativePath);
4685
4722
  let oldContent = null;
4686
4723
  try {
4687
- oldContent = await fs13.readFile(absolutePath, "utf-8");
4724
+ oldContent = await fs14.readFile(absolutePath, "utf-8");
4688
4725
  } catch {
4689
4726
  }
4690
4727
  if (oldContent === null) {
@@ -4741,7 +4778,7 @@ Existing .claude/ harness found:`);
4741
4778
  lines.push(` Agents: ${profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"}`);
4742
4779
  return lines.join("\n");
4743
4780
  }
4744
- function buildOptimizeIntent(profile, analysis) {
4781
+ function buildOptimizeIntent(profile, analysis, packedSource) {
4745
4782
  const parts = [];
4746
4783
  parts.push("## Project Profile (scanned from actual codebase)\n");
4747
4784
  parts.push(buildProfileSummary(profile));
@@ -4826,6 +4863,13 @@ ${profile.existingClaudeMd}`);
4826
4863
  }
4827
4864
  }
4828
4865
  }
4866
+ if (packedSource) {
4867
+ parts.push(`
4868
+
4869
+ ## Sampled Source Code (reference for project-specific content)
4870
+
4871
+ ${packedSource}`);
4872
+ }
4829
4873
  return parts.join("\n");
4830
4874
  }
4831
4875
  var optimizeCommand;
@@ -4843,6 +4887,7 @@ var init_optimize = __esm({
4843
4887
  init_logo();
4844
4888
  init_analyze();
4845
4889
  init_types3();
4890
+ init_persist();
4846
4891
  optimizeCommand = new Command2("optimize").description("Scan an existing project and generate or optimize its Claude Code environment").option("-y, --yes", "Skip confirmation prompts").option("--audit-only", "Only audit the existing harness, don't generate changes").option("--diff", "Preview changes as a diff without writing").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (options) => {
4847
4892
  printCompactBanner();
4848
4893
  const config = await loadConfig();
@@ -4866,13 +4911,19 @@ var init_optimize = __esm({
4866
4911
  console.log(ui.section("Codebase Analysis"));
4867
4912
  const analysisSpinner = ora({ text: "Analyzing source code...", indent: 2 }).start();
4868
4913
  let analysis = null;
4914
+ let packedSource = "";
4869
4915
  try {
4870
- analysis = await analyzeProject(targetDir, profile, config);
4916
+ const result = await analyzeProject(targetDir, profile, config);
4917
+ analysis = result.analysis;
4918
+ packedSource = result.packedSource;
4871
4919
  analysisSpinner.succeed("Codebase analyzed");
4872
4920
  console.log(ui.kv("Purpose:", analysis.purpose));
4873
4921
  console.log(ui.kv("Domain:", analysis.domain));
4874
4922
  console.log(ui.kv("Modules:", analysis.key_modules.map((m) => m.name).join(", ") || "none detected"));
4875
4923
  console.log(ui.kv("Workflows:", analysis.workflows.map((w) => w.name).join(", ") || "none detected"));
4924
+ if (packedSource) {
4925
+ console.log(ui.kv("Source:", `${packedSource.length.toLocaleString()} chars sampled`));
4926
+ }
4876
4927
  } catch (err) {
4877
4928
  if (err instanceof AnalysisError) {
4878
4929
  analysisSpinner.fail("Analysis failed");
@@ -4940,7 +4991,7 @@ Run \`kairn analyze\` for details.`));
4940
4991
  }
4941
4992
  }
4942
4993
  }
4943
- const intent = buildOptimizeIntent(profile, analysis);
4994
+ const intent = buildOptimizeIntent(profile, analysis, packedSource);
4944
4995
  let spec;
4945
4996
  const spinner = ora({ text: "Compiling optimized environment...", indent: 2 }).start();
4946
4997
  try {
@@ -4954,6 +5005,13 @@ Run \`kairn analyze\` for details.`));
4954
5005
  console.log(ui.errorBox("KAIRN \u2014 Error", `Optimization failed: ${msg}`));
4955
5006
  process.exit(1);
4956
5007
  }
5008
+ if (spec.ir) {
5009
+ try {
5010
+ await persistHarnessIR(targetDir, spec.ir);
5011
+ } catch {
5012
+ console.log(ui.warn("Could not persist harness IR to .kairn/harness-ir.json"));
5013
+ }
5014
+ }
4957
5015
  const registry = await loadRegistry();
4958
5016
  const summary = summarizeSpec(spec, registry);
4959
5017
  console.log("");
@@ -5034,37 +5092,37 @@ Run \`kairn analyze\` for details.`));
5034
5092
  });
5035
5093
 
5036
5094
  // src/evolve/baseline.ts
5037
- import fs20 from "fs/promises";
5038
- import path20 from "path";
5095
+ import fs21 from "fs/promises";
5096
+ import path21 from "path";
5039
5097
  async function snapshotBaseline(projectRoot, workspacePath) {
5040
- const claudeDir = path20.join(projectRoot, ".claude");
5041
- const baselineDir = path20.join(workspacePath, "baseline");
5042
- const iter0Dir = path20.join(workspacePath, "iterations", "0", "harness");
5098
+ const claudeDir = path21.join(projectRoot, ".claude");
5099
+ const baselineDir = path21.join(workspacePath, "baseline");
5100
+ const iter0Dir = path21.join(workspacePath, "iterations", "0", "harness");
5043
5101
  try {
5044
- await fs20.access(claudeDir);
5102
+ await fs21.access(claudeDir);
5045
5103
  } catch {
5046
5104
  throw new Error(`.claude/ directory not found in ${projectRoot}`);
5047
5105
  }
5048
5106
  await copyDir(claudeDir, baselineDir);
5049
5107
  await copyDir(claudeDir, iter0Dir);
5050
- const mcpJsonPath = path20.join(projectRoot, ".mcp.json");
5108
+ const mcpJsonPath = path21.join(projectRoot, ".mcp.json");
5051
5109
  try {
5052
- await fs20.access(mcpJsonPath);
5053
- await fs20.copyFile(mcpJsonPath, path20.join(baselineDir, ".mcp.json"));
5054
- await fs20.copyFile(mcpJsonPath, path20.join(iter0Dir, ".mcp.json"));
5110
+ await fs21.access(mcpJsonPath);
5111
+ await fs21.copyFile(mcpJsonPath, path21.join(baselineDir, ".mcp.json"));
5112
+ await fs21.copyFile(mcpJsonPath, path21.join(iter0Dir, ".mcp.json"));
5055
5113
  } catch {
5056
5114
  }
5057
5115
  }
5058
5116
  async function copyDir(src, dest) {
5059
- await fs20.mkdir(dest, { recursive: true });
5060
- const entries = await fs20.readdir(src, { withFileTypes: true });
5117
+ await fs21.mkdir(dest, { recursive: true });
5118
+ const entries = await fs21.readdir(src, { withFileTypes: true });
5061
5119
  for (const entry of entries) {
5062
- const srcPath = path20.join(src, entry.name);
5063
- const destPath = path20.join(dest, entry.name);
5120
+ const srcPath = path21.join(src, entry.name);
5121
+ const destPath = path21.join(dest, entry.name);
5064
5122
  if (entry.isDirectory()) {
5065
5123
  await copyDir(srcPath, destPath);
5066
5124
  } else {
5067
- await fs20.copyFile(srcPath, destPath);
5125
+ await fs21.copyFile(srcPath, destPath);
5068
5126
  }
5069
5127
  }
5070
5128
  }
@@ -5075,32 +5133,32 @@ var init_baseline = __esm({
5075
5133
  });
5076
5134
 
5077
5135
  // src/evolve/trace.ts
5078
- import fs21 from "fs/promises";
5079
- import path21 from "path";
5136
+ import fs22 from "fs/promises";
5137
+ import path22 from "path";
5080
5138
  async function loadTrace(traceDir) {
5081
- const stdout = await fs21.readFile(path21.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5082
- const stderr = await fs21.readFile(path21.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5083
- const filesChangedStr = await fs21.readFile(
5084
- path21.join(traceDir, "files_changed.json"),
5139
+ const stdout = await fs22.readFile(path22.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5140
+ const stderr = await fs22.readFile(path22.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5141
+ const filesChangedStr = await fs22.readFile(
5142
+ path22.join(traceDir, "files_changed.json"),
5085
5143
  "utf-8"
5086
5144
  ).catch(() => "{}");
5087
- const timingStr = await fs21.readFile(
5088
- path21.join(traceDir, "timing.json"),
5145
+ const timingStr = await fs22.readFile(
5146
+ path22.join(traceDir, "timing.json"),
5089
5147
  "utf-8"
5090
5148
  ).catch(() => "{}");
5091
- const scoreStr = await fs21.readFile(
5092
- path21.join(traceDir, "score.json"),
5149
+ const scoreStr = await fs22.readFile(
5150
+ path22.join(traceDir, "score.json"),
5093
5151
  "utf-8"
5094
5152
  ).catch(() => '{"pass": false}');
5095
- const toolCallsStr = await fs21.readFile(
5096
- path21.join(traceDir, "tool_calls.jsonl"),
5153
+ const toolCallsStr = await fs22.readFile(
5154
+ path22.join(traceDir, "tool_calls.jsonl"),
5097
5155
  "utf-8"
5098
5156
  ).catch(() => "");
5099
5157
  const toolCalls = toolCallsStr.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
5100
- const parentDir = path21.basename(path21.dirname(traceDir));
5158
+ const parentDir = path22.basename(path22.dirname(traceDir));
5101
5159
  const iteration = parseInt(parentDir, 10) || 0;
5102
5160
  return {
5103
- taskId: path21.basename(traceDir),
5161
+ taskId: path22.basename(traceDir),
5104
5162
  iteration,
5105
5163
  stdout,
5106
5164
  stderr,
@@ -5111,12 +5169,12 @@ async function loadTrace(traceDir) {
5111
5169
  };
5112
5170
  }
5113
5171
  async function loadIterationTraces(workspacePath, iteration) {
5114
- const tracesDir = path21.join(workspacePath, "traces", iteration.toString());
5172
+ const tracesDir = path22.join(workspacePath, "traces", iteration.toString());
5115
5173
  const traces = [];
5116
5174
  try {
5117
- const taskDirs = await fs21.readdir(tracesDir);
5175
+ const taskDirs = await fs22.readdir(tracesDir);
5118
5176
  for (const taskId of taskDirs) {
5119
- const trace = await loadTrace(path21.join(tracesDir, taskId));
5177
+ const trace = await loadTrace(path22.join(tracesDir, taskId));
5120
5178
  traces.push(trace);
5121
5179
  }
5122
5180
  } catch {
@@ -5124,39 +5182,39 @@ async function loadIterationTraces(workspacePath, iteration) {
5124
5182
  return traces;
5125
5183
  }
5126
5184
  async function writeTrace(traceDir, trace) {
5127
- await fs21.mkdir(traceDir, { recursive: true });
5128
- await fs21.writeFile(path21.join(traceDir, "stdout.log"), trace.stdout, "utf-8");
5129
- await fs21.writeFile(path21.join(traceDir, "stderr.log"), trace.stderr, "utf-8");
5185
+ await fs22.mkdir(traceDir, { recursive: true });
5186
+ await fs22.writeFile(path22.join(traceDir, "stdout.log"), trace.stdout, "utf-8");
5187
+ await fs22.writeFile(path22.join(traceDir, "stderr.log"), trace.stderr, "utf-8");
5130
5188
  const toolCallsLines = trace.toolCalls.map((tc) => JSON.stringify(tc)).join("\n");
5131
- await fs21.writeFile(path21.join(traceDir, "tool_calls.jsonl"), toolCallsLines, "utf-8");
5132
- await fs21.writeFile(
5133
- path21.join(traceDir, "files_changed.json"),
5189
+ await fs22.writeFile(path22.join(traceDir, "tool_calls.jsonl"), toolCallsLines, "utf-8");
5190
+ await fs22.writeFile(
5191
+ path22.join(traceDir, "files_changed.json"),
5134
5192
  JSON.stringify(trace.filesChanged, null, 2),
5135
5193
  "utf-8"
5136
5194
  );
5137
- await fs21.writeFile(
5138
- path21.join(traceDir, "timing.json"),
5195
+ await fs22.writeFile(
5196
+ path22.join(traceDir, "timing.json"),
5139
5197
  JSON.stringify(trace.timing, null, 2),
5140
5198
  "utf-8"
5141
5199
  );
5142
- await fs21.writeFile(
5143
- path21.join(traceDir, "score.json"),
5200
+ await fs22.writeFile(
5201
+ path22.join(traceDir, "score.json"),
5144
5202
  JSON.stringify(trace.score, null, 2),
5145
5203
  "utf-8"
5146
5204
  );
5147
5205
  }
5148
5206
  async function writeScore(traceDir, score) {
5149
- await fs21.writeFile(
5150
- path21.join(traceDir, "score.json"),
5207
+ await fs22.writeFile(
5208
+ path22.join(traceDir, "score.json"),
5151
5209
  JSON.stringify(score, null, 2),
5152
5210
  "utf-8"
5153
5211
  );
5154
5212
  }
5155
5213
  async function writeIterationLog(workspacePath, log) {
5156
- const iterDir = path21.join(workspacePath, "iterations", log.iteration.toString());
5157
- await fs21.mkdir(iterDir, { recursive: true });
5158
- await fs21.writeFile(
5159
- path21.join(iterDir, "scores.json"),
5214
+ const iterDir = path22.join(workspacePath, "iterations", log.iteration.toString());
5215
+ await fs22.mkdir(iterDir, { recursive: true });
5216
+ await fs22.writeFile(
5217
+ path22.join(iterDir, "scores.json"),
5160
5218
  JSON.stringify({
5161
5219
  score: log.score,
5162
5220
  taskResults: log.taskResults,
@@ -5164,27 +5222,27 @@ async function writeIterationLog(workspacePath, log) {
5164
5222
  }, null, 2),
5165
5223
  "utf-8"
5166
5224
  );
5167
- await fs21.writeFile(
5168
- path21.join(iterDir, "proposer_reasoning.md"),
5225
+ await fs22.writeFile(
5226
+ path22.join(iterDir, "proposer_reasoning.md"),
5169
5227
  log.proposal?.reasoning ?? "Baseline evaluation (no proposal)",
5170
5228
  "utf-8"
5171
5229
  );
5172
- await fs21.writeFile(
5173
- path21.join(iterDir, "mutation_diff.patch"),
5230
+ await fs22.writeFile(
5231
+ path22.join(iterDir, "mutation_diff.patch"),
5174
5232
  log.diffPatch ?? "",
5175
5233
  "utf-8"
5176
5234
  );
5177
5235
  }
5178
5236
  async function loadIterationLog(workspacePath, iteration) {
5179
- const iterDir = path21.join(workspacePath, "iterations", iteration.toString());
5237
+ const iterDir = path22.join(workspacePath, "iterations", iteration.toString());
5180
5238
  try {
5181
- await fs21.access(iterDir);
5239
+ await fs22.access(iterDir);
5182
5240
  } catch {
5183
5241
  return null;
5184
5242
  }
5185
- const scoresStr = await fs21.readFile(path21.join(iterDir, "scores.json"), "utf-8").catch(() => "{}");
5186
- const reasoning = await fs21.readFile(path21.join(iterDir, "proposer_reasoning.md"), "utf-8").catch(() => "");
5187
- const diffPatch = await fs21.readFile(path21.join(iterDir, "mutation_diff.patch"), "utf-8").catch(() => "");
5243
+ const scoresStr = await fs22.readFile(path22.join(iterDir, "scores.json"), "utf-8").catch(() => "{}");
5244
+ const reasoning = await fs22.readFile(path22.join(iterDir, "proposer_reasoning.md"), "utf-8").catch(() => "");
5245
+ const diffPatch = await fs22.readFile(path22.join(iterDir, "mutation_diff.patch"), "utf-8").catch(() => "");
5188
5246
  const scoresData = JSON.parse(scoresStr);
5189
5247
  const proposal = reasoning ? { reasoning, mutations: [], expectedImpact: {} } : null;
5190
5248
  return {
@@ -5555,12 +5613,12 @@ Return ONLY valid JSON:
5555
5613
  // src/evolve/runner.ts
5556
5614
  import { exec as exec3, spawn } from "child_process";
5557
5615
  import { promisify as promisify3 } from "util";
5558
- import fs22 from "fs/promises";
5616
+ import fs23 from "fs/promises";
5559
5617
  import os4 from "os";
5560
- import path22 from "path";
5618
+ import path23 from "path";
5561
5619
  async function deployMcpJson(harnessPath, workDir) {
5562
- const src = path22.join(harnessPath, ".mcp.json");
5563
- await fs22.copyFile(src, path22.join(workDir, ".mcp.json")).catch(() => {
5620
+ const src = path23.join(harnessPath, ".mcp.json");
5621
+ await fs23.copyFile(src, path23.join(workDir, ".mcp.json")).catch(() => {
5564
5622
  });
5565
5623
  }
5566
5624
  async function createIsolatedWorkspace(projectRoot, harnessPath) {
@@ -5570,40 +5628,40 @@ async function createIsolatedWorkspace(projectRoot, harnessPath) {
5570
5628
  cwd: projectRoot,
5571
5629
  timeout: 5e3
5572
5630
  });
5573
- const tmpDir2 = path22.join(os4.tmpdir(), `kairn-evolve-wt-${suffix}`);
5631
+ const tmpDir2 = path23.join(os4.tmpdir(), `kairn-evolve-wt-${suffix}`);
5574
5632
  await execAsync3(`git worktree add --detach "${tmpDir2}" HEAD`, {
5575
5633
  cwd: projectRoot,
5576
5634
  timeout: 3e4
5577
5635
  });
5578
- await fs22.rm(path22.join(tmpDir2, ".claude"), { recursive: true, force: true });
5579
- await copyDir(harnessPath, path22.join(tmpDir2, ".claude"));
5636
+ await fs23.rm(path23.join(tmpDir2, ".claude"), { recursive: true, force: true });
5637
+ await copyDir(harnessPath, path23.join(tmpDir2, ".claude"));
5580
5638
  await deployMcpJson(harnessPath, tmpDir2);
5581
5639
  return { workDir: tmpDir2, isWorktree: true };
5582
5640
  } catch {
5583
5641
  }
5584
- const tmpDir = await fs22.mkdtemp(path22.join(os4.tmpdir(), `kairn-evolve-cp-`));
5642
+ const tmpDir = await fs23.mkdtemp(path23.join(os4.tmpdir(), `kairn-evolve-cp-`));
5585
5643
  await copyProjectDir(projectRoot, tmpDir);
5586
- await fs22.rm(path22.join(tmpDir, ".claude"), { recursive: true, force: true });
5587
- await copyDir(harnessPath, path22.join(tmpDir, ".claude"));
5644
+ await fs23.rm(path23.join(tmpDir, ".claude"), { recursive: true, force: true });
5645
+ await copyDir(harnessPath, path23.join(tmpDir, ".claude"));
5588
5646
  await deployMcpJson(harnessPath, tmpDir);
5589
5647
  return { workDir: tmpDir, isWorktree: false };
5590
5648
  }
5591
5649
  async function copyProjectDir(src, dest) {
5592
- await fs22.mkdir(dest, { recursive: true });
5650
+ await fs23.mkdir(dest, { recursive: true });
5593
5651
  let entries;
5594
5652
  try {
5595
- entries = await fs22.readdir(src, { withFileTypes: true });
5653
+ entries = await fs23.readdir(src, { withFileTypes: true });
5596
5654
  } catch {
5597
5655
  return;
5598
5656
  }
5599
5657
  for (const entry of entries) {
5600
5658
  if (COPY_SKIP_DIRS.has(entry.name)) continue;
5601
- const srcPath = path22.join(src, entry.name);
5602
- const destPath = path22.join(dest, entry.name);
5659
+ const srcPath = path23.join(src, entry.name);
5660
+ const destPath = path23.join(dest, entry.name);
5603
5661
  if (entry.isDirectory()) {
5604
5662
  await copyDir(srcPath, destPath);
5605
5663
  } else {
5606
- await fs22.copyFile(srcPath, destPath);
5664
+ await fs23.copyFile(srcPath, destPath);
5607
5665
  }
5608
5666
  }
5609
5667
  }
@@ -5615,7 +5673,7 @@ async function cleanupIsolatedWorkspace(workDir, isWorktree, projectRoot) {
5615
5673
  timeout: 1e4
5616
5674
  });
5617
5675
  } catch {
5618
- await fs22.rm(workDir, { recursive: true, force: true }).catch(() => {
5676
+ await fs23.rm(workDir, { recursive: true, force: true }).catch(() => {
5619
5677
  });
5620
5678
  await execAsync3("git worktree prune", {
5621
5679
  cwd: projectRoot,
@@ -5624,12 +5682,12 @@ async function cleanupIsolatedWorkspace(workDir, isWorktree, projectRoot) {
5624
5682
  });
5625
5683
  }
5626
5684
  } else {
5627
- await fs22.rm(workDir, { recursive: true, force: true }).catch(() => {
5685
+ await fs23.rm(workDir, { recursive: true, force: true }).catch(() => {
5628
5686
  });
5629
5687
  }
5630
5688
  }
5631
5689
  async function runTask(task, harnessPath, traceDir, iteration, projectRoot) {
5632
- await fs22.mkdir(traceDir, { recursive: true });
5690
+ await fs23.mkdir(traceDir, { recursive: true });
5633
5691
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5634
5692
  const startMs = Date.now();
5635
5693
  const root = projectRoot ?? process.cwd();
@@ -5709,13 +5767,13 @@ async function snapshotFileList(dir) {
5709
5767
  async function walk(current) {
5710
5768
  let entries;
5711
5769
  try {
5712
- entries = await fs22.readdir(current, { withFileTypes: true });
5770
+ entries = await fs23.readdir(current, { withFileTypes: true });
5713
5771
  } catch {
5714
5772
  return;
5715
5773
  }
5716
5774
  for (const entry of entries) {
5717
- const fullPath = path22.join(current, entry.name);
5718
- const relativePath = path22.relative(dir, fullPath);
5775
+ const fullPath = path23.join(current, entry.name);
5776
+ const relativePath = path23.relative(dir, fullPath);
5719
5777
  if (relativePath.startsWith(".claude")) continue;
5720
5778
  if (relativePath.startsWith("node_modules")) continue;
5721
5779
  if (relativePath.startsWith(".git")) continue;
@@ -5723,7 +5781,7 @@ async function snapshotFileList(dir) {
5723
5781
  await walk(fullPath);
5724
5782
  } else {
5725
5783
  try {
5726
- const stat = await fs22.stat(fullPath);
5784
+ const stat = await fs23.stat(fullPath);
5727
5785
  result[relativePath] = stat.mtimeMs;
5728
5786
  } catch {
5729
5787
  }
@@ -5802,7 +5860,7 @@ function computeStddev(values, mean) {
5802
5860
  }
5803
5861
  async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config, onProgress, runsPerTask = 1, parallelTasks = 1) {
5804
5862
  const results = {};
5805
- const projectRoot = path22.resolve(workspacePath, "..");
5863
+ const projectRoot = path23.resolve(workspacePath, "..");
5806
5864
  const effectiveRuns = Math.max(1, runsPerTask);
5807
5865
  const concurrency = Math.max(1, parallelTasks);
5808
5866
  const evaluateTask = async (task) => {
@@ -5812,7 +5870,7 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5812
5870
  const runScores = [];
5813
5871
  let passCount = 0;
5814
5872
  for (let run = 0; run < effectiveRuns; run++) {
5815
- const traceDir = path22.join(
5873
+ const traceDir = path23.join(
5816
5874
  workspacePath,
5817
5875
  "traces",
5818
5876
  iteration.toString(),
@@ -5825,8 +5883,8 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5825
5883
  message: `Run ${run + 1}/${effectiveRuns} of ${task.id}`
5826
5884
  });
5827
5885
  await runTask(task, harnessPath, traceDir, iteration, projectRoot);
5828
- const stdout = await fs22.readFile(path22.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5829
- const stderr = await fs22.readFile(path22.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5886
+ const stdout = await fs23.readFile(path23.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5887
+ const stderr = await fs23.readFile(path23.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5830
5888
  const score = await scoreTask(task, traceDir, stdout, stderr, config);
5831
5889
  await writeScore(traceDir, score);
5832
5890
  runScores.push(score.score ?? (score.pass ? 100 : 0));
@@ -5846,7 +5904,7 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5846
5904
  }
5847
5905
  };
5848
5906
  } else {
5849
- const traceDir = path22.join(
5907
+ const traceDir = path23.join(
5850
5908
  workspacePath,
5851
5909
  "traces",
5852
5910
  iteration.toString(),
@@ -5855,8 +5913,8 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5855
5913
  const taskResult = await runTask(task, harnessPath, traceDir, iteration, projectRoot);
5856
5914
  finalScore = taskResult.score;
5857
5915
  if (config) {
5858
- const stdout = await fs22.readFile(path22.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5859
- const stderr = await fs22.readFile(path22.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5916
+ const stdout = await fs23.readFile(path23.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5917
+ const stderr = await fs23.readFile(path23.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5860
5918
  finalScore = await scoreTask(task, traceDir, stdout, stderr, config);
5861
5919
  await writeScore(traceDir, finalScore);
5862
5920
  }
@@ -5904,12 +5962,12 @@ __export(memory_exports, {
5904
5962
  loadProposerMemory: () => loadProposerMemory,
5905
5963
  saveRunSummary: () => saveRunSummary
5906
5964
  });
5907
- import fs23 from "fs/promises";
5908
- import path23 from "path";
5965
+ import fs24 from "fs/promises";
5966
+ import path24 from "path";
5909
5967
  async function loadProposerMemory(workspacePath) {
5910
- const memoryPath = path23.join(workspacePath, MEMORY_FILE);
5968
+ const memoryPath = path24.join(workspacePath, MEMORY_FILE);
5911
5969
  try {
5912
- const raw = await fs23.readFile(memoryPath, "utf-8");
5970
+ const raw = await fs24.readFile(memoryPath, "utf-8");
5913
5971
  const parsed = JSON.parse(raw);
5914
5972
  if (Array.isArray(parsed)) return parsed;
5915
5973
  return [];
@@ -5948,8 +6006,8 @@ async function saveRunSummary(workspacePath, summary) {
5948
6006
  const existing = await loadProposerMemory(workspacePath);
5949
6007
  existing.push(summary);
5950
6008
  const trimmed = existing.slice(-MAX_ENTRIES);
5951
- const memoryPath = path23.join(workspacePath, MEMORY_FILE);
5952
- await fs23.writeFile(memoryPath, JSON.stringify(trimmed, null, 2), "utf-8");
6009
+ const memoryPath = path24.join(workspacePath, MEMORY_FILE);
6010
+ await fs24.writeFile(memoryPath, JSON.stringify(trimmed, null, 2), "utf-8");
5953
6011
  }
5954
6012
  function formatMemoryForProposer(memory) {
5955
6013
  if (memory.length === 0) return "";
@@ -5994,26 +6052,26 @@ __export(knowledge_exports, {
5994
6052
  savePattern: () => savePattern,
5995
6053
  saveProjectHistory: () => saveProjectHistory
5996
6054
  });
5997
- import fs24 from "fs/promises";
5998
- import path24 from "path";
6055
+ import fs25 from "fs/promises";
6056
+ import path25 from "path";
5999
6057
  import crypto2 from "crypto";
6000
6058
  function getKnowledgeDir() {
6001
- return path24.join(getKairnDir(), "knowledge");
6059
+ return path25.join(getKairnDir(), "knowledge");
6002
6060
  }
6003
6061
  function getPatternsPath() {
6004
- return path24.join(getKnowledgeDir(), "patterns.jsonl");
6062
+ return path25.join(getKnowledgeDir(), "patterns.jsonl");
6005
6063
  }
6006
6064
  function getProjectsDir() {
6007
- return path24.join(getKnowledgeDir(), "projects");
6065
+ return path25.join(getKnowledgeDir(), "projects");
6008
6066
  }
6009
6067
  function getConvergencePath() {
6010
- return path24.join(getKnowledgeDir(), "convergence.json");
6068
+ return path25.join(getKnowledgeDir(), "convergence.json");
6011
6069
  }
6012
6070
  async function loadKnowledgeBase(filter) {
6013
6071
  const patternsPath = getPatternsPath();
6014
6072
  let content;
6015
6073
  try {
6016
- content = await fs24.readFile(patternsPath, "utf-8");
6074
+ content = await fs25.readFile(patternsPath, "utf-8");
6017
6075
  } catch {
6018
6076
  return [];
6019
6077
  }
@@ -6033,9 +6091,9 @@ async function loadKnowledgeBase(filter) {
6033
6091
  }
6034
6092
  async function savePattern(pattern) {
6035
6093
  const dir = getKnowledgeDir();
6036
- await fs24.mkdir(dir, { recursive: true });
6094
+ await fs25.mkdir(dir, { recursive: true });
6037
6095
  const patternsPath = getPatternsPath();
6038
- await fs24.appendFile(patternsPath, JSON.stringify(pattern) + "\n", "utf-8");
6096
+ await fs25.appendFile(patternsPath, JSON.stringify(pattern) + "\n", "utf-8");
6039
6097
  }
6040
6098
  async function extractAndSavePatterns(history, projectName, language) {
6041
6099
  const patterns = [];
@@ -6107,13 +6165,13 @@ function formatKnowledgeForArchitect(patterns, language) {
6107
6165
  }
6108
6166
  async function saveProjectHistory(projectName, summary) {
6109
6167
  const projectsDir = getProjectsDir();
6110
- await fs24.mkdir(projectsDir, { recursive: true });
6111
- const filePath = path24.join(projectsDir, `${projectName}.json`);
6112
- await fs24.writeFile(filePath, JSON.stringify(summary, null, 2), "utf-8");
6168
+ await fs25.mkdir(projectsDir, { recursive: true });
6169
+ const filePath = path25.join(projectsDir, `${projectName}.json`);
6170
+ await fs25.writeFile(filePath, JSON.stringify(summary, null, 2), "utf-8");
6113
6171
  }
6114
6172
  async function loadConvergence() {
6115
6173
  try {
6116
- const content = await fs24.readFile(getConvergencePath(), "utf-8");
6174
+ const content = await fs25.readFile(getConvergencePath(), "utf-8");
6117
6175
  return JSON.parse(content);
6118
6176
  } catch {
6119
6177
  return null;
@@ -6121,8 +6179,8 @@ async function loadConvergence() {
6121
6179
  }
6122
6180
  async function saveConvergence(convergence) {
6123
6181
  const dir = getKnowledgeDir();
6124
- await fs24.mkdir(dir, { recursive: true });
6125
- await fs24.writeFile(
6182
+ await fs25.mkdir(dir, { recursive: true });
6183
+ await fs25.writeFile(
6126
6184
  getConvergencePath(),
6127
6185
  JSON.stringify(convergence, null, 2),
6128
6186
  "utf-8"
@@ -6136,25 +6194,112 @@ var init_knowledge = __esm({
6136
6194
  });
6137
6195
 
6138
6196
  // src/evolve/proposer.ts
6139
- import fs25 from "fs/promises";
6140
- import path25 from "path";
6197
+ import fs26 from "fs/promises";
6198
+ import path26 from "path";
6199
+ function countSettingsHooks(settings) {
6200
+ let count = 0;
6201
+ const categories = settings.hooks;
6202
+ for (const key of Object.keys(categories)) {
6203
+ const entries = categories[key];
6204
+ if (entries) {
6205
+ count += entries.length;
6206
+ }
6207
+ }
6208
+ return count;
6209
+ }
6210
+ function buildIRSummary(ir) {
6211
+ const lines = [];
6212
+ lines.push("## Harness Structure (IR)");
6213
+ const meta = ir.meta;
6214
+ const techParts = [meta.techStack.language];
6215
+ if (meta.techStack.framework) techParts.push(meta.techStack.framework);
6216
+ if (meta.techStack.buildTool) techParts.push(meta.techStack.buildTool);
6217
+ lines.push(`Project: ${meta.name || "(unnamed)"} \u2014 ${techParts.join(", ")}`);
6218
+ const sectionIds = ir.sections.map((s) => s.id);
6219
+ lines.push(`Sections (${ir.sections.length}): ${sectionIds.join(", ") || "none"}`);
6220
+ const commandSummaries = ir.commands.map((c) => c.name);
6221
+ lines.push(`Commands (${ir.commands.length}): ${commandSummaries.join(", ") || "none"}`);
6222
+ const ruleSummaries = ir.rules.map((r) => {
6223
+ const pathHint = r.paths && r.paths.length > 0 ? ` (${r.paths.join(", ")})` : "";
6224
+ return `${r.name}${pathHint}`;
6225
+ });
6226
+ lines.push(`Rules (${ir.rules.length}): ${ruleSummaries.join(", ") || "none"}`);
6227
+ const agentNames = ir.agents.map((a) => a.name);
6228
+ lines.push(`Agents (${ir.agents.length}): ${agentNames.join(", ") || "none"}`);
6229
+ const serverNames = ir.mcpServers.map((s) => s.id);
6230
+ lines.push(`MCP Servers (${ir.mcpServers.length}): ${serverNames.join(", ") || "none"}`);
6231
+ if (ir.skills.length > 0) {
6232
+ const skillNames = ir.skills.map((s) => s.name);
6233
+ lines.push(`Skills (${ir.skills.length}): ${skillNames.join(", ")}`);
6234
+ }
6235
+ if (ir.docs.length > 0) {
6236
+ const docNames = ir.docs.map((d) => d.name);
6237
+ lines.push(`Docs (${ir.docs.length}): ${docNames.join(", ")}`);
6238
+ }
6239
+ if (ir.hooks.length > 0) {
6240
+ const hookNames = ir.hooks.map((h) => h.name);
6241
+ lines.push(`Hooks (${ir.hooks.length}): ${hookNames.join(", ")}`);
6242
+ }
6243
+ const settingsParts = [];
6244
+ if (ir.settings.statusLine) {
6245
+ settingsParts.push("statusLine=enabled");
6246
+ }
6247
+ if (ir.settings.denyPatterns && ir.settings.denyPatterns.length > 0) {
6248
+ settingsParts.push(`denyPatterns=${ir.settings.denyPatterns.length}`);
6249
+ }
6250
+ const hooksCount = countSettingsHooks(ir.settings);
6251
+ if (hooksCount > 0) {
6252
+ settingsParts.push(`hooks=${hooksCount}`);
6253
+ }
6254
+ lines.push(`Settings: ${settingsParts.join(", ") || "default"}`);
6255
+ return lines.join("\n");
6256
+ }
6257
+ function formatAnalysisForProposer(analysis) {
6258
+ const lines = [];
6259
+ lines.push(`Purpose: ${analysis.purpose}`);
6260
+ lines.push(`Domain: ${analysis.domain}`);
6261
+ lines.push(`Architecture: ${analysis.architecture_style}`);
6262
+ lines.push(`Deployment: ${analysis.deployment_model}`);
6263
+ if (analysis.key_modules.length > 0) {
6264
+ lines.push("");
6265
+ lines.push("Key Modules:");
6266
+ for (const mod of analysis.key_modules) {
6267
+ lines.push(`- ${mod.name} (${mod.path}): ${mod.description}`);
6268
+ }
6269
+ }
6270
+ if (analysis.workflows.length > 0) {
6271
+ lines.push("");
6272
+ lines.push("Workflows:");
6273
+ for (const wf of analysis.workflows) {
6274
+ lines.push(`- ${wf.name}: ${wf.description} (trigger: ${wf.trigger})`);
6275
+ }
6276
+ }
6277
+ if (analysis.config_keys.length > 0) {
6278
+ lines.push("");
6279
+ lines.push("Config Keys:");
6280
+ for (const key of analysis.config_keys) {
6281
+ lines.push(`- ${key.name}: ${key.purpose}`);
6282
+ }
6283
+ }
6284
+ return lines.join("\n");
6285
+ }
6141
6286
  async function readHarnessFiles(harnessPath) {
6142
6287
  const result = {};
6143
6288
  async function walk(dir, prefix) {
6144
6289
  let entries;
6145
6290
  try {
6146
- entries = await fs25.readdir(dir, { withFileTypes: true });
6291
+ entries = await fs26.readdir(dir, { withFileTypes: true });
6147
6292
  } catch {
6148
6293
  return;
6149
6294
  }
6150
6295
  for (const entry of entries) {
6151
- const relativePath = prefix ? path25.join(prefix, entry.name) : entry.name;
6152
- const fullPath = path25.join(dir, entry.name);
6296
+ const relativePath = prefix ? path26.join(prefix, entry.name) : entry.name;
6297
+ const fullPath = path26.join(dir, entry.name);
6153
6298
  if (entry.isDirectory()) {
6154
6299
  await walk(fullPath, relativePath);
6155
6300
  } else if (entry.isFile()) {
6156
6301
  try {
6157
- result[relativePath] = await fs25.readFile(fullPath, "utf-8");
6302
+ result[relativePath] = await fs26.readFile(fullPath, "utf-8");
6158
6303
  } catch {
6159
6304
  }
6160
6305
  }
@@ -6170,7 +6315,7 @@ function truncateStdout(stdout, limit) {
6170
6315
  return `[...truncated, showing last ${limit} chars...]
6171
6316
  ${stdout.slice(-limit)}`;
6172
6317
  }
6173
- function buildProposerUserMessage(harnessFiles, traces, tasks, history, memorySection) {
6318
+ function buildProposerUserMessage(harnessFiles, traces, tasks, history, memorySection, projectContext) {
6174
6319
  const harnessSection = ["## Current Harness Files\n"];
6175
6320
  const fileEntries = Object.entries(harnessFiles);
6176
6321
  if (fileEntries.length === 0) {
@@ -6197,7 +6342,24 @@ ${content}
6197
6342
  );
6198
6343
  }
6199
6344
  }
6200
- const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n");
6345
+ let projectSection = "";
6346
+ if (projectContext) {
6347
+ const parts = ["## Project Understanding\n"];
6348
+ parts.push("### Analysis Summary");
6349
+ parts.push(formatAnalysisForProposer(projectContext.analysis));
6350
+ parts.push("");
6351
+ parts.push("### Harness Structure");
6352
+ parts.push(projectContext.irSummary);
6353
+ if (projectContext.keySourceFiles) {
6354
+ parts.push("");
6355
+ parts.push("### Key Source Files");
6356
+ parts.push("```");
6357
+ parts.push(projectContext.keySourceFiles);
6358
+ parts.push("```");
6359
+ }
6360
+ projectSection = "\n" + parts.join("\n") + "\n";
6361
+ }
6362
+ const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + projectSection;
6201
6363
  const remainingBudget = MAX_CONTEXT_CHARS - fixedContent.length;
6202
6364
  if (remainingBudget <= 0) {
6203
6365
  return fixedContent + "\n\n[...traces and history omitted \u2014 harness + tasks fill context budget...]";
@@ -6359,7 +6521,7 @@ function parseProposerResponse(raw) {
6359
6521
  expectedImpact
6360
6522
  };
6361
6523
  }
6362
- async function propose(iteration, workspacePath, harnessPath, history, tasks, config, proposerModel) {
6524
+ async function propose(iteration, workspacePath, harnessPath, history, tasks, config, proposerModel, projectContext) {
6363
6525
  const harnessFiles = await readHarnessFiles(harnessPath);
6364
6526
  const traces = await loadIterationTraces(workspacePath, iteration);
6365
6527
  const { loadProposerMemory: loadProposerMemory2, formatMemoryForProposer: formatMemoryForProposer2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
@@ -6373,7 +6535,7 @@ async function propose(iteration, workspacePath, harnessPath, history, tasks, co
6373
6535
  } catch {
6374
6536
  }
6375
6537
  const combinedMemory = memorySection + (knowledgeSection ? "\n" + knowledgeSection : "");
6376
- const userMessage = buildProposerUserMessage(harnessFiles, traces, tasks, history, combinedMemory || void 0);
6538
+ const userMessage = buildProposerUserMessage(harnessFiles, traces, tasks, history, combinedMemory || void 0, projectContext);
6377
6539
  const proposerConfig = { ...config, model: proposerModel };
6378
6540
  const response = await callLLM(proposerConfig, userMessage, {
6379
6541
  systemPrompt: PROPOSER_SYSTEM_PROMPT,
@@ -6460,8 +6622,8 @@ Return ONLY valid JSON.`;
6460
6622
  });
6461
6623
 
6462
6624
  // src/ir/parser.ts
6463
- import fs26 from "fs/promises";
6464
- import path26 from "path";
6625
+ import fs27 from "fs/promises";
6626
+ import path27 from "path";
6465
6627
  function slugify(heading) {
6466
6628
  return heading.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
6467
6629
  }
@@ -6723,33 +6885,33 @@ function parseMcpConfig(content) {
6723
6885
  }
6724
6886
  async function readDirSafe(dirPath) {
6725
6887
  try {
6726
- return await fs26.readdir(dirPath);
6888
+ return await fs27.readdir(dirPath);
6727
6889
  } catch {
6728
6890
  return [];
6729
6891
  }
6730
6892
  }
6731
6893
  async function readFileSafe2(filePath) {
6732
6894
  try {
6733
- return await fs26.readFile(filePath, "utf-8");
6895
+ return await fs27.readFile(filePath, "utf-8");
6734
6896
  } catch {
6735
6897
  return null;
6736
6898
  }
6737
6899
  }
6738
6900
  async function isDirectory(filePath) {
6739
6901
  try {
6740
- const stat = await fs26.stat(filePath);
6902
+ const stat = await fs27.stat(filePath);
6741
6903
  return stat.isDirectory();
6742
6904
  } catch {
6743
6905
  return false;
6744
6906
  }
6745
6907
  }
6746
6908
  async function parseCommands(harnessPath) {
6747
- const dirPath = path26.join(harnessPath, "commands");
6909
+ const dirPath = path27.join(harnessPath, "commands");
6748
6910
  const entries = await readDirSafe(dirPath);
6749
6911
  const nodes = [];
6750
6912
  for (const entry of entries) {
6751
6913
  if (!entry.endsWith(".md")) continue;
6752
- const filePath = path26.join(dirPath, entry);
6914
+ const filePath = path27.join(dirPath, entry);
6753
6915
  const content = await readFileSafe2(filePath);
6754
6916
  if (content === null) continue;
6755
6917
  const name = entry.replace(/\.md$/, "");
@@ -6760,12 +6922,12 @@ async function parseCommands(harnessPath) {
6760
6922
  return nodes;
6761
6923
  }
6762
6924
  async function parseRules(harnessPath) {
6763
- const dirPath = path26.join(harnessPath, "rules");
6925
+ const dirPath = path27.join(harnessPath, "rules");
6764
6926
  const entries = await readDirSafe(dirPath);
6765
6927
  const nodes = [];
6766
6928
  for (const entry of entries) {
6767
6929
  if (!entry.endsWith(".md")) continue;
6768
- const filePath = path26.join(dirPath, entry);
6930
+ const filePath = path27.join(dirPath, entry);
6769
6931
  const rawContent = await readFileSafe2(filePath);
6770
6932
  if (rawContent === null) continue;
6771
6933
  const name = entry.replace(/\.md$/, "");
@@ -6780,12 +6942,12 @@ async function parseRules(harnessPath) {
6780
6942
  return nodes;
6781
6943
  }
6782
6944
  async function parseAgents(harnessPath) {
6783
- const dirPath = path26.join(harnessPath, "agents");
6945
+ const dirPath = path27.join(harnessPath, "agents");
6784
6946
  const entries = await readDirSafe(dirPath);
6785
6947
  const nodes = [];
6786
6948
  for (const entry of entries) {
6787
6949
  if (!entry.endsWith(".md")) continue;
6788
- const filePath = path26.join(dirPath, entry);
6950
+ const filePath = path27.join(dirPath, entry);
6789
6951
  const rawContent = await readFileSafe2(filePath);
6790
6952
  if (rawContent === null) continue;
6791
6953
  const fileBaseName = entry.replace(/\.md$/, "");
@@ -6829,20 +6991,20 @@ async function parseAgents(harnessPath) {
6829
6991
  return nodes;
6830
6992
  }
6831
6993
  async function parseSkills(harnessPath) {
6832
- const dirPath = path26.join(harnessPath, "skills");
6994
+ const dirPath = path27.join(harnessPath, "skills");
6833
6995
  const entries = await readDirSafe(dirPath);
6834
6996
  const nodes = [];
6835
6997
  for (const entry of entries) {
6836
- const entryPath = path26.join(dirPath, entry);
6998
+ const entryPath = path27.join(dirPath, entry);
6837
6999
  if (entry.endsWith(".md")) {
6838
7000
  const content = await readFileSafe2(entryPath);
6839
7001
  if (content === null) continue;
6840
7002
  const name = entry.replace(/\.md$/, "");
6841
7003
  nodes.push({ name, content });
6842
7004
  } else if (await isDirectory(entryPath)) {
6843
- let content = await readFileSafe2(path26.join(entryPath, "skill.md"));
7005
+ let content = await readFileSafe2(path27.join(entryPath, "skill.md"));
6844
7006
  if (content === null) {
6845
- content = await readFileSafe2(path26.join(entryPath, "SKILL.md"));
7007
+ content = await readFileSafe2(path27.join(entryPath, "SKILL.md"));
6846
7008
  }
6847
7009
  if (content === null) continue;
6848
7010
  nodes.push({ name: entry, content });
@@ -6851,12 +7013,12 @@ async function parseSkills(harnessPath) {
6851
7013
  return nodes;
6852
7014
  }
6853
7015
  async function parseDocs(harnessPath) {
6854
- const dirPath = path26.join(harnessPath, "docs");
7016
+ const dirPath = path27.join(harnessPath, "docs");
6855
7017
  const entries = await readDirSafe(dirPath);
6856
7018
  const nodes = [];
6857
7019
  for (const entry of entries) {
6858
7020
  if (!entry.endsWith(".md")) continue;
6859
- const filePath = path26.join(dirPath, entry);
7021
+ const filePath = path27.join(dirPath, entry);
6860
7022
  const content = await readFileSafe2(filePath);
6861
7023
  if (content === null) continue;
6862
7024
  const name = entry.replace(/\.md$/, "");
@@ -6865,12 +7027,12 @@ async function parseDocs(harnessPath) {
6865
7027
  return nodes;
6866
7028
  }
6867
7029
  async function parseHooks(harnessPath) {
6868
- const dirPath = path26.join(harnessPath, "hooks");
7030
+ const dirPath = path27.join(harnessPath, "hooks");
6869
7031
  const entries = await readDirSafe(dirPath);
6870
7032
  const nodes = [];
6871
7033
  for (const entry of entries) {
6872
7034
  if (!entry.endsWith(".mjs")) continue;
6873
- const filePath = path26.join(dirPath, entry);
7035
+ const filePath = path27.join(dirPath, entry);
6874
7036
  const content = await readFileSafe2(filePath);
6875
7037
  if (content === null) continue;
6876
7038
  const name = entry.replace(/\.mjs$/, "");
@@ -6881,7 +7043,7 @@ async function parseHooks(harnessPath) {
6881
7043
  async function parseHarness(harnessPath) {
6882
7044
  const ir = createEmptyIR();
6883
7045
  const claudeMdContent = await readFileSafe2(
6884
- path26.join(harnessPath, "CLAUDE.md")
7046
+ path27.join(harnessPath, "CLAUDE.md")
6885
7047
  );
6886
7048
  if (claudeMdContent !== null) {
6887
7049
  const { meta, sections } = parseClaudeMd(claudeMdContent);
@@ -6893,7 +7055,7 @@ async function parseHarness(harnessPath) {
6893
7055
  ir.sections = sections;
6894
7056
  }
6895
7057
  const settingsContent = await readFileSafe2(
6896
- path26.join(harnessPath, "settings.json")
7058
+ path27.join(harnessPath, "settings.json")
6897
7059
  );
6898
7060
  if (settingsContent !== null) {
6899
7061
  ir.settings = parseSettings(settingsContent);
@@ -6914,7 +7076,7 @@ async function parseHarness(harnessPath) {
6914
7076
  ir.hooks = hooks;
6915
7077
  const mcpServers = [];
6916
7078
  const seenIds = /* @__PURE__ */ new Set();
6917
- const parentMcpPath = path26.join(path26.dirname(harnessPath), ".mcp.json");
7079
+ const parentMcpPath = path27.join(path27.dirname(harnessPath), ".mcp.json");
6918
7080
  const parentMcpContent = await readFileSafe2(parentMcpPath);
6919
7081
  if (parentMcpContent !== null) {
6920
7082
  for (const node of parseMcpConfig(parentMcpContent)) {
@@ -6924,7 +7086,7 @@ async function parseHarness(harnessPath) {
6924
7086
  }
6925
7087
  }
6926
7088
  }
6927
- const innerMcpPath = path26.join(harnessPath, ".mcp.json");
7089
+ const innerMcpPath = path27.join(harnessPath, ".mcp.json");
6928
7090
  const innerMcpContent = await readFileSafe2(innerMcpPath);
6929
7091
  if (innerMcpContent !== null) {
6930
7092
  for (const node of parseMcpConfig(innerMcpContent)) {
@@ -7161,8 +7323,8 @@ function deepSet(obj, segments, value) {
7161
7323
  const child = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
7162
7324
  return { ...obj, [head]: deepSet(child, rest, value) };
7163
7325
  }
7164
- function applySettingsUpdate(settings, path37, value) {
7165
- const segments = path37.split(".");
7326
+ function applySettingsUpdate(settings, path39, value) {
7327
+ const segments = path39.split(".");
7166
7328
  const topKey = segments[0];
7167
7329
  if (STRUCTURED_SETTINGS_KEYS.has(topKey)) {
7168
7330
  const settingsRecord = { ...settings };
@@ -7620,10 +7782,10 @@ var init_diff = __esm({
7620
7782
  });
7621
7783
 
7622
7784
  // src/evolve/mutator.ts
7623
- import fs27 from "fs/promises";
7624
- import path27 from "path";
7785
+ import fs28 from "fs/promises";
7786
+ import path28 from "path";
7625
7787
  async function applyMutations(currentHarnessPath, nextIterationDir, mutations) {
7626
- const newHarnessPath = path27.join(nextIterationDir, "harness");
7788
+ const newHarnessPath = path28.join(nextIterationDir, "harness");
7627
7789
  let baselineIR = null;
7628
7790
  try {
7629
7791
  baselineIR = await parseHarness(currentHarnessPath);
@@ -7664,6 +7826,11 @@ async function applyMutationsViaIR(currentHarnessPath, newHarnessPath, mutations
7664
7826
  for (const mutation of rawTextMutations) {
7665
7827
  await applyLegacyMutation(newHarnessPath, mutation);
7666
7828
  }
7829
+ await fs28.writeFile(
7830
+ path28.join(newHarnessPath, "..", "harness-ir.json"),
7831
+ JSON.stringify(currentIR, null, 2),
7832
+ "utf-8"
7833
+ );
7667
7834
  const irDiff = diffIR(baselineIR, currentIR);
7668
7835
  let diffPatch = formatIRDiff(irDiff);
7669
7836
  if (diffPatch === "No changes." && rawTextMutations.length > 0) {
@@ -7700,9 +7867,9 @@ async function renderAffectedFiles(ir, targetDir, touchedCategories) {
7700
7867
  for (const [relativePath, content] of fileMap) {
7701
7868
  const category = getFileCategory(relativePath);
7702
7869
  if (touchedCategories.has(category)) {
7703
- const fullPath = path27.join(targetDir, relativePath);
7704
- await fs27.mkdir(path27.dirname(fullPath), { recursive: true });
7705
- await fs27.writeFile(fullPath, content, "utf-8");
7870
+ const fullPath = path28.join(targetDir, relativePath);
7871
+ await fs28.mkdir(path28.dirname(fullPath), { recursive: true });
7872
+ await fs28.writeFile(fullPath, content, "utf-8");
7706
7873
  }
7707
7874
  }
7708
7875
  if (touchedCategories.has("commands")) {
@@ -7715,11 +7882,11 @@ async function renderAffectedFiles(ir, targetDir, touchedCategories) {
7715
7882
  await deleteOrphanedFiles(targetDir, "agents", fileMap);
7716
7883
  }
7717
7884
  if (touchedCategories.has("mcp") && !fileMap.has(".mcp.json")) {
7718
- await fs27.unlink(path27.join(targetDir, ".mcp.json")).catch(() => {
7885
+ await fs28.unlink(path28.join(targetDir, ".mcp.json")).catch(() => {
7719
7886
  });
7720
7887
  }
7721
7888
  if (touchedCategories.has("settings") && !fileMap.has("settings.json")) {
7722
- await fs27.unlink(path27.join(targetDir, "settings.json")).catch(() => {
7889
+ await fs28.unlink(path28.join(targetDir, "settings.json")).catch(() => {
7723
7890
  });
7724
7891
  }
7725
7892
  }
@@ -7736,17 +7903,17 @@ function getFileCategory(relativePath) {
7736
7903
  return "unknown";
7737
7904
  }
7738
7905
  async function deleteOrphanedFiles(targetDir, subdir, renderedMap) {
7739
- const subdirPath = path27.join(targetDir, subdir);
7906
+ const subdirPath = path28.join(targetDir, subdir);
7740
7907
  let entries;
7741
7908
  try {
7742
- entries = await fs27.readdir(subdirPath);
7909
+ entries = await fs28.readdir(subdirPath);
7743
7910
  } catch {
7744
7911
  return;
7745
7912
  }
7746
7913
  for (const entry of entries) {
7747
7914
  const relativePath = `${subdir}/${entry}`;
7748
7915
  if (!renderedMap.has(relativePath)) {
7749
- await fs27.unlink(path27.join(subdirPath, entry)).catch(() => {
7916
+ await fs28.unlink(path28.join(subdirPath, entry)).catch(() => {
7750
7917
  });
7751
7918
  }
7752
7919
  }
@@ -7763,56 +7930,56 @@ async function applyLegacyMutation(harnessPath, mutation) {
7763
7930
  if (mutation.file.includes("..")) {
7764
7931
  return;
7765
7932
  }
7766
- const filePath = path27.join(harnessPath, mutation.file);
7933
+ const filePath = path28.join(harnessPath, mutation.file);
7767
7934
  if (mutation.action === "replace") {
7768
7935
  if (!mutation.oldText) {
7769
7936
  return;
7770
7937
  }
7771
7938
  let content;
7772
7939
  try {
7773
- content = await fs27.readFile(filePath, "utf-8");
7940
+ content = await fs28.readFile(filePath, "utf-8");
7774
7941
  } catch {
7775
7942
  return;
7776
7943
  }
7777
7944
  if (!content.includes(mutation.oldText)) {
7778
7945
  return;
7779
7946
  }
7780
- await fs27.writeFile(
7947
+ await fs28.writeFile(
7781
7948
  filePath,
7782
7949
  content.replace(mutation.oldText, mutation.newText),
7783
7950
  "utf-8"
7784
7951
  );
7785
7952
  } else if (mutation.action === "add_section") {
7786
7953
  try {
7787
- const content = await fs27.readFile(filePath, "utf-8");
7788
- await fs27.writeFile(
7954
+ const content = await fs28.readFile(filePath, "utf-8");
7955
+ await fs28.writeFile(
7789
7956
  filePath,
7790
7957
  content + "\n\n" + mutation.newText,
7791
7958
  "utf-8"
7792
7959
  );
7793
7960
  } catch {
7794
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
7795
- await fs27.writeFile(filePath, mutation.newText, "utf-8");
7961
+ await fs28.mkdir(path28.dirname(filePath), { recursive: true });
7962
+ await fs28.writeFile(filePath, mutation.newText, "utf-8");
7796
7963
  }
7797
7964
  } else if (mutation.action === "create_file") {
7798
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
7799
- await fs27.writeFile(filePath, mutation.newText, "utf-8");
7965
+ await fs28.mkdir(path28.dirname(filePath), { recursive: true });
7966
+ await fs28.writeFile(filePath, mutation.newText, "utf-8");
7800
7967
  } else if (mutation.action === "delete_section") {
7801
7968
  if (!mutation.oldText) {
7802
7969
  return;
7803
7970
  }
7804
7971
  let sectionContent;
7805
7972
  try {
7806
- sectionContent = await fs27.readFile(filePath, "utf-8");
7973
+ sectionContent = await fs28.readFile(filePath, "utf-8");
7807
7974
  } catch {
7808
7975
  return;
7809
7976
  }
7810
7977
  if (!sectionContent.includes(mutation.oldText)) {
7811
7978
  return;
7812
7979
  }
7813
- await fs27.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
7980
+ await fs28.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
7814
7981
  } else if (mutation.action === "delete_file") {
7815
- await fs27.unlink(filePath).catch(() => {
7982
+ await fs28.unlink(filePath).catch(() => {
7816
7983
  });
7817
7984
  }
7818
7985
  }
@@ -7880,17 +8047,17 @@ async function readAllFiles(dir) {
7880
8047
  async function walk(current) {
7881
8048
  let entries;
7882
8049
  try {
7883
- entries = await fs27.readdir(current, { withFileTypes: true });
8050
+ entries = await fs28.readdir(current, { withFileTypes: true });
7884
8051
  } catch {
7885
8052
  return;
7886
8053
  }
7887
8054
  for (const entry of entries) {
7888
- const fullPath = path27.join(current, entry.name);
7889
- const relativePath = path27.relative(dir, fullPath);
8055
+ const fullPath = path28.join(current, entry.name);
8056
+ const relativePath = path28.relative(dir, fullPath);
7890
8057
  if (entry.isDirectory()) {
7891
8058
  await walk(fullPath);
7892
8059
  } else {
7893
- result[relativePath] = await fs27.readFile(fullPath, "utf-8");
8060
+ result[relativePath] = await fs28.readFile(fullPath, "utf-8");
7894
8061
  }
7895
8062
  }
7896
8063
  }
@@ -8030,7 +8197,7 @@ ${taskScores}
8030
8197
  }
8031
8198
  return "## Iteration History\n\n(History omitted to fit context budget)\n";
8032
8199
  }
8033
- function buildArchitectUserMessage(harnessFiles, traces, tasks, history, knowledgeContext) {
8200
+ function buildArchitectUserMessage(harnessFiles, traces, tasks, history, knowledgeContext, projectContext) {
8034
8201
  const harnessSection = ["## Current Harness Files\n"];
8035
8202
  const fileEntries = Object.entries(harnessFiles);
8036
8203
  if (fileEntries.length === 0) {
@@ -8059,11 +8226,28 @@ ${content}
8059
8226
  }
8060
8227
  const summarySection = buildEvolutionSummary(history);
8061
8228
  const workingSection = buildWhatsWorking(history, tasks);
8229
+ let projectSection = "";
8230
+ if (projectContext) {
8231
+ const parts = ["## Project Understanding\n"];
8232
+ parts.push("### Analysis Summary");
8233
+ parts.push(formatAnalysisForProposer(projectContext.analysis));
8234
+ parts.push("");
8235
+ parts.push("### Harness Structure");
8236
+ parts.push(projectContext.irSummary);
8237
+ if (projectContext.keySourceFiles) {
8238
+ parts.push("");
8239
+ parts.push("### Key Source Files");
8240
+ parts.push("```");
8241
+ parts.push(projectContext.keySourceFiles);
8242
+ parts.push("```");
8243
+ }
8244
+ projectSection = "\n" + parts.join("\n") + "\n";
8245
+ }
8062
8246
  const knowledgeSection = knowledgeContext ? `## Knowledge Base (patterns from other projects)
8063
8247
 
8064
8248
  ${knowledgeContext}
8065
8249
  ` : "";
8066
- const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + "\n" + summarySection + (summarySection ? "\n" : "") + workingSection + (workingSection ? "\n" : "") + knowledgeSection;
8250
+ const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + "\n" + summarySection + (summarySection ? "\n" : "") + workingSection + (workingSection ? "\n" : "") + projectSection + knowledgeSection;
8067
8251
  const remainingBudget = MAX_CONTEXT_CHARS2 - fixedContent.length;
8068
8252
  if (remainingBudget <= 0) {
8069
8253
  return fixedContent + "\n\n[...traces and history omitted \u2014 context budget exceeded...]";
@@ -8074,7 +8258,7 @@ ${knowledgeContext}
8074
8258
  const historySection = buildHistorySection2(history, historyBudget);
8075
8259
  return fixedContent + "\n" + traceSection + "\n" + historySection;
8076
8260
  }
8077
- async function proposeArchitecture(iteration, workspacePath, harnessPath, history, tasks, config, architectModel, knowledgeContext) {
8261
+ async function proposeArchitecture(iteration, workspacePath, harnessPath, history, tasks, config, architectModel, knowledgeContext, projectContext) {
8078
8262
  const harnessFiles = await readHarnessFiles(harnessPath);
8079
8263
  const traces = await loadIterationTraces(workspacePath, iteration);
8080
8264
  let effectiveKnowledge = knowledgeContext;
@@ -8091,7 +8275,8 @@ async function proposeArchitecture(iteration, workspacePath, harnessPath, histor
8091
8275
  traces,
8092
8276
  tasks,
8093
8277
  history,
8094
- effectiveKnowledge
8278
+ effectiveKnowledge,
8279
+ projectContext
8095
8280
  );
8096
8281
  const architectConfig = { ...config, model: architectModel };
8097
8282
  const response = await callLLM(architectConfig, userMessage, {
@@ -8206,8 +8391,8 @@ var init_schedule = __esm({
8206
8391
  });
8207
8392
 
8208
8393
  // src/evolve/sampling.ts
8209
- import fs28 from "fs/promises";
8210
- import path28 from "path";
8394
+ import fs29 from "fs/promises";
8395
+ import path29 from "path";
8211
8396
  function initBeliefs(tasks) {
8212
8397
  return tasks.map((task) => ({
8213
8398
  taskId: task.id,
@@ -8268,9 +8453,9 @@ function updateBeliefs(beliefs, results) {
8268
8453
  });
8269
8454
  }
8270
8455
  async function loadBeliefs(workspacePath) {
8271
- const beliefsPath = path28.join(workspacePath, "task-beliefs.json");
8456
+ const beliefsPath = path29.join(workspacePath, "task-beliefs.json");
8272
8457
  try {
8273
- const content = await fs28.readFile(beliefsPath, "utf-8");
8458
+ const content = await fs29.readFile(beliefsPath, "utf-8");
8274
8459
  const parsed = JSON.parse(content);
8275
8460
  if (!Array.isArray(parsed)) return null;
8276
8461
  for (const entry of parsed) {
@@ -8284,9 +8469,9 @@ async function loadBeliefs(workspacePath) {
8284
8469
  }
8285
8470
  }
8286
8471
  async function saveBeliefs(workspacePath, beliefs) {
8287
- const beliefsPath = path28.join(workspacePath, "task-beliefs.json");
8288
- await fs28.mkdir(path28.dirname(beliefsPath), { recursive: true });
8289
- await fs28.writeFile(beliefsPath, JSON.stringify(beliefs, null, 2), "utf-8");
8472
+ const beliefsPath = path29.join(workspacePath, "task-beliefs.json");
8473
+ await fs29.mkdir(path29.dirname(beliefsPath), { recursive: true });
8474
+ await fs29.writeFile(beliefsPath, JSON.stringify(beliefs, null, 2), "utf-8");
8290
8475
  }
8291
8476
  var init_sampling = __esm({
8292
8477
  "src/evolve/sampling.ts"() {
@@ -8295,8 +8480,8 @@ var init_sampling = __esm({
8295
8480
  });
8296
8481
 
8297
8482
  // src/evolve/regularization.ts
8298
- import fs29 from "fs/promises";
8299
- import path29 from "path";
8483
+ import fs30 from "fs/promises";
8484
+ import path30 from "path";
8300
8485
  async function measureComplexity(harnessPath) {
8301
8486
  let totalLines = 0;
8302
8487
  let totalFiles = 0;
@@ -8430,18 +8615,18 @@ async function readAllFilesRecursive(dir) {
8430
8615
  async function walk(current) {
8431
8616
  let entries;
8432
8617
  try {
8433
- entries = await fs29.readdir(current, { withFileTypes: true });
8618
+ entries = await fs30.readdir(current, { withFileTypes: true });
8434
8619
  } catch {
8435
8620
  return;
8436
8621
  }
8437
8622
  for (const entry of entries) {
8438
- const fullPath = path29.join(current, entry.name);
8439
- const relativePath = path29.relative(dir, fullPath);
8623
+ const fullPath = path30.join(current, entry.name);
8624
+ const relativePath = path30.relative(dir, fullPath);
8440
8625
  if (entry.isDirectory()) {
8441
8626
  await walk(fullPath);
8442
8627
  } else {
8443
8628
  try {
8444
- result[relativePath] = await fs29.readFile(fullPath, "utf-8");
8629
+ result[relativePath] = await fs30.readFile(fullPath, "utf-8");
8445
8630
  } catch {
8446
8631
  }
8447
8632
  }
@@ -8538,14 +8723,56 @@ var init_targeting = __esm({
8538
8723
  "test-writing": ["verification", "commands"],
8539
8724
  "config-change": ["settings", "mcp"],
8540
8725
  "documentation": ["general"],
8541
- "persistence-completion": ["commands", "verification"]
8726
+ "persistence-completion": ["commands", "verification"],
8727
+ "real-bug-fix": ["general"],
8728
+ "real-feature-add": ["general"],
8729
+ "codebase-question": ["general"]
8542
8730
  };
8543
8731
  }
8544
8732
  });
8545
8733
 
8546
8734
  // src/evolve/loop.ts
8547
- import fs30 from "fs/promises";
8548
- import path30 from "path";
8735
+ import fs31 from "fs/promises";
8736
+ import path31 from "path";
8737
+ async function loadProjectContext(projectDir) {
8738
+ try {
8739
+ const analysisPath = path31.join(projectDir, CACHE_FILENAME);
8740
+ const analysisRaw = await fs31.readFile(analysisPath, "utf-8");
8741
+ const analysisCache = JSON.parse(analysisRaw);
8742
+ const analysis = analysisCache.analysis;
8743
+ let keySourceFiles = "";
8744
+ try {
8745
+ const packedPath = path31.join(projectDir, PACKED_SOURCE_FILENAME);
8746
+ const packedSource = await fs31.readFile(packedPath, "utf-8");
8747
+ keySourceFiles = packedSource.slice(0, KEY_SOURCE_CHARS_LIMIT);
8748
+ } catch {
8749
+ }
8750
+ return { analysis, keySourceFiles };
8751
+ } catch {
8752
+ return null;
8753
+ }
8754
+ }
8755
+ async function buildProjectContext(projectData, harnessPath) {
8756
+ let irSummary = "";
8757
+ try {
8758
+ const irPath = path31.join(harnessPath, "..", "harness-ir.json");
8759
+ const irRaw = await fs31.readFile(irPath, "utf-8");
8760
+ const ir = JSON.parse(irRaw);
8761
+ irSummary = buildIRSummary(ir);
8762
+ } catch {
8763
+ try {
8764
+ const ir = await parseHarness(harnessPath);
8765
+ irSummary = buildIRSummary(ir);
8766
+ } catch {
8767
+ irSummary = "(IR not available)";
8768
+ }
8769
+ }
8770
+ return {
8771
+ analysis: projectData.analysis,
8772
+ irSummary,
8773
+ keySourceFiles: projectData.keySourceFiles || void 0
8774
+ };
8775
+ }
8549
8776
  function computeMutationCap(iter, maxIterations, maxMutations) {
8550
8777
  if (maxIterations <= 1) return maxMutations;
8551
8778
  const progress = iter / (maxIterations - 1);
@@ -8564,7 +8791,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8564
8791
  let baselineComplexity = null;
8565
8792
  let baselineIR = null;
8566
8793
  if (useKL) {
8567
- const baselineHarness = path30.join(workspacePath, "iterations", "0", "harness");
8794
+ const baselineHarness = path31.join(workspacePath, "iterations", "0", "harness");
8568
8795
  try {
8569
8796
  baselineIR = await parseHarness(baselineHarness);
8570
8797
  baselineComplexity = measureComplexityFromIR(baselineIR);
@@ -8576,20 +8803,22 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8576
8803
  }
8577
8804
  }
8578
8805
  let lastChangedAspects = null;
8806
+ const projectDir = path31.resolve(workspacePath, "..");
8807
+ const projectData = await loadProjectContext(projectDir);
8579
8808
  let rngState = evolveConfig.rngSeed ?? 42;
8580
8809
  const rng = () => {
8581
8810
  rngState = rngState * 1664525 + 1013904223 & 4294967295;
8582
8811
  return (rngState >>> 0) / 4294967296;
8583
8812
  };
8584
8813
  for (let iter = 0; iter < evolveConfig.maxIterations; iter++) {
8585
- const harnessPath = path30.join(
8814
+ const harnessPath = path31.join(
8586
8815
  workspacePath,
8587
8816
  "iterations",
8588
8817
  iter.toString(),
8589
8818
  "harness"
8590
8819
  );
8591
8820
  try {
8592
- await fs30.access(harnessPath);
8821
+ await fs31.access(harnessPath);
8593
8822
  } catch {
8594
8823
  if (iter === 0) {
8595
8824
  throw new Error(
@@ -8698,7 +8927,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8698
8927
  }
8699
8928
  const diffRatio = await computeDiffRatio(
8700
8929
  harnessPath,
8701
- path30.join(workspacePath, "iterations", "0", "harness")
8930
+ path31.join(workspacePath, "iterations", "0", "harness")
8702
8931
  );
8703
8932
  currentComplexity.diffFromBaseline = diffRatio;
8704
8933
  iterComplexityCost = computeComplexityCost(currentComplexity, baselineComplexity);
@@ -8767,7 +8996,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8767
8996
  };
8768
8997
  await writeIterationLog(workspacePath, rollbackLog);
8769
8998
  history.push(rollbackLog);
8770
- const bestHarnessPath = path30.join(
8999
+ const bestHarnessPath = path31.join(
8771
9000
  workspacePath,
8772
9001
  "iterations",
8773
9002
  bestIteration.toString(),
@@ -8776,6 +9005,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8776
9005
  if (iter + 1 < evolveConfig.maxIterations) {
8777
9006
  onProgress?.({ type: "proposing", iteration: iter, message: "Proposing new mutations after rollback" });
8778
9007
  try {
9008
+ const rollbackProjectCtx = projectData ? await buildProjectContext(projectData, bestHarnessPath) : void 0;
8779
9009
  let rollbackProposal = await propose(
8780
9010
  iter,
8781
9011
  workspacePath,
@@ -8783,7 +9013,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8783
9013
  history,
8784
9014
  tasks,
8785
9015
  kairnConfig,
8786
- evolveConfig.proposerModel
9016
+ evolveConfig.proposerModel,
9017
+ rollbackProjectCtx
8787
9018
  );
8788
9019
  const rollbackCap = computeMutationCap(iter, evolveConfig.maxIterations, evolveConfig.maxMutationsPerIteration);
8789
9020
  if (rollbackProposal.mutations.length > rollbackCap) {
@@ -8792,7 +9023,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8792
9023
  mutations: rollbackProposal.mutations.slice(0, rollbackCap)
8793
9024
  };
8794
9025
  }
8795
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
9026
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
8796
9027
  await applyMutations(bestHarnessPath, nextIterDir2, rollbackProposal.mutations);
8797
9028
  try {
8798
9029
  const rollbackIR = await parseHarness(bestHarnessPath);
@@ -8807,8 +9038,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8807
9038
  mutationCount: rollbackProposal.mutations.length
8808
9039
  });
8809
9040
  } catch {
8810
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8811
- await copyDir(bestHarnessPath, path30.join(nextIterDir2, "harness"));
9041
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9042
+ await copyDir(bestHarnessPath, path31.join(nextIterDir2, "harness"));
8812
9043
  }
8813
9044
  }
8814
9045
  continue;
@@ -8859,6 +9090,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8859
9090
  onProgress?.({ type: "architect-start", iteration: iter });
8860
9091
  let architectProposal;
8861
9092
  try {
9093
+ const architectProjectCtx = projectData ? await buildProjectContext(projectData, harnessPath) : void 0;
8862
9094
  architectProposal = await proposeArchitecture(
8863
9095
  iter,
8864
9096
  workspacePath,
@@ -8866,7 +9098,9 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8866
9098
  history,
8867
9099
  tasks,
8868
9100
  kairnConfig,
8869
- evolveConfig.architectModel
9101
+ evolveConfig.architectModel,
9102
+ void 0,
9103
+ architectProjectCtx
8870
9104
  );
8871
9105
  const architectCap = computeArchitectMutationBudget(iter, evolveConfig.maxIterations);
8872
9106
  if (architectProposal.mutations.length > architectCap) {
@@ -8882,8 +9116,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8882
9116
  iteration: iter,
8883
9117
  message: `Architect failed: ${errMsg}. Falling back to reactive proposer.`
8884
9118
  });
8885
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8886
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9119
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9120
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8887
9121
  const skipLog = {
8888
9122
  iteration: iter,
8889
9123
  score: aggregate,
@@ -8900,7 +9134,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8900
9134
  continue;
8901
9135
  }
8902
9136
  onProgress?.({ type: "architect-staging", iteration: iter });
8903
- const stagingDir = path30.join(workspacePath, "staging", iter.toString());
9137
+ const stagingDir = path31.join(workspacePath, "staging", iter.toString());
8904
9138
  let stagingHarnessPath;
8905
9139
  try {
8906
9140
  const stagingResult = await applyMutations(
@@ -8911,8 +9145,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8911
9145
  stagingHarnessPath = stagingResult.newHarnessPath;
8912
9146
  } catch {
8913
9147
  onProgress?.({ type: "architect-rejected", iteration: iter, score: 0, message: "Failed to apply mutations to staging" });
8914
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8915
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9148
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9149
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8916
9150
  const rejectLog = {
8917
9151
  iteration: iter,
8918
9152
  score: aggregate,
@@ -8926,7 +9160,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8926
9160
  };
8927
9161
  await writeIterationLog(workspacePath, rejectLog);
8928
9162
  history.push(rejectLog);
8929
- await fs30.rm(stagingDir, { recursive: true, force: true }).catch(() => {
9163
+ await fs31.rm(stagingDir, { recursive: true, force: true }).catch(() => {
8930
9164
  });
8931
9165
  continue;
8932
9166
  }
@@ -8943,12 +9177,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8943
9177
  );
8944
9178
  if (stagingScore >= bestScore) {
8945
9179
  onProgress?.({ type: "architect-accepted", iteration: iter, score: stagingScore });
8946
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8947
- await copyDir(stagingHarnessPath, path30.join(nextIterDir2, "harness"));
9180
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9181
+ await copyDir(stagingHarnessPath, path31.join(nextIterDir2, "harness"));
8948
9182
  } else {
8949
9183
  onProgress?.({ type: "architect-rejected", iteration: iter, score: stagingScore });
8950
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8951
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9184
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9185
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8952
9186
  }
8953
9187
  const architectLog = {
8954
9188
  iteration: iter,
@@ -8963,13 +9197,14 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8963
9197
  };
8964
9198
  await writeIterationLog(workspacePath, architectLog);
8965
9199
  history.push(architectLog);
8966
- await fs30.rm(stagingDir, { recursive: true, force: true }).catch(() => {
9200
+ await fs31.rm(stagingDir, { recursive: true, force: true }).catch(() => {
8967
9201
  });
8968
9202
  continue;
8969
9203
  }
8970
9204
  onProgress?.({ type: "proposing", iteration: iter });
8971
9205
  let proposal;
8972
9206
  try {
9207
+ const iterProjectCtx = projectData ? await buildProjectContext(projectData, harnessPath) : void 0;
8973
9208
  proposal = await propose(
8974
9209
  iter,
8975
9210
  workspacePath,
@@ -8977,7 +9212,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8977
9212
  history,
8978
9213
  tasks,
8979
9214
  kairnConfig,
8980
- evolveConfig.proposerModel
9215
+ evolveConfig.proposerModel,
9216
+ iterProjectCtx
8981
9217
  );
8982
9218
  const iterCap = computeMutationCap(iter, evolveConfig.maxIterations, evolveConfig.maxMutationsPerIteration);
8983
9219
  if (proposal.mutations.length > iterCap) {
@@ -8993,12 +9229,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8993
9229
  iteration: iter,
8994
9230
  message: `Proposer failed: ${errMsg}`
8995
9231
  });
8996
- const nextIterDir2 = path30.join(
9232
+ const nextIterDir2 = path31.join(
8997
9233
  workspacePath,
8998
9234
  "iterations",
8999
9235
  (iter + 1).toString()
9000
9236
  );
9001
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9237
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
9002
9238
  const skipLog = {
9003
9239
  iteration: iter,
9004
9240
  score: aggregate,
@@ -9014,7 +9250,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9014
9250
  history.push(skipLog);
9015
9251
  continue;
9016
9252
  }
9017
- const nextIterDir = path30.join(
9253
+ const nextIterDir = path31.join(
9018
9254
  workspacePath,
9019
9255
  "iterations",
9020
9256
  (iter + 1).toString()
@@ -9035,7 +9271,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9035
9271
  lastChangedAspects = null;
9036
9272
  }
9037
9273
  } catch {
9038
- await copyDir(harnessPath, path30.join(nextIterDir, "harness"));
9274
+ await copyDir(harnessPath, path31.join(nextIterDir, "harness"));
9039
9275
  lastChangedAspects = null;
9040
9276
  }
9041
9277
  onProgress?.({
@@ -9059,8 +9295,9 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9059
9295
  }
9060
9296
  if (evolveConfig.usePrincipal && history.length >= 2) {
9061
9297
  onProgress?.({ type: "proposing", iteration: history.length, message: "Principal Proposer synthesizing final harness" });
9062
- const baselineHarnessPath = path30.join(workspacePath, "iterations", "0", "harness");
9298
+ const baselineHarnessPath = path31.join(workspacePath, "iterations", "0", "harness");
9063
9299
  try {
9300
+ const principalProjectCtx = projectData ? await buildProjectContext(projectData, baselineHarnessPath) : void 0;
9064
9301
  let principalProposal = await propose(
9065
9302
  history.length,
9066
9303
  workspacePath,
@@ -9068,7 +9305,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9068
9305
  history,
9069
9306
  tasks,
9070
9307
  kairnConfig,
9071
- evolveConfig.proposerModel
9308
+ evolveConfig.proposerModel,
9309
+ principalProjectCtx
9072
9310
  );
9073
9311
  if (principalProposal.mutations.length > evolveConfig.maxMutationsPerIteration) {
9074
9312
  principalProposal = {
@@ -9077,7 +9315,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9077
9315
  };
9078
9316
  }
9079
9317
  const principalIterNum = history.length;
9080
- const principalIterDir = path30.join(workspacePath, "iterations", principalIterNum.toString());
9318
+ const principalIterDir = path31.join(workspacePath, "iterations", principalIterNum.toString());
9081
9319
  const mutResult = await applyMutations(baselineHarnessPath, principalIterDir, principalProposal.mutations);
9082
9320
  onProgress?.({ type: "iteration-start", iteration: principalIterNum });
9083
9321
  const { results: principalResults, aggregate: principalAggregate } = await evaluateAll(
@@ -9118,7 +9356,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9118
9356
  }
9119
9357
  try {
9120
9358
  const { extractAndSavePatterns: extractAndSavePatterns2 } = await Promise.resolve().then(() => (init_knowledge(), knowledge_exports));
9121
- const projectName = path30.basename(path30.resolve(workspacePath, ".."));
9359
+ const projectName = path31.basename(path31.resolve(workspacePath, ".."));
9122
9360
  await extractAndSavePatterns2(history, projectName, null);
9123
9361
  } catch {
9124
9362
  }
@@ -9134,6 +9372,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9134
9372
  baselineScore
9135
9373
  };
9136
9374
  }
9375
+ var KEY_SOURCE_CHARS_LIMIT;
9137
9376
  var init_loop = __esm({
9138
9377
  "src/evolve/loop.ts"() {
9139
9378
  "use strict";
@@ -9149,11 +9388,13 @@ var init_loop = __esm({
9149
9388
  init_parser();
9150
9389
  init_translate();
9151
9390
  init_targeting();
9391
+ init_cache();
9392
+ KEY_SOURCE_CHARS_LIMIT = 1e4;
9152
9393
  }
9153
9394
  });
9154
9395
 
9155
9396
  // src/evolve/synthesis.ts
9156
- import path33 from "path";
9397
+ import path34 from "path";
9157
9398
  function buildMetaPrincipalSystemPrompt(numBranches) {
9158
9399
  return `You are reviewing the COMPLETE results of ${numBranches} independent evolution runs.
9159
9400
  Each branch explored different mutations and saw different task subsets.
@@ -9309,7 +9550,7 @@ async function runSynthesis(context, kairnConfig, evolveConfig, workspacePath) {
9309
9550
  if (mutations.length === 0) {
9310
9551
  return null;
9311
9552
  }
9312
- const synthesisDir = path33.join(workspacePath, "synthesis");
9553
+ const synthesisDir = path34.join(workspacePath, "synthesis");
9313
9554
  const { newHarnessPath } = await applyMutations(
9314
9555
  context.baselineHarnessPath,
9315
9556
  synthesisDir,
@@ -9343,26 +9584,26 @@ __export(population_exports, {
9343
9584
  initBranches: () => initBranches,
9344
9585
  runPopulation: () => runPopulation
9345
9586
  });
9346
- import fs33 from "fs/promises";
9347
- import path34 from "path";
9587
+ import fs34 from "fs/promises";
9588
+ import path35 from "path";
9348
9589
  async function initBranches(workspacePath, baselinePath, numBranches) {
9349
- const branchesDir = path34.join(workspacePath, "branches");
9350
- await fs33.mkdir(branchesDir, { recursive: true });
9590
+ const branchesDir = path35.join(workspacePath, "branches");
9591
+ await fs34.mkdir(branchesDir, { recursive: true });
9351
9592
  const configs = [];
9352
9593
  for (let i = 0; i < numBranches; i++) {
9353
- const branchPath = path34.join(branchesDir, i.toString());
9354
- const harnessPath = path34.join(branchPath, "iterations", "0", "harness");
9594
+ const branchPath = path35.join(branchesDir, i.toString());
9595
+ const harnessPath = path35.join(branchPath, "iterations", "0", "harness");
9355
9596
  await copyDir(baselinePath, harnessPath);
9356
- const tasksYaml = path34.join(workspacePath, "tasks.yaml");
9597
+ const tasksYaml = path35.join(workspacePath, "tasks.yaml");
9357
9598
  try {
9358
- await fs33.access(tasksYaml);
9359
- await fs33.copyFile(tasksYaml, path34.join(branchPath, "tasks.yaml"));
9599
+ await fs34.access(tasksYaml);
9600
+ await fs34.copyFile(tasksYaml, path35.join(branchPath, "tasks.yaml"));
9360
9601
  } catch {
9361
9602
  }
9362
- const configYaml = path34.join(workspacePath, "config.yaml");
9603
+ const configYaml = path35.join(workspacePath, "config.yaml");
9363
9604
  try {
9364
- await fs33.access(configYaml);
9365
- await fs33.copyFile(configYaml, path34.join(branchPath, "config.yaml"));
9605
+ await fs34.access(configYaml);
9606
+ await fs34.copyFile(configYaml, path35.join(branchPath, "config.yaml"));
9366
9607
  } catch {
9367
9608
  }
9368
9609
  const seed = 42 + i * 1337;
@@ -9376,7 +9617,7 @@ async function initBranches(workspacePath, baselinePath, numBranches) {
9376
9617
  }
9377
9618
  async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, numBranches, onProgress) {
9378
9619
  const branches = numBranches ?? evolveConfig.pbtBranches;
9379
- const baselinePath = path34.join(workspacePath, "baseline");
9620
+ const baselinePath = path35.join(workspacePath, "baseline");
9380
9621
  const branchConfigs = await initBranches(workspacePath, baselinePath, branches);
9381
9622
  const branchPromises = branchConfigs.map(async (branchConfig) => {
9382
9623
  const branchEvolveConfig = {
@@ -9396,7 +9637,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9396
9637
  branchEvolveConfig,
9397
9638
  branchProgress
9398
9639
  );
9399
- const finalHarnessPath = path34.join(
9640
+ const finalHarnessPath = path35.join(
9400
9641
  branchConfig.workspacePath,
9401
9642
  "iterations",
9402
9643
  result.bestIteration.toString(),
@@ -9404,8 +9645,8 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9404
9645
  );
9405
9646
  let beliefs = [];
9406
9647
  try {
9407
- const beliefsPath = path34.join(branchConfig.workspacePath, "task-beliefs.json");
9408
- const beliefsContent = await fs33.readFile(beliefsPath, "utf-8");
9648
+ const beliefsPath = path35.join(branchConfig.workspacePath, "task-beliefs.json");
9649
+ const beliefsContent = await fs34.readFile(beliefsPath, "utf-8");
9409
9650
  beliefs = JSON.parse(beliefsContent);
9410
9651
  } catch {
9411
9652
  }
@@ -9427,7 +9668,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9427
9668
  }
9428
9669
  let synthesizedResult;
9429
9670
  try {
9430
- const baselinePath2 = path34.join(workspacePath, "baseline");
9671
+ const baselinePath2 = path35.join(workspacePath, "baseline");
9431
9672
  const synthesisResult = await runSynthesis(
9432
9673
  { branches: branchResults, tasks, baselineHarnessPath: baselinePath2 },
9433
9674
  kairnConfig,
@@ -9489,8 +9730,8 @@ __export(research_exports, {
9489
9730
  formatResearchReport: () => formatResearchReport,
9490
9731
  runResearch: () => runResearch
9491
9732
  });
9492
- import fs34 from "fs/promises";
9493
- import path35 from "path";
9733
+ import fs35 from "fs/promises";
9734
+ import path36 from "path";
9494
9735
  import os5 from "os";
9495
9736
  import { exec as exec4 } from "child_process";
9496
9737
  import { promisify as promisify4 } from "util";
@@ -9509,8 +9750,8 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9509
9750
  }
9510
9751
  const allPatterns = [];
9511
9752
  const repoResults = [];
9512
- const tempBase = path35.join(os5.tmpdir(), `kairn-research-${Date.now()}`);
9513
- await fs34.mkdir(tempBase, { recursive: true });
9753
+ const tempBase = path36.join(os5.tmpdir(), `kairn-research-${Date.now()}`);
9754
+ await fs35.mkdir(tempBase, { recursive: true });
9514
9755
  try {
9515
9756
  for (let i = 0; i < config.repos.length; i++) {
9516
9757
  const url = config.repos[i];
@@ -9522,7 +9763,7 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9522
9763
  totalRepos: config.repos.length,
9523
9764
  message: `Cloning ${name}...`
9524
9765
  });
9525
- const repoDir = path35.join(tempBase, name);
9766
+ const repoDir = path36.join(tempBase, name);
9526
9767
  try {
9527
9768
  await execAsync4(`git clone --depth 1 '${url.replace(/'/g, "'\\''")}' '${repoDir.replace(/'/g, "'\\''")}' 2>/dev/null`, { timeout: 6e4 });
9528
9769
  } catch (err) {
@@ -9536,10 +9777,10 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9536
9777
  repoResults.push({ repo: name, bestScore: 0, patternsFound: 0 });
9537
9778
  continue;
9538
9779
  }
9539
- const claudeDir = path35.join(repoDir, ".claude");
9780
+ const claudeDir = path36.join(repoDir, ".claude");
9540
9781
  let hasHarness = false;
9541
9782
  try {
9542
- await fs34.access(claudeDir);
9783
+ await fs35.access(claudeDir);
9543
9784
  hasHarness = true;
9544
9785
  } catch {
9545
9786
  onProgress?.({
@@ -9589,7 +9830,7 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9589
9830
  onProgress?.({ type: "research-complete", message: "Research complete" });
9590
9831
  return report;
9591
9832
  } finally {
9592
- await fs34.rm(tempBase, { recursive: true, force: true }).catch(() => {
9833
+ await fs35.rm(tempBase, { recursive: true, force: true }).catch(() => {
9593
9834
  });
9594
9835
  }
9595
9836
  }
@@ -9960,6 +10201,7 @@ async function detectExistingRepo(dir) {
9960
10201
  }
9961
10202
 
9962
10203
  // src/commands/describe.ts
10204
+ init_persist();
9963
10205
  var describeCommand = new Command3("describe").description("Describe your workflow and generate a Claude Code environment").argument("[intent]", "What you want your agent to do").option("-y, --yes", "Skip confirmation prompt").option("-q, --quick", "Skip clarification questions").option("--runtime <runtime>", "Target runtime (claude-code or hermes)", "claude-code").action(async (intentArg, options) => {
9964
10206
  printFullBanner("The Agent Environment Compiler");
9965
10207
  const config = await loadConfig();
@@ -10059,6 +10301,13 @@ Autonomy level: ${autonomyLevel} (${autonomyLabel(autonomyLevel)})`;
10059
10301
  `));
10060
10302
  process.exit(1);
10061
10303
  }
10304
+ if (spec.ir) {
10305
+ try {
10306
+ await persistHarnessIR(process.cwd(), spec.ir);
10307
+ } catch {
10308
+ console.log(ui.warn("Could not persist harness IR to .kairn/harness-ir.json"));
10309
+ }
10310
+ }
10062
10311
  const registry = await loadRegistry();
10063
10312
  const summary = summarizeSpec(spec, registry);
10064
10313
  console.log("");
@@ -10133,14 +10382,14 @@ init_ui();
10133
10382
  init_logo();
10134
10383
  import { Command as Command4 } from "commander";
10135
10384
  import chalk7 from "chalk";
10136
- import fs14 from "fs/promises";
10137
- import path14 from "path";
10385
+ import fs15 from "fs/promises";
10386
+ import path15 from "path";
10138
10387
  var listCommand = new Command4("list").description("Show saved environments").action(async () => {
10139
10388
  printCompactBanner();
10140
10389
  const envsDir = getEnvsDir();
10141
10390
  let files;
10142
10391
  try {
10143
- files = await fs14.readdir(envsDir);
10392
+ files = await fs15.readdir(envsDir);
10144
10393
  } catch {
10145
10394
  console.log(chalk7.dim(" No environments yet. Run ") + chalk7.bold("kairn describe") + chalk7.dim(" to create one.\n"));
10146
10395
  return;
@@ -10153,7 +10402,7 @@ var listCommand = new Command4("list").description("Show saved environments").ac
10153
10402
  let first = true;
10154
10403
  for (const file of jsonFiles) {
10155
10404
  try {
10156
- const data = await fs14.readFile(path14.join(envsDir, file), "utf-8");
10405
+ const data = await fs15.readFile(path15.join(envsDir, file), "utf-8");
10157
10406
  const spec = JSON.parse(data);
10158
10407
  const date = new Date(spec.created_at).toLocaleDateString();
10159
10408
  const toolCount = spec.tools?.length ?? 0;
@@ -10178,8 +10427,8 @@ init_ui();
10178
10427
  init_logo();
10179
10428
  import { Command as Command5 } from "commander";
10180
10429
  import chalk8 from "chalk";
10181
- import fs15 from "fs/promises";
10182
- import path15 from "path";
10430
+ import fs16 from "fs/promises";
10431
+ import path16 from "path";
10183
10432
  var activateCommand = new Command5("activate").description("Re-deploy a saved environment to the current directory").argument("<env_id>", "Environment ID (from kairn list)").action(async (envId) => {
10184
10433
  printCompactBanner();
10185
10434
  const envsDir = getEnvsDir();
@@ -10189,7 +10438,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10189
10438
  let fromTemplate = false;
10190
10439
  let envFiles = [];
10191
10440
  try {
10192
- envFiles = await fs15.readdir(envsDir);
10441
+ envFiles = await fs16.readdir(envsDir);
10193
10442
  } catch {
10194
10443
  }
10195
10444
  match = envFiles.find(
@@ -10200,7 +10449,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10200
10449
  } else {
10201
10450
  let templateFiles = [];
10202
10451
  try {
10203
- templateFiles = await fs15.readdir(templatesDir);
10452
+ templateFiles = await fs16.readdir(templatesDir);
10204
10453
  } catch {
10205
10454
  }
10206
10455
  match = templateFiles.find(
@@ -10216,7 +10465,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10216
10465
  process.exit(1);
10217
10466
  }
10218
10467
  }
10219
- const data = await fs15.readFile(path15.join(sourceDir, match), "utf-8");
10468
+ const data = await fs16.readFile(path16.join(sourceDir, match), "utf-8");
10220
10469
  const spec = JSON.parse(data);
10221
10470
  const label = fromTemplate ? chalk8.dim(" (template)") : "";
10222
10471
  console.log(chalk8.cyan(` Activating: ${spec.name}`) + label);
@@ -10236,21 +10485,21 @@ init_ui();
10236
10485
  init_logo();
10237
10486
  import { Command as Command6 } from "commander";
10238
10487
  import chalk9 from "chalk";
10239
- import fs16 from "fs/promises";
10240
- import path16 from "path";
10488
+ import fs17 from "fs/promises";
10489
+ import path17 from "path";
10241
10490
  import { fileURLToPath as fileURLToPath4 } from "url";
10242
10491
  var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
10243
10492
  async function getLocalRegistryPath() {
10244
10493
  const __filename3 = fileURLToPath4(import.meta.url);
10245
- const __dirname3 = path16.dirname(__filename3);
10494
+ const __dirname3 = path17.dirname(__filename3);
10246
10495
  const candidates = [
10247
- path16.resolve(__dirname3, "../registry/tools.json"),
10248
- path16.resolve(__dirname3, "../src/registry/tools.json"),
10249
- path16.resolve(__dirname3, "../../src/registry/tools.json")
10496
+ path17.resolve(__dirname3, "../registry/tools.json"),
10497
+ path17.resolve(__dirname3, "../src/registry/tools.json"),
10498
+ path17.resolve(__dirname3, "../../src/registry/tools.json")
10250
10499
  ];
10251
10500
  for (const candidate of candidates) {
10252
10501
  try {
10253
- await fs16.access(candidate);
10502
+ await fs17.access(candidate);
10254
10503
  return candidate;
10255
10504
  } catch {
10256
10505
  continue;
@@ -10287,10 +10536,10 @@ var updateRegistryCommand = new Command6("update-registry").description("Fetch t
10287
10536
  const registryPath = await getLocalRegistryPath();
10288
10537
  const backupPath = registryPath + ".bak";
10289
10538
  try {
10290
- await fs16.copyFile(registryPath, backupPath);
10539
+ await fs17.copyFile(registryPath, backupPath);
10291
10540
  } catch {
10292
10541
  }
10293
- await fs16.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
10542
+ await fs17.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
10294
10543
  console.log(ui.success(`Registry updated: ${tools.length} tools`));
10295
10544
  console.log(chalk9.dim(` Saved to: ${registryPath}`));
10296
10545
  console.log(chalk9.dim(` Backup: ${backupPath}
@@ -10669,14 +10918,14 @@ init_ui();
10669
10918
  init_logo();
10670
10919
  import { Command as Command9 } from "commander";
10671
10920
  import chalk12 from "chalk";
10672
- import fs17 from "fs/promises";
10673
- import path17 from "path";
10921
+ import fs18 from "fs/promises";
10922
+ import path18 from "path";
10674
10923
  var templatesCommand = new Command9("templates").description("Browse available templates").option("--category <cat>", "filter templates by category keyword").option("--json", "output raw JSON array").action(async (options) => {
10675
10924
  printCompactBanner();
10676
10925
  const templatesDir = getTemplatesDir();
10677
10926
  let files;
10678
10927
  try {
10679
- files = await fs17.readdir(templatesDir);
10928
+ files = await fs18.readdir(templatesDir);
10680
10929
  } catch {
10681
10930
  console.log(
10682
10931
  chalk12.dim(
@@ -10701,8 +10950,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
10701
10950
  const templates = [];
10702
10951
  for (const file of jsonFiles) {
10703
10952
  try {
10704
- const data = await fs17.readFile(
10705
- path17.join(templatesDir, file),
10953
+ const data = await fs18.readFile(
10954
+ path18.join(templatesDir, file),
10706
10955
  "utf-8"
10707
10956
  );
10708
10957
  const spec = JSON.parse(data);
@@ -10753,8 +11002,8 @@ init_loader();
10753
11002
  import { Command as Command10 } from "commander";
10754
11003
  import { password as password3 } from "@inquirer/prompts";
10755
11004
  import chalk13 from "chalk";
10756
- import fs18 from "fs/promises";
10757
- import path18 from "path";
11005
+ import fs19 from "fs/promises";
11006
+ import path19 from "path";
10758
11007
  var keysCommand = new Command10("keys").description("Add or update API keys for the current environment").option("--show", "Show which keys are set vs missing").action(async (options) => {
10759
11008
  printCompactBanner();
10760
11009
  const targetDir = process.cwd();
@@ -10866,8 +11115,8 @@ var keysCommand = new Command10("keys").description("Add or update API keys for
10866
11115
  for (const [key, value] of envEntries) {
10867
11116
  envLines.push(`${key}=${value}`);
10868
11117
  }
10869
- const envPath = path18.join(targetDir, ".env");
10870
- await fs18.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
11118
+ const envPath = path19.join(targetDir, ".env");
11119
+ await fs19.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
10871
11120
  console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
10872
11121
  console.log("");
10873
11122
  });
@@ -10877,90 +11126,131 @@ init_ui();
10877
11126
  import { Command as Command11 } from "commander";
10878
11127
  import chalk14 from "chalk";
10879
11128
  import ora2 from "ora";
10880
- import fs35 from "fs/promises";
10881
- import path36 from "path";
11129
+ import fs36 from "fs/promises";
11130
+ import path37 from "path";
10882
11131
  import { parse as yamlParse2 } from "yaml";
10883
11132
  import { confirm as confirm4, input as input4, select as select4 } from "@inquirer/prompts";
10884
11133
 
10885
11134
  // src/evolve/init.ts
10886
11135
  init_config();
10887
- import fs19 from "fs/promises";
10888
- import path19 from "path";
11136
+ init_cache();
11137
+ import fs20 from "fs/promises";
11138
+ import path20 from "path";
10889
11139
  import { stringify as yamlStringify } from "yaml";
10890
11140
 
10891
11141
  // src/evolve/templates.ts
10892
11142
  init_llm();
10893
11143
  var EVAL_TEMPLATES = {
11144
+ // --- Harness-sensitivity templates (probe whether agent follows .claude/ harness) ---
10894
11145
  "add-feature": {
10895
11146
  id: "add-feature",
10896
11147
  name: "Add Feature",
10897
11148
  description: "Can the agent add a new capability?",
10898
- bestFor: ["feature-development", "api-building", "full-stack"]
11149
+ bestFor: ["feature-development", "api-building", "full-stack"],
11150
+ category: "harness-sensitivity"
10899
11151
  },
10900
11152
  "fix-bug": {
10901
11153
  id: "fix-bug",
10902
11154
  name: "Fix Bug",
10903
11155
  description: "Can the agent diagnose and fix a problem?",
10904
- bestFor: ["maintenance", "debugging", "qa"]
11156
+ bestFor: ["maintenance", "debugging", "qa"],
11157
+ category: "harness-sensitivity"
10905
11158
  },
10906
11159
  "refactor": {
10907
11160
  id: "refactor",
10908
11161
  name: "Refactor",
10909
11162
  description: "Can the agent restructure code?",
10910
- bestFor: ["maintenance", "architecture", "backend"]
11163
+ bestFor: ["maintenance", "architecture", "backend"],
11164
+ category: "harness-sensitivity"
10911
11165
  },
10912
11166
  "test-writing": {
10913
11167
  id: "test-writing",
10914
11168
  name: "Test Writing",
10915
11169
  description: "Can the agent write tests?",
10916
- bestFor: ["tdd", "qa", "backend"]
11170
+ bestFor: ["tdd", "qa", "backend"],
11171
+ category: "harness-sensitivity"
10917
11172
  },
10918
11173
  "config-change": {
10919
11174
  id: "config-change",
10920
11175
  name: "Config Change",
10921
11176
  description: "Can the agent update configuration?",
10922
- bestFor: ["devops", "infrastructure", "backend"]
11177
+ bestFor: ["devops", "infrastructure", "backend"],
11178
+ category: "harness-sensitivity"
10923
11179
  },
10924
11180
  "documentation": {
10925
11181
  id: "documentation",
10926
11182
  name: "Documentation",
10927
11183
  description: "Can the agent write and update docs?",
10928
- bestFor: ["content", "api-building", "full-stack"]
11184
+ bestFor: ["content", "api-building", "full-stack"],
11185
+ category: "harness-sensitivity"
10929
11186
  },
10930
11187
  "convention-adherence": {
10931
11188
  id: "convention-adherence",
10932
11189
  name: "Convention Adherence",
10933
11190
  description: "Does the agent follow all project conventions defined in CLAUDE.md?",
10934
- bestFor: ["feature-development", "full-stack", "backend", "maintenance"]
11191
+ bestFor: ["feature-development", "full-stack", "backend", "maintenance"],
11192
+ category: "harness-sensitivity"
10935
11193
  },
10936
11194
  "workflow-compliance": {
10937
11195
  id: "workflow-compliance",
10938
11196
  name: "Workflow Compliance",
10939
11197
  description: "Does the agent use the project workflow commands and skills?",
10940
- bestFor: ["feature-development", "full-stack", "tdd", "qa"]
11198
+ bestFor: ["feature-development", "full-stack", "tdd", "qa"],
11199
+ category: "harness-sensitivity"
10941
11200
  },
10942
11201
  "rule-compliance": {
10943
11202
  id: "rule-compliance",
10944
11203
  name: "Rule Compliance",
10945
11204
  description: "Does the agent follow all project rules without violations?",
10946
- bestFor: ["feature-development", "backend", "maintenance", "architecture"]
11205
+ bestFor: ["feature-development", "backend", "maintenance", "architecture"],
11206
+ category: "harness-sensitivity"
10947
11207
  },
10948
11208
  "intent-routing": {
10949
11209
  id: "intent-routing",
10950
11210
  name: "Intent Routing",
10951
11211
  description: "Test that natural language prompts route to the correct workflow command via intent hooks",
10952
- bestFor: ["feature-development", "full-stack", "api-building"]
11212
+ bestFor: ["feature-development", "full-stack", "api-building"],
11213
+ category: "harness-sensitivity"
10953
11214
  },
10954
11215
  "persistence-completion": {
10955
11216
  id: "persistence-completion",
10956
11217
  name: "Persistence Completion",
10957
11218
  description: "Can the agent complete a multi-criterion task using the persistence loop?",
10958
- bestFor: ["feature-development", "full-stack", "api-building", "maintenance"]
11219
+ bestFor: ["feature-development", "full-stack", "api-building", "maintenance"],
11220
+ category: "harness-sensitivity"
11221
+ },
11222
+ // --- Substantive SWE-bench-style templates (test real coding ability) ---
11223
+ "real-bug-fix": {
11224
+ id: "real-bug-fix",
11225
+ name: "Real Bug Fix",
11226
+ description: "Injects a known bug into a source file and asks the agent to diagnose and fix it, mimicking a real GitHub issue",
11227
+ bestFor: ["debugging", "maintenance", "qa", "backend"],
11228
+ category: "substantive"
11229
+ },
11230
+ "real-feature-add": {
11231
+ id: "real-feature-add",
11232
+ name: "Real Feature Add",
11233
+ description: "Describes a concrete feature with clear acceptance criteria and verifies the agent implements it correctly",
11234
+ bestFor: ["feature-development", "full-stack", "api-building", "backend"],
11235
+ category: "substantive"
11236
+ },
11237
+ "codebase-question": {
11238
+ id: "codebase-question",
11239
+ name: "Codebase Question",
11240
+ description: "Asks a factual question about codebase knowledge and checks the answer via LLM-as-judge against a known-correct answer",
11241
+ bestFor: ["research", "architecture", "maintenance", "debugging"],
11242
+ category: "substantive"
10959
11243
  }
10960
11244
  };
11245
+ var SUBSTANTIVE_FLOOR = [
11246
+ "real-bug-fix",
11247
+ "real-feature-add",
11248
+ "codebase-question"
11249
+ ];
11250
+ var MAX_HARNESS_TEMPLATES = 4;
10961
11251
  function selectTemplatesForWorkflow(workflowType) {
10962
- const mapping = {
10963
- "feature-development": ["add-feature", "test-writing", "convention-adherence", "workflow-compliance", "intent-routing", "persistence-completion"],
11252
+ const harnessMapping = {
11253
+ "feature-development": ["add-feature", "test-writing", "intent-routing", "convention-adherence", "workflow-compliance", "persistence-completion"],
10964
11254
  "api-building": ["add-feature", "fix-bug", "test-writing", "convention-adherence", "persistence-completion"],
10965
11255
  "full-stack": ["add-feature", "fix-bug", "test-writing", "convention-adherence", "persistence-completion"],
10966
11256
  "maintenance": ["fix-bug", "refactor", "test-writing", "rule-compliance", "persistence-completion"],
@@ -10974,7 +11264,8 @@ function selectTemplatesForWorkflow(workflowType) {
10974
11264
  "content": ["documentation", "add-feature", "convention-adherence"],
10975
11265
  "research": ["documentation", "add-feature", "convention-adherence"]
10976
11266
  };
10977
- return mapping[workflowType] || ["add-feature", "fix-bug", "test-writing", "convention-adherence"];
11267
+ const harness = (harnessMapping[workflowType] ?? ["add-feature", "fix-bug", "test-writing", "convention-adherence"]).slice(0, MAX_HARNESS_TEMPLATES);
11268
+ return [...SUBSTANTIVE_FLOOR, ...harness];
10978
11269
  }
10979
11270
  var TASK_GENERATION_PROMPT = `You are an eval task generator for Claude Code agent environments. Given a project's CLAUDE.md, project structure, and selected eval templates, generate concrete, project-specific tasks.
10980
11271
 
@@ -10988,6 +11279,15 @@ IMPORTANT: For harness-aware templates (convention-adherence, workflow-complianc
10988
11279
 
10989
11280
  These harness-aware tasks are critical \u2014 they test whether the .claude/ environment actually improves agent behavior.
10990
11281
 
11282
+ SUBSTANTIVE SWE-bench-style templates test real coding ability beyond harness adherence:
11283
+ - real-bug-fix: Inject a known bug into a source file (e.g., swap variable names, remove an import, introduce an off-by-one error). Write the task description like a real GitHub issue: "When X happens, Y is broken." The setup command should apply the bug. Scorer: run the test suite or check the specific file was fixed correctly. Use scoring "pass-fail".
11284
+ - real-feature-add: Describe a concrete feature with clear acceptance criteria (e.g., "Add a --verbose flag that prints debug output"). The feature should be small, self-contained, and testable. Scorer: verify the feature exists, tests pass, and no regressions. Use scoring "pass-fail" or "rubric".
11285
+ - codebase-question: Ask a factual question about the codebase that requires reading and understanding source code (e.g., "What function handles authentication?" or "What environment variables does this project need?"). Include the known-correct answer in expected_outcome. Scorer: LLM-as-judge checks answer accuracy against the known-correct answer. Use scoring "llm-judge".
11286
+
11287
+ Each task MUST include a "category" field:
11288
+ - "harness-sensitivity" for templates that test .claude/ harness adherence
11289
+ - "substantive" for SWE-bench-style templates that test real coding ability
11290
+
10991
11291
  Return a JSON object with a "tasks" array. Each task has:
10992
11292
  - id: kebab-case identifier (e.g., "add-health-endpoint")
10993
11293
  - template: which eval template this instantiates
@@ -10995,8 +11295,11 @@ Return a JSON object with a "tasks" array. Each task has:
10995
11295
  - setup: shell commands to prepare the workspace (e.g., "npm install")
10996
11296
  - expected_outcome: multi-line string describing what success looks like
10997
11297
  - scoring: "pass-fail", "llm-judge", or "rubric"
11298
+ - category: "harness-sensitivity" or "substantive"
10998
11299
  - timeout: seconds (300 for features/bugs, 600 for refactors, 180 for config/docs/tests)
10999
11300
 
11301
+ BALANCE REQUIREMENT: Generate an equal number of harness-sensitivity tasks and substantive tasks. If you are given N templates total, aim for ceil(N/2) harness-sensitivity tasks and floor(N/2) substantive tasks. Do not skew toward one category.
11302
+
11000
11303
  Return ONLY valid JSON, no markdown fences.`;
11001
11304
  function parseJsonResponse(raw) {
11002
11305
  let cleaned = raw.trim();
@@ -11038,7 +11341,56 @@ function validateTask(obj, index) {
11038
11341
  }
11039
11342
  return record;
11040
11343
  }
11041
- function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11344
+ function buildAnalysisContext(analysis) {
11345
+ const lines = ["## Project Analysis", ""];
11346
+ lines.push(`Purpose: ${analysis.purpose}`);
11347
+ lines.push(`Domain: ${analysis.domain}`);
11348
+ lines.push(`Architecture: ${analysis.architecture_style}`);
11349
+ lines.push(`Deployment: ${analysis.deployment_model}`);
11350
+ lines.push("");
11351
+ if (analysis.key_modules.length > 0) {
11352
+ lines.push("### Key Modules");
11353
+ lines.push("");
11354
+ for (const mod of analysis.key_modules) {
11355
+ lines.push(`- **${mod.name}** (${mod.path}): ${mod.description}`);
11356
+ if (mod.responsibilities.length > 0) {
11357
+ lines.push(` Responsibilities: ${mod.responsibilities.join(", ")}`);
11358
+ }
11359
+ }
11360
+ lines.push("");
11361
+ }
11362
+ if (analysis.workflows.length > 0) {
11363
+ lines.push("### Workflows");
11364
+ lines.push("");
11365
+ for (const wf of analysis.workflows) {
11366
+ lines.push(`- **${wf.name}**: ${wf.description} (trigger: ${wf.trigger})`);
11367
+ lines.push(` Steps: ${wf.steps.join(" -> ")}`);
11368
+ }
11369
+ lines.push("");
11370
+ }
11371
+ if (analysis.config_keys.length > 0) {
11372
+ lines.push("### Config Keys");
11373
+ lines.push("");
11374
+ for (const key of analysis.config_keys) {
11375
+ lines.push(`- ${key.name}: ${key.purpose}`);
11376
+ }
11377
+ lines.push("");
11378
+ }
11379
+ if (analysis.dataflow.length > 0) {
11380
+ lines.push("### Data Flow");
11381
+ lines.push("");
11382
+ for (const edge of analysis.dataflow) {
11383
+ lines.push(`- ${edge.from} -> ${edge.to}: ${edge.data}`);
11384
+ }
11385
+ lines.push("");
11386
+ }
11387
+ lines.push("IMPORTANT: Use this analysis to generate domain-specific tasks:");
11388
+ lines.push("- real-bug-fix tasks should reference actual module names and paths listed above");
11389
+ lines.push("- codebase-question tasks should ask about actual workflows, modules, and config keys");
11390
+ lines.push("- real-feature-add tasks should extend actual functionality in the modules listed above");
11391
+ return lines.join("\n");
11392
+ }
11393
+ function buildTaskGenerationMessage(claudeMd, projectProfile, templates, analysis) {
11042
11394
  const profileLines = [
11043
11395
  `Language: ${projectProfile.language ?? "unknown"}`,
11044
11396
  `Framework: ${projectProfile.framework ?? "none"}`,
@@ -11049,7 +11401,7 @@ function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11049
11401
  const meta = EVAL_TEMPLATES[t];
11050
11402
  return `- ${t}: ${meta.description}`;
11051
11403
  }).join("\n");
11052
- return [
11404
+ const sections = [
11053
11405
  "## CLAUDE.md",
11054
11406
  "",
11055
11407
  claudeMd,
@@ -11057,16 +11409,23 @@ function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11057
11409
  "## Project Profile",
11058
11410
  "",
11059
11411
  ...profileLines,
11060
- "",
11412
+ ""
11413
+ ];
11414
+ if (analysis) {
11415
+ sections.push(buildAnalysisContext(analysis));
11416
+ sections.push("");
11417
+ }
11418
+ sections.push(
11061
11419
  "## Selected Eval Templates",
11062
11420
  "",
11063
11421
  templateDescriptions,
11064
11422
  "",
11065
11423
  "Generate concrete, project-specific tasks for each template above."
11066
- ].join("\n");
11424
+ );
11425
+ return sections.join("\n");
11067
11426
  }
11068
- async function generateTasksFromTemplates(claudeMd, projectProfile, templates, config) {
11069
- const userMessage = buildTaskGenerationMessage(claudeMd, projectProfile, templates);
11427
+ async function generateTasksFromTemplates(claudeMd, projectProfile, templates, config, analysis) {
11428
+ const userMessage = buildTaskGenerationMessage(claudeMd, projectProfile, templates, analysis);
11070
11429
  const rawResponse = await callLLM(config, userMessage, {
11071
11430
  systemPrompt: TASK_GENERATION_PROMPT,
11072
11431
  maxTokens: 4096
@@ -11088,10 +11447,10 @@ async function generateTasksFromTemplates(claudeMd, projectProfile, templates, c
11088
11447
 
11089
11448
  // src/evolve/init.ts
11090
11449
  async function createEvolveWorkspace(projectRoot, config) {
11091
- const workspace = path19.join(projectRoot, ".kairn-evolve");
11092
- await fs19.mkdir(path19.join(workspace, "baseline"), { recursive: true });
11093
- await fs19.mkdir(path19.join(workspace, "traces"), { recursive: true });
11094
- await fs19.mkdir(path19.join(workspace, "iterations"), { recursive: true });
11450
+ const workspace = path20.join(projectRoot, ".kairn-evolve");
11451
+ await fs20.mkdir(path20.join(workspace, "baseline"), { recursive: true });
11452
+ await fs20.mkdir(path20.join(workspace, "traces"), { recursive: true });
11453
+ await fs20.mkdir(path20.join(workspace, "iterations"), { recursive: true });
11095
11454
  const configObj = {
11096
11455
  model: config.model,
11097
11456
  proposer_model: config.proposerModel,
@@ -11099,8 +11458,8 @@ async function createEvolveWorkspace(projectRoot, config) {
11099
11458
  max_iterations: config.maxIterations,
11100
11459
  parallel_tasks: config.parallelTasks
11101
11460
  };
11102
- await fs19.writeFile(
11103
- path19.join(workspace, "config.yaml"),
11461
+ await fs20.writeFile(
11462
+ path20.join(workspace, "config.yaml"),
11104
11463
  yamlStringify(configObj),
11105
11464
  "utf-8"
11106
11465
  );
@@ -11116,12 +11475,13 @@ async function writeTasksFile(workspacePath, tasks) {
11116
11475
  expected_outcome: t.expected_outcome,
11117
11476
  scoring: t.scoring,
11118
11477
  ...t.rubric ? { rubric: t.rubric } : {},
11119
- timeout: t.timeout
11478
+ timeout: t.timeout,
11479
+ ...t.category ? { category: t.category } : {}
11120
11480
  }))
11121
11481
  };
11122
11482
  const header = "# .kairn-evolve/tasks.yaml\n# Auto-generated by kairn evolve init \u2014 edit freely\n";
11123
- await fs19.writeFile(
11124
- path19.join(workspacePath, "tasks.yaml"),
11483
+ await fs20.writeFile(
11484
+ path20.join(workspacePath, "tasks.yaml"),
11125
11485
  header + yamlStringify(doc),
11126
11486
  "utf-8"
11127
11487
  );
@@ -11134,8 +11494,8 @@ async function buildProjectProfile(projectRoot) {
11134
11494
  keyFiles: []
11135
11495
  };
11136
11496
  try {
11137
- const pkgStr = await fs19.readFile(
11138
- path19.join(projectRoot, "package.json"),
11497
+ const pkgStr = await fs20.readFile(
11498
+ path20.join(projectRoot, "package.json"),
11139
11499
  "utf-8"
11140
11500
  );
11141
11501
  const pkg2 = JSON.parse(pkgStr);
@@ -11162,18 +11522,18 @@ async function buildProjectProfile(projectRoot) {
11162
11522
  }
11163
11523
  if (!profile.language) {
11164
11524
  try {
11165
- await fs19.access(path19.join(projectRoot, "pyproject.toml"));
11525
+ await fs20.access(path20.join(projectRoot, "pyproject.toml"));
11166
11526
  profile.language = "python";
11167
11527
  } catch {
11168
11528
  try {
11169
- await fs19.access(path19.join(projectRoot, "requirements.txt"));
11529
+ await fs20.access(path20.join(projectRoot, "requirements.txt"));
11170
11530
  profile.language = "python";
11171
11531
  } catch {
11172
11532
  }
11173
11533
  }
11174
11534
  }
11175
11535
  try {
11176
- const entries = await fs19.readdir(projectRoot);
11536
+ const entries = await fs20.readdir(projectRoot);
11177
11537
  const keyPatterns = [
11178
11538
  "README.md",
11179
11539
  "package.json",
@@ -11189,6 +11549,16 @@ async function buildProjectProfile(projectRoot) {
11189
11549
  }
11190
11550
  return profile;
11191
11551
  }
11552
+ async function loadProjectAnalysis(projectRoot) {
11553
+ try {
11554
+ const cache = await readCache(projectRoot);
11555
+ if (cache?.analysis) {
11556
+ return cache.analysis;
11557
+ }
11558
+ } catch {
11559
+ }
11560
+ return void 0;
11561
+ }
11192
11562
  async function autoGenerateTasks(projectRoot, workflowType) {
11193
11563
  const config = await loadConfig();
11194
11564
  if (!config) {
@@ -11196,15 +11566,16 @@ async function autoGenerateTasks(projectRoot, workflowType) {
11196
11566
  }
11197
11567
  let claudeMd = "";
11198
11568
  try {
11199
- claudeMd = await fs19.readFile(
11200
- path19.join(projectRoot, ".claude", "CLAUDE.md"),
11569
+ claudeMd = await fs20.readFile(
11570
+ path20.join(projectRoot, ".claude", "CLAUDE.md"),
11201
11571
  "utf-8"
11202
11572
  );
11203
11573
  } catch {
11204
11574
  }
11205
11575
  const profile = await buildProjectProfile(projectRoot);
11576
+ const analysis = await loadProjectAnalysis(projectRoot);
11206
11577
  const templates = selectTemplatesForWorkflow(workflowType);
11207
- return generateTasksFromTemplates(claudeMd, profile, templates, config);
11578
+ return generateTasksFromTemplates(claudeMd, profile, templates, config, analysis);
11208
11579
  }
11209
11580
 
11210
11581
  // src/commands/evolve.ts
@@ -11216,8 +11587,8 @@ init_loop();
11216
11587
 
11217
11588
  // src/evolve/report.ts
11218
11589
  init_trace();
11219
- import fs31 from "fs/promises";
11220
- import path31 from "path";
11590
+ import fs32 from "fs/promises";
11591
+ import path32 from "path";
11221
11592
 
11222
11593
  // src/evolve/diagnosis.ts
11223
11594
  function numericScore(s) {
@@ -11267,10 +11638,10 @@ function numericScore2(s) {
11267
11638
  return s.score ?? (s.pass ? 100 : 0);
11268
11639
  }
11269
11640
  async function loadAllIterations(workspacePath) {
11270
- const iterDir = path31.join(workspacePath, "iterations");
11641
+ const iterDir = path32.join(workspacePath, "iterations");
11271
11642
  let entries;
11272
11643
  try {
11273
- entries = await fs31.readdir(iterDir);
11644
+ entries = await fs32.readdir(iterDir);
11274
11645
  } catch {
11275
11646
  return [];
11276
11647
  }
@@ -11284,13 +11655,41 @@ async function loadAllIterations(workspacePath) {
11284
11655
  }
11285
11656
  async function loadTasks(workspacePath) {
11286
11657
  try {
11287
- const content = await fs31.readFile(path31.join(workspacePath, "tasks.yaml"), "utf-8");
11658
+ const content = await fs32.readFile(path32.join(workspacePath, "tasks.yaml"), "utf-8");
11288
11659
  const parsed = yamlParse(content);
11289
- return parsed?.tasks ?? [];
11660
+ const tasks = parsed?.tasks ?? [];
11661
+ return tasks.map((t) => ({
11662
+ ...t,
11663
+ category: t.category ?? "harness-sensitivity"
11664
+ }));
11290
11665
  } catch {
11291
11666
  return [];
11292
11667
  }
11293
11668
  }
11669
+ function computeCategoryBreakdown(tasks, taskResults) {
11670
+ const harnessTasks = [];
11671
+ const substantiveTasks = [];
11672
+ for (const task of tasks) {
11673
+ const result = taskResults[task.id];
11674
+ if (!result) continue;
11675
+ const score = numericScore2(result);
11676
+ const category = task.category ?? "harness-sensitivity";
11677
+ if (category === "harness-sensitivity") {
11678
+ harnessTasks.push(score);
11679
+ } else {
11680
+ substantiveTasks.push(score);
11681
+ }
11682
+ }
11683
+ if (harnessTasks.length === 0 || substantiveTasks.length === 0) {
11684
+ return void 0;
11685
+ }
11686
+ const harnessAvg = harnessTasks.reduce((a, b) => a + b, 0) / harnessTasks.length;
11687
+ const substantiveAvg = substantiveTasks.reduce((a, b) => a + b, 0) / substantiveTasks.length;
11688
+ return {
11689
+ harnessAdherence: { score: harnessAvg, count: harnessTasks.length },
11690
+ substantiveTasks: { score: substantiveAvg, count: substantiveTasks.length }
11691
+ };
11692
+ }
11294
11693
  function buildLeaderboard(iterations, tasks) {
11295
11694
  const taskIds = tasks.map((t) => t.id);
11296
11695
  return taskIds.map((taskId) => {
@@ -11350,6 +11749,11 @@ async function generateMarkdownReport(workspacePath) {
11350
11749
  lines.push(`| Best score | ${bestIter.score.toFixed(1)}% |`);
11351
11750
  lines.push(`| Best iteration | ${bestIter.iteration} |`);
11352
11751
  lines.push(`| Improvement | ${improvement >= 0 ? "+" : ""}${improvement.toFixed(1)} points |`);
11752
+ const categoryBreakdown = computeCategoryBreakdown(tasks, bestIter.taskResults);
11753
+ if (categoryBreakdown) {
11754
+ lines.push(`| Harness adherence | ${categoryBreakdown.harnessAdherence.score.toFixed(1)}% (${categoryBreakdown.harnessAdherence.count} tasks) |`);
11755
+ lines.push(`| Substantive tasks | ${categoryBreakdown.substantiveTasks.score.toFixed(1)}% (${categoryBreakdown.substantiveTasks.count} tasks) |`);
11756
+ }
11353
11757
  lines.push("");
11354
11758
  lines.push("## Iterations");
11355
11759
  lines.push("");
@@ -11433,10 +11837,12 @@ async function generateJsonReport(workspacePath) {
11433
11837
  const iterations = await loadAllIterations(workspacePath);
11434
11838
  const tasks = await loadTasks(workspacePath);
11435
11839
  const baselineScore = iterations.length > 0 ? iterations[0].score : 0;
11436
- const bestIter = iterations.length > 0 ? iterations.reduce((best, curr) => curr.score > best.score ? curr : best, iterations[0]) : { score: 0, iteration: 0 };
11840
+ const bestIterLog = iterations.length > 0 ? iterations.reduce((best, curr) => curr.score > best.score ? curr : best, iterations[0]) : void 0;
11841
+ const bestIter = bestIterLog ?? { score: 0, iteration: 0 };
11437
11842
  const improvement = bestIter.score - baselineScore;
11438
11843
  const counterfactuals = diagnoseCounterfactuals(iterations, tasks);
11439
11844
  const leaderboard = buildLeaderboard(iterations, tasks);
11845
+ const categoryBreakdown = bestIterLog ? computeCategoryBreakdown(tasks, bestIterLog.taskResults) : void 0;
11440
11846
  return {
11441
11847
  overview: {
11442
11848
  title: "Evolution Report",
@@ -11444,7 +11850,8 @@ async function generateJsonReport(workspacePath) {
11444
11850
  baselineScore,
11445
11851
  bestScore: bestIter.score,
11446
11852
  bestIteration: bestIter.iteration,
11447
- improvement
11853
+ improvement,
11854
+ ...categoryBreakdown ? { categoryBreakdown } : {}
11448
11855
  },
11449
11856
  iterations: iterations.map((iter) => {
11450
11857
  const stddevs = Object.values(iter.taskResults).map((s) => s.variance?.stddev).filter((v) => v !== void 0);
@@ -11470,13 +11877,13 @@ init_mutator();
11470
11877
  init_baseline();
11471
11878
  init_mutator();
11472
11879
  init_trace();
11473
- import fs32 from "fs/promises";
11474
- import path32 from "path";
11880
+ import fs33 from "fs/promises";
11881
+ import path33 from "path";
11475
11882
  async function listIterations(workspacePath) {
11476
- const iterationsDir = path32.join(workspacePath, "iterations");
11883
+ const iterationsDir = path33.join(workspacePath, "iterations");
11477
11884
  let entries;
11478
11885
  try {
11479
- entries = await fs32.readdir(iterationsDir);
11886
+ entries = await fs33.readdir(iterationsDir);
11480
11887
  } catch {
11481
11888
  return [];
11482
11889
  }
@@ -11485,7 +11892,7 @@ async function listIterations(workspacePath) {
11485
11892
  const n = parseInt(entry, 10);
11486
11893
  if (!isNaN(n)) {
11487
11894
  try {
11488
- await fs32.access(path32.join(iterationsDir, entry, "harness"));
11895
+ await fs33.access(path33.join(iterationsDir, entry, "harness"));
11489
11896
  nums.push(n);
11490
11897
  } catch {
11491
11898
  }
@@ -11511,16 +11918,16 @@ async function listFilesRecursive(dir) {
11511
11918
  async function walk(current) {
11512
11919
  let entries;
11513
11920
  try {
11514
- entries = await fs32.readdir(current, { withFileTypes: true });
11921
+ entries = await fs33.readdir(current, { withFileTypes: true });
11515
11922
  } catch {
11516
11923
  return;
11517
11924
  }
11518
11925
  for (const entry of entries) {
11519
- const fullPath = path32.join(current, entry.name);
11926
+ const fullPath = path33.join(current, entry.name);
11520
11927
  if (entry.isDirectory()) {
11521
11928
  await walk(fullPath);
11522
11929
  } else {
11523
- results.push(path32.relative(dir, fullPath));
11930
+ results.push(path33.relative(dir, fullPath));
11524
11931
  }
11525
11932
  }
11526
11933
  }
@@ -11528,10 +11935,10 @@ async function listFilesRecursive(dir) {
11528
11935
  return results;
11529
11936
  }
11530
11937
  async function findBestPBTHarness(workspacePath) {
11531
- const branchesDir = path32.join(workspacePath, "branches");
11938
+ const branchesDir = path33.join(workspacePath, "branches");
11532
11939
  let branchEntries;
11533
11940
  try {
11534
- branchEntries = await fs32.readdir(branchesDir);
11941
+ branchEntries = await fs33.readdir(branchesDir);
11535
11942
  } catch {
11536
11943
  return null;
11537
11944
  }
@@ -11539,7 +11946,7 @@ async function findBestPBTHarness(workspacePath) {
11539
11946
  let bestPath = "";
11540
11947
  let bestLabel = "";
11541
11948
  for (const branchId of branchEntries) {
11542
- const branchPath = path32.join(branchesDir, branchId);
11949
+ const branchPath = path33.join(branchesDir, branchId);
11543
11950
  const branchIterations = await listIterations(branchPath);
11544
11951
  if (branchIterations.length === 0) continue;
11545
11952
  const bestIter = await findBestIteration(branchPath, branchIterations);
@@ -11547,13 +11954,13 @@ async function findBestPBTHarness(workspacePath) {
11547
11954
  const score = log?.score ?? 0;
11548
11955
  if (score > bestScore) {
11549
11956
  bestScore = score;
11550
- bestPath = path32.join(branchPath, "iterations", bestIter.toString(), "harness");
11957
+ bestPath = path33.join(branchPath, "iterations", bestIter.toString(), "harness");
11551
11958
  bestLabel = `branch ${branchId}, iteration ${bestIter} (${score.toFixed(1)}%)`;
11552
11959
  }
11553
11960
  }
11554
- const synthesisHarness = path32.join(workspacePath, "synthesis", "harness");
11961
+ const synthesisHarness = path33.join(workspacePath, "synthesis", "harness");
11555
11962
  try {
11556
- await fs32.access(synthesisHarness);
11963
+ await fs33.access(synthesisHarness);
11557
11964
  const synthesisLog = await loadIterationLog(workspacePath, 999);
11558
11965
  const synthScore = synthesisLog?.score ?? 0;
11559
11966
  if (synthScore > bestScore) {
@@ -11572,26 +11979,26 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration, pbt)
11572
11979
  if (!pbtResult) {
11573
11980
  throw new Error("No PBT results found. Run `kairn evolve pbt` first.");
11574
11981
  }
11575
- const claudeDir2 = path32.join(projectRoot, ".claude");
11982
+ const claudeDir2 = path33.join(projectRoot, ".claude");
11576
11983
  const diffPreview2 = await generateDiff2(claudeDir2, pbtResult.harnessPath);
11577
11984
  const currentFiles2 = await listFilesRecursive(claudeDir2);
11578
11985
  const targetFiles2 = await listFilesRecursive(pbtResult.harnessPath);
11579
11986
  const allPaths2 = /* @__PURE__ */ new Set([...currentFiles2, ...targetFiles2]);
11580
11987
  const filesChanged2 = [];
11581
11988
  for (const filePath of allPaths2) {
11582
- const currentContent = await fs32.readFile(path32.join(claudeDir2, filePath), "utf-8").catch(() => null);
11583
- const targetContent = await fs32.readFile(path32.join(pbtResult.harnessPath, filePath), "utf-8").catch(() => null);
11989
+ const currentContent = await fs33.readFile(path33.join(claudeDir2, filePath), "utf-8").catch(() => null);
11990
+ const targetContent = await fs33.readFile(path33.join(pbtResult.harnessPath, filePath), "utf-8").catch(() => null);
11584
11991
  if (currentContent !== targetContent) {
11585
11992
  filesChanged2.push(filePath);
11586
11993
  }
11587
11994
  }
11588
- await fs32.rm(claudeDir2, { recursive: true, force: true });
11995
+ await fs33.rm(claudeDir2, { recursive: true, force: true });
11589
11996
  await copyDir(pbtResult.harnessPath, claudeDir2);
11590
- const harnessMcpJson2 = path32.join(pbtResult.harnessPath, ".mcp.json");
11591
- const projectMcpJson2 = path32.join(projectRoot, ".mcp.json");
11997
+ const harnessMcpJson2 = path33.join(pbtResult.harnessPath, ".mcp.json");
11998
+ const projectMcpJson2 = path33.join(projectRoot, ".mcp.json");
11592
11999
  try {
11593
- await fs32.access(harnessMcpJson2);
11594
- await fs32.copyFile(harnessMcpJson2, projectMcpJson2);
12000
+ await fs33.access(harnessMcpJson2);
12001
+ await fs33.copyFile(harnessMcpJson2, projectMcpJson2);
11595
12002
  if (!filesChanged2.includes(".mcp.json")) filesChanged2.push(".mcp.json");
11596
12003
  } catch {
11597
12004
  }
@@ -11617,37 +12024,37 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration, pbt)
11617
12024
  } else {
11618
12025
  iter = await findBestIteration(workspacePath, iterations);
11619
12026
  }
11620
- const harnessPath = path32.join(
12027
+ const harnessPath = path33.join(
11621
12028
  workspacePath,
11622
12029
  "iterations",
11623
12030
  iter.toString(),
11624
12031
  "harness"
11625
12032
  );
11626
- const claudeDir = path32.join(projectRoot, ".claude");
12033
+ const claudeDir = path33.join(projectRoot, ".claude");
11627
12034
  const diffPreview = await generateDiff2(claudeDir, harnessPath);
11628
12035
  const currentFiles = await listFilesRecursive(claudeDir);
11629
12036
  const targetFiles = await listFilesRecursive(harnessPath);
11630
12037
  const allPaths = /* @__PURE__ */ new Set([...currentFiles, ...targetFiles]);
11631
12038
  const filesChanged = [];
11632
12039
  for (const filePath of allPaths) {
11633
- const currentContent = await fs32.readFile(path32.join(claudeDir, filePath), "utf-8").catch(() => null);
11634
- const targetContent = await fs32.readFile(path32.join(harnessPath, filePath), "utf-8").catch(() => null);
12040
+ const currentContent = await fs33.readFile(path33.join(claudeDir, filePath), "utf-8").catch(() => null);
12041
+ const targetContent = await fs33.readFile(path33.join(harnessPath, filePath), "utf-8").catch(() => null);
11635
12042
  if (currentContent !== targetContent) {
11636
12043
  filesChanged.push(filePath);
11637
12044
  }
11638
12045
  }
11639
- await fs32.rm(claudeDir, { recursive: true, force: true });
12046
+ await fs33.rm(claudeDir, { recursive: true, force: true });
11640
12047
  await copyDir(harnessPath, claudeDir);
11641
- const harnessMcpJson = path32.join(harnessPath, ".mcp.json");
11642
- const projectMcpJson = path32.join(projectRoot, ".mcp.json");
12048
+ const harnessMcpJson = path33.join(harnessPath, ".mcp.json");
12049
+ const projectMcpJson = path33.join(projectRoot, ".mcp.json");
11643
12050
  try {
11644
- await fs32.access(harnessMcpJson);
11645
- const currentMcp = await fs32.readFile(projectMcpJson, "utf-8").catch(() => null);
11646
- const targetMcp = await fs32.readFile(harnessMcpJson, "utf-8").catch(() => null);
12051
+ await fs33.access(harnessMcpJson);
12052
+ const currentMcp = await fs33.readFile(projectMcpJson, "utf-8").catch(() => null);
12053
+ const targetMcp = await fs33.readFile(harnessMcpJson, "utf-8").catch(() => null);
11647
12054
  if (currentMcp !== targetMcp) {
11648
12055
  filesChanged.push(".mcp.json");
11649
12056
  }
11650
- await fs32.copyFile(harnessMcpJson, projectMcpJson);
12057
+ await fs33.copyFile(harnessMcpJson, projectMcpJson);
11651
12058
  } catch {
11652
12059
  }
11653
12060
  return {
@@ -11680,7 +12087,7 @@ var DEFAULT_CONFIG = {
11680
12087
  };
11681
12088
  async function loadEvolveConfigFromWorkspace(workspacePath) {
11682
12089
  try {
11683
- const configStr = await fs35.readFile(path36.join(workspacePath, "config.yaml"), "utf-8");
12090
+ const configStr = await fs36.readFile(path37.join(workspacePath, "config.yaml"), "utf-8");
11684
12091
  const parsed = yamlParse2(configStr);
11685
12092
  return {
11686
12093
  model: parsed.model ?? DEFAULT_CONFIG.model,
@@ -11710,9 +12117,9 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11710
12117
  try {
11711
12118
  const projectRoot = process.cwd();
11712
12119
  console.log(ui.section("Evolve Init"));
11713
- const claudeDir = path36.join(projectRoot, ".claude");
12120
+ const claudeDir = path37.join(projectRoot, ".claude");
11714
12121
  try {
11715
- await fs35.access(claudeDir);
12122
+ await fs36.access(claudeDir);
11716
12123
  } catch {
11717
12124
  console.log(ui.error("No .claude/ directory found. Run kairn describe first."));
11718
12125
  process.exit(1);
@@ -11762,7 +12169,7 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11762
12169
  if (config) {
11763
12170
  let claudeMd = "";
11764
12171
  try {
11765
- claudeMd = await fs35.readFile(path36.join(claudeDir, "CLAUDE.md"), "utf-8");
12172
+ claudeMd = await fs36.readFile(path37.join(claudeDir, "CLAUDE.md"), "utf-8");
11766
12173
  } catch {
11767
12174
  }
11768
12175
  const profile = await buildProjectProfile(projectRoot);
@@ -11793,16 +12200,16 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11793
12200
  evolveCommand.command("baseline").description("Snapshot current .claude/ directory as baseline").action(async () => {
11794
12201
  try {
11795
12202
  const projectRoot = process.cwd();
11796
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12203
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
11797
12204
  console.log(ui.section("Evolve Baseline"));
11798
12205
  try {
11799
- await fs35.access(workspace);
12206
+ await fs36.access(workspace);
11800
12207
  } catch {
11801
12208
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
11802
12209
  process.exit(1);
11803
12210
  }
11804
12211
  await snapshotBaseline(projectRoot, workspace);
11805
- const baselineDir = path36.join(workspace, "baseline");
12212
+ const baselineDir = path37.join(workspace, "baseline");
11806
12213
  const fileCount = await countFiles(baselineDir);
11807
12214
  console.log(ui.success(`Baseline snapshot created (${fileCount} files)`));
11808
12215
  } catch (err) {
@@ -11814,18 +12221,18 @@ evolveCommand.command("baseline").description("Snapshot current .claude/ directo
11814
12221
  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").option("--parallel <n>", "Run up to N tasks concurrently", "1").option("--max-mutations <n>", "Max mutations per iteration", "3").option("--prune-threshold <n>", "Skip tasks scoring above this on middle iterations", "95").option("--max-task-drop <n>", "Roll back if any task drops more than N points", "20").option("--principal", "Run Principal Proposer as final iteration").option("--eval-sample <n>", "Sample N tasks per middle iteration (0 = all)", "0").option("--sampling <strategy>", "Task sampling strategy: thompson or uniform", "thompson").option("--kl-lambda <n>", "KL regularization strength (0 = disabled)", "0.1").option("--architect-every <n>", "Run architect proposer every N iterations (default: 3)").option("--schedule <type>", "Architect schedule: explore-exploit, constant, or adaptive (default: explore-exploit)").option("--architect-model <model>", "Model for architect proposer (defaults to proposer model)").option("-i, --interactive", "Configure evolution settings interactively").action(async (options) => {
11815
12222
  try {
11816
12223
  const projectRoot = process.cwd();
11817
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12224
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
11818
12225
  console.log(ui.section("Evolve Run"));
11819
12226
  try {
11820
- await fs35.access(workspace);
12227
+ await fs36.access(workspace);
11821
12228
  } catch {
11822
12229
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
11823
12230
  process.exit(1);
11824
12231
  }
11825
- const tasksPath = path36.join(workspace, "tasks.yaml");
12232
+ const tasksPath = path37.join(workspace, "tasks.yaml");
11826
12233
  let tasksContent;
11827
12234
  try {
11828
- tasksContent = await fs35.readFile(tasksPath, "utf-8");
12235
+ tasksContent = await fs36.readFile(tasksPath, "utf-8");
11829
12236
  } catch {
11830
12237
  console.log(ui.error("No tasks.yaml found. Run kairn evolve init first."));
11831
12238
  process.exit(1);
@@ -11844,15 +12251,15 @@ evolveCommand.command("run").description("Run tasks against the current harness"
11844
12251
  console.log(ui.info(`Running ${tasksToRun.length} task(s)...`));
11845
12252
  console.log("");
11846
12253
  const config = await loadConfig();
11847
- const harnessPath = path36.join(projectRoot, ".claude");
12254
+ const harnessPath = path37.join(projectRoot, ".claude");
11848
12255
  const results = [];
11849
12256
  for (const task of tasksToRun) {
11850
- const traceDir = path36.join(workspace, "traces", "0", task.id);
12257
+ const traceDir = path37.join(workspace, "traces", "0", task.id);
11851
12258
  const spinner = ora2(`Running: ${task.id}`).start();
11852
12259
  const result = await runTask(task, harnessPath, traceDir, 0);
11853
12260
  if (config) {
11854
- const stdout = await fs35.readFile(path36.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
11855
- const stderr = await fs35.readFile(path36.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
12261
+ const stdout = await fs36.readFile(path37.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
12262
+ const stderr = await fs36.readFile(path37.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
11856
12263
  const score = await scoreTask(task, traceDir, stdout, stderr, config);
11857
12264
  result.score = score;
11858
12265
  await writeScore(traceDir, score);
@@ -12019,7 +12426,7 @@ evolveCommand.command("run").description("Run tasks against the current harness"
12019
12426
  }
12020
12427
  }
12021
12428
  try {
12022
- await fs35.access(path36.join(workspace, "iterations", "0", "harness"));
12429
+ await fs36.access(path37.join(workspace, "iterations", "0", "harness"));
12023
12430
  } catch {
12024
12431
  console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
12025
12432
  process.exit(1);
@@ -12126,16 +12533,16 @@ evolveCommand.command("run").description("Run tasks against the current harness"
12126
12533
  evolveCommand.command("pbt").description("Run Population-Based Training with parallel evolution branches").option("--branches <n>", "Number of parallel branches", "3").option("--iterations <n>", "Iterations per branch", "5").option("--parallel <n>", "Tasks per branch concurrently", "2").option("--sampling <strategy>", "Task sampling strategy: thompson or uniform", "thompson").option("--kl-lambda <n>", "KL regularization strength (0 = disabled)", "0.1").option("--eval-sample <n>", "Sample N tasks per middle iteration (0 = all)", "5").action(async (options) => {
12127
12534
  try {
12128
12535
  const projectRoot = process.cwd();
12129
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12536
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12130
12537
  console.log(ui.section("Evolve PBT"));
12131
12538
  try {
12132
- await fs35.access(workspace);
12539
+ await fs36.access(workspace);
12133
12540
  } catch {
12134
12541
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12135
12542
  process.exit(1);
12136
12543
  }
12137
12544
  try {
12138
- await fs35.access(path36.join(workspace, "iterations", "0", "harness"));
12545
+ await fs36.access(path37.join(workspace, "iterations", "0", "harness"));
12139
12546
  } catch {
12140
12547
  console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
12141
12548
  process.exit(1);
@@ -12155,8 +12562,8 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
12155
12562
  if (sampling === "thompson" || sampling === "uniform") {
12156
12563
  evolveConfig.samplingStrategy = sampling;
12157
12564
  }
12158
- const tasksPath = path36.join(workspace, "tasks.yaml");
12159
- const tasksContent = await fs35.readFile(tasksPath, "utf-8");
12565
+ const tasksPath = path37.join(workspace, "tasks.yaml");
12566
+ const tasksContent = await fs36.readFile(tasksPath, "utf-8");
12160
12567
  const parsed = yamlParse2(tasksContent);
12161
12568
  if (!parsed?.tasks || parsed.tasks.length === 0) {
12162
12569
  console.log(ui.error("No tasks found in tasks.yaml"));
@@ -12214,10 +12621,10 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
12214
12621
  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) => {
12215
12622
  try {
12216
12623
  const projectRoot = process.cwd();
12217
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12624
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12218
12625
  console.log(ui.section("Evolve Apply"));
12219
12626
  try {
12220
- await fs35.access(workspace);
12627
+ await fs36.access(workspace);
12221
12628
  } catch {
12222
12629
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12223
12630
  process.exit(1);
@@ -12258,9 +12665,9 @@ evolveCommand.command("apply").description("Apply the best evolved harness to yo
12258
12665
  evolveCommand.command("report").description("Generate a summary report of the evolution run").option("--json", "Output machine-readable JSON instead of Markdown").action(async (options) => {
12259
12666
  try {
12260
12667
  const projectRoot = process.cwd();
12261
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12668
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12262
12669
  try {
12263
- await fs35.access(workspace);
12670
+ await fs36.access(workspace);
12264
12671
  } catch {
12265
12672
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12266
12673
  process.exit(1);
@@ -12281,23 +12688,23 @@ evolveCommand.command("report").description("Generate a summary report of the ev
12281
12688
  evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes between two iterations").action(async (iter1Str, iter2Str) => {
12282
12689
  try {
12283
12690
  const projectRoot = process.cwd();
12284
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12691
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12285
12692
  const iter1 = parseInt(iter1Str, 10);
12286
12693
  const iter2 = parseInt(iter2Str, 10);
12287
12694
  if (isNaN(iter1) || isNaN(iter2)) {
12288
12695
  console.log(ui.error("Both arguments must be integers (iteration numbers)"));
12289
12696
  process.exit(1);
12290
12697
  }
12291
- const harness1 = path36.join(workspace, "iterations", iter1.toString(), "harness");
12292
- const harness2 = path36.join(workspace, "iterations", iter2.toString(), "harness");
12698
+ const harness1 = path37.join(workspace, "iterations", iter1.toString(), "harness");
12699
+ const harness2 = path37.join(workspace, "iterations", iter2.toString(), "harness");
12293
12700
  try {
12294
- await fs35.access(harness1);
12701
+ await fs36.access(harness1);
12295
12702
  } catch {
12296
12703
  console.log(ui.error(`Iteration ${iter1} harness not found at ${harness1}`));
12297
12704
  process.exit(1);
12298
12705
  }
12299
12706
  try {
12300
- await fs35.access(harness2);
12707
+ await fs36.access(harness2);
12301
12708
  } catch {
12302
12709
  console.log(ui.error(`Iteration ${iter2} harness not found at ${harness2}`));
12303
12710
  process.exit(1);
@@ -12404,7 +12811,7 @@ evolveCommand.command("research").description("Run cross-repo research to discov
12404
12811
  const reportText = formatResearchReport2(report);
12405
12812
  console.log("\n" + reportText);
12406
12813
  if (options.output) {
12407
- await fs35.writeFile(options.output, reportText, "utf-8");
12814
+ await fs36.writeFile(options.output, reportText, "utf-8");
12408
12815
  console.log(chalk14.green(`
12409
12816
  Report saved to ${options.output}`));
12410
12817
  }
@@ -12421,10 +12828,10 @@ evolveCommand.command("research").description("Run cross-repo research to discov
12421
12828
  async function countFiles(dir) {
12422
12829
  let count = 0;
12423
12830
  try {
12424
- const entries = await fs35.readdir(dir, { withFileTypes: true });
12831
+ const entries = await fs36.readdir(dir, { withFileTypes: true });
12425
12832
  for (const entry of entries) {
12426
12833
  if (entry.isDirectory()) {
12427
- count += await countFiles(path36.join(dir, entry.name));
12834
+ count += await countFiles(path37.join(dir, entry.name));
12428
12835
  } else {
12429
12836
  count++;
12430
12837
  }
@@ -12440,12 +12847,42 @@ init_scan();
12440
12847
  init_analyze();
12441
12848
  init_types3();
12442
12849
  init_cache();
12850
+ init_proposer();
12443
12851
  init_ui();
12444
12852
  init_logo();
12445
12853
  import { Command as Command12 } from "commander";
12446
12854
  import chalk15 from "chalk";
12855
+ import fs37 from "fs/promises";
12856
+ import path38 from "path";
12447
12857
  import ora3 from "ora";
12448
12858
  async function analyzeAction(options) {
12859
+ if (options.ir) {
12860
+ const irPath = path38.join(process.cwd(), ".kairn", "harness-ir.json");
12861
+ try {
12862
+ const raw = await fs37.readFile(irPath, "utf-8");
12863
+ const ir = JSON.parse(raw);
12864
+ if (!options.json) {
12865
+ printCompactBanner();
12866
+ console.log(ui.section("Harness IR"));
12867
+ console.log(buildIRSummary(ir));
12868
+ console.log("");
12869
+ console.log(chalk15.dim(` Source: ${irPath}`));
12870
+ console.log("");
12871
+ } else {
12872
+ console.log(JSON.stringify(ir, null, 2));
12873
+ }
12874
+ } catch {
12875
+ if (options.json) {
12876
+ console.log(JSON.stringify({ error: "No harness IR found. Run `kairn optimize` first." }));
12877
+ } else {
12878
+ printCompactBanner();
12879
+ console.log(ui.warn("No harness IR found. Run `kairn optimize` first."));
12880
+ console.log("");
12881
+ }
12882
+ process.exit(1);
12883
+ }
12884
+ return;
12885
+ }
12449
12886
  if (!options.json) {
12450
12887
  printCompactBanner();
12451
12888
  }
@@ -12498,11 +12935,14 @@ async function analyzeAction(options) {
12498
12935
  indent: 2
12499
12936
  }).start();
12500
12937
  let analysis;
12938
+ let packedSource = "";
12501
12939
  try {
12502
- analysis = await analyzeProject(targetDir, profile, config, {
12940
+ const result = await analyzeProject(targetDir, profile, config, {
12503
12941
  refresh: options.refresh,
12504
12942
  tokenBudget: options.tokenBudget
12505
12943
  });
12944
+ analysis = result.analysis;
12945
+ packedSource = result.packedSource;
12506
12946
  analysisSpinner?.succeed(
12507
12947
  options.refresh ? "Re-analyzed from scratch" : "Codebase analyzed"
12508
12948
  );
@@ -12580,9 +13020,10 @@ async function analyzeAction(options) {
12580
13020
  }
12581
13021
  }
12582
13022
  console.log("");
13023
+ const packedStats = packedSource ? ` \xB7 ${packedSource.length.toLocaleString()} chars packed` : "";
12583
13024
  console.log(
12584
13025
  chalk15.dim(
12585
- ` Sampled ${analysis.sampled_files.length} files \xB7 analyzed ${analysis.analyzed_at}`
13026
+ ` Sampled ${analysis.sampled_files.length} files${packedStats} \xB7 analyzed ${analysis.analyzed_at}`
12586
13027
  )
12587
13028
  );
12588
13029
  console.log(ui.divider());
@@ -12590,7 +13031,7 @@ async function analyzeAction(options) {
12590
13031
  }
12591
13032
  var analyzeCommand = new Command12("analyze").description(
12592
13033
  "Analyze project source code to understand purpose, architecture, and workflows"
12593
- ).option("--refresh", "Force re-analysis, bypassing cache").option("--json", "Output raw JSON (for piping)").option("--token-budget <tokens>", "Max tokens of source code to sample (default: 60000)", parseInt).action(analyzeAction);
13034
+ ).option("--refresh", "Force re-analysis, bypassing cache").option("--json", "Output raw JSON (for piping)").option("--ir", "Display the persisted harness IR from .kairn/harness-ir.json").option("--token-budget <tokens>", "Max tokens of source code to sample (default: 60000)", parseInt).action(analyzeAction);
12594
13035
 
12595
13036
  // src/cli.ts
12596
13037
  var require2 = createRequire(import.meta.url);