@rely-ai/caliber 1.35.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +249 -74
  2. 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 {
@@ -3325,6 +3370,11 @@ Quality constraints (the output is scored deterministically):
3325
3370
  - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
3326
3371
  - Preserve the existing structure (headings, bullet style, formatting)
3327
3372
 
3373
+ Cross-agent sync:
3374
+ - When a code change affects documentation, update ALL provided platform configs together.
3375
+ - A renamed command, moved file, or changed convention must be reflected in every config (CLAUDE.md, AGENTS.md, copilot instructions, skills across all platforms).
3376
+ - Cross-agent consistency is critical \u2014 all agents working on this repo must have the same, accurate context.
3377
+
3328
3378
  Managed content:
3329
3379
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
3330
3380
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
@@ -3338,7 +3388,6 @@ Return a JSON object with this exact shape:
3338
3388
  "readmeMd": "<updated content or null>",
3339
3389
  "cursorrules": "<updated content or null>",
3340
3390
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
3341
- "claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
3342
3391
  "copilotInstructions": "<updated content or null>",
3343
3392
  "copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
3344
3393
  },
@@ -3930,7 +3979,7 @@ var STOP_HOOK_SCRIPT_CONTENT = `#!/bin/sh
3930
3979
  if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3931
3980
  exit 0
3932
3981
  fi
3933
- FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
3982
+ FLAG="/tmp/caliber-nudge-$(echo "$PWD" | (shasum 2>/dev/null || sha1sum 2>/dev/null || md5sum 2>/dev/null || cksum) | cut -c1-8)"
3934
3983
  find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
3935
3984
  if [ -f "$FLAG" ]; then
3936
3985
  exit 0
@@ -3961,11 +4010,13 @@ function installStopHook() {
3961
4010
  }
3962
4011
  settings.hooks.Stop.push({
3963
4012
  matcher: "",
3964
- hooks: [{
3965
- type: "command",
3966
- command: STOP_HOOK_SCRIPT_PATH,
3967
- description: STOP_HOOK_DESCRIPTION
3968
- }]
4013
+ hooks: [
4014
+ {
4015
+ type: "command",
4016
+ command: STOP_HOOK_SCRIPT_PATH,
4017
+ description: STOP_HOOK_DESCRIPTION
4018
+ }
4019
+ ]
3969
4020
  });
3970
4021
  writeSettings(settings);
3971
4022
  return { installed: true, alreadyInstalled: false };
@@ -3997,16 +4048,20 @@ function getPrecommitBlock() {
3997
4048
  const invoke = npx ? bin : `"${bin}"`;
3998
4049
  return `${PRECOMMIT_START}
3999
4050
  if ${guard}; then
4051
+ mkdir -p .caliber
4000
4052
  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
4053
+ ${invoke} refresh --quiet 2>.caliber/refresh-hook.log || true
4054
+ ${invoke} learn finalize 2>>.caliber/refresh-hook.log || true
4055
+ 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
4056
  fi
