@rely-ai/caliber 1.35.1 → 1.37.0-dev.1774891449

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 +82 -56
  2. package/dist/bin.js +490 -143
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -389,7 +389,10 @@ function getCursorSetupRule() {
389
389
  function stripManagedBlocks(content) {
390
390
  let result = content;
391
391
  for (const [start, end] of MANAGED_BLOCK_PAIRS) {
392
- const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
392
+ const regex = new RegExp(
393
+ `\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
394
+ "g"
395
+ );
393
396
  result = result.replace(regex, "\n");
394
397
  }
395
398
  return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
@@ -401,7 +404,7 @@ var init_pre_commit_block = __esm({
401
404
  init_resolve_caliber();
402
405
  BLOCK_START = "<!-- caliber:managed:pre-commit -->";
403
406
  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";
407
+ MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md .agents/ .opencode/";
405
408
  CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
406
409
  LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
407
410
  LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
@@ -1100,10 +1103,21 @@ __export(lock_exports, {
1100
1103
  import fs39 from "fs";
1101
1104
  import path32 from "path";
1102
1105
  import os8 from "os";
1106
+ import crypto5 from "crypto";
1107
+ function buildLockPath() {
1108
+ const cwd = process.cwd();
1109
+ const hash = crypto5.createHash("md5").update(cwd).digest("hex").slice(0, 8);
1110
+ return path32.join(os8.tmpdir(), `.caliber-${hash}.lock`);
1111
+ }
1112
+ function getLockFile() {
1113
+ if (!_lockPath) _lockPath = buildLockPath();
1114
+ return _lockPath;
1115
+ }
1103
1116
  function isCaliberRunning() {
1104
1117
  try {
1105
- if (!fs39.existsSync(LOCK_FILE)) return false;
1106
- const raw = fs39.readFileSync(LOCK_FILE, "utf-8").trim();
1118
+ const lockFile = buildLockPath();
1119
+ if (!fs39.existsSync(lockFile)) return false;
1120
+ const raw = fs39.readFileSync(lockFile, "utf-8").trim();
1107
1121
  const { pid, ts } = JSON.parse(raw);
1108
1122
  if (Date.now() - ts > STALE_MS) return false;
1109
1123
  try {
@@ -1118,22 +1132,23 @@ function isCaliberRunning() {
1118
1132
  }
1119
1133
  function acquireLock() {
1120
1134
  try {
1121
- fs39.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
1135
+ fs39.writeFileSync(getLockFile(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
1122
1136
  } catch {
1123
1137
  }
1124
1138
  }
1125
1139
  function releaseLock() {
1126
1140
  try {
1127
- if (fs39.existsSync(LOCK_FILE)) fs39.unlinkSync(LOCK_FILE);
1141
+ const lockFile = getLockFile();
1142
+ if (fs39.existsSync(lockFile)) fs39.unlinkSync(lockFile);
1128
1143
  } catch {
1129
1144
  }
1130
1145
  }
1131
- var LOCK_FILE, STALE_MS;
1146
+ var STALE_MS, _lockPath;
1132
1147
  var init_lock = __esm({
1133
1148
  "src/lib/lock.ts"() {
1134
1149
  "use strict";
1135
- LOCK_FILE = path32.join(os8.tmpdir(), ".caliber.lock");
1136
1150
  STALE_MS = 10 * 60 * 1e3;
1151
+ _lockPath = null;
1137
1152
  }
1138
1153
  });
1139
1154
 
@@ -1291,9 +1306,30 @@ var LEARNING_ROI_FILE = "roi-stats.json";
1291
1306
  var PERSONAL_LEARNINGS_FILE = path2.join(AUTH_DIR, "personal-learnings.md");
1292
1307
  var LEARNING_FINALIZE_LOG = "finalize.log";
1293
1308
  var LEARNING_LAST_ERROR_FILE = "last-error.json";
1309
+ var REFRESH_LAST_ERROR_FILE = path2.join(CALIBER_DIR, "last-refresh-error.json");
1294
1310
  var MIN_SESSIONS_FOR_COMPARISON = 3;
1295
1311
 
1296
1312
  // src/fingerprint/existing-config.ts
1313
+ function readSkillsFromDir(skillsDir) {
1314
+ if (!fs2.existsSync(skillsDir)) return void 0;
1315
+ try {
1316
+ const skills = fs2.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).reduce((acc, entry) => {
1317
+ const skillPath = path3.join(skillsDir, entry.name, "SKILL.md");
1318
+ try {
1319
+ acc.push({
1320
+ name: entry.name,
1321
+ filename: "SKILL.md",
1322
+ content: fs2.readFileSync(skillPath, "utf-8")
1323
+ });
1324
+ } catch {
1325
+ }
1326
+ return acc;
1327
+ }, []);
1328
+ return skills.length > 0 ? skills : void 0;
1329
+ } catch {
1330
+ return void 0;
1331
+ }
1332
+ }
1297
1333
  function readExistingConfigs(dir) {
1298
1334
  const configs = {};
1299
1335
  const readmeMdPath = path3.join(dir, "README.md");
@@ -1324,7 +1360,10 @@ function readExistingConfigs(dir) {
1324
1360
  const entryPath = path3.join(skillsDir, entry);
1325
1361
  const skillMdPath = path3.join(entryPath, "SKILL.md");
1326
1362
  if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
1327
- skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
1363
+ skills.push({
1364
+ filename: `${entry}/SKILL.md`,
1365
+ content: fs2.readFileSync(skillMdPath, "utf-8")
1366
+ });
1328
1367
  } else if (entry.endsWith(".md")) {
1329
1368
  skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
1330
1369
  }
@@ -1348,20 +1387,26 @@ function readExistingConfigs(dir) {
1348
1387
  } catch {
1349
1388
  }
1350
1389
  }
1351
- const cursorSkillsDir = path3.join(dir, ".cursor", "skills");
1352
- if (fs2.existsSync(cursorSkillsDir)) {
1390
+ configs.cursorSkills = readSkillsFromDir(path3.join(dir, ".cursor", "skills"));
1391
+ const copilotPath = path3.join(dir, ".github", "copilot-instructions.md");
1392
+ if (fs2.existsSync(copilotPath)) {
1393
+ configs.copilotInstructions = fs2.readFileSync(copilotPath, "utf-8");
1394
+ }
1395
+ const copilotInstructionsDir = path3.join(dir, ".github", "instructions");
1396
+ if (fs2.existsSync(copilotInstructionsDir)) {
1353
1397
  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
- }));
1398
+ const files = fs2.readdirSync(copilotInstructionsDir).filter((f) => f.endsWith(".instructions.md"));
1399
+ if (files.length > 0) {
1400
+ configs.copilotInstructionFiles = files.map((f) => ({
1401
+ filename: f,
1402
+ content: fs2.readFileSync(path3.join(copilotInstructionsDir, f), "utf-8")
1403
+ }));
1404
+ }
1362
1405
  } catch {
1363
1406
  }
1364
1407
  }
1408
+ configs.codexSkills = readSkillsFromDir(path3.join(dir, ".agents", "skills"));
1409
+ configs.opencodeSkills = readSkillsFromDir(path3.join(dir, ".opencode", "skills"));
1365
1410
  const mcpJsonPath = path3.join(dir, ".mcp.json");
1366
1411
  if (fs2.existsSync(mcpJsonPath)) {
1367
1412
  try {
@@ -3056,6 +3101,7 @@ var SKILL_FORMAT_RULES = `All skills follow the OpenSkills standard (agentskills
3056
3101
 
3057
3102
  Skill field requirements:
3058
3103
  - "name": kebab-case (lowercase letters, numbers, hyphens only). Becomes the directory name.
3104
+ - "name" MUST NOT be any of these reserved names (they are managed by Caliber automatically): "setup-caliber", "find-skills", "save-learning". Do NOT generate skills with these names.
3059
3105
  - "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
3060
3106
  - "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
3061
3107
 
@@ -3099,7 +3145,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
3099
3145
  Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
3100
3146
  README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
3101
3147
  var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
3102
- - 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.
3148
+ - 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.
3103
3149
 
3104
3150
  Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
3105
3151
  GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
@@ -3237,7 +3283,7 @@ Structure:
3237
3283
  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"
3238
3284
 
3239
3285
  Rules:
3240
- - Max 150 lines. Focus on actionable instructions, not documentation prose.
3286
+ - Max 400 lines. Focus on actionable instructions, not documentation prose.
3241
3287
  - 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.
3242
3288
  - Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
3243
3289
  - 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\`."
@@ -3295,7 +3341,7 @@ Rules:
3295
3341
  - Update the "fileDescriptions" to reflect any changes you make.
3296
3342
 
3297
3343
  Quality constraints \u2014 your changes are scored, so do not break these:
3298
- - 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.
3344
+ - 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.
3299
3345
  - Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
3300
3346
  - Do NOT add directory tree listings in code blocks.
3301
3347
  - Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
@@ -3319,14 +3365,20 @@ CONSERVATIVE UPDATE means:
3319
3365
  - NEVER replace specific paths/commands with generic prose
3320
3366
 
3321
3367
  Quality constraints (the output is scored deterministically):
3322
- - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
3368
+ - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
3323
3369
  - Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
3324
3370
  - Every file path, command, and identifier must be in backticks
3325
3371
  - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
3326
3372
  - Preserve the existing structure (headings, bullet style, formatting)
3327
3373
 
3374
+ Cross-agent sync:
3375
+ - When a code change affects documentation, update ALL provided platform configs together.
3376
+ - A renamed command, moved file, or changed convention must be reflected in every config (CLAUDE.md, AGENTS.md, copilot instructions, skills across all platforms).
3377
+ - Cross-agent consistency is critical \u2014 all agents working on this repo must have the same, accurate context.
3378
+
3328
3379
  Managed content:
3329
3380
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
3381
+ - Keep context sync blocks (<!-- caliber:managed:sync --> ... <!-- /caliber:managed:sync -->) intact
3330
3382
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
3331
3383
  - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
3332
3384
 
@@ -3338,14 +3390,16 @@ Return a JSON object with this exact shape:
3338
3390
  "readmeMd": "<updated content or null>",
3339
3391
  "cursorrules": "<updated content or null>",
3340
3392
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
3341
- "claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
3342
3393
  "copilotInstructions": "<updated content or null>",
3343
3394
  "copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
3344
3395
  },
3345
3396
  "changesSummary": "<1-2 sentence summary of what was updated and why>",
3397
+ "fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
3346
3398
  "docsUpdated": ["CLAUDE.md", "README.md"]
3347
3399
  }
3348
3400
 
3401
+ 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").
3402
+
3349
3403
  Respond with ONLY the JSON object, no markdown fences or extra text.`;
3350
3404
  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.
3351
3405
 
@@ -3930,7 +3984,7 @@ var STOP_HOOK_SCRIPT_CONTENT = `#!/bin/sh
3930
3984
  if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3931
3985
  exit 0
3932
3986
  fi
3933
- FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
3987
+ FLAG="/tmp/caliber-nudge-$(echo "$PWD" | (shasum 2>/dev/null || sha1sum 2>/dev/null || md5sum 2>/dev/null || cksum) | cut -c1-8)"
3934
3988
  find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
3935
3989
  if [ -f "$FLAG" ]; then
3936
3990
  exit 0
@@ -3961,11 +4015,13 @@ function installStopHook() {
3961
4015
  }
3962
4016
  settings.hooks.Stop.push({
3963
4017
  matcher: "",
3964
- hooks: [{
3965
- type: "command",
3966
- command: STOP_HOOK_SCRIPT_PATH,
3967
- description: STOP_HOOK_DESCRIPTION
3968
- }]
4018
+ hooks: [
4019
+ {
4020
+ type: "command",
4021
+ command: STOP_HOOK_SCRIPT_PATH,
4022
+ description: STOP_HOOK_DESCRIPTION
4023
+ }
4024
+ ]
3969
4025
  });
3970
4026
  writeSettings(settings);
3971
4027
  return { installed: true, alreadyInstalled: false };
@@ -3997,16 +4053,20 @@ function getPrecommitBlock() {
3997
4053
  const invoke = npx ? bin : `"${bin}"`;
3998
4054
  return `${PRECOMMIT_START}
3999
4055
  if ${guard}; then
4056
+ mkdir -p .caliber
4000
4057
  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
4058
+ ${invoke} refresh --quiet 2>.caliber/refresh-hook.log || true
4059
+ ${invoke} learn finalize 2>>.caliber/refresh-hook.log || true
4060
+ 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
4061
  fi
4005
4062
  ${PRECOMMIT_END}`;
4006
4063
  }
4007
4064
  function getGitHooksDir() {
4008
4065
  try {
4009
- const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4066
+ const gitDir = execSync8("git rev-parse --git-dir", {
4067
+ encoding: "utf-8",
4068
+ stdio: ["pipe", "pipe", "pipe"]
4069
+ }).trim();
4010
4070
  return path10.join(gitDir, "hooks");
4011
4071
  } catch {
4012
4072
  return null;
@@ -5199,7 +5259,7 @@ import fs12 from "fs";
5199
5259
  import path12 from "path";
5200
5260
  function writeClaudeConfig(config) {
5201
5261
  const written = [];
5202
- fs12.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
5262
+ fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
5203
5263
  written.push("CLAUDE.md");
5204
5264
  if (config.skills?.length) {
5205
5265
  for (const skill of config.skills) {
@@ -5245,7 +5305,8 @@ function writeCursorConfig(config) {
5245
5305
  }
5246
5306
  const preCommitRule = getCursorPreCommitRule();
5247
5307
  const learningsRule = getCursorLearningsRule();
5248
- const allRules = [...config.rules || [], preCommitRule, learningsRule];
5308
+ const syncRule = getCursorSyncRule();
5309
+ const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
5249
5310
  const rulesDir = path13.join(".cursor", "rules");
5250
5311
  if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
5251
5312
  for (const rule of allRules) {
@@ -5294,7 +5355,10 @@ import fs14 from "fs";
5294
5355
  import path14 from "path";
5295
5356
  function writeCodexConfig(config) {
5296
5357
  const written = [];
5297
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5358
+ fs14.writeFileSync(
5359
+ "AGENTS.md",
5360
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5361
+ );
5298
5362
  written.push("AGENTS.md");
5299
5363
  if (config.skills?.length) {
5300
5364
  for (const skill of config.skills) {
@@ -5323,7 +5387,10 @@ function writeGithubCopilotConfig(config) {
5323
5387
  const written = [];
5324
5388
  if (config.instructions) {
5325
5389
  fs15.mkdirSync(".github", { recursive: true });
5326
- fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
5390
+ fs15.writeFileSync(
5391
+ path15.join(".github", "copilot-instructions.md"),
5392
+ appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions, "copilot")))
5393
+ );
5327
5394
  written.push(".github/copilot-instructions.md");
5328
5395
  }
5329
5396
  if (config.instructionFiles?.length) {
@@ -5345,7 +5412,10 @@ import path17 from "path";
5345
5412
  function writeOpencodeConfig(config, agentsMdAlreadyWritten = false) {
5346
5413
  const written = [];
5347
5414
  if (!agentsMdAlreadyWritten) {
5348
- fs17.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5415
+ fs17.writeFileSync(
5416
+ "AGENTS.md",
5417
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5418
+ );
5349
5419
  written.push("AGENTS.md");
5350
5420
  }
5351
5421
  if (config.skills?.length) {
@@ -6052,11 +6122,11 @@ var POINTS_LEARNED_CONTENT = 2;
6052
6122
  var POINTS_SOURCES_CONFIGURED = 3;
6053
6123
  var POINTS_SOURCES_REFERENCED = 3;
6054
6124
  var TOKEN_BUDGET_THRESHOLDS = [
6055
- { maxTokens: 2e3, points: 6 },
6056
- { maxTokens: 3500, points: 5 },
6057
- { maxTokens: 5e3, points: 4 },
6058
- { maxTokens: 8e3, points: 2 },
6059
- { maxTokens: 12e3, points: 1 }
6125
+ { maxTokens: 5e3, points: 6 },
6126
+ { maxTokens: 8e3, points: 5 },
6127
+ { maxTokens: 12e3, points: 4 },
6128
+ { maxTokens: 16e3, points: 2 },
6129
+ { maxTokens: 24e3, points: 1 }
6060
6130
  ];
6061
6131
  var CODE_BLOCK_THRESHOLDS = [
6062
6132
  { minBlocks: 3, points: 8 },
@@ -9686,27 +9756,36 @@ async function initCommand(options) {
9686
9756
  const bin = resolveCaliber();
9687
9757
  const firstRun = isFirstRun(process.cwd());
9688
9758
  if (firstRun) {
9689
- console.log(brand.bold(`
9759
+ console.log(
9760
+ brand.bold(`
9690
9761
  \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
9762
  \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
9763
  \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
9764
  \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
9765
  \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
9766
  \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
- `));
9767
+ `)
9768
+ );
9697
9769
  console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
9698
9770
  console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
9699
9771
  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"));
9772
+ console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
9773
+ console.log(chalk14.dim(" 2. Setup Detect stack, install sync hooks & skills"));
9774
+ console.log(chalk14.dim(" 3. Generate Audit existing config or generate from scratch"));
9775
+ console.log(chalk14.dim(" 4. Finalize Review changes and score your setup\n"));
9703
9776
  } else {
9704
9777
  console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 setting up continuous sync\n"));
9705
9778
  }
9706
9779
  const platforms = detectPlatforms();
9707
9780
  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"));
9781
+ console.log(
9782
+ chalk14.yellow(" \u26A0 No supported AI platforms detected (Claude, Cursor, Codex, OpenCode).")
9783
+ );
9784
+ console.log(
9785
+ chalk14.yellow(
9786
+ " Caliber will still generate config files, but they won't be auto-installed.\n"
9787
+ )
9788
+ );
9710
9789
  }
9711
9790
  const report = options.debugReport ? new DebugReport() : null;
9712
9791
  console.log(title.bold(" Step 1/3 \u2014 Connect\n"));
@@ -9761,9 +9840,12 @@ async function initCommand(options) {
9761
9840
  console.log(chalk14.dim(modelLine + "\n"));
9762
9841
  if (report) {
9763
9842
  report.markStep("Provider connection");
9764
- report.addSection("LLM Provider", `- **Provider**: ${config.provider}
9843
+ report.addSection(
9844
+ "LLM Provider",
9845
+ `- **Provider**: ${config.provider}
9765
9846
  - **Model**: ${displayModel}
9766
- - **Fast model**: ${fastModel || "none"}`);
9847
+ - **Fast model**: ${fastModel || "none"}`
9848
+ );
9767
9849
  }
9768
9850
  await validateModel({ fast: true });
9769
9851
  let targetAgent;
@@ -9788,10 +9870,11 @@ async function initCommand(options) {
9788
9870
  console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
9789
9871
  `));
9790
9872
  trackInitAgentSelected(targetAgent, agentAutoDetected);
9791
- console.log(title.bold(" Step 2/3 \u2014 Build\n"));
9873
+ console.log(title.bold(" Step 2/4 \u2014 Setup\n"));
9874
+ console.log(chalk14.dim(" Installing sync infrastructure...\n"));
9792
9875
  const hookResult = installPreCommitHook();
9793
9876
  if (hookResult.installed) {
9794
- console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed`);
9877
+ console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 configs sync on every commit`);
9795
9878
  } else if (hookResult.alreadyInstalled) {
9796
9879
  console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 active`);
9797
9880
  }
@@ -9799,13 +9882,20 @@ async function initCommand(options) {
9799
9882
  console.log(` ${chalk14.green("\u2713")} Onboarding hook \u2014 nudges new team members to set up`);
9800
9883
  const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
9801
9884
  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 });
9885
+ if (agent === "claude" && !fs34.existsSync(".claude"))
9886
+ fs34.mkdirSync(".claude", { recursive: true });
9887
+ if (agent === "cursor" && !fs34.existsSync(".cursor"))
9888
+ fs34.mkdirSync(".cursor", { recursive: true });
9889
+ if (agent === "codex" && !fs34.existsSync(".agents"))
9890
+ fs34.mkdirSync(".agents", { recursive: true });
9805
9891
  }
9806
9892
  const skillsWritten = ensureBuiltinSkills2();
9807
9893
  if (skillsWritten.length > 0) {
9808
- console.log(` ${chalk14.green("\u2713")} Agent skills installed`);
9894
+ console.log(
9895
+ ` ${chalk14.green("\u2713")} Agent skills installed \u2014 /setup-caliber, /find-skills, /save-learning`
9896
+ );
9897
+ } else {
9898
+ console.log(` ${chalk14.green("\u2713")} Agent skills \u2014 already installed`);
9809
9899
  }
9810
9900
  const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
9811
9901
  if (hasLearnableAgent) {
@@ -9815,15 +9905,31 @@ async function initCommand(options) {
9815
9905
  trackInitLearnEnabled(true);
9816
9906
  }
9817
9907
  console.log("");
9908
+ console.log(chalk14.dim(" New team members can run /setup-caliber inside their coding agent"));
9909
+ console.log(chalk14.dim(" (Claude Code or Cursor) to get set up automatically.\n"));
9818
9910
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
9819
- log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
9911
+ console.log(chalk14.dim(" Current config score:"));
9912
+ displayScoreSummary(baselineScore);
9913
+ if (options.verbose) {
9914
+ for (const c of baselineScore.checks) {
9915
+ log(
9916
+ options.verbose,
9917
+ ` ${c.passed ? "\u2713" : "\u2717"} ${c.name}: ${c.earnedPoints}/${c.maxPoints}${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
9918
+ );
9919
+ }
9920
+ }
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));
@@ -9837,15 +9943,27 @@ async function initCommand(options) {
9837
9943
  ]);
