@rely-ai/caliber 1.35.1 → 1.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.js +475 -152
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -285,9 +285,9 @@ No API key? No problem. Caliber works with your existing AI tool subscription:
285
285
  | **Claude Code** (your seat) | `caliber config` → Claude Code | Inherited from Claude Code |
286
286
  | **Cursor** (your seat) | `caliber config` → Cursor | Inherited from Cursor |
287
287
  | **Anthropic** | `export ANTHROPIC_API_KEY=sk-ant-...` | `claude-sonnet-4-6` |
288
- | **OpenAI** | `export OPENAI_API_KEY=sk-...` | `gpt-4.1` |
288
+ | **OpenAI** | `export OPENAI_API_KEY=sk-...` | `gpt-5.4-mini` |
289
289
  | **Vertex AI** | `export VERTEX_PROJECT_ID=my-project` | `claude-sonnet-4-6` |
290
- | **Custom endpoint** | `OPENAI_API_KEY` + `OPENAI_BASE_URL` | `gpt-4.1` |
290
+ | **Custom endpoint** | `OPENAI_API_KEY` + `OPENAI_BASE_URL` | `gpt-5.4-mini` |
291
291
 
292
292
  Override the model for any provider: `export CALIBER_MODEL=<model-name>` or use `caliber config`.
293
293
 
package/dist/bin.js CHANGED
@@ -35,9 +35,9 @@ function getMaxPromptTokens() {
35
35
  return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
36
36
  }
37
37
  function loadConfig() {
38
- const envConfig = resolveFromEnv();
39
- if (envConfig) return envConfig;
40
- return readConfigFile();
38
+ const fileConfig = readConfigFile();
39
+ if (fileConfig) return fileConfig;
40
+ return resolveFromEnv();
41
41
  }
