@rely-ai/caliber 1.30.6 → 1.31.0-dev.1774710550

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 +666 -483
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -231,20 +231,20 @@ var init_types = __esm({
231
231
  });
232
232
 
233
233
  // src/utils/editor.ts
234
- import { execSync as execSync13, spawn as spawn3 } from "child_process";
235
- import fs26 from "fs";
236
- import path22 from "path";
234
+ import { execSync as execSync14, spawn as spawn3 } from "child_process";
235
+ import fs27 from "fs";
236
+ import path23 from "path";
237
237
  import os6 from "os";
238
238
  function getEmptyFilePath(proposedPath) {
239
- fs26.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
240
- const tempPath = path22.join(DIFF_TEMP_DIR, path22.basename(proposedPath));
241
- fs26.writeFileSync(tempPath, "");
239
+ fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
240
+ const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
241
+ fs27.writeFileSync(tempPath, "");
242
242
  return tempPath;
243
243
  }
244
244
  function commandExists(cmd) {
245
245
  try {
246
246
  const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
247
- execSync13(check, { stdio: "ignore" });
247
+ execSync14(check, { stdio: "ignore" });
248
248
  return true;
249
249
  } catch {
250
250
  return false;
@@ -278,7 +278,7 @@ var init_editor = __esm({
278
278
  "src/utils/editor.ts"() {
279
279
  "use strict";
280
280
  IS_WINDOWS3 = process.platform === "win32";
281
- DIFF_TEMP_DIR = path22.join(os6.tmpdir(), "caliber-diff");
281
+ DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
282
282
  }
283
283
  });
284
284
 
@@ -290,7 +290,7 @@ __export(review_exports, {
290
290
  promptWantsReview: () => promptWantsReview
291
291
  });
292
292
  import chalk10 from "chalk";
293
- import fs27 from "fs";
293
+ import fs28 from "fs";
294
294
  import select4 from "@inquirer/select";
295
295
  import { createTwoFilesPatch } from "diff";
296
296
  async function promptWantsReview() {
@@ -327,8 +327,8 @@ async function openReview(method, stagedFiles) {
327
327
  return;
328
328
  }
329
329
  const fileInfos = stagedFiles.map((file) => {
330
- const proposed = fs27.readFileSync(file.proposedPath, "utf-8");
331
- const current = file.currentPath ? fs27.readFileSync(file.currentPath, "utf-8") : "";
330
+ const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
331
+ const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
332
332
  const patch = createTwoFilesPatch(
333
333
  file.isNew ? "/dev/null" : file.relativePath,
334
334
  file.relativePath,
@@ -503,13 +503,13 @@ __export(lock_exports, {
503
503
  isCaliberRunning: () => isCaliberRunning,
504
504
  releaseLock: () => releaseLock
505
505
  });
506
- import fs37 from "fs";
507
- import path29 from "path";
506
+ import fs38 from "fs";
507
+ import path30 from "path";
508
508
  import os8 from "os";
509
509
  function isCaliberRunning() {
510
510
  try {
511
- if (!fs37.existsSync(LOCK_FILE)) return false;
512
- const raw = fs37.readFileSync(LOCK_FILE, "utf-8").trim();
511
+ if (!fs38.existsSync(LOCK_FILE)) return false;
512
+ const raw = fs38.readFileSync(LOCK_FILE, "utf-8").trim();
513
513
  const { pid, ts } = JSON.parse(raw);
514
514
  if (Date.now() - ts > STALE_MS) return false;
515
515
  try {
@@ -524,13 +524,13 @@ function isCaliberRunning() {
524
524
  }
525
525
  function acquireLock() {
526
526
  try {
527
- fs37.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
527
+ fs38.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
528
528
  } catch {
529
529
  }
530
530
  }
531
531
  function releaseLock() {
532
532
  try {
533
- if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
533
+ if (fs38.existsSync(LOCK_FILE)) fs38.unlinkSync(LOCK_FILE);
534
534
  } catch {
535
535
  }
536
536
  }
@@ -538,7 +538,7 @@ var LOCK_FILE, STALE_MS;
538
538
  var init_lock = __esm({
539
539
  "src/lib/lock.ts"() {
540
540
  "use strict";
541
- LOCK_FILE = path29.join(os8.tmpdir(), ".caliber.lock");
541
+ LOCK_FILE = path30.join(os8.tmpdir(), ".caliber.lock");
542
542
  STALE_MS = 10 * 60 * 1e3;
543
543
  }
544
544
  });
@@ -550,9 +550,9 @@ import path38 from "path";
550
550
  import { fileURLToPath } from "url";
551
551
 
552
552
  // src/commands/init.ts
553
- import path25 from "path";
553
+ import path26 from "path";
554
554
  import chalk14 from "chalk";
555
- import fs32 from "fs";
555
+ import fs33 from "fs";
556
556
 
557
557
  // src/fingerprint/index.ts
558
558
  import fs8 from "fs";
@@ -2450,7 +2450,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
2450
2450
  Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
2451
2451
  README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
2452
2452
  var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
2453
- - CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
2453
+ - CLAUDE.md / AGENTS.md: MUST be under 400 lines for maximum score. Aim for 200-350 lines. Be thorough \u2014 commands, architecture overview, key conventions, data flow, and important patterns. Use bullet points and tables, not prose.
2454
2454
 
2455
2455
  Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
2456
2456
  GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
@@ -2578,7 +2578,7 @@ Structure:
2578
2578
  5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
2579
2579
 
2580
2580
  Rules:
2581
- - Max 150 lines. Focus on actionable instructions, not documentation prose.
2581
+ - Max 400 lines. Focus on actionable instructions, not documentation prose.
2582
2582
  - Study existing code in the project context to extract the real patterns being used. A skill for "create API route" should show the exact file structure, imports, error handling, and naming that existing routes use.
2583
2583
  - Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
2584
2584
  - Never use ambiguous language. Instead of "handle errors properly", write "Wrap the DB call in try/catch. On failure, return { error: string, code: number } matching the ErrorResponse type in \`src/types.ts\`."
@@ -2632,7 +2632,7 @@ Rules:
2632
2632
  - Update the "fileDescriptions" to reflect any changes you make.
2633
2633
 
2634
2634
  Quality constraints \u2014 your changes are scored, so do not break these:
2635
- - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
2635
+ - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
2636
2636
  - Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
2637
2637
  - Do NOT add directory tree listings in code blocks.
2638
2638
  - Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
@@ -2656,7 +2656,7 @@ CONSERVATIVE UPDATE means:
2656
2656
  - NEVER replace specific paths/commands with generic prose
2657
2657
 
2658
2658
  Quality constraints (the output is scored deterministically):
2659
- - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
2659
+ - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
2660
2660
  - Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
2661
2661
  - Every file path, command, and identifier must be in backticks
2662
2662
  - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
@@ -2664,6 +2664,7 @@ Quality constraints (the output is scored deterministically):
2664
2664
 
2665
2665
  Managed content:
2666
2666
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
2667
+ - Keep context sync blocks (<!-- caliber:managed:sync --> ... <!-- /caliber:managed:sync -->) intact
2667
2668
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
2668
2669
  - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
2669
2670
 
@@ -2679,9 +2680,12 @@ Return a JSON object with this exact shape:
2679
2680
  "copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
2680
2681
  },
2681
2682
  "changesSummary": "<1-2 sentence summary of what was updated and why>",
2683
+ "fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
2682
2684
  "docsUpdated": ["CLAUDE.md", "README.md"]
2683
2685
  }
2684
2686
 
2687
+ The "fileChanges" array MUST include one entry per file that was updated (non-null in updatedDocs). Each entry describes what specifically changed in that file \u2014 be concrete (e.g. "added auth middleware section" not "updated docs").
2688
+
2685
2689
  Respond with ONLY the JSON object, no markdown fences or extra text.`;
2686
2690
  var LEARN_SYSTEM_PROMPT = `You are an expert developer experience engineer. You analyze raw tool call events from AI coding sessions to extract reusable operational lessons that will help future LLM sessions work more effectively in this project.
2687
2691
 
@@ -3173,9 +3177,151 @@ function getCursorConfigDir() {
3173
3177
  return path8.join(home, ".config", "Cursor");
3174
3178
  }
3175
3179
 
3176
- // src/fingerprint/sources.ts
3180
+ // src/lib/hooks.ts
3181
+ init_resolve_caliber();
3177
3182
  import fs10 from "fs";
3178
3183
  import path9 from "path";
3184
+ import { execSync as execSync8 } from "child_process";
3185
+ var SETTINGS_PATH = path9.join(".claude", "settings.json");
3186
+ var REFRESH_TAIL = "refresh --quiet";
3187
+ var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
3188
+ function getHookCommand() {
3189
+ return `${resolveCaliber()} ${REFRESH_TAIL}`;
3190
+ }
3191
+ function readSettings() {
3192
+ if (!fs10.existsSync(SETTINGS_PATH)) return {};
3193
+ try {
3194
+ return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
3195
+ } catch {
3196
+ return {};
3197
+ }
3198
+ }
3199
+ function writeSettings(settings) {
3200
+ const dir = path9.dirname(SETTINGS_PATH);
3201
+ if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
3202
+ fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3203
+ }
3204
+ function findHookIndex(sessionEnd) {
3205
+ return sessionEnd.findIndex(
3206
+ (entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
3207
+ );
3208
+ }
3209
+ function isHookInstalled() {
3210
+ const settings = readSettings();
3211
+ const sessionEnd = settings.hooks?.SessionEnd;
3212
+ if (!Array.isArray(sessionEnd)) return false;
3213
+ return findHookIndex(sessionEnd) !== -1;
3214
+ }
3215
+ function installHook() {
3216
+ const settings = readSettings();
3217
+ if (!settings.hooks) settings.hooks = {};
3218
+ if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
3219
+ if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
3220
+ return { installed: false, alreadyInstalled: true };
3221
+ }
3222
+ settings.hooks.SessionEnd.push({
3223
+ matcher: "",
3224
+ hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
3225
+ });
3226
+ writeSettings(settings);
3227
+ return { installed: true, alreadyInstalled: false };
3228
+ }
3229
+ function removeHook() {
3230
+ const settings = readSettings();
3231
+ const sessionEnd = settings.hooks?.SessionEnd;
3232
+ if (!Array.isArray(sessionEnd)) {
3233
+ return { removed: false, notFound: true };
3234
+ }
3235
+ const idx = findHookIndex(sessionEnd);
3236
+ if (idx === -1) {
3237
+ return { removed: false, notFound: true };
3238
+ }
3239
+ sessionEnd.splice(idx, 1);
3240
+ if (sessionEnd.length === 0) {
3241
+ delete settings.hooks.SessionEnd;
3242
+ }
3243
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
3244
+ delete settings.hooks;
3245
+ }
3246
+ writeSettings(settings);
3247
+ return { removed: true, notFound: false };
3248
+ }
3249
+ var PRECOMMIT_START = "# caliber:pre-commit:start";
3250
+ var PRECOMMIT_END = "# caliber:pre-commit:end";
3251
+ function getPrecommitBlock() {
3252
+ const bin = resolveCaliber();
3253
+ const npx = isNpxResolution();
3254
+ const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
3255
+ const invoke = npx ? bin : `"${bin}"`;
3256
+ return `${PRECOMMIT_START}
3257
+ if ${guard}; then
3258
+ echo "\\033[2mcaliber: refreshing docs...\\033[0m"
3259
+ ${invoke} refresh 2>/dev/null || true
3260
+ ${invoke} learn finalize 2>/dev/null || true
3261
+ git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
3262
+ fi
3263
+ ${PRECOMMIT_END}`;
3264
+ }
3265
+ function getGitHooksDir() {
3266
+ try {
3267
+ const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3268
+ return path9.join(gitDir, "hooks");
3269
+ } catch {
3270
+ return null;
3271
+ }
3272
+ }
3273
+ function getPreCommitPath() {
3274
+ const hooksDir = getGitHooksDir();
3275
+ return hooksDir ? path9.join(hooksDir, "pre-commit") : null;
3276
+ }
3277
+ function isPreCommitHookInstalled() {
3278
+ const hookPath = getPreCommitPath();
3279
+ if (!hookPath || !fs10.existsSync(hookPath)) return false;
3280
+ const content = fs10.readFileSync(hookPath, "utf-8");
3281
+ return content.includes(PRECOMMIT_START);
3282
+ }
3283
+ function installPreCommitHook() {
3284
+ if (isPreCommitHookInstalled()) {
3285
+ return { installed: false, alreadyInstalled: true };
3286
+ }
3287
+ const hookPath = getPreCommitPath();
3288
+ if (!hookPath) return { installed: false, alreadyInstalled: false };
3289
+ const hooksDir = path9.dirname(hookPath);
3290
+ if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
3291
+ let content = "";
3292
+ if (fs10.existsSync(hookPath)) {
3293
+ content = fs10.readFileSync(hookPath, "utf-8");
3294
+ if (!content.endsWith("\n")) content += "\n";
3295
+ content += "\n" + getPrecommitBlock() + "\n";
3296
+ } else {
3297
+ content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
3298
+ }
3299
+ fs10.writeFileSync(hookPath, content);
3300
+ fs10.chmodSync(hookPath, 493);
3301
+ return { installed: true, alreadyInstalled: false };
3302
+ }
3303
+ function removePreCommitHook() {
3304
+ const hookPath = getPreCommitPath();
3305
+ if (!hookPath || !fs10.existsSync(hookPath)) {
3306
+ return { removed: false, notFound: true };
3307
+ }
3308
+ let content = fs10.readFileSync(hookPath, "utf-8");
3309
+ if (!content.includes(PRECOMMIT_START)) {
3310
+ return { removed: false, notFound: true };
3311
+ }
3312
+ const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
3313
+ content = content.replace(regex, "\n");
3314
+ if (content.trim() === "#!/bin/sh" || content.trim() === "") {
3315
+ fs10.unlinkSync(hookPath);
3316
+ } else {
3317
+ fs10.writeFileSync(hookPath, content);
3318
+ }
3319
+ return { removed: true, notFound: false };
3320
+ }
3321
+
3322
+ // src/fingerprint/sources.ts
3323
+ import fs11 from "fs";
3324
+ import path10 from "path";
3179
3325
 
3180
3326
  // src/scoring/utils.ts
3181
3327
  import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
@@ -3494,7 +3640,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
3494
3640
  var README_CONTENT_LIMIT = 1e3;
3495
3641
  var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
3496
3642
  function loadSourcesConfig(dir) {
3497
- const configPath = path9.join(dir, ".caliber", "sources.json");
3643
+ const configPath = path10.join(dir, ".caliber", "sources.json");
3498
3644
  const content = readFileOrNull(configPath);
3499
3645
  if (!content) return [];
3500
3646
  try {
@@ -3512,29 +3658,29 @@ function loadSourcesConfig(dir) {
3512
3658
  }
3513
3659
  }
3514
3660
  function writeSourcesConfig(dir, sources2) {
3515
- const configDir = path9.join(dir, ".caliber");
3516
- if (!fs10.existsSync(configDir)) {
3517
- fs10.mkdirSync(configDir, { recursive: true });
3661
+ const configDir = path10.join(dir, ".caliber");
3662
+ if (!fs11.existsSync(configDir)) {
3663
+ fs11.mkdirSync(configDir, { recursive: true });
3518
3664
  }
3519
- const configPath = path9.join(configDir, "sources.json");
3520
- fs10.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
3665
+ const configPath = path10.join(configDir, "sources.json");
3666
+ fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
3521
3667
  }
3522
3668
  function detectSourceType(absPath) {
3523
3669
  try {
3524
- return fs10.statSync(absPath).isDirectory() ? "repo" : "file";
3670
+ return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
3525
3671
  } catch {
3526
3672
  return "file";
3527
3673
  }
3528
3674
  }
3529
3675
  function isInsideDir(childPath, parentDir) {
3530
- const relative2 = path9.relative(parentDir, childPath);
3531
- return !relative2.startsWith("..") && !path9.isAbsolute(relative2);
3676
+ const relative2 = path10.relative(parentDir, childPath);
3677
+ return !relative2.startsWith("..") && !path10.isAbsolute(relative2);
3532
3678
  }
3533
3679
  function resolveAllSources(dir, cliSources, workspaces) {
3534
3680
  const seen = /* @__PURE__ */ new Map();
3535
- const projectRoot = path9.resolve(dir);
3681
+ const projectRoot = path10.resolve(dir);
3536
3682
  for (const src of cliSources) {
3537
- const absPath = path9.resolve(dir, src);
3683
+ const absPath = path10.resolve(dir, src);
3538
3684
  if (seen.has(absPath)) continue;
3539
3685
  const type = detectSourceType(absPath);
3540
3686
  seen.set(absPath, {
@@ -3547,12 +3693,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
3547
3693
  for (const cfg of configSources) {
3548
3694
  if (cfg.type === "url") continue;
3549
3695
  if (!cfg.path) continue;
3550
- const absPath = path9.resolve(dir, cfg.path);
3696
+ const absPath = path10.resolve(dir, cfg.path);
3551
3697
  if (seen.has(absPath)) continue;
3552
3698
  seen.set(absPath, { absPath, config: cfg, origin: "config" });
3553
3699
  }
3554
3700
  for (const ws of workspaces) {
3555
- const absPath = path9.resolve(dir, ws);
3701
+ const absPath = path10.resolve(dir, ws);
3556
3702
  if (seen.has(absPath)) continue;
3557
3703
  if (!isInsideDir(absPath, projectRoot)) continue;
3558
3704
  seen.set(absPath, {
@@ -3565,7 +3711,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
3565
3711
  for (const [absPath, resolved] of seen) {
3566
3712
  let stat;
3567
3713
  try {
3568
- stat = fs10.statSync(absPath);
3714
+ stat = fs11.statSync(absPath);
3569
3715
  } catch {
3570
3716
  console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
3571
3717
  continue;
@@ -3594,13 +3740,13 @@ function collectSourceSummary(resolved, projectDir) {
3594
3740
  if (config.type === "file") {
3595
3741
  return collectFileSummary(resolved, projectDir);
3596
3742
  }
3597
- const summaryPath = path9.join(absPath, ".caliber", "summary.json");
3743
+ const summaryPath = path10.join(absPath, ".caliber", "summary.json");
3598
3744
  const summaryContent = readFileOrNull(summaryPath);
3599
3745
  if (summaryContent) {
3600
3746
  try {
3601
3747
  const published = JSON.parse(summaryContent);
3602
3748
  return {
3603
- name: published.name || path9.basename(absPath),
3749
+ name: published.name || path10.basename(absPath),
3604
3750
  type: "repo",
3605
3751
  role: config.role || published.role || "related-repo",
3606
3752
  description: config.description || published.description || "",
@@ -3620,18 +3766,18 @@ function collectRepoSummary(resolved, projectDir) {
3620
3766
  let topLevelDirs;
3621
3767
  let keyFiles;
3622
3768
  try {
3623
- const entries = fs10.readdirSync(absPath, { withFileTypes: true });
3769
+ const entries = fs11.readdirSync(absPath, { withFileTypes: true });
3624
3770
  topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
3625
3771
  keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
3626
3772
  } catch {
3627
3773
  }
3628
- const claudeMdContent = readFileOrNull(path9.join(absPath, "CLAUDE.md"));
3774
+ const claudeMdContent = readFileOrNull(path10.join(absPath, "CLAUDE.md"));
3629
3775
  const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
3630
- const readmeContent = readFileOrNull(path9.join(absPath, "README.md"));
3776
+ const readmeContent = readFileOrNull(path10.join(absPath, "README.md"));
3631
3777
  const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
3632
3778
  const gitRemoteUrl = getGitRemoteUrl(absPath);
3633
3779
  return {
3634
- name: packageName || path9.basename(absPath),
3780
+ name: packageName || path10.basename(absPath),
3635
3781
  type: "repo",
3636
3782
  role: config.role || "related-repo",
3637
3783
  description: config.description || "",
@@ -3648,7 +3794,7 @@ function collectFileSummary(resolved, projectDir) {
3648
3794
  const { config, origin, absPath } = resolved;
3649
3795
  const content = readFileOrNull(absPath);
3650
3796
  return {
3651
- name: path9.basename(absPath),
3797
+ name: path10.basename(absPath),
3652
3798
  type: "file",
3653
3799
  role: config.role || "reference-doc",
3654
3800
  description: config.description || content?.slice(0, 100).split("\n")[0] || "",
@@ -4287,39 +4433,52 @@ ${f.content}
4287
4433
  }
4288
4434
 
4289
4435
  // src/writers/index.ts
4290
- import fs18 from "fs";
4436
+ import fs19 from "fs";
4291
4437
 
4292
4438
  // src/writers/claude/index.ts
4293
- import fs11 from "fs";
4294
- import path10 from "path";
4439
+ import fs12 from "fs";
4440
+ import path11 from "path";
4295
4441
 
4296
4442
  // src/writers/pre-commit-block.ts
4297
4443
  init_resolve_caliber();
4298
4444
  var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
4299
4445
  var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
4300
- var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
4446
+ var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
4301
4447
  function getPreCommitBlock() {
4302
4448
  const bin = resolveCaliber();
4303
4449
  return `${BLOCK_START}
4304
4450
  ## Before Committing
4305
4451
 
4306
- Run \`${bin} refresh\` before creating git commits to keep docs in sync with code changes.
4307
- After it completes, stage any modified doc files before committing:
4452
+ **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
4308
4453
 
4454
+ First, check if the pre-commit hook is already installed:
4309
4455
  \`\`\`bash
4310
- ${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null
4456
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
4311
4457
  \`\`\`
4458
+
4459
+ - If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
4460
+ - If **no-hook**: run Caliber manually before committing:
4461
+ 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
4462
+ 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
4463
+ 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
4464
+
4465
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
4312
4466
  ${BLOCK_END}`;
4313
4467
  }
4314
4468
  var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
4315
4469
  function getCursorRuleContent() {
4316
4470
  const bin = resolveCaliber();
4317
4471
  return `---
4318
- description: Run caliber refresh before git commits to keep docs in sync
4472
+ description: Run Caliber sync before git commits to keep agent configs up to date
4319
4473
  alwaysApply: true
4320
4474
  ---
4321
- Before creating git commits, run \`${bin} refresh\` to update docs based on code changes,
4322
- then stage modified doc files (${MANAGED_DOC_PATHS}).
4475
+ Before every git commit, check if the Caliber pre-commit hook is installed:
4476
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
4477
+
4478
+ If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
4479
+ If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
4480
+
4481
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
4323
4482
  `;
4324
4483
  }
4325
4484
  function hasPreCommitBlock(content) {
@@ -4360,17 +4519,52 @@ function appendLearningsBlock(content) {
4360
4519
  function getCursorLearningsRule() {
4361
4520
  return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
4362
4521
  }
4522
+ var SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
4523
+ var SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
4524
+ function getSyncBlock() {
4525
+ const bin = resolveCaliber();
4526
+ return `${SYNC_BLOCK_START}
4527
+ ## Context Sync
4528
+
4529
+ This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
4530
+ Configs update automatically before each commit via \`${bin} refresh\`.
4531
+ If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
4532
+ ${SYNC_BLOCK_END}`;
4533
+ }
4534
+ function hasSyncBlock(content) {
4535
+ return content.includes(SYNC_BLOCK_START);
4536
+ }
4537
+ function appendSyncBlock(content) {
4538
+ if (hasSyncBlock(content)) return content;
4539
+ const trimmed = content.trimEnd();
4540
+ return trimmed + "\n\n" + getSyncBlock() + "\n";
4541
+ }
4542
+ var CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
4543
+ function getCursorSyncContent() {
4544
+ const bin = resolveCaliber();
4545
+ return `---
4546
+ description: This project uses Caliber for automatic AI agent context sync
4547
+ alwaysApply: true
4548
+ ---
4549
+ This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
4550
+ in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
4551
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
4552
+ `;
4553
+ }
4554
+ function getCursorSyncRule() {
4555
+ return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
4556
+ }
4363
4557
 
4364
4558
  // src/writers/claude/index.ts
4365
4559
  function writeClaudeConfig(config) {
4366
4560
  const written = [];
4367
- fs11.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
4561
+ fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
4368
4562
  written.push("CLAUDE.md");
4369
4563
  if (config.skills?.length) {
4370
4564
  for (const skill of config.skills) {
4371
- const skillDir = path10.join(".claude", "skills", skill.name);
4372
- if (!fs11.existsSync(skillDir)) fs11.mkdirSync(skillDir, { recursive: true });
4373
- const skillPath = path10.join(skillDir, "SKILL.md");
4565
+ const skillDir = path11.join(".claude", "skills", skill.name);
4566
+ if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
4567
+ const skillPath = path11.join(skillDir, "SKILL.md");
4374
4568
  const frontmatter = [
4375
4569
  "---",
4376
4570
  `name: ${skill.name}`,
@@ -4378,50 +4572,51 @@ function writeClaudeConfig(config) {
4378
4572
  "---",
4379
4573
  ""
4380
4574
  ].join("\n");
4381
- fs11.writeFileSync(skillPath, frontmatter + skill.content);
4575
+ fs12.writeFileSync(skillPath, frontmatter + skill.content);
4382
4576
  written.push(skillPath);
4383
4577
  }
4384
4578
  }
4385
4579
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4386
4580
  let existingServers = {};
4387
4581
  try {
4388
- if (fs11.existsSync(".mcp.json")) {
4389
- const existing = JSON.parse(fs11.readFileSync(".mcp.json", "utf-8"));
4582
+ if (fs12.existsSync(".mcp.json")) {
4583
+ const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
4390
4584
  if (existing.mcpServers) existingServers = existing.mcpServers;
4391
4585
  }
4392
4586
  } catch {
4393
4587
  }
4394
4588
  const mergedServers = { ...existingServers, ...config.mcpServers };
4395
- fs11.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4589
+ fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4396
4590
  written.push(".mcp.json");
4397
4591
  }
4398
4592
  return written;
4399
4593
  }
4400
4594
 
4401
4595
  // src/writers/cursor/index.ts
4402
- import fs12 from "fs";
4403
- import path11 from "path";
4596
+ import fs13 from "fs";
4597
+ import path12 from "path";
4404
4598
  function writeCursorConfig(config) {
4405
4599
  const written = [];
4406
4600
  if (config.cursorrules) {
4407
- fs12.writeFileSync(".cursorrules", config.cursorrules);
4601
+ fs13.writeFileSync(".cursorrules", config.cursorrules);
4408
4602
  written.push(".cursorrules");
4409
4603
  }
4410
4604
  const preCommitRule = getCursorPreCommitRule();
4411
4605
  const learningsRule = getCursorLearningsRule();
4412
- const allRules = [...config.rules || [], preCommitRule, learningsRule];
4413
- const rulesDir = path11.join(".cursor", "rules");
4414
- if (!fs12.existsSync(rulesDir)) fs12.mkdirSync(rulesDir, { recursive: true });
4606
+ const syncRule = getCursorSyncRule();
4607
+ const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
4608
+ const rulesDir = path12.join(".cursor", "rules");
4609
+ if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
4415
4610
  for (const rule of allRules) {
4416
- const rulePath = path11.join(rulesDir, rule.filename);
4417
- fs12.writeFileSync(rulePath, rule.content);
4611
+ const rulePath = path12.join(rulesDir, rule.filename);
4612
+ fs13.writeFileSync(rulePath, rule.content);
4418
4613
  written.push(rulePath);
4419
4614
  }
4420
4615
  if (config.skills?.length) {
4421
4616
  for (const skill of config.skills) {
4422
- const skillDir = path11.join(".cursor", "skills", skill.name);
4423
- if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
4424
- const skillPath = path11.join(skillDir, "SKILL.md");
4617
+ const skillDir = path12.join(".cursor", "skills", skill.name);
4618
+ if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
4619
+ const skillPath = path12.join(skillDir, "SKILL.md");
4425
4620
  const frontmatter = [
4426
4621
  "---",
4427
4622
  `name: ${skill.name}`,
@@ -4429,41 +4624,41 @@ function writeCursorConfig(config) {
4429
4624
  "---",
4430
4625
  ""
4431
4626
  ].join("\n");
4432
- fs12.writeFileSync(skillPath, frontmatter + skill.content);
4627
+ fs13.writeFileSync(skillPath, frontmatter + skill.content);
4433
4628
  written.push(skillPath);
4434
4629
  }
4435
4630
  }
4436
4631
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4437
4632
  const cursorDir = ".cursor";
4438
- if (!fs12.existsSync(cursorDir)) fs12.mkdirSync(cursorDir, { recursive: true });
4439
- const mcpPath = path11.join(cursorDir, "mcp.json");
4633
+ if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
4634
+ const mcpPath = path12.join(cursorDir, "mcp.json");
4440
4635
  let existingServers = {};
4441
4636
  try {
4442
- if (fs12.existsSync(mcpPath)) {
4443
- const existing = JSON.parse(fs12.readFileSync(mcpPath, "utf-8"));
4637
+ if (fs13.existsSync(mcpPath)) {
4638
+ const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
4444
4639
  if (existing.mcpServers) existingServers = existing.mcpServers;
4445
4640
  }
4446
4641
  } catch {
4447
4642
  }
4448
4643
  const mergedServers = { ...existingServers, ...config.mcpServers };
4449
- fs12.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
4644
+ fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
4450
4645
  written.push(mcpPath);
4451
4646
  }
4452
4647
  return written;
4453
4648
  }
4454
4649
 
4455
4650
  // src/writers/codex/index.ts
4456
- import fs13 from "fs";
4457
- import path12 from "path";
4651
+ import fs14 from "fs";
4652
+ import path13 from "path";
4458
4653
  function writeCodexConfig(config) {
4459
4654
  const written = [];
4460
- fs13.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4655
+ fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4461
4656
  written.push("AGENTS.md");
4462
4657
  if (config.skills?.length) {
4463
4658
  for (const skill of config.skills) {
4464
- const skillDir = path12.join(".agents", "skills", skill.name);
4465
- if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
4466
- const skillPath = path12.join(skillDir, "SKILL.md");
4659
+ const skillDir = path13.join(".agents", "skills", skill.name);
4660
+ if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
4661
+ const skillPath = path13.join(skillDir, "SKILL.md");
4467
4662
  const frontmatter = [
4468
4663
  "---",
4469
4664
  `name: ${skill.name}`,
@@ -4471,7 +4666,7 @@ function writeCodexConfig(config) {
4471
4666
  "---",
4472
4667
  ""
4473
4668
  ].join("\n");
4474
- fs13.writeFileSync(skillPath, frontmatter + skill.content);
4669
+ fs14.writeFileSync(skillPath, frontmatter + skill.content);
4475
4670
  written.push(skillPath);
4476
4671
  }
4477
4672
  }
@@ -4479,20 +4674,20 @@ function writeCodexConfig(config) {
4479
4674
  }
4480
4675
 
4481
4676
  // src/writers/github-copilot/index.ts
4482
- import fs14 from "fs";
4483
- import path13 from "path";
4677
+ import fs15 from "fs";
4678
+ import path14 from "path";
4484
4679
  function writeGithubCopilotConfig(config) {
4485
4680
  const written = [];
4486
4681
  if (config.instructions) {
4487
- fs14.mkdirSync(".github", { recursive: true });
4488
- fs14.writeFileSync(path13.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
4682
+ fs15.mkdirSync(".github", { recursive: true });
4683
+ fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
4489
4684
  written.push(".github/copilot-instructions.md");
4490
4685
  }
4491
4686
  if (config.instructionFiles?.length) {
4492
- const instructionsDir = path13.join(".github", "instructions");
4493
- fs14.mkdirSync(instructionsDir, { recursive: true });
4687
+ const instructionsDir = path14.join(".github", "instructions");
4688
+ fs15.mkdirSync(instructionsDir, { recursive: true });
4494
4689
  for (const file of config.instructionFiles) {
4495
- fs14.writeFileSync(path13.join(instructionsDir, file.filename), file.content);
4690
+ fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
4496
4691
  written.push(`.github/instructions/${file.filename}`);
4497
4692
  }
4498
4693
  }
@@ -4500,37 +4695,37 @@ function writeGithubCopilotConfig(config) {
4500
4695
  }
4501
4696
 
4502
4697
  // src/writers/backup.ts
4503
- import fs15 from "fs";
4504
- import path14 from "path";
4698
+ import fs16 from "fs";
4699
+ import path15 from "path";
4505
4700
  function createBackup(files) {
4506
4701
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4507
- const backupDir = path14.join(BACKUPS_DIR, timestamp);
4702
+ const backupDir = path15.join(BACKUPS_DIR, timestamp);
4508
4703
  for (const file of files) {
4509
- if (!fs15.existsSync(file)) continue;
4510
- const dest = path14.join(backupDir, file);
4511
- const destDir = path14.dirname(dest);
4512
- if (!fs15.existsSync(destDir)) {
4513
- fs15.mkdirSync(destDir, { recursive: true });
4704
+ if (!fs16.existsSync(file)) continue;
4705
+ const dest = path15.join(backupDir, file);
4706
+ const destDir = path15.dirname(dest);
4707
+ if (!fs16.existsSync(destDir)) {
4708
+ fs16.mkdirSync(destDir, { recursive: true });
4514
4709
  }
4515
- fs15.copyFileSync(file, dest);
4710
+ fs16.copyFileSync(file, dest);
4516
4711
  }
4517
4712
  return backupDir;
4518
4713
  }
4519
4714
  function restoreBackup(backupDir, file) {
4520
- const backupFile = path14.join(backupDir, file);
4521
- if (!fs15.existsSync(backupFile)) return false;
4522
- const destDir = path14.dirname(file);
4523
- if (!fs15.existsSync(destDir)) {
4524
- fs15.mkdirSync(destDir, { recursive: true });
4715
+ const backupFile = path15.join(backupDir, file);
4716
+ if (!fs16.existsSync(backupFile)) return false;
4717
+ const destDir = path15.dirname(file);
4718
+ if (!fs16.existsSync(destDir)) {
4719
+ fs16.mkdirSync(destDir, { recursive: true });
4525
4720
  }
4526
- fs15.copyFileSync(backupFile, file);
4721
+ fs16.copyFileSync(backupFile, file);
4527
4722
  return true;
4528
4723
  }
4529
4724
 
4530
4725
  // src/lib/builtin-skills.ts
4531
4726
  init_resolve_caliber();
4532
- import fs16 from "fs";
4533
- import path15 from "path";
4727
+ import fs17 from "fs";
4728
+ import path16 from "path";
4534
4729
  function buildSkillContent(skill) {
4535
4730
  const frontmatter = `---
4536
4731
  name: ${skill.name}
@@ -4666,21 +4861,128 @@ var SAVE_LEARNING_SKILL = {
4666
4861
  return getSaveLearningContent();
4667
4862
  }
4668
4863
  };
4669
- var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL];
4864
+ function getSetupCaliberContent() {
4865
+ const bin = resolveCaliber();
4866
+ return `# Setup Caliber
4867
+
4868
+ Set up Caliber for automatic AI agent context sync on this project.
4869
+ Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
4870
+ in sync with your codebase \u2014 automatically on every commit.
4871
+
4872
+ ## Instructions
4873
+
4874
+ 1. Check if Caliber is already set up locally:
4875
+ \`\`\`bash
4876
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
4877
+ \`\`\`
4878
+
4879
+ 2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
4880
+ Your agent configs update automatically on every commit." Then stop.
4881
+
4882
+ 3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
4883
+ or for your team?"
4884
+
4885
+ ### Solo Setup
4886
+
4887
+ a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
4888
+ on every commit."
4889
+ b. Install the pre-commit hook:
4890
+ \`\`\`bash
4891
+ ${bin} hooks --install
4892
+ \`\`\`
4893
+ c. Run a refresh to make sure configs are current:
4894
+ \`\`\`bash
4895
+ ${bin} refresh
4896
+ \`\`\`
4897
+ d. Tell the user what happened and that configs will auto-sync on every commit.
4898
+
4899
+ ### Team Setup
4900
+
4901
+ a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
4902
+ plus a GitHub Action for automatic sync across the team."
4903
+ b. Install the local pre-commit hook:
4904
+ \`\`\`bash
4905
+ ${bin} hooks --install
4906
+ \`\`\`
4907
+ c. Run a refresh:
4908
+ \`\`\`bash
4909
+ ${bin} refresh
4910
+ \`\`\`
4911
+ d. Create the GitHub Action workflow for nightly sync. Write this file to
4912
+ \`.github/workflows/caliber-sync.yml\`:
4913
+ \`\`\`yaml
4914
+ name: Caliber Sync
4915
+ on:
4916
+ schedule:
4917
+ - cron: '0 3 * * 1-5'
4918
+ pull_request:
4919
+ types: [opened, synchronize]
4920
+ workflow_dispatch:
4921
+ jobs:
4922
+ sync:
4923
+ runs-on: ubuntu-latest
4924
+ steps:
4925
+ - uses: actions/checkout@v4
4926
+ - uses: caliber-ai-org/ai-setup@v1
4927
+ with:
4928
+ mode: sync
4929
+ auto-refresh: true
4930
+ comment: true
4931
+ github-token: \${{ secrets.GITHUB_TOKEN }}
4932
+ env:
4933
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
4934
+ \`\`\`
4935
+ e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
4936
+ to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
4937
+ workflow file. From now on, every PR gets a Caliber sync check and configs
4938
+ refresh nightly."
4939
+ f. Offer to commit the workflow file and the updated configs.
4940
+
4941
+ ### If \`${bin}\` is not found
4942
+
4943
+ Install it first:
4944
+ \`\`\`bash
4945
+ npm install -g @rely-ai/caliber
4946
+ \`\`\`
4947
+ Or use npx (no install needed):
4948
+ \`\`\`bash
4949
+ npx @rely-ai/caliber hooks --install
4950
+ \`\`\`
4951
+
4952
+ ## When to trigger
4953
+
4954
+ - When a developer first opens this project and Caliber hooks are not installed
4955
+ - When the user asks about keeping agent configs up to date
4956
+ - When the user asks "how do I set up Caliber" or "what is Caliber"
4957
+
4958
+ ## When NOT to trigger
4959
+
4960
+ - Hooks are already installed and working
4961
+ - The user is in the middle of time-sensitive work
4962
+ `;
4963
+ }
4964
+ var SETUP_CALIBER_SKILL = {
4965
+ name: "setup-caliber",
4966
+ description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
4967
+ get content() {
4968
+ return getSetupCaliberContent();
4969
+ }
4970
+ };
4971
+ var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
4670
4972
  var PLATFORM_CONFIGS = [
4671
- { platformDir: ".claude", skillsDir: path15.join(".claude", "skills") },
4672
- { platformDir: ".cursor", skillsDir: path15.join(".cursor", "skills") },
4673
- { platformDir: ".agents", skillsDir: path15.join(".agents", "skills") }
4973
+ { platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
4974
+ { platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
4975
+ { platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
4674
4976
  ];
4675
4977
  function ensureBuiltinSkills() {
4676
4978
  const written = [];
4677
4979
  for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
4678
- if (!fs16.existsSync(platformDir)) continue;
4980
+ if (!fs17.existsSync(platformDir)) continue;
4679
4981
  for (const skill of BUILTIN_SKILLS) {
4680
- const skillPath = path15.join(skillsDir, skill.name, "SKILL.md");
4681
- if (fs16.existsSync(skillPath)) continue;
4682
- fs16.mkdirSync(path15.dirname(skillPath), { recursive: true });
4683
- fs16.writeFileSync(skillPath, buildSkillContent(skill));
4982
+ const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
4983
+ if (fs17.existsSync(skillPath)) continue;
4984
+ fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
4985
+ fs17.writeFileSync(skillPath, buildSkillContent(skill));
4684
4986
  written.push(skillPath);
4685
4987
  }
4686
4988
  }
@@ -4688,33 +4990,33 @@ function ensureBuiltinSkills() {
4688
4990
  }
4689
4991
 
4690
4992
  // src/writers/manifest.ts
4691
- import fs17 from "fs";
4993
+ import fs18 from "fs";
4692
4994
  import crypto3 from "crypto";
4693
4995
  function readManifest() {
4694
4996
  try {
4695
- if (!fs17.existsSync(MANIFEST_FILE)) return null;
4696
- return JSON.parse(fs17.readFileSync(MANIFEST_FILE, "utf-8"));
4997
+ if (!fs18.existsSync(MANIFEST_FILE)) return null;
4998
+ return JSON.parse(fs18.readFileSync(MANIFEST_FILE, "utf-8"));
4697
4999
  } catch {
4698
5000
  return null;
4699
5001
  }
4700
5002
  }
4701
5003
  function writeManifest(manifest) {
4702
- if (!fs17.existsSync(CALIBER_DIR)) {
4703
- fs17.mkdirSync(CALIBER_DIR, { recursive: true });
5004
+ if (!fs18.existsSync(CALIBER_DIR)) {
5005
+ fs18.mkdirSync(CALIBER_DIR, { recursive: true });
4704
5006
  }
4705
- fs17.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
5007
+ fs18.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
4706
5008
  }
4707
5009
  function fileChecksum(filePath) {
4708
- const content = fs17.readFileSync(filePath);
5010
+ const content = fs18.readFileSync(filePath);
4709
5011
  return crypto3.createHash("sha256").update(content).digest("hex");
4710
5012
  }
4711
5013
 
4712
5014
  // src/writers/index.ts
4713
5015
  function writeSetup(setup) {
4714
5016
  const filesToWrite = getFilesToWrite(setup);
4715
- const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs18.existsSync(f));
5017
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
4716
5018
  const existingFiles = [
4717
- ...filesToWrite.filter((f) => fs18.existsSync(f)),
5019
+ ...filesToWrite.filter((f) => fs19.existsSync(f)),
4718
5020
  ...filesToDelete
4719
5021
  ];
4720
5022
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
@@ -4733,7 +5035,7 @@ function writeSetup(setup) {
4733
5035
  }
4734
5036
  const deleted = [];
4735
5037
  for (const filePath of filesToDelete) {
4736
- fs18.unlinkSync(filePath);
5038
+ fs19.unlinkSync(filePath);
4737
5039
  deleted.push(filePath);
4738
5040
  }
4739
5041
  written.push(...ensureBuiltinSkills());
@@ -4764,8 +5066,8 @@ function undoSetup() {
4764
5066
  const removed = [];
4765
5067
  for (const entry of manifest.entries) {
4766
5068
  if (entry.action === "created") {
4767
- if (fs18.existsSync(entry.path)) {
4768
- fs18.unlinkSync(entry.path);
5069
+ if (fs19.existsSync(entry.path)) {
5070
+ fs19.unlinkSync(entry.path);
4769
5071
  removed.push(entry.path);
4770
5072
  }
4771
5073
  } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
@@ -4774,8 +5076,8 @@ function undoSetup() {
4774
5076
  }
4775
5077
  }
4776
5078
  }
4777
- if (fs18.existsSync(MANIFEST_FILE)) {
4778
- fs18.unlinkSync(MANIFEST_FILE);
5079
+ if (fs19.existsSync(MANIFEST_FILE)) {
5080
+ fs19.unlinkSync(MANIFEST_FILE);
4779
5081
  }
4780
5082
  return { restored, removed };
4781
5083
  }
@@ -4816,22 +5118,22 @@ function getFilesToWrite(setup) {
4816
5118
  }
4817
5119
  function ensureGitignore() {
4818
5120
  const gitignorePath = ".gitignore";
4819
- if (fs18.existsSync(gitignorePath)) {
4820
- const content = fs18.readFileSync(gitignorePath, "utf-8");
5121
+ if (fs19.existsSync(gitignorePath)) {
5122
+ const content = fs19.readFileSync(gitignorePath, "utf-8");
4821
5123
  if (!content.includes(".caliber/")) {
4822
- fs18.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
5124
+ fs19.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
4823
5125
  }
4824
5126
  } else {
4825
- fs18.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
5127
+ fs19.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
4826
5128
  }
4827
5129
  }
4828
5130
 
4829
5131
  // src/writers/staging.ts
4830
- import fs19 from "fs";
4831
- import path16 from "path";
4832
- var STAGED_DIR = path16.join(CALIBER_DIR, "staged");
4833
- var PROPOSED_DIR = path16.join(STAGED_DIR, "proposed");
4834
- var CURRENT_DIR = path16.join(STAGED_DIR, "current");
5132
+ import fs20 from "fs";
5133
+ import path17 from "path";
5134
+ var STAGED_DIR = path17.join(CALIBER_DIR, "staged");
5135
+ var PROPOSED_DIR = path17.join(STAGED_DIR, "proposed");
5136
+ var CURRENT_DIR = path17.join(STAGED_DIR, "current");
4835
5137
  function normalizeContent(content) {
4836
5138
  return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
4837
5139
  }
@@ -4841,20 +5143,20 @@ function stageFiles(files, projectDir) {
4841
5143
  let modifiedFiles = 0;
4842
5144
  const stagedFiles = [];
4843
5145
  for (const file of files) {
4844
- const originalPath = path16.join(projectDir, file.path);
4845
- if (fs19.existsSync(originalPath)) {
4846
- const existing = fs19.readFileSync(originalPath, "utf-8");
5146
+ const originalPath = path17.join(projectDir, file.path);
5147
+ if (fs20.existsSync(originalPath)) {
5148
+ const existing = fs20.readFileSync(originalPath, "utf-8");
4847
5149
  if (normalizeContent(existing) === normalizeContent(file.content)) {
4848
5150
  continue;
4849
5151
  }
4850
5152
  }
4851
- const proposedPath = path16.join(PROPOSED_DIR, file.path);
4852
- fs19.mkdirSync(path16.dirname(proposedPath), { recursive: true });
4853
- fs19.writeFileSync(proposedPath, file.content);
4854
- if (fs19.existsSync(originalPath)) {
4855
- const currentPath = path16.join(CURRENT_DIR, file.path);
4856
- fs19.mkdirSync(path16.dirname(currentPath), { recursive: true });
4857
- fs19.copyFileSync(originalPath, currentPath);
5153
+ const proposedPath = path17.join(PROPOSED_DIR, file.path);
5154
+ fs20.mkdirSync(path17.dirname(proposedPath), { recursive: true });
5155
+ fs20.writeFileSync(proposedPath, file.content);
5156
+ if (fs20.existsSync(originalPath)) {
5157
+ const currentPath = path17.join(CURRENT_DIR, file.path);
5158
+ fs20.mkdirSync(path17.dirname(currentPath), { recursive: true });
5159
+ fs20.copyFileSync(originalPath, currentPath);
4858
5160
  modifiedFiles++;
4859
5161
  stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
4860
5162
  } else {
@@ -4865,13 +5167,13 @@ function stageFiles(files, projectDir) {
4865
5167
  return { newFiles, modifiedFiles, stagedFiles };
4866
5168
  }
4867
5169
  function cleanupStaging() {
4868
- if (fs19.existsSync(STAGED_DIR)) {
4869
- fs19.rmSync(STAGED_DIR, { recursive: true, force: true });
5170
+ if (fs20.existsSync(STAGED_DIR)) {
5171
+ fs20.rmSync(STAGED_DIR, { recursive: true, force: true });
4870
5172
  }
4871
5173
  }
4872
5174
 
4873
5175
  // src/commands/setup-files.ts
4874
- import fs20 from "fs";
5176
+ import fs21 from "fs";
4875
5177
  function collectSetupFiles(setup, targetAgent) {
4876
5178
  const files = [];
4877
5179
  const claude = setup.claude;
@@ -4930,7 +5232,7 @@ function collectSetupFiles(setup, targetAgent) {
4930
5232
  }
4931
5233
  }
4932
5234
  const codexTargeted = targetAgent ? targetAgent.includes("codex") : false;
4933
- if (codexTargeted && !fs20.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
5235
+ if (codexTargeted && !fs21.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
4934
5236
  const agentRefs = [];
4935
5237
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
4936
5238
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
@@ -4948,9 +5250,9 @@ ${agentRefs.join(" ")}
4948
5250
 
4949
5251
  // src/lib/learning-hooks.ts
4950
5252
  init_resolve_caliber();
4951
- import fs21 from "fs";
4952
- import path17 from "path";
4953
- var SETTINGS_PATH = path17.join(".claude", "settings.json");
5253
+ import fs22 from "fs";
5254
+ import path18 from "path";
5255
+ var SETTINGS_PATH2 = path18.join(".claude", "settings.json");
4954
5256
  var HOOK_TAILS = [
4955
5257
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
4956
5258
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
@@ -4966,24 +5268,24 @@ function getHookConfigs() {
4966
5268
  description
4967
5269
  }));
4968
5270
  }
4969
- function readSettings() {
4970
- if (!fs21.existsSync(SETTINGS_PATH)) return {};
5271
+ function readSettings2() {
5272
+ if (!fs22.existsSync(SETTINGS_PATH2)) return {};
4971
5273
  try {
4972
- return JSON.parse(fs21.readFileSync(SETTINGS_PATH, "utf-8"));
5274
+ return JSON.parse(fs22.readFileSync(SETTINGS_PATH2, "utf-8"));
4973
5275
  } catch {
4974
5276
  return {};
4975
5277
  }
4976
5278
  }
4977
- function writeSettings(settings) {
4978
- const dir = path17.dirname(SETTINGS_PATH);
4979
- if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
4980
- fs21.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
5279
+ function writeSettings2(settings) {
5280
+ const dir = path18.dirname(SETTINGS_PATH2);
5281
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5282
+ fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
4981
5283
  }
4982
5284
  function hasLearningHook(matchers, tail) {
4983
5285
  return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
4984
5286
  }
4985
5287
  function areLearningHooksInstalled() {
4986
- const settings = readSettings();
5288
+ const settings = readSettings2();
4987
5289
  if (!settings.hooks) return false;
4988
5290
  return HOOK_TAILS.every((cfg) => {
4989
5291
  const matchers = settings.hooks[cfg.event];
@@ -4994,7 +5296,7 @@ function installLearningHooks() {
4994
5296
  if (areLearningHooksInstalled()) {
4995
5297
  return { installed: false, alreadyInstalled: true };
4996
5298
  }
4997
- const settings = readSettings();
5299
+ const settings = readSettings2();
4998
5300
  if (!settings.hooks) settings.hooks = {};
4999
5301
  const configs = getHookConfigs();
5000
5302
  for (const cfg of configs) {
@@ -5008,10 +5310,10 @@ function installLearningHooks() {
5008
5310
  });
5009
5311
  }
5010
5312
  }
5011
- writeSettings(settings);
5313
+ writeSettings2(settings);
5012
5314
  return { installed: true, alreadyInstalled: false };
5013
5315
  }
5014
- var CURSOR_HOOKS_PATH = path17.join(".cursor", "hooks.json");
5316
+ var CURSOR_HOOKS_PATH = path18.join(".cursor", "hooks.json");
5015
5317
  var CURSOR_HOOK_EVENTS = [
5016
5318
  { event: "postToolUse", tail: "learn observe" },
5017
5319
  { event: "postToolUseFailure", tail: "learn observe --failure" },
@@ -5019,17 +5321,17 @@ var CURSOR_HOOK_EVENTS = [
5019
5321
  { event: "sessionEnd", tail: "learn finalize --auto" }
5020
5322
  ];
5021
5323
  function readCursorHooks() {
5022
- if (!fs21.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
5324
+ if (!fs22.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
5023
5325
  try {
5024
- return JSON.parse(fs21.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
5326
+ return JSON.parse(fs22.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
5025
5327
  } catch {
5026
5328
  return { version: 1, hooks: {} };
5027
5329
  }
5028
5330
  }
5029
5331
  function writeCursorHooks(config) {
5030
- const dir = path17.dirname(CURSOR_HOOKS_PATH);
5031
- if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
5032
- fs21.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
5332
+ const dir = path18.dirname(CURSOR_HOOKS_PATH);
5333
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5334
+ fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
5033
5335
  }
5034
5336
  function hasCursorHook(entries, tail) {
5035
5337
  return entries.some((e) => isCaliberCommand(e.command, tail));
@@ -5076,7 +5378,7 @@ function removeCursorLearningHooks() {
5076
5378
  return { removed: true, notFound: false };
5077
5379
  }
5078
5380
  function removeLearningHooks() {
5079
- const settings = readSettings();
5381
+ const settings = readSettings2();
5080
5382
  if (!settings.hooks) return { removed: false, notFound: true };
5081
5383
  let removedAny = false;
5082
5384
  for (const cfg of HOOK_TAILS) {
@@ -5093,7 +5395,7 @@ function removeLearningHooks() {
5093
5395
  delete settings.hooks;
5094
5396
  }
5095
5397
  if (!removedAny) return { removed: false, notFound: true };
5096
- writeSettings(settings);
5398
+ writeSettings2(settings);
5097
5399
  return { removed: true, notFound: false };
5098
5400
  }
5099
5401
 
@@ -5101,10 +5403,10 @@ function removeLearningHooks() {
5101
5403
  init_resolve_caliber();
5102
5404
 
5103
5405
  // src/lib/state.ts
5104
- import fs22 from "fs";
5105
- import path18 from "path";
5106
- import { execSync as execSync8 } from "child_process";
5107
- var STATE_FILE = path18.join(CALIBER_DIR, ".caliber-state.json");
5406
+ import fs23 from "fs";
5407
+ import path19 from "path";
5408
+ import { execSync as execSync9 } from "child_process";
5409
+ var STATE_FILE = path19.join(CALIBER_DIR, ".caliber-state.json");
5108
5410
  function normalizeTargetAgent(value) {
5109
5411
  if (Array.isArray(value)) return value;
5110
5412
  if (typeof value === "string") {
@@ -5115,8 +5417,8 @@ function normalizeTargetAgent(value) {
5115
5417
  }
5116
5418
  function readState() {
5117
5419
  try {
5118
- if (!fs22.existsSync(STATE_FILE)) return null;
5119
- const raw = JSON.parse(fs22.readFileSync(STATE_FILE, "utf-8"));
5420
+ if (!fs23.existsSync(STATE_FILE)) return null;
5421
+ const raw = JSON.parse(fs23.readFileSync(STATE_FILE, "utf-8"));
5120
5422
  if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
5121
5423
  return raw;
5122
5424
  } catch {
@@ -5124,14 +5426,14 @@ function readState() {
5124
5426
  }
5125
5427
  }
5126
5428
  function writeState(state) {
5127
- if (!fs22.existsSync(CALIBER_DIR)) {
5128
- fs22.mkdirSync(CALIBER_DIR, { recursive: true });
5429
+ if (!fs23.existsSync(CALIBER_DIR)) {
5430
+ fs23.mkdirSync(CALIBER_DIR, { recursive: true });
5129
5431
  }
5130
- fs22.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
5432
+ fs23.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
5131
5433
  }
5132
5434
  function getCurrentHeadSha() {
5133
5435
  try {
5134
- return execSync8("git rev-parse HEAD", {
5436
+ return execSync9("git rev-parse HEAD", {
5135
5437
  encoding: "utf-8",
5136
5438
  stdio: ["pipe", "pipe", "pipe"]
5137
5439
  }).trim();
@@ -5278,11 +5580,11 @@ var POINTS_LEARNED_CONTENT = 2;
5278
5580
  var POINTS_SOURCES_CONFIGURED = 3;
5279
5581
  var POINTS_SOURCES_REFERENCED = 3;
5280
5582
  var TOKEN_BUDGET_THRESHOLDS = [
5281
- { maxTokens: 2e3, points: 6 },
5282
- { maxTokens: 3500, points: 5 },
5283
- { maxTokens: 5e3, points: 4 },
5284
- { maxTokens: 8e3, points: 2 },
5285
- { maxTokens: 12e3, points: 1 }
5583
+ { maxTokens: 5e3, points: 6 },
5584
+ { maxTokens: 8e3, points: 5 },
5585
+ { maxTokens: 12e3, points: 4 },
5586
+ { maxTokens: 16e3, points: 2 },
5587
+ { maxTokens: 24e3, points: 1 }
5286
5588
  ];
5287
5589
  var CODE_BLOCK_THRESHOLDS = [
5288
5590
  { minBlocks: 3, points: 8 },
@@ -5744,7 +6046,7 @@ function checkGrounding(dir) {
5744
6046
 
5745
6047
  // src/scoring/checks/accuracy.ts
5746
6048
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
5747
- import { execSync as execSync9 } from "child_process";
6049
+ import { execSync as execSync10 } from "child_process";
5748
6050
  import { join as join5 } from "path";
5749
6051
  init_resolve_caliber();
5750
6052
  function validateReferences(dir) {
@@ -5754,13 +6056,13 @@ function validateReferences(dir) {
5754
6056
  }
5755
6057
  function detectGitDrift(dir) {
5756
6058
  try {
5757
- execSync9("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
6059
+ execSync10("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
5758
6060
  } catch {
5759
6061
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
5760
6062
  }
5761
6063
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
5762
6064
  try {
5763
- const headTimestamp = execSync9(
6065
+ const headTimestamp = execSync10(
5764
6066
  "git log -1 --format=%ct HEAD",
5765
6067
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5766
6068
  ).trim();
@@ -5781,7 +6083,7 @@ function detectGitDrift(dir) {
5781
6083
  let latestConfigCommitHash = null;
5782
6084
  for (const file of configFiles) {
5783
6085
  try {
5784
- const hash = execSync9(
6086
+ const hash = execSync10(
5785
6087
  `git log -1 --format=%H -- "${file}"`,
5786
6088
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5787
6089
  ).trim();
@@ -5790,7 +6092,7 @@ function detectGitDrift(dir) {
5790
6092
  latestConfigCommitHash = hash;
5791
6093
  } else {
5792
6094
  try {
5793
- execSync9(
6095
+ execSync10(
5794
6096
  `git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
5795
6097
  { cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
5796
6098
  );
@@ -5805,12 +6107,12 @@ function detectGitDrift(dir) {
5805
6107
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
5806
6108
  }
5807
6109
  try {
5808
- const countStr = execSync9(
6110
+ const countStr = execSync10(
5809
6111
  `git rev-list --count ${latestConfigCommitHash}..HEAD`,
5810
6112
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5811
6113
  ).trim();
5812
6114
  const commitsSince = parseInt(countStr, 10) || 0;
5813
- const lastDate = execSync9(
6115
+ const lastDate = execSync10(
5814
6116
  `git log -1 --format=%ci ${latestConfigCommitHash}`,
5815
6117
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5816
6118
  ).trim();
@@ -5883,12 +6185,12 @@ function checkAccuracy(dir) {
5883
6185
  // src/scoring/checks/freshness.ts
5884
6186
  init_resolve_caliber();
5885
6187
  import { existsSync as existsSync5, statSync as statSync3 } from "fs";
5886
- import { execSync as execSync10 } from "child_process";
6188
+ import { execSync as execSync11 } from "child_process";
5887
6189
  import { join as join6 } from "path";
5888
6190
  function getCommitsSinceConfigUpdate(dir) {
5889
6191
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
5890
6192
  try {
5891
- const headTimestamp = execSync10(
6193
+ const headTimestamp = execSync11(
5892
6194
  "git log -1 --format=%ct HEAD",
5893
6195
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5894
6196
  ).trim();
@@ -5908,12 +6210,12 @@ function getCommitsSinceConfigUpdate(dir) {
5908
6210
  }
5909
6211
  for (const file of configFiles) {
5910
6212
  try {
5911
- const hash = execSync10(
6213
+ const hash = execSync11(
5912
6214
  `git log -1 --format=%H -- "${file}"`,
5913
6215
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5914
6216
  ).trim();
5915
6217
  if (hash) {
5916
- const countStr = execSync10(
6218
+ const countStr = execSync11(
5917
6219
  `git rev-list --count ${hash}..HEAD`,
5918
6220
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5919
6221
  ).trim();
@@ -6031,12 +6333,12 @@ function checkFreshness(dir) {
6031
6333
 
6032
6334
  // src/scoring/checks/bonus.ts
6033
6335
  import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
6034
- import { execSync as execSync11 } from "child_process";
6336
+ import { execSync as execSync12 } from "child_process";
6035
6337
  import { join as join7 } from "path";
6036
6338
  init_resolve_caliber();
6037
6339
  function hasPreCommitHook(dir) {
6038
6340
  try {
6039
- const gitDir = execSync11("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6341
+ const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6040
6342
  const hookPath = join7(gitDir, "hooks", "pre-commit");
6041
6343
  const content = readFileOrNull(hookPath);
6042
6344
  return content ? content.includes("caliber") : false;
@@ -6193,22 +6495,22 @@ function checkSources(dir) {
6193
6495
  }
6194
6496
 
6195
6497
  // src/scoring/dismissed.ts
6196
- import fs23 from "fs";
6197
- import path19 from "path";
6198
- var DISMISSED_FILE = path19.join(CALIBER_DIR, "dismissed-checks.json");
6498
+ import fs24 from "fs";
6499
+ import path20 from "path";
6500
+ var DISMISSED_FILE = path20.join(CALIBER_DIR, "dismissed-checks.json");
6199
6501
  function readDismissedChecks() {
6200
6502
  try {
6201
- if (!fs23.existsSync(DISMISSED_FILE)) return [];
6202
- return JSON.parse(fs23.readFileSync(DISMISSED_FILE, "utf-8"));
6503
+ if (!fs24.existsSync(DISMISSED_FILE)) return [];
6504
+ return JSON.parse(fs24.readFileSync(DISMISSED_FILE, "utf-8"));
6203
6505
  } catch {
6204
6506
  return [];
6205
6507
  }
6206
6508
  }
6207
6509
  function writeDismissedChecks(checks) {
6208
- if (!fs23.existsSync(CALIBER_DIR)) {
6209
- fs23.mkdirSync(CALIBER_DIR, { recursive: true });
6510
+ if (!fs24.existsSync(CALIBER_DIR)) {
6511
+ fs24.mkdirSync(CALIBER_DIR, { recursive: true });
6210
6512
  }
6211
- fs23.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
6513
+ fs24.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
6212
6514
  }
6213
6515
  function getDismissedIds() {
6214
6516
  return new Set(readDismissedChecks().map((c) => c.id));
@@ -6463,27 +6765,27 @@ import { PostHog } from "posthog-node";
6463
6765
  import chalk5 from "chalk";
6464
6766
 
6465
6767
  // src/telemetry/config.ts
6466
- import fs24 from "fs";
6467
- import path20 from "path";
6768
+ import fs25 from "fs";
6769
+ import path21 from "path";
6468
6770
  import os5 from "os";
6469
6771
  import crypto4 from "crypto";
6470
- import { execSync as execSync12 } from "child_process";
6471
- var CONFIG_DIR2 = path20.join(os5.homedir(), ".caliber");
6472
- var CONFIG_FILE2 = path20.join(CONFIG_DIR2, "config.json");
6772
+ import { execSync as execSync13 } from "child_process";
6773
+ var CONFIG_DIR2 = path21.join(os5.homedir(), ".caliber");
6774
+ var CONFIG_FILE2 = path21.join(CONFIG_DIR2, "config.json");
6473
6775
  var runtimeDisabled = false;
6474
6776
  function readConfig() {
6475
6777
  try {
6476
- if (!fs24.existsSync(CONFIG_FILE2)) return {};
6477
- return JSON.parse(fs24.readFileSync(CONFIG_FILE2, "utf-8"));
6778
+ if (!fs25.existsSync(CONFIG_FILE2)) return {};
6779
+ return JSON.parse(fs25.readFileSync(CONFIG_FILE2, "utf-8"));
6478
6780
  } catch {
6479
6781
  return {};
6480
6782
  }
6481
6783
  }
6482
6784
  function writeConfig(config) {
6483
- if (!fs24.existsSync(CONFIG_DIR2)) {
6484
- fs24.mkdirSync(CONFIG_DIR2, { recursive: true });
6785
+ if (!fs25.existsSync(CONFIG_DIR2)) {
6786
+ fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
6485
6787
  }
6486
- fs24.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
6788
+ fs25.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
6487
6789
  }
6488
6790
  function getMachineId() {
6489
6791
  const config = readConfig();
@@ -6495,7 +6797,7 @@ function getMachineId() {
6495
6797
  var EMAIL_HASH_KEY = "caliber-telemetry-v1";
6496
6798
  function getGitEmailHash() {
6497
6799
  try {
6498
- const email = execSync12("git config user.email", { encoding: "utf-8" }).trim();
6800
+ const email = execSync13("git config user.email", { encoding: "utf-8" }).trim();
6499
6801
  if (!email) return void 0;
6500
6802
  return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
6501
6803
  } catch {
@@ -7694,8 +7996,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
7694
7996
  }
7695
7997
 
7696
7998
  // src/lib/debug-report.ts
7697
- import fs25 from "fs";
7698
- import path21 from "path";
7999
+ import fs26 from "fs";
8000
+ import path22 from "path";
7699
8001
  var DebugReport = class {
7700
8002
  sections = [];
7701
8003
  startTime;
@@ -7764,11 +8066,11 @@ var DebugReport = class {
7764
8066
  lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
7765
8067
  lines.push("");
7766
8068
  }
7767
- const dir = path21.dirname(outputPath);
7768
- if (!fs25.existsSync(dir)) {
7769
- fs25.mkdirSync(dir, { recursive: true });
8069
+ const dir = path22.dirname(outputPath);
8070
+ if (!fs26.existsSync(dir)) {
8071
+ fs26.mkdirSync(dir, { recursive: true });
7770
8072
  }
7771
- fs25.writeFileSync(outputPath, lines.join("\n"));
8073
+ fs26.writeFileSync(outputPath, lines.join("\n"));
7772
8074
  }
7773
8075
  };
7774
8076
  function formatMs(ms) {
@@ -8169,7 +8471,7 @@ import chalk11 from "chalk";
8169
8471
  import ora3 from "ora";
8170
8472
  import select5 from "@inquirer/select";
8171
8473
  import checkbox from "@inquirer/checkbox";
8172
- import fs28 from "fs";
8474
+ import fs29 from "fs";
8173
8475
 
8174
8476
  // src/ai/refine.ts
8175
8477
  async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
@@ -8310,10 +8612,10 @@ init_config();
8310
8612
  init_review();
8311
8613
  function detectAgents(dir) {
8312
8614
  const agents = [];
8313
- if (fs28.existsSync(`${dir}/.claude`)) agents.push("claude");
8314
- if (fs28.existsSync(`${dir}/.cursor`)) agents.push("cursor");
8315
- if (fs28.existsSync(`${dir}/.agents`) || fs28.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
8316
- if (fs28.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
8615
+ if (fs29.existsSync(`${dir}/.claude`)) agents.push("claude");
8616
+ if (fs29.existsSync(`${dir}/.cursor`)) agents.push("cursor");
8617
+ if (fs29.existsSync(`${dir}/.agents`) || fs29.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
8618
+ if (fs29.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
8317
8619
  return agents;
8318
8620
  }
8319
8621
  async function promptAgent(detected) {
@@ -8435,18 +8737,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
8435
8737
 
8436
8738
  // src/commands/init-display.ts
8437
8739
  import chalk12 from "chalk";
8438
- import fs29 from "fs";
8740
+ import fs30 from "fs";
8439
8741
  function formatWhatChanged(setup) {
8440
8742
  const lines = [];
8441
8743
  const claude = setup.claude;
8442
8744
  const codex = setup.codex;
8443
8745
  const cursor = setup.cursor;
8444
8746
  if (claude?.claudeMd) {
8445
- const action = fs29.existsSync("CLAUDE.md") ? "Updated" : "Created";
8747
+ const action = fs30.existsSync("CLAUDE.md") ? "Updated" : "Created";
8446
8748
  lines.push(`${action} CLAUDE.md`);
8447
8749
  }
8448
8750
  if (codex?.agentsMd) {
8449
- const action = fs29.existsSync("AGENTS.md") ? "Updated" : "Created";
8751
+ const action = fs30.existsSync("AGENTS.md") ? "Updated" : "Created";
8450
8752
  lines.push(`${action} AGENTS.md`);
8451
8753
  }
8452
8754
  const allSkills = [];
@@ -8483,7 +8785,7 @@ function printSetupSummary(setup) {
8483
8785
  };
8484
8786
  if (claude) {
8485
8787
  if (claude.claudeMd) {
8486
- const icon = fs29.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
8788
+ const icon = fs30.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
8487
8789
  const desc = getDescription("CLAUDE.md");
8488
8790
  console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
8489
8791
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -8493,7 +8795,7 @@ function printSetupSummary(setup) {
8493
8795
  if (Array.isArray(skills) && skills.length > 0) {
8494
8796
  for (const skill of skills) {
8495
8797
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
8496
- const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8798
+ const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8497
8799
  const desc = getDescription(skillPath);
8498
8800
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
8499
8801
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -8504,7 +8806,7 @@ function printSetupSummary(setup) {
8504
8806
  const codex = setup.codex;
8505
8807
  if (codex) {
8506
8808
  if (codex.agentsMd) {
8507
- const icon = fs29.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
8809
+ const icon = fs30.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
8508
8810
  const desc = getDescription("AGENTS.md");
8509
8811
  console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
8510
8812
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -8514,7 +8816,7 @@ function printSetupSummary(setup) {
8514
8816
  if (Array.isArray(codexSkills) && codexSkills.length > 0) {
8515
8817
  for (const skill of codexSkills) {
8516
8818
  const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
8517
- const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8819
+ const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8518
8820
  const desc = getDescription(skillPath);
8519
8821
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
8520
8822
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -8524,7 +8826,7 @@ function printSetupSummary(setup) {
8524
8826
  }
8525
8827
  if (cursor) {
8526
8828
  if (cursor.cursorrules) {
8527
- const icon = fs29.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
8829
+ const icon = fs30.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
8528
8830
  const desc = getDescription(".cursorrules");
8529
8831
  console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
8530
8832
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -8534,7 +8836,7 @@ function printSetupSummary(setup) {
8534
8836
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
8535
8837
  for (const skill of cursorSkills) {
8536
8838
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
8537
- const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8839
+ const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8538
8840
  const desc = getDescription(skillPath);
8539
8841
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
8540
8842
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -8545,7 +8847,7 @@ function printSetupSummary(setup) {
8545
8847
  if (Array.isArray(rulesArr) && rulesArr.length > 0) {
8546
8848
  for (const rule of rulesArr) {
8547
8849
  const rulePath = `.cursor/rules/${rule.filename}`;
8548
- const icon = fs29.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
8850
+ const icon = fs30.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
8549
8851
  const desc = getDescription(rulePath);
8550
8852
  console.log(` ${icon} ${chalk12.bold(rulePath)}`);
8551
8853
  if (desc) {
@@ -8592,12 +8894,12 @@ function displayTokenUsage() {
8592
8894
  // src/commands/init-helpers.ts
8593
8895
  init_config();
8594
8896
  import chalk13 from "chalk";
8595
- import fs30 from "fs";
8596
- import path23 from "path";
8897
+ import fs31 from "fs";
8898
+ import path24 from "path";
8597
8899
  function isFirstRun(dir) {
8598
- const caliberDir = path23.join(dir, ".caliber");
8900
+ const caliberDir = path24.join(dir, ".caliber");
8599
8901
  try {
8600
- const stat = fs30.statSync(caliberDir);
8902
+ const stat = fs31.statSync(caliberDir);
8601
8903
  return !stat.isDirectory();
8602
8904
  } catch {
8603
8905
  return true;
@@ -8650,8 +8952,8 @@ function ensurePermissions(fingerprint) {
8650
8952
  const settingsPath = ".claude/settings.json";
8651
8953
  let settings = {};
8652
8954
  try {
8653
- if (fs30.existsSync(settingsPath)) {
8654
- settings = JSON.parse(fs30.readFileSync(settingsPath, "utf-8"));
8955
+ if (fs31.existsSync(settingsPath)) {
8956
+ settings = JSON.parse(fs31.readFileSync(settingsPath, "utf-8"));
8655
8957
  }
8656
8958
  } catch {
8657
8959
  }
@@ -8660,12 +8962,12 @@ function ensurePermissions(fingerprint) {
8660
8962
  if (Array.isArray(allow) && allow.length > 0) return;
8661
8963
  permissions.allow = derivePermissions(fingerprint);
8662
8964
  settings.permissions = permissions;
8663
- if (!fs30.existsSync(".claude")) fs30.mkdirSync(".claude", { recursive: true });
8664
- fs30.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8965
+ if (!fs31.existsSync(".claude")) fs31.mkdirSync(".claude", { recursive: true });
8966
+ fs31.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8665
8967
  }
8666
8968
  function writeErrorLog(config, rawOutput, error, stopReason) {
8667
8969
  try {
8668
- const logPath = path23.join(process.cwd(), ".caliber", "error-log.md");
8970
+ const logPath = path24.join(process.cwd(), ".caliber", "error-log.md");
8669
8971
  const lines = [
8670
8972
  `# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
8671
8973
  "",
@@ -8678,8 +8980,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
8678
8980
  lines.push("## Error", "```", error, "```", "");
8679
8981
  }
8680
8982
  lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
8681
- fs30.mkdirSync(path23.join(process.cwd(), ".caliber"), { recursive: true });
8682
- fs30.writeFileSync(logPath, lines.join("\n"));
8983
+ fs31.mkdirSync(path24.join(process.cwd(), ".caliber"), { recursive: true });
8984
+ fs31.writeFileSync(logPath, lines.join("\n"));
8683
8985
  console.log(chalk13.dim(`
8684
8986
  Error log written to .caliber/error-log.md`));
8685
8987
  } catch {
@@ -8730,13 +9032,13 @@ ${JSON.stringify(checkList, null, 2)}`,
8730
9032
  }
8731
9033
 
8732
9034
  // src/scoring/history.ts
8733
- import fs31 from "fs";
8734
- import path24 from "path";
9035
+ import fs32 from "fs";
9036
+ import path25 from "path";
8735
9037
  var HISTORY_FILE = "score-history.jsonl";
8736
9038
  var MAX_ENTRIES = 500;
8737
9039
  var TRIM_THRESHOLD = MAX_ENTRIES + 50;
8738
9040
  function historyFilePath() {
8739
- return path24.join(CALIBER_DIR, HISTORY_FILE);
9041
+ return path25.join(CALIBER_DIR, HISTORY_FILE);
8740
9042
  }
8741
9043
  function recordScore(result, trigger) {
8742
9044
  const entry = {
@@ -8747,14 +9049,14 @@ function recordScore(result, trigger) {
8747
9049
  trigger
8748
9050
  };
8749
9051
  try {
8750
- fs31.mkdirSync(CALIBER_DIR, { recursive: true });
9052
+ fs32.mkdirSync(CALIBER_DIR, { recursive: true });
8751
9053
  const filePath = historyFilePath();
8752
- fs31.appendFileSync(filePath, JSON.stringify(entry) + "\n");
8753
- const stat = fs31.statSync(filePath);
9054
+ fs32.appendFileSync(filePath, JSON.stringify(entry) + "\n");
9055
+ const stat = fs32.statSync(filePath);
8754
9056
  if (stat.size > TRIM_THRESHOLD * 120) {
8755
- const lines = fs31.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9057
+ const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
8756
9058
  if (lines.length > MAX_ENTRIES) {
8757
- fs31.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
9059
+ fs32.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
8758
9060
  }
8759
9061
  }
8760
9062
  } catch {
@@ -8763,7 +9065,7 @@ function recordScore(result, trigger) {
8763
9065
  function readScoreHistory() {
8764
9066
  const filePath = historyFilePath();
8765
9067
  try {
8766
- const content = fs31.readFileSync(filePath, "utf-8");
9068
+ const content = fs32.readFileSync(filePath, "utf-8");
8767
9069
  const entries = [];
8768
9070
  for (const line of content.split("\n")) {
8769
9071
  if (!line) continue;
@@ -8809,13 +9111,13 @@ async function initCommand(options) {
8809
9111
  \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
8810
9112
  \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
8811
9113
  `));
8812
- console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
8813
- console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
9114
+ console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
9115
+ console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
8814
9116
  console.log(title.bold(" How it works:\n"));
8815
9117
  console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
8816
9118
  console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
8817
9119
  console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
8818
- console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
9120
+ console.log(chalk14.dim(" 4. Finalize Install sync hooks and score your setup\n"));
8819
9121
  } else {
8820
9122
  console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
8821
9123
  }
@@ -9186,7 +9488,7 @@ async function initCommand(options) {
9186
9488
  const { default: ora9 } = await import("ora");
9187
9489
  const writeSpinner = ora9("Writing config files...").start();
9188
9490
  try {
9189
- if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
9491
+ if (targetAgent.includes("codex") && !fs33.existsSync("AGENTS.md") && !generatedSetup.codex) {
9190
9492
  const claude = generatedSetup.claude;
9191
9493
  const cursor = generatedSetup.cursor;
9192
9494
  const agentRefs = [];
@@ -9229,6 +9531,12 @@ ${agentRefs.join(" ")}
9229
9531
  throw new Error("__exit__");
9230
9532
  }
9231
9533
  if (fingerprint) ensurePermissions(fingerprint);
9534
+ const hookResult = installPreCommitHook();
9535
+ if (hookResult.installed) {
9536
+ console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 configs sync on every commit`);
9537
+ } else if (hookResult.alreadyInstalled) {
9538
+ console.log(chalk14.dim(" Pre-commit hook already installed"));
9539
+ }
9232
9540
  const sha = getCurrentHeadSha();
9233
9541
  writeState({
9234
9542
  lastRefreshSha: sha ?? "",
@@ -9306,9 +9614,9 @@ ${agentRefs.join(" ")}
9306
9614
  if (targetAgent.includes("cursor")) installCursorLearningHooks();
9307
9615
  }
9308
9616
  }
9309
- console.log(chalk14.bold.green("\n Configuration complete!"));
9310
- console.log(chalk14.dim(" Your AI agents now understand your project's architecture, build commands,"));
9311
- console.log(chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n"));
9617
+ console.log(chalk14.bold.green("\n Caliber is set up!"));
9618
+ console.log(chalk14.dim(" Your AI agent configs will now stay in sync with your codebase automatically."));
9619
+ console.log(chalk14.dim(" Every commit refreshes configs for all your agents. All changes are backed up.\n"));
9312
9620
  const done = chalk14.green("\u2713");
9313
9621
  const skip = chalk14.dim("\u2013");
9314
9622
  console.log(chalk14.bold(" What was configured:\n"));
@@ -9336,9 +9644,9 @@ ${agentRefs.join(" ")}
9336
9644
  }
9337
9645
  if (report) {
9338
9646
  report.markStep("Finished");
9339
- const reportPath = path25.join(process.cwd(), ".caliber", "debug-report.md");
9647
+ const reportPath = path26.join(process.cwd(), ".caliber", "debug-report.md");
9340
9648
  report.write(reportPath);
9341
- console.log(chalk14.dim(` Debug report written to ${path25.relative(process.cwd(), reportPath)}
9649
+ console.log(chalk14.dim(` Debug report written to ${path26.relative(process.cwd(), reportPath)}
9342
9650
  `));
9343
9651
  }
9344
9652
  }
@@ -9377,7 +9685,7 @@ function undoCommand() {
9377
9685
 
9378
9686
  // src/commands/status.ts
9379
9687
  import chalk16 from "chalk";
9380
- import fs33 from "fs";
9688
+ import fs34 from "fs";
9381
9689
  init_config();
9382
9690
  init_resolve_caliber();
9383
9691
  async function statusCommand(options) {
@@ -9406,7 +9714,7 @@ async function statusCommand(options) {
9406
9714
  }
9407
9715
  console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
9408
9716
  for (const entry of manifest.entries) {
9409
- const exists = fs33.existsSync(entry.path);
9717
+ const exists = fs34.existsSync(entry.path);
9410
9718
  const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
9411
9719
  console.log(` ${icon} ${entry.path} (${entry.action})`);
9412
9720
  }
@@ -9563,9 +9871,9 @@ async function regenerateCommand(options) {
9563
9871
  }
9564
9872
 
9565
9873
  // src/commands/score.ts
9566
- import fs34 from "fs";
9874
+ import fs35 from "fs";
9567
9875
  import os7 from "os";
9568
- import path26 from "path";
9876
+ import path27 from "path";
9569
9877
  import { execFileSync as execFileSync2 } from "child_process";
9570
9878
  import chalk18 from "chalk";
9571
9879
  init_resolve_caliber();
@@ -9573,12 +9881,12 @@ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS
9573
9881
  var CONFIG_DIRS = [".claude", ".cursor"];
9574
9882
  function scoreBaseRef(ref, target) {
9575
9883
  if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
9576
- const tmpDir = fs34.mkdtempSync(path26.join(os7.tmpdir(), "caliber-compare-"));
9884
+ const tmpDir = fs35.mkdtempSync(path27.join(os7.tmpdir(), "caliber-compare-"));
9577
9885
  try {
9578
9886
  for (const file of CONFIG_FILES) {
9579
9887
  try {
9580
9888
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9581
- fs34.writeFileSync(path26.join(tmpDir, file), content);
9889
+ fs35.writeFileSync(path27.join(tmpDir, file), content);
9582
9890
  } catch {
9583
9891
  }
9584
9892
  }
@@ -9586,10 +9894,10 @@ function scoreBaseRef(ref, target) {
9586
9894
  try {
9587
9895
  const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
9588
9896
  for (const file of files) {
9589
- const filePath = path26.join(tmpDir, file);
9590
- fs34.mkdirSync(path26.dirname(filePath), { recursive: true });
9897
+ const filePath = path27.join(tmpDir, file);
9898
+ fs35.mkdirSync(path27.dirname(filePath), { recursive: true });
9591
9899
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9592
- fs34.writeFileSync(filePath, content);
9900
+ fs35.writeFileSync(filePath, content);
9593
9901
  }
9594
9902
  } catch {
9595
9903
  }
@@ -9599,7 +9907,7 @@ function scoreBaseRef(ref, target) {
9599
9907
  } catch {
9600
9908
  return null;
9601
9909
  } finally {
9602
- fs34.rmSync(tmpDir, { recursive: true, force: true });
9910
+ fs35.rmSync(tmpDir, { recursive: true, force: true });
9603
9911
  }
9604
9912
  }
9605
9913
  async function scoreCommand(options) {
@@ -9661,13 +9969,13 @@ async function scoreCommand(options) {
9661
9969
  }
9662
9970
 
9663
9971
  // src/commands/refresh.ts
9664
- import fs38 from "fs";
9665
- import path30 from "path";
9972
+ import fs39 from "fs";
9973
+ import path31 from "path";
9666
9974
  import chalk19 from "chalk";
9667
9975
  import ora6 from "ora";
9668
9976
 
9669
9977
  // src/lib/git-diff.ts
9670
- import { execSync as execSync14 } from "child_process";
9978
+ import { execSync as execSync15 } from "child_process";
9671
9979
  var MAX_DIFF_BYTES = 1e5;
9672
9980
  var DOC_PATTERNS = [
9673
9981
  "CLAUDE.md",
@@ -9682,7 +9990,7 @@ function excludeArgs() {
9682
9990
  }
9683
9991
  function safeExec(cmd) {
9684
9992
  try {
9685
- return execSync14(cmd, {
9993
+ return execSync15(cmd, {
9686
9994
  encoding: "utf-8",
9687
9995
  stdio: ["pipe", "pipe", "pipe"],
9688
9996
  maxBuffer: 10 * 1024 * 1024
@@ -9740,48 +10048,48 @@ function collectDiff(lastSha) {
9740
10048
  }
9741
10049
 
9742
10050
  // src/writers/refresh.ts
9743
- import fs35 from "fs";
9744
- import path27 from "path";
10051
+ import fs36 from "fs";
10052
+ import path28 from "path";
9745
10053
  function writeRefreshDocs(docs) {
9746
10054
  const written = [];
9747
10055
  if (docs.claudeMd) {
9748
- fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10056
+ fs36.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.claudeMd))));
9749
10057
  written.push("CLAUDE.md");
9750
10058
  }
9751
10059
  if (docs.readmeMd) {
9752
- fs35.writeFileSync("README.md", docs.readmeMd);
10060
+ fs36.writeFileSync("README.md", docs.readmeMd);
9753
10061
  written.push("README.md");
9754
10062
  }
9755
10063
  if (docs.cursorrules) {
9756
- fs35.writeFileSync(".cursorrules", docs.cursorrules);
10064
+ fs36.writeFileSync(".cursorrules", docs.cursorrules);
9757
10065
  written.push(".cursorrules");
9758
10066
  }
9759
10067
  if (docs.cursorRules) {
9760
- const rulesDir = path27.join(".cursor", "rules");
9761
- if (!fs35.existsSync(rulesDir)) fs35.mkdirSync(rulesDir, { recursive: true });
10068
+ const rulesDir = path28.join(".cursor", "rules");
10069
+ if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
9762
10070
  for (const rule of docs.cursorRules) {
9763
- fs35.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
10071
+ fs36.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
9764
10072
  written.push(`.cursor/rules/${rule.filename}`);
9765
10073
  }
9766
10074
  }
9767
10075
  if (docs.claudeSkills) {
9768
- const skillsDir = path27.join(".claude", "skills");
9769
- if (!fs35.existsSync(skillsDir)) fs35.mkdirSync(skillsDir, { recursive: true });
10076
+ const skillsDir = path28.join(".claude", "skills");
10077
+ if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
9770
10078
  for (const skill of docs.claudeSkills) {
9771
- fs35.writeFileSync(path27.join(skillsDir, skill.filename), skill.content);
10079
+ fs36.writeFileSync(path28.join(skillsDir, skill.filename), skill.content);
9772
10080
  written.push(`.claude/skills/${skill.filename}`);
9773
10081
  }
9774
10082
  }
9775
10083
  if (docs.copilotInstructions) {
9776
- fs35.mkdirSync(".github", { recursive: true });
9777
- fs35.writeFileSync(path27.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10084
+ fs36.mkdirSync(".github", { recursive: true });
10085
+ fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions))));
9778
10086
  written.push(".github/copilot-instructions.md");
9779
10087
  }
9780
10088
  if (docs.copilotInstructionFiles) {
9781
- const instructionsDir = path27.join(".github", "instructions");
9782
- fs35.mkdirSync(instructionsDir, { recursive: true });
10089
+ const instructionsDir = path28.join(".github", "instructions");
10090
+ fs36.mkdirSync(instructionsDir, { recursive: true });
9783
10091
  for (const file of docs.copilotInstructionFiles) {
9784
- fs35.writeFileSync(path27.join(instructionsDir, file.filename), file.content);
10092
+ fs36.writeFileSync(path28.join(instructionsDir, file.filename), file.content);
9785
10093
  written.push(`.github/instructions/${file.filename}`);
9786
10094
  }
9787
10095
  }
@@ -9867,8 +10175,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
9867
10175
  }
9868
10176
 
9869
10177
  // src/learner/writer.ts
9870
- import fs36 from "fs";
9871
- import path28 from "path";
10178
+ import fs37 from "fs";
10179
+ import path29 from "path";
9872
10180
 
9873
10181
  // src/learner/utils.ts
9874
10182
  var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
@@ -9985,20 +10293,20 @@ function deduplicateLearnedItems(existing, incoming) {
9985
10293
  }
9986
10294
  function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
9987
10295
  const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
9988
- fs36.writeFileSync(filePath, header + merged + "\n");
9989
- if (mode) fs36.chmodSync(filePath, mode);
10296
+ fs37.writeFileSync(filePath, header + merged + "\n");
10297
+ if (mode) fs37.chmodSync(filePath, mode);
9990
10298
  return { newCount, newItems };
9991
10299
  }
9992
10300
  function writeLearnedSection(content) {
9993
10301
  return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
9994
10302
  }
9995
10303
  function writeLearnedSkill(skill) {
9996
- const skillDir = path28.join(".claude", "skills", skill.name);
9997
- if (!fs36.existsSync(skillDir)) fs36.mkdirSync(skillDir, { recursive: true });
9998
- const skillPath = path28.join(skillDir, "SKILL.md");
9999
- if (!skill.isNew && fs36.existsSync(skillPath)) {
10000
- const existing = fs36.readFileSync(skillPath, "utf-8");
10001
- fs36.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
10304
+ const skillDir = path29.join(".claude", "skills", skill.name);
10305
+ if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
10306
+ const skillPath = path29.join(skillDir, "SKILL.md");
10307
+ if (!skill.isNew && fs37.existsSync(skillPath)) {
10308
+ const existing = fs37.readFileSync(skillPath, "utf-8");
10309
+ fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
10002
10310
  } else {
10003
10311
  const frontmatter = [
10004
10312
  "---",
@@ -10007,12 +10315,12 @@ function writeLearnedSkill(skill) {
10007
10315
  "---",
10008
10316
  ""
10009
10317
  ].join("\n");
10010
- fs36.writeFileSync(skillPath, frontmatter + skill.content);
10318
+ fs37.writeFileSync(skillPath, frontmatter + skill.content);
10011
10319
  }
10012
10320
  return skillPath;
10013
10321
  }
10014
10322
  function writePersonalLearnedSection(content) {
10015
- if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
10323
+ if (!fs37.existsSync(AUTH_DIR)) fs37.mkdirSync(AUTH_DIR, { recursive: true });
10016
10324
  return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
10017
10325
  }
10018
10326
  function addLearning(bullet, scope = "project") {
@@ -10025,55 +10333,64 @@ function addLearning(bullet, scope = "project") {
10025
10333
  return { file: LEARNINGS_FILE, added: result.newCount > 0 };
10026
10334
  }
10027
10335
  function readPersonalLearnings() {
10028
- if (!fs36.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
10029
- const content = fs36.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
10336
+ if (!fs37.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
10337
+ const content = fs37.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
10030
10338
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
10031
10339
  return bullets || null;
10032
10340
  }
10033
10341
  function readLearnedSection() {
10034
- if (fs36.existsSync(LEARNINGS_FILE)) {
10035
- const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
10342
+ if (fs37.existsSync(LEARNINGS_FILE)) {
10343
+ const content2 = fs37.readFileSync(LEARNINGS_FILE, "utf-8");
10036
10344
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
10037
10345
  return bullets || null;
10038
10346
  }
10039
10347
  const claudeMdPath = "CLAUDE.md";
10040
- if (!fs36.existsSync(claudeMdPath)) return null;
10041
- const content = fs36.readFileSync(claudeMdPath, "utf-8");
10348
+ if (!fs37.existsSync(claudeMdPath)) return null;
10349
+ const content = fs37.readFileSync(claudeMdPath, "utf-8");
10042
10350
  const startIdx = content.indexOf(LEARNED_START);
10043
10351
  const endIdx = content.indexOf(LEARNED_END);
10044
10352
  if (startIdx === -1 || endIdx === -1) return null;
10045
10353
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
10046
10354
  }
10047
10355
  function migrateInlineLearnings() {
10048
- if (fs36.existsSync(LEARNINGS_FILE)) return false;
10356
+ if (fs37.existsSync(LEARNINGS_FILE)) return false;
10049
10357
  const claudeMdPath = "CLAUDE.md";
10050
- if (!fs36.existsSync(claudeMdPath)) return false;
10051
- const content = fs36.readFileSync(claudeMdPath, "utf-8");
10358
+ if (!fs37.existsSync(claudeMdPath)) return false;
10359
+ const content = fs37.readFileSync(claudeMdPath, "utf-8");
10052
10360
  const startIdx = content.indexOf(LEARNED_START);
10053
10361
  const endIdx = content.indexOf(LEARNED_END);
10054
10362
  if (startIdx === -1 || endIdx === -1) return false;
10055
10363
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
10056
10364
  if (!section) return false;
10057
- fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
10365
+ fs37.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
10058
10366
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
10059
- fs36.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
10367
+ fs37.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
10060
10368
  return true;
10061
10369
  }
10062
10370
 
10063
10371
  // src/commands/refresh.ts
10064
10372
  init_config();
10065
10373
  init_resolve_caliber();
10374
+ function detectSyncedAgents(writtenFiles) {
10375
+ const agents = [];
10376
+ const joined = writtenFiles.join(" ");
10377
+ if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
10378
+ if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
10379
+ if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
10380
+ if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
10381
+ return agents;
10382
+ }
10066
10383
  function log2(quiet, ...args) {
10067
10384
  if (!quiet) console.log(...args);
10068
10385
  }
10069
10386
  function discoverGitRepos(parentDir) {
10070
10387
  const repos = [];
10071
10388
  try {
10072
- const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
10389
+ const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
10073
10390
  for (const entry of entries) {
10074
10391
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
10075
- const childPath = path30.join(parentDir, entry.name);
10076
- if (fs38.existsSync(path30.join(childPath, ".git"))) {
10392
+ const childPath = path31.join(parentDir, entry.name);
10393
+ if (fs39.existsSync(path31.join(childPath, ".git"))) {
10077
10394
  repos.push(childPath);
10078
10395
  }
10079
10396
  }
@@ -10159,9 +10476,9 @@ async function refreshSingleRepo(repoDir, options) {
10159
10476
  const filesToWrite = response.docsUpdated || [];
10160
10477
  const preRefreshContents = /* @__PURE__ */ new Map();
10161
10478
  for (const filePath of filesToWrite) {
10162
- const fullPath = path30.resolve(repoDir, filePath);
10479
+ const fullPath = path31.resolve(repoDir, filePath);
10163
10480
  try {
10164
- preRefreshContents.set(filePath, fs38.readFileSync(fullPath, "utf-8"));
10481
+ preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
10165
10482
  } catch {
10166
10483
  preRefreshContents.set(filePath, null);
10167
10484
  }
@@ -10171,14 +10488,14 @@ async function refreshSingleRepo(repoDir, options) {
10171
10488
  const postScore = computeLocalScore(repoDir, targetAgent);
10172
10489
  if (postScore.score < preScore.score) {
10173
10490
  for (const [filePath, content] of preRefreshContents) {
10174
- const fullPath = path30.resolve(repoDir, filePath);
10491
+ const fullPath = path31.resolve(repoDir, filePath);
10175
10492
  if (content === null) {
10176
10493
  try {
10177
- fs38.unlinkSync(fullPath);
10494
+ fs39.unlinkSync(fullPath);
10178
10495
  } catch {
10179
10496
  }
10180
10497
  } else {
10181
- fs38.writeFileSync(fullPath, content);
10498
+ fs39.writeFileSync(fullPath, content);
10182
10499
  }
10183
10500
  }
10184
10501
  spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
@@ -10190,8 +10507,18 @@ async function refreshSingleRepo(repoDir, options) {
10190
10507
  }
10191
10508
  recordScore(postScore, "refresh");
10192
10509
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
10510
+ const fileChangesMap = new Map(
10511
+ (response.fileChanges || []).map((fc) => [fc.file, fc.description])
10512
+ );
10193
10513
  for (const file of written) {
10194
- log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
10514
+ const desc = fileChangesMap.get(file);
10515
+ const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
10516
+ log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
10517
+ }
10518
+ const agents = detectSyncedAgents(written);
10519
+ if (agents.length > 1) {
10520
+ log2(quiet, chalk19.cyan(`
10521
+ ${agents.length} agent formats in sync (${agents.join(", ")})`));
10195
10522
  }
10196
10523
  if (response.changesSummary) {
10197
10524
  log2(quiet, chalk19.dim(`
@@ -10233,7 +10560,7 @@ async function refreshCommand(options) {
10233
10560
  `));
10234
10561
  const originalDir = process.cwd();
10235
10562
  for (const repo of repos) {
10236
- const repoName = path30.basename(repo);
10563
+ const repoName = path31.basename(repo);
10237
10564
  try {
10238
10565
  process.chdir(repo);
10239
10566
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -10255,150 +10582,6 @@ async function refreshCommand(options) {
10255
10582
  // src/commands/hooks.ts
10256
10583
  import chalk20 from "chalk";
10257
10584
  import fs40 from "fs";
10258
-
10259
- // src/lib/hooks.ts
10260
- init_resolve_caliber();
10261
- import fs39 from "fs";
10262
- import path31 from "path";
10263
- import { execSync as execSync15 } from "child_process";
10264
- var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
10265
- var REFRESH_TAIL = "refresh --quiet";
10266
- var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
10267
- function getHookCommand() {
10268
- return `${resolveCaliber()} ${REFRESH_TAIL}`;
10269
- }
10270
- function readSettings2() {
10271
- if (!fs39.existsSync(SETTINGS_PATH2)) return {};
10272
- try {
10273
- return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
10274
- } catch {
10275
- return {};
10276
- }
10277
- }
10278
- function writeSettings2(settings) {
10279
- const dir = path31.dirname(SETTINGS_PATH2);
10280
- if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
10281
- fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
10282
- }
10283
- function findHookIndex(sessionEnd) {
10284
- return sessionEnd.findIndex(
10285
- (entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
10286
- );
10287
- }
10288
- function isHookInstalled() {
10289
- const settings = readSettings2();
10290
- const sessionEnd = settings.hooks?.SessionEnd;
10291
- if (!Array.isArray(sessionEnd)) return false;
10292
- return findHookIndex(sessionEnd) !== -1;
10293
- }
10294
- function installHook() {
10295
- const settings = readSettings2();
10296
- if (!settings.hooks) settings.hooks = {};
10297
- if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
10298
- if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
10299
- return { installed: false, alreadyInstalled: true };
10300
- }
10301
- settings.hooks.SessionEnd.push({
10302
- matcher: "",
10303
- hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
10304
- });
10305
- writeSettings2(settings);
10306
- return { installed: true, alreadyInstalled: false };
10307
- }
10308
- function removeHook() {
10309
- const settings = readSettings2();
10310
- const sessionEnd = settings.hooks?.SessionEnd;
10311
- if (!Array.isArray(sessionEnd)) {
10312
- return { removed: false, notFound: true };
10313
- }
10314
- const idx = findHookIndex(sessionEnd);
10315
- if (idx === -1) {
10316
- return { removed: false, notFound: true };
10317
- }
10318
- sessionEnd.splice(idx, 1);
10319
- if (sessionEnd.length === 0) {
10320
- delete settings.hooks.SessionEnd;
10321
- }
10322
- if (settings.hooks && Object.keys(settings.hooks).length === 0) {
10323
- delete settings.hooks;
10324
- }
10325
- writeSettings2(settings);
10326
- return { removed: true, notFound: false };
10327
- }
10328
- var PRECOMMIT_START = "# caliber:pre-commit:start";
10329
- var PRECOMMIT_END = "# caliber:pre-commit:end";
10330
- function getPrecommitBlock() {
10331
- const bin = resolveCaliber();
10332
- const npx = isNpxResolution();
10333
- const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
10334
- const invoke = npx ? bin : `"${bin}"`;
10335
- return `${PRECOMMIT_START}
10336
- if ${guard}; then
10337
- echo "\\033[2mcaliber: refreshing docs...\\033[0m"
10338
- ${invoke} refresh 2>/dev/null || true
10339
- ${invoke} learn finalize 2>/dev/null || true
10340
- git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
10341
- fi
10342
- ${PRECOMMIT_END}`;
10343
- }
10344
- function getGitHooksDir() {
10345
- try {
10346
- const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10347
- return path31.join(gitDir, "hooks");
10348
- } catch {
10349
- return null;
10350
- }
10351
- }
10352
- function getPreCommitPath() {
10353
- const hooksDir = getGitHooksDir();
10354
- return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
10355
- }
10356
- function isPreCommitHookInstalled() {
10357
- const hookPath = getPreCommitPath();
10358
- if (!hookPath || !fs39.existsSync(hookPath)) return false;
10359
- const content = fs39.readFileSync(hookPath, "utf-8");
10360
- return content.includes(PRECOMMIT_START);
10361
- }
10362
- function installPreCommitHook() {
10363
- if (isPreCommitHookInstalled()) {
10364
- return { installed: false, alreadyInstalled: true };
10365
- }
10366
- const hookPath = getPreCommitPath();
10367
- if (!hookPath) return { installed: false, alreadyInstalled: false };
10368
- const hooksDir = path31.dirname(hookPath);
10369
- if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
10370
- let content = "";
10371
- if (fs39.existsSync(hookPath)) {
10372
- content = fs39.readFileSync(hookPath, "utf-8");
10373
- if (!content.endsWith("\n")) content += "\n";
10374
- content += "\n" + getPrecommitBlock() + "\n";
10375
- } else {
10376
- content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
10377
- }
10378
- fs39.writeFileSync(hookPath, content);
10379
- fs39.chmodSync(hookPath, 493);
10380
- return { installed: true, alreadyInstalled: false };
10381
- }
10382
- function removePreCommitHook() {
10383
- const hookPath = getPreCommitPath();
10384
- if (!hookPath || !fs39.existsSync(hookPath)) {
10385
- return { removed: false, notFound: true };
10386
- }
10387
- let content = fs39.readFileSync(hookPath, "utf-8");
10388
- if (!content.includes(PRECOMMIT_START)) {
10389
- return { removed: false, notFound: true };
10390
- }
10391
- const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
10392
- content = content.replace(regex, "\n");
10393
- if (content.trim() === "#!/bin/sh" || content.trim() === "") {
10394
- fs39.unlinkSync(hookPath);
10395
- } else {
10396
- fs39.writeFileSync(hookPath, content);
10397
- }
10398
- return { removed: true, notFound: false };
10399
- }
10400
-
10401
- // src/commands/hooks.ts
10402
10585
  var HOOKS = [
10403
10586
  {
10404
10587
  id: "session-end",