kairn-cli 2.6.0 → 2.7.1
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 +1799 -165
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1409,68 +1409,1619 @@ 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
|
+
const knownKeys = /* @__PURE__ */ new Set(["name", "model", "disallowedTools"]);
|
|
1835
|
+
const extra = {};
|
|
1836
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
1837
|
+
if (!knownKeys.has(key)) {
|
|
1838
|
+
extra[key] = value;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (Object.keys(extra).length > 0) {
|
|
1842
|
+
node.extraFrontmatter = extra;
|
|
1843
|
+
}
|
|
1844
|
+
nodes.push(node);
|
|
1845
|
+
}
|
|
1846
|
+
return nodes;
|
|
1847
|
+
}
|
|
1848
|
+
async function parseSkills(harnessPath) {
|
|
1849
|
+
const dirPath = path21.join(harnessPath, "skills");
|
|
1850
|
+
const entries = await readDirSafe(dirPath);
|
|
1851
|
+
const nodes = [];
|
|
1852
|
+
for (const entry of entries) {
|
|
1853
|
+
const entryPath = path21.join(dirPath, entry);
|
|
1854
|
+
if (entry.endsWith(".md")) {
|
|
1855
|
+
const content = await readFileSafe2(entryPath);
|
|
1856
|
+
if (content === null) continue;
|
|
1857
|
+
const name = entry.replace(/\.md$/, "");
|
|
1858
|
+
nodes.push({ name, content });
|
|
1859
|
+
} else if (await isDirectory(entryPath)) {
|
|
1860
|
+
let content = await readFileSafe2(path21.join(entryPath, "skill.md"));
|
|
1861
|
+
if (content === null) {
|
|
1862
|
+
content = await readFileSafe2(path21.join(entryPath, "SKILL.md"));
|
|
1863
|
+
}
|
|
1864
|
+
if (content === null) continue;
|
|
1865
|
+
nodes.push({ name: entry, content });
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return nodes;
|
|
1869
|
+
}
|
|
1870
|
+
async function parseDocs(harnessPath) {
|
|
1871
|
+
const dirPath = path21.join(harnessPath, "docs");
|
|
1872
|
+
const entries = await readDirSafe(dirPath);
|
|
1873
|
+
const nodes = [];
|
|
1874
|
+
for (const entry of entries) {
|
|
1875
|
+
if (!entry.endsWith(".md")) continue;
|
|
1876
|
+
const filePath = path21.join(dirPath, entry);
|
|
1877
|
+
const content = await readFileSafe2(filePath);
|
|
1878
|
+
if (content === null) continue;
|
|
1879
|
+
const name = entry.replace(/\.md$/, "");
|
|
1880
|
+
nodes.push({ name, content });
|
|
1881
|
+
}
|
|
1882
|
+
return nodes;
|
|
1883
|
+
}
|
|
1884
|
+
async function parseHooks(harnessPath) {
|
|
1885
|
+
const dirPath = path21.join(harnessPath, "hooks");
|
|
1886
|
+
const entries = await readDirSafe(dirPath);
|
|
1887
|
+
const nodes = [];
|
|
1888
|
+
for (const entry of entries) {
|
|
1889
|
+
if (!entry.endsWith(".mjs")) continue;
|
|
1890
|
+
const filePath = path21.join(dirPath, entry);
|
|
1891
|
+
const content = await readFileSafe2(filePath);
|
|
1892
|
+
if (content === null) continue;
|
|
1893
|
+
const name = entry.replace(/\.mjs$/, "");
|
|
1894
|
+
nodes.push({ name, content, type: "command" });
|
|
1895
|
+
}
|
|
1896
|
+
return nodes;
|
|
1897
|
+
}
|
|
1898
|
+
async function parseHarness(harnessPath) {
|
|
1899
|
+
const ir = createEmptyIR();
|
|
1900
|
+
const claudeMdContent = await readFileSafe2(
|
|
1901
|
+
path21.join(harnessPath, "CLAUDE.md")
|
|
1902
|
+
);
|
|
1903
|
+
if (claudeMdContent !== null) {
|
|
1904
|
+
const { meta, sections } = parseClaudeMd(claudeMdContent);
|
|
1905
|
+
ir.meta = {
|
|
1906
|
+
...ir.meta,
|
|
1907
|
+
...meta,
|
|
1908
|
+
techStack: { ...ir.meta.techStack, ...meta.techStack }
|
|
1909
|
+
};
|
|
1910
|
+
ir.sections = sections;
|
|
1911
|
+
}
|
|
1912
|
+
const settingsContent = await readFileSafe2(
|
|
1913
|
+
path21.join(harnessPath, "settings.json")
|
|
1914
|
+
);
|
|
1915
|
+
if (settingsContent !== null) {
|
|
1916
|
+
ir.settings = parseSettings(settingsContent);
|
|
1917
|
+
}
|
|
1918
|
+
const [commands, rules, agents, skills, docs, hooks] = await Promise.all([
|
|
1919
|
+
parseCommands(harnessPath),
|
|
1920
|
+
parseRules(harnessPath),
|
|
1921
|
+
parseAgents(harnessPath),
|
|
1922
|
+
parseSkills(harnessPath),
|
|
1923
|
+
parseDocs(harnessPath),
|
|
1924
|
+
parseHooks(harnessPath)
|
|
1925
|
+
]);
|
|
1926
|
+
ir.commands = commands;
|
|
1927
|
+
ir.rules = rules;
|
|
1928
|
+
ir.agents = agents;
|
|
1929
|
+
ir.skills = skills;
|
|
1930
|
+
ir.docs = docs;
|
|
1931
|
+
ir.hooks = hooks;
|
|
1932
|
+
const mcpServers = [];
|
|
1933
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1934
|
+
const parentMcpPath = path21.join(path21.dirname(harnessPath), ".mcp.json");
|
|
1935
|
+
const parentMcpContent = await readFileSafe2(parentMcpPath);
|
|
1936
|
+
if (parentMcpContent !== null) {
|
|
1937
|
+
for (const node of parseMcpConfig(parentMcpContent)) {
|
|
1938
|
+
if (!seenIds.has(node.id)) {
|
|
1939
|
+
seenIds.add(node.id);
|
|
1940
|
+
mcpServers.push(node);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
const innerMcpPath = path21.join(harnessPath, ".mcp.json");
|
|
1945
|
+
const innerMcpContent = await readFileSafe2(innerMcpPath);
|
|
1946
|
+
if (innerMcpContent !== null) {
|
|
1947
|
+
for (const node of parseMcpConfig(innerMcpContent)) {
|
|
1948
|
+
if (!seenIds.has(node.id)) {
|
|
1949
|
+
seenIds.add(node.id);
|
|
1950
|
+
mcpServers.push(node);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
ir.mcpServers = mcpServers;
|
|
1955
|
+
return ir;
|
|
1956
|
+
}
|
|
1957
|
+
var SECTION_ID_MAP;
|
|
1958
|
+
var init_parser = __esm({
|
|
1959
|
+
"src/ir/parser.ts"() {
|
|
1960
|
+
"use strict";
|
|
1961
|
+
init_types();
|
|
1962
|
+
SECTION_ID_MAP = [
|
|
1963
|
+
{ pattern: /^(purpose|about|what)\b/i, id: "purpose" },
|
|
1964
|
+
{ pattern: /^(tech\s*stack|technology|stack)\b/i, id: "tech-stack" },
|
|
1965
|
+
{ pattern: /^(commands|key\s*commands)\b/i, id: "commands" },
|
|
1966
|
+
{ pattern: /^architecture\b/i, id: "architecture" },
|
|
1967
|
+
{ pattern: /^conventions?\b/i, id: "conventions" },
|
|
1968
|
+
{ pattern: /^verification\b/i, id: "verification" },
|
|
1969
|
+
{ pattern: /^(known\s*gotchas|gotchas)\b/i, id: "gotchas" },
|
|
1970
|
+
{ pattern: /^output\b/i, id: "output" },
|
|
1971
|
+
{ pattern: /^debugging\b/i, id: "debugging" },
|
|
1972
|
+
{ pattern: /^git\b/i, id: "git" }
|
|
1973
|
+
];
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// src/ir/translate.ts
|
|
1978
|
+
function extractName(filePath, pattern) {
|
|
1979
|
+
const match = filePath.match(pattern);
|
|
1980
|
+
return match ? match[1] : null;
|
|
1981
|
+
}
|
|
1982
|
+
function findSectionContaining(ir, text) {
|
|
1983
|
+
return ir.sections.find((s) => s.content.includes(text));
|
|
1984
|
+
}
|
|
1985
|
+
function translateClaudeMdMutation(mutation, ir) {
|
|
1986
|
+
switch (mutation.action) {
|
|
1987
|
+
case "replace": {
|
|
1988
|
+
if (mutation.oldText === void 0) {
|
|
1989
|
+
return buildRawText(mutation);
|
|
1990
|
+
}
|
|
1991
|
+
const section = findSectionContaining(ir, mutation.oldText);
|
|
1992
|
+
if (!section) {
|
|
1993
|
+
return buildRawText(mutation);
|
|
1994
|
+
}
|
|
1995
|
+
return {
|
|
1996
|
+
type: "update_section",
|
|
1997
|
+
sectionId: section.id,
|
|
1998
|
+
content: section.content.replace(mutation.oldText, mutation.newText),
|
|
1999
|
+
rationale: mutation.rationale
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
case "add_section": {
|
|
2003
|
+
const headingMatch = mutation.newText.match(/^## (.+)/);
|
|
2004
|
+
if (!headingMatch) {
|
|
2005
|
+
return buildRawText(mutation);
|
|
2006
|
+
}
|
|
2007
|
+
const headingText = headingMatch[1].trim();
|
|
2008
|
+
const sectionId = resolveSectionId(headingText);
|
|
2009
|
+
const heading = `## ${headingText}`;
|
|
2010
|
+
const newlineIdx = mutation.newText.indexOf("\n");
|
|
2011
|
+
const content = newlineIdx >= 0 ? mutation.newText.slice(newlineIdx + 1).trim() : "";
|
|
2012
|
+
const nextOrder = ir.sections.length;
|
|
2013
|
+
return {
|
|
2014
|
+
type: "add_section",
|
|
2015
|
+
section: createSection(sectionId, heading, content, nextOrder),
|
|
2016
|
+
rationale: mutation.rationale
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
case "delete_section": {
|
|
2020
|
+
if (mutation.oldText === void 0) {
|
|
2021
|
+
return buildRawText(mutation);
|
|
2022
|
+
}
|
|
2023
|
+
const section = findSectionContaining(ir, mutation.oldText);
|
|
2024
|
+
if (!section) {
|
|
2025
|
+
return buildRawText(mutation);
|
|
2026
|
+
}
|
|
2027
|
+
return {
|
|
2028
|
+
type: "remove_section",
|
|
2029
|
+
sectionId: section.id,
|
|
2030
|
+
rationale: mutation.rationale
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
default:
|
|
2034
|
+
return buildRawText(mutation);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
function translateCommandMutation(mutation, name) {
|
|
2038
|
+
switch (mutation.action) {
|
|
2039
|
+
case "create_file":
|
|
2040
|
+
return {
|
|
2041
|
+
type: "add_command",
|
|
2042
|
+
command: createCommandNode(name, mutation.newText),
|
|
2043
|
+
rationale: mutation.rationale
|
|
2044
|
+
};
|
|
2045
|
+
case "delete_file":
|
|
2046
|
+
return {
|
|
2047
|
+
type: "remove_command",
|
|
2048
|
+
name,
|
|
2049
|
+
rationale: mutation.rationale
|
|
2050
|
+
};
|
|
2051
|
+
case "replace":
|
|
2052
|
+
return {
|
|
2053
|
+
type: "update_command",
|
|
2054
|
+
name,
|
|
2055
|
+
content: mutation.newText,
|
|
2056
|
+
rationale: mutation.rationale
|
|
2057
|
+
};
|
|
2058
|
+
default:
|
|
2059
|
+
return buildRawText(mutation);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
function translateRuleMutation(mutation, name) {
|
|
2063
|
+
switch (mutation.action) {
|
|
2064
|
+
case "create_file":
|
|
2065
|
+
return {
|
|
2066
|
+
type: "add_rule",
|
|
2067
|
+
rule: createRuleNode(name, mutation.newText),
|
|
2068
|
+
rationale: mutation.rationale
|
|
2069
|
+
};
|
|
2070
|
+
case "delete_file":
|
|
2071
|
+
return {
|
|
2072
|
+
type: "remove_rule",
|
|
2073
|
+
name,
|
|
2074
|
+
rationale: mutation.rationale
|
|
2075
|
+
};
|
|
2076
|
+
case "replace":
|
|
2077
|
+
return {
|
|
2078
|
+
type: "update_rule",
|
|
2079
|
+
name,
|
|
2080
|
+
content: mutation.newText,
|
|
2081
|
+
rationale: mutation.rationale
|
|
2082
|
+
};
|
|
2083
|
+
default:
|
|
2084
|
+
return buildRawText(mutation);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
function translateAgentMutation(mutation, name) {
|
|
2088
|
+
switch (mutation.action) {
|
|
2089
|
+
case "create_file":
|
|
2090
|
+
return {
|
|
2091
|
+
type: "add_agent",
|
|
2092
|
+
agent: createAgentNode(name, mutation.newText),
|
|
2093
|
+
rationale: mutation.rationale
|
|
2094
|
+
};
|
|
2095
|
+
case "delete_file":
|
|
2096
|
+
return {
|
|
2097
|
+
type: "remove_agent",
|
|
2098
|
+
name,
|
|
2099
|
+
rationale: mutation.rationale
|
|
2100
|
+
};
|
|
2101
|
+
case "replace":
|
|
2102
|
+
return {
|
|
2103
|
+
type: "update_agent",
|
|
2104
|
+
name,
|
|
2105
|
+
changes: { content: mutation.newText },
|
|
2106
|
+
rationale: mutation.rationale
|
|
2107
|
+
};
|
|
2108
|
+
default:
|
|
2109
|
+
return buildRawText(mutation);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function buildRawText(mutation) {
|
|
2113
|
+
return {
|
|
2114
|
+
type: "raw_text",
|
|
2115
|
+
file: mutation.file,
|
|
2116
|
+
action: mutation.action,
|
|
2117
|
+
oldText: mutation.oldText,
|
|
2118
|
+
newText: mutation.newText,
|
|
2119
|
+
rationale: mutation.rationale
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
function translateMutation(mutation, ir) {
|
|
2123
|
+
if (mutation.file === "CLAUDE.md") {
|
|
2124
|
+
return translateClaudeMdMutation(mutation, ir);
|
|
2125
|
+
}
|
|
2126
|
+
const commandName = extractName(mutation.file, COMMANDS_PATH_RE);
|
|
2127
|
+
if (commandName !== null) {
|
|
2128
|
+
return translateCommandMutation(mutation, commandName);
|
|
2129
|
+
}
|
|
2130
|
+
const ruleName = extractName(mutation.file, RULES_PATH_RE);
|
|
2131
|
+
if (ruleName !== null) {
|
|
2132
|
+
return translateRuleMutation(mutation, ruleName);
|
|
2133
|
+
}
|
|
2134
|
+
const agentName = extractName(mutation.file, AGENTS_PATH_RE);
|
|
2135
|
+
if (agentName !== null) {
|
|
2136
|
+
return translateAgentMutation(mutation, agentName);
|
|
2137
|
+
}
|
|
2138
|
+
return buildRawText(mutation);
|
|
2139
|
+
}
|
|
2140
|
+
function translateMutations(mutations, ir) {
|
|
2141
|
+
return mutations.map((m) => translateMutation(m, ir));
|
|
2142
|
+
}
|
|
2143
|
+
var COMMANDS_PATH_RE, RULES_PATH_RE, AGENTS_PATH_RE;
|
|
2144
|
+
var init_translate = __esm({
|
|
2145
|
+
"src/ir/translate.ts"() {
|
|
2146
|
+
"use strict";
|
|
2147
|
+
init_types();
|
|
2148
|
+
init_parser();
|
|
2149
|
+
COMMANDS_PATH_RE = /^commands\/([^/]+?)(?:\.md)?$/;
|
|
2150
|
+
RULES_PATH_RE = /^rules\/([^/]+?)(?:\.md)?$/;
|
|
2151
|
+
AGENTS_PATH_RE = /^agents\/([^/]+?)(?:\.md)?$/;
|
|
2152
|
+
}
|
|
2153
|
+
});
|
|
2154
|
+
|
|
2155
|
+
// src/ir/mutations.ts
|
|
2156
|
+
function sectionExists(ir, id) {
|
|
2157
|
+
return ir.sections.some((s) => s.id === id);
|
|
2158
|
+
}
|
|
2159
|
+
function commandExists(ir, name) {
|
|
2160
|
+
return ir.commands.some((c) => c.name === name);
|
|
2161
|
+
}
|
|
2162
|
+
function ruleExists(ir, name) {
|
|
2163
|
+
return ir.rules.some((r) => r.name === name);
|
|
2164
|
+
}
|
|
2165
|
+
function agentExists(ir, name) {
|
|
2166
|
+
return ir.agents.some((a) => a.name === name);
|
|
2167
|
+
}
|
|
2168
|
+
function mcpServerExists(ir, id) {
|
|
2169
|
+
return ir.mcpServers.some((s) => s.id === id);
|
|
2170
|
+
}
|
|
2171
|
+
function deepSet(obj, segments, value) {
|
|
2172
|
+
if (segments.length === 0) return obj;
|
|
2173
|
+
const [head, ...rest] = segments;
|
|
2174
|
+
if (rest.length === 0) {
|
|
2175
|
+
return { ...obj, [head]: value };
|
|
2176
|
+
}
|
|
2177
|
+
const existing = obj[head];
|
|
2178
|
+
const child = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
2179
|
+
return { ...obj, [head]: deepSet(child, rest, value) };
|
|
2180
|
+
}
|
|
2181
|
+
function applySettingsUpdate(settings, path31, value) {
|
|
2182
|
+
const segments = path31.split(".");
|
|
2183
|
+
const topKey = segments[0];
|
|
2184
|
+
if (STRUCTURED_SETTINGS_KEYS.has(topKey)) {
|
|
2185
|
+
const settingsRecord = { ...settings };
|
|
2186
|
+
const updated = deepSet(settingsRecord, segments, value);
|
|
2187
|
+
return {
|
|
2188
|
+
...settings,
|
|
2189
|
+
...updated,
|
|
2190
|
+
// Preserve raw as-is (deepSet may have overwritten it if path happened to be "raw")
|
|
2191
|
+
raw: settings.raw
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
const updatedRaw = deepSet({ ...settings.raw }, segments, value);
|
|
2195
|
+
return {
|
|
2196
|
+
...settings,
|
|
2197
|
+
raw: updatedRaw
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
function applyIRMutation(ir, mutation) {
|
|
2201
|
+
switch (mutation.type) {
|
|
2202
|
+
// -- Sections ----------------------------------------------------------
|
|
2203
|
+
case "update_section": {
|
|
2204
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2205
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2206
|
+
}
|
|
2207
|
+
return {
|
|
2208
|
+
...ir,
|
|
2209
|
+
sections: ir.sections.map(
|
|
2210
|
+
(s) => s.id === mutation.sectionId ? { ...s, content: mutation.content } : s
|
|
2211
|
+
)
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
case "add_section": {
|
|
2215
|
+
if (sectionExists(ir, mutation.section.id)) {
|
|
2216
|
+
throw new Error(`Section '${mutation.section.id}' already exists`);
|
|
2217
|
+
}
|
|
2218
|
+
return {
|
|
2219
|
+
...ir,
|
|
2220
|
+
sections: [...ir.sections, { ...mutation.section }]
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
case "remove_section": {
|
|
2224
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2225
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2226
|
+
}
|
|
2227
|
+
return {
|
|
2228
|
+
...ir,
|
|
2229
|
+
sections: ir.sections.filter((s) => s.id !== mutation.sectionId)
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
case "reorder_section": {
|
|
2233
|
+
if (!sectionExists(ir, mutation.sectionId)) {
|
|
2234
|
+
throw new Error(`Section '${mutation.sectionId}' not found`);
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
...ir,
|
|
2238
|
+
sections: ir.sections.map(
|
|
2239
|
+
(s) => s.id === mutation.sectionId ? { ...s, order: mutation.newOrder } : s
|
|
2240
|
+
)
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
// -- Commands ----------------------------------------------------------
|
|
2244
|
+
case "add_command": {
|
|
2245
|
+
if (commandExists(ir, mutation.command.name)) {
|
|
2246
|
+
throw new Error(`Command '${mutation.command.name}' already exists`);
|
|
2247
|
+
}
|
|
2248
|
+
return {
|
|
2249
|
+
...ir,
|
|
2250
|
+
commands: [...ir.commands, { ...mutation.command }]
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
case "update_command": {
|
|
2254
|
+
if (!commandExists(ir, mutation.name)) {
|
|
2255
|
+
throw new Error(`Command '${mutation.name}' not found`);
|
|
2256
|
+
}
|
|
2257
|
+
return {
|
|
2258
|
+
...ir,
|
|
2259
|
+
commands: ir.commands.map(
|
|
2260
|
+
(c) => c.name === mutation.name ? { ...c, content: mutation.content } : c
|
|
2261
|
+
)
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
case "remove_command": {
|
|
2265
|
+
if (!commandExists(ir, mutation.name)) {
|
|
2266
|
+
throw new Error(`Command '${mutation.name}' not found`);
|
|
2267
|
+
}
|
|
2268
|
+
return {
|
|
2269
|
+
...ir,
|
|
2270
|
+
commands: ir.commands.filter((c) => c.name !== mutation.name)
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
// -- Rules -------------------------------------------------------------
|
|
2274
|
+
case "add_rule": {
|
|
2275
|
+
if (ruleExists(ir, mutation.rule.name)) {
|
|
2276
|
+
throw new Error(`Rule '${mutation.rule.name}' already exists`);
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
...ir,
|
|
2280
|
+
rules: [...ir.rules, { ...mutation.rule }]
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
case "update_rule": {
|
|
2284
|
+
if (!ruleExists(ir, mutation.name)) {
|
|
2285
|
+
throw new Error(`Rule '${mutation.name}' not found`);
|
|
2286
|
+
}
|
|
2287
|
+
return {
|
|
2288
|
+
...ir,
|
|
2289
|
+
rules: ir.rules.map(
|
|
2290
|
+
(r) => r.name === mutation.name ? { ...r, content: mutation.content } : r
|
|
2291
|
+
)
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
case "remove_rule": {
|
|
2295
|
+
if (!ruleExists(ir, mutation.name)) {
|
|
2296
|
+
throw new Error(`Rule '${mutation.name}' not found`);
|
|
2297
|
+
}
|
|
2298
|
+
return {
|
|
2299
|
+
...ir,
|
|
2300
|
+
rules: ir.rules.filter((r) => r.name !== mutation.name)
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
// -- Agents ------------------------------------------------------------
|
|
2304
|
+
case "add_agent": {
|
|
2305
|
+
if (agentExists(ir, mutation.agent.name)) {
|
|
2306
|
+
throw new Error(`Agent '${mutation.agent.name}' already exists`);
|
|
2307
|
+
}
|
|
2308
|
+
return {
|
|
2309
|
+
...ir,
|
|
2310
|
+
agents: [...ir.agents, { ...mutation.agent }]
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
case "update_agent": {
|
|
2314
|
+
if (!agentExists(ir, mutation.name)) {
|
|
2315
|
+
throw new Error(`Agent '${mutation.name}' not found`);
|
|
2316
|
+
}
|
|
2317
|
+
return {
|
|
2318
|
+
...ir,
|
|
2319
|
+
agents: ir.agents.map(
|
|
2320
|
+
(a) => a.name === mutation.name ? { ...a, ...mutation.changes } : a
|
|
2321
|
+
)
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
case "remove_agent": {
|
|
2325
|
+
if (!agentExists(ir, mutation.name)) {
|
|
2326
|
+
throw new Error(`Agent '${mutation.name}' not found`);
|
|
2327
|
+
}
|
|
2328
|
+
return {
|
|
2329
|
+
...ir,
|
|
2330
|
+
agents: ir.agents.filter((a) => a.name !== mutation.name)
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
// -- MCP Servers -------------------------------------------------------
|
|
2334
|
+
case "add_mcp_server": {
|
|
2335
|
+
if (mcpServerExists(ir, mutation.server.id)) {
|
|
2336
|
+
throw new Error(`MCP server '${mutation.server.id}' already exists`);
|
|
2337
|
+
}
|
|
2338
|
+
return {
|
|
2339
|
+
...ir,
|
|
2340
|
+
mcpServers: [...ir.mcpServers, { ...mutation.server }]
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
case "remove_mcp_server": {
|
|
2344
|
+
if (!mcpServerExists(ir, mutation.id)) {
|
|
2345
|
+
throw new Error(`MCP server '${mutation.id}' not found`);
|
|
2346
|
+
}
|
|
2347
|
+
return {
|
|
2348
|
+
...ir,
|
|
2349
|
+
mcpServers: ir.mcpServers.filter((s) => s.id !== mutation.id)
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
// -- Settings ----------------------------------------------------------
|
|
2353
|
+
case "update_settings": {
|
|
2354
|
+
return {
|
|
2355
|
+
...ir,
|
|
2356
|
+
settings: applySettingsUpdate(ir.settings, mutation.path, mutation.value)
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
// -- Raw text (legacy fallback) ----------------------------------------
|
|
2360
|
+
case "raw_text": {
|
|
2361
|
+
console.warn(
|
|
2362
|
+
"raw_text mutation is a legacy fallback \u2014 the text operation will be applied during rendering"
|
|
2363
|
+
);
|
|
2364
|
+
return { ...ir };
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
var STRUCTURED_SETTINGS_KEYS;
|
|
2369
|
+
var init_mutations = __esm({
|
|
2370
|
+
"src/ir/mutations.ts"() {
|
|
2371
|
+
"use strict";
|
|
2372
|
+
STRUCTURED_SETTINGS_KEYS = /* @__PURE__ */ new Set(["statusLine", "hooks", "denyPatterns"]);
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
|
|
2376
|
+
// src/ir/renderer.ts
|
|
2377
|
+
function renderClaudeMd(_meta, sections) {
|
|
2378
|
+
const sorted = [...sections].sort((a, b) => a.order - b.order);
|
|
2379
|
+
const blocks = [];
|
|
2380
|
+
for (const section of sorted) {
|
|
2381
|
+
if (section.heading && section.content) {
|
|
2382
|
+
blocks.push(`${section.heading}
|
|
2383
|
+
|
|
2384
|
+
${section.content}`);
|
|
2385
|
+
} else if (section.heading) {
|
|
2386
|
+
blocks.push(section.heading);
|
|
2387
|
+
} else if (section.content) {
|
|
2388
|
+
blocks.push(section.content);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (blocks.length === 0) {
|
|
2392
|
+
return "\n";
|
|
2393
|
+
}
|
|
2394
|
+
return blocks.join("\n\n") + "\n";
|
|
2395
|
+
}
|
|
2396
|
+
function renderSettings(settings) {
|
|
2397
|
+
const result = JSON.parse(
|
|
2398
|
+
JSON.stringify(settings.raw)
|
|
2399
|
+
);
|
|
2400
|
+
if (settings.denyPatterns && settings.denyPatterns.length > 0) {
|
|
2401
|
+
const permissions = result["permissions"] ?? {};
|
|
2402
|
+
permissions["deny"] = settings.denyPatterns;
|
|
2403
|
+
result["permissions"] = permissions;
|
|
2404
|
+
}
|
|
2405
|
+
if (settings.statusLine) {
|
|
2406
|
+
result["statusLine"] = settings.statusLine;
|
|
2407
|
+
}
|
|
2408
|
+
const hookEvents = [
|
|
2409
|
+
"PreToolUse",
|
|
2410
|
+
"PostToolUse",
|
|
2411
|
+
"UserPromptSubmit",
|
|
2412
|
+
"SessionStart",
|
|
2413
|
+
"PostCompact"
|
|
2414
|
+
];
|
|
2415
|
+
const hooksObj = {};
|
|
2416
|
+
let hasHooks = false;
|
|
2417
|
+
for (const event of hookEvents) {
|
|
2418
|
+
const entries = settings.hooks[event];
|
|
2419
|
+
if (entries && entries.length > 0) {
|
|
2420
|
+
hooksObj[event] = entries;
|
|
2421
|
+
hasHooks = true;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
if (hasHooks) {
|
|
2425
|
+
result["hooks"] = hooksObj;
|
|
2426
|
+
}
|
|
2427
|
+
return JSON.stringify(result, null, 2) + "\n";
|
|
2428
|
+
}
|
|
2429
|
+
function renderMcpConfig(servers) {
|
|
2430
|
+
if (servers.length === 0) {
|
|
2431
|
+
return "";
|
|
2432
|
+
}
|
|
2433
|
+
const mcpServers = {};
|
|
2434
|
+
for (const server of servers) {
|
|
2435
|
+
const entry = {
|
|
2436
|
+
command: server.command,
|
|
2437
|
+
args: server.args
|
|
2438
|
+
};
|
|
2439
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
2440
|
+
entry["env"] = server.env;
|
|
2441
|
+
}
|
|
2442
|
+
mcpServers[server.id] = entry;
|
|
2443
|
+
}
|
|
2444
|
+
return JSON.stringify({ mcpServers }, null, 2) + "\n";
|
|
2445
|
+
}
|
|
2446
|
+
function renderRuleWithFrontmatter(rule) {
|
|
2447
|
+
if (!rule.paths || rule.paths.length === 0) {
|
|
2448
|
+
return rule.content;
|
|
2449
|
+
}
|
|
2450
|
+
const yamlLines = ["---", "paths:"];
|
|
2451
|
+
for (const p of rule.paths) {
|
|
2452
|
+
yamlLines.push(` - ${p}`);
|
|
2453
|
+
}
|
|
2454
|
+
yamlLines.push("---");
|
|
2455
|
+
return yamlLines.join("\n") + "\n\n" + rule.content;
|
|
2456
|
+
}
|
|
2457
|
+
function renderAgentWithFrontmatter(agent) {
|
|
2458
|
+
const hasModel = agent.model !== void 0;
|
|
2459
|
+
const hasDisallowed = agent.disallowedTools !== void 0 && agent.disallowedTools.length > 0;
|
|
2460
|
+
const hasExtra = agent.extraFrontmatter !== void 0 && Object.keys(agent.extraFrontmatter).length > 0;
|
|
2461
|
+
if (!hasModel && !hasDisallowed && !hasExtra) {
|
|
2462
|
+
return agent.content;
|
|
2463
|
+
}
|
|
2464
|
+
const yamlLines = ["---"];
|
|
2465
|
+
if (hasModel) {
|
|
2466
|
+
yamlLines.push(`model: ${agent.model}`);
|
|
2467
|
+
}
|
|
2468
|
+
if (hasDisallowed) {
|
|
2469
|
+
yamlLines.push("disallowedTools:");
|
|
2470
|
+
for (const tool of agent.disallowedTools) {
|
|
2471
|
+
yamlLines.push(` - ${tool}`);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
if (hasExtra) {
|
|
2475
|
+
for (const [key, value] of Object.entries(agent.extraFrontmatter)) {
|
|
2476
|
+
if (Array.isArray(value)) {
|
|
2477
|
+
yamlLines.push(`${key}:`);
|
|
2478
|
+
for (const item of value) {
|
|
2479
|
+
yamlLines.push(` - ${String(item)}`);
|
|
2480
|
+
}
|
|
2481
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2482
|
+
yamlLines.push(`${key}:`);
|
|
2483
|
+
for (const [subKey, subVal] of Object.entries(value)) {
|
|
2484
|
+
yamlLines.push(` ${subKey}: ${String(subVal)}`);
|
|
2485
|
+
}
|
|
2486
|
+
} else {
|
|
2487
|
+
yamlLines.push(`${key}: ${String(value)}`);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
yamlLines.push("---");
|
|
2492
|
+
return yamlLines.join("\n") + "\n\n" + agent.content;
|
|
2493
|
+
}
|
|
2494
|
+
function settingsHasContent(settings) {
|
|
2495
|
+
if (settings.statusLine) return true;
|
|
2496
|
+
if (settings.denyPatterns && settings.denyPatterns.length > 0) return true;
|
|
2497
|
+
if (Object.keys(settings.raw).length > 0) return true;
|
|
2498
|
+
const hookEvents = [
|
|
2499
|
+
"PreToolUse",
|
|
2500
|
+
"PostToolUse",
|
|
2501
|
+
"UserPromptSubmit",
|
|
2502
|
+
"SessionStart",
|
|
2503
|
+
"PostCompact"
|
|
2504
|
+
];
|
|
2505
|
+
for (const event of hookEvents) {
|
|
2506
|
+
const entries = settings.hooks[event];
|
|
2507
|
+
if (entries && entries.length > 0) return true;
|
|
2508
|
+
}
|
|
2509
|
+
return false;
|
|
2510
|
+
}
|
|
2511
|
+
function renderHarness(ir) {
|
|
2512
|
+
const files = /* @__PURE__ */ new Map();
|
|
2513
|
+
if (ir.sections.length > 0 || ir.meta.name) {
|
|
2514
|
+
files.set("CLAUDE.md", renderClaudeMd(ir.meta, ir.sections));
|
|
2515
|
+
}
|
|
2516
|
+
if (settingsHasContent(ir.settings)) {
|
|
2517
|
+
files.set("settings.json", renderSettings(ir.settings));
|
|
2518
|
+
}
|
|
2519
|
+
for (const cmd of ir.commands) {
|
|
2520
|
+
files.set(`commands/${cmd.name}.md`, cmd.content);
|
|
2521
|
+
}
|
|
2522
|
+
for (const rule of ir.rules) {
|
|
2523
|
+
files.set(`rules/${rule.name}.md`, renderRuleWithFrontmatter(rule));
|
|
2524
|
+
}
|
|
2525
|
+
for (const agent of ir.agents) {
|
|
2526
|
+
files.set(`agents/${agent.name}.md`, renderAgentWithFrontmatter(agent));
|
|
2527
|
+
}
|
|
2528
|
+
for (const skill of ir.skills) {
|
|
2529
|
+
files.set(`skills/${skill.name}.md`, skill.content);
|
|
2530
|
+
}
|
|
2531
|
+
for (const doc of ir.docs) {
|
|
2532
|
+
files.set(`docs/${doc.name}.md`, doc.content);
|
|
2533
|
+
}
|
|
2534
|
+
for (const hook of ir.hooks) {
|
|
2535
|
+
files.set(`hooks/${hook.name}.mjs`, hook.content);
|
|
2536
|
+
}
|
|
2537
|
+
const mcpContent = renderMcpConfig(ir.mcpServers);
|
|
2538
|
+
if (mcpContent) {
|
|
2539
|
+
files.set(".mcp.json", mcpContent);
|
|
2540
|
+
}
|
|
2541
|
+
return files;
|
|
2542
|
+
}
|
|
2543
|
+
var init_renderer = __esm({
|
|
2544
|
+
"src/ir/renderer.ts"() {
|
|
2545
|
+
"use strict";
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
// src/ir/diff.ts
|
|
2550
|
+
function diffIR(before, after) {
|
|
2551
|
+
const diff = createEmptyDiff();
|
|
2552
|
+
diffSections(before.sections, after.sections, diff);
|
|
2553
|
+
diffByName(before.commands, after.commands, diff.commands);
|
|
2554
|
+
diffByName(before.rules, after.rules, diff.rules);
|
|
2555
|
+
diffAgents(before.agents, after.agents, diff);
|
|
2556
|
+
diffMcpServers(before.mcpServers, after.mcpServers, diff);
|
|
2557
|
+
diffSettings(before.settings, after.settings, diff);
|
|
2558
|
+
return diff;
|
|
2559
|
+
}
|
|
2560
|
+
function formatIRDiff(diff) {
|
|
2561
|
+
const blocks = [];
|
|
2562
|
+
const sectionLines = formatSectionBlock(diff);
|
|
2563
|
+
if (sectionLines.length > 0) {
|
|
2564
|
+
blocks.push(["Sections:", ...sectionLines].join("\n"));
|
|
2565
|
+
}
|
|
2566
|
+
const commandLines = formatNamedBlock(diff.commands, "commands");
|
|
2567
|
+
if (commandLines.length > 0) {
|
|
2568
|
+
blocks.push(["Commands:", ...commandLines].join("\n"));
|
|
2569
|
+
}
|
|
2570
|
+
const ruleLines = formatNamedBlock(diff.rules, "rules");
|
|
2571
|
+
if (ruleLines.length > 0) {
|
|
2572
|
+
blocks.push(["Rules:", ...ruleLines].join("\n"));
|
|
2573
|
+
}
|
|
2574
|
+
const agentLines = formatAgentBlock(diff);
|
|
2575
|
+
if (agentLines.length > 0) {
|
|
2576
|
+
blocks.push(["Agents:", ...agentLines].join("\n"));
|
|
2577
|
+
}
|
|
2578
|
+
const mcpLines = formatMcpBlock(diff);
|
|
2579
|
+
if (mcpLines.length > 0) {
|
|
2580
|
+
blocks.push(["MCP Servers:", ...mcpLines].join("\n"));
|
|
2581
|
+
}
|
|
2582
|
+
const settingsLines = formatSettingsBlock(diff);
|
|
2583
|
+
if (settingsLines.length > 0) {
|
|
2584
|
+
blocks.push(["Settings:", ...settingsLines].join("\n"));
|
|
2585
|
+
}
|
|
2586
|
+
if (blocks.length === 0) {
|
|
2587
|
+
return "No changes.";
|
|
2588
|
+
}
|
|
2589
|
+
return blocks.join("\n\n");
|
|
2590
|
+
}
|
|
2591
|
+
function diffSections(beforeList, afterList, diff) {
|
|
2592
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2593
|
+
for (const s of beforeList) {
|
|
2594
|
+
beforeMap.set(s.id, s);
|
|
2595
|
+
}
|
|
2596
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2597
|
+
for (const s of afterList) {
|
|
2598
|
+
afterMap.set(s.id, s);
|
|
2599
|
+
}
|
|
2600
|
+
for (const s of afterList) {
|
|
2601
|
+
if (!beforeMap.has(s.id)) {
|
|
2602
|
+
diff.sections.added.push(s);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
for (const s of beforeList) {
|
|
2606
|
+
if (!afterMap.has(s.id)) {
|
|
2607
|
+
diff.sections.removed.push(s);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
for (const [id, afterSection] of afterMap) {
|
|
2611
|
+
const beforeSection = beforeMap.get(id);
|
|
2612
|
+
if (beforeSection === void 0) continue;
|
|
2613
|
+
if (beforeSection.content !== afterSection.content) {
|
|
2614
|
+
diff.sections.modified.push({
|
|
2615
|
+
id,
|
|
2616
|
+
before: beforeSection.content,
|
|
2617
|
+
after: afterSection.content
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
if (beforeSection.order !== afterSection.order) {
|
|
2621
|
+
diff.sections.reordered.push({
|
|
2622
|
+
id,
|
|
2623
|
+
oldOrder: beforeSection.order,
|
|
2624
|
+
newOrder: afterSection.order
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
function diffByName(beforeList, afterList, target) {
|
|
2630
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2631
|
+
for (const n of beforeList) {
|
|
2632
|
+
beforeMap.set(n.name, n);
|
|
2633
|
+
}
|
|
2634
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2635
|
+
for (const n of afterList) {
|
|
2636
|
+
afterMap.set(n.name, n);
|
|
2637
|
+
}
|
|
2638
|
+
for (const n of afterList) {
|
|
2639
|
+
if (!beforeMap.has(n.name)) {
|
|
2640
|
+
target.added.push(n);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
for (const n of beforeList) {
|
|
2644
|
+
if (!afterMap.has(n.name)) {
|
|
2645
|
+
target.removed.push(n.name);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
for (const [name, afterNode] of afterMap) {
|
|
2649
|
+
const beforeNode = beforeMap.get(name);
|
|
2650
|
+
if (beforeNode === void 0) continue;
|
|
2651
|
+
if (beforeNode.content !== afterNode.content) {
|
|
2652
|
+
target.modified.push({
|
|
2653
|
+
name,
|
|
2654
|
+
before: beforeNode.content,
|
|
2655
|
+
after: afterNode.content
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function diffAgents(beforeList, afterList, diff) {
|
|
2661
|
+
const beforeMap = /* @__PURE__ */ new Map();
|
|
2662
|
+
for (const a of beforeList) {
|
|
2663
|
+
beforeMap.set(a.name, a);
|
|
2664
|
+
}
|
|
2665
|
+
const afterMap = /* @__PURE__ */ new Map();
|
|
2666
|
+
for (const a of afterList) {
|
|
2667
|
+
afterMap.set(a.name, a);
|
|
2668
|
+
}
|
|
2669
|
+
for (const a of afterList) {
|
|
2670
|
+
if (!beforeMap.has(a.name)) {
|
|
2671
|
+
diff.agents.added.push(a);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
for (const a of beforeList) {
|
|
2675
|
+
if (!afterMap.has(a.name)) {
|
|
2676
|
+
diff.agents.removed.push(a.name);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
for (const [name, afterAgent] of afterMap) {
|
|
2680
|
+
const beforeAgent = beforeMap.get(name);
|
|
2681
|
+
if (beforeAgent === void 0) continue;
|
|
2682
|
+
const changeParts = [];
|
|
2683
|
+
if (beforeAgent.model !== afterAgent.model) {
|
|
2684
|
+
const from = beforeAgent.model ?? "none";
|
|
2685
|
+
const to = afterAgent.model ?? "none";
|
|
2686
|
+
changeParts.push(`model changed from ${from} to ${to}`);
|
|
2687
|
+
}
|
|
2688
|
+
if (beforeAgent.content !== afterAgent.content) {
|
|
2689
|
+
changeParts.push("content updated");
|
|
2690
|
+
}
|
|
2691
|
+
const beforeTools = JSON.stringify(beforeAgent.disallowedTools ?? []);
|
|
2692
|
+
const afterTools = JSON.stringify(afterAgent.disallowedTools ?? []);
|
|
2693
|
+
if (beforeTools !== afterTools) {
|
|
2694
|
+
changeParts.push("disallowedTools changed");
|
|
2695
|
+
}
|
|
2696
|
+
if (changeParts.length > 0) {
|
|
2697
|
+
diff.agents.modified.push({
|
|
2698
|
+
name,
|
|
2699
|
+
changes: changeParts.join("; ")
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
function diffMcpServers(beforeList, afterList, diff) {
|
|
2705
|
+
const beforeIds = new Set(beforeList.map((s) => s.id));
|
|
2706
|
+
const afterIds = new Set(afterList.map((s) => s.id));
|
|
2707
|
+
for (const s of afterList) {
|
|
2708
|
+
if (!beforeIds.has(s.id)) {
|
|
2709
|
+
diff.mcpServers.added.push(s);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
for (const s of beforeList) {
|
|
2713
|
+
if (!afterIds.has(s.id)) {
|
|
2714
|
+
diff.mcpServers.removed.push(s.id);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
function diffSettings(before, after, diff) {
|
|
2719
|
+
if (!deepEqual(before.statusLine, after.statusLine)) {
|
|
2720
|
+
diff.settings.changes.push({
|
|
2721
|
+
path: "statusLine",
|
|
2722
|
+
before: before.statusLine,
|
|
2723
|
+
after: after.statusLine
|
|
2724
|
+
});
|
|
2725
|
+
}
|
|
2726
|
+
if (!deepEqual(before.denyPatterns, after.denyPatterns)) {
|
|
2727
|
+
diff.settings.changes.push({
|
|
2728
|
+
path: "denyPatterns",
|
|
2729
|
+
before: before.denyPatterns,
|
|
2730
|
+
after: after.denyPatterns
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
if (!deepEqual(before.hooks, after.hooks)) {
|
|
2734
|
+
diff.settings.changes.push({
|
|
2735
|
+
path: "hooks",
|
|
2736
|
+
before: before.hooks,
|
|
2737
|
+
after: after.hooks
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
function formatSectionBlock(diff) {
|
|
2742
|
+
const lines = [];
|
|
2743
|
+
for (const s of diff.sections.added) {
|
|
2744
|
+
lines.push(` + Added: ${s.heading}`);
|
|
2745
|
+
}
|
|
2746
|
+
for (const s of diff.sections.removed) {
|
|
2747
|
+
lines.push(` - Removed: ${s.heading}`);
|
|
2748
|
+
}
|
|
2749
|
+
for (const m of diff.sections.modified) {
|
|
2750
|
+
lines.push(` ~ Modified: ${m.id} (content changed)`);
|
|
2751
|
+
}
|
|
2752
|
+
for (const r of diff.sections.reordered) {
|
|
2753
|
+
lines.push(
|
|
2754
|
+
` \u2195 Reordered: ${r.id} (${r.oldOrder} \u2192 ${r.newOrder})`
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
return lines;
|
|
2758
|
+
}
|
|
2759
|
+
function formatNamedBlock(bucket, _category) {
|
|
2760
|
+
const lines = [];
|
|
2761
|
+
for (const n of bucket.added) {
|
|
2762
|
+
lines.push(` + Added: ${n.name}`);
|
|
2763
|
+
}
|
|
2764
|
+
for (const name of bucket.removed) {
|
|
2765
|
+
lines.push(` - Removed: ${name}`);
|
|
2766
|
+
}
|
|
2767
|
+
for (const m of bucket.modified) {
|
|
2768
|
+
lines.push(` ~ Modified: ${m.name} (content changed)`);
|
|
2769
|
+
}
|
|
2770
|
+
return lines;
|
|
2771
|
+
}
|
|
2772
|
+
function formatAgentBlock(diff) {
|
|
2773
|
+
const lines = [];
|
|
2774
|
+
for (const a of diff.agents.added) {
|
|
2775
|
+
lines.push(` + Added: ${a.name}`);
|
|
2776
|
+
}
|
|
2777
|
+
for (const name of diff.agents.removed) {
|
|
2778
|
+
lines.push(` - Removed: ${name}`);
|
|
2779
|
+
}
|
|
2780
|
+
for (const m of diff.agents.modified) {
|
|
2781
|
+
lines.push(` ~ Modified: ${m.name} (${m.changes})`);
|
|
2782
|
+
}
|
|
2783
|
+
return lines;
|
|
2784
|
+
}
|
|
2785
|
+
function formatMcpBlock(diff) {
|
|
2786
|
+
const lines = [];
|
|
2787
|
+
for (const s of diff.mcpServers.added) {
|
|
2788
|
+
lines.push(` + Added: ${s.id}`);
|
|
2789
|
+
}
|
|
2790
|
+
for (const id of diff.mcpServers.removed) {
|
|
2791
|
+
lines.push(` - Removed: ${id}`);
|
|
2792
|
+
}
|
|
2793
|
+
return lines;
|
|
2794
|
+
}
|
|
2795
|
+
function formatSettingsBlock(diff) {
|
|
2796
|
+
const lines = [];
|
|
2797
|
+
for (const c of diff.settings.changes) {
|
|
2798
|
+
lines.push(` ~ ${c.path} changed`);
|
|
2799
|
+
}
|
|
2800
|
+
return lines;
|
|
2801
|
+
}
|
|
2802
|
+
function deepEqual(a, b) {
|
|
2803
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
2804
|
+
}
|
|
2805
|
+
var init_diff = __esm({
|
|
2806
|
+
"src/ir/diff.ts"() {
|
|
2807
|
+
"use strict";
|
|
2808
|
+
init_types();
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
|
|
1412
2812
|
// src/evolve/mutator.ts
|
|
1413
|
-
import
|
|
1414
|
-
import
|
|
2813
|
+
import fs22 from "fs/promises";
|
|
2814
|
+
import path22 from "path";
|
|
1415
2815
|
async function applyMutations(currentHarnessPath, nextIterationDir, mutations) {
|
|
1416
|
-
const newHarnessPath =
|
|
2816
|
+
const newHarnessPath = path22.join(nextIterationDir, "harness");
|
|
2817
|
+
let baselineIR = null;
|
|
2818
|
+
try {
|
|
2819
|
+
baselineIR = await parseHarness(currentHarnessPath);
|
|
2820
|
+
} catch {
|
|
2821
|
+
}
|
|
2822
|
+
if (baselineIR !== null) {
|
|
2823
|
+
return applyMutationsViaIR(
|
|
2824
|
+
currentHarnessPath,
|
|
2825
|
+
newHarnessPath,
|
|
2826
|
+
mutations,
|
|
2827
|
+
baselineIR
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
return applyMutationsLegacy(currentHarnessPath, newHarnessPath, mutations);
|
|
2831
|
+
}
|
|
2832
|
+
async function applyMutationsViaIR(currentHarnessPath, newHarnessPath, mutations, baselineIR) {
|
|
1417
2833
|
await copyDir(currentHarnessPath, newHarnessPath);
|
|
1418
|
-
|
|
1419
|
-
|
|
2834
|
+
const irMutations = translateMutations(mutations, baselineIR);
|
|
2835
|
+
let currentIR = baselineIR;
|
|
2836
|
+
const rawTextMutations = [];
|
|
2837
|
+
const touchedCategories = /* @__PURE__ */ new Set();
|
|
2838
|
+
for (let i = 0; i < irMutations.length; i++) {
|
|
2839
|
+
const irMut = irMutations[i];
|
|
2840
|
+
if (irMut.type === "raw_text") {
|
|
2841
|
+
rawTextMutations.push(mutations[i]);
|
|
1420
2842
|
continue;
|
|
1421
2843
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
2844
|
+
try {
|
|
2845
|
+
currentIR = applyIRMutation(currentIR, irMut);
|
|
2846
|
+
touchedCategories.add(getMutationCategory(irMut.type));
|
|
2847
|
+
} catch {
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
if (touchedCategories.size > 0) {
|
|
2852
|
+
await renderAffectedFiles(currentIR, newHarnessPath, touchedCategories);
|
|
2853
|
+
}
|
|
2854
|
+
for (const mutation of rawTextMutations) {
|
|
2855
|
+
await applyLegacyMutation(newHarnessPath, mutation);
|
|
2856
|
+
}
|
|
2857
|
+
const irDiff = diffIR(baselineIR, currentIR);
|
|
2858
|
+
let diffPatch = formatIRDiff(irDiff);
|
|
2859
|
+
if (diffPatch === "No changes." && rawTextMutations.length > 0) {
|
|
2860
|
+
diffPatch = await generateDiffLegacy(currentHarnessPath, newHarnessPath);
|
|
2861
|
+
}
|
|
2862
|
+
if (diffPatch === "No changes.") {
|
|
2863
|
+
diffPatch = "";
|
|
2864
|
+
}
|
|
2865
|
+
return { newHarnessPath, diffPatch };
|
|
2866
|
+
}
|
|
2867
|
+
function getMutationCategory(mutationType) {
|
|
2868
|
+
if (mutationType.includes("section") || mutationType.includes("reorder")) {
|
|
2869
|
+
return "claude_md";
|
|
2870
|
+
}
|
|
2871
|
+
if (mutationType.includes("command")) {
|
|
2872
|
+
return "commands";
|
|
2873
|
+
}
|
|
2874
|
+
if (mutationType.includes("rule")) {
|
|
2875
|
+
return "rules";
|
|
2876
|
+
}
|
|
2877
|
+
if (mutationType.includes("agent")) {
|
|
2878
|
+
return "agents";
|
|
2879
|
+
}
|
|
2880
|
+
if (mutationType.includes("mcp")) {
|
|
2881
|
+
return "mcp";
|
|
2882
|
+
}
|
|
2883
|
+
if (mutationType.includes("settings")) {
|
|
2884
|
+
return "settings";
|
|
2885
|
+
}
|
|
2886
|
+
return "unknown";
|
|
2887
|
+
}
|
|
2888
|
+
async function renderAffectedFiles(ir, targetDir, touchedCategories) {
|
|
2889
|
+
const fileMap = renderHarness(ir);
|
|
2890
|
+
for (const [relativePath, content] of fileMap) {
|
|
2891
|
+
const category = getFileCategory(relativePath);
|
|
2892
|
+
if (touchedCategories.has(category)) {
|
|
2893
|
+
const fullPath = path22.join(targetDir, relativePath);
|
|
2894
|
+
await fs22.mkdir(path22.dirname(fullPath), { recursive: true });
|
|
2895
|
+
await fs22.writeFile(fullPath, content, "utf-8");
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (touchedCategories.has("commands")) {
|
|
2899
|
+
await deleteOrphanedFiles(targetDir, "commands", fileMap);
|
|
2900
|
+
}
|
|
2901
|
+
if (touchedCategories.has("rules")) {
|
|
2902
|
+
await deleteOrphanedFiles(targetDir, "rules", fileMap);
|
|
2903
|
+
}
|
|
2904
|
+
if (touchedCategories.has("agents")) {
|
|
2905
|
+
await deleteOrphanedFiles(targetDir, "agents", fileMap);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
function getFileCategory(relativePath) {
|
|
2909
|
+
if (relativePath === "CLAUDE.md") return "claude_md";
|
|
2910
|
+
if (relativePath.startsWith("commands/")) return "commands";
|
|
2911
|
+
if (relativePath.startsWith("rules/")) return "rules";
|
|
2912
|
+
if (relativePath.startsWith("agents/")) return "agents";
|
|
2913
|
+
if (relativePath.startsWith("skills/")) return "skills";
|
|
2914
|
+
if (relativePath.startsWith("docs/")) return "docs";
|
|
2915
|
+
if (relativePath.startsWith("hooks/")) return "hooks";
|
|
2916
|
+
if (relativePath === "settings.json") return "settings";
|
|
2917
|
+
if (relativePath === ".mcp.json") return "mcp";
|
|
2918
|
+
return "unknown";
|
|
2919
|
+
}
|
|
2920
|
+
async function deleteOrphanedFiles(targetDir, subdir, renderedMap) {
|
|
2921
|
+
const subdirPath = path22.join(targetDir, subdir);
|
|
2922
|
+
let entries;
|
|
2923
|
+
try {
|
|
2924
|
+
entries = await fs22.readdir(subdirPath);
|
|
2925
|
+
} catch {
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
for (const entry of entries) {
|
|
2929
|
+
const relativePath = `${subdir}/${entry}`;
|
|
2930
|
+
if (!renderedMap.has(relativePath)) {
|
|
2931
|
+
await fs22.unlink(path22.join(subdirPath, entry)).catch(() => {
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
async function applyMutationsLegacy(currentHarnessPath, newHarnessPath, mutations) {
|
|
2937
|
+
await copyDir(currentHarnessPath, newHarnessPath);
|
|
2938
|
+
for (const mutation of mutations) {
|
|
2939
|
+
await applyLegacyMutation(newHarnessPath, mutation);
|
|
2940
|
+
}
|
|
2941
|
+
const diffPatch = await generateDiffLegacy(currentHarnessPath, newHarnessPath);
|
|
2942
|
+
return { newHarnessPath, diffPatch };
|
|
2943
|
+
}
|
|
2944
|
+
async function applyLegacyMutation(harnessPath, mutation) {
|
|
2945
|
+
if (mutation.file.includes("..")) {
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
const filePath = path22.join(harnessPath, mutation.file);
|
|
2949
|
+
if (mutation.action === "replace") {
|
|
2950
|
+
if (!mutation.oldText) {
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
let content;
|
|
2954
|
+
try {
|
|
2955
|
+
content = await fs22.readFile(filePath, "utf-8");
|
|
2956
|
+
} catch {
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
if (!content.includes(mutation.oldText)) {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
await fs22.writeFile(
|
|
2963
|
+
filePath,
|
|
2964
|
+
content.replace(mutation.oldText, mutation.newText),
|
|
2965
|
+
"utf-8"
|
|
2966
|
+
);
|
|
2967
|
+
} else if (mutation.action === "add_section") {
|
|
2968
|
+
try {
|
|
2969
|
+
const content = await fs22.readFile(filePath, "utf-8");
|
|
2970
|
+
await fs22.writeFile(
|
|
1432
2971
|
filePath,
|
|
1433
|
-
content
|
|
2972
|
+
content + "\n\n" + mutation.newText,
|
|
1434
2973
|
"utf-8"
|
|
1435
2974
|
);
|
|
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
|
-
|
|
2975
|
+
} catch {
|
|
2976
|
+
await fs22.mkdir(path22.dirname(filePath), { recursive: true });
|
|
2977
|
+
await fs22.writeFile(filePath, mutation.newText, "utf-8");
|
|
2978
|
+
}
|
|
2979
|
+
} else if (mutation.action === "create_file") {
|
|
2980
|
+
await fs22.mkdir(path22.dirname(filePath), { recursive: true });
|
|
2981
|
+
await fs22.writeFile(filePath, mutation.newText, "utf-8");
|
|
2982
|
+
} else if (mutation.action === "delete_section") {
|
|
2983
|
+
if (!mutation.oldText) {
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
let sectionContent;
|
|
2987
|
+
try {
|
|
2988
|
+
sectionContent = await fs22.readFile(filePath, "utf-8");
|
|
2989
|
+
} catch {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
if (!sectionContent.includes(mutation.oldText)) {
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
await fs22.writeFile(filePath, sectionContent.replace(mutation.oldText, ""), "utf-8");
|
|
2996
|
+
} else if (mutation.action === "delete_file") {
|
|
2997
|
+
await fs22.unlink(filePath).catch(() => {
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async function generateDiff2(oldDir, newDir) {
|
|
3002
|
+
try {
|
|
3003
|
+
const oldIR = await parseHarness(oldDir);
|
|
3004
|
+
const newIR = await parseHarness(newDir);
|
|
3005
|
+
const oldHasContent = oldIR.sections.length > 0 || oldIR.commands.length > 0 || oldIR.rules.length > 0 || oldIR.agents.length > 0;
|
|
3006
|
+
const newHasContent = newIR.sections.length > 0 || newIR.commands.length > 0 || newIR.rules.length > 0 || newIR.agents.length > 0;
|
|
3007
|
+
if (oldHasContent || newHasContent) {
|
|
3008
|
+
const irDiff = diffIR(oldIR, newIR);
|
|
3009
|
+
const formatted = formatIRDiff(irDiff);
|
|
3010
|
+
if (formatted === "No changes.") {
|
|
3011
|
+
const legacyDiff2 = await generateDiffLegacy(oldDir, newDir);
|
|
3012
|
+
return legacyDiff2;
|
|
1460
3013
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
3014
|
+
const legacyDiff = await generateDiffLegacy(oldDir, newDir);
|
|
3015
|
+
if (legacyDiff && !formatted.includes(legacyDiff)) {
|
|
3016
|
+
return formatted + "\n\n" + legacyDiff;
|
|
1463
3017
|
}
|
|
1464
|
-
|
|
1465
|
-
} else if (mutation.action === "delete_file") {
|
|
1466
|
-
await fs21.unlink(filePath).catch(() => {
|
|
1467
|
-
});
|
|
3018
|
+
return formatted;
|
|
1468
3019
|
}
|
|
3020
|
+
} catch {
|
|
1469
3021
|
}
|
|
1470
|
-
|
|
1471
|
-
return { newHarnessPath, diffPatch };
|
|
3022
|
+
return generateDiffLegacy(oldDir, newDir);
|
|
1472
3023
|
}
|
|
1473
|
-
async function
|
|
3024
|
+
async function generateDiffLegacy(oldDir, newDir) {
|
|
1474
3025
|
const oldFiles = await readAllFiles(oldDir);
|
|
1475
3026
|
const newFiles = await readAllFiles(newDir);
|
|
1476
3027
|
const allPaths = /* @__PURE__ */ new Set([
|
|
@@ -1511,17 +3062,17 @@ async function readAllFiles(dir) {
|
|
|
1511
3062
|
async function walk(current) {
|
|
1512
3063
|
let entries;
|
|
1513
3064
|
try {
|
|
1514
|
-
entries = await
|
|
3065
|
+
entries = await fs22.readdir(current, { withFileTypes: true });
|
|
1515
3066
|
} catch {
|
|
1516
3067
|
return;
|
|
1517
3068
|
}
|
|
1518
3069
|
for (const entry of entries) {
|
|
1519
|
-
const fullPath =
|
|
1520
|
-
const relativePath =
|
|
3070
|
+
const fullPath = path22.join(current, entry.name);
|
|
3071
|
+
const relativePath = path22.relative(dir, fullPath);
|
|
1521
3072
|
if (entry.isDirectory()) {
|
|
1522
3073
|
await walk(fullPath);
|
|
1523
3074
|
} else {
|
|
1524
|
-
result[relativePath] = await
|
|
3075
|
+
result[relativePath] = await fs22.readFile(fullPath, "utf-8");
|
|
1525
3076
|
}
|
|
1526
3077
|
}
|
|
1527
3078
|
}
|
|
@@ -1532,12 +3083,17 @@ var init_mutator = __esm({
|
|
|
1532
3083
|
"src/evolve/mutator.ts"() {
|
|
1533
3084
|
"use strict";
|
|
1534
3085
|
init_baseline();
|
|
3086
|
+
init_parser();
|
|
3087
|
+
init_translate();
|
|
3088
|
+
init_mutations();
|
|
3089
|
+
init_renderer();
|
|
3090
|
+
init_diff();
|
|
1535
3091
|
}
|
|
1536
3092
|
});
|
|
1537
3093
|
|
|
1538
3094
|
// src/evolve/sampling.ts
|
|
1539
|
-
import
|
|
1540
|
-
import
|
|
3095
|
+
import fs23 from "fs/promises";
|
|
3096
|
+
import path23 from "path";
|
|
1541
3097
|
function initBeliefs(tasks) {
|
|
1542
3098
|
return tasks.map((task) => ({
|
|
1543
3099
|
taskId: task.id,
|
|
@@ -1598,9 +3154,9 @@ function updateBeliefs(beliefs, results) {
|
|
|
1598
3154
|
});
|
|
1599
3155
|
}
|
|
1600
3156
|
async function loadBeliefs(workspacePath) {
|
|
1601
|
-
const beliefsPath =
|
|
3157
|
+
const beliefsPath = path23.join(workspacePath, "task-beliefs.json");
|
|
1602
3158
|
try {
|
|
1603
|
-
const content = await
|
|
3159
|
+
const content = await fs23.readFile(beliefsPath, "utf-8");
|
|
1604
3160
|
const parsed = JSON.parse(content);
|
|
1605
3161
|
if (!Array.isArray(parsed)) return null;
|
|
1606
3162
|
for (const entry of parsed) {
|
|
@@ -1614,9 +3170,9 @@ async function loadBeliefs(workspacePath) {
|
|
|
1614
3170
|
}
|
|
1615
3171
|
}
|
|
1616
3172
|
async function saveBeliefs(workspacePath, beliefs) {
|
|
1617
|
-
const beliefsPath =
|
|
1618
|
-
await
|
|
1619
|
-
await
|
|
3173
|
+
const beliefsPath = path23.join(workspacePath, "task-beliefs.json");
|
|
3174
|
+
await fs23.mkdir(path23.dirname(beliefsPath), { recursive: true });
|
|
3175
|
+
await fs23.writeFile(beliefsPath, JSON.stringify(beliefs, null, 2), "utf-8");
|
|
1620
3176
|
}
|
|
1621
3177
|
var init_sampling = __esm({
|
|
1622
3178
|
"src/evolve/sampling.ts"() {
|
|
@@ -1625,8 +3181,8 @@ var init_sampling = __esm({
|
|
|
1625
3181
|
});
|
|
1626
3182
|
|
|
1627
3183
|
// src/evolve/regularization.ts
|
|
1628
|
-
import
|
|
1629
|
-
import
|
|
3184
|
+
import fs24 from "fs/promises";
|
|
3185
|
+
import path24 from "path";
|
|
1630
3186
|
async function measureComplexity(harnessPath) {
|
|
1631
3187
|
let totalLines = 0;
|
|
1632
3188
|
let totalFiles = 0;
|
|
@@ -1657,6 +3213,64 @@ async function measureComplexity(harnessPath) {
|
|
|
1657
3213
|
diffFromBaseline: 0
|
|
1658
3214
|
};
|
|
1659
3215
|
}
|
|
3216
|
+
function measureComplexityFromIR(ir) {
|
|
3217
|
+
const totalSections = ir.sections.length;
|
|
3218
|
+
const totalRules = ir.rules.length;
|
|
3219
|
+
const totalCommands = ir.commands.length;
|
|
3220
|
+
let totalFiles = 0;
|
|
3221
|
+
if (ir.sections.length > 0 || ir.meta.name) {
|
|
3222
|
+
totalFiles += 1;
|
|
3223
|
+
}
|
|
3224
|
+
totalFiles += ir.commands.length;
|
|
3225
|
+
totalFiles += ir.rules.length;
|
|
3226
|
+
totalFiles += ir.agents.length;
|
|
3227
|
+
totalFiles += ir.skills.length;
|
|
3228
|
+
totalFiles += ir.docs.length;
|
|
3229
|
+
totalFiles += ir.hooks.length;
|
|
3230
|
+
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(
|
|
3231
|
+
(entries) => entries !== void 0 && entries.length > 0
|
|
3232
|
+
);
|
|
3233
|
+
if (hasSettings) {
|
|
3234
|
+
totalFiles += 1;
|
|
3235
|
+
}
|
|
3236
|
+
if (ir.mcpServers.length > 0) {
|
|
3237
|
+
totalFiles += 1;
|
|
3238
|
+
}
|
|
3239
|
+
let totalLines = 0;
|
|
3240
|
+
for (const section of ir.sections) {
|
|
3241
|
+
totalLines += countLines(section.content);
|
|
3242
|
+
}
|
|
3243
|
+
for (const cmd of ir.commands) {
|
|
3244
|
+
totalLines += countLines(cmd.content);
|
|
3245
|
+
}
|
|
3246
|
+
for (const rule of ir.rules) {
|
|
3247
|
+
totalLines += countLines(rule.content);
|
|
3248
|
+
}
|
|
3249
|
+
for (const agent of ir.agents) {
|
|
3250
|
+
totalLines += countLines(agent.content);
|
|
3251
|
+
}
|
|
3252
|
+
for (const skill of ir.skills) {
|
|
3253
|
+
totalLines += countLines(skill.content);
|
|
3254
|
+
}
|
|
3255
|
+
for (const doc of ir.docs) {
|
|
3256
|
+
totalLines += countLines(doc.content);
|
|
3257
|
+
}
|
|
3258
|
+
for (const hook of ir.hooks) {
|
|
3259
|
+
totalLines += countLines(hook.content);
|
|
3260
|
+
}
|
|
3261
|
+
return {
|
|
3262
|
+
totalLines,
|
|
3263
|
+
totalFiles,
|
|
3264
|
+
totalSections,
|
|
3265
|
+
totalRules,
|
|
3266
|
+
totalCommands,
|
|
3267
|
+
diffFromBaseline: 0
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
function countLines(content) {
|
|
3271
|
+
if (!content) return 0;
|
|
3272
|
+
return content.split("\n").length;
|
|
3273
|
+
}
|
|
1660
3274
|
function computeComplexityCost(current, baseline) {
|
|
1661
3275
|
const baselineLines = Math.max(baseline.totalLines, 1);
|
|
1662
3276
|
const lineDelta = (current.totalLines - baseline.totalLines) / baselineLines;
|
|
@@ -1702,18 +3316,18 @@ async function readAllFilesRecursive(dir) {
|
|
|
1702
3316
|
async function walk(current) {
|
|
1703
3317
|
let entries;
|
|
1704
3318
|
try {
|
|
1705
|
-
entries = await
|
|
3319
|
+
entries = await fs24.readdir(current, { withFileTypes: true });
|
|
1706
3320
|
} catch {
|
|
1707
3321
|
return;
|
|
1708
3322
|
}
|
|
1709
3323
|
for (const entry of entries) {
|
|
1710
|
-
const fullPath =
|
|
1711
|
-
const relativePath =
|
|
3324
|
+
const fullPath = path24.join(current, entry.name);
|
|
3325
|
+
const relativePath = path24.relative(dir, fullPath);
|
|
1712
3326
|
if (entry.isDirectory()) {
|
|
1713
3327
|
await walk(fullPath);
|
|
1714
3328
|
} else {
|
|
1715
3329
|
try {
|
|
1716
|
-
result[relativePath] = await
|
|
3330
|
+
result[relativePath] = await fs24.readFile(fullPath, "utf-8");
|
|
1717
3331
|
} catch {
|
|
1718
3332
|
}
|
|
1719
3333
|
}
|
|
@@ -1729,8 +3343,8 @@ var init_regularization = __esm({
|
|
|
1729
3343
|
});
|
|
1730
3344
|
|
|
1731
3345
|
// src/evolve/loop.ts
|
|
1732
|
-
import
|
|
1733
|
-
import
|
|
3346
|
+
import fs25 from "fs/promises";
|
|
3347
|
+
import path25 from "path";
|
|
1734
3348
|
function computeMutationCap(iter, maxIterations, maxMutations) {
|
|
1735
3349
|
if (maxIterations <= 1) return maxMutations;
|
|
1736
3350
|
const progress = iter / (maxIterations - 1);
|
|
@@ -1747,27 +3361,33 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1747
3361
|
let beliefs = useThompson ? await loadBeliefs(workspacePath) ?? initBeliefs(tasks) : [];
|
|
1748
3362
|
const useKL = evolveConfig.klLambda > 0;
|
|
1749
3363
|
let baselineComplexity = null;
|
|
3364
|
+
let baselineIR = null;
|
|
1750
3365
|
if (useKL) {
|
|
1751
|
-
const baselineHarness =
|
|
3366
|
+
const baselineHarness = path25.join(workspacePath, "iterations", "0", "harness");
|
|
1752
3367
|
try {
|
|
1753
|
-
|
|
3368
|
+
baselineIR = await parseHarness(baselineHarness);
|
|
3369
|
+
baselineComplexity = measureComplexityFromIR(baselineIR);
|
|
1754
3370
|
} catch {
|
|
3371
|
+
try {
|
|
3372
|
+
baselineComplexity = await measureComplexity(baselineHarness);
|
|
3373
|
+
} catch {
|
|
3374
|
+
}
|
|
1755
3375
|
}
|
|
1756
3376
|
}
|
|
1757
|
-
let rngState = 42;
|
|
3377
|
+
let rngState = evolveConfig.rngSeed ?? 42;
|
|
1758
3378
|
const rng = () => {
|
|
1759
3379
|
rngState = rngState * 1664525 + 1013904223 & 4294967295;
|
|
1760
3380
|
return (rngState >>> 0) / 4294967296;
|
|
1761
3381
|
};
|
|
1762
3382
|
for (let iter = 0; iter < evolveConfig.maxIterations; iter++) {
|
|
1763
|
-
const harnessPath =
|
|
3383
|
+
const harnessPath = path25.join(
|
|
1764
3384
|
workspacePath,
|
|
1765
3385
|
"iterations",
|
|
1766
3386
|
iter.toString(),
|
|
1767
3387
|
"harness"
|
|
1768
3388
|
);
|
|
1769
3389
|
try {
|
|
1770
|
-
await
|
|
3390
|
+
await fs25.access(harnessPath);
|
|
1771
3391
|
} catch {
|
|
1772
3392
|
if (iter === 0) {
|
|
1773
3393
|
throw new Error(
|
|
@@ -1851,10 +3471,16 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1851
3471
|
let aggregate = rawAggregate;
|
|
1852
3472
|
let iterComplexityCost;
|
|
1853
3473
|
if (useKL && baselineComplexity) {
|
|
1854
|
-
|
|
3474
|
+
let currentComplexity;
|
|
3475
|
+
try {
|
|
3476
|
+
const iterIR = await parseHarness(harnessPath);
|
|
3477
|
+
currentComplexity = measureComplexityFromIR(iterIR);
|
|
3478
|
+
} catch {
|
|
3479
|
+
currentComplexity = await measureComplexity(harnessPath);
|
|
3480
|
+
}
|
|
1855
3481
|
const diffRatio = await computeDiffRatio(
|
|
1856
3482
|
harnessPath,
|
|
1857
|
-
|
|
3483
|
+
path25.join(workspacePath, "iterations", "0", "harness")
|
|
1858
3484
|
);
|
|
1859
3485
|
currentComplexity.diffFromBaseline = diffRatio;
|
|
1860
3486
|
iterComplexityCost = computeComplexityCost(currentComplexity, baselineComplexity);
|
|
@@ -1872,7 +3498,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1872
3498
|
if (iter === 0) {
|
|
1873
3499
|
baselineScore = aggregate;
|
|
1874
3500
|
if (useKL && !baselineComplexity) {
|
|
1875
|
-
|
|
3501
|
+
try {
|
|
3502
|
+
baselineIR = await parseHarness(harnessPath);
|
|
3503
|
+
baselineComplexity = measureComplexityFromIR(baselineIR);
|
|
3504
|
+
} catch {
|
|
3505
|
+
baselineComplexity = await measureComplexity(harnessPath);
|
|
3506
|
+
}
|
|
1876
3507
|
}
|
|
1877
3508
|
}
|
|
1878
3509
|
let shouldRollback = iter > 0 && aggregate < bestScore;
|
|
@@ -1917,7 +3548,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1917
3548
|
};
|
|
1918
3549
|
await writeIterationLog(workspacePath, rollbackLog);
|
|
1919
3550
|
history.push(rollbackLog);
|
|
1920
|
-
const bestHarnessPath =
|
|
3551
|
+
const bestHarnessPath = path25.join(
|
|
1921
3552
|
workspacePath,
|
|
1922
3553
|
"iterations",
|
|
1923
3554
|
bestIteration.toString(),
|
|
@@ -1942,7 +3573,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1942
3573
|
mutations: rollbackProposal.mutations.slice(0, rollbackCap)
|
|
1943
3574
|
};
|
|
1944
3575
|
}
|
|
1945
|
-
const nextIterDir2 =
|
|
3576
|
+
const nextIterDir2 = path25.join(workspacePath, "iterations", (iter + 1).toString());
|
|
1946
3577
|
await applyMutations(bestHarnessPath, nextIterDir2, rollbackProposal.mutations);
|
|
1947
3578
|
onProgress?.({
|
|
1948
3579
|
type: "mutations-applied",
|
|
@@ -1950,8 +3581,8 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
1950
3581
|
mutationCount: rollbackProposal.mutations.length
|
|
1951
3582
|
});
|
|
1952
3583
|
} catch {
|
|
1953
|
-
const nextIterDir2 =
|
|
1954
|
-
await copyDir(bestHarnessPath,
|
|
3584
|
+
const nextIterDir2 = path25.join(workspacePath, "iterations", (iter + 1).toString());
|
|
3585
|
+
await copyDir(bestHarnessPath, path25.join(nextIterDir2, "harness"));
|
|
1955
3586
|
}
|
|
1956
3587
|
}
|
|
1957
3588
|
continue;
|
|
@@ -2015,12 +3646,12 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2015
3646
|
iteration: iter,
|
|
2016
3647
|
message: `Proposer failed: ${errMsg}`
|
|
2017
3648
|
});
|
|
2018
|
-
const nextIterDir2 =
|
|
3649
|
+
const nextIterDir2 = path25.join(
|
|
2019
3650
|
workspacePath,
|
|
2020
3651
|
"iterations",
|
|
2021
3652
|
(iter + 1).toString()
|
|
2022
3653
|
);
|
|
2023
|
-
await copyDir(harnessPath,
|
|
3654
|
+
await copyDir(harnessPath, path25.join(nextIterDir2, "harness"));
|
|
2024
3655
|
const skipLog = {
|
|
2025
3656
|
iteration: iter,
|
|
2026
3657
|
score: aggregate,
|
|
@@ -2035,7 +3666,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2035
3666
|
history.push(skipLog);
|
|
2036
3667
|
continue;
|
|
2037
3668
|
}
|
|
2038
|
-
const nextIterDir =
|
|
3669
|
+
const nextIterDir = path25.join(
|
|
2039
3670
|
workspacePath,
|
|
2040
3671
|
"iterations",
|
|
2041
3672
|
(iter + 1).toString()
|
|
@@ -2049,7 +3680,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2049
3680
|
);
|
|
2050
3681
|
diffPatch = mutationResult.diffPatch;
|
|
2051
3682
|
} catch {
|
|
2052
|
-
await copyDir(harnessPath,
|
|
3683
|
+
await copyDir(harnessPath, path25.join(nextIterDir, "harness"));
|
|
2053
3684
|
}
|
|
2054
3685
|
onProgress?.({
|
|
2055
3686
|
type: "mutations-applied",
|
|
@@ -2071,7 +3702,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2071
3702
|
}
|
|
2072
3703
|
if (evolveConfig.usePrincipal && history.length >= 2) {
|
|
2073
3704
|
onProgress?.({ type: "proposing", iteration: history.length, message: "Principal Proposer synthesizing final harness" });
|
|
2074
|
-
const baselineHarnessPath =
|
|
3705
|
+
const baselineHarnessPath = path25.join(workspacePath, "iterations", "0", "harness");
|
|
2075
3706
|
try {
|
|
2076
3707
|
const principalProposal = await propose(
|
|
2077
3708
|
history.length,
|
|
@@ -2086,7 +3717,7 @@ async function evolve(workspacePath, tasks, kairnConfig, evolveConfig, onProgres
|
|
|
2086
3717
|
principalProposal.mutations = principalProposal.mutations.slice(0, evolveConfig.maxMutationsPerIteration);
|
|
2087
3718
|
}
|
|
2088
3719
|
const principalIterNum = history.length;
|
|
2089
|
-
const principalIterDir =
|
|
3720
|
+
const principalIterDir = path25.join(workspacePath, "iterations", principalIterNum.toString());
|
|
2090
3721
|
const mutResult = await applyMutations(baselineHarnessPath, principalIterDir, principalProposal.mutations);
|
|
2091
3722
|
onProgress?.({ type: "iteration-start", iteration: principalIterNum });
|
|
2092
3723
|
const { results: principalResults, aggregate: principalAggregate } = await evaluateAll(
|
|
@@ -2147,11 +3778,12 @@ var init_loop = __esm({
|
|
|
2147
3778
|
init_baseline();
|
|
2148
3779
|
init_sampling();
|
|
2149
3780
|
init_regularization();
|
|
3781
|
+
init_parser();
|
|
2150
3782
|
}
|
|
2151
3783
|
});
|
|
2152
3784
|
|
|
2153
3785
|
// src/evolve/synthesis.ts
|
|
2154
|
-
import
|
|
3786
|
+
import path28 from "path";
|
|
2155
3787
|
function buildMetaPrincipalSystemPrompt(numBranches) {
|
|
2156
3788
|
return `You are reviewing the COMPLETE results of ${numBranches} independent evolution runs.
|
|
2157
3789
|
Each branch explored different mutations and saw different task subsets.
|
|
@@ -2306,7 +3938,7 @@ async function runSynthesis(context, kairnConfig, evolveConfig, workspacePath) {
|
|
|
2306
3938
|
if (mutations.length === 0) {
|
|
2307
3939
|
return null;
|
|
2308
3940
|
}
|
|
2309
|
-
const synthesisDir =
|
|
3941
|
+
const synthesisDir = path28.join(workspacePath, "synthesis");
|
|
2310
3942
|
const { newHarnessPath } = await applyMutations(
|
|
2311
3943
|
context.baselineHarnessPath,
|
|
2312
3944
|
synthesisDir,
|
|
@@ -2340,26 +3972,26 @@ __export(population_exports, {
|
|
|
2340
3972
|
initBranches: () => initBranches,
|
|
2341
3973
|
runPopulation: () => runPopulation
|
|
2342
3974
|
});
|
|
2343
|
-
import
|
|
2344
|
-
import
|
|
3975
|
+
import fs28 from "fs/promises";
|
|
3976
|
+
import path29 from "path";
|
|
2345
3977
|
async function initBranches(workspacePath, baselinePath, numBranches) {
|
|
2346
|
-
const branchesDir =
|
|
2347
|
-
await
|
|
3978
|
+
const branchesDir = path29.join(workspacePath, "branches");
|
|
3979
|
+
await fs28.mkdir(branchesDir, { recursive: true });
|
|
2348
3980
|
const configs = [];
|
|
2349
3981
|
for (let i = 0; i < numBranches; i++) {
|
|
2350
|
-
const branchPath =
|
|
2351
|
-
const harnessPath =
|
|
3982
|
+
const branchPath = path29.join(branchesDir, i.toString());
|
|
3983
|
+
const harnessPath = path29.join(branchPath, "iterations", "0", "harness");
|
|
2352
3984
|
await copyDir(baselinePath, harnessPath);
|
|
2353
|
-
const tasksYaml =
|
|
3985
|
+
const tasksYaml = path29.join(workspacePath, "tasks.yaml");
|
|
2354
3986
|
try {
|
|
2355
|
-
await
|
|
2356
|
-
await
|
|
3987
|
+
await fs28.access(tasksYaml);
|
|
3988
|
+
await fs28.copyFile(tasksYaml, path29.join(branchPath, "tasks.yaml"));
|
|
2357
3989
|
} catch {
|
|
2358
3990
|
}
|
|
2359
|
-
const configYaml =
|
|
3991
|
+
const configYaml = path29.join(workspacePath, "config.yaml");
|
|
2360
3992
|
try {
|
|
2361
|
-
await
|
|
2362
|
-
await
|
|
3993
|
+
await fs28.access(configYaml);
|
|
3994
|
+
await fs28.copyFile(configYaml, path29.join(branchPath, "config.yaml"));
|
|
2363
3995
|
} catch {
|
|
2364
3996
|
}
|
|
2365
3997
|
const seed = 42 + i * 1337;
|
|
@@ -2373,13 +4005,15 @@ async function initBranches(workspacePath, baselinePath, numBranches) {
|
|
|
2373
4005
|
}
|
|
2374
4006
|
async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, numBranches, onProgress) {
|
|
2375
4007
|
const branches = numBranches ?? evolveConfig.pbtBranches;
|
|
2376
|
-
const baselinePath =
|
|
4008
|
+
const baselinePath = path29.join(workspacePath, "baseline");
|
|
2377
4009
|
const branchConfigs = await initBranches(workspacePath, baselinePath, branches);
|
|
2378
4010
|
const branchPromises = branchConfigs.map(async (branchConfig) => {
|
|
2379
4011
|
const branchEvolveConfig = {
|
|
2380
4012
|
...evolveConfig,
|
|
2381
4013
|
// Disable principal for individual branches — synthesis replaces it
|
|
2382
|
-
usePrincipal: false
|
|
4014
|
+
usePrincipal: false,
|
|
4015
|
+
// Each branch gets its own RNG seed for Thompson Sampling diversity
|
|
4016
|
+
rngSeed: branchConfig.seed
|
|
2383
4017
|
};
|
|
2384
4018
|
const branchProgress = onProgress ? (event) => {
|
|
2385
4019
|
onProgress({ ...event, branchId: branchConfig.branchId });
|
|
@@ -2391,7 +4025,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2391
4025
|
branchEvolveConfig,
|
|
2392
4026
|
branchProgress
|
|
2393
4027
|
);
|
|
2394
|
-
const finalHarnessPath =
|
|
4028
|
+
const finalHarnessPath = path29.join(
|
|
2395
4029
|
branchConfig.workspacePath,
|
|
2396
4030
|
"iterations",
|
|
2397
4031
|
result.bestIteration.toString(),
|
|
@@ -2399,8 +4033,8 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2399
4033
|
);
|
|
2400
4034
|
let beliefs = [];
|
|
2401
4035
|
try {
|
|
2402
|
-
const beliefsPath =
|
|
2403
|
-
const beliefsContent = await
|
|
4036
|
+
const beliefsPath = path29.join(branchConfig.workspacePath, "task-beliefs.json");
|
|
4037
|
+
const beliefsContent = await fs28.readFile(beliefsPath, "utf-8");
|
|
2404
4038
|
beliefs = JSON.parse(beliefsContent);
|
|
2405
4039
|
} catch {
|
|
2406
4040
|
}
|
|
@@ -2422,7 +4056,7 @@ async function runPopulation(workspacePath, tasks, kairnConfig, evolveConfig, nu
|
|
|
2422
4056
|
}
|
|
2423
4057
|
let synthesizedResult;
|
|
2424
4058
|
try {
|
|
2425
|
-
const baselinePath2 =
|
|
4059
|
+
const baselinePath2 = path29.join(workspacePath, "baseline");
|
|
2426
4060
|
const synthesisResult = await runSynthesis(
|
|
2427
4061
|
{ branches: branchResults, tasks, baselineHarnessPath: baselinePath2 },
|
|
2428
4062
|
kairnConfig,
|
|
@@ -2597,7 +4231,7 @@ var ui = {
|
|
|
2597
4231
|
// Key-value pairs
|
|
2598
4232
|
kv: (key, value) => ` ${chalk.cyan(key.padEnd(14))} ${value}`,
|
|
2599
4233
|
// File list
|
|
2600
|
-
file: (
|
|
4234
|
+
file: (path31) => chalk.dim(` ${path31}`),
|
|
2601
4235
|
// Tool display
|
|
2602
4236
|
tool: (name, reason) => ` ${warmStone("\u25CF")} ${chalk.bold(name)}
|
|
2603
4237
|
${chalk.dim(reason)}`,
|
|
@@ -6449,8 +8083,8 @@ var keysCommand = new Command10("keys").description("Add or update API keys for
|
|
|
6449
8083
|
import { Command as Command11 } from "commander";
|
|
6450
8084
|
import chalk14 from "chalk";
|
|
6451
8085
|
import ora2 from "ora";
|
|
6452
|
-
import
|
|
6453
|
-
import
|
|
8086
|
+
import fs29 from "fs/promises";
|
|
8087
|
+
import path30 from "path";
|
|
6454
8088
|
import { parse as yamlParse2 } from "yaml";
|
|
6455
8089
|
import { confirm as confirm4, input as input4, select as select4 } from "@inquirer/prompts";
|
|
6456
8090
|
|
|
@@ -6780,8 +8414,8 @@ init_loop();
|
|
|
6780
8414
|
|
|
6781
8415
|
// src/evolve/report.ts
|
|
6782
8416
|
init_trace();
|
|
6783
|
-
import
|
|
6784
|
-
import
|
|
8417
|
+
import fs26 from "fs/promises";
|
|
8418
|
+
import path26 from "path";
|
|
6785
8419
|
|
|
6786
8420
|
// src/evolve/diagnosis.ts
|
|
6787
8421
|
function numericScore(s) {
|
|
@@ -6831,10 +8465,10 @@ function numericScore2(s) {
|
|
|
6831
8465
|
return s.score ?? (s.pass ? 100 : 0);
|
|
6832
8466
|
}
|
|
6833
8467
|
async function loadAllIterations(workspacePath) {
|
|
6834
|
-
const iterDir =
|
|
8468
|
+
const iterDir = path26.join(workspacePath, "iterations");
|
|
6835
8469
|
let entries;
|
|
6836
8470
|
try {
|
|
6837
|
-
entries = await
|
|
8471
|
+
entries = await fs26.readdir(iterDir);
|
|
6838
8472
|
} catch {
|
|
6839
8473
|
return [];
|
|
6840
8474
|
}
|
|
@@ -6848,7 +8482,7 @@ async function loadAllIterations(workspacePath) {
|
|
|
6848
8482
|
}
|
|
6849
8483
|
async function loadTasks(workspacePath) {
|
|
6850
8484
|
try {
|
|
6851
|
-
const content = await
|
|
8485
|
+
const content = await fs26.readFile(path26.join(workspacePath, "tasks.yaml"), "utf-8");
|
|
6852
8486
|
const parsed = yamlParse(content);
|
|
6853
8487
|
return parsed?.tasks ?? [];
|
|
6854
8488
|
} catch {
|
|
@@ -7028,13 +8662,13 @@ init_mutator();
|
|
|
7028
8662
|
init_baseline();
|
|
7029
8663
|
init_mutator();
|
|
7030
8664
|
init_trace();
|
|
7031
|
-
import
|
|
7032
|
-
import
|
|
8665
|
+
import fs27 from "fs/promises";
|
|
8666
|
+
import path27 from "path";
|
|
7033
8667
|
async function listIterations(workspacePath) {
|
|
7034
|
-
const iterationsDir =
|
|
8668
|
+
const iterationsDir = path27.join(workspacePath, "iterations");
|
|
7035
8669
|
let entries;
|
|
7036
8670
|
try {
|
|
7037
|
-
entries = await
|
|
8671
|
+
entries = await fs27.readdir(iterationsDir);
|
|
7038
8672
|
} catch {
|
|
7039
8673
|
return [];
|
|
7040
8674
|
}
|
|
@@ -7043,7 +8677,7 @@ async function listIterations(workspacePath) {
|
|
|
7043
8677
|
const n = parseInt(entry, 10);
|
|
7044
8678
|
if (!isNaN(n)) {
|
|
7045
8679
|
try {
|
|
7046
|
-
await
|
|
8680
|
+
await fs27.access(path27.join(iterationsDir, entry, "harness"));
|
|
7047
8681
|
nums.push(n);
|
|
7048
8682
|
} catch {
|
|
7049
8683
|
}
|
|
@@ -7069,16 +8703,16 @@ async function listFilesRecursive(dir) {
|
|
|
7069
8703
|
async function walk(current) {
|
|
7070
8704
|
let entries;
|
|
7071
8705
|
try {
|
|
7072
|
-
entries = await
|
|
8706
|
+
entries = await fs27.readdir(current, { withFileTypes: true });
|
|
7073
8707
|
} catch {
|
|
7074
8708
|
return;
|
|
7075
8709
|
}
|
|
7076
8710
|
for (const entry of entries) {
|
|
7077
|
-
const fullPath =
|
|
8711
|
+
const fullPath = path27.join(current, entry.name);
|
|
7078
8712
|
if (entry.isDirectory()) {
|
|
7079
8713
|
await walk(fullPath);
|
|
7080
8714
|
} else {
|
|
7081
|
-
results.push(
|
|
8715
|
+
results.push(path27.relative(dir, fullPath));
|
|
7082
8716
|
}
|
|
7083
8717
|
}
|
|
7084
8718
|
}
|
|
@@ -7101,37 +8735,37 @@ async function applyEvolution(workspacePath, projectRoot, targetIteration) {
|
|
|
7101
8735
|
} else {
|
|
7102
8736
|
iter = await findBestIteration(workspacePath, iterations);
|
|
7103
8737
|
}
|
|
7104
|
-
const harnessPath =
|
|
8738
|
+
const harnessPath = path27.join(
|
|
7105
8739
|
workspacePath,
|
|
7106
8740
|
"iterations",
|
|
7107
8741
|
iter.toString(),
|
|
7108
8742
|
"harness"
|
|
7109
8743
|
);
|
|
7110
|
-
const claudeDir =
|
|
8744
|
+
const claudeDir = path27.join(projectRoot, ".claude");
|
|
7111
8745
|
const diffPreview = await generateDiff2(claudeDir, harnessPath);
|
|
7112
8746
|
const currentFiles = await listFilesRecursive(claudeDir);
|
|
7113
8747
|
const targetFiles = await listFilesRecursive(harnessPath);
|
|
7114
8748
|
const allPaths = /* @__PURE__ */ new Set([...currentFiles, ...targetFiles]);
|
|
7115
8749
|
const filesChanged = [];
|
|
7116
8750
|
for (const filePath of allPaths) {
|
|
7117
|
-
const currentContent = await
|
|
7118
|
-
const targetContent = await
|
|
8751
|
+
const currentContent = await fs27.readFile(path27.join(claudeDir, filePath), "utf-8").catch(() => null);
|
|
8752
|
+
const targetContent = await fs27.readFile(path27.join(harnessPath, filePath), "utf-8").catch(() => null);
|
|
7119
8753
|
if (currentContent !== targetContent) {
|
|
7120
8754
|
filesChanged.push(filePath);
|
|
7121
8755
|
}
|
|
7122
8756
|
}
|
|
7123
|
-
await
|
|
8757
|
+
await fs27.rm(claudeDir, { recursive: true, force: true });
|
|
7124
8758
|
await copyDir(harnessPath, claudeDir);
|
|
7125
|
-
const harnessMcpJson =
|
|
7126
|
-
const projectMcpJson =
|
|
8759
|
+
const harnessMcpJson = path27.join(harnessPath, ".mcp.json");
|
|
8760
|
+
const projectMcpJson = path27.join(projectRoot, ".mcp.json");
|
|
7127
8761
|
try {
|
|
7128
|
-
await
|
|
7129
|
-
const currentMcp = await
|
|
7130
|
-
const targetMcp = await
|
|
8762
|
+
await fs27.access(harnessMcpJson);
|
|
8763
|
+
const currentMcp = await fs27.readFile(projectMcpJson, "utf-8").catch(() => null);
|
|
8764
|
+
const targetMcp = await fs27.readFile(harnessMcpJson, "utf-8").catch(() => null);
|
|
7131
8765
|
if (currentMcp !== targetMcp) {
|
|
7132
8766
|
filesChanged.push(".mcp.json");
|
|
7133
8767
|
}
|
|
7134
|
-
await
|
|
8768
|
+
await fs27.copyFile(harnessMcpJson, projectMcpJson);
|
|
7135
8769
|
} catch {
|
|
7136
8770
|
}
|
|
7137
8771
|
return {
|
|
@@ -7160,7 +8794,7 @@ var DEFAULT_CONFIG = {
|
|
|
7160
8794
|
};
|
|
7161
8795
|
async function loadEvolveConfigFromWorkspace(workspacePath) {
|
|
7162
8796
|
try {
|
|
7163
|
-
const configStr = await
|
|
8797
|
+
const configStr = await fs29.readFile(path30.join(workspacePath, "config.yaml"), "utf-8");
|
|
7164
8798
|
const parsed = yamlParse2(configStr);
|
|
7165
8799
|
return {
|
|
7166
8800
|
model: parsed.model ?? DEFAULT_CONFIG.model,
|
|
@@ -7187,9 +8821,9 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7187
8821
|
try {
|
|
7188
8822
|
const projectRoot = process.cwd();
|
|
7189
8823
|
console.log(ui.section("Evolve Init"));
|
|
7190
|
-
const claudeDir =
|
|
8824
|
+
const claudeDir = path30.join(projectRoot, ".claude");
|
|
7191
8825
|
try {
|
|
7192
|
-
await
|
|
8826
|
+
await fs29.access(claudeDir);
|
|
7193
8827
|
} catch {
|
|
7194
8828
|
console.log(ui.error("No .claude/ directory found. Run kairn describe first."));
|
|
7195
8829
|
process.exit(1);
|
|
@@ -7239,7 +8873,7 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7239
8873
|
if (config) {
|
|
7240
8874
|
let claudeMd = "";
|
|
7241
8875
|
try {
|
|
7242
|
-
claudeMd = await
|
|
8876
|
+
claudeMd = await fs29.readFile(path30.join(claudeDir, "CLAUDE.md"), "utf-8");
|
|
7243
8877
|
} catch {
|
|
7244
8878
|
}
|
|
7245
8879
|
const profile = await buildProjectProfile(projectRoot);
|
|
@@ -7270,16 +8904,16 @@ evolveCommand.command("init").description("Initialize an evolution workspace wit
|
|
|
7270
8904
|
evolveCommand.command("baseline").description("Snapshot current .claude/ directory as baseline").action(async () => {
|
|
7271
8905
|
try {
|
|
7272
8906
|
const projectRoot = process.cwd();
|
|
7273
|
-
const workspace =
|
|
8907
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7274
8908
|
console.log(ui.section("Evolve Baseline"));
|
|
7275
8909
|
try {
|
|
7276
|
-
await
|
|
8910
|
+
await fs29.access(workspace);
|
|
7277
8911
|
} catch {
|
|
7278
8912
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7279
8913
|
process.exit(1);
|
|
7280
8914
|
}
|
|
7281
8915
|
await snapshotBaseline(projectRoot, workspace);
|
|
7282
|
-
const baselineDir =
|
|
8916
|
+
const baselineDir = path30.join(workspace, "baseline");
|
|
7283
8917
|
const fileCount = await countFiles(baselineDir);
|
|
7284
8918
|
console.log(ui.success(`Baseline snapshot created (${fileCount} files)`));
|
|
7285
8919
|
} catch (err) {
|
|
@@ -7291,18 +8925,18 @@ evolveCommand.command("baseline").description("Snapshot current .claude/ directo
|
|
|
7291
8925
|
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
8926
|
try {
|
|
7293
8927
|
const projectRoot = process.cwd();
|
|
7294
|
-
const workspace =
|
|
8928
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7295
8929
|
console.log(ui.section("Evolve Run"));
|
|
7296
8930
|
try {
|
|
7297
|
-
await
|
|
8931
|
+
await fs29.access(workspace);
|
|
7298
8932
|
} catch {
|
|
7299
8933
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7300
8934
|
process.exit(1);
|
|
7301
8935
|
}
|
|
7302
|
-
const tasksPath =
|
|
8936
|
+
const tasksPath = path30.join(workspace, "tasks.yaml");
|
|
7303
8937
|
let tasksContent;
|
|
7304
8938
|
try {
|
|
7305
|
-
tasksContent = await
|
|
8939
|
+
tasksContent = await fs29.readFile(tasksPath, "utf-8");
|
|
7306
8940
|
} catch {
|
|
7307
8941
|
console.log(ui.error("No tasks.yaml found. Run kairn evolve init first."));
|
|
7308
8942
|
process.exit(1);
|
|
@@ -7321,15 +8955,15 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7321
8955
|
console.log(ui.info(`Running ${tasksToRun.length} task(s)...`));
|
|
7322
8956
|
console.log("");
|
|
7323
8957
|
const config = await loadConfig();
|
|
7324
|
-
const harnessPath =
|
|
8958
|
+
const harnessPath = path30.join(projectRoot, ".claude");
|
|
7325
8959
|
const results = [];
|
|
7326
8960
|
for (const task of tasksToRun) {
|
|
7327
|
-
const traceDir =
|
|
8961
|
+
const traceDir = path30.join(workspace, "traces", "0", task.id);
|
|
7328
8962
|
const spinner = ora2(`Running: ${task.id}`).start();
|
|
7329
8963
|
const result = await runTask(task, harnessPath, traceDir, 0);
|
|
7330
8964
|
if (config) {
|
|
7331
|
-
const stdout = await
|
|
7332
|
-
const stderr = await
|
|
8965
|
+
const stdout = await fs29.readFile(path30.join(traceDir, "stdout.log"), "utf-8").catch(() => "");
|
|
8966
|
+
const stderr = await fs29.readFile(path30.join(traceDir, "stderr.log"), "utf-8").catch(() => "");
|
|
7333
8967
|
const score = await scoreTask(task, traceDir, stdout, stderr, config);
|
|
7334
8968
|
result.score = score;
|
|
7335
8969
|
await writeScore(traceDir, score);
|
|
@@ -7477,7 +9111,7 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7477
9111
|
evolveConfig.klLambda = klLambda;
|
|
7478
9112
|
}
|
|
7479
9113
|
try {
|
|
7480
|
-
await
|
|
9114
|
+
await fs29.access(path30.join(workspace, "iterations", "0", "harness"));
|
|
7481
9115
|
} catch {
|
|
7482
9116
|
console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
|
|
7483
9117
|
process.exit(1);
|
|
@@ -7571,16 +9205,16 @@ evolveCommand.command("run").description("Run tasks against the current harness"
|
|
|
7571
9205
|
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
9206
|
try {
|
|
7573
9207
|
const projectRoot = process.cwd();
|
|
7574
|
-
const workspace =
|
|
9208
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7575
9209
|
console.log(ui.section("Evolve PBT"));
|
|
7576
9210
|
try {
|
|
7577
|
-
await
|
|
9211
|
+
await fs29.access(workspace);
|
|
7578
9212
|
} catch {
|
|
7579
9213
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7580
9214
|
process.exit(1);
|
|
7581
9215
|
}
|
|
7582
9216
|
try {
|
|
7583
|
-
await
|
|
9217
|
+
await fs29.access(path30.join(workspace, "iterations", "0", "harness"));
|
|
7584
9218
|
} catch {
|
|
7585
9219
|
console.log(ui.error("No baseline harness found. Run kairn evolve baseline first."));
|
|
7586
9220
|
process.exit(1);
|
|
@@ -7600,8 +9234,8 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
|
|
|
7600
9234
|
if (sampling === "thompson" || sampling === "uniform") {
|
|
7601
9235
|
evolveConfig.samplingStrategy = sampling;
|
|
7602
9236
|
}
|
|
7603
|
-
const tasksPath =
|
|
7604
|
-
const tasksContent = await
|
|
9237
|
+
const tasksPath = path30.join(workspace, "tasks.yaml");
|
|
9238
|
+
const tasksContent = await fs29.readFile(tasksPath, "utf-8");
|
|
7605
9239
|
const parsed = yamlParse2(tasksContent);
|
|
7606
9240
|
if (!parsed?.tasks || parsed.tasks.length === 0) {
|
|
7607
9241
|
console.log(ui.error("No tasks found in tasks.yaml"));
|
|
@@ -7659,10 +9293,10 @@ evolveCommand.command("pbt").description("Run Population-Based Training with par
|
|
|
7659
9293
|
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
9294
|
try {
|
|
7661
9295
|
const projectRoot = process.cwd();
|
|
7662
|
-
const workspace =
|
|
9296
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7663
9297
|
console.log(ui.section("Evolve Apply"));
|
|
7664
9298
|
try {
|
|
7665
|
-
await
|
|
9299
|
+
await fs29.access(workspace);
|
|
7666
9300
|
} catch {
|
|
7667
9301
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7668
9302
|
process.exit(1);
|
|
@@ -7703,9 +9337,9 @@ evolveCommand.command("apply").description("Apply the best evolved harness to yo
|
|
|
7703
9337
|
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
9338
|
try {
|
|
7705
9339
|
const projectRoot = process.cwd();
|
|
7706
|
-
const workspace =
|
|
9340
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7707
9341
|
try {
|
|
7708
|
-
await
|
|
9342
|
+
await fs29.access(workspace);
|
|
7709
9343
|
} catch {
|
|
7710
9344
|
console.log(ui.error("No .kairn-evolve/ directory found. Run kairn evolve init first."));
|
|
7711
9345
|
process.exit(1);
|
|
@@ -7726,23 +9360,23 @@ evolveCommand.command("report").description("Generate a summary report of the ev
|
|
|
7726
9360
|
evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes between two iterations").action(async (iter1Str, iter2Str) => {
|
|
7727
9361
|
try {
|
|
7728
9362
|
const projectRoot = process.cwd();
|
|
7729
|
-
const workspace =
|
|
9363
|
+
const workspace = path30.join(projectRoot, ".kairn-evolve");
|
|
7730
9364
|
const iter1 = parseInt(iter1Str, 10);
|
|
7731
9365
|
const iter2 = parseInt(iter2Str, 10);
|
|
7732
9366
|
if (isNaN(iter1) || isNaN(iter2)) {
|
|
7733
9367
|
console.log(ui.error("Both arguments must be integers (iteration numbers)"));
|
|
7734
9368
|
process.exit(1);
|
|
7735
9369
|
}
|
|
7736
|
-
const harness1 =
|
|
7737
|
-
const harness2 =
|
|
9370
|
+
const harness1 = path30.join(workspace, "iterations", iter1.toString(), "harness");
|
|
9371
|
+
const harness2 = path30.join(workspace, "iterations", iter2.toString(), "harness");
|
|
7738
9372
|
try {
|
|
7739
|
-
await
|
|
9373
|
+
await fs29.access(harness1);
|
|
7740
9374
|
} catch {
|
|
7741
9375
|
console.log(ui.error(`Iteration ${iter1} harness not found at ${harness1}`));
|
|
7742
9376
|
process.exit(1);
|
|
7743
9377
|
}
|
|
7744
9378
|
try {
|
|
7745
|
-
await
|
|
9379
|
+
await fs29.access(harness2);
|
|
7746
9380
|
} catch {
|
|
7747
9381
|
console.log(ui.error(`Iteration ${iter2} harness not found at ${harness2}`));
|
|
7748
9382
|
process.exit(1);
|
|
@@ -7797,10 +9431,10 @@ evolveCommand.command("diff <iter1> <iter2>").description("Show harness changes
|
|
|
7797
9431
|
async function countFiles(dir) {
|
|
7798
9432
|
let count = 0;
|
|
7799
9433
|
try {
|
|
7800
|
-
const entries = await
|
|
9434
|
+
const entries = await fs29.readdir(dir, { withFileTypes: true });
|
|
7801
9435
|
for (const entry of entries) {
|
|
7802
9436
|
if (entry.isDirectory()) {
|
|
7803
|
-
count += await countFiles(
|
|
9437
|
+
count += await countFiles(path30.join(dir, entry.name));
|
|
7804
9438
|
} else {
|
|
7805
9439
|
count++;
|
|
7806
9440
|
}
|