9838
9944
  const passingCount = baselineScore.checks.filter((c) => c.passed).length;
9839
9945
  const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
9840
- trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9841
9946
  let skipGeneration = false;
9842
- if (hasExistingConfig && baselineScore.score === 100 && !options.force) {
9843
- skipGeneration = true;
9947
+ if (hasExistingConfig && baselineScore.score === 100) {
9948
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
9949
+ console.log(chalk14.bold.green("\n Your config is already optimal.\n"));
9950
+ skipGeneration = !options.force;
9844
9951
  } else if (hasExistingConfig && !options.force && !options.autoApprove) {
9845
- console.log(chalk14.dim(` Config score: ${baselineScore.score}/100 \u2014 Caliber can improve this.
9846
- `));
9847
- const improveAnswer = await confirm2({ message: "Improve your existing configs?" });
9848
- skipGeneration = !improveAnswer;
9952
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9953
+ console.log(
9954
+ chalk14.dim("\n Sync infrastructure is ready. Caliber can also audit your existing")
9955
+ );
9956
+ console.log(chalk14.dim(" configs and improve them using AI.\n"));
9957
+ const auditAnswer = await promptInput(" Audit and improve your existing config? (Y/n) ");
9958
+ skipGeneration = auditAnswer.toLowerCase() === "n";
9959
+ } else if (!hasExistingConfig && !options.force && !options.autoApprove) {
9960
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9961
+ console.log(chalk14.dim("\n Sync infrastructure is ready. Caliber can also generate tailored"));
9962
+ console.log(chalk14.dim(" CLAUDE.md, Cursor rules, and Codex configs for your project.\n"));
9963
+ const generateAnswer = await promptInput(" Generate agent configs? (Y/n) ");
9964
+ skipGeneration = generateAnswer.toLowerCase() === "n";
9965
+ } else {
9966
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9849
9967
  }
9850
9968
  if (skipGeneration) {
9851
9969
  const {
@@ -9873,7 +9991,12 @@ async function initCommand(options) {
9873
9991
  if (targetAgent.includes("cursor")) {
9874
9992
  const rulesDir = path28.join(".cursor", "rules");
9875
9993
  if (!fs34.existsSync(rulesDir)) fs34.mkdirSync(rulesDir, { recursive: true });
9876
- for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
9994
+ for (const rule of [
9995
+ getCursorPreCommitRule2(),
9996
+ getCursorLearningsRule2(),
9997
+ getCursorSyncRule2(),
9998
+ getCursorSetupRule2()
9999
+ ]) {
9877
10000
  fs34.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
9878
10001
  }
9879
10002
  console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
@@ -9905,9 +10028,12 @@ async function initCommand(options) {
9905
10028
  trackInitCompleted("sync-only", baselineScore.score);
9906
10029
  console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
9907
10030
  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"));
10031
+ console.log(
10032
+ chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n")
10033
+ );
9909
10034
  return;
9910
10035
  }
10036
+ console.log(title.bold("\n Step 3/4 \u2014 Generate\n"));
9911
10037
  const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
9912
10038
  console.log(chalk14.dim(genModelInfo + "\n"));
9913
10039
  if (report) report.markStep("Generation");
@@ -9923,8 +10049,14 @@ async function initCommand(options) {
9923
10049
  const TASK_STACK = display.add("Detecting project stack", { pipelineLabel: "Scan" });
9924
10050
  const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
9925
10051
  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" });
10052
+ const TASK_SKILLS_SEARCH = display.add("Searching community skills", {
10053
+ depth: 1,
10054
+ pipelineLabel: "Search",
10055
+ pipelineRow: 1
10056
+ });
10057
+ const TASK_SCORE_REFINE = display.add("Validating & refining config", {
10058
+ pipelineLabel: "Validate"
10059
+ });
9928
10060
  display.start();
9929
10061
  display.enableWaitingContent();
9930
10062
  try {
@@ -9934,21 +10066,39 @@ async function initCommand(options) {
9934
10066
  const stackSummary = stackParts.join(", ") || "no languages";
9935
10067
  const largeRepoNote = fingerprint.fileTree.length > 5e3 ? ` (${fingerprint.fileTree.length.toLocaleString()} files, smart sampling active)` : "";
9936
10068
  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`);
10069
+ trackInitProjectDiscovered(
10070
+ fingerprint.languages.length,
10071
+ fingerprint.frameworks.length,
10072
+ fingerprint.fileTree.length
10073
+ );
10074
+ log(
10075
+ options.verbose,
10076
+ `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`
10077
+ );
9939
10078
  const cliSources = options.source || [];
9940
10079
  const workspaces = getDetectedWorkspaces(process.cwd());
9941
10080
  const sources2 = resolveAllSources(process.cwd(), cliSources, workspaces);
9942
10081
  if (sources2.length > 0) {
9943
10082
  fingerprint.sources = sources2;
9944
- log(options.verbose, `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`);
10083
+ log(
10084
+ options.verbose,
10085
+ `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`
10086
+ );
9945
10087
  }
9946
10088
  if (report) {
9947
- report.addJson("Fingerprint: Git", { remote: fingerprint.gitRemoteUrl, packageName: fingerprint.packageName });
10089
+ report.addJson("Fingerprint: Git", {
10090
+ remote: fingerprint.gitRemoteUrl,
10091
+ packageName: fingerprint.packageName
10092
+ });
9948
10093
  report.addCodeBlock("Fingerprint: File Tree", fingerprint.fileTree.join("\n"));
9949
- report.addJson("Fingerprint: Detected Stack", { languages: fingerprint.languages, frameworks: fingerprint.frameworks, tools: fingerprint.tools });
10094
+ report.addJson("Fingerprint: Detected Stack", {
10095
+ languages: fingerprint.languages,
10096
+ frameworks: fingerprint.frameworks,
10097
+ tools: fingerprint.tools
10098
+ });
9950
10099
  report.addJson("Fingerprint: Existing Configs", fingerprint.existingConfigs);
9951
- if (fingerprint.codeAnalysis) report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
10100
+ if (fingerprint.codeAnalysis)
10101
+ report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
9952
10102
  }
9953
10103
  const isEmpty = fingerprint.fileTree.length < 3;
9954
10104
  if (isEmpty) {
@@ -9979,13 +10129,26 @@ async function initCommand(options) {
9979
10129
  let passingChecks;
9980
10130
  let currentScore;
9981
10131
  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 }));
10132
+ const currentLlmFixable = localBaseline.checks.filter(
10133
+ (c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id)
10134
+ );
10135
+ failingChecks = currentLlmFixable.map((c) => ({
10136
+ name: c.name,
10137
+ suggestion: c.suggestion,
10138
+ fix: c.fix
10139
+ }));
9984
10140
  passingChecks = localBaseline.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
9985
10141
  currentScore = localBaseline.score;
9986
10142
  }
9987
10143
  if (report) {
9988
- const fullPrompt = buildGeneratePrompt(fingerprint, targetAgent, fingerprint.description, failingChecks, currentScore, passingChecks);
10144
+ const fullPrompt = buildGeneratePrompt(
10145
+ fingerprint,
10146
+ targetAgent,
10147
+ fingerprint.description,
10148
+ failingChecks,
10149
+ currentScore,
10150
+ passingChecks
10151
+ );
9989
10152
  report.addCodeBlock("Generation: Full LLM Prompt", fullPrompt);
9990
10153
  }
9991
10154
  const result = await generateSetup(
@@ -10105,8 +10268,11 @@ async function initCommand(options) {
10105
10268
  if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
10106
10269
  report.addJson("Generation: Parsed Config", generatedSetup);
10107
10270
  }
10108
- log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
10109
- console.log(title.bold(" Step 3/3 \u2014 Done\n"));
10271
+ log(
10272
+ options.verbose,
10273
+ `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`
10274
+ );
10275
+ console.log(title.bold(" Step 4/4 \u2014 Finalize\n"));
10110
10276
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
10111
10277
  const staged = stageFiles(setupFiles, process.cwd());
10112
10278
  const totalChanges = staged.newFiles + staged.modifiedFiles;
@@ -10117,10 +10283,18 @@ async function initCommand(options) {
10117
10283
  }
10118
10284
  console.log("");
10119
10285
  }
10120
- console.log(chalk14.dim(` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`));
10286
+ console.log(
10287
+ chalk14.dim(
10288
+ ` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`
10289
+ )
10290
+ );
10121
10291
  if (skillSearchResult.results.length > 0) {
10122
- console.log(chalk14.dim(` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
10123
- `));
10292
+ console.log(
10293
+ chalk14.dim(
10294
+ ` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
10295
+ `
10296
+ )
10297
+ );
10124
10298
  } else {
10125
10299
  console.log("");
10126
10300
  }
@@ -10156,8 +10330,12 @@ async function initCommand(options) {
10156
10330
  }
10157
10331
  const updatedFiles = collectSetupFiles(generatedSetup, targetAgent);
10158
10332
  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
- `));
10333
+ console.log(
10334
+ chalk14.dim(
10335
+ ` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
10336
+ `
10337
+ )
10338
+ );
10161
10339
  printSetupSummary(generatedSetup);
10162
10340
  const { openReview: openRev } = await Promise.resolve().then(() => (init_review(), review_exports));
10163
10341
  await openRev("terminal", restaged.stagedFiles);
@@ -10183,7 +10361,8 @@ async function initCommand(options) {
10183
10361
  const agentRefs = [];
10184
10362
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
10185
10363
  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.");
10364
+ if (agentRefs.length === 0)
10365
+ agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
10187
10366
  const stubContent = `# AGENTS.md
10188
10367
 
10189
10368
  This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
@@ -10230,24 +10409,39 @@ ${agentRefs.join(" ")}
10230
10409
  if (afterScore.score < baselineScore.score) {
10231
10410
  trackInitScoreRegression(baselineScore.score, afterScore.score);
10232
10411
  console.log("");
10233
- console.log(chalk14.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
10412
+ console.log(
10413
+ chalk14.yellow(
10414
+ ` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`
10415
+ )
10416
+ );
10234
10417
  try {
10235
10418
  const { restored, removed } = undoSetup();
10236
10419
  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.`));
10420
+ console.log(
10421
+ chalk14.dim(
10422
+ ` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`
10423
+ )
10424
+ );
10238
10425
  }
10239
10426
  } catch {
10240
10427
  }
10241
- console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n"));
10428
+ console.log(
10429
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n")
10430
+ );
10242
10431
  return;
10243
10432
  }
10244
10433
  if (report) {
10245
10434
  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})
10435
+ report.addSection(
10436
+ "Scoring: Post-Write",
10437
+ `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
10247
10438
 
10248
10439
  | Check | Passed | Points | Max |
10249
10440
  |-------|--------|--------|-----|
10250
- ` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
10441
+ ` + afterScore.checks.map(
10442
+ (c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
10443
+ ).join("\n")
10444
+ );
10251
10445
  }
10252
10446
  recordScore(afterScore, "init");
10253
10447
  trackInitCompleted("full-generation", afterScore.score);
@@ -10255,7 +10449,10 @@ ${agentRefs.join(" ")}
10255
10449
  if (options.verbose) {
10256
10450
  log(options.verbose, `Final score: ${afterScore.score}/100`);
10257
10451
  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}` : ""}`);
10452
+ log(
10453
+ options.verbose,
10454
+ ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
10455
+ );
10259
10456
  }
10260
10457
  }
10261
10458
  let communitySkillsInstalled = 0;
@@ -10272,21 +10469,32 @@ ${agentRefs.join(" ")}
10272
10469
  const done = chalk14.green("\u2713");
10273
10470
  console.log(chalk14.bold.green("\n Caliber is set up!\n"));
10274
10471
  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")}`);
