@rely-ai/caliber 1.37.1 → 1.38.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 +393 -239
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1099,14 +1099,14 @@ __export(lock_exports, {
1099
1099
  isCaliberRunning: () => isCaliberRunning,
1100
1100
  releaseLock: () => releaseLock
1101
1101
  });
1102
- import fs39 from "fs";
1103
- import path32 from "path";
1102
+ import fs40 from "fs";
1103
+ import path33 from "path";
1104
1104
  import os8 from "os";
1105
1105
  import crypto5 from "crypto";
1106
1106
  function buildLockPath() {
1107
1107
  const cwd = process.cwd();
1108
1108
  const hash = crypto5.createHash("md5").update(cwd).digest("hex").slice(0, 8);
1109
- return path32.join(os8.tmpdir(), `.caliber-${hash}.lock`);
1109
+ return path33.join(os8.tmpdir(), `.caliber-${hash}.lock`);
1110
1110
  }
1111
1111
  function getLockFile() {
1112
1112
  if (!_lockPath) _lockPath = buildLockPath();
@@ -1115,8 +1115,8 @@ function getLockFile() {
1115
1115
  function isCaliberRunning() {
1116
1116
  try {
1117
1117
  const lockFile = buildLockPath();
1118
- if (!fs39.existsSync(lockFile)) return false;
1119
- const raw = fs39.readFileSync(lockFile, "utf-8").trim();
1118
+ if (!fs40.existsSync(lockFile)) return false;
1119
+ const raw = fs40.readFileSync(lockFile, "utf-8").trim();
1120
1120
  const { pid, ts } = JSON.parse(raw);
1121
1121
  if (Date.now() - ts > STALE_MS) return false;
1122
1122
  try {
@@ -1131,14 +1131,14 @@ function isCaliberRunning() {
1131
1131
  }
1132
1132
  function acquireLock() {
1133
1133
  try {
1134
- fs39.writeFileSync(getLockFile(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
1134
+ fs40.writeFileSync(getLockFile(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
1135
1135
  } catch {
1136
1136
  }
1137
1137
  }
1138
1138
  function releaseLock() {
1139
1139
  try {
1140
1140
  const lockFile = getLockFile();
1141
- if (fs39.existsSync(lockFile)) fs39.unlinkSync(lockFile);
1141
+ if (fs40.existsSync(lockFile)) fs40.unlinkSync(lockFile);
1142
1142
  } catch {
1143
1143
  }
1144
1144
  }
@@ -1153,8 +1153,8 @@ var init_lock = __esm({
1153
1153
 
1154
1154
  // src/cli.ts
1155
1155
  import { Command } from "commander";
1156
- import fs50 from "fs";
1157
- import path41 from "path";
1156
+ import fs51 from "fs";
1157
+ import path42 from "path";
1158
1158
  import { fileURLToPath } from "url";
1159
1159
 
1160
1160
  // src/commands/init.ts
@@ -3695,7 +3695,7 @@ function getDetectedWorkspaces(dir) {
3695
3695
 
3696
3696
  // src/fingerprint/index.ts
3697
3697
  async function collectFingerprint(dir) {
3698
- const gitRemoteUrl = getGitRemoteUrl();
3698
+ const gitRemoteUrl = getGitRemoteUrl(dir);
3699
3699
  const fileTree = getFileTree(dir);
3700
3700
  const existingConfigs = readExistingConfigs(dir);
3701
3701
  const packageName = readPackageName(dir);
@@ -4698,15 +4698,15 @@ init_config();
4698
4698
  // src/utils/dependencies.ts
4699
4699
  import { readFileSync as readFileSync2 } from "fs";
4700
4700
  import { join as join2 } from "path";
4701
- function readFileOrNull2(path43) {
4701
+ function readFileOrNull2(path44) {
4702
4702
  try {
4703
- return readFileSync2(path43, "utf-8");
4703
+ return readFileSync2(path44, "utf-8");
4704
4704
  } catch {
4705
4705
  return null;
4706
4706
  }
4707
4707
  }
4708
- function readJsonOrNull(path43) {
4709
- const content = readFileOrNull2(path43);
4708
+ function readJsonOrNull(path44) {
4709
+ const content = readFileOrNull2(path44);
4710
4710
  if (!content) return null;
4711
4711
  try {
4712
4712
  return JSON.parse(content);
@@ -7626,8 +7626,8 @@ function trackInitSkillsSearch(searched, installedCount) {
7626
7626
  function trackInitScoreRegression(oldScore, newScore) {
7627
7627
  trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
7628
7628
  }
7629
- function trackInitCompleted(path43, score) {
7630
- trackEvent("init_completed", { path: path43, score });
7629
+ function trackInitCompleted(path44, score) {
7630
+ trackEvent("init_completed", { path: path44, score });
7631
7631
  }
7632
7632
  function trackRegenerateCompleted(action, durationMs) {
7633
7633
  trackEvent("regenerate_completed", { action, duration_ms: durationMs });
@@ -8640,11 +8640,11 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks, options) {
8640
8640
  const maxIterations = options?.thorough ? 3 : MAX_REFINE_ITERATIONS;
8641
8641
  const minPoints = options?.thorough ? 1 : MIN_POINTS_TO_REFINE;
8642
8642
  const existsCache = /* @__PURE__ */ new Map();
8643
- const cachedExists = (path43) => {
8644
- const cached = existsCache.get(path43);
8643
+ const cachedExists = (path44) => {
8644
+ const cached = existsCache.get(path44);
8645
8645
  if (cached !== void 0) return cached;
8646
- const result = existsSync9(path43);
8647
- existsCache.set(path43, result);
8646
+ const result = existsSync9(path44);
8647
+ existsCache.set(path44, result);
8648
8648
  return result;
8649
8649
  };
8650
8650
  const projectStructure = collectProjectStructure(dir);
@@ -11051,8 +11051,8 @@ async function scoreCommand(options) {
11051
11051
  }
11052
11052
 
11053
11053
  // src/commands/refresh.ts
11054
- import fs40 from "fs";
11055
- import path33 from "path";
11054
+ import fs41 from "fs";
11055
+ import path34 from "path";
11056
11056
  import chalk19 from "chalk";
11057
11057
  import ora6 from "ora";
11058
11058
 
@@ -11139,51 +11139,87 @@ function collectDiff(lastSha) {
11139
11139
  const summary = parts.join(", ") || "no changes";
11140
11140
  return { hasChanges, committedDiff, stagedDiff, unstagedDiff, changedFiles, summary };
11141
11141
  }
11142
+ function scopeDiffToDir(diff, dir, allConfigDirs) {
11143
+ if (dir === ".") {
11144
+ const otherDirs = allConfigDirs.filter((d) => d !== ".");
11145
+ if (otherDirs.length === 0) return diff;
11146
+ const changedFiles2 = diff.changedFiles.filter(
11147
+ (f) => !otherDirs.some((d) => f.startsWith(`${d}/`))
11148
+ );
11149
+ const hasChanges2 = changedFiles2.length > 0;
11150
+ return {
11151
+ ...diff,
11152
+ changedFiles: changedFiles2,
11153
+ hasChanges: hasChanges2,
11154
+ summary: hasChanges2 ? `${changedFiles2.length} files changed` : "no changes"
11155
+ };
11156
+ }
11157
+ const prefix = `${dir}/`;
11158
+ const changedFiles = diff.changedFiles.filter((f) => f.startsWith(prefix)).map((f) => f.slice(prefix.length));
11159
+ const hasChanges = changedFiles.length > 0;
11160
+ return {
11161
+ ...diff,
11162
+ changedFiles,
11163
+ hasChanges,
11164
+ summary: hasChanges ? `${changedFiles.length} files changed` : "no changes"
11165
+ };
11166
+ }
11142
11167
 
11143
11168
  // src/writers/refresh.ts
11144
11169
  init_pre_commit_block();
11145
11170
  import fs37 from "fs";
11146
11171
  import path30 from "path";
11147
- function writeRefreshDocs(docs) {
11172
+ function writeRefreshDocs(docs, dir = ".") {
11148
11173
  const written = [];
11174
+ const p = (relPath) => (dir === "." ? relPath : path30.join(dir, relPath)).replace(/\\/g, "/");
11175
+ const ensureParent = (filePath) => {
11176
+ const parent = path30.dirname(filePath);
11177
+ if (parent !== "." && !fs37.existsSync(parent)) fs37.mkdirSync(parent, { recursive: true });
11178
+ };
11149
11179
  if (docs.agentsMd) {
11150
- fs37.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
11151
- written.push("AGENTS.md");
11180
+ const filePath = p("AGENTS.md");
11181
+ ensureParent(filePath);
11182
+ fs37.writeFileSync(filePath, appendManagedBlocks(docs.agentsMd, "codex"));
11183
+ written.push(filePath);
11152
11184
  }
11153
11185
  if (docs.claudeMd) {
11154
- fs37.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd));
11155
- written.push("CLAUDE.md");
11186
+ const filePath = p("CLAUDE.md");
11187
+ ensureParent(filePath);
11188
+ fs37.writeFileSync(filePath, appendManagedBlocks(docs.claudeMd));
11189
+ written.push(filePath);
11156
11190
  }
11157
11191
  if (docs.readmeMd) {
11158
- fs37.writeFileSync("README.md", docs.readmeMd);
11159
- written.push("README.md");
11192
+ const filePath = p("README.md");
11193
+ ensureParent(filePath);
11194
+ fs37.writeFileSync(filePath, docs.readmeMd);
11195
+ written.push(filePath);
11160
11196
  }
11161
11197
  if (docs.cursorrules) {
11162
- fs37.writeFileSync(".cursorrules", docs.cursorrules);
11163
- written.push(".cursorrules");
11198
+ const filePath = p(".cursorrules");
11199
+ ensureParent(filePath);
11200
+ fs37.writeFileSync(filePath, docs.cursorrules);
11201
+ written.push(filePath);
11164
11202
  }
11165
11203
  if (docs.cursorRules) {
11166
- const rulesDir = path30.join(".cursor", "rules");
11204
+ const rulesDir = p(path30.join(".cursor", "rules"));
11167
11205
  if (!fs37.existsSync(rulesDir)) fs37.mkdirSync(rulesDir, { recursive: true });
11168
11206
  for (const rule of docs.cursorRules) {
11169
11207
  fs37.writeFileSync(path30.join(rulesDir, rule.filename), rule.content);
11170
- written.push(`.cursor/rules/${rule.filename}`);
11208
+ written.push(p(path30.join(".cursor", "rules", rule.filename)));
11171
11209
  }
11172
11210
  }
11173
11211
  if (docs.copilotInstructions) {
11174
- fs37.mkdirSync(".github", { recursive: true });
11175
- fs37.writeFileSync(
11176
- path30.join(".github", "copilot-instructions.md"),
11177
- appendManagedBlocks(docs.copilotInstructions, "copilot")
11178
- );
11179
- written.push(".github/copilot-instructions.md");
11212
+ const filePath = p(path30.join(".github", "copilot-instructions.md"));
11213
+ ensureParent(filePath);
11214
+ fs37.writeFileSync(filePath, appendManagedBlocks(docs.copilotInstructions, "copilot"));
11215
+ written.push(filePath);
11180
11216
  }
11181
11217
  if (docs.copilotInstructionFiles) {
11182
- const instructionsDir = path30.join(".github", "instructions");
11183
- fs37.mkdirSync(instructionsDir, { recursive: true });
11218
+ const instructionsDir = p(path30.join(".github", "instructions"));
11219
+ if (!fs37.existsSync(instructionsDir)) fs37.mkdirSync(instructionsDir, { recursive: true });
11184
11220
  for (const file of docs.copilotInstructionFiles) {
11185
11221
  fs37.writeFileSync(path30.join(instructionsDir, file.filename), file.content);
11186
- written.push(`.github/instructions/${file.filename}`);
11222
+ written.push(p(path30.join(".github", "instructions", file.filename)));
11187
11223
  }
11188
11224
  }
11189
11225
  return written;
@@ -11192,8 +11228,15 @@ function writeRefreshDocs(docs) {
11192
11228
  // src/ai/refresh.ts
11193
11229
  init_config();
11194
11230
  init_pre_commit_block();
11195
- async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
11196
- const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
11231
+ async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2, scope) {
11232
+ const prompt = buildRefreshPrompt(
11233
+ diff,
11234
+ existingDocs,
11235
+ projectContext,
11236
+ learnedSection,
11237
+ sources2,
11238
+ scope
11239
+ );
11197
11240
  const fastModel = getFastModel();
11198
11241
  const raw = await llmCall({
11199
11242
  system: REFRESH_SYSTEM_PROMPT,
@@ -11203,8 +11246,14 @@ async function refreshDocs(diff, existingDocs, projectContext, learnedSection, s
11203
11246
  });
11204
11247
  return parseJsonResponse(raw);
11205
11248
  }
11206
- function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2) {
11249
+ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2, scope) {
11207
11250
  const parts = [];
11251
+ if (scope) {
11252
+ parts.push(`You are updating docs for the \`${scope}\` subdirectory of a monorepo.`);
11253
+ parts.push("Only include changes relevant to files under this directory.");
11254
+ parts.push("The changed files list has been filtered to this directory already.");
11255
+ parts.push("Ignore diff content for files outside this directory.\n");
11256
+ }
11208
11257
  parts.push("Update documentation based on the following code changes.\n");
11209
11258
  if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
11210
11259
  if (projectContext.languages?.length)
@@ -11486,10 +11535,77 @@ function migrateInlineLearnings() {
11486
11535
  init_config();
11487
11536
  init_resolve_caliber();
11488
11537
  init_builtin_skills();
11538
+
11539
+ // src/lib/config-discovery.ts
11540
+ import fs39 from "fs";
11541
+ import path32 from "path";
11542
+ var CONFIG_FILE_MARKERS = [
11543
+ "CLAUDE.md",
11544
+ "AGENTS.md",
11545
+ ".cursorrules",
11546
+ ".github/copilot-instructions.md"
11547
+ ];
11548
+ var CONFIG_DIR_MARKERS = [".cursor/rules", ".github/instructions", ".opencode/skills"];
11549
+ var IGNORE_DIRS3 = /* @__PURE__ */ new Set([
11550
+ "node_modules",
11551
+ ".git",
11552
+ ".next",
11553
+ "dist",
11554
+ "build",
11555
+ ".cache",
11556
+ ".turbo",
11557
+ "coverage",
11558
+ ".caliber",
11559
+ "__pycache__",
11560
+ ".venv",
11561
+ "vendor",
11562
+ "target"
11563
+ ]);
11564
+ var MAX_DEPTH = 4;
11565
+ function hasConfigFiles(dir) {
11566
+ for (const marker of CONFIG_FILE_MARKERS) {
11567
+ if (fs39.existsSync(path32.join(dir, marker))) return true;
11568
+ }
11569
+ for (const marker of CONFIG_DIR_MARKERS) {
11570
+ const markerPath = path32.join(dir, marker);
11571
+ if (fs39.existsSync(markerPath) && fs39.statSync(markerPath).isDirectory()) return true;
11572
+ }
11573
+ return false;
11574
+ }
11575
+ function discoverConfigDirs(rootDir) {
11576
+ const dirs = [];
11577
+ if (hasConfigFiles(rootDir)) {
11578
+ dirs.push(".");
11579
+ }
11580
+ walkForConfigs(rootDir, rootDir, 0, dirs);
11581
+ dirs.sort();
11582
+ return dirs;
11583
+ }
11584
+ function walkForConfigs(baseDir, currentDir, depth, result) {
11585
+ if (depth >= MAX_DEPTH) return;
11586
+ let entries;
11587
+ try {
11588
+ entries = fs39.readdirSync(currentDir, { withFileTypes: true });
11589
+ } catch {
11590
+ return;
11591
+ }
11592
+ for (const entry of entries) {
11593
+ if (!entry.isDirectory()) continue;
11594
+ if (entry.name.startsWith(".") || IGNORE_DIRS3.has(entry.name)) continue;
11595
+ const fullPath = path32.join(currentDir, entry.name);
11596
+ const relPath = path32.relative(baseDir, fullPath).replace(/\\/g, "/");
11597
+ if (hasConfigFiles(fullPath)) {
11598
+ result.push(relPath);
11599
+ }
11600
+ walkForConfigs(baseDir, fullPath, depth + 1, result);
11601
+ }
11602
+ }
11603
+
11604
+ // src/commands/refresh.ts
11489
11605
  function writeRefreshError(error) {
11490
11606
  try {
11491
- if (!fs40.existsSync(CALIBER_DIR)) fs40.mkdirSync(CALIBER_DIR, { recursive: true });
11492
- fs40.writeFileSync(
11607
+ if (!fs41.existsSync(CALIBER_DIR)) fs41.mkdirSync(CALIBER_DIR, { recursive: true });
11608
+ fs41.writeFileSync(
11493
11609
  REFRESH_LAST_ERROR_FILE,
11494
11610
  JSON.stringify(
11495
11611
  {
@@ -11508,15 +11624,15 @@ function writeRefreshError(error) {
11508
11624
  }
11509
11625
  function readRefreshError() {
11510
11626
  try {
11511
- if (!fs40.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11512
- return JSON.parse(fs40.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
11627
+ if (!fs41.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11628
+ return JSON.parse(fs41.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
11513
11629
  } catch {
11514
11630
  return null;
11515
11631
  }
11516
11632
  }
11517
11633
  function clearRefreshError() {
11518
11634
  try {
11519
- if (fs40.existsSync(REFRESH_LAST_ERROR_FILE)) fs40.unlinkSync(REFRESH_LAST_ERROR_FILE);
11635
+ if (fs41.existsSync(REFRESH_LAST_ERROR_FILE)) fs41.unlinkSync(REFRESH_LAST_ERROR_FILE);
11520
11636
  } catch {
11521
11637
  }
11522
11638
  }
@@ -11536,11 +11652,11 @@ function log2(quiet, ...args) {
11536
11652
  function discoverGitRepos(parentDir) {
11537
11653
  const repos = [];
11538
11654
  try {
11539
- const entries = fs40.readdirSync(parentDir, { withFileTypes: true });
11655
+ const entries = fs41.readdirSync(parentDir, { withFileTypes: true });
11540
11656
  for (const entry of entries) {
11541
11657
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
11542
- const childPath = path33.join(parentDir, entry.name);
11543
- if (fs40.existsSync(path33.join(childPath, ".git"))) {
11658
+ const childPath = path34.join(parentDir, entry.name);
11659
+ if (fs41.existsSync(path34.join(childPath, ".git"))) {
11544
11660
  repos.push(childPath);
11545
11661
  }
11546
11662
  }
@@ -11548,51 +11664,33 @@ function discoverGitRepos(parentDir) {
11548
11664
  }
11549
11665
  return repos.sort();
11550
11666
  }
11551
- function collectFilesToWrite(updatedDocs) {
11667
+ function collectFilesToWrite(updatedDocs, dir = ".") {
11552
11668
  const files = [];
11553
- if (updatedDocs.agentsMd) files.push("AGENTS.md");
11554
- if (updatedDocs.claudeMd) files.push("CLAUDE.md");
11555
- if (updatedDocs.readmeMd) files.push("README.md");
11556
- if (updatedDocs.cursorrules) files.push(".cursorrules");
11669
+ const p = (relPath) => (dir === "." ? relPath : path34.join(dir, relPath)).replace(/\\/g, "/");
11670
+ if (updatedDocs.agentsMd) files.push(p("AGENTS.md"));
11671
+ if (updatedDocs.claudeMd) files.push(p("CLAUDE.md"));
11672
+ if (updatedDocs.readmeMd) files.push(p("README.md"));
11673
+ if (updatedDocs.cursorrules) files.push(p(".cursorrules"));
11557
11674
  if (Array.isArray(updatedDocs.cursorRules)) {
11558
11675
  for (const r of updatedDocs.cursorRules)
11559
- files.push(`.cursor/rules/${r.filename}`);
11676
+ files.push(p(`.cursor/rules/${r.filename}`));
11560
11677
  }
11561
- if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
11678
+ if (updatedDocs.copilotInstructions) files.push(p(".github/copilot-instructions.md"));
11562
11679
  if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
11563
11680
  for (const f of updatedDocs.copilotInstructionFiles)
11564
- files.push(`.github/instructions/${f.filename}`);
11681
+ files.push(p(`.github/instructions/${f.filename}`));
11565
11682
  }
11566
11683
  return files;
11567
11684
  }
11568
11685
  var REFRESH_COOLDOWN_MS = 3e4;
11569
- async function refreshSingleRepo(repoDir, options) {
11686
+ async function refreshDir(repoDir, dir, diff, options) {
11570
11687
  const quiet = !!options.quiet;
11571
11688
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11572
- const state = readState();
11573
- const lastSha = state?.lastRefreshSha ?? null;
11574
- const currentSha = getCurrentHeadSha();
11575
- if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11576
- const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11577
- if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11578
- log2(
11579
- quiet,
11580
- chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11581
- );
11582
- return;
11583
- }
11584
- }
11585
- const diff = collectDiff(lastSha);
11586
- if (!diff.hasChanges) {
11587
- if (currentSha) {
11588
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11589
- }
11590
- log2(quiet, chalk19.dim(`${prefix}No changes since last refresh.`));
11591
- return;
11592
- }
11689
+ const absDir = dir === "." ? repoDir : path34.resolve(repoDir, dir);
11690
+ const scope = dir === "." ? void 0 : dir;
11593
11691
  const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
11594
11692
  const learnedSection = readLearnedSection();
11595
- const fingerprint = await collectFingerprint(repoDir);
11693
+ const fingerprint = await collectFingerprint(absDir);
11596
11694
  const existingDocs = fingerprint.existingConfigs;
11597
11695
  const projectContext = {
11598
11696
  languages: fingerprint.languages,
@@ -11600,8 +11698,8 @@ async function refreshSingleRepo(repoDir, options) {
11600
11698
  packageName: fingerprint.packageName,
11601
11699
  fileTree: fingerprint.fileTree
11602
11700
  };
11603
- const workspaces = getDetectedWorkspaces(repoDir);
11604
- const sources2 = resolveAllSources(repoDir, [], workspaces);
11701
+ const workspaces = getDetectedWorkspaces(absDir);
11702
+ const sources2 = resolveAllSources(absDir, [], workspaces);
11605
11703
  const diffPayload = {
11606
11704
  committed: diff.committedDiff,
11607
11705
  staged: diff.stagedDiff,
@@ -11617,7 +11715,8 @@ async function refreshSingleRepo(repoDir, options) {
11617
11715
  existingDocs,
11618
11716
  projectContext,
11619
11717
  learnedSection,
11620
- sourcesPayload
11718
+ sourcesPayload,
11719
+ scope
11621
11720
  );
11622
11721
  } catch (firstErr) {
11623
11722
  const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
@@ -11628,7 +11727,8 @@ async function refreshSingleRepo(repoDir, options) {
11628
11727
  existingDocs,
11629
11728
  projectContext,
11630
11729
  learnedSection,
11631
- sourcesPayload
11730
+ sourcesPayload,
11731
+ scope
11632
11732
  );
11633
11733
  } catch {
11634
11734
  spinner?.fail(`${prefix}Refresh failed after retry`);
@@ -11637,10 +11737,7 @@ async function refreshSingleRepo(repoDir, options) {
11637
11737
  }
11638
11738
  if (!response.docsUpdated || response.docsUpdated.length === 0) {
11639
11739
  spinner?.succeed(`${prefix}No doc updates needed`);
11640
- if (currentSha) {
11641
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11642
- }
11643
- return;
11740
+ return { written: [] };
11644
11741
  }
11645
11742
  if (options.dryRun) {
11646
11743
  spinner?.info(`${prefix}Dry run \u2014 would update:`);
@@ -11651,49 +11748,53 @@ async function refreshSingleRepo(repoDir, options) {
11651
11748
  console.log(chalk19.dim(`
11652
11749
  ${response.changesSummary}`));
11653
11750
  }
11654
- return;
11751
+ return { written: [] };
11655
11752
  }
11656
- const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11657
- const preScore = computeLocalScore(repoDir, targetAgent);
11658
- const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
11753
+ const allFilesToWrite = collectFilesToWrite(response.updatedDocs, dir);
11659
11754
  const preRefreshContents = /* @__PURE__ */ new Map();
11660
11755
  for (const filePath of allFilesToWrite) {
11661
- const fullPath = path33.resolve(repoDir, filePath);
11756
+ const fullPath = path34.resolve(repoDir, filePath);
11662
11757
  try {
11663
- preRefreshContents.set(filePath, fs40.readFileSync(fullPath, "utf-8"));
11758
+ preRefreshContents.set(filePath, fs41.readFileSync(fullPath, "utf-8"));
11664
11759
  } catch {
11665
11760
  preRefreshContents.set(filePath, null);
11666
11761
  }
11667
11762
  }
11668
- const written = writeRefreshDocs(response.updatedDocs);
11763
+ const state = readState();
11764
+ const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11765
+ const runQualityGate = dir === ".";
11766
+ const preScore = runQualityGate ? computeLocalScore(absDir, targetAgent) : null;
11767
+ const written = writeRefreshDocs(response.updatedDocs, dir);
11669
11768
  const trigger = quiet ? "hook" : "manual";
11670
11769
  trackRefreshCompleted(written.length, Date.now(), trigger);
11671
- const postScore = computeLocalScore(repoDir, targetAgent);
11672
- if (postScore.score < preScore.score) {
11673
- for (const [filePath, content] of preRefreshContents) {
11674
- const fullPath = path33.resolve(repoDir, filePath);
11675
- if (content === null) {
11676
- try {
11677
- fs40.unlinkSync(fullPath);
11678
- } catch {
11770
+ if (runQualityGate && preScore) {
11771
+ const postScore = computeLocalScore(absDir, targetAgent);
11772
+ if (postScore.score < preScore.score) {
11773
+ for (const [filePath, content] of preRefreshContents) {
11774
+ const fullPath = path34.resolve(repoDir, filePath);
11775
+ if (content === null) {
11776
+ try {
11777
+ fs41.unlinkSync(fullPath);
11778
+ } catch {
11779
+ }
11780
+ } else {
11781
+ fs41.writeFileSync(fullPath, content);
11679
11782
  }
11680
- } else {
11681
- fs40.writeFileSync(fullPath, content);
11682
11783
  }
11784
+ spinner?.warn(
11785
+ `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11786
+ );
11787
+ log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11788
+ return { written: [] };
11683
11789
  }
11684
- spinner?.warn(
11685
- `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11686
- );
11687
- log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11688
- if (currentSha) {
11689
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11690
- }
11691
- return;
11790
+ recordScore(postScore, "refresh");
11692
11791
  }
11693
- recordScore(postScore, "refresh");
11694
11792
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
11695
11793
  const fileChangesMap = new Map(
11696
- (response.fileChanges || []).map((fc) => [fc.file, fc.description])
11794
+ (response.fileChanges || []).map((fc) => [
11795
+ fc.file,
11796
+ fc.description
11797
+ ])
11697
11798
  );
11698
11799
  for (const file of written) {
11699
11800
  const desc = fileChangesMap.get(file);
@@ -11709,6 +11810,59 @@ async function refreshSingleRepo(repoDir, options) {
11709
11810
  log2(quiet, chalk19.dim(`
11710
11811
  ${response.changesSummary}`));
11711
11812
  }
11813
+ return { written };
11814
+ }
11815
+ async function refreshSingleRepo(repoDir, options) {
11816
+ const quiet = !!options.quiet;
11817
+ const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11818
+ const state = readState();
11819
+ const lastSha = state?.lastRefreshSha ?? null;
11820
+ const currentSha = getCurrentHeadSha();
11821
+ if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11822
+ const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11823
+ if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11824
+ log2(
11825
+ quiet,
11826
+ chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11827
+ );
11828
+ return;
11829
+ }
11830
+ }
11831
+ const diff = collectDiff(lastSha);
11832
+ if (!diff.hasChanges) {
11833
+ if (currentSha) {
11834
+ writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11835
+ }
11836
+ log2(quiet, chalk19.dim(`${prefix}No changes since last refresh.`));
11837
+ return;
11838
+ }
11839
+ const configDirs = discoverConfigDirs(repoDir);
11840
+ if (configDirs.length <= 1) {
11841
+ await refreshDir(repoDir, ".", diff, options);
11842
+ } else {
11843
+ log2(quiet, chalk19.dim(`${prefix}Found configs in ${configDirs.length} directories
11844
+ `));
11845
+ let hadFailure = false;
11846
+ for (const dir of configDirs) {
11847
+ const scopedDiff = scopeDiffToDir(diff, dir, configDirs);
11848
+ if (!scopedDiff.hasChanges) continue;
11849
+ const dirLabel = dir === "." ? "root" : dir;
11850
+ try {
11851
+ await refreshDir(repoDir, dir, scopedDiff, { ...options, label: dirLabel });
11852
+ } catch (err) {
11853
+ hadFailure = true;
11854
+ log2(
11855
+ quiet,
11856
+ chalk19.yellow(
11857
+ ` ${dirLabel}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`
11858
+ )
11859
+ );
11860
+ }
11861
+ }
11862
+ if (hadFailure) {
11863
+ return;
11864
+ }
11865
+ }
11712
11866
  const builtinWritten = ensureBuiltinSkills();
11713
11867
  for (const file of builtinWritten) {
11714
11868
  log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
@@ -11765,7 +11919,7 @@ async function refreshCommand(options) {
11765
11919
  `));
11766
11920
  const originalDir = process.cwd();
11767
11921
  for (const repo of repos) {
11768
- const repoName = path33.basename(repo);
11922
+ const repoName = path34.basename(repo);
11769
11923
  try {
11770
11924
  process.chdir(repo);
11771
11925
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -11793,7 +11947,7 @@ async function refreshCommand(options) {
11793
11947
 
11794
11948
  // src/commands/hooks.ts
11795
11949
  import chalk20 from "chalk";
11796
- import fs41 from "fs";
11950
+ import fs42 from "fs";
11797
11951
  var HOOKS = [
11798
11952
  {
11799
11953
  id: "session-end",
@@ -11837,11 +11991,11 @@ async function hooksCommand(options) {
11837
11991
  console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
11838
11992
  }
11839
11993
  }
11840
- if (fs41.existsSync(".claude")) {
11994
+ if (fs42.existsSync(".claude")) {
11841
11995
  const r = installLearningHooks();
11842
11996
  if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
11843
11997
  }
11844
- if (fs41.existsSync(".cursor")) {
11998
+ if (fs42.existsSync(".cursor")) {
11845
11999
  const r = installCursorLearningHooks();
11846
12000
  if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
11847
12001
  }
@@ -12009,8 +12163,8 @@ async function configCommand() {
12009
12163
  }
12010
12164
 
12011
12165
  // src/commands/learn.ts
12012
- import fs45 from "fs";
12013
- import path37 from "path";
12166
+ import fs46 from "fs";
12167
+ import path38 from "path";
12014
12168
  import chalk23 from "chalk";
12015
12169
 
12016
12170
  // src/learner/stdin.ts
@@ -12041,8 +12195,8 @@ function readStdin() {
12041
12195
  }
12042
12196
 
12043
12197
  // src/learner/storage.ts
12044
- import fs42 from "fs";
12045
- import path34 from "path";
12198
+ import fs43 from "fs";
12199
+ import path35 from "path";
12046
12200
  var MAX_RESPONSE_LENGTH = 2e3;
12047
12201
  var DEFAULT_STATE = {
12048
12202
  sessionId: null,
@@ -12051,15 +12205,15 @@ var DEFAULT_STATE = {
12051
12205
  lastAnalysisEventCount: 0
12052
12206
  };
12053
12207
  function ensureLearningDir() {
12054
- if (!fs42.existsSync(getLearningDir())) {
12055
- fs42.mkdirSync(getLearningDir(), { recursive: true });
12208
+ if (!fs43.existsSync(getLearningDir())) {
12209
+ fs43.mkdirSync(getLearningDir(), { recursive: true });
12056
12210
  }
12057
12211
  }
12058
12212
  function sessionFilePath() {
12059
- return path34.join(getLearningDir(), LEARNING_SESSION_FILE);
12213
+ return path35.join(getLearningDir(), LEARNING_SESSION_FILE);
12060
12214
  }
12061
12215
  function stateFilePath() {
12062
- return path34.join(getLearningDir(), LEARNING_STATE_FILE);
12216
+ return path35.join(getLearningDir(), LEARNING_STATE_FILE);
12063
12217
  }
12064
12218
  function truncateResponse(response) {
12065
12219
  const str = JSON.stringify(response);
@@ -12069,10 +12223,10 @@ function truncateResponse(response) {
12069
12223
  function trimSessionFileIfNeeded(filePath) {
12070
12224
  const state = readState2();
12071
12225
  if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
12072
- const lines = fs42.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
12226
+ const lines = fs43.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
12073
12227
  if (lines.length > LEARNING_MAX_EVENTS) {
12074
12228
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
12075
- fs42.writeFileSync(filePath, kept.join("\n") + "\n");
12229
+ fs43.writeFileSync(filePath, kept.join("\n") + "\n");
12076
12230
  }
12077
12231
  }
12078
12232
  }
@@ -12080,19 +12234,19 @@ function appendEvent(event) {
12080
12234
  ensureLearningDir();
12081
12235
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
12082
12236
  const filePath = sessionFilePath();
12083
- fs42.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
12237
+ fs43.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
12084
12238
  trimSessionFileIfNeeded(filePath);
12085
12239
  }
12086
12240
  function appendPromptEvent(event) {
12087
12241
  ensureLearningDir();
12088
12242
  const filePath = sessionFilePath();
12089
- fs42.appendFileSync(filePath, JSON.stringify(event) + "\n");
12243
+ fs43.appendFileSync(filePath, JSON.stringify(event) + "\n");
12090
12244
  trimSessionFileIfNeeded(filePath);
12091
12245
  }
12092
12246
  function readAllEvents() {
12093
12247
  const filePath = sessionFilePath();
12094
- if (!fs42.existsSync(filePath)) return [];
12095
- const lines = fs42.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
12248
+ if (!fs43.existsSync(filePath)) return [];
12249
+ const lines = fs43.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
12096
12250
  const events = [];
12097
12251
  for (const line of lines) {
12098
12252
  try {
@@ -12104,26 +12258,26 @@ function readAllEvents() {
12104
12258
  }
12105
12259
  function getEventCount() {
12106
12260
  const filePath = sessionFilePath();
12107
- if (!fs42.existsSync(filePath)) return 0;
12108
- const content = fs42.readFileSync(filePath, "utf-8");
12261
+ if (!fs43.existsSync(filePath)) return 0;
12262
+ const content = fs43.readFileSync(filePath, "utf-8");
12109
12263
  return content.split("\n").filter(Boolean).length;
12110
12264
  }
12111
12265
  function clearSession() {
12112
12266
  const filePath = sessionFilePath();
12113
- if (fs42.existsSync(filePath)) fs42.unlinkSync(filePath);
12267
+ if (fs43.existsSync(filePath)) fs43.unlinkSync(filePath);
12114
12268
  }
12115
12269
  function readState2() {
12116
12270
  const filePath = stateFilePath();
12117
- if (!fs42.existsSync(filePath)) return { ...DEFAULT_STATE };
12271
+ if (!fs43.existsSync(filePath)) return { ...DEFAULT_STATE };
12118
12272
  try {
12119
- return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
12273
+ return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
12120
12274
  } catch {
12121
12275
  return { ...DEFAULT_STATE };
12122
12276
  }
12123
12277
  }
12124
12278
  function writeState2(state) {
12125
12279
  ensureLearningDir();
12126
- fs42.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
12280
+ fs43.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
12127
12281
  }
12128
12282
  function resetState() {
12129
12283
  writeState2({ ...DEFAULT_STATE });
@@ -12131,16 +12285,16 @@ function resetState() {
12131
12285
  var LOCK_FILE = "finalize.lock";
12132
12286
  var LOCK_STALE_MS = 5 * 60 * 1e3;
12133
12287
  function lockFilePath() {
12134
- return path34.join(getLearningDir(), LOCK_FILE);
12288
+ return path35.join(getLearningDir(), LOCK_FILE);
12135
12289
  }
12136
12290
  function acquireFinalizeLock() {
12137
12291
  ensureLearningDir();
12138
12292
  const lockPath = lockFilePath();
12139
- if (fs42.existsSync(lockPath)) {
12293
+ if (fs43.existsSync(lockPath)) {
12140
12294
  try {
12141
- const stat = fs42.statSync(lockPath);
12295
+ const stat = fs43.statSync(lockPath);
12142
12296
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
12143
- const pid = parseInt(fs42.readFileSync(lockPath, "utf-8").trim(), 10);
12297
+ const pid = parseInt(fs43.readFileSync(lockPath, "utf-8").trim(), 10);
12144
12298
  if (!isNaN(pid) && isProcessAlive(pid)) {
12145
12299
  return false;
12146
12300
  }
@@ -12148,12 +12302,12 @@ function acquireFinalizeLock() {
12148
12302
  } catch {
12149
12303
  }
12150
12304
  try {
12151
- fs42.unlinkSync(lockPath);
12305
+ fs43.unlinkSync(lockPath);
12152
12306
  } catch {
12153
12307
  }
12154
12308
  }
12155
12309
  try {
12156
- fs42.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
12310
+ fs43.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
12157
12311
  return true;
12158
12312
  } catch {
12159
12313
  return false;
@@ -12170,30 +12324,30 @@ function isProcessAlive(pid) {
12170
12324
  function releaseFinalizeLock() {
12171
12325
  const lockPath = lockFilePath();
12172
12326
  try {
12173
- if (fs42.existsSync(lockPath)) fs42.unlinkSync(lockPath);
12327
+ if (fs43.existsSync(lockPath)) fs43.unlinkSync(lockPath);
12174
12328
  } catch {
12175
12329
  }
12176
12330
  }
12177
12331
 
12178
12332
  // src/lib/notifications.ts
12179
- import fs43 from "fs";
12180
- import path35 from "path";
12333
+ import fs44 from "fs";
12334
+ import path36 from "path";
12181
12335
  import chalk22 from "chalk";
12182
12336
  function notificationFilePath() {
12183
- return path35.join(getLearningDir(), "last-finalize-summary.json");
12337
+ return path36.join(getLearningDir(), "last-finalize-summary.json");
12184
12338
  }
12185
12339
  function writeFinalizeSummary(summary) {
12186
12340
  try {
12187
12341
  ensureLearningDir();
12188
- fs43.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
12342
+ fs44.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
12189
12343
  } catch {
12190
12344
  }
12191
12345
  }
12192
12346
  function checkPendingNotifications() {
12193
12347
  try {
12194
- if (!fs43.existsSync(notificationFilePath())) return;
12195
- const raw = fs43.readFileSync(notificationFilePath(), "utf-8");
12196
- fs43.unlinkSync(notificationFilePath());
12348
+ if (!fs44.existsSync(notificationFilePath())) return;
12349
+ const raw = fs44.readFileSync(notificationFilePath(), "utf-8");
12350
+ fs44.unlinkSync(notificationFilePath());
12197
12351
  const summary = JSON.parse(raw);
12198
12352
  if (!summary.newItemCount || summary.newItemCount === 0) return;
12199
12353
  const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
@@ -12209,7 +12363,7 @@ function checkPendingNotifications() {
12209
12363
  console.log("");
12210
12364
  } catch {
12211
12365
  try {
12212
- fs43.unlinkSync(notificationFilePath());
12366
+ fs44.unlinkSync(notificationFilePath());
12213
12367
  } catch {
12214
12368
  }
12215
12369
  }
@@ -12361,8 +12515,8 @@ function calculateSessionWaste(events) {
12361
12515
  init_config();
12362
12516
 
12363
12517
  // src/learner/roi.ts
12364
- import fs44 from "fs";
12365
- import path36 from "path";
12518
+ import fs45 from "fs";
12519
+ import path37 from "path";
12366
12520
  var DEFAULT_TOTALS = {
12367
12521
  totalWasteTokens: 0,
12368
12522
  totalWasteSeconds: 0,
@@ -12376,19 +12530,19 @@ var DEFAULT_TOTALS = {
12376
12530
  lastSessionTimestamp: ""
12377
12531
  };
12378
12532
  function roiFilePath() {
12379
- return path36.join(getLearningDir(), LEARNING_ROI_FILE);
12533
+ return path37.join(getLearningDir(), LEARNING_ROI_FILE);
12380
12534
  }
12381
12535
  function readROIStats() {
12382
12536
  const filePath = roiFilePath();
12383
- if (!fs44.existsSync(filePath)) {
12537
+ if (!fs45.existsSync(filePath)) {
12384
12538
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
12385
12539
  }
12386
12540
  try {
12387
- return JSON.parse(fs44.readFileSync(filePath, "utf-8"));
12541
+ return JSON.parse(fs45.readFileSync(filePath, "utf-8"));
12388
12542
  } catch {
12389
12543
  try {
12390
12544
  const corruptPath = filePath + ".corrupt";
12391
- fs44.renameSync(filePath, corruptPath);
12545
+ fs45.renameSync(filePath, corruptPath);
12392
12546
  console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
12393
12547
  } catch {
12394
12548
  }
@@ -12397,7 +12551,7 @@ function readROIStats() {
12397
12551
  }
12398
12552
  function writeROIStats(stats) {
12399
12553
  ensureLearningDir();
12400
- fs44.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
12554
+ fs45.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
12401
12555
  }
12402
12556
  function recalculateTotals(stats) {
12403
12557
  const totals = stats.totals;
@@ -12606,9 +12760,9 @@ var AUTO_SETTLE_MS = 200;
12606
12760
  var INCREMENTAL_INTERVAL = 50;
12607
12761
  function writeFinalizeError(message) {
12608
12762
  try {
12609
- const errorPath = path37.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12610
- if (!fs45.existsSync(getLearningDir())) fs45.mkdirSync(getLearningDir(), { recursive: true });
12611
- fs45.writeFileSync(errorPath, JSON.stringify({
12763
+ const errorPath = path38.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12764
+ if (!fs46.existsSync(getLearningDir())) fs46.mkdirSync(getLearningDir(), { recursive: true });
12765
+ fs46.writeFileSync(errorPath, JSON.stringify({
12612
12766
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12613
12767
  error: message,
12614
12768
  pid: process.pid
@@ -12618,9 +12772,9 @@ function writeFinalizeError(message) {
12618
12772
  }
12619
12773
  function readFinalizeError() {
12620
12774
  try {
12621
- const errorPath = path37.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12622
- if (!fs45.existsSync(errorPath)) return null;
12623
- return JSON.parse(fs45.readFileSync(errorPath, "utf-8"));
12775
+ const errorPath = path38.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12776
+ if (!fs46.existsSync(errorPath)) return null;
12777
+ return JSON.parse(fs46.readFileSync(errorPath, "utf-8"));
12624
12778
  } catch {
12625
12779
  return null;
12626
12780
  }
@@ -12667,14 +12821,14 @@ async function learnObserveCommand(options) {
12667
12821
  const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
12668
12822
  const bin = resolveCaliber2();
12669
12823
  const { spawn: spawn4 } = await import("child_process");
12670
- const logPath = path37.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12671
- if (!fs45.existsSync(getLearningDir())) fs45.mkdirSync(getLearningDir(), { recursive: true });
12672
- const logFd = fs45.openSync(logPath, "a");
12824
+ const logPath = path38.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12825
+ if (!fs46.existsSync(getLearningDir())) fs46.mkdirSync(getLearningDir(), { recursive: true });
12826
+ const logFd = fs46.openSync(logPath, "a");
12673
12827
  spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
12674
12828
  detached: true,
12675
12829
  stdio: ["ignore", logFd, logFd]
12676
12830
  }).unref();
12677
- fs45.closeSync(logFd);
12831
+ fs46.closeSync(logFd);
12678
12832
  } catch {
12679
12833
  }
12680
12834
  }
@@ -12881,7 +13035,7 @@ async function learnFinalizeCommand(options) {
12881
13035
  }
12882
13036
  async function learnInstallCommand() {
12883
13037
  let anyInstalled = false;
12884
- if (fs45.existsSync(".claude")) {
13038
+ if (fs46.existsSync(".claude")) {
12885
13039
  const r = installLearningHooks();
12886
13040
  if (r.installed) {
12887
13041
  console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
@@ -12890,7 +13044,7 @@ async function learnInstallCommand() {
12890
13044
  console.log(chalk23.dim(" Claude Code hooks already installed"));
12891
13045
  }
12892
13046
  }
12893
- if (fs45.existsSync(".cursor")) {
13047
+ if (fs46.existsSync(".cursor")) {
12894
13048
  const r = installCursorLearningHooks();
12895
13049
  if (r.installed) {
12896
13050
  console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
@@ -12899,7 +13053,7 @@ async function learnInstallCommand() {
12899
13053
  console.log(chalk23.dim(" Cursor hooks already installed"));
12900
13054
  }
12901
13055
  }
12902
- if (!fs45.existsSync(".claude") && !fs45.existsSync(".cursor")) {
13056
+ if (!fs46.existsSync(".claude") && !fs46.existsSync(".cursor")) {
12903
13057
  console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
12904
13058
  console.log(chalk23.dim(` Run \`${resolveCaliber()} init\` first, or create the directory manually.`));
12905
13059
  return;
@@ -12957,8 +13111,8 @@ async function learnStatusCommand() {
12957
13111
  if (lastError) {
12958
13112
  console.log(`Last error: ${chalk23.red(lastError.error)}`);
12959
13113
  console.log(chalk23.dim(` at ${lastError.timestamp}`));
12960
- const logPath = path37.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12961
- if (fs45.existsSync(logPath)) {
13114
+ const logPath = path38.join(getLearningDir(), LEARNING_FINALIZE_LOG);
13115
+ if (fs46.existsSync(logPath)) {
12962
13116
  console.log(chalk23.dim(` Full log: ${logPath}`));
12963
13117
  }
12964
13118
  }
@@ -13038,11 +13192,11 @@ async function learnDeleteCommand(indexStr) {
13038
13192
  }
13039
13193
  const item = items[targetIdx];
13040
13194
  const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
13041
- if (!fs45.existsSync(filePath)) {
13195
+ if (!fs46.existsSync(filePath)) {
13042
13196
  console.log(chalk23.red("Learnings file not found."));
13043
13197
  return;
13044
13198
  }
13045
- const content = fs45.readFileSync(filePath, "utf-8");
13199
+ const content = fs46.readFileSync(filePath, "utf-8");
13046
13200
  const lines = content.split("\n");
13047
13201
  const bulletsOfSource = items.filter((i) => i.source === item.source);
13048
13202
  const posInFile = bulletsOfSource.indexOf(item);
@@ -13063,9 +13217,9 @@ async function learnDeleteCommand(indexStr) {
13063
13217
  }
13064
13218
  const bulletToRemove = lines[lineToRemove];
13065
13219
  const newLines = lines.filter((_, i) => i !== lineToRemove);
13066
- fs45.writeFileSync(filePath, newLines.join("\n"));
13220
+ fs46.writeFileSync(filePath, newLines.join("\n"));
13067
13221
  if (item.source === "personal") {
13068
- fs45.chmodSync(filePath, 384);
13222
+ fs46.chmodSync(filePath, 384);
13069
13223
  }
13070
13224
  const roiStats = readROIStats();
13071
13225
  const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
@@ -13238,8 +13392,8 @@ async function insightsCommand(options) {
13238
13392
  }
13239
13393
 
13240
13394
  // src/commands/sources.ts
13241
- import fs46 from "fs";
13242
- import path38 from "path";
13395
+ import fs47 from "fs";
13396
+ import path39 from "path";
13243
13397
  import chalk25 from "chalk";
13244
13398
  init_resolve_caliber();
13245
13399
  async function sourcesListCommand() {
@@ -13256,9 +13410,9 @@ async function sourcesListCommand() {
13256
13410
  if (configSources.length > 0) {
13257
13411
  for (const source of configSources) {
13258
13412
  const sourcePath = source.path || source.url || "";
13259
- const exists = source.path ? fs46.existsSync(path38.resolve(dir, source.path)) : false;
13413
+ const exists = source.path ? fs47.existsSync(path39.resolve(dir, source.path)) : false;
13260
13414
  const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
13261
- const hasSummary = source.path && fs46.existsSync(path38.join(path38.resolve(dir, source.path), ".caliber", "summary.json"));
13415
+ const hasSummary = source.path && fs47.existsSync(path39.join(path39.resolve(dir, source.path), ".caliber", "summary.json"));
13262
13416
  console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
13263
13417
  console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
13264
13418
  if (source.description) console.log(` ${chalk25.dim(source.description)}`);
@@ -13268,7 +13422,7 @@ async function sourcesListCommand() {
13268
13422
  if (workspaces.length > 0) {
13269
13423
  console.log(chalk25.dim(" Auto-detected workspaces:"));
13270
13424
  for (const ws of workspaces) {
13271
- const exists = fs46.existsSync(path38.resolve(dir, ws));
13425
+ const exists = fs47.existsSync(path39.resolve(dir, ws));
13272
13426
  console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
13273
13427
  }
13274
13428
  console.log("");
@@ -13276,8 +13430,8 @@ async function sourcesListCommand() {
13276
13430
  }
13277
13431
  async function sourcesAddCommand(sourcePath) {
13278
13432
  const dir = process.cwd();
13279
- const absPath = path38.resolve(dir, sourcePath);
13280
- if (!fs46.existsSync(absPath)) {
13433
+ const absPath = path39.resolve(dir, sourcePath);
13434
+ if (!fs47.existsSync(absPath)) {
13281
13435
  console.log(chalk25.red(`
13282
13436
  Path not found: ${sourcePath}
13283
13437
  `));
@@ -13292,7 +13446,7 @@ async function sourcesAddCommand(sourcePath) {
13292
13446
  }
13293
13447
  const existing = loadSourcesConfig(dir);
13294
13448
  const alreadyConfigured = existing.some(
13295
- (s) => s.path && path38.resolve(dir, s.path) === absPath
13449
+ (s) => s.path && path39.resolve(dir, s.path) === absPath
13296
13450
  );
13297
13451
  if (alreadyConfigured) {
13298
13452
  console.log(chalk25.yellow(`
@@ -13340,8 +13494,8 @@ async function sourcesRemoveCommand(name) {
13340
13494
  }
13341
13495
 
13342
13496
  // src/commands/publish.ts
13343
- import fs47 from "fs";
13344
- import path39 from "path";
13497
+ import fs48 from "fs";
13498
+ import path40 from "path";
13345
13499
  import chalk26 from "chalk";
13346
13500
  import ora7 from "ora";
13347
13501
  init_config();
@@ -13356,10 +13510,10 @@ async function publishCommand() {
13356
13510
  const spinner = ora7("Generating project summary...").start();
13357
13511
  try {
13358
13512
  const fingerprint = await collectFingerprint(dir);
13359
- const claudeMd = readFileOrNull(path39.join(dir, "CLAUDE.md"));
13513
+ const claudeMd = readFileOrNull(path40.join(dir, "CLAUDE.md"));
13360
13514
  const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
13361
13515
  const summary = {
13362
- name: fingerprint.packageName || path39.basename(dir),
13516
+ name: fingerprint.packageName || path40.basename(dir),
13363
13517
  version: "1.0.0",
13364
13518
  description: fingerprint.description || "",
13365
13519
  languages: fingerprint.languages,
@@ -13371,7 +13525,7 @@ async function publishCommand() {
13371
13525
  summary.conventions = claudeMd.slice(0, 2e3);
13372
13526
  }
13373
13527
  try {
13374
- const pkgContent = readFileOrNull(path39.join(dir, "package.json"));
13528
+ const pkgContent = readFileOrNull(path40.join(dir, "package.json"));
13375
13529
  if (pkgContent) {
13376
13530
  const pkg3 = JSON.parse(pkgContent);
13377
13531
  if (pkg3.scripts) {
@@ -13384,14 +13538,14 @@ async function publishCommand() {
13384
13538
  }
13385
13539
  } catch {
13386
13540
  }
13387
- const outputDir = path39.join(dir, ".caliber");
13388
- if (!fs47.existsSync(outputDir)) {
13389
- fs47.mkdirSync(outputDir, { recursive: true });
13541
+ const outputDir = path40.join(dir, ".caliber");
13542
+ if (!fs48.existsSync(outputDir)) {
13543
+ fs48.mkdirSync(outputDir, { recursive: true });
13390
13544
  }
13391
- const outputPath = path39.join(outputDir, "summary.json");
13392
- fs47.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
13545
+ const outputPath = path40.join(outputDir, "summary.json");
13546
+ fs48.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
13393
13547
  spinner.succeed("Project summary published");
13394
- console.log(` ${chalk26.green("\u2713")} ${path39.relative(dir, outputPath)}`);
13548
+ console.log(` ${chalk26.green("\u2713")} ${path40.relative(dir, outputPath)}`);
13395
13549
  console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
13396
13550
  console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
13397
13551
  } catch (err) {
@@ -13404,7 +13558,7 @@ async function publishCommand() {
13404
13558
 
13405
13559
  // src/commands/bootstrap.ts
13406
13560
  init_builtin_skills();
13407
- import fs48 from "fs";
13561
+ import fs49 from "fs";
13408
13562
  import chalk27 from "chalk";
13409
13563
  var PLATFORM_SKILL_DIRS = {
13410
13564
  claude: ".claude/skills",
@@ -13425,8 +13579,8 @@ async function bootstrapCommand() {
13425
13579
  for (const skill of BUILTIN_SKILLS) {
13426
13580
  const skillDir = `${skillsDir}/${skill.name}`;
13427
13581
  const skillPath = `${skillDir}/SKILL.md`;
13428
- fs48.mkdirSync(skillDir, { recursive: true });
13429
- fs48.writeFileSync(skillPath, buildSkillContent(skill));
13582
+ fs49.mkdirSync(skillDir, { recursive: true });
13583
+ fs49.writeFileSync(skillPath, buildSkillContent(skill));
13430
13584
  written.push(skillPath);
13431
13585
  }
13432
13586
  }
@@ -13443,8 +13597,8 @@ async function bootstrapCommand() {
13443
13597
  }
13444
13598
 
13445
13599
  // src/commands/uninstall.ts
13446
- import fs49 from "fs";
13447
- import path40 from "path";
13600
+ import fs50 from "fs";
13601
+ import path41 from "path";
13448
13602
  import chalk28 from "chalk";
13449
13603
  import confirm3 from "@inquirer/confirm";
13450
13604
  init_pre_commit_block();
@@ -13452,18 +13606,18 @@ init_builtin_skills();
13452
13606
  init_config();
13453
13607
  var MANAGED_DOC_FILES = [
13454
13608
  "CLAUDE.md",
13455
- path40.join(".github", "copilot-instructions.md"),
13609
+ path41.join(".github", "copilot-instructions.md"),
13456
13610
  "AGENTS.md"
13457
13611
  ];
13458
13612
  var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
13459
- var CURSOR_RULES_DIR = path40.join(".cursor", "rules");
13613
+ var CURSOR_RULES_DIR = path41.join(".cursor", "rules");
13460
13614
  function removeCaliberCursorRules() {
13461
13615
  const removed = [];
13462
- if (!fs49.existsSync(CURSOR_RULES_DIR)) return removed;
13463
- for (const file of fs49.readdirSync(CURSOR_RULES_DIR)) {
13616
+ if (!fs50.existsSync(CURSOR_RULES_DIR)) return removed;
13617
+ for (const file of fs50.readdirSync(CURSOR_RULES_DIR)) {
13464
13618
  if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
13465
- const fullPath = path40.join(CURSOR_RULES_DIR, file);
13466
- fs49.unlinkSync(fullPath);
13619
+ const fullPath = path41.join(CURSOR_RULES_DIR, file);
13620
+ fs50.unlinkSync(fullPath);
13467
13621
  removed.push(fullPath);
13468
13622
  }
13469
13623
  }
@@ -13472,11 +13626,11 @@ function removeCaliberCursorRules() {
13472
13626
  function removeBuiltinSkills() {
13473
13627
  const removed = [];
13474
13628
  for (const skillsDir of SKILL_DIRS) {
13475
- if (!fs49.existsSync(skillsDir)) continue;
13629
+ if (!fs50.existsSync(skillsDir)) continue;
13476
13630
  for (const name of BUILTIN_SKILL_NAMES) {
13477
- const skillDir = path40.join(skillsDir, name);
13478
- if (fs49.existsSync(skillDir)) {
13479
- fs49.rmSync(skillDir, { recursive: true });
13631
+ const skillDir = path41.join(skillsDir, name);
13632
+ if (fs50.existsSync(skillDir)) {
13633
+ fs50.rmSync(skillDir, { recursive: true });
13480
13634
  removed.push(skillDir);
13481
13635
  }
13482
13636
  }
@@ -13486,15 +13640,15 @@ function removeBuiltinSkills() {
13486
13640
  function stripManagedBlocksFromFiles() {
13487
13641
  const modified = [];
13488
13642
  for (const filePath of MANAGED_DOC_FILES) {
13489
- if (!fs49.existsSync(filePath)) continue;
13490
- const original = fs49.readFileSync(filePath, "utf-8");
13643
+ if (!fs50.existsSync(filePath)) continue;
13644
+ const original = fs50.readFileSync(filePath, "utf-8");
13491
13645
  const stripped = stripManagedBlocks(original);
13492
13646
  if (stripped !== original) {
13493
13647
  const trimmed = stripped.trim();
13494
13648
  if (!trimmed || /^#\s*\S*$/.test(trimmed)) {
13495
- fs49.unlinkSync(filePath);
13649
+ fs50.unlinkSync(filePath);
13496
13650
  } else {
13497
- fs49.writeFileSync(filePath, stripped);
13651
+ fs50.writeFileSync(filePath, stripped);
13498
13652
  }
13499
13653
  modified.push(filePath);
13500
13654
  }
@@ -13502,8 +13656,8 @@ function stripManagedBlocksFromFiles() {
13502
13656
  return modified;
13503
13657
  }
13504
13658
  function removeDirectory(dir) {
13505
- if (!fs49.existsSync(dir)) return false;
13506
- fs49.rmSync(dir, { recursive: true });
13659
+ if (!fs50.existsSync(dir)) return false;
13660
+ fs50.rmSync(dir, { recursive: true });
13507
13661
  return true;
13508
13662
  }
13509
13663
  async function uninstallCommand(options) {
@@ -13560,8 +13714,8 @@ async function uninstallCommand(options) {
13560
13714
  console.log(` ${chalk28.red("\u2717")} ${skill}/`);
13561
13715
  }
13562
13716
  if (removedSkills.length > 0) actions.push("builtin skills");
13563
- if (fs49.existsSync("CALIBER_LEARNINGS.md")) {
13564
- fs49.unlinkSync("CALIBER_LEARNINGS.md");
13717
+ if (fs50.existsSync("CALIBER_LEARNINGS.md")) {
13718
+ fs50.unlinkSync("CALIBER_LEARNINGS.md");
13565
13719
  console.log(` ${chalk28.red("\u2717")} CALIBER_LEARNINGS.md`);
13566
13720
  actions.push("learnings file");
13567
13721
  }
@@ -13575,18 +13729,18 @@ async function uninstallCommand(options) {
13575
13729
  }
13576
13730
  trackUninstallExecuted();
13577
13731
  const configPath = getConfigFilePath();
13578
- if (fs49.existsSync(configPath)) {
13732
+ if (fs50.existsSync(configPath)) {
13579
13733
  console.log("");
13580
13734
  const removeConfig = options.force || await confirm3({
13581
13735
  message: `Remove global config (~/.caliber/config.json)? This affects all projects.`
13582
13736
  });
13583
13737
  if (removeConfig) {
13584
- fs49.unlinkSync(configPath);
13738
+ fs50.unlinkSync(configPath);
13585
13739
  console.log(` ${chalk28.red("\u2717")} ${configPath}`);
13586
- const configDir = path40.dirname(configPath);
13740
+ const configDir = path41.dirname(configPath);
13587
13741
  try {
13588
- const remaining = fs49.readdirSync(configDir);
13589
- if (remaining.length === 0) fs49.rmdirSync(configDir);
13742
+ const remaining = fs50.readdirSync(configDir);
13743
+ if (remaining.length === 0) fs50.rmdirSync(configDir);
13590
13744
  } catch {
13591
13745
  }
13592
13746
  }
@@ -13597,8 +13751,8 @@ async function uninstallCommand(options) {
13597
13751
  }
13598
13752
 
13599
13753
  // src/cli.ts
13600
- var __dirname = path41.dirname(fileURLToPath(import.meta.url));
13601
- var pkg = JSON.parse(fs50.readFileSync(path41.resolve(__dirname, "..", "package.json"), "utf-8"));
13754
+ var __dirname = path42.dirname(fileURLToPath(import.meta.url));
13755
+ var pkg = JSON.parse(fs51.readFileSync(path42.resolve(__dirname, "..", "package.json"), "utf-8"));
13602
13756
  var program = new Command();
13603
13757
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
13604
13758
  program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI context infrastructure for coding agents").version(displayVersion).option("--no-traces", "Disable anonymous telemetry for this run");
@@ -13709,15 +13863,15 @@ learn.command("delete <index>").description("Delete a learning by its index numb
13709
13863
  learn.command("add <content>").description("Add a learning directly (used by agent skills)").option("--personal", "Save as a personal learning instead of project-level").action(tracked("learn:add", learnAddCommand));
13710
13864
 
13711
13865
  // src/utils/version-check.ts
13712
- import fs51 from "fs";
13713
- import path42 from "path";
13866
+ import fs52 from "fs";
13867
+ import path43 from "path";
13714
13868
  import { fileURLToPath as fileURLToPath2 } from "url";
13715
13869
  import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
13716
13870
  import chalk29 from "chalk";
13717
13871
  import ora8 from "ora";
13718
13872
  import confirm4 from "@inquirer/confirm";
13719
- var __dirname_vc = path42.dirname(fileURLToPath2(import.meta.url));
13720
- var pkg2 = JSON.parse(fs51.readFileSync(path42.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
13873
+ var __dirname_vc = path43.dirname(fileURLToPath2(import.meta.url));
13874
+ var pkg2 = JSON.parse(fs52.readFileSync(path43.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
13721
13875
  function getChannel(version) {
13722
13876
  const match = version.match(/-(dev|next)\./);
13723
13877
  return match ? match[1] : "latest";
@@ -13744,8 +13898,8 @@ function getInstalledVersion() {
13744
13898
  encoding: "utf-8",
13745
13899
  stdio: ["pipe", "pipe", "pipe"]
13746
13900
  }).trim();
13747
- const pkgPath = path42.join(globalRoot, "@rely-ai", "caliber", "package.json");
13748
- return JSON.parse(fs51.readFileSync(pkgPath, "utf-8")).version;
13901
+ const pkgPath = path43.join(globalRoot, "@rely-ai", "caliber", "package.json");
13902
+ return JSON.parse(fs52.readFileSync(pkgPath, "utf-8")).version;
13749
13903
  } catch {
13750
13904
  return null;
13751
13905
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.37.1",
3
+ "version": "1.38.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": {