kairn-cli 2.14.0 → 2.16.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.
Files changed (3) hide show
  1. package/dist/cli.js +1036 -474
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
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 [];
@@ -3873,14 +3892,56 @@ function detectFramework(deps) {
3873
3892
  }
3874
3893
  return detected.length > 0 ? detected.join(" + ") : null;
3875
3894
  }
3876
- function detectLanguage(dir, keyFiles) {
3877
- if (keyFiles.some((f) => f === "tsconfig.json")) return "TypeScript";
3878
- if (keyFiles.some((f) => f === "package.json")) return "JavaScript";
3879
- if (keyFiles.some((f) => f === "pyproject.toml" || f === "setup.py" || f === "requirements.txt")) return "Python";
3880
- if (keyFiles.some((f) => f === "Cargo.toml")) return "Rust";
3881
- if (keyFiles.some((f) => f === "go.mod")) return "Go";
3882
- if (keyFiles.some((f) => f === "Gemfile")) return "Ruby";
3883
- return null;
3895
+ function detectLanguagesFromFiles(files) {
3896
+ const matched = [];
3897
+ for (const signal of LANGUAGE_SIGNALS) {
3898
+ if (files.some((f) => signal.files.includes(f))) {
3899
+ matched.push(signal.language);
3900
+ }
3901
+ }
3902
+ return matched;
3903
+ }
3904
+ async function detectLanguageLocations(dir, keyFiles) {
3905
+ const rootHits = detectLanguagesFromFiles(keyFiles);
3906
+ if (rootHits.length > 0) {
3907
+ return rootHits.map((language) => ({ language, subdirs: [] }));
3908
+ }
3909
+ const entries = await listDirSafe(dir);
3910
+ const langSubdirs = /* @__PURE__ */ new Map();
3911
+ const counts = /* @__PURE__ */ new Map();
3912
+ for (const entry of entries) {
3913
+ const subPath = path10.join(dir, entry);
3914
+ try {
3915
+ const stat = await fs10.stat(subPath);
3916
+ if (!stat.isDirectory()) continue;
3917
+ } catch {
3918
+ continue;
3919
+ }
3920
+ const subFiles = await listDirSafe(subPath);
3921
+ const subLangs = detectLanguagesFromFiles(subFiles);
3922
+ for (const lang of subLangs) {
3923
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
3924
+ const existing = langSubdirs.get(lang);
3925
+ if (existing) {
3926
+ existing.push(entry);
3927
+ } else {
3928
+ langSubdirs.set(lang, [entry]);
3929
+ }
3930
+ }
3931
+ }
3932
+ if (counts.size === 0) return [];
3933
+ const precedence = /* @__PURE__ */ new Map();
3934
+ for (let i = 0; i < LANGUAGE_SIGNALS.length; i++) {
3935
+ precedence.set(LANGUAGE_SIGNALS[i].language, i);
3936
+ }
3937
+ return [...counts.entries()].sort((a, b) => {
3938
+ const freqDiff = b[1] - a[1];
3939
+ if (freqDiff !== 0) return freqDiff;
3940
+ return (precedence.get(a[0]) ?? 999) - (precedence.get(b[0]) ?? 999);
3941
+ }).map(([lang]) => ({
3942
+ language: lang,
3943
+ subdirs: langSubdirs.get(lang) ?? []
3944
+ }));
3884
3945
  }
3885
3946
  function extractEnvKeys(content) {
3886
3947
  const keys = [];
@@ -3891,7 +3952,7 @@ function extractEnvKeys(content) {
3891
3952
  return keys;
3892
3953
  }
3893
3954
  async function scanProject(dir) {
3894
- const pkg2 = await readJsonSafe(path9.join(dir, "package.json"));
3955
+ const pkg2 = await readJsonSafe(path10.join(dir, "package.json"));
3895
3956
  const deps = pkg2?.dependencies ? Object.keys(pkg2.dependencies) : [];
3896
3957
  const devDeps = pkg2?.devDependencies ? Object.keys(pkg2.devDependencies) : [];
3897
3958
  const allDeps = [...deps, ...devDeps];
@@ -3915,23 +3976,25 @@ async function scanProject(dir) {
3915
3976
  "CLAUDE.md"
3916
3977
  ].includes(f)
3917
3978
  );
3918
- const language = detectLanguage(dir, keyFiles);
3979
+ const languageLocations = await detectLanguageLocations(dir, keyFiles);
3980
+ const detectedLanguages = languageLocations.map((d) => d.language);
3981
+ const language = detectedLanguages[0] ?? null;
3919
3982
  const framework = detectFramework(allDeps);
3920
3983
  const typescript = keyFiles.includes("tsconfig.json") || allDeps.includes("typescript");
3921
3984
  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"));
3985
+ const hasTests = testCommand !== null || await fileExists(path10.join(dir, "tests")) || await fileExists(path10.join(dir, "__tests__")) || await fileExists(path10.join(dir, "test"));
3923
3986
  const buildCommand = scripts.build || null;
3924
3987
  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"));
3988
+ const hasSrc = await fileExists(path10.join(dir, "src"));
3989
+ const hasDocker = await fileExists(path10.join(dir, "docker-compose.yml")) || await fileExists(path10.join(dir, "Dockerfile"));
3990
+ const hasCi = await fileExists(path10.join(dir, ".github/workflows"));
3991
+ const hasEnvFile = await fileExists(path10.join(dir, ".env")) || await fileExists(path10.join(dir, ".env.example"));
3929
3992
  let envKeys = [];
3930
- const envExample = await readFileSafe(path9.join(dir, ".env.example"));
3993
+ const envExample = await readFileSafe(path10.join(dir, ".env.example"));
3931
3994
  if (envExample) {
3932
3995
  envKeys = extractEnvKeys(envExample);
3933
3996
  }
3934
- const claudeDir = path9.join(dir, ".claude");
3997
+ const claudeDir = path10.join(dir, ".claude");
3935
3998
  const hasClaudeDir = await fileExists(claudeDir);
3936
3999
  let existingClaudeMd = null;
3937
4000
  let existingSettings = null;
@@ -3943,27 +4006,29 @@ async function scanProject(dir) {
3943
4006
  let mcpServerCount = 0;
3944
4007
  let claudeMdLineCount = 0;
3945
4008
  if (hasClaudeDir) {
3946
- existingClaudeMd = await readFileSafe(path9.join(claudeDir, "CLAUDE.md"));
4009
+ existingClaudeMd = await readFileSafe(path10.join(claudeDir, "CLAUDE.md"));
3947
4010
  if (existingClaudeMd) {
3948
4011
  claudeMdLineCount = existingClaudeMd.split("\n").length;
3949
4012
  }
3950
- existingSettings = await readJsonSafe(path9.join(claudeDir, "settings.json"));
3951
- existingMcpConfig = await readJsonSafe(path9.join(dir, ".mcp.json"));
4013
+ existingSettings = await readJsonSafe(path10.join(claudeDir, "settings.json"));
4014
+ existingMcpConfig = await readJsonSafe(path10.join(dir, ".mcp.json"));
3952
4015
  if (existingMcpConfig?.mcpServers) {
3953
4016
  mcpServerCount = Object.keys(existingMcpConfig.mcpServers).length;
3954
4017
  }
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", ""));
4018
+ existingCommands = (await listDirSafe(path10.join(claudeDir, "commands"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
4019
+ existingRules = (await listDirSafe(path10.join(claudeDir, "rules"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
4020
+ existingSkills = await listDirSafe(path10.join(claudeDir, "skills"));
4021
+ existingAgents = (await listDirSafe(path10.join(claudeDir, "agents"))).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
3959
4022
  }
3960
- const name = pkg2?.name || path9.basename(dir);
4023
+ const name = pkg2?.name || path10.basename(dir);
3961
4024
  const description = pkg2?.description || "";
3962
4025
  return {
3963
4026
  name,
3964
4027
  description,
3965
4028
  directory: dir,
3966
4029
  language,
4030
+ languages: detectedLanguages,
4031
+ languageLocations,
3967
4032
  framework,
3968
4033
  typescript,
3969
4034
  dependencies: deps,
@@ -3991,9 +4056,18 @@ async function scanProject(dir) {
3991
4056
  keyFiles
3992
4057
  };
3993
4058
  }
4059
+ var LANGUAGE_SIGNALS;
3994
4060
  var init_scan = __esm({
3995
4061
  "src/scanner/scan.ts"() {
3996
4062
  "use strict";
4063
+ LANGUAGE_SIGNALS = [
4064
+ { files: ["tsconfig.json"], language: "TypeScript" },
4065
+ { files: ["package.json"], language: "JavaScript" },
4066
+ { files: ["pyproject.toml", "setup.py", "requirements.txt"], language: "Python" },
4067
+ { files: ["Cargo.toml"], language: "Rust" },
4068
+ { files: ["go.mod"], language: "Go" },
4069
+ { files: ["Gemfile"], language: "Ruby" }
4070
+ ];
3997
4071
  }
3998
4072
  });
3999
4073
 
@@ -4014,13 +4088,14 @@ var init_types3 = __esm({
4014
4088
  });
4015
4089
 
4016
4090
  // src/analyzer/patterns.ts
4017
- import fs10 from "fs/promises";
4018
- import path10 from "path";
4091
+ import fs11 from "fs/promises";
4092
+ import path11 from "path";
4019
4093
  function getStrategy(language) {
4020
4094
  if (language === null) {
4021
4095
  return null;
4022
4096
  }
4023
4097
  const key = language.toLowerCase();
4098
+ if (key === "javascript") return STRATEGIES["typescript"] ?? null;
4024
4099
  return STRATEGIES[key] ?? null;
4025
4100
  }
4026
4101
  function getAlwaysInclude() {
@@ -4051,6 +4126,26 @@ function classifyFilePriority(filePath, strategy) {
4051
4126
  }
4052
4127
  return 3 /* OTHER */;
4053
4128
  }
4129
+ function dedupe(arr) {
4130
+ return Array.from(new Set(arr));
4131
+ }
4132
+ function mergeStrategies(strategies) {
4133
+ if (strategies.length === 0) {
4134
+ throw new Error("mergeStrategies requires at least one strategy");
4135
+ }
4136
+ if (strategies.length === 1) {
4137
+ return strategies[0];
4138
+ }
4139
+ return {
4140
+ language: strategies.map((s) => s.language).join("/"),
4141
+ extensions: dedupe(strategies.flatMap((s) => s.extensions)),
4142
+ entryPoints: dedupe(strategies.flatMap((s) => s.entryPoints)),
4143
+ domainPatterns: dedupe(strategies.flatMap((s) => s.domainPatterns)),
4144
+ configPatterns: dedupe(strategies.flatMap((s) => s.configPatterns)),
4145
+ excludePatterns: dedupe(strategies.flatMap((s) => s.excludePatterns)),
4146
+ maxFilesPerCategory: Math.max(...strategies.map((s) => s.maxFilesPerCategory))
4147
+ };
4148
+ }
4054
4149
  function extractPathsFromScripts(scripts) {
4055
4150
  const paths = [];
4056
4151
  const interestingScripts = ["start", "dev", "serve", "main", "server"];
@@ -4080,7 +4175,7 @@ function extractPathsFromScripts(scripts) {
4080
4175
  async function resolveNodeEntryPoints(dir) {
4081
4176
  const paths = [];
4082
4177
  try {
4083
- const raw = await fs10.readFile(path10.join(dir, "package.json"), "utf-8");
4178
+ const raw = await fs11.readFile(path11.join(dir, "package.json"), "utf-8");
4084
4179
  const pkg2 = JSON.parse(raw);
4085
4180
  if (typeof pkg2.main === "string") {
4086
4181
  paths.push(pkg2.main);
@@ -4119,7 +4214,7 @@ async function resolveNodeEntryPoints(dir) {
4119
4214
  async function resolvePythonEntryPoints(dir) {
4120
4215
  const paths = [];
4121
4216
  try {
4122
- const raw = await fs10.readFile(path10.join(dir, "pyproject.toml"), "utf-8");
4217
+ const raw = await fs11.readFile(path11.join(dir, "pyproject.toml"), "utf-8");
4123
4218
  const scriptMatches = raw.matchAll(/^\s*\w+\s*=\s*"([^":]+)/gm);
4124
4219
  for (const m of scriptMatches) {
4125
4220
  const modulePath = m[1].replace(/\./g, "/") + ".py";
@@ -4130,17 +4225,17 @@ async function resolvePythonEntryPoints(dir) {
4130
4225
  }
4131
4226
  for (const f of ["manage.py", "wsgi.py", "asgi.py"]) {
4132
4227
  try {
4133
- await fs10.access(path10.join(dir, f));
4228
+ await fs11.access(path11.join(dir, f));
4134
4229
  paths.push(f);
4135
4230
  } catch {
4136
4231
  }
4137
4232
  }
4138
4233
  try {
4139
- const entries = await fs10.readdir(path10.join(dir, "src"), { withFileTypes: true });
4234
+ const entries = await fs11.readdir(path11.join(dir, "src"), { withFileTypes: true });
4140
4235
  for (const entry of entries) {
4141
4236
  if (entry.isDirectory()) {
4142
4237
  try {
4143
- await fs10.access(path10.join(dir, "src", entry.name, "__main__.py"));
4238
+ await fs11.access(path11.join(dir, "src", entry.name, "__main__.py"));
4144
4239
  paths.push(`src/${entry.name}/__main__.py`);
4145
4240
  paths.push(`src/${entry.name}/__init__.py`);
4146
4241
  } catch {
@@ -4154,7 +4249,7 @@ async function resolvePythonEntryPoints(dir) {
4154
4249
  async function resolveRustEntryPoints(dir) {
4155
4250
  const paths = [];
4156
4251
  try {
4157
- const raw = await fs10.readFile(path10.join(dir, "Cargo.toml"), "utf-8");
4252
+ const raw = await fs11.readFile(path11.join(dir, "Cargo.toml"), "utf-8");
4158
4253
  const binPaths = raw.matchAll(/path\s*=\s*"([^"]+\.rs)"/g);
4159
4254
  for (const m of binPaths) paths.push(m[1]);
4160
4255
  } catch {
@@ -4164,7 +4259,7 @@ async function resolveRustEntryPoints(dir) {
4164
4259
  async function resolveGoEntryPoints(dir) {
4165
4260
  const paths = [];
4166
4261
  try {
4167
- const cmdEntries = await fs10.readdir(path10.join(dir, "cmd"), { withFileTypes: true });
4262
+ const cmdEntries = await fs11.readdir(path11.join(dir, "cmd"), { withFileTypes: true });
4168
4263
  for (const entry of cmdEntries) {
4169
4264
  if (entry.isDirectory()) {
4170
4265
  paths.push(`cmd/${entry.name}/main.go`);
@@ -4330,9 +4425,9 @@ var init_patterns = __esm({
4330
4425
  });
4331
4426
 
4332
4427
  // src/analyzer/repomix-adapter.ts
4333
- import path11 from "path";
4428
+ import path12 from "path";
4334
4429
  import os3 from "os";
4335
- import fs11 from "fs/promises";
4430
+ import fs12 from "fs/promises";
4336
4431
  import {
4337
4432
  pack,
4338
4433
  mergeConfigs,
@@ -4342,7 +4437,7 @@ import {
4342
4437
  } from "repomix";
4343
4438
  async function packCodebase(dir, options) {
4344
4439
  setLogLevel(0);
4345
- const tempOutputFile = path11.join(
4440
+ const tempOutputFile = path12.join(
4346
4441
  os3.tmpdir(),
4347
4442
  `kairn-repomix-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`
4348
4443
  );
@@ -4413,7 +4508,7 @@ ${f.content}`).join("\n\n");
4413
4508
  message
4414
4509
  );
4415
4510
  } finally {
4416
- await fs11.rm(tempOutputFile, { force: true }).catch(() => {
4511
+ await fs12.rm(tempOutputFile, { force: true }).catch(() => {
4417
4512
  });
4418
4513
  }
4419
4514
  }
@@ -4425,50 +4520,64 @@ var init_repomix_adapter = __esm({
4425
4520
  });
4426
4521
 
4427
4522
  // src/analyzer/cache.ts
4428
- import fs12 from "fs/promises";
4523
+ import fs13 from "fs/promises";
4429
4524
  import fsSync from "fs";
4430
- import path12 from "path";
4525
+ import path13 from "path";
4431
4526
  import { createHash } from "crypto";
4432
4527
  import { fileURLToPath as fileURLToPath3 } from "url";
4433
4528
  function getKairnVersion() {
4434
- let dir = path12.dirname(fileURLToPath3(import.meta.url));
4529
+ let dir = path13.dirname(fileURLToPath3(import.meta.url));
4435
4530
  for (let i = 0; i < 5; i++) {
4436
4531
  try {
4437
- const pkgPath = path12.join(dir, "package.json");
4532
+ const pkgPath = path13.join(dir, "package.json");
4438
4533
  const content = fsSync.readFileSync(pkgPath, "utf-8");
4439
4534
  const parsed = JSON.parse(content);
4440
4535
  if (parsed.name === "kairn-cli") return parsed.version ?? "0.0.0";
4441
4536
  } catch {
4442
4537
  }
4443
- dir = path12.dirname(dir);
4538
+ dir = path13.dirname(dir);
4444
4539
  }
4445
4540
  return "0.0.0";
4446
4541
  }
4447
4542
  async function readCache(dir) {
4448
- const filePath = path12.join(dir, CACHE_FILENAME);
4543
+ const filePath = path13.join(dir, CACHE_FILENAME);
4449
4544
  try {
4450
- const raw = await fs12.readFile(filePath, "utf-8");
4545
+ const raw = await fs13.readFile(filePath, "utf-8");
4451
4546
  const parsed = JSON.parse(raw);
4452
- return parsed;
4547
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.analysis !== "object" || parsed.analysis === null) {
4548
+ return null;
4549
+ }
4550
+ const cache = parsed;
4551
+ let packedSource = null;
4552
+ try {
4553
+ const packedPath = path13.join(dir, PACKED_SOURCE_FILENAME);
4554
+ packedSource = await fs13.readFile(packedPath, "utf-8");
4555
+ } catch {
4556
+ }
4557
+ return { ...cache, packedSource };
4453
4558
  } catch {
4454
4559
  return null;
4455
4560
  }
4456
4561
  }
4457
- async function writeCache(dir, analysis) {
4458
- const filePath = path12.join(dir, CACHE_FILENAME);
4562
+ async function writeCache(dir, analysis, packedSource) {
4563
+ const filePath = path13.join(dir, CACHE_FILENAME);
4459
4564
  const cache = {
4460
4565
  analysis,
4461
4566
  content_hash: analysis.content_hash,
4462
4567
  kairn_version: KAIRN_VERSION
4463
4568
  };
4464
- await fs12.writeFile(filePath, JSON.stringify(cache, null, 2), "utf-8");
4569
+ await fs13.writeFile(filePath, JSON.stringify(cache, null, 2), "utf-8");
4570
+ if (packedSource !== void 0) {
4571
+ const packedPath = path13.join(dir, PACKED_SOURCE_FILENAME);
4572
+ await fs13.writeFile(packedPath, packedSource, "utf-8");
4573
+ }
4465
4574
  }
4466
4575
  async function computeContentHash(filePaths, dir) {
4467
4576
  const hash = createHash("sha256");
4468
4577
  for (const filePath of filePaths) {
4469
4578
  try {
4470
- const fullPath = path12.join(dir, filePath);
4471
- const content = await fs12.readFile(fullPath, "utf-8");
4579
+ const fullPath = path13.join(dir, filePath);
4580
+ const content = await fs13.readFile(fullPath, "utf-8");
4472
4581
  hash.update(content);
4473
4582
  } catch {
4474
4583
  }
@@ -4478,12 +4587,13 @@ async function computeContentHash(filePaths, dir) {
4478
4587
  function isCacheValid(cache, currentHash) {
4479
4588
  return cache.content_hash === currentHash && cache.kairn_version === KAIRN_VERSION;
4480
4589
  }
4481
- var KAIRN_VERSION, CACHE_FILENAME;
4590
+ var KAIRN_VERSION, CACHE_FILENAME, PACKED_SOURCE_FILENAME;
4482
4591
  var init_cache = __esm({
4483
4592
  "src/analyzer/cache.ts"() {
4484
4593
  "use strict";
4485
4594
  KAIRN_VERSION = getKairnVersion();
4486
4595
  CACHE_FILENAME = ".kairn-analysis.json";
4596
+ PACKED_SOURCE_FILENAME = ".kairn-packed-source.txt";
4487
4597
  }
4488
4598
  });
4489
4599
 
@@ -4507,6 +4617,39 @@ function validateArray(raw, guard) {
4507
4617
  if (!Array.isArray(raw)) return [];
4508
4618
  return raw.filter(guard);
4509
4619
  }
4620
+ function scopeStrategyToSubdirs(strategy, subdirs) {
4621
+ if (subdirs.length === 0) return strategy;
4622
+ return {
4623
+ ...strategy,
4624
+ entryPoints: subdirs.flatMap(
4625
+ (dir) => strategy.entryPoints.map((ep) => `${dir}/${ep}`)
4626
+ ),
4627
+ domainPatterns: subdirs.flatMap(
4628
+ (dir) => strategy.domainPatterns.map((dp) => `${dir}/${dp}`)
4629
+ ),
4630
+ configPatterns: subdirs.flatMap(
4631
+ (dir) => strategy.configPatterns.map((cp) => `${dir}/${cp}`)
4632
+ )
4633
+ // excludePatterns: keep global (they use ** globs)
4634
+ };
4635
+ }
4636
+ function getLanguageWeight(filePath, languageLocations) {
4637
+ if (languageLocations.length <= 1) return 0;
4638
+ const subdirToRank = /* @__PURE__ */ new Map();
4639
+ for (let rank2 = 0; rank2 < languageLocations.length; rank2++) {
4640
+ for (const subdir of languageLocations[rank2].subdirs) {
4641
+ subdirToRank.set(subdir, rank2);
4642
+ }
4643
+ }
4644
+ if (subdirToRank.size === 0) return 0;
4645
+ const firstSlash = filePath.indexOf("/");
4646
+ if (firstSlash === -1) {
4647
+ return 0;
4648
+ }
4649
+ const firstSegment = filePath.slice(0, firstSlash);
4650
+ const rank = subdirToRank.get(firstSegment);
4651
+ return rank ?? 0;
4652
+ }
4510
4653
  async function analyzeProject(dir, profile, config, options) {
4511
4654
  if (!options?.refresh) {
4512
4655
  const cache = await readCache(dir);
@@ -4516,24 +4659,31 @@ async function analyzeProject(dir, profile, config, options) {
4516
4659
  dir
4517
4660
  );
4518
4661
  if (isCacheValid(cache, currentHash)) {
4519
- return cache.analysis;
4662
+ return {
4663
+ analysis: cache.analysis,
4664
+ packedSource: cache.packedSource ?? ""
4665
+ };
4520
4666
  }
4521
4667
  }
4522
4668
  }
4523
- const baseStrategy = getStrategy(profile.language);
4524
- if (!baseStrategy) {
4669
+ const languageLocations = profile.languageLocations ?? profile.languages.map((lang) => ({ language: lang, subdirs: [] }));
4670
+ const resolvedStrategies = await Promise.all(
4671
+ languageLocations.map(async ({ language, subdirs }) => {
4672
+ const base = getStrategy(language);
4673
+ if (!base) return null;
4674
+ const enriched = await resolveStrategy(dir, base, profile.framework, profile.scripts);
4675
+ return scopeStrategyToSubdirs(enriched, subdirs);
4676
+ })
4677
+ );
4678
+ const validStrategies = resolvedStrategies.filter((s) => s !== null);
4679
+ if (validStrategies.length === 0) {
4525
4680
  throw new AnalysisError(
4526
- `No sampling strategy for language: ${profile.language ?? "unknown"}`,
4681
+ `No sampling strategy for languages: ${profile.languages.join(", ") || "none detected"}`,
4527
4682
  "no_entry_point",
4528
4683
  "Supported: Python, TypeScript, Go, Rust"
4529
4684
  );
4530
4685
  }
4531
- const strategy = await resolveStrategy(
4532
- dir,
4533
- baseStrategy,
4534
- profile.framework,
4535
- profile.scripts
4536
- );
4686
+ const strategy = mergeStrategies(validStrategies);
4537
4687
  const include = [
4538
4688
  ...strategy.entryPoints,
4539
4689
  ...strategy.domainPatterns.map((p) => p + "**/*"),
@@ -4544,7 +4694,11 @@ async function analyzeProject(dir, profile, config, options) {
4544
4694
  include,
4545
4695
  exclude: strategy.excludePatterns,
4546
4696
  maxTokens: options?.tokenBudget ?? DEFAULT_TOKEN_BUDGET,
4547
- prioritize: (filePath) => classifyFilePriority(filePath, strategy)
4697
+ prioritize: (filePath) => {
4698
+ const baseTier = classifyFilePriority(filePath, strategy);
4699
+ const langWeight = getLanguageWeight(filePath, languageLocations);
4700
+ return baseTier + langWeight * 0.1;
4701
+ }
4548
4702
  });
4549
4703
  if (packed.fileCount === 0) {
4550
4704
  throw new AnalysisError(
@@ -4582,8 +4736,8 @@ async function analyzeProject(dir, profile, config, options) {
4582
4736
  content_hash: contentHash,
4583
4737
  analyzed_at: (/* @__PURE__ */ new Date()).toISOString()
4584
4738
  };
4585
- await writeCache(dir, analysis);
4586
- return analysis;
4739
+ await writeCache(dir, analysis, packed.content);
4740
+ return { analysis, packedSource: packed.content };
4587
4741
  }
4588
4742
  function buildUserMessage6(language, profile, packedContent) {
4589
4743
  return [
@@ -4656,8 +4810,8 @@ import { Command as Command2 } from "commander";
4656
4810
  import { confirm as confirm2 } from "@inquirer/prompts";
4657
4811
  import chalk5 from "chalk";
4658
4812
  import ora from "ora";
4659
- import fs13 from "fs/promises";
4660
- import path13 from "path";
4813
+ import fs14 from "fs/promises";
4814
+ import path14 from "path";
4661
4815
  function simpleDiff(oldContent, newContent) {
4662
4816
  const oldLines = oldContent.split("\n");
4663
4817
  const newLines = newContent.split("\n");
@@ -4681,10 +4835,10 @@ async function generateDiff(spec, targetDir) {
4681
4835
  const fileMap = buildFileMap(spec);
4682
4836
  const results = [];
4683
4837
  for (const [relativePath, newContent] of fileMap) {
4684
- const absolutePath = path13.join(targetDir, relativePath);
4838
+ const absolutePath = path14.join(targetDir, relativePath);
4685
4839
  let oldContent = null;
4686
4840
  try {
4687
- oldContent = await fs13.readFile(absolutePath, "utf-8");
4841
+ oldContent = await fs14.readFile(absolutePath, "utf-8");
4688
4842
  } catch {
4689
4843
  }
4690
4844
  if (oldContent === null) {
@@ -4714,7 +4868,7 @@ function buildProfileSummary(profile) {
4714
4868
  const lines = [];
4715
4869
  lines.push(`Project: ${profile.name}`);
4716
4870
  if (profile.description) lines.push(`Description: ${profile.description}`);
4717
- if (profile.language) lines.push(`Language: ${profile.language}`);
4871
+ if (profile.languages.length > 0) lines.push(`Languages: ${profile.languages.join(", ")}`);
4718
4872
  if (profile.framework) lines.push(`Framework: ${profile.framework}`);
4719
4873
  if (profile.dependencies.length > 0) {
4720
4874
  lines.push(`Dependencies: ${profile.dependencies.join(", ")}`);
@@ -4741,7 +4895,7 @@ Existing .claude/ harness found:`);
4741
4895
  lines.push(` Agents: ${profile.existingAgents.length > 0 ? profile.existingAgents.join(", ") : "none"}`);
4742
4896
  return lines.join("\n");
4743
4897
  }
4744
- function buildOptimizeIntent(profile, analysis) {
4898
+ function buildOptimizeIntent(profile, analysis, packedSource) {
4745
4899
  const parts = [];
4746
4900
  parts.push("## Project Profile (scanned from actual codebase)\n");
4747
4901
  parts.push(buildProfileSummary(profile));
@@ -4826,6 +4980,13 @@ ${profile.existingClaudeMd}`);
4826
4980
  }
4827
4981
  }
4828
4982
  }
4983
+ if (packedSource) {
4984
+ parts.push(`
4985
+
4986
+ ## Sampled Source Code (reference for project-specific content)
4987
+
4988
+ ${packedSource}`);
4989
+ }
4829
4990
  return parts.join("\n");
4830
4991
  }
4831
4992
  var optimizeCommand;
@@ -4843,6 +5004,7 @@ var init_optimize = __esm({
4843
5004
  init_logo();
4844
5005
  init_analyze();
4845
5006
  init_types3();
5007
+ init_persist();
4846
5008
  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
5009
  printCompactBanner();
4848
5010
  const config = await loadConfig();
@@ -4855,7 +5017,7 @@ var init_optimize = __esm({
4855
5017
  const scanSpinner = ora({ text: "Scanning project...", indent: 2 }).start();
4856
5018
  const profile = await scanProject(targetDir);
4857
5019
  scanSpinner.stop();
4858
- if (profile.language) console.log(ui.kv("Language:", profile.language));
5020
+ if (profile.languages.length > 0) console.log(ui.kv("Languages:", profile.languages.join(", ")));
4859
5021
  if (profile.framework) console.log(ui.kv("Framework:", profile.framework));
4860
5022
  console.log(ui.kv("Dependencies:", String(profile.dependencies.length)));
4861
5023
  if (profile.testCommand) console.log(ui.kv("Tests:", profile.testCommand));
@@ -4866,13 +5028,19 @@ var init_optimize = __esm({
4866
5028
  console.log(ui.section("Codebase Analysis"));
4867
5029
  const analysisSpinner = ora({ text: "Analyzing source code...", indent: 2 }).start();
4868
5030
  let analysis = null;
5031
+ let packedSource = "";
4869
5032
  try {
4870
- analysis = await analyzeProject(targetDir, profile, config);
5033
+ const result = await analyzeProject(targetDir, profile, config);
5034
+ analysis = result.analysis;
5035
+ packedSource = result.packedSource;
4871
5036
  analysisSpinner.succeed("Codebase analyzed");
4872
5037
  console.log(ui.kv("Purpose:", analysis.purpose));
4873
5038
  console.log(ui.kv("Domain:", analysis.domain));
4874
5039
  console.log(ui.kv("Modules:", analysis.key_modules.map((m) => m.name).join(", ") || "none detected"));
4875
5040
  console.log(ui.kv("Workflows:", analysis.workflows.map((w) => w.name).join(", ") || "none detected"));
5041
+ if (packedSource) {
5042
+ console.log(ui.kv("Source:", `${packedSource.length.toLocaleString()} chars sampled`));
5043
+ }
4876
5044
  } catch (err) {
4877
5045
  if (err instanceof AnalysisError) {
4878
5046
  analysisSpinner.fail("Analysis failed");
@@ -4940,7 +5108,7 @@ Run \`kairn analyze\` for details.`));
4940
5108
  }
4941
5109
  }
4942
5110
  }
4943
- const intent = buildOptimizeIntent(profile, analysis);
5111
+ const intent = buildOptimizeIntent(profile, analysis, packedSource);
4944
5112
  let spec;
4945
5113
  const spinner = ora({ text: "Compiling optimized environment...", indent: 2 }).start();
4946
5114
  try {
@@ -4954,6 +5122,13 @@ Run \`kairn analyze\` for details.`));
4954
5122
  console.log(ui.errorBox("KAIRN \u2014 Error", `Optimization failed: ${msg}`));
4955
5123
  process.exit(1);
4956
5124
  }
5125
+ if (spec.ir) {
5126
+ try {
5127
+ await persistHarnessIR(targetDir, spec.ir);
5128
+ } catch {
5129
+ console.log(ui.warn("Could not persist harness IR to .kairn/harness-ir.json"));
5130
+ }
5131
+ }
4957
5132
  const registry = await loadRegistry();
4958
5133
  const summary = summarizeSpec(spec, registry);
4959
5134
  console.log("");
@@ -5034,37 +5209,37 @@ Run \`kairn analyze\` for details.`));
5034
5209
  });
5035
5210
 
5036
5211
  // src/evolve/baseline.ts
5037
- import fs20 from "fs/promises";
5038
- import path20 from "path";
5212
+ import fs21 from "fs/promises";
5213
+ import path21 from "path";
5039
5214
  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");
5215
+ const claudeDir = path21.join(projectRoot, ".claude");
5216
+ const baselineDir = path21.join(workspacePath, "baseline");
5217
+ const iter0Dir = path21.join(workspacePath, "iterations", "0", "harness");
5043
5218
  try {
5044
- await fs20.access(claudeDir);
5219
+ await fs21.access(claudeDir);
5045
5220
  } catch {
5046
5221
  throw new Error(`.claude/ directory not found in ${projectRoot}`);
5047
5222
  }
5048
5223
  await copyDir(claudeDir, baselineDir);
5049
5224
  await copyDir(claudeDir, iter0Dir);
5050
- const mcpJsonPath = path20.join(projectRoot, ".mcp.json");
5225
+ const mcpJsonPath = path21.join(projectRoot, ".mcp.json");
5051
5226
  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"));
5227
+ await fs21.access(mcpJsonPath);
5228
+ await fs21.copyFile(mcpJsonPath, path21.join(baselineDir, ".mcp.json"));
5229
+ await fs21.copyFile(mcpJsonPath, path21.join(iter0Dir, ".mcp.json"));
5055
5230
  } catch {
5056
5231
  }
5057
5232
  }
5058
5233
  async function copyDir(src, dest) {
5059
- await fs20.mkdir(dest, { recursive: true });
5060
- const entries = await fs20.readdir(src, { withFileTypes: true });
5234
+ await fs21.mkdir(dest, { recursive: true });
5235
+ const entries = await fs21.readdir(src, { withFileTypes: true });
5061
5236
  for (const entry of entries) {
5062
- const srcPath = path20.join(src, entry.name);
5063
- const destPath = path20.join(dest, entry.name);
5237
+ const srcPath = path21.join(src, entry.name);
5238
+ const destPath = path21.join(dest, entry.name);
5064
5239
  if (entry.isDirectory()) {
5065
5240
  await copyDir(srcPath, destPath);
5066
5241
  } else {
5067
- await fs20.copyFile(srcPath, destPath);
5242
+ await fs21.copyFile(srcPath, destPath);
5068
5243
  }
5069
5244
  }
5070
5245
  }
@@ -5075,32 +5250,32 @@ var init_baseline = __esm({
5075
5250
  });
5076
5251
 
5077
5252
  // src/evolve/trace.ts
5078
- import fs21 from "fs/promises";
5079
- import path21 from "path";
5253
+ import fs22 from "fs/promises";
5254
+ import path22 from "path";
5080
5255
  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"),
5256
+ const stdout = await fs22.readFile(path22.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
5257
+ const stderr = await fs22.readFile(path22.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5258
+ const filesChangedStr = await fs22.readFile(
5259
+ path22.join(traceDir, "files_changed.json"),
5085
5260
  "utf-8"
5086
5261
  ).catch(() => "{}");
5087
- const timingStr = await fs21.readFile(
5088
- path21.join(traceDir, "timing.json"),
5262
+ const timingStr = await fs22.readFile(
5263
+ path22.join(traceDir, "timing.json"),
5089
5264
  "utf-8"
5090
5265
  ).catch(() => "{}");
5091
- const scoreStr = await fs21.readFile(
5092
- path21.join(traceDir, "score.json"),
5266
+ const scoreStr = await fs22.readFile(
5267
+ path22.join(traceDir, "score.json"),
5093
5268
  "utf-8"
5094
5269
  ).catch(() => '{"pass": false}');
5095
- const toolCallsStr = await fs21.readFile(
5096
- path21.join(traceDir, "tool_calls.jsonl"),
5270
+ const toolCallsStr = await fs22.readFile(
5271
+ path22.join(traceDir, "tool_calls.jsonl"),
5097
5272
  "utf-8"
5098
5273
  ).catch(() => "");
5099
5274
  const toolCalls = toolCallsStr.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
5100
- const parentDir = path21.basename(path21.dirname(traceDir));
5275
+ const parentDir = path22.basename(path22.dirname(traceDir));
5101
5276
  const iteration = parseInt(parentDir, 10) || 0;
5102
5277
  return {
5103
- taskId: path21.basename(traceDir),
5278
+ taskId: path22.basename(traceDir),
5104
5279
  iteration,
5105
5280
  stdout,
5106
5281
  stderr,
@@ -5111,12 +5286,12 @@ async function loadTrace(traceDir) {
5111
5286
  };
5112
5287
  }
5113
5288
  async function loadIterationTraces(workspacePath, iteration) {
5114
- const tracesDir = path21.join(workspacePath, "traces", iteration.toString());
5289
+ const tracesDir = path22.join(workspacePath, "traces", iteration.toString());
5115
5290
  const traces = [];
5116
5291
  try {
5117
- const taskDirs = await fs21.readdir(tracesDir);
5292
+ const taskDirs = await fs22.readdir(tracesDir);
5118
5293
  for (const taskId of taskDirs) {
5119
- const trace = await loadTrace(path21.join(tracesDir, taskId));
5294
+ const trace = await loadTrace(path22.join(tracesDir, taskId));
5120
5295
  traces.push(trace);
5121
5296
  }
5122
5297
  } catch {
@@ -5124,39 +5299,39 @@ async function loadIterationTraces(workspacePath, iteration) {
5124
5299
  return traces;
5125
5300
  }
5126
5301
  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");
5302
+ await fs22.mkdir(traceDir, { recursive: true });
5303
+ await fs22.writeFile(path22.join(traceDir, "stdout.log"), trace.stdout, "utf-8");
5304
+ await fs22.writeFile(path22.join(traceDir, "stderr.log"), trace.stderr, "utf-8");
5130
5305
  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"),
5306
+ await fs22.writeFile(path22.join(traceDir, "tool_calls.jsonl"), toolCallsLines, "utf-8");
5307
+ await fs22.writeFile(
5308
+ path22.join(traceDir, "files_changed.json"),
5134
5309
  JSON.stringify(trace.filesChanged, null, 2),
5135
5310
  "utf-8"
5136
5311
  );
5137
- await fs21.writeFile(
5138
- path21.join(traceDir, "timing.json"),
5312
+ await fs22.writeFile(
5313
+ path22.join(traceDir, "timing.json"),
5139
5314
  JSON.stringify(trace.timing, null, 2),
5140
5315
  "utf-8"
5141
5316
  );
5142
- await fs21.writeFile(
5143
- path21.join(traceDir, "score.json"),
5317
+ await fs22.writeFile(
5318
+ path22.join(traceDir, "score.json"),
5144
5319
  JSON.stringify(trace.score, null, 2),
5145
5320
  "utf-8"
5146
5321
  );
5147
5322
  }
5148
5323
  async function writeScore(traceDir, score) {
5149
- await fs21.writeFile(
5150
- path21.join(traceDir, "score.json"),
5324
+ await fs22.writeFile(
5325
+ path22.join(traceDir, "score.json"),
5151
5326
  JSON.stringify(score, null, 2),
5152
5327
  "utf-8"
5153
5328
  );
5154
5329
  }
5155
5330
  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"),
5331
+ const iterDir = path22.join(workspacePath, "iterations", log.iteration.toString());
5332
+ await fs22.mkdir(iterDir, { recursive: true });
5333
+ await fs22.writeFile(
5334
+ path22.join(iterDir, "scores.json"),
5160
5335
  JSON.stringify({
5161
5336
  score: log.score,
5162
5337
  taskResults: log.taskResults,
@@ -5164,27 +5339,27 @@ async function writeIterationLog(workspacePath, log) {
5164
5339
  }, null, 2),
5165
5340
  "utf-8"
5166
5341
  );
5167
- await fs21.writeFile(
5168
- path21.join(iterDir, "proposer_reasoning.md"),
5342
+ await fs22.writeFile(
5343
+ path22.join(iterDir, "proposer_reasoning.md"),
5169
5344
  log.proposal?.reasoning ?? "Baseline evaluation (no proposal)",
5170
5345
  "utf-8"
5171
5346
  );
5172
- await fs21.writeFile(
5173
- path21.join(iterDir, "mutation_diff.patch"),
5347
+ await fs22.writeFile(
5348
+ path22.join(iterDir, "mutation_diff.patch"),
5174
5349
  log.diffPatch ?? "",
5175
5350
  "utf-8"
5176
5351
  );
5177
5352
  }
5178
5353
  async function loadIterationLog(workspacePath, iteration) {
5179
- const iterDir = path21.join(workspacePath, "iterations", iteration.toString());
5354
+ const iterDir = path22.join(workspacePath, "iterations", iteration.toString());
5180
5355
  try {
5181
- await fs21.access(iterDir);
5356
+ await fs22.access(iterDir);
5182
5357
  } catch {
5183
5358
  return null;
5184
5359
  }
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(() => "");
5360
+ const scoresStr = await fs22.readFile(path22.join(iterDir, "scores.json"), "utf-8").catch(() => "{}");
5361
+ const reasoning = await fs22.readFile(path22.join(iterDir, "proposer_reasoning.md"), "utf-8").catch(() => "");
5362
+ const diffPatch = await fs22.readFile(path22.join(iterDir, "mutation_diff.patch"), "utf-8").catch(() => "");
5188
5363
  const scoresData = JSON.parse(scoresStr);
5189
5364
  const proposal = reasoning ? { reasoning, mutations: [], expectedImpact: {} } : null;
5190
5365
  return {
@@ -5555,12 +5730,12 @@ Return ONLY valid JSON:
5555
5730
  // src/evolve/runner.ts
5556
5731
  import { exec as exec3, spawn } from "child_process";
5557
5732
  import { promisify as promisify3 } from "util";
5558
- import fs22 from "fs/promises";
5733
+ import fs23 from "fs/promises";
5559
5734
  import os4 from "os";
5560
- import path22 from "path";
5735
+ import path23 from "path";
5561
5736
  async function deployMcpJson(harnessPath, workDir) {
5562
- const src = path22.join(harnessPath, ".mcp.json");
5563
- await fs22.copyFile(src, path22.join(workDir, ".mcp.json")).catch(() => {
5737
+ const src = path23.join(harnessPath, ".mcp.json");
5738
+ await fs23.copyFile(src, path23.join(workDir, ".mcp.json")).catch(() => {
5564
5739
  });
5565
5740
  }
5566
5741
  async function createIsolatedWorkspace(projectRoot, harnessPath) {
@@ -5570,40 +5745,40 @@ async function createIsolatedWorkspace(projectRoot, harnessPath) {
5570
5745
  cwd: projectRoot,
5571
5746
  timeout: 5e3
5572
5747
  });
5573
- const tmpDir2 = path22.join(os4.tmpdir(), `kairn-evolve-wt-${suffix}`);
5748
+ const tmpDir2 = path23.join(os4.tmpdir(), `kairn-evolve-wt-${suffix}`);
5574
5749
  await execAsync3(`git worktree add --detach "${tmpDir2}" HEAD`, {
5575
5750
  cwd: projectRoot,
5576
5751
  timeout: 3e4
5577
5752
  });
5578
- await fs22.rm(path22.join(tmpDir2, ".claude"), { recursive: true, force: true });
5579
- await copyDir(harnessPath, path22.join(tmpDir2, ".claude"));
5753
+ await fs23.rm(path23.join(tmpDir2, ".claude"), { recursive: true, force: true });
5754
+ await copyDir(harnessPath, path23.join(tmpDir2, ".claude"));
5580
5755
  await deployMcpJson(harnessPath, tmpDir2);
5581
5756
  return { workDir: tmpDir2, isWorktree: true };
5582
5757
  } catch {
5583
5758
  }
5584
- const tmpDir = await fs22.mkdtemp(path22.join(os4.tmpdir(), `kairn-evolve-cp-`));
5759
+ const tmpDir = await fs23.mkdtemp(path23.join(os4.tmpdir(), `kairn-evolve-cp-`));
5585
5760
  await copyProjectDir(projectRoot, tmpDir);
5586
- await fs22.rm(path22.join(tmpDir, ".claude"), { recursive: true, force: true });
5587
- await copyDir(harnessPath, path22.join(tmpDir, ".claude"));
5761
+ await fs23.rm(path23.join(tmpDir, ".claude"), { recursive: true, force: true });
5762
+ await copyDir(harnessPath, path23.join(tmpDir, ".claude"));
5588
5763
  await deployMcpJson(harnessPath, tmpDir);
5589
5764
  return { workDir: tmpDir, isWorktree: false };
5590
5765
  }
5591
5766
  async function copyProjectDir(src, dest) {
5592
- await fs22.mkdir(dest, { recursive: true });
5767
+ await fs23.mkdir(dest, { recursive: true });
5593
5768
  let entries;
5594
5769
  try {
5595
- entries = await fs22.readdir(src, { withFileTypes: true });
5770
+ entries = await fs23.readdir(src, { withFileTypes: true });
5596
5771
  } catch {
5597
5772
  return;
5598
5773
  }
5599
5774
  for (const entry of entries) {
5600
5775
  if (COPY_SKIP_DIRS.has(entry.name)) continue;
5601
- const srcPath = path22.join(src, entry.name);
5602
- const destPath = path22.join(dest, entry.name);
5776
+ const srcPath = path23.join(src, entry.name);
5777
+ const destPath = path23.join(dest, entry.name);
5603
5778
  if (entry.isDirectory()) {
5604
5779
  await copyDir(srcPath, destPath);
5605
5780
  } else {
5606
- await fs22.copyFile(srcPath, destPath);
5781
+ await fs23.copyFile(srcPath, destPath);
5607
5782
  }
5608
5783
  }
5609
5784
  }
@@ -5615,7 +5790,7 @@ async function cleanupIsolatedWorkspace(workDir, isWorktree, projectRoot) {
5615
5790
  timeout: 1e4
5616
5791
  });
5617
5792
  } catch {
5618
- await fs22.rm(workDir, { recursive: true, force: true }).catch(() => {
5793
+ await fs23.rm(workDir, { recursive: true, force: true }).catch(() => {
5619
5794
  });
5620
5795
  await execAsync3("git worktree prune", {
5621
5796
  cwd: projectRoot,
@@ -5624,12 +5799,12 @@ async function cleanupIsolatedWorkspace(workDir, isWorktree, projectRoot) {
5624
5799
  });
5625
5800
  }
5626
5801
  } else {
5627
- await fs22.rm(workDir, { recursive: true, force: true }).catch(() => {
5802
+ await fs23.rm(workDir, { recursive: true, force: true }).catch(() => {
5628
5803
  });
5629
5804
  }
5630
5805
  }
5631
5806
  async function runTask(task, harnessPath, traceDir, iteration, projectRoot) {
5632
- await fs22.mkdir(traceDir, { recursive: true });
5807
+ await fs23.mkdir(traceDir, { recursive: true });
5633
5808
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5634
5809
  const startMs = Date.now();
5635
5810
  const root = projectRoot ?? process.cwd();
@@ -5709,13 +5884,13 @@ async function snapshotFileList(dir) {
5709
5884
  async function walk(current) {
5710
5885
  let entries;
5711
5886
  try {
5712
- entries = await fs22.readdir(current, { withFileTypes: true });
5887
+ entries = await fs23.readdir(current, { withFileTypes: true });
5713
5888
  } catch {
5714
5889
  return;
5715
5890
  }
5716
5891
  for (const entry of entries) {
5717
- const fullPath = path22.join(current, entry.name);
5718
- const relativePath = path22.relative(dir, fullPath);
5892
+ const fullPath = path23.join(current, entry.name);
5893
+ const relativePath = path23.relative(dir, fullPath);
5719
5894
  if (relativePath.startsWith(".claude")) continue;
5720
5895
  if (relativePath.startsWith("node_modules")) continue;
5721
5896
  if (relativePath.startsWith(".git")) continue;
@@ -5723,7 +5898,7 @@ async function snapshotFileList(dir) {
5723
5898
  await walk(fullPath);
5724
5899
  } else {
5725
5900
  try {
5726
- const stat = await fs22.stat(fullPath);
5901
+ const stat = await fs23.stat(fullPath);
5727
5902
  result[relativePath] = stat.mtimeMs;
5728
5903
  } catch {
5729
5904
  }
@@ -5802,7 +5977,7 @@ function computeStddev(values, mean) {
5802
5977
  }
5803
5978
  async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config, onProgress, runsPerTask = 1, parallelTasks = 1) {
5804
5979
  const results = {};
5805
- const projectRoot = path22.resolve(workspacePath, "..");
5980
+ const projectRoot = path23.resolve(workspacePath, "..");
5806
5981
  const effectiveRuns = Math.max(1, runsPerTask);
5807
5982
  const concurrency = Math.max(1, parallelTasks);
5808
5983
  const evaluateTask = async (task) => {
@@ -5812,7 +5987,7 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5812
5987
  const runScores = [];
5813
5988
  let passCount = 0;
5814
5989
  for (let run = 0; run < effectiveRuns; run++) {
5815
- const traceDir = path22.join(
5990
+ const traceDir = path23.join(
5816
5991
  workspacePath,
5817
5992
  "traces",
5818
5993
  iteration.toString(),
@@ -5825,8 +6000,8 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5825
6000
  message: `Run ${run + 1}/${effectiveRuns} of ${task.id}`
5826
6001
  });
5827
6002
  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(() => "");
6003
+ const stdout = await fs23.readFile(path23.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
6004
+ const stderr = await fs23.readFile(path23.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5830
6005
  const score = await scoreTask(task, traceDir, stdout, stderr, config);
5831
6006
  await writeScore(traceDir, score);
5832
6007
  runScores.push(score.score ?? (score.pass ? 100 : 0));
@@ -5846,7 +6021,7 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5846
6021
  }
5847
6022
  };
5848
6023
  } else {
5849
- const traceDir = path22.join(
6024
+ const traceDir = path23.join(
5850
6025
  workspacePath,
5851
6026
  "traces",
5852
6027
  iteration.toString(),
@@ -5855,8 +6030,8 @@ async function evaluateAll(tasks, harnessPath, workspacePath, iteration, config,
5855
6030
  const taskResult = await runTask(task, harnessPath, traceDir, iteration, projectRoot);
5856
6031
  finalScore = taskResult.score;
5857
6032
  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(() => "");
6033
+ const stdout = await fs23.readFile(path23.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
6034
+ const stderr = await fs23.readFile(path23.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
5860
6035
  finalScore = await scoreTask(task, traceDir, stdout, stderr, config);
5861
6036
  await writeScore(traceDir, finalScore);
5862
6037
  }
@@ -5904,12 +6079,12 @@ __export(memory_exports, {
5904
6079
  loadProposerMemory: () => loadProposerMemory,
5905
6080
  saveRunSummary: () => saveRunSummary
5906
6081
  });
5907
- import fs23 from "fs/promises";
5908
- import path23 from "path";
6082
+ import fs24 from "fs/promises";
6083
+ import path24 from "path";
5909
6084
  async function loadProposerMemory(workspacePath) {
5910
- const memoryPath = path23.join(workspacePath, MEMORY_FILE);
6085
+ const memoryPath = path24.join(workspacePath, MEMORY_FILE);
5911
6086
  try {
5912
- const raw = await fs23.readFile(memoryPath, "utf-8");
6087
+ const raw = await fs24.readFile(memoryPath, "utf-8");
5913
6088
  const parsed = JSON.parse(raw);
5914
6089
  if (Array.isArray(parsed)) return parsed;
5915
6090
  return [];
@@ -5948,8 +6123,8 @@ async function saveRunSummary(workspacePath, summary) {
5948
6123
  const existing = await loadProposerMemory(workspacePath);
5949
6124
  existing.push(summary);
5950
6125
  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");
6126
+ const memoryPath = path24.join(workspacePath, MEMORY_FILE);
6127
+ await fs24.writeFile(memoryPath, JSON.stringify(trimmed, null, 2), "utf-8");
5953
6128
  }
5954
6129
  function formatMemoryForProposer(memory) {
5955
6130
  if (memory.length === 0) return "";
@@ -5994,26 +6169,26 @@ __export(knowledge_exports, {
5994
6169
  savePattern: () => savePattern,
5995
6170
  saveProjectHistory: () => saveProjectHistory
5996
6171
  });
5997
- import fs24 from "fs/promises";
5998
- import path24 from "path";
6172
+ import fs25 from "fs/promises";
6173
+ import path25 from "path";
5999
6174
  import crypto2 from "crypto";
6000
6175
  function getKnowledgeDir() {
6001
- return path24.join(getKairnDir(), "knowledge");
6176
+ return path25.join(getKairnDir(), "knowledge");
6002
6177
  }
6003
6178
  function getPatternsPath() {
6004
- return path24.join(getKnowledgeDir(), "patterns.jsonl");
6179
+ return path25.join(getKnowledgeDir(), "patterns.jsonl");
6005
6180
  }
6006
6181
  function getProjectsDir() {
6007
- return path24.join(getKnowledgeDir(), "projects");
6182
+ return path25.join(getKnowledgeDir(), "projects");
6008
6183
  }
6009
6184
  function getConvergencePath() {
6010
- return path24.join(getKnowledgeDir(), "convergence.json");
6185
+ return path25.join(getKnowledgeDir(), "convergence.json");
6011
6186
  }
6012
6187
  async function loadKnowledgeBase(filter) {
6013
6188
  const patternsPath = getPatternsPath();
6014
6189
  let content;
6015
6190
  try {
6016
- content = await fs24.readFile(patternsPath, "utf-8");
6191
+ content = await fs25.readFile(patternsPath, "utf-8");
6017
6192
  } catch {
6018
6193
  return [];
6019
6194
  }
@@ -6033,9 +6208,9 @@ async function loadKnowledgeBase(filter) {
6033
6208
  }
6034
6209
  async function savePattern(pattern) {
6035
6210
  const dir = getKnowledgeDir();
6036
- await fs24.mkdir(dir, { recursive: true });
6211
+ await fs25.mkdir(dir, { recursive: true });
6037
6212
  const patternsPath = getPatternsPath();
6038
- await fs24.appendFile(patternsPath, JSON.stringify(pattern) + "\n", "utf-8");
6213
+ await fs25.appendFile(patternsPath, JSON.stringify(pattern) + "\n", "utf-8");
6039
6214
  }
6040
6215
  async function extractAndSavePatterns(history, projectName, language) {
6041
6216
  const patterns = [];
@@ -6107,13 +6282,13 @@ function formatKnowledgeForArchitect(patterns, language) {
6107
6282
  }
6108
6283
  async function saveProjectHistory(projectName, summary) {
6109
6284
  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");
6285
+ await fs25.mkdir(projectsDir, { recursive: true });
6286
+ const filePath = path25.join(projectsDir, `${projectName}.json`);
6287
+ await fs25.writeFile(filePath, JSON.stringify(summary, null, 2), "utf-8");
6113
6288
  }
6114
6289
  async function loadConvergence() {
6115
6290
  try {
6116
- const content = await fs24.readFile(getConvergencePath(), "utf-8");
6291
+ const content = await fs25.readFile(getConvergencePath(), "utf-8");
6117
6292
  return JSON.parse(content);
6118
6293
  } catch {
6119
6294
  return null;
@@ -6121,8 +6296,8 @@ async function loadConvergence() {
6121
6296
  }
6122
6297
  async function saveConvergence(convergence) {
6123
6298
  const dir = getKnowledgeDir();
6124
- await fs24.mkdir(dir, { recursive: true });
6125
- await fs24.writeFile(
6299
+ await fs25.mkdir(dir, { recursive: true });
6300
+ await fs25.writeFile(
6126
6301
  getConvergencePath(),
6127
6302
  JSON.stringify(convergence, null, 2),
6128
6303
  "utf-8"
@@ -6136,25 +6311,112 @@ var init_knowledge = __esm({
6136
6311
  });
6137
6312
 
6138
6313
  // src/evolve/proposer.ts
6139
- import fs25 from "fs/promises";
6140
- import path25 from "path";
6314
+ import fs26 from "fs/promises";
6315
+ import path26 from "path";
6316
+ function countSettingsHooks(settings) {
6317
+ let count = 0;
6318
+ const categories = settings.hooks;
6319
+ for (const key of Object.keys(categories)) {
6320
+ const entries = categories[key];
6321
+ if (entries) {
6322
+ count += entries.length;
6323
+ }
6324
+ }
6325
+ return count;
6326
+ }
6327
+ function buildIRSummary(ir) {
6328
+ const lines = [];
6329
+ lines.push("## Harness Structure (IR)");
6330
+ const meta = ir.meta;
6331
+ const techParts = [meta.techStack.language];
6332
+ if (meta.techStack.framework) techParts.push(meta.techStack.framework);
6333
+ if (meta.techStack.buildTool) techParts.push(meta.techStack.buildTool);
6334
+ lines.push(`Project: ${meta.name || "(unnamed)"} \u2014 ${techParts.join(", ")}`);
6335
+ const sectionIds = ir.sections.map((s) => s.id);
6336
+ lines.push(`Sections (${ir.sections.length}): ${sectionIds.join(", ") || "none"}`);
6337
+ const commandSummaries = ir.commands.map((c) => c.name);
6338
+ lines.push(`Commands (${ir.commands.length}): ${commandSummaries.join(", ") || "none"}`);
6339
+ const ruleSummaries = ir.rules.map((r) => {
6340
+ const pathHint = r.paths && r.paths.length > 0 ? ` (${r.paths.join(", ")})` : "";
6341
+ return `${r.name}${pathHint}`;
6342
+ });
6343
+ lines.push(`Rules (${ir.rules.length}): ${ruleSummaries.join(", ") || "none"}`);
6344
+ const agentNames = ir.agents.map((a) => a.name);
6345
+ lines.push(`Agents (${ir.agents.length}): ${agentNames.join(", ") || "none"}`);
6346
+ const serverNames = ir.mcpServers.map((s) => s.id);
6347
+ lines.push(`MCP Servers (${ir.mcpServers.length}): ${serverNames.join(", ") || "none"}`);
6348
+ if (ir.skills.length > 0) {
6349
+ const skillNames = ir.skills.map((s) => s.name);
6350
+ lines.push(`Skills (${ir.skills.length}): ${skillNames.join(", ")}`);
6351
+ }
6352
+ if (ir.docs.length > 0) {
6353
+ const docNames = ir.docs.map((d) => d.name);
6354
+ lines.push(`Docs (${ir.docs.length}): ${docNames.join(", ")}`);
6355
+ }
6356
+ if (ir.hooks.length > 0) {
6357
+ const hookNames = ir.hooks.map((h) => h.name);
6358
+ lines.push(`Hooks (${ir.hooks.length}): ${hookNames.join(", ")}`);
6359
+ }
6360
+ const settingsParts = [];
6361
+ if (ir.settings.statusLine) {
6362
+ settingsParts.push("statusLine=enabled");
6363
+ }
6364
+ if (ir.settings.denyPatterns && ir.settings.denyPatterns.length > 0) {
6365
+ settingsParts.push(`denyPatterns=${ir.settings.denyPatterns.length}`);
6366
+ }
6367
+ const hooksCount = countSettingsHooks(ir.settings);
6368
+ if (hooksCount > 0) {
6369
+ settingsParts.push(`hooks=${hooksCount}`);
6370
+ }
6371
+ lines.push(`Settings: ${settingsParts.join(", ") || "default"}`);
6372
+ return lines.join("\n");
6373
+ }
6374
+ function formatAnalysisForProposer(analysis) {
6375
+ const lines = [];
6376
+ lines.push(`Purpose: ${analysis.purpose}`);
6377
+ lines.push(`Domain: ${analysis.domain}`);
6378
+ lines.push(`Architecture: ${analysis.architecture_style}`);
6379
+ lines.push(`Deployment: ${analysis.deployment_model}`);
6380
+ if (analysis.key_modules.length > 0) {
6381
+ lines.push("");
6382
+ lines.push("Key Modules:");
6383
+ for (const mod of analysis.key_modules) {
6384
+ lines.push(`- ${mod.name} (${mod.path}): ${mod.description}`);
6385
+ }
6386
+ }
6387
+ if (analysis.workflows.length > 0) {
6388
+ lines.push("");
6389
+ lines.push("Workflows:");
6390
+ for (const wf of analysis.workflows) {
6391
+ lines.push(`- ${wf.name}: ${wf.description} (trigger: ${wf.trigger})`);
6392
+ }
6393
+ }
6394
+ if (analysis.config_keys.length > 0) {
6395
+ lines.push("");
6396
+ lines.push("Config Keys:");
6397
+ for (const key of analysis.config_keys) {
6398
+ lines.push(`- ${key.name}: ${key.purpose}`);
6399
+ }
6400
+ }
6401
+ return lines.join("\n");
6402
+ }
6141
6403
  async function readHarnessFiles(harnessPath) {
6142
6404
  const result = {};
6143
6405
  async function walk(dir, prefix) {
6144
6406
  let entries;
6145
6407
  try {
6146
- entries = await fs25.readdir(dir, { withFileTypes: true });
6408
+ entries = await fs26.readdir(dir, { withFileTypes: true });
6147
6409
  } catch {
6148
6410
  return;
6149
6411
  }
6150
6412
  for (const entry of entries) {
6151
- const relativePath = prefix ? path25.join(prefix, entry.name) : entry.name;
6152
- const fullPath = path25.join(dir, entry.name);
6413
+ const relativePath = prefix ? path26.join(prefix, entry.name) : entry.name;
6414
+ const fullPath = path26.join(dir, entry.name);
6153
6415
  if (entry.isDirectory()) {
6154
6416
  await walk(fullPath, relativePath);
6155
6417
  } else if (entry.isFile()) {
6156
6418
  try {
6157
- result[relativePath] = await fs25.readFile(fullPath, "utf-8");
6419
+ result[relativePath] = await fs26.readFile(fullPath, "utf-8");
6158
6420
  } catch {
6159
6421
  }
6160
6422
  }
@@ -6170,7 +6432,7 @@ function truncateStdout(stdout, limit) {
6170
6432
  return `[...truncated, showing last ${limit} chars...]
6171
6433
  ${stdout.slice(-limit)}`;
6172
6434
  }
6173
- function buildProposerUserMessage(harnessFiles, traces, tasks, history, memorySection) {
6435
+ function buildProposerUserMessage(harnessFiles, traces, tasks, history, memorySection, projectContext) {
6174
6436
  const harnessSection = ["## Current Harness Files\n"];
6175
6437
  const fileEntries = Object.entries(harnessFiles);
6176
6438
  if (fileEntries.length === 0) {
@@ -6197,7 +6459,24 @@ ${content}
6197
6459
  );
6198
6460
  }
6199
6461
  }
6200
- const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n");
6462
+ let projectSection = "";
6463
+ if (projectContext) {
6464
+ const parts = ["## Project Understanding\n"];
6465
+ parts.push("### Analysis Summary");
6466
+ parts.push(formatAnalysisForProposer(projectContext.analysis));
6467
+ parts.push("");
6468
+ parts.push("### Harness Structure");
6469
+ parts.push(projectContext.irSummary);
6470
+ if (projectContext.keySourceFiles) {
6471
+ parts.push("");
6472
+ parts.push("### Key Source Files");
6473
+ parts.push("```");
6474
+ parts.push(projectContext.keySourceFiles);
6475
+ parts.push("```");
6476
+ }
6477
+ projectSection = "\n" + parts.join("\n") + "\n";
6478
+ }
6479
+ const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + projectSection;
6201
6480
  const remainingBudget = MAX_CONTEXT_CHARS - fixedContent.length;
6202
6481
  if (remainingBudget <= 0) {
6203
6482
  return fixedContent + "\n\n[...traces and history omitted \u2014 harness + tasks fill context budget...]";
@@ -6359,7 +6638,7 @@ function parseProposerResponse(raw) {
6359
6638
  expectedImpact
6360
6639
  };
6361
6640
  }
6362
- async function propose(iteration, workspacePath, harnessPath, history, tasks, config, proposerModel) {
6641
+ async function propose(iteration, workspacePath, harnessPath, history, tasks, config, proposerModel, projectContext) {
6363
6642
  const harnessFiles = await readHarnessFiles(harnessPath);
6364
6643
  const traces = await loadIterationTraces(workspacePath, iteration);
6365
6644
  const { loadProposerMemory: loadProposerMemory2, formatMemoryForProposer: formatMemoryForProposer2 } = await Promise.resolve().then(() => (init_memory(), memory_exports));
@@ -6373,7 +6652,7 @@ async function propose(iteration, workspacePath, harnessPath, history, tasks, co
6373
6652
  } catch {
6374
6653
  }
6375
6654
  const combinedMemory = memorySection + (knowledgeSection ? "\n" + knowledgeSection : "");
6376
- const userMessage = buildProposerUserMessage(harnessFiles, traces, tasks, history, combinedMemory || void 0);
6655
+ const userMessage = buildProposerUserMessage(harnessFiles, traces, tasks, history, combinedMemory || void 0, projectContext);
6377
6656
  const proposerConfig = { ...config, model: proposerModel };
6378
6657
  const response = await callLLM(proposerConfig, userMessage, {
6379
6658
  systemPrompt: PROPOSER_SYSTEM_PROMPT,
@@ -6460,8 +6739,8 @@ Return ONLY valid JSON.`;
6460
6739
  });
6461
6740
 
6462
6741
  // src/ir/parser.ts
6463
- import fs26 from "fs/promises";
6464
- import path26 from "path";
6742
+ import fs27 from "fs/promises";
6743
+ import path27 from "path";
6465
6744
  function slugify(heading) {
6466
6745
  return heading.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
6467
6746
  }
@@ -6723,33 +7002,33 @@ function parseMcpConfig(content) {
6723
7002
  }
6724
7003
  async function readDirSafe(dirPath) {
6725
7004
  try {
6726
- return await fs26.readdir(dirPath);
7005
+ return await fs27.readdir(dirPath);
6727
7006
  } catch {
6728
7007
  return [];
6729
7008
  }
6730
7009
  }
6731
7010
  async function readFileSafe2(filePath) {
6732
7011
  try {
6733
- return await fs26.readFile(filePath, "utf-8");
7012
+ return await fs27.readFile(filePath, "utf-8");
6734
7013
  } catch {
6735
7014
  return null;
6736
7015
  }
6737
7016
  }
6738
7017
  async function isDirectory(filePath) {
6739
7018
  try {
6740
- const stat = await fs26.stat(filePath);
7019
+ const stat = await fs27.stat(filePath);
6741
7020
  return stat.isDirectory();
6742
7021
  } catch {
6743
7022
  return false;
6744
7023
  }
6745
7024
  }
6746
7025
  async function parseCommands(harnessPath) {
6747
- const dirPath = path26.join(harnessPath, "commands");
7026
+ const dirPath = path27.join(harnessPath, "commands");
6748
7027
  const entries = await readDirSafe(dirPath);
6749
7028
  const nodes = [];
6750
7029
  for (const entry of entries) {
6751
7030
  if (!entry.endsWith(".md")) continue;
6752
- const filePath = path26.join(dirPath, entry);
7031
+ const filePath = path27.join(dirPath, entry);
6753
7032
  const content = await readFileSafe2(filePath);
6754
7033
  if (content === null) continue;
6755
7034
  const name = entry.replace(/\.md$/, "");
@@ -6760,12 +7039,12 @@ async function parseCommands(harnessPath) {
6760
7039
  return nodes;
6761
7040
  }
6762
7041
  async function parseRules(harnessPath) {
6763
- const dirPath = path26.join(harnessPath, "rules");
7042
+ const dirPath = path27.join(harnessPath, "rules");
6764
7043
  const entries = await readDirSafe(dirPath);
6765
7044
  const nodes = [];
6766
7045
  for (const entry of entries) {
6767
7046
  if (!entry.endsWith(".md")) continue;
6768
- const filePath = path26.join(dirPath, entry);
7047
+ const filePath = path27.join(dirPath, entry);
6769
7048
  const rawContent = await readFileSafe2(filePath);
6770
7049
  if (rawContent === null) continue;
6771
7050
  const name = entry.replace(/\.md$/, "");
@@ -6780,12 +7059,12 @@ async function parseRules(harnessPath) {
6780
7059
  return nodes;
6781
7060
  }
6782
7061
  async function parseAgents(harnessPath) {
6783
- const dirPath = path26.join(harnessPath, "agents");
7062
+ const dirPath = path27.join(harnessPath, "agents");
6784
7063
  const entries = await readDirSafe(dirPath);
6785
7064
  const nodes = [];
6786
7065
  for (const entry of entries) {
6787
7066
  if (!entry.endsWith(".md")) continue;
6788
- const filePath = path26.join(dirPath, entry);
7067
+ const filePath = path27.join(dirPath, entry);
6789
7068
  const rawContent = await readFileSafe2(filePath);
6790
7069
  if (rawContent === null) continue;
6791
7070
  const fileBaseName = entry.replace(/\.md$/, "");
@@ -6829,20 +7108,20 @@ async function parseAgents(harnessPath) {
6829
7108
  return nodes;
6830
7109
  }
6831
7110
  async function parseSkills(harnessPath) {
6832
- const dirPath = path26.join(harnessPath, "skills");
7111
+ const dirPath = path27.join(harnessPath, "skills");
6833
7112
  const entries = await readDirSafe(dirPath);
6834
7113
  const nodes = [];
6835
7114
  for (const entry of entries) {
6836
- const entryPath = path26.join(dirPath, entry);
7115
+ const entryPath = path27.join(dirPath, entry);
6837
7116
  if (entry.endsWith(".md")) {
6838
7117
  const content = await readFileSafe2(entryPath);
6839
7118
  if (content === null) continue;
6840
7119
  const name = entry.replace(/\.md$/, "");
6841
7120
  nodes.push({ name, content });
6842
7121
  } else if (await isDirectory(entryPath)) {
6843
- let content = await readFileSafe2(path26.join(entryPath, "skill.md"));
7122
+ let content = await readFileSafe2(path27.join(entryPath, "skill.md"));
6844
7123
  if (content === null) {
6845
- content = await readFileSafe2(path26.join(entryPath, "SKILL.md"));
7124
+ content = await readFileSafe2(path27.join(entryPath, "SKILL.md"));
6846
7125
  }
6847
7126
  if (content === null) continue;
6848
7127
  nodes.push({ name: entry, content });
@@ -6851,12 +7130,12 @@ async function parseSkills(harnessPath) {
6851
7130
  return nodes;
6852
7131
  }
6853
7132
  async function parseDocs(harnessPath) {
6854
- const dirPath = path26.join(harnessPath, "docs");
7133
+ const dirPath = path27.join(harnessPath, "docs");
6855
7134
  const entries = await readDirSafe(dirPath);
6856
7135
  const nodes = [];
6857
7136
  for (const entry of entries) {
6858
7137
  if (!entry.endsWith(".md")) continue;
6859
- const filePath = path26.join(dirPath, entry);
7138
+ const filePath = path27.join(dirPath, entry);
6860
7139
  const content = await readFileSafe2(filePath);
6861
7140
  if (content === null) continue;
6862
7141
  const name = entry.replace(/\.md$/, "");
@@ -6865,12 +7144,12 @@ async function parseDocs(harnessPath) {
6865
7144
  return nodes;
6866
7145
  }
6867
7146
  async function parseHooks(harnessPath) {
6868
- const dirPath = path26.join(harnessPath, "hooks");
7147
+ const dirPath = path27.join(harnessPath, "hooks");
6869
7148
  const entries = await readDirSafe(dirPath);
6870
7149
  const nodes = [];
6871
7150
  for (const entry of entries) {
6872
7151
  if (!entry.endsWith(".mjs")) continue;
6873
- const filePath = path26.join(dirPath, entry);
7152
+ const filePath = path27.join(dirPath, entry);
6874
7153
  const content = await readFileSafe2(filePath);
6875
7154
  if (content === null) continue;
6876
7155
  const name = entry.replace(/\.mjs$/, "");
@@ -6881,7 +7160,7 @@ async function parseHooks(harnessPath) {
6881
7160
  async function parseHarness(harnessPath) {
6882
7161
  const ir = createEmptyIR();
6883
7162
  const claudeMdContent = await readFileSafe2(
6884
- path26.join(harnessPath, "CLAUDE.md")
7163
+ path27.join(harnessPath, "CLAUDE.md")
6885
7164
  );
6886
7165
  if (claudeMdContent !== null) {
6887
7166
  const { meta, sections } = parseClaudeMd(claudeMdContent);
@@ -6893,7 +7172,7 @@ async function parseHarness(harnessPath) {
6893
7172
  ir.sections = sections;
6894
7173
  }
6895
7174
  const settingsContent = await readFileSafe2(
6896
- path26.join(harnessPath, "settings.json")
7175
+ path27.join(harnessPath, "settings.json")
6897
7176
  );
6898
7177
  if (settingsContent !== null) {
6899
7178
  ir.settings = parseSettings(settingsContent);
@@ -6914,7 +7193,7 @@ async function parseHarness(harnessPath) {
6914
7193
  ir.hooks = hooks;
6915
7194
  const mcpServers = [];
6916
7195
  const seenIds = /* @__PURE__ */ new Set();
6917
- const parentMcpPath = path26.join(path26.dirname(harnessPath), ".mcp.json");
7196
+ const parentMcpPath = path27.join(path27.dirname(harnessPath), ".mcp.json");
6918
7197
  const parentMcpContent = await readFileSafe2(parentMcpPath);
6919
7198
  if (parentMcpContent !== null) {
6920
7199
  for (const node of parseMcpConfig(parentMcpContent)) {
@@ -6924,7 +7203,7 @@ async function parseHarness(harnessPath) {
6924
7203
  }
6925
7204
  }
6926
7205
  }
6927
- const innerMcpPath = path26.join(harnessPath, ".mcp.json");
7206
+ const innerMcpPath = path27.join(harnessPath, ".mcp.json");
6928
7207
  const innerMcpContent = await readFileSafe2(innerMcpPath);
6929
7208
  if (innerMcpContent !== null) {
6930
7209
  for (const node of parseMcpConfig(innerMcpContent)) {
@@ -7161,8 +7440,8 @@ function deepSet(obj, segments, value) {
7161
7440
  const child = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
7162
7441
  return { ...obj, [head]: deepSet(child, rest, value) };
7163
7442
  }
7164
- function applySettingsUpdate(settings, path37, value) {
7165
- const segments = path37.split(".");
7443
+ function applySettingsUpdate(settings, path39, value) {
7444
+ const segments = path39.split(".");
7166
7445
  const topKey = segments[0];
7167
7446
  if (STRUCTURED_SETTINGS_KEYS.has(topKey)) {
7168
7447
  const settingsRecord = { ...settings };
@@ -7620,10 +7899,10 @@ var init_diff = __esm({
7620
7899
  });
7621
7900
 
7622
7901
  // src/evolve/mutator.ts
7623
- import fs27 from "fs/promises";
7624
- import path27 from "path";
7902
+ import fs28 from "fs/promises";
7903
+ import path28 from "path";
7625
7904
  async function applyMutations(currentHarnessPath, nextIterationDir, mutations) {
7626
- const newHarnessPath = path27.join(nextIterationDir, "harness");
7905
+ const newHarnessPath = path28.join(nextIterationDir, "harness");
7627
7906
  let baselineIR = null;
7628
7907
  try {
7629
7908
  baselineIR = await parseHarness(currentHarnessPath);
@@ -7664,6 +7943,11 @@ async function applyMutationsViaIR(currentHarnessPath, newHarnessPath, mutations
7664
7943
  for (const mutation of rawTextMutations) {
7665
7944
  await applyLegacyMutation(newHarnessPath, mutation);
7666
7945
  }
7946
+ await fs28.writeFile(
7947
+ path28.join(newHarnessPath, "..", "harness-ir.json"),
7948
+ JSON.stringify(currentIR, null, 2),
7949
+ "utf-8"
7950
+ );
7667
7951
  const irDiff = diffIR(baselineIR, currentIR);
7668
7952
  let diffPatch = formatIRDiff(irDiff);
7669
7953
  if (diffPatch === "No changes." && rawTextMutations.length > 0) {
@@ -7700,9 +7984,9 @@ async function renderAffectedFiles(ir, targetDir, touchedCategories) {
7700
7984
  for (const [relativePath, content] of fileMap) {
7701
7985
  const category = getFileCategory(relativePath);
7702
7986
  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");
7987
+ const fullPath = path28.join(targetDir, relativePath);
7988
+ await fs28.mkdir(path28.dirname(fullPath), { recursive: true });
7989
+ await fs28.writeFile(fullPath, content, "utf-8");
7706
7990
  }
7707
7991
  }
7708
7992
  if (touchedCategories.has("commands")) {
@@ -7715,11 +7999,11 @@ async function renderAffectedFiles(ir, targetDir, touchedCategories) {
7715
7999
  await deleteOrphanedFiles(targetDir, "agents", fileMap);
7716
8000
  }
7717
8001
  if (touchedCategories.has("mcp") && !fileMap.has(".mcp.json")) {
7718
- await fs27.unlink(path27.join(targetDir, ".mcp.json")).catch(() => {
8002
+ await fs28.unlink(path28.join(targetDir, ".mcp.json")).catch(() => {
7719
8003
  });
7720
8004
  }
7721
8005
  if (touchedCategories.has("settings") && !fileMap.has("settings.json")) {
7722
- await fs27.unlink(path27.join(targetDir, "settings.json")).catch(() => {
8006
+ await fs28.unlink(path28.join(targetDir, "settings.json")).catch(() => {
7723
8007
  });
7724
8008
  }
7725
8009
  }
@@ -7736,17 +8020,17 @@ function getFileCategory(relativePath) {
7736
8020
  return "unknown";
7737
8021
  }
7738
8022
  async function deleteOrphanedFiles(targetDir, subdir, renderedMap) {
7739
- const subdirPath = path27.join(targetDir, subdir);
8023
+ const subdirPath = path28.join(targetDir, subdir);
7740
8024
  let entries;
7741
8025
  try {
7742
- entries = await fs27.readdir(subdirPath);
8026
+ entries = await fs28.readdir(subdirPath);
7743
8027
  } catch {
7744
8028
  return;
7745
8029
  }
7746
8030
  for (const entry of entries) {
7747
8031
  const relativePath = `${subdir}/${entry}`;
7748
8032
  if (!renderedMap.has(relativePath)) {
7749
- await fs27.unlink(path27.join(subdirPath, entry)).catch(() => {
8033
+ await fs28.unlink(path28.join(subdirPath, entry)).catch(() => {
7750
8034
  });
7751
8035
  }
7752
8036
  }
@@ -7763,56 +8047,56 @@ async function applyLegacyMutation(harnessPath, mutation) {
7763
8047
  if (mutation.file.includes("..")) {
7764
8048
  return;
7765
8049
  }
7766
- const filePath = path27.join(harnessPath, mutation.file);
8050
+ const filePath = path28.join(harnessPath, mutation.file);
7767
8051
  if (mutation.action === "replace") {
7768
8052
  if (!mutation.oldText) {
7769
8053
  return;
7770
8054
  }
7771
8055
  let content;
7772
8056
  try {
7773
- content = await fs27.readFile(filePath, "utf-8");
8057
+ content = await fs28.readFile(filePath, "utf-8");
7774
8058
  } catch {
7775
8059
  return;
7776
8060
  }
7777
8061
  if (!content.includes(mutation.oldText)) {
7778
8062
  return;
7779
8063
  }
7780
- await fs27.writeFile(
8064
+ await fs28.writeFile(
7781
8065
  filePath,
7782
8066
  content.replace(mutation.oldText, mutation.newText),
7783
8067
  "utf-8"
7784
8068
  );
7785
8069
  } else if (mutation.action === "add_section") {
7786
8070
  try {
7787
- const content = await fs27.readFile(filePath, "utf-8");
7788
- await fs27.writeFile(
8071
+ const content = await fs28.readFile(filePath, "utf-8");
8072
+ await fs28.writeFile(
7789
8073
  filePath,
7790
8074
  content + "\n\n" + mutation.newText,
7791
8075
  "utf-8"
7792
8076
  );
7793
8077
  } catch {
7794
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
7795
- await fs27.writeFile(filePath, mutation.newText, "utf-8");
8078
+ await fs28.mkdir(path28.dirname(filePath), { recursive: true });
8079
+ await fs28.writeFile(filePath, mutation.newText, "utf-8");
7796
8080
  }
7797
8081
  } else if (mutation.action === "create_file") {
7798
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
7799
- await fs27.writeFile(filePath, mutation.newText, "utf-8");
8082
+ await fs28.mkdir(path28.dirname(filePath), { recursive: true });
8083
+ await fs28.writeFile(filePath, mutation.newText, "utf-8");
7800
8084
  } else if (mutation.action === "delete_section") {
7801
8085
  if (!mutation.oldText) {
7802
8086
  return;
7803
8087
  }
7804
8088
  let sectionContent;
7805
8089
  try {
7806
- sectionContent = await fs27.readFile(filePath, "utf-8");
8090
+ sectionContent = await fs28.readFile(filePath, "utf-8");
7807
8091
  } catch {
7808
8092
  return;
7809
8093
  }
7810
8094
  if (!sectionContent.includes(mutation.oldText)) {
7811
8095
  return;
7812
8096
  }
7813
- await fs27.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
8097
+ await fs28.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
7814
8098
  } else if (mutation.action === "delete_file") {
7815
- await fs27.unlink(filePath).catch(() => {
8099
+ await fs28.unlink(filePath).catch(() => {
7816
8100
  });
7817
8101
  }
7818
8102
  }
@@ -7880,17 +8164,17 @@ async function readAllFiles(dir) {
7880
8164
  async function walk(current) {
7881
8165
  let entries;
7882
8166
  try {
7883
- entries = await fs27.readdir(current, { withFileTypes: true });
8167
+ entries = await fs28.readdir(current, { withFileTypes: true });
7884
8168
  } catch {
7885
8169
  return;
7886
8170
  }
7887
8171
  for (const entry of entries) {
7888
- const fullPath = path27.join(current, entry.name);
7889
- const relativePath = path27.relative(dir, fullPath);
8172
+ const fullPath = path28.join(current, entry.name);
8173
+ const relativePath = path28.relative(dir, fullPath);
7890
8174
  if (entry.isDirectory()) {
7891
8175
  await walk(fullPath);
7892
8176
  } else {
7893
- result[relativePath] = await fs27.readFile(fullPath, "utf-8");
8177
+ result[relativePath] = await fs28.readFile(fullPath, "utf-8");
7894
8178
  }
7895
8179
  }
7896
8180
  }
@@ -8030,7 +8314,7 @@ ${taskScores}
8030
8314
  }
8031
8315
  return "## Iteration History\n\n(History omitted to fit context budget)\n";
8032
8316
  }
8033
- function buildArchitectUserMessage(harnessFiles, traces, tasks, history, knowledgeContext) {
8317
+ function buildArchitectUserMessage(harnessFiles, traces, tasks, history, knowledgeContext, projectContext) {
8034
8318
  const harnessSection = ["## Current Harness Files\n"];
8035
8319
  const fileEntries = Object.entries(harnessFiles);
8036
8320
  if (fileEntries.length === 0) {
@@ -8059,11 +8343,28 @@ ${content}
8059
8343
  }
8060
8344
  const summarySection = buildEvolutionSummary(history);
8061
8345
  const workingSection = buildWhatsWorking(history, tasks);
8346
+ let projectSection = "";
8347
+ if (projectContext) {
8348
+ const parts = ["## Project Understanding\n"];
8349
+ parts.push("### Analysis Summary");
8350
+ parts.push(formatAnalysisForProposer(projectContext.analysis));
8351
+ parts.push("");
8352
+ parts.push("### Harness Structure");
8353
+ parts.push(projectContext.irSummary);
8354
+ if (projectContext.keySourceFiles) {
8355
+ parts.push("");
8356
+ parts.push("### Key Source Files");
8357
+ parts.push("```");
8358
+ parts.push(projectContext.keySourceFiles);
8359
+ parts.push("```");
8360
+ }
8361
+ projectSection = "\n" + parts.join("\n") + "\n";
8362
+ }
8062
8363
  const knowledgeSection = knowledgeContext ? `## Knowledge Base (patterns from other projects)
8063
8364
 
8064
8365
  ${knowledgeContext}
8065
8366
  ` : "";
8066
- const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + "\n" + summarySection + (summarySection ? "\n" : "") + workingSection + (workingSection ? "\n" : "") + knowledgeSection;
8367
+ const fixedContent = harnessSection.join("\n") + "\n" + taskSection.join("\n") + "\n" + summarySection + (summarySection ? "\n" : "") + workingSection + (workingSection ? "\n" : "") + projectSection + knowledgeSection;
8067
8368
  const remainingBudget = MAX_CONTEXT_CHARS2 - fixedContent.length;
8068
8369
  if (remainingBudget <= 0) {
8069
8370
  return fixedContent + "\n\n[...traces and history omitted \u2014 context budget exceeded...]";
@@ -8074,7 +8375,7 @@ ${knowledgeContext}
8074
8375
  const historySection = buildHistorySection2(history, historyBudget);
8075
8376
  return fixedContent + "\n" + traceSection + "\n" + historySection;
8076
8377
  }
8077
- async function proposeArchitecture(iteration, workspacePath, harnessPath, history, tasks, config, architectModel, knowledgeContext) {
8378
+ async function proposeArchitecture(iteration, workspacePath, harnessPath, history, tasks, config, architectModel, knowledgeContext, projectContext) {
8078
8379
  const harnessFiles = await readHarnessFiles(harnessPath);
8079
8380
  const traces = await loadIterationTraces(workspacePath, iteration);
8080
8381
  let effectiveKnowledge = knowledgeContext;
@@ -8091,7 +8392,8 @@ async function proposeArchitecture(iteration, workspacePath, harnessPath, histor
8091
8392
  traces,
8092
8393
  tasks,
8093
8394
  history,
8094
- effectiveKnowledge
8395
+ effectiveKnowledge,
8396
+ projectContext
8095
8397
  );
8096
8398
  const architectConfig = { ...config, model: architectModel };
8097
8399
  const response = await callLLM(architectConfig, userMessage, {
@@ -8206,8 +8508,8 @@ var init_schedule = __esm({
8206
8508
  });
8207
8509
 
8208
8510
  // src/evolve/sampling.ts
8209
- import fs28 from "fs/promises";
8210
- import path28 from "path";
8511
+ import fs29 from "fs/promises";
8512
+ import path29 from "path";
8211
8513
  function initBeliefs(tasks) {
8212
8514
  return tasks.map((task) => ({
8213
8515
  taskId: task.id,
@@ -8268,9 +8570,9 @@ function updateBeliefs(beliefs, results) {
8268
8570
  });
8269
8571
  }
8270
8572
  async function loadBeliefs(workspacePath) {
8271
- const beliefsPath = path28.join(workspacePath, "task-beliefs.json");
8573
+ const beliefsPath = path29.join(workspacePath, "task-beliefs.json");
8272
8574
  try {
8273
- const content = await fs28.readFile(beliefsPath, "utf-8");
8575
+ const content = await fs29.readFile(beliefsPath, "utf-8");
8274
8576
  const parsed = JSON.parse(content);
8275
8577
  if (!Array.isArray(parsed)) return null;
8276
8578
  for (const entry of parsed) {
@@ -8284,9 +8586,9 @@ async function loadBeliefs(workspacePath) {
8284
8586
  }
8285
8587
  }
8286
8588
  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");
8589
+ const beliefsPath = path29.join(workspacePath, "task-beliefs.json");
8590
+ await fs29.mkdir(path29.dirname(beliefsPath), { recursive: true });
8591
+ await fs29.writeFile(beliefsPath, JSON.stringify(beliefs, null, 2), "utf-8");
8290
8592
  }
8291
8593
  var init_sampling = __esm({
8292
8594
  "src/evolve/sampling.ts"() {
@@ -8295,8 +8597,8 @@ var init_sampling = __esm({
8295
8597
  });
8296
8598
 
8297
8599
  // src/evolve/regularization.ts
8298
- import fs29 from "fs/promises";
8299
- import path29 from "path";
8600
+ import fs30 from "fs/promises";
8601
+ import path30 from "path";
8300
8602
  async function measureComplexity(harnessPath) {
8301
8603
  let totalLines = 0;
8302
8604
  let totalFiles = 0;
@@ -8430,18 +8732,18 @@ async function readAllFilesRecursive(dir) {
8430
8732
  async function walk(current) {
8431
8733
  let entries;
8432
8734
  try {
8433
- entries = await fs29.readdir(current, { withFileTypes: true });
8735
+ entries = await fs30.readdir(current, { withFileTypes: true });
8434
8736
  } catch {
8435
8737
  return;
8436
8738
  }
8437
8739
  for (const entry of entries) {
8438
- const fullPath = path29.join(current, entry.name);
8439
- const relativePath = path29.relative(dir, fullPath);
8740
+ const fullPath = path30.join(current, entry.name);
8741
+ const relativePath = path30.relative(dir, fullPath);
8440
8742
  if (entry.isDirectory()) {
8441
8743
  await walk(fullPath);
8442
8744
  } else {
8443
8745
  try {
8444
- result[relativePath] = await fs29.readFile(fullPath, "utf-8");
8746
+ result[relativePath] = await fs30.readFile(fullPath, "utf-8");
8445
8747
  } catch {
8446
8748
  }
8447
8749
  }
@@ -8538,14 +8840,56 @@ var init_targeting = __esm({
8538
8840
  "test-writing": ["verification", "commands"],
8539
8841
  "config-change": ["settings", "mcp"],
8540
8842
  "documentation": ["general"],
8541
- "persistence-completion": ["commands", "verification"]
8843
+ "persistence-completion": ["commands", "verification"],
8844
+ "real-bug-fix": ["general"],
8845
+ "real-feature-add": ["general"],
8846
+ "codebase-question": ["general"]
8542
8847
  };
8543
8848
  }
8544
8849
  });
8545
8850
 
8546
8851
  // src/evolve/loop.ts
8547
- import fs30 from "fs/promises";
8548
- import path30 from "path";
8852
+ import fs31 from "fs/promises";
8853
+ import path31 from "path";
8854
+ async function loadProjectContext(projectDir) {
8855
+ try {
8856
+ const analysisPath = path31.join(projectDir, CACHE_FILENAME);
8857
+ const analysisRaw = await fs31.readFile(analysisPath, "utf-8");
8858
+ const analysisCache = JSON.parse(analysisRaw);
8859
+ const analysis = analysisCache.analysis;
8860
+ let keySourceFiles = "";
8861
+ try {
8862
+ const packedPath = path31.join(projectDir, PACKED_SOURCE_FILENAME);
8863
+ const packedSource = await fs31.readFile(packedPath, "utf-8");
8864
+ keySourceFiles = packedSource.slice(0, KEY_SOURCE_CHARS_LIMIT);
8865
+ } catch {
8866
+ }
8867
+ return { analysis, keySourceFiles };
8868
+ } catch {
8869
+ return null;
8870
+ }
8871
+ }
8872
+ async function buildProjectContext(projectData, harnessPath) {
8873
+ let irSummary = "";
8874
+ try {
8875
+ const irPath = path31.join(harnessPath, "..", "harness-ir.json");
8876
+ const irRaw = await fs31.readFile(irPath, "utf-8");
8877
+ const ir = JSON.parse(irRaw);
8878
+ irSummary = buildIRSummary(ir);
8879
+ } catch {
8880
+ try {
8881
+ const ir = await parseHarness(harnessPath);
8882
+ irSummary = buildIRSummary(ir);
8883
+ } catch {
8884
+ irSummary = "(IR not available)";
8885
+ }
8886
+ }
8887
+ return {
8888
+ analysis: projectData.analysis,
8889
+ irSummary,
8890
+ keySourceFiles: projectData.keySourceFiles || void 0
8891
+ };
8892
+ }
8549
8893
  function computeMutationCap(iter, maxIterations, maxMutations) {
8550
8894
  if (maxIterations <= 1) return maxMutations;
8551
8895
  const progress = iter / (maxIterations - 1);
@@ -8564,7 +8908,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8564
8908
  let baselineComplexity = null;
8565
8909
  let baselineIR = null;
8566
8910
  if (useKL) {
8567
- const baselineHarness = path30.join(workspacePath, "iterations", "0", "harness");
8911
+ const baselineHarness = path31.join(workspacePath, "iterations", "0", "harness");
8568
8912
  try {
8569
8913
  baselineIR = await parseHarness(baselineHarness);
8570
8914
  baselineComplexity = measureComplexityFromIR(baselineIR);
@@ -8576,20 +8920,22 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8576
8920
  }
8577
8921
  }
8578
8922
  let lastChangedAspects = null;
8923
+ const projectDir = path31.resolve(workspacePath, "..");
8924
+ const projectData = await loadProjectContext(projectDir);
8579
8925
  let rngState = evolveConfig.rngSeed ?? 42;
8580
8926
  const rng = () => {
8581
8927
  rngState = rngState * 1664525 + 1013904223 & 4294967295;
8582
8928
  return (rngState >>> 0) / 4294967296;
8583
8929
  };
8584
8930
  for (let iter = 0; iter < evolveConfig.maxIterations; iter++) {
8585
- const harnessPath = path30.join(
8931
+ const harnessPath = path31.join(
8586
8932
  workspacePath,
8587
8933
  "iterations",
8588
8934
  iter.toString(),
8589
8935
  "harness"
8590
8936
  );
8591
8937
  try {
8592
- await fs30.access(harnessPath);
8938
+ await fs31.access(harnessPath);
8593
8939
  } catch {
8594
8940
  if (iter === 0) {
8595
8941
  throw new Error(
@@ -8698,7 +9044,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8698
9044
  }
8699
9045
  const diffRatio = await computeDiffRatio(
8700
9046
  harnessPath,
8701
- path30.join(workspacePath, "iterations", "0", "harness")
9047
+ path31.join(workspacePath, "iterations", "0", "harness")
8702
9048
  );
8703
9049
  currentComplexity.diffFromBaseline = diffRatio;
8704
9050
  iterComplexityCost = computeComplexityCost(currentComplexity, baselineComplexity);
@@ -8767,7 +9113,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8767
9113
  };
8768
9114
  await writeIterationLog(workspacePath, rollbackLog);
8769
9115
  history.push(rollbackLog);
8770
- const bestHarnessPath = path30.join(
9116
+ const bestHarnessPath = path31.join(
8771
9117
  workspacePath,
8772
9118
  "iterations",
8773
9119
  bestIteration.toString(),
@@ -8776,6 +9122,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8776
9122
  if (iter + 1 < evolveConfig.maxIterations) {
8777
9123
  onProgress?.({ type: "proposing", iteration: iter, message: "Proposing new mutations after rollback" });
8778
9124
  try {
9125
+ const rollbackProjectCtx = projectData ? await buildProjectContext(projectData, bestHarnessPath) : void 0;
8779
9126
  let rollbackProposal = await propose(
8780
9127
  iter,
8781
9128
  workspacePath,
@@ -8783,7 +9130,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8783
9130
  history,
8784
9131
  tasks,
8785
9132
  kairnConfig,
8786
- evolveConfig.proposerModel
9133
+ evolveConfig.proposerModel,
9134
+ rollbackProjectCtx
8787
9135
  );
8788
9136
  const rollbackCap = computeMutationCap(iter, evolveConfig.maxIterations, evolveConfig.maxMutationsPerIteration);
8789
9137
  if (rollbackProposal.mutations.length > rollbackCap) {
@@ -8792,7 +9140,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8792
9140
  mutations: rollbackProposal.mutations.slice(0, rollbackCap)
8793
9141
  };
8794
9142
  }
8795
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
9143
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
8796
9144
  await applyMutations(bestHarnessPath, nextIterDir2, rollbackProposal.mutations);
8797
9145
  try {
8798
9146
  const rollbackIR = await parseHarness(bestHarnessPath);
@@ -8807,8 +9155,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8807
9155
  mutationCount: rollbackProposal.mutations.length
8808
9156
  });
8809
9157
  } catch {
8810
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8811
- await copyDir(bestHarnessPath, path30.join(nextIterDir2, "harness"));
9158
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9159
+ await copyDir(bestHarnessPath, path31.join(nextIterDir2, "harness"));
8812
9160
  }
8813
9161
  }
8814
9162
  continue;
@@ -8859,6 +9207,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8859
9207
  onProgress?.({ type: "architect-start", iteration: iter });
8860
9208
  let architectProposal;
8861
9209
  try {
9210
+ const architectProjectCtx = projectData ? await buildProjectContext(projectData, harnessPath) : void 0;
8862
9211
  architectProposal = await proposeArchitecture(
8863
9212
  iter,
8864
9213
  workspacePath,
@@ -8866,7 +9215,9 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8866
9215
  history,
8867
9216
  tasks,
8868
9217
  kairnConfig,
8869
- evolveConfig.architectModel
9218
+ evolveConfig.architectModel,
9219
+ void 0,
9220
+ architectProjectCtx
8870
9221
  );
8871
9222
  const architectCap = computeArchitectMutationBudget(iter, evolveConfig.maxIterations);
8872
9223
  if (architectProposal.mutations.length > architectCap) {
@@ -8882,8 +9233,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8882
9233
  iteration: iter,
8883
9234
  message: `Architect failed: ${errMsg}. Falling back to reactive proposer.`
8884
9235
  });
8885
- const nextIterDir2 = path30.join(workspacePath, "iterations", (iter + 1).toString());
8886
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9236
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9237
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8887
9238
  const skipLog = {
8888
9239
  iteration: iter,
8889
9240
  score: aggregate,
@@ -8900,7 +9251,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8900
9251
  continue;
8901
9252
  }
8902
9253
  onProgress?.({ type: "architect-staging", iteration: iter });
8903
- const stagingDir = path30.join(workspacePath, "staging", iter.toString());
9254
+ const stagingDir = path31.join(workspacePath, "staging", iter.toString());
8904
9255
  let stagingHarnessPath;
8905
9256
  try {
8906
9257
  const stagingResult = await applyMutations(
@@ -8911,8 +9262,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8911
9262
  stagingHarnessPath = stagingResult.newHarnessPath;
8912
9263
  } catch {
8913
9264
  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"));
9265
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9266
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8916
9267
  const rejectLog = {
8917
9268
  iteration: iter,
8918
9269
  score: aggregate,
@@ -8926,7 +9277,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8926
9277
  };
8927
9278
  await writeIterationLog(workspacePath, rejectLog);
8928
9279
  history.push(rejectLog);
8929
- await fs30.rm(stagingDir, { recursive: true, force: true }).catch(() => {
9280
+ await fs31.rm(stagingDir, { recursive: true, force: true }).catch(() => {
8930
9281
  });
8931
9282
  continue;
8932
9283
  }
@@ -8943,12 +9294,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8943
9294
  );
8944
9295
  if (stagingScore >= bestScore) {
8945
9296
  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"));
9297
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9298
+ await copyDir(stagingHarnessPath, path31.join(nextIterDir2, "harness"));
8948
9299
  } else {
8949
9300
  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"));
9301
+ const nextIterDir2 = path31.join(workspacePath, "iterations", (iter + 1).toString());
9302
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
8952
9303
  }
8953
9304
  const architectLog = {
8954
9305
  iteration: iter,
@@ -8963,13 +9314,14 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8963
9314
  };
8964
9315
  await writeIterationLog(workspacePath, architectLog);
8965
9316
  history.push(architectLog);
8966
- await fs30.rm(stagingDir, { recursive: true, force: true }).catch(() => {
9317
+ await fs31.rm(stagingDir, { recursive: true, force: true }).catch(() => {
8967
9318
  });
8968
9319
  continue;
8969
9320
  }
8970
9321
  onProgress?.({ type: "proposing", iteration: iter });
8971
9322
  let proposal;
8972
9323
  try {
9324
+ const iterProjectCtx = projectData ? await buildProjectContext(projectData, harnessPath) : void 0;
8973
9325
  proposal = await propose(
8974
9326
  iter,
8975
9327
  workspacePath,
@@ -8977,7 +9329,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8977
9329
  history,
8978
9330
  tasks,
8979
9331
  kairnConfig,
8980
- evolveConfig.proposerModel
9332
+ evolveConfig.proposerModel,
9333
+ iterProjectCtx
8981
9334
  );
8982
9335
  const iterCap = computeMutationCap(iter, evolveConfig.maxIterations, evolveConfig.maxMutationsPerIteration);
8983
9336
  if (proposal.mutations.length > iterCap) {
@@ -8993,12 +9346,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
8993
9346
  iteration: iter,
8994
9347
  message: `Proposer failed: ${errMsg}`
8995
9348
  });
8996
- const nextIterDir2 = path30.join(
9349
+ const nextIterDir2 = path31.join(
8997
9350
  workspacePath,
8998
9351
  "iterations",
8999
9352
  (iter + 1).toString()
9000
9353
  );
9001
- await copyDir(harnessPath, path30.join(nextIterDir2, "harness"));
9354
+ await copyDir(harnessPath, path31.join(nextIterDir2, "harness"));
9002
9355
  const skipLog = {
9003
9356
  iteration: iter,
9004
9357
  score: aggregate,
@@ -9014,7 +9367,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9014
9367
  history.push(skipLog);
9015
9368
  continue;
9016
9369
  }
9017
- const nextIterDir = path30.join(
9370
+ const nextIterDir = path31.join(
9018
9371
  workspacePath,
9019
9372
  "iterations",
9020
9373
  (iter + 1).toString()
@@ -9035,7 +9388,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9035
9388
  lastChangedAspects = null;
9036
9389
  }
9037
9390
  } catch {
9038
- await copyDir(harnessPath, path30.join(nextIterDir, "harness"));
9391
+ await copyDir(harnessPath, path31.join(nextIterDir, "harness"));
9039
9392
  lastChangedAspects = null;
9040
9393
  }
9041
9394
  onProgress?.({
@@ -9059,8 +9412,9 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9059
9412
  }
9060
9413
  if (evolveConfig.usePrincipal && history.length >= 2) {
9061
9414
  onProgress?.({ type: "proposing", iteration: history.length, message: "Principal Proposer synthesizing final harness" });
9062
- const baselineHarnessPath = path30.join(workspacePath, "iterations", "0", "harness");
9415
+ const baselineHarnessPath = path31.join(workspacePath, "iterations", "0", "harness");
9063
9416
  try {
9417
+ const principalProjectCtx = projectData ? await buildProjectContext(projectData, baselineHarnessPath) : void 0;
9064
9418
  let principalProposal = await propose(
9065
9419
  history.length,
9066
9420
  workspacePath,
@@ -9068,7 +9422,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9068
9422
  history,
9069
9423
  tasks,
9070
9424
  kairnConfig,
9071
- evolveConfig.proposerModel
9425
+ evolveConfig.proposerModel,
9426
+ principalProjectCtx
9072
9427
  );
9073
9428
  if (principalProposal.mutations.length > evolveConfig.maxMutationsPerIteration) {
9074
9429
  principalProposal = {
@@ -9077,7 +9432,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9077
9432
  };
9078
9433
  }
9079
9434
  const principalIterNum = history.length;
9080
- const principalIterDir = path30.join(workspacePath, "iterations", principalIterNum.toString());
9435
+ const principalIterDir = path31.join(workspacePath, "iterations", principalIterNum.toString());
9081
9436
  const mutResult = await applyMutations(baselineHarnessPath, principalIterDir, principalProposal.mutations);
9082
9437
  onProgress?.({ type: "iteration-start", iteration: principalIterNum });
9083
9438
  const { results: principalResults, aggregate: principalAggregate } = await evaluateAll(
@@ -9118,7 +9473,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9118
9473
  }
9119
9474
  try {
9120
9475
  const { extractAndSavePatterns: extractAndSavePatterns2 } = await Promise.resolve().then(() => (init_knowledge(), knowledge_exports));
9121
- const projectName = path30.basename(path30.resolve(workspacePath, ".."));
9476
+ const projectName = path31.basename(path31.resolve(workspacePath, ".."));
9122
9477
  await extractAndSavePatterns2(history, projectName, null);
9123
9478
  } catch {
9124
9479
  }
@@ -9134,6 +9489,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
9134
9489
  baselineScore
9135
9490
  };
9136
9491
  }
9492
+ var KEY_SOURCE_CHARS_LIMIT;
9137
9493
  var init_loop = __esm({
9138
9494
  "src/evolve/loop.ts"() {
9139
9495
  "use strict";
@@ -9149,11 +9505,13 @@ var init_loop = __esm({
9149
9505
  init_parser();
9150
9506
  init_translate();
9151
9507
  init_targeting();
9508
+ init_cache();
9509
+ KEY_SOURCE_CHARS_LIMIT = 1e4;
9152
9510
  }
9153
9511
  });
9154
9512
 
9155
9513
  // src/evolve/synthesis.ts
9156
- import path33 from "path";
9514
+ import path34 from "path";
9157
9515
  function buildMetaPrincipalSystemPrompt(numBranches) {
9158
9516
  return `You are reviewing the COMPLETE results of ${numBranches} independent evolution runs.
9159
9517
  Each branch explored different mutations and saw different task subsets.
@@ -9309,7 +9667,7 @@ async function runSynthesis(context, kairnConfig, evolveConfig, workspacePath) {
9309
9667
  if (mutations.length === 0) {
9310
9668
  return null;
9311
9669
  }
9312
- const synthesisDir = path33.join(workspacePath, "synthesis");
9670
+ const synthesisDir = path34.join(workspacePath, "synthesis");
9313
9671
  const { newHarnessPath } = await applyMutations(
9314
9672
  context.baselineHarnessPath,
9315
9673
  synthesisDir,
@@ -9343,26 +9701,26 @@ __export(population_exports, {
9343
9701
  initBranches: () => initBranches,
9344
9702
  runPopulation: () => runPopulation
9345
9703
  });
9346
- import fs33 from "fs/promises";
9347
- import path34 from "path";
9704
+ import fs34 from "fs/promises";
9705
+ import path35 from "path";
9348
9706
  async function initBranches(workspacePath, baselinePath, numBranches) {
9349
- const branchesDir = path34.join(workspacePath, "branches");
9350
- await fs33.mkdir(branchesDir, { recursive: true });
9707
+ const branchesDir = path35.join(workspacePath, "branches");
9708
+ await fs34.mkdir(branchesDir, { recursive: true });
9351
9709
  const configs = [];
9352
9710
  for (let i = 0; i < numBranches; i++) {
9353
- const branchPath = path34.join(branchesDir, i.toString());
9354
- const harnessPath = path34.join(branchPath, "iterations", "0", "harness");
9711
+ const branchPath = path35.join(branchesDir, i.toString());
9712
+ const harnessPath = path35.join(branchPath, "iterations", "0", "harness");
9355
9713
  await copyDir(baselinePath, harnessPath);
9356
- const tasksYaml = path34.join(workspacePath, "tasks.yaml");
9714
+ const tasksYaml = path35.join(workspacePath, "tasks.yaml");
9357
9715
  try {
9358
- await fs33.access(tasksYaml);
9359
- await fs33.copyFile(tasksYaml, path34.join(branchPath, "tasks.yaml"));
9716
+ await fs34.access(tasksYaml);
9717
+ await fs34.copyFile(tasksYaml, path35.join(branchPath, "tasks.yaml"));
9360
9718
  } catch {
9361
9719
  }
9362
- const configYaml = path34.join(workspacePath, "config.yaml");
9720
+ const configYaml = path35.join(workspacePath, "config.yaml");
9363
9721
  try {
9364
- await fs33.access(configYaml);
9365
- await fs33.copyFile(configYaml, path34.join(branchPath, "config.yaml"));
9722
+ await fs34.access(configYaml);
9723
+ await fs34.copyFile(configYaml, path35.join(branchPath, "config.yaml"));
9366
9724
  } catch {
9367
9725
  }
9368
9726
  const seed = 42 + i * 1337;
@@ -9376,7 +9734,7 @@ async function initBranches(workspacePath, baselinePath, numBranches) {
9376
9734
  }
9377
9735
  async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, numBranches, onProgress) {
9378
9736
  const branches = numBranches ?? evolveConfig.pbtBranches;
9379
- const baselinePath = path34.join(workspacePath, "baseline");
9737
+ const baselinePath = path35.join(workspacePath, "baseline");
9380
9738
  const branchConfigs = await initBranches(workspacePath, baselinePath, branches);
9381
9739
  const branchPromises = branchConfigs.map(async (branchConfig) => {
9382
9740
  const branchEvolveConfig = {
@@ -9396,7 +9754,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9396
9754
  branchEvolveConfig,
9397
9755
  branchProgress
9398
9756
  );
9399
- const finalHarnessPath = path34.join(
9757
+ const finalHarnessPath = path35.join(
9400
9758
  branchConfig.workspacePath,
9401
9759
  "iterations",
9402
9760
  result.bestIteration.toString(),
@@ -9404,8 +9762,8 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9404
9762
  );
9405
9763
  let beliefs = [];
9406
9764
  try {
9407
- const beliefsPath = path34.join(branchConfig.workspacePath, "task-beliefs.json");
9408
- const beliefsContent = await fs33.readFile(beliefsPath, "utf-8");
9765
+ const beliefsPath = path35.join(branchConfig.workspacePath, "task-beliefs.json");
9766
+ const beliefsContent = await fs34.readFile(beliefsPath, "utf-8");
9409
9767
  beliefs = JSON.parse(beliefsContent);
9410
9768
  } catch {
9411
9769
  }
@@ -9427,7 +9785,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
9427
9785
  }
9428
9786
  let synthesizedResult;
9429
9787
  try {
9430
- const baselinePath2 = path34.join(workspacePath, "baseline");
9788
+ const baselinePath2 = path35.join(workspacePath, "baseline");
9431
9789
  const synthesisResult = await runSynthesis(
9432
9790
  { branches: branchResults, tasks, baselineHarnessPath: baselinePath2 },
9433
9791
  kairnConfig,
@@ -9489,8 +9847,8 @@ __export(research_exports, {
9489
9847
  formatResearchReport: () => formatResearchReport,
9490
9848
  runResearch: () => runResearch
9491
9849
  });
9492
- import fs34 from "fs/promises";
9493
- import path35 from "path";
9850
+ import fs35 from "fs/promises";
9851
+ import path36 from "path";
9494
9852
  import os5 from "os";
9495
9853
  import { exec as exec4 } from "child_process";
9496
9854
  import { promisify as promisify4 } from "util";
@@ -9509,8 +9867,8 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9509
9867
  }
9510
9868
  const allPatterns = [];
9511
9869
  const repoResults = [];
9512
- const tempBase = path35.join(os5.tmpdir(), `kairn-research-${Date.now()}`);
9513
- await fs34.mkdir(tempBase, { recursive: true });
9870
+ const tempBase = path36.join(os5.tmpdir(), `kairn-research-${Date.now()}`);
9871
+ await fs35.mkdir(tempBase, { recursive: true });
9514
9872
  try {
9515
9873
  for (let i = 0; i < config.repos.length; i++) {
9516
9874
  const url = config.repos[i];
@@ -9522,7 +9880,7 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9522
9880
  totalRepos: config.repos.length,
9523
9881
  message: `Cloning ${name}...`
9524
9882
  });
9525
- const repoDir = path35.join(tempBase, name);
9883
+ const repoDir = path36.join(tempBase, name);
9526
9884
  try {
9527
9885
  await execAsync4(`git clone --depth 1 '${url.replace(/'/g, "'\\''")}' '${repoDir.replace(/'/g, "'\\''")}' 2>/dev/null`, { timeout: 6e4 });
9528
9886
  } catch (err) {
@@ -9536,10 +9894,10 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9536
9894
  repoResults.push({ repo: name, bestScore: 0, patternsFound: 0 });
9537
9895
  continue;
9538
9896
  }
9539
- const claudeDir = path35.join(repoDir, ".claude");
9897
+ const claudeDir = path36.join(repoDir, ".claude");
9540
9898
  let hasHarness = false;
9541
9899
  try {
9542
- await fs34.access(claudeDir);
9900
+ await fs35.access(claudeDir);
9543
9901
  hasHarness = true;
9544
9902
  } catch {
9545
9903
  onProgress?.({
@@ -9589,7 +9947,7 @@ async function runResearch(config, _kairnConfig, _evolveConfig, onProgress) {
9589
9947
  onProgress?.({ type: "research-complete", message: "Research complete" });
9590
9948
  return report;
9591
9949
  } finally {
9592
- await fs34.rm(tempBase, { recursive: true, force: true }).catch(() => {
9950
+ await fs35.rm(tempBase, { recursive: true, force: true }).catch(() => {
9593
9951
  });
9594
9952
  }
9595
9953
  }
@@ -9960,6 +10318,7 @@ async function detectExistingRepo(dir) {
9960
10318
  }
9961
10319
 
9962
10320
  // src/commands/describe.ts
10321
+ init_persist();
9963
10322
  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
10323
  printFullBanner("The Agent Environment Compiler");
9965
10324
  const config = await loadConfig();
@@ -10059,6 +10418,13 @@ Autonomy level: ${autonomyLevel} (${autonomyLabel(autonomyLevel)})`;
10059
10418
  `));
10060
10419
  process.exit(1);
10061
10420
  }
10421
+ if (spec.ir) {
10422
+ try {
10423
+ await persistHarnessIR(process.cwd(), spec.ir);
10424
+ } catch {
10425
+ console.log(ui.warn("Could not persist harness IR to .kairn/harness-ir.json"));
10426
+ }
10427
+ }
10062
10428
  const registry = await loadRegistry();
10063
10429
  const summary = summarizeSpec(spec, registry);
10064
10430
  console.log("");
@@ -10133,14 +10499,14 @@ init_ui();
10133
10499
  init_logo();
10134
10500
  import { Command as Command4 } from "commander";
10135
10501
  import chalk7 from "chalk";
10136
- import fs14 from "fs/promises";
10137
- import path14 from "path";
10502
+ import fs15 from "fs/promises";
10503
+ import path15 from "path";
10138
10504
  var listCommand = new Command4("list").description("Show saved environments").action(async () => {
10139
10505
  printCompactBanner();
10140
10506
  const envsDir = getEnvsDir();
10141
10507
  let files;
10142
10508
  try {
10143
- files = await fs14.readdir(envsDir);
10509
+ files = await fs15.readdir(envsDir);
10144
10510
  } catch {
10145
10511
  console.log(chalk7.dim(" No environments yet. Run ") + chalk7.bold("kairn describe") + chalk7.dim(" to create one.\n"));
10146
10512
  return;
@@ -10153,7 +10519,7 @@ var listCommand = new Command4("list").description("Show saved environments").ac
10153
10519
  let first = true;
10154
10520
  for (const file of jsonFiles) {
10155
10521
  try {
10156
- const data = await fs14.readFile(path14.join(envsDir, file), "utf-8");
10522
+ const data = await fs15.readFile(path15.join(envsDir, file), "utf-8");
10157
10523
  const spec = JSON.parse(data);
10158
10524
  const date = new Date(spec.created_at).toLocaleDateString();
10159
10525
  const toolCount = spec.tools?.length ?? 0;
@@ -10178,8 +10544,8 @@ init_ui();
10178
10544
  init_logo();
10179
10545
  import { Command as Command5 } from "commander";
10180
10546
  import chalk8 from "chalk";
10181
- import fs15 from "fs/promises";
10182
- import path15 from "path";
10547
+ import fs16 from "fs/promises";
10548
+ import path16 from "path";
10183
10549
  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
10550
  printCompactBanner();
10185
10551
  const envsDir = getEnvsDir();
@@ -10189,7 +10555,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10189
10555
  let fromTemplate = false;
10190
10556
  let envFiles = [];
10191
10557
  try {
10192
- envFiles = await fs15.readdir(envsDir);
10558
+ envFiles = await fs16.readdir(envsDir);
10193
10559
  } catch {
10194
10560
  }
10195
10561
  match = envFiles.find(
@@ -10200,7 +10566,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10200
10566
  } else {
10201
10567
  let templateFiles = [];
10202
10568
  try {
10203
- templateFiles = await fs15.readdir(templatesDir);
10569
+ templateFiles = await fs16.readdir(templatesDir);
10204
10570
  } catch {
10205
10571
  }
10206
10572
  match = templateFiles.find(
@@ -10216,7 +10582,7 @@ var activateCommand = new Command5("activate").description("Re-deploy a saved en
10216
10582
  process.exit(1);
10217
10583
  }
10218
10584
  }
10219
- const data = await fs15.readFile(path15.join(sourceDir, match), "utf-8");
10585
+ const data = await fs16.readFile(path16.join(sourceDir, match), "utf-8");
10220
10586
  const spec = JSON.parse(data);
10221
10587
  const label = fromTemplate ? chalk8.dim(" (template)") : "";
10222
10588
  console.log(chalk8.cyan(` Activating: ${spec.name}`) + label);
@@ -10236,21 +10602,21 @@ init_ui();
10236
10602
  init_logo();
10237
10603
  import { Command as Command6 } from "commander";
10238
10604
  import chalk9 from "chalk";
10239
- import fs16 from "fs/promises";
10240
- import path16 from "path";
10605
+ import fs17 from "fs/promises";
10606
+ import path17 from "path";
10241
10607
  import { fileURLToPath as fileURLToPath4 } from "url";
10242
10608
  var REGISTRY_URL = "https://raw.githubusercontent.com/ashtonperlroth/kairn/main/src/registry/tools.json";
10243
10609
  async function getLocalRegistryPath() {
10244
10610
  const __filename3 = fileURLToPath4(import.meta.url);
10245
- const __dirname3 = path16.dirname(__filename3);
10611
+ const __dirname3 = path17.dirname(__filename3);
10246
10612
  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")
10613
+ path17.resolve(__dirname3, "../registry/tools.json"),
10614
+ path17.resolve(__dirname3, "../src/registry/tools.json"),
10615
+ path17.resolve(__dirname3, "../../src/registry/tools.json")
10250
10616
  ];
10251
10617
  for (const candidate of candidates) {
10252
10618
  try {
10253
- await fs16.access(candidate);
10619
+ await fs17.access(candidate);
10254
10620
  return candidate;
10255
10621
  } catch {
10256
10622
  continue;
@@ -10287,10 +10653,10 @@ var updateRegistryCommand = new Command6("update-registry").description("Fetch t
10287
10653
  const registryPath = await getLocalRegistryPath();
10288
10654
  const backupPath = registryPath + ".bak";
10289
10655
  try {
10290
- await fs16.copyFile(registryPath, backupPath);
10656
+ await fs17.copyFile(registryPath, backupPath);
10291
10657
  } catch {
10292
10658
  }
10293
- await fs16.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
10659
+ await fs17.writeFile(registryPath, JSON.stringify(tools, null, 2), "utf-8");
10294
10660
  console.log(ui.success(`Registry updated: ${tools.length} tools`));
10295
10661
  console.log(chalk9.dim(` Saved to: ${registryPath}`));
10296
10662
  console.log(chalk9.dim(` Backup: ${backupPath}
@@ -10669,14 +11035,14 @@ init_ui();
10669
11035
  init_logo();
10670
11036
  import { Command as Command9 } from "commander";
10671
11037
  import chalk12 from "chalk";
10672
- import fs17 from "fs/promises";
10673
- import path17 from "path";
11038
+ import fs18 from "fs/promises";
11039
+ import path18 from "path";
10674
11040
  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
11041
  printCompactBanner();
10676
11042
  const templatesDir = getTemplatesDir();
10677
11043
  let files;
10678
11044
  try {
10679
- files = await fs17.readdir(templatesDir);
11045
+ files = await fs18.readdir(templatesDir);
10680
11046
  } catch {
10681
11047
  console.log(
10682
11048
  chalk12.dim(
@@ -10701,8 +11067,8 @@ var templatesCommand = new Command9("templates").description("Browse available t
10701
11067
  const templates = [];
10702
11068
  for (const file of jsonFiles) {
10703
11069
  try {
10704
- const data = await fs17.readFile(
10705
- path17.join(templatesDir, file),
11070
+ const data = await fs18.readFile(
11071
+ path18.join(templatesDir, file),
10706
11072
  "utf-8"
10707
11073
  );
10708
11074
  const spec = JSON.parse(data);
@@ -10753,8 +11119,8 @@ init_loader();
10753
11119
  import { Command as Command10 } from "commander";
10754
11120
  import { password as password3 } from "@inquirer/prompts";
10755
11121
  import chalk13 from "chalk";
10756
- import fs18 from "fs/promises";
10757
- import path18 from "path";
11122
+ import fs19 from "fs/promises";
11123
+ import path19 from "path";
10758
11124
  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
11125
  printCompactBanner();
10760
11126
  const targetDir = process.cwd();
@@ -10866,8 +11232,8 @@ var keysCommand = new Command10("keys").description("Add or update API keys for
10866
11232
  for (const [key, value] of envEntries) {
10867
11233
  envLines.push(`${key}=${value}`);
10868
11234
  }
10869
- const envPath = path18.join(targetDir, ".env");
10870
- await fs18.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
11235
+ const envPath = path19.join(targetDir, ".env");
11236
+ await fs19.writeFile(envPath, envLines.join("\n") + "\n", "utf-8");
10871
11237
  console.log(chalk13.green(` \u2713 ${keysEntered} key(s) saved to .env`));
10872
11238
  console.log("");
10873
11239
  });
@@ -10877,90 +11243,131 @@ init_ui();
10877
11243
  import { Command as Command11 } from "commander";
10878
11244
  import chalk14 from "chalk";
10879
11245
  import ora2 from "ora";
10880
- import fs35 from "fs/promises";
10881
- import path36 from "path";
11246
+ import fs36 from "fs/promises";
11247
+ import path37 from "path";
10882
11248
  import { parse as yamlParse2 } from "yaml";
10883
11249
  import { confirm as confirm4, input as input4, select as select4 } from "@inquirer/prompts";
10884
11250
 
10885
11251
  // src/evolve/init.ts
10886
11252
  init_config();
10887
- import fs19 from "fs/promises";
10888
- import path19 from "path";
11253
+ init_cache();
11254
+ import fs20 from "fs/promises";
11255
+ import path20 from "path";
10889
11256
  import { stringify as yamlStringify } from "yaml";
10890
11257
 
10891
11258
  // src/evolve/templates.ts
10892
11259
  init_llm();
10893
11260
  var EVAL_TEMPLATES = {
11261
+ // --- Harness-sensitivity templates (probe whether agent follows .claude/ harness) ---
10894
11262
  "add-feature": {
10895
11263
  id: "add-feature",
10896
11264
  name: "Add Feature",
10897
11265
  description: "Can the agent add a new capability?",
10898
- bestFor: ["feature-development", "api-building", "full-stack"]
11266
+ bestFor: ["feature-development", "api-building", "full-stack"],
11267
+ category: "harness-sensitivity"
10899
11268
  },
10900
11269
  "fix-bug": {
10901
11270
  id: "fix-bug",
10902
11271
  name: "Fix Bug",
10903
11272
  description: "Can the agent diagnose and fix a problem?",
10904
- bestFor: ["maintenance", "debugging", "qa"]
11273
+ bestFor: ["maintenance", "debugging", "qa"],
11274
+ category: "harness-sensitivity"
10905
11275
  },
10906
11276
  "refactor": {
10907
11277
  id: "refactor",
10908
11278
  name: "Refactor",
10909
11279
  description: "Can the agent restructure code?",
10910
- bestFor: ["maintenance", "architecture", "backend"]
11280
+ bestFor: ["maintenance", "architecture", "backend"],
11281
+ category: "harness-sensitivity"
10911
11282
  },
10912
11283
  "test-writing": {
10913
11284
  id: "test-writing",
10914
11285
  name: "Test Writing",
10915
11286
  description: "Can the agent write tests?",
10916
- bestFor: ["tdd", "qa", "backend"]
11287
+ bestFor: ["tdd", "qa", "backend"],
11288
+ category: "harness-sensitivity"
10917
11289
  },
10918
11290
  "config-change": {
10919
11291
  id: "config-change",
10920
11292
  name: "Config Change",
10921
11293
  description: "Can the agent update configuration?",
10922
- bestFor: ["devops", "infrastructure", "backend"]
11294
+ bestFor: ["devops", "infrastructure", "backend"],
11295
+ category: "harness-sensitivity"
10923
11296
  },
10924
11297
  "documentation": {
10925
11298
  id: "documentation",
10926
11299
  name: "Documentation",
10927
11300
  description: "Can the agent write and update docs?",
10928
- bestFor: ["content", "api-building", "full-stack"]
11301
+ bestFor: ["content", "api-building", "full-stack"],
11302
+ category: "harness-sensitivity"
10929
11303
  },
10930
11304
  "convention-adherence": {
10931
11305
  id: "convention-adherence",
10932
11306
  name: "Convention Adherence",
10933
11307
  description: "Does the agent follow all project conventions defined in CLAUDE.md?",
10934
- bestFor: ["feature-development", "full-stack", "backend", "maintenance"]
11308
+ bestFor: ["feature-development", "full-stack", "backend", "maintenance"],
11309
+ category: "harness-sensitivity"
10935
11310
  },
10936
11311
  "workflow-compliance": {
10937
11312
  id: "workflow-compliance",
10938
11313
  name: "Workflow Compliance",
10939
11314
  description: "Does the agent use the project workflow commands and skills?",
10940
- bestFor: ["feature-development", "full-stack", "tdd", "qa"]
11315
+ bestFor: ["feature-development", "full-stack", "tdd", "qa"],
11316
+ category: "harness-sensitivity"
10941
11317
  },
10942
11318
  "rule-compliance": {
10943
11319
  id: "rule-compliance",
10944
11320
  name: "Rule Compliance",
10945
11321
  description: "Does the agent follow all project rules without violations?",
10946
- bestFor: ["feature-development", "backend", "maintenance", "architecture"]
11322
+ bestFor: ["feature-development", "backend", "maintenance", "architecture"],
11323
+ category: "harness-sensitivity"
10947
11324
  },
10948
11325
  "intent-routing": {
10949
11326
  id: "intent-routing",
10950
11327
  name: "Intent Routing",
10951
11328
  description: "Test that natural language prompts route to the correct workflow command via intent hooks",
10952
- bestFor: ["feature-development", "full-stack", "api-building"]
11329
+ bestFor: ["feature-development", "full-stack", "api-building"],
11330
+ category: "harness-sensitivity"
10953
11331
  },
10954
11332
  "persistence-completion": {
10955
11333
  id: "persistence-completion",
10956
11334
  name: "Persistence Completion",
10957
11335
  description: "Can the agent complete a multi-criterion task using the persistence loop?",
10958
- bestFor: ["feature-development", "full-stack", "api-building", "maintenance"]
11336
+ bestFor: ["feature-development", "full-stack", "api-building", "maintenance"],
11337
+ category: "harness-sensitivity"
11338
+ },
11339
+ // --- Substantive SWE-bench-style templates (test real coding ability) ---
11340
+ "real-bug-fix": {
11341
+ id: "real-bug-fix",
11342
+ name: "Real Bug Fix",
11343
+ description: "Injects a known bug into a source file and asks the agent to diagnose and fix it, mimicking a real GitHub issue",
11344
+ bestFor: ["debugging", "maintenance", "qa", "backend"],
11345
+ category: "substantive"
11346
+ },
11347
+ "real-feature-add": {
11348
+ id: "real-feature-add",
11349
+ name: "Real Feature Add",
11350
+ description: "Describes a concrete feature with clear acceptance criteria and verifies the agent implements it correctly",
11351
+ bestFor: ["feature-development", "full-stack", "api-building", "backend"],
11352
+ category: "substantive"
11353
+ },
11354
+ "codebase-question": {
11355
+ id: "codebase-question",
11356
+ name: "Codebase Question",
11357
+ description: "Asks a factual question about codebase knowledge and checks the answer via LLM-as-judge against a known-correct answer",
11358
+ bestFor: ["research", "architecture", "maintenance", "debugging"],
11359
+ category: "substantive"
10959
11360
  }
10960
11361
  };
11362
+ var SUBSTANTIVE_FLOOR = [
11363
+ "real-bug-fix",
11364
+ "real-feature-add",
11365
+ "codebase-question"
11366
+ ];
11367
+ var MAX_HARNESS_TEMPLATES = 4;
10961
11368
  function selectTemplatesForWorkflow(workflowType) {
10962
- const mapping = {
10963
- "feature-development": ["add-feature", "test-writing", "convention-adherence", "workflow-compliance", "intent-routing", "persistence-completion"],
11369
+ const harnessMapping = {
11370
+ "feature-development": ["add-feature", "test-writing", "intent-routing", "convention-adherence", "workflow-compliance", "persistence-completion"],
10964
11371
  "api-building": ["add-feature", "fix-bug", "test-writing", "convention-adherence", "persistence-completion"],
10965
11372
  "full-stack": ["add-feature", "fix-bug", "test-writing", "convention-adherence", "persistence-completion"],
10966
11373
  "maintenance": ["fix-bug", "refactor", "test-writing", "rule-compliance", "persistence-completion"],
@@ -10974,7 +11381,8 @@ function selectTemplatesForWorkflow(workflowType) {
10974
11381
  "content": ["documentation", "add-feature", "convention-adherence"],
10975
11382
  "research": ["documentation", "add-feature", "convention-adherence"]
10976
11383
  };
10977
- return mapping[workflowType] || ["add-feature", "fix-bug", "test-writing", "convention-adherence"];
11384
+ const harness = (harnessMapping[workflowType] ?? ["add-feature", "fix-bug", "test-writing", "convention-adherence"]).slice(0, MAX_HARNESS_TEMPLATES);
11385
+ return [...SUBSTANTIVE_FLOOR, ...harness];
10978
11386
  }
10979
11387
  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
11388
 
@@ -10988,6 +11396,15 @@ IMPORTANT: For harness-aware templates (convention-adherence, workflow-complianc
10988
11396
 
10989
11397
  These harness-aware tasks are critical \u2014 they test whether the .claude/ environment actually improves agent behavior.
10990
11398
 
11399
+ SUBSTANTIVE SWE-bench-style templates test real coding ability beyond harness adherence:
11400
+ - 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".
11401
+ - 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".
11402
+ - 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".
11403
+
11404
+ Each task MUST include a "category" field:
11405
+ - "harness-sensitivity" for templates that test .claude/ harness adherence
11406
+ - "substantive" for SWE-bench-style templates that test real coding ability
11407
+
10991
11408
  Return a JSON object with a "tasks" array. Each task has:
10992
11409
  - id: kebab-case identifier (e.g., "add-health-endpoint")
10993
11410
  - template: which eval template this instantiates
@@ -10995,8 +11412,11 @@ Return a JSON object with a "tasks" array. Each task has:
10995
11412
  - setup: shell commands to prepare the workspace (e.g., "npm install")
10996
11413
  - expected_outcome: multi-line string describing what success looks like
10997
11414
  - scoring: "pass-fail", "llm-judge", or "rubric"
11415
+ - category: "harness-sensitivity" or "substantive"
10998
11416
  - timeout: seconds (300 for features/bugs, 600 for refactors, 180 for config/docs/tests)
10999
11417
 
11418
+ 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.
11419
+
11000
11420
  Return ONLY valid JSON, no markdown fences.`;
11001
11421
  function parseJsonResponse(raw) {
11002
11422
  let cleaned = raw.trim();
@@ -11038,9 +11458,58 @@ function validateTask(obj, index) {
11038
11458
  }
11039
11459
  return record;
11040
11460
  }
11041
- function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11461
+ function buildAnalysisContext(analysis) {
11462
+ const lines = ["## Project Analysis", ""];
11463
+ lines.push(`Purpose: ${analysis.purpose}`);
11464
+ lines.push(`Domain: ${analysis.domain}`);
11465
+ lines.push(`Architecture: ${analysis.architecture_style}`);
11466
+ lines.push(`Deployment: ${analysis.deployment_model}`);
11467
+ lines.push("");
11468
+ if (analysis.key_modules.length > 0) {
11469
+ lines.push("### Key Modules");
11470
+ lines.push("");
11471
+ for (const mod of analysis.key_modules) {
11472
+ lines.push(`- **${mod.name}** (${mod.path}): ${mod.description}`);
11473
+ if (mod.responsibilities.length > 0) {
11474
+ lines.push(` Responsibilities: ${mod.responsibilities.join(", ")}`);
11475
+ }
11476
+ }
11477
+ lines.push("");
11478
+ }
11479
+ if (analysis.workflows.length > 0) {
11480
+ lines.push("### Workflows");
11481
+ lines.push("");
11482
+ for (const wf of analysis.workflows) {
11483
+ lines.push(`- **${wf.name}**: ${wf.description} (trigger: ${wf.trigger})`);
11484
+ lines.push(` Steps: ${wf.steps.join(" -> ")}`);
11485
+ }
11486
+ lines.push("");
11487
+ }
11488
+ if (analysis.config_keys.length > 0) {
11489
+ lines.push("### Config Keys");
11490
+ lines.push("");
11491
+ for (const key of analysis.config_keys) {
11492
+ lines.push(`- ${key.name}: ${key.purpose}`);
11493
+ }
11494
+ lines.push("");
11495
+ }
11496
+ if (analysis.dataflow.length > 0) {
11497
+ lines.push("### Data Flow");
11498
+ lines.push("");
11499
+ for (const edge of analysis.dataflow) {
11500
+ lines.push(`- ${edge.from} -> ${edge.to}: ${edge.data}`);
11501
+ }
11502
+ lines.push("");
11503
+ }
11504
+ lines.push("IMPORTANT: Use this analysis to generate domain-specific tasks:");
11505
+ lines.push("- real-bug-fix tasks should reference actual module names and paths listed above");
11506
+ lines.push("- codebase-question tasks should ask about actual workflows, modules, and config keys");
11507
+ lines.push("- real-feature-add tasks should extend actual functionality in the modules listed above");
11508
+ return lines.join("\n");
11509
+ }
11510
+ function buildTaskGenerationMessage(claudeMd, projectProfile, templates, analysis) {
11042
11511
  const profileLines = [
11043
- `Language: ${projectProfile.language ?? "unknown"}`,
11512
+ `Languages: ${projectProfile.languages.length > 0 ? projectProfile.languages.join(", ") : "unknown"}`,
11044
11513
  `Framework: ${projectProfile.framework ?? "none"}`,
11045
11514
  `Scripts: ${Object.entries(projectProfile.scripts).map(([k, v]) => `${k}=${v}`).join(", ") || "none"}`,
11046
11515
  `Key files: ${projectProfile.keyFiles.join(", ") || "none"}`
@@ -11049,7 +11518,7 @@ function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11049
11518
  const meta = EVAL_TEMPLATES[t];
11050
11519
  return `- ${t}: ${meta.description}`;
11051
11520
  }).join("\n");
11052
- return [
11521
+ const sections = [
11053
11522
  "## CLAUDE.md",
11054
11523
  "",
11055
11524
  claudeMd,
@@ -11057,16 +11526,23 @@ function buildTaskGenerationMessage(claudeMd, projectProfile, templates) {
11057
11526
  "## Project Profile",
11058
11527
  "",
11059
11528
  ...profileLines,
11060
- "",
11529
+ ""
11530
+ ];
11531
+ if (analysis) {
11532
+ sections.push(buildAnalysisContext(analysis));
11533
+ sections.push("");
11534
+ }
11535
+ sections.push(
11061
11536
  "## Selected Eval Templates",
11062
11537
  "",
11063
11538
  templateDescriptions,
11064
11539
  "",
11065
11540
  "Generate concrete, project-specific tasks for each template above."
11066
- ].join("\n");
11541
+ );
11542
+ return sections.join("\n");
11067
11543
  }
11068
- async function generateTasksFromTemplates(claudeMd, projectProfile, templates, config) {
11069
- const userMessage = buildTaskGenerationMessage(claudeMd, projectProfile, templates);
11544
+ async function generateTasksFromTemplates(claudeMd, projectProfile, templates, config, analysis) {
11545
+ const userMessage = buildTaskGenerationMessage(claudeMd, projectProfile, templates, analysis);
11070
11546
  const rawResponse = await callLLM(config, userMessage, {
11071
11547
  systemPrompt: TASK_GENERATION_PROMPT,
11072
11548
  maxTokens: 4096
@@ -11088,10 +11564,10 @@ async function generateTasksFromTemplates(claudeMd, projectProfile, templates, c
11088
11564
 
11089
11565
  // src/evolve/init.ts
11090
11566
  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 });
11567
+ const workspace = path20.join(projectRoot, ".kairn-evolve");
11568
+ await fs20.mkdir(path20.join(workspace, "baseline"), { recursive: true });
11569
+ await fs20.mkdir(path20.join(workspace, "traces"), { recursive: true });
11570
+ await fs20.mkdir(path20.join(workspace, "iterations"), { recursive: true });
11095
11571
  const configObj = {
11096
11572
  model: config.model,
11097
11573
  proposer_model: config.proposerModel,
@@ -11099,8 +11575,8 @@ async function createEvolveWorkspace(projectRoot, config) {
11099
11575
  max_iterations: config.maxIterations,
11100
11576
  parallel_tasks: config.parallelTasks
11101
11577
  };
11102
- await fs19.writeFile(
11103
- path19.join(workspace, "config.yaml"),
11578
+ await fs20.writeFile(
11579
+ path20.join(workspace, "config.yaml"),
11104
11580
  yamlStringify(configObj),
11105
11581
  "utf-8"
11106
11582
  );
@@ -11116,12 +11592,13 @@ async function writeTasksFile(workspacePath, tasks) {
11116
11592
  expected_outcome: t.expected_outcome,
11117
11593
  scoring: t.scoring,
11118
11594
  ...t.rubric ? { rubric: t.rubric } : {},
11119
- timeout: t.timeout
11595
+ timeout: t.timeout,
11596
+ ...t.category ? { category: t.category } : {}
11120
11597
  }))
11121
11598
  };
11122
11599
  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"),
11600
+ await fs20.writeFile(
11601
+ path20.join(workspacePath, "tasks.yaml"),
11125
11602
  header + yamlStringify(doc),
11126
11603
  "utf-8"
11127
11604
  );
@@ -11129,17 +11606,19 @@ async function writeTasksFile(workspacePath, tasks) {
11129
11606
  async function buildProjectProfile(projectRoot) {
11130
11607
  const profile = {
11131
11608
  language: null,
11609
+ languages: [],
11132
11610
  framework: null,
11133
11611
  scripts: {},
11134
11612
  keyFiles: []
11135
11613
  };
11136
11614
  try {
11137
- const pkgStr = await fs19.readFile(
11138
- path19.join(projectRoot, "package.json"),
11615
+ const pkgStr = await fs20.readFile(
11616
+ path20.join(projectRoot, "package.json"),
11139
11617
  "utf-8"
11140
11618
  );
11141
11619
  const pkg2 = JSON.parse(pkgStr);
11142
11620
  profile.language = "typescript";
11621
+ profile.languages = ["typescript"];
11143
11622
  if (pkg2.scripts && typeof pkg2.scripts === "object") {
11144
11623
  profile.scripts = pkg2.scripts;
11145
11624
  }
@@ -11162,18 +11641,20 @@ async function buildProjectProfile(projectRoot) {
11162
11641
  }
11163
11642
  if (!profile.language) {
11164
11643
  try {
11165
- await fs19.access(path19.join(projectRoot, "pyproject.toml"));
11644
+ await fs20.access(path20.join(projectRoot, "pyproject.toml"));
11166
11645
  profile.language = "python";
11646
+ profile.languages = ["python"];
11167
11647
  } catch {
11168
11648
  try {
11169
- await fs19.access(path19.join(projectRoot, "requirements.txt"));
11649
+ await fs20.access(path20.join(projectRoot, "requirements.txt"));
11170
11650
  profile.language = "python";
11651
+ profile.languages = ["python"];
11171
11652
  } catch {
11172
11653
  }
11173
11654
  }
11174
11655
  }
11175
11656
  try {
11176
- const entries = await fs19.readdir(projectRoot);
11657
+ const entries = await fs20.readdir(projectRoot);
11177
11658
  const keyPatterns = [
11178
11659
  "README.md",
11179
11660
  "package.json",
@@ -11189,6 +11670,16 @@ async function buildProjectProfile(projectRoot) {
11189
11670
  }
11190
11671
  return profile;
11191
11672
  }
11673
+ async function loadProjectAnalysis(projectRoot) {
11674
+ try {
11675
+ const cache = await readCache(projectRoot);
11676
+ if (cache?.analysis) {
11677
+ return cache.analysis;
11678
+ }
11679
+ } catch {
11680
+ }
11681
+ return void 0;
11682
+ }
11192
11683
  async function autoGenerateTasks(projectRoot, workflowType) {
11193
11684
  const config = await loadConfig();
11194
11685
  if (!config) {
@@ -11196,15 +11687,16 @@ async function autoGenerateTasks(projectRoot, workflowType) {
11196
11687
  }
11197
11688
  let claudeMd = "";
11198
11689
  try {
11199
- claudeMd = await fs19.readFile(
11200
- path19.join(projectRoot, ".claude", "CLAUDE.md"),
11690
+ claudeMd = await fs20.readFile(
11691
+ path20.join(projectRoot, ".claude", "CLAUDE.md"),
11201
11692
  "utf-8"
11202
11693
  );
11203
11694
  } catch {
11204
11695
  }
11205
11696
  const profile = await buildProjectProfile(projectRoot);
11697
+ const analysis = await loadProjectAnalysis(projectRoot);
11206
11698
  const templates = selectTemplatesForWorkflow(workflowType);
11207
- return generateTasksFromTemplates(claudeMd, profile, templates, config);
11699
+ return generateTasksFromTemplates(claudeMd, profile, templates, config, analysis);
11208
11700
  }
11209
11701
 
11210
11702
  // src/commands/evolve.ts
@@ -11216,8 +11708,8 @@ init_loop();
11216
11708
 
11217
11709
  // src/evolve/report.ts
11218
11710
  init_trace();
11219
- import fs31 from "fs/promises";
11220
- import path31 from "path";
11711
+ import fs32 from "fs/promises";
11712
+ import path32 from "path";
11221
11713
 
11222
11714
  // src/evolve/diagnosis.ts
11223
11715
  function numericScore(s) {
@@ -11267,10 +11759,10 @@ function numericScore2(s) {
11267
11759
  return s.score ?? (s.pass ? 100 : 0);
11268
11760
  }
11269
11761
  async function loadAllIterations(workspacePath) {
11270
- const iterDir = path31.join(workspacePath, "iterations");
11762
+ const iterDir = path32.join(workspacePath, "iterations");
11271
11763
  let entries;
11272
11764
  try {
11273
- entries = await fs31.readdir(iterDir);
11765
+ entries = await fs32.readdir(iterDir);
11274
11766
  } catch {
11275
11767
  return [];
11276
11768
  }
@@ -11284,13 +11776,41 @@ async function loadAllIterations(workspacePath) {
11284
11776
  }
11285
11777
  async function loadTasks(workspacePath) {
11286
11778
  try {
11287
- const content = await fs31.readFile(path31.join(workspacePath, "tasks.yaml"), "utf-8");
11779
+ const content = await fs32.readFile(path32.join(workspacePath, "tasks.yaml"), "utf-8");
11288
11780
  const parsed = yamlParse(content);
11289
- return parsed?.tasks ?? [];
11781
+ const tasks = parsed?.tasks ?? [];
11782
+ return tasks.map((t) => ({
11783
+ ...t,
11784
+ category: t.category ?? "harness-sensitivity"
11785
+ }));
11290
11786
  } catch {
11291
11787
  return [];
11292
11788
  }
11293
11789
  }
11790
+ function computeCategoryBreakdown(tasks, taskResults) {
11791
+ const harnessTasks = [];
11792
+ const substantiveTasks = [];
11793
+ for (const task of tasks) {
11794
+ const result = taskResults[task.id];
11795
+ if (!result) continue;
11796
+ const score = numericScore2(result);
11797
+ const category = task.category ?? "harness-sensitivity";
11798
+ if (category === "harness-sensitivity") {
11799
+ harnessTasks.push(score);
11800
+ } else {
11801
+ substantiveTasks.push(score);
11802
+ }
11803
+ }
11804
+ if (harnessTasks.length === 0 || substantiveTasks.length === 0) {
11805
+ return void 0;
11806
+ }
11807
+ const harnessAvg = harnessTasks.reduce((a, b) => a + b, 0) / harnessTasks.length;
11808
+ const substantiveAvg = substantiveTasks.reduce((a, b) => a + b, 0) / substantiveTasks.length;
11809
+ return {
11810
+ harnessAdherence: { score: harnessAvg, count: harnessTasks.length },
11811
+ substantiveTasks: { score: substantiveAvg, count: substantiveTasks.length }
11812
+ };
11813
+ }
11294
11814
  function buildLeaderboard(iterations, tasks) {
11295
11815
  const taskIds = tasks.map((t) => t.id);
11296
11816
  return taskIds.map((taskId) => {
@@ -11350,6 +11870,11 @@ async function generateMarkdownReport(workspacePath) {
11350
11870
  lines.push(`| Best score | ${bestIter.score.toFixed(1)}% |`);
11351
11871
  lines.push(`| Best iteration | ${bestIter.iteration} |`);
11352
11872
  lines.push(`| Improvement | ${improvement >= 0 ? "+" : ""}${improvement.toFixed(1)} points |`);
11873
+ const categoryBreakdown = computeCategoryBreakdown(tasks, bestIter.taskResults);
11874
+ if (categoryBreakdown) {
11875
+ lines.push(`| Harness adherence | ${categoryBreakdown.harnessAdherence.score.toFixed(1)}% (${categoryBreakdown.harnessAdherence.count} tasks) |`);
11876
+ lines.push(`| Substantive tasks | ${categoryBreakdown.substantiveTasks.score.toFixed(1)}% (${categoryBreakdown.substantiveTasks.count} tasks) |`);
11877
+ }
11353
11878
  lines.push("");
11354
11879
  lines.push("## Iterations");
11355
11880
  lines.push("");
@@ -11433,10 +11958,12 @@ async function generateJsonReport(workspacePath) {
11433
11958
  const iterations = await loadAllIterations(workspacePath);
11434
11959
  const tasks = await loadTasks(workspacePath);
11435
11960
  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 };
11961
+ const bestIterLog = iterations.length > 0 ? iterations.reduce((best, curr) => curr.score > best.score ? curr : best, iterations[0]) : void 0;
11962
+ const bestIter = bestIterLog ?? { score: 0, iteration: 0 };
11437
11963
  const improvement = bestIter.score - baselineScore;
11438
11964
  const counterfactuals = diagnoseCounterfactuals(iterations, tasks);
11439
11965
  const leaderboard = buildLeaderboard(iterations, tasks);
11966
+ const categoryBreakdown = bestIterLog ? computeCategoryBreakdown(tasks, bestIterLog.taskResults) : void 0;
11440
11967
  return {
11441
11968
  overview: {
11442
11969
  title: "Evolution Report",
@@ -11444,7 +11971,8 @@ async function generateJsonReport(workspacePath) {
11444
11971
  baselineScore,
11445
11972
  bestScore: bestIter.score,
11446
11973
  bestIteration: bestIter.iteration,
11447
- improvement
11974
+ improvement,
11975
+ ...categoryBreakdown ? { categoryBreakdown } : {}
11448
11976
  },
11449
11977
  iterations: iterations.map((iter) => {
11450
11978
  const stddevs = Object.values(iter.taskResults).map((s) => s.variance?.stddev).filter((v) => v !== void 0);
@@ -11470,13 +11998,13 @@ init_mutator();
11470
11998
  init_baseline();
11471
11999
  init_mutator();
11472
12000
  init_trace();
11473
- import fs32 from "fs/promises";
11474
- import path32 from "path";
12001
+ import fs33 from "fs/promises";
12002
+ import path33 from "path";
11475
12003
  async function listIterations(workspacePath) {
11476
- const iterationsDir = path32.join(workspacePath, "iterations");
12004
+ const iterationsDir = path33.join(workspacePath, "iterations");
11477
12005
  let entries;
11478
12006
  try {
11479
- entries = await fs32.readdir(iterationsDir);
12007
+ entries = await fs33.readdir(iterationsDir);
11480
12008
  } catch {
11481
12009
  return [];
11482
12010
  }
@@ -11485,7 +12013,7 @@ async function listIterations(workspacePath) {
11485
12013
  const n = parseInt(entry, 10);
11486
12014
  if (!isNaN(n)) {
11487
12015
  try {
11488
- await fs32.access(path32.join(iterationsDir, entry, "harness"));
12016
+ await fs33.access(path33.join(iterationsDir, entry, "harness"));
11489
12017
  nums.push(n);
11490
12018
  } catch {
11491
12019
  }
@@ -11511,16 +12039,16 @@ async function listFilesRecursive(dir) {
11511
12039
  async function walk(current) {
11512
12040
  let entries;
11513
12041
  try {
11514
- entries = await fs32.readdir(current, { withFileTypes: true });
12042
+ entries = await fs33.readdir(current, { withFileTypes: true });
11515
12043
  } catch {
11516
12044
  return;
11517
12045
  }
11518
12046
  for (const entry of entries) {
11519
- const fullPath = path32.join(current, entry.name);
12047
+ const fullPath = path33.join(current, entry.name);
11520
12048
  if (entry.isDirectory()) {
11521
12049
  await walk(fullPath);
11522
12050
  } else {
11523
- results.push(path32.relative(dir, fullPath));
12051
+ results.push(path33.relative(dir, fullPath));
11524
12052
  }
11525
12053
  }
11526
12054
  }
@@ -11528,10 +12056,10 @@ async function listFilesRecursive(dir) {
11528
12056
  return results;
11529
12057
  }
11530
12058
  async function findBestPBTHarness(workspacePath) {
11531
- const branchesDir = path32.join(workspacePath, "branches");
12059
+ const branchesDir = path33.join(workspacePath, "branches");
11532
12060
  let branchEntries;
11533
12061
  try {
11534
- branchEntries = await fs32.readdir(branchesDir);
12062
+ branchEntries = await fs33.readdir(branchesDir);
11535
12063
  } catch {
11536
12064
  return null;
11537
12065
  }
@@ -11539,7 +12067,7 @@ async function findBestPBTHarness(workspacePath) {
11539
12067
  let bestPath = "";
11540
12068
  let bestLabel = "";
11541
12069
  for (const branchId of branchEntries) {
11542
- const branchPath = path32.join(branchesDir, branchId);
12070
+ const branchPath = path33.join(branchesDir, branchId);
11543
12071
  const branchIterations = await listIterations(branchPath);
11544
12072
  if (branchIterations.length === 0) continue;
11545
12073
  const bestIter = await findBestIteration(branchPath, branchIterations);
@@ -11547,13 +12075,13 @@ async function findBestPBTHarness(workspacePath) {
11547
12075
  const score = log?.score ?? 0;
11548
12076
  if (score > bestScore) {
11549
12077
  bestScore = score;
11550
- bestPath = path32.join(branchPath, "iterations", bestIter.toString(), "harness");
12078
+ bestPath = path33.join(branchPath, "iterations", bestIter.toString(), "harness");
11551
12079
  bestLabel = `branch ${branchId}, iteration ${bestIter} (${score.toFixed(1)}%)`;
11552
12080
  }
11553
12081
  }
11554
- const synthesisHarness = path32.join(workspacePath, "synthesis", "harness");
12082
+ const synthesisHarness = path33.join(workspacePath, "synthesis", "harness");
11555
12083
  try {
11556
- await fs32.access(synthesisHarness);
12084
+ await fs33.access(synthesisHarness);
11557
12085
  const synthesisLog = await loadIterationLog(workspacePath, 999);
11558
12086
  const synthScore = synthesisLog?.score ?? 0;
11559
12087
  if (synthScore > bestScore) {
@@ -11572,26 +12100,26 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration, pbt)
11572
12100
  if (!pbtResult) {
11573
12101
  throw new Error("No PBT results found. Run `kairn evolve pbt` first.");
11574
12102
  }
11575
- const claudeDir2 = path32.join(projectRoot, ".claude");
12103
+ const claudeDir2 = path33.join(projectRoot, ".claude");
11576
12104
  const diffPreview2 = await generateDiff2(claudeDir2, pbtResult.harnessPath);
11577
12105
  const currentFiles2 = await listFilesRecursive(claudeDir2);
11578
12106
  const targetFiles2 = await listFilesRecursive(pbtResult.harnessPath);
11579
12107
  const allPaths2 = /* @__PURE__ */ new Set([...currentFiles2, ...targetFiles2]);
11580
12108
  const filesChanged2 = [];
11581
12109
  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);
12110
+ const currentContent = await fs33.readFile(path33.join(claudeDir2, filePath), "utf-8").catch(() => null);
12111
+ const targetContent = await fs33.readFile(path33.join(pbtResult.harnessPath, filePath), "utf-8").catch(() => null);
11584
12112
  if (currentContent !== targetContent) {
11585
12113
  filesChanged2.push(filePath);
11586
12114
  }
11587
12115
  }
11588
- await fs32.rm(claudeDir2, { recursive: true, force: true });
12116
+ await fs33.rm(claudeDir2, { recursive: true, force: true });
11589
12117
  await copyDir(pbtResult.harnessPath, claudeDir2);
11590
- const harnessMcpJson2 = path32.join(pbtResult.harnessPath, ".mcp.json");
11591
- const projectMcpJson2 = path32.join(projectRoot, ".mcp.json");
12118
+ const harnessMcpJson2 = path33.join(pbtResult.harnessPath, ".mcp.json");
12119
+ const projectMcpJson2 = path33.join(projectRoot, ".mcp.json");
11592
12120
  try {
11593
- await fs32.access(harnessMcpJson2);
11594
- await fs32.copyFile(harnessMcpJson2, projectMcpJson2);
12121
+ await fs33.access(harnessMcpJson2);
12122
+ await fs33.copyFile(harnessMcpJson2, projectMcpJson2);
11595
12123
  if (!filesChanged2.includes(".mcp.json")) filesChanged2.push(".mcp.json");
11596
12124
  } catch {
11597
12125
  }
@@ -11617,37 +12145,37 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration, pbt)
11617
12145
  } else {
11618
12146
  iter = await findBestIteration(workspacePath, iterations);
11619
12147
  }
11620
- const harnessPath = path32.join(
12148
+ const harnessPath = path33.join(
11621
12149
  workspacePath,
11622
12150
  "iterations",
11623
12151
  iter.toString(),
11624
12152
  "harness"
11625
12153
  );
11626
- const claudeDir = path32.join(projectRoot, ".claude");
12154
+ const claudeDir = path33.join(projectRoot, ".claude");
11627
12155
  const diffPreview = await generateDiff2(claudeDir, harnessPath);
11628
12156
  const currentFiles = await listFilesRecursive(claudeDir);
11629
12157
  const targetFiles = await listFilesRecursive(harnessPath);
11630
12158
  const allPaths = /* @__PURE__ */ new Set([...currentFiles, ...targetFiles]);
11631
12159
  const filesChanged = [];
11632
12160
  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);
12161
+ const currentContent = await fs33.readFile(path33.join(claudeDir, filePath), "utf-8").catch(() => null);
12162
+ const targetContent = await fs33.readFile(path33.join(harnessPath, filePath), "utf-8").catch(() => null);
11635
12163
  if (currentContent !== targetContent) {
11636
12164
  filesChanged.push(filePath);
11637
12165
  }
11638
12166
  }
11639
- await fs32.rm(claudeDir, { recursive: true, force: true });
12167
+ await fs33.rm(claudeDir, { recursive: true, force: true });
11640
12168
  await copyDir(harnessPath, claudeDir);
11641
- const harnessMcpJson = path32.join(harnessPath, ".mcp.json");
11642
- const projectMcpJson = path32.join(projectRoot, ".mcp.json");
12169
+ const harnessMcpJson = path33.join(harnessPath, ".mcp.json");
12170
+ const projectMcpJson = path33.join(projectRoot, ".mcp.json");
11643
12171
  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);
12172
+ await fs33.access(harnessMcpJson);
12173
+ const currentMcp = await fs33.readFile(projectMcpJson, "utf-8").catch(() => null);
12174
+ const targetMcp = await fs33.readFile(harnessMcpJson, "utf-8").catch(() => null);
11647
12175
  if (currentMcp !== targetMcp) {
11648
12176
  filesChanged.push(".mcp.json");
11649
12177
  }
11650
- await fs32.copyFile(harnessMcpJson, projectMcpJson);
12178
+ await fs33.copyFile(harnessMcpJson, projectMcpJson);
11651
12179
  } catch {
11652
12180
  }
11653
12181
  return {
@@ -11680,7 +12208,7 @@ var DEFAULT_CONFIG = {
11680
12208
  };
11681
12209
  async function loadEvolveConfigFromWorkspace(workspacePath) {
11682
12210
  try {
11683
- const configStr = await fs35.readFile(path36.join(workspacePath, "config.yaml"), "utf-8");
12211
+ const configStr = await fs36.readFile(path37.join(workspacePath, "config.yaml"), "utf-8");
11684
12212
  const parsed = yamlParse2(configStr);
11685
12213
  return {
11686
12214
  model: parsed.model ?? DEFAULT_CONFIG.model,
@@ -11710,9 +12238,9 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11710
12238
  try {
11711
12239
  const projectRoot = process.cwd();
11712
12240
  console.log(ui.section("Evolve Init"));
11713
- const claudeDir = path36.join(projectRoot, ".claude");
12241
+ const claudeDir = path37.join(projectRoot, ".claude");
11714
12242
  try {
11715
- await fs35.access(claudeDir);
12243
+ await fs36.access(claudeDir);
11716
12244
  } catch {
11717
12245
  console.log(ui.error("No .claude/ directory found. Run kairn describe first."));
11718
12246
  process.exit(1);
@@ -11762,7 +12290,7 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11762
12290
  if (config) {
11763
12291
  let claudeMd = "";
11764
12292
  try {
11765
- claudeMd = await fs35.readFile(path36.join(claudeDir, "CLAUDE.md"), "utf-8");
12293
+ claudeMd = await fs36.readFile(path37.join(claudeDir, "CLAUDE.md"), "utf-8");
11766
12294
  } catch {
11767
12295
  }
11768
12296
  const profile = await buildProjectProfile(projectRoot);
@@ -11793,16 +12321,16 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
11793
12321
  evolveCommand.command("baseline").description("Snapshot current .claude/ directory as baseline").action(async () => {
11794
12322
  try {
11795
12323
  const projectRoot = process.cwd();
11796
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12324
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
11797
12325
  console.log(ui.section("Evolve Baseline"));
11798
12326
  try {
11799
- await fs35.access(workspace);
12327
+ await fs36.access(workspace);
11800
12328
  } catch {
11801
12329
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
11802
12330
  process.exit(1);
11803
12331
  }
11804
12332
  await snapshotBaseline(projectRoot, workspace);
11805
- const baselineDir = path36.join(workspace, "baseline");
12333
+ const baselineDir = path37.join(workspace, "baseline");
11806
12334
  const fileCount = await countFiles(baselineDir);
11807
12335
  console.log(ui.success(`Baseline snapshot created (${fileCount} files)`));
11808
12336
  } catch (err) {
@@ -11814,18 +12342,18 @@ evolveCommand.command("baseline").description("Snapshot current .claude/ directo
11814
12342
  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
12343
  try {
11816
12344
  const projectRoot = process.cwd();
11817
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12345
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
11818
12346
  console.log(ui.section("Evolve Run"));
11819
12347
  try {
11820
- await fs35.access(workspace);
12348
+ await fs36.access(workspace);
11821
12349
  } catch {
11822
12350
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
11823
12351
  process.exit(1);
11824
12352
  }
11825
- const tasksPath = path36.join(workspace, "tasks.yaml");
12353
+ const tasksPath = path37.join(workspace, "tasks.yaml");
11826
12354
  let tasksContent;
11827
12355
  try {
11828
- tasksContent = await fs35.readFile(tasksPath, "utf-8");
12356
+ tasksContent = await fs36.readFile(tasksPath, "utf-8");
11829
12357
  } catch {
11830
12358
  console.log(ui.error("No tasks.yaml found. Run kairn evolve init first."));
11831
12359
  process.exit(1);
@@ -11844,15 +12372,15 @@ evolveCommand.command("run").description("Run tasks against the current harness"
11844
12372
  console.log(ui.info(`Running ${tasksToRun.length} task(s)...`));
11845
12373
  console.log("");
11846
12374
  const config = await loadConfig();
11847
- const harnessPath = path36.join(projectRoot, ".claude");
12375
+ const harnessPath = path37.join(projectRoot, ".claude");
11848
12376
  const results = [];
11849
12377
  for (const task of tasksToRun) {
11850
- const traceDir = path36.join(workspace, "traces", "0", task.id);
12378
+ const traceDir = path37.join(workspace, "traces", "0", task.id);
11851
12379
  const spinner = ora2(`Running: ${task.id}`).start();
11852
12380
  const result = await runTask(task, harnessPath, traceDir, 0);
11853
12381
  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(() => "");
12382
+ const stdout = await fs36.readFile(path37.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
12383
+ const stderr = await fs36.readFile(path37.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
11856
12384
  const score = await scoreTask(task, traceDir, stdout, stderr, config);
11857
12385
  result.score = score;
11858
12386
  await writeScore(traceDir, score);
@@ -12019,7 +12547,7 @@ evolveCommand.command("run").description("Run tasks against the current harness"
12019
12547
  }
12020
12548
  }
12021
12549
  try {
12022
- await fs35.access(path36.join(workspace, "iterations", "0", "harness"));
12550
+ await fs36.access(path37.join(workspace, "iterations", "0", "harness"));
12023
12551
  } catch {
12024
12552
  console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
12025
12553
  process.exit(1);
@@ -12126,16 +12654,16 @@ evolveCommand.command("run").description("Run tasks against the current harness"
12126
12654
  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
12655
  try {
12128
12656
  const projectRoot = process.cwd();
12129
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12657
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12130
12658
  console.log(ui.section("Evolve PBT"));
12131
12659
  try {
12132
- await fs35.access(workspace);
12660
+ await fs36.access(workspace);
12133
12661
  } catch {
12134
12662
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12135
12663
  process.exit(1);
12136
12664
  }
12137
12665
  try {
12138
- await fs35.access(path36.join(workspace, "iterations", "0", "harness"));
12666
+ await fs36.access(path37.join(workspace, "iterations", "0", "harness"));
12139
12667
  } catch {
12140
12668
  console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
12141
12669
  process.exit(1);
@@ -12155,8 +12683,8 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
12155
12683
  if (sampling === "thompson" || sampling === "uniform") {
12156
12684
  evolveConfig.samplingStrategy = sampling;
12157
12685
  }
12158
- const tasksPath = path36.join(workspace, "tasks.yaml");
12159
- const tasksContent = await fs35.readFile(tasksPath, "utf-8");
12686
+ const tasksPath = path37.join(workspace, "tasks.yaml");
12687
+ const tasksContent = await fs36.readFile(tasksPath, "utf-8");
12160
12688
  const parsed = yamlParse2(tasksContent);
12161
12689
  if (!parsed?.tasks || parsed.tasks.length === 0) {
12162
12690
  console.log(ui.error("No tasks found in tasks.yaml"));
@@ -12214,10 +12742,10 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
12214
12742
  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
12743
  try {
12216
12744
  const projectRoot = process.cwd();
12217
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12745
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12218
12746
  console.log(ui.section("Evolve Apply"));
12219
12747
  try {
12220
- await fs35.access(workspace);
12748
+ await fs36.access(workspace);
12221
12749
  } catch {
12222
12750
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12223
12751
  process.exit(1);
@@ -12258,9 +12786,9 @@ evolveCommand.command("apply").description("Apply the best evolved harness to yo
12258
12786
  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
12787
  try {
12260
12788
  const projectRoot = process.cwd();
12261
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12789
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12262
12790
  try {
12263
- await fs35.access(workspace);
12791
+ await fs36.access(workspace);
12264
12792
  } catch {
12265
12793
  console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
12266
12794
  process.exit(1);
@@ -12281,23 +12809,23 @@ evolveCommand.command("report").description("Generate a summary report of the ev
12281
12809
  evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes between two iterations").action(async (iter1Str, iter2Str) => {
12282
12810
  try {
12283
12811
  const projectRoot = process.cwd();
12284
- const workspace = path36.join(projectRoot, ".kairn-evolve");
12812
+ const workspace = path37.join(projectRoot, ".kairn-evolve");
12285
12813
  const iter1 = parseInt(iter1Str, 10);
12286
12814
  const iter2 = parseInt(iter2Str, 10);
12287
12815
  if (isNaN(iter1) || isNaN(iter2)) {
12288
12816
  console.log(ui.error("Both arguments must be integers (iteration numbers)"));
12289
12817
  process.exit(1);
12290
12818
  }
12291
- const harness1 = path36.join(workspace, "iterations", iter1.toString(), "harness");
12292
- const harness2 = path36.join(workspace, "iterations", iter2.toString(), "harness");
12819
+ const harness1 = path37.join(workspace, "iterations", iter1.toString(), "harness");
12820
+ const harness2 = path37.join(workspace, "iterations", iter2.toString(), "harness");
12293
12821
  try {
12294
- await fs35.access(harness1);
12822
+ await fs36.access(harness1);
12295
12823
  } catch {
12296
12824
  console.log(ui.error(`Iteration ${iter1} harness not found at ${harness1}`));
12297
12825
  process.exit(1);
12298
12826
  }
12299
12827
  try {
12300
- await fs35.access(harness2);
12828
+ await fs36.access(harness2);
12301
12829
  } catch {
12302
12830
  console.log(ui.error(`Iteration ${iter2} harness not found at ${harness2}`));
12303
12831
  process.exit(1);
@@ -12404,7 +12932,7 @@ evolveCommand.command("research").description("Run cross-repo research to discov
12404
12932
  const reportText = formatResearchReport2(report);
12405
12933
  console.log("\n" + reportText);
12406
12934
  if (options.output) {
12407
- await fs35.writeFile(options.output, reportText, "utf-8");
12935
+ await fs36.writeFile(options.output, reportText, "utf-8");
12408
12936
  console.log(chalk14.green(`
12409
12937
  Report saved to ${options.output}`));
12410
12938
  }
@@ -12421,10 +12949,10 @@ evolveCommand.command("research").description("Run cross-repo research to discov
12421
12949
  async function countFiles(dir) {
12422
12950
  let count = 0;
12423
12951
  try {
12424
- const entries = await fs35.readdir(dir, { withFileTypes: true });
12952
+ const entries = await fs36.readdir(dir, { withFileTypes: true });
12425
12953
  for (const entry of entries) {
12426
12954
  if (entry.isDirectory()) {
12427
- count += await countFiles(path36.join(dir, entry.name));
12955
+ count += await countFiles(path37.join(dir, entry.name));
12428
12956
  } else {
12429
12957
  count++;
12430
12958
  }
@@ -12440,12 +12968,42 @@ init_scan();
12440
12968
  init_analyze();
12441
12969
  init_types3();
12442
12970
  init_cache();
12971
+ init_proposer();
12443
12972
  init_ui();
12444
12973
  init_logo();
12445
12974
  import { Command as Command12 } from "commander";
12446
12975
  import chalk15 from "chalk";
12976
+ import fs37 from "fs/promises";
12977
+ import path38 from "path";
12447
12978
  import ora3 from "ora";
12448
12979
  async function analyzeAction(options) {
12980
+ if (options.ir) {
12981
+ const irPath = path38.join(process.cwd(), ".kairn", "harness-ir.json");
12982
+ try {
12983
+ const raw = await fs37.readFile(irPath, "utf-8");
12984
+ const ir = JSON.parse(raw);
12985
+ if (!options.json) {
12986
+ printCompactBanner();
12987
+ console.log(ui.section("Harness IR"));
12988
+ console.log(buildIRSummary(ir));
12989
+ console.log("");
12990
+ console.log(chalk15.dim(` Source: ${irPath}`));
12991
+ console.log("");
12992
+ } else {
12993
+ console.log(JSON.stringify(ir, null, 2));
12994
+ }
12995
+ } catch {
12996
+ if (options.json) {
12997
+ console.log(JSON.stringify({ error: "No harness IR found. Run `kairn optimize` first." }));
12998
+ } else {
12999
+ printCompactBanner();
13000
+ console.log(ui.warn("No harness IR found. Run `kairn optimize` first."));
13001
+ console.log("");
13002
+ }
13003
+ process.exit(1);
13004
+ }
13005
+ return;
13006
+ }
12449
13007
  if (!options.json) {
12450
13008
  printCompactBanner();
12451
13009
  }
@@ -12473,8 +13031,8 @@ async function analyzeAction(options) {
12473
13031
  const profile = await scanProject(targetDir);
12474
13032
  scanSpinner?.succeed("Project scanned");
12475
13033
  if (!options.json) {
12476
- if (profile.language)
12477
- console.log(ui.kv("Language:", profile.language));
13034
+ if (profile.languages.length > 0)
13035
+ console.log(ui.kv("Languages:", profile.languages.join(", ")));
12478
13036
  if (profile.framework)
12479
13037
  console.log(ui.kv("Framework:", profile.framework));
12480
13038
  }
@@ -12498,11 +13056,14 @@ async function analyzeAction(options) {
12498
13056
  indent: 2
12499
13057
  }).start();
12500
13058
  let analysis;
13059
+ let packedSource = "";
12501
13060
  try {
12502
- analysis = await analyzeProject(targetDir, profile, config, {
13061
+ const result = await analyzeProject(targetDir, profile, config, {
12503
13062
  refresh: options.refresh,
12504
13063
  tokenBudget: options.tokenBudget
12505
13064
  });
13065
+ analysis = result.analysis;
13066
+ packedSource = result.packedSource;
12506
13067
  analysisSpinner?.succeed(
12507
13068
  options.refresh ? "Re-analyzed from scratch" : "Codebase analyzed"
12508
13069
  );
@@ -12580,9 +13141,10 @@ async function analyzeAction(options) {
12580
13141
  }
12581
13142
  }
12582
13143
  console.log("");
13144
+ const packedStats = packedSource ? ` \xB7 ${packedSource.length.toLocaleString()} chars packed` : "";
12583
13145
  console.log(
12584
13146
  chalk15.dim(
12585
- ` Sampled ${analysis.sampled_files.length} files \xB7 analyzed ${analysis.analyzed_at}`
13147
+ ` Sampled ${analysis.sampled_files.length} files${packedStats} \xB7 analyzed ${analysis.analyzed_at}`
12586
13148
  )
12587
13149
  );
12588
13150
  console.log(ui.divider());
@@ -12590,7 +13152,7 @@ async function analyzeAction(options) {
12590
13152
  }
12591
13153
  var analyzeCommand = new Command12("analyze").description(
12592
13154
  "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);
13155
+ ).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
13156
 
12595
13157
  // src/cli.ts
12596
13158
  var require2 = createRequire(import.meta.url);