@rely-ai/caliber 1.1.1 → 1.1.2

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 +430 -736
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -32,17 +32,17 @@ __export(constants_exports, {
32
32
  LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
33
33
  MANIFEST_FILE: () => MANIFEST_FILE
34
34
  });
35
- import path10 from "path";
35
+ import path8 from "path";
36
36
  import os2 from "os";
37
37
  var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
38
38
  var init_constants = __esm({
39
39
  "src/constants.ts"() {
40
40
  "use strict";
41
- AUTH_DIR = path10.join(os2.homedir(), ".caliber");
41
+ AUTH_DIR = path8.join(os2.homedir(), ".caliber");
42
42
  CALIBER_DIR = ".caliber";
43
- MANIFEST_FILE = path10.join(CALIBER_DIR, "manifest.json");
44
- BACKUPS_DIR = path10.join(CALIBER_DIR, "backups");
45
- LEARNING_DIR = path10.join(CALIBER_DIR, "learning");
43
+ MANIFEST_FILE = path8.join(CALIBER_DIR, "manifest.json");
44
+ BACKUPS_DIR = path8.join(CALIBER_DIR, "backups");
45
+ LEARNING_DIR = path8.join(CALIBER_DIR, "learning");
46
46
  LEARNING_SESSION_FILE = "current-session.jsonl";
47
47
  LEARNING_STATE_FILE = "state.json";
48
48
  LEARNING_MAX_EVENTS = 500;
@@ -51,8 +51,8 @@ var init_constants = __esm({
51
51
 
52
52
  // src/cli.ts
53
53
  import { Command } from "commander";
54
- import fs27 from "fs";
55
- import path24 from "path";
54
+ import fs25 from "fs";
55
+ import path21 from "path";
56
56
  import { fileURLToPath } from "url";
57
57
 
58
58
  // src/commands/onboard.ts
@@ -60,11 +60,11 @@ import chalk5 from "chalk";
60
60
  import ora2 from "ora";
61
61
  import readline4 from "readline";
62
62
  import select2 from "@inquirer/select";
63
- import fs20 from "fs";
63
+ import fs18 from "fs";
64
64
 
65
65
  // src/fingerprint/index.ts
66
- import fs7 from "fs";
67
- import path7 from "path";
66
+ import fs6 from "fs";
67
+ import path5 from "path";
68
68
 
69
69
  // src/fingerprint/git.ts
70
70
  import { execSync } from "child_process";
@@ -91,54 +91,9 @@ function isGitRepo() {
91
91
  return isInsideGitRepo();
92
92
  }
93
93
 
94
- // src/fingerprint/package-json.ts
94
+ // src/fingerprint/file-tree.ts
95
95
  import fs from "fs";
96
96
  import path from "path";
97
- import { globSync } from "glob";
98
- var WORKSPACE_GLOBS = [
99
- "apps/*/package.json",
100
- "packages/*/package.json",
101
- "services/*/package.json",
102
- "libs/*/package.json"
103
- ];
104
- function analyzePackageJson(dir) {
105
- const rootPkgPath = path.join(dir, "package.json");
106
- let name;
107
- const languages = [];
108
- if (fs.existsSync(rootPkgPath)) {
109
- try {
110
- const pkg3 = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
111
- name = pkg3.name;
112
- const allDeps = { ...pkg3.dependencies, ...pkg3.devDependencies };
113
- if (allDeps.typescript || allDeps["@types/node"]) {
114
- languages.push("TypeScript");
115
- }
116
- languages.push("JavaScript");
117
- } catch {
118
- }
119
- }
120
- for (const glob of WORKSPACE_GLOBS) {
121
- const matches = globSync(glob, { cwd: dir, absolute: true });
122
- for (const pkgPath of matches) {
123
- try {
124
- const pkg3 = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
125
- const deps = { ...pkg3.dependencies, ...pkg3.devDependencies };
126
- if (deps.typescript || deps["@types/node"]) {
127
- languages.push("TypeScript");
128
- }
129
- } catch {
130
- }
131
- }
132
- }
133
- return {
134
- name,
135
- languages: [...new Set(languages)]
136
- };
137
- }
138
-
139
- // src/fingerprint/file-tree.ts
140
- import fs2 from "fs";
141
- import path2 from "path";
142
97
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
143
98
  "node_modules",
144
99
  ".git",
@@ -161,10 +116,10 @@ function getFileTree(dir, maxDepth = 3) {
161
116
  }
162
117
  function scan(base, rel, depth, maxDepth, result) {
163
118
  if (depth > maxDepth) return;
164
- const fullPath = path2.join(base, rel);
119
+ const fullPath = path.join(base, rel);
165
120
  let entries;
166
121
  try {
167
- entries = fs2.readdirSync(fullPath, { withFileTypes: true });
122
+ entries = fs.readdirSync(fullPath, { withFileTypes: true });
168
123
  } catch {
169
124
  return;
170
125
  }
@@ -181,122 +136,87 @@ function scan(base, rel, depth, maxDepth, result) {
181
136
  }
182
137
  }
183
138
 
184
- // src/fingerprint/languages.ts
185
- import path3 from "path";
186
- var EXT_TO_LANG = {
187
- ".ts": "TypeScript",
188
- ".tsx": "TypeScript",
189
- ".js": "JavaScript",
190
- ".jsx": "JavaScript",
191
- ".py": "Python",
192
- ".go": "Go",
193
- ".rs": "Rust",
194
- ".rb": "Ruby",
195
- ".java": "Java",
196
- ".kt": "Kotlin",
197
- ".swift": "Swift",
198
- ".cs": "C#",
199
- ".cpp": "C++",
200
- ".c": "C",
201
- ".php": "PHP",
202
- ".dart": "Dart",
203
- ".ex": "Elixir",
204
- ".exs": "Elixir",
205
- ".scala": "Scala",
206
- ".zig": "Zig"
207
- };
208
- function detectLanguages(fileTree) {
209
- const langs = /* @__PURE__ */ new Set();
210
- for (const file of fileTree) {
211
- const ext = path3.extname(file).toLowerCase();
212
- if (EXT_TO_LANG[ext]) {
213
- langs.add(EXT_TO_LANG[ext]);
214
- }
215
- }
216
- return [...langs];
217
- }
218
-
219
139
  // src/fingerprint/existing-config.ts
220
- import fs3 from "fs";
221
- import path4 from "path";
140
+ import fs2 from "fs";
141
+ import path2 from "path";
222
142
  function readExistingConfigs(dir) {
223
143
  const configs = {};
224
- const readmeMdPath = path4.join(dir, "README.md");
225
- if (fs3.existsSync(readmeMdPath)) {
226
- configs.readmeMd = fs3.readFileSync(readmeMdPath, "utf-8");
144
+ const readmeMdPath = path2.join(dir, "README.md");
145
+ if (fs2.existsSync(readmeMdPath)) {
146
+ configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
227
147
  }
228
- const claudeMdPath = path4.join(dir, "CLAUDE.md");
229
- if (fs3.existsSync(claudeMdPath)) {
230
- configs.claudeMd = fs3.readFileSync(claudeMdPath, "utf-8");
148
+ const claudeMdPath = path2.join(dir, "CLAUDE.md");
149
+ if (fs2.existsSync(claudeMdPath)) {
150
+ configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
231
151
  }
232
- const claudeSettingsPath = path4.join(dir, ".claude", "settings.json");
233
- if (fs3.existsSync(claudeSettingsPath)) {
152
+ const claudeSettingsPath = path2.join(dir, ".claude", "settings.json");
153
+ if (fs2.existsSync(claudeSettingsPath)) {
234
154
  try {
235
- configs.claudeSettings = JSON.parse(fs3.readFileSync(claudeSettingsPath, "utf-8"));
155
+ configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
236
156
  } catch {
237
157
  }
238
158
  }
239
- const skillsDir = path4.join(dir, ".claude", "skills");
240
- if (fs3.existsSync(skillsDir)) {
159
+ const skillsDir = path2.join(dir, ".claude", "skills");
160
+ if (fs2.existsSync(skillsDir)) {
241
161
  try {
242
- const entries = fs3.readdirSync(skillsDir);
162
+ const entries = fs2.readdirSync(skillsDir);
243
163
  const skills = [];
244
164
  for (const entry of entries) {
245
- const entryPath = path4.join(skillsDir, entry);
246
- const skillMdPath = path4.join(entryPath, "SKILL.md");
247
- if (fs3.statSync(entryPath).isDirectory() && fs3.existsSync(skillMdPath)) {
248
- skills.push({ filename: `${entry}/SKILL.md`, content: fs3.readFileSync(skillMdPath, "utf-8") });
165
+ const entryPath = path2.join(skillsDir, entry);
166
+ const skillMdPath = path2.join(entryPath, "SKILL.md");
167
+ if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
168
+ skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
249
169
  } else if (entry.endsWith(".md")) {
250
- skills.push({ filename: entry, content: fs3.readFileSync(entryPath, "utf-8") });
170
+ skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
251
171
  }
252
172
  }
253
173
  if (skills.length > 0) configs.claudeSkills = skills;
254
174
  } catch {
255
175
  }
256
176
  }
257
- const cursorrulesPath = path4.join(dir, ".cursorrules");
258
- if (fs3.existsSync(cursorrulesPath)) {
259
- configs.cursorrules = fs3.readFileSync(cursorrulesPath, "utf-8");
177
+ const cursorrulesPath = path2.join(dir, ".cursorrules");
178
+ if (fs2.existsSync(cursorrulesPath)) {
179
+ configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
260
180
  }
261
- const cursorRulesDir = path4.join(dir, ".cursor", "rules");
262
- if (fs3.existsSync(cursorRulesDir)) {
181
+ const cursorRulesDir = path2.join(dir, ".cursor", "rules");
182
+ if (fs2.existsSync(cursorRulesDir)) {
263
183
  try {
264
- const files = fs3.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
184
+ const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
265
185
  configs.cursorRules = files.map((f) => ({
266
186
  filename: f,
267
- content: fs3.readFileSync(path4.join(cursorRulesDir, f), "utf-8")
187
+ content: fs2.readFileSync(path2.join(cursorRulesDir, f), "utf-8")
268
188
  }));
269
189
  } catch {
270
190
  }
271
191
  }
272
- const cursorSkillsDir = path4.join(dir, ".cursor", "skills");
273
- if (fs3.existsSync(cursorSkillsDir)) {
192
+ const cursorSkillsDir = path2.join(dir, ".cursor", "skills");
193
+ if (fs2.existsSync(cursorSkillsDir)) {
274
194
  try {
275
- const slugs = fs3.readdirSync(cursorSkillsDir).filter((f) => {
276
- return fs3.statSync(path4.join(cursorSkillsDir, f)).isDirectory();
195
+ const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
196
+ return fs2.statSync(path2.join(cursorSkillsDir, f)).isDirectory();
277
197
  });
278
- configs.cursorSkills = slugs.filter((slug) => fs3.existsSync(path4.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
198
+ configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path2.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
279
199
  name,
280
200
  filename: "SKILL.md",
281
- content: fs3.readFileSync(path4.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
201
+ content: fs2.readFileSync(path2.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
282
202
  }));
283
203
  } catch {
284
204
  }
285
205
  }
286
- const mcpJsonPath = path4.join(dir, ".mcp.json");
287
- if (fs3.existsSync(mcpJsonPath)) {
206
+ const mcpJsonPath = path2.join(dir, ".mcp.json");
207
+ if (fs2.existsSync(mcpJsonPath)) {
288
208
  try {
289
- const mcpJson = JSON.parse(fs3.readFileSync(mcpJsonPath, "utf-8"));
209
+ const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
290
210
  if (mcpJson.mcpServers) {
291
211
  configs.claudeMcpServers = mcpJson.mcpServers;
292
212
  }
293
213
  } catch {
294
214
  }
295
215
  }
296
- const cursorMcpPath = path4.join(dir, ".cursor", "mcp.json");
297
- if (fs3.existsSync(cursorMcpPath)) {
216
+ const cursorMcpPath = path2.join(dir, ".cursor", "mcp.json");
217
+ if (fs2.existsSync(cursorMcpPath)) {
298
218
  try {
299
- const cursorMcpJson = JSON.parse(fs3.readFileSync(cursorMcpPath, "utf-8"));
219
+ const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
300
220
  if (cursorMcpJson.mcpServers) {
301
221
  configs.cursorMcpServers = cursorMcpJson.mcpServers;
302
222
  }
@@ -307,8 +227,8 @@ function readExistingConfigs(dir) {
307
227
  }
308
228
 
309
229
  // src/fingerprint/code-analysis.ts
310
- import fs4 from "fs";
311
- import path5 from "path";
230
+ import fs3 from "fs";
231
+ import path3 from "path";
312
232
  var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
313
233
  "node_modules",
314
234
  ".git",
@@ -379,16 +299,16 @@ function analyzeCode(dir) {
379
299
  let truncated = false;
380
300
  const fileSummaries = [];
381
301
  for (const relPath of sourceFiles) {
382
- const fullPath = path5.join(dir, relPath);
302
+ const fullPath = path3.join(dir, relPath);
383
303
  let content;
384
304
  try {
385
- content = fs4.readFileSync(fullPath, "utf-8");
305
+ content = fs3.readFileSync(fullPath, "utf-8");
386
306
  } catch {
387
307
  continue;
388
308
  }
389
309
  const lineCount = content.split("\n").length;
390
310
  if (lineCount > 500) continue;
391
- const ext = path5.extname(relPath);
311
+ const ext = path3.extname(relPath);
392
312
  const language = resolveLanguage(ext);
393
313
  if (!language) continue;
394
314
  const summary = language === "py" ? extractPython(relPath, content) : extractTypeScriptJavaScript(relPath, content, language);
@@ -404,10 +324,10 @@ function analyzeCode(dir) {
404
324
  }
405
325
  function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir) {
406
326
  if (depth > maxDepth) return;
407
- const fullPath = path5.join(base, rel);
327
+ const fullPath = path3.join(base, rel);
408
328
  let entries;
409
329
  try {
410
- entries = fs4.readdirSync(fullPath, { withFileTypes: true });
330
+ entries = fs3.readdirSync(fullPath, { withFileTypes: true });
411
331
  } catch {
412
332
  return;
413
333
  }
@@ -426,12 +346,12 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
426
346
  } else {
427
347
  if (CONFIG_FILE_NAMES.has(entry.name)) {
428
348
  try {
429
- const content = fs4.readFileSync(path5.join(base, relPath), "utf-8");
349
+ const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
430
350
  configFiles.push({ path: relPath, content });
431
351
  } catch {
432
352
  }
433
353
  }
434
- const ext = path5.extname(entry.name);
354
+ const ext = path3.extname(entry.name);
435
355
  if (SOURCE_EXTENSIONS.has(ext) && !entry.name.endsWith(".d.ts")) {
436
356
  sourceFiles.push(relPath);
437
357
  }
@@ -439,10 +359,10 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
439
359
  }
440
360
  }
441
361
  function collectConfigsFromDir(base, relDir, pattern, configFiles) {
442
- const fullDir = path5.join(base, relDir);
362
+ const fullDir = path3.join(base, relDir);
443
363
  let entries;
444
364
  try {
445
- entries = fs4.readdirSync(fullDir, { withFileTypes: true });
365
+ entries = fs3.readdirSync(fullDir, { withFileTypes: true });
446
366
  } catch {
447
367
  return;
448
368
  }
@@ -450,7 +370,7 @@ function collectConfigsFromDir(base, relDir, pattern, configFiles) {
450
370
  if (entry.isFile() && pattern.test(entry.name)) {
451
371
  const relPath = `${relDir}/${entry.name}`;
452
372
  try {
453
- const content = fs4.readFileSync(path5.join(base, relPath), "utf-8");
373
+ const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
454
374
  configFiles.push({ path: relPath, content });
455
375
  } catch {
456
376
  }
@@ -464,7 +384,7 @@ function sortByPriority(files) {
464
384
  const servicePattern = /(service|lib|utils)/i;
465
385
  const testPattern = /(test|spec|__tests__)/i;
466
386
  function priority(filePath) {
467
- const base = path5.basename(filePath);
387
+ const base = path3.basename(filePath);
468
388
  if (entryPointNames.has(base)) return 0;
469
389
  if (routePattern.test(filePath)) return 1;
470
390
  if (schemaPattern.test(filePath)) return 2;
@@ -583,11 +503,11 @@ function estimateSummarySize(summary) {
583
503
  }
584
504
 
585
505
  // src/llm/config.ts
586
- import fs5 from "fs";
587
- import path6 from "path";
506
+ import fs4 from "fs";
507
+ import path4 from "path";
588
508
  import os from "os";
589
- var CONFIG_DIR = path6.join(os.homedir(), ".caliber");
590
- var CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
509
+ var CONFIG_DIR = path4.join(os.homedir(), ".caliber");
510
+ var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
591
511
  var DEFAULT_MODELS = {
592
512
  anthropic: "claude-sonnet-4-6",
593
513
  vertex: "claude-sonnet-4-6",
@@ -641,8 +561,8 @@ function resolveFromEnv() {
641
561
  }
642
562
  function readConfigFile() {
643
563
  try {
644
- if (!fs5.existsSync(CONFIG_FILE)) return null;
645
- const raw = fs5.readFileSync(CONFIG_FILE, "utf-8");
564
+ if (!fs4.existsSync(CONFIG_FILE)) return null;
565
+ const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
646
566
  const parsed = JSON.parse(raw);
647
567
  if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
648
568
  return null;
@@ -653,18 +573,21 @@ function readConfigFile() {
653
573
  }
654
574
  }
655
575
  function writeConfigFile(config) {
656
- if (!fs5.existsSync(CONFIG_DIR)) {
657
- fs5.mkdirSync(CONFIG_DIR, { recursive: true });
576
+ if (!fs4.existsSync(CONFIG_DIR)) {
577
+ fs4.mkdirSync(CONFIG_DIR, { recursive: true });
658
578
  }
659
579
  const sanitized = { ...config };
660
580
  if (sanitized.apiKey) {
661
581
  sanitized.apiKey = sanitized.apiKey.trim();
662
582
  }
663
- fs5.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
583
+ fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
664
584
  }
665
585
  function getConfigFilePath() {
666
586
  return CONFIG_FILE;
667
587
  }
588
+ function getFastModel() {
589
+ return process.env.CALIBER_FAST_MODEL || process.env.ANTHROPIC_SMALL_FAST_MODEL || void 0;
590
+ }
668
591
 
669
592
  // src/llm/anthropic.ts
670
593
  import Anthropic from "@anthropic-ai/sdk";
@@ -710,7 +633,7 @@ var AnthropicProvider = class {
710
633
  };
711
634
 
712
635
  // src/llm/vertex.ts
713
- import fs6 from "fs";
636
+ import fs5 from "fs";
714
637
  import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
715
638
  import { GoogleAuth } from "google-auth-library";
716
639
  var VertexProvider = class {
@@ -733,7 +656,7 @@ var VertexProvider = class {
733
656
  }
734
657
  } else {
735
658
  try {
736
- creds = JSON.parse(fs6.readFileSync(raw, "utf-8"));
659
+ creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
737
660
  } catch {
738
661
  throw new Error(`Cannot read credentials file: ${raw}`);
739
662
  }
@@ -1470,22 +1393,26 @@ All markdown content inside string values must be properly escaped for JSON (new
1470
1393
 
1471
1394
  If there's nothing worth learning from the events (routine successful operations), return:
1472
1395
  {"claudeMdLearnedSection": null, "skills": null, "explanations": ["No actionable patterns found in these events."]}`;
1473
- var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages and frameworks from project file trees and dependency files.
1396
+ var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages, frameworks, and external tools/services from project file trees and dependency files.
1474
1397
 
1475
1398
  Analyze the provided file tree and dependency file contents. Return a JSON object with:
1476
- - "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust")
1477
- - "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js")
1399
+ - "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust", "HCL")
1400
+ - "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js", "Terraform")
1401
+ - "tools": array of external tools, services, and platforms the project integrates with \u2014 things that could have an MCP server or API integration (e.g. "PostgreSQL", "Redis", "Stripe", "Sentry", "AWS", "GCP", "GitHub", "Slack", "Docker", "Kubernetes", "Datadog", "PagerDuty", "MongoDB", "Elasticsearch")
1478
1402
 
1479
1403
  Be thorough \u2014 look for signals in:
1480
1404
  - Dependency files (package.json, pyproject.toml, requirements.txt, go.mod, Cargo.toml, etc.)
1481
1405
  - File extensions and directory structure
1482
- - Configuration files (e.g. next.config.js implies Next.js)
1406
+ - Configuration files (e.g. next.config.js implies Next.js, .tf files imply Terraform + cloud providers)
1407
+ - Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
1408
+ - CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
1409
+ - Environment variable patterns and service references in code
1483
1410
 
1484
- Only include frameworks/languages you're confident about. Return ONLY the JSON object.`;
1411
+ Only include items you're confident about. Return ONLY the JSON object.`;
1485
1412
 
1486
1413
  // src/ai/detect.ts
1487
- async function detectFrameworks(fileTree, fileContents) {
1488
- const parts = ["Analyze this project and detect languages and frameworks.\n"];
1414
+ async function detectProjectStack(fileTree, fileContents) {
1415
+ const parts = ["Analyze this project and detect languages, frameworks, and external tools/services.\n"];
1489
1416
  if (fileTree.length > 0) {
1490
1417
  parts.push("File tree:");
1491
1418
  parts.push(fileTree.join("\n"));
@@ -1498,7 +1425,7 @@ async function detectFrameworks(fileTree, fileContents) {
1498
1425
  parts.push(content);
1499
1426
  }
1500
1427
  }
1501
- const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
1428
+ const fastModel = getFastModel();
1502
1429
  const result = await llmJsonCall({
1503
1430
  system: FINGERPRINT_SYSTEM_PROMPT,
1504
1431
  prompt: parts.join("\n"),
@@ -1506,29 +1433,39 @@ async function detectFrameworks(fileTree, fileContents) {
1506
1433
  });
1507
1434
  return {
1508
1435
  languages: Array.isArray(result.languages) ? result.languages : [],
1509
- frameworks: Array.isArray(result.frameworks) ? result.frameworks : []
1436
+ frameworks: Array.isArray(result.frameworks) ? result.frameworks : [],
1437
+ tools: Array.isArray(result.tools) ? result.tools : []
1510
1438
  };
1511
1439
  }
1512
1440
 
1513
1441
  // src/fingerprint/index.ts
1514
1442
  function collectFingerprint(dir) {
1515
1443
  const gitRemoteUrl = getGitRemoteUrl();
1516
- const pkgInfo = analyzePackageJson(dir);
1517
1444
  const fileTree = getFileTree(dir);
1518
- const fileLangs = detectLanguages(fileTree);
1519
1445
  const existingConfigs = readExistingConfigs(dir);
1520
1446
  const codeAnalysis = analyzeCode(dir);
1521
- const languages = [.../* @__PURE__ */ new Set([...pkgInfo.languages, ...fileLangs])];
1447
+ const packageName = readPackageName(dir);
1522
1448
  return {
1523
1449
  gitRemoteUrl,
1524
- packageName: pkgInfo.name,
1525
- languages,
1450
+ packageName,
1451
+ languages: [],
1526
1452
  frameworks: [],
1453
+ tools: [],
1527
1454
  fileTree,
1528
1455
  existingConfigs,
1529
1456
  codeAnalysis
1530
1457
  };
1531
1458
  }
1459
+ function readPackageName(dir) {
1460
+ try {
1461
+ const pkgPath = path5.join(dir, "package.json");
1462
+ if (!fs6.existsSync(pkgPath)) return void 0;
1463
+ const pkg3 = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
1464
+ return pkg3.name;
1465
+ } catch {
1466
+ return void 0;
1467
+ }
1468
+ }
1532
1469
  var DEP_FILE_PATTERNS = [
1533
1470
  "package.json",
1534
1471
  "pyproject.toml",
@@ -1550,12 +1487,12 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
1550
1487
  const fileContents = {};
1551
1488
  let totalSize = 0;
1552
1489
  for (const treePath of fingerprint.fileTree) {
1553
- const basename = path7.basename(treePath);
1490
+ const basename = path5.basename(treePath);
1554
1491
  if (!DEP_FILE_PATTERNS.includes(basename)) continue;
1555
- const fullPath = path7.join(dir, treePath);
1556
- if (!fs7.existsSync(fullPath)) continue;
1492
+ const fullPath = path5.join(dir, treePath);
1493
+ if (!fs6.existsSync(fullPath)) continue;
1557
1494
  try {
1558
- const content = fs7.readFileSync(fullPath, "utf-8");
1495
+ const content = fs6.readFileSync(fullPath, "utf-8");
1559
1496
  if (totalSize + content.length > MAX_CONTENT_SIZE) break;
1560
1497
  fileContents[treePath] = content;
1561
1498
  totalSize += content.length;
@@ -1564,7 +1501,7 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
1564
1501
  }
1565
1502
  }
1566
1503
  if (Object.keys(fileContents).length === 0 && fingerprint.fileTree.length === 0) return;
1567
- const result = await detectFrameworks(fingerprint.fileTree, fileContents);
1504
+ const result = await detectProjectStack(fingerprint.fileTree, fileContents);
1568
1505
  if (result.languages?.length) {
1569
1506
  const langSet = new Set(fingerprint.languages);
1570
1507
  for (const lang of result.languages) langSet.add(lang);
@@ -1575,6 +1512,11 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
1575
1512
  for (const fw of result.frameworks) fwSet.add(fw);
1576
1513
  fingerprint.frameworks = [...fwSet];
1577
1514
  }
1515
+ if (result.tools?.length) {
1516
+ const toolSet = new Set(fingerprint.tools);
1517
+ for (const tool of result.tools) toolSet.add(tool);
1518
+ fingerprint.tools = [...toolSet];
1519
+ }
1578
1520
  } catch {
1579
1521
  }
1580
1522
  }
@@ -1875,20 +1817,20 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
1875
1817
  }
1876
1818
 
1877
1819
  // src/writers/index.ts
1878
- import fs12 from "fs";
1820
+ import fs11 from "fs";
1879
1821
 
1880
1822
  // src/writers/claude/index.ts
1881
- import fs8 from "fs";
1882
- import path8 from "path";
1823
+ import fs7 from "fs";
1824
+ import path6 from "path";
1883
1825
  function writeClaudeConfig(config) {
1884
1826
  const written = [];
1885
- fs8.writeFileSync("CLAUDE.md", config.claudeMd);
1827
+ fs7.writeFileSync("CLAUDE.md", config.claudeMd);
1886
1828
  written.push("CLAUDE.md");
1887
1829
  if (config.skills?.length) {
1888
1830
  for (const skill of config.skills) {
1889
- const skillDir = path8.join(".claude", "skills", skill.name);
1890
- if (!fs8.existsSync(skillDir)) fs8.mkdirSync(skillDir, { recursive: true });
1891
- const skillPath = path8.join(skillDir, "SKILL.md");
1831
+ const skillDir = path6.join(".claude", "skills", skill.name);
1832
+ if (!fs7.existsSync(skillDir)) fs7.mkdirSync(skillDir, { recursive: true });
1833
+ const skillPath = path6.join(skillDir, "SKILL.md");
1892
1834
  const frontmatter = [
1893
1835
  "---",
1894
1836
  `name: ${skill.name}`,
@@ -1896,49 +1838,49 @@ function writeClaudeConfig(config) {
1896
1838
  "---",
1897
1839
  ""
1898
1840
  ].join("\n");
1899
- fs8.writeFileSync(skillPath, frontmatter + skill.content);
1841
+ fs7.writeFileSync(skillPath, frontmatter + skill.content);
1900
1842
  written.push(skillPath);
1901
1843
  }
1902
1844
  }
1903
1845
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
1904
1846
  let existingServers = {};
1905
1847
  try {
1906
- if (fs8.existsSync(".mcp.json")) {
1907
- const existing = JSON.parse(fs8.readFileSync(".mcp.json", "utf-8"));
1848
+ if (fs7.existsSync(".mcp.json")) {
1849
+ const existing = JSON.parse(fs7.readFileSync(".mcp.json", "utf-8"));
1908
1850
  if (existing.mcpServers) existingServers = existing.mcpServers;
1909
1851
  }
1910
1852
  } catch {
1911
1853
  }
1912
1854
  const mergedServers = { ...existingServers, ...config.mcpServers };
1913
- fs8.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
1855
+ fs7.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
1914
1856
  written.push(".mcp.json");
1915
1857
  }
1916
1858
  return written;
1917
1859
  }
1918
1860
 
1919
1861
  // src/writers/cursor/index.ts
1920
- import fs9 from "fs";
1921
- import path9 from "path";
1862
+ import fs8 from "fs";
1863
+ import path7 from "path";
1922
1864
  function writeCursorConfig(config) {
1923
1865
  const written = [];
1924
1866
  if (config.cursorrules) {
1925
- fs9.writeFileSync(".cursorrules", config.cursorrules);
1867
+ fs8.writeFileSync(".cursorrules", config.cursorrules);
1926
1868
  written.push(".cursorrules");
1927
1869
  }
1928
1870
  if (config.rules?.length) {
1929
- const rulesDir = path9.join(".cursor", "rules");
1930
- if (!fs9.existsSync(rulesDir)) fs9.mkdirSync(rulesDir, { recursive: true });
1871
+ const rulesDir = path7.join(".cursor", "rules");
1872
+ if (!fs8.existsSync(rulesDir)) fs8.mkdirSync(rulesDir, { recursive: true });
1931
1873
  for (const rule of config.rules) {
1932
- const rulePath = path9.join(rulesDir, rule.filename);
1933
- fs9.writeFileSync(rulePath, rule.content);
1874
+ const rulePath = path7.join(rulesDir, rule.filename);
1875
+ fs8.writeFileSync(rulePath, rule.content);
1934
1876
  written.push(rulePath);
1935
1877
  }
1936
1878
  }
1937
1879
  if (config.skills?.length) {
1938
1880
  for (const skill of config.skills) {
1939
- const skillDir = path9.join(".cursor", "skills", skill.name);
1940
- if (!fs9.existsSync(skillDir)) fs9.mkdirSync(skillDir, { recursive: true });
1941
- const skillPath = path9.join(skillDir, "SKILL.md");
1881
+ const skillDir = path7.join(".cursor", "skills", skill.name);
1882
+ if (!fs8.existsSync(skillDir)) fs8.mkdirSync(skillDir, { recursive: true });
1883
+ const skillPath = path7.join(skillDir, "SKILL.md");
1942
1884
  const frontmatter = [
1943
1885
  "---",
1944
1886
  `name: ${skill.name}`,
@@ -1946,24 +1888,24 @@ function writeCursorConfig(config) {
1946
1888
  "---",
1947
1889
  ""
1948
1890
  ].join("\n");
1949
- fs9.writeFileSync(skillPath, frontmatter + skill.content);
1891
+ fs8.writeFileSync(skillPath, frontmatter + skill.content);
1950
1892
  written.push(skillPath);
1951
1893
  }
1952
1894
  }
1953
1895
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
1954
1896
  const cursorDir = ".cursor";
1955
- if (!fs9.existsSync(cursorDir)) fs9.mkdirSync(cursorDir, { recursive: true });
1956
- const mcpPath = path9.join(cursorDir, "mcp.json");
1897
+ if (!fs8.existsSync(cursorDir)) fs8.mkdirSync(cursorDir, { recursive: true });
1898
+ const mcpPath = path7.join(cursorDir, "mcp.json");
1957
1899
  let existingServers = {};
1958
1900
  try {
1959
- if (fs9.existsSync(mcpPath)) {
1960
- const existing = JSON.parse(fs9.readFileSync(mcpPath, "utf-8"));
1901
+ if (fs8.existsSync(mcpPath)) {
1902
+ const existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
1961
1903
  if (existing.mcpServers) existingServers = existing.mcpServers;
1962
1904
  }
1963
1905
  } catch {
1964
1906
  }
1965
1907
  const mergedServers = { ...existingServers, ...config.mcpServers };
1966
- fs9.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
1908
+ fs8.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
1967
1909
  written.push(mcpPath);
1968
1910
  }
1969
1911
  return written;
@@ -1971,62 +1913,62 @@ function writeCursorConfig(config) {
1971
1913
 
1972
1914
  // src/writers/backup.ts
1973
1915
  init_constants();
1974
- import fs10 from "fs";
1975
- import path11 from "path";
1916
+ import fs9 from "fs";
1917
+ import path9 from "path";
1976
1918
  function createBackup(files) {
1977
1919
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1978
- const backupDir = path11.join(BACKUPS_DIR, timestamp);
1920
+ const backupDir = path9.join(BACKUPS_DIR, timestamp);
1979
1921
  for (const file of files) {
1980
- if (!fs10.existsSync(file)) continue;
1981
- const dest = path11.join(backupDir, file);
1982
- const destDir = path11.dirname(dest);
1983
- if (!fs10.existsSync(destDir)) {
1984
- fs10.mkdirSync(destDir, { recursive: true });
1922
+ if (!fs9.existsSync(file)) continue;
1923
+ const dest = path9.join(backupDir, file);
1924
+ const destDir = path9.dirname(dest);
1925
+ if (!fs9.existsSync(destDir)) {
1926
+ fs9.mkdirSync(destDir, { recursive: true });
1985
1927
  }
1986
- fs10.copyFileSync(file, dest);
1928
+ fs9.copyFileSync(file, dest);
1987
1929
  }
1988
1930
  return backupDir;
1989
1931
  }
1990
1932
  function restoreBackup(backupDir, file) {
1991
- const backupFile = path11.join(backupDir, file);
1992
- if (!fs10.existsSync(backupFile)) return false;
1993
- const destDir = path11.dirname(file);
1994
- if (!fs10.existsSync(destDir)) {
1995
- fs10.mkdirSync(destDir, { recursive: true });
1933
+ const backupFile = path9.join(backupDir, file);
1934
+ if (!fs9.existsSync(backupFile)) return false;
1935
+ const destDir = path9.dirname(file);
1936
+ if (!fs9.existsSync(destDir)) {
1937
+ fs9.mkdirSync(destDir, { recursive: true });
1996
1938
  }
1997
- fs10.copyFileSync(backupFile, file);
1939
+ fs9.copyFileSync(backupFile, file);
1998
1940
  return true;
1999
1941
  }
2000
1942
 
2001
1943
  // src/writers/manifest.ts
2002
1944
  init_constants();
2003
- import fs11 from "fs";
1945
+ import fs10 from "fs";
2004
1946
  import crypto from "crypto";
2005
1947
  function readManifest() {
2006
1948
  try {
2007
- if (!fs11.existsSync(MANIFEST_FILE)) return null;
2008
- return JSON.parse(fs11.readFileSync(MANIFEST_FILE, "utf-8"));
1949
+ if (!fs10.existsSync(MANIFEST_FILE)) return null;
1950
+ return JSON.parse(fs10.readFileSync(MANIFEST_FILE, "utf-8"));
2009
1951
  } catch {
2010
1952
  return null;
2011
1953
  }
2012
1954
  }
2013
1955
  function writeManifest(manifest) {
2014
- if (!fs11.existsSync(CALIBER_DIR)) {
2015
- fs11.mkdirSync(CALIBER_DIR, { recursive: true });
1956
+ if (!fs10.existsSync(CALIBER_DIR)) {
1957
+ fs10.mkdirSync(CALIBER_DIR, { recursive: true });
2016
1958
  }
2017
- fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
1959
+ fs10.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
2018
1960
  }
2019
1961
  function fileChecksum(filePath) {
2020
- const content = fs11.readFileSync(filePath);
1962
+ const content = fs10.readFileSync(filePath);
2021
1963
  return crypto.createHash("sha256").update(content).digest("hex");
2022
1964
  }
2023
1965
 
2024
1966
  // src/writers/index.ts
2025
1967
  function writeSetup(setup) {
2026
1968
  const filesToWrite = getFilesToWrite(setup);
2027
- const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs12.existsSync(f));
1969
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs11.existsSync(f));
2028
1970
  const existingFiles = [
2029
- ...filesToWrite.filter((f) => fs12.existsSync(f)),
1971
+ ...filesToWrite.filter((f) => fs11.existsSync(f)),
2030
1972
  ...filesToDelete
2031
1973
  ];
2032
1974
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
@@ -2039,7 +1981,7 @@ function writeSetup(setup) {
2039
1981
  }
2040
1982
  const deleted = [];
2041
1983
  for (const filePath of filesToDelete) {
2042
- fs12.unlinkSync(filePath);
1984
+ fs11.unlinkSync(filePath);
2043
1985
  deleted.push(filePath);
2044
1986
  }
2045
1987
  ensureGitignore();
@@ -2069,8 +2011,8 @@ function undoSetup() {
2069
2011
  const removed = [];
2070
2012
  for (const entry of manifest.entries) {
2071
2013
  if (entry.action === "created") {
2072
- if (fs12.existsSync(entry.path)) {
2073
- fs12.unlinkSync(entry.path);
2014
+ if (fs11.existsSync(entry.path)) {
2015
+ fs11.unlinkSync(entry.path);
2074
2016
  removed.push(entry.path);
2075
2017
  }
2076
2018
  } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
@@ -2080,8 +2022,8 @@ function undoSetup() {
2080
2022
  }
2081
2023
  }
2082
2024
  const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
2083
- if (fs12.existsSync(MANIFEST_FILE2)) {
2084
- fs12.unlinkSync(MANIFEST_FILE2);
2025
+ if (fs11.existsSync(MANIFEST_FILE2)) {
2026
+ fs11.unlinkSync(MANIFEST_FILE2);
2085
2027
  }
2086
2028
  return { restored, removed };
2087
2029
  }
@@ -2110,23 +2052,23 @@ function getFilesToWrite(setup) {
2110
2052
  }
2111
2053
  function ensureGitignore() {
2112
2054
  const gitignorePath = ".gitignore";
2113
- if (fs12.existsSync(gitignorePath)) {
2114
- const content = fs12.readFileSync(gitignorePath, "utf-8");
2055
+ if (fs11.existsSync(gitignorePath)) {
2056
+ const content = fs11.readFileSync(gitignorePath, "utf-8");
2115
2057
  if (!content.includes(".caliber/")) {
2116
- fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
2058
+ fs11.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
2117
2059
  }
2118
2060
  } else {
2119
- fs12.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
2061
+ fs11.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
2120
2062
  }
2121
2063
  }
2122
2064
 
2123
2065
  // src/writers/staging.ts
2124
2066
  init_constants();
2125
- import fs13 from "fs";
2126
- import path12 from "path";
2127
- var STAGED_DIR = path12.join(CALIBER_DIR, "staged");
2128
- var PROPOSED_DIR = path12.join(STAGED_DIR, "proposed");
2129
- var CURRENT_DIR = path12.join(STAGED_DIR, "current");
2067
+ import fs12 from "fs";
2068
+ import path10 from "path";
2069
+ var STAGED_DIR = path10.join(CALIBER_DIR, "staged");
2070
+ var PROPOSED_DIR = path10.join(STAGED_DIR, "proposed");
2071
+ var CURRENT_DIR = path10.join(STAGED_DIR, "current");
2130
2072
  function normalizeContent(content) {
2131
2073
  return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2132
2074
  }
@@ -2136,20 +2078,20 @@ function stageFiles(files, projectDir) {
2136
2078
  let modifiedFiles = 0;
2137
2079
  const stagedFiles = [];
2138
2080
  for (const file of files) {
2139
- const originalPath = path12.join(projectDir, file.path);
2140
- if (fs13.existsSync(originalPath)) {
2141
- const existing = fs13.readFileSync(originalPath, "utf-8");
2081
+ const originalPath = path10.join(projectDir, file.path);
2082
+ if (fs12.existsSync(originalPath)) {
2083
+ const existing = fs12.readFileSync(originalPath, "utf-8");
2142
2084
  if (normalizeContent(existing) === normalizeContent(file.content)) {
2143
2085
  continue;
2144
2086
  }
2145
2087
  }
2146
- const proposedPath = path12.join(PROPOSED_DIR, file.path);
2147
- fs13.mkdirSync(path12.dirname(proposedPath), { recursive: true });
2148
- fs13.writeFileSync(proposedPath, file.content);
2149
- if (fs13.existsSync(originalPath)) {
2150
- const currentPath = path12.join(CURRENT_DIR, file.path);
2151
- fs13.mkdirSync(path12.dirname(currentPath), { recursive: true });
2152
- fs13.copyFileSync(originalPath, currentPath);
2088
+ const proposedPath = path10.join(PROPOSED_DIR, file.path);
2089
+ fs12.mkdirSync(path10.dirname(proposedPath), { recursive: true });
2090
+ fs12.writeFileSync(proposedPath, file.content);
2091
+ if (fs12.existsSync(originalPath)) {
2092
+ const currentPath = path10.join(CURRENT_DIR, file.path);
2093
+ fs12.mkdirSync(path10.dirname(currentPath), { recursive: true });
2094
+ fs12.copyFileSync(originalPath, currentPath);
2153
2095
  modifiedFiles++;
2154
2096
  stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
2155
2097
  } else {
@@ -2160,8 +2102,8 @@ function stageFiles(files, projectDir) {
2160
2102
  return { newFiles, modifiedFiles, stagedFiles };
2161
2103
  }
2162
2104
  function cleanupStaging() {
2163
- if (fs13.existsSync(STAGED_DIR)) {
2164
- fs13.rmSync(STAGED_DIR, { recursive: true, force: true });
2105
+ if (fs12.existsSync(STAGED_DIR)) {
2106
+ fs12.rmSync(STAGED_DIR, { recursive: true, force: true });
2165
2107
  }
2166
2108
  }
2167
2109
 
@@ -2207,24 +2149,24 @@ function openDiffsInEditor(editor, files) {
2207
2149
  import { createTwoFilesPatch } from "diff";
2208
2150
 
2209
2151
  // src/lib/hooks.ts
2210
- import fs14 from "fs";
2211
- import path13 from "path";
2152
+ import fs13 from "fs";
2153
+ import path11 from "path";
2212
2154
  import { execSync as execSync5 } from "child_process";
2213
- var SETTINGS_PATH = path13.join(".claude", "settings.json");
2155
+ var SETTINGS_PATH = path11.join(".claude", "settings.json");
2214
2156
  var HOOK_COMMAND = "caliber refresh --quiet";
2215
2157
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
2216
2158
  function readSettings() {
2217
- if (!fs14.existsSync(SETTINGS_PATH)) return {};
2159
+ if (!fs13.existsSync(SETTINGS_PATH)) return {};
2218
2160
  try {
2219
- return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
2161
+ return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
2220
2162
  } catch {
2221
2163
  return {};
2222
2164
  }
2223
2165
  }
2224
2166
  function writeSettings(settings) {
2225
- const dir = path13.dirname(SETTINGS_PATH);
2226
- if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
2227
- fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
2167
+ const dir = path11.dirname(SETTINGS_PATH);
2168
+ if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
2169
+ fs13.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
2228
2170
  }
2229
2171
  function findHookIndex(sessionEnd) {
2230
2172
  return sessionEnd.findIndex(
@@ -2282,19 +2224,19 @@ ${PRECOMMIT_END}`;
2282
2224
  function getGitHooksDir() {
2283
2225
  try {
2284
2226
  const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
2285
- return path13.join(gitDir, "hooks");
2227
+ return path11.join(gitDir, "hooks");
2286
2228
  } catch {
2287
2229
  return null;
2288
2230
  }
2289
2231
  }
2290
2232
  function getPreCommitPath() {
2291
2233
  const hooksDir = getGitHooksDir();
2292
- return hooksDir ? path13.join(hooksDir, "pre-commit") : null;
2234
+ return hooksDir ? path11.join(hooksDir, "pre-commit") : null;
2293
2235
  }
2294
2236
  function isPreCommitHookInstalled() {
2295
2237
  const hookPath = getPreCommitPath();
2296
- if (!hookPath || !fs14.existsSync(hookPath)) return false;
2297
- const content = fs14.readFileSync(hookPath, "utf-8");
2238
+ if (!hookPath || !fs13.existsSync(hookPath)) return false;
2239
+ const content = fs13.readFileSync(hookPath, "utf-8");
2298
2240
  return content.includes(PRECOMMIT_START);
2299
2241
  }
2300
2242
  function installPreCommitHook() {
@@ -2303,43 +2245,43 @@ function installPreCommitHook() {
2303
2245
  }
2304
2246
  const hookPath = getPreCommitPath();
2305
2247
  if (!hookPath) return { installed: false, alreadyInstalled: false };
2306
- const hooksDir = path13.dirname(hookPath);
2307
- if (!fs14.existsSync(hooksDir)) fs14.mkdirSync(hooksDir, { recursive: true });
2248
+ const hooksDir = path11.dirname(hookPath);
2249
+ if (!fs13.existsSync(hooksDir)) fs13.mkdirSync(hooksDir, { recursive: true });
2308
2250
  let content = "";
2309
- if (fs14.existsSync(hookPath)) {
2310
- content = fs14.readFileSync(hookPath, "utf-8");
2251
+ if (fs13.existsSync(hookPath)) {
2252
+ content = fs13.readFileSync(hookPath, "utf-8");
2311
2253
  if (!content.endsWith("\n")) content += "\n";
2312
2254
  content += "\n" + PRECOMMIT_BLOCK + "\n";
2313
2255
  } else {
2314
2256
  content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
2315
2257
  }
2316
- fs14.writeFileSync(hookPath, content);
2317
- fs14.chmodSync(hookPath, 493);
2258
+ fs13.writeFileSync(hookPath, content);
2259
+ fs13.chmodSync(hookPath, 493);
2318
2260
  return { installed: true, alreadyInstalled: false };
2319
2261
  }
2320
2262
  function removePreCommitHook() {
2321
2263
  const hookPath = getPreCommitPath();
2322
- if (!hookPath || !fs14.existsSync(hookPath)) {
2264
+ if (!hookPath || !fs13.existsSync(hookPath)) {
2323
2265
  return { removed: false, notFound: true };
2324
2266
  }
2325
- let content = fs14.readFileSync(hookPath, "utf-8");
2267
+ let content = fs13.readFileSync(hookPath, "utf-8");
2326
2268
  if (!content.includes(PRECOMMIT_START)) {
2327
2269
  return { removed: false, notFound: true };
2328
2270
  }
2329
2271
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
2330
2272
  content = content.replace(regex, "\n");
2331
2273
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
2332
- fs14.unlinkSync(hookPath);
2274
+ fs13.unlinkSync(hookPath);
2333
2275
  } else {
2334
- fs14.writeFileSync(hookPath, content);
2276
+ fs13.writeFileSync(hookPath, content);
2335
2277
  }
2336
2278
  return { removed: true, notFound: false };
2337
2279
  }
2338
2280
 
2339
2281
  // src/lib/learning-hooks.ts
2340
- import fs15 from "fs";
2341
- import path14 from "path";
2342
- var SETTINGS_PATH2 = path14.join(".claude", "settings.json");
2282
+ import fs14 from "fs";
2283
+ import path12 from "path";
2284
+ var SETTINGS_PATH2 = path12.join(".claude", "settings.json");
2343
2285
  var HOOK_CONFIGS = [
2344
2286
  {
2345
2287
  event: "PostToolUse",
@@ -2358,17 +2300,17 @@ var HOOK_CONFIGS = [
2358
2300
  }
2359
2301
  ];
2360
2302
  function readSettings2() {
2361
- if (!fs15.existsSync(SETTINGS_PATH2)) return {};
2303
+ if (!fs14.existsSync(SETTINGS_PATH2)) return {};
2362
2304
  try {
2363
- return JSON.parse(fs15.readFileSync(SETTINGS_PATH2, "utf-8"));
2305
+ return JSON.parse(fs14.readFileSync(SETTINGS_PATH2, "utf-8"));
2364
2306
  } catch {
2365
2307
  return {};
2366
2308
  }
2367
2309
  }
2368
2310
  function writeSettings2(settings) {
2369
- const dir = path14.dirname(SETTINGS_PATH2);
2370
- if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
2371
- fs15.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
2311
+ const dir = path12.dirname(SETTINGS_PATH2);
2312
+ if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
2313
+ fs14.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
2372
2314
  }
2373
2315
  function hasLearningHook(matchers, command) {
2374
2316
  return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
@@ -2425,23 +2367,23 @@ function removeLearningHooks() {
2425
2367
 
2426
2368
  // src/lib/state.ts
2427
2369
  init_constants();
2428
- import fs16 from "fs";
2429
- import path15 from "path";
2370
+ import fs15 from "fs";
2371
+ import path13 from "path";
2430
2372
  import { execSync as execSync6 } from "child_process";
2431
- var STATE_FILE = path15.join(CALIBER_DIR, ".caliber-state.json");
2373
+ var STATE_FILE = path13.join(CALIBER_DIR, ".caliber-state.json");
2432
2374
  function readState() {
2433
2375
  try {
2434
- if (!fs16.existsSync(STATE_FILE)) return null;
2435
- return JSON.parse(fs16.readFileSync(STATE_FILE, "utf-8"));
2376
+ if (!fs15.existsSync(STATE_FILE)) return null;
2377
+ return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
2436
2378
  } catch {
2437
2379
  return null;
2438
2380
  }
2439
2381
  }
2440
2382
  function writeState(state) {
2441
- if (!fs16.existsSync(CALIBER_DIR)) {
2442
- fs16.mkdirSync(CALIBER_DIR, { recursive: true });
2383
+ if (!fs15.existsSync(CALIBER_DIR)) {
2384
+ fs15.mkdirSync(CALIBER_DIR, { recursive: true });
2443
2385
  }
2444
- fs16.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
2386
+ fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
2445
2387
  }
2446
2388
  function getCurrentHeadSha() {
2447
2389
  try {
@@ -2724,15 +2666,15 @@ function computeGrade(score) {
2724
2666
  // src/scoring/checks/coverage.ts
2725
2667
  import { readFileSync, readdirSync } from "fs";
2726
2668
  import { join } from "path";
2727
- function readFileOrNull(path26) {
2669
+ function readFileOrNull(path23) {
2728
2670
  try {
2729
- return readFileSync(path26, "utf-8");
2671
+ return readFileSync(path23, "utf-8");
2730
2672
  } catch {
2731
2673
  return null;
2732
2674
  }
2733
2675
  }
2734
- function readJsonOrNull(path26) {
2735
- const content = readFileOrNull(path26);
2676
+ function readJsonOrNull(path23) {
2677
+ const content = readFileOrNull(path23);
2736
2678
  if (!content) return null;
2737
2679
  try {
2738
2680
  return JSON.parse(content);
@@ -3088,9 +3030,9 @@ function checkExistence(dir) {
3088
3030
  // src/scoring/checks/quality.ts
3089
3031
  import { readFileSync as readFileSync3 } from "fs";
3090
3032
  import { join as join3 } from "path";
3091
- function readFileOrNull2(path26) {
3033
+ function readFileOrNull2(path23) {
3092
3034
  try {
3093
- return readFileSync3(path26, "utf-8");
3035
+ return readFileSync3(path23, "utf-8");
3094
3036
  } catch {
3095
3037
  return null;
3096
3038
  }
@@ -3241,15 +3183,15 @@ function checkQuality(dir) {
3241
3183
  // src/scoring/checks/accuracy.ts
3242
3184
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
3243
3185
  import { join as join4 } from "path";
3244
- function readFileOrNull3(path26) {
3186
+ function readFileOrNull3(path23) {
3245
3187
  try {
3246
- return readFileSync4(path26, "utf-8");
3188
+ return readFileSync4(path23, "utf-8");
3247
3189
  } catch {
3248
3190
  return null;
3249
3191
  }
3250
3192
  }
3251
- function readJsonOrNull2(path26) {
3252
- const content = readFileOrNull3(path26);
3193
+ function readJsonOrNull2(path23) {
3194
+ const content = readFileOrNull3(path23);
3253
3195
  if (!content) return null;
3254
3196
  try {
3255
3197
  return JSON.parse(content);
@@ -3432,9 +3374,9 @@ function checkAccuracy(dir) {
3432
3374
  // src/scoring/checks/freshness.ts
3433
3375
  import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3434
3376
  import { join as join5 } from "path";
3435
- function readFileOrNull4(path26) {
3377
+ function readFileOrNull4(path23) {
3436
3378
  try {
3437
- return readFileSync5(path26, "utf-8");
3379
+ return readFileSync5(path23, "utf-8");
3438
3380
  } catch {
3439
3381
  return null;
3440
3382
  }
@@ -3544,9 +3486,9 @@ function checkFreshness(dir) {
3544
3486
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
3545
3487
  import { execSync as execSync7 } from "child_process";
3546
3488
  import { join as join6 } from "path";
3547
- function readFileOrNull5(path26) {
3489
+ function readFileOrNull5(path23) {
3548
3490
  try {
3549
- return readFileSync6(path26, "utf-8");
3491
+ return readFileSync6(path23, "utf-8");
3550
3492
  } catch {
3551
3493
  return null;
3552
3494
  }
@@ -3641,22 +3583,22 @@ function checkBonus(dir) {
3641
3583
 
3642
3584
  // src/scoring/dismissed.ts
3643
3585
  init_constants();
3644
- import fs17 from "fs";
3645
- import path16 from "path";
3646
- var DISMISSED_FILE = path16.join(CALIBER_DIR, "dismissed-checks.json");
3586
+ import fs16 from "fs";
3587
+ import path14 from "path";
3588
+ var DISMISSED_FILE = path14.join(CALIBER_DIR, "dismissed-checks.json");
3647
3589
  function readDismissedChecks() {
3648
3590
  try {
3649
- if (!fs17.existsSync(DISMISSED_FILE)) return [];
3650
- return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
3591
+ if (!fs16.existsSync(DISMISSED_FILE)) return [];
3592
+ return JSON.parse(fs16.readFileSync(DISMISSED_FILE, "utf-8"));
3651
3593
  } catch {
3652
3594
  return [];
3653
3595
  }
3654
3596
  }
3655
3597
  function writeDismissedChecks(checks) {
3656
- if (!fs17.existsSync(CALIBER_DIR)) {
3657
- fs17.mkdirSync(CALIBER_DIR, { recursive: true });
3598
+ if (!fs16.existsSync(CALIBER_DIR)) {
3599
+ fs16.mkdirSync(CALIBER_DIR, { recursive: true });
3658
3600
  }
3659
- fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
3601
+ fs16.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
3660
3602
  }
3661
3603
  function getDismissedIds() {
3662
3604
  return new Set(readDismissedChecks().map((c) => c.id));
@@ -3857,283 +3799,8 @@ function displayScoreDelta(before, after) {
3857
3799
  import chalk4 from "chalk";
3858
3800
  import ora from "ora";
3859
3801
  import readline3 from "readline";
3860
- import fs19 from "fs";
3861
- import path18 from "path";
3862
-
3863
- // src/mcp/deps.ts
3864
- import fs18 from "fs";
3865
- import path17 from "path";
3866
- function extractAllDeps(dir) {
3867
- const deps = /* @__PURE__ */ new Set();
3868
- parsePackageJson(dir, deps);
3869
- parseRequirementsTxt(dir, deps);
3870
- parsePyprojectToml(dir, deps);
3871
- parseGoMod(dir, deps);
3872
- parseCargoToml(dir, deps);
3873
- parseGemfile(dir, deps);
3874
- parseComposerJson(dir, deps);
3875
- return Array.from(deps);
3876
- }
3877
- function readFileSafe(filePath) {
3878
- try {
3879
- if (fs18.existsSync(filePath)) {
3880
- return fs18.readFileSync(filePath, "utf-8");
3881
- }
3882
- } catch {
3883
- }
3884
- return null;
3885
- }
3886
- function parsePackageJson(dir, deps) {
3887
- const content = readFileSafe(path17.join(dir, "package.json"));
3888
- if (!content) return;
3889
- try {
3890
- const pkg3 = JSON.parse(content);
3891
- const allDeps = {
3892
- ...pkg3.dependencies,
3893
- ...pkg3.devDependencies
3894
- };
3895
- for (const name of Object.keys(allDeps)) {
3896
- deps.add(name);
3897
- }
3898
- } catch {
3899
- }
3900
- }
3901
- function parseRequirementsTxt(dir, deps) {
3902
- const content = readFileSafe(path17.join(dir, "requirements.txt"));
3903
- if (!content) return;
3904
- for (const line of content.split("\n")) {
3905
- const trimmed = line.trim();
3906
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
3907
- const match = trimmed.match(/^([a-zA-Z0-9_-]+(?:\[[^\]]*\])?)/);
3908
- if (match) {
3909
- deps.add(match[1].replace(/\[.*\]/, "").toLowerCase());
3910
- }
3911
- }
3912
- }
3913
- function parsePyprojectToml(dir, deps) {
3914
- const content = readFileSafe(path17.join(dir, "pyproject.toml"));
3915
- if (!content) return;
3916
- const depsMatch = content.match(/\bdependencies\s*=\s*\[([\s\S]*?)\]/);
3917
- if (depsMatch) {
3918
- const items = depsMatch[1].matchAll(/"([a-zA-Z0-9_-]+)/g);
3919
- for (const m of items) {
3920
- deps.add(m[1].toLowerCase());
3921
- }
3922
- }
3923
- }
3924
- function parseGoMod(dir, deps) {
3925
- const content = readFileSafe(path17.join(dir, "go.mod"));
3926
- if (!content) return;
3927
- const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/g);
3928
- if (requireBlock) {
3929
- for (const block of requireBlock) {
3930
- const lines = block.split("\n");
3931
- for (const line of lines) {
3932
- const match = line.trim().match(/^([a-zA-Z0-9./\-_]+)\s/);
3933
- if (match && !match[1].startsWith("//") && match[1].includes("/")) {
3934
- const parts = match[1].split("/");
3935
- deps.add(parts[parts.length - 1]);
3936
- }
3937
- }
3938
- }
3939
- }
3940
- }
3941
- function parseCargoToml(dir, deps) {
3942
- const content = readFileSafe(path17.join(dir, "Cargo.toml"));
3943
- if (!content) return;
3944
- const sections = content.split(/\[/);
3945
- for (const section of sections) {
3946
- if (section.startsWith("dependencies]") || section.startsWith("dev-dependencies]")) {
3947
- const lines = section.split("\n").slice(1);
3948
- for (const line of lines) {
3949
- if (line.startsWith("[")) break;
3950
- const match = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
3951
- if (match) {
3952
- deps.add(match[1]);
3953
- }
3954
- }
3955
- }
3956
- }
3957
- }
3958
- function parseGemfile(dir, deps) {
3959
- const content = readFileSafe(path17.join(dir, "Gemfile"));
3960
- if (!content) return;
3961
- const gemPattern = /gem\s+['"]([a-zA-Z0-9_-]+)['"]/g;
3962
- let match;
3963
- while ((match = gemPattern.exec(content)) !== null) {
3964
- deps.add(match[1]);
3965
- }
3966
- }
3967
- function parseComposerJson(dir, deps) {
3968
- const content = readFileSafe(path17.join(dir, "composer.json"));
3969
- if (!content) return;
3970
- try {
3971
- const composer = JSON.parse(content);
3972
- const allDeps = {
3973
- ...composer.require,
3974
- ...composer["require-dev"]
3975
- };
3976
- for (const name of Object.keys(allDeps)) {
3977
- if (name !== "php" && !name.startsWith("ext-")) {
3978
- deps.add(name);
3979
- }
3980
- }
3981
- } catch {
3982
- }
3983
- }
3984
-
3985
- // src/mcp/prompts.ts
3986
- var CLASSIFY_DEPS_PROMPT = `You classify software dependencies into two categories:
3987
-
3988
- **Tools** (MCP-worthy): Services, platforms, APIs, databases, SaaS products, and cloud services that have their own web dashboards, APIs, or external infrastructure. Examples: supabase, stripe, sentry, datadog, firebase, mongodb, redis, slack, linear, github, vercel, aws-sdk, twilio, sendgrid, algolia, elasticsearch, prisma, planetscale, neon, clerk, auth0.
3989
-
3990
- **Libraries** (skip): Utility packages, frameworks, build tools, test runners, and local-only code that does NOT connect to an external service. Examples: lodash, react, express, vitest, webpack, zod, chalk, commander, typescript, eslint, prettier, axios, dayjs, uuid.
3991
-
3992
- Given a list of dependencies, return ONLY the tool dependencies as a JSON array of strings.
3993
- Return ONLY the JSON array, no explanation.`;
3994
- var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
3995
-
3996
- Score each candidate from 0-100 based on:
3997
- - **Relevance** (40%): How directly does it match the project's detected tool dependencies?
3998
- - **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
3999
- - **Quality signals** (30%): Stars, recent activity, vendor/official status
4000
-
4001
- Return a JSON array where each element has:
4002
- - "index": the candidate's index number
4003
- - "score": relevance score 0-100
4004
- - "reason": one-liner explaining the score (max 80 chars)
4005
-
4006
- Be selective. Only score candidates that would genuinely help developers working on this project.
4007
- Return ONLY the JSON array.`;
4008
- var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
4009
-
4010
- Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
4011
-
4012
- Return a JSON object with:
4013
- - "command": the executable command (e.g., "npx", "uvx", "node", "docker")
4014
- - "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
4015
- - "env": array of objects, each with:
4016
- - "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
4017
- - "description": brief description of what this value is (e.g., "Personal access token from dashboard")
4018
- - "required": boolean
4019
-
4020
- If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
4021
- If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
4022
- Return ONLY the JSON object.`;
4023
-
4024
- // src/mcp/classify.ts
4025
- async function classifyDeps(allDeps) {
4026
- if (allDeps.length === 0) return [];
4027
- try {
4028
- const result = await llmJsonCall({
4029
- system: CLASSIFY_DEPS_PROMPT,
4030
- prompt: `Dependencies:
4031
- ${JSON.stringify(allDeps)}`,
4032
- maxTokens: 2e3
4033
- });
4034
- if (!Array.isArray(result)) return [];
4035
- const inputLower = new Set(allDeps.map((d) => d.toLowerCase()));
4036
- return result.filter((d) => typeof d === "string" && inputLower.has(d.toLowerCase()));
4037
- } catch {
4038
- return fallbackClassify(allDeps);
4039
- }
4040
- }
4041
- function fallbackClassify(deps) {
4042
- const utilityPatterns = [
4043
- /^@types\//,
4044
- /^eslint/,
4045
- /^prettier/,
4046
- /^@typescript-eslint\//,
4047
- /^@commitlint\//,
4048
- /^@eslint\//,
4049
- /^webpack/,
4050
- /^rollup/,
4051
- /^babel/,
4052
- /^@babel\//,
4053
- /^postcss/,
4054
- /^tailwindcss/,
4055
- /^autoprefixer/
4056
- ];
4057
- const utilities = /* @__PURE__ */ new Set([
4058
- "typescript",
4059
- "tslib",
4060
- "ts-node",
4061
- "tsx",
4062
- "prettier",
4063
- "eslint",
4064
- "rimraf",
4065
- "cross-env",
4066
- "dotenv",
4067
- "nodemon",
4068
- "husky",
4069
- "lint-staged",
4070
- "commitlint",
4071
- "chalk",
4072
- "ora",
4073
- "commander",
4074
- "yargs",
4075
- "meow",
4076
- "inquirer",
4077
- "glob",
4078
- "minimatch",
4079
- "micromatch",
4080
- "diff",
4081
- "semver",
4082
- "uuid",
4083
- "nanoid",
4084
- "debug",
4085
- "ms",
4086
- "lodash",
4087
- "underscore",
4088
- "ramda",
4089
- "tsup",
4090
- "esbuild",
4091
- "rollup",
4092
- "webpack",
4093
- "vite",
4094
- "parcel",
4095
- "vitest",
4096
- "jest",
4097
- "mocha",
4098
- "chai",
4099
- "ava",
4100
- "tap",
4101
- "fs-extra",
4102
- "mkdirp",
4103
- "del",
4104
- "path-to-regexp",
4105
- "strip-ansi",
4106
- "ansi-colors",
4107
- "react",
4108
- "react-dom",
4109
- "next",
4110
- "vue",
4111
- "angular",
4112
- "svelte",
4113
- "express",
4114
- "fastify",
4115
- "koa",
4116
- "hapi",
4117
- "zod",
4118
- "joi",
4119
- "yup",
4120
- "ajv",
4121
- "axios",
4122
- "node-fetch",
4123
- "got",
4124
- "undici",
4125
- "moment",
4126
- "dayjs",
4127
- "date-fns",
4128
- "luxon"
4129
- ]);
4130
- return deps.filter((d) => {
4131
- const lower = d.toLowerCase();
4132
- if (utilities.has(lower)) return false;
4133
- if (utilityPatterns.some((p) => p.test(lower))) return false;
4134
- return true;
4135
- });
4136
- }
3802
+ import fs17 from "fs";
3803
+ import path15 from "path";
4137
3804
 
4138
3805
  // src/mcp/search.ts
4139
3806
  var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
@@ -4285,6 +3952,37 @@ async function searchVendorOrg(dep) {
4285
3952
  }
4286
3953
  }
4287
3954
 
3955
+ // src/mcp/prompts.ts
3956
+ var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
3957
+
3958
+ Score each candidate from 0-100 based on:
3959
+ - **Relevance** (40%): How directly does it match the project's detected tool dependencies?
3960
+ - **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
3961
+ - **Quality signals** (30%): Stars, recent activity, vendor/official status
3962
+
3963
+ Return a JSON array where each element has:
3964
+ - "index": the candidate's index number
3965
+ - "score": relevance score 0-100
3966
+ - "reason": one-liner explaining the score (max 80 chars)
3967
+
3968
+ Be selective. Only score candidates that would genuinely help developers working on this project.
3969
+ Return ONLY the JSON array.`;
3970
+ var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
3971
+
3972
+ Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
3973
+
3974
+ Return a JSON object with:
3975
+ - "command": the executable command (e.g., "npx", "uvx", "node", "docker")
3976
+ - "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
3977
+ - "env": array of objects, each with:
3978
+ - "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
3979
+ - "description": brief description of what this value is (e.g., "Personal access token from dashboard")
3980
+ - "required": boolean
3981
+
3982
+ If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
3983
+ If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
3984
+ Return ONLY the JSON object.`;
3985
+
4288
3986
  // src/mcp/validate.ts
4289
3987
  var MIN_STARS = 100;
4290
3988
  var MAX_AGE_DAYS = 180;
@@ -4377,40 +4075,28 @@ ${truncated}`,
4377
4075
  // src/mcp/index.ts
4378
4076
  async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
4379
4077
  console.log(chalk4.hex("#6366f1").bold("\n MCP Server Discovery\n"));
4380
- const spinner = ora("Analyzing dependencies for tool integrations...").start();
4381
- const allDeps = extractAllDeps(dir);
4382
- if (allDeps.length === 0) {
4383
- spinner.succeed(chalk4.dim("No dependencies found \u2014 skipping MCP discovery"));
4384
- return { installed: 0, names: [] };
4385
- }
4386
- const toolDeps = await classifyDeps(allDeps);
4078
+ const toolDeps = fingerprint.tools;
4387
4079
  if (toolDeps.length === 0) {
4388
- spinner.succeed(chalk4.dim("No tool dependencies detected \u2014 skipping MCP discovery"));
4389
- console.log(chalk4.dim(` All deps (${allDeps.length}): ${allDeps.slice(0, 10).join(", ")}${allDeps.length > 10 ? "..." : ""}`));
4080
+ console.log(chalk4.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
4390
4081
  return { installed: 0, names: [] };
4391
4082
  }
4392
- spinner.succeed(`Found ${toolDeps.length} tool dependenc${toolDeps.length === 1 ? "y" : "ies"}: ${toolDeps.join(", ")}`);
4393
- console.log(chalk4.dim(` All deps (${allDeps.length}): ${allDeps.slice(0, 10).join(", ")}${allDeps.length > 10 ? "..." : ""}`));
4394
- console.log(chalk4.dim(` Classified as tools: ${toolDeps.join(", ")}`));
4083
+ const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
4084
+ console.log(chalk4.dim(` Detected: ${toolDeps.join(", ")}`));
4395
4085
  const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
4396
4086
  const filteredDeps = toolDeps.filter((d) => {
4397
- const lower = d.toLowerCase().replace(/^@[^/]+\//, "");
4087
+ const lower = d.toLowerCase();
4398
4088
  return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
4399
4089
  });
4400
4090
  if (filteredDeps.length === 0) {
4401
- console.log(chalk4.dim(" All detected tools already have MCP servers configured."));
4402
- console.log(chalk4.dim(` Existing MCPs: ${existingMcps.join(", ")}`));
4091
+ spinner.succeed(chalk4.dim("All detected tools already have MCP servers configured"));
4403
4092
  return { installed: 0, names: [] };
4404
4093
  }
4405
- const searchSpinner = ora("Searching for MCP servers...").start();
4406
4094
  const candidates = await searchAllMcpSources(filteredDeps);
4407
4095
  if (candidates.length === 0) {
4408
- searchSpinner.succeed(chalk4.dim("No MCP servers found for your dependencies"));
4409
- console.log(chalk4.dim(` Searched for: ${filteredDeps.join(", ")}`));
4096
+ spinner.succeed(chalk4.dim("No MCP servers found for detected tools"));
4410
4097
  return { installed: 0, names: [] };
4411
4098
  }
4412
- searchSpinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"}`);
4413
- console.log(chalk4.dim(` Sources: ${candidates.map((c) => c.repoFullName).join(", ")}`));
4099
+ spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
4414
4100
  const scoreSpinner = ora("Scoring MCP candidates...").start();
4415
4101
  const scored = await validateAndScore(candidates, filteredDeps);
4416
4102
  if (scored.length === 0) {
@@ -4462,26 +4148,26 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
4462
4148
  return { installed: 0, names: [] };
4463
4149
  }
4464
4150
  if (targetAgent === "claude" || targetAgent === "both") {
4465
- writeMcpJson(path18.join(dir, ".mcp.json"), mcpServers);
4151
+ writeMcpJson(path15.join(dir, ".mcp.json"), mcpServers);
4466
4152
  }
4467
4153
  if (targetAgent === "cursor" || targetAgent === "both") {
4468
- const cursorDir = path18.join(dir, ".cursor");
4469
- if (!fs19.existsSync(cursorDir)) fs19.mkdirSync(cursorDir, { recursive: true });
4470
- writeMcpJson(path18.join(cursorDir, "mcp.json"), mcpServers);
4154
+ const cursorDir = path15.join(dir, ".cursor");
4155
+ if (!fs17.existsSync(cursorDir)) fs17.mkdirSync(cursorDir, { recursive: true });
4156
+ writeMcpJson(path15.join(cursorDir, "mcp.json"), mcpServers);
4471
4157
  }
4472
4158
  return { installed: installedNames.length, names: installedNames };
4473
4159
  }
4474
4160
  function writeMcpJson(filePath, mcpServers) {
4475
4161
  let existing = {};
4476
4162
  try {
4477
- if (fs19.existsSync(filePath)) {
4478
- const parsed = JSON.parse(fs19.readFileSync(filePath, "utf-8"));
4163
+ if (fs17.existsSync(filePath)) {
4164
+ const parsed = JSON.parse(fs17.readFileSync(filePath, "utf-8"));
4479
4165
  if (parsed.mcpServers) existing = parsed.mcpServers;
4480
4166
  }
4481
4167
  } catch {
4482
4168
  }
4483
4169
  const merged = { ...existing, ...mcpServers };
4484
- fs19.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
4170
+ fs17.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
4485
4171
  }
4486
4172
  function getExistingMcpNames(fingerprint, targetAgent) {
4487
4173
  const names = [];
@@ -4618,11 +4304,12 @@ async function initCommand(options) {
4618
4304
  console.log(chalk5.dim(" Caliber analyzes your codebase and creates tailored config files"));
4619
4305
  console.log(chalk5.dim(" so your AI coding agents understand your project from day one.\n"));
4620
4306
  console.log(title.bold(" How onboarding works:\n"));
4621
- console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
4622
- console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
4623
- console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
4624
- console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes\n"));
4625
- console.log(title.bold(" Step 1/4 \u2014 Connect your LLM\n"));
4307
+ console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
4308
+ console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
4309
+ console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
4310
+ console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes"));
4311
+ console.log(chalk5.dim(" 5. Enhance Discover MCP servers for your tools\n"));
4312
+ console.log(title.bold(" Step 1/5 \u2014 Connect your LLM\n"));
4626
4313
  let config = loadConfig();
4627
4314
  if (!config) {
4628
4315
  console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
@@ -4642,10 +4329,10 @@ async function initCommand(options) {
4642
4329
  console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
4643
4330
  }
4644
4331
  const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
4645
- const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4332
+ const fastModel = getFastModel();
4646
4333
  const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
4647
4334
  console.log(chalk5.dim(modelLine + "\n"));
4648
- console.log(title.bold(" Step 2/4 \u2014 Discover your project\n"));
4335
+ console.log(title.bold(" Step 2/5 \u2014 Discover your project\n"));
4649
4336
  console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
4650
4337
  const spinner = ora2("Analyzing project...").start();
4651
4338
  const fingerprint = collectFingerprint(process.cwd());
@@ -4686,7 +4373,7 @@ async function initCommand(options) {
4686
4373
  passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
4687
4374
  currentScore = baselineScore.score;
4688
4375
  if (failingChecks.length > 0) {
4689
- console.log(title.bold(" Step 3/4 \u2014 Fine-tuning\n"));
4376
+ console.log(title.bold(" Step 3/5 \u2014 Fine-tuning\n"));
4690
4377
  console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
4691
4378
  `));
4692
4379
  for (const check of failingChecks) {
@@ -4695,11 +4382,11 @@ async function initCommand(options) {
4695
4382
  console.log("");
4696
4383
  }
4697
4384
  } else if (hasExistingConfig) {
4698
- console.log(title.bold(" Step 3/4 \u2014 Improve your setup\n"));
4385
+ console.log(title.bold(" Step 3/5 \u2014 Improve your setup\n"));
4699
4386
  console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
4700
4387
  console.log(chalk5.dim(" and preparing improvements.\n"));
4701
4388
  } else {
4702
- console.log(title.bold(" Step 3/4 \u2014 Build your agent setup\n"));
4389
+ console.log(title.bold(" Step 3/5 \u2014 Build your agent setup\n"));
4703
4390
  console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
4704
4391
  }
4705
4392
  console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
@@ -4760,7 +4447,7 @@ async function initCommand(options) {
4760
4447
  role: "assistant",
4761
4448
  content: summarizeSetup("Initial generation", generatedSetup)
4762
4449
  });
4763
- console.log(title.bold(" Step 4/4 \u2014 Review and apply\n"));
4450
+ console.log(title.bold(" Step 4/5 \u2014 Review and apply\n"));
4764
4451
  const setupFiles = collectSetupFiles(generatedSetup);
4765
4452
  const staged = stageFiles(setupFiles, process.cwd());
4766
4453
  console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
@@ -4819,17 +4506,24 @@ async function initCommand(options) {
4819
4506
  console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
4820
4507
  throw new Error("__exit__");
4821
4508
  }
4822
- try {
4823
- const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
4824
- if (mcpResult.installed > 0) {
4825
- console.log(chalk5.bold(`
4509
+ console.log(title.bold("\n Step 5/5 \u2014 Enhance with MCP servers\n"));
4510
+ console.log(chalk5.dim(" MCP servers connect your AI agents to external tools and services"));
4511
+ console.log(chalk5.dim(" like databases, APIs, and platforms your project depends on.\n"));
4512
+ if (fingerprint.tools.length > 0) {
4513
+ try {
4514
+ const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
4515
+ if (mcpResult.installed > 0) {
4516
+ console.log(chalk5.bold(`
4826
4517
  ${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
4827
- for (const name of mcpResult.names) {
4828
- console.log(` ${chalk5.green("\u2713")} ${name}`);
4518
+ for (const name of mcpResult.names) {
4519
+ console.log(` ${chalk5.green("\u2713")} ${name}`);
4520
+ }
4829
4521
  }
4522
+ } catch (err) {
4523
+ console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
4830
4524
  }
4831
- } catch (err) {
4832
- console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
4525
+ } else {
4526
+ console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
4833
4527
  }
4834
4528
  ensurePermissions();
4835
4529
  const sha = getCurrentHeadSha();
@@ -4938,12 +4632,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
4938
4632
  }
4939
4633
  function summarizeSetup(action, setup) {
4940
4634
  const descriptions = setup.fileDescriptions;
4941
- const files = descriptions ? Object.entries(descriptions).map(([path26, desc]) => ` ${path26}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4635
+ const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4942
4636
  return `${action}. Files:
4943
4637
  ${files}`;
4944
4638
  }
4945
4639
  async function classifyRefineIntent(message) {
4946
- const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4640
+ const fastModel = getFastModel();
4947
4641
  try {
4948
4642
  const result = await llmJsonCall({
4949
4643
  system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
@@ -4960,7 +4654,7 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
4960
4654
  }
4961
4655
  }
4962
4656
  async function evaluateDismissals(failingChecks, fingerprint) {
4963
- const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4657
+ const fastModel = getFastModel();
4964
4658
  const checkList = failingChecks.map((c) => ({
4965
4659
  id: c.id,
4966
4660
  name: c.name,
@@ -5058,8 +4752,8 @@ async function openReview(method, stagedFiles) {
5058
4752
  return;
5059
4753
  }
5060
4754
  const fileInfos = stagedFiles.map((file) => {
5061
- const proposed = fs20.readFileSync(file.proposedPath, "utf-8");
5062
- const current = file.currentPath ? fs20.readFileSync(file.currentPath, "utf-8") : "";
4755
+ const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
4756
+ const current = file.currentPath ? fs18.readFileSync(file.currentPath, "utf-8") : "";
5063
4757
  const patch = createTwoFilesPatch(
5064
4758
  file.isNew ? "/dev/null" : file.relativePath,
5065
4759
  file.relativePath,
@@ -5242,7 +4936,7 @@ function printSetupSummary(setup) {
5242
4936
  };
5243
4937
  if (claude) {
5244
4938
  if (claude.claudeMd) {
5245
- const icon = fs20.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
4939
+ const icon = fs18.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
5246
4940
  const desc = getDescription("CLAUDE.md");
5247
4941
  console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
5248
4942
  if (desc) console.log(chalk5.dim(` ${desc}`));
@@ -5252,7 +4946,7 @@ function printSetupSummary(setup) {
5252
4946
  if (Array.isArray(skills) && skills.length > 0) {
5253
4947
  for (const skill of skills) {
5254
4948
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
5255
- const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
4949
+ const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
5256
4950
  const desc = getDescription(skillPath);
5257
4951
  console.log(` ${icon} ${chalk5.bold(skillPath)}`);
5258
4952
  console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
@@ -5262,7 +4956,7 @@ function printSetupSummary(setup) {
5262
4956
  }
5263
4957
  if (cursor) {
5264
4958
  if (cursor.cursorrules) {
5265
- const icon = fs20.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
4959
+ const icon = fs18.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
5266
4960
  const desc = getDescription(".cursorrules");
5267
4961
  console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
5268
4962
  if (desc) console.log(chalk5.dim(` ${desc}`));
@@ -5272,7 +4966,7 @@ function printSetupSummary(setup) {
5272
4966
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
5273
4967
  for (const skill of cursorSkills) {
5274
4968
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
5275
- const icon = fs20.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
4969
+ const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
5276
4970
  const desc = getDescription(skillPath);
5277
4971
  console.log(` ${icon} ${chalk5.bold(skillPath)}`);
5278
4972
  console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
@@ -5283,7 +4977,7 @@ function printSetupSummary(setup) {
5283
4977
  if (Array.isArray(rules) && rules.length > 0) {
5284
4978
  for (const rule of rules) {
5285
4979
  const rulePath = `.cursor/rules/${rule.filename}`;
5286
- const icon = fs20.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
4980
+ const icon = fs18.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
5287
4981
  const desc = getDescription(rulePath);
5288
4982
  console.log(` ${icon} ${chalk5.bold(rulePath)}`);
5289
4983
  if (desc) {
@@ -5296,7 +4990,7 @@ function printSetupSummary(setup) {
5296
4990
  }
5297
4991
  }
5298
4992
  }
5299
- if (!fs20.existsSync("AGENTS.md")) {
4993
+ if (!fs18.existsSync("AGENTS.md")) {
5300
4994
  console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
5301
4995
  console.log(chalk5.dim(" Cross-agent coordination file"));
5302
4996
  console.log("");
@@ -5324,8 +5018,8 @@ function ensurePermissions() {
5324
5018
  const settingsPath = ".claude/settings.json";
5325
5019
  let settings = {};
5326
5020
  try {
5327
- if (fs20.existsSync(settingsPath)) {
5328
- settings = JSON.parse(fs20.readFileSync(settingsPath, "utf-8"));
5021
+ if (fs18.existsSync(settingsPath)) {
5022
+ settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
5329
5023
  }
5330
5024
  } catch {
5331
5025
  }
@@ -5339,8 +5033,8 @@ function ensurePermissions() {
5339
5033
  "Bash(git *)"
5340
5034
  ];
5341
5035
  settings.permissions = permissions;
5342
- if (!fs20.existsSync(".claude")) fs20.mkdirSync(".claude", { recursive: true });
5343
- fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5036
+ if (!fs18.existsSync(".claude")) fs18.mkdirSync(".claude", { recursive: true });
5037
+ fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5344
5038
  }
5345
5039
  function collectSetupFiles(setup) {
5346
5040
  const files = [];
@@ -5370,7 +5064,7 @@ function collectSetupFiles(setup) {
5370
5064
  }
5371
5065
  }
5372
5066
  }
5373
- if (!fs20.existsSync("AGENTS.md")) {
5067
+ if (!fs18.existsSync("AGENTS.md")) {
5374
5068
  const agentRefs = [];
5375
5069
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
5376
5070
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
@@ -5421,7 +5115,7 @@ function undoCommand() {
5421
5115
 
5422
5116
  // src/commands/status.ts
5423
5117
  import chalk7 from "chalk";
5424
- import fs21 from "fs";
5118
+ import fs19 from "fs";
5425
5119
  async function statusCommand(options) {
5426
5120
  const config = loadConfig();
5427
5121
  const manifest = readManifest();
@@ -5447,7 +5141,7 @@ async function statusCommand(options) {
5447
5141
  }
5448
5142
  console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
5449
5143
  for (const entry of manifest.entries) {
5450
- const exists = fs21.existsSync(entry.path);
5144
+ const exists = fs19.existsSync(entry.path);
5451
5145
  const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
5452
5146
  console.log(` ${icon} ${entry.path} (${entry.action})`);
5453
5147
  }
@@ -5534,13 +5228,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
5534
5228
  import { join as join8, dirname as dirname2 } from "path";
5535
5229
 
5536
5230
  // src/scanner/index.ts
5537
- import fs22 from "fs";
5538
- import path19 from "path";
5231
+ import fs20 from "fs";
5232
+ import path16 from "path";
5539
5233
  import crypto2 from "crypto";
5540
5234
  function scanLocalState(dir) {
5541
5235
  const items = [];
5542
- const claudeMdPath = path19.join(dir, "CLAUDE.md");
5543
- if (fs22.existsSync(claudeMdPath)) {
5236
+ const claudeMdPath = path16.join(dir, "CLAUDE.md");
5237
+ if (fs20.existsSync(claudeMdPath)) {
5544
5238
  items.push({
5545
5239
  type: "rule",
5546
5240
  platform: "claude",
@@ -5549,10 +5243,10 @@ function scanLocalState(dir) {
5549
5243
  path: claudeMdPath
5550
5244
  });
5551
5245
  }
5552
- const skillsDir = path19.join(dir, ".claude", "skills");
5553
- if (fs22.existsSync(skillsDir)) {
5554
- for (const file of fs22.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
5555
- const filePath = path19.join(skillsDir, file);
5246
+ const skillsDir = path16.join(dir, ".claude", "skills");
5247
+ if (fs20.existsSync(skillsDir)) {
5248
+ for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
5249
+ const filePath = path16.join(skillsDir, file);
5556
5250
  items.push({
5557
5251
  type: "skill",
5558
5252
  platform: "claude",
@@ -5562,10 +5256,10 @@ function scanLocalState(dir) {
5562
5256
  });
5563
5257
  }
5564
5258
  }
5565
- const mcpJsonPath = path19.join(dir, ".mcp.json");
5566
- if (fs22.existsSync(mcpJsonPath)) {
5259
+ const mcpJsonPath = path16.join(dir, ".mcp.json");
5260
+ if (fs20.existsSync(mcpJsonPath)) {
5567
5261
  try {
5568
- const mcpJson = JSON.parse(fs22.readFileSync(mcpJsonPath, "utf-8"));
5262
+ const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
5569
5263
  if (mcpJson.mcpServers) {
5570
5264
  for (const name of Object.keys(mcpJson.mcpServers)) {
5571
5265
  items.push({
@@ -5580,8 +5274,8 @@ function scanLocalState(dir) {
5580
5274
  } catch {
5581
5275
  }
5582
5276
  }
5583
- const cursorrulesPath = path19.join(dir, ".cursorrules");
5584
- if (fs22.existsSync(cursorrulesPath)) {
5277
+ const cursorrulesPath = path16.join(dir, ".cursorrules");
5278
+ if (fs20.existsSync(cursorrulesPath)) {
5585
5279
  items.push({
5586
5280
  type: "rule",
5587
5281
  platform: "cursor",
@@ -5590,10 +5284,10 @@ function scanLocalState(dir) {
5590
5284
  path: cursorrulesPath
5591
5285
  });
5592
5286
  }
5593
- const cursorRulesDir = path19.join(dir, ".cursor", "rules");
5594
- if (fs22.existsSync(cursorRulesDir)) {
5595
- for (const file of fs22.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
5596
- const filePath = path19.join(cursorRulesDir, file);
5287
+ const cursorRulesDir = path16.join(dir, ".cursor", "rules");
5288
+ if (fs20.existsSync(cursorRulesDir)) {
5289
+ for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
5290
+ const filePath = path16.join(cursorRulesDir, file);
5597
5291
  items.push({
5598
5292
  type: "rule",
5599
5293
  platform: "cursor",
@@ -5603,12 +5297,12 @@ function scanLocalState(dir) {
5603
5297
  });
5604
5298
  }
5605
5299
  }
5606
- const cursorSkillsDir = path19.join(dir, ".cursor", "skills");
5607
- if (fs22.existsSync(cursorSkillsDir)) {
5300
+ const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
5301
+ if (fs20.existsSync(cursorSkillsDir)) {
5608
5302
  try {
5609
- for (const name of fs22.readdirSync(cursorSkillsDir)) {
5610
- const skillFile = path19.join(cursorSkillsDir, name, "SKILL.md");
5611
- if (fs22.existsSync(skillFile)) {
5303
+ for (const name of fs20.readdirSync(cursorSkillsDir)) {
5304
+ const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
5305
+ if (fs20.existsSync(skillFile)) {
5612
5306
  items.push({
5613
5307
  type: "skill",
5614
5308
  platform: "cursor",
@@ -5621,10 +5315,10 @@ function scanLocalState(dir) {
5621
5315
  } catch {
5622
5316
  }
5623
5317
  }
5624
- const cursorMcpPath = path19.join(dir, ".cursor", "mcp.json");
5625
- if (fs22.existsSync(cursorMcpPath)) {
5318
+ const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
5319
+ if (fs20.existsSync(cursorMcpPath)) {
5626
5320
  try {
5627
- const mcpJson = JSON.parse(fs22.readFileSync(cursorMcpPath, "utf-8"));
5321
+ const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
5628
5322
  if (mcpJson.mcpServers) {
5629
5323
  for (const name of Object.keys(mcpJson.mcpServers)) {
5630
5324
  items.push({
@@ -5642,7 +5336,7 @@ function scanLocalState(dir) {
5642
5336
  return items;
5643
5337
  }
5644
5338
  function hashFile(filePath) {
5645
- const text = fs22.readFileSync(filePath, "utf-8");
5339
+ const text = fs20.readFileSync(filePath, "utf-8");
5646
5340
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
5647
5341
  }
5648
5342
  function hashJson(obj) {
@@ -6218,8 +5912,8 @@ async function scoreCommand(options) {
6218
5912
  }
6219
5913
 
6220
5914
  // src/commands/refresh.ts
6221
- import fs24 from "fs";
6222
- import path21 from "path";
5915
+ import fs22 from "fs";
5916
+ import path18 from "path";
6223
5917
  import chalk11 from "chalk";
6224
5918
  import ora6 from "ora";
6225
5919
 
@@ -6296,37 +5990,37 @@ function collectDiff(lastSha) {
6296
5990
  }
6297
5991
 
6298
5992
  // src/writers/refresh.ts
6299
- import fs23 from "fs";
6300
- import path20 from "path";
5993
+ import fs21 from "fs";
5994
+ import path17 from "path";
6301
5995
  function writeRefreshDocs(docs) {
6302
5996
  const written = [];
6303
5997
  if (docs.claudeMd) {
6304
- fs23.writeFileSync("CLAUDE.md", docs.claudeMd);
5998
+ fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
6305
5999
  written.push("CLAUDE.md");
6306
6000
  }
6307
6001
  if (docs.readmeMd) {
6308
- fs23.writeFileSync("README.md", docs.readmeMd);
6002
+ fs21.writeFileSync("README.md", docs.readmeMd);
6309
6003
  written.push("README.md");
6310
6004
  }
6311
6005
  if (docs.cursorrules) {
6312
- fs23.writeFileSync(".cursorrules", docs.cursorrules);
6006
+ fs21.writeFileSync(".cursorrules", docs.cursorrules);
6313
6007
  written.push(".cursorrules");
6314
6008
  }
6315
6009
  if (docs.cursorRules) {
6316
- const rulesDir = path20.join(".cursor", "rules");
6317
- if (!fs23.existsSync(rulesDir)) fs23.mkdirSync(rulesDir, { recursive: true });
6010
+ const rulesDir = path17.join(".cursor", "rules");
6011
+ if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
6318
6012
  for (const rule of docs.cursorRules) {
6319
- const filePath = path20.join(rulesDir, rule.filename);
6320
- fs23.writeFileSync(filePath, rule.content);
6013
+ const filePath = path17.join(rulesDir, rule.filename);
6014
+ fs21.writeFileSync(filePath, rule.content);
6321
6015
  written.push(filePath);
6322
6016
  }
6323
6017
  }
6324
6018
  if (docs.claudeSkills) {
6325
- const skillsDir = path20.join(".claude", "skills");
6326
- if (!fs23.existsSync(skillsDir)) fs23.mkdirSync(skillsDir, { recursive: true });
6019
+ const skillsDir = path17.join(".claude", "skills");
6020
+ if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
6327
6021
  for (const skill of docs.claudeSkills) {
6328
- const filePath = path20.join(skillsDir, skill.filename);
6329
- fs23.writeFileSync(filePath, skill.content);
6022
+ const filePath = path17.join(skillsDir, skill.filename);
6023
+ fs21.writeFileSync(filePath, skill.content);
6330
6024
  written.push(filePath);
6331
6025
  }
6332
6026
  }
@@ -6401,11 +6095,11 @@ function log(quiet, ...args) {
6401
6095
  function discoverGitRepos(parentDir) {
6402
6096
  const repos = [];
6403
6097
  try {
6404
- const entries = fs24.readdirSync(parentDir, { withFileTypes: true });
6098
+ const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
6405
6099
  for (const entry of entries) {
6406
6100
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
6407
- const childPath = path21.join(parentDir, entry.name);
6408
- if (fs24.existsSync(path21.join(childPath, ".git"))) {
6101
+ const childPath = path18.join(parentDir, entry.name);
6102
+ if (fs22.existsSync(path18.join(childPath, ".git"))) {
6409
6103
  repos.push(childPath);
6410
6104
  }
6411
6105
  }
@@ -6500,7 +6194,7 @@ async function refreshCommand(options) {
6500
6194
  `));
6501
6195
  const originalDir = process.cwd();
6502
6196
  for (const repo of repos) {
6503
- const repoName = path21.basename(repo);
6197
+ const repoName = path18.basename(repo);
6504
6198
  try {
6505
6199
  process.chdir(repo);
6506
6200
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -6747,8 +6441,8 @@ function readStdin() {
6747
6441
 
6748
6442
  // src/learner/storage.ts
6749
6443
  init_constants();
6750
- import fs25 from "fs";
6751
- import path22 from "path";
6444
+ import fs23 from "fs";
6445
+ import path19 from "path";
6752
6446
  var MAX_RESPONSE_LENGTH = 2e3;
6753
6447
  var DEFAULT_STATE = {
6754
6448
  sessionId: null,
@@ -6756,15 +6450,15 @@ var DEFAULT_STATE = {
6756
6450
  lastAnalysisTimestamp: null
6757
6451
  };
6758
6452
  function ensureLearningDir() {
6759
- if (!fs25.existsSync(LEARNING_DIR)) {
6760
- fs25.mkdirSync(LEARNING_DIR, { recursive: true });
6453
+ if (!fs23.existsSync(LEARNING_DIR)) {
6454
+ fs23.mkdirSync(LEARNING_DIR, { recursive: true });
6761
6455
  }
6762
6456
  }
6763
6457
  function sessionFilePath() {
6764
- return path22.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6458
+ return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6765
6459
  }
6766
6460
  function stateFilePath() {
6767
- return path22.join(LEARNING_DIR, LEARNING_STATE_FILE);
6461
+ return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
6768
6462
  }
6769
6463
  function truncateResponse(response) {
6770
6464
  const str = JSON.stringify(response);
@@ -6775,50 +6469,50 @@ function appendEvent(event) {
6775
6469
  ensureLearningDir();
6776
6470
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
6777
6471
  const filePath = sessionFilePath();
6778
- fs25.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6472
+ fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6779
6473
  const count = getEventCount();
6780
6474
  if (count > LEARNING_MAX_EVENTS) {
6781
- const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6475
+ const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6782
6476
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
6783
- fs25.writeFileSync(filePath, kept.join("\n") + "\n");
6477
+ fs23.writeFileSync(filePath, kept.join("\n") + "\n");
6784
6478
  }
6785
6479
  }
6786
6480
  function readAllEvents() {
6787
6481
  const filePath = sessionFilePath();
6788
- if (!fs25.existsSync(filePath)) return [];
6789
- const lines = fs25.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6482
+ if (!fs23.existsSync(filePath)) return [];
6483
+ const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6790
6484
  return lines.map((line) => JSON.parse(line));
6791
6485
  }
6792
6486
  function getEventCount() {
6793
6487
  const filePath = sessionFilePath();
6794
- if (!fs25.existsSync(filePath)) return 0;
6795
- const content = fs25.readFileSync(filePath, "utf-8");
6488
+ if (!fs23.existsSync(filePath)) return 0;
6489
+ const content = fs23.readFileSync(filePath, "utf-8");
6796
6490
  return content.split("\n").filter(Boolean).length;
6797
6491
  }
6798
6492
  function clearSession() {
6799
6493
  const filePath = sessionFilePath();
6800
- if (fs25.existsSync(filePath)) fs25.unlinkSync(filePath);
6494
+ if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
6801
6495
  }
6802
6496
  function readState2() {
6803
6497
  const filePath = stateFilePath();
6804
- if (!fs25.existsSync(filePath)) return { ...DEFAULT_STATE };
6498
+ if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
6805
6499
  try {
6806
- return JSON.parse(fs25.readFileSync(filePath, "utf-8"));
6500
+ return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
6807
6501
  } catch {
6808
6502
  return { ...DEFAULT_STATE };
6809
6503
  }
6810
6504
  }
6811
6505
  function writeState2(state) {
6812
6506
  ensureLearningDir();
6813
- fs25.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6507
+ fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6814
6508
  }
6815
6509
  function resetState() {
6816
6510
  writeState2({ ...DEFAULT_STATE });
6817
6511
  }
6818
6512
 
6819
6513
  // src/learner/writer.ts
6820
- import fs26 from "fs";
6821
- import path23 from "path";
6514
+ import fs24 from "fs";
6515
+ import path20 from "path";
6822
6516
  var LEARNED_START = "<!-- caliber:learned -->";
6823
6517
  var LEARNED_END = "<!-- /caliber:learned -->";
6824
6518
  function writeLearnedContent(update) {
@@ -6838,8 +6532,8 @@ function writeLearnedContent(update) {
6838
6532
  function writeLearnedSection(content) {
6839
6533
  const claudeMdPath = "CLAUDE.md";
6840
6534
  let existing = "";
6841
- if (fs26.existsSync(claudeMdPath)) {
6842
- existing = fs26.readFileSync(claudeMdPath, "utf-8");
6535
+ if (fs24.existsSync(claudeMdPath)) {
6536
+ existing = fs24.readFileSync(claudeMdPath, "utf-8");
6843
6537
  }
6844
6538
  const section = `${LEARNED_START}
6845
6539
  ${content}
@@ -6853,15 +6547,15 @@ ${LEARNED_END}`;
6853
6547
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
6854
6548
  updated = existing + separator + "\n" + section + "\n";
6855
6549
  }
6856
- fs26.writeFileSync(claudeMdPath, updated);
6550
+ fs24.writeFileSync(claudeMdPath, updated);
6857
6551
  }
6858
6552
  function writeLearnedSkill(skill) {
6859
- const skillDir = path23.join(".claude", "skills", skill.name);
6860
- if (!fs26.existsSync(skillDir)) fs26.mkdirSync(skillDir, { recursive: true });
6861
- const skillPath = path23.join(skillDir, "SKILL.md");
6862
- if (!skill.isNew && fs26.existsSync(skillPath)) {
6863
- const existing = fs26.readFileSync(skillPath, "utf-8");
6864
- fs26.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6553
+ const skillDir = path20.join(".claude", "skills", skill.name);
6554
+ if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
6555
+ const skillPath = path20.join(skillDir, "SKILL.md");
6556
+ if (!skill.isNew && fs24.existsSync(skillPath)) {
6557
+ const existing = fs24.readFileSync(skillPath, "utf-8");
6558
+ fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6865
6559
  } else {
6866
6560
  const frontmatter = [
6867
6561
  "---",
@@ -6870,14 +6564,14 @@ function writeLearnedSkill(skill) {
6870
6564
  "---",
6871
6565
  ""
6872
6566
  ].join("\n");
6873
- fs26.writeFileSync(skillPath, frontmatter + skill.content);
6567
+ fs24.writeFileSync(skillPath, frontmatter + skill.content);
6874
6568
  }
6875
6569
  return skillPath;
6876
6570
  }
6877
6571
  function readLearnedSection() {
6878
6572
  const claudeMdPath = "CLAUDE.md";
6879
- if (!fs26.existsSync(claudeMdPath)) return null;
6880
- const content = fs26.readFileSync(claudeMdPath, "utf-8");
6573
+ if (!fs24.existsSync(claudeMdPath)) return null;
6574
+ const content = fs24.readFileSync(claudeMdPath, "utf-8");
6881
6575
  const startIdx = content.indexOf(LEARNED_START);
6882
6576
  const endIdx = content.indexOf(LEARNED_END);
6883
6577
  if (startIdx === -1 || endIdx === -1) return null;
@@ -7061,9 +6755,9 @@ Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
7061
6755
  }
7062
6756
 
7063
6757
  // src/cli.ts
7064
- var __dirname = path24.dirname(fileURLToPath(import.meta.url));
6758
+ var __dirname = path21.dirname(fileURLToPath(import.meta.url));
7065
6759
  var pkg = JSON.parse(
7066
- fs27.readFileSync(path24.resolve(__dirname, "..", "package.json"), "utf-8")
6760
+ fs25.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
7067
6761
  );
7068
6762
  var program = new Command();
7069
6763
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -7085,22 +6779,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
7085
6779
  learn.command("status").description("Show learning system status").action(learnStatusCommand);
7086
6780
 
7087
6781
  // src/utils/version-check.ts
7088
- import fs28 from "fs";
7089
- import path25 from "path";
6782
+ import fs26 from "fs";
6783
+ import path22 from "path";
7090
6784
  import { fileURLToPath as fileURLToPath2 } from "url";
7091
6785
  import { execSync as execSync9 } from "child_process";
7092
6786
  import chalk15 from "chalk";
7093
6787
  import ora7 from "ora";
7094
6788
  import confirm2 from "@inquirer/confirm";
7095
- var __dirname_vc = path25.dirname(fileURLToPath2(import.meta.url));
6789
+ var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
7096
6790
  var pkg2 = JSON.parse(
7097
- fs28.readFileSync(path25.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6791
+ fs26.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
7098
6792
  );
7099
6793
  function getInstalledVersion() {
7100
6794
  try {
7101
6795
  const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
7102
- const pkgPath = path25.join(globalRoot, "@rely-ai", "caliber", "package.json");
7103
- return JSON.parse(fs28.readFileSync(pkgPath, "utf-8")).version;
6796
+ const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
6797
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
7104
6798
  } catch {
7105
6799
  return null;
7106
6800
  }