10276
- 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")}`);
10472
+ console.log(
10473
+ ` ${done} Continuous sync ${chalk14.dim("pre-commit hook keeps all agent configs in sync")}`
10474
+ );
10475
+ console.log(
10476
+ ` ${done} Config generated ${title(`${bin} score`)} ${chalk14.dim("for full breakdown")}`
10477
+ );
10478
+ console.log(
10479
+ ` ${done} Agent skills ${chalk14.dim("/setup-caliber for new team members")}`
10480
+ );
10278
10481
  if (hasLearnableAgent) {
10279
- console.log(` ${done} Session learning ${chalk14.dim("learns from your corrections")}`);
10482
+ console.log(
10483
+ ` ${done} Session learning ${chalk14.dim("agent learns from your feedback")}`
10484
+ );
10280
10485
  }
10281
10486
  if (communitySkillsInstalled > 0) {
10282
- console.log(` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} installed for your stack`)}`);
10487
+ console.log(
10488
+ ` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} skill${communitySkillsInstalled > 1 ? "s" : ""} installed for your stack`)}`
10489
+ );
10283
10490
  }
10284
10491
  console.log(chalk14.bold("\n What happens next:\n"));
10285
- console.log(chalk14.dim(" Every commit syncs your agent configs automatically."));
10286
- console.log(chalk14.dim(" New team members run /setup-caliber to get set up instantly.\n"));
10287
- console.log(` ${title(`${bin} score`)} Full scoring breakdown`);
10288
- console.log(` ${title(`${bin} skills`)} Find community skills`);
10289
- console.log(` ${title(`${bin} undo`)} Revert changes`);
10492
+ console.log(chalk14.dim(" Every commit will automatically sync your agent configs."));
10493
+ console.log(chalk14.dim(" New team members can run /setup-caliber to get set up instantly.\n"));
10494
+ console.log(chalk14.bold(" Explore:\n"));
10495
+ console.log(` ${title(`${bin} score`)} Full scoring breakdown with improvement tips`);
10496
+ console.log(` ${title(`${bin} skills`)} Find community skills for your stack`);
10497
+ console.log(` ${title(`${bin} undo`)} Revert all changes from this run`);
10290
10498
  console.log(` ${title(`${bin} uninstall`)} Remove Caliber completely`);