4005
4057
  ${PRECOMMIT_END}`;
4006
4058
  }
4007
4059
  function getGitHooksDir() {
4008
4060
  try {
4009
- const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
4061
+ const gitDir = execSync8("git rev-parse --git-dir", {
4062
+ encoding: "utf-8",
4063
+ stdio: ["pipe", "pipe", "pipe"]
4064
+ }).trim();
4010
4065
  return path10.join(gitDir, "hooks");
4011
4066
  } catch {
4012
4067
  return null;
@@ -5294,7 +5349,10 @@ import fs14 from "fs";
5294
5349
  import path14 from "path";
5295
5350
  function writeCodexConfig(config) {
5296
5351
  const written = [];
5297
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5352
+ fs14.writeFileSync(
5353
+ "AGENTS.md",
5354
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5355
+ );
5298
5356
  written.push("AGENTS.md");
5299
5357
  if (config.skills?.length) {
5300
5358
  for (const skill of config.skills) {
@@ -5323,7 +5381,10 @@ function writeGithubCopilotConfig(config) {
5323
5381
  const written = [];
5324
5382
  if (config.instructions) {
5325
5383
  fs15.mkdirSync(".github", { recursive: true });
5326
- fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
5384
+ fs15.writeFileSync(
5385
+ path15.join(".github", "copilot-instructions.md"),
5386
+ appendLearningsBlock(appendPreCommitBlock(config.instructions, "copilot"))
5387
+ );
5327
5388
  written.push(".github/copilot-instructions.md");
5328
5389
  }
5329
5390
  if (config.instructionFiles?.length) {
@@ -5345,7 +5406,10 @@ import path17 from "path";
5345
5406
  function writeOpencodeConfig(config, agentsMdAlreadyWritten = false) {
5346
5407
  const written = [];
5347
5408
  if (!agentsMdAlreadyWritten) {
5348
- fs17.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5409
+ fs17.writeFileSync(
5410
+ "AGENTS.md",
5411
+ appendLearningsBlock(appendPreCommitBlock(config.agentsMd, "codex"))
5412
+ );
5349
5413
  written.push("AGENTS.md");
5350
5414
  }
5351
5415
  if (config.skills?.length) {
@@ -10660,12 +10724,23 @@ import { execSync as execSync15 } from "child_process";
10660
10724
  var MAX_DIFF_BYTES = 1e5;
10661
10725
  var DOC_PATTERNS = [
10662
10726
  "CLAUDE.md",
10727
+ "AGENTS.md",
10663
10728
  "README.md",
10664
10729
  ".cursorrules",
10665
10730
  ".cursor/rules/",
10731
+ ".cursor/skills/",
10666
10732
  ".claude/skills/",
10733
+ ".agents/skills/",
10734
+ ".opencode/skills/",
10735
+ ".github/copilot-instructions.md",
10736
+ ".github/instructions/",
10667
10737
  "CALIBER_LEARNINGS.md"
10668
10738
  ];
10739
+ function truncateAtLineEnd(text, maxBytes) {
10740
+ if (text.length <= maxBytes) return text;
10741
+ const lastNewline = text.lastIndexOf("\n", maxBytes);
10742
+ return lastNewline === -1 ? text.slice(0, maxBytes) : text.slice(0, lastNewline);
10743
+ }
10669
10744
  function excludeArgs() {
10670
10745
  return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
10671
10746
  }
@@ -10714,9 +10789,9 @@ function collectDiff(lastSha) {
10714
10789
  const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
10715
10790
  if (totalSize > MAX_DIFF_BYTES) {
10716
10791
  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));
10792
+ committedDiff = truncateAtLineEnd(committedDiff, Math.floor(committedDiff.length * ratio));
10793
+ stagedDiff = truncateAtLineEnd(stagedDiff, Math.floor(stagedDiff.length * ratio));
10794
+ unstagedDiff = truncateAtLineEnd(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
10720
10795
  }
10721
10796
  const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
10722
10797
  const parts = [];
@@ -10735,11 +10810,11 @@ import path30 from "path";
10735
10810
  function writeRefreshDocs(docs) {
10736
10811
  const written = [];
10737
10812
  if (docs.agentsMd) {
10738
- fs37.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(docs.agentsMd)));
10813
+ fs37.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10739
10814
  written.push("AGENTS.md");
10740
10815
  }
10741
10816
  if (docs.claudeMd) {
10742
- fs37.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10817
+ fs37.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd));
10743
10818
  written.push("CLAUDE.md");
10744
10819
  }
10745
10820
  if (docs.readmeMd) {
@@ -10758,17 +10833,12 @@ function writeRefreshDocs(docs) {
10758
10833
  written.push(`.cursor/rules/${rule.filename}`);
10759
10834
  }
10760
10835
  }
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
10836
  if (docs.copilotInstructions) {
10770
10837
  fs37.mkdirSync(".github", { recursive: true });
10771
- fs37.writeFileSync(path30.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10838
+ fs37.writeFileSync(
10839
+ path30.join(".github", "copilot-instructions.md"),
10840
+ appendManagedBlocks(docs.copilotInstructions, "copilot")
10841
+ );
10772
10842
  written.push(".github/copilot-instructions.md");
10773
10843
  }
10774
10844
  if (docs.copilotInstructionFiles) {
@@ -10784,6 +10854,7 @@ function writeRefreshDocs(docs) {
10784
10854
 
10785
10855
  // src/ai/refresh.ts
10786
10856
  init_config();
10857
+ init_pre_commit_block();
10787
10858
  async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
10788
10859
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
10789
10860
  const fastModel = getFastModel();
@@ -10799,13 +10870,17 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
10799
10870
  const parts = [];
10800
10871
  parts.push("Update documentation based on the following code changes.\n");
10801
10872
  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(", ")}`);
