@rely-ai/caliber 1.31.0-dev.1774741028 → 1.31.0-dev.1774743306

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 +721 -585
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -9,155 +9,6 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/llm/config.ts
13
- var config_exports = {};
14
- __export(config_exports, {
15
- DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
16
- DEFAULT_MODELS: () => DEFAULT_MODELS,
17
- MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
18
- getConfigFilePath: () => getConfigFilePath,
19
- getDisplayModel: () => getDisplayModel,
20
- getFastModel: () => getFastModel,
21
- getMaxPromptTokens: () => getMaxPromptTokens,
22
- loadConfig: () => loadConfig,
23
- readConfigFile: () => readConfigFile,
24
- resolveFromEnv: () => resolveFromEnv,
25
- writeConfigFile: () => writeConfigFile
26
- });
27
- import fs4 from "fs";
28
- import path5 from "path";
29
- import os2 from "os";
30
- function getMaxPromptTokens() {
31
- const config = loadConfig();
32
- const model = process.env.CALIBER_MODEL || config?.model;
33
- const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
34
- const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
35
- return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
36
- }
37
- function loadConfig() {
38
- const envConfig = resolveFromEnv();
39
- if (envConfig) return envConfig;
40
- return readConfigFile();
41
- }
42
- function resolveFromEnv() {
43
- if (process.env.ANTHROPIC_API_KEY) {
44
- return {
45
- provider: "anthropic",
46
- apiKey: process.env.ANTHROPIC_API_KEY,
47
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
48
- };
49
- }
50
- if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
51
- return {
52
- provider: "vertex",
53
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
54
- vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
55
- vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
56
- vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
57
- };
58
- }
59
- if (process.env.OPENAI_API_KEY) {
60
- return {
61
- provider: "openai",
62
- apiKey: process.env.OPENAI_API_KEY,
63
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
64
- baseUrl: process.env.OPENAI_BASE_URL
65
- };
66
- }
67
- if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
68
- return {
69
- provider: "cursor",
70
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
71
- };
72
- }
73
- if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
74
- return {
75
- provider: "claude-cli",
76
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
77
- };
78
- }
79
- return null;
80
- }
81
- function readConfigFile() {
82
- try {
83
- if (!fs4.existsSync(CONFIG_FILE)) return null;
84
- const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
85
- const parsed = JSON.parse(raw);
86
- if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
87
- return null;
88
- }
89
- return parsed;
90
- } catch {
91
- return null;
92
- }
93
- }
94
- function writeConfigFile(config) {
95
- if (!fs4.existsSync(CONFIG_DIR)) {
96
- fs4.mkdirSync(CONFIG_DIR, { recursive: true });
97
- }
98
- const sanitized = { ...config };
99
- if (sanitized.apiKey) {
100
- sanitized.apiKey = sanitized.apiKey.trim();
101
- }
102
- fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
103
- }
104
- function getConfigFilePath() {
105
- return CONFIG_FILE;
106
- }
107
- function getDisplayModel(config) {
108
- if (config.model === "default" && config.provider === "claude-cli") {
109
- return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
110
- }
111
- return config.model;
112
- }
113
- function getFastModel() {
114
- if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
115
- const config = loadConfig();
116
- const provider = config?.provider;
117
- if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
118
- return process.env.ANTHROPIC_SMALL_FAST_MODEL;
119
- }
120
- if (config?.fastModel) return config.fastModel;
121
- if (provider) return DEFAULT_FAST_MODELS[provider];
122
- return void 0;
123
- }
124
- var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, MODEL_CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, INPUT_BUDGET_FRACTION, MAX_PROMPT_TOKENS_CAP, MIN_PROMPT_TOKENS, DEFAULT_FAST_MODELS;
125
- var init_config = __esm({
126
- "src/llm/config.ts"() {
127
- "use strict";
128
- CONFIG_DIR = path5.join(os2.homedir(), ".caliber");
129
- CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
130
- DEFAULT_MODELS = {
131
- anthropic: "claude-sonnet-4-6",
132
- vertex: "claude-sonnet-4-6",
133
- openai: "gpt-4.1",
134
- cursor: "sonnet-4.6",
135
- "claude-cli": "default"
136
- };
137
- MODEL_CONTEXT_WINDOWS = {
138
- "claude-sonnet-4-6": 2e5,
139
- "claude-opus-4-6": 2e5,
140
- "claude-haiku-4-5-20251001": 2e5,
141
- "claude-sonnet-4-5-20250514": 2e5,
142
- "gpt-4.1": 1e6,
143
- "gpt-4.1-mini": 1e6,
144
- "gpt-4o": 128e3,
145
- "gpt-4o-mini": 128e3,
146
- "sonnet-4.6": 2e5
147
- };
148
- DEFAULT_CONTEXT_WINDOW = 2e5;
149
- INPUT_BUDGET_FRACTION = 0.6;
150
- MAX_PROMPT_TOKENS_CAP = 3e5;
151
- MIN_PROMPT_TOKENS = 3e4;
152
- DEFAULT_FAST_MODELS = {
153
- anthropic: "claude-haiku-4-5-20251001",
154
- vertex: "claude-haiku-4-5-20251001",
155
- openai: "gpt-4.1-mini",
156
- cursor: "gpt-5.3-codex-fast"
157
- };
158
- }
159
- });
160
-
161
12
  // src/lib/resolve-caliber.ts
162
13
  var resolve_caliber_exports = {};