10291
10499
  console.log("");
10292
10500
  if (options.showTokens) {
@@ -10296,8 +10504,10 @@ ${agentRefs.join(" ")}
10296
10504
  report.markStep("Finished");
10297
10505
  const reportPath = path28.join(process.cwd(), ".caliber", "debug-report.md");
10298
10506
  report.write(reportPath);
10299
- console.log(chalk14.dim(` Debug report written to ${path28.relative(process.cwd(), reportPath)}
10300
- `));
10507
+ console.log(
10508
+ chalk14.dim(` Debug report written to ${path28.relative(process.cwd(), reportPath)}
10509
+ `)
10510
+ );
10301
10511
  }
10302
10512
  }
10303
10513
 
@@ -10660,12 +10870,23 @@ import { execSync as execSync15 } from "child_process";
10660
10870
  var MAX_DIFF_BYTES = 1e5;
10661
10871
  var DOC_PATTERNS = [
10662
10872
  "CLAUDE.md",
10873
+ "AGENTS.md",
10663
10874
  "README.md",
10664
10875
  ".cursorrules",
10665
10876
  ".cursor/rules/",
10877
+ ".cursor/skills/",
10666
10878
  ".claude/skills/",
10879
+ ".agents/skills/",
10880
+ ".opencode/skills/",
10881
+ ".github/copilot-instructions.md",
10882
+ ".github/instructions/",
10667
10883
  "CALIBER_LEARNINGS.md"
10668
10884
  ];
