@rely-ai/caliber 1.37.0 → 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 +711 -366
  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
@@ -2352,6 +2352,55 @@ function isRateLimitError(stderr) {
2352
2352
  return /rate limit|too many requests|429/i.test(stderr);
2353
2353
  }
2354
2354
 
2355
+ // src/llm/utils.ts
2356
+ function extractJson(text) {
2357
+ const startIdx = text.search(/[[{]/);
2358
+ if (startIdx === -1) return null;
2359
+ let depth = 0;
2360
+ let inString = false;
2361
+ let escaped = false;
2362
+ for (let i = startIdx; i < text.length; i++) {
2363
+ const ch = text[i];
2364
+ if (escaped) {
2365
+ escaped = false;
2366
+ continue;
2367
+ }
2368
+ if (ch === "\\" && inString) {
2369
+ escaped = true;
2370
+ continue;
2371
+ }
2372
+ if (ch === '"') {
2373
+ inString = !inString;
2374
+ continue;
2375
+ }
2376
+ if (inString) continue;
2377
+ if (ch === "{" || ch === "[") depth++;
2378
+ if (ch === "}" || ch === "]") {
2379
+ depth--;
2380
+ if (depth === 0) return text.slice(startIdx, i + 1);
2381
+ }
2382
+ }
2383
+ return null;
2384
+ }
2385
+ function stripMarkdownFences(text) {
2386
+ return text.replace(/^```(?:json)?\s*/im, "").replace(/```\s*$/m, "").trim();
2387
+ }
2388
+ function parseJsonResponse(raw) {
2389
+ const cleaned = stripMarkdownFences(raw);
2390
+ try {
2391
+ return JSON.parse(cleaned);
2392
+ } catch {
2393
+ }
2394
+ const json = extractJson(cleaned);
2395
+ if (!json) {
2396
+ throw new Error(`No JSON found in LLM response: ${raw.slice(0, 200)}`);
2397
+ }
2398
+ return JSON.parse(json);
2399
+ }
2400
+ function estimateTokens(text) {
2401
+ return Math.ceil(text.length / 4);
2402
+ }
2403
+
2355
2404
  // src/llm/cursor-acp.ts
2356
2405
  var AGENT_BIN = "agent";
2357
2406
  var IS_WINDOWS = process.platform === "win32";
@@ -2376,12 +2425,33 @@ var CursorAcpProvider = class {
2376
2425
  async call(options) {
2377
2426
  const prompt = this.buildPrompt(options);
2378
2427
  const model = options.model || this.defaultModel;
2379
- return this.runPrint(model, prompt);
2428
+ const result = await this.runPrint(model, prompt);
2429
+ trackUsage(model, {
2430
+ inputTokens: estimateTokens(prompt),
2431
+ outputTokens: estimateTokens(result)
2432
+ });
2433
+ return result;
2380
2434
  }
2381
2435
  async stream(options, callbacks) {
2382
2436
  const prompt = this.buildPrompt(options);
2383
2437
  const model = options.model || this.defaultModel;
2384
- return this.runPrintStream(model, prompt, callbacks);
2438
+ const inputEstimate = estimateTokens(prompt);
2439
+ let outputChars = 0;
2440
+ const wrappedCallbacks = {
2441
+ onText: (text) => {
2442
+ outputChars += text.length;
2443
+ callbacks.onText(text);
2444
+ },
2445
+ onEnd: (meta) => {
2446
+ trackUsage(model, {
2447
+ inputTokens: inputEstimate,
2448
+ outputTokens: Math.ceil(outputChars / 4)
2449
+ });
2450
+ callbacks.onEnd(meta);
2451
+ },
2452
+ onError: (err) => callbacks.onError(err)
2453
+ };
2454
+ return this.runPrintStream(model, prompt, wrappedCallbacks);
2385
2455
  }
2386
2456
  /**
2387
2457
  * Pre-spawn an agent process so it's ready when the first call comes.
@@ -2473,7 +2543,11 @@ var CursorAcpProvider = class {
2473
2543
  this.killWithEscalation(child);
2474
2544
  if (!settled) {
2475
2545
  settled = true;
2476
- reject(new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`));
2546
+ reject(
2547
+ new Error(
2548
+ `Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`
2549
+ )
2550
+ );
2477
2551
  }
2478
2552
  }, this.timeoutMs);
2479
2553
  timer.unref();
@@ -2509,7 +2583,9 @@ var CursorAcpProvider = class {
2509
2583
  this.killWithEscalation(child);
2510
2584
  if (!settled) {
2511
2585
  settled = true;
2512
- const err = new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`);
2586
+ const err = new Error(
2587
+ `Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`
2588
+ );
2513
2589
  callbacks.onError(err);
2514
2590
  reject(err);
2515
2591
  }
@@ -2616,7 +2692,10 @@ function isCursorAgentAvailable() {
2616
2692
  }
2617
2693
  function isCursorLoggedIn() {
2618
2694
  try {
2619
- const result = execSync5(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
2695
+ const result = execSync5(`${AGENT_BIN} status`, {
2696
+ stdio: ["ignore", "pipe", "ignore"],
2697
+ timeout: 5e3
2698
+ });
2620
2699
  return !result.toString().includes("not logged in");
2621
2700
  } catch {
2622
2701
  return false;
@@ -2653,20 +2732,29 @@ var ClaudeCliProvider = class {
2653
2732
  }
2654
2733
  async call(options) {
2655
2734
  const combined = this.buildCombinedPrompt(options);
2656
- return this.runClaudePrint(combined, options.model);
2735
+ const result = await this.runClaudePrint(combined, options.model);
2736
+ trackUsage(options.model || this.defaultModel, {
2737
+ inputTokens: estimateTokens(combined),
2738
+ outputTokens: estimateTokens(result)
2739
+ });
2740
+ return result;
2657
2741
  }
2658
2742
  async stream(options, callbacks) {
2659
2743
  const combined = this.buildCombinedPrompt(options);
2744
+ const inputEstimate = estimateTokens(combined);
2660
2745
  const args = ["-p"];
2661
2746
  if (options.model) args.push("--model", options.model);
2662
2747
  const child = spawnClaude(args);
2663
2748
  child.stdin.end(combined);
2664
2749
  let settled = false;
2750
+ let outputChars = 0;
2665
2751
  const chunks = [];
2666
2752
  const stderrChunks = [];
2667
2753
  child.stdout.on("data", (chunk) => {
2668
2754
  chunks.push(chunk);
2669
- callbacks.onText(chunk.toString("utf-8"));
2755
+ const text = chunk.toString("utf-8");
2756
+ outputChars += text.length;
2757
+ callbacks.onText(text);
2670
2758
  });
2671
2759
  child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
2672
2760
  const timer = setTimeout(() => {
@@ -2692,6 +2780,11 @@ var ClaudeCliProvider = class {
2692
2780
  if (settled) return;
2693
2781
  settled = true;
2694
2782
  if (code === 0) {
2783
+ const model = options.model || this.defaultModel;
2784
+ trackUsage(model, {
2785
+ inputTokens: inputEstimate,
2786
+ outputTokens: Math.ceil(outputChars / 4)
2787
+ });
2695
2788
  callbacks.onEnd({ stopReason: "end_turn" });
2696
2789
  } else {
2697
2790
  const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
@@ -2766,55 +2859,6 @@ function isClaudeCliAvailable() {
2766
2859
  }
2767
2860
  }
2768
2861
 
2769
- // src/llm/utils.ts
2770
- function extractJson(text) {
2771
- const startIdx = text.search(/[[{]/);
2772
- if (startIdx === -1) return null;
2773
- let depth = 0;
2774
- let inString = false;
2775
- let escaped = false;
2776
- for (let i = startIdx; i < text.length; i++) {
2777
- const ch = text[i];
2778
- if (escaped) {
2779
- escaped = false;
2780
- continue;
2781
- }
2782
- if (ch === "\\" && inString) {
2783
- escaped = true;
2784
- continue;
2785
- }
2786
- if (ch === '"') {
2787
- inString = !inString;
2788
- continue;
2789
- }
2790
- if (inString) continue;
2791
- if (ch === "{" || ch === "[") depth++;
2792
- if (ch === "}" || ch === "]") {
2793
- depth--;
2794
- if (depth === 0) return text.slice(startIdx, i + 1);
2795
- }
2796
- }
2797
- return null;
2798
- }
2799
- function stripMarkdownFences(text) {
2800
- return text.replace(/^```(?:json)?\s*/im, "").replace(/```\s*$/m, "").trim();
2801
- }
2802
- function parseJsonResponse(raw) {
2803
- const cleaned = stripMarkdownFences(raw);
2804
- try {
2805
- return JSON.parse(cleaned);
2806
- } catch {
2807
- }
2808
- const json = extractJson(cleaned);
2809
- if (!json) {
2810
- throw new Error(`No JSON found in LLM response: ${raw.slice(0, 200)}`);
2811
- }
2812
- return JSON.parse(json);
2813
- }
2814
- function estimateTokens(text) {
2815
- return Math.ceil(text.length / 4);
2816
- }
2817
-
2818
2862
  // src/llm/model-recovery.ts
2819
2863
  init_config();
2820
2864
  init_resolve_caliber();
@@ -3651,7 +3695,7 @@ function getDetectedWorkspaces(dir) {
3651
3695
 
3652
3696
  // src/fingerprint/index.ts
3653
3697
  async function collectFingerprint(dir) {
3654
- const gitRemoteUrl = getGitRemoteUrl();
3698
+ const gitRemoteUrl = getGitRemoteUrl(dir);
3655
3699
  const fileTree = getFileTree(dir);
3656
3700
  const existingConfigs = readExistingConfigs(dir);
3657
3701
  const packageName = readPackageName(dir);
@@ -4654,15 +4698,15 @@ init_config();
4654
4698
  // src/utils/dependencies.ts
4655
4699
  import { readFileSync as readFileSync2 } from "fs";
4656
4700
  import { join as join2 } from "path";
4657
- function readFileOrNull2(path43) {
4701
+ function readFileOrNull2(path44) {
4658
4702
  try {
4659
- return readFileSync2(path43, "utf-8");
4703
+ return readFileSync2(path44, "utf-8");
4660
4704
  } catch {
4661
4705
  return null;
4662
4706
  }
4663
4707
  }
4664
- function readJsonOrNull(path43) {
4665
- const content = readFileOrNull2(path43);
4708
+ function readJsonOrNull(path44) {
4709
+ const content = readFileOrNull2(path44);
4666
4710
  if (!content) return null;
4667
4711
  try {
4668
4712
  return JSON.parse(content);
@@ -4761,9 +4805,25 @@ function isTransientError2(error) {
4761
4805
  async function generateSetup(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks, options) {
4762
4806
  const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95 || options?.forceTargetedFix;
4763
4807
  if (isTargetedFix) {
4764
- return generateMonolithic(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks);
4808
+ return generateMonolithic(
4809
+ fingerprint,
4810
+ targetAgent,
4811
+ prompt,
4812
+ callbacks,
4813
+ failingChecks,
4814
+ currentScore,
4815
+ passingChecks
4816
+ );
4765
4817
  }
4766
- const coreResult = await generateCore(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks);
4818
+ const coreResult = await generateCore(
4819
+ fingerprint,
4820
+ targetAgent,
4821
+ prompt,
4822
+ callbacks,
4823
+ failingChecks,
4824
+ currentScore,
4825
+ passingChecks
4826
+ );
4767
4827
  if (!coreResult.setup) {
4768
4828
  return coreResult;
4769
4829
  }
@@ -4812,6 +4872,7 @@ function mergeSkillResults(results, setup) {
4812
4872
  }
4813
4873
  return { succeeded, failed };
4814
4874
  }
4875
+ var MAX_SKILL_TOPICS = 5;
4815
4876
  function collectSkillTopics(setup, targetAgent, fingerprint) {
4816
4877
  const topics = [];
4817
4878
  for (const platform of ["claude", "codex", "opencode", "cursor"]) {
@@ -4832,12 +4893,18 @@ function collectSkillTopics(setup, targetAgent, fingerprint) {
4832
4893
  delete platformObj.skillTopics;
4833
4894
  }
4834
4895
  }
4835
- return topics;
4896
+ return topics.slice(0, MAX_SKILL_TOPICS);
4836
4897
  }
4837
4898
  function getDefaultSkillTopics(fingerprint) {
4838
4899
  const topics = [
4839
- { name: "development-workflow", description: "Development setup and common workflows. Use when starting development, running the project, or setting up the environment." },
4840
- { name: "testing-guide", description: "Testing patterns and commands. Use when writing tests, running test suites, or debugging test failures." }
4900
+ {
4901
+ name: "development-workflow",
4902
+ description: "Development setup and common workflows. Use when starting development, running the project, or setting up the environment."
4903
+ },
4904
+ {
4905
+ name: "testing-guide",
4906
+ description: "Testing patterns and commands. Use when writing tests, running test suites, or debugging test failures."
4907
+ }
4841
4908
  ];
4842
4909
  if (fingerprint.frameworks.length > 0) {
4843
4910
  topics.push({
@@ -4855,8 +4922,10 @@ function getDefaultSkillTopics(fingerprint) {
4855
4922
  function buildSkillContext(fingerprint, setup, allDeps) {
4856
4923
  const parts = [];
4857
4924
  if (fingerprint.packageName) parts.push(`Project: ${fingerprint.packageName}`);
4858
- if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
4859
- if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
4925
+ if (fingerprint.languages.length > 0)
4926
+ parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
4927
+ if (fingerprint.frameworks.length > 0)
4928
+ parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
4860
4929
  const claude = setup.claude;
4861
4930
  const claudeMd = claude?.claudeMd;
4862
4931
  if (claudeMd) {
@@ -4943,7 +5012,9 @@ async function streamGeneration(config) {
4943
5012
  }
4944
5013
  }
4945
5014
  sentStatuses = completedLines.length;
4946
- const jsonStartMatch = preJsonBuffer.match(/(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/);
5015
+ const jsonStartMatch = preJsonBuffer.match(
5016
+ /(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/
5017
+ );
4947
5018
  if (jsonStartMatch) {
4948
5019
  const matchIndex = preJsonBuffer.indexOf("{", jsonStartMatch.index);
4949
5020
  inJson = true;
@@ -4958,7 +5029,9 @@ async function streamGeneration(config) {
4958
5029
  let setup = null;
4959
5030
  let jsonToParse = (jsonContent || preJsonBuffer).replace(/```\s*$/g, "").trim();
4960
5031
  if (!jsonContent && preJsonBuffer) {
4961
- const fallbackMatch = preJsonBuffer.match(/(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/);
5032
+ const fallbackMatch = preJsonBuffer.match(
5033
+ /(?:^|\n)\s*(?:```json\s*\n\s*)?\{(?=\s*")/
5034
+ );
4962
5035
  if (fallbackMatch) {
4963
5036
  const matchIndex = preJsonBuffer.indexOf("{", fallbackMatch.index);
4964
5037
  jsonToParse = preJsonBuffer.slice(matchIndex).replace(/```\s*$/g, "").trim();
@@ -4969,7 +5042,10 @@ async function streamGeneration(config) {
4969
5042
  } catch {
4970
5043
  }
4971
5044
  if (!setup && stopReason === "max_tokens" && attempt < MAX_RETRIES2) {
4972
- if (config.callbacks) config.callbacks.onStatus("Output was truncated, retrying with higher token limit...");
5045
+ if (config.callbacks)
5046
+ config.callbacks.onStatus(
5047
+ "Output was truncated, retrying with higher token limit..."
5048
+ );
4973
5049
  setTimeout(() => attemptGeneration().then(resolve3), 1e3);
4974
5050
  return;
4975
5051
  }
@@ -4982,12 +5058,18 @@ async function streamGeneration(config) {
4982
5058
  if (config.callbacks) config.callbacks.onComplete(setup, explanation);
4983
5059
  resolve3({ setup, explanation, stopReason: stopReason ?? void 0 });
4984
5060
  } else {
4985
- resolve3({ setup: null, explanation, raw: preJsonBuffer, stopReason: stopReason ?? void 0 });
5061
+ resolve3({
5062
+ setup: null,
5063
+ explanation,
5064
+ raw: preJsonBuffer,
5065
+ stopReason: stopReason ?? void 0
5066
+ });
4986
5067
  }
4987
5068
  },
4988
5069
  onError: (error) => {
4989
5070
  if (isTransientError2(error) && attempt < MAX_RETRIES2) {
4990
- if (config.callbacks) config.callbacks.onStatus("Connection interrupted, retrying...");
5071
+ if (config.callbacks)
5072
+ config.callbacks.onStatus("Connection interrupted, retrying...");
4991
5073
  setTimeout(() => attemptGeneration().then(resolve3), 2e3);
4992
5074
  return;
4993
5075
  }
@@ -5004,7 +5086,14 @@ async function streamGeneration(config) {
5004
5086
  return attemptGeneration();
5005
5087
  }
5006
5088
  async function generateCore(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks) {
5007
- const userMessage = buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks);
5089
+ const userMessage = buildGeneratePrompt(
5090
+ fingerprint,
5091
+ targetAgent,
5092
+ prompt,
5093
+ failingChecks,
5094
+ currentScore,
5095
+ passingChecks
5096
+ );
5008
5097
  return streamGeneration({
5009
5098
  systemPrompt: CORE_GENERATION_PROMPT,
5010
5099
  userMessage,
@@ -5015,7 +5104,14 @@ async function generateCore(fingerprint, targetAgent, prompt, callbacks, failing
5015
5104
  });
5016
5105
  }
5017
5106
  async function generateMonolithic(fingerprint, targetAgent, prompt, callbacks, failingChecks, currentScore, passingChecks) {
5018
- const userMessage = buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks);
5107
+ const userMessage = buildGeneratePrompt(
5108
+ fingerprint,
5109
+ targetAgent,
5110
+ prompt,
5111
+ failingChecks,
5112
+ currentScore,
5113
+ passingChecks
5114
+ );
5019
5115
  return streamGeneration({
5020
5116
  systemPrompt: GENERATION_SYSTEM_PROMPT,
5021
5117
  userMessage,
@@ -5096,9 +5192,11 @@ function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, cu
5096
5192
  const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
5097
5193
  if (isTargetedFix) {
5098
5194
  parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
5099
- parts.push(`
5195
+ parts.push(
5196
+ `
5100
5197
  The existing config is already high quality. ONLY fix these specific failing checks:
5101
- `);
5198
+ `
5199
+ );
5102
5200
  for (const check of failingChecks) {
5103
5201
  if (check.fix) {
5104
5202
  parts.push(`- **${check.name}**`);
@@ -5129,15 +5227,19 @@ IMPORTANT RULES FOR TARGETED FIX:
5129
5227
  - For grounding issues: Add references to the listed project directories in the appropriate sections.
5130
5228
  - Every path or name you reference MUST exist in the project \u2014 use the file tree provided below.`);
5131
5229
  } else if (hasExistingConfigs) {
5132
- parts.push(`Audit and improve the existing coding agent configuration for target: ${targetAgent}`);
5230
+ parts.push(
5231
+ `Audit and improve the existing coding agent configuration for target: ${targetAgent}`
5232
+ );
5133
5233
  } else {
5134
5234
  parts.push(`Generate an initial coding agent configuration for target: ${targetAgent}`);
5135
5235
  }
5136
5236
  if (fingerprint.gitRemoteUrl) parts.push(`
5137
5237
  Git remote: ${fingerprint.gitRemoteUrl}`);
5138
5238
  if (fingerprint.packageName) parts.push(`Package name: ${fingerprint.packageName}`);
5139
- if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
5140
- if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
5239
+ if (fingerprint.languages.length > 0)
5240
+ parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
5241
+ if (fingerprint.frameworks.length > 0)
5242
+ parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
5141
5243
  if (fingerprint.description) parts.push(`Project description: ${fingerprint.description}`);
5142
5244
  if (fingerprint.fileTree.length > 0) {
5143
5245
  const caPaths = fingerprint.codeAnalysis?.files.map((f) => f.path) ?? [];
@@ -5146,36 +5248,52 @@ Git remote: ${fingerprint.gitRemoteUrl}`);
5146
5248
  File tree (${tree.length}/${fingerprint.fileTree.length}):
5147
5249
  ${tree.join("\n")}`);
5148
5250
  }
5149
- if (existing.claudeMd) parts.push(`
5251
+ if (existing.claudeMd)
5252
+ parts.push(
5253
+ `
5150
5254
  Existing CLAUDE.md:
5151
- ${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5152
- if (existing.agentsMd) parts.push(`
5255
+ ${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5256
+ );
5257
+ if (existing.agentsMd)
5258
+ parts.push(
5259
+ `
5153
5260
  Existing AGENTS.md:
5154
- ${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5155
- if (existing.readmeMd) parts.push(`
5261
+ ${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5262
+ );
5263
+ if (existing.readmeMd)
5264
+ parts.push(
5265
+ `
5156
5266
  Existing README.md:
5157
- ${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
5267
+ ${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`
5268
+ );
5158
5269
  if (existing.claudeSkills?.length) {
5159
5270
  parts.push("\n--- Existing Claude Skills ---");
5160
5271
  for (const skill of existing.claudeSkills.slice(0, LIMITS.SKILLS_MAX)) {
5161
- parts.push(`
5272
+ parts.push(
5273
+ `
5162
5274
  [.claude/skills/${skill.filename}]
5163
- ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5275
+ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`
5276
+ );
5164
5277
  }
5165
5278
  if (existing.claudeSkills.length > LIMITS.SKILLS_MAX) {
5166
5279
  parts.push(`
5167
5280
  (${existing.claudeSkills.length - LIMITS.SKILLS_MAX} more skills omitted)`);
5168
5281
  }
5169
5282
  }
5170
- if (existing.cursorrules) parts.push(`
5283
+ if (existing.cursorrules)
5284
+ parts.push(
5285
+ `
5171
5286
  Existing .cursorrules:
5172
- ${truncate(existing.cursorrules, LIMITS.EXISTING_CONFIG_CHARS)}`);
5287
+ ${truncate(existing.cursorrules, LIMITS.EXISTING_CONFIG_CHARS)}`
5288
+ );
5173
5289
  if (existing.cursorRules?.length) {
5174
5290
  parts.push("\n--- Existing Cursor Rules ---");
5175
5291
  for (const rule of existing.cursorRules.slice(0, LIMITS.RULES_MAX)) {
5176
- parts.push(`
5292
+ parts.push(
5293
+ `
5177
5294
  [.cursor/rules/${rule.filename}]
5178
- ${truncate(rule.content, LIMITS.SKILL_CHARS)}`);
5295
+ ${truncate(rule.content, LIMITS.SKILL_CHARS)}`
5296
+ );
5179
5297
  }
5180
5298
  if (existing.cursorRules.length > LIMITS.RULES_MAX) {
5181
5299
  parts.push(`
@@ -5185,9 +5303,11 @@ ${truncate(rule.content, LIMITS.SKILL_CHARS)}`);
5185
5303
  if (existing.cursorSkills?.length) {
5186
5304
  parts.push("\n--- Existing Cursor Skills ---");
5187
5305
  for (const skill of existing.cursorSkills.slice(0, LIMITS.SKILLS_MAX)) {
5188
- parts.push(`
5306
+ parts.push(
5307
+ `
5189
5308
  [.cursor/skills/${skill.name}/SKILL.md]
5190
- ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5309
+ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`
5310
+ );
5191
5311
  }
5192
5312
  if (existing.cursorSkills.length > LIMITS.SKILLS_MAX) {
5193
5313
  parts.push(`
@@ -5195,9 +5315,11 @@ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`);
5195
5315
  }
5196
5316
  }
5197
5317
  if (existing.personalLearnings) {
5198
- parts.push(`
5318
+ parts.push(
5319
+ `
5199
5320
  --- Personal Learnings (developer-specific, include in generated configs) ---
5200
- ${existing.personalLearnings}`);
5321
+ ${existing.personalLearnings}`
5322
+ );
5201
5323
  }
5202
5324
  const allDeps = extractAllDeps(process.cwd());
5203
5325
  if (allDeps.length > 0) {
@@ -5263,7 +5385,10 @@ import fs12 from "fs";
5263
5385
  import path12 from "path";
5264
5386
  function writeClaudeConfig(config) {
5265
5387
  const written = [];
5266
- fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
5388
+ fs12.writeFileSync(
5389
+ "CLAUDE.md",
5390
+ appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd)))
5391
+ );
5267
5392
  written.push("CLAUDE.md");
5268
5393
  if (config.skills?.length) {
5269
5394
  for (const skill of config.skills) {
@@ -7501,8 +7626,8 @@ function trackInitSkillsSearch(searched, installedCount) {
7501
7626
  function trackInitScoreRegression(oldScore, newScore) {
7502
7627
  trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
7503
7628
  }
7504
- function trackInitCompleted(path43, score) {
7505
- trackEvent("init_completed", { path: path43, score });
7629
+ function trackInitCompleted(path44, score) {
7630
+ trackEvent("init_completed", { path: path44, score });
7506
7631
  }
7507
7632
  function trackRegenerateCompleted(action, durationMs) {
7508
7633
  trackEvent("regenerate_completed", { action, duration_ms: durationMs });
@@ -7864,10 +7989,13 @@ async function searchSkills(fingerprint, targetPlatforms, onStatus) {
7864
7989
  if (!newCandidates.length) {
7865
7990
  return { results: [], contentMap: /* @__PURE__ */ new Map() };
7866
7991
  }
7867
- onStatus?.(`Scoring ${newCandidates.length} candidates...`);
7868
7992
  let results;
7869
7993
  const config = loadConfig();
7870
- if (config) {
7994
+ if (newCandidates.length <= 5) {
7995
+ onStatus?.("Few candidates \u2014 skipping scoring");
7996
+ results = newCandidates.map((c) => ({ ...c, score: 70 }));
7997
+ } else if (config) {
7998
+ onStatus?.(`Scoring ${newCandidates.length} candidates...`);
7871
7999
  try {
7872
8000
  const projectContext = buildProjectContext(fingerprint, targetPlatforms);
7873
8001
  results = await scoreWithLLM(newCandidates, projectContext, technologies);
@@ -8286,7 +8414,8 @@ function printSkills(recs) {
8286
8414
  // src/ai/score-refine.ts
8287
8415
  import { existsSync as existsSync9 } from "fs";
8288
8416
  import ora2 from "ora";
8289
- var MAX_REFINE_ITERATIONS = 2;
8417
+ var MAX_REFINE_ITERATIONS = 1;
8418
+ var MIN_POINTS_TO_REFINE = 4;
8290
8419
  function extractConfigContent(setup) {
8291
8420
  const claude = setup.claude;
8292
8421
  const codex = setup.codex;
@@ -8296,7 +8425,11 @@ function extractConfigContent(setup) {
8296
8425
  cursorrules = cursor.cursorrules;
8297
8426
  }
8298
8427
  const skills = [];
8299
- for (const [platform, obj] of [["claude", claude], ["codex", codex], ["cursor", cursor]]) {
8428
+ for (const [platform, obj] of [
8429
+ ["claude", claude],
8430
+ ["codex", codex],
8431
+ ["cursor", cursor]
8432
+ ]) {
8300
8433
  const platformSkills = obj?.skills;
8301
8434
  if (Array.isArray(platformSkills)) {
8302
8435
  for (const skill of platformSkills) {
@@ -8315,8 +8448,12 @@ function extractConfigContent(setup) {
8315
8448
  }
8316
8449
  function buildGroundingFixInstruction(unmentionedTopDirs, projectStructure) {
8317
8450
  const dirDescriptions = unmentionedTopDirs.slice(0, 8).map((dir) => {
8318
- const subdirs = projectStructure.dirs.filter((d) => d.startsWith(`${dir}/`) && !d.includes("/", dir.length + 1));
8319
- const files = projectStructure.files.filter((f) => f.startsWith(`${dir}/`) && !f.includes("/", dir.length + 1));
8451
+ const subdirs = projectStructure.dirs.filter(
8452
+ (d) => d.startsWith(`${dir}/`) && !d.includes("/", dir.length + 1)
8453
+ );
8454
+ const files = projectStructure.files.filter(
8455
+ (f) => f.startsWith(`${dir}/`) && !f.includes("/", dir.length + 1)
8456
+ );
8320
8457
  const children = [...subdirs.slice(0, 4), ...files.slice(0, 2)];
8321
8458
  const childList = children.map((c) => c.split("/").pop()).join(", ");
8322
8459
  return childList ? `- \`${dir}/\` (contains: ${childList})` : `- \`${dir}/\``;
@@ -8361,7 +8498,9 @@ function validateSetup(setup, dir, checkExists = existsSync9, projectStructure)
8361
8498
  const content = claudeMd ?? agentsMd ?? "";
8362
8499
  if (content) {
8363
8500
  const structure2 = analyzeMarkdownStructure(content);
8364
- const blockThreshold = CODE_BLOCK_THRESHOLDS.find((t) => structure2.codeBlockCount >= t.minBlocks);
8501
+ const blockThreshold = CODE_BLOCK_THRESHOLDS.find(
8502
+ (t) => structure2.codeBlockCount >= t.minBlocks
8503
+ );
8365
8504
  const blockPoints = blockThreshold?.points ?? 0;
8366
8505
  const maxBlockPoints = CODE_BLOCK_THRESHOLDS[0].points;
8367
8506
  if (blockPoints < maxBlockPoints && structure2.codeBlockCount < CODE_BLOCK_THRESHOLDS[0].minBlocks) {
@@ -8486,7 +8625,9 @@ function buildFeedbackMessage(issues) {
8486
8625
  ];
8487
8626
  for (let i = 0; i < issues.length; i++) {
8488
8627
  const issue = issues[i];
8489
- lines.push(`${i + 1}. ${issue.check.toUpperCase()} (-${issue.pointsLost} pts): ${issue.detail}`);
8628
+ lines.push(
8629
+ `${i + 1}. ${issue.check.toUpperCase()} (-${issue.pointsLost} pts): ${issue.detail}`
8630
+ );
8490
8631
  lines.push(` Action: ${issue.fixInstruction}
8491
8632
  `);
8492
8633
  }
@@ -8495,20 +8636,22 @@ function buildFeedbackMessage(issues) {
8495
8636
  function countIssuePoints(issues) {
8496
8637
  return issues.reduce((sum, i) => sum + i.pointsLost, 0);
8497
8638
  }
8498
- async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8639
+ async function scoreAndRefine(setup, dir, sessionHistory, callbacks, options) {
8640
+ const maxIterations = options?.thorough ? 3 : MAX_REFINE_ITERATIONS;
8641
+ const minPoints = options?.thorough ? 1 : MIN_POINTS_TO_REFINE;
8499
8642
  const existsCache = /* @__PURE__ */ new Map();
8500
- const cachedExists = (path43) => {
8501
- const cached = existsCache.get(path43);
8643
+ const cachedExists = (path44) => {
8644
+ const cached = existsCache.get(path44);
8502
8645
  if (cached !== void 0) return cached;
8503
- const result = existsSync9(path43);
8504
- existsCache.set(path43, result);
8646
+ const result = existsSync9(path44);
8647
+ existsCache.set(path44, result);
8505
8648
  return result;
8506
8649
  };
8507
8650
  const projectStructure = collectProjectStructure(dir);
8508
8651
  let currentSetup = setup;
8509
8652
  let bestSetup = setup;
8510
8653
  let bestLostPoints = Infinity;
8511
- for (let iteration = 0; iteration < MAX_REFINE_ITERATIONS; iteration++) {
8654
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
8512
8655
  const issues = validateSetup(currentSetup, dir, cachedExists, projectStructure);
8513
8656
  const lostPoints = countIssuePoints(issues);
8514
8657
  if (lostPoints < bestLostPoints) {
@@ -8519,10 +8662,16 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8519
8662
  if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
8520
8663
  return bestSetup;
8521
8664
  }
8665
+ if (lostPoints < minPoints) {
8666
+ if (callbacks?.onStatus) callbacks.onStatus("Minor issues \u2014 skipping refinement");
8667
+ return bestSetup;
8668
+ }
8522
8669
  const pointIssues = issues.filter((i) => i.pointsLost > 0);
8523
8670
  const pointIssueNames = pointIssues.map((i) => i.check).join(", ");
8524
8671
  if (callbacks?.onStatus) {
8525
- callbacks.onStatus(`Fixing ${pointIssues.length} scoring issue${pointIssues.length === 1 ? "" : "s"}: ${pointIssueNames}...`);
8672
+ callbacks.onStatus(
8673
+ `Fixing ${pointIssues.length} scoring issue${pointIssues.length === 1 ? "" : "s"}: ${pointIssueNames}...`
8674
+ );
8526
8675
  }
8527
8676
  const refined = await applyTargetedFixes(currentSetup, issues);
8528
8677
  if (!refined) {
@@ -8557,17 +8706,19 @@ async function applyTargetedFixes(setup, issues) {
8557
8706
  }
8558
8707
  for (const skill of skills) {
8559
8708
  if (failingChecks.has(`Skill quality: ${skill.name}`)) {
8560
- targets.push({ key: `skill:${skill.name}`, label: `Skill: ${skill.name}`, content: skill.content });
8709
+ targets.push({
8710
+ key: `skill:${skill.name}`,
8711
+ label: `Skill: ${skill.name}`,
8712
+ content: skill.content
8713
+ });
8561
8714
  }
8562
8715
  }
8563
8716
  if (targets.length === 0) return null;
8564
8717
  const feedbackMessage = buildFeedbackMessage(issues);
8565
- const contentBlock = targets.map(
8566
- (t) => `### ${t.label}
8718
+ const contentBlock = targets.map((t) => `### ${t.label}
8567
8719
  \`\`\`markdown
8568
8720
  ${t.content}
8569
- \`\`\``
8570
- ).join("\n\n");
8721
+ \`\`\``).join("\n\n");
8571
8722
  const prompt = [
8572
8723
  "Here are the config files with scoring issues:\n",
8573
8724
  contentBlock,
@@ -8618,14 +8769,20 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
8618
8769
  return null;
8619
8770
  }
8620
8771
  }
8621
- async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
8772
+ async function runScoreRefineWithSpinner(setup, dir, sessionHistory, options) {
8622
8773
  const spinner = ora2("Validating config against scoring criteria...").start();
8623
8774
  try {
8624
- const refined = await scoreAndRefine(setup, dir, sessionHistory, {
8625
- onStatus: (msg) => {
8626
- spinner.text = msg;
8627
- }
8628
- });
8775
+ const refined = await scoreAndRefine(
8776
+ setup,
8777
+ dir,
8778
+ sessionHistory,
8779
+ {
8780
+ onStatus: (msg) => {
8781
+ spinner.text = msg;
8782
+ }
8783
+ },
8784
+ options
8785
+ );
8629
8786
  if (refined !== setup) {
8630
8787
  spinner.succeed("Config refined based on scoring feedback");
8631
8788
  } else {
@@ -9365,6 +9522,7 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
9365
9522
  // src/commands/init-display.ts
9366
9523
  import chalk12 from "chalk";
9367
9524
  import fs31 from "fs";
9525
+ init_types();
9368
9526
  function formatWhatChanged(setup) {
9369
9527
  const lines = [];
9370
9528
  const claude = setup.claude;
@@ -9534,7 +9692,11 @@ function displayTokenUsage() {
9534
9692
  console.log(chalk12.dim(" Token tracking not available for this provider.\n"));
9535
9693
  return;
9536
9694
  }
9537
- console.log(chalk12.bold(" Token usage:\n"));
9695
+ const config = loadConfig();
9696
+ const isEstimated = config != null && isSeatBased(config.provider);
9697
+ const label = isEstimated ? "Estimated token usage:" : "Token usage:";
9698
+ console.log(chalk12.bold(` ${label}
9699
+ `));
9538
9700
  let totalIn = 0;
9539
9701
  let totalOut = 0;
9540
9702
  for (const m of summary) {
@@ -9552,6 +9714,9 @@ function displayTokenUsage() {
9552
9714
  ` ${chalk12.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`
9553
9715
  );
9554
9716
  }
9717
+ if (isEstimated) {
9718
+ console.log(chalk12.dim(" (Estimated from character count)"));
9719
+ }
9555
9720
  console.log("");
9556
9721
  }
9557
9722
 
@@ -10233,9 +10398,15 @@ async function initCommand(options) {
10233
10398
  content: summarizeSetup("Initial generation", generatedSetup)
10234
10399
  });
10235
10400
  try {
10236
- const refined = await scoreAndRefine(generatedSetup, process.cwd(), sessionHistory, {
10237
- onStatus: (msg) => display.update(TASK_SCORE_REFINE, "running", msg)
10238
- });
10401
+ const refined = await scoreAndRefine(
10402
+ generatedSetup,
10403
+ process.cwd(),
10404
+ sessionHistory,
10405
+ {
10406
+ onStatus: (msg) => display.update(TASK_SCORE_REFINE, "running", msg)
10407
+ },
10408
+ { thorough: options.thorough }
10409
+ );
10239
10410
  if (refined !== generatedSetup) {
10240
10411
  display.update(TASK_SCORE_REFINE, "done", "Refined");
10241
10412
  generatedSetup = refined;
@@ -10880,8 +11051,8 @@ async function scoreCommand(options) {
10880
11051
  }
10881
11052
 
10882
11053
  // src/commands/refresh.ts
10883
- import fs40 from "fs";
10884
- import path33 from "path";
11054
+ import fs41 from "fs";
11055
+ import path34 from "path";
10885
11056
  import chalk19 from "chalk";
10886
11057
  import ora6 from "ora";
10887
11058
 
@@ -10968,51 +11139,87 @@ function collectDiff(lastSha) {
10968
11139
  const summary = parts.join(", ") || "no changes";
10969
11140
  return { hasChanges, committedDiff, stagedDiff, unstagedDiff, changedFiles, summary };
10970
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
+ }
10971
11167
 
10972
11168
  // src/writers/refresh.ts
10973
11169
  init_pre_commit_block();
10974
11170
  import fs37 from "fs";
10975
11171
  import path30 from "path";
10976
- function writeRefreshDocs(docs) {
11172
+ function writeRefreshDocs(docs, dir = ".") {
10977
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
+ };
10978
11179
  if (docs.agentsMd) {
10979
- fs37.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10980
- 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);
10981
11184
  }
10982
11185
  if (docs.claudeMd) {
10983
- fs37.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd));
10984
- written.push("CLAUDE.md");
11186
+ const filePath = p("CLAUDE.md");
11187
+ ensureParent(filePath);
11188
+ fs37.writeFileSync(filePath, appendManagedBlocks(docs.claudeMd));
11189
+ written.push(filePath);
10985
11190
  }
10986
11191
  if (docs.readmeMd) {
10987
- fs37.writeFileSync("README.md", docs.readmeMd);
10988
- written.push("README.md");
11192
+ const filePath = p("README.md");
11193
+ ensureParent(filePath);
11194
+ fs37.writeFileSync(filePath, docs.readmeMd);
11195
+ written.push(filePath);
10989
11196
  }
10990
11197
  if (docs.cursorrules) {
10991
- fs37.writeFileSync(".cursorrules", docs.cursorrules);
10992
- written.push(".cursorrules");
11198
+ const filePath = p(".cursorrules");
11199
+ ensureParent(filePath);
11200
+ fs37.writeFileSync(filePath, docs.cursorrules);
11201
+ written.push(filePath);
10993
11202
  }
10994
11203
  if (docs.cursorRules) {
10995
- const rulesDir = path30.join(".cursor", "rules");
11204
+ const rulesDir = p(path30.join(".cursor", "rules"));
10996
11205
  if (!fs37.existsSync(rulesDir)) fs37.mkdirSync(rulesDir, { recursive: true });
10997
11206
  for (const rule of docs.cursorRules) {
10998
11207
  fs37.writeFileSync(path30.join(rulesDir, rule.filename), rule.content);
10999
- written.push(`.cursor/rules/${rule.filename}`);
11208
+ written.push(p(path30.join(".cursor", "rules", rule.filename)));
11000
11209
  }
11001
11210
  }
11002
11211
  if (docs.copilotInstructions) {
11003
- fs37.mkdirSync(".github", { recursive: true });
11004
- fs37.writeFileSync(
11005
- path30.join(".github", "copilot-instructions.md"),
11006
- appendManagedBlocks(docs.copilotInstructions, "copilot")
11007
- );
11008
- 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);
11009
11216
  }
11010
11217
  if (docs.copilotInstructionFiles) {
11011
- const instructionsDir = path30.join(".github", "instructions");
11012
- fs37.mkdirSync(instructionsDir, { recursive: true });
11218
+ const instructionsDir = p(path30.join(".github", "instructions"));
11219
+ if (!fs37.existsSync(instructionsDir)) fs37.mkdirSync(instructionsDir, { recursive: true });
11013
11220
  for (const file of docs.copilotInstructionFiles) {
11014
11221
  fs37.writeFileSync(path30.join(instructionsDir, file.filename), file.content);
11015
- written.push(`.github/instructions/${file.filename}`);
11222
+ written.push(p(path30.join(".github", "instructions", file.filename)));
11016
11223
  }
11017
11224
  }
11018
11225
  return written;
@@ -11021,8 +11228,15 @@ function writeRefreshDocs(docs) {
11021
11228
  // src/ai/refresh.ts
11022
11229
  init_config();
11023
11230
  init_pre_commit_block();
11024
- async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
11025
- 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
+ );
11026
11240
  const fastModel = getFastModel();
11027
11241
  const raw = await llmCall({
11028
11242
  system: REFRESH_SYSTEM_PROMPT,
@@ -11032,8 +11246,14 @@ async function refreshDocs(diff, existingDocs, projectContext, learnedSection, s
11032
11246
  });
11033
11247
  return parseJsonResponse(raw);
11034
11248
  }
11035
- function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2) {
11249
+ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2, scope) {
11036
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
+ }
11037
11257
  parts.push("Update documentation based on the following code changes.\n");
11038
11258
  if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
11039
11259
  if (projectContext.languages?.length)
@@ -11315,10 +11535,77 @@ function migrateInlineLearnings() {
11315
11535
  init_config();
11316
11536
  init_resolve_caliber();
11317
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
11318
11605
  function writeRefreshError(error) {
11319
11606
  try {
11320
- if (!fs40.existsSync(CALIBER_DIR)) fs40.mkdirSync(CALIBER_DIR, { recursive: true });
11321
- fs40.writeFileSync(
11607
+ if (!fs41.existsSync(CALIBER_DIR)) fs41.mkdirSync(CALIBER_DIR, { recursive: true });
11608
+ fs41.writeFileSync(
11322
11609
  REFRESH_LAST_ERROR_FILE,
11323
11610
  JSON.stringify(
11324
11611
  {
@@ -11337,15 +11624,15 @@ function writeRefreshError(error) {
11337
11624
  }
11338
11625
  function readRefreshError() {
11339
11626
  try {
11340
- if (!fs40.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
11341
- 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"));
11342
11629
  } catch {
11343
11630
  return null;
11344
11631
  }
11345
11632
  }
11346
11633
  function clearRefreshError() {
11347
11634
  try {
11348
- 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);
11349
11636
  } catch {
11350
11637
  }
11351
11638
  }
@@ -11354,7 +11641,8 @@ function detectSyncedAgents(writtenFiles) {
11354
11641
  const joined = writtenFiles.join(" ");
11355
11642
  if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
11356
11643
  if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
11357
- if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
11644
+ if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/"))
11645
+ agents.push("Copilot");
11358
11646
  if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
11359
11647
  return agents;
11360
11648
  }
@@ -11364,11 +11652,11 @@ function log2(quiet, ...args) {
11364
11652
  function discoverGitRepos(parentDir) {
11365
11653
  const repos = [];
11366
11654
  try {
11367
- const entries = fs40.readdirSync(parentDir, { withFileTypes: true });
11655
+ const entries = fs41.readdirSync(parentDir, { withFileTypes: true });
11368
11656
  for (const entry of entries) {
11369
11657
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
11370
- const childPath = path33.join(parentDir, entry.name);
11371
- if (fs40.existsSync(path33.join(childPath, ".git"))) {
11658
+ const childPath = path34.join(parentDir, entry.name);
11659
+ if (fs41.existsSync(path34.join(childPath, ".git"))) {
11372
11660
  repos.push(childPath);
11373
11661
  }
11374
11662
  }
@@ -11376,51 +11664,33 @@ function discoverGitRepos(parentDir) {
11376
11664
  }
11377
11665
  return repos.sort();
11378
11666
  }
11379
- function collectFilesToWrite(updatedDocs) {
11667
+ function collectFilesToWrite(updatedDocs, dir = ".") {
11380
11668
  const files = [];
11381
- if (updatedDocs.agentsMd) files.push("AGENTS.md");
11382
- if (updatedDocs.claudeMd) files.push("CLAUDE.md");
11383
- if (updatedDocs.readmeMd) files.push("README.md");
11384
- 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"));
11385
11674
  if (Array.isArray(updatedDocs.cursorRules)) {
11386
11675
  for (const r of updatedDocs.cursorRules)
11387
- files.push(`.cursor/rules/${r.filename}`);
11676
+ files.push(p(`.cursor/rules/${r.filename}`));
11388
11677
  }
11389
- if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
11678
+ if (updatedDocs.copilotInstructions) files.push(p(".github/copilot-instructions.md"));
11390
11679
  if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
11391
11680
  for (const f of updatedDocs.copilotInstructionFiles)
11392
- files.push(`.github/instructions/${f.filename}`);
11681
+ files.push(p(`.github/instructions/${f.filename}`));
11393
11682
  }
11394
11683
  return files;
11395
11684
  }
11396
11685
  var REFRESH_COOLDOWN_MS = 3e4;
11397
- async function refreshSingleRepo(repoDir, options) {
11686
+ async function refreshDir(repoDir, dir, diff, options) {
11398
11687
  const quiet = !!options.quiet;
11399
11688
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
11400
- const state = readState();
11401
- const lastSha = state?.lastRefreshSha ?? null;
11402
- const currentSha = getCurrentHeadSha();
11403
- if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
11404
- const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
11405
- if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
11406
- log2(
11407
- quiet,
11408
- chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
11409
- );
11410
- return;
11411
- }
11412
- }
11413
- const diff = collectDiff(lastSha);
11414
- if (!diff.hasChanges) {
11415
- if (currentSha) {
11416
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11417
- }
11418
- log2(quiet, chalk19.dim(`${prefix}No changes since last refresh.`));
11419
- return;
11420
- }
11689
+ const absDir = dir === "." ? repoDir : path34.resolve(repoDir, dir);
11690
+ const scope = dir === "." ? void 0 : dir;
11421
11691
  const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
11422
11692
  const learnedSection = readLearnedSection();
11423
- const fingerprint = await collectFingerprint(repoDir);
11693
+ const fingerprint = await collectFingerprint(absDir);
11424
11694
  const existingDocs = fingerprint.existingConfigs;
11425
11695
  const projectContext = {
11426
11696
  languages: fingerprint.languages,
@@ -11428,8 +11698,8 @@ async function refreshSingleRepo(repoDir, options) {
11428
11698
  packageName: fingerprint.packageName,
11429
11699
  fileTree: fingerprint.fileTree
11430
11700
  };
11431
- const workspaces = getDetectedWorkspaces(repoDir);
11432
- const sources2 = resolveAllSources(repoDir, [], workspaces);
11701
+ const workspaces = getDetectedWorkspaces(absDir);
11702
+ const sources2 = resolveAllSources(absDir, [], workspaces);
11433
11703
  const diffPayload = {
11434
11704
  committed: diff.committedDiff,
11435
11705
  staged: diff.stagedDiff,
@@ -11445,7 +11715,8 @@ async function refreshSingleRepo(repoDir, options) {
11445
11715
  existingDocs,
11446
11716
  projectContext,
11447
11717
  learnedSection,
11448
- sourcesPayload
11718
+ sourcesPayload,
11719
+ scope
11449
11720
  );
11450
11721
  } catch (firstErr) {
11451
11722
  const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
@@ -11456,7 +11727,8 @@ async function refreshSingleRepo(repoDir, options) {
11456
11727
  existingDocs,
11457
11728
  projectContext,
11458
11729
  learnedSection,
11459
- sourcesPayload
11730
+ sourcesPayload,
11731
+ scope
11460
11732
  );
11461
11733
  } catch {
11462
11734
  spinner?.fail(`${prefix}Refresh failed after retry`);
@@ -11465,10 +11737,7 @@ async function refreshSingleRepo(repoDir, options) {
11465
11737
  }
11466
11738
  if (!response.docsUpdated || response.docsUpdated.length === 0) {
11467
11739
  spinner?.succeed(`${prefix}No doc updates needed`);
11468
- if (currentSha) {
11469
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11470
- }
11471
- return;
11740
+ return { written: [] };
11472
11741
  }
11473
11742
  if (options.dryRun) {
11474
11743
  spinner?.info(`${prefix}Dry run \u2014 would update:`);
@@ -11479,49 +11748,53 @@ async function refreshSingleRepo(repoDir, options) {
11479
11748
  console.log(chalk19.dim(`
11480
11749
  ${response.changesSummary}`));
11481
11750
  }
11482
- return;
11751
+ return { written: [] };
11483
11752
  }
11484
- const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
11485
- const preScore = computeLocalScore(repoDir, targetAgent);
11486
- const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
11753
+ const allFilesToWrite = collectFilesToWrite(response.updatedDocs, dir);
11487
11754
  const preRefreshContents = /* @__PURE__ */ new Map();
11488
11755
  for (const filePath of allFilesToWrite) {
11489
- const fullPath = path33.resolve(repoDir, filePath);
11756
+ const fullPath = path34.resolve(repoDir, filePath);
11490
11757
  try {
11491
- preRefreshContents.set(filePath, fs40.readFileSync(fullPath, "utf-8"));
11758
+ preRefreshContents.set(filePath, fs41.readFileSync(fullPath, "utf-8"));
11492
11759
  } catch {
11493
11760
  preRefreshContents.set(filePath, null);
11494
11761
  }
11495
11762
  }
11496
- 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);
11497
11768
  const trigger = quiet ? "hook" : "manual";
11498
11769
  trackRefreshCompleted(written.length, Date.now(), trigger);
11499
- const postScore = computeLocalScore(repoDir, targetAgent);
11500
- if (postScore.score < preScore.score) {
11501
- for (const [filePath, content] of preRefreshContents) {
11502
- const fullPath = path33.resolve(repoDir, filePath);
11503
- if (content === null) {
11504
- try {
11505
- fs40.unlinkSync(fullPath);
11506
- } 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);
11507
11782
  }
11508
- } else {
11509
- fs40.writeFileSync(fullPath, content);
11510
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: [] };
11511
11789
  }
11512
- spinner?.warn(
11513
- `${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
11514
- );
11515
- log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
11516
- if (currentSha) {
11517
- writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
11518
- }
11519
- return;
11790
+ recordScore(postScore, "refresh");
11520
11791
  }
11521
- recordScore(postScore, "refresh");
11522
11792
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
11523
11793
  const fileChangesMap = new Map(
11524
- (response.fileChanges || []).map((fc) => [fc.file, fc.description])
11794
+ (response.fileChanges || []).map((fc) => [
11795
+ fc.file,
11796
+ fc.description
11797
+ ])
11525
11798
  );
11526
11799
  for (const file of written) {
11527
11800
  const desc = fileChangesMap.get(file);
@@ -11537,6 +11810,59 @@ async function refreshSingleRepo(repoDir, options) {
11537
11810
  log2(quiet, chalk19.dim(`
11538
11811
  ${response.changesSummary}`));
11539
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
+ }
11540
11866
  const builtinWritten = ensureBuiltinSkills();
11541
11867
  for (const file of builtinWritten) {
11542
11868
  log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
@@ -11593,7 +11919,7 @@ async function refreshCommand(options) {
11593
11919
  `));
11594
11920
  const originalDir = process.cwd();
11595
11921
  for (const repo of repos) {
11596
- const repoName = path33.basename(repo);
11922
+ const repoName = path34.basename(repo);
11597
11923
  try {
11598
11924
  process.chdir(repo);
11599
11925
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -11621,7 +11947,7 @@ async function refreshCommand(options) {
11621
11947
 
11622
11948
  // src/commands/hooks.ts
11623
11949
  import chalk20 from "chalk";
11624
- import fs41 from "fs";
11950
+ import fs42 from "fs";
11625
11951
  var HOOKS = [
11626
11952
  {
11627
11953
  id: "session-end",
@@ -11665,11 +11991,11 @@ async function hooksCommand(options) {
11665
11991
  console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
11666
11992
  }
11667
11993
  }
11668
- if (fs41.existsSync(".claude")) {
11994
+ if (fs42.existsSync(".claude")) {
11669
11995
  const r = installLearningHooks();
11670
11996
  if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
11671
11997
  }
11672
- if (fs41.existsSync(".cursor")) {
11998
+ if (fs42.existsSync(".cursor")) {
11673
11999
  const r = installCursorLearningHooks();
11674
12000
  if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
11675
12001
  }
@@ -11837,8 +12163,8 @@ async function configCommand() {
11837
12163
  }
11838
12164
 
11839
12165
  // src/commands/learn.ts
11840
- import fs45 from "fs";
11841
- import path37 from "path";
12166
+ import fs46 from "fs";
12167
+ import path38 from "path";
11842
12168
  import chalk23 from "chalk";
11843
12169
 
11844
12170
  // src/learner/stdin.ts
@@ -11869,8 +12195,8 @@ function readStdin() {
11869
12195
  }
11870
12196
 
11871
12197
  // src/learner/storage.ts
11872
- import fs42 from "fs";
11873
- import path34 from "path";
12198
+ import fs43 from "fs";
12199
+ import path35 from "path";
11874
12200
  var MAX_RESPONSE_LENGTH = 2e3;
11875
12201
  var DEFAULT_STATE = {
11876
12202
  sessionId: null,
@@ -11879,15 +12205,15 @@ var DEFAULT_STATE = {
11879
12205
  lastAnalysisEventCount: 0
11880
12206
  };
11881
12207
  function ensureLearningDir() {
11882
- if (!fs42.existsSync(getLearningDir())) {
11883
- fs42.mkdirSync(getLearningDir(), { recursive: true });
12208
+ if (!fs43.existsSync(getLearningDir())) {
12209
+ fs43.mkdirSync(getLearningDir(), { recursive: true });
11884
12210
  }
11885
12211
  }
11886
12212
  function sessionFilePath() {
11887
- return path34.join(getLearningDir(), LEARNING_SESSION_FILE);
12213
+ return path35.join(getLearningDir(), LEARNING_SESSION_FILE);
11888
12214
  }
11889
12215
  function stateFilePath() {
11890
- return path34.join(getLearningDir(), LEARNING_STATE_FILE);
12216
+ return path35.join(getLearningDir(), LEARNING_STATE_FILE);
11891
12217
  }
11892
12218
  function truncateResponse(response) {
11893
12219
  const str = JSON.stringify(response);
@@ -11897,10 +12223,10 @@ function truncateResponse(response) {
11897
12223
  function trimSessionFileIfNeeded(filePath) {
11898
12224
  const state = readState2();
11899
12225
  if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
11900
- const lines = fs42.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
12226
+ const lines = fs43.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
11901
12227
  if (lines.length > LEARNING_MAX_EVENTS) {
11902
12228
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
11903
- fs42.writeFileSync(filePath, kept.join("\n") + "\n");
12229
+ fs43.writeFileSync(filePath, kept.join("\n") + "\n");
11904
12230
  }
11905
12231
  }
11906
12232
  }
@@ -11908,19 +12234,19 @@ function appendEvent(event) {
11908
12234
  ensureLearningDir();
11909
12235
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
11910
12236
  const filePath = sessionFilePath();
11911
- fs42.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
12237
+ fs43.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
11912
12238
  trimSessionFileIfNeeded(filePath);
11913
12239
  }
11914
12240
  function appendPromptEvent(event) {
11915
12241
  ensureLearningDir();
11916
12242
  const filePath = sessionFilePath();
11917
- fs42.appendFileSync(filePath, JSON.stringify(event) + "\n");
12243
+ fs43.appendFileSync(filePath, JSON.stringify(event) + "\n");
11918
12244
  trimSessionFileIfNeeded(filePath);
11919
12245
  }
11920
12246
  function readAllEvents() {
11921
12247
  const filePath = sessionFilePath();
11922
- if (!fs42.existsSync(filePath)) return [];
11923
- 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);
11924
12250
  const events = [];
11925
12251
  for (const line of lines) {
11926
12252
  try {
@@ -11932,26 +12258,26 @@ function readAllEvents() {
11932
12258
  }
11933
12259
  function getEventCount() {
11934
12260
  const filePath = sessionFilePath();
11935
- if (!fs42.existsSync(filePath)) return 0;
11936
- const content = fs42.readFileSync(filePath, "utf-8");
12261
+ if (!fs43.existsSync(filePath)) return 0;
12262
+ const content = fs43.readFileSync(filePath, "utf-8");
11937
12263
  return content.split("\n").filter(Boolean).length;
11938
12264
  }
11939
12265
  function clearSession() {
11940
12266
  const filePath = sessionFilePath();
11941
- if (fs42.existsSync(filePath)) fs42.unlinkSync(filePath);
12267
+ if (fs43.existsSync(filePath)) fs43.unlinkSync(filePath);
11942
12268
  }
11943
12269
  function readState2() {
11944
12270
  const filePath = stateFilePath();
11945
- if (!fs42.existsSync(filePath)) return { ...DEFAULT_STATE };
12271
+ if (!fs43.existsSync(filePath)) return { ...DEFAULT_STATE };
11946
12272
  try {
11947
- return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
12273
+ return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
11948
12274
  } catch {
11949
12275
  return { ...DEFAULT_STATE };
11950
12276
  }
11951
12277
  }
11952
12278
  function writeState2(state) {
11953
12279
  ensureLearningDir();
11954
- fs42.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
12280
+ fs43.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
11955
12281
  }
11956
12282
  function resetState() {
11957
12283
  writeState2({ ...DEFAULT_STATE });
@@ -11959,16 +12285,16 @@ function resetState() {
11959
12285
  var LOCK_FILE = "finalize.lock";
11960
12286
  var LOCK_STALE_MS = 5 * 60 * 1e3;
11961
12287
  function lockFilePath() {
11962
- return path34.join(getLearningDir(), LOCK_FILE);
12288
+ return path35.join(getLearningDir(), LOCK_FILE);
11963
12289
  }
11964
12290
  function acquireFinalizeLock() {
11965
12291
  ensureLearningDir();
11966
12292
  const lockPath = lockFilePath();
11967
- if (fs42.existsSync(lockPath)) {
12293
+ if (fs43.existsSync(lockPath)) {
11968
12294
  try {
11969
- const stat = fs42.statSync(lockPath);
12295
+ const stat = fs43.statSync(lockPath);
11970
12296
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
11971
- const pid = parseInt(fs42.readFileSync(lockPath, "utf-8").trim(), 10);
12297
+ const pid = parseInt(fs43.readFileSync(lockPath, "utf-8").trim(), 10);
11972
12298
  if (!isNaN(pid) && isProcessAlive(pid)) {
11973
12299
  return false;
11974
12300
  }
@@ -11976,12 +12302,12 @@ function acquireFinalizeLock() {
11976
12302
  } catch {
11977
12303
  }
11978
12304
  try {
11979
- fs42.unlinkSync(lockPath);
12305
+ fs43.unlinkSync(lockPath);
11980
12306
  } catch {
11981
12307
  }
11982
12308
  }
11983
12309
  try {
11984
- fs42.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
12310
+ fs43.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
11985
12311
  return true;
11986
12312
  } catch {
11987
12313
  return false;
@@ -11998,30 +12324,30 @@ function isProcessAlive(pid) {
11998
12324
  function releaseFinalizeLock() {
11999
12325
  const lockPath = lockFilePath();
12000
12326
  try {
12001
- if (fs42.existsSync(lockPath)) fs42.unlinkSync(lockPath);
12327
+ if (fs43.existsSync(lockPath)) fs43.unlinkSync(lockPath);
12002
12328
  } catch {
12003
12329
  }
12004
12330
  }
12005
12331
 
12006
12332
  // src/lib/notifications.ts
12007
- import fs43 from "fs";
12008
- import path35 from "path";
12333
+ import fs44 from "fs";
12334
+ import path36 from "path";
12009
12335
  import chalk22 from "chalk";
12010
12336
  function notificationFilePath() {
12011
- return path35.join(getLearningDir(), "last-finalize-summary.json");
12337
+ return path36.join(getLearningDir(), "last-finalize-summary.json");
12012
12338
  }
12013
12339
  function writeFinalizeSummary(summary) {
12014
12340
  try {
12015
12341
  ensureLearningDir();
12016
- fs43.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
12342
+ fs44.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
12017
12343
  } catch {
12018
12344
  }
12019
12345
  }
12020
12346
  function checkPendingNotifications() {
12021
12347
  try {
12022
- if (!fs43.existsSync(notificationFilePath())) return;
12023
- const raw = fs43.readFileSync(notificationFilePath(), "utf-8");
12024
- fs43.unlinkSync(notificationFilePath());
12348
+ if (!fs44.existsSync(notificationFilePath())) return;
12349
+ const raw = fs44.readFileSync(notificationFilePath(), "utf-8");
12350
+ fs44.unlinkSync(notificationFilePath());
12025
12351
  const summary = JSON.parse(raw);
12026
12352
  if (!summary.newItemCount || summary.newItemCount === 0) return;
12027
12353
  const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
@@ -12037,7 +12363,7 @@ function checkPendingNotifications() {
12037
12363
  console.log("");
12038
12364
  } catch {
12039
12365
  try {
12040
- fs43.unlinkSync(notificationFilePath());
12366
+ fs44.unlinkSync(notificationFilePath());
12041
12367
  } catch {
12042
12368
  }
12043
12369
  }
@@ -12189,8 +12515,8 @@ function calculateSessionWaste(events) {
12189
12515
  init_config();
12190
12516
 
12191
12517
  // src/learner/roi.ts
12192
- import fs44 from "fs";
12193
- import path36 from "path";
12518
+ import fs45 from "fs";
12519
+ import path37 from "path";
12194
12520
  var DEFAULT_TOTALS = {
12195
12521
  totalWasteTokens: 0,
12196
12522
  totalWasteSeconds: 0,
@@ -12204,19 +12530,19 @@ var DEFAULT_TOTALS = {
12204
12530
  lastSessionTimestamp: ""
12205
12531
  };
12206
12532
  function roiFilePath() {
12207
- return path36.join(getLearningDir(), LEARNING_ROI_FILE);
12533
+ return path37.join(getLearningDir(), LEARNING_ROI_FILE);
12208
12534
  }
12209
12535
  function readROIStats() {
12210
12536
  const filePath = roiFilePath();
12211
- if (!fs44.existsSync(filePath)) {
12537
+ if (!fs45.existsSync(filePath)) {
12212
12538
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
12213
12539
  }
12214
12540
  try {
12215
- return JSON.parse(fs44.readFileSync(filePath, "utf-8"));
12541
+ return JSON.parse(fs45.readFileSync(filePath, "utf-8"));
12216
12542
  } catch {
12217
12543
  try {
12218
12544
  const corruptPath = filePath + ".corrupt";
12219
- fs44.renameSync(filePath, corruptPath);
12545
+ fs45.renameSync(filePath, corruptPath);
12220
12546
  console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
12221
12547
  } catch {
12222
12548
  }
@@ -12225,7 +12551,7 @@ function readROIStats() {
12225
12551
  }
12226
12552
  function writeROIStats(stats) {
12227
12553
  ensureLearningDir();
12228
- fs44.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
12554
+ fs45.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
12229
12555
  }
12230
12556
  function recalculateTotals(stats) {
12231
12557
  const totals = stats.totals;
@@ -12434,9 +12760,9 @@ var AUTO_SETTLE_MS = 200;
12434
12760
  var INCREMENTAL_INTERVAL = 50;
12435
12761
  function writeFinalizeError(message) {
12436
12762
  try {
12437
- const errorPath = path37.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12438
- if (!fs45.existsSync(getLearningDir())) fs45.mkdirSync(getLearningDir(), { recursive: true });
12439
- 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({
12440
12766
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12441
12767
  error: message,
12442
12768
  pid: process.pid
@@ -12446,9 +12772,9 @@ function writeFinalizeError(message) {
12446
12772
  }
12447
12773
  function readFinalizeError() {
12448
12774
  try {
12449
- const errorPath = path37.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
12450
- if (!fs45.existsSync(errorPath)) return null;
12451
- 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"));
12452
12778
  } catch {
12453
12779
  return null;
12454
12780
  }
@@ -12495,14 +12821,14 @@ async function learnObserveCommand(options) {
12495
12821
  const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
12496
12822
  const bin = resolveCaliber2();
12497
12823
  const { spawn: spawn4 } = await import("child_process");
12498
- const logPath = path37.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12499
- if (!fs45.existsSync(getLearningDir())) fs45.mkdirSync(getLearningDir(), { recursive: true });
12500
- 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");
12501
12827
  spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
12502
12828
  detached: true,
12503
12829
  stdio: ["ignore", logFd, logFd]
12504
12830
  }).unref();
12505
- fs45.closeSync(logFd);
12831
+ fs46.closeSync(logFd);
12506
12832
  } catch {
12507
12833
  }
12508
12834
  }
@@ -12709,7 +13035,7 @@ async function learnFinalizeCommand(options) {
12709
13035
  }
12710
13036
  async function learnInstallCommand() {
12711
13037
  let anyInstalled = false;
12712
- if (fs45.existsSync(".claude")) {
13038
+ if (fs46.existsSync(".claude")) {
12713
13039
  const r = installLearningHooks();
12714
13040
  if (r.installed) {
12715
13041
  console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
@@ -12718,7 +13044,7 @@ async function learnInstallCommand() {
12718
13044
  console.log(chalk23.dim(" Claude Code hooks already installed"));
12719
13045
  }
12720
13046
  }
12721
- if (fs45.existsSync(".cursor")) {
13047
+ if (fs46.existsSync(".cursor")) {
12722
13048
  const r = installCursorLearningHooks();
12723
13049
  if (r.installed) {
12724
13050
  console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
@@ -12727,7 +13053,7 @@ async function learnInstallCommand() {
12727
13053
  console.log(chalk23.dim(" Cursor hooks already installed"));
12728
13054
  }
12729
13055
  }
12730
- if (!fs45.existsSync(".claude") && !fs45.existsSync(".cursor")) {
13056
+ if (!fs46.existsSync(".claude") && !fs46.existsSync(".cursor")) {
12731
13057
  console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
12732
13058
  console.log(chalk23.dim(` Run \`${resolveCaliber()} init\` first, or create the directory manually.`));
12733
13059
  return;
@@ -12785,8 +13111,8 @@ async function learnStatusCommand() {
12785
13111
  if (lastError) {
12786
13112
  console.log(`Last error: ${chalk23.red(lastError.error)}`);
12787
13113
  console.log(chalk23.dim(` at ${lastError.timestamp}`));
12788
- const logPath = path37.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12789
- if (fs45.existsSync(logPath)) {
13114
+ const logPath = path38.join(getLearningDir(), LEARNING_FINALIZE_LOG);
13115
+ if (fs46.existsSync(logPath)) {
12790
13116
  console.log(chalk23.dim(` Full log: ${logPath}`));
12791
13117
  }
12792
13118
  }
@@ -12866,11 +13192,11 @@ async function learnDeleteCommand(indexStr) {
12866
13192
  }
12867
13193
  const item = items[targetIdx];
12868
13194
  const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
12869
- if (!fs45.existsSync(filePath)) {
13195
+ if (!fs46.existsSync(filePath)) {
12870
13196
  console.log(chalk23.red("Learnings file not found."));
12871
13197
  return;
12872
13198
  }
12873
- const content = fs45.readFileSync(filePath, "utf-8");
13199
+ const content = fs46.readFileSync(filePath, "utf-8");
12874
13200
  const lines = content.split("\n");
12875
13201
  const bulletsOfSource = items.filter((i) => i.source === item.source);
12876
13202
  const posInFile = bulletsOfSource.indexOf(item);
@@ -12891,9 +13217,9 @@ async function learnDeleteCommand(indexStr) {
12891
13217
  }
12892
13218
  const bulletToRemove = lines[lineToRemove];
12893
13219
  const newLines = lines.filter((_, i) => i !== lineToRemove);
12894
- fs45.writeFileSync(filePath, newLines.join("\n"));
13220
+ fs46.writeFileSync(filePath, newLines.join("\n"));
12895
13221
  if (item.source === "personal") {
12896
- fs45.chmodSync(filePath, 384);
13222
+ fs46.chmodSync(filePath, 384);
12897
13223
  }
12898
13224
  const roiStats = readROIStats();
12899
13225
  const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
@@ -13066,8 +13392,8 @@ async function insightsCommand(options) {
13066
13392
  }
13067
13393
 
13068
13394
  // src/commands/sources.ts
13069
- import fs46 from "fs";
13070
- import path38 from "path";
13395
+ import fs47 from "fs";
13396
+ import path39 from "path";
13071
13397
  import chalk25 from "chalk";
13072
13398
  init_resolve_caliber();
13073
13399
  async function sourcesListCommand() {
@@ -13084,9 +13410,9 @@ async function sourcesListCommand() {
13084
13410
  if (configSources.length > 0) {
13085
13411
  for (const source of configSources) {
13086
13412
  const sourcePath = source.path || source.url || "";
13087
- 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;
13088
13414
  const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
13089
- 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"));
13090
13416
  console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
13091
13417
  console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
13092
13418
  if (source.description) console.log(` ${chalk25.dim(source.description)}`);
@@ -13096,7 +13422,7 @@ async function sourcesListCommand() {
13096
13422
  if (workspaces.length > 0) {
13097
13423
  console.log(chalk25.dim(" Auto-detected workspaces:"));
13098
13424
  for (const ws of workspaces) {
13099
- const exists = fs46.existsSync(path38.resolve(dir, ws));
13425
+ const exists = fs47.existsSync(path39.resolve(dir, ws));
13100
13426
  console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
13101
13427
  }
13102
13428
  console.log("");
@@ -13104,8 +13430,8 @@ async function sourcesListCommand() {
13104
13430
  }
13105
13431
  async function sourcesAddCommand(sourcePath) {
13106
13432
  const dir = process.cwd();
13107
- const absPath = path38.resolve(dir, sourcePath);
13108
- if (!fs46.existsSync(absPath)) {
13433
+ const absPath = path39.resolve(dir, sourcePath);
13434
+ if (!fs47.existsSync(absPath)) {
13109
13435
  console.log(chalk25.red(`
13110
13436
  Path not found: ${sourcePath}
13111
13437
  `));
@@ -13120,7 +13446,7 @@ async function sourcesAddCommand(sourcePath) {
13120
13446
  }
13121
13447
  const existing = loadSourcesConfig(dir);
13122
13448
  const alreadyConfigured = existing.some(
13123
- (s) => s.path && path38.resolve(dir, s.path) === absPath
13449
+ (s) => s.path && path39.resolve(dir, s.path) === absPath
13124
13450
  );
13125
13451
  if (alreadyConfigured) {
13126
13452
  console.log(chalk25.yellow(`
@@ -13168,8 +13494,8 @@ async function sourcesRemoveCommand(name) {
13168
13494
  }
13169
13495
 
13170
13496
  // src/commands/publish.ts
13171
- import fs47 from "fs";
13172
- import path39 from "path";
13497
+ import fs48 from "fs";
13498
+ import path40 from "path";
13173
13499
  import chalk26 from "chalk";
13174
13500
  import ora7 from "ora";
13175
13501
  init_config();
@@ -13184,10 +13510,10 @@ async function publishCommand() {
13184
13510
  const spinner = ora7("Generating project summary...").start();
13185
13511
  try {
13186
13512
  const fingerprint = await collectFingerprint(dir);
13187
- const claudeMd = readFileOrNull(path39.join(dir, "CLAUDE.md"));
13513
+ const claudeMd = readFileOrNull(path40.join(dir, "CLAUDE.md"));
13188
13514
  const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
13189
13515
  const summary = {
13190
- name: fingerprint.packageName || path39.basename(dir),
13516
+ name: fingerprint.packageName || path40.basename(dir),
13191
13517
  version: "1.0.0",
13192
13518
  description: fingerprint.description || "",
13193
13519
  languages: fingerprint.languages,
@@ -13199,7 +13525,7 @@ async function publishCommand() {
13199
13525
  summary.conventions = claudeMd.slice(0, 2e3);
13200
13526
  }
13201
13527
  try {
13202
- const pkgContent = readFileOrNull(path39.join(dir, "package.json"));
13528
+ const pkgContent = readFileOrNull(path40.join(dir, "package.json"));
13203
13529
  if (pkgContent) {
13204
13530
  const pkg3 = JSON.parse(pkgContent);
13205
13531
  if (pkg3.scripts) {
@@ -13212,14 +13538,14 @@ async function publishCommand() {
13212
13538
  }
13213
13539
  } catch {
13214
13540
  }
13215
- const outputDir = path39.join(dir, ".caliber");
13216
- if (!fs47.existsSync(outputDir)) {
13217
- fs47.mkdirSync(outputDir, { recursive: true });
13541
+ const outputDir = path40.join(dir, ".caliber");
13542
+ if (!fs48.existsSync(outputDir)) {
13543
+ fs48.mkdirSync(outputDir, { recursive: true });
13218
13544
  }
13219
- const outputPath = path39.join(outputDir, "summary.json");
13220
- 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");
13221
13547
  spinner.succeed("Project summary published");
13222
- console.log(` ${chalk26.green("\u2713")} ${path39.relative(dir, outputPath)}`);
13548
+ console.log(` ${chalk26.green("\u2713")} ${path40.relative(dir, outputPath)}`);
13223
13549
  console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
13224
13550
  console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
13225
13551
  } catch (err) {
@@ -13232,7 +13558,7 @@ async function publishCommand() {
13232
13558
 
13233
13559
  // src/commands/bootstrap.ts
13234
13560
  init_builtin_skills();
13235
- import fs48 from "fs";
13561
+ import fs49 from "fs";
13236
13562
  import chalk27 from "chalk";
13237
13563
  var PLATFORM_SKILL_DIRS = {
13238
13564
  claude: ".claude/skills",
@@ -13253,8 +13579,8 @@ async function bootstrapCommand() {
13253
13579
  for (const skill of BUILTIN_SKILLS) {
13254
13580
  const skillDir = `${skillsDir}/${skill.name}`;
13255
13581
  const skillPath = `${skillDir}/SKILL.md`;
13256
- fs48.mkdirSync(skillDir, { recursive: true });
13257
- fs48.writeFileSync(skillPath, buildSkillContent(skill));
13582
+ fs49.mkdirSync(skillDir, { recursive: true });
13583
+ fs49.writeFileSync(skillPath, buildSkillContent(skill));
13258
13584
  written.push(skillPath);
13259
13585
  }
13260
13586
  }
@@ -13271,8 +13597,8 @@ async function bootstrapCommand() {
13271
13597
  }
13272
13598
 
13273
13599
  // src/commands/uninstall.ts
13274
- import fs49 from "fs";
13275
- import path40 from "path";
13600
+ import fs50 from "fs";
13601
+ import path41 from "path";
13276
13602
  import chalk28 from "chalk";
13277
13603
  import confirm3 from "@inquirer/confirm";
13278
13604
  init_pre_commit_block();
@@ -13280,18 +13606,18 @@ init_builtin_skills();
13280
13606
  init_config();
13281
13607
  var MANAGED_DOC_FILES = [
13282
13608
  "CLAUDE.md",
13283
- path40.join(".github", "copilot-instructions.md"),
13609
+ path41.join(".github", "copilot-instructions.md"),
13284
13610
  "AGENTS.md"
13285
13611
  ];
13286
13612
  var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
13287
- var CURSOR_RULES_DIR = path40.join(".cursor", "rules");
13613
+ var CURSOR_RULES_DIR = path41.join(".cursor", "rules");
13288
13614
  function removeCaliberCursorRules() {
13289
13615
  const removed = [];
13290
- if (!fs49.existsSync(CURSOR_RULES_DIR)) return removed;
13291
- 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)) {
13292
13618
  if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
13293
- const fullPath = path40.join(CURSOR_RULES_DIR, file);
13294
- fs49.unlinkSync(fullPath);
13619
+ const fullPath = path41.join(CURSOR_RULES_DIR, file);
13620
+ fs50.unlinkSync(fullPath);
13295
13621
  removed.push(fullPath);
13296
13622
  }
13297
13623
  }
@@ -13300,11 +13626,11 @@ function removeCaliberCursorRules() {
13300
13626
  function removeBuiltinSkills() {
13301
13627
  const removed = [];
13302
13628
  for (const skillsDir of SKILL_DIRS) {
13303
- if (!fs49.existsSync(skillsDir)) continue;
13629
+ if (!fs50.existsSync(skillsDir)) continue;
13304
13630
  for (const name of BUILTIN_SKILL_NAMES) {
13305
- const skillDir = path40.join(skillsDir, name);
13306
- if (fs49.existsSync(skillDir)) {
13307
- fs49.rmSync(skillDir, { recursive: true });
13631
+ const skillDir = path41.join(skillsDir, name);
13632
+ if (fs50.existsSync(skillDir)) {
13633
+ fs50.rmSync(skillDir, { recursive: true });
13308
13634
  removed.push(skillDir);
13309
13635
  }
13310
13636
  }
@@ -13314,15 +13640,15 @@ function removeBuiltinSkills() {
13314
13640
  function stripManagedBlocksFromFiles() {
13315
13641
  const modified = [];
13316
13642
  for (const filePath of MANAGED_DOC_FILES) {
13317
- if (!fs49.existsSync(filePath)) continue;
13318
- const original = fs49.readFileSync(filePath, "utf-8");
13643
+ if (!fs50.existsSync(filePath)) continue;
13644
+ const original = fs50.readFileSync(filePath, "utf-8");
13319
13645
  const stripped = stripManagedBlocks(original);
13320
13646
  if (stripped !== original) {
13321
13647
  const trimmed = stripped.trim();
13322
13648
  if (!trimmed || /^#\s*\S*$/.test(trimmed)) {
13323
- fs49.unlinkSync(filePath);
13649
+ fs50.unlinkSync(filePath);
13324
13650
  } else {
13325
- fs49.writeFileSync(filePath, stripped);
13651
+ fs50.writeFileSync(filePath, stripped);
13326
13652
  }
13327
13653
  modified.push(filePath);
13328
13654
  }
@@ -13330,8 +13656,8 @@ function stripManagedBlocksFromFiles() {
13330
13656
  return modified;
13331
13657
  }
13332
13658
  function removeDirectory(dir) {
13333
- if (!fs49.existsSync(dir)) return false;
13334
- fs49.rmSync(dir, { recursive: true });
13659
+ if (!fs50.existsSync(dir)) return false;
13660
+ fs50.rmSync(dir, { recursive: true });
13335
13661
  return true;
13336
13662
  }
13337
13663
  async function uninstallCommand(options) {
@@ -13388,8 +13714,8 @@ async function uninstallCommand(options) {
13388
13714
  console.log(` ${chalk28.red("\u2717")} ${skill}/`);
13389
13715
  }
13390
13716
  if (removedSkills.length > 0) actions.push("builtin skills");
13391
- if (fs49.existsSync("CALIBER_LEARNINGS.md")) {
13392
- fs49.unlinkSync("CALIBER_LEARNINGS.md");
13717
+ if (fs50.existsSync("CALIBER_LEARNINGS.md")) {
13718
+ fs50.unlinkSync("CALIBER_LEARNINGS.md");
13393
13719
  console.log(` ${chalk28.red("\u2717")} CALIBER_LEARNINGS.md`);
13394
13720
  actions.push("learnings file");
13395
13721
  }
@@ -13403,18 +13729,18 @@ async function uninstallCommand(options) {
13403
13729
  }
13404
13730
  trackUninstallExecuted();
13405
13731
  const configPath = getConfigFilePath();
13406
- if (fs49.existsSync(configPath)) {
13732
+ if (fs50.existsSync(configPath)) {
13407
13733
  console.log("");
13408
13734
  const removeConfig = options.force || await confirm3({
13409
13735
  message: `Remove global config (~/.caliber/config.json)? This affects all projects.`
13410
13736
  });
13411
13737
  if (removeConfig) {
13412
- fs49.unlinkSync(configPath);
13738
+ fs50.unlinkSync(configPath);
13413
13739
  console.log(` ${chalk28.red("\u2717")} ${configPath}`);
13414
- const configDir = path40.dirname(configPath);
13740
+ const configDir = path41.dirname(configPath);
13415
13741
  try {
13416
- const remaining = fs49.readdirSync(configDir);
13417
- if (remaining.length === 0) fs49.rmdirSync(configDir);
13742
+ const remaining = fs50.readdirSync(configDir);
13743
+ if (remaining.length === 0) fs50.rmdirSync(configDir);
13418
13744
  } catch {
13419
13745
  }
13420
13746
  }
@@ -13425,10 +13751,8 @@ async function uninstallCommand(options) {
13425
13751
  }
13426
13752
 
13427
13753
  // src/cli.ts
13428
- var __dirname = path41.dirname(fileURLToPath(import.meta.url));
13429
- var pkg = JSON.parse(
13430
- fs50.readFileSync(path41.resolve(__dirname, "..", "package.json"), "utf-8")
13431
- );
13754
+ var __dirname = path42.dirname(fileURLToPath(import.meta.url));
13755
+ var pkg = JSON.parse(fs51.readFileSync(path42.resolve(__dirname, "..", "package.json"), "utf-8"));
13432
13756
  var program = new Command();
13433
13757
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
13434
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");
@@ -13483,22 +13807,38 @@ function parseAgentOption(value) {
13483
13807
  if (value === "both") return ["claude", "cursor"];
13484
13808
  if (value === "all") return ["claude", "cursor", "codex", "opencode", "github-copilot"];
13485
13809
  const valid = ["claude", "cursor", "codex", "opencode", "github-copilot"];
13486
- const agents = [...new Set(value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a)))];
13810
+ const agents = [
13811
+ ...new Set(
13812
+ value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a))
13813
+ )
13814
+ ];
13487
13815
  if (agents.length === 0) {
13488
- console.error(`Invalid agent "${value}". Choose from: claude, cursor, codex, opencode, github-copilot (comma-separated for multiple)`);
13816
+ console.error(
13817
+ `Invalid agent "${value}". Choose from: claude, cursor, codex, opencode, github-copilot (comma-separated for multiple)`
13818
+ );
13489
13819
  process.exit(1);
13490
13820
  }
13491
13821
  return agents;
13492
13822
  }
13493
- program.command("init").description("Initialize your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot", parseAgentOption).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").action(tracked("init", initCommand));
13494
- program.command("bootstrap").description("Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init").action(tracked("bootstrap", bootstrapCommand));
13823
+ program.command("init").description("Initialize your project for AI-assisted development").option(
13824
+ "--agent <type>",
13825
+ "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot",
13826
+ parseAgentOption
13827
+ ).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").option("--thorough", "Deep analysis \u2014 more refinement passes for maximum quality").action(tracked("init", initCommand));
13828
+ program.command("bootstrap").description(
13829
+ "Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init"
13830
+ ).action(tracked("bootstrap", bootstrapCommand));
13495
13831
  program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
13496
13832
  program.command("uninstall").description("Remove all Caliber resources from this project").option("--force", "Skip confirmation prompt").action(tracked("uninstall", (options) => uninstallCommand(options)));
13497
13833
  program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
13498
13834
  program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate config").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
13499
13835
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
13500
13836
  program.command("skills").description("Discover and install community skills for your project").option("--query <terms>", 'Search for skills by topic (e.g. "react frontend")').option("--install <slugs>", "Install specific skills by slug (comma-separated)").action(tracked("skills", recommendCommand));
13501
- program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
13837
+ program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option(
13838
+ "--agent <type>",
13839
+ "Target agents (comma-separated): claude, cursor, codex, opencode, github-copilot",
13840
+ parseAgentOption
13841
+ ).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
13502
13842
  program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(tracked("refresh", refreshCommand));
13503
13843
  program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(tracked("hooks", hooksCommand));
13504
13844
  program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
@@ -13509,7 +13849,12 @@ sources.command("remove").description("Remove a configured source").argument("<n
13509
13849
  program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
13510
13850
  var learn = program.command("learn").description("Manage session learning \u2014 extract patterns from your AI coding sessions");
13511
13851
  learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").option("--prompt", "Record a user prompt event").action(tracked("learn:observe", learnObserveCommand));
13512
- learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
13852
+ learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(
13853
+ tracked(
13854
+ "learn:finalize",
13855
+ (opts) => learnFinalizeCommand(opts)
13856
+ )
13857
+ );
13513
13858
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
13514
13859
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
13515
13860
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
@@ -13518,15 +13863,15 @@ learn.command("delete <index>").description("Delete a learning by its index numb
13518
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));
13519
13864
 
13520
13865
  // src/utils/version-check.ts
13521
- import fs51 from "fs";
13522
- import path42 from "path";
13866
+ import fs52 from "fs";
13867
+ import path43 from "path";
13523
13868
  import { fileURLToPath as fileURLToPath2 } from "url";
13524
13869
  import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
13525
13870
  import chalk29 from "chalk";
13526
13871
  import ora8 from "ora";
13527
13872
  import confirm4 from "@inquirer/confirm";
13528
- var __dirname_vc = path42.dirname(fileURLToPath2(import.meta.url));
13529
- 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"));
13530
13875
  function getChannel(version) {
13531
13876
  const match = version.match(/-(dev|next)\./);
13532
13877
  return match ? match[1] : "latest";
@@ -13553,8 +13898,8 @@ function getInstalledVersion() {
13553
13898
  encoding: "utf-8",
13554
13899
  stdio: ["pipe", "pipe", "pipe"]
13555
13900
  }).trim();
13556
- const pkgPath = path42.join(globalRoot, "@rely-ai", "caliber", "package.json");
13557
- 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;
13558
13903
  } catch {
13559
13904
  return null;
13560
13905
  }