10873
+ if (projectContext.languages?.length)
10874
+ parts.push(`Languages: ${projectContext.languages.join(", ")}`);
10875
+ if (projectContext.frameworks?.length)
10876
+ parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
10804
10877
  if (projectContext.fileTree?.length) {
10805
10878
  const tree = projectContext.fileTree.slice(0, 200);
10806
- parts.push(`
10879
+ parts.push(
10880
+ `
10807
10881
  File tree (${tree.length}/${projectContext.fileTree.length} \u2014 only reference paths from this list):
10808
- ${tree.join("\n")}`);
10882
+ ${tree.join("\n")}`
10883
+ );
10809
10884
  }
10810
10885
  parts.push(`
10811
10886
  Changed files: ${diff.changedFiles.join(", ")}`);
@@ -10825,11 +10900,11 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10825
10900
  parts.push("\n--- Current Documentation ---");
10826
10901
  if (existingDocs.agentsMd) {
10827
10902
  parts.push("\n[AGENTS.md]");
10828
- parts.push(existingDocs.agentsMd);
10903
+ parts.push(stripManagedBlocks(existingDocs.agentsMd));
10829
10904
  }
10830
10905
  if (existingDocs.claudeMd) {
10831
10906
  parts.push("\n[CLAUDE.md]");
10832
- parts.push(existingDocs.claudeMd);
10907
+ parts.push(stripManagedBlocks(existingDocs.claudeMd));
10833
10908
  }
10834
10909
  if (existingDocs.readmeMd) {
10835
10910
  parts.push("\n[README.md]");
@@ -10839,20 +10914,25 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10839
10914
  parts.push("\n[.cursorrules]");
10840
10915
  parts.push(existingDocs.cursorrules);
10841
10916
  }
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
10917
  if (existingDocs.cursorRules?.length) {
10850
10918
  for (const rule of existingDocs.cursorRules) {
10919
+ if (rule.filename.startsWith("caliber-")) continue;
10851
10920
  parts.push(`
10852
10921
  [.cursor/rules/${rule.filename}]`);
10853
10922
  parts.push(rule.content);
10854
10923
  }
10855
10924
  }