10885
+ function truncateAtLineEnd(text, maxBytes) {
10886
+ if (text.length <= maxBytes) return text;
10887
+ const lastNewline = text.lastIndexOf("\n", maxBytes);
10888
+ return lastNewline === -1 ? text.slice(0, maxBytes) : text.slice(0, lastNewline);
10889
+ }
10669
10890
  function excludeArgs() {
10670
10891
  return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
10671
10892
  }
@@ -10714,9 +10935,9 @@ function collectDiff(lastSha) {
10714
10935
  const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
10715
10936
  if (totalSize > MAX_DIFF_BYTES) {
10716
10937
  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));
10938
+ committedDiff = truncateAtLineEnd(committedDiff, Math.floor(committedDiff.length * ratio));
10939
+ stagedDiff = truncateAtLineEnd(stagedDiff, Math.floor(stagedDiff.length * ratio));
10940
+ unstagedDiff = truncateAtLineEnd(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
10720
10941
  }
10721
10942
  const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
10722
10943
  const parts = [];
@@ -10735,11 +10956,11 @@ import path30 from "path";
10735
10956
  function writeRefreshDocs(docs) {
10736
10957
  const written = [];
10737
10958
  if (docs.agentsMd) {
10738
- fs37.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(docs.agentsMd)));
10959
+ fs37.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10739
10960
  written.push("AGENTS.md");
