@rely-ai/caliber 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +357 -174
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -396,6 +396,7 @@ function readExistingConfigs(dir) {
396
396
  // src/fingerprint/code-analysis.ts
397
397
  import fs3 from "fs";
398
398
  import path3 from "path";
399
+ import { execSync as execSync2 } from "child_process";
399
400
  var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
400
401
  "node_modules",
401
402
  ".git",
@@ -503,59 +504,78 @@ var SKIP_PATTERNS = [
503
504
  /\.generated\./,
504
505
  /\.snap$/
505
506
  ];
506
- var COMMENT_LINE_PATTERNS = {
507
- "c-style": /^\s*\/\//,
508
- // ts, js, go, rs, java, etc.
509
- "hash": /^\s*#/,
510
- // py, sh, yaml, tf, etc.
511
- "html": /^\s*<!--.*-->\s*$/
507
+ var COMMENT_LINE = {
508
+ "c": /^\s*\/\//,
509
+ "h": /^\s*#/,
510
+ "x": /^\s*<!--.*-->\s*$/
512
511
  };
513
- var EXT_TO_COMMENT_STYLE = {
514
- ".ts": "c-style",
515
- ".tsx": "c-style",
516
- ".js": "c-style",
517
- ".jsx": "c-style",
518
- ".mjs": "c-style",
519
- ".cjs": "c-style",
520
- ".go": "c-style",
521
- ".rs": "c-style",
522
- ".java": "c-style",
523
- ".kt": "c-style",
524
- ".scala": "c-style",
525
- ".cs": "c-style",
526
- ".c": "c-style",
527
- ".cpp": "c-style",
528
- ".h": "c-style",
529
- ".hpp": "c-style",
530
- ".swift": "c-style",
531
- ".php": "c-style",
532
- ".py": "hash",
533
- ".pyw": "hash",
534
- ".rb": "hash",
535
- ".sh": "hash",
536
- ".bash": "hash",
537
- ".zsh": "hash",
538
- ".fish": "hash",
539
- ".r": "hash",
540
- ".tf": "hash",
541
- ".hcl": "hash",
542
- ".yaml": "hash",
543
- ".yml": "hash",
544
- ".toml": "hash",
545
- ".ini": "hash",
546
- ".cfg": "hash",
547
- ".env": "hash",
548
- ".html": "html",
549
- ".xml": "html",
550
- ".svg": "html",
551
- ".vue": "html",
552
- ".svelte": "html"
512
+ var EXT_COMMENT = {
513
+ ".ts": "c",
514
+ ".tsx": "c",
515
+ ".js": "c",
516
+ ".jsx": "c",
517
+ ".mjs": "c",
518
+ ".cjs": "c",
519
+ ".go": "c",
520
+ ".rs": "c",
521
+ ".java": "c",
522
+ ".kt": "c",
523
+ ".scala": "c",
524
+ ".cs": "c",
525
+ ".c": "c",
526
+ ".cpp": "c",
527
+ ".h": "c",
528
+ ".hpp": "c",
529
+ ".swift": "c",
530
+ ".php": "c",
531
+ ".py": "h",
532
+ ".pyw": "h",
533
+ ".rb": "h",
534
+ ".sh": "h",
535
+ ".bash": "h",
536
+ ".zsh": "h",
537
+ ".fish": "h",
538
+ ".r": "h",
539
+ ".tf": "h",
540
+ ".hcl": "h",
541
+ ".yaml": "h",
542
+ ".yml": "h",
543
+ ".toml": "h",
544
+ ".ini": "h",
545
+ ".cfg": "h",
546
+ ".env": "h",
547
+ ".html": "x",
548
+ ".xml": "x",
549
+ ".vue": "x",
550
+ ".svelte": "x"
553
551
  };
552
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
553
+ ".ts",
554
+ ".tsx",
555
+ ".js",
556
+ ".jsx",
557
+ ".mjs",
558
+ ".cjs",
559
+ ".py",
560
+ ".pyw",
561
+ ".go",
562
+ ".rs",
563
+ ".rb",
564
+ ".java",
565
+ ".kt",
566
+ ".scala",
567
+ ".cs",
568
+ ".c",
569
+ ".cpp",
570
+ ".h",
571
+ ".hpp",
572
+ ".swift",
573
+ ".php"
574
+ ]);
554
575
  var TOKEN_BUDGET = 18e4;
555
576
  var CHAR_BUDGET = TOKEN_BUDGET * 4;
556
577
  function compressContent(content, ext) {
557
- const commentStyle = EXT_TO_COMMENT_STYLE[ext];
558
- const commentPattern = commentStyle ? COMMENT_LINE_PATTERNS[commentStyle] : null;
578
+ const cp = EXT_COMMENT[ext] ? COMMENT_LINE[EXT_COMMENT[ext]] : null;
559
579
  const lines = content.split("\n");
560
580
  const result = [];
561
581
  let prevBlank = false;
@@ -577,12 +597,11 @@ function compressContent(content, ext) {
577
597
  continue;
578
598
  }
579
599
  prevBlank = false;
580
- if (commentPattern && commentPattern.test(trimmed)) continue;
581
- const leadingMatch = line.match(/^(\s*)/);
582
- if (leadingMatch) {
583
- const spaces = leadingMatch[1].replace(/\t/g, " ").length;
584
- const normalizedIndent = " ".repeat(Math.floor(spaces / 2) * 2);
585
- result.push(normalizedIndent + line.trimStart().trimEnd());
600
+ if (cp && cp.test(trimmed)) continue;
601
+ const leading = line.match(/^(\s*)/);
602
+ if (leading) {
603
+ const spaces = leading[1].replace(/\t/g, " ").length;
604
+ result.push(" ".repeat(Math.floor(spaces / 2) * 2) + line.trimStart().trimEnd());
586
605
  } else {
587
606
  result.push(trimmed);
588
607
  }
@@ -590,93 +609,255 @@ function compressContent(content, ext) {
590
609
  while (result.length > 0 && result[result.length - 1] === "") result.pop();
591
610
  return result.join("\n");
592
611
  }
593
- function structuralFingerprint(content, ext) {
594
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
595
- const lineCount = lines.length;
596
- const sizeBucket = Math.floor(lineCount / 10) * 10;
597
- const firstLine = lines[0]?.trim().slice(0, 60) || "";
598
- const imports = lines.filter((l) => /^\s*(import |from |require\(|use )/.test(l)).length;
599
- const exports = lines.filter((l) => /^\s*export /.test(l)).length;
600
- const functions = lines.filter((l) => /^\s*(function |def |func |fn |pub fn )/.test(l)).length;
601
- return `${ext}:${sizeBucket}:${imports}:${exports}:${functions}:${firstLine}`;
602
- }
603
- function deduplicateFiles(files) {
604
- const groups = /* @__PURE__ */ new Map();
605
- for (const f of files) {
606
- const fp = structuralFingerprint(f.content, f.ext);
607
- const group = groups.get(fp) || [];
608
- group.push({ path: f.path, content: f.content });
609
- groups.set(fp, group);
612
+ function extractSkeleton(content, ext) {
613
+ if (!SOURCE_EXTENSIONS.has(ext)) return content;
614
+ const lines = content.split("\n");
615
+ const result = [];
616
+ let braceDepth = 0;
617
+ let inSignature = false;
618
+ let skipBody = false;
619
+ if ([".py", ".pyw", ".rb"].includes(ext)) {
620
+ return extractSkeletonIndentBased(lines, ext);
621
+ }
622
+ for (let i = 0; i < lines.length; i++) {
623
+ const line = lines[i];
624
+ const trimmed = line.trim();
625
+ if (/^\s*(import |from |require\(|use |package |module )/.test(line)) {
626
+ result.push(line);
627
+ continue;
628
+ }
629
+ if (/^\s*(export\s+)?(interface|type|enum)\s/.test(trimmed)) {
630
+ result.push(line);
631
+ let depth = 0;
632
+ for (let j = i; j < lines.length; j++) {
633
+ if (j > i) result.push(lines[j]);
634
+ depth += (lines[j].match(/{/g) || []).length;
635
+ depth -= (lines[j].match(/}/g) || []).length;
636
+ if (depth <= 0 && j > i) {
637
+ i = j;
638
+ break;
639
+ }
640
+ }
641
+ continue;
642
+ }
643
+ const isFnOrClass = /^\s*(export\s+)?(default\s+)?(async\s+)?(function|class|const\s+\w+\s*=\s*(async\s*)?\(|pub\s+fn|fn|func)\s/.test(trimmed) || /^\s*(def|func|fn|pub fn|pub async fn)\s/.test(trimmed);
644
+ if (isFnOrClass && braceDepth === 0) {
645
+ result.push(line);
646
+ const opens = (line.match(/{/g) || []).length;
647
+ const closes = (line.match(/}/g) || []).length;
648
+ if (opens > closes) {
649
+ skipBody = true;
650
+ braceDepth = opens - closes;
651
+ result.push(" ".repeat(line.search(/\S/)) + " // ...");
652
+ }
653
+ continue;
654
+ }
655
+ if (skipBody) {
656
+ braceDepth += (line.match(/{/g) || []).length;
657
+ braceDepth -= (line.match(/}/g) || []).length;
658
+ if (braceDepth <= 0) {
659
+ result.push(line);
660
+ skipBody = false;
661
+ braceDepth = 0;
662
+ }
663
+ continue;
664
+ }
665
+ if (braceDepth === 0) {
666
+ result.push(line);
667
+ }
610
668
  }
669
+ return result.join("\n");
670
+ }
671
+ function extractSkeletonIndentBased(lines, ext) {
611
672
  const result = [];
612
- for (const [, group] of groups) {
613
- const representative = group[0];
614
- result.push({
615
- path: representative.path,
616
- content: representative.content,
617
- size: representative.content.length
618
- });
619
- if (group.length > 1) {
620
- const similarPaths = group.slice(1).map((f) => f.path);
621
- const summary = `(${similarPaths.length} similar file${similarPaths.length === 1 ? "" : "s"}: ${similarPaths.join(", ")})`;
622
- result.push({
623
- path: `[similar to ${representative.path}]`,
624
- content: summary,
625
- size: summary.length
626
- });
673
+ let skipIndent = -1;
674
+ for (const line of lines) {
675
+ const trimmed = line.trim();
676
+ if (trimmed.length === 0) continue;
677
+ const indent = line.search(/\S/);
678
+ if (/^(import |from )/.test(trimmed)) {
679
+ result.push(line);
680
+ skipIndent = -1;
681
+ continue;
682
+ }
683
+ if (/^(class |def |async def )/.test(trimmed)) {
684
+ result.push(line);
685
+ skipIndent = indent;
686
+ continue;
687
+ }
688
+ if (trimmed.startsWith("@")) {
689
+ result.push(line);
690
+ skipIndent = -1;
691
+ continue;
692
+ }
693
+ if (skipIndent >= 0 && indent > skipIndent) {
694
+ continue;
695
+ }
696
+ skipIndent = -1;
697
+ result.push(line);
698
+ }
699
+ return result.join("\n");
700
+ }
701
+ function extractImports(content, filePath) {
702
+ const imports = [];
703
+ const dir = path3.dirname(filePath);
704
+ for (const line of content.split("\n")) {
705
+ const trimmed = line.trim();
706
+ const jsMatch = trimmed.match(/(?:from|require\()\s*['"]([^'"]+)['"]/);
707
+ if (jsMatch && jsMatch[1].startsWith(".")) {
708
+ imports.push(path3.normalize(path3.join(dir, jsMatch[1])));
709
+ continue;
710
+ }
711
+ const pyMatch = trimmed.match(/^from\s+(\.[.\w]*)\s+import/);
712
+ if (pyMatch) {
713
+ const modulePath = pyMatch[1].replace(/\./g, "/");
714
+ imports.push(path3.normalize(path3.join(dir, modulePath)));
715
+ continue;
716
+ }
717
+ const goMatch = trimmed.match(/^\s*"([^"]+)"/);
718
+ if (goMatch && !goMatch[1].includes(".")) {
719
+ imports.push(goMatch[1]);
720
+ }
721
+ }
722
+ return imports;
723
+ }
724
+ function buildImportCounts(files) {
725
+ const counts = /* @__PURE__ */ new Map();
726
+ for (const [filePath, content] of files) {
727
+ const ext = path3.extname(filePath).toLowerCase();
728
+ if (!SOURCE_EXTENSIONS.has(ext)) continue;
729
+ const imports = extractImports(content, filePath);
730
+ for (const imp of imports) {
731
+ const candidates = [imp, imp + ".ts", imp + ".js", imp + ".tsx", imp + ".jsx", imp + "/index.ts", imp + "/index.js", imp + ".py"];
732
+ for (const candidate of candidates) {
733
+ const normalized = candidate.replace(/\\/g, "/");
734
+ if (files.has(normalized)) {
735
+ counts.set(normalized, (counts.get(normalized) || 0) + 1);
736
+ break;
737
+ }
738
+ }
739
+ }
740
+ }
741
+ return counts;
742
+ }
743
+ function getGitFrequency(dir) {
744
+ const freq = /* @__PURE__ */ new Map();
745
+ try {
746
+ const output = execSync2(
747
+ 'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
748
+ { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
749
+ );
750
+ for (const line of output.split("\n")) {
751
+ const trimmed = line.trim();
752
+ if (trimmed) freq.set(trimmed, (freq.get(trimmed) || 0) + 1);
627
753
  }
754
+ } catch {
755
+ }
756
+ return freq;
757
+ }
758
+ function groupByDirectory(files) {
759
+ const groups = /* @__PURE__ */ new Map();
760
+ for (const f of files) {
761
+ const dir = path3.dirname(f.path);
762
+ const group = groups.get(dir) || [];
763
+ group.push(f);
764
+ groups.set(dir, group);
628
765
  }
629
- return result;
766
+ return groups;
767
+ }
768
+ function structuralFingerprint(content, ext) {
769
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
770
+ const bucket = Math.floor(lines.length / 10) * 10;
771
+ const first = (lines[0] || "").trim().slice(0, 50);
772
+ const imports = lines.filter((l) => /^\s*(import |from |require\(|use )/.test(l)).length;
773
+ const fns = lines.filter((l) => /^\s*(export\s+)?(async\s+)?(function |def |func |fn |pub fn |class )/.test(l)).length;
774
+ return `${ext}:${bucket}:${imports}:${fns}:${first}`;
630
775
  }
631
776
  function analyzeCode(dir) {
632
- const allFiles = [];
633
- walkDir(dir, "", 0, 10, allFiles);
634
- sortByPriority(allFiles);
777
+ const allPaths = [];
778
+ walkDir(dir, "", 0, 10, allPaths);
635
779
  let totalChars = 0;
636
- for (const relPath of allFiles) {
780
+ for (const relPath of allPaths) {
637
781
  try {
638
- const stat = fs3.statSync(path3.join(dir, relPath));
639
- totalChars += stat.size;
782
+ totalChars += fs3.statSync(path3.join(dir, relPath)).size;
640
783
  } catch {
641
784
  }
642
785
  }
643
- const readFiles = [];
644
- for (const relPath of allFiles) {
645
- const fullPath = path3.join(dir, relPath);
646
- let rawContent;
786
+ const fileContents = /* @__PURE__ */ new Map();
787
+ for (const relPath of allPaths) {
647
788
  try {
648
- rawContent = fs3.readFileSync(fullPath, "utf-8");
789
+ const content = fs3.readFileSync(path3.join(dir, relPath), "utf-8");
790
+ if (content.split("\n").length <= 500) fileContents.set(relPath, content);
649
791
  } catch {
650
- continue;
651
792
  }
652
- if (rawContent.split("\n").length > 500) continue;
793
+ }
794
+ const importCounts = buildImportCounts(fileContents);
795
+ const gitFreq = getGitFrequency(dir);
796
+ const scored = [];
797
+ let compressedChars = 0;
798
+ for (const [relPath, rawContent] of fileContents) {
653
799
  const ext = path3.extname(relPath).toLowerCase();
654
800
  const compressed = compressContent(rawContent, ext);
655
- readFiles.push({
656
- path: relPath,
657
- content: compressed,
658
- ext,
659
- rawSize: rawContent.length
660
- });
661
- }
662
- const deduped = deduplicateFiles(readFiles);
801
+ const skeleton = extractSkeleton(compressed, ext);
802
+ compressedChars += compressed.length;
803
+ const priorityScore = filePriority(relPath);
804
+ const importScore = Math.min(importCounts.get(relPath) || 0, 20) * 2;
805
+ const gitScore = Math.min(gitFreq.get(relPath) || 0, 10) * 3;
806
+ const score = priorityScore + importScore + gitScore;
807
+ scored.push({ path: relPath, rawContent, compressed, skeleton, ext, score });
808
+ }
809
+ scored.sort((a, b) => b.score - a.score);
810
+ const dirGroups = groupByDirectory(scored);
811
+ const result = [];
663
812
  let includedChars = 0;
664
- let truncated = false;
665
- const files = [];
666
- for (const file of deduped) {
667
- const entrySize = file.path.length + file.content.length + 10;
668
- if (includedChars + entrySize > CHAR_BUDGET) {
669
- truncated = true;
670
- continue;
813
+ let dupGroups = 0;
814
+ for (const [dirPath, group] of dirGroups) {
815
+ if (group.length === 0) continue;
816
+ const rep = group[0];
817
+ const repFP = structuralFingerprint(rep.compressed, rep.ext);
818
+ const similar = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) === repFP);
819
+ const unique = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) !== repFP);
820
+ const repEntry = { path: rep.path, content: rep.compressed, size: rep.compressed.length };
821
+ const repSize = rep.path.length + rep.compressed.length + 10;
822
+ if (includedChars + repSize <= CHAR_BUDGET) {
823
+ result.push(repEntry);
824
+ includedChars += repSize;
825
+ }
826
+ if (similar.length > 0) {
827
+ dupGroups++;
828
+ const names = similar.map((f) => path3.basename(f.path));
829
+ const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
830
+ const summarySize = summary.length + 30;
831
+ if (includedChars + summarySize <= CHAR_BUDGET) {
832
+ result.push({ path: `[similar to ${rep.path}]`, content: summary, size: summary.length });
833
+ includedChars += summarySize;
834
+ }
835
+ }
836
+ for (const f of unique) {
837
+ const skeletonSize = f.path.length + f.skeleton.length + 10;
838
+ if (includedChars + skeletonSize <= CHAR_BUDGET) {
839
+ result.push({ path: f.path, content: f.skeleton, size: f.skeleton.length });
840
+ includedChars += skeletonSize;
841
+ }
671
842
  }
672
- files.push(file);
673
- includedChars += entrySize;
843
+ }
844
+ const includedPaths = new Set(result.map((f) => f.path));
845
+ for (const f of scored) {
846
+ if (includedPaths.has(f.path)) continue;
847
+ const skeletonSize = f.path.length + f.skeleton.length + 10;
848
+ if (includedChars + skeletonSize > CHAR_BUDGET) continue;
849
+ result.push({ path: f.path, content: f.skeleton, size: f.skeleton.length });
850
+ includedChars += skeletonSize;
674
851
  }
675
852
  return {
676
- files,
677
- truncated,
853
+ files: result,
854
+ truncated: includedChars >= CHAR_BUDGET * 0.95,
678
855
  totalProjectTokens: Math.ceil(totalChars / 4),
679
- includedTokens: Math.ceil(includedChars / 4)
856
+ compressedTokens: Math.ceil(compressedChars / 4),
857
+ includedTokens: Math.ceil(includedChars / 4),
858
+ filesAnalyzed: fileContents.size,
859
+ filesIncluded: result.length,
860
+ duplicateGroups: dupGroups
680
861
  };
681
862
  }
682
863
  function walkDir(base, rel, depth, maxDepth, files) {
@@ -704,8 +885,9 @@ function walkDir(base, rel, depth, maxDepth, files) {
704
885
  }
705
886
  }
706
887
  }
707
- function sortByPriority(files) {
708
- const entryPointNames = /* @__PURE__ */ new Set([
888
+ function filePriority(filePath) {
889
+ const base = path3.basename(filePath);
890
+ const entryPoints = /* @__PURE__ */ new Set([
709
891
  "index.ts",
710
892
  "index.js",
711
893
  "index.tsx",
@@ -722,22 +904,13 @@ function sortByPriority(files) {
722
904
  "mod.rs",
723
905
  "lib.rs"
724
906
  ]);
725
- const configPattern = /\.(json|ya?ml|toml|ini|cfg|env)$|config\.|Makefile|Dockerfile/i;
726
- const routePattern = /(route|api|controller|endpoint|handler)/i;
727
- const schemaPattern = /(types|schema|models|entities|migration)/i;
728
- const servicePattern = /(service|lib|utils|helper|middleware)/i;
729
- const testPattern = /(test|spec|__tests__|_test\.|\.test\.)/i;
730
- function priority(filePath) {
731
- const base = path3.basename(filePath);
732
- if (entryPointNames.has(base)) return 0;
733
- if (configPattern.test(filePath)) return 1;
734
- if (routePattern.test(filePath)) return 2;
735
- if (schemaPattern.test(filePath)) return 3;
736
- if (servicePattern.test(filePath)) return 4;
737
- if (testPattern.test(filePath)) return 6;
738
- return 5;
739
- }
740
- files.sort((a, b) => priority(a) - priority(b));
907
+ if (entryPoints.has(base)) return 40;
908
+ if (/\.(json|ya?ml|toml|ini|cfg|env)$|config\.|Makefile|Dockerfile/i.test(filePath)) return 35;
909
+ if (/(route|api|controller|endpoint|handler)/i.test(filePath)) return 30;
910
+ if (/(types|schema|models|entities|migration)/i.test(filePath)) return 25;
911
+ if (/(service|lib|utils|helper|middleware)/i.test(filePath)) return 20;
912
+ if (/(test|spec|__tests__|_test\.|\.test\.)/i.test(filePath)) return 5;
913
+ return 15;
741
914
  }
742
915
 
743
916
  // src/llm/index.ts
@@ -1019,7 +1192,7 @@ var OpenAICompatProvider = class {
1019
1192
  };
1020
1193
 
1021
1194
  // src/llm/cursor-acp.ts
1022
- import { spawn, execSync as execSync2 } from "child_process";
1195
+ import { spawn, execSync as execSync3 } from "child_process";
1023
1196
  import readline from "readline";
1024
1197
  var ACP_AGENT_BIN = "agent";
1025
1198
  var CursorAcpProvider = class {
@@ -1169,7 +1342,7 @@ ${msg.content}
1169
1342
  };
1170
1343
  function isCursorAgentAvailable() {
1171
1344
  try {
1172
- execSync2(`which ${ACP_AGENT_BIN}`, { stdio: "ignore" });
1345
+ execSync3(`which ${ACP_AGENT_BIN}`, { stdio: "ignore" });
1173
1346
  return true;
1174
1347
  } catch {
1175
1348
  return false;
@@ -1177,7 +1350,7 @@ function isCursorAgentAvailable() {
1177
1350
  }
1178
1351
 
1179
1352
  // src/llm/claude-cli.ts
1180
- import { spawn as spawn2, execSync as execSync3 } from "child_process";
1353
+ import { spawn as spawn2, execSync as execSync4 } from "child_process";
1181
1354
  var CLAUDE_CLI_BIN = "claude";
1182
1355
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
1183
1356
  var ClaudeCliProvider = class {
@@ -1296,7 +1469,7 @@ ${msg.content}
1296
1469
  function isClaudeCliAvailable() {
1297
1470
  try {
1298
1471
  const cmd = process.platform === "win32" ? `where ${CLAUDE_CLI_BIN}` : `which ${CLAUDE_CLI_BIN}`;
1299
- execSync3(cmd, { stdio: "ignore" });
1472
+ execSync4(cmd, { stdio: "ignore" });
1300
1473
  return true;
1301
1474
  } catch {
1302
1475
  return false;
@@ -3030,10 +3203,10 @@ import select2 from "@inquirer/select";
3030
3203
  import { createTwoFilesPatch } from "diff";
3031
3204
 
3032
3205
  // src/utils/editor.ts
3033
- import { execSync as execSync4, spawn as spawn3 } from "child_process";
3206
+ import { execSync as execSync5, spawn as spawn3 } from "child_process";
3034
3207
  function commandExists(cmd) {
3035
3208
  try {
3036
- execSync4(`which ${cmd}`, { stdio: "ignore" });
3209
+ execSync5(`which ${cmd}`, { stdio: "ignore" });
3037
3210
  return true;
3038
3211
  } catch {
3039
3212
  return false;
@@ -3333,11 +3506,11 @@ ${agentRefs.join(" ")}
3333
3506
  // src/lib/hooks.ts
3334
3507
  import fs17 from "fs";
3335
3508
  import path12 from "path";
3336
- import { execSync as execSync6 } from "child_process";
3509
+ import { execSync as execSync7 } from "child_process";
3337
3510
 
3338
3511
  // src/lib/resolve-caliber.ts
3339
3512
  import fs16 from "fs";
3340
- import { execSync as execSync5 } from "child_process";
3513
+ import { execSync as execSync6 } from "child_process";
3341
3514
  var _resolved = null;
3342
3515
  function resolveCaliber() {
3343
3516
  if (_resolved) return _resolved;
@@ -3347,7 +3520,7 @@ function resolveCaliber() {
3347
3520
  return _resolved;
3348
3521
  }
3349
3522
  try {
3350
- const found = execSync5("which caliber", {
3523
+ const found = execSync6("which caliber", {
3351
3524
  encoding: "utf-8",
3352
3525
  stdio: ["pipe", "pipe", "pipe"]
3353
3526
  }).trim();
@@ -3452,7 +3625,7 @@ ${PRECOMMIT_END}`;
3452
3625
  }
3453
3626
  function getGitHooksDir() {
3454
3627
  try {
3455
- const gitDir = execSync6("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3628
+ const gitDir = execSync7("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3456
3629
  return path12.join(gitDir, "hooks");
3457
3630
  } catch {
3458
3631
  return null;
@@ -3596,7 +3769,7 @@ function removeLearningHooks() {
3596
3769
  init_constants();
3597
3770
  import fs19 from "fs";
3598
3771
  import path14 from "path";
3599
- import { execSync as execSync7 } from "child_process";
3772
+ import { execSync as execSync8 } from "child_process";
3600
3773
  var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
3601
3774
  function normalizeTargetAgent(value) {
3602
3775
  if (Array.isArray(value)) return value;
@@ -3624,7 +3797,7 @@ function writeState(state) {
3624
3797
  }
3625
3798
  function getCurrentHeadSha() {
3626
3799
  try {
3627
- return execSync7("git rev-parse HEAD", {
3800
+ return execSync8("git rev-parse HEAD", {
3628
3801
  encoding: "utf-8",
3629
3802
  stdio: ["pipe", "pipe", "pipe"]
3630
3803
  }).trim();
@@ -4547,7 +4720,7 @@ function checkGrounding(dir) {
4547
4720
 
4548
4721
  // src/scoring/checks/accuracy.ts
4549
4722
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
4550
- import { execSync as execSync8 } from "child_process";
4723
+ import { execSync as execSync9 } from "child_process";
4551
4724
  import { join as join5 } from "path";
4552
4725
  function validateReferences(dir) {
4553
4726
  const configContent = collectPrimaryConfigContent(dir);
@@ -4579,13 +4752,13 @@ function validateReferences(dir) {
4579
4752
  }
4580
4753
  function detectGitDrift(dir) {
4581
4754
  try {
4582
- execSync8("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
4755
+ execSync9("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
4583
4756
  } catch {
4584
4757
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
4585
4758
  }
4586
4759
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
4587
4760
  try {
4588
- const headTimestamp = execSync8(
4761
+ const headTimestamp = execSync9(
4589
4762
  "git log -1 --format=%ct HEAD",
4590
4763
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4591
4764
  ).trim();
@@ -4606,7 +4779,7 @@ function detectGitDrift(dir) {
4606
4779
  let latestConfigCommitHash = null;
4607
4780
  for (const file of configFiles) {
4608
4781
  try {
4609
- const hash = execSync8(
4782
+ const hash = execSync9(
4610
4783
  `git log -1 --format=%H -- "${file}"`,
4611
4784
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4612
4785
  ).trim();
@@ -4615,7 +4788,7 @@ function detectGitDrift(dir) {
4615
4788
  latestConfigCommitHash = hash;
4616
4789
  } else {
4617
4790
  try {
4618
- execSync8(
4791
+ execSync9(
4619
4792
  `git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
4620
4793
  { cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
4621
4794
  );
@@ -4630,12 +4803,12 @@ function detectGitDrift(dir) {
4630
4803
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
4631
4804
  }
4632
4805
  try {
4633
- const countStr = execSync8(
4806
+ const countStr = execSync9(
4634
4807
  `git rev-list --count ${latestConfigCommitHash}..HEAD`,
4635
4808
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4636
4809
  ).trim();
4637
4810
  const commitsSince = parseInt(countStr, 10) || 0;
4638
- const lastDate = execSync8(
4811
+ const lastDate = execSync9(
4639
4812
  `git log -1 --format=%ci ${latestConfigCommitHash}`,
4640
4813
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4641
4814
  ).trim();
@@ -4707,12 +4880,12 @@ function checkAccuracy(dir) {
4707
4880
 
4708
4881
  // src/scoring/checks/freshness.ts
4709
4882
  import { existsSync as existsSync5, statSync as statSync3 } from "fs";
4710
- import { execSync as execSync9 } from "child_process";
4883
+ import { execSync as execSync10 } from "child_process";
4711
4884
  import { join as join6 } from "path";
4712
4885
  function getCommitsSinceConfigUpdate(dir) {
4713
4886
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
4714
4887
  try {
4715
- const headTimestamp = execSync9(
4888
+ const headTimestamp = execSync10(
4716
4889
  "git log -1 --format=%ct HEAD",
4717
4890
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4718
4891
  ).trim();
@@ -4732,12 +4905,12 @@ function getCommitsSinceConfigUpdate(dir) {
4732
4905
  }
4733
4906
  for (const file of configFiles) {
4734
4907
  try {
4735
- const hash = execSync9(
4908
+ const hash = execSync10(
4736
4909
  `git log -1 --format=%H -- "${file}"`,
4737
4910
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4738
4911
  ).trim();
4739
4912
  if (hash) {
4740
- const countStr = execSync9(
4913
+ const countStr = execSync10(
4741
4914
  `git rev-list --count ${hash}..HEAD`,
4742
4915
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4743
4916
  ).trim();
@@ -4855,11 +5028,11 @@ function checkFreshness(dir) {
4855
5028
 
4856
5029
  // src/scoring/checks/bonus.ts
4857
5030
  import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
4858
- import { execSync as execSync10 } from "child_process";
5031
+ import { execSync as execSync11 } from "child_process";
4859
5032
  import { join as join7 } from "path";
4860
5033
  function hasPreCommitHook(dir) {
4861
5034
  try {
4862
- const gitDir = execSync10("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
5035
+ const gitDir = execSync11("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4863
5036
  const hookPath = join7(gitDir, "hooks", "pre-commit");
4864
5037
  const content = readFileOrNull2(hookPath);
4865
5038
  return content ? content.includes("caliber") : false;
@@ -5327,7 +5500,7 @@ import fs22 from "fs";
5327
5500
  import path17 from "path";
5328
5501
  import os3 from "os";
5329
5502
  import crypto3 from "crypto";
5330
- import { execSync as execSync11 } from "child_process";
5503
+ import { execSync as execSync12 } from "child_process";
5331
5504
  var CONFIG_DIR2 = path17.join(os3.homedir(), ".caliber");
5332
5505
  var CONFIG_FILE2 = path17.join(CONFIG_DIR2, "config.json");
5333
5506
  var runtimeDisabled = false;
@@ -5354,7 +5527,7 @@ function getMachineId() {
5354
5527
  }
5355
5528
  function getGitEmailHash() {
5356
5529
  try {
5357
- const email = execSync11("git config user.email", { encoding: "utf-8" }).trim();
5530
+ const email = execSync12("git config user.email", { encoding: "utf-8" }).trim();
5358
5531
  if (!email) return void 0;
5359
5532
  return crypto3.createHash("sha256").update(email).digest("hex");
5360
5533
  } catch {
@@ -6169,15 +6342,25 @@ async function initCommand(options) {
6169
6342
  spinner.succeed("Project analyzed");
6170
6343
  log(options.verbose, `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`);
6171
6344
  if (options.verbose && fingerprint.codeAnalysis) {
6172
- log(options.verbose, `Code analysis: ${fingerprint.codeAnalysis.files.length} files, ~${fingerprint.codeAnalysis.includedTokens.toLocaleString()} tokens${fingerprint.codeAnalysis.truncated ? ` (trimmed from ~${fingerprint.codeAnalysis.totalProjectTokens.toLocaleString()})` : ""}`);
6345
+ log(options.verbose, `Code analysis: ${fingerprint.codeAnalysis.filesIncluded}/${fingerprint.codeAnalysis.filesAnalyzed} files, ~${fingerprint.codeAnalysis.includedTokens.toLocaleString()} tokens, ${fingerprint.codeAnalysis.duplicateGroups} dedup groups`);
6173
6346
  }
6174
6347
  trackInitProjectDiscovered(fingerprint.languages.length, fingerprint.frameworks.length, fingerprint.fileTree.length);
6175
6348
  console.log(chalk8.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
6176
6349
  console.log(chalk8.dim(` Files: ${fingerprint.fileTree.length} found`));
6177
6350
  if (fingerprint.codeAnalysis) {
6178
6351
  const ca = fingerprint.codeAnalysis;
6179
- const contextInfo = ca.truncated ? `Context: ~${ca.includedTokens.toLocaleString()} tokens (${Math.round(ca.includedTokens / ca.totalProjectTokens * 100)}% of ${ca.totalProjectTokens.toLocaleString()} total)` : `Context: ~${ca.includedTokens.toLocaleString()} tokens`;
6180
- console.log(chalk8.dim(` ${contextInfo}`));
6352
+ const compressionPct = ca.totalProjectTokens > 0 ? Math.round((1 - ca.compressedTokens / ca.totalProjectTokens) * 100) : 0;
6353
+ const parts = [`Context: ~${ca.includedTokens.toLocaleString()} tokens sent`];
6354
+ if (ca.truncated) {
6355
+ parts.push(`(${Math.round(ca.includedTokens / ca.totalProjectTokens * 100)}% of ${ca.totalProjectTokens.toLocaleString()} total)`);
6356
+ }
6357
+ if (compressionPct > 5) {
6358
+ parts.push(`compressed ${compressionPct}%`);
6359
+ }
6360
+ if (ca.duplicateGroups > 0) {
6361
+ parts.push(`${ca.duplicateGroups} duplicate group${ca.duplicateGroups === 1 ? "" : "s"} merged`);
6362
+ }
6363
+ console.log(chalk8.dim(` ${parts.join(" \xB7 ")}`));
6181
6364
  }
6182
6365
  console.log("");
6183
6366
  if (report) {
@@ -7152,7 +7335,7 @@ import chalk13 from "chalk";
7152
7335
  import ora5 from "ora";
7153
7336
 
7154
7337
  // src/lib/git-diff.ts
7155
- import { execSync as execSync12 } from "child_process";
7338
+ import { execSync as execSync13 } from "child_process";
7156
7339
  var MAX_DIFF_BYTES = 1e5;
7157
7340
  var DOC_PATTERNS = [
7158
7341
  "CLAUDE.md",
@@ -7166,7 +7349,7 @@ function excludeArgs() {
7166
7349
  }
7167
7350
  function safeExec(cmd) {
7168
7351
  try {
7169
- return execSync12(cmd, {
7352
+ return execSync13(cmd, {
7170
7353
  encoding: "utf-8",
7171
7354
  stdio: ["pipe", "pipe", "pipe"],
7172
7355
  maxBuffer: 10 * 1024 * 1024
@@ -8093,7 +8276,7 @@ learn.command("status").description("Show learning system status").action(tracke
8093
8276
  import fs32 from "fs";
8094
8277
  import path26 from "path";
8095
8278
  import { fileURLToPath as fileURLToPath2 } from "url";
8096
- import { execSync as execSync13 } from "child_process";
8279
+ import { execSync as execSync14 } from "child_process";
8097
8280
  import chalk17 from "chalk";
8098
8281
  import ora6 from "ora";
8099
8282
  import confirm2 from "@inquirer/confirm";
@@ -8103,7 +8286,7 @@ var pkg2 = JSON.parse(
8103
8286
  );
8104
8287
  function getInstalledVersion() {
8105
8288
  try {
8106
- const globalRoot = execSync13("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8289
+ const globalRoot = execSync14("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8107
8290
  const pkgPath = path26.join(globalRoot, "@rely-ai", "caliber", "package.json");
8108
8291
  return JSON.parse(fs32.readFileSync(pkgPath, "utf-8")).version;
8109
8292
  } catch {
@@ -8148,7 +8331,7 @@ Update available: ${current} -> ${latest}`)
8148
8331
  }
8149
8332
  const spinner = ora6("Updating caliber...").start();
8150
8333
  try {
8151
- execSync13(`npm install -g @rely-ai/caliber@${latest}`, {
8334
+ execSync14(`npm install -g @rely-ai/caliber@${latest}`, {
8152
8335
  stdio: "pipe",
8153
8336
  timeout: 12e4,
8154
8337
  env: { ...process.env, npm_config_fund: "false", npm_config_audit: "false" }
@@ -8165,7 +8348,7 @@ Update available: ${current} -> ${latest}`)
8165
8348
  console.log(chalk17.dim(`
8166
8349
  Restarting: caliber ${args.join(" ")}
8167
8350
  `));
8168
- execSync13(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
8351
+ execSync14(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
8169
8352
  stdio: "inherit",
8170
8353
  env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
8171
8354
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
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": {