10925
+ if (existingDocs.copilotInstructions) {
10926
+ parts.push("\n[.github/copilot-instructions.md]");
10927
+ parts.push(stripManagedBlocks(existingDocs.copilotInstructions));
10928
+ }
10929
+ if (existingDocs.copilotInstructionFiles?.length) {
10930
+ for (const file of existingDocs.copilotInstructionFiles) {
10931
+ parts.push(`
10932
+ [.github/instructions/${file.filename}]`);
10933
+ parts.push(file.content);
10934
+ }
10935
+ }
10856
10936
  if (learnedSection) {
10857
10937
  parts.push("\n--- Learned Patterns (from session learning) ---");
10858
10938
  parts.push("Consider these accumulated learnings when deciding what to update:");
@@ -11062,6 +11142,40 @@ function migrateInlineLearnings() {
11062
11142
  init_config();
11063
11143
  init_resolve_caliber();
11064
11144
  init_builtin_skills();
11145
+ function writeRefreshError(error) {
11146
+ try {
11147
+ if (!fs40.existsSync(CALIBER_DIR)) fs40.mkdirSync(CALIBER_DIR, { recursive: true });
11148
+ fs40.writeFileSync(
11149
+ REFRESH_LAST_ERROR_FILE,
11150
+ JSON.stringify(
11151
+ {
11152
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11153
+ error: error instanceof Error ? error.message : String(error),
11154
+ stack: error instanceof Error ? error.stack : void 0,
11155
+ cwd: process.cwd(),
11156
+ nodeVersion: process.version
11157
+ },
11158
+ null,
11159
+ 2
11160
+ )
11161
+ );
11162
+ } catch {
11163
+ }
11164
+ }
11165
+ function readRefreshError() {
11166
+ try {
11167
+ if (!fs40.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11168
+ return JSON.parse(fs40.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
11169
+ } catch {
11170
+ return null;
11171
+ }
11172
+ }
11173
+ function clearRefreshError() {
11174
+ try {
11175
+ if (fs40.existsSync(REFRESH_LAST_ERROR_FILE)) fs40.unlinkSync(REFRESH_LAST_ERROR_FILE);
11176
+ } catch {
11177
+ }
11178
+ }
11065
11179
  function log2(quiet, ...args) {
11066
11180
  if (!quiet) console.log(...args);
11067
11181
  }
@@ -11080,21 +11194,41 @@ function discoverGitRepos(parentDir) {
11080
11194
  }
11081
11195
  return repos.sort();
11082
11196
  }
11197
+ function collectFilesToWrite(updatedDocs) {
11198
+ const files = [];
11199
+ if (updatedDocs.agentsMd) files.push("AGENTS.md");
11200
+ if (updatedDocs.claudeMd) files.push("CLAUDE.md");
11201
+ if (updatedDocs.readmeMd) files.push("README.md");
11202
+ if (updatedDocs.cursorrules) files.push(".cursorrules");
11203
+ if (Array.isArray(updatedDocs.cursorRules)) {
11204
+ for (const r of updatedDocs.cursorRules)
11205
+ files.push(`.cursor/rules/${r.filename}`);
11206
+ }
11207
+ if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
11208
+ if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
11209
+ for (const f of updatedDocs.copilotInstructionFiles)
11210
+ files.push(`.github/instructions/${f.filename}`);
11211
+ }
11212
+ return files;
11213
+ }
11083
11214
  var REFRESH_COOLDOWN_MS = 3e4;
11084
11215
  async function refreshSingleRepo(repoDir, options) {
11085
11216
  const quiet = !!options.quiet;
11086
11217
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11087
11218
  const state = readState();
11088
11219
  const lastSha = state?.lastRefreshSha ?? null;
11089
- if (state?.lastRefreshTimestamp) {
11220
+ const currentSha = getCurrentHeadSha();
11221
+ if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11090
11222
  const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11091
11223
  if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11092
- log2(quiet, chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`));
11224
+ log2(
11225
+ quiet,
11226
+ chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11227
+ );
11093
11228
  return;
11094
11229
  }
11095
11230
  }
11096
11231
  const diff = collectDiff(lastSha);
11097
- const currentSha = getCurrentHeadSha();
11098
11232
  if (!diff.hasChanges) {
11099
11233
  if (currentSha) {
11100
11234
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11103,9 +11237,9 @@ async function refreshSingleRepo(repoDir, options) {
11103
11237
  return;
11104
11238
  }
11105
11239
  const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
11106
- const existingDocs = readExistingConfigs(repoDir);
11107
11240
  const learnedSection = readLearnedSection();
11108
11241
  const fingerprint = await collectFingerprint(repoDir);
11242
+ const existingDocs = fingerprint.existingConfigs;
11109
11243
  const projectContext = {
11110
11244
  languages: fingerprint.languages,
11111
11245
  frameworks: fingerprint.frameworks,
@@ -11124,12 +11258,24 @@ async function refreshSingleRepo(repoDir, options) {
11124
11258
  const sourcesPayload = sources2.length > 0 ? sources2 : void 0;
11125
11259
  let response;
11126
11260
  try {
11127
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11261
+ response = await refreshDocs(
11262
+ diffPayload,
11263
+ existingDocs,
11264
+ projectContext,
11265
+ learnedSection,
11266
+ sourcesPayload
11267
+ );
11128
11268
  } catch (firstErr) {
11129
11269
  const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
11130
11270
  if (!isTransient) throw firstErr;
11131
11271
  try {
11132
- response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
11272
+ response = await refreshDocs(
11273
+ diffPayload,
11274
+ existingDocs,
11275
+ projectContext,
11276
+ learnedSection,
11277
+ sourcesPayload
11278
+ );
11133
11279
  } catch {
11134
11280
  spinner?.fail(`${prefix}Refresh failed after retry`);
11135
11281
  throw firstErr;
@@ -11155,9 +11301,9 @@ async function refreshSingleRepo(repoDir, options) {
11155
11301
  }
11156
11302
  const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11157
11303
  const preScore = computeLocalScore(repoDir, targetAgent);
11158
- const filesToWrite = response.docsUpdated || [];
11304
+ const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
11159
11305
  const preRefreshContents = /* @__PURE__ */ new Map();
11160
- for (const filePath of filesToWrite) {
11306
+ for (const filePath of allFilesToWrite) {
11161
11307
  const fullPath = path33.resolve(repoDir, filePath);
11162
11308
  try {
11163
11309
  preRefreshContents.set(filePath, fs40.readFileSync(fullPath, "utf-8"));
@@ -11181,7 +11327,9 @@ async function refreshSingleRepo(repoDir, options) {
11181
11327
  fs40.writeFileSync(fullPath, content);
11182
11328
  }
11183
11329
  }
11184
- spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
11330
+ spinner?.warn(
11331
+ `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11332
+ );
11185
11333
  log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11186
11334
  if (currentSha) {
11187
11335
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
@@ -11201,6 +11349,7 @@ async function refreshSingleRepo(repoDir, options) {
11201
11349
  for (const file of builtinWritten) {
11202
11350
  log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
11203
11351
  }
11352
+ clearRefreshError();
11204
11353
  if (currentSha) {
11205
11354
  writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11206
11355
  }
@@ -11211,11 +11360,28 @@ async function refreshCommand(options) {
11211
11360
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
11212
11361
  if (isCaliberRunning2()) return;
11213
11362
  }
11363
+ if (!quiet) {
11364
+ const lastError = readRefreshError();
11365
+ if (lastError) {
11366
+ console.log(chalk19.yellow(`
11367
+ \u26A0 Last refresh failed (${lastError.timestamp}):`));
11368
+ console.log(chalk19.dim(` ${lastError.error}`));
11369
+ console.log(
11370
+ chalk19.dim(
11371
+ ` Run with --debug for full details, or report at https://github.com/caliber-ai-org/ai-setup/issues
11372
+ `
11373
+ )
11374
+ );
11375
+ clearRefreshError();
11376
+ }
11377
+ }
11214
11378
  try {
11215
11379
  const config = loadConfig();
11216
11380
  if (!config) {
11217
11381
  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."));
11382
+ console.log(
11383
+ chalk19.red("No LLM provider configured. Run ") + chalk19.hex("#83D1EB")(`${resolveCaliber()} config`) + chalk19.red(" (e.g. choose Cursor) or set an API key.")
11384
+ );
11219
11385
  throw new Error("__exit__");
11220
11386
  }
11221
11387
  await validateModel({ fast: true });
@@ -11226,7 +11392,9 @@ async function refreshCommand(options) {
11226
11392
  const repos = discoverGitRepos(process.cwd());
11227
11393
  if (repos.length === 0) {
11228
11394
  if (quiet) return;
11229
- console.log(chalk19.red("Not inside a git repository and no git repos found in child directories."));
11395
+ console.log(
11396
+ chalk19.red("Not inside a git repository and no git repos found in child directories.")
11397
+ );
11230
11398
  throw new Error("__exit__");
11231
11399
  }
11232
11400
  log2(quiet, chalk19.dim(`Found ${repos.length} git repo${repos.length === 1 ? "" : "s"}
@@ -11239,12 +11407,19 @@ async function refreshCommand(options) {
11239
11407
  await refreshSingleRepo(repo, { ...options, label: repoName });
11240
11408
  } catch (err) {
11241
11409
  if (err instanceof Error && err.message === "__exit__") continue;
11242
- log2(quiet, chalk19.yellow(`${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`));
11410
+ writeRefreshError(err);
11411
+ log2(
11412
+ quiet,
11413
+ chalk19.yellow(
11414
+ `${repoName}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`
11415
+ )
11416
+ );
11243
11417
  }
11244
11418
  }
11245
11419
  process.chdir(originalDir);
11246
11420
  } catch (err) {
11247
11421
  if (err instanceof Error && err.message === "__exit__") throw err;
11422
+ writeRefreshError(err);
11248
11423
  if (quiet) return;
11249
11424
  const msg = err instanceof Error ? err.message : "Unknown error";
11250
11425
  console.log(chalk19.red(`Refresh failed: ${msg}`));
@@ -11589,10 +11764,10 @@ function writeState2(state) {
11589
11764
  function resetState() {
11590
11765
  writeState2({ ...DEFAULT_STATE });
11591
11766
  }
11592
- var LOCK_FILE2 = "finalize.lock";
11767
+ var LOCK_FILE = "finalize.lock";
11593
11768
  var LOCK_STALE_MS = 5 * 60 * 1e3;
11594
11769
  function lockFilePath() {
11595
- return path34.join(getLearningDir(), LOCK_FILE2);
11770
+ return path34.join(getLearningDir(), LOCK_FILE);
11596
11771
  }
11597
11772
  function acquireFinalizeLock() {
11598
11773
  ensureLearningDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
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": {