10740
10961
  }
10741
10962
  if (docs.claudeMd) {
10742
- fs37.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10963
+ fs37.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd));
10743
10964
  written.push("CLAUDE.md");
10744
10965
  }
10745
10966
  if (docs.readmeMd) {
@@ -10758,17 +10979,12 @@ function writeRefreshDocs(docs) {
10758
10979
  written.push(`.cursor/rules/${rule.filename}`);
10759
10980
  }
10760
10981
  }
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
10982
  if (docs.copilotInstructions) {
10770
10983
  fs37.mkdirSync(".github", { recursive: true });
10771
- fs37.writeFileSync(path30.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10984
+ fs37.writeFileSync(
10985
+ path30.join(".github", "copilot-instructions.md"),
10986
+ appendManagedBlocks(docs.copilotInstructions, "copilot")
10987
+ );
10772
10988
  written.push(".github/copilot-instructions.md");
10773
10989
  }
10774
10990
  if (docs.copilotInstructionFiles) {
@@ -10784,6 +11000,7 @@ function writeRefreshDocs(docs) {
10784
11000
 
10785
11001
  // src/ai/refresh.ts
10786
11002
  init_config();
11003
+ init_pre_commit_block();
10787
11004
  async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
10788
11005
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
10789
11006
  const fastModel = getFastModel();
@@ -10799,13 +11016,17 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
10799
11016
  const parts = [];
10800
11017
  parts.push("Update documentation based on the following code changes.\n");
10801
11018
  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(", ")}`);
11019
+ if (projectContext.languages?.length)
11020
+ parts.push(`Languages: ${projectContext.languages.join(", ")}`);
11021
+ if (projectContext.frameworks?.length)
11022
+ parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
10804
11023
  if (projectContext.fileTree?.length) {
10805
11024
  const tree = projectContext.fileTree.slice(0, 200);
10806
- parts.push(`
11025
+ parts.push(
11026
+ `
10807
11027
  File tree (${tree.length}/${projectContext.fileTree.length} \u2014 only reference paths from this list):
10808
- ${tree.join("\n")}`);
11028
+ ${tree.join("\n")}`
11029
+ );
10809
11030
  }
10810
11031
  parts.push(`
10811
11032
  Changed files: ${diff.changedFiles.join(", ")}`);
@@ -10825,11 +11046,11 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10825
11046
  parts.push("\n--- Current Documentation ---");
10826
11047
  if (existingDocs.agentsMd) {
10827
11048
  parts.push("\n[AGENTS.md]");
10828
- parts.push(existingDocs.agentsMd);
11049
+ parts.push(stripManagedBlocks(existingDocs.agentsMd));
10829
11050
  }
10830
11051
  if (existingDocs.claudeMd) {
10831
11052
  parts.push("\n[CLAUDE.md]");
10832
- parts.push(existingDocs.claudeMd);
11053
+ parts.push(stripManagedBlocks(existingDocs.claudeMd));
10833
11054
  }
10834
11055
  if (existingDocs.readmeMd) {
10835
11056
  parts.push("\n[README.md]");
@@ -10848,11 +11069,23 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10848
11069
  }
10849
11070
  if (existingDocs.cursorRules?.length) {
10850
11071
  for (const rule of existingDocs.cursorRules) {
11072
+ if (rule.filename.startsWith("caliber-")) continue;
10851
11073
  parts.push(`
10852
11074
  [.cursor/rules/${rule.filename}]`);
10853
11075
  parts.push(rule.content);
10854
11076
  }
10855
11077
  }
11078
+ if (existingDocs.copilotInstructions) {
11079
+ parts.push("\n[.github/copilot-instructions.md]");
11080
+ parts.push(stripManagedBlocks(existingDocs.copilotInstructions));
11081
+ }
11082
+ if (existingDocs.copilotInstructionFiles?.length) {
11083
+ for (const file of existingDocs.copilotInstructionFiles) {
11084
+ parts.push(`
11085
+ [.github/instructions/${file.filename}]`);
11086
+ parts.push(file.content);
11087
+ }
11088
+ }
10856
11089
  if (learnedSection) {
10857
11090
  parts.push("\n--- Learned Patterns (from session learning) ---");
10858
11091
  parts.push("Consider these accumulated learnings when deciding what to update:");
@@ -11062,6 +11295,49 @@ function migrateInlineLearnings() {
11062
11295
  init_config();
11063
11296
  init_resolve_caliber();
11064
11297
  init_builtin_skills();
11298
+ function writeRefreshError(error) {
11299
+ try {
11300
+ if (!fs40.existsSync(CALIBER_DIR)) fs40.mkdirSync(CALIBER_DIR, { recursive: true });
11301
+ fs40.writeFileSync(
11302
+ REFRESH_LAST_ERROR_FILE,
11303
+ JSON.stringify(
11304
+ {
11305
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11306
+ error: error instanceof Error ? error.message : String(error),
11307
+ stack: error instanceof Error ? error.stack : void 0,
11308
+ cwd: process.cwd(),
11309
+ nodeVersion: process.version
11310
+ },
11311
+ null,
11312
+ 2
11313
+ )
11314
+ );
11315
+ } catch {
11316
+ }
11317
+ }
11318
+ function readRefreshError() {
11319
+ try {
11320
+ if (!fs40.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11321
+ return JSON.parse(fs40.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
11322
+ } catch {
11323
+ return null;
11324
+ }
11325
+ }
11326
+ function clearRefreshError() {
11327
+ try {
11328
+ if (fs40.existsSync(REFRESH_LAST_ERROR_FILE)) fs40.unlinkSync(REFRESH_LAST_ERROR_FILE);
11329
+ } catch {
11330
+ }
11331
+ }
11332
+ function detectSyncedAgents(writtenFiles) {
11333
+ const agents = [];
11334
+ const joined = writtenFiles.join(" ");
11335
+ if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
11336
+ if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
11337
+ if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
11338
+ if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
11339
+ return agents;
11340
+ }
11065
11341
  function log2(quiet, ...args) {
11066
11342
  if (!quiet) console.log(...args);
11067
11343
  }
@@ -11080,21 +11356,41 @@ function discoverGitRepos(parentDir) {
11080
11356
  }
11081
11357
  return repos.sort();
11082
11358
  }
11359
+ function collectFilesToWrite(updatedDocs) {
11360
+ const files = [];
11361
+ if (updatedDocs.agentsMd) files.push("AGENTS.md");
11362
+ if (updatedDocs.claudeMd) files.push("CLAUDE.md");
11363
+ if (updatedDocs.readmeMd) files.push("README.md");
11364
+ if (updatedDocs.cursorrules) files.push(".cursorrules");
11365
+ if (Array.isArray(updatedDocs.cursorRules)) {
11366
+ for (const r of updatedDocs.cursorRules)
11367
+ files.push(`.cursor/rules/${r.filename}`);
11368
+ }
11369
+ if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
11370
+ if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
11371
+ for (const f of updatedDocs.copilotInstructionFiles)
11372
+ files.push(`.github/instructions/${f.filename}`);
11373
+ }
11374
+ return files;
11375
+ }
11083
11376
  var REFRESH_COOLDOWN_MS = 3e4;
11084
11377
  async function refreshSingleRepo(repoDir, options) {
11085
11378
  const quiet = !!options.quiet;
11086
11379
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11087
11380
  const state = readState();
11088
11381
  const lastSha = state?.lastRefreshSha ?? null;
11089
- if (state?.lastRefreshTimestamp) {
11382
+ const currentSha = getCurrentHeadSha();
11383
+ if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11090
11384
  const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11091
11385
  if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11092
- log2(quiet, chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`));
11386
+ log2(
11387
+ quiet,
11388
+ chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11389
+ );
11093
11390
  return;
