@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.
Files changed (2) hide show
  1. package/dist/bin.js +206 -71
  2. 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 (config?.provider) return DEFAULT_FAST_MODELS[config.provider];
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: "default",
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 = "default";
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
- let duplicatePercent = 0;
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
- const entryLower = entry.toLowerCase();
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 matchesConfig = (name) => {
4791
- const escaped = name.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
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
- let densityPoints = 0;
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 validateSetup(setup, dir, checkExists = existsSync9) {
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 structure = analyzeMarkdownStructure(content);
6491
- const blockThreshold = CODE_BLOCK_THRESHOLDS.find((t) => structure.codeBlockCount >= t.minBlocks);
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 && structure.codeBlockCount < CODE_BLOCK_THRESHOLDS[0].minBlocks) {
6527
+ if (blockPoints < maxBlockPoints && structure2.codeBlockCount < CODE_BLOCK_THRESHOLDS[0].minBlocks) {
6495
6528
  issues.push({
6496
6529
  check: "Executable content",
6497
- detail: `${structure.codeBlockCount} code block${structure.codeBlockCount === 1 ? "" : "s"} (need \u2265${CODE_BLOCK_THRESHOLDS[0].minBlocks} for full points)`,
6498
- fixInstruction: `Add ${CODE_BLOCK_THRESHOLDS[0].minBlocks - structure.codeBlockCount} more code blocks with actual project commands (build, test, lint, deploy).`,
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 (structure.h2Count < 3 || structure.listItemCount < 3) {
6558
+ if (structure2.h2Count < 3 || structure2.listItemCount < 3) {
6526
6559
  const parts = [];
6527
- if (structure.h2Count < 3) parts.push(`add ${3 - structure.h2Count} more ## sections`);
6528
- if (structure.listItemCount < 3) parts.push("use bullet lists for multi-item instructions");
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: `${structure.h2Count} sections, ${structure.listItemCount} list items`,
6564
+ detail: `${structure2.h2Count} sections, ${structure2.listItemCount} list items`,
6532
6565
  fixInstruction: `Improve structure: ${parts.join(" and ")}.`,
6533
- pointsLost: POINTS_HAS_STRUCTURE - ((structure.h2Count >= 3 ? 1 : 0) + (structure.listItemCount >= 3 ? 1 : 0))
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 (issues.length === 0) {
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
- const issueNames = issues.map((i) => i.check).join(", ");
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: ${issues.map((i) => i.check).join(", ")}`
6694
+ content: `Fix scoring issues: ${pointIssueNames}`
6589
6695
  });
6590
6696
  sessionHistory.push({
6591
6697
  role: "assistant",
6592
- content: `Applied scoring fixes for: ${issues.map((i) => i.check).join(", ")}`
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: 8e3
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
- if (typeof fixes[target.key] === "string" && fixes[target.key].length > 50) {
6637
- const parent = target.key === "claudeMd" ? patched.claude : patched.codex;
6638
- if (parent) parent[target.key] = fixes[target.key];
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.21.1",
3
+ "version": "1.22.0-dev.1773745340",
4
4
  "description": "Analyze your codebase and generate optimized AI agent configs (CLAUDE.md, .cursorrules, skills) — no API key needed",
5
5
  "type": "module",
6
6
  "bin": {