42
42
  function resolveFromEnv() {
43
43
  if (process.env.ANTHROPIC_API_KEY) {
@@ -130,7 +130,7 @@ var init_config = __esm({
130
130
  DEFAULT_MODELS = {
131
131
  anthropic: "claude-sonnet-4-6",
132
132
  vertex: "claude-sonnet-4-6",
133
- openai: "gpt-4.1",
133
+ openai: "gpt-5.4-mini",
134
134
  cursor: "sonnet-4.6",
135
135
  "claude-cli": "default"
136
136
  };
@@ -139,8 +139,7 @@ var init_config = __esm({
139
139
  "claude-opus-4-6": 2e5,
140
140
  "claude-haiku-4-5-20251001": 2e5,
141
141
  "claude-sonnet-4-5-20250514": 2e5,
142
- "gpt-4.1": 1e6,
143
- "gpt-4.1-mini": 1e6,
142
+ "gpt-5.4-mini": 1e6,
144
143
  "gpt-4o": 128e3,
145
144
  "gpt-4o-mini": 128e3,
146
145
  "sonnet-4.6": 2e5
@@ -152,7 +151,7 @@ var init_config = __esm({
152
151
  DEFAULT_FAST_MODELS = {
153
152
  anthropic: "claude-haiku-4-5-20251001",
154
153
  vertex: "claude-haiku-4-5-20251001",
155
- openai: "gpt-4.1-mini",
154
+ openai: "gpt-5.4-mini",
156
155
  cursor: "gpt-5.3-codex-fast"
157
156
  };
158
157
  }
@@ -389,7 +388,10 @@ function getCursorSetupRule() {
389
388
  function stripManagedBlocks(content) {
390
389
  let result = content;
391
390
  for (const [start, end] of MANAGED_BLOCK_PAIRS) {
392
- const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
391
+ const regex = new RegExp(
392
+ `\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
393
+ "g"
394
+ );
393
395
  result = result.replace(regex, "\n");
394
396
  }
395
397
  return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
@@ -401,7 +403,7 @@ var init_pre_commit_block = __esm({
401
403
  init_resolve_caliber();
402
404
  BLOCK_START = "<!-- caliber:managed:pre-commit -->";
403
405
  BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
404
- MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
406
+ MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md .agents/ .opencode/";
405
407
  CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
406
408
  LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
407
409
  LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
@@ -1100,10 +1102,21 @@ __export(lock_exports, {
1100
1102
  import fs39 from "fs";
1101
1103
  import path32 from "path";
1102
1104
  import os8 from "os";
1105
+ import crypto5 from "crypto";
1106
+ function buildLockPath() {
1107
+ const cwd = process.cwd();
1108
+ const hash = crypto5.createHash("md5").update(cwd).digest("hex").slice(0, 8);
1109
+ return path32.join(os8.tmpdir(), `.caliber-${hash}.lock`);
1110
+ }
1111
+ function getLockFile() {
1112
+ if (!_lockPath) _lockPath = buildLockPath();
1113
+ return _lockPath;
1114
+ }
1103
1115
  function isCaliberRunning() {
1104
1116
  try {
1105
- if (!fs39.existsSync(LOCK_FILE)) return false;
1106
- const raw = fs39.readFileSync(LOCK_FILE, "utf-8").trim();
1117
+ const lockFile = buildLockPath();
1118
+ if (!fs39.existsSync(lockFile)) return false;
1119
+ const raw = fs39.readFileSync(lockFile, "utf-8").trim();
1107
1120
  const { pid, ts } = JSON.parse(raw);
1108
1121
  if (Date.now() - ts > STALE_MS) return false;
1109
1122
  try {
@@ -1118,22 +1131,23 @@ function isCaliberRunning() {
1118
1131
  }
1119
1132
  function acquireLock() {
1120
1133
  try {
1121
- fs39.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
1134
+ fs39.writeFileSync(getLockFile(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
1122
1135
  } catch {
1123
1136
  }
1124
1137
  }
1125
1138
  function releaseLock() {
1126
1139
  try {
1127
- if (fs39.existsSync(LOCK_FILE)) fs39.unlinkSync(LOCK_FILE);
1140
+ const lockFile = getLockFile();
1141
+ if (fs39.existsSync(lockFile)) fs39.unlinkSync(lockFile);
1128
1142
  } catch {
1129
1143
  }
1130
1144
  }
1131
- var LOCK_FILE, STALE_MS;
1145
+ var STALE_MS, _lockPath;
1132
1146
  var init_lock = __esm({
1133
1147
  "src/lib/lock.ts"() {
1134
1148
  "use strict";
1135
- LOCK_FILE = path32.join(os8.tmpdir(), ".caliber.lock");
1136
1149
  STALE_MS = 10 * 60 * 1e3;
1150
+ _lockPath = null;
1137
1151
  }
1138
1152
  });
1139
1153
 
@@ -1291,9 +1305,30 @@ var LEARNING_ROI_FILE = "roi-stats.json";
1291
1305
  var PERSONAL_LEARNINGS_FILE = path2.join(AUTH_DIR, "personal-learnings.md");
1292
1306
  var LEARNING_FINALIZE_LOG = "finalize.log";
1293
1307
  var LEARNING_LAST_ERROR_FILE = "last-error.json";
1308
+ var REFRESH_LAST_ERROR_FILE = path2.join(CALIBER_DIR, "last-refresh-error.json");
1294
1309
  var MIN_SESSIONS_FOR_COMPARISON = 3;
1295
1310
 
1296
1311
  // src/fingerprint/existing-config.ts
1312
+ function readSkillsFromDir(skillsDir) {
1313
+ if (!fs2.existsSync(skillsDir)) return void 0;
1314
+ try {
1315
+ const skills = fs2.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).reduce((acc, entry) => {
1316
+ const skillPath = path3.join(skillsDir, entry.name, "SKILL.md");
1317
+ try {
1318
+ acc.push({
1319
+ name: entry.name,
1320
+ filename: "SKILL.md",
1321
+ content: fs2.readFileSync(skillPath, "utf-8")
1322
+ });
1323
+ } catch {
1324
+ }
1325
+ return acc;
1326
+ }, []);
1327
+ return skills.length > 0 ? skills : void 0;
1328
+ } catch {
1329
+ return void 0;
1330
+ }
1331
+ }
1297
1332
  function readExistingConfigs(dir) {
1298
1333
  const configs = {};
1299
1334
  const readmeMdPath = path3.join(dir, "README.md");
@@ -1324,7 +1359,10 @@ function readExistingConfigs(dir) {
1324
1359
  const entryPath = path3.join(skillsDir, entry);
1325
1360
  const skillMdPath = path3.join(entryPath, "SKILL.md");
1326
1361
  if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
1327
- skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
1362
+ skills.push({
1363
+ filename: `${entry}/SKILL.md`,
1364
+ content: fs2.readFileSync(skillMdPath, "utf-8")
1365
+ });
1328
1366
  } else if (entry.endsWith(".md")) {
1329
1367
  skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
1330
1368
  }
@@ -1348,20 +1386,26 @@ function readExistingConfigs(dir) {
1348
1386
  } catch {
1349
1387
  }
1350
1388
  }
1351
- const cursorSkillsDir = path3.join(dir, ".cursor", "skills");
1352
- if (fs2.existsSync(cursorSkillsDir)) {
1389
+ configs.cursorSkills = readSkillsFromDir(path3.join(dir, ".cursor", "skills"));
1390
+ const copilotPath = path3.join(dir, ".github", "copilot-instructions.md");
1391
+ if (fs2.existsSync(copilotPath)) {
1392
+ configs.copilotInstructions = fs2.readFileSync(copilotPath, "utf-8");
1393
+ }
1394
+ const copilotInstructionsDir = path3.join(dir, ".github", "instructions");
1395
+ if (fs2.existsSync(copilotInstructionsDir)) {
1353
1396
  try {
1354
- const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
1355
- return fs2.statSync(path3.join(cursorSkillsDir, f)).isDirectory();
1356
- });
1357
- configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path3.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
1358
- name,
1359
- filename: "SKILL.md",
1360
- content: fs2.readFileSync(path3.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
1361
- }));
1397
+ const files = fs2.readdirSync(copilotInstructionsDir).filter((f) => f.endsWith(".instructions.md"));
1398
+ if (files.length > 0) {
1399
+ configs.copilotInstructionFiles = files.map((f) => ({
1400
+ filename: f,
1401
+ content: fs2.readFileSync(path3.join(copilotInstructionsDir, f), "utf-8")
1402
+ }));
1403
+ }
1362
1404
  } catch {
1363
1405
  }
1364
1406
  }
1407
+ configs.codexSkills = readSkillsFromDir(path3.join(dir, ".agents", "skills"));
1408
+ configs.opencodeSkills = readSkillsFromDir(path3.join(dir, ".opencode", "skills"));
1365
1409
  const mcpJsonPath = path3.join(dir, ".mcp.json");
1366
1410
  if (fs2.existsSync(mcpJsonPath)) {
1367
1411
  try {
@@ -2791,13 +2835,7 @@ var KNOWN_MODELS = {
2791
2835
  "claude-opus-4-6@20250605",
2792
2836
  "claude-opus-4-1-20250620"
2793
2837
  ],
2794
- openai: [
2795
- "gpt-4.1",
2796
- "gpt-4.1-mini",
2797
- "gpt-4o",
2798
- "gpt-4o-mini",
2799
- "o3-mini"
2800
- ],
2838
+ openai: ["gpt-5.4-mini", "gpt-4o", "gpt-4o-mini", "o3-mini"],
2801
2839
  cursor: ["auto", "composer-1.5"],
2802
2840
  "claude-cli": []
2803
2841
  };
@@ -2805,7 +2843,8 @@ function isModelNotAvailableError(error) {
2805
2843
  const msg = error.message.toLowerCase();
2806
2844
  const status = error.status;
2807
2845
  if (status === 404 && msg.includes("model")) return true;
2808
- if (msg.includes("model") && (msg.includes("not found") || msg.includes("not_found"))) return true;
2846
+ if (msg.includes("model") && (msg.includes("not found") || msg.includes("not_found")))
2847
+ return true;
2809
2848
  if (msg.includes("model") && msg.includes("not available")) return true;
2810
2849
  if (msg.includes("model") && msg.includes("does not exist")) return true;
2811
2850
  if (msg.includes("publisher model")) return true;
@@ -2831,12 +2870,18 @@ function filterRelevantModels(models, provider) {
2831
2870
  async function handleModelNotAvailable(failedModel, provider, config) {
2832
2871
  if (!process.stdin.isTTY) {
2833
2872
  console.error(
2834
- chalk.red(`Model "${failedModel}" is not available. Run \`${resolveCaliber()} config\` to select a different model.`)
2873
+ chalk.red(
2874
+ `Model "${failedModel}" is not available. Run \`${resolveCaliber()} config\` to select a different model.`
2875
+ )
2835
2876
  );
2836
2877
  return null;
2837
2878
  }
2838
- console.log(chalk.yellow(`
2839
- \u26A0 Model "${failedModel}" is not available on your ${config.provider} deployment.`));
2879
+ console.log(
2880
+ chalk.yellow(
2881
+ `
2882
+ \u26A0 Model "${failedModel}" is not available on your ${config.provider} deployment.`
2883
+ )
2884
+ );
2840
2885
  let models = [];
2841
2886
  if (provider.listModels) {
2842
2887
  try {
@@ -2850,7 +2895,11 @@ async function handleModelNotAvailable(failedModel, provider, config) {
2850
2895
  }
2851
2896
  models = models.filter((m) => m !== failedModel);
2852
2897
  if (models.length === 0) {
2853
- console.log(chalk.red(` No alternative models found. Run \`${resolveCaliber()} config\` to configure manually.`));
2898
+ console.log(
2899
+ chalk.red(
2900
+ ` No alternative models found. Run \`${resolveCaliber()} config\` to configure manually.`
2901
+ )
2902
+ );
2854
2903
  return null;
2855
2904
  }
2856
2905
  console.log("");
@@ -3325,6 +3374,11 @@ Quality constraints (the output is scored deterministically):
3325
3374
  - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
3326
3375
  - Preserve the existing structure (headings, bullet style, formatting)
3327
3376
 
3377
+ Cross-agent sync:
3378
+ - When a code change affects documentation, update ALL provided platform configs together.
3379
+ - A renamed command, moved file, or changed convention must be reflected in every config (CLAUDE.md, AGENTS.md, copilot instructions, skills across all platforms).
3380
+ - Cross-agent consistency is critical \u2014 all agents working on this repo must have the same, accurate context.
3381
+
3328
3382
  Managed content:
3329
3383
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
3330
3384
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
@@ -3338,7 +3392,6 @@ Return a JSON object with this exact shape:
3338
3392
  "readmeMd": "<updated content or null>",
3339
3393
  "cursorrules": "<updated content or null>",
3340
3394
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
3341
- "claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
3342
3395
  "copilotInstructions": "<updated content or null>",
3343
3396
  "copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
3344
3397
  },
@@ -3930,7 +3983,7 @@ var STOP_HOOK_SCRIPT_CONTENT = `#!/bin/sh
3930
3983
  if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3931
3984
  exit 0
3932
3985
  fi
3933
- FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
3986
+ FLAG="/tmp/caliber-nudge-$(echo "$PWD" | (shasum 2>/dev/null || sha1sum 2>/dev/null || md5sum 2>/dev/null || cksum) | cut -c1-8)"
3934
3987
  find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
3935
3988
  if [ -f "$FLAG" ]; then
3936
3989
  exit 0
@@ -3961,11 +4014,13 @@ function installStopHook() {
3961
4014
  }
3962
4015
  settings.hooks.Stop.push({
3963
4016
  matcher: "",
3964
- hooks: [{
3965
- type: "command",
3966
- command: STOP_HOOK_SCRIPT_PATH,
3967
- description: STOP_HOOK_DESCRIPTION
3968
- }]
4017
+ hooks: [
4018
+ {
4019
+ type: "command",
4020
+ command: STOP_HOOK_SCRIPT_PATH,
4021
+ description: STOP_HOOK_DESCRIPTION
4022
+ }
4023
+ ]
3969
4024
  });
3970
4025
  writeSettings(settings);
3971
4026
  return { installed: true, alreadyInstalled: false };
@@ -3997,16 +4052,20 @@ function getPrecommitBlock() {
3997
4052
  const invoke = npx ? bin : `"${bin}"`;
3998
4053
  return `${PRECOMMIT_START}
3999
4054
  if ${guard}; then
4055
+ mkdir -p .caliber
4000
4056
  echo "\\033[2mcaliber: refreshing docs...\\033[0m"
4001
- ${invoke} refresh 2>/dev/null || true
4002
- ${invoke} learn finalize 2>/dev/null || true
4003
- git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
4057
+ ${invoke} refresh --quiet 2>.caliber/refresh-hook.log || true
4058
+ ${invoke} learn finalize 2>>.caliber/refresh-hook.log || true
4059
+ git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md .github/ .agents/ .opencode/ 2>/dev/null | xargs git add 2>/dev/null || true
4004
4060
  fi
4005
4061
  ${PRECOMMIT_END}`;
4006
4062
  }
4007
4063
  function getGitHooksDir() {
4008
4064
  try {
4009
- const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4065
+ const gitDir = execSync8("git rev-parse --git-dir", {
4066
+ encoding: "utf-8",
4067
+ stdio: ["pipe", "pipe", "pipe"]
4068
+ }).trim();
4010
4069
  return path10.join(gitDir, "hooks");
4011
4070
  } catch {
4012
4071
  return null;
@@ -5294,7 +5353,10 @@ import fs14 from "fs";
5294
5353
  import path14 from "path";
5295
5354
  function writeCodexConfig(config) {
5296
5355
  const written = [];
5297
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5356
+ fs14.writeFileSync(
5357
+ "AGENTS.md",
5358
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5359
+ );
5298
5360
  written.push("AGENTS.md");
5299
5361
  if (config.skills?.length) {
5300
5362
  for (const skill of config.skills) {
@@ -5323,7 +5385,10 @@ function writeGithubCopilotConfig(config) {
5323
5385
  const written = [];
5324
5386
  if (config.instructions) {
5325
5387
  fs15.mkdirSync(".github", { recursive: true });
5326
- fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
5388
+ fs15.writeFileSync(
5389
+ path15.join(".github", "copilot-instructions.md"),
5390
+ appendLearningsBlock(appendPreCommitBlock(config.instructions, "copilot"))
5391
+ );
5327
5392
  written.push(".github/copilot-instructions.md");
5328
5393
  }
5329
5394
  if (config.instructionFiles?.length) {
@@ -5345,7 +5410,10 @@ import path17 from "path";
5345
5410
  function writeOpencodeConfig(config, agentsMdAlreadyWritten = false) {
5346
5411
  const written = [];
5347
5412
  if (!agentsMdAlreadyWritten) {
5348
- fs17.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5413
+ fs17.writeFileSync(
5414
+ "AGENTS.md",
5415
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5416
+ );
5349
5417
  written.push("AGENTS.md");
5350
5418
  }
5351
5419
  if (config.skills?.length) {
@@ -6224,14 +6292,17 @@ function checkExistence(dir) {
6224
6292
  const opencodeSkills = countFiles(join3(dir, ".opencode", "skills"), /SKILL\.md$/);
6225
6293
  const skillCount = claudeSkills.length + codexSkills.length + opencodeSkills.length;
6226
6294
  const skillBase = skillCount >= 1 ? POINTS_SKILLS_EXIST : 0;
6227
- const skillBonus = Math.min((skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA, POINTS_SKILLS_BONUS_CAP);
6295
+ const skillBonus = Math.min(
6296
+ (skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA,
6297
+ POINTS_SKILLS_BONUS_CAP
6298
+ );
6228
6299
  const skillPoints = skillCount >= 1 ? skillBase + Math.max(0, skillBonus) : 0;
6229
6300
  const maxSkillPoints = POINTS_SKILLS_EXIST + POINTS_SKILLS_BONUS_CAP;
6230
6301
  checks.push({
6231
6302
  id: "skills_exist",
6232
6303
  name: "Skills configured",
6233
6304
  category: "existence",
6234
- maxPoints: maxSkillPoints,
6305
+ maxPoints: skillCount >= 1 ? maxSkillPoints : 0,
6235
6306
  earnedPoints: Math.min(skillPoints, maxSkillPoints),
6236
6307
  passed: skillCount >= 1,
6237
6308
  detail: skillCount === 0 ? "No skills found" : `${skillCount} skill${skillCount === 1 ? "" : "s"} found`,
@@ -6264,7 +6335,7 @@ function checkExistence(dir) {
6264
6335
  id: "mcp_servers",
6265
6336
  name: "MCP servers configured",
6266
6337
  category: "existence",
6267
- maxPoints: POINTS_MCP_SERVERS,
6338
+ maxPoints: mcp.count >= 1 ? POINTS_MCP_SERVERS : 0,
6268
6339
  earnedPoints: mcp.count >= 1 ? POINTS_MCP_SERVERS : 0,
6269
6340
  passed: mcp.count >= 1,
6270
6341
  detail: mcp.count > 0 ? `${mcp.count} server${mcp.count === 1 ? "" : "s"} in ${mcp.sources.join(", ")}` : "No MCP servers configured",
@@ -6799,7 +6870,11 @@ init_resolve_caliber();
6799
6870
  init_pre_commit_block();
6800
6871
  function hasPreCommitHook(dir) {
6801
6872
  try {
6802
- const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6873
+ const gitDir = execSync12("git rev-parse --git-dir", {
6874
+ cwd: dir,
6875
+ encoding: "utf-8",
6876
+ stdio: ["pipe", "pipe", "pipe"]
6877
+ }).trim();
6803
6878
  const hookPath = join7(gitDir, "hooks", "pre-commit");
6804
6879
  const content = readFileOrNull(hookPath);
6805
6880
  return content ? content.includes("caliber") : false;
@@ -6890,9 +6965,9 @@ function checkBonus(dir) {
6890
6965
  id: "open_skills_format",
6891
6966
  name: "Skills use OpenSkills format",
6892
6967
  category: "bonus",
6893
- maxPoints: POINTS_OPEN_SKILLS_FORMAT,
6968
+ maxPoints: totalSkillFiles > 0 ? POINTS_OPEN_SKILLS_FORMAT : 0,
6894
6969
  earnedPoints: allOpenSkills ? POINTS_OPEN_SKILLS_FORMAT : 0,
6895
- passed: allOpenSkills,
6970
+ passed: allOpenSkills || totalSkillFiles === 0,
6896
6971
  detail: totalSkillFiles === 0 ? "No skills to check" : allOpenSkills ? `All ${totalSkillFiles} skill${totalSkillFiles === 1 ? "" : "s"} use SKILL.md with frontmatter` : `${openSkillsCount}/${totalSkillFiles} use OpenSkills format`,
6897
6972
  suggestion: totalSkillFiles > 0 && !allOpenSkills ? "Migrate skills to .claude/skills/{name}/SKILL.md with YAML frontmatter" : void 0,
6898
6973
  fix: totalSkillFiles > 0 && !allOpenSkills ? {
@@ -9686,27 +9761,47 @@ async function initCommand(options) {
9686
9761
  const bin = resolveCaliber();
9687
9762
  const firstRun = isFirstRun(process.cwd());
9688
9763
  if (firstRun) {
9689
- console.log(brand.bold(`
9764
+ console.log(
9765
+ brand.bold(`
9690
9766
  \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
9691
9767
  \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
9692
9768
  \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
9693
9769
  \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
9694
9770
  \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
9695
9771
  \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
9696
- `));
9772
+ `)
9773
+ );
9697
9774
  console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
9698
9775
  console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
9699
- console.log(title.bold(" How it works:\n"));
9700
- console.log(chalk14.dim(" 1. Connect Auto-detect your LLM provider and agents"));
9701
- console.log(chalk14.dim(" 2. Build Install sync, scan your project, generate configs"));
9702
- console.log(chalk14.dim(" 3. Done Review score and start syncing\n"));
9776
+ console.log(title.bold(" What this does:\n"));
9777
+ console.log(
9778
+ chalk14.dim(" Caliber reads your project structure (file tree, package.json, etc.)")
9779
+ );
9780
+ console.log(chalk14.dim(" and generates agent config files (CLAUDE.md, .cursor/rules/, etc.)."));
9781
+ console.log(chalk14.dim(" You review all changes before anything is written to disk.\n"));
9782
+ console.log(title.bold(" Steps:\n"));
9783
+ console.log(
9784
+ chalk14.dim(" 1. Connect Pick your LLM provider (or use your existing subscription)")
9785
+ );
9786
+ console.log(
9787
+ chalk14.dim(" 2. Build Scan project, generate configs, install pre-commit sync")
9788
+ );
9789
+ console.log(
9790
+ chalk14.dim(" 3. Review See exactly what changed \u2014 accept, refine, or decline\n")
9791
+ );
9703
9792
  } else {
9704
9793
  console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 setting up continuous sync\n"));
9705
9794
  }
9706
9795
  const platforms = detectPlatforms();
9707
9796
  if (!platforms.claude && !platforms.cursor && !platforms.codex && !platforms.opencode) {
9708
- console.log(chalk14.yellow(" \u26A0 No supported AI platforms detected (Claude, Cursor, Codex, OpenCode)."));
9709
- console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
9797
+ console.log(
9798
+ chalk14.yellow(" \u26A0 No supported AI platforms detected (Claude, Cursor, Codex, OpenCode).")
9799
+ );
9800
+ console.log(
9801
+ chalk14.yellow(
9802
+ " Caliber will still generate config files, but they won't be auto-installed.\n"
9803
+ )
9804
+ );
9710
9805
  }
9711
9806
  const report = options.debugReport ? new DebugReport() : null;
9712
9807
  console.log(title.bold(" Step 1/3 \u2014 Connect\n"));
@@ -9761,9 +9856,12 @@ async function initCommand(options) {
9761
9856
  console.log(chalk14.dim(modelLine + "\n"));
9762
9857
  if (report) {
9763
9858
  report.markStep("Provider connection");
9764
- report.addSection("LLM Provider", `- **Provider**: ${config.provider}
9859
+ report.addSection(
9860
+ "LLM Provider",
9861
+ `- **Provider**: ${config.provider}
9765
9862
  - **Model**: ${displayModel}
9766
- - **Fast model**: ${fastModel || "none"}`);
9863
+ - **Fast model**: ${fastModel || "none"}`
9864
+ );
9767
9865
  }
9768
9866
  await validateModel({ fast: true });
9769
9867
  let targetAgent;
@@ -9799,9 +9897,12 @@ async function initCommand(options) {
9799
9897
  console.log(` ${chalk14.green("\u2713")} Onboarding hook \u2014 nudges new team members to set up`);
9800
9898
  const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
9801
9899
  for (const agent of targetAgent) {
9802
- if (agent === "claude" && !fs34.existsSync(".claude")) fs34.mkdirSync(".claude", { recursive: true });
9803
- if (agent === "cursor" && !fs34.existsSync(".cursor")) fs34.mkdirSync(".cursor", { recursive: true });
9804
- if (agent === "codex" && !fs34.existsSync(".agents")) fs34.mkdirSync(".agents", { recursive: true });
9900
+ if (agent === "claude" && !fs34.existsSync(".claude"))
9901
+ fs34.mkdirSync(".claude", { recursive: true });
9902
+ if (agent === "cursor" && !fs34.existsSync(".cursor"))
9903
+ fs34.mkdirSync(".cursor", { recursive: true });
9904
+ if (agent === "codex" && !fs34.existsSync(".agents"))
9905
+ fs34.mkdirSync(".agents", { recursive: true });
9805
9906
  }
9806
9907
  const skillsWritten = ensureBuiltinSkills2();
9807
9908
  if (skillsWritten.length > 0) {
@@ -9819,11 +9920,16 @@ async function initCommand(options) {
9819
9920
  log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
9820
9921
  if (report) {
9821
9922
  report.markStep("Baseline scoring");
9822
- report.addSection("Scoring: Baseline", `**Score**: ${baselineScore.score}/100
9923
+ report.addSection(
9924
+ "Scoring: Baseline",
9925
+ `**Score**: ${baselineScore.score}/100
9823
9926
 
9824
9927
  | Check | Passed | Points | Max |
9825
9928
  |-------|--------|--------|-----|
9826
- ` + baselineScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
9929
+ ` + baselineScore.checks.map(
9930
+ (c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
9931
+ ).join("\n")
9932
+ );
9827
9933
  report.addSection("Generation: Target Agents", targetAgent.join(", "));
9828
9934
  }
9829
9935
  const hasExistingConfig = !!(baselineScore.checks.some((c) => c.id === "claude_md_exists" && c.passed) || baselineScore.checks.some((c) => c.id === "cursorrules_exists" && c.passed));
@@ -9842,8 +9948,19 @@ async function initCommand(options) {
9842
9948
  if (hasExistingConfig && baselineScore.score === 100 && !options.force) {
9843
9949
  skipGeneration = true;
9844
9950
  } else if (hasExistingConfig && !options.force && !options.autoApprove) {
9845
- console.log(chalk14.dim(` Config score: ${baselineScore.score}/100 \u2014 Caliber can improve this.
9951
+ const topGains = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0).sort((a, b) => b.maxPoints - b.earnedPoints - (a.maxPoints - a.earnedPoints)).slice(0, 3);
9952
+ console.log(chalk14.dim(` Config score: ${baselineScore.score}/100
9846
9953
  `));
9954
+ if (topGains.length > 0) {
9955
+ console.log(chalk14.dim(" Top improvements Caliber can make:"));
9956
+ for (const c of topGains) {
9957
+ const pts = c.maxPoints - c.earnedPoints;
9958
+ console.log(
9959
+ chalk14.dim(` +${pts} pts`) + chalk14.white(` ${c.name}`) + (c.suggestion ? chalk14.gray(` \u2014 ${c.suggestion}`) : "")
9960
+ );
9961
+ }
9962
+ console.log("");
9963
+ }
9847
9964
  const improveAnswer = await confirm2({ message: "Improve your existing configs?" });
9848
9965
  skipGeneration = !improveAnswer;
9849
9966
  }
@@ -9873,7 +9990,12 @@ async function initCommand(options) {
9873
9990
  if (targetAgent.includes("cursor")) {
9874
9991
  const rulesDir = path28.join(".cursor", "rules");
9875
9992
  if (!fs34.existsSync(rulesDir)) fs34.mkdirSync(rulesDir, { recursive: true });
9876
- for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
9993
+ for (const rule of [
9994
+ getCursorPreCommitRule2(),
9995
+ getCursorLearningsRule2(),
9996
+ getCursorSyncRule2(),
9997
+ getCursorSetupRule2()
9998
+ ]) {
9877
9999
  fs34.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
9878
10000
  }
9879
10001
  console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
@@ -9905,7 +10027,9 @@ async function initCommand(options) {
9905
10027
  trackInitCompleted("sync-only", baselineScore.score);
9906
10028
  console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
9907
10029
  console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
9908
- console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
10030
+ console.log(
10031
+ chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n")
10032
+ );
9909
10033
  return;
9910
10034
  }
9911
10035
  const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
@@ -9923,8 +10047,14 @@ async function initCommand(options) {
9923
10047
  const TASK_STACK = display.add("Detecting project stack", { pipelineLabel: "Scan" });
9924
10048
  const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
9925
10049
  const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
9926
- const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
9927
- const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
10050
+ const TASK_SKILLS_SEARCH = display.add("Searching community skills", {
10051
+ depth: 1,
10052
+ pipelineLabel: "Search",
10053
+ pipelineRow: 1
10054
+ });
10055
+ const TASK_SCORE_REFINE = display.add("Validating & refining config", {
10056
+ pipelineLabel: "Validate"
10057
+ });
9928
10058
  display.start();
9929
10059
  display.enableWaitingContent();
9930
10060
  try {
@@ -9934,21 +10064,39 @@ async function initCommand(options) {
9934
10064
  const stackSummary = stackParts.join(", ") || "no languages";
9935
10065
  const largeRepoNote = fingerprint.fileTree.length > 5e3 ? ` (${fingerprint.fileTree.length.toLocaleString()} files, smart sampling active)` : "";
9936
10066
  display.update(TASK_STACK, "done", stackSummary + largeRepoNote);
9937
- trackInitProjectDiscovered(fingerprint.languages.length, fingerprint.frameworks.length, fingerprint.fileTree.length);
9938
- log(options.verbose, `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`);
10067
+ trackInitProjectDiscovered(
10068
+ fingerprint.languages.length,
10069
+ fingerprint.frameworks.length,
10070
+ fingerprint.fileTree.length
10071
+ );
10072
+ log(
10073
+ options.verbose,
10074
+ `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`
10075
+ );
9939
10076
  const cliSources = options.source || [];
9940
10077
  const workspaces = getDetectedWorkspaces(process.cwd());
9941
10078
  const sources2 = resolveAllSources(process.cwd(), cliSources, workspaces);
9942
10079
  if (sources2.length > 0) {
9943
10080
  fingerprint.sources = sources2;
9944
- log(options.verbose, `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`);
10081
+ log(
10082
+ options.verbose,
10083
+ `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`
10084
+ );
9945
10085
  }
9946
10086
  if (report) {
9947
- report.addJson("Fingerprint: Git", { remote: fingerprint.gitRemoteUrl, packageName: fingerprint.packageName });
10087
+ report.addJson("Fingerprint: Git", {
10088
+ remote: fingerprint.gitRemoteUrl,
10089
+ packageName: fingerprint.packageName
10090
+ });
9948
10091
  report.addCodeBlock("Fingerprint: File Tree", fingerprint.fileTree.join("\n"));
9949
- report.addJson("Fingerprint: Detected Stack", { languages: fingerprint.languages, frameworks: fingerprint.frameworks, tools: fingerprint.tools });
10092
+ report.addJson("Fingerprint: Detected Stack", {
10093
+ languages: fingerprint.languages,
10094
+ frameworks: fingerprint.frameworks,
10095
+ tools: fingerprint.tools
10096
+ });
9950
10097
  report.addJson("Fingerprint: Existing Configs", fingerprint.existingConfigs);
9951
- if (fingerprint.codeAnalysis) report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
10098
+ if (fingerprint.codeAnalysis)
10099
+ report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
9952
10100
  }
9953
10101
  const isEmpty = fingerprint.fileTree.length < 3;
9954
10102
  if (isEmpty) {
@@ -9979,13 +10127,26 @@ async function initCommand(options) {
9979
10127
  let passingChecks;
9980
10128
  let currentScore;
9981
10129
  if (hasExistingConfig && localBaseline.score >= 95 && !options.force) {
9982
- const currentLlmFixable = localBaseline.checks.filter((c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id));
9983
- failingChecks = currentLlmFixable.map((c) => ({ name: c.name, suggestion: c.suggestion, fix: c.fix }));
10130
+ const currentLlmFixable = localBaseline.checks.filter(
10131
+ (c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id)
10132
+ );
10133
+ failingChecks = currentLlmFixable.map((c) => ({
10134
+ name: c.name,
10135
+ suggestion: c.suggestion,
10136
+ fix: c.fix
10137
+ }));
9984
10138
  passingChecks = localBaseline.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
9985
10139
  currentScore = localBaseline.score;
9986
10140
  }
9987
10141
  if (report) {
9988
- const fullPrompt = buildGeneratePrompt(fingerprint, targetAgent, fingerprint.description, failingChecks, currentScore, passingChecks);
10142
+ const fullPrompt = buildGeneratePrompt(
10143
+ fingerprint,
10144
+ targetAgent,
10145
+ fingerprint.description,
10146
+ failingChecks,
10147
+ currentScore,
10148
+ passingChecks
10149
+ );
9989
10150
  report.addCodeBlock("Generation: Full LLM Prompt", fullPrompt);
9990
10151
  }
9991
10152
  const result = await generateSetup(
@@ -10105,7 +10266,10 @@ async function initCommand(options) {
10105
10266
  if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
10106
10267
  report.addJson("Generation: Parsed Config", generatedSetup);
10107
10268
  }
10108
- log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
10269
+ log(
10270
+ options.verbose,
10271
+ `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`
10272
+ );
10109
10273
  console.log(title.bold(" Step 3/3 \u2014 Done\n"));
10110
10274
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
10111
10275
  const staged = stageFiles(setupFiles, process.cwd());
@@ -10117,10 +10281,18 @@ async function initCommand(options) {
10117
10281
  }
10118
10282
  console.log("");
10119
10283
  }
10120
- console.log(chalk14.dim(` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`));
10284
+ console.log(
10285
+ chalk14.dim(
10286
+ ` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`
10287
+ )
10288
+ );
10121
10289
  if (skillSearchResult.results.length > 0) {
10122
- console.log(chalk14.dim(` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
10123
- `));
10290
+ console.log(
10291
+ chalk14.dim(
10292
+ ` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
10293
+ `
10294
+ )
10295
+ );
10124
10296
  } else {
10125
10297
  console.log("");
10126
10298
  }
@@ -10156,8 +10328,12 @@ async function initCommand(options) {
10156
10328
  }
10157
10329
  const updatedFiles = collectSetupFiles(generatedSetup, targetAgent);
10158
10330
  const restaged = stageFiles(updatedFiles, process.cwd());
10159
- console.log(chalk14.dim(` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
10160
- `));
10331
+ console.log(
10332
+ chalk14.dim(
10333
+ ` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
10334
+ `
10335
+ )
10336
+ );
10161
10337
  printSetupSummary(generatedSetup);
10162
10338
  const { openReview: openRev } = await Promise.resolve().then(() => (init_review(), review_exports));
10163
10339
  await openRev("terminal", restaged.stagedFiles);
@@ -10183,7 +10359,8 @@ async function initCommand(options) {
10183
10359
  const agentRefs = [];
10184
10360
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
10185
10361
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
10186
- if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
10362
+ if (agentRefs.length === 0)
10363
+ agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
10187
10364
  const stubContent = `# AGENTS.md
10188
10365
 
10189
10366
  This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
@@ -10230,24 +10407,39 @@ ${agentRefs.join(" ")}
10230
10407
  if (afterScore.score < baselineScore.score) {
10231
10408
  trackInitScoreRegression(baselineScore.score, afterScore.score);
10232
10409
  console.log("");
10233
- console.log(chalk14.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
10410
+ console.log(
10411
+ chalk14.yellow(
10412
+ ` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`
10413
+ )
10414
+ );
10234
10415
  try {
10235
10416
  const { restored, removed } = undoSetup();
10236
10417
  if (restored.length > 0 || removed.length > 0) {
10237
- console.log(chalk14.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
10418
+ console.log(
10419
+ chalk14.dim(
10420
+ ` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`
10421
+ )
10422
+ );
10238
10423
  }
10239
10424
  } catch {
10240
10425
  }
10241
- console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n"));
10426
+ console.log(
10427
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n")
10428
+ );
10242
10429
  return;
10243
10430
  }
10244
10431
  if (report) {
10245
10432
  report.markStep("Post-write scoring");
10246
- report.addSection("Scoring: Post-Write", `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
10433
+ report.addSection(
10434
+ "Scoring: Post-Write",
10435
+ `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
10247
10436
 
10248
10437
  | Check | Passed | Points | Max |
10249
10438
  |-------|--------|--------|-----|
10250
- ` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
10439
+ ` + afterScore.checks.map(
10440
+ (c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
10441
+ ).join("\n")
10442
+ );
10251
10443
  }
10252
10444
  recordScore(afterScore, "init");
10253
10445
  trackInitCompleted("full-generation", afterScore.score);
@@ -10255,7 +10447,10 @@ ${agentRefs.join(" ")}
10255
10447
  if (options.verbose) {
10256
10448
  log(options.verbose, `Final score: ${afterScore.score}/100`);
10257
10449
  for (const c of afterScore.checks.filter((ch) => !ch.passed)) {
10258
- log(options.verbose, ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`);
10450
+ log(
10451
+ options.verbose,
10452
+ ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
10453
+ );
10259
10454
  }
10260
10455
  }
10261
10456
  let communitySkillsInstalled = 0;
@@ -10272,14 +10467,20 @@ ${agentRefs.join(" ")}
10272
10467
  const done = chalk14.green("\u2713");
10273
10468
  console.log(chalk14.bold.green("\n Caliber is set up!\n"));
10274
10469
  console.log(chalk14.bold(" What's configured:\n"));
10275
- console.log(` ${done} Continuous sync ${chalk14.dim("pre-commit hook keeps all agent configs in sync")}`);
10470
+ console.log(
10471
+ ` ${done} Continuous sync ${chalk14.dim("pre-commit hook keeps all agent configs in sync")}`
10472
+ );
10276
10473
  console.log(` ${done} Config generated ${chalk14.dim(`score: ${afterScore.score}/100`)}`);
10277
- console.log(` ${done} Agent skills ${chalk14.dim("/setup-caliber for new team members")}`);
10474
+ console.log(
10475
+ ` ${done} Agent skills ${chalk14.dim("/setup-caliber for new team members")}`
10476
+ );
10278
10477
  if (hasLearnableAgent) {
10279
10478
  console.log(` ${done} Session learning ${chalk14.dim("learns from your corrections")}`);
10280
10479
  }
10281
10480
  if (communitySkillsInstalled > 0) {
10282
- console.log(` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} installed for your stack`)}`);
10481
+ console.log(
10482
+ ` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} installed for your stack`)}`
10483
+ );
10283
10484
  }
10284
10485
  console.log(chalk14.bold("\n What happens next:\n"));
10285
10486
  console.log(chalk14.dim(" Every commit syncs your agent configs automatically."));
@@ -10296,8 +10497,10 @@ ${agentRefs.join(" ")}
10296
10497
  report.markStep("Finished");
10297
10498
  const reportPath = path28.join(process.cwd(), ".caliber", "debug-report.md");
10298
10499
  report.write(reportPath);
10299
- console.log(chalk14.dim(` Debug report written to ${path28.relative(process.cwd(), reportPath)}
10300
- `));
10500
+ console.log(
10501
+ chalk14.dim(` Debug report written to ${path28.relative(process.cwd(), reportPath)}
10502
+ `)
10503
+ );
10301
10504
  }
10302
10505
  }
10303
10506
 
@@ -10633,18 +10836,27 @@ async function scoreCommand(options) {
10633
10836
  const separator = chalk18.gray(" " + "\u2500".repeat(53));
10634
10837
  console.log(separator);
10635
10838
  const bin = resolveCaliber();
10636
- if (result.score < 40) {
10839
+ const failing = result.checks.filter((c) => !c.passed && c.maxPoints > 0).sort((a, b) => b.maxPoints - b.earnedPoints - (a.maxPoints - a.earnedPoints));
10840
+ if (result.score < 70 && failing.length > 0) {
10841
+ const topFix = failing[0];
10842
+ const pts = topFix.maxPoints - topFix.earnedPoints;
10637
10843
  console.log(
10638
- chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to generate a complete, optimized config.")
10844
+ chalk18.gray(" Biggest gain: ") + chalk18.yellow(`+${pts} pts`) + chalk18.gray(` from "${topFix.name}"`) + (topFix.suggestion ? chalk18.gray(` \u2014 ${topFix.suggestion}`) : "")
10639
10845
  );
10640
- } else if (result.score < 70) {
10641
10846
  console.log(
10642
- chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to improve your config.")
10847
+ chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to auto-fix these.")
10643
10848
  );
10644
- } else {
10849
+ } else if (failing.length > 0) {
10645
10850
  console.log(
10646
- chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} regenerate`) + chalk18.gray(" to rebuild from scratch.")
10851
+ chalk18.green(" Looking good!") + chalk18.gray(
10852
+ ` ${failing.length} check${failing.length === 1 ? "" : "s"} can still be improved.`
10853
+ )
10647
10854
  );
10855
+ console.log(
10856
+ chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to improve, or ") + chalk18.hex("#83D1EB")(`${bin} regenerate`) + chalk18.gray(" to rebuild from scratch.")
10857
+ );
10858
+ } else {
10859
+ console.log(chalk18.green(" Perfect score! Your agent configs are fully optimized."));
10648
10860
  }
10649
10861
  console.log("");
10650
10862
  }
@@ -10660,12 +10872,23 @@ import { execSync as execSync15 } from "child_process";
10660
10872
  var MAX_DIFF_BYTES = 1e5;
10661
10873
  var DOC_PATTERNS = [
10662
10874
  "CLAUDE.md",
10875
+ "AGENTS.md",
10663
10876
  "README.md",
10664
10877
  ".cursorrules",
10665
10878
  ".cursor/rules/",
10879
+ ".cursor/skills/",
10666
10880
  ".claude/skills/",
10881
+ ".agents/skills/",
10882
+ ".opencode/skills/",
10883
+ ".github/copilot-instructions.md",
10884
+ ".github/instructions/",
10667
10885
  "CALIBER_LEARNINGS.md"
10668
10886
  ];
10887
+ function truncateAtLineEnd(text, maxBytes) {
10888
+ if (text.length <= maxBytes) return text;
10889
+ const lastNewline = text.lastIndexOf("\n", maxBytes);
10890
+ return lastNewline === -1 ? text.slice(0, maxBytes) : text.slice(0, lastNewline);
10891
+ }
10669
10892
  function excludeArgs() {
10670
10893
  return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
10671
10894
  }
@@ -10714,9 +10937,9 @@ function collectDiff(lastSha) {
10714
10937
  const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
10715
10938
  if (totalSize > MAX_DIFF_BYTES) {
10716
10939
  const ratio = MAX_DIFF_BYTES / totalSize;
10717
- committedDiff = committedDiff.slice(0, Math.floor(committedDiff.length * ratio));
10718
- stagedDiff = stagedDiff.slice(0, Math.floor(stagedDiff.length * ratio));
10719
- unstagedDiff = unstagedDiff.slice(0, Math.floor(unstagedDiff.length * ratio));
10940
+ committedDiff = truncateAtLineEnd(committedDiff, Math.floor(committedDiff.length * ratio));
10941
+ stagedDiff = truncateAtLineEnd(stagedDiff, Math.floor(stagedDiff.length * ratio));
10942
+ unstagedDiff = truncateAtLineEnd(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
10720
10943
  }
10721
10944
  const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
10722
10945
  const parts = [];
@@ -10735,11 +10958,11 @@ import path30 from "path";
10735
10958
  function writeRefreshDocs(docs) {
10736
10959
  const written = [];
10737
10960
  if (docs.agentsMd) {
10738
- fs37.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(docs.agentsMd)));
10961
+ fs37.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10739
10962
  written.push("AGENTS.md");
10740
10963
  }
10741
10964
  if (docs.claudeMd) {
10742
- fs37.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10965
+ fs37.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd));
10743
10966
  written.push("CLAUDE.md");
10744
10967
  }
10745
10968
  if (docs.readmeMd) {
@@ -10758,17 +10981,12 @@ function writeRefreshDocs(docs) {
10758
10981
  written.push(`.cursor/rules/${rule.filename}`);
10759
10982
  }
10760
10983
  }
10761
- if (docs.claudeSkills) {
10762
- const skillsDir = path30.join(".claude", "skills");
10763
- if (!fs37.existsSync(skillsDir)) fs37.mkdirSync(skillsDir, { recursive: true });
10764
- for (const skill of docs.claudeSkills) {
10765
- fs37.writeFileSync(path30.join(skillsDir, skill.filename), skill.content);
10766
- written.push(`.claude/skills/${skill.filename}`);
10767
- }
10768
- }
10769
10984
  if (docs.copilotInstructions) {
10770
10985
  fs37.mkdirSync(".github", { recursive: true });
10771
- fs37.writeFileSync(path30.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10986
+ fs37.writeFileSync(
10987
+ path30.join(".github", "copilot-instructions.md"),
10988
+ appendManagedBlocks(docs.copilotInstructions, "copilot")
10989
+ );
10772
10990
  written.push(".github/copilot-instructions.md");
10773
10991
  }
10774
10992
  if (docs.copilotInstructionFiles) {
@@ -10784,6 +11002,7 @@ function writeRefreshDocs(docs) {
10784
11002
 
10785
11003
  // src/ai/refresh.ts
10786
11004
  init_config();
11005
+ init_pre_commit_block();
10787
11006
  async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
10788
11007
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
10789
11008
  const fastModel = getFastModel();
@@ -10799,13 +11018,17 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
10799
11018
  const parts = [];
10800
11019
  parts.push("Update documentation based on the following code changes.\n");
10801
11020
  if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
10802
- if (projectContext.languages?.length) parts.push(`Languages: ${projectContext.languages.join(", ")}`);
10803
- if (projectContext.frameworks?.length) parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
11021
+ if (projectContext.languages?.length)
11022
+ parts.push(`Languages: ${projectContext.languages.join(", ")}`);
11023
+ if (projectContext.frameworks?.length)
11024
+ parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
10804
11025
  if (projectContext.fileTree?.length) {
10805
11026
  const tree = projectContext.fileTree.slice(0, 200);
10806
- parts.push(`
11027
+ parts.push(
11028
+ `
10807
11029
  File tree (${tree.length}/${projectContext.fileTree.length} \u2014 only reference paths from this list):
10808
- ${tree.join("\n")}`);
11030
+ ${tree.join("\n")}`
11031
+ );
10809
11032
  }
10810
11033
  parts.push(`
10811
11034
  Changed files: ${diff.changedFiles.join(", ")}`);
@@ -10825,11 +11048,11 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10825
11048
  parts.push("\n--- Current Documentation ---");
10826
11049
  if (existingDocs.agentsMd) {
10827
11050
  parts.push("\n[AGENTS.md]");
10828
- parts.push(existingDocs.agentsMd);
11051
+ parts.push(stripManagedBlocks(existingDocs.agentsMd));
10829
11052
  }
10830
11053
  if (existingDocs.claudeMd) {
10831
11054
  parts.push("\n[CLAUDE.md]");
10832
- parts.push(existingDocs.claudeMd);
11055
+ parts.push(stripManagedBlocks(existingDocs.claudeMd));
10833
11056
  }
10834
11057
  if (existingDocs.readmeMd) {
10835
11058
  parts.push("\n[README.md]");
@@ -10839,20 +11062,25 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10839
11062
  parts.push("\n[.cursorrules]");
10840
11063
  parts.push(existingDocs.cursorrules);
10841
11064
  }
10842
- if (existingDocs.claudeSkills?.length) {
10843
- for (const skill of existingDocs.claudeSkills) {
10844
- parts.push(`
10845
- [.claude/skills/${skill.filename}]`);
10846
- parts.push(skill.content);
10847
- }
10848
- }
10849
11065
  if (existingDocs.cursorRules?.length) {
10850
11066
  for (const rule of existingDocs.cursorRules) {
11067
+ if (rule.filename.startsWith("caliber-")) continue;
10851
11068
  parts.push(`
10852
11069
  [.cursor/rules/${rule.filename}]`);
10853
11070
  parts.push(rule.content);
10854
11071
  }
10855
11072
  }
11073
+ if (existingDocs.copilotInstructions) {
11074
+ parts.push("\n[.github/copilot-instructions.md]");
11075
+ parts.push(stripManagedBlocks(existingDocs.copilotInstructions));
11076
+ }
11077
+ if (existingDocs.copilotInstructionFiles?.length) {
11078
+ for (const file of existingDocs.copilotInstructionFiles) {
11079
+ parts.push(`
11080
+ [.github/instructions/${file.filename}]`);
11081
+ parts.push(file.content);
11082
+ }
11083
+ }
10856
11084
  if (learnedSection) {
10857
11085
  parts.push("\n--- Learned Patterns (from session learning) ---");
10858
11086
  parts.push("Consider these accumulated learnings when deciding what to update:");
@@ -11062,6 +11290,40 @@ function migrateInlineLearnings() {
11062
11290
  init_config();
11063
11291
  init_resolve_caliber();
11064
11292
  init_builtin_skills();
11293
+ function writeRefreshError(error) {
11294
+ try {
11295
+ if (!fs40.existsSync(CALIBER_DIR)) fs40.mkdirSync(CALIBER_DIR, { recursive: true });
11296
+ fs40.writeFileSync(
11297
+ REFRESH_LAST_ERROR_FILE,
11298
+ JSON.stringify(
11299
+ {
11300
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11301
+ error: error instanceof Error ? error.message : String(error),
11302
+ stack: error instanceof Error ? error.stack : void 0,
11303
+ cwd: process.cwd(),
11304
+ nodeVersion: process.version
11305
+ },
11306
+ null,
11307
+ 2
11308
+ )
11309
+ );
11310
+ } catch {
11311
+ }
11312
+ }
11313
+ function readRefreshError() {
11314
+ try {
11315
+ if (!fs40.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11316
+ return JSON.parse(fs40.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
11317
+ } catch {
11318
+ return null;
11319
+ }
11320
+ }
11321
+ function clearRefreshError() {
11322
+ try {
11323
+ if (fs40.existsSync(REFRESH_LAST_ERROR_FILE)) fs40.unlinkSync(REFRESH_LAST_ERROR_FILE);
11324
+ } catch {
11325
+ }
11326
+ }
11065
11327
  function log2(quiet, ...args) {
11066
11328
  if (!quiet) console.log(...args);
11067
11329
  }
@@ -11080,21 +11342,41 @@ function discoverGitRepos(parentDir) {
11080
11342
  }
11081
11343
  return repos.sort();
11082
11344
  }
11345
+ function collectFilesToWrite(updatedDocs) {
11346
+ const files = [];
11347
+ if (updatedDocs.agentsMd) files.push("AGENTS.md");
11348
+ if (updatedDocs.claudeMd) files.push("CLAUDE.md");
11349
+ if (updatedDocs.readmeMd) files.push("README.md");
11350
+ if (updatedDocs.cursorrules) files.push(".cursorrules");
11351
+ if (Array.isArray(updatedDocs.cursorRules)) {
11352
+ for (const r of updatedDocs.cursorRules)
11353
+ files.push(`.cursor/rules/${r.filename}`);
11354
+ }
11355
+ if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
11356
+ if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
11357
+ for (const f of updatedDocs.copilotInstructionFiles)
11358
+ files.push(`.github/instructions/${f.filename}`);
11359
+ }
11360
+ return files;
11361
+ }
11083
11362
  var REFRESH_COOLDOWN_MS = 3e4;
11084
11363
  async function refreshSingleRepo(repoDir, options) {
11085
11364
  const quiet = !!options.quiet;
11086
11365
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11087
11366
  const state = readState();
11088
11367
  const lastSha = state?.lastRefreshSha ?? null;
11089
- if (state?.lastRefreshTimestamp) {
11368
+ const currentSha = getCurrentHeadSha();
11369
+ if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11090
11370
  const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11091
11371
  if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11092
- log2(quiet, chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`));
11372
+ log2(
11373
+ quiet,
11374
+ chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11375
+ );
11093
11376
  return;
11094
11377
  }
11095
11378
  }
11096
11379
  const diff = collectDiff(lastSha);
11097
- const currentSha = getCurrentHeadSha();
11098
11380
  if (!diff.hasChanges) {
11099
11381
  if (currentSha) {
11100
11382
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11103,9 +11385,9 @@ async function refreshSingleRepo(repoDir, options) {
11103
11385
  return;
11104
11386
  }
11105
11387
  const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
11106
- const existingDocs = readExistingConfigs(repoDir);
11107
11388
  const learnedSection = readLearnedSection();
11108
11389
  const fingerprint = await collectFingerprint(repoDir);
11390
+ const existingDocs = fingerprint.existingConfigs;
11109
11391
  const projectContext = {
11110
11392
  languages: fingerprint.languages,
11111
11393
  frameworks: fingerprint.frameworks,
@@ -11124,12 +11406,24 @@ async function refreshSingleRepo(repoDir, options) {
11124
11406
  const sourcesPayload = sources2.length > 0 ? sources2 : void 0;
11125
11407
  let response;
11126
11408
  try {
11127
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11409
+ response = await refreshDocs(
11410
+ diffPayload,
11411
+ existingDocs,
11412
+ projectContext,
11413
+ learnedSection,
11414
+ sourcesPayload
11415
+ );
11128
11416
  } catch (firstErr) {
11129
11417
  const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
11130
11418
  if (!isTransient) throw firstErr;
11131
11419
  try {
11132
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11420
+ response = await refreshDocs(
11421
+ diffPayload,
11422
+ existingDocs,
11423
+ projectContext,
11424
+ learnedSection,
11425
+ sourcesPayload
11426
+ );
11133
11427
  } catch {
11134
11428
  spinner?.fail(`${prefix}Refresh failed after retry`);
11135
11429
  throw firstErr;
@@ -11155,9 +11449,9 @@ async function refreshSingleRepo(repoDir, options) {
11155
11449
  }
11156
11450
  const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11157
11451
  const preScore = computeLocalScore(repoDir, targetAgent);
11158
- const filesToWrite = response.docsUpdated || [];
11452
+ const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
11159
11453
  const preRefreshContents = /* @__PURE__ */ new Map();
11160
- for (const filePath of filesToWrite) {
11454
+ for (const filePath of allFilesToWrite) {
11161
11455
  const fullPath = path33.resolve(repoDir, filePath);
11162
11456
  try {
11163
11457
  preRefreshContents.set(filePath, fs40.readFileSync(fullPath, "utf-8"));
@@ -11181,7 +11475,9 @@ async function refreshSingleRepo(repoDir, options) {
11181
11475
  fs40.writeFileSync(fullPath, content);
11182
11476
  }
11183
11477
  }
11184
- spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
11478
+ spinner?.warn(
11479
+ `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11480
+ );
11185
11481
  log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11186
11482
  if (currentSha) {
11187
11483
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11201,6 +11497,7 @@ async function refreshSingleRepo(repoDir, options) {
11201
11497
  for (const file of builtinWritten) {
11202
11498
  log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
11203
11499
  }
11500
+ clearRefreshError();
11204
11501
  if (currentSha) {
11205
11502
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11206
11503
  }
@@ -11211,11 +11508,28 @@ async function refreshCommand(options) {
11211
11508
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
11212
11509
  if (isCaliberRunning2()) return;
11213
11510
  }
11511
+ if (!quiet) {
11512
+ const lastError = readRefreshError();
11513
+ if (lastError) {
11514
+ console.log(chalk19.yellow(`
11515
+ \u26A0 Last refresh failed (${lastError.timestamp}):`));
11516
+ console.log(chalk19.dim(` ${lastError.error}`));
11517
+ console.log(
11518
+ chalk19.dim(
11519
+ ` Run with --debug for full details, or report at https://github.com/caliber-ai-org/ai-setup/issues
11520
+ `
11521
+ )
11522
+ );
11523
+ clearRefreshError();
11524
+ }
11525
+ }
11214
11526
  try {
11215
11527
  const config = loadConfig();
11216
11528
  if (!config) {
11217
11529
  if (quiet) return;
11218
- console.log(chalk19.red("No LLM provider configured. Run ") + chalk19.hex("#83D1EB")(`${resolveCaliber()} config`) + chalk19.red(" (e.g. choose Cursor) or set an API key."));
11530
+ console.log(
11531
+ chalk19.red("No LLM provider configured. Run ") + chalk19.hex("#83D1EB")(`${resolveCaliber()} config`) + chalk19.red(" (e.g. choose Cursor) or set an API key.")
11532
+ );
11219
11533
  throw new Error("__exit__");
11220
11534
  }
11221
11535
  await validateModel({ fast: true });
@@ -11226,7 +11540,9 @@ async function refreshCommand(options) {
11226
11540
  const repos = discoverGitRepos(process.cwd());
11227
11541
  if (repos.length === 0) {
11228
11542
  if (quiet) return;
11229
- console.log(chalk19.red("Not inside a git repository and no git repos found in child directories."));
11543
+ console.log(
11544
+ chalk19.red("Not inside a git repository and no git repos found in child directories.")
11545
+ );
11230
11546
  throw new Error("__exit__");
11231
11547
  }
11232
11548
  log2(quiet, chalk19.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
@@ -11239,12 +11555,19 @@ async function refreshCommand(options) {
11239
11555
  await refreshSingleRepo(repo, { ...options, label: repoName });
11240
11556
  } catch (err) {
11241
11557
  if (err instanceof Error && err.message === "__exit__") continue;
11242
- log2(quiet, chalk19.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
11558
+ writeRefreshError(err);
11559
+ log2(
11560
+ quiet,
11561
+ chalk19.yellow(
11562
+ `${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`
11563
+ )
11564
+ );
11243
11565
  }
11244
11566
  }
11245
11567
  process.chdir(originalDir);
11246
11568
  } catch (err) {
11247
11569
  if (err instanceof Error && err.message === "__exit__") throw err;
11570
+ writeRefreshError(err);
11248
11571
  if (quiet) return;
11249
11572
  const msg = err instanceof Error ? err.message : "Unknown error";
11250
11573
  console.log(chalk19.red(`Refresh failed: ${msg}`));
@@ -11589,10 +11912,10 @@ function writeState2(state) {
11589
11912
  function resetState() {
11590
11913
  writeState2({ ...DEFAULT_STATE });
11591
11914
  }
11592
- var LOCK_FILE2 = "finalize.lock";
11915
+ var LOCK_FILE = "finalize.lock";
11593
11916
  var LOCK_STALE_MS = 5 * 60 * 1e3;
11594
11917
  function lockFilePath() {
11595
- return path34.join(getLearningDir(), LOCK_FILE2);
11918
+ return path34.join(getLearningDir(), LOCK_FILE);
11596
11919
  }
11597
11920
  function acquireFinalizeLock() {
11598
11921
  ensureLearningDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.35.1",
3
+ "version": "1.36.1",
4
4
  "description": "AI context infrastructure for coding agents — keeps CLAUDE.md, Cursor rules, and skills in sync as your codebase evolves",
5
5
  "type": "module",
6
6
  "bin": {