11094
11391
  }
11095
11392
  }
11096
11393
  const diff = collectDiff(lastSha);
11097
- const currentSha = getCurrentHeadSha();
11098
11394
  if (!diff.hasChanges) {
11099
11395
  if (currentSha) {
11100
11396
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11103,9 +11399,9 @@ async function refreshSingleRepo(repoDir, options) {
11103
11399
  return;
11104
11400
  }
11105
11401
  const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
11106
- const existingDocs = readExistingConfigs(repoDir);
11107
11402
  const learnedSection = readLearnedSection();
11108
11403
  const fingerprint = await collectFingerprint(repoDir);
11404
+ const existingDocs = fingerprint.existingConfigs;
11109
11405
  const projectContext = {
11110
11406
  languages: fingerprint.languages,
11111
11407
  frameworks: fingerprint.frameworks,
@@ -11124,12 +11420,24 @@ async function refreshSingleRepo(repoDir, options) {
11124
11420
  const sourcesPayload = sources2.length > 0 ? sources2 : void 0;
11125
11421
  let response;
11126
11422
  try {
11127
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11423
+ response = await refreshDocs(
11424
+ diffPayload,
11425
+ existingDocs,
11426
+ projectContext,
11427
+ learnedSection,
11428
+ sourcesPayload
11429
+ );
11128
11430
  } catch (firstErr) {
11129
11431
  const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
11130
11432
  if (!isTransient) throw firstErr;
11131
11433
  try {
11132
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11434
+ response = await refreshDocs(
11435
+ diffPayload,
11436
+ existingDocs,
11437
+ projectContext,
11438
+ learnedSection,
11439
+ sourcesPayload
11440
+ );
11133
11441
  } catch {
11134
11442
  spinner?.fail(`${prefix}Refresh failed after retry`);
11135
11443
  throw firstErr;
@@ -11155,9 +11463,9 @@ async function refreshSingleRepo(repoDir, options) {
11155
11463
  }
11156
11464
  const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11157
11465
  const preScore = computeLocalScore(repoDir, targetAgent);
11158
- const filesToWrite = response.docsUpdated || [];
11466
+ const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
11159
11467
  const preRefreshContents = /* @__PURE__ */ new Map();
11160
- for (const filePath of filesToWrite) {
11468
+ for (const filePath of allFilesToWrite) {
11161
11469
  const fullPath = path33.resolve(repoDir, filePath);
11162
11470
  try {
11163
11471
  preRefreshContents.set(filePath, fs40.readFileSync(fullPath, "utf-8"));
@@ -11181,7 +11489,9 @@ async function refreshSingleRepo(repoDir, options) {
11181
11489
  fs40.writeFileSync(fullPath, content);
11182
11490
  }
11183
11491
  }
11184
- spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
11492
+ spinner?.warn(
11493
+ `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11494
+ );
11185
11495
  log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11186
11496
  if (currentSha) {
11187
11497
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11190,8 +11500,18 @@ async function refreshSingleRepo(repoDir, options) {
11190
11500
  }
11191
11501
  recordScore(postScore, "refresh");
11192
11502
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
11503
+ const fileChangesMap = new Map(
11504
+ (response.fileChanges || []).map((fc) => [fc.file, fc.description])
11505
+ );
11193
11506
  for (const file of written) {
11194
- log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
11507
+ const desc = fileChangesMap.get(file);
11508
+ const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
11509
+ log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
11510
+ }
11511
+ const agents = detectSyncedAgents(written);
11512
+ if (agents.length > 1) {
11513
+ log2(quiet, chalk19.cyan(`
11514
+ ${agents.length} agent formats in sync (${agents.join(", ")})`));
11195
11515
  }
11196
11516
  if (response.changesSummary) {
11197
11517
  log2(quiet, chalk19.dim(`
@@ -11201,6 +11521,7 @@ async function refreshSingleRepo(repoDir, options) {
11201
11521
  for (const file of builtinWritten) {
11202
11522
  log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
11203
11523
  }
11524
+ clearRefreshError();
11204
11525
  if (currentSha) {
11205
11526
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11206
11527
  }
@@ -11211,11 +11532,28 @@ async function refreshCommand(options) {
11211
11532
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
11212
11533
  if (isCaliberRunning2()) return;
11213
11534
  }
11535
+ if (!quiet) {
11536
+ const lastError = readRefreshError();
11537
+ if (lastError) {
11538
+ console.log(chalk19.yellow(`
11539
+ \u26A0 Last refresh failed (${lastError.timestamp}):`));
11540
+ console.log(chalk19.dim(` ${lastError.error}`));
11541
+ console.log(
11542
+ chalk19.dim(
11543
+ ` Run with --debug for full details, or report at https://github.com/caliber-ai-org/ai-setup/issues
11544
+ `
11545
+ )
11546
+ );
11547
+ clearRefreshError();
11548
+ }
11549
+ }
11214
11550
  try {
11215
11551
  const config = loadConfig();
11216
11552
  if (!config) {
11217
11553
  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."));
11554
+ console.log(
11555
+ chalk19.red("No LLM provider configured. Run ") + chalk19.hex("#83D1EB")(`${resolveCaliber()} config`) + chalk19.red(" (e.g. choose Cursor) or set an API key.")
11556
+ );
11219
11557
  throw new Error("__exit__");
11220
11558
  }
11221
11559
  await validateModel({ fast: true });
@@ -11226,7 +11564,9 @@ async function refreshCommand(options) {
11226
11564
  const repos = discoverGitRepos(process.cwd());
11227
11565
  if (repos.length === 0) {
11228
11566
  if (quiet) return;
11229
- console.log(chalk19.red("Not inside a git repository and no git repos found in child directories."));
11567
+ console.log(
11568
+ chalk19.red("Not inside a git repository and no git repos found in child directories.")
11569
+ );
11230
11570
  throw new Error("__exit__");
11231
11571
  }
11232
11572
  log2(quiet, chalk19.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
@@ -11239,12 +11579,19 @@ async function refreshCommand(options) {
11239
11579
  await refreshSingleRepo(repo, { ...options, label: repoName });
11240
11580
  } catch (err) {
11241
11581
  if (err instanceof Error && err.message === "__exit__") continue;
11242
- log2(quiet, chalk19.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
11582
+ writeRefreshError(err);
11583
+ log2(
11584
+ quiet,
11585
+ chalk19.yellow(
11586
+ `${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`
11587
+ )
11588
+ );
11243
11589
  }
11244
11590
  }
11245
11591
  process.chdir(originalDir);
11246
11592
  } catch (err) {
11247
11593
  if (err instanceof Error && err.message === "__exit__") throw err;
11594
+ writeRefreshError(err);
11248
11595
  if (quiet) return;
11249
11596
  const msg = err instanceof Error ? err.message : "Unknown error";
11250
11597
  console.log(chalk19.red(`Refresh failed: ${msg}`));
@@ -11589,10 +11936,10 @@ function writeState2(state) {
11589
11936
  function resetState() {
11590
11937
  writeState2({ ...DEFAULT_STATE });
11591
11938
  }
11592
- var LOCK_FILE2 = "finalize.lock";
11939
+ var LOCK_FILE = "finalize.lock";
11593
11940
  var LOCK_STALE_MS = 5 * 60 * 1e3;
11594
11941
  function lockFilePath() {
11595
- return path34.join(getLearningDir(), LOCK_FILE2);
11942
+ return path34.join(getLearningDir(), LOCK_FILE);
11596
11943
  }
11597
11944
  function acquireFinalizeLock() {
11598
11945
  ensureLearningDir();