163
14
  __export(resolve_caliber_exports, {
@@ -166,8 +17,8 @@ __export(resolve_caliber_exports, {
166
17
  resetResolvedCaliber: () => resetResolvedCaliber,
167
18
  resolveCaliber: () => resolveCaliber
168
19
  });
169
- import fs6 from "fs";
170
- import { execSync as execSync4 } from "child_process";
20
+ import fs2 from "fs";
21
+ import { execSync as execSync3 } from "child_process";
171
22
  function resolveCaliber() {
172
23
  if (_resolved) return _resolved;
173
24
  const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
@@ -177,7 +28,7 @@ function resolveCaliber() {
177
28
  }
178
29
  try {
179
30
  const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
180
- execSync4(whichCmd, {
31
+ execSync3(whichCmd, {
181
32
  encoding: "utf-8",
182
33
  stdio: ["pipe", "pipe", "pipe"]
183
34
  });
@@ -186,7 +37,7 @@ function resolveCaliber() {
186
37
  } catch {
187
38
  }
188
39
  const binPath = process.argv[1];
189
- if (binPath && /caliber/.test(binPath) && fs6.existsSync(binPath)) {
40
+ if (binPath && /caliber/.test(binPath) && fs2.existsSync(binPath)) {
190
41
  _resolved = binPath;
191
42
  return _resolved;
192
43
  }
@@ -214,156 +65,6 @@ var init_resolve_caliber = __esm({
214
65
  }
215
66
  });
216
67
 
217
- // src/llm/types.ts
218
- var types_exports = {};
219
- __export(types_exports, {
220
- isSeatBased: () => isSeatBased
221
- });
222
- function isSeatBased(provider) {
223
- return SEAT_BASED_PROVIDERS.has(provider);
224
- }
225
- var SEAT_BASED_PROVIDERS;
226
- var init_types = __esm({
227
- "src/llm/types.ts"() {
228
- "use strict";
229
- SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
230
- }
231
- });
232
-
233
- // src/writers/pre-commit-block.ts
234
- var pre_commit_block_exports = {};
235
- __export(pre_commit_block_exports, {
236
- appendLearningsBlock: () => appendLearningsBlock,
237
- appendPreCommitBlock: () => appendPreCommitBlock,
238
- appendSyncBlock: () => appendSyncBlock,
239
- getCursorLearningsRule: () => getCursorLearningsRule,
240
- getCursorPreCommitRule: () => getCursorPreCommitRule,
241
- getCursorSyncRule: () => getCursorSyncRule,
242
- hasLearningsBlock: () => hasLearningsBlock,
243
- hasPreCommitBlock: () => hasPreCommitBlock,
244
- hasSyncBlock: () => hasSyncBlock
245
- });
246
- function getPreCommitBlock() {
247
- const bin = resolveCaliber();
248
- return `${BLOCK_START}
249
- ## Before Committing
250
-
251
- **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
252
-
253
- First, check if the pre-commit hook is already installed:
254
- \`\`\`bash
255
- grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
256
- \`\`\`
257
-
258
- - If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
259
- - If **no-hook**: run Caliber manually before committing:
260
- 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
261
- 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
262
- 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
263
-
264
- If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."
265
- ${BLOCK_END}`;
266
- }
267
- function getCursorRuleContent() {
268
- const bin = resolveCaliber();
269
- return `---
270
- description: Run Caliber sync before git commits to keep agent configs up to date
271
- alwaysApply: true
272
- ---
273
- Before every git commit, check if the Caliber pre-commit hook is installed:
274
- Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
275
-
276
- If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
277
- If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
278
-
279
- If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
280
- `;
281
- }
282
- function hasPreCommitBlock(content) {
283
- return content.includes(BLOCK_START);
284
- }
285
- function appendPreCommitBlock(content) {
286
- if (hasPreCommitBlock(content)) return content;
287
- const trimmed = content.trimEnd();
288
- return trimmed + "\n\n" + getPreCommitBlock() + "\n";
289
- }
290
- function getCursorPreCommitRule() {
291
- return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
292
- }
293
- function hasLearningsBlock(content) {
294
- return content.includes(LEARNINGS_BLOCK_START);
295
- }
296
- function appendLearningsBlock(content) {
297
- if (hasLearningsBlock(content)) return content;
298
- const trimmed = content.trimEnd();
299
- return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
300
- }
301
- function getCursorLearningsRule() {
302
- return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
303
- }
304
- function getSyncBlock() {
305
- const bin = resolveCaliber();
306
- return `${SYNC_BLOCK_START}
307
- ## Context Sync
308
-
309
- This project uses [Caliber](https://github.com/caliber-ai-org/ai-setup) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
310
- Configs update automatically before each commit via \`${bin} refresh\`.
311
- If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
312
- ${SYNC_BLOCK_END}`;
313
- }
314
- function hasSyncBlock(content) {
315
- return content.includes(SYNC_BLOCK_START);
316
- }
317
- function appendSyncBlock(content) {
318
- if (hasSyncBlock(content)) return content;
319
- const trimmed = content.trimEnd();
320
- return trimmed + "\n\n" + getSyncBlock() + "\n";
321
- }
322
- function getCursorSyncContent() {
323
- const bin = resolveCaliber();
324
- return `---
325
- description: This project uses Caliber for automatic AI agent context sync
326
- alwaysApply: true
327
- ---
328
- This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
329
- in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
330
- If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
331
- `;
332
- }
333
- function getCursorSyncRule() {
334
- return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
335
- }
336
- var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME;
337
- var init_pre_commit_block = __esm({
338
- "src/writers/pre-commit-block.ts"() {
339
- "use strict";
340
- init_resolve_caliber();
341
- BLOCK_START = "<!-- caliber:managed:pre-commit -->";
342
- BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
343
- MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
344
- CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
345
- LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
346
- LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
347
- LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
348
- ## Session Learnings
349
-
350
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
351
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
352
- ${LEARNINGS_BLOCK_END}`;
353
- CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
354
- CURSOR_LEARNINGS_CONTENT = `---
355
- description: Reference session-learned patterns from CALIBER_LEARNINGS.md
356
- alwaysApply: true
357
- ---
358
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
359
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
360
- `;
361
- SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
362
- SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
363
- CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
364
- }
365
- });
366
-
367
68
  // src/lib/builtin-skills.ts
368
69
  var builtin_skills_exports = {};
369
70
  __export(builtin_skills_exports, {
@@ -375,8 +76,8 @@ __export(builtin_skills_exports, {
375
76
  buildSkillContent: () => buildSkillContent,
376
77
  ensureBuiltinSkills: () => ensureBuiltinSkills
377
78
  });
378
- import fs17 from "fs";
379
- import path16 from "path";
79
+ import fs3 from "fs";
80
+ import path3 from "path";
380
81
  function buildSkillContent(skill) {
381
82
  const frontmatter = `---
382
83
  name: ${skill.name}
@@ -541,20 +242,37 @@ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "HOOK_ACTIVE" || ech
541
242
  caliber hooks --install
542
243
  \`\`\`
543
244
 
544
- ### Step 3: Check if agent configs exist
245
+ ### Step 3: Detect agents and check if configs exist
246
+
247
+ First, detect which coding agents are configured in this project:
248
+ \`\`\`bash
249
+ AGENTS=""
250
+ [ -d .claude ] && AGENTS="claude"
251
+ [ -d .cursor ] && AGENTS="\${AGENTS:+$AGENTS,}cursor"
252
+ [ -d .agents ] || [ -f AGENTS.md ] && AGENTS="\${AGENTS:+$AGENTS,}codex"
253
+ [ -f .github/copilot-instructions.md ] && AGENTS="\${AGENTS:+$AGENTS,}github-copilot"
254
+ echo "DETECTED_AGENTS=\${AGENTS:-none}"
255
+ \`\`\`
545
256
 
257
+ If no agents are detected, ask the user which coding agents they use (Claude Code, Cursor, Codex, GitHub Copilot).
258
+ Build the agent list from their answer as a comma-separated string (e.g. "claude,cursor").
259
+
260
+ Then check if agent configs exist:
546
261
  \`\`\`bash
547
262
  echo "CLAUDE_MD=$([ -f CLAUDE.md ] && echo exists || echo missing)"
548
263
  echo "CURSOR_RULES=$([ -d .cursor/rules ] && ls .cursor/rules/*.mdc 2>/dev/null | wc -l | tr -d ' ' || echo 0)"
549
264
  echo "AGENTS_MD=$([ -f AGENTS.md ] && echo exists || echo missing)"
265
+ echo "COPILOT=$([ -f .github/copilot-instructions.md ] && echo exists || echo missing)"
550
266
  \`\`\`
551
267
 
552
- - If configs exist \u2192 Tell the user which configs are present. Move to Step 4.
268
+ - If configs exist for the detected agents \u2192 Tell the user which configs are present. Move to Step 4.
553
269
  - If configs are missing \u2192 Tell the user: "No agent configs found. I'll generate them now."
270
+ Use the detected or user-selected agent list:
554
271
  \`\`\`bash
555
- caliber init --auto-approve
272
+ caliber init --auto-approve --agent <comma-separated-agents>
556
273
  \`\`\`
557
- This generates CLAUDE.md, Cursor rules, AGENTS.md, and skills for all detected agents.
274
+ For example: \`caliber init --auto-approve --agent claude,cursor\`
275
+ This generates CLAUDE.md, Cursor rules, AGENTS.md, skills, and sync infrastructure for the specified agents.
558
276
 
559
277
  ### Step 4: Check if configs are fresh
560
278
 
@@ -688,52 +406,402 @@ From now on, every commit keeps all your agent configs in sync automatically.
688
406
  - The user is in the middle of time-sensitive work
689
407
  `;
690
408
  }
691
- function ensureBuiltinSkills() {
692
- const written = [];
693
- for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
694
- if (!fs17.existsSync(platformDir)) continue;
695
- for (const skill of BUILTIN_SKILLS) {
696
- const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
697
- fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
698
- fs17.writeFileSync(skillPath, buildSkillContent(skill));
699
- written.push(skillPath);
409
+ function ensureBuiltinSkills() {
410
+ const written = [];
411
+ for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
412
+ if (!fs3.existsSync(platformDir)) continue;
413
+ for (const skill of BUILTIN_SKILLS) {
414
+ const skillPath = path3.join(skillsDir, skill.name, "SKILL.md");
415
+ fs3.mkdirSync(path3.dirname(skillPath), { recursive: true });
416
+ fs3.writeFileSync(skillPath, buildSkillContent(skill));
417
+ written.push(skillPath);
418
+ }
419
+ }
420
+ return written;
421
+ }
422
+ var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
423
+ var init_builtin_skills = __esm({
424
+ "src/lib/builtin-skills.ts"() {
425
+ "use strict";
426
+ init_resolve_caliber();
427
+ FIND_SKILLS_SKILL = {
428
+ name: "find-skills",
429
+ description: "Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.",
430
+ get content() {
431
+ return getFindSkillsContent();
432
+ }
433
+ };
434
+ SAVE_LEARNING_SKILL = {
435
+ name: "save-learning",
436
+ description: "Saves user instructions as persistent learnings for future sessions. Use when the user says 'remember this', 'always do X', 'from now on', 'never do Y', or gives any instruction they want persisted across sessions. Proactively suggest when the user states a preference, convention, or rule they clearly want followed in the future.",
437
+ get content() {
438
+ return getSaveLearningContent();
439
+ }
440
+ };
441
+ SETUP_CALIBER_SKILL = {
442
+ name: "setup-caliber",
443
+ description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
444
+ get content() {
445
+ return getSetupCaliberContent();
446
+ }
447
+ };
448
+ BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
449
+ PLATFORM_CONFIGS = [
450
+ { platformDir: ".claude", skillsDir: path3.join(".claude", "skills") },
451
+ { platformDir: ".cursor", skillsDir: path3.join(".cursor", "skills") },
452
+ { platformDir: ".agents", skillsDir: path3.join(".agents", "skills") }
453
+ ];
454
+ BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
455
+ }
456
+ });
457
+
458
+ // src/writers/pre-commit-block.ts
459
+ var pre_commit_block_exports = {};
460
+ __export(pre_commit_block_exports, {
461
+ appendLearningsBlock: () => appendLearningsBlock,
462
+ appendManagedBlocks: () => appendManagedBlocks,
463
+ appendPreCommitBlock: () => appendPreCommitBlock,
464
+ appendSyncBlock: () => appendSyncBlock,
465
+ getCursorLearningsRule: () => getCursorLearningsRule,
466
+ getCursorPreCommitRule: () => getCursorPreCommitRule,
467
+ getCursorSetupRule: () => getCursorSetupRule,
468
+ getCursorSyncRule: () => getCursorSyncRule,
469
+ hasLearningsBlock: () => hasLearningsBlock,
470
+ hasPreCommitBlock: () => hasPreCommitBlock,
471
+ hasSyncBlock: () => hasSyncBlock
472
+ });
473
+ function getCaliberNotFoundFallback(platform, bin) {
474
+ switch (platform) {
475
+ case "claude":
476
+ return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
477
+ case "codex":
478
+ return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
479
+ case "copilot":
480
+ return `If \`${bin}\` is not found, install it by running \`npm install -g @rely-ai/caliber\`, then run \`caliber hooks --install\` and \`caliber refresh\`.`;
481
+ }
482
+ }
483
+ function getPreCommitBlock(platform = "claude") {
484
+ const bin = resolveCaliber();
485
+ return `${BLOCK_START}
486
+ ## Before Committing
487
+
488
+ **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
489
+
490
+ First, check if the pre-commit hook is already installed:
491
+ \`\`\`bash
492
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
493
+ \`\`\`
494
+
495
+ - If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
496
+ - If **no-hook**: run Caliber manually before committing:
497
+ 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
498
+ 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
499
+ 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
500
+
501
+ ${getCaliberNotFoundFallback(platform, bin)}
502
+ ${BLOCK_END}`;
503
+ }
504
+ function getCursorRuleContent() {
505
+ const bin = resolveCaliber();
506
+ return `---
507
+ description: Run Caliber sync before git commits to keep agent configs up to date
508
+ alwaysApply: true
509
+ ---
510
+ Before every git commit, check if the Caliber pre-commit hook is installed:
511
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
512
+
513
+ If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
514
+ If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
515
+
516
+ If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
517
+ `;
518
+ }
519
+ function hasPreCommitBlock(content) {
520
+ return content.includes(BLOCK_START);
521
+ }
522
+ function appendPreCommitBlock(content, platform = "claude") {
523
+ if (hasPreCommitBlock(content)) return content;
524
+ const trimmed = content.trimEnd();
525
+ return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
526
+ }
527
+ function getCursorPreCommitRule() {
528
+ return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
529
+ }
530
+ function hasLearningsBlock(content) {
531
+ return content.includes(LEARNINGS_BLOCK_START);
532
+ }
533
+ function appendLearningsBlock(content) {
534
+ if (hasLearningsBlock(content)) return content;
535
+ const trimmed = content.trimEnd();
536
+ return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
537
+ }
538
+ function getCursorLearningsRule() {
539
+ return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
540
+ }
541
+ function getSyncSetupInstruction(platform, bin) {
542
+ switch (platform) {
543
+ case "claude":
544
+ return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
545
+ case "codex":
546
+ return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
547
+ case "copilot":
548
+ return `If the pre-commit hook is not set up, install it by running:
549
+ \`\`\`bash
550
+ npm install -g @rely-ai/caliber
551
+ caliber hooks --install
552
+ caliber refresh
553
+ git add ${MANAGED_DOC_PATHS} 2>/dev/null
554
+ \`\`\``;
555
+ }
556
+ }
557
+ function getSyncBlock(platform = "claude") {
558
+ const bin = resolveCaliber();
559
+ return `${SYNC_BLOCK_START}
560
+ ## Context Sync
561
+
562
+ This project uses [Caliber](https://github.com/caliber-ai-org/ai-setup) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
563
+ Configs update automatically before each commit via \`${bin} refresh\`.
564
+ ${getSyncSetupInstruction(platform, bin)}
565
+ ${SYNC_BLOCK_END}`;
566
+ }
567
+ function hasSyncBlock(content) {
568
+ return content.includes(SYNC_BLOCK_START);
569
+ }
570
+ function appendSyncBlock(content, platform = "claude") {
571
+ if (hasSyncBlock(content)) return content;
572
+ const trimmed = content.trimEnd();
573
+ return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
574
+ }
575
+ function appendManagedBlocks(content, platform = "claude") {
576
+ return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
577
+ }
578
+ function getCursorSyncContent() {
579
+ const bin = resolveCaliber();
580
+ return `---
581
+ description: This project uses Caliber for automatic AI agent context sync
582
+ alwaysApply: true
583
+ ---
584
+ This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
585
+ in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
586
+ If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
587
+ `;
588
+ }
589
+ function getCursorSyncRule() {
590
+ return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
591
+ }
592
+ function getCursorSetupContent() {
593
+ return `---
594
+ description: Check if Caliber agent config sync is set up and offer installation on new sessions
595
+ alwaysApply: true
596
+ ---
597
+ This project uses Caliber to keep AI agent configs in sync automatically.
598
+ At the start of each session, silently check if sync is active:
599
+
600
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
601
+
602
+ If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
603
+ If NO_SYNC: Tell the user: "This project uses Caliber for automatic agent config sync, but it's not set up on your machine yet. Want me to set it up? It takes about 30 seconds."
604
+
605
+ If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
606
+ `;
607
+ }
608
+ function getCursorSetupRule() {
609
+ return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
610
+ }
611
+ var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME, CURSOR_SETUP_FILENAME;
612
+ var init_pre_commit_block = __esm({
613
+ "src/writers/pre-commit-block.ts"() {
614
+ "use strict";
615
+ init_resolve_caliber();
616
+ BLOCK_START = "<!-- caliber:managed:pre-commit -->";
617
+ BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
618
+ MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
619
+ CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
620
+ LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
621
+ LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
622
+ LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
623
+ ## Session Learnings
624
+
625
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
626
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
627
+ ${LEARNINGS_BLOCK_END}`;
628
+ CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
629
+ CURSOR_LEARNINGS_CONTENT = `---
630
+ description: Reference session-learned patterns from CALIBER_LEARNINGS.md
631
+ alwaysApply: true
632
+ ---
633
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
634
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
635
+ `;
636
+ SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
637
+ SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
638
+ CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
639
+ CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
640
+ }
641
+ });
642
+
643
+ // src/llm/config.ts
644
+ var config_exports = {};
645
+ __export(config_exports, {
646
+ DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
647
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
648
+ MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
649
+ getConfigFilePath: () => getConfigFilePath,
650
+ getDisplayModel: () => getDisplayModel,
651
+ getFastModel: () => getFastModel,
652
+ getMaxPromptTokens: () => getMaxPromptTokens,
653
+ loadConfig: () => loadConfig,
654
+ readConfigFile: () => readConfigFile,
655
+ resolveFromEnv: () => resolveFromEnv,
656
+ writeConfigFile: () => writeConfigFile
657
+ });
658
+ import fs6 from "fs";
659
+ import path6 from "path";
660
+ import os2 from "os";
661
+ function getMaxPromptTokens() {
662
+ const config = loadConfig();
663
+ const model = process.env.CALIBER_MODEL || config?.model;
664
+ const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
665
+ const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
666
+ return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
667
+ }
668
+ function loadConfig() {
669
+ const envConfig = resolveFromEnv();
670
+ if (envConfig) return envConfig;
671
+ return readConfigFile();
672
+ }
673
+ function resolveFromEnv() {
674
+ if (process.env.ANTHROPIC_API_KEY) {
675
+ return {
676
+ provider: "anthropic",
677
+ apiKey: process.env.ANTHROPIC_API_KEY,
678
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
679
+ };
680
+ }
681
+ if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
682
+ return {
683
+ provider: "vertex",
684
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
685
+ vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
686
+ vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
687
+ vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
688
+ };
689
+ }
690
+ if (process.env.OPENAI_API_KEY) {
691
+ return {
692
+ provider: "openai",
693
+ apiKey: process.env.OPENAI_API_KEY,
694
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
695
+ baseUrl: process.env.OPENAI_BASE_URL
696
+ };
697
+ }
698
+ if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
699
+ return {
700
+ provider: "cursor",
701
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
702
+ };
703
+ }
704
+ if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
705
+ return {
706
+ provider: "claude-cli",
707
+ model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
708
+ };
709
+ }
710
+ return null;
711
+ }
712
+ function readConfigFile() {
713
+ try {
714
+ if (!fs6.existsSync(CONFIG_FILE)) return null;
715
+ const raw = fs6.readFileSync(CONFIG_FILE, "utf-8");
716
+ const parsed = JSON.parse(raw);
717
+ if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
718
+ return null;
700
719
  }
720
+ return parsed;
721
+ } catch {
722
+ return null;
701
723
  }
702
- return written;
703
724
  }
704
- var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
705
- var init_builtin_skills = __esm({
706
- "src/lib/builtin-skills.ts"() {
725
+ function writeConfigFile(config) {
726
+ if (!fs6.existsSync(CONFIG_DIR)) {
727
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
728
+ }
729
+ const sanitized = { ...config };
730
+ if (sanitized.apiKey) {
731
+ sanitized.apiKey = sanitized.apiKey.trim();
732
+ }
733
+ fs6.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
734
+ }
735
+ function getConfigFilePath() {
736
+ return CONFIG_FILE;
737
+ }
738
+ function getDisplayModel(config) {
739
+ if (config.model === "default" && config.provider === "claude-cli") {
740
+ return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
741
+ }
742
+ return config.model;
743
+ }
744
+ function getFastModel() {
745
+ if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
746
+ const config = loadConfig();
747
+ const provider = config?.provider;
748
+ if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
749
+ return process.env.ANTHROPIC_SMALL_FAST_MODEL;
750
+ }
751
+ if (config?.fastModel) return config.fastModel;
752
+ if (provider) return DEFAULT_FAST_MODELS[provider];
753
+ return void 0;
754
+ }
755
+ var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, MODEL_CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, INPUT_BUDGET_FRACTION, MAX_PROMPT_TOKENS_CAP, MIN_PROMPT_TOKENS, DEFAULT_FAST_MODELS;
756
+ var init_config = __esm({
757
+ "src/llm/config.ts"() {
707
758
  "use strict";
708
- init_resolve_caliber();
709
- FIND_SKILLS_SKILL = {
710
- name: "find-skills",
711
- description: "Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.",
712
- get content() {
713
- return getFindSkillsContent();
714
- }
759
+ CONFIG_DIR = path6.join(os2.homedir(), ".caliber");
760
+ CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
761
+ DEFAULT_MODELS = {
762
+ anthropic: "claude-sonnet-4-6",
763
+ vertex: "claude-sonnet-4-6",
764
+ openai: "gpt-4.1",
765
+ cursor: "sonnet-4.6",
766
+ "claude-cli": "default"
715
767
  };
716
- SAVE_LEARNING_SKILL = {
717
- name: "save-learning",
718
- description: "Saves user instructions as persistent learnings for future sessions. Use when the user says 'remember this', 'always do X', 'from now on', 'never do Y', or gives any instruction they want persisted across sessions. Proactively suggest when the user states a preference, convention, or rule they clearly want followed in the future.",
719
- get content() {
720
- return getSaveLearningContent();
721
- }
768
+ MODEL_CONTEXT_WINDOWS = {
769
+ "claude-sonnet-4-6": 2e5,
770
+ "claude-opus-4-6": 2e5,
771
+ "claude-haiku-4-5-20251001": 2e5,
772
+ "claude-sonnet-4-5-20250514": 2e5,
773
+ "gpt-4.1": 1e6,
774
+ "gpt-4.1-mini": 1e6,
775
+ "gpt-4o": 128e3,
776
+ "gpt-4o-mini": 128e3,
777
+ "sonnet-4.6": 2e5
722
778
  };
723
- SETUP_CALIBER_SKILL = {
724
- name: "setup-caliber",
725
- description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
726
- get content() {
727
- return getSetupCaliberContent();
728
- }
779
+ DEFAULT_CONTEXT_WINDOW = 2e5;
780
+ INPUT_BUDGET_FRACTION = 0.6;
781
+ MAX_PROMPT_TOKENS_CAP = 3e5;
782
+ MIN_PROMPT_TOKENS = 3e4;
783
+ DEFAULT_FAST_MODELS = {
784
+ anthropic: "claude-haiku-4-5-20251001",
785
+ vertex: "claude-haiku-4-5-20251001",
786
+ openai: "gpt-4.1-mini",
787
+ cursor: "gpt-5.3-codex-fast"
729
788
  };
730
- BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
731
- PLATFORM_CONFIGS = [
732
- { platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
733
- { platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
734
- { platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
735
- ];
736
- BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
789
+ }
790
+ });
791
+
792
+ // src/llm/types.ts
793
+ var types_exports = {};
794
+ __export(types_exports, {
795
+ isSeatBased: () => isSeatBased
796
+ });
797
+ function isSeatBased(provider) {
798
+ return SEAT_BASED_PROVIDERS.has(provider);
799
+ }
800
+ var SEAT_BASED_PROVIDERS;
801
+ var init_types = __esm({
802
+ "src/llm/types.ts"() {
803
+ "use strict";
804
+ SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
737
805
  }
738
806
  });
739
807
 
@@ -1062,8 +1130,8 @@ import chalk14 from "chalk";
1062
1130
  import fs33 from "fs";
1063
1131
 
1064
1132
  // src/fingerprint/index.ts
1065
- import fs8 from "fs";
1066
- import path7 from "path";
1133
+ import fs9 from "fs";
1134
+ import path8 from "path";
1067
1135
 
1068
1136
  // src/fingerprint/git.ts
1069
1137
  import { execSync } from "child_process";
@@ -1171,8 +1239,8 @@ function scan(base, rel, depth, maxDepth, result) {
1171
1239
  }
1172
1240
 
1173
1241
  // src/fingerprint/existing-config.ts
1174
- import fs2 from "fs";
1175
- import path3 from "path";
1242
+ import fs4 from "fs";
1243
+ import path4 from "path";
1176
1244
 
1177
1245
  // src/constants.ts
1178
1246
  import path2 from "path";
@@ -1207,97 +1275,121 @@ var LEARNING_LAST_ERROR_FILE = "last-error.json";
1207
1275
  var MIN_SESSIONS_FOR_COMPARISON = 3;
1208
1276
 
1209
1277
  // src/fingerprint/existing-config.ts
1278
+ init_builtin_skills();
1279
+ init_pre_commit_block();
1280
+ var MANAGED_CURSOR_RULES = /* @__PURE__ */ new Set([
1281
+ getCursorPreCommitRule().filename,
1282
+ getCursorLearningsRule().filename,
1283
+ getCursorSyncRule().filename,
1284
+ getCursorSetupRule().filename
1285
+ ]);
1210
1286
  function readExistingConfigs(dir) {
1211
1287
  const configs = {};
1212
- const readmeMdPath = path3.join(dir, "README.md");
1213
- if (fs2.existsSync(readmeMdPath)) {
1214
- configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
1288
+ const readmeMdPath = path4.join(dir, "README.md");
1289
+ if (fs4.existsSync(readmeMdPath)) {
1290
+ configs.readmeMd = fs4.readFileSync(readmeMdPath, "utf-8");
1215
1291
  }
1216
- const agentsMdPath = path3.join(dir, "AGENTS.md");
1217
- if (fs2.existsSync(agentsMdPath)) {
1218
- configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
1292
+ const agentsMdPath = path4.join(dir, "AGENTS.md");
1293
+ if (fs4.existsSync(agentsMdPath)) {
1294
+ configs.agentsMd = fs4.readFileSync(agentsMdPath, "utf-8");
1219
1295
  }
1220
- const claudeMdPath = path3.join(dir, "CLAUDE.md");
1221
- if (fs2.existsSync(claudeMdPath)) {
1222
- configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
1296
+ const claudeMdPath = path4.join(dir, "CLAUDE.md");
1297
+ if (fs4.existsSync(claudeMdPath)) {
1298
+ configs.claudeMd = fs4.readFileSync(claudeMdPath, "utf-8");
1223
1299
  }
1224
- const claudeSettingsPath = path3.join(dir, ".claude", "settings.json");
1225
- if (fs2.existsSync(claudeSettingsPath)) {
1300
+ const claudeSettingsPath = path4.join(dir, ".claude", "settings.json");
1301
+ if (fs4.existsSync(claudeSettingsPath)) {
1226
1302
  try {
1227
- configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
1303
+ configs.claudeSettings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
1228
1304
  } catch {
1229
1305
  }
1230
1306
  }
1231
- const skillsDir = path3.join(dir, ".claude", "skills");
1232
- if (fs2.existsSync(skillsDir)) {
1307
+ const skillsDir = path4.join(dir, ".claude", "skills");
1308
+ if (fs4.existsSync(skillsDir)) {
1233
1309
  try {
1234
- const entries = fs2.readdirSync(skillsDir);
1310
+ const entries = fs4.readdirSync(skillsDir);
1235
1311
  const skills = [];
1236
1312
  for (const entry of entries) {
1237
- const entryPath = path3.join(skillsDir, entry);
1238
- const skillMdPath = path3.join(entryPath, "SKILL.md");
1239
- if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
1240
- skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
1313
+ if (BUILTIN_SKILL_NAMES.has(entry)) continue;
1314
+ const entryPath = path4.join(skillsDir, entry);
1315
+ const skillMdPath = path4.join(entryPath, "SKILL.md");
1316
+ if (fs4.statSync(entryPath).isDirectory() && fs4.existsSync(skillMdPath)) {
1317
+ skills.push({ filename: `${entry}/SKILL.md`, content: fs4.readFileSync(skillMdPath, "utf-8") });
1241
1318
  } else if (entry.endsWith(".md")) {
1242
- skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
1319
+ skills.push({ filename: entry, content: fs4.readFileSync(entryPath, "utf-8") });
1243
1320
  }
1244
1321
  }
1245
1322
  if (skills.length > 0) configs.claudeSkills = skills;
1246
1323
  } catch {
1247
1324
  }
1248
1325
  }
1249
- const cursorrulesPath = path3.join(dir, ".cursorrules");
1250
- if (fs2.existsSync(cursorrulesPath)) {
1251
- configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
1326
+ const cursorrulesPath = path4.join(dir, ".cursorrules");
1327
+ if (fs4.existsSync(cursorrulesPath)) {
1328
+ configs.cursorrules = fs4.readFileSync(cursorrulesPath, "utf-8");
1252
1329
  }
1253
- const cursorRulesDir = path3.join(dir, ".cursor", "rules");
1254
- if (fs2.existsSync(cursorRulesDir)) {
1330
+ const cursorRulesDir = path4.join(dir, ".cursor", "rules");
1331
+ if (fs4.existsSync(cursorRulesDir)) {
1255
1332
  try {
1256
- const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
1333
+ const files = fs4.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc") && !MANAGED_CURSOR_RULES.has(f));
1257
1334
  configs.cursorRules = files.map((f) => ({
1258
1335
  filename: f,
1259
- content: fs2.readFileSync(path3.join(cursorRulesDir, f), "utf-8")
1336
+ content: fs4.readFileSync(path4.join(cursorRulesDir, f), "utf-8")
1260
1337
  }));
1261
1338
  } catch {
1262
1339
  }
1263
1340
  }
1264
- const cursorSkillsDir = path3.join(dir, ".cursor", "skills");
1265
- if (fs2.existsSync(cursorSkillsDir)) {
1341
+ const cursorSkillsDir = path4.join(dir, ".cursor", "skills");
1342
+ if (fs4.existsSync(cursorSkillsDir)) {
1266
1343
  try {
1267
- const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
1268
- return fs2.statSync(path3.join(cursorSkillsDir, f)).isDirectory();
1344
+ const slugs = fs4.readdirSync(cursorSkillsDir).filter((f) => {
1345
+ return fs4.statSync(path4.join(cursorSkillsDir, f)).isDirectory() && !BUILTIN_SKILL_NAMES.has(f);
1269
1346
  });
1270
- configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path3.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
1347
+ configs.cursorSkills = slugs.filter((slug) => fs4.existsSync(path4.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
1271
1348
  name,
1272
1349
  filename: "SKILL.md",
1273
- content: fs2.readFileSync(path3.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
1350
+ content: fs4.readFileSync(path4.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
1274
1351
  }));
1275
1352
  } catch {
1276
1353
  }
1277
1354
  }
1278
- const mcpJsonPath = path3.join(dir, ".mcp.json");
1279
- if (fs2.existsSync(mcpJsonPath)) {
1355
+ const mcpJsonPath = path4.join(dir, ".mcp.json");
1356
+ if (fs4.existsSync(mcpJsonPath)) {
1280
1357
  try {
1281
- const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
1358
+ const mcpJson = JSON.parse(fs4.readFileSync(mcpJsonPath, "utf-8"));
1282
1359
  if (mcpJson.mcpServers) {
1283
1360
  configs.claudeMcpServers = mcpJson.mcpServers;
1284
1361
  }
1285
1362
  } catch {
1286
1363
  }
1287
1364
  }
1288
- const cursorMcpPath = path3.join(dir, ".cursor", "mcp.json");
1289
- if (fs2.existsSync(cursorMcpPath)) {
1365
+ const cursorMcpPath = path4.join(dir, ".cursor", "mcp.json");
1366
+ if (fs4.existsSync(cursorMcpPath)) {
1290
1367
  try {
1291
- const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
1368
+ const cursorMcpJson = JSON.parse(fs4.readFileSync(cursorMcpPath, "utf-8"));
1292
1369
  if (cursorMcpJson.mcpServers) {
1293
1370
  configs.cursorMcpServers = cursorMcpJson.mcpServers;
1294
1371
  }
1295
1372
  } catch {
1296
1373
  }
1297
1374
  }
1298
- if (fs2.existsSync(PERSONAL_LEARNINGS_FILE)) {
1375
+ const copilotPath = path4.join(dir, ".github", "copilot-instructions.md");
1376
+ if (fs4.existsSync(copilotPath)) {
1377
+ configs.copilotInstructions = fs4.readFileSync(copilotPath, "utf-8");
1378
+ }
1379
+ const instructionsDir = path4.join(dir, ".github", "instructions");
1380
+ if (fs4.existsSync(instructionsDir)) {
1381
+ try {
1382
+ const files = fs4.readdirSync(instructionsDir).filter((f) => f.endsWith(".md"));
1383
+ configs.copilotInstructionFiles = files.map((f) => ({
1384
+ filename: f,
1385
+ content: fs4.readFileSync(path4.join(instructionsDir, f), "utf-8")
1386
+ }));
1387
+ } catch {
1388
+ }
1389
+ }
1390
+ if (fs4.existsSync(PERSONAL_LEARNINGS_FILE)) {
1299
1391
  try {
1300
- const content = fs2.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
1392
+ const content = fs4.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
1301
1393
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
1302
1394
  if (bullets) configs.personalLearnings = bullets;
1303
1395
  } catch {
@@ -1307,9 +1399,9 @@ function readExistingConfigs(dir) {
1307
1399
  }
1308
1400
 
1309
1401
  // src/fingerprint/code-analysis.ts
1310
- import fs3 from "fs";
1311
- import path4 from "path";
1312
- import { execSync as execSync3 } from "child_process";
1402
+ import fs5 from "fs";
1403
+ import path5 from "path";
1404
+ import { execSync as execSync4 } from "child_process";
1313
1405
 
1314
1406
  // src/lib/sanitize.ts
1315
1407
  var KNOWN_PREFIX_PATTERNS = [
@@ -1653,18 +1745,18 @@ function extractSkeletonIndentBased(lines, ext) {
1653
1745
  }
1654
1746
  function extractImports(content, filePath) {
1655
1747
  const imports = [];
1656
- const dir = path4.dirname(filePath);
1748
+ const dir = path5.dirname(filePath);
1657
1749
  for (const line of content.split("\n")) {
1658
1750
  const trimmed = line.trim();
1659
1751
  const jsMatch = trimmed.match(/(?:from|require\()\s*['"]([^'"]+)['"]/);
1660
1752
  if (jsMatch && jsMatch[1].startsWith(".")) {
1661
- imports.push(path4.normalize(path4.join(dir, jsMatch[1])));
1753
+ imports.push(path5.normalize(path5.join(dir, jsMatch[1])));
1662
1754
  continue;
1663
1755
  }
1664
1756
  const pyMatch = trimmed.match(/^from\s+(\.[.\w]*)\s+import/);
1665
1757
  if (pyMatch) {
1666
1758
  const modulePath = pyMatch[1].replace(/\./g, "/");
1667
- imports.push(path4.normalize(path4.join(dir, modulePath)));
1759
+ imports.push(path5.normalize(path5.join(dir, modulePath)));
1668
1760
  continue;
1669
1761
  }
1670
1762
  const goMatch = trimmed.match(/^\s*"([^"]+)"/);
@@ -1677,7 +1769,7 @@ function extractImports(content, filePath) {
1677
1769
  function buildImportCounts(files) {
1678
1770
  const counts = /* @__PURE__ */ new Map();
1679
1771
  for (const [filePath, content] of files) {
1680
- const ext = path4.extname(filePath).toLowerCase();
1772
+ const ext = path5.extname(filePath).toLowerCase();
1681
1773
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
1682
1774
  const imports = extractImports(content, filePath);
1683
1775
  for (const imp of imports) {
@@ -1696,7 +1788,7 @@ function buildImportCounts(files) {
1696
1788
  function getGitFrequency(dir) {
1697
1789
  const freq = /* @__PURE__ */ new Map();
1698
1790
  try {
1699
- const output = execSync3(
1791
+ const output = execSync4(
1700
1792
  'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
1701
1793
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
1702
1794
  );
@@ -1711,7 +1803,7 @@ function getGitFrequency(dir) {
1711
1803
  function groupByDirectory(files) {
1712
1804
  const groups = /* @__PURE__ */ new Map();
1713
1805
  for (const f of files) {
1714
- const dir = path4.dirname(f.path);
1806
+ const dir = path5.dirname(f.path);
1715
1807
  const group = groups.get(dir) || [];
1716
1808
  group.push(f);
1717
1809
  groups.set(dir, group);
@@ -1732,14 +1824,14 @@ function analyzeCode(dir) {
1732
1824
  let totalChars = 0;
1733
1825
  for (const relPath of allPaths) {
1734
1826
  try {
1735
- totalChars += fs3.statSync(path4.join(dir, relPath)).size;
1827
+ totalChars += fs5.statSync(path5.join(dir, relPath)).size;
1736
1828
  } catch {
1737
1829
  }
1738
1830
  }
1739
1831
  const fileContents = /* @__PURE__ */ new Map();
1740
1832
  for (const relPath of allPaths) {
1741
1833
  try {
1742
- const content = fs3.readFileSync(path4.join(dir, relPath), "utf-8");
1834
+ const content = fs5.readFileSync(path5.join(dir, relPath), "utf-8");
1743
1835
  if (content.split("\n").length <= 500) fileContents.set(relPath, content);
1744
1836
  } catch {
1745
1837
  }
@@ -1749,7 +1841,7 @@ function analyzeCode(dir) {
1749
1841
  const scored = [];
1750
1842
  let compressedChars = 0;
1751
1843
  for (const [relPath, rawContent] of fileContents) {
1752
- const ext = path4.extname(relPath).toLowerCase();
1844
+ const ext = path5.extname(relPath).toLowerCase();
1753
1845
  const compressed = compressContent(rawContent, ext);
1754
1846
  const skeleton = extractSkeleton(compressed, ext);
1755
1847
  compressedChars += compressed.length;
@@ -1778,7 +1870,7 @@ function analyzeCode(dir) {
1778
1870
  }
1779
1871
  if (similar.length > 0) {
1780
1872
  dupGroups++;
1781
- const names = similar.map((f) => path4.basename(f.path));
1873
+ const names = similar.map((f) => path5.basename(f.path));
1782
1874
  const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
1783
1875
  const summarySize = summary.length + 30;
1784
1876
  if (includedChars + summarySize <= CHAR_BUDGET) {
@@ -1815,10 +1907,10 @@ function analyzeCode(dir) {
1815
1907
  }
1816
1908
  function walkDir(base, rel, depth, maxDepth, files) {
1817
1909
  if (depth > maxDepth) return;
1818
- const fullPath = path4.join(base, rel);
1910
+ const fullPath = path5.join(base, rel);
1819
1911
  let entries;
1820
1912
  try {
1821
- entries = fs3.readdirSync(fullPath, { withFileTypes: true });
1913
+ entries = fs5.readdirSync(fullPath, { withFileTypes: true });
1822
1914
  } catch {
1823
1915
  return;
1824
1916
  }
@@ -1831,7 +1923,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
1831
1923
  } else if (entry.isFile()) {
1832
1924
  if (SKIP_FILES.has(entry.name)) continue;
1833
1925
  if (SKIP_PATTERNS.some((p) => p.test(entry.name))) continue;
1834
- const ext = path4.extname(entry.name).toLowerCase();
1926
+ const ext = path5.extname(entry.name).toLowerCase();
1835
1927
  if (TEXT_EXTENSIONS.has(ext) || depth === 0 && !ext && !entry.name.startsWith(".")) {
1836
1928
  files.push(relPath);
1837
1929
  }
@@ -1839,7 +1931,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
1839
1931
  }
1840
1932
  }
1841
1933
  function filePriority(filePath) {
1842
- const base = path4.basename(filePath);
1934
+ const base = path5.basename(filePath);
1843
1935
  const entryPoints = /* @__PURE__ */ new Set([
1844
1936
  "index.ts",
1845
1937
  "index.js",
@@ -1971,7 +2063,7 @@ var AnthropicProvider = class {
1971
2063
  };
1972
2064
 
1973
2065
  // src/llm/vertex.ts
1974
- import fs5 from "fs";
2066
+ import fs7 from "fs";
1975
2067
  import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
1976
2068
  import { GoogleAuth } from "google-auth-library";
1977
2069
  var VertexProvider = class {
@@ -1994,7 +2086,7 @@ var VertexProvider = class {
1994
2086
  }
1995
2087
  } else {
1996
2088
  try {
1997
- creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
2089
+ creds = JSON.parse(fs7.readFileSync(raw, "utf-8"));
1998
2090
  } catch {
1999
2091
  throw new Error(`Cannot read credentials file: ${raw}`);
2000
2092
  }
@@ -3163,6 +3255,11 @@ CONSERVATIVE UPDATE means:
3163
3255
  - NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
3164
3256
  - NEVER replace specific paths/commands with generic prose
3165
3257
 
3258
+ Cross-agent sync:
3259
+ - When a change affects CLAUDE.md, apply the same semantic update to AGENTS.md and copilot instructions if they exist
3260
+ - Each file uses its own format and conventions \u2014 do NOT copy content verbatim between them
3261
+ - The goal is consistency: all agent config files should reflect the same project state after refresh
3262
+
3166
3263
  Quality constraints (the output is scored deterministically):
3167
3264
  - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
3168
3265
  - Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
@@ -3171,16 +3268,17 @@ Quality constraints (the output is scored deterministically):
3171
3268
  - Preserve the existing structure (headings, bullet style, formatting)
3172
3269
 
3173
3270
  Managed content:
3174
- - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
3175
- - Keep context sync blocks (<!-- caliber:managed:sync --> ... <!-- /caliber:managed:sync -->) intact
3271
+ - All blocks between <!-- caliber:managed:* --> and <!-- /caliber:managed:* --> are managed by Caliber \u2014 copy them verbatim, do NOT modify their contents
3272
+ - This includes: pre-commit, sync, and learnings blocks
3176
3273
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
3177
- - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
3274
+ - Preserve any references to CALIBER_LEARNINGS.md
3178
3275
 
3179
3276
  Return a JSON object with this exact shape:
3180
3277
  {
3181
3278
  "updatedDocs": {
3182
3279
  "claudeMd": "<updated content or null>",
3183
3280
  "readmeMd": "<updated content or null>",
3281
+ "agentsMd": "<updated content or null>",
3184
3282
  "cursorrules": "<updated content or null>",
3185
3283
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
3186
3284
  "claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
@@ -3342,15 +3440,15 @@ async function detectProjectStack(fileTree, suffixCounts) {
3342
3440
  init_config();
3343
3441
 
3344
3442
  // src/fingerprint/cache.ts
3345
- import fs7 from "fs";
3346
- import path6 from "path";
3443
+ import fs8 from "fs";
3444
+ import path7 from "path";
3347
3445
  import crypto from "crypto";
3348
3446
  import { execSync as execSync7 } from "child_process";
3349
3447
  var CACHE_VERSION = 1;
3350
3448
  var CACHE_DIR = ".caliber/cache";
3351
3449
  var CACHE_FILE = "fingerprint.json";
3352
3450
  function getCachePath(dir) {
3353
- return path6.join(dir, CACHE_DIR, CACHE_FILE);
3451
+ return path7.join(dir, CACHE_DIR, CACHE_FILE);
3354
3452
  }
3355
3453
  function getGitHead(dir) {
3356
3454
  try {
@@ -3387,8 +3485,8 @@ function computeTreeSignature(fileTree, dir) {
3387
3485
  function loadFingerprintCache(dir, fileTree) {
3388
3486
  const cachePath = getCachePath(dir);
3389
3487
  try {
3390
- if (!fs7.existsSync(cachePath)) return null;
3391
- const raw = fs7.readFileSync(cachePath, "utf-8");
3488
+ if (!fs8.existsSync(cachePath)) return null;
3489
+ const raw = fs8.readFileSync(cachePath, "utf-8");
3392
3490
  const cache = JSON.parse(raw);
3393
3491
  if (cache.version !== CACHE_VERSION) return null;
3394
3492
  const currentHead = getGitHead(dir);
@@ -3409,9 +3507,9 @@ function loadFingerprintCache(dir, fileTree) {
3409
3507
  function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks, tools, workspaces) {
3410
3508
  const cachePath = getCachePath(dir);
3411
3509
  try {
3412
- const cacheDir = path6.dirname(cachePath);
3413
- if (!fs7.existsSync(cacheDir)) {
3414
- fs7.mkdirSync(cacheDir, { recursive: true });
3510
+ const cacheDir = path7.dirname(cachePath);
3511
+ if (!fs8.existsSync(cacheDir)) {
3512
+ fs8.mkdirSync(cacheDir, { recursive: true });
3415
3513
  }
3416
3514
  const cache = {
3417
3515
  version: CACHE_VERSION,
@@ -3423,15 +3521,15 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
3423
3521
  tools,
3424
3522
  workspaces
3425
3523
  };
3426
- fs7.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
3524
+ fs8.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
3427
3525
  } catch {
3428
3526
  }
3429
3527
  }
3430
3528
  function getDetectedWorkspaces(dir) {
3431
3529
  const cachePath = getCachePath(dir);
3432
3530
  try {
3433
- if (!fs7.existsSync(cachePath)) return [];
3434
- const raw = fs7.readFileSync(cachePath, "utf-8");
3531
+ if (!fs8.existsSync(cachePath)) return [];
3532
+ const raw = fs8.readFileSync(cachePath, "utf-8");
3435
3533
  const cache = JSON.parse(raw);
3436
3534
  return cache.workspaces ?? [];
3437
3535
  } catch {
@@ -3483,9 +3581,9 @@ async function collectFingerprint(dir) {
3483
3581
  }
3484
3582
  function readPackageName(dir) {
3485
3583
  try {
3486
- const pkgPath = path7.join(dir, "package.json");
3487
- if (!fs8.existsSync(pkgPath)) return void 0;
3488
- const pkg3 = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
3584
+ const pkgPath = path8.join(dir, "package.json");
3585
+ if (!fs9.existsSync(pkgPath)) return void 0;
3586
+ const pkg3 = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
3489
3587
  return pkg3.name;
3490
3588
  } catch {
3491
3589
  return void 0;
@@ -3499,7 +3597,7 @@ async function enrichWithLLM(fingerprint) {
3499
3597
  const suffixCounts = {};
3500
3598
  for (const entry of fingerprint.fileTree) {
3501
3599
  if (entry.endsWith("/")) continue;
3502
- const ext = path7.extname(entry).toLowerCase();
3600
+ const ext = path8.extname(entry).toLowerCase();
3503
3601
  if (ext) {
3504
3602
  suffixCounts[ext] = (suffixCounts[ext] || 0) + 1;
3505
3603
  }
@@ -3515,22 +3613,22 @@ async function enrichWithLLM(fingerprint) {
3515
3613
  }
3516
3614
 
3517
3615
  // src/scanner/index.ts
3518
- import fs9 from "fs";
3519
- import path8 from "path";
3616
+ import fs10 from "fs";
3617
+ import path9 from "path";
3520
3618
  import crypto2 from "crypto";
3521
3619
  import os4 from "os";
3522
3620
  function detectPlatforms() {
3523
3621
  const home = os4.homedir();
3524
3622
  return {
3525
- claude: fs9.existsSync(path8.join(home, ".claude")),
3526
- cursor: fs9.existsSync(getCursorConfigDir()),
3527
- codex: fs9.existsSync(path8.join(home, ".codex"))
3623
+ claude: fs10.existsSync(path9.join(home, ".claude")),
3624
+ cursor: fs10.existsSync(getCursorConfigDir()),
3625
+ codex: fs10.existsSync(path9.join(home, ".codex"))
3528
3626
  };
3529
3627
  }
3530
3628
  function scanLocalState(dir) {
3531
3629
  const items = [];
3532
- const claudeMdPath = path8.join(dir, "CLAUDE.md");
3533
- if (fs9.existsSync(claudeMdPath)) {
3630
+ const claudeMdPath = path9.join(dir, "CLAUDE.md");
3631
+ if (fs10.existsSync(claudeMdPath)) {
3534
3632
  items.push({
3535
3633
  type: "rule",
3536
3634
  platform: "claude",
@@ -3539,10 +3637,10 @@ function scanLocalState(dir) {
3539
3637
  path: claudeMdPath
3540
3638
  });
3541
3639
  }
3542
- const skillsDir = path8.join(dir, ".claude", "skills");
3543
- if (fs9.existsSync(skillsDir)) {
3544
- for (const file of fs9.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
3545
- const filePath = path8.join(skillsDir, file);
3640
+ const skillsDir = path9.join(dir, ".claude", "skills");
3641
+ if (fs10.existsSync(skillsDir)) {
3642
+ for (const file of fs10.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
3643
+ const filePath = path9.join(skillsDir, file);
3546
3644
  items.push({
3547
3645
  type: "skill",
3548
3646
  platform: "claude",
@@ -3552,10 +3650,10 @@ function scanLocalState(dir) {
3552
3650
  });
3553
3651
  }
3554
3652
  }
3555
- const mcpJsonPath = path8.join(dir, ".mcp.json");
3556
- if (fs9.existsSync(mcpJsonPath)) {
3653
+ const mcpJsonPath = path9.join(dir, ".mcp.json");
3654
+ if (fs10.existsSync(mcpJsonPath)) {
3557
3655
  try {
3558
- const mcpJson = JSON.parse(fs9.readFileSync(mcpJsonPath, "utf-8"));
3656
+ const mcpJson = JSON.parse(fs10.readFileSync(mcpJsonPath, "utf-8"));
3559
3657
  if (mcpJson.mcpServers) {
3560
3658
  for (const name of Object.keys(mcpJson.mcpServers)) {
3561
3659
  items.push({
@@ -3571,8 +3669,8 @@ function scanLocalState(dir) {
3571
3669
  warnScanSkip(".mcp.json", error);
3572
3670
  }
3573
3671
  }
3574
- const agentsMdPath = path8.join(dir, "AGENTS.md");
3575
- if (fs9.existsSync(agentsMdPath)) {
3672
+ const agentsMdPath = path9.join(dir, "AGENTS.md");
3673
+ if (fs10.existsSync(agentsMdPath)) {
3576
3674
  items.push({
3577
3675
  type: "rule",
3578
3676
  platform: "codex",
@@ -3581,12 +3679,12 @@ function scanLocalState(dir) {
3581
3679
  path: agentsMdPath
3582
3680
  });
3583
3681
  }
3584
- const codexSkillsDir = path8.join(dir, ".agents", "skills");
3585
- if (fs9.existsSync(codexSkillsDir)) {
3682
+ const codexSkillsDir = path9.join(dir, ".agents", "skills");
3683
+ if (fs10.existsSync(codexSkillsDir)) {
3586
3684
  try {
3587
- for (const name of fs9.readdirSync(codexSkillsDir)) {
3588
- const skillFile = path8.join(codexSkillsDir, name, "SKILL.md");
3589
- if (fs9.existsSync(skillFile)) {
3685
+ for (const name of fs10.readdirSync(codexSkillsDir)) {
3686
+ const skillFile = path9.join(codexSkillsDir, name, "SKILL.md");
3687
+ if (fs10.existsSync(skillFile)) {
3590
3688
  items.push({
3591
3689
  type: "skill",
3592
3690
  platform: "codex",
@@ -3600,8 +3698,8 @@ function scanLocalState(dir) {
3600
3698
  warnScanSkip(".agents/skills", error);
3601
3699
  }
3602
3700
  }
3603
- const cursorrulesPath = path8.join(dir, ".cursorrules");
3604
- if (fs9.existsSync(cursorrulesPath)) {
3701
+ const cursorrulesPath = path9.join(dir, ".cursorrules");
3702
+ if (fs10.existsSync(cursorrulesPath)) {
3605
3703
  items.push({
3606
3704
  type: "rule",
3607
3705
  platform: "cursor",
@@ -3610,10 +3708,10 @@ function scanLocalState(dir) {
3610
3708
  path: cursorrulesPath
3611
3709
  });
3612
3710
  }
3613
- const cursorRulesDir = path8.join(dir, ".cursor", "rules");
3614
- if (fs9.existsSync(cursorRulesDir)) {
3615
- for (const file of fs9.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
3616
- const filePath = path8.join(cursorRulesDir, file);
3711
+ const cursorRulesDir = path9.join(dir, ".cursor", "rules");
3712
+ if (fs10.existsSync(cursorRulesDir)) {
3713
+ for (const file of fs10.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
3714
+ const filePath = path9.join(cursorRulesDir, file);
3617
3715
  items.push({
3618
3716
  type: "rule",
3619
3717
  platform: "cursor",
@@ -3623,12 +3721,12 @@ function scanLocalState(dir) {
3623
3721
  });
3624
3722
  }
3625
3723
  }
3626
- const cursorSkillsDir = path8.join(dir, ".cursor", "skills");
3627
- if (fs9.existsSync(cursorSkillsDir)) {
3724
+ const cursorSkillsDir = path9.join(dir, ".cursor", "skills");
3725
+ if (fs10.existsSync(cursorSkillsDir)) {
3628
3726
  try {
3629
- for (const name of fs9.readdirSync(cursorSkillsDir)) {
3630
- const skillFile = path8.join(cursorSkillsDir, name, "SKILL.md");
3631
- if (fs9.existsSync(skillFile)) {
3727
+ for (const name of fs10.readdirSync(cursorSkillsDir)) {
3728
+ const skillFile = path9.join(cursorSkillsDir, name, "SKILL.md");
3729
+ if (fs10.existsSync(skillFile)) {
3632
3730
  items.push({
3633
3731
  type: "skill",
3634
3732
  platform: "cursor",
@@ -3642,10 +3740,10 @@ function scanLocalState(dir) {
3642
3740
  warnScanSkip(".cursor/skills", error);
3643
3741
  }
3644
3742
  }
3645
- const cursorMcpPath = path8.join(dir, ".cursor", "mcp.json");
3646
- if (fs9.existsSync(cursorMcpPath)) {
3743
+ const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
3744
+ if (fs10.existsSync(cursorMcpPath)) {
3647
3745
  try {
3648
- const mcpJson = JSON.parse(fs9.readFileSync(cursorMcpPath, "utf-8"));
3746
+ const mcpJson = JSON.parse(fs10.readFileSync(cursorMcpPath, "utf-8"));
3649
3747
  if (mcpJson.mcpServers) {
3650
3748
  for (const name of Object.keys(mcpJson.mcpServers)) {
3651
3749
  items.push({
@@ -3664,7 +3762,7 @@ function scanLocalState(dir) {
3664
3762
  return items;
3665
3763
  }
3666
3764
  function hashFile(filePath) {
3667
- const text = fs9.readFileSync(filePath, "utf-8");
3765
+ const text = fs10.readFileSync(filePath, "utf-8");
3668
3766
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
3669
3767
  }
3670
3768
  function hashJson(obj) {
@@ -3677,37 +3775,37 @@ function warnScanSkip(target, error) {
3677
3775
  function getCursorConfigDir() {
3678
3776
  const home = os4.homedir();
3679
3777
  if (process.platform === "darwin") {
3680
- return path8.join(home, "Library", "Application Support", "Cursor");
3778
+ return path9.join(home, "Library", "Application Support", "Cursor");
3681
3779
  }
3682
3780
  if (process.platform === "win32") {
3683
- return path8.join(home, "AppData", "Roaming", "Cursor");
3781
+ return path9.join(home, "AppData", "Roaming", "Cursor");
3684
3782
  }
3685
- return path8.join(home, ".config", "Cursor");
3783
+ return path9.join(home, ".config", "Cursor");
3686
3784
  }
3687
3785
 
3688
3786
  // src/lib/hooks.ts
3689
3787
  init_resolve_caliber();
3690
- import fs10 from "fs";
3691
- import path9 from "path";
3788
+ import fs11 from "fs";
3789
+ import path10 from "path";
3692
3790
  import { execSync as execSync8 } from "child_process";
3693
- var SETTINGS_PATH = path9.join(".claude", "settings.json");
3791
+ var SETTINGS_PATH = path10.join(".claude", "settings.json");
3694
3792
  var REFRESH_TAIL = "refresh --quiet";
3695
3793
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
3696
3794
  function getHookCommand() {
3697
3795
  return `${resolveCaliber()} ${REFRESH_TAIL}`;
3698
3796
  }
3699
3797
  function readSettings() {
3700
- if (!fs10.existsSync(SETTINGS_PATH)) return {};
3798
+ if (!fs11.existsSync(SETTINGS_PATH)) return {};
3701
3799
  try {
3702
- return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
3800
+ return JSON.parse(fs11.readFileSync(SETTINGS_PATH, "utf-8"));
3703
3801
  } catch {
3704
3802
  return {};
3705
3803
  }
3706
3804
  }
3707
3805
  function writeSettings(settings) {
3708
- const dir = path9.dirname(SETTINGS_PATH);
3709
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
3710
- fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3806
+ const dir = path10.dirname(SETTINGS_PATH);
3807
+ if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
3808
+ fs11.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3711
3809
  }
3712
3810
  function findHookIndex(sessionEnd) {
3713
3811
  return sessionEnd.findIndex(
@@ -3773,19 +3871,19 @@ ${PRECOMMIT_END}`;
3773
3871
  function getGitHooksDir() {
3774
3872
  try {
3775
3873
  const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3776
- return path9.join(gitDir, "hooks");
3874
+ return path10.join(gitDir, "hooks");
3777
3875
  } catch {
3778
3876
  return null;
3779
3877
  }
3780
3878
  }
3781
3879
  function getPreCommitPath() {
3782
3880
  const hooksDir = getGitHooksDir();
3783
- return hooksDir ? path9.join(hooksDir, "pre-commit") : null;
3881
+ return hooksDir ? path10.join(hooksDir, "pre-commit") : null;
3784
3882
  }
3785
3883
  function isPreCommitHookInstalled() {
3786
3884
  const hookPath = getPreCommitPath();
3787
- if (!hookPath || !fs10.existsSync(hookPath)) return false;
3788
- const content = fs10.readFileSync(hookPath, "utf-8");
3885
+ if (!hookPath || !fs11.existsSync(hookPath)) return false;
3886
+ const content = fs11.readFileSync(hookPath, "utf-8");
3789
3887
  return content.includes(PRECOMMIT_START);
3790
3888
  }
3791
3889
  function installPreCommitHook() {
@@ -3794,42 +3892,42 @@ function installPreCommitHook() {
3794
3892
  }
3795
3893
  const hookPath = getPreCommitPath();
3796
3894
  if (!hookPath) return { installed: false, alreadyInstalled: false };
3797
- const hooksDir = path9.dirname(hookPath);
3798
- if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
3895
+ const hooksDir = path10.dirname(hookPath);
3896
+ if (!fs11.existsSync(hooksDir)) fs11.mkdirSync(hooksDir, { recursive: true });
3799
3897
  let content = "";
3800
- if (fs10.existsSync(hookPath)) {
3801
- content = fs10.readFileSync(hookPath, "utf-8");
3898
+ if (fs11.existsSync(hookPath)) {
3899
+ content = fs11.readFileSync(hookPath, "utf-8");
3802
3900
  if (!content.endsWith("\n")) content += "\n";
3803
3901
  content += "\n" + getPrecommitBlock() + "\n";
3804
3902
  } else {
3805
3903
  content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
3806
3904
  }
3807
- fs10.writeFileSync(hookPath, content);
3808
- fs10.chmodSync(hookPath, 493);
3905
+ fs11.writeFileSync(hookPath, content);
3906
+ fs11.chmodSync(hookPath, 493);
3809
3907
  return { installed: true, alreadyInstalled: false };
3810
3908
  }
3811
3909
  function removePreCommitHook() {
3812
3910
  const hookPath = getPreCommitPath();
3813
- if (!hookPath || !fs10.existsSync(hookPath)) {
3911
+ if (!hookPath || !fs11.existsSync(hookPath)) {
3814
3912
  return { removed: false, notFound: true };
3815
3913
  }
3816
- let content = fs10.readFileSync(hookPath, "utf-8");
3914
+ let content = fs11.readFileSync(hookPath, "utf-8");
3817
3915
  if (!content.includes(PRECOMMIT_START)) {
3818
3916
  return { removed: false, notFound: true };
3819
3917
  }
3820
3918
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
3821
3919
  content = content.replace(regex, "\n");
3822
3920
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
3823
- fs10.unlinkSync(hookPath);
3921
+ fs11.unlinkSync(hookPath);
3824
3922
  } else {
3825
- fs10.writeFileSync(hookPath, content);
3923
+ fs11.writeFileSync(hookPath, content);
3826
3924
  }
3827
3925
  return { removed: true, notFound: false };
3828
3926
  }
3829
3927
 
3830
3928
  // src/fingerprint/sources.ts
3831
- import fs11 from "fs";
3832
- import path10 from "path";
3929
+ import fs12 from "fs";
3930
+ import path11 from "path";
3833
3931
 
3834
3932
  // src/scoring/utils.ts
3835
3933
  import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
@@ -4148,7 +4246,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
4148
4246
  var README_CONTENT_LIMIT = 1e3;
4149
4247
  var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
4150
4248
  function loadSourcesConfig(dir) {
4151
- const configPath = path10.join(dir, ".caliber", "sources.json");
4249
+ const configPath = path11.join(dir, ".caliber", "sources.json");
4152
4250
  const content = readFileOrNull(configPath);
4153
4251
  if (!content) return [];
4154
4252
  try {
@@ -4166,29 +4264,29 @@ function loadSourcesConfig(dir) {
4166
4264
  }
4167
4265
  }
4168
4266
  function writeSourcesConfig(dir, sources2) {
4169
- const configDir = path10.join(dir, ".caliber");
4170
- if (!fs11.existsSync(configDir)) {
4171
- fs11.mkdirSync(configDir, { recursive: true });
4267
+ const configDir = path11.join(dir, ".caliber");
4268
+ if (!fs12.existsSync(configDir)) {
4269
+ fs12.mkdirSync(configDir, { recursive: true });
4172
4270
  }
4173
- const configPath = path10.join(configDir, "sources.json");
4174
- fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
4271
+ const configPath = path11.join(configDir, "sources.json");
4272
+ fs12.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
4175
4273
  }
4176
4274
  function detectSourceType(absPath) {
4177
4275
  try {
4178
- return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
4276
+ return fs12.statSync(absPath).isDirectory() ? "repo" : "file";
4179
4277
  } catch {
4180
4278
  return "file";
4181
4279
  }
4182
4280
  }
4183
4281
  function isInsideDir(childPath, parentDir) {
4184
- const relative2 = path10.relative(parentDir, childPath);
4185
- return !relative2.startsWith("..") && !path10.isAbsolute(relative2);
4282
+ const relative2 = path11.relative(parentDir, childPath);
4283
+ return !relative2.startsWith("..") && !path11.isAbsolute(relative2);
4186
4284
  }
4187
4285
  function resolveAllSources(dir, cliSources, workspaces) {
4188
4286
  const seen = /* @__PURE__ */ new Map();
4189
- const projectRoot = path10.resolve(dir);
4287
+ const projectRoot = path11.resolve(dir);
4190
4288
  for (const src of cliSources) {
4191
- const absPath = path10.resolve(dir, src);
4289
+ const absPath = path11.resolve(dir, src);
4192
4290
  if (seen.has(absPath)) continue;
4193
4291
  const type = detectSourceType(absPath);
4194
4292
  seen.set(absPath, {
@@ -4201,12 +4299,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
4201
4299
  for (const cfg of configSources) {
4202
4300
  if (cfg.type === "url") continue;
4203
4301
  if (!cfg.path) continue;
4204
- const absPath = path10.resolve(dir, cfg.path);
4302
+ const absPath = path11.resolve(dir, cfg.path);
4205
4303
  if (seen.has(absPath)) continue;
4206
4304
  seen.set(absPath, { absPath, config: cfg, origin: "config" });
4207
4305
  }
4208
4306
  for (const ws of workspaces) {
4209
- const absPath = path10.resolve(dir, ws);
4307
+ const absPath = path11.resolve(dir, ws);
4210
4308
  if (seen.has(absPath)) continue;
4211
4309
  if (!isInsideDir(absPath, projectRoot)) continue;
4212
4310
  seen.set(absPath, {
@@ -4219,7 +4317,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
4219
4317
  for (const [absPath, resolved] of seen) {
4220
4318
  let stat;
4221
4319
  try {
4222
- stat = fs11.statSync(absPath);
4320
+ stat = fs12.statSync(absPath);
4223
4321
  } catch {
4224
4322
  console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
4225
4323
  continue;
@@ -4248,13 +4346,13 @@ function collectSourceSummary(resolved, projectDir) {
4248
4346
  if (config.type === "file") {
4249
4347
  return collectFileSummary(resolved, projectDir);
4250
4348
  }
4251
- const summaryPath = path10.join(absPath, ".caliber", "summary.json");
4349
+ const summaryPath = path11.join(absPath, ".caliber", "summary.json");
4252
4350
  const summaryContent = readFileOrNull(summaryPath);
4253
4351
  if (summaryContent) {
4254
4352
  try {
4255
4353
  const published = JSON.parse(summaryContent);
4256
4354
  return {
4257
- name: published.name || path10.basename(absPath),
4355
+ name: published.name || path11.basename(absPath),
4258
4356
  type: "repo",
4259
4357
  role: config.role || published.role || "related-repo",
4260
4358
  description: config.description || published.description || "",
@@ -4274,18 +4372,18 @@ function collectRepoSummary(resolved, projectDir) {
4274
4372
  let topLevelDirs;
4275
4373
  let keyFiles;
4276
4374
  try {
4277
- const entries = fs11.readdirSync(absPath, { withFileTypes: true });
4375
+ const entries = fs12.readdirSync(absPath, { withFileTypes: true });
4278
4376
  topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
4279
4377
  keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
4280
4378
  } catch {
4281
4379
  }
4282
- const claudeMdContent = readFileOrNull(path10.join(absPath, "CLAUDE.md"));
4380
+ const claudeMdContent = readFileOrNull(path11.join(absPath, "CLAUDE.md"));
4283
4381
  const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
4284
- const readmeContent = readFileOrNull(path10.join(absPath, "README.md"));
4382
+ const readmeContent = readFileOrNull(path11.join(absPath, "README.md"));
4285
4383
  const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
4286
4384
  const gitRemoteUrl = getGitRemoteUrl(absPath);
4287
4385
  return {
4288
- name: packageName || path10.basename(absPath),
4386
+ name: packageName || path11.basename(absPath),
4289
4387
  type: "repo",
4290
4388
  role: config.role || "related-repo",
4291
4389
  description: config.description || "",
@@ -4302,7 +4400,7 @@ function collectFileSummary(resolved, projectDir) {
4302
4400
  const { config, origin, absPath } = resolved;
4303
4401
  const content = readFileOrNull(absPath);
4304
4402
  return {
4305
- name: path10.basename(absPath),
4403
+ name: path11.basename(absPath),
4306
4404
  type: "file",
4307
4405
  role: config.role || "reference-doc",
4308
4406
  description: config.description || content?.slice(0, 100).split("\n")[0] || "",
@@ -4945,17 +5043,17 @@ import fs19 from "fs";
4945
5043
 
4946
5044
  // src/writers/claude/index.ts
4947
5045
  init_pre_commit_block();
4948
- import fs12 from "fs";
4949
- import path11 from "path";
5046
+ import fs13 from "fs";
5047
+ import path12 from "path";
4950
5048
  function writeClaudeConfig(config) {
4951
5049
  const written = [];
4952
- fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
5050
+ fs13.writeFileSync("CLAUDE.md", appendManagedBlocks(config.claudeMd, "claude"));
4953
5051
  written.push("CLAUDE.md");
4954
5052
  if (config.skills?.length) {
4955
5053
  for (const skill of config.skills) {
4956
- const skillDir = path11.join(".claude", "skills", skill.name);
4957
- if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
4958
- const skillPath = path11.join(skillDir, "SKILL.md");
5054
+ const skillDir = path12.join(".claude", "skills", skill.name);
5055
+ if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
5056
+ const skillPath = path12.join(skillDir, "SKILL.md");
4959
5057
  const frontmatter = [
4960
5058
  "---",
4961
5059
  `name: ${skill.name}`,
@@ -4963,21 +5061,21 @@ function writeClaudeConfig(config) {
4963
5061
  "---",
4964
5062
  ""
4965
5063
  ].join("\n");
4966
- fs12.writeFileSync(skillPath, frontmatter + skill.content);
5064
+ fs13.writeFileSync(skillPath, frontmatter + skill.content);
4967
5065
  written.push(skillPath);
4968
5066
  }
4969
5067
  }
4970
5068
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4971
5069
  let existingServers = {};
4972
5070
  try {
4973
- if (fs12.existsSync(".mcp.json")) {
4974
- const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
5071
+ if (fs13.existsSync(".mcp.json")) {
5072
+ const existing = JSON.parse(fs13.readFileSync(".mcp.json", "utf-8"));
4975
5073
  if (existing.mcpServers) existingServers = existing.mcpServers;
4976
5074
  }
4977
5075
  } catch {
4978
5076
  }
4979
5077
  const mergedServers = { ...existingServers, ...config.mcpServers };
4980
- fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
5078
+ fs13.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4981
5079
  written.push(".mcp.json");
4982
5080
  }
4983
5081
  return written;
@@ -4985,30 +5083,31 @@ function writeClaudeConfig(config) {
4985
5083
 
4986
5084
  // src/writers/cursor/index.ts
4987
5085
  init_pre_commit_block();
4988
- import fs13 from "fs";
4989
- import path12 from "path";
5086
+ import fs14 from "fs";
5087
+ import path13 from "path";
4990
5088
  function writeCursorConfig(config) {
4991
5089
  const written = [];
4992
5090
  if (config.cursorrules) {
4993
- fs13.writeFileSync(".cursorrules", config.cursorrules);
5091
+ fs14.writeFileSync(".cursorrules", config.cursorrules);
4994
5092
  written.push(".cursorrules");
4995
5093
  }
4996
5094
  const preCommitRule = getCursorPreCommitRule();
4997
5095
  const learningsRule = getCursorLearningsRule();
4998
5096
  const syncRule = getCursorSyncRule();
4999
- const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
5000
- const rulesDir = path12.join(".cursor", "rules");
5001
- if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
5097
+ const setupRule = getCursorSetupRule();
5098
+ const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule, setupRule];
5099
+ const rulesDir = path13.join(".cursor", "rules");
5100
+ if (!fs14.existsSync(rulesDir)) fs14.mkdirSync(rulesDir, { recursive: true });
5002
5101
  for (const rule of allRules) {
5003
- const rulePath = path12.join(rulesDir, rule.filename);
5004
- fs13.writeFileSync(rulePath, rule.content);
5102
+ const rulePath = path13.join(rulesDir, rule.filename);
5103
+ fs14.writeFileSync(rulePath, rule.content);
5005
5104
  written.push(rulePath);
5006
5105
  }
5007
5106
  if (config.skills?.length) {
5008
5107
  for (const skill of config.skills) {
5009
- const skillDir = path12.join(".cursor", "skills", skill.name);
5010
- if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
5011
- const skillPath = path12.join(skillDir, "SKILL.md");
5108
+ const skillDir = path13.join(".cursor", "skills", skill.name);
5109
+ if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
5110
+ const skillPath = path13.join(skillDir, "SKILL.md");
5012
5111
  const frontmatter = [
5013
5112
  "---",
5014
5113
  `name: ${skill.name}`,
@@ -5016,24 +5115,24 @@ function writeCursorConfig(config) {
5016
5115
  "---",
5017
5116
  ""
5018
5117
  ].join("\n");
5019
- fs13.writeFileSync(skillPath, frontmatter + skill.content);
5118
+ fs14.writeFileSync(skillPath, frontmatter + skill.content);
5020
5119
  written.push(skillPath);
5021
5120
  }
5022
5121
  }
5023
5122
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
5024
5123
  const cursorDir = ".cursor";
5025
- if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
5026
- const mcpPath = path12.join(cursorDir, "mcp.json");
5124
+ if (!fs14.existsSync(cursorDir)) fs14.mkdirSync(cursorDir, { recursive: true });
5125
+ const mcpPath = path13.join(cursorDir, "mcp.json");
5027
5126
  let existingServers = {};
5028
5127
  try {
5029
- if (fs13.existsSync(mcpPath)) {
5030
- const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
5128
+ if (fs14.existsSync(mcpPath)) {
5129
+ const existing = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
5031
5130
  if (existing.mcpServers) existingServers = existing.mcpServers;
5032
5131
  }
5033
5132
  } catch {
5034
5133
  }
5035
5134
  const mergedServers = { ...existingServers, ...config.mcpServers };
5036
- fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
5135
+ fs14.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
5037
5136
  written.push(mcpPath);
5038
5137
  }
5039
5138
  return written;
@@ -5041,17 +5140,17 @@ function writeCursorConfig(config) {
5041
5140
 
5042
5141
  // src/writers/codex/index.ts
5043
5142
  init_pre_commit_block();
5044
- import fs14 from "fs";
5045
- import path13 from "path";
5143
+ import fs15 from "fs";
5144
+ import path14 from "path";
5046
5145
  function writeCodexConfig(config) {
5047
5146
  const written = [];
5048
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5147
+ fs15.writeFileSync("AGENTS.md", appendManagedBlocks(config.agentsMd, "codex"));
5049
5148
  written.push("AGENTS.md");
5050
5149
  if (config.skills?.length) {
5051
5150
  for (const skill of config.skills) {
5052
- const skillDir = path13.join(".agents", "skills", skill.name);
5053
- if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
5054
- const skillPath = path13.join(skillDir, "SKILL.md");
5151
+ const skillDir = path14.join(".agents", "skills", skill.name);
5152
+ if (!fs15.existsSync(skillDir)) fs15.mkdirSync(skillDir, { recursive: true });
5153
+ const skillPath = path14.join(skillDir, "SKILL.md");
5055
5154
  const frontmatter = [
5056
5155
  "---",
5057
5156
  `name: ${skill.name}`,
@@ -5059,7 +5158,7 @@ function writeCodexConfig(config) {
5059
5158
  "---",
5060
5159
  ""
5061
5160
  ].join("\n");
5062
- fs14.writeFileSync(skillPath, frontmatter + skill.content);
5161
+ fs15.writeFileSync(skillPath, frontmatter + skill.content);
5063
5162
  written.push(skillPath);
5064
5163
  }
5065
5164
  }
@@ -5068,20 +5167,20 @@ function writeCodexConfig(config) {
5068
5167
 
5069
5168
  // src/writers/github-copilot/index.ts
5070
5169
  init_pre_commit_block();
5071
- import fs15 from "fs";
5072
- import path14 from "path";
5170
+ import fs16 from "fs";
5171
+ import path15 from "path";
5073
5172
  function writeGithubCopilotConfig(config) {
5074
5173
  const written = [];
5075
5174
  if (config.instructions) {
5076
- fs15.mkdirSync(".github", { recursive: true });
5077
- fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
5175
+ fs16.mkdirSync(".github", { recursive: true });
5176
+ fs16.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendManagedBlocks(config.instructions, "copilot"));
5078
5177
  written.push(".github/copilot-instructions.md");
5079
5178
  }
5080
5179
  if (config.instructionFiles?.length) {
5081
- const instructionsDir = path14.join(".github", "instructions");
5082
- fs15.mkdirSync(instructionsDir, { recursive: true });
5180
+ const instructionsDir = path15.join(".github", "instructions");
5181
+ fs16.mkdirSync(instructionsDir, { recursive: true });
5083
5182
  for (const file of config.instructionFiles) {
5084
- fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
5183
+ fs16.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
5085
5184
  written.push(`.github/instructions/${file.filename}`);
5086
5185
  }
5087
5186
  }
@@ -5089,30 +5188,30 @@ function writeGithubCopilotConfig(config) {
5089
5188
  }
5090
5189
 
5091
5190
  // src/writers/backup.ts
5092
- import fs16 from "fs";
5093
- import path15 from "path";
5191
+ import fs17 from "fs";
5192
+ import path16 from "path";
5094
5193
  function createBackup(files) {
5095
5194
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5096
- const backupDir = path15.join(BACKUPS_DIR, timestamp);
5195
+ const backupDir = path16.join(BACKUPS_DIR, timestamp);
5097
5196
  for (const file of files) {
5098
- if (!fs16.existsSync(file)) continue;
5099
- const dest = path15.join(backupDir, file);
5100
- const destDir = path15.dirname(dest);
5101
- if (!fs16.existsSync(destDir)) {
5102
- fs16.mkdirSync(destDir, { recursive: true });
5197
+ if (!fs17.existsSync(file)) continue;
5198
+ const dest = path16.join(backupDir, file);
5199
+ const destDir = path16.dirname(dest);
5200
+ if (!fs17.existsSync(destDir)) {
5201
+ fs17.mkdirSync(destDir, { recursive: true });
5103
5202
  }
5104
- fs16.copyFileSync(file, dest);
5203
+ fs17.copyFileSync(file, dest);
5105
5204
  }
5106
5205
  return backupDir;
5107
5206
  }
5108
5207
  function restoreBackup(backupDir, file) {
5109
- const backupFile = path15.join(backupDir, file);
5110
- if (!fs16.existsSync(backupFile)) return false;
5111
- const destDir = path15.dirname(file);
5112
- if (!fs16.existsSync(destDir)) {
5113
- fs16.mkdirSync(destDir, { recursive: true });
5208
+ const backupFile = path16.join(backupDir, file);
5209
+ if (!fs17.existsSync(backupFile)) return false;
5210
+ const destDir = path16.dirname(file);
5211
+ if (!fs17.existsSync(destDir)) {
5212
+ fs17.mkdirSync(destDir, { recursive: true });
5114
5213
  }
5115
- fs16.copyFileSync(backupFile, file);
5214
+ fs17.copyFileSync(backupFile, file);
5116
5215
  return true;
5117
5216
  }
5118
5217
 
@@ -9290,8 +9389,9 @@ async function initCommand(options) {
9290
9389
  if (options.agent) {
9291
9390
  targetAgent = options.agent;
9292
9391
  } else if (options.autoApprove) {
9293
- targetAgent = ["claude"];
9294
- log(options.verbose, "Auto-approve: defaulting to claude agent");
9392
+ const detected = detectAgents(process.cwd());
9393
+ targetAgent = detected.length > 0 ? detected : ["claude"];
9394
+ log(options.verbose, `Auto-approve: using ${targetAgent.join(", ")}`);
9295
9395
  } else {
9296
9396
  const detected = detectAgents(process.cwd());
9297
9397
  targetAgent = await promptAgent(detected.length > 0 ? detected : void 0);
@@ -9372,12 +9472,11 @@ async function initCommand(options) {
9372
9472
  }
9373
9473
  if (skipGeneration) {
9374
9474
  const {
9375
- appendPreCommitBlock: appendPreCommitBlock2,
9376
- appendLearningsBlock: appendLearningsBlock2,
9377
- appendSyncBlock: appendSyncBlock2,
9475
+ appendManagedBlocks: appendManagedBlocks2,
9378
9476
  getCursorPreCommitRule: getCursorPreCommitRule2,
9379
9477
  getCursorLearningsRule: getCursorLearningsRule2,
9380
- getCursorSyncRule: getCursorSyncRule2
9478
+ getCursorSyncRule: getCursorSyncRule2,
9479
+ getCursorSetupRule: getCursorSetupRule2
9381
9480
  } = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
9382
9481
  const claudeMdPath = "CLAUDE.md";
9383
9482
  let claudeContent = "";
@@ -9389,7 +9488,7 @@ async function initCommand(options) {
9389
9488
  claudeContent = `# ${path26.basename(process.cwd())}
9390
9489
  `;
9391
9490
  }
9392
- const updatedClaude = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(claudeContent)));
9491
+ const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
9393
9492
  if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
9394
9493
  fs33.writeFileSync(claudeMdPath, updatedClaude);
9395
9494
  console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
@@ -9397,7 +9496,7 @@ async function initCommand(options) {
9397
9496
  if (targetAgent.includes("cursor")) {
9398
9497
  const rulesDir = path26.join(".cursor", "rules");
9399
9498
  if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
9400
- for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2()]) {
9499
+ for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
9401
9500
  fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
9402
9501
  }
9403
9502
  console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
@@ -9414,7 +9513,7 @@ async function initCommand(options) {
9414
9513
  copilotContent = `# ${path26.basename(process.cwd())}
9415
9514
  `;
9416
9515
  }
9417
- const updatedCopilot = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(copilotContent)));
9516
+ const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
9418
9517
  if (updatedCopilot !== copilotContent) {
9419
9518
  fs33.writeFileSync(copilotPath, updatedCopilot);
9420
9519
  console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
@@ -10193,14 +10292,24 @@ var MAX_DIFF_BYTES = 1e5;
10193
10292
  var DOC_PATTERNS = [
10194
10293
  "CLAUDE.md",
10195
10294
  "README.md",
10295
+ "AGENTS.md",
10196
10296
  ".cursorrules",
10197
10297
  ".cursor/rules/",
10298
+ ".cursor/skills/",
10198
10299
  ".claude/skills/",
10300
+ ".agents/skills/",
10301
+ ".github/copilot-instructions.md",
10302
+ ".github/instructions/",
10199
10303
  "CALIBER_LEARNINGS.md"
10200
10304
  ];
10201
10305
  function excludeArgs() {
10202
10306
  return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
10203
10307
  }
10308
+ function truncateAtLine(text, maxBytes) {
10309
+ if (text.length <= maxBytes) return text;
10310
+ const cut = text.lastIndexOf("\n", maxBytes);
10311
+ return cut > 0 ? text.slice(0, cut) : text.slice(0, maxBytes);
10312
+ }
10204
10313
  function safeExec(cmd) {
10205
10314
  try {
10206
10315
  return execSync15(cmd, {
@@ -10246,9 +10355,9 @@ function collectDiff(lastSha) {
10246
10355
  const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
10247
10356
  if (totalSize > MAX_DIFF_BYTES) {
10248
10357
  const ratio = MAX_DIFF_BYTES / totalSize;
10249
- committedDiff = committedDiff.slice(0, Math.floor(committedDiff.length * ratio));
10250
- stagedDiff = stagedDiff.slice(0, Math.floor(stagedDiff.length * ratio));
10251
- unstagedDiff = unstagedDiff.slice(0, Math.floor(unstagedDiff.length * ratio));
10358
+ committedDiff = truncateAtLine(committedDiff, Math.floor(committedDiff.length * ratio));
10359
+ stagedDiff = truncateAtLine(stagedDiff, Math.floor(stagedDiff.length * ratio));
10360
+ unstagedDiff = truncateAtLine(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
10252
10361
  }
10253
10362
  const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
10254
10363
  const parts = [];
@@ -10267,13 +10376,17 @@ import path28 from "path";
10267
10376
  function writeRefreshDocs(docs) {
10268
10377
  const written = [];
10269
10378
  if (docs.claudeMd) {
10270
- fs36.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.claudeMd))));
10379
+ fs36.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd, "claude"));
10271
10380
  written.push("CLAUDE.md");
10272
10381
  }
10273
10382
  if (docs.readmeMd) {
10274
10383
  fs36.writeFileSync("README.md", docs.readmeMd);
10275
10384
  written.push("README.md");
10276
10385
  }
10386
+ if (docs.agentsMd) {
10387
+ fs36.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10388
+ written.push("AGENTS.md");
10389
+ }
10277
10390
  if (docs.cursorrules) {
10278
10391
  fs36.writeFileSync(".cursorrules", docs.cursorrules);
10279
10392
  written.push(".cursorrules");
@@ -10296,7 +10409,7 @@ function writeRefreshDocs(docs) {
10296
10409
  }
10297
10410
  if (docs.copilotInstructions) {
10298
10411
  fs36.mkdirSync(".github", { recursive: true });
10299
- fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions))));
10412
+ fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendManagedBlocks(docs.copilotInstructions, "copilot"));
10300
10413
  written.push(".github/copilot-instructions.md");
10301
10414
  }
10302
10415
  if (docs.copilotInstructionFiles) {
@@ -10315,10 +10428,18 @@ init_config();
10315
10428
  async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
10316
10429
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
10317
10430
  const fastModel = getFastModel();
10431
+ const docCount = [
10432
+ existingDocs.claudeMd,
10433
+ existingDocs.readmeMd,
10434
+ existingDocs.agentsMd,
10435
+ existingDocs.cursorrules,
10436
+ existingDocs.copilotInstructions
10437
+ ].filter(Boolean).length + (existingDocs.cursorRules?.length ?? 0) + (existingDocs.claudeSkills?.length ?? 0);
10438
+ const maxTokens = Math.min(32768, Math.max(8192, docCount * 4096));
10318
10439
  const raw = await llmCall({
10319
10440
  system: REFRESH_SYSTEM_PROMPT,
10320
10441
  prompt,
10321
- maxTokens: 16384,
10442
+ maxTokens,
10322
10443
  ...fastModel ? { model: fastModel } : {}
10323
10444
  });
10324
10445
  return parseJsonResponse(raw);
@@ -10377,6 +10498,21 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10377
10498
  parts.push(rule.content);
10378
10499
  }
10379
10500
  }
10501
+ if (existingDocs.agentsMd) {
10502
+ parts.push("\n[AGENTS.md]");
10503
+ parts.push(existingDocs.agentsMd);
10504
+ }
10505
+ if (existingDocs.copilotInstructions) {
10506
+ parts.push("\n[.github/copilot-instructions.md]");
10507
+ parts.push(existingDocs.copilotInstructions);
10508
+ }
10509
+ if (existingDocs.copilotInstructionFiles?.length) {
10510
+ for (const file of existingDocs.copilotInstructionFiles) {
10511
+ parts.push(`
10512
+ [.github/instructions/${file.filename}]`);
10513
+ parts.push(file.content);
10514
+ }
10515
+ }
10380
10516
  if (learnedSection) {
10381
10517
  parts.push("\n--- Learned Patterns (from session learning) ---");
10382
10518
  parts.push("Consider these accumulated learnings when deciding what to update:");