@rely-ai/caliber 1.21.1 → 1.22.0-dev.1773745340
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/bin.js +206 -71
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -114,10 +114,13 @@ function getDisplayModel(config) {
|
|
|
114
114
|
}
|
|
115
115
|
function getFastModel() {
|
|
116
116
|
if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
|
|
117
|
-
if (process.env.ANTHROPIC_SMALL_FAST_MODEL) return process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
118
117
|
const config = loadConfig();
|
|
118
|
+
const provider = config?.provider;
|
|
119
|
+
if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex")) {
|
|
120
|
+
return process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
121
|
+
}
|
|
119
122
|
if (config?.fastModel) return config.fastModel;
|
|
120
|
-
if (
|
|
123
|
+
if (provider) return DEFAULT_FAST_MODELS[provider];
|
|
121
124
|
return void 0;
|
|
122
125
|
}
|
|
123
126
|
var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, DEFAULT_FAST_MODELS;
|
|
@@ -130,7 +133,7 @@ var init_config = __esm({
|
|
|
130
133
|
anthropic: "claude-sonnet-4-6",
|
|
131
134
|
vertex: "claude-sonnet-4-6",
|
|
132
135
|
openai: "gpt-4.1",
|
|
133
|
-
cursor: "
|
|
136
|
+
cursor: "auto",
|
|
134
137
|
"claude-cli": "default"
|
|
135
138
|
};
|
|
136
139
|
DEFAULT_FAST_MODELS = {
|
|
@@ -1227,7 +1230,11 @@ var CursorAcpProvider = class {
|
|
|
1227
1230
|
}
|
|
1228
1231
|
async runAcpPrompt(options, callbacks) {
|
|
1229
1232
|
const combinedPrompt = this.buildCombinedPrompt(options);
|
|
1233
|
+
const model = options.model || this.defaultModel;
|
|
1230
1234
|
const args = ["acp"];
|
|
1235
|
+
if (model && model !== "auto" && model !== "default") {
|
|
1236
|
+
args.unshift("--model", model);
|
|
1237
|
+
}
|
|
1231
1238
|
if (this.cursorApiKey) {
|
|
1232
1239
|
args.unshift("--api-key", this.cursorApiKey);
|
|
1233
1240
|
}
|
|
@@ -1745,6 +1752,7 @@ async function validateModel(options) {
|
|
|
1745
1752
|
const provider = getProvider();
|
|
1746
1753
|
const config = cachedConfig;
|
|
1747
1754
|
if (!config) return;
|
|
1755
|
+
if (config.provider === "cursor" || config.provider === "claude-cli") return;
|
|
1748
1756
|
const modelsToCheck = [config.model];
|
|
1749
1757
|
if (options?.fast) {
|
|
1750
1758
|
const { getFastModel: getFastModel2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
@@ -1856,6 +1864,12 @@ Safety: Never include API keys, tokens, or credentials in config files.
|
|
|
1856
1864
|
Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.`;
|
|
1857
1865
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
1858
1866
|
- CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
|
|
1867
|
+
|
|
1868
|
+
Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
|
|
1869
|
+
GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
|
|
1870
|
+
BAD: The entry point of the application is located in the src directory. The LLM module handles different providers.
|
|
1871
|
+
For command sections, use code blocks with one command per line.
|
|
1872
|
+
|
|
1859
1873
|
- Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
|
|
1860
1874
|
- Cursor rules: max 5 .mdc files.
|
|
1861
1875
|
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.`;
|
|
@@ -4037,7 +4051,7 @@ async function runInteractiveProviderSetup(options) {
|
|
|
4037
4051
|
break;
|
|
4038
4052
|
}
|
|
4039
4053
|
case "cursor": {
|
|
4040
|
-
config.model =
|
|
4054
|
+
config.model = DEFAULT_MODELS.cursor;
|
|
4041
4055
|
if (!isCursorAgentAvailable()) {
|
|
4042
4056
|
console.log(chalk5.yellow("\n Cursor Agent CLI not found."));
|
|
4043
4057
|
console.log(chalk5.dim(" Install it: ") + chalk5.hex("#83D1EB")("curl https://cursor.com/install -fsS | bash"));
|
|
@@ -4597,6 +4611,29 @@ function countTreeLines(content) {
|
|
|
4597
4611
|
}
|
|
4598
4612
|
return count;
|
|
4599
4613
|
}
|
|
4614
|
+
function calculateDuplicatePercent(content1, content2) {
|
|
4615
|
+
const lines1 = new Set(content1.split("\n").map((l) => l.trim()).filter((l) => l.length > 10));
|
|
4616
|
+
const lines2 = content2.split("\n").map((l) => l.trim()).filter((l) => l.length > 10);
|
|
4617
|
+
const overlapping = lines2.filter((l) => lines1.has(l)).length;
|
|
4618
|
+
return lines2.length > 0 ? Math.round(overlapping / lines2.length * 100) : 0;
|
|
4619
|
+
}
|
|
4620
|
+
function calculateDensityPoints(density, maxPoints) {
|
|
4621
|
+
if (density >= 40) return maxPoints;
|
|
4622
|
+
if (density >= 25) return Math.round(maxPoints * 0.75);
|
|
4623
|
+
if (density >= 15) return Math.round(maxPoints * 0.5);
|
|
4624
|
+
if (density >= 5) return Math.round(maxPoints * 0.25);
|
|
4625
|
+
return 0;
|
|
4626
|
+
}
|
|
4627
|
+
function isEntryMentioned(entry, contentLower) {
|
|
4628
|
+
const entryLower = entry.toLowerCase();
|
|
4629
|
+
const variants = [entryLower, entryLower.replace(/\\/g, "/")];
|
|
4630
|
+
const lastSegment = entry.split("/").pop()?.toLowerCase();
|
|
4631
|
+
if (lastSegment && lastSegment.length > 3) variants.push(lastSegment);
|
|
4632
|
+
return variants.some((v) => {
|
|
4633
|
+
const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4634
|
+
return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(contentLower);
|
|
4635
|
+
});
|
|
4636
|
+
}
|
|
4600
4637
|
function classifyLine(line, inCodeBlock) {
|
|
4601
4638
|
if (inCodeBlock) return "concrete";
|
|
4602
4639
|
const trimmed = line.trim();
|
|
@@ -4706,15 +4743,7 @@ function checkQuality(dir) {
|
|
|
4706
4743
|
instruction: "Remove directory tree listings from code blocks. Reference key directories inline instead."
|
|
4707
4744
|
} : void 0
|
|
4708
4745
|
});
|
|
4709
|
-
|
|
4710
|
-
if (claudeMd && cursorrules) {
|
|
4711
|
-
const claudeLines = new Set(
|
|
4712
|
-
claudeMd.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
4713
|
-
);
|
|
4714
|
-
const cursorLines = cursorrules.split("\n").map((l) => l.trim()).filter((l) => l.length > 10);
|
|
4715
|
-
const overlapping = cursorLines.filter((l) => claudeLines.has(l)).length;
|
|
4716
|
-
duplicatePercent = cursorLines.length > 0 ? Math.round(overlapping / cursorLines.length * 100) : 0;
|
|
4717
|
-
}
|
|
4746
|
+
const duplicatePercent = claudeMd && cursorrules ? calculateDuplicatePercent(claudeMd, cursorrules) : 0;
|
|
4718
4747
|
const hasDuplicates = duplicatePercent > 50;
|
|
4719
4748
|
checks.push({
|
|
4720
4749
|
id: "no_duplicate_content",
|
|
@@ -4764,20 +4793,7 @@ function checkGrounding(dir) {
|
|
|
4764
4793
|
const mentioned = [];
|
|
4765
4794
|
const notMentioned = [];
|
|
4766
4795
|
for (const entry of meaningfulEntries) {
|
|
4767
|
-
|
|
4768
|
-
const variants = [
|
|
4769
|
-
entryLower,
|
|
4770
|
-
entryLower.replace(/\\/g, "/")
|
|
4771
|
-
];
|
|
4772
|
-
const lastSegment = entry.split("/").pop()?.toLowerCase();
|
|
4773
|
-
if (lastSegment && lastSegment.length > 3) {
|
|
4774
|
-
variants.push(lastSegment);
|
|
4775
|
-
}
|
|
4776
|
-
const ismentioned = variants.some((v) => {
|
|
4777
|
-
const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4778
|
-
return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(configLower);
|
|
4779
|
-
});
|
|
4780
|
-
if (ismentioned) {
|
|
4796
|
+
if (isEntryMentioned(entry, configLower)) {
|
|
4781
4797
|
mentioned.push(entry);
|
|
4782
4798
|
} else {
|
|
4783
4799
|
notMentioned.push(entry);
|
|
@@ -4787,12 +4803,8 @@ function checkGrounding(dir) {
|
|
|
4787
4803
|
const groundingThreshold = GROUNDING_THRESHOLDS.find((t) => groundingRatio >= t.minRatio);
|
|
4788
4804
|
const groundingPoints = meaningfulEntries.length === 0 ? 0 : groundingThreshold?.points ?? 0;
|
|
4789
4805
|
const topDirs = projectStructure.dirs.filter((d) => !d.includes("/")).filter((d) => d.length > 2);
|
|
4790
|
-
const
|
|
4791
|
-
|
|
4792
|
-
return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(configLower);
|
|
4793
|
-
};
|
|
4794
|
-
const unmentionedTopDirs = topDirs.filter((d) => !matchesConfig(d));
|
|
4795
|
-
const mentionedTopDirs = topDirs.filter((d) => matchesConfig(d));
|
|
4806
|
+
const unmentionedTopDirs = topDirs.filter((d) => !isEntryMentioned(d, configLower));
|
|
4807
|
+
const mentionedTopDirs = topDirs.filter((d) => isEntryMentioned(d, configLower));
|
|
4796
4808
|
checks.push({
|
|
4797
4809
|
id: "project_grounding",
|
|
4798
4810
|
name: "Project grounding",
|
|
@@ -4817,18 +4829,7 @@ function checkGrounding(dir) {
|
|
|
4817
4829
|
const mdStructure = analyzeMarkdownStructure(configContent);
|
|
4818
4830
|
const totalSpecificRefs = refs.length + mdStructure.inlineCodeCount;
|
|
4819
4831
|
const density = mdStructure.nonEmptyLines > 0 ? totalSpecificRefs / mdStructure.nonEmptyLines * 100 : 0;
|
|
4820
|
-
|
|
4821
|
-
if (configContent.length === 0) {
|
|
4822
|
-
densityPoints = 0;
|
|
4823
|
-
} else if (density >= 40) {
|
|
4824
|
-
densityPoints = POINTS_REFERENCE_DENSITY;
|
|
4825
|
-
} else if (density >= 25) {
|
|
4826
|
-
densityPoints = Math.round(POINTS_REFERENCE_DENSITY * 0.75);
|
|
4827
|
-
} else if (density >= 15) {
|
|
4828
|
-
densityPoints = Math.round(POINTS_REFERENCE_DENSITY * 0.5);
|
|
4829
|
-
} else if (density >= 5) {
|
|
4830
|
-
densityPoints = Math.round(POINTS_REFERENCE_DENSITY * 0.25);
|
|
4831
|
-
}
|
|
4832
|
+
const densityPoints = configContent.length === 0 ? 0 : calculateDensityPoints(density, POINTS_REFERENCE_DENSITY);
|
|
4832
4833
|
checks.push({
|
|
4833
4834
|
id: "reference_density",
|
|
4834
4835
|
name: "Reference density",
|
|
@@ -6449,14 +6450,46 @@ var MAX_REFINE_ITERATIONS = 2;
|
|
|
6449
6450
|
function extractConfigContent(setup) {
|
|
6450
6451
|
const claude = setup.claude;
|
|
6451
6452
|
const codex = setup.codex;
|
|
6453
|
+
const cursor = setup.cursor;
|
|
6454
|
+
let cursorrules = null;
|
|
6455
|
+
if (typeof cursor?.cursorrules === "string" && cursor.cursorrules.length > 0) {
|
|
6456
|
+
cursorrules = cursor.cursorrules;
|
|
6457
|
+
}
|
|
6458
|
+
const skills = [];
|
|
6459
|
+
for (const [platform, obj] of [["claude", claude], ["codex", codex], ["cursor", cursor]]) {
|
|
6460
|
+
const platformSkills = obj?.skills;
|
|
6461
|
+
if (Array.isArray(platformSkills)) {
|
|
6462
|
+
for (const skill of platformSkills) {
|
|
6463
|
+
if (typeof skill.content === "string" && skill.content.length > 0) {
|
|
6464
|
+
skills.push({ name: skill.name, content: skill.content, platform });
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
}
|
|
6452
6469
|
return {
|
|
6453
6470
|
claudeMd: claude?.claudeMd ?? null,
|
|
6454
|
-
agentsMd: codex?.agentsMd ?? null
|
|
6471
|
+
agentsMd: codex?.agentsMd ?? null,
|
|
6472
|
+
cursorrules,
|
|
6473
|
+
skills
|
|
6455
6474
|
};
|
|
6456
6475
|
}
|
|
6457
|
-
function
|
|
6476
|
+
function buildGroundingFixInstruction(unmentionedTopDirs, projectStructure) {
|
|
6477
|
+
const dirDescriptions = unmentionedTopDirs.slice(0, 8).map((dir) => {
|
|
6478
|
+
const subdirs = projectStructure.dirs.filter((d) => d.startsWith(`${dir}/`) && !d.includes("/", dir.length + 1));
|
|
6479
|
+
const files = projectStructure.files.filter((f) => f.startsWith(`${dir}/`) && !f.includes("/", dir.length + 1));
|
|
6480
|
+
const children = [...subdirs.slice(0, 4), ...files.slice(0, 2)];
|
|
6481
|
+
const childList = children.map((c) => c.split("/").pop()).join(", ");
|
|
6482
|
+
return childList ? `- \`${dir}/\` (contains: ${childList})` : `- \`${dir}/\``;
|
|
6483
|
+
});
|
|
6484
|
+
return [
|
|
6485
|
+
"Reference these project directories with descriptions of their contents:",
|
|
6486
|
+
...dirDescriptions,
|
|
6487
|
+
"Mention them naturally in architecture descriptions using dense inline references."
|
|
6488
|
+
].join("\n");
|
|
6489
|
+
}
|
|
6490
|
+
function validateSetup(setup, dir, checkExists = existsSync9, projectStructure) {
|
|
6458
6491
|
const issues = [];
|
|
6459
|
-
const { claudeMd, agentsMd } = extractConfigContent(setup);
|
|
6492
|
+
const { claudeMd, agentsMd, cursorrules, skills } = extractConfigContent(setup);
|
|
6460
6493
|
const primaryContent = [claudeMd, agentsMd].filter(Boolean).join("\n");
|
|
6461
6494
|
if (!primaryContent) return issues;
|
|
6462
6495
|
const refs = validateFileReferences(primaryContent, dir, checkExists);
|
|
@@ -6487,15 +6520,15 @@ function validateSetup(setup, dir, checkExists = existsSync9) {
|
|
|
6487
6520
|
}
|
|
6488
6521
|
const content = claudeMd ?? agentsMd ?? "";
|
|
6489
6522
|
if (content) {
|
|
6490
|
-
const
|
|
6491
|
-
const blockThreshold = CODE_BLOCK_THRESHOLDS.find((t) =>
|
|
6523
|
+
const structure2 = analyzeMarkdownStructure(content);
|
|
6524
|
+
const blockThreshold = CODE_BLOCK_THRESHOLDS.find((t) => structure2.codeBlockCount >= t.minBlocks);
|
|
6492
6525
|
const blockPoints = blockThreshold?.points ?? 0;
|
|
6493
6526
|
const maxBlockPoints = CODE_BLOCK_THRESHOLDS[0].points;
|
|
6494
|
-
if (blockPoints < maxBlockPoints &&
|
|
6527
|
+
if (blockPoints < maxBlockPoints && structure2.codeBlockCount < CODE_BLOCK_THRESHOLDS[0].minBlocks) {
|
|
6495
6528
|
issues.push({
|
|
6496
6529
|
check: "Executable content",
|
|
6497
|
-
detail: `${
|
|
6498
|
-
fixInstruction: `Add ${CODE_BLOCK_THRESHOLDS[0].minBlocks -
|
|
6530
|
+
detail: `${structure2.codeBlockCount} code block${structure2.codeBlockCount === 1 ? "" : "s"} (need \u2265${CODE_BLOCK_THRESHOLDS[0].minBlocks} for full points)`,
|
|
6531
|
+
fixInstruction: `Add ${CODE_BLOCK_THRESHOLDS[0].minBlocks - structure2.codeBlockCount} more code blocks with actual project commands (build, test, lint, deploy).`,
|
|
6499
6532
|
pointsLost: maxBlockPoints - blockPoints
|
|
6500
6533
|
});
|
|
6501
6534
|
}
|
|
@@ -6522,15 +6555,86 @@ function validateSetup(setup, dir, checkExists = existsSync9) {
|
|
|
6522
6555
|
pointsLost: POINTS_NO_DIR_TREE
|
|
6523
6556
|
});
|
|
6524
6557
|
}
|
|
6525
|
-
if (
|
|
6558
|
+
if (structure2.h2Count < 3 || structure2.listItemCount < 3) {
|
|
6526
6559
|
const parts = [];
|
|
6527
|
-
if (
|
|
6528
|
-
if (
|
|
6560
|
+
if (structure2.h2Count < 3) parts.push(`add ${3 - structure2.h2Count} more ## sections`);
|
|
6561
|
+
if (structure2.listItemCount < 3) parts.push("use bullet lists for multi-item instructions");
|
|
6529
6562
|
issues.push({
|
|
6530
6563
|
check: "Structured with headings",
|
|
6531
|
-
detail: `${
|
|
6564
|
+
detail: `${structure2.h2Count} sections, ${structure2.listItemCount} list items`,
|
|
6532
6565
|
fixInstruction: `Improve structure: ${parts.join(" and ")}.`,
|
|
6533
|
-
pointsLost: POINTS_HAS_STRUCTURE - ((
|
|
6566
|
+
pointsLost: POINTS_HAS_STRUCTURE - ((structure2.h2Count >= 3 ? 1 : 0) + (structure2.listItemCount >= 3 ? 1 : 0))
|
|
6567
|
+
});
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
const structure = projectStructure ?? collectProjectStructure(dir);
|
|
6571
|
+
const allEntries = [...structure.dirs, ...structure.files].filter((e) => e.length > 2);
|
|
6572
|
+
if (allEntries.length > 0) {
|
|
6573
|
+
const contentLower = primaryContent.toLowerCase();
|
|
6574
|
+
const mentionedEntries = allEntries.filter((e) => isEntryMentioned(e, contentLower));
|
|
6575
|
+
const groundingRatio = mentionedEntries.length / allEntries.length;
|
|
6576
|
+
const groundingThreshold = GROUNDING_THRESHOLDS.find((t) => groundingRatio >= t.minRatio);
|
|
6577
|
+
const groundingPoints = groundingThreshold?.points ?? 0;
|
|
6578
|
+
const groundingLost = POINTS_PROJECT_GROUNDING - groundingPoints;
|
|
6579
|
+
if (groundingLost > 0) {
|
|
6580
|
+
const topDirs = structure.dirs.filter((d) => !d.includes("/") && d.length > 2);
|
|
6581
|
+
const unmentionedTopDirs = topDirs.filter((d) => !isEntryMentioned(d, contentLower));
|
|
6582
|
+
if (unmentionedTopDirs.length > 0) {
|
|
6583
|
+
issues.push({
|
|
6584
|
+
check: "Project grounding",
|
|
6585
|
+
detail: `${mentionedEntries.length}/${allEntries.length} project entries referenced (${Math.round(groundingRatio * 100)}%)`,
|
|
6586
|
+
fixInstruction: buildGroundingFixInstruction(unmentionedTopDirs, structure),
|
|
6587
|
+
pointsLost: groundingLost
|
|
6588
|
+
});
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
const allRefs = extractReferences(primaryContent);
|
|
6593
|
+
const primaryStructure = analyzeMarkdownStructure(primaryContent);
|
|
6594
|
+
const totalSpecificRefs = allRefs.length + primaryStructure.inlineCodeCount;
|
|
6595
|
+
const density = primaryStructure.nonEmptyLines > 0 ? totalSpecificRefs / primaryStructure.nonEmptyLines * 100 : 0;
|
|
6596
|
+
const densityPoints = calculateDensityPoints(density, POINTS_REFERENCE_DENSITY);
|
|
6597
|
+
const densityLost = POINTS_REFERENCE_DENSITY - densityPoints;
|
|
6598
|
+
if (densityLost > 0) {
|
|
6599
|
+
issues.push({
|
|
6600
|
+
check: "Reference density",
|
|
6601
|
+
detail: `${totalSpecificRefs} references across ${primaryStructure.nonEmptyLines} lines (${Math.round(density)}% density, need \u226540% for full points)`,
|
|
6602
|
+
fixInstruction: `Add more backtick references around file paths, commands, and identifiers. Use the dense reference style: \`src/api/\` routes \xB7 \`src/models/\` data. Current density: ${Math.round(density)}%, target: \u226540%.`,
|
|
6603
|
+
pointsLost: densityLost
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
6606
|
+
if (claudeMd && cursorrules) {
|
|
6607
|
+
const duplicatePercent = calculateDuplicatePercent(claudeMd, cursorrules);
|
|
6608
|
+
if (duplicatePercent > 50) {
|
|
6609
|
+
issues.push({
|
|
6610
|
+
check: "No duplicate content",
|
|
6611
|
+
detail: `${duplicatePercent}% overlap between CLAUDE.md and .cursorrules`,
|
|
6612
|
+
fixInstruction: "Deduplicate content. Keep shared instructions in CLAUDE.md only. Make .cursorrules contain only Cursor-specific settings and platform differences.",
|
|
6613
|
+
pointsLost: POINTS_NO_DUPLICATES
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
for (const skill of skills) {
|
|
6618
|
+
const skillIssues = [];
|
|
6619
|
+
const skillRefs = validateFileReferences(skill.content, dir, checkExists);
|
|
6620
|
+
if (skillRefs.invalid.length > 0) {
|
|
6621
|
+
skillIssues.push(`invalid refs: ${skillRefs.invalid.slice(0, 3).join(", ")}`);
|
|
6622
|
+
}
|
|
6623
|
+
const skillStructure = analyzeMarkdownStructure(skill.content);
|
|
6624
|
+
if (skillStructure.codeBlockCount === 0 && skill.content.length > 200) {
|
|
6625
|
+
skillIssues.push("no code blocks");
|
|
6626
|
+
}
|
|
6627
|
+
const { concrete, abstract } = countConcreteness(skill.content);
|
|
6628
|
+
const total = concrete + abstract;
|
|
6629
|
+
if (total > 3 && concrete / total < 0.3) {
|
|
6630
|
+
skillIssues.push("low concreteness");
|
|
6631
|
+
}
|
|
6632
|
+
if (skillIssues.length > 0) {
|
|
6633
|
+
issues.push({
|
|
6634
|
+
check: `Skill quality: ${skill.name}`,
|
|
6635
|
+
detail: skillIssues.join("; "),
|
|
6636
|
+
fixInstruction: `Fix skill "${skill.name}": ${skillIssues.join(", ")}.${skillRefs.invalid.length > 0 ? ` Remove invalid paths: ${skillRefs.invalid.join(", ")}.` : ""} Add code blocks and specific file references.`,
|
|
6637
|
+
pointsLost: 0
|
|
6534
6638
|
});
|
|
6535
6639
|
}
|
|
6536
6640
|
}
|
|
@@ -6560,23 +6664,25 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
6560
6664
|
existsCache.set(path29, result);
|
|
6561
6665
|
return result;
|
|
6562
6666
|
};
|
|
6667
|
+
const projectStructure = collectProjectStructure(dir);
|
|
6563
6668
|
let currentSetup = setup;
|
|
6564
6669
|
let bestSetup = setup;
|
|
6565
6670
|
let bestLostPoints = Infinity;
|
|
6566
6671
|
for (let iteration = 0; iteration < MAX_REFINE_ITERATIONS; iteration++) {
|
|
6567
|
-
const issues = validateSetup(currentSetup, dir, cachedExists);
|
|
6672
|
+
const issues = validateSetup(currentSetup, dir, cachedExists, projectStructure);
|
|
6568
6673
|
const lostPoints = countIssuePoints(issues);
|
|
6569
6674
|
if (lostPoints < bestLostPoints) {
|
|
6570
6675
|
bestSetup = currentSetup;
|
|
6571
6676
|
bestLostPoints = lostPoints;
|
|
6572
6677
|
}
|
|
6573
|
-
if (
|
|
6678
|
+
if (lostPoints === 0) {
|
|
6574
6679
|
if (callbacks?.onStatus) callbacks.onStatus("Setup passes all scoring checks");
|
|
6575
6680
|
return bestSetup;
|
|
6576
6681
|
}
|
|
6682
|
+
const pointIssues = issues.filter((i) => i.pointsLost > 0);
|
|
6683
|
+
const pointIssueNames = pointIssues.map((i) => i.check).join(", ");
|
|
6577
6684
|
if (callbacks?.onStatus) {
|
|
6578
|
-
|
|
6579
|
-
callbacks.onStatus(`Fixing ${issues.length} scoring issue${issues.length === 1 ? "" : "s"}: ${issueNames}...`);
|
|
6685
|
+
callbacks.onStatus(`Fixing ${pointIssues.length} scoring issue${pointIssues.length === 1 ? "" : "s"}: ${pointIssueNames}...`);
|
|
6580
6686
|
}
|
|
6581
6687
|
const refined = await applyTargetedFixes(currentSetup, issues);
|
|
6582
6688
|
if (!refined) {
|
|
@@ -6585,15 +6691,15 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
6585
6691
|
}
|
|
6586
6692
|
sessionHistory.push({
|
|
6587
6693
|
role: "user",
|
|
6588
|
-
content: `Fix scoring issues: ${
|
|
6694
|
+
content: `Fix scoring issues: ${pointIssueNames}`
|
|
6589
6695
|
});
|
|
6590
6696
|
sessionHistory.push({
|
|
6591
6697
|
role: "assistant",
|
|
6592
|
-
content: `Applied scoring fixes for: ${
|
|
6698
|
+
content: `Applied scoring fixes for: ${pointIssueNames}`
|
|
6593
6699
|
});
|
|
6594
6700
|
currentSetup = refined;
|
|
6595
6701
|
}
|
|
6596
|
-
const finalIssues = validateSetup(currentSetup, dir, cachedExists);
|
|
6702
|
+
const finalIssues = validateSetup(currentSetup, dir, cachedExists, projectStructure);
|
|
6597
6703
|
const finalLostPoints = countIssuePoints(finalIssues);
|
|
6598
6704
|
if (finalLostPoints < bestLostPoints) {
|
|
6599
6705
|
bestSetup = currentSetup;
|
|
@@ -6601,10 +6707,19 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
6601
6707
|
return bestSetup;
|
|
6602
6708
|
}
|
|
6603
6709
|
async function applyTargetedFixes(setup, issues) {
|
|
6604
|
-
const { claudeMd, agentsMd } = extractConfigContent(setup);
|
|
6710
|
+
const { claudeMd, agentsMd, cursorrules, skills } = extractConfigContent(setup);
|
|
6605
6711
|
const targets = [];
|
|
6606
6712
|
if (claudeMd) targets.push({ key: "claudeMd", label: "CLAUDE.md", content: claudeMd });
|
|
6607
6713
|
if (agentsMd) targets.push({ key: "agentsMd", label: "AGENTS.md", content: agentsMd });
|
|
6714
|
+
const failingChecks = new Set(issues.map((i) => i.check));
|
|
6715
|
+
if (cursorrules && failingChecks.has("No duplicate content")) {
|
|
6716
|
+
targets.push({ key: "cursorrules", label: ".cursorrules", content: cursorrules });
|
|
6717
|
+
}
|
|
6718
|
+
for (const skill of skills) {
|
|
6719
|
+
if (failingChecks.has(`Skill quality: ${skill.name}`)) {
|
|
6720
|
+
targets.push({ key: `skill:${skill.name}`, label: `Skill: ${skill.name}`, content: skill.content });
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6608
6723
|
if (targets.length === 0) return null;
|
|
6609
6724
|
const feedbackMessage = buildFeedbackMessage(issues);
|
|
6610
6725
|
const contentBlock = targets.map(
|
|
@@ -6621,11 +6736,12 @@ ${t.content}
|
|
|
6621
6736
|
`
|
|
6622
6737
|
Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"${t.key}"`).join(", ")}. Each value is the fixed markdown string. No code fences, no explanations.`
|
|
6623
6738
|
].join("\n");
|
|
6739
|
+
const maxTokens = Math.min(12e3, 4e3 + targets.length * 2e3);
|
|
6624
6740
|
try {
|
|
6625
6741
|
const raw = await llmCall({
|
|
6626
|
-
system: "You fix scoring issues in AI agent configuration files. Return only a JSON object with the fixed content \u2014 no explanations, no code fences.",
|
|
6742
|
+
system: "You fix scoring issues in AI agent configuration files. You may receive CLAUDE.md, AGENTS.md, .cursorrules, and/or skill files. Return only a JSON object with the fixed content \u2014 no explanations, no code fences.",
|
|
6627
6743
|
prompt,
|
|
6628
|
-
maxTokens
|
|
6744
|
+
maxTokens
|
|
6629
6745
|
});
|
|
6630
6746
|
const cleaned = stripMarkdownFences(raw);
|
|
6631
6747
|
const jsonStart = cleaned.indexOf("{");
|
|
@@ -6633,9 +6749,28 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
|
|
|
6633
6749
|
const fixes = JSON.parse(jsonToParse);
|
|
6634
6750
|
const patched = structuredClone(setup);
|
|
6635
6751
|
for (const target of targets) {
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6752
|
+
const fixedValue = fixes[target.key];
|
|
6753
|
+
if (typeof fixedValue !== "string" || fixedValue.length < 50) continue;
|
|
6754
|
+
if (target.key === "claudeMd") {
|
|
6755
|
+
const parent = patched.claude;
|
|
6756
|
+
if (parent) parent.claudeMd = fixedValue;
|
|
6757
|
+
} else if (target.key === "agentsMd") {
|
|
6758
|
+
const parent = patched.codex;
|
|
6759
|
+
if (parent) parent.agentsMd = fixedValue;
|
|
6760
|
+
} else if (target.key === "cursorrules") {
|
|
6761
|
+
const parent = patched.cursor;
|
|
6762
|
+
if (parent) parent.cursorrules = fixedValue;
|
|
6763
|
+
} else if (target.key.startsWith("skill:")) {
|
|
6764
|
+
const skillName = target.key.slice(6);
|
|
6765
|
+
for (const platform of ["claude", "cursor", "codex"]) {
|
|
6766
|
+
const platformObj = patched[platform];
|
|
6767
|
+
const platformSkills = platformObj?.skills;
|
|
6768
|
+
const skill = platformSkills?.find((s) => s.name === skillName);
|
|
6769
|
+
if (skill) {
|
|
6770
|
+
skill.content = fixedValue;
|
|
6771
|
+
break;
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6639
6774
|
}
|
|
6640
6775
|
}
|
|
6641
6776
|
return patched;
|
package/package.json
CHANGED