@rely-ai/caliber 1.37.0-dev.1774893636 → 1.37.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.
Files changed (2) hide show
  1. package/dist/bin.js +319 -128
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2352,6 +2352,55 @@ function isRateLimitError(stderr) {
2352
2352
  return /rate limit|too many requests|429/i.test(stderr);
2353
2353
  }
2354
2354
 
2355
+ // src/llm/utils.ts
2356
+ function extractJson(text) {
2357
+ const startIdx = text.search(/[[{]/);
2358
+ if (startIdx === -1) return null;
2359
+ let depth = 0;
2360
+ let inString = false;
2361
+ let escaped = false;
2362
+ for (let i = startIdx; i < text.length; i++) {
2363
+ const ch = text[i];
2364
+ if (escaped) {
2365
+ escaped = false;
2366
+ continue;
2367
+ }
2368
+ if (ch === "\\" && inString) {
2369
+ escaped = true;
2370
+ continue;
2371
+ }
2372
+ if (ch === '"') {
2373
+ inString = !inString;
2374
+ continue;
2375
+ }
2376
+ if (inString) continue;
2377
+ if (ch === "{" || ch === "[") depth++;
2378
+ if (ch === "}" || ch === "]") {
2379
+ depth--;
2380
+ if (depth === 0) return text.slice(startIdx, i + 1);
2381
+ }
2382
+ }
2383
+ return null;
2384
+ }
2385
+ function stripMarkdownFences(text) {
2386
+ return text.replace(/^```(?:json)?\s*/im, "").replace(/```\s*$/m, "").trim();
2387
+ }
2388
+ function parseJsonResponse(raw) {
2389
+ const cleaned = stripMarkdownFences(raw);
2390
+ try {
2391
+ return JSON.parse(cleaned);
2392
+ } catch {
2393
+ }
2394
+ const json = extractJson(cleaned);
2395
+ if (!json) {
2396
+ throw new Error(`No JSON found in LLM response: ${raw.slice(0, 200)}`);
2397
+ }
2398
+ return JSON.parse(json);
2399
+ }
2400
+ function estimateTokens(text) {
2401
+ return Math.ceil(text.length / 4);
2402
+ }
2403
+
2355
2404
  // src/llm/cursor-acp.ts
2356
2405
  var AGENT_BIN = "agent";
2357
2406
  var IS_WINDOWS = process.platform === "win32";
@@ -2376,12 +2425,33 @@ var CursorAcpProvider = class {
2376
2425
  async call(options) {
2377
2426
  const prompt = this.buildPrompt(options);
2378
2427
  const model = options.model || this.defaultModel;
2379
- return this.runPrint(model, prompt);
2428
+ const result = await this.runPrint(model, prompt);
2429
+ trackUsage(model, {
2430
+ inputTokens: estimateTokens(prompt),
2431
+ outputTokens: estimateTokens(result)
2432
+ });
2433
+ return result;
2380
2434
  }
2381
2435
  async stream(options, callbacks) {
2382
2436
  const prompt = this.buildPrompt(options);
2383
2437
  const model = options.model || this.defaultModel;
2384
- return this.runPrintStream(model, prompt, callbacks);
2438
+ const inputEstimate = estimateTokens(prompt);
2439
+ let outputChars = 0;
2440
+ const wrappedCallbacks = {
2441
+ onText: (text) => {
2442
+ outputChars += text.length;
2443
+ callbacks.onText(text);
2444
+ },
2445
+ onEnd: (meta) => {
2446
+ trackUsage(model, {
2447
+ inputTokens: inputEstimate,
2448
+ outputTokens: Math.ceil(outputChars / 4)
2449
+ });
2450
+ callbacks.onEnd(meta);
2451
+ },
2452
+ onError: (err) => callbacks.onError(err)
2453
+ };
2454
+ return this.runPrintStream(model, prompt, wrappedCallbacks);
2385
2455
  }
2386
2456
  /**
2387
2457
  * Pre-spawn an agent process so it's ready when the first call comes.
@@ -2473,7 +2543,11 @@ var CursorAcpProvider = class {
2473
2543
  this.killWithEscalation(child);
2474
2544
  if (!settled) {
2475
2545
  settled = true;
2476
- reject(new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`));
2546
+ reject(
2547
+ new Error(
2548
+ `Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`
2549
+ )
2550
+ );
2477
2551
  }
2478
2552
  }, this.timeoutMs);
2479
2553
  timer.unref();
@@ -2509,7 +2583,9 @@ var CursorAcpProvider = class {
2509
2583
  this.killWithEscalation(child);
2510
2584
  if (!settled) {
2511
2585
  settled = true;
2512
- const err = new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`);
2586
+ const err = new Error(
2587
+ `Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`
2588
+ );
2513
2589
  callbacks.onError(err);
2514
2590
  reject(err);
2515
2591
  }
@@ -2616,7 +2692,10 @@ function isCursorAgentAvailable() {
2616
2692
  }
2617
2693
  function isCursorLoggedIn() {
2618
2694
  try {
2619
- const result = execSync5(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
2695
+ const result = execSync5(`${AGENT_BIN} status`, {
2696
+ stdio: ["ignore", "pipe", "ignore"],
2697
+ timeout: 5e3
2698
+ });
2620
2699
  return !result.toString().includes("not logged in");
2621
2700
  } catch {
2622
2701
  return false;
@@ -2653,20 +2732,29 @@ var ClaudeCliProvider = class {
2653
2732
  }
2654
2733
  async call(options) {
2655
2734
  const combined = this.buildCombinedPrompt(options);
2656
- return this.runClaudePrint(combined, options.model);
2735
+ const result = await this.runClaudePrint(combined, options.model);
2736
+ trackUsage(options.model || this.defaultModel, {
2737
+ inputTokens: estimateTokens(combined),
2738
+ outputTokens: estimateTokens(result)
2739
+ });
2740
+ return result;
2657
2741
  }
2658
2742
  async stream(options, callbacks) {
2659
2743
  const combined = this.buildCombinedPrompt(options);
2744
+ const inputEstimate = estimateTokens(combined);
2660
2745
  const args = ["-p"];
2661
2746
  if (options.model) args.push("--model", options.model);
2662
2747
  const child = spawnClaude(args);
2663
2748
  child.stdin.end(combined);
2664
2749
  let settled = false;
2750
+ let outputChars = 0;
2665
2751
  const chunks = [];
2666
2752
  const stderrChunks = [];
2667
2753
  child.stdout.on("data", (chunk) => {
2668
2754
  chunks.push(chunk);
2669
- callbacks.onText(chunk.toString("utf-8"));
2755
+ const text = chunk.toString("utf-8");
2756
+ outputChars += text.length;
2757
+ callbacks.onText(text);
2670
2758
  });
2671
2759
  child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
2672
2760
  const timer = setTimeout(() => {
@@ -2692,6 +2780,11 @@ var ClaudeCliProvider = class {
2692
2780
  if (settled) return;
2693
2781
  settled = true;
2694
2782
  if (code === 0) {
2783
+ const model = options.model || this.defaultModel;
2784
+ trackUsage(model, {
2785
+ inputTokens: inputEstimate,
2786
+ outputTokens: Math.ceil(outputChars / 4)
2787
+ });
2695
2788
  callbacks.onEnd({ stopReason: "end_turn" });
2696
2789
  } else {
2697
2790
  const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
@@ -2766,55 +2859,6 @@ function isClaudeCliAvailable() {
2766
2859
  }
2767
2860
  }
2768
2861
 
2769
- // src/llm/utils.ts
2770
- function extractJson(text) {
2771
- const startIdx = text.search(/[[{]/);
2772
- if (startIdx === -1) return null;
2773
- let depth = 0;
2774
- let inString = false;
2775
- let escaped = false;
2776
- for (let i = startIdx; i < text.length; i++) {
2777
- const ch = text[i];
2778
- if (escaped) {
2779
- escaped = false;
2780
- continue;
2781
- }
2782
- if (ch === "\\" && inString) {
2783
- escaped = true;
2784
- continue;
2785
- }
2786
- if (ch === '"') {
2787
- inString = !inString;
2788
- continue;
2789
- }
2790
- if (inString) continue;
2791
- if (ch === "{" || ch === "[") depth++;
2792
- if (ch === "}" || ch === "]") {
2793
- depth--;
2794
- if (depth === 0) return text.slice(startIdx, i + 1);
2795
- }
2796
- }
2797
- return null;
2798
- }
2799
- function stripMarkdownFences(text) {
2800
- return text.replace(/^```(?:json)?\s*/im, "").replace(/```\s*$/m, "").trim();
2801
- }
2802
- function parseJsonResponse(raw) {
2803
- const cleaned = stripMarkdownFences(raw);
2804
- try {
2805
- return JSON.parse(cleaned);
2806
- } catch {
2807
- }
2808
- const json = extractJson(cleaned);
2809
- if (!json) {
2810
- throw new Error(`No JSON found in LLM response: ${raw.slice(0, 200)}`);
2811
- }
2812
- return JSON.parse(json);
2813
- }
2814
- function estimateTokens(text) {
2815
- return Math.ceil(text.length / 4);
2816
- }
2817
-
2818
2862
  // src/llm/model-recovery.ts
2819
2863
  init_config();
2820
2864
  init_resolve_caliber();
@@ -4761,9 +4805,25 @@ function isTransientError2(error) {
4761
4805
  async function generateSetup(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks, options) {
4762
4806
  const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95 || options?.forceTargetedFix;
4763
4807
  if (isTargetedFix) {
4764
- return generateMonolithic(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks);
4808
+ return generateMonolithic(
4809
+ fingerprint,
4810
+ targetAgent,
4811
+ prompt,
4812
+ callbacks,
4813
+ failingChecks,
4814
+ currentScore,
4815
+ passingChecks
4816
+ );
4765
4817
  }
4766
- const coreResult = await generateCore(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks);
4818
+ const coreResult = await generateCore(
4819
+ fingerprint,
4820
+ targetAgent,
4821
+ prompt,
4822
+ callbacks,
4823
+ failingChecks,
4824
+ currentScore,
4825
+ passingChecks
4826
+ );
4767
4827
  if (!coreResult.setup) {
4768
4828
  return coreResult;
4769
4829
  }
@@ -4812,6 +4872,7 @@ function mergeSkillResults(results, setup) {
4812
4872
  }
4813
4873
  return { succeeded, failed };
4814
4874
  }
4875
+ var MAX_SKILL_TOPICS = 5;
4815
4876
  function collectSkillTopics(setup, targetAgent, fingerprint) {
4816
4877
  const topics = [];
4817
4878
  for (const platform of ["claude", "codex", "opencode", "cursor"]) {
@@ -4832,12 +4893,18 @@ function collectSkillTopics(setup, targetAgent, fingerprint) {
4832
4893
  delete platformObj.skillTopics;
4833
4894
  }
4834
4895
  }
4835
- return topics;
4896
+ return topics.slice(0, MAX_SKILL_TOPICS);
4836
4897
  }
4837
4898
  function getDefaultSkillTopics(fingerprint) {
4838
4899
  const topics = [
4839
- { name: "development-workflow", description: "Development setup and common workflows. Use when starting development, running the project, or setting up the environment." },
4840
- { name: "testing-guide", description: "Testing patterns and commands. Use when writing tests, running test suites, or debugging test failures." }
4900
+ {
4901
+ name: "development-workflow",
4902
+ description: "Development setup and common workflows. Use when starting development, running the project, or setting up the environment."
4903
+ },
4904
+ {
4905
+ name: "testing-guide",
4906
+ description: "Testing patterns and commands. Use when writing tests, running test suites, or debugging test failures."
4907
+ }
4841
4908
  ];
4842
4909
  if (fingerprint.frameworks.length > 0) {
4843
4910
  topics.push({
@@ -4855,8 +4922,10 @@ function getDefaultSkillTopics(fingerprint) {
4855
4922
  function buildSkillContext(fingerprint, setup, allDeps) {
4856
4923
  const parts = [];
4857
4924
  if (fingerprint.packageName) parts.push(`Project: ${fingerprint.packageName}`);
4858
- if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
4859
- if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
4925
+ if (fingerprint.languages.length > 0)
4926
+ parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
4927
+ if (fingerprint.frameworks.length > 0)
4928
+ parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
4860
4929
  const claude = setup.claude;
4861
4930
  const claudeMd = claude?.claudeMd;
4862
4931
  if (claudeMd) {
@@ -4943,7 +5012,9 @@ async function streamGeneration(config) {
4943
5012
  }
4944
5013
  }
4945
5014
  sentStatuses = completedLines.length;
4946
- const jsonStartMatch = preJsonBuffer.match(/(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/);
5015
+ const jsonStartMatch = preJsonBuffer.match(
5016
+ /(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/
5017
+ );
4947
5018
  if (jsonStartMatch) {
4948
5019
  const matchIndex = preJsonBuffer.indexOf("{", jsonStartMatch.index);
4949
5020
  inJson = true;
@@ -4958,7 +5029,9 @@ async function streamGeneration(config) {
4958
5029
  let setup = null;
4959
5030
  let jsonToParse = (jsonContent || preJsonBuffer).replace(/```\s*$/g, "").trim();
4960
5031
  if (!jsonContent && preJsonBuffer) {
4961
- const fallbackMatch = preJsonBuffer.match(/(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/);
5032
+ const fallbackMatch = preJsonBuffer.match(
5033
+ /(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/
5034
+ );
4962
5035
  if (fallbackMatch) {
4963
5036
  const matchIndex = preJsonBuffer.indexOf("{", fallbackMatch.index);
4964
5037
  jsonToParse = preJsonBuffer.slice(matchIndex).replace(/```\s*$/g, "").trim();
@@ -4969,7 +5042,10 @@ async function streamGeneration(config) {
4969
5042
  } catch {
4970
5043
  }
4971
5044
  if (!setup && stopReason === "max_tokens" && attempt < MAX_RETRIES2) {
4972
- if (config.callbacks) config.callbacks.onStatus("Output was truncated, retrying with higher token limit...");
5045
+ if (config.callbacks)
5046
+ config.callbacks.onStatus(
5047
+ "Output was truncated, retrying with higher token limit..."
5048
+ );
4973
5049
  setTimeout(() => attemptGeneration().then(resolve3), 1e3);
4974
5050
  return;
4975
5051
  }
@@ -4982,12 +5058,18 @@ async function streamGeneration(config) {
4982
5058
  if (config.callbacks) config.callbacks.onComplete(setup, explanation);
4983
5059
  resolve3({ setup, explanation, stopReason: stopReason ?? void 0 });
4984
5060
  } else {
4985
- resolve3({ setup: null, explanation, raw: preJsonBuffer, stopReason: stopReason ?? void 0 });
5061
+ resolve3({
5062
+ setup: null,
5063
+ explanation,
5064
+ raw: preJsonBuffer,
5065
+ stopReason: stopReason ?? void 0
5066
+ });
4986
5067
  }
4987
5068
  },
4988
5069
  onError: (error) => {
4989
5070
  if (isTransientError2(error) && attempt < MAX_RETRIES2) {
4990
- if (config.callbacks) config.callbacks.onStatus("Connection interrupted, retrying...");
5071
+ if (config.callbacks)
5072
+ config.callbacks.onStatus("Connection interrupted, retrying...");
4991
5073
  setTimeout(() => attemptGeneration().then(resolve3), 2e3);
4992
5074
  return;
4993
5075
  }
@@ -5004,7 +5086,14 @@ async function streamGeneration(config) {
5004
5086
  return attemptGeneration();
5005
5087
  }
5006
5088
  async function generateCore(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks) {
5007
- const userMessage = buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks);
5089
+ const userMessage = buildGeneratePrompt(
5090
+ fingerprint,
5091
+ targetAgent,
5092
+ prompt,
5093
+ failingChecks,
5094
+ currentScore,
5095
+ passingChecks
5096
+ );
5008
5097
  return streamGeneration({
5009
5098
  systemPrompt: CORE_GENERATION_PROMPT,
5010
5099
  userMessage,
@@ -5015,7 +5104,14 @@ async function generateCore(fingerprint, targetAgent, prompt, callbacks, failing
5015
5104
  });
5016
5105
  }
5017
5106
  async function generateMonolithic(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks) {
5018
- const userMessage = buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks);
5107
+ const userMessage = buildGeneratePrompt(
5108
+ fingerprint,
5109
+ targetAgent,
5110
+ prompt,
5111
+ failingChecks,
5112
+ currentScore,
5113
+ passingChecks
5114
+ );
5019
5115
  return streamGeneration({
5020
5116
  systemPrompt: GENERATION_SYSTEM_PROMPT,
5021
5117
  userMessage,
@@ -5096,9 +5192,11 @@ function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, cu
5096
5192
  const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
5097
5193
  if (isTargetedFix) {
5098
5194
  parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
5099
- parts.push(`
5195
+ parts.push(
5196
+ `
5100
5197
  The existing config is already high quality. ONLY fix these specific failing checks:
5101
- `);
5198
+ `
5199
+ );
5102
5200
  for (const check of failingChecks) {
5103
5201
  if (check.fix) {
5104
5202
  parts.push(`- **${check.name}**`);
@@ -5129,15 +5227,19 @@ IMPORTANT RULES FOR TARGETED FIX:
5129
5227
  - For grounding issues: Add references to the listed project directories in the appropriate sections.
5130
5228
  - Every path or name you reference MUST exist in the project \u2014 use the file tree provided below.`);
5131
5229
  } else if (hasExistingConfigs) {
5132
- parts.push(`Audit and improve the existing coding agent configuration for target: ${targetAgent}`);
5230
+ parts.push(
5231
+ `Audit and improve the existing coding agent configuration for target: ${targetAgent}`
5232
+ );
5133
5233
  } else {
5134
5234
  parts.push(`Generate an initial coding agent configuration for target: ${targetAgent}`);
5135
5235
  }
5136
5236
  if (fingerprint.gitRemoteUrl) parts.push(`
5137
5237
  Git remote: ${fingerprint.gitRemoteUrl}`);
5138
5238
  if (fingerprint.packageName) parts.push(`Package name: ${fingerprint.packageName}`);
5139
- if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
5140
- if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
5239
+ if (fingerprint.languages.length > 0)
5240
+ parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
5241
+ if (fingerprint.frameworks.length > 0)
5242
+ parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
5141
5243
  if (fingerprint.description) parts.push(`Project description: ${fingerprint.description}`);
5142
5244
  if (fingerprint.fileTree.length > 0) {
5143
5245
  const caPaths = fingerprint.codeAnalysis?.files.map((f) => f.path) ?? [];
@@ -5146,36 +5248,52 @@ Git remote: ${fingerprint.gitRemoteUrl}`);
5146
5248
  File tree (${tree.length}/${fingerprint.fileTree.length}):
5147
5249
  ${tree.join("\n")}`);
5148
5250
  }
5149
- if (existing.claudeMd) parts.push(`
5251
+ if (existing.claudeMd)
5252
+ parts.push(
5253
+ `
5150
5254
  Existing CLAUDE.md:
5151
- ${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5152
- if (existing.agentsMd) parts.push(`
5255
+ ${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5256
+ );
5257
+ if (existing.agentsMd)
5258
+ parts.push(
5259
+ `
5153
5260
  Existing AGENTS.md:
5154
- ${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5155
- if (existing.readmeMd) parts.push(`
5261
+ ${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5262
+ );
5263
+ if (existing.readmeMd)
5264
+ parts.push(
5265
+ `
5156
5266
  Existing README.md:
5157
- ${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5267
+ ${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5268
+ );
5158
5269
  if (existing.claudeSkills?.length) {
5159
5270
  parts.push("\n--- Existing Claude Skills ---");
5160
5271
  for (const skill of existing.claudeSkills.slice(0, LIMITS.SKILLS_MAX)) {
5161
- parts.push(`
5272
+ parts.push(
5273
+ `
5162
5274
  [.claude/skills/${skill.filename}]
5163
- ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5275
+ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`
5276
+ );
5164
5277
  }
5165
5278
  if (existing.claudeSkills.length > LIMITS.SKILLS_MAX) {
5166
5279
  parts.push(`
5167
5280
  (${existing.claudeSkills.length - LIMITS.SKILLS_MAX} more skills omitted)`);
5168
5281
  }
5169
5282
  }
5170
- if (existing.cursorrules) parts.push(`
5283
+ if (existing.cursorrules)
5284
+ parts.push(
5285
+ `
5171
5286
  Existing .cursorrules:
5172
- ${truncate(existing.cursorrules, LIMITS.EXISTING_CONFIG_CHARS)}`);
5287
+ ${truncate(existing.cursorrules, LIMITS.EXISTING_CONFIG_CHARS)}`
5288
+ );
5173
5289
  if (existing.cursorRules?.length) {
5174
5290
  parts.push("\n--- Existing Cursor Rules ---");
5175
5291
  for (const rule of existing.cursorRules.slice(0, LIMITS.RULES_MAX)) {
5176
- parts.push(`
5292
+ parts.push(
5293
+ `
5177
5294
  [.cursor/rules/${rule.filename}]
5178
- ${truncate(rule.content, LIMITS.SKILL_CHARS)}`);
5295
+ ${truncate(rule.content, LIMITS.SKILL_CHARS)}`
5296
+ );
5179
5297
  }
5180
5298
  if (existing.cursorRules.length > LIMITS.RULES_MAX) {
5181
5299
  parts.push(`
@@ -5185,9 +5303,11 @@ ${truncate(rule.content, LIMITS.SKILL_CHARS)}`);
5185
5303
  if (existing.cursorSkills?.length) {
5186
5304
  parts.push("\n--- Existing Cursor Skills ---");
5187
5305
  for (const skill of existing.cursorSkills.slice(0, LIMITS.SKILLS_MAX)) {
5188
- parts.push(`
5306
+ parts.push(
5307
+ `
5189
5308
  [.cursor/skills/${skill.name}/SKILL.md]
5190
- ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5309
+ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`
5310
+ );
5191
5311
  }
5192
5312
  if (existing.cursorSkills.length > LIMITS.SKILLS_MAX) {
5193
5313
  parts.push(`
@@ -5195,9 +5315,11 @@ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5195
5315
  }
5196
5316
  }
5197
5317
  if (existing.personalLearnings) {
5198
- parts.push(`
5318
+ parts.push(
5319
+ `
5199
5320
  --- Personal Learnings (developer-specific, include in generated configs) ---
5200
- ${existing.personalLearnings}`);
5321
+ ${existing.personalLearnings}`
5322
+ );
5201
5323
  }
5202
5324
  const allDeps = extractAllDeps(process.cwd());
5203
5325
  if (allDeps.length > 0) {
@@ -5263,7 +5385,10 @@ import fs12 from "fs";
5263
5385
  import path12 from "path";
5264
5386
  function writeClaudeConfig(config) {
5265
5387
  const written = [];
5266
- fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
5388
+ fs12.writeFileSync(
5389
+ "CLAUDE.md",
5390
+ appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd)))
5391
+ );
5267
5392
  written.push("CLAUDE.md");
5268
5393
  if (config.skills?.length) {
5269
5394
  for (const skill of config.skills) {
@@ -7864,10 +7989,13 @@ async function searchSkills(fingerprint, targetPlatforms, onStatus) {
7864
7989
  if (!newCandidates.length) {
7865
7990
  return { results: [], contentMap: /* @__PURE__ */ new Map() };
7866
7991
  }
7867
- onStatus?.(`Scoring ${newCandidates.length} candidates...`);
7868
7992
  let results;
7869
7993
  const config = loadConfig();
7870
- if (config) {
7994
+ if (newCandidates.length <= 5) {
7995
+ onStatus?.("Few candidates \u2014 skipping scoring");
7996
+ results = newCandidates.map((c) => ({ ...c, score: 70 }));
7997
+ } else if (config) {
7998
+ onStatus?.(`Scoring ${newCandidates.length} candidates...`);
7871
7999
  try {
7872
8000
  const projectContext = buildProjectContext(fingerprint, targetPlatforms);
7873
8001
  results = await scoreWithLLM(newCandidates, projectContext, technologies);
@@ -8286,7 +8414,8 @@ function printSkills(recs) {
8286
8414
  // src/ai/score-refine.ts
8287
8415
  import { existsSync as existsSync9 } from "fs";
8288
8416
  import ora2 from "ora";
8289
- var MAX_REFINE_ITERATIONS = 2;
8417
+ var MAX_REFINE_ITERATIONS = 1;
8418
+ var MIN_POINTS_TO_REFINE = 4;
8290
8419
  function extractConfigContent(setup) {
8291
8420
  const claude = setup.claude;
8292
8421
  const codex = setup.codex;
@@ -8296,7 +8425,11 @@ function extractConfigContent(setup) {
8296
8425
  cursorrules = cursor.cursorrules;
8297
8426
  }
8298
8427
  const skills = [];
8299
- for (const [platform, obj] of [["claude", claude], ["codex", codex], ["cursor", cursor]]) {
8428
+ for (const [platform, obj] of [
8429
+ ["claude", claude],
8430
+ ["codex", codex],
8431
+ ["cursor", cursor]
8432
+ ]) {
8300
8433
  const platformSkills = obj?.skills;
8301
8434
  if (Array.isArray(platformSkills)) {
8302
8435
  for (const skill of platformSkills) {
@@ -8315,8 +8448,12 @@ function extractConfigContent(setup) {
8315
8448
  }
8316
8449
  function buildGroundingFixInstruction(unmentionedTopDirs, projectStructure) {
8317
8450
  const dirDescriptions = unmentionedTopDirs.slice(0, 8).map((dir) => {
8318
- const subdirs = projectStructure.dirs.filter((d) => d.startsWith(`${dir}/`) && !d.includes("/", dir.length + 1));
8319
- const files = projectStructure.files.filter((f) => f.startsWith(`${dir}/`) && !f.includes("/", dir.length + 1));
8451
+ const subdirs = projectStructure.dirs.filter(
8452
+ (d) => d.startsWith(`${dir}/`) && !d.includes("/", dir.length + 1)
8453
+ );
8454
+ const files = projectStructure.files.filter(
8455
+ (f) => f.startsWith(`${dir}/`) && !f.includes("/", dir.length + 1)
8456
+ );
8320
8457
  const children = [...subdirs.slice(0, 4), ...files.slice(0, 2)];
8321
8458
  const childList = children.map((c) => c.split("/").pop()).join(", ");
8322
8459
  return childList ? `- \`${dir}/\` (contains: ${childList})` : `- \`${dir}/\``;
@@ -8361,7 +8498,9 @@ function validateSetup(setup, dir, checkExists = existsSync9, projectStructure)
8361
8498
  const content = claudeMd ?? agentsMd ?? "";
8362
8499
  if (content) {
8363
8500
  const structure2 = analyzeMarkdownStructure(content);
8364
- const blockThreshold = CODE_BLOCK_THRESHOLDS.find((t) => structure2.codeBlockCount >= t.minBlocks);
8501
+ const blockThreshold = CODE_BLOCK_THRESHOLDS.find(
8502
+ (t) => structure2.codeBlockCount >= t.minBlocks
8503
+ );
8365
8504
  const blockPoints = blockThreshold?.points ?? 0;
8366
8505
  const maxBlockPoints = CODE_BLOCK_THRESHOLDS[0].points;
8367
8506
  if (blockPoints < maxBlockPoints && structure2.codeBlockCount < CODE_BLOCK_THRESHOLDS[0].minBlocks) {
@@ -8486,7 +8625,9 @@ function buildFeedbackMessage(issues) {
8486
8625
  ];
8487
8626
  for (let i = 0; i < issues.length; i++) {
8488
8627
  const issue = issues[i];
8489
- lines.push(`${i + 1}. ${issue.check.toUpperCase()} (-${issue.pointsLost} pts): ${issue.detail}`);
8628
+ lines.push(
8629
+ `${i + 1}. ${issue.check.toUpperCase()} (-${issue.pointsLost} pts): ${issue.detail}`
8630
+ );
8490
8631
  lines.push(` Action: ${issue.fixInstruction}
8491
8632
  `);
8492
8633
  }
@@ -8495,7 +8636,9 @@ function buildFeedbackMessage(issues) {
8495
8636
  function countIssuePoints(issues) {
8496
8637
  return issues.reduce((sum, i) => sum + i.pointsLost, 0);
8497
8638
  }
8498
- async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8639
+ async function scoreAndRefine(setup, dir, sessionHistory, callbacks, options) {
8640
+ const maxIterations = options?.thorough ? 3 : MAX_REFINE_ITERATIONS;
8641
+ const minPoints = options?.thorough ? 1 : MIN_POINTS_TO_REFINE;
8499
8642
  const existsCache = /* @__PURE__ */ new Map();
8500
8643
  const cachedExists = (path43) => {
8501
8644
  const cached = existsCache.get(path43);
@@ -8508,7 +8651,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8508
8651
  let currentSetup = setup;
8509
8652
  let bestSetup = setup;
8510
8653
  let bestLostPoints = Infinity;
8511
- for (let iteration = 0; iteration < MAX_REFINE_ITERATIONS; iteration++) {
8654
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
8512
8655
  const issues = validateSetup(currentSetup, dir, cachedExists, projectStructure);
8513
8656
  const lostPoints = countIssuePoints(issues);
8514
8657
  if (lostPoints < bestLostPoints) {
@@ -8519,10 +8662,16 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8519
8662
  if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
8520
8663
  return bestSetup;
8521
8664
  }
8665
+ if (lostPoints < minPoints) {
8666
+ if (callbacks?.onStatus) callbacks.onStatus("Minor issues \u2014 skipping refinement");
8667
+ return bestSetup;
8668
+ }
8522
8669
  const pointIssues = issues.filter((i) => i.pointsLost > 0);
8523
8670
  const pointIssueNames = pointIssues.map((i) => i.check).join(", ");
8524
8671
  if (callbacks?.onStatus) {
8525
- callbacks.onStatus(`Fixing ${pointIssues.length} scoring issue${pointIssues.length === 1 ? "" : "s"}: ${pointIssueNames}...`);
8672
+ callbacks.onStatus(
8673
+ `Fixing ${pointIssues.length} scoring issue${pointIssues.length === 1 ? "" : "s"}: ${pointIssueNames}...`
8674
+ );
8526
8675
  }
8527
8676
  const refined = await applyTargetedFixes(currentSetup, issues);
8528
8677
  if (!refined) {
@@ -8557,17 +8706,19 @@ async function applyTargetedFixes(setup, issues) {
8557
8706
  }
8558
8707
  for (const skill of skills) {
8559
8708
  if (failingChecks.has(`Skill quality: ${skill.name}`)) {
8560
- targets.push({ key: `skill:${skill.name}`, label: `Skill: ${skill.name}`, content: skill.content });
8709
+ targets.push({
8710
+ key: `skill:${skill.name}`,
8711
+ label: `Skill: ${skill.name}`,
8712
+ content: skill.content
8713
+ });
8561
8714
  }
8562
8715
  }
8563
8716
  if (targets.length === 0) return null;
8564
8717
  const feedbackMessage = buildFeedbackMessage(issues);
8565
- const contentBlock = targets.map(
8566
- (t) => `### ${t.label}
8718
+ const contentBlock = targets.map((t) => `### ${t.label}
8567
8719
  \`\`\`markdown
8568
8720
  ${t.content}
8569
- \`\`\``
8570
- ).join("\n\n");
8721
+ \`\`\``).join("\n\n");
8571
8722
  const prompt = [
8572
8723
  "Here are the config files with scoring issues:\n",
8573
8724
  contentBlock,
@@ -8618,14 +8769,20 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
8618
8769
  return null;
8619
8770
  }
8620
8771
  }
8621
- async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
8772
+ async function runScoreRefineWithSpinner(setup, dir, sessionHistory, options) {
8622
8773
  const spinner = ora2("Validating config against scoring criteria...").start();
8623
8774
  try {
8624
- const refined = await scoreAndRefine(setup, dir, sessionHistory, {
8625
- onStatus: (msg) => {
8626
- spinner.text = msg;
8627
- }
8628
- });
8775
+ const refined = await scoreAndRefine(
8776
+ setup,
8777
+ dir,
8778
+ sessionHistory,
8779
+ {
8780
+ onStatus: (msg) => {
8781
+ spinner.text = msg;
8782
+ }
8783
+ },
8784
+ options
8785
+ );
8629
8786
  if (refined !== setup) {
8630
8787
  spinner.succeed("Config refined based on scoring feedback");
8631
8788
  } else {
@@ -9365,6 +9522,7 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
9365
9522
  // src/commands/init-display.ts
9366
9523
  import chalk12 from "chalk";
9367
9524
  import fs31 from "fs";
9525
+ init_types();
9368
9526
  function formatWhatChanged(setup) {
9369
9527
  const lines = [];
9370
9528
  const claude = setup.claude;
@@ -9534,7 +9692,11 @@ function displayTokenUsage() {
9534
9692
  console.log(chalk12.dim(" Token tracking not available for this provider.\n"));
9535
9693
  return;
9536
9694
  }
9537
- console.log(chalk12.bold(" Token usage:\n"));
9695
+ const config = loadConfig();
9696
+ const isEstimated = config != null && isSeatBased(config.provider);
9697
+ const label = isEstimated ? "Estimated token usage:" : "Token usage:";
9698
+ console.log(chalk12.bold(` ${label}
9699
+ `));
9538
9700
  let totalIn = 0;
9539
9701
  let totalOut = 0;
9540
9702
  for (const m of summary) {
@@ -9552,6 +9714,9 @@ function displayTokenUsage() {
9552
9714
  ` ${chalk12.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`
9553
9715
  );
9554
9716
  }
9717
+ if (isEstimated) {
9718
+ console.log(chalk12.dim(" (Estimated from character count)"));
9719
+ }
9555
9720
  console.log("");
9556
9721
  }
9557
9722
 
@@ -10233,9 +10398,15 @@ async function initCommand(options) {
10233
10398
  content: summarizeSetup("Initial generation", generatedSetup)
10234
10399
  });
10235
10400
  try {
10236
- const refined = await scoreAndRefine(generatedSetup, process.cwd(), sessionHistory, {
10237
- onStatus: (msg) => display.update(TASK_SCORE_REFINE, "running", msg)
10238
- });
10401
+ const refined = await scoreAndRefine(
10402
+ generatedSetup,
10403
+ process.cwd(),
10404
+ sessionHistory,
10405
+ {
10406
+ onStatus: (msg) => display.update(TASK_SCORE_REFINE, "running", msg)
10407
+ },
10408
+ { thorough: options.thorough }
10409
+ );
10239
10410
  if (refined !== generatedSetup) {
10240
10411
  display.update(TASK_SCORE_REFINE, "done", "Refined");
10241
10412
  generatedSetup = refined;
@@ -11354,7 +11525,8 @@ function detectSyncedAgents(writtenFiles) {
11354
11525
  const joined = writtenFiles.join(" ");
11355
11526
  if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
11356
11527
  if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
11357
- if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
11528
+ if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/"))
11529
+ agents.push("Copilot");
11358
11530
  if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
11359
11531
  return agents;
11360
11532
  }
@@ -13426,9 +13598,7 @@ async function uninstallCommand(options) {
13426
13598
 
13427
13599
  // src/cli.ts
13428
13600
  var __dirname = path41.dirname(fileURLToPath(import.meta.url));
13429
- var pkg = JSON.parse(
13430
- fs50.readFileSync(path41.resolve(__dirname, "..", "package.json"), "utf-8")
13431
- );
13601
+ var pkg = JSON.parse(fs50.readFileSync(path41.resolve(__dirname, "..", "package.json"), "utf-8"));
13432
13602
  var program = new Command();
13433
13603
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
13434
13604
  program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI context infrastructure for coding agents").version(displayVersion).option("--no-traces", "Disable anonymous telemetry for this run");
@@ -13483,22 +13653,38 @@ function parseAgentOption(value) {
13483
13653
  if (value === "both") return ["claude", "cursor"];
13484
13654
  if (value === "all") return ["claude", "cursor", "codex", "opencode", "github-copilot"];
13485
13655
  const valid = ["claude", "cursor", "codex", "opencode", "github-copilot"];
13486
- const agents = [...new Set(value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a)))];
13656
+ const agents = [
13657
+ ...new Set(
13658
+ value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a))
13659
+ )
13660
+ ];
13487
13661
  if (agents.length === 0) {
13488
- console.error(`Invalid agent "${value}". Choose from: claude, cursor, codex, opencode, github-copilot (comma-separated for multiple)`);
13662
+ console.error(
13663
+ `Invalid agent "${value}". Choose from: claude, cursor, codex, opencode, github-copilot (comma-separated for multiple)`
13664
+ );
13489
13665
  process.exit(1);
13490
13666
  }
13491
13667
  return agents;
13492
13668
  }
13493
- program.command("init").description("Initialize your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot", parseAgentOption).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").action(tracked("init", initCommand));
13494
- program.command("bootstrap").description("Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init").action(tracked("bootstrap", bootstrapCommand));
13669
+ program.command("init").description("Initialize your project for AI-assisted development").option(
13670
+ "--agent <type>",
13671
+ "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot",
13672
+ parseAgentOption
13673
+ ).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").option("--thorough", "Deep analysis \u2014 more refinement passes for maximum quality").action(tracked("init", initCommand));
13674
+ program.command("bootstrap").description(
13675
+ "Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init"
13676
+ ).action(tracked("bootstrap", bootstrapCommand));
13495
13677
  program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
13496
13678
  program.command("uninstall").description("Remove all Caliber resources from this project").option("--force", "Skip confirmation prompt").action(tracked("uninstall", (options) => uninstallCommand(options)));
13497
13679
  program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
13498
13680
  program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate config").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
13499
13681
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
13500
13682
  program.command("skills").description("Discover and install community skills for your project").option("--query <terms>", 'Search for skills by topic (e.g. "react frontend")').option("--install <slugs>", "Install specific skills by slug (comma-separated)").action(tracked("skills", recommendCommand));
13501
- program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
13683
+ program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option(
13684
+ "--agent <type>",
13685
+ "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot",
13686
+ parseAgentOption
13687
+ ).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
13502
13688
  program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(tracked("refresh", refreshCommand));
13503
13689
  program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(tracked("hooks", hooksCommand));
13504
13690
  program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
@@ -13509,7 +13695,12 @@ sources.command("remove").description("Remove a configured source").argument("<n
13509
13695
  program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
13510
13696
  var learn = program.command("learn").description("Manage session learning \u2014 extract patterns from your AI coding sessions");
13511
13697
  learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").option("--prompt", "Record a user prompt event").action(tracked("learn:observe", learnObserveCommand));
13512
- learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
13698
+ learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(
13699
+ tracked(
13700
+ "learn:finalize",
13701
+ (opts) => learnFinalizeCommand(opts)
13702
+ )
13703
+ );
13513
13704
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
13514
13705
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
13515
13706
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.37.0-dev.1774893636",
3
+ "version": "1.37.1",
4
4
  "description": "AI context infrastructure for coding agents — keeps CLAUDE.md, Cursor rules, and skills in sync as your codebase evolves",
5
5
  "type": "module",
6
6
  "bin": {