kairn-cli 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1765 -163
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1409,68 +1409,1589 @@ Return ONLY valid JSON.`;
|
|
|
1409
1409
|
}
|
|
1410
1410
|
});
|
|
1411
1411
|
|
|
1412
|
+
// src/ir/types.ts
|
|
1413
|
+
function createEmptyIR() {
|
|
1414
|
+
return {
|
|
1415
|
+
meta: {
|
|
1416
|
+
name: "",
|
|
1417
|
+
purpose: "",
|
|
1418
|
+
techStack: { language: "" },
|
|
1419
|
+
autonomyLevel: 2
|
|
1420
|
+
},
|
|
1421
|
+
sections: [],
|
|
1422
|
+
commands: [],
|
|
1423
|
+
rules: [],
|
|
1424
|
+
agents: [],
|
|
1425
|
+
skills: [],
|
|
1426
|
+
docs: [],
|
|
1427
|
+
hooks: [],
|
|
1428
|
+
settings: createEmptySettings(),
|
|
1429
|
+
mcpServers: [],
|
|
1430
|
+
intents: []
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function createEmptySettings() {
|
|
1434
|
+
return { hooks: {}, raw: {} };
|
|
1435
|
+
}
|
|
1436
|
+
function createSection(id, heading, content, order) {
|
|
1437
|
+
return { id, heading, content, order };
|
|
1438
|
+
}
|
|
1439
|
+
function createCommandNode(name, content, description) {
|
|
1440
|
+
return { name, description: description ?? "", content };
|
|
1441
|
+
}
|
|
1442
|
+
function createRuleNode(name, content, paths) {
|
|
1443
|
+
const node = { name, content };
|
|
1444
|
+
if (paths !== void 0) {
|
|
1445
|
+
node.paths = paths;
|
|
1446
|
+
}
|
|
1447
|
+
return node;
|
|
1448
|
+
}
|
|
1449
|
+
function createAgentNode(name, content, model) {
|
|
1450
|
+
const node = { name, content };
|
|
1451
|
+
if (model !== void 0) {
|
|
1452
|
+
node.model = model;
|
|
1453
|
+
}
|
|
1454
|
+
return node;
|
|
1455
|
+
}
|
|
1456
|
+
function createEmptyDiff() {
|
|
1457
|
+
return {
|
|
1458
|
+
sections: {
|
|
1459
|
+
added: [],
|
|
1460
|
+
removed: [],
|
|
1461
|
+
modified: [],
|
|
1462
|
+
reordered: []
|
|
1463
|
+
},
|
|
1464
|
+
commands: {
|
|
1465
|
+
added: [],
|
|
1466
|
+
removed: [],
|
|
1467
|
+
modified: []
|
|
1468
|
+
},
|
|
1469
|
+
rules: {
|
|
1470
|
+
added: [],
|
|
1471
|
+
removed: [],
|
|
1472
|
+
modified: []
|
|
1473
|
+
},
|
|
1474
|
+
agents: {
|
|
1475
|
+
added: [],
|
|
1476
|
+
removed: [],
|
|
1477
|
+
modified: []
|
|
1478
|
+
},
|
|
1479
|
+
mcpServers: {
|
|
1480
|
+
added: [],
|
|
1481
|
+
removed: []
|
|
1482
|
+
},
|
|
1483
|
+
settings: {
|
|
1484
|
+
changes: []
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
var init_types = __esm({
|
|
1489
|
+
"src/ir/types.ts"() {
|
|
1490
|
+
"use strict";
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// src/ir/parser.ts
|
|
1495
|
+
import fs21 from "fs/promises";
|
|
1496
|
+
import path21 from "path";
|
|
1497
|
+
function slugify(heading) {
|
|
1498
|
+
return heading.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1499
|
+
}
|
|
1500
|
+
function resolveSectionId(heading) {
|
|
1501
|
+
const trimmed = heading.trim();
|
|
1502
|
+
for (const entry of SECTION_ID_MAP) {
|
|
1503
|
+
if (entry.pattern.test(trimmed)) {
|
|
1504
|
+
return entry.id;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return `custom-${slugify(trimmed)}`;
|
|
1508
|
+
}
|
|
1509
|
+
function parseYamlFrontmatter(content) {
|
|
1510
|
+
if (!content.startsWith("---\n") && !content.startsWith("---\r\n")) {
|
|
1511
|
+
return { frontmatter: {}, body: content };
|
|
1512
|
+
}
|
|
1513
|
+
const secondDash = content.indexOf("\n---", 3);
|
|
1514
|
+
if (secondDash === -1) {
|
|
1515
|
+
return { frontmatter: {}, body: content };
|
|
1516
|
+
}
|
|
1517
|
+
const yamlBlock = content.slice(4, secondDash);
|
|
1518
|
+
const afterClose = secondDash + 4;
|
|
1519
|
+
const body = content.slice(afterClose).replace(/^\r?\n/, "");
|
|
1520
|
+
const frontmatter = {};
|
|
1521
|
+
const lines = yamlBlock.split("\n");
|
|
1522
|
+
let currentKey = null;
|
|
1523
|
+
let currentList = null;
|
|
1524
|
+
for (const line of lines) {
|
|
1525
|
+
const trimmed = line.trimEnd();
|
|
1526
|
+
if (currentKey !== null && /^\s+-\s+/.test(trimmed)) {
|
|
1527
|
+
if (currentList === null) {
|
|
1528
|
+
currentList = [];
|
|
1529
|
+
}
|
|
1530
|
+
let value = trimmed.replace(/^\s+-\s+/, "").trim();
|
|
1531
|
+
value = stripQuotes(value);
|
|
1532
|
+
currentList.push(value);
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
if (currentKey !== null && currentList !== null) {
|
|
1536
|
+
frontmatter[currentKey] = currentList;
|
|
1537
|
+
currentKey = null;
|
|
1538
|
+
currentList = null;
|
|
1539
|
+
}
|
|
1540
|
+
const colonIdx = trimmed.indexOf(":");
|
|
1541
|
+
if (colonIdx > 0) {
|
|
1542
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
1543
|
+
const rawValue = trimmed.slice(colonIdx + 1).trim();
|
|
1544
|
+
if (rawValue === "") {
|
|
1545
|
+
currentKey = key;
|
|
1546
|
+
currentList = null;
|
|
1547
|
+
} else {
|
|
1548
|
+
frontmatter[key] = stripQuotes(rawValue);
|
|
1549
|
+
currentKey = null;
|
|
1550
|
+
currentList = null;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (currentKey !== null && currentList !== null) {
|
|
1555
|
+
frontmatter[currentKey] = currentList;
|
|
1556
|
+
}
|
|
1557
|
+
return { frontmatter, body };
|
|
1558
|
+
}
|
|
1559
|
+
function stripQuotes(value) {
|
|
1560
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1561
|
+
return value.slice(1, -1);
|
|
1562
|
+
}
|
|
1563
|
+
return value;
|
|
1564
|
+
}
|
|
1565
|
+
function parseClaudeMd(content) {
|
|
1566
|
+
const meta = {
|
|
1567
|
+
techStack: { language: "" },
|
|
1568
|
+
autonomyLevel: 2
|
|
1569
|
+
};
|
|
1570
|
+
const sections = [];
|
|
1571
|
+
const chunks = content.split(/^## /gm);
|
|
1572
|
+
const preamble = chunks[0];
|
|
1573
|
+
const titleMatch = preamble.match(/^# (.+)$/m);
|
|
1574
|
+
if (titleMatch) {
|
|
1575
|
+
meta.name = titleMatch[1].trim();
|
|
1576
|
+
} else {
|
|
1577
|
+
meta.name = "";
|
|
1578
|
+
}
|
|
1579
|
+
const preambleHeading = meta.name ? `# ${meta.name}` : "";
|
|
1580
|
+
const preambleContent = titleMatch ? preamble.slice(preamble.indexOf(titleMatch[0]) + titleMatch[0].length).trim() : preamble.trim();
|
|
1581
|
+
sections.push({
|
|
1582
|
+
id: "preamble",
|
|
1583
|
+
heading: preambleHeading,
|
|
1584
|
+
content: preambleContent,
|
|
1585
|
+
order: 0
|
|
1586
|
+
});
|
|
1587
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
1588
|
+
const chunk = chunks[i];
|
|
1589
|
+
const newlineIdx = chunk.indexOf("\n");
|
|
1590
|
+
const heading = newlineIdx >= 0 ? chunk.slice(0, newlineIdx).trim() : chunk.trim();
|
|
1591
|
+
const sectionContent = newlineIdx >= 0 ? chunk.slice(newlineIdx + 1).trim() : "";
|
|
1592
|
+
const sectionId = resolveSectionId(heading);
|
|
1593
|
+
sections.push({
|
|
1594
|
+
id: sectionId,
|
|
1595
|
+
heading: `## ${heading}`,
|
|
1596
|
+
content: sectionContent,
|
|
1597
|
+
order: i
|
|
1598
|
+
});
|
|
1599
|
+
if (sectionId === "purpose") {
|
|
1600
|
+
const firstParagraph = sectionContent.split(/\n\n/)[0].trim();
|
|
1601
|
+
meta.purpose = firstParagraph;
|
|
1602
|
+
}
|
|
1603
|
+
if (sectionId === "tech-stack") {
|
|
1604
|
+
meta.techStack = extractTechStack(sectionContent);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return { meta, sections };
|
|
1608
|
+
}
|
|
1609
|
+
function extractTechStack(content) {
|
|
1610
|
+
const stack = { language: "" };
|
|
1611
|
+
const lines = content.split("\n");
|
|
1612
|
+
for (const line of lines) {
|
|
1613
|
+
const trimmed = line.trim();
|
|
1614
|
+
if (!trimmed.startsWith("-")) continue;
|
|
1615
|
+
const bullet = trimmed.slice(1).trim().toLowerCase();
|
|
1616
|
+
if (!stack.language && (bullet.includes("typescript") || bullet.includes("javascript"))) {
|
|
1617
|
+
stack.language = bullet.includes("typescript") ? "TypeScript" : "JavaScript";
|
|
1618
|
+
} else if (!stack.language && bullet.includes("python")) {
|
|
1619
|
+
stack.language = "Python";
|
|
1620
|
+
} else if (!stack.language && bullet.includes("rust")) {
|
|
1621
|
+
stack.language = "Rust";
|
|
1622
|
+
} else if (!stack.language && bullet.includes("go ") || !stack.language && bullet.startsWith("go,")) {
|
|
1623
|
+
stack.language = "Go";
|
|
1624
|
+
}
|
|
1625
|
+
if (!stack.buildTool) {
|
|
1626
|
+
const buildTools = [
|
|
1627
|
+
"tsup",
|
|
1628
|
+
"webpack",
|
|
1629
|
+
"vite",
|
|
1630
|
+
"esbuild",
|
|
1631
|
+
"rollup",
|
|
1632
|
+
"parcel",
|
|
1633
|
+
"turbopack",
|
|
1634
|
+
"swc",
|
|
1635
|
+
"cargo",
|
|
1636
|
+
"make",
|
|
1637
|
+
"cmake"
|
|
1638
|
+
];
|
|
1639
|
+
for (const tool of buildTools) {
|
|
1640
|
+
if (bullet.includes(tool)) {
|
|
1641
|
+
stack.buildTool = tool;
|
|
1642
|
+
break;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
if (!stack.testRunner) {
|
|
1647
|
+
const testRunners = [
|
|
1648
|
+
"vitest",
|
|
1649
|
+
"jest",
|
|
1650
|
+
"mocha",
|
|
1651
|
+
"ava",
|
|
1652
|
+
"tap",
|
|
1653
|
+
"pytest",
|
|
1654
|
+
"cargo test"
|
|
1655
|
+
];
|
|
1656
|
+
for (const runner of testRunners) {
|
|
1657
|
+
if (bullet.includes(runner)) {
|
|
1658
|
+
stack.testRunner = runner;
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (!stack.framework) {
|
|
1664
|
+
const frameworks = [
|
|
1665
|
+
"commander.js",
|
|
1666
|
+
"commander",
|
|
1667
|
+
"express",
|
|
1668
|
+
"fastify",
|
|
1669
|
+
"next.js",
|
|
1670
|
+
"nextjs",
|
|
1671
|
+
"react",
|
|
1672
|
+
"vue",
|
|
1673
|
+
"angular",
|
|
1674
|
+
"svelte",
|
|
1675
|
+
"django",
|
|
1676
|
+
"flask",
|
|
1677
|
+
"actix",
|
|
1678
|
+
"axum"
|
|
1679
|
+
];
|
|
1680
|
+
for (const fw of frameworks) {
|
|
1681
|
+
if (bullet.includes(fw)) {
|
|
1682
|
+
stack.framework = fw.charAt(0).toUpperCase() + fw.slice(1);
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
if (!stack.packageManager) {
|
|
1688
|
+
const pkgManagers = ["pnpm", "yarn", "bun", "npm", "cargo", "pip"];
|
|
1689
|
+
for (const pm of pkgManagers) {
|
|
1690
|
+
if (bullet.includes(`${pm} `)) {
|
|
1691
|
+
stack.packageManager = pm;
|
|
1692
|
+
break;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return stack;
|
|
1698
|
+
}
|
|
1699
|
+
function parseSettings(content) {
|
|
1700
|
+
const parsed = JSON.parse(content);
|
|
1701
|
+
const settings = createEmptySettings();
|
|
1702
|
+
const permissions = parsed["permissions"];
|
|
1703
|
+
if (permissions) {
|
|
1704
|
+
const deny = permissions["deny"];
|
|
1705
|
+
if (Array.isArray(deny) && deny.length > 0) {
|
|
1706
|
+
settings.denyPatterns = deny;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
const statusLine = parsed["statusLine"];
|
|
1710
|
+
if (statusLine && typeof statusLine.command === "string") {
|
|
1711
|
+
settings.statusLine = { command: statusLine.command };
|
|
1712
|
+
}
|
|
1713
|
+
const hooksRaw = parsed["hooks"];
|
|
1714
|
+
if (hooksRaw && typeof hooksRaw === "object") {
|
|
1715
|
+
const knownEvents = [
|
|
1716
|
+
"PreToolUse",
|
|
1717
|
+
"PostToolUse",
|
|
1718
|
+
"UserPromptSubmit",
|
|
1719
|
+
"SessionStart",
|
|
1720
|
+
"PostCompact"
|
|
1721
|
+
];
|
|
1722
|
+
for (const event of knownEvents) {
|
|
1723
|
+
const entries = hooksRaw[event];
|
|
1724
|
+
if (Array.isArray(entries) && entries.length > 0) {
|
|
1725
|
+
settings.hooks[event] = entries;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
const extractedKeys = /* @__PURE__ */ new Set(["permissions", "hooks", "statusLine"]);
|
|
1730
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1731
|
+
if (!extractedKeys.has(key)) {
|
|
1732
|
+
settings.raw[key] = value;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return settings;
|
|
1736
|
+
}
|
|
1737
|
+
function parseMcpConfig(content) {
|
|
1738
|
+
const parsed = JSON.parse(content);
|
|
1739
|
+
const servers = parsed["mcpServers"];
|
|
1740
|
+
if (!servers || typeof servers !== "object") {
|
|
1741
|
+
return [];
|
|
1742
|
+
}
|
|
1743
|
+
const nodes = [];
|
|
1744
|
+
for (const [id, config] of Object.entries(servers)) {
|
|
1745
|
+
const command = config["command"];
|
|
1746
|
+
const args = config["args"] ?? [];
|
|
1747
|
+
const env = config["env"];
|
|
1748
|
+
const node = { id, command, args };
|
|
1749
|
+
if (env && typeof env === "object" && Object.keys(env).length > 0) {
|
|
1750
|
+
node.env = env;
|
|
1751
|
+
}
|
|
1752
|
+
nodes.push(node);
|
|
1753
|
+
}
|
|
1754
|
+
return nodes;
|
|
1755
|
+
}
|
|
1756
|
+
async function readDirSafe(dirPath) {
|
|
1757
|
+
try {
|
|
1758
|
+
return await fs21.readdir(dirPath);
|
|
1759
|
+
} catch {
|
|
1760
|
+
return [];
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
async function readFileSafe2(filePath) {
|
|
1764
|
+
try {
|
|
1765
|
+
return await fs21.readFile(filePath, "utf-8");
|
|
1766
|
+
} catch {
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async function isDirectory(filePath) {
|
|
1771
|
+
try {
|
|
1772
|
+
const stat = await fs21.stat(filePath);
|
|
1773
|
+
return stat.isDirectory();
|
|
1774
|
+
} catch {
|
|
1775
|
+
return false;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
async function parseCommands(harnessPath) {
|
|
1779
|
+
const dirPath = path21.join(harnessPath, "commands");
|
|
1780
|
+
const entries = await readDirSafe(dirPath);
|
|
1781
|
+
const nodes = [];
|
|
1782
|
+
for (const entry of entries) {
|
|
1783
|
+
if (!entry.endsWith(".md")) continue;
|
|
1784
|
+
const filePath = path21.join(dirPath, entry);
|
|
1785
|
+
const content = await readFileSafe2(filePath);
|
|
1786
|
+
if (content === null) continue;
|
|
1787
|
+
const name = entry.replace(/\.md$/, "");
|
|
1788
|
+
const firstLine = content.split("\n")[0].trim();
|
|
1789
|
+
const description = firstLine && !firstLine.startsWith("#") && !firstLine.startsWith("```") ? firstLine : "";
|
|
1790
|
+
nodes.push({ name, description, content });
|
|
1791
|
+
}
|
|
1792
|
+
return nodes;
|
|
1793
|
+
}
|
|
1794
|
+
async function parseRules(harnessPath) {
|
|
1795
|
+
const dirPath = path21.join(harnessPath, "rules");
|
|
1796
|
+
const entries = await readDirSafe(dirPath);
|
|
1797
|
+
const nodes = [];
|
|
1798
|
+
for (const entry of entries) {
|
|
1799
|
+
if (!entry.endsWith(".md")) continue;
|
|
1800
|
+
const filePath = path21.join(dirPath, entry);
|
|
1801
|
+
const rawContent = await readFileSafe2(filePath);
|
|
1802
|
+
if (rawContent === null) continue;
|
|
1803
|
+
const name = entry.replace(/\.md$/, "");
|
|
1804
|
+
const { frontmatter, body } = parseYamlFrontmatter(rawContent);
|
|
1805
|
+
const node = { name, content: body };
|
|
1806
|
+
const paths = frontmatter["paths"];
|
|
1807
|
+
if (Array.isArray(paths) && paths.length > 0) {
|
|
1808
|
+
node.paths = paths;
|
|
1809
|
+
}
|
|
1810
|
+
nodes.push(node);
|
|
1811
|
+
}
|
|
1812
|
+
return nodes;
|
|
1813
|
+
}
|
|
1814
|
+
async function parseAgents(harnessPath) {
|
|
1815
|
+
const dirPath = path21.join(harnessPath, "agents");
|
|
1816
|
+
const entries = await readDirSafe(dirPath);
|
|
1817
|
+
const nodes = [];
|
|
1818
|
+
for (const entry of entries) {
|
|
1819
|
+
if (!entry.endsWith(".md")) continue;
|
|
1820
|
+
const filePath = path21.join(dirPath, entry);
|
|
1821
|
+
const rawContent = await readFileSafe2(filePath);
|
|
1822
|
+
if (rawContent === null) continue;
|
|
1823
|
+
const fileBaseName = entry.replace(/\.md$/, "");
|
|
1824
|
+
const { frontmatter, body } = parseYamlFrontmatter(rawContent);
|
|
1825
|
+
const name = typeof frontmatter["name"] === "string" ? frontmatter["name"] : fileBaseName;
|
|
1826
|
+
const node = { name, content: body };
|
|
1827
|
+
if (typeof frontmatter["model"] === "string") {
|
|
1828
|
+
node.model = frontmatter["model"];
|
|
1829
|
+
}
|
|
1830
|
+
const disallowedTools = frontmatter["disallowedTools"];
|
|
1831
|
+
if (Array.isArray(disallowedTools)) {
|
|
1832
|
+
node.disallowedTools = disallowedTools;
|
|
1833
|
+
}
|
|
1834
|
+
nodes.push(node);
|
|
1835
|
+
}
|
|
1836
|
+
return nodes;
|
|
1837
|
+
}
|
|
1838
|
+
async function parseSkills(harnessPath) {
|
|
1839
|
+
const dirPath = path21.join(harnessPath, "skills");
|
|
1840
|
+
const entries = await readDirSafe(dirPath);
|
|
1841
|
+
const nodes = [];
|
|
1842
|
+
for (const entry of entries) {
|
|
1843
|
+
const entryPath = path21.join(dirPath, entry);
|
|
1844
|
+
if (entry.endsWith(".md")) {
|
|
1845
|
+
const content = await readFileSafe2(entryPath);
|
|
1846
|
+
if (content === null) continue;
|
|
1847
|
+
const name = entry.replace(/\.md$/, "");
|
|
1848
|
+
nodes.push({ name, content });
|
|
1849
|
+
} else if (await isDirectory(entryPath)) {
|
|
1850
|
+
const skillMdPath = path21.join(entryPath, "skill.md");
|
|
1851
|
+
const content = await readFileSafe2(skillMdPath);
|
|
1852
|
+
if (content === null) continue;
|
|
1853
|
+
nodes.push({ name: entry, content });
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return nodes;
|
|
1857
|
+
}
|
|
1858
|
+
async function parseDocs(harnessPath) {
|
|
1859
|
+
const dirPath = path21.join(harnessPath, "docs");
|
|
1860
|
+
const entries = await readDirSafe(dirPath);
|
|
1861
|
+
const nodes = [];
|
|
1862
|
+
for (const entry of entries) {
|
|
1863
|
+
if (!entry.endsWith(".md")) continue;
|
|
1864
|
+
const filePath = path21.join(dirPath, entry);
|
|
1865
|
+
const content = await readFileSafe2(filePath);
|
|
1866
|
+
if (content === null) continue;
|
|
1867
|
+
const name = entry.replace(/\.md$/, "");
|
|
1868
|
+
nodes.push({ name, content });
|
|
1869
|
+
}
|
|
1870
|
+
return nodes;
|
|
1871
|
+
}
|
|
1872
|
+
async function parseHooks(harnessPath) {
|
|
1873
|
+
const dirPath = path21.join(harnessPath, "hooks");
|
|
1874
|
+
const entries = await readDirSafe(dirPath);
|
|
1875
|
+
const nodes = [];
|
|
1876
|
+
for (const entry of entries) {
|
|
1877
|
+
if (!entry.endsWith(".mjs")) continue;
|
|
1878
|
+
const filePath = path21.join(dirPath, entry);
|
|
1879
|
+
const content = await readFileSafe2(filePath);
|
|
1880
|
+
if (content === null) continue;
|
|
1881
|
+
const name = entry.replace(/\.mjs$/, "");
|
|
1882
|
+
nodes.push({ name, content, type: "command" });
|
|
1883
|
+
}
|
|
1884
|
+
return nodes;
|
|
1885
|
+
}
|
|
1886
|
+
async function parseHarness(harnessPath) {
|
|
1887
|
+
const ir = createEmptyIR();
|
|
1888
|
+
const claudeMdContent = await readFileSafe2(
|
|
1889
|
+
path21.join(harnessPath, "CLAUDE.md")
|
|
1890
|
+
);
|
|
1891
|
+
if (claudeMdContent !== null) {
|
|
1892
|
+
const { meta, sections } = parseClaudeMd(claudeMdContent);
|
|
1893
|
+
ir.meta = {
|
|
1894
|
+
...ir.meta,
|
|
1895
|
+
...meta,
|
|
1896
|
+
techStack: { ...ir.meta.techStack, ...meta.techStack }
|
|
1897
|
+
};
|
|
1898
|
+
ir.sections = sections;
|
|
1899
|
+
}
|
|
1900
|
+
const settingsContent = await readFileSafe2(
|
|
1901
|
+
path21.join(harnessPath, "settings.json")
|
|
1902
|
+
);
|
|
1903
|
+
if (settingsContent !== null) {
|
|
1904
|
+
ir.settings = parseSettings(settingsContent);
|
|
1905
|
+
}
|
|
1906
|
+
const [commands, rules, agents, skills, docs, hooks] = await Promise.all([
|
|
1907
|
+
parseCommands(harnessPath),
|
|
1908
|
+
parseRules(harnessPath),
|
|
1909
|
+
parseAgents(harnessPath),
|
|
1910
|
+
parseSkills(harnessPath),
|
|
1911
|
+
parseDocs(harnessPath),
|
|
1912
|
+
parseHooks(harnessPath)
|
|
1913
|
+
]);
|
|
1914
|
+
ir.commands = commands;
|
|
1915
|
+
ir.rules = rules;
|
|
1916
|
+
ir.agents = agents;
|
|
1917
|
+
ir.skills = skills;
|
|
1918
|
+
ir.docs = docs;
|
|
1919
|
+
ir.hooks = hooks;
|
|
1920
|
+
const mcpServers = [];
|
|
1921
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1922
|
+
const parentMcpPath = path21.join(path21.dirname(harnessPath), ".mcp.json");
|
|
1923
|
+
const parentMcpContent = await readFileSafe2(parentMcpPath);
|
|
1924
|
+
if (parentMcpContent !== null) {
|
|
1925
|
+
for (const node of parseMcpConfig(parentMcpContent)) {
|
|
1926
|
+
if (!seenIds.has(node.id)) {
|
|
1927
|
+
seenIds.add(node.id);
|
|
1928
|
+
mcpServers.push(node);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
const innerMcpPath = path21.join(harnessPath, ".mcp.json");
|
|
1933
|
+
const innerMcpContent = await readFileSafe2(innerMcpPath);
|
|
1934
|
+
if (innerMcpContent !== null) {
|
|
1935
|
+
for (const node of parseMcpConfig(innerMcpContent)) {
|
|
1936
|
+
if (!seenIds.has(node.id)) {
|
|
1937
|
+
seenIds.add(node.id);
|
|
1938
|
+
mcpServers.push(node);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
ir.mcpServers = mcpServers;
|
|
1943
|
+
return ir;
|
|
1944
|
+
}
|
|
1945
|
+
var SECTION_ID_MAP;
|
|
1946
|
+
var init_parser = __esm({
|
|
1947
|
+
"src/ir/parser.ts"() {
|
|
1948
|
+
"use strict";
|
|
1949
|
+
init_types();
|
|
1950
|
+
SECTION_ID_MAP = [
|
|
1951
|
+
{ pattern: /^(purpose|about|what)\b/i, id: "purpose" },
|
|
1952
|
+
{ pattern: /^(tech\s*stack|technology|stack)\b/i, id: "tech-stack" },
|
|
1953
|
+
{ pattern: /^(commands|key\s*commands)\b/i, id: "commands" },
|
|
1954
|
+
{ pattern: /^architecture\b/i, id: "architecture" },
|
|
1955
|
+
{ pattern: /^conventions?\b/i, id: "conventions" },
|
|
1956
|
+
{ pattern: /^verification\b/i, id: "verification" },
|
|
1957
|
+
{ pattern: /^(known\s*gotchas|gotchas)\b/i, id: "gotchas" },
|
|
1958
|
+
{ pattern: /^output\b/i, id: "output" },
|
|
1959
|
+
{ pattern: /^debugging\b/i, id: "debugging" },
|
|
1960
|
+
{ pattern: /^git\b/i, id: "git" }
|
|
1961
|
+
];
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// src/ir/translate.ts
|
|
1966
|
+
function extractName(filePath, pattern) {
|
|
1967
|
+
const match = filePath.match(pattern);
|
|
1968
|
+
return match ? match[1] : null;
|
|
1969
|
+
}
|
|
1970
|
+
function findSectionContaining(ir, text) {
|
|
1971
|
+
return ir.sections.find((s) => s.content.includes(text));
|
|
1972
|
+
}
|
|
1973
|
+
function translateClaudeMdMutation(mutation, ir) {
|
|
1974
|
+
switch (mutation.action) {
|
|
1975
|
+
case "replace": {
|
|
1976
|
+
if (mutation.oldText === void 0) {
|
|
1977
|
+
return buildRawText(mutation);
|
|
1978
|
+
}
|
|
1979
|
+
const section = findSectionContaining(ir, mutation.oldText);
|
|
1980
|
+
if (!section) {
|
|
1981
|
+
return buildRawText(mutation);
|
|
1982
|
+
}
|
|
1983
|
+
return {
|
|
1984
|
+
type: "update_section",
|
|
1985
|
+
sectionId: section.id,
|
|
1986
|
+
content: section.content.replace(mutation.oldText, mutation.newText),
|
|
1987
|
+
rationale: mutation.rationale
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
case "add_section": {
|
|
1991
|
+
const headingMatch = mutation.newText.match(/^## (.+)/);
|
|
1992
|
+
if (!headingMatch) {
|
|
1993
|
+
return buildRawText(mutation);
|
|
1994
|
+
}
|
|
1995
|
+
const headingText = headingMatch[1].trim();
|
|
1996
|
+
const sectionId = resolveSectionId(headingText);
|
|
1997
|
+
const heading = `## ${headingText}`;
|
|
1998
|
+
const newlineIdx = mutation.newText.indexOf("\n");
|
|
1999
|
+
const content = newlineIdx >= 0 ? mutation.newText.slice(newlineIdx + 1).trim() : "";
|
|
2000
|
+
const nextOrder = ir.sections.length;
|
|
2001
|
+
return {
|
|
2002
|
+
type: "add_section",
|
|
2003
|
+
section: createSection(sectionId, heading, content, nextOrder),
|
|
2004
|
+
rationale: mutation.rationale
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
case "delete_section": {
|
|
2008
|
+
if (mutation.oldText === void 0) {
|
|
2009
|
+
return buildRawText(mutation);
|
|
2010
|
+
}
|
|
2011
|
+
const section = findSectionContaining(ir, mutation.oldText);
|
|
2012
|
+
if (!section) {
|
|
2013
|
+
return buildRawText(mutation);
|
|
2014
|
+
}
|
|
2015
|
+
return {
|
|
2016
|
+
type: "remove_section",
|
|
2017
|
+
sectionId: section.id,
|
|
2018
|
+
rationale: mutation.rationale
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
default:
|
|
2022
|
+
return buildRawText(mutation);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
function translateCommandMutation(mutation, name) {
|
|
2026
|
+
switch (mutation.action) {
|
|
2027
|
+
case "create_file":
|
|
2028
|
+
return {
|
|
2029
|
+
type: "add_command",
|
|
2030
|
+
command: createCommandNode(name, mutation.newText),
|
|
2031
|
+
rationale: mutation.rationale
|
|
2032
|
+
};
|
|
2033
|
+
case "delete_file":
|
|
2034
|
+
return {
|
|
2035
|
+
type: "remove_command",
|
|
2036
|
+
name,
|
|
2037
|
+
rationale: mutation.rationale
|
|
2038
|
+
};
|
|
2039
|
+
case "replace":
|
|
2040
|
+
return {
|
|
2041
|
+
type: "update_command",
|
|
2042
|
+
name,
|
|
2043
|
+
content: mutation.newText,
|
|
2044
|
+
rationale: mutation.rationale
|
|
2045
|
+
};
|
|
2046
|
+
default:
|
|
2047
|
+
return buildRawText(mutation);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
function translateRuleMutation(mutation, name) {
|
|
2051
|
+
switch (mutation.action) {
|
|
2052
|
+
case "create_file":
|
|
2053
|
+
return {
|
|
2054
|
+
type: "add_rule",
|
|
2055
|
+
rule: createRuleNode(name, mutation.newText),
|
|
2056
|
+
rationale: mutation.rationale
|
|
2057
|
+
};
|
|
2058
|
+
case "delete_file":
|
|
2059
|
+
return {
|
|
2060
|
+
type: "remove_rule",
|
|
2061
|
+
name,
|
|
2062
|
+
rationale: mutation.rationale
|
|
2063
|
+
};
|
|
2064
|
+
case "replace":
|
|
2065
|
+
return {
|
|
2066
|
+
type: "update_rule",
|
|
2067
|
+
name,
|
|
2068
|
+
content: mutation.newText,
|
|
2069
|
+
rationale: mutation.rationale
|
|
2070
|
+
};
|
|
2071
|
+
default:
|
|
2072
|
+
return buildRawText(mutation);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function translateAgentMutation(mutation, name) {
|
|
2076
|
+
switch (mutation.action) {
|
|
2077
|
+
case "create_file":
|
|
2078
|
+
return {
|
|
2079
|
+
type: "add_agent",
|
|
2080
|
+
agent: createAgentNode(name, mutation.newText),
|
|
2081
|
+
rationale: mutation.rationale
|
|
2082
|
+
};
|
|
2083
|
+
case "delete_file":
|
|
2084
|
+
return {
|
|
2085
|
+
type: "remove_agent",
|
|
2086
|
+
name,
|
|
2087
|
+
rationale: mutation.rationale
|
|
2088
|
+
};
|
|
2089
|
+
case "replace":
|
|
2090
|
+
return {
|
|
2091
|
+
type: "update_agent",
|
|
2092
|
+
name,
|
|
2093
|
+
changes: { content: mutation.newText },
|
|
2094
|
+
rationale: mutation.rationale
|
|
2095
|
+
};
|
|
2096
|
+
default:
|
|
2097
|
+
return buildRawText(mutation);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
function buildRawText(mutation) {
|
|
2101
|
+
return {
|
|
2102
|
+
type: "raw_text",
|
|
2103
|
+
file: mutation.file,
|
|
2104
|
+
action: mutation.action,
|
|
2105
|
+
oldText: mutation.oldText,
|
|
2106
|
+
newText: mutation.newText,
|
|
2107
|
+
rationale: mutation.rationale
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
function translateMutation(mutation, ir) {
|
|
2111
|
+
if (mutation.file === "CLAUDE.md") {
|
|
2112
|
+
return translateClaudeMdMutation(mutation, ir);
|
|
2113
|
+
}
|
|
2114
|
+
const commandName = extractName(mutation.file, COMMANDS_PATH_RE);
|
|
2115
|
+
if (commandName !== null) {
|
|
2116
|
+
return translateCommandMutation(mutation, commandName);
|
|
2117
|
+
}
|
|
2118
|
+
const ruleName = extractName(mutation.file, RULES_PATH_RE);
|
|
2119
|
+
if (ruleName !== null) {
|
|
2120
|
+
return translateRuleMutation(mutation, ruleName);
|
|
2121
|
+
}
|
|
2122
|
+
const agentName = extractName(mutation.file, AGENTS_PATH_RE);
|
|
2123
|
+
if (agentName !== null) {
|
|
2124
|
+
return translateAgentMutation(mutation, agentName);
|
|
2125
|
+
}
|
|
2126
|
+
return buildRawText(mutation);
|
|
2127
|
+
}
|
|
2128
|
+
function translateMutations(mutations, ir) {
|
|
2129
|
+
return mutations.map((m) => translateMutation(m, ir));
|
|
2130
|
+
}
|
|
2131
|
+
var COMMANDS_PATH_RE, RULES_PATH_RE, AGENTS_PATH_RE;
|
|
2132
|
+
var init_translate = __esm({
|
|
2133
|
+
"src/ir/translate.ts"() {
|
|
2134
|
+
"use strict";
|
|
2135
|
+
init_types();
|
|
2136
|
+
init_parser();
|
|
2137
|
+
COMMANDS_PATH_RE = /^commands\/([^/]+?)(?:\.md)?$/;
|
|
2138
|
+
RULES_PATH_RE = /^rules\/([^/]+?)(?:\.md)?$/;
|
|
2139
|
+
AGENTS_PATH_RE = /^agents\/([^/]+?)(?:\.md)?$/;
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
// src/ir/mutations.ts
|
|
2144
|
+
function sectionExists(ir, id) {
|
|
2145
|
+
return ir.sections.some((s) => s.id === id);
|
|
2146
|
+
}
|
|
2147
|
+
function commandExists(ir, name) {
|
|
2148
|
+
return ir.commands.some((c) => c.name === name);
|
|
2149
|
+
}
|
|
2150
|
+
function ruleExists(ir, name) {
|
|
2151
|
+
return ir.rules.some((r) => r.name === name);
|
|
2152
|
+
}
|
|
2153
|
+
function agentExists(ir, name) {
|
|
2154
|
+
return ir.agents.some((a) => a.name === name);
|
|
2155
|
+
}
|
|
2156
|
+
function mcpServerExists(ir, id) {
|
|
2157
|
+
return ir.mcpServers.some((s) => s.id === id);
|
|
2158
|
+
}
|
|
2159
|
+
function deepSet(obj, segments, value) {
|
|
2160
|
+
if (segments.length === 0) return obj;
|
|
2161
|
+
const [head, ...rest] = segments;
|
|
2162
|
+
if (rest.length === 0) {
|
|
2163
|
+
return { ...obj, [head]: value };
|
|
2164
|
+
}
|
|
2165
|
+
const existing = obj[head];
|
|
2166
|
+
const child = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
2167
|
+
return { ...obj, [head]: deepSet(child, rest, value) };
|
|
2168
|
+
}
|
|
2169
|
+
function applySettingsUpdate(settings, path31, value) {
|
|
2170
|
+
const segments = path31.split(".");
|
|
2171
|
+
const topKey = segments[0];
|
|
2172
|
+
if (STRUCTURED_SETTINGS_KEYS.has(topKey)) {
|
|
2173
|
+
const settingsRecord = { ...settings };
|
|
2174
|
+
const updated = deepSet(settingsRecord, segments, value);
|
|
2175
|
+
return {
|
|
2176
|
+
...settings,
|
|
2177
|
+
...updated,
|
|
2178
|
+
// Preserve raw as-is (deepSet may have overwritten it if path happened to be "raw")
|
|
2179
|
+
raw: settings.raw
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
const updatedRaw = deepSet({ ...settings.raw }, segments, value);
|
|
2183
|
+
return {
|
|
2184
|
+
...settings,
|
|
2185
|
+
raw: updatedRaw
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
function applyIRMutation(ir, mutation) {
|
|
2189
|
+
switch (mutation.type) {
|
|
2190
|
+
// -- Sections ----------------------------------------------------------
|
|
2191
|
+
case "update_section": {
|
|
2192
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2193
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2194
|
+
}
|
|
2195
|
+
return {
|
|
2196
|
+
...ir,
|
|
2197
|
+
sections: ir.sections.map(
|
|
2198
|
+
(s) => s.id === mutation.sectionId ? { ...s, content: mutation.content } : s
|
|
2199
|
+
)
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
case "add_section": {
|
|
2203
|
+
if (sectionExists(ir, mutation.section.id)) {
|
|
2204
|
+
throw new Error(`Section '${mutation.section.id}' already exists`);
|
|
2205
|
+
}
|
|
2206
|
+
return {
|
|
2207
|
+
...ir,
|
|
2208
|
+
sections: [...ir.sections, { ...mutation.section }]
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
case "remove_section": {
|
|
2212
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2213
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2214
|
+
}
|
|
2215
|
+
return {
|
|
2216
|
+
...ir,
|
|
2217
|
+
sections: ir.sections.filter((s) => s.id !== mutation.sectionId)
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
case "reorder_section": {
|
|
2221
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2222
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2223
|
+
}
|
|
2224
|
+
return {
|
|
2225
|
+
...ir,
|
|
2226
|
+
sections: ir.sections.map(
|
|
2227
|
+
(s) => s.id === mutation.sectionId ? { ...s, order: mutation.newOrder } : s
|
|
2228
|
+
)
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
// -- Commands ----------------------------------------------------------
|
|
2232
|
+
case "add_command": {
|
|
2233
|
+
if (commandExists(ir, mutation.command.name)) {
|
|
2234
|
+
throw new Error(`Command '${mutation.command.name}' already exists`);
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
...ir,
|
|
2238
|
+
commands: [...ir.commands, { ...mutation.command }]
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
case "update_command": {
|
|
2242
|
+
if (!commandExists(ir, mutation.name)) {
|
|
2243
|
+
throw new Error(`Command '${mutation.name}' not found`);
|
|
2244
|
+
}
|
|
2245
|
+
return {
|
|
2246
|
+
...ir,
|
|
2247
|
+
commands: ir.commands.map(
|
|
2248
|
+
(c) => c.name === mutation.name ? { ...c, content: mutation.content } : c
|
|
2249
|
+
)
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
case "remove_command": {
|
|
2253
|
+
if (!commandExists(ir, mutation.name)) {
|
|
2254
|
+
throw new Error(`Command '${mutation.name}' not found`);
|
|
2255
|
+
}
|
|
2256
|
+
return {
|
|
2257
|
+
...ir,
|
|
2258
|
+
commands: ir.commands.filter((c) => c.name !== mutation.name)
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
// -- Rules -------------------------------------------------------------
|
|
2262
|
+
case "add_rule": {
|
|
2263
|
+
if (ruleExists(ir, mutation.rule.name)) {
|
|
2264
|
+
throw new Error(`Rule '${mutation.rule.name}' already exists`);
|
|
2265
|
+
}
|
|
2266
|
+
return {
|
|
2267
|
+
...ir,
|
|
2268
|
+
rules: [...ir.rules, { ...mutation.rule }]
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
case "update_rule": {
|
|
2272
|
+
if (!ruleExists(ir, mutation.name)) {
|
|
2273
|
+
throw new Error(`Rule '${mutation.name}' not found`);
|
|
2274
|
+
}
|
|
2275
|
+
return {
|
|
2276
|
+
...ir,
|
|
2277
|
+
rules: ir.rules.map(
|
|
2278
|
+
(r) => r.name === mutation.name ? { ...r, content: mutation.content } : r
|
|
2279
|
+
)
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
case "remove_rule": {
|
|
2283
|
+
if (!ruleExists(ir, mutation.name)) {
|
|
2284
|
+
throw new Error(`Rule '${mutation.name}' not found`);
|
|
2285
|
+
}
|
|
2286
|
+
return {
|
|
2287
|
+
...ir,
|
|
2288
|
+
rules: ir.rules.filter((r) => r.name !== mutation.name)
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
// -- Agents ------------------------------------------------------------
|
|
2292
|
+
case "add_agent": {
|
|
2293
|
+
if (agentExists(ir, mutation.agent.name)) {
|
|
2294
|
+
throw new Error(`Agent '${mutation.agent.name}' already exists`);
|
|
2295
|
+
}
|
|
2296
|
+
return {
|
|
2297
|
+
...ir,
|
|
2298
|
+
agents: [...ir.agents, { ...mutation.agent }]
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
case "update_agent": {
|
|
2302
|
+
if (!agentExists(ir, mutation.name)) {
|
|
2303
|
+
throw new Error(`Agent '${mutation.name}' not found`);
|
|
2304
|
+
}
|
|
2305
|
+
return {
|
|
2306
|
+
...ir,
|
|
2307
|
+
agents: ir.agents.map(
|
|
2308
|
+
(a) => a.name === mutation.name ? { ...a, ...mutation.changes } : a
|
|
2309
|
+
)
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
case "remove_agent": {
|
|
2313
|
+
if (!agentExists(ir, mutation.name)) {
|
|
2314
|
+
throw new Error(`Agent '${mutation.name}' not found`);
|
|
2315
|
+
}
|
|
2316
|
+
return {
|
|
2317
|
+
...ir,
|
|
2318
|
+
agents: ir.agents.filter((a) => a.name !== mutation.name)
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
// -- MCP Servers -------------------------------------------------------
|
|
2322
|
+
case "add_mcp_server": {
|
|
2323
|
+
if (mcpServerExists(ir, mutation.server.id)) {
|
|
2324
|
+
throw new Error(`MCP server '${mutation.server.id}' already exists`);
|
|
2325
|
+
}
|
|
2326
|
+
return {
|
|
2327
|
+
...ir,
|
|
2328
|
+
mcpServers: [...ir.mcpServers, { ...mutation.server }]
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
case "remove_mcp_server": {
|
|
2332
|
+
if (!mcpServerExists(ir, mutation.id)) {
|
|
2333
|
+
throw new Error(`MCP server '${mutation.id}' not found`);
|
|
2334
|
+
}
|
|
2335
|
+
return {
|
|
2336
|
+
...ir,
|
|
2337
|
+
mcpServers: ir.mcpServers.filter((s) => s.id !== mutation.id)
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
// -- Settings ----------------------------------------------------------
|
|
2341
|
+
case "update_settings": {
|
|
2342
|
+
return {
|
|
2343
|
+
...ir,
|
|
2344
|
+
settings: applySettingsUpdate(ir.settings, mutation.path, mutation.value)
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
// -- Raw text (legacy fallback) ----------------------------------------
|
|
2348
|
+
case "raw_text": {
|
|
2349
|
+
console.warn(
|
|
2350
|
+
"raw_text mutation is a legacy fallback \u2014 the text operation will be applied during rendering"
|
|
2351
|
+
);
|
|
2352
|
+
return { ...ir };
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
var STRUCTURED_SETTINGS_KEYS;
|
|
2357
|
+
var init_mutations = __esm({
|
|
2358
|
+
"src/ir/mutations.ts"() {
|
|
2359
|
+
"use strict";
|
|
2360
|
+
STRUCTURED_SETTINGS_KEYS = /* @__PURE__ */ new Set(["statusLine", "hooks", "denyPatterns"]);
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
|
|
2364
|
+
// src/ir/renderer.ts
|
|
2365
|
+
function renderClaudeMd(_meta, sections) {
|
|
2366
|
+
const sorted = [...sections].sort((a, b) => a.order - b.order);
|
|
2367
|
+
const blocks = [];
|
|
2368
|
+
for (const section of sorted) {
|
|
2369
|
+
if (section.heading && section.content) {
|
|
2370
|
+
blocks.push(`${section.heading}
|
|
2371
|
+
|
|
2372
|
+
${section.content}`);
|
|
2373
|
+
} else if (section.heading) {
|
|
2374
|
+
blocks.push(section.heading);
|
|
2375
|
+
} else if (section.content) {
|
|
2376
|
+
blocks.push(section.content);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (blocks.length === 0) {
|
|
2380
|
+
return "\n";
|
|
2381
|
+
}
|
|
2382
|
+
return blocks.join("\n\n") + "\n";
|
|
2383
|
+
}
|
|
2384
|
+
function renderSettings(settings) {
|
|
2385
|
+
const result = JSON.parse(
|
|
2386
|
+
JSON.stringify(settings.raw)
|
|
2387
|
+
);
|
|
2388
|
+
if (settings.denyPatterns && settings.denyPatterns.length > 0) {
|
|
2389
|
+
const permissions = result["permissions"] ?? {};
|
|
2390
|
+
permissions["deny"] = settings.denyPatterns;
|
|
2391
|
+
result["permissions"] = permissions;
|
|
2392
|
+
}
|
|
2393
|
+
if (settings.statusLine) {
|
|
2394
|
+
result["statusLine"] = settings.statusLine;
|
|
2395
|
+
}
|
|
2396
|
+
const hookEvents = [
|
|
2397
|
+
"PreToolUse",
|
|
2398
|
+
"PostToolUse",
|
|
2399
|
+
"UserPromptSubmit",
|
|
2400
|
+
"SessionStart",
|
|
2401
|
+
"PostCompact"
|
|
2402
|
+
];
|
|
2403
|
+
const hooksObj = {};
|
|
2404
|
+
let hasHooks = false;
|
|
2405
|
+
for (const event of hookEvents) {
|
|
2406
|
+
const entries = settings.hooks[event];
|
|
2407
|
+
if (entries && entries.length > 0) {
|
|
2408
|
+
hooksObj[event] = entries;
|
|
2409
|
+
hasHooks = true;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (hasHooks) {
|
|
2413
|
+
result["hooks"] = hooksObj;
|
|
2414
|
+
}
|
|
2415
|
+
return JSON.stringify(result, null, 2) + "\n";
|
|
2416
|
+
}
|
|
2417
|
+
function renderMcpConfig(servers) {
|
|
2418
|
+
if (servers.length === 0) {
|
|
2419
|
+
return "";
|
|
2420
|
+
}
|
|
2421
|
+
const mcpServers = {};
|
|
2422
|
+
for (const server of servers) {
|
|
2423
|
+
const entry = {
|
|
2424
|
+
command: server.command,
|
|
2425
|
+
args: server.args
|
|
2426
|
+
};
|
|
2427
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
2428
|
+
entry["env"] = server.env;
|
|
2429
|
+
}
|
|
2430
|
+
mcpServers[server.id] = entry;
|
|
2431
|
+
}
|
|
2432
|
+
return JSON.stringify({ mcpServers }, null, 2) + "\n";
|
|
2433
|
+
}
|
|
2434
|
+
function renderRuleWithFrontmatter(rule) {
|
|
2435
|
+
if (!rule.paths || rule.paths.length === 0) {
|
|
2436
|
+
return rule.content;
|
|
2437
|
+
}
|
|
2438
|
+
const yamlLines = ["---", "paths:"];
|
|
2439
|
+
for (const p of rule.paths) {
|
|
2440
|
+
yamlLines.push(` - ${p}`);
|
|
2441
|
+
}
|
|
2442
|
+
yamlLines.push("---");
|
|
2443
|
+
return yamlLines.join("\n") + "\n\n" + rule.content;
|
|
2444
|
+
}
|
|
2445
|
+
function renderAgentWithFrontmatter(agent) {
|
|
2446
|
+
const hasModel = agent.model !== void 0;
|
|
2447
|
+
const hasDisallowed = agent.disallowedTools !== void 0 && agent.disallowedTools.length > 0;
|
|
2448
|
+
if (!hasModel && !hasDisallowed) {
|
|
2449
|
+
return agent.content;
|
|
2450
|
+
}
|
|
2451
|
+
const yamlLines = ["---"];
|
|
2452
|
+
if (hasModel) {
|
|
2453
|
+
yamlLines.push(`model: ${agent.model}`);
|
|
2454
|
+
}
|
|
2455
|
+
if (hasDisallowed) {
|
|
2456
|
+
yamlLines.push("disallowedTools:");
|
|
2457
|
+
for (const tool of agent.disallowedTools) {
|
|
2458
|
+
yamlLines.push(` - ${tool}`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
yamlLines.push("---");
|
|
2462
|
+
return yamlLines.join("\n") + "\n\n" + agent.content;
|
|
2463
|
+
}
|
|
2464
|
+
function settingsHasContent(settings) {
|
|
2465
|
+
if (settings.statusLine) return true;
|
|
2466
|
+
if (settings.denyPatterns && settings.denyPatterns.length > 0) return true;
|
|
2467
|
+
if (Object.keys(settings.raw).length > 0) return true;
|
|
2468
|
+
const hookEvents = [
|
|
2469
|
+
"PreToolUse",
|
|
2470
|
+
"PostToolUse",
|
|
2471
|
+
"UserPromptSubmit",
|
|
2472
|
+
"SessionStart",
|
|
2473
|
+
"PostCompact"
|
|
2474
|
+
];
|
|
2475
|
+
for (const event of hookEvents) {
|
|
2476
|
+
const entries = settings.hooks[event];
|
|
2477
|
+
if (entries && entries.length > 0) return true;
|
|
2478
|
+
}
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
function renderHarness(ir) {
|
|
2482
|
+
const files = /* @__PURE__ */ new Map();
|
|
2483
|
+
if (ir.sections.length > 0 || ir.meta.name) {
|
|
2484
|
+
files.set("CLAUDE.md", renderClaudeMd(ir.meta, ir.sections));
|
|
2485
|
+
}
|
|
2486
|
+
if (settingsHasContent(ir.settings)) {
|
|
2487
|
+
files.set("settings.json", renderSettings(ir.settings));
|
|
2488
|
+
}
|
|
2489
|
+
for (const cmd of ir.commands) {
|
|
2490
|
+
files.set(`commands/${cmd.name}.md`, cmd.content);
|
|
2491
|
+
}
|
|
2492
|
+
for (const rule of ir.rules) {
|
|
2493
|
+
files.set(`rules/${rule.name}.md`, renderRuleWithFrontmatter(rule));
|
|
2494
|
+
}
|
|
2495
|
+
for (const agent of ir.agents) {
|
|
2496
|
+
files.set(`agents/${agent.name}.md`, renderAgentWithFrontmatter(agent));
|
|
2497
|
+
}
|
|
2498
|
+
for (const skill of ir.skills) {
|
|
2499
|
+
files.set(`skills/${skill.name}.md`, skill.content);
|
|
2500
|
+
}
|
|
2501
|
+
for (const doc of ir.docs) {
|
|
2502
|
+
files.set(`docs/${doc.name}.md`, doc.content);
|
|
2503
|
+
}
|
|
2504
|
+
for (const hook of ir.hooks) {
|
|
2505
|
+
files.set(`hooks/${hook.name}.mjs`, hook.content);
|
|
2506
|
+
}
|
|
2507
|
+
const mcpContent = renderMcpConfig(ir.mcpServers);
|
|
2508
|
+
if (mcpContent) {
|
|
2509
|
+
files.set(".mcp.json", mcpContent);
|
|
2510
|
+
}
|
|
2511
|
+
return files;
|
|
2512
|
+
}
|
|
2513
|
+
var init_renderer = __esm({
|
|
2514
|
+
"src/ir/renderer.ts"() {
|
|
2515
|
+
"use strict";
|
|
2516
|
+
}
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
// src/ir/diff.ts
|
|
2520
|
+
function diffIR(before, after) {
|
|
2521
|
+
const diff = createEmptyDiff();
|
|
2522
|
+
diffSections(before.sections, after.sections, diff);
|
|
2523
|
+
diffByName(before.commands, after.commands, diff.commands);
|
|
2524
|
+
diffByName(before.rules, after.rules, diff.rules);
|
|
2525
|
+
diffAgents(before.agents, after.agents, diff);
|
|
2526
|
+
diffMcpServers(before.mcpServers, after.mcpServers, diff);
|
|
2527
|
+
diffSettings(before.settings, after.settings, diff);
|
|
2528
|
+
return diff;
|
|
2529
|
+
}
|
|
2530
|
+
function formatIRDiff(diff) {
|
|
2531
|
+
const blocks = [];
|
|
2532
|
+
const sectionLines = formatSectionBlock(diff);
|
|
2533
|
+
if (sectionLines.length > 0) {
|
|
2534
|
+
blocks.push(["Sections:", ...sectionLines].join("\n"));
|
|
2535
|
+
}
|
|
2536
|
+
const commandLines = formatNamedBlock(diff.commands, "commands");
|
|
2537
|
+
if (commandLines.length > 0) {
|
|
2538
|
+
blocks.push(["Commands:", ...commandLines].join("\n"));
|
|
2539
|
+
}
|
|
2540
|
+
const ruleLines = formatNamedBlock(diff.rules, "rules");
|
|
2541
|
+
if (ruleLines.length > 0) {
|
|
2542
|
+
blocks.push(["Rules:", ...ruleLines].join("\n"));
|
|
2543
|
+
}
|
|
2544
|
+
const agentLines = formatAgentBlock(diff);
|
|
2545
|
+
if (agentLines.length > 0) {
|
|
2546
|
+
blocks.push(["Agents:", ...agentLines].join("\n"));
|
|
2547
|
+
}
|
|
2548
|
+
const mcpLines = formatMcpBlock(diff);
|
|
2549
|
+
if (mcpLines.length > 0) {
|
|
2550
|
+
blocks.push(["MCP Servers:", ...mcpLines].join("\n"));
|
|
2551
|
+
}
|
|
2552
|
+
const settingsLines = formatSettingsBlock(diff);
|
|
2553
|
+
if (settingsLines.length > 0) {
|
|
2554
|
+
blocks.push(["Settings:", ...settingsLines].join("\n"));
|
|
2555
|
+
}
|
|
2556
|
+
if (blocks.length === 0) {
|
|
2557
|
+
return "No changes.";
|
|
2558
|
+
}
|
|
2559
|
+
return blocks.join("\n\n");
|
|
2560
|
+
}
|
|
2561
|
+
function diffSections(beforeList, afterList, diff) {
|
|
2562
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2563
|
+
for (const s of beforeList) {
|
|
2564
|
+
beforeMap.set(s.id, s);
|
|
2565
|
+
}
|
|
2566
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2567
|
+
for (const s of afterList) {
|
|
2568
|
+
afterMap.set(s.id, s);
|
|
2569
|
+
}
|
|
2570
|
+
for (const s of afterList) {
|
|
2571
|
+
if (!beforeMap.has(s.id)) {
|
|
2572
|
+
diff.sections.added.push(s);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
for (const s of beforeList) {
|
|
2576
|
+
if (!afterMap.has(s.id)) {
|
|
2577
|
+
diff.sections.removed.push(s);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
for (const [id, afterSection] of afterMap) {
|
|
2581
|
+
const beforeSection = beforeMap.get(id);
|
|
2582
|
+
if (beforeSection === void 0) continue;
|
|
2583
|
+
if (beforeSection.content !== afterSection.content) {
|
|
2584
|
+
diff.sections.modified.push({
|
|
2585
|
+
id,
|
|
2586
|
+
before: beforeSection.content,
|
|
2587
|
+
after: afterSection.content
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
if (beforeSection.order !== afterSection.order) {
|
|
2591
|
+
diff.sections.reordered.push({
|
|
2592
|
+
id,
|
|
2593
|
+
oldOrder: beforeSection.order,
|
|
2594
|
+
newOrder: afterSection.order
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
function diffByName(beforeList, afterList, target) {
|
|
2600
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2601
|
+
for (const n of beforeList) {
|
|
2602
|
+
beforeMap.set(n.name, n);
|
|
2603
|
+
}
|
|
2604
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2605
|
+
for (const n of afterList) {
|
|
2606
|
+
afterMap.set(n.name, n);
|
|
2607
|
+
}
|
|
2608
|
+
for (const n of afterList) {
|
|
2609
|
+
if (!beforeMap.has(n.name)) {
|
|
2610
|
+
target.added.push(n);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
for (const n of beforeList) {
|
|
2614
|
+
if (!afterMap.has(n.name)) {
|
|
2615
|
+
target.removed.push(n.name);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
for (const [name, afterNode] of afterMap) {
|
|
2619
|
+
const beforeNode = beforeMap.get(name);
|
|
2620
|
+
if (beforeNode === void 0) continue;
|
|
2621
|
+
if (beforeNode.content !== afterNode.content) {
|
|
2622
|
+
target.modified.push({
|
|
2623
|
+
name,
|
|
2624
|
+
before: beforeNode.content,
|
|
2625
|
+
after: afterNode.content
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
function diffAgents(beforeList, afterList, diff) {
|
|
2631
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2632
|
+
for (const a of beforeList) {
|
|
2633
|
+
beforeMap.set(a.name, a);
|
|
2634
|
+
}
|
|
2635
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2636
|
+
for (const a of afterList) {
|
|
2637
|
+
afterMap.set(a.name, a);
|
|
2638
|
+
}
|
|
2639
|
+
for (const a of afterList) {
|
|
2640
|
+
if (!beforeMap.has(a.name)) {
|
|
2641
|
+
diff.agents.added.push(a);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
for (const a of beforeList) {
|
|
2645
|
+
if (!afterMap.has(a.name)) {
|
|
2646
|
+
diff.agents.removed.push(a.name);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
for (const [name, afterAgent] of afterMap) {
|
|
2650
|
+
const beforeAgent = beforeMap.get(name);
|
|
2651
|
+
if (beforeAgent === void 0) continue;
|
|
2652
|
+
const changeParts = [];
|
|
2653
|
+
if (beforeAgent.model !== afterAgent.model) {
|
|
2654
|
+
const from = beforeAgent.model ?? "none";
|
|
2655
|
+
const to = afterAgent.model ?? "none";
|
|
2656
|
+
changeParts.push(`model changed from ${from} to ${to}`);
|
|
2657
|
+
}
|
|
2658
|
+
if (beforeAgent.content !== afterAgent.content) {
|
|
2659
|
+
changeParts.push("content updated");
|
|
2660
|
+
}
|
|
2661
|
+
const beforeTools = JSON.stringify(beforeAgent.disallowedTools ?? []);
|
|
2662
|
+
const afterTools = JSON.stringify(afterAgent.disallowedTools ?? []);
|
|
2663
|
+
if (beforeTools !== afterTools) {
|
|
2664
|
+
changeParts.push("disallowedTools changed");
|
|
2665
|
+
}
|
|
2666
|
+
if (changeParts.length > 0) {
|
|
2667
|
+
diff.agents.modified.push({
|
|
2668
|
+
name,
|
|
2669
|
+
changes: changeParts.join("; ")
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
function diffMcpServers(beforeList, afterList, diff) {
|
|
2675
|
+
const beforeIds = new Set(beforeList.map((s) => s.id));
|
|
2676
|
+
const afterIds = new Set(afterList.map((s) => s.id));
|
|
2677
|
+
for (const s of afterList) {
|
|
2678
|
+
if (!beforeIds.has(s.id)) {
|
|
2679
|
+
diff.mcpServers.added.push(s);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
for (const s of beforeList) {
|
|
2683
|
+
if (!afterIds.has(s.id)) {
|
|
2684
|
+
diff.mcpServers.removed.push(s.id);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
function diffSettings(before, after, diff) {
|
|
2689
|
+
if (!deepEqual(before.statusLine, after.statusLine)) {
|
|
2690
|
+
diff.settings.changes.push({
|
|
2691
|
+
path: "statusLine",
|
|
2692
|
+
before: before.statusLine,
|
|
2693
|
+
after: after.statusLine
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
if (!deepEqual(before.denyPatterns, after.denyPatterns)) {
|
|
2697
|
+
diff.settings.changes.push({
|
|
2698
|
+
path: "denyPatterns",
|
|
2699
|
+
before: before.denyPatterns,
|
|
2700
|
+
after: after.denyPatterns
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
if (!deepEqual(before.hooks, after.hooks)) {
|
|
2704
|
+
diff.settings.changes.push({
|
|
2705
|
+
path: "hooks",
|
|
2706
|
+
before: before.hooks,
|
|
2707
|
+
after: after.hooks
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function formatSectionBlock(diff) {
|
|
2712
|
+
const lines = [];
|
|
2713
|
+
for (const s of diff.sections.added) {
|
|
2714
|
+
lines.push(` + Added: ${s.heading}`);
|
|
2715
|
+
}
|
|
2716
|
+
for (const s of diff.sections.removed) {
|
|
2717
|
+
lines.push(` - Removed: ${s.heading}`);
|
|
2718
|
+
}
|
|
2719
|
+
for (const m of diff.sections.modified) {
|
|
2720
|
+
lines.push(` ~ Modified: ${m.id} (content changed)`);
|
|
2721
|
+
}
|
|
2722
|
+
for (const r of diff.sections.reordered) {
|
|
2723
|
+
lines.push(
|
|
2724
|
+
` \u2195 Reordered: ${r.id} (${r.oldOrder} \u2192 ${r.newOrder})`
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
return lines;
|
|
2728
|
+
}
|
|
2729
|
+
function formatNamedBlock(bucket, _category) {
|
|
2730
|
+
const lines = [];
|
|
2731
|
+
for (const n of bucket.added) {
|
|
2732
|
+
lines.push(` + Added: ${n.name}`);
|
|
2733
|
+
}
|
|
2734
|
+
for (const name of bucket.removed) {
|
|
2735
|
+
lines.push(` - Removed: ${name}`);
|
|
2736
|
+
}
|
|
2737
|
+
for (const m of bucket.modified) {
|
|
2738
|
+
lines.push(` ~ Modified: ${m.name} (content changed)`);
|
|
2739
|
+
}
|
|
2740
|
+
return lines;
|
|
2741
|
+
}
|
|
2742
|
+
function formatAgentBlock(diff) {
|
|
2743
|
+
const lines = [];
|
|
2744
|
+
for (const a of diff.agents.added) {
|
|
2745
|
+
lines.push(` + Added: ${a.name}`);
|
|
2746
|
+
}
|
|
2747
|
+
for (const name of diff.agents.removed) {
|
|
2748
|
+
lines.push(` - Removed: ${name}`);
|
|
2749
|
+
}
|
|
2750
|
+
for (const m of diff.agents.modified) {
|
|
2751
|
+
lines.push(` ~ Modified: ${m.name} (${m.changes})`);
|
|
2752
|
+
}
|
|
2753
|
+
return lines;
|
|
2754
|
+
}
|
|
2755
|
+
function formatMcpBlock(diff) {
|
|
2756
|
+
const lines = [];
|
|
2757
|
+
for (const s of diff.mcpServers.added) {
|
|
2758
|
+
lines.push(` + Added: ${s.id}`);
|
|
2759
|
+
}
|
|
2760
|
+
for (const id of diff.mcpServers.removed) {
|
|
2761
|
+
lines.push(` - Removed: ${id}`);
|
|
2762
|
+
}
|
|
2763
|
+
return lines;
|
|
2764
|
+
}
|
|
2765
|
+
function formatSettingsBlock(diff) {
|
|
2766
|
+
const lines = [];
|
|
2767
|
+
for (const c of diff.settings.changes) {
|
|
2768
|
+
lines.push(` ~ ${c.path} changed`);
|
|
2769
|
+
}
|
|
2770
|
+
return lines;
|
|
2771
|
+
}
|
|
2772
|
+
function deepEqual(a, b) {
|
|
2773
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
2774
|
+
}
|
|
2775
|
+
var init_diff = __esm({
|
|
2776
|
+
"src/ir/diff.ts"() {
|
|
2777
|
+
"use strict";
|
|
2778
|
+
init_types();
|
|
2779
|
+
}
|
|
2780
|
+
});
|
|
2781
|
+
|
|
1412
2782
|
// src/evolve/mutator.ts
|
|
1413
|
-
import
|
|
1414
|
-
import
|
|
2783
|
+
import fs22 from "fs/promises";
|
|
2784
|
+
import path22 from "path";
|
|
1415
2785
|
async function applyMutations(currentHarnessPath, nextIterationDir, mutations) {
|
|
1416
|
-
const newHarnessPath =
|
|
2786
|
+
const newHarnessPath = path22.join(nextIterationDir, "harness");
|
|
2787
|
+
let baselineIR = null;
|
|
2788
|
+
try {
|
|
2789
|
+
baselineIR = await parseHarness(currentHarnessPath);
|
|
2790
|
+
} catch {
|
|
2791
|
+
}
|
|
2792
|
+
if (baselineIR !== null) {
|
|
2793
|
+
return applyMutationsViaIR(
|
|
2794
|
+
currentHarnessPath,
|
|
2795
|
+
newHarnessPath,
|
|
2796
|
+
mutations,
|
|
2797
|
+
baselineIR
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
return applyMutationsLegacy(currentHarnessPath, newHarnessPath, mutations);
|
|
2801
|
+
}
|
|
2802
|
+
async function applyMutationsViaIR(currentHarnessPath, newHarnessPath, mutations, baselineIR) {
|
|
1417
2803
|
await copyDir(currentHarnessPath, newHarnessPath);
|
|
1418
|
-
|
|
1419
|
-
|
|
2804
|
+
const irMutations = translateMutations(mutations, baselineIR);
|
|
2805
|
+
let currentIR = baselineIR;
|
|
2806
|
+
const rawTextMutations = [];
|
|
2807
|
+
const touchedCategories = /* @__PURE__ */ new Set();
|
|
2808
|
+
for (let i = 0; i < irMutations.length; i++) {
|
|
2809
|
+
const irMut = irMutations[i];
|
|
2810
|
+
if (irMut.type === "raw_text") {
|
|
2811
|
+
rawTextMutations.push(mutations[i]);
|
|
1420
2812
|
continue;
|
|
1421
2813
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
2814
|
+
try {
|
|
2815
|
+
currentIR = applyIRMutation(currentIR, irMut);
|
|
2816
|
+
touchedCategories.add(getMutationCategory(irMut.type));
|
|
2817
|
+
} catch {
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
if (touchedCategories.size > 0) {
|
|
2822
|
+
await renderAffectedFiles(currentIR, newHarnessPath, touchedCategories);
|
|
2823
|
+
}
|
|
2824
|
+
for (const mutation of rawTextMutations) {
|
|
2825
|
+
await applyLegacyMutation(newHarnessPath, mutation);
|
|
2826
|
+
}
|
|
2827
|
+
const irDiff = diffIR(baselineIR, currentIR);
|
|
2828
|
+
let diffPatch = formatIRDiff(irDiff);
|
|
2829
|
+
if (diffPatch === "No changes." && rawTextMutations.length > 0) {
|
|
2830
|
+
diffPatch = await generateDiffLegacy(currentHarnessPath, newHarnessPath);
|
|
2831
|
+
}
|
|
2832
|
+
if (diffPatch === "No changes.") {
|
|
2833
|
+
diffPatch = "";
|
|
2834
|
+
}
|
|
2835
|
+
return { newHarnessPath, diffPatch };
|
|
2836
|
+
}
|
|
2837
|
+
function getMutationCategory(mutationType) {
|
|
2838
|
+
if (mutationType.includes("section") || mutationType.includes("reorder")) {
|
|
2839
|
+
return "claude_md";
|
|
2840
|
+
}
|
|
2841
|
+
if (mutationType.includes("command")) {
|
|
2842
|
+
return "commands";
|
|
2843
|
+
}
|
|
2844
|
+
if (mutationType.includes("rule")) {
|
|
2845
|
+
return "rules";
|
|
2846
|
+
}
|
|
2847
|
+
if (mutationType.includes("agent")) {
|
|
2848
|
+
return "agents";
|
|
2849
|
+
}
|
|
2850
|
+
if (mutationType.includes("mcp")) {
|
|
2851
|
+
return "mcp";
|
|
2852
|
+
}
|
|
2853
|
+
if (mutationType.includes("settings")) {
|
|
2854
|
+
return "settings";
|
|
2855
|
+
}
|
|
2856
|
+
return "unknown";
|
|
2857
|
+
}
|
|
2858
|
+
async function renderAffectedFiles(ir, targetDir, touchedCategories) {
|
|
2859
|
+
const fileMap = renderHarness(ir);
|
|
2860
|
+
for (const [relativePath, content] of fileMap) {
|
|
2861
|
+
const category = getFileCategory(relativePath);
|
|
2862
|
+
if (touchedCategories.has(category)) {
|
|
2863
|
+
const fullPath = path22.join(targetDir, relativePath);
|
|
2864
|
+
await fs22.mkdir(path22.dirname(fullPath), { recursive: true });
|
|
2865
|
+
await fs22.writeFile(fullPath, content, "utf-8");
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
if (touchedCategories.has("commands")) {
|
|
2869
|
+
await deleteOrphanedFiles(targetDir, "commands", fileMap);
|
|
2870
|
+
}
|
|
2871
|
+
if (touchedCategories.has("rules")) {
|
|
2872
|
+
await deleteOrphanedFiles(targetDir, "rules", fileMap);
|
|
2873
|
+
}
|
|
2874
|
+
if (touchedCategories.has("agents")) {
|
|
2875
|
+
await deleteOrphanedFiles(targetDir, "agents", fileMap);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
function getFileCategory(relativePath) {
|
|
2879
|
+
if (relativePath === "CLAUDE.md") return "claude_md";
|
|
2880
|
+
if (relativePath.startsWith("commands/")) return "commands";
|
|
2881
|
+
if (relativePath.startsWith("rules/")) return "rules";
|
|
2882
|
+
if (relativePath.startsWith("agents/")) return "agents";
|
|
2883
|
+
if (relativePath.startsWith("skills/")) return "skills";
|
|
2884
|
+
if (relativePath.startsWith("docs/")) return "docs";
|
|
2885
|
+
if (relativePath.startsWith("hooks/")) return "hooks";
|
|
2886
|
+
if (relativePath === "settings.json") return "settings";
|
|
2887
|
+
if (relativePath === ".mcp.json") return "mcp";
|
|
2888
|
+
return "unknown";
|
|
2889
|
+
}
|
|
2890
|
+
async function deleteOrphanedFiles(targetDir, subdir, renderedMap) {
|
|
2891
|
+
const subdirPath = path22.join(targetDir, subdir);
|
|
2892
|
+
let entries;
|
|
2893
|
+
try {
|
|
2894
|
+
entries = await fs22.readdir(subdirPath);
|
|
2895
|
+
} catch {
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
for (const entry of entries) {
|
|
2899
|
+
const relativePath = `${subdir}/${entry}`;
|
|
2900
|
+
if (!renderedMap.has(relativePath)) {
|
|
2901
|
+
await fs22.unlink(path22.join(subdirPath, entry)).catch(() => {
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async function applyMutationsLegacy(currentHarnessPath, newHarnessPath, mutations) {
|
|
2907
|
+
await copyDir(currentHarnessPath, newHarnessPath);
|
|
2908
|
+
for (const mutation of mutations) {
|
|
2909
|
+
await applyLegacyMutation(newHarnessPath, mutation);
|
|
2910
|
+
}
|
|
2911
|
+
const diffPatch = await generateDiffLegacy(currentHarnessPath, newHarnessPath);
|
|
2912
|
+
return { newHarnessPath, diffPatch };
|
|
2913
|
+
}
|
|
2914
|
+
async function applyLegacyMutation(harnessPath, mutation) {
|
|
2915
|
+
if (mutation.file.includes("..")) {
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
const filePath = path22.join(harnessPath, mutation.file);
|
|
2919
|
+
if (mutation.action === "replace") {
|
|
2920
|
+
if (!mutation.oldText) {
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
let content;
|
|
2924
|
+
try {
|
|
2925
|
+
content = await fs22.readFile(filePath, "utf-8");
|
|
2926
|
+
} catch {
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
if (!content.includes(mutation.oldText)) {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
await fs22.writeFile(
|
|
2933
|
+
filePath,
|
|
2934
|
+
content.replace(mutation.oldText, mutation.newText),
|
|
2935
|
+
"utf-8"
|
|
2936
|
+
);
|
|
2937
|
+
} else if (mutation.action === "add_section") {
|
|
2938
|
+
try {
|
|
2939
|
+
const content = await fs22.readFile(filePath, "utf-8");
|
|
2940
|
+
await fs22.writeFile(
|
|
1432
2941
|
filePath,
|
|
1433
|
-
content
|
|
2942
|
+
content + "\n\n" + mutation.newText,
|
|
1434
2943
|
"utf-8"
|
|
1435
2944
|
);
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
await
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2945
|
+
} catch {
|
|
2946
|
+
await fs22.mkdir(path22.dirname(filePath), { recursive: true });
|
|
2947
|
+
await fs22.writeFile(filePath, mutation.newText, "utf-8");
|
|
2948
|
+
}
|
|
2949
|
+
} else if (mutation.action === "create_file") {
|
|
2950
|
+
await fs22.mkdir(path22.dirname(filePath), { recursive: true });
|
|
2951
|
+
await fs22.writeFile(filePath, mutation.newText, "utf-8");
|
|
2952
|
+
} else if (mutation.action === "delete_section") {
|
|
2953
|
+
if (!mutation.oldText) {
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
let sectionContent;
|
|
2957
|
+
try {
|
|
2958
|
+
sectionContent = await fs22.readFile(filePath, "utf-8");
|
|
2959
|
+
} catch {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
if (!sectionContent.includes(mutation.oldText)) {
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
await fs22.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
|
|
2966
|
+
} else if (mutation.action === "delete_file") {
|
|
2967
|
+
await fs22.unlink(filePath).catch(() => {
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
async function generateDiff2(oldDir, newDir) {
|
|
2972
|
+
try {
|
|
2973
|
+
const oldIR = await parseHarness(oldDir);
|
|
2974
|
+
const newIR = await parseHarness(newDir);
|
|
2975
|
+
const oldHasContent = oldIR.sections.length > 0 || oldIR.commands.length > 0 || oldIR.rules.length > 0 || oldIR.agents.length > 0;
|
|
2976
|
+
const newHasContent = newIR.sections.length > 0 || newIR.commands.length > 0 || newIR.rules.length > 0 || newIR.agents.length > 0;
|
|
2977
|
+
if (oldHasContent || newHasContent) {
|
|
2978
|
+
const irDiff = diffIR(oldIR, newIR);
|
|
2979
|
+
const formatted = formatIRDiff(irDiff);
|
|
2980
|
+
if (formatted === "No changes.") {
|
|
2981
|
+
const legacyDiff2 = await generateDiffLegacy(oldDir, newDir);
|
|
2982
|
+
return legacyDiff2;
|
|
1460
2983
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
2984
|
+
const legacyDiff = await generateDiffLegacy(oldDir, newDir);
|
|
2985
|
+
if (legacyDiff && !formatted.includes(legacyDiff)) {
|
|
2986
|
+
return formatted + "\n\n" + legacyDiff;
|
|
1463
2987
|
}
|
|
1464
|
-
|
|
1465
|
-
} else if (mutation.action === "delete_file") {
|
|
1466
|
-
await fs21.unlink(filePath).catch(() => {
|
|
1467
|
-
});
|
|
2988
|
+
return formatted;
|
|
1468
2989
|
}
|
|
2990
|
+
} catch {
|
|
1469
2991
|
}
|
|
1470
|
-
|
|
1471
|
-
return { newHarnessPath, diffPatch };
|
|
2992
|
+
return generateDiffLegacy(oldDir, newDir);
|
|
1472
2993
|
}
|
|
1473
|
-
async function
|
|
2994
|
+
async function generateDiffLegacy(oldDir, newDir) {
|
|
1474
2995
|
const oldFiles = await readAllFiles(oldDir);
|
|
1475
2996
|
const newFiles = await readAllFiles(newDir);
|
|
1476
2997
|
const allPaths = /* @__PURE__ */ new Set([
|
|
@@ -1511,17 +3032,17 @@ async function readAllFiles(dir) {
|
|
|
1511
3032
|
async function walk(current) {
|
|
1512
3033
|
let entries;
|
|
1513
3034
|
try {
|
|
1514
|
-
entries = await
|
|
3035
|
+
entries = await fs22.readdir(current, { withFileTypes: true });
|
|
1515
3036
|
} catch {
|
|
1516
3037
|
return;
|
|
1517
3038
|
}
|
|
1518
3039
|
for (const entry of entries) {
|
|
1519
|
-
const fullPath =
|
|
1520
|
-
const relativePath =
|
|
3040
|
+
const fullPath = path22.join(current, entry.name);
|
|
3041
|
+
const relativePath = path22.relative(dir, fullPath);
|
|
1521
3042
|
if (entry.isDirectory()) {
|
|
1522
3043
|
await walk(fullPath);
|
|
1523
3044
|
} else {
|
|
1524
|
-
result[relativePath] = await
|
|
3045
|
+
result[relativePath] = await fs22.readFile(fullPath, "utf-8");
|
|
1525
3046
|
}
|
|
1526
3047
|
}
|
|
1527
3048
|
}
|
|
@@ -1532,12 +3053,17 @@ var init_mutator = __esm({
|
|
|
1532
3053
|
"src/evolve/mutator.ts"() {
|
|
1533
3054
|
"use strict";
|
|
1534
3055
|
init_baseline();
|
|
3056
|
+
init_parser();
|
|
3057
|
+
init_translate();
|
|
3058
|
+
init_mutations();
|
|
3059
|
+
init_renderer();
|
|
3060
|
+
init_diff();
|
|
1535
3061
|
}
|
|
1536
3062
|
});
|
|
1537
3063
|
|
|
1538
3064
|
// src/evolve/sampling.ts
|
|
1539
|
-
import
|
|
1540
|
-
import
|
|
3065
|
+
import fs23 from "fs/promises";
|
|
3066
|
+
import path23 from "path";
|
|
1541
3067
|
function initBeliefs(tasks) {
|
|
1542
3068
|
return tasks.map((task) => ({
|
|
1543
3069
|
taskId: task.id,
|
|
@@ -1598,9 +3124,9 @@ function updateBeliefs(beliefs, results) {
|
|
|
1598
3124
|
});
|
|
1599
3125
|
}
|
|
1600
3126
|
async function loadBeliefs(workspacePath) {
|
|
1601
|
-
const beliefsPath =
|
|
3127
|
+
const beliefsPath = path23.join(workspacePath, "task-beliefs.json");
|
|
1602
3128
|
try {
|
|
1603
|
-
const content = await
|
|
3129
|
+
const content = await fs23.readFile(beliefsPath, "utf-8");
|
|
1604
3130
|
const parsed = JSON.parse(content);
|
|
1605
3131
|
if (!Array.isArray(parsed)) return null;
|
|
1606
3132
|
for (const entry of parsed) {
|
|
@@ -1614,9 +3140,9 @@ async function loadBeliefs(workspacePath) {
|
|
|
1614
3140
|
}
|
|
1615
3141
|
}
|
|
1616
3142
|
async function saveBeliefs(workspacePath, beliefs) {
|
|
1617
|
-
const beliefsPath =
|
|
1618
|
-
await
|
|
1619
|
-
await
|
|
3143
|
+
const beliefsPath = path23.join(workspacePath, "task-beliefs.json");
|
|
3144
|
+
await fs23.mkdir(path23.dirname(beliefsPath), { recursive: true });
|
|
3145
|
+
await fs23.writeFile(beliefsPath, JSON.stringify(beliefs, null, 2), "utf-8");
|
|
1620
3146
|
}
|
|
1621
3147
|
var init_sampling = __esm({
|
|
1622
3148
|
"src/evolve/sampling.ts"() {
|
|
@@ -1625,8 +3151,8 @@ var init_sampling = __esm({
|
|
|
1625
3151
|
});
|
|
1626
3152
|
|
|
1627
3153
|
// src/evolve/regularization.ts
|
|
1628
|
-
import
|
|
1629
|
-
import
|
|
3154
|
+
import fs24 from "fs/promises";
|
|
3155
|
+
import path24 from "path";
|
|
1630
3156
|
async function measureComplexity(harnessPath) {
|
|
1631
3157
|
let totalLines = 0;
|
|
1632
3158
|
let totalFiles = 0;
|
|
@@ -1657,6 +3183,64 @@ async function measureComplexity(harnessPath) {
|
|
|
1657
3183
|
diffFromBaseline: 0
|
|
1658
3184
|
};
|
|
1659
3185
|
}
|
|
3186
|
+
function measureComplexityFromIR(ir) {
|
|
3187
|
+
const totalSections = ir.sections.length;
|
|
3188
|
+
const totalRules = ir.rules.length;
|
|
3189
|
+
const totalCommands = ir.commands.length;
|
|
3190
|
+
let totalFiles = 0;
|
|
3191
|
+
if (ir.sections.length > 0 || ir.meta.name) {
|
|
3192
|
+
totalFiles += 1;
|
|
3193
|
+
}
|
|
3194
|
+
totalFiles += ir.commands.length;
|
|
3195
|
+
totalFiles += ir.rules.length;
|
|
3196
|
+
totalFiles += ir.agents.length;
|
|
3197
|
+
totalFiles += ir.skills.length;
|
|
3198
|
+
totalFiles += ir.docs.length;
|
|
3199
|
+
totalFiles += ir.hooks.length;
|
|
3200
|
+
const hasSettings = ir.settings.statusLine !== void 0 || ir.settings.denyPatterns !== void 0 && ir.settings.denyPatterns.length > 0 || Object.keys(ir.settings.raw).length > 0 || Object.values(ir.settings.hooks).some(
|
|
3201
|
+
(entries) => entries !== void 0 && entries.length > 0
|
|
3202
|
+
);
|
|
3203
|
+
if (hasSettings) {
|
|
3204
|
+
totalFiles += 1;
|
|
3205
|
+
}
|
|
3206
|
+
if (ir.mcpServers.length > 0) {
|
|
3207
|
+
totalFiles += 1;
|
|
3208
|
+
}
|
|
3209
|
+
let totalLines = 0;
|
|
3210
|
+
for (const section of ir.sections) {
|
|
3211
|
+
totalLines += countLines(section.content);
|
|
3212
|
+
}
|
|
3213
|
+
for (const cmd of ir.commands) {
|
|
3214
|
+
totalLines += countLines(cmd.content);
|
|
3215
|
+
}
|
|
3216
|
+
for (const rule of ir.rules) {
|
|
3217
|
+
totalLines += countLines(rule.content);
|
|
3218
|
+
}
|
|
3219
|
+
for (const agent of ir.agents) {
|
|
3220
|
+
totalLines += countLines(agent.content);
|
|
3221
|
+
}
|
|
3222
|
+
for (const skill of ir.skills) {
|
|
3223
|
+
totalLines += countLines(skill.content);
|
|
3224
|
+
}
|
|
3225
|
+
for (const doc of ir.docs) {
|
|
3226
|
+
totalLines += countLines(doc.content);
|
|
3227
|
+
}
|
|
3228
|
+
for (const hook of ir.hooks) {
|
|
3229
|
+
totalLines += countLines(hook.content);
|
|
3230
|
+
}
|
|
3231
|
+
return {
|
|
3232
|
+
totalLines,
|
|
3233
|
+
totalFiles,
|
|
3234
|
+
totalSections,
|
|
3235
|
+
totalRules,
|
|
3236
|
+
totalCommands,
|
|
3237
|
+
diffFromBaseline: 0
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
function countLines(content) {
|
|
3241
|
+
if (!content) return 0;
|
|
3242
|
+
return content.split("\n").length;
|
|
3243
|
+
}
|
|
1660
3244
|
function computeComplexityCost(current, baseline) {
|
|
1661
3245
|
const baselineLines = Math.max(baseline.totalLines, 1);
|
|
1662
3246
|
const lineDelta = (current.totalLines - baseline.totalLines) / baselineLines;
|
|
@@ -1702,18 +3286,18 @@ async function readAllFilesRecursive(dir) {
|
|
|
1702
3286
|
async function walk(current) {
|
|
1703
3287
|
let entries;
|
|
1704
3288
|
try {
|
|
1705
|
-
entries = await
|
|
3289
|
+
entries = await fs24.readdir(current, { withFileTypes: true });
|
|
1706
3290
|
} catch {
|
|
1707
3291
|
return;
|
|
1708
3292
|
}
|
|
1709
3293
|
for (const entry of entries) {
|
|
1710
|
-
const fullPath =
|
|
1711
|
-
const relativePath =
|
|
3294
|
+
const fullPath = path24.join(current, entry.name);
|
|
3295
|
+
const relativePath = path24.relative(dir, fullPath);
|
|
1712
3296
|
if (entry.isDirectory()) {
|
|
1713
3297
|
await walk(fullPath);
|
|
1714
3298
|
} else {
|
|
1715
3299
|
try {
|
|
1716
|
-
result[relativePath] = await
|
|
3300
|
+
result[relativePath] = await fs24.readFile(fullPath, "utf-8");
|
|
1717
3301
|
} catch {
|
|
1718
3302
|
}
|
|
1719
3303
|
}
|
|
@@ -1729,8 +3313,8 @@ var init_regularization = __esm({
|
|
|
1729
3313
|
});
|
|
1730
3314
|
|
|
1731
3315
|
// src/evolve/loop.ts
|
|
1732
|
-
import
|
|
1733
|
-
import
|
|
3316
|
+
import fs25 from "fs/promises";
|
|
3317
|
+
import path25 from "path";
|
|
1734
3318
|
function computeMutationCap(iter, maxIterations, maxMutations) {
|
|
1735
3319
|
if (maxIterations <= 1) return maxMutations;
|
|
1736
3320
|
const progress = iter / (maxIterations - 1);
|
|
@@ -1747,11 +3331,17 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1747
3331
|
let beliefs = useThompson ? await loadBeliefs(workspacePath) ?? initBeliefs(tasks) : [];
|
|
1748
3332
|
const useKL = evolveConfig.klLambda > 0;
|
|
1749
3333
|
let baselineComplexity = null;
|
|
3334
|
+
let baselineIR = null;
|
|
1750
3335
|
if (useKL) {
|
|
1751
|
-
const baselineHarness =
|
|
3336
|
+
const baselineHarness = path25.join(workspacePath, "iterations", "0", "harness");
|
|
1752
3337
|
try {
|
|
1753
|
-
|
|
3338
|
+
baselineIR = await parseHarness(baselineHarness);
|
|
3339
|
+
baselineComplexity = measureComplexityFromIR(baselineIR);
|
|
1754
3340
|
} catch {
|
|
3341
|
+
try {
|
|
3342
|
+
baselineComplexity = await measureComplexity(baselineHarness);
|
|
3343
|
+
} catch {
|
|
3344
|
+
}
|
|
1755
3345
|
}
|
|
1756
3346
|
}
|
|
1757
3347
|
let rngState = 42;
|
|
@@ -1760,14 +3350,14 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1760
3350
|
return (rngState >>> 0) / 4294967296;
|
|
1761
3351
|
};
|
|
1762
3352
|
for (let iter = 0; iter < evolveConfig.maxIterations; iter++) {
|
|
1763
|
-
const harnessPath =
|
|
3353
|
+
const harnessPath = path25.join(
|
|
1764
3354
|
workspacePath,
|
|
1765
3355
|
"iterations",
|
|
1766
3356
|
iter.toString(),
|
|
1767
3357
|
"harness"
|
|
1768
3358
|
);
|
|
1769
3359
|
try {
|
|
1770
|
-
await
|
|
3360
|
+
await fs25.access(harnessPath);
|
|
1771
3361
|
} catch {
|
|
1772
3362
|
if (iter === 0) {
|
|
1773
3363
|
throw new Error(
|
|
@@ -1851,10 +3441,16 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1851
3441
|
let aggregate = rawAggregate;
|
|
1852
3442
|
let iterComplexityCost;
|
|
1853
3443
|
if (useKL && baselineComplexity) {
|
|
1854
|
-
|
|
3444
|
+
let currentComplexity;
|
|
3445
|
+
try {
|
|
3446
|
+
const iterIR = await parseHarness(harnessPath);
|
|
3447
|
+
currentComplexity = measureComplexityFromIR(iterIR);
|
|
3448
|
+
} catch {
|
|
3449
|
+
currentComplexity = await measureComplexity(harnessPath);
|
|
3450
|
+
}
|
|
1855
3451
|
const diffRatio = await computeDiffRatio(
|
|
1856
3452
|
harnessPath,
|
|
1857
|
-
|
|
3453
|
+
path25.join(workspacePath, "iterations", "0", "harness")
|
|
1858
3454
|
);
|
|
1859
3455
|
currentComplexity.diffFromBaseline = diffRatio;
|
|
1860
3456
|
iterComplexityCost = computeComplexityCost(currentComplexity, baselineComplexity);
|
|
@@ -1872,7 +3468,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1872
3468
|
if (iter === 0) {
|
|
1873
3469
|
baselineScore = aggregate;
|
|
1874
3470
|
if (useKL && !baselineComplexity) {
|
|
1875
|
-
|
|
3471
|
+
try {
|
|
3472
|
+
baselineIR = await parseHarness(harnessPath);
|
|
3473
|
+
baselineComplexity = measureComplexityFromIR(baselineIR);
|
|
3474
|
+
} catch {
|
|
3475
|
+
baselineComplexity = await measureComplexity(harnessPath);
|
|
3476
|
+
}
|
|
1876
3477
|
}
|
|
1877
3478
|
}
|
|
1878
3479
|
let shouldRollback = iter > 0 && aggregate < bestScore;
|
|
@@ -1917,7 +3518,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1917
3518
|
};
|
|
1918
3519
|
await writeIterationLog(workspacePath, rollbackLog);
|
|
1919
3520
|
history.push(rollbackLog);
|
|
1920
|
-
const bestHarnessPath =
|
|
3521
|
+
const bestHarnessPath = path25.join(
|
|
1921
3522
|
workspacePath,
|
|
1922
3523
|
"iterations",
|
|
1923
3524
|
bestIteration.toString(),
|
|
@@ -1942,7 +3543,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1942
3543
|
mutations: rollbackProposal.mutations.slice(0, rollbackCap)
|
|
1943
3544
|
};
|
|
1944
3545
|
}
|
|
1945
|
-
const nextIterDir2 =
|
|
3546
|
+
const nextIterDir2 = path25.join(workspacePath, "iterations", (iter + 1).toString());
|
|
1946
3547
|
await applyMutations(bestHarnessPath, nextIterDir2, rollbackProposal.mutations);
|
|
1947
3548
|
onProgress?.({
|
|
1948
3549
|
type: "mutations-applied",
|
|
@@ -1950,8 +3551,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1950
3551
|
mutationCount: rollbackProposal.mutations.length
|
|
1951
3552
|
});
|
|
1952
3553
|
} catch {
|
|
1953
|
-
const nextIterDir2 =
|
|
1954
|
-
await copyDir(bestHarnessPath,
|
|
3554
|
+
const nextIterDir2 = path25.join(workspacePath, "iterations", (iter + 1).toString());
|
|
3555
|
+
await copyDir(bestHarnessPath, path25.join(nextIterDir2, "harness"));
|
|
1955
3556
|
}
|
|
1956
3557
|
}
|
|
1957
3558
|
continue;
|
|
@@ -2015,12 +3616,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2015
3616
|
iteration: iter,
|
|
2016
3617
|
message: `Proposer failed: ${errMsg}`
|
|
2017
3618
|
});
|
|
2018
|
-
const nextIterDir2 =
|
|
3619
|
+
const nextIterDir2 = path25.join(
|
|
2019
3620
|
workspacePath,
|
|
2020
3621
|
"iterations",
|
|
2021
3622
|
(iter + 1).toString()
|
|
2022
3623
|
);
|
|
2023
|
-
await copyDir(harnessPath,
|
|
3624
|
+
await copyDir(harnessPath, path25.join(nextIterDir2, "harness"));
|
|
2024
3625
|
const skipLog = {
|
|
2025
3626
|
iteration: iter,
|
|
2026
3627
|
score: aggregate,
|
|
@@ -2035,7 +3636,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2035
3636
|
history.push(skipLog);
|
|
2036
3637
|
continue;
|
|
2037
3638
|
}
|
|
2038
|
-
const nextIterDir =
|
|
3639
|
+
const nextIterDir = path25.join(
|
|
2039
3640
|
workspacePath,
|
|
2040
3641
|
"iterations",
|
|
2041
3642
|
(iter + 1).toString()
|
|
@@ -2049,7 +3650,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2049
3650
|
);
|
|
2050
3651
|
diffPatch = mutationResult.diffPatch;
|
|
2051
3652
|
} catch {
|
|
2052
|
-
await copyDir(harnessPath,
|
|
3653
|
+
await copyDir(harnessPath, path25.join(nextIterDir, "harness"));
|
|
2053
3654
|
}
|
|
2054
3655
|
onProgress?.({
|
|
2055
3656
|
type: "mutations-applied",
|
|
@@ -2071,7 +3672,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2071
3672
|
}
|
|
2072
3673
|
if (evolveConfig.usePrincipal && history.length >= 2) {
|
|
2073
3674
|
onProgress?.({ type: "proposing", iteration: history.length, message: "Principal Proposer synthesizing final harness" });
|
|
2074
|
-
const baselineHarnessPath =
|
|
3675
|
+
const baselineHarnessPath = path25.join(workspacePath, "iterations", "0", "harness");
|
|
2075
3676
|
try {
|
|
2076
3677
|
const principalProposal = await propose(
|
|
2077
3678
|
history.length,
|
|
@@ -2086,7 +3687,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2086
3687
|
principalProposal.mutations = principalProposal.mutations.slice(0, evolveConfig.maxMutationsPerIteration);
|
|
2087
3688
|
}
|
|
2088
3689
|
const principalIterNum = history.length;
|
|
2089
|
-
const principalIterDir =
|
|
3690
|
+
const principalIterDir = path25.join(workspacePath, "iterations", principalIterNum.toString());
|
|
2090
3691
|
const mutResult = await applyMutations(baselineHarnessPath, principalIterDir, principalProposal.mutations);
|
|
2091
3692
|
onProgress?.({ type: "iteration-start", iteration: principalIterNum });
|
|
2092
3693
|
const { results: principalResults, aggregate: principalAggregate } = await evaluateAll(
|
|
@@ -2147,11 +3748,12 @@ var init_loop = __esm({
|
|
|
2147
3748
|
init_baseline();
|
|
2148
3749
|
init_sampling();
|
|
2149
3750
|
init_regularization();
|
|
3751
|
+
init_parser();
|
|
2150
3752
|
}
|
|
2151
3753
|
});
|
|
2152
3754
|
|
|
2153
3755
|
// src/evolve/synthesis.ts
|
|
2154
|
-
import
|
|
3756
|
+
import path28 from "path";
|
|
2155
3757
|
function buildMetaPrincipalSystemPrompt(numBranches) {
|
|
2156
3758
|
return `You are reviewing the COMPLETE results of ${numBranches} independent evolution runs.
|
|
2157
3759
|
Each branch explored different mutations and saw different task subsets.
|
|
@@ -2306,7 +3908,7 @@ async function runSynthesis(context, kairnConfig, evolveConfig, workspacePath) {
|
|
|
2306
3908
|
if (mutations.length === 0) {
|
|
2307
3909
|
return null;
|
|
2308
3910
|
}
|
|
2309
|
-
const synthesisDir =
|
|
3911
|
+
const synthesisDir = path28.join(workspacePath, "synthesis");
|
|
2310
3912
|
const { newHarnessPath } = await applyMutations(
|
|
2311
3913
|
context.baselineHarnessPath,
|
|
2312
3914
|
synthesisDir,
|
|
@@ -2340,26 +3942,26 @@ __export(population_exports, {
|
|
|
2340
3942
|
initBranches: () => initBranches,
|
|
2341
3943
|
runPopulation: () => runPopulation
|
|
2342
3944
|
});
|
|
2343
|
-
import
|
|
2344
|
-
import
|
|
3945
|
+
import fs28 from "fs/promises";
|
|
3946
|
+
import path29 from "path";
|
|
2345
3947
|
async function initBranches(workspacePath, baselinePath, numBranches) {
|
|
2346
|
-
const branchesDir =
|
|
2347
|
-
await
|
|
3948
|
+
const branchesDir = path29.join(workspacePath, "branches");
|
|
3949
|
+
await fs28.mkdir(branchesDir, { recursive: true });
|
|
2348
3950
|
const configs = [];
|
|
2349
3951
|
for (let i = 0; i < numBranches; i++) {
|
|
2350
|
-
const branchPath =
|
|
2351
|
-
const harnessPath =
|
|
3952
|
+
const branchPath = path29.join(branchesDir, i.toString());
|
|
3953
|
+
const harnessPath = path29.join(branchPath, "iterations", "0", "harness");
|
|
2352
3954
|
await copyDir(baselinePath, harnessPath);
|
|
2353
|
-
const tasksYaml =
|
|
3955
|
+
const tasksYaml = path29.join(workspacePath, "tasks.yaml");
|
|
2354
3956
|
try {
|
|
2355
|
-
await
|
|
2356
|
-
await
|
|
3957
|
+
await fs28.access(tasksYaml);
|
|
3958
|
+
await fs28.copyFile(tasksYaml, path29.join(branchPath, "tasks.yaml"));
|
|
2357
3959
|
} catch {
|
|
2358
3960
|
}
|
|
2359
|
-
const configYaml =
|
|
3961
|
+
const configYaml = path29.join(workspacePath, "config.yaml");
|
|
2360
3962
|
try {
|
|
2361
|
-
await
|
|
2362
|
-
await
|
|
3963
|
+
await fs28.access(configYaml);
|
|
3964
|
+
await fs28.copyFile(configYaml, path29.join(branchPath, "config.yaml"));
|
|
2363
3965
|
} catch {
|
|
2364
3966
|
}
|
|
2365
3967
|
const seed = 42 + i * 1337;
|
|
@@ -2373,7 +3975,7 @@ async function initBranches(workspacePath, baselinePath, numBranches) {
|
|
|
2373
3975
|
}
|
|
2374
3976
|
async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, numBranches, onProgress) {
|
|
2375
3977
|
const branches = numBranches ?? evolveConfig.pbtBranches;
|
|
2376
|
-
const baselinePath =
|
|
3978
|
+
const baselinePath = path29.join(workspacePath, "baseline");
|
|
2377
3979
|
const branchConfigs = await initBranches(workspacePath, baselinePath, branches);
|
|
2378
3980
|
const branchPromises = branchConfigs.map(async (branchConfig) => {
|
|
2379
3981
|
const branchEvolveConfig = {
|
|
@@ -2391,7 +3993,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2391
3993
|
branchEvolveConfig,
|
|
2392
3994
|
branchProgress
|
|
2393
3995
|
);
|
|
2394
|
-
const finalHarnessPath =
|
|
3996
|
+
const finalHarnessPath = path29.join(
|
|
2395
3997
|
branchConfig.workspacePath,
|
|
2396
3998
|
"iterations",
|
|
2397
3999
|
result.bestIteration.toString(),
|
|
@@ -2399,8 +4001,8 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2399
4001
|
);
|
|
2400
4002
|
let beliefs = [];
|
|
2401
4003
|
try {
|
|
2402
|
-
const beliefsPath =
|
|
2403
|
-
const beliefsContent = await
|
|
4004
|
+
const beliefsPath = path29.join(branchConfig.workspacePath, "task-beliefs.json");
|
|
4005
|
+
const beliefsContent = await fs28.readFile(beliefsPath, "utf-8");
|
|
2404
4006
|
beliefs = JSON.parse(beliefsContent);
|
|
2405
4007
|
} catch {
|
|
2406
4008
|
}
|
|
@@ -2422,7 +4024,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2422
4024
|
}
|
|
2423
4025
|
let synthesizedResult;
|
|
2424
4026
|
try {
|
|
2425
|
-
const baselinePath2 =
|
|
4027
|
+
const baselinePath2 = path29.join(workspacePath, "baseline");
|
|
2426
4028
|
const synthesisResult = await runSynthesis(
|
|
2427
4029
|
{ branches: branchResults, tasks, baselineHarnessPath: baselinePath2 },
|
|
2428
4030
|
kairnConfig,
|
|
@@ -2597,7 +4199,7 @@ var ui = {
|
|
|
2597
4199
|
// Key-value pairs
|
|
2598
4200
|
kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
|
|
2599
4201
|
// File list
|
|
2600
|
-
file: (
|
|
4202
|
+
file: (path31) => chalk.dim(` ${path31}`),
|
|
2601
4203
|
// Tool display
|
|
2602
4204
|
tool: (name, reason) => ` ${warmStone("\u25CF")} ${chalk.bold(name)}
|
|
2603
4205
|
${chalk.dim(reason)}`,
|
|
@@ -6449,8 +8051,8 @@ var keysCommand = new Command10("keys").description("Add or update API keys for
|
|
|
6449
8051
|
import { Command as Command11 } from "commander";
|
|
6450
8052
|
import chalk14 from "chalk";
|
|
6451
8053
|
import ora2 from "ora";
|
|
6452
|
-
import
|
|
6453
|
-
import
|
|
8054
|
+
import fs29 from "fs/promises";
|
|
8055
|
+
import path30 from "path";
|
|
6454
8056
|
import { parse as yamlParse2 } from "yaml";
|
|
6455
8057
|
import { confirm as confirm4, input as input4, select as select4 } from "@inquirer/prompts";
|
|
6456
8058
|
|
|
@@ -6780,8 +8382,8 @@ init_loop();
|
|
|
6780
8382
|
|
|
6781
8383
|
// src/evolve/report.ts
|
|
6782
8384
|
init_trace();
|
|
6783
|
-
import
|
|
6784
|
-
import
|
|
8385
|
+
import fs26 from "fs/promises";
|
|
8386
|
+
import path26 from "path";
|
|
6785
8387
|
|
|
6786
8388
|
// src/evolve/diagnosis.ts
|
|
6787
8389
|
function numericScore(s) {
|
|
@@ -6831,10 +8433,10 @@ function numericScore2(s) {
|
|
|
6831
8433
|
return s.score ?? (s.pass ? 100 : 0);
|
|
6832
8434
|
}
|
|
6833
8435
|
async function loadAllIterations(workspacePath) {
|
|
6834
|
-
const iterDir =
|
|
8436
|
+
const iterDir = path26.join(workspacePath, "iterations");
|
|
6835
8437
|
let entries;
|
|
6836
8438
|
try {
|
|
6837
|
-
entries = await
|
|
8439
|
+
entries = await fs26.readdir(iterDir);
|
|
6838
8440
|
} catch {
|
|
6839
8441
|
return [];
|
|
6840
8442
|
}
|
|
@@ -6848,7 +8450,7 @@ async function loadAllIterations(workspacePath) {
|
|
|
6848
8450
|
}
|
|
6849
8451
|
async function loadTasks(workspacePath) {
|
|
6850
8452
|
try {
|
|
6851
|
-
const content = await
|
|
8453
|
+
const content = await fs26.readFile(path26.join(workspacePath, "tasks.yaml"), "utf-8");
|
|
6852
8454
|
const parsed = yamlParse(content);
|
|
6853
8455
|
return parsed?.tasks ?? [];
|
|
6854
8456
|
} catch {
|
|
@@ -7028,13 +8630,13 @@ init_mutator();
|
|
|
7028
8630
|
init_baseline();
|
|
7029
8631
|
init_mutator();
|
|
7030
8632
|
init_trace();
|
|
7031
|
-
import
|
|
7032
|
-
import
|
|
8633
|
+
import fs27 from "fs/promises";
|
|
8634
|
+
import path27 from "path";
|
|
7033
8635
|
async function listIterations(workspacePath) {
|
|
7034
|
-
const iterationsDir =
|
|
8636
|
+
const iterationsDir = path27.join(workspacePath, "iterations");
|
|
7035
8637
|
let entries;
|
|
7036
8638
|
try {
|
|
7037
|
-
entries = await
|
|
8639
|
+
entries = await fs27.readdir(iterationsDir);
|
|
7038
8640
|
} catch {
|
|
7039
8641
|
return [];
|
|
7040
8642
|
}
|
|
@@ -7043,7 +8645,7 @@ async function listIterations(workspacePath) {
|
|
|
7043
8645
|
const n = parseInt(entry, 10);
|
|
7044
8646
|
if (!isNaN(n)) {
|
|
7045
8647
|
try {
|
|
7046
|
-
await
|
|
8648
|
+
await fs27.access(path27.join(iterationsDir, entry, "harness"));
|
|
7047
8649
|
nums.push(n);
|
|
7048
8650
|
} catch {
|
|
7049
8651
|
}
|
|
@@ -7069,16 +8671,16 @@ async function listFilesRecursive(dir) {
|
|
|
7069
8671
|
async function walk(current) {
|
|
7070
8672
|
let entries;
|
|
7071
8673
|
try {
|
|
7072
|
-
entries = await
|
|
8674
|
+
entries = await fs27.readdir(current, { withFileTypes: true });
|
|
7073
8675
|
} catch {
|
|
7074
8676
|
return;
|
|
7075
8677
|
}
|
|
7076
8678
|
for (const entry of entries) {
|
|
7077
|
-
const fullPath =
|
|
8679
|
+
const fullPath = path27.join(current, entry.name);
|
|
7078
8680
|
if (entry.isDirectory()) {
|
|
7079
8681
|
await walk(fullPath);
|
|
7080
8682
|
} else {
|
|
7081
|
-
results.push(
|
|
8683
|
+
results.push(path27.relative(dir, fullPath));
|
|
7082
8684
|
}
|
|
7083
8685
|
}
|
|
7084
8686
|
}
|
|
@@ -7101,37 +8703,37 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration) {
|
|
|
7101
8703
|
} else {
|
|
7102
8704
|
iter = await findBestIteration(workspacePath, iterations);
|
|
7103
8705
|
}
|
|
7104
|
-
const harnessPath =
|
|
8706
|
+
const harnessPath = path27.join(
|
|
7105
8707
|
workspacePath,
|
|
7106
8708
|
"iterations",
|
|
7107
8709
|
iter.toString(),
|
|
7108
8710
|
"harness"
|
|
7109
8711
|
);
|
|
7110
|
-
const claudeDir =
|
|
8712
|
+
const claudeDir = path27.join(projectRoot, ".claude");
|
|
7111
8713
|
const diffPreview = await generateDiff2(claudeDir, harnessPath);
|
|
7112
8714
|
const currentFiles = await listFilesRecursive(claudeDir);
|
|
7113
8715
|
const targetFiles = await listFilesRecursive(harnessPath);
|
|
7114
8716
|
const allPaths = /* @__PURE__ */ new Set([...currentFiles, ...targetFiles]);
|
|
7115
8717
|
const filesChanged = [];
|
|
7116
8718
|
for (const filePath of allPaths) {
|
|
7117
|
-
const currentContent = await
|
|
7118
|
-
const targetContent = await
|
|
8719
|
+
const currentContent = await fs27.readFile(path27.join(claudeDir, filePath), "utf-8").catch(() => null);
|
|
8720
|
+
const targetContent = await fs27.readFile(path27.join(harnessPath, filePath), "utf-8").catch(() => null);
|
|
7119
8721
|
if (currentContent !== targetContent) {
|
|
7120
8722
|
filesChanged.push(filePath);
|
|
7121
8723
|
}
|
|
7122
8724
|
}
|
|
7123
|
-
await
|
|
8725
|
+
await fs27.rm(claudeDir, { recursive: true, force: true });
|
|
7124
8726
|
await copyDir(harnessPath, claudeDir);
|
|
7125
|
-
const harnessMcpJson =
|
|
7126
|
-
const projectMcpJson =
|
|
8727
|
+
const harnessMcpJson = path27.join(harnessPath, ".mcp.json");
|
|
8728
|
+
const projectMcpJson = path27.join(projectRoot, ".mcp.json");
|
|
7127
8729
|
try {
|
|
7128
|
-
await
|
|
7129
|
-
const currentMcp = await
|
|
7130
|
-
const targetMcp = await
|
|
8730
|
+
await fs27.access(harnessMcpJson);
|
|
8731
|
+
const currentMcp = await fs27.readFile(projectMcpJson, "utf-8").catch(() => null);
|
|
8732
|
+
const targetMcp = await fs27.readFile(harnessMcpJson, "utf-8").catch(() => null);
|
|
7131
8733
|
if (currentMcp !== targetMcp) {
|
|
7132
8734
|
filesChanged.push(".mcp.json");
|
|
7133
8735
|
}
|
|
7134
|
-
await
|
|
8736
|
+
await fs27.copyFile(harnessMcpJson, projectMcpJson);
|
|
7135
8737
|
} catch {
|
|
7136
8738
|
}
|
|
7137
8739
|
return {
|
|
@@ -7160,7 +8762,7 @@ var DEFAULT_CONFIG = {
|
|
|
7160
8762
|
};
|
|
7161
8763
|
async function loadEvolveConfigFromWorkspace(workspacePath) {
|
|
7162
8764
|
try {
|
|
7163
|
-
const configStr = await
|
|
8765
|
+
const configStr = await fs29.readFile(path30.join(workspacePath, "config.yaml"), "utf-8");
|
|
7164
8766
|
const parsed = yamlParse2(configStr);
|
|
7165
8767
|
return {
|
|
7166
8768
|
model: parsed.model ?? DEFAULT_CONFIG.model,
|
|
@@ -7187,9 +8789,9 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7187
8789
|
try {
|
|
7188
8790
|
const projectRoot = process.cwd();
|
|
7189
8791
|
console.log(ui.section("Evolve Init"));
|
|
7190
|
-
const claudeDir =
|
|
8792
|
+
const claudeDir = path30.join(projectRoot, ".claude");
|
|
7191
8793
|
try {
|
|
7192
|
-
await
|
|
8794
|
+
await fs29.access(claudeDir);
|
|
7193
8795
|
} catch {
|
|
7194
8796
|
console.log(ui.error("No .claude/ directory found. Run kairn describe first."));
|
|
7195
8797
|
process.exit(1);
|
|
@@ -7239,7 +8841,7 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7239
8841
|
if (config) {
|
|
7240
8842
|
let claudeMd = "";
|
|
7241
8843
|
try {
|
|
7242
|
-
claudeMd = await
|
|
8844
|
+
claudeMd = await fs29.readFile(path30.join(claudeDir, "CLAUDE.md"), "utf-8");
|
|
7243
8845
|
} catch {
|
|
7244
8846
|
}
|
|
7245
8847
|
const profile = await buildProjectProfile(projectRoot);
|
|
@@ -7270,16 +8872,16 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7270
8872
|
evolveCommand.command("baseline").description("Snapshot current .claude/ directory as baseline").action(async () => {
|
|
7271
8873
|
try {
|
|
7272
8874
|
const projectRoot = process.cwd();
|
|
7273
|
-
const workspace =
|
|
8875
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7274
8876
|
console.log(ui.section("Evolve Baseline"));
|
|
7275
8877
|
try {
|
|
7276
|
-
await
|
|
8878
|
+
await fs29.access(workspace);
|
|
7277
8879
|
} catch {
|
|
7278
8880
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7279
8881
|
process.exit(1);
|
|
7280
8882
|
}
|
|
7281
8883
|
await snapshotBaseline(projectRoot, workspace);
|
|
7282
|
-
const baselineDir =
|
|
8884
|
+
const baselineDir = path30.join(workspace, "baseline");
|
|
7283
8885
|
const fileCount = await countFiles(baselineDir);
|
|
7284
8886
|
console.log(ui.success(`Baseline snapshot created (${fileCount} files)`));
|
|
7285
8887
|
} catch (err) {
|
|
@@ -7291,18 +8893,18 @@ evolveCommand.command("baseline").description("Snapshot current .claude/ directo
|
|
|
7291
8893
|
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("-i, --interactive", "Configure evolution settings interactively").action(async (options) => {
|
|
7292
8894
|
try {
|
|
7293
8895
|
const projectRoot = process.cwd();
|
|
7294
|
-
const workspace =
|
|
8896
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7295
8897
|
console.log(ui.section("Evolve Run"));
|
|
7296
8898
|
try {
|
|
7297
|
-
await
|
|
8899
|
+
await fs29.access(workspace);
|
|
7298
8900
|
} catch {
|
|
7299
8901
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7300
8902
|
process.exit(1);
|
|
7301
8903
|
}
|
|
7302
|
-
const tasksPath =
|
|
8904
|
+
const tasksPath = path30.join(workspace, "tasks.yaml");
|
|
7303
8905
|
let tasksContent;
|
|
7304
8906
|
try {
|
|
7305
|
-
tasksContent = await
|
|
8907
|
+
tasksContent = await fs29.readFile(tasksPath, "utf-8");
|
|
7306
8908
|
} catch {
|
|
7307
8909
|
console.log(ui.error("No tasks.yaml found. Run kairn evolve init first."));
|
|
7308
8910
|
process.exit(1);
|
|
@@ -7321,15 +8923,15 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7321
8923
|
console.log(ui.info(`Running ${tasksToRun.length} task(s)...`));
|
|
7322
8924
|
console.log("");
|
|
7323
8925
|
const config = await loadConfig();
|
|
7324
|
-
const harnessPath =
|
|
8926
|
+
const harnessPath = path30.join(projectRoot, ".claude");
|
|
7325
8927
|
const results = [];
|
|
7326
8928
|
for (const task of tasksToRun) {
|
|
7327
|
-
const traceDir =
|
|
8929
|
+
const traceDir = path30.join(workspace, "traces", "0", task.id);
|
|
7328
8930
|
const spinner = ora2(`Running: ${task.id}`).start();
|
|
7329
8931
|
const result = await runTask(task, harnessPath, traceDir, 0);
|
|
7330
8932
|
if (config) {
|
|
7331
|
-
const stdout = await
|
|
7332
|
-
const stderr = await
|
|
8933
|
+
const stdout = await fs29.readFile(path30.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
|
|
8934
|
+
const stderr = await fs29.readFile(path30.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
|
|
7333
8935
|
const score = await scoreTask(task, traceDir, stdout, stderr, config);
|
|
7334
8936
|
result.score = score;
|
|
7335
8937
|
await writeScore(traceDir, score);
|
|
@@ -7477,7 +9079,7 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7477
9079
|
evolveConfig.klLambda = klLambda;
|
|
7478
9080
|
}
|
|
7479
9081
|
try {
|
|
7480
|
-
await
|
|
9082
|
+
await fs29.access(path30.join(workspace, "iterations", "0", "harness"));
|
|
7481
9083
|
} catch {
|
|
7482
9084
|
console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
|
|
7483
9085
|
process.exit(1);
|
|
@@ -7571,16 +9173,16 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7571
9173
|
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) => {
|
|
7572
9174
|
try {
|
|
7573
9175
|
const projectRoot = process.cwd();
|
|
7574
|
-
const workspace =
|
|
9176
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7575
9177
|
console.log(ui.section("Evolve PBT"));
|
|
7576
9178
|
try {
|
|
7577
|
-
await
|
|
9179
|
+
await fs29.access(workspace);
|
|
7578
9180
|
} catch {
|
|
7579
9181
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7580
9182
|
process.exit(1);
|
|
7581
9183
|
}
|
|
7582
9184
|
try {
|
|
7583
|
-
await
|
|
9185
|
+
await fs29.access(path30.join(workspace, "iterations", "0", "harness"));
|
|
7584
9186
|
} catch {
|
|
7585
9187
|
console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
|
|
7586
9188
|
process.exit(1);
|
|
@@ -7600,8 +9202,8 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
|
|
|
7600
9202
|
if (sampling === "thompson" || sampling === "uniform") {
|
|
7601
9203
|
evolveConfig.samplingStrategy = sampling;
|
|
7602
9204
|
}
|
|
7603
|
-
const tasksPath =
|
|
7604
|
-
const tasksContent = await
|
|
9205
|
+
const tasksPath = path30.join(workspace, "tasks.yaml");
|
|
9206
|
+
const tasksContent = await fs29.readFile(tasksPath, "utf-8");
|
|
7605
9207
|
const parsed = yamlParse2(tasksContent);
|
|
7606
9208
|
if (!parsed?.tasks || parsed.tasks.length === 0) {
|
|
7607
9209
|
console.log(ui.error("No tasks found in tasks.yaml"));
|
|
@@ -7659,10 +9261,10 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
|
|
|
7659
9261
|
evolveCommand.command("apply").description("Apply the best evolved harness to your project").option("--iter <n>", "Apply a specific iteration instead of the best").option("--force", "Apply even if git working tree is dirty").option("--no-commit", "Skip automatic git commit after applying").action(async (options) => {
|
|
7660
9262
|
try {
|
|
7661
9263
|
const projectRoot = process.cwd();
|
|
7662
|
-
const workspace =
|
|
9264
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7663
9265
|
console.log(ui.section("Evolve Apply"));
|
|
7664
9266
|
try {
|
|
7665
|
-
await
|
|
9267
|
+
await fs29.access(workspace);
|
|
7666
9268
|
} catch {
|
|
7667
9269
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7668
9270
|
process.exit(1);
|
|
@@ -7703,9 +9305,9 @@ evolveCommand.command("apply").description("Apply the best evolved harness to yo
|
|
|
7703
9305
|
evolveCommand.command("report").description("Generate a summary report of the evolution run").option("--json", "Output machine-readable JSON instead of Markdown").action(async (options) => {
|
|
7704
9306
|
try {
|
|
7705
9307
|
const projectRoot = process.cwd();
|
|
7706
|
-
const workspace =
|
|
9308
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7707
9309
|
try {
|
|
7708
|
-
await
|
|
9310
|
+
await fs29.access(workspace);
|
|
7709
9311
|
} catch {
|
|
7710
9312
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7711
9313
|
process.exit(1);
|
|
@@ -7726,23 +9328,23 @@ evolveCommand.command("report").description("Generate a summary report of the ev
|
|
|
7726
9328
|
evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes between two iterations").action(async (iter1Str, iter2Str) => {
|
|
7727
9329
|
try {
|
|
7728
9330
|
const projectRoot = process.cwd();
|
|
7729
|
-
const workspace =
|
|
9331
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7730
9332
|
const iter1 = parseInt(iter1Str, 10);
|
|
7731
9333
|
const iter2 = parseInt(iter2Str, 10);
|
|
7732
9334
|
if (isNaN(iter1) || isNaN(iter2)) {
|
|
7733
9335
|
console.log(ui.error("Both arguments must be integers (iteration numbers)"));
|
|
7734
9336
|
process.exit(1);
|
|
7735
9337
|
}
|
|
7736
|
-
const harness1 =
|
|
7737
|
-
const harness2 =
|
|
9338
|
+
const harness1 = path30.join(workspace, "iterations", iter1.toString(), "harness");
|
|
9339
|
+
const harness2 = path30.join(workspace, "iterations", iter2.toString(), "harness");
|
|
7738
9340
|
try {
|
|
7739
|
-
await
|
|
9341
|
+
await fs29.access(harness1);
|
|
7740
9342
|
} catch {
|
|
7741
9343
|
console.log(ui.error(`Iteration ${iter1} harness not found at ${harness1}`));
|
|
7742
9344
|
process.exit(1);
|
|
7743
9345
|
}
|
|
7744
9346
|
try {
|
|
7745
|
-
await
|
|
9347
|
+
await fs29.access(harness2);
|
|
7746
9348
|
} catch {
|
|
7747
9349
|
console.log(ui.error(`Iteration ${iter2} harness not found at ${harness2}`));
|
|
7748
9350
|
process.exit(1);
|
|
@@ -7797,10 +9399,10 @@ evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes
|
|
|
7797
9399
|
async function countFiles(dir) {
|
|
7798
9400
|
let count = 0;
|
|
7799
9401
|
try {
|
|
7800
|
-
const entries = await
|
|
9402
|
+
const entries = await fs29.readdir(dir, { withFileTypes: true });
|
|
7801
9403
|
for (const entry of entries) {
|
|
7802
9404
|
if (entry.isDirectory()) {
|
|
7803
|
-
count += await countFiles(
|
|
9405
|
+
count += await countFiles(path30.join(dir, entry.name));
|
|
7804
9406
|
} else {
|
|
7805
9407
|
count++;
|
|
7806
9408
|
}
|