@rely-ai/caliber 1.31.0-dev.1774815271 → 1.32.0-dev.1774815754

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +22 -70
  2. package/dist/bin.js +1076 -994
  3. package/package.json +24 -5
package/dist/bin.js CHANGED
@@ -9,6 +9,155 @@ 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 path6 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 = path6.join(os2.homedir(), ".caliber");
129
+ CONFIG_FILE = path6.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
+
12
161
  // src/lib/resolve-caliber.ts
13
162
  var resolve_caliber_exports = {};
14
163
  __export(resolve_caliber_exports, {
@@ -17,8 +166,8 @@ __export(resolve_caliber_exports, {
17
166
  resetResolvedCaliber: () => resetResolvedCaliber,
18
167
  resolveCaliber: () => resolveCaliber
19
168
  });
20
- import fs2 from "fs";
21
- import { execSync as execSync3 } from "child_process";
169
+ import fs6 from "fs";
170
+ import { execSync as execSync4 } from "child_process";
22
171
  function resolveCaliber() {
23
172
  if (_resolved) return _resolved;
24
173
  const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
@@ -28,7 +177,7 @@ function resolveCaliber() {
28
177
  }
29
178
  try {
30
179
  const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
31
- execSync3(whichCmd, {
180
+ execSync4(whichCmd, {
32
181
  encoding: "utf-8",
33
182
  stdio: ["pipe", "pipe", "pipe"]
34
183
  });
@@ -37,7 +186,7 @@ function resolveCaliber() {
37
186
  } catch {
38
187
  }
39
188
  const binPath = process.argv[1];
40
- if (binPath && /caliber/.test(binPath) && fs2.existsSync(binPath)) {
189
+ if (binPath && /caliber/.test(binPath) && fs6.existsSync(binPath)) {
41
190
  _resolved = binPath;
42
191
  return _resolved;
43
192
  }
@@ -65,65 +214,283 @@ var init_resolve_caliber = __esm({
65
214
  }
66
215
  });
67
216
 
68
- // src/lib/builtin-skills.ts
69
- var builtin_skills_exports = {};
70
- __export(builtin_skills_exports, {
71
- BUILTIN_SKILLS: () => BUILTIN_SKILLS,
72
- BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
73
- FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
74
- PLATFORM_CONFIGS: () => PLATFORM_CONFIGS,
75
- SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
76
- SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
77
- buildSkillContent: () => buildSkillContent,
78
- ensureBuiltinSkills: () => ensureBuiltinSkills
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
+ }
79
231
  });
80
- import fs3 from "fs";
81
- import path3 from "path";
82
- function buildSkillContent(skill) {
83
- const frontmatter = `---
84
- name: ${skill.name}
85
- description: ${skill.description}
86
- ---
87
232
 
88
- `;
89
- return frontmatter + skill.content;
233
+ // src/writers/pre-commit-block.ts
234
+ var pre_commit_block_exports = {};
235
+ __export(pre_commit_block_exports, {
236
+ appendLearningsBlock: () => appendLearningsBlock,
237
+ appendManagedBlocks: () => appendManagedBlocks,
238
+ appendPreCommitBlock: () => appendPreCommitBlock,
239
+ appendSyncBlock: () => appendSyncBlock,
240
+ getCursorLearningsRule: () => getCursorLearningsRule,
241
+ getCursorPreCommitRule: () => getCursorPreCommitRule,
242
+ getCursorSetupRule: () => getCursorSetupRule,
243
+ getCursorSyncRule: () => getCursorSyncRule,
244
+ hasLearningsBlock: () => hasLearningsBlock,
245
+ hasPreCommitBlock: () => hasPreCommitBlock,
246
+ hasSyncBlock: () => hasSyncBlock,
247
+ stripManagedBlocks: () => stripManagedBlocks
248
+ });
249
+ function getCaliberNotFoundFallback(platform, bin) {
250
+ switch (platform) {
251
+ case "claude":
252
+ return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
253
+ case "codex":
254
+ return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
255
+ case "copilot":
256
+ return `If \`${bin}\` is not found, tell the developer to set up Caliber by running \`/setup-caliber\` in Claude Code or Cursor. Alternatively, they can run these commands in their terminal:
257
+ \`\`\`
258
+ npx @rely-ai/caliber hooks --install
259
+ npx @rely-ai/caliber refresh
260
+ \`\`\``;
261
+ }
90
262
  }
91
- function getFindSkillsContent() {
263
+ function getPreCommitBlock(platform = "claude") {
92
264
  const bin = resolveCaliber();
93
- return `# Find Skills
94
-
95
- Search the public skill registry for community-contributed skills
96
- relevant to the user's current task and install them into this project.
97
-
98
- ## Instructions
265
+ return `${BLOCK_START}
266
+ ## Before Committing
99
267
 
100
- 1. Identify the key technologies, frameworks, or task types from the
101
- user's request that might have community skills available
102
- 2. Ask the user: "Would you like me to search for community skills
103
- for [identified technologies]?"
104
- 3. If the user agrees, run:
105
- \`\`\`bash
106
- ${bin} skills --query "<relevant terms>"
107
- \`\`\`
108
- This outputs the top 5 matching skills with scores and descriptions.
109
- 4. Present the results to the user and ask which ones to install
110
- 5. Install the selected skills:
111
- \`\`\`bash
112
- ${bin} skills --install <slug1>,<slug2>
113
- \`\`\`
114
- 6. Read the installed SKILL.md files to load them into your current
115
- context so you can use them immediately in this session
116
- 7. Summarize what was installed and continue with the user's task
268
+ **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
117
269
 
118
- ## Examples
270
+ First, check if the pre-commit hook is already installed:
271
+ \`\`\`bash
272
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
273
+ \`\`\`
119
274
 
120
- User: "let's build a web app using React"
121
- -> "I notice you want to work with React. Would you like me to search
122
- for community skills that could help with React development?"
123
- -> If yes: run \`${bin} skills --query "react frontend"\`
124
- -> Show the user the results, ask which to install
125
- -> Run \`${bin} skills --install <selected-slugs>\`
126
- -> Read the installed files and continue
275
+ - 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."
276
+ - If **no-hook**: run Caliber manually before committing:
277
+ 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
278
+ 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
279
+ 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
280
+
281
+ ${getCaliberNotFoundFallback(platform, bin)}
282
+ ${BLOCK_END}`;
283
+ }
284
+ function getCursorRuleContent() {
285
+ const bin = resolveCaliber();
286
+ return `---
287
+ description: Run Caliber sync before git commits to keep agent configs up to date
288
+ alwaysApply: true
289
+ ---
290
+ Before every git commit, check if the Caliber pre-commit hook is installed:
291
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
292
+
293
+ If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
294
+ If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
295
+
296
+ If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
297
+ `;
298
+ }
299
+ function hasPreCommitBlock(content) {
300
+ return content.includes(BLOCK_START);
301
+ }
302
+ function appendPreCommitBlock(content, platform = "claude") {
303
+ if (hasPreCommitBlock(content)) return content;
304
+ const trimmed = content.trimEnd();
305
+ return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
306
+ }
307
+ function getCursorPreCommitRule() {
308
+ return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
309
+ }
310
+ function hasLearningsBlock(content) {
311
+ return content.includes(LEARNINGS_BLOCK_START);
312
+ }
313
+ function appendLearningsBlock(content) {
314
+ if (hasLearningsBlock(content)) return content;
315
+ const trimmed = content.trimEnd();
316
+ return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
317
+ }
318
+ function getCursorLearningsRule() {
319
+ return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
320
+ }
321
+ function getSyncSetupInstruction(platform) {
322
+ switch (platform) {
323
+ case "claude":
324
+ return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
325
+ case "codex":
326
+ return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
327
+ case "copilot":
328
+ return `If the pre-commit hook is not set up, the developer should run \`/setup-caliber\` in Claude Code or Cursor for automated setup. Alternatively, run in terminal:
329
+ \`\`\`bash
330
+ npx @rely-ai/caliber hooks --install
331
+ npx @rely-ai/caliber refresh
332
+ git add ${MANAGED_DOC_PATHS} 2>/dev/null
333
+ \`\`\``;
334
+ }
335
+ }
336
+ function getSyncBlock(platform = "claude") {
337
+ const bin = resolveCaliber();
338
+ return `${SYNC_BLOCK_START}
339
+ ## Context Sync
340
+
341
+ 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.
342
+ Configs update automatically before each commit via \`${bin} refresh\`.
343
+ ${getSyncSetupInstruction(platform)}
344
+ ${SYNC_BLOCK_END}`;
345
+ }
346
+ function hasSyncBlock(content) {
347
+ return content.includes(SYNC_BLOCK_START);
348
+ }
349
+ function appendSyncBlock(content, platform = "claude") {
350
+ if (hasSyncBlock(content)) return content;
351
+ const trimmed = content.trimEnd();
352
+ return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
353
+ }
354
+ function appendManagedBlocks(content, platform = "claude") {
355
+ return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
356
+ }
357
+ function getCursorSyncContent() {
358
+ const bin = resolveCaliber();
359
+ return `---
360
+ description: This project uses Caliber for automatic AI agent context sync
361
+ alwaysApply: true
362
+ ---
363
+ This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
364
+ in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
365
+ If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
366
+ `;
367
+ }
368
+ function getCursorSyncRule() {
369
+ return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
370
+ }
371
+ function getCursorSetupContent() {
372
+ return `---
373
+ description: Check if Caliber agent config sync is set up and offer installation on new sessions
374
+ alwaysApply: true
375
+ ---
376
+ This project uses Caliber to keep AI agent configs in sync automatically.
377
+ At the start of each session, silently check if sync is active:
378
+
379
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
380
+
381
+ If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
382
+ 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."
383
+
384
+ If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
385
+ `;
386
+ }
387
+ function getCursorSetupRule() {
388
+ return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
389
+ }
390
+ function stripManagedBlocks(content) {
391
+ let result = content;
392
+ for (const [start, end] of MANAGED_BLOCK_PAIRS) {
393
+ const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
394
+ result = result.replace(regex, "\n");
395
+ }
396
+ return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
397
+ }
398
+ 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, MANAGED_BLOCK_PAIRS;
399
+ var init_pre_commit_block = __esm({
400
+ "src/writers/pre-commit-block.ts"() {
401
+ "use strict";
402
+ init_resolve_caliber();
403
+ BLOCK_START = "<!-- caliber:managed:pre-commit -->";
404
+ BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
405
+ MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
406
+ CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
407
+ LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
408
+ LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
409
+ LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
410
+ ## Session Learnings
411
+
412
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
413
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
414
+ ${LEARNINGS_BLOCK_END}`;
415
+ CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
416
+ CURSOR_LEARNINGS_CONTENT = `---
417
+ description: Reference session-learned patterns from CALIBER_LEARNINGS.md
418
+ alwaysApply: true
419
+ ---
420
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
421
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
422
+ `;
423
+ SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
424
+ SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
425
+ CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
426
+ CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
427
+ MANAGED_BLOCK_PAIRS = [
428
+ [BLOCK_START, BLOCK_END],
429
+ [LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END],
430
+ [SYNC_BLOCK_START, SYNC_BLOCK_END]
431
+ ];
432
+ }
433
+ });
434
+
435
+ // src/lib/builtin-skills.ts
436
+ var builtin_skills_exports = {};
437
+ __export(builtin_skills_exports, {
438
+ BUILTIN_SKILLS: () => BUILTIN_SKILLS,
439
+ BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
440
+ FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
441
+ PLATFORM_CONFIGS: () => PLATFORM_CONFIGS,
442
+ SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
443
+ SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
444
+ buildSkillContent: () => buildSkillContent,
445
+ ensureBuiltinSkills: () => ensureBuiltinSkills
446
+ });
447
+ import fs17 from "fs";
448
+ import path17 from "path";
449
+ function buildSkillContent(skill) {
450
+ const frontmatter = `---
451
+ name: ${skill.name}
452
+ description: ${skill.description}
453
+ ---
454
+
455
+ `;
456
+ return frontmatter + skill.content;
457
+ }
458
+ function getFindSkillsContent() {
459
+ const bin = resolveCaliber();
460
+ return `# Find Skills
461
+
462
+ Search the public skill registry for community-contributed skills
463
+ relevant to the user's current task and install them into this project.
464
+
465
+ ## Instructions
466
+
467
+ 1. Identify the key technologies, frameworks, or task types from the
468
+ user's request that might have community skills available
469
+ 2. Ask the user: "Would you like me to search for community skills
470
+ for [identified technologies]?"
471
+ 3. If the user agrees, run:
472
+ \`\`\`bash
473
+ ${bin} skills --query "<relevant terms>"
474
+ \`\`\`
475
+ This outputs the top 5 matching skills with scores and descriptions.
476
+ 4. Present the results to the user and ask which ones to install
477
+ 5. Install the selected skills:
478
+ \`\`\`bash
479
+ ${bin} skills --install <slug1>,<slug2>
480
+ \`\`\`
481
+ 6. Read the installed SKILL.md files to load them into your current
482
+ context so you can use them immediately in this session
483
+ 7. Summarize what was installed and continue with the user's task
484
+
485
+ ## Examples
486
+
487
+ User: "let's build a web app using React"
488
+ -> "I notice you want to work with React. Would you like me to search
489
+ for community skills that could help with React development?"
490
+ -> If yes: run \`${bin} skills --query "react frontend"\`
491
+ -> Show the user the results, ask which to install
492
+ -> Run \`${bin} skills --install <selected-slugs>\`
493
+ -> Read the installed files and continue
127
494
 
128
495
  User: "help me set up Docker for this project"
129
496
  -> "Would you like me to search for Docker-related skills?"
@@ -411,427 +778,60 @@ From now on, every commit keeps all your agent configs in sync automatically.
411
778
  function ensureBuiltinSkills() {
412
779
  const written = [];
413
780
  for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
414
- if (!fs3.existsSync(platformDir)) continue;
781
+ if (!fs17.existsSync(platformDir)) continue;
415
782
  for (const skill of BUILTIN_SKILLS) {
416
- const skillPath = path3.join(skillsDir, skill.name, "SKILL.md");
417
- fs3.mkdirSync(path3.dirname(skillPath), { recursive: true });
418
- fs3.writeFileSync(skillPath, buildSkillContent(skill));
783
+ const skillPath = path17.join(skillsDir, skill.name, "SKILL.md");
784
+ fs17.mkdirSync(path17.dirname(skillPath), { recursive: true });
785
+ fs17.writeFileSync(skillPath, buildSkillContent(skill));
419
786
  written.push(skillPath);
420
787
  }
421
788
  }
422
789
  return written;
423
- }
424
- var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
425
- var init_builtin_skills = __esm({
426
- "src/lib/builtin-skills.ts"() {
427
- "use strict";
428
- init_resolve_caliber();
429
- FIND_SKILLS_SKILL = {
430
- name: "find-skills",
431
- 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.",
432
- get content() {
433
- return getFindSkillsContent();
434
- }
435
- };
436
- SAVE_LEARNING_SKILL = {
437
- name: "save-learning",
438
- 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.",
439
- get content() {
440
- return getSaveLearningContent();
441
- }
442
- };
443
- SETUP_CALIBER_SKILL = {
444
- name: "setup-caliber",
445
- 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.",
446
- get content() {
447
- return getSetupCaliberContent();
448
- }
449
- };
450
- BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
451
- PLATFORM_CONFIGS = [
452
- { platformDir: ".claude", skillsDir: path3.join(".claude", "skills") },
453
- { platformDir: ".cursor", skillsDir: path3.join(".cursor", "skills") },
454
- { platformDir: ".agents", skillsDir: path3.join(".agents", "skills") }
455
- ];
456
- BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
457
- }
458
- });
459
-
460
- // src/writers/pre-commit-block.ts
461
- var pre_commit_block_exports = {};
462
- __export(pre_commit_block_exports, {
463
- appendLearningsBlock: () => appendLearningsBlock,
464
- appendManagedBlocks: () => appendManagedBlocks,
465
- appendPreCommitBlock: () => appendPreCommitBlock,
466
- appendSyncBlock: () => appendSyncBlock,
467
- getCursorLearningsRule: () => getCursorLearningsRule,
468
- getCursorPreCommitRule: () => getCursorPreCommitRule,
469
- getCursorSetupRule: () => getCursorSetupRule,
470
- getCursorSyncRule: () => getCursorSyncRule,
471
- hasLearningsBlock: () => hasLearningsBlock,
472
- hasPreCommitBlock: () => hasPreCommitBlock,
473
- hasSyncBlock: () => hasSyncBlock,
474
- stripManagedBlocks: () => stripManagedBlocks
475
- });
476
- function getCaliberNotFoundFallback(platform, bin) {
477
- switch (platform) {
478
- case "claude":
479
- return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
480
- case "codex":
481
- return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
482
- case "copilot":
483
- return `If \`${bin}\` is not found, tell the developer to set up Caliber by running \`/setup-caliber\` in Claude Code or Cursor. Alternatively, they can run these commands in their terminal:
484
- \`\`\`
485
- npx @rely-ai/caliber hooks --install
486
- npx @rely-ai/caliber refresh
487
- \`\`\``;
488
- }
489
- }
490
- function getPreCommitBlock(platform = "claude") {
491
- const bin = resolveCaliber();
492
- return `${BLOCK_START}
493
- ## Before Committing
494
-
495
- **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
496
-
497
- First, check if the pre-commit hook is already installed:
498
- \`\`\`bash
499
- grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
500
- \`\`\`
501
-
502
- - 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."
503
- - If **no-hook**: run Caliber manually before committing:
504
- 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
505
- 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
506
- 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
507
-
508
- ${getCaliberNotFoundFallback(platform, bin)}
509
- ${BLOCK_END}`;
510
- }
511
- function getCursorRuleContent() {
512
- const bin = resolveCaliber();
513
- return `---
514
- description: Run Caliber sync before git commits to keep agent configs up to date
515
- alwaysApply: true
516
- ---
517
- Before every git commit, check if the Caliber pre-commit hook is installed:
518
- Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
519
-
520
- If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
521
- If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
522
-
523
- If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
524
- `;
525
- }
526
- function hasPreCommitBlock(content) {
527
- return content.includes(BLOCK_START);
528
- }
529
- function appendPreCommitBlock(content, platform = "claude") {
530
- if (hasPreCommitBlock(content)) return content;
531
- const trimmed = content.trimEnd();
532
- return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
533
- }
534
- function getCursorPreCommitRule() {
535
- return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
536
- }
537
- function hasLearningsBlock(content) {
538
- return content.includes(LEARNINGS_BLOCK_START);
539
- }
540
- function appendLearningsBlock(content) {
541
- if (hasLearningsBlock(content)) return content;
542
- const trimmed = content.trimEnd();
543
- return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
544
- }
545
- function getCursorLearningsRule() {
546
- return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
547
- }
548
- function getSyncSetupInstruction(platform, bin) {
549
- switch (platform) {
550
- case "claude":
551
- return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
552
- case "codex":
553
- return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
554
- case "copilot":
555
- return `If the pre-commit hook is not set up, the developer should run \`/setup-caliber\` in Claude Code or Cursor for automated setup. Alternatively, run in terminal:
556
- \`\`\`bash
557
- npx @rely-ai/caliber hooks --install
558
- npx @rely-ai/caliber refresh
559
- git add ${MANAGED_DOC_PATHS} 2>/dev/null
560
- \`\`\``;
561
- }
562
- }
563
- function getSyncBlock(platform = "claude") {
564
- const bin = resolveCaliber();
565
- return `${SYNC_BLOCK_START}
566
- ## Context Sync
567
-
568
- 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.
569
- Configs update automatically before each commit via \`${bin} refresh\`.
570
- ${getSyncSetupInstruction(platform, bin)}
571
- ${SYNC_BLOCK_END}`;
572
- }
573
- function hasSyncBlock(content) {
574
- return content.includes(SYNC_BLOCK_START);
575
- }
576
- function appendSyncBlock(content, platform = "claude") {
577
- if (hasSyncBlock(content)) return content;
578
- const trimmed = content.trimEnd();
579
- return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
580
- }
581
- function appendManagedBlocks(content, platform = "claude") {
582
- return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
583
- }
584
- function getCursorSyncContent() {
585
- const bin = resolveCaliber();
586
- return `---
587
- description: This project uses Caliber for automatic AI agent context sync
588
- alwaysApply: true
589
- ---
590
- This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
591
- in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
592
- If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
593
- `;
594
- }
595
- function getCursorSyncRule() {
596
- return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
597
- }
598
- function getCursorSetupContent() {
599
- return `---
600
- description: Check if Caliber agent config sync is set up and offer installation on new sessions
601
- alwaysApply: true
602
- ---
603
- This project uses Caliber to keep AI agent configs in sync automatically.
604
- At the start of each session, silently check if sync is active:
605
-
606
- Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
607
-
608
- If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
609
- 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."
610
-
611
- If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
612
- `;
613
- }
614
- function getCursorSetupRule() {
615
- return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
616
- }
617
- function stripManagedBlocks(content) {
618
- let result = content;
619
- for (const [start, end] of MANAGED_BLOCK_PAIRS) {
620
- const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
621
- result = result.replace(regex, "\n");
622
- }
623
- return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
624
- }
625
- 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, MANAGED_BLOCK_PAIRS;
626
- var init_pre_commit_block = __esm({
627
- "src/writers/pre-commit-block.ts"() {
628
- "use strict";
629
- init_resolve_caliber();
630
- BLOCK_START = "<!-- caliber:managed:pre-commit -->";
631
- BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
632
- MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
633
- CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
634
- LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
635
- LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
636
- LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
637
- ## Session Learnings
638
-
639
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
640
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
641
- ${LEARNINGS_BLOCK_END}`;
642
- CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
643
- CURSOR_LEARNINGS_CONTENT = `---
644
- description: Reference session-learned patterns from CALIBER_LEARNINGS.md
645
- alwaysApply: true
646
- ---
647
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
648
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
649
- `;
650
- SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
651
- SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
652
- CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
653
- CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
654
- MANAGED_BLOCK_PAIRS = [
655
- [BLOCK_START, BLOCK_END],
656
- [LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END],
657
- [SYNC_BLOCK_START, SYNC_BLOCK_END]
658
- ];
659
- }
660
- });
661
-
662
- // src/llm/config.ts
663
- var config_exports = {};
664
- __export(config_exports, {
665
- DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
666
- DEFAULT_MODELS: () => DEFAULT_MODELS,
667
- MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
668
- getConfigFilePath: () => getConfigFilePath,
669
- getDisplayModel: () => getDisplayModel,
670
- getFastModel: () => getFastModel,
671
- getMaxPromptTokens: () => getMaxPromptTokens,
672
- loadConfig: () => loadConfig,
673
- readConfigFile: () => readConfigFile,
674
- resolveFromEnv: () => resolveFromEnv,
675
- writeConfigFile: () => writeConfigFile
676
- });
677
- import fs6 from "fs";
678
- import path6 from "path";
679
- import os2 from "os";
680
- function getMaxPromptTokens() {
681
- const config = loadConfig();
682
- const model = process.env.CALIBER_MODEL || config?.model;
683
- const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
684
- const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
685
- return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
686
- }
687
- function loadConfig() {
688
- const envConfig = resolveFromEnv();
689
- if (envConfig) return envConfig;
690
- return readConfigFile();
691
- }
692
- function resolveFromEnv() {
693
- if (process.env.ANTHROPIC_API_KEY) {
694
- return {
695
- provider: "anthropic",
696
- apiKey: process.env.ANTHROPIC_API_KEY,
697
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
698
- };
699
- }
700
- if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
701
- return {
702
- provider: "vertex",
703
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
704
- vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
705
- vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
706
- vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
707
- };
708
- }
709
- if (process.env.OPENAI_API_KEY) {
710
- return {
711
- provider: "openai",
712
- apiKey: process.env.OPENAI_API_KEY,
713
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
714
- baseUrl: process.env.OPENAI_BASE_URL
715
- };
716
- }
717
- if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
718
- return {
719
- provider: "cursor",
720
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
721
- };
722
- }
723
- if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
724
- return {
725
- provider: "claude-cli",
726
- model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
727
- };
728
- }
729
- return null;
730
- }
731
- function readConfigFile() {
732
- try {
733
- if (!fs6.existsSync(CONFIG_FILE)) return null;
734
- const raw = fs6.readFileSync(CONFIG_FILE, "utf-8");
735
- const parsed = JSON.parse(raw);
736
- if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
737
- return null;
738
- }
739
- return parsed;
740
- } catch {
741
- return null;
742
- }
743
- }
744
- function writeConfigFile(config) {
745
- if (!fs6.existsSync(CONFIG_DIR)) {
746
- fs6.mkdirSync(CONFIG_DIR, { recursive: true });
747
- }
748
- const sanitized = { ...config };
749
- if (sanitized.apiKey) {
750
- sanitized.apiKey = sanitized.apiKey.trim();
751
- }
752
- fs6.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
753
- }
754
- function getConfigFilePath() {
755
- return CONFIG_FILE;
756
- }
757
- function getDisplayModel(config) {
758
- if (config.model === "default" && config.provider === "claude-cli") {
759
- return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
760
- }
761
- return config.model;
762
- }
763
- function getFastModel() {
764
- if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
765
- const config = loadConfig();
766
- const provider = config?.provider;
767
- if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
768
- return process.env.ANTHROPIC_SMALL_FAST_MODEL;
769
- }
770
- if (config?.fastModel) return config.fastModel;
771
- if (provider) return DEFAULT_FAST_MODELS[provider];
772
- return void 0;
773
- }
774
- 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;
775
- var init_config = __esm({
776
- "src/llm/config.ts"() {
777
- "use strict";
778
- CONFIG_DIR = path6.join(os2.homedir(), ".caliber");
779
- CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
780
- DEFAULT_MODELS = {
781
- anthropic: "claude-sonnet-4-6",
782
- vertex: "claude-sonnet-4-6",
783
- openai: "gpt-4.1",
784
- cursor: "sonnet-4.6",
785
- "claude-cli": "default"
786
- };
787
- MODEL_CONTEXT_WINDOWS = {
788
- "claude-sonnet-4-6": 2e5,
789
- "claude-opus-4-6": 2e5,
790
- "claude-haiku-4-5-20251001": 2e5,
791
- "claude-sonnet-4-5-20250514": 2e5,
792
- "gpt-4.1": 1e6,
793
- "gpt-4.1-mini": 1e6,
794
- "gpt-4o": 128e3,
795
- "gpt-4o-mini": 128e3,
796
- "sonnet-4.6": 2e5
797
- };
798
- DEFAULT_CONTEXT_WINDOW = 2e5;
799
- INPUT_BUDGET_FRACTION = 0.6;
800
- MAX_PROMPT_TOKENS_CAP = 3e5;
801
- MIN_PROMPT_TOKENS = 3e4;
802
- DEFAULT_FAST_MODELS = {
803
- anthropic: "claude-haiku-4-5-20251001",
804
- vertex: "claude-haiku-4-5-20251001",
805
- openai: "gpt-4.1-mini",
806
- cursor: "gpt-5.3-codex-fast"
807
- };
808
- }
809
- });
810
-
811
- // src/llm/types.ts
812
- var types_exports = {};
813
- __export(types_exports, {
814
- isSeatBased: () => isSeatBased
815
- });
816
- function isSeatBased(provider) {
817
- return SEAT_BASED_PROVIDERS.has(provider);
818
- }
819
- var SEAT_BASED_PROVIDERS;
820
- var init_types = __esm({
821
- "src/llm/types.ts"() {
790
+ }
791
+ var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
792
+ var init_builtin_skills = __esm({
793
+ "src/lib/builtin-skills.ts"() {
822
794
  "use strict";
823
- SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
795
+ init_resolve_caliber();
796
+ FIND_SKILLS_SKILL = {
797
+ name: "find-skills",
798
+ 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.",
799
+ get content() {
800
+ return getFindSkillsContent();
801
+ }
802
+ };
803
+ SAVE_LEARNING_SKILL = {
804
+ name: "save-learning",
805
+ 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.",
806
+ get content() {
807
+ return getSaveLearningContent();
808
+ }
809
+ };
810
+ SETUP_CALIBER_SKILL = {
811
+ name: "setup-caliber",
812
+ 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.",
813
+ get content() {
814
+ return getSetupCaliberContent();
815
+ }
816
+ };
817
+ BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
818
+ PLATFORM_CONFIGS = [
819
+ { platformDir: ".claude", skillsDir: path17.join(".claude", "skills") },
820
+ { platformDir: ".cursor", skillsDir: path17.join(".cursor", "skills") },
821
+ { platformDir: ".agents", skillsDir: path17.join(".agents", "skills") }
822
+ ];
823
+ BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
824
824
  }
825
825
  });
826
826
 
827
827
  // src/utils/editor.ts
828
828
  import { execSync as execSync14, spawn as spawn3 } from "child_process";
829
829
  import fs27 from "fs";
830
- import path23 from "path";
830
+ import path24 from "path";
831
831
  import os6 from "os";
832
832
  function getEmptyFilePath(proposedPath) {
833
833
  fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
834
- const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
834
+ const tempPath = path24.join(DIFF_TEMP_DIR, path24.basename(proposedPath));
835
835
  fs27.writeFileSync(tempPath, "");
836
836
  return tempPath;
837
837
  }
@@ -872,7 +872,7 @@ var init_editor = __esm({
872
872
  "src/utils/editor.ts"() {
873
873
  "use strict";
874
874
  IS_WINDOWS3 = process.platform === "win32";
875
- DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
875
+ DIFF_TEMP_DIR = path24.join(os6.tmpdir(), "caliber-diff");
876
876
  }
877
877
  });
878
878
 
@@ -1098,7 +1098,7 @@ __export(lock_exports, {
1098
1098
  releaseLock: () => releaseLock
1099
1099
  });
1100
1100
  import fs38 from "fs";
1101
- import path30 from "path";
1101
+ import path31 from "path";
1102
1102
  import os8 from "os";
1103
1103
  function isCaliberRunning() {
1104
1104
  try {
@@ -1132,7 +1132,7 @@ var LOCK_FILE, STALE_MS;
1132
1132
  var init_lock = __esm({
1133
1133
  "src/lib/lock.ts"() {
1134
1134
  "use strict";
1135
- LOCK_FILE = path30.join(os8.tmpdir(), ".caliber.lock");
1135
+ LOCK_FILE = path31.join(os8.tmpdir(), ".caliber.lock");
1136
1136
  STALE_MS = 10 * 60 * 1e3;
1137
1137
  }
1138
1138
  });
@@ -1140,16 +1140,16 @@ var init_lock = __esm({
1140
1140
  // src/cli.ts
1141
1141
  import { Command } from "commander";
1142
1142
  import fs49 from "fs";
1143
- import path39 from "path";
1143
+ import path40 from "path";
1144
1144
  import { fileURLToPath } from "url";
1145
1145
 
1146
1146
  // src/commands/init.ts
1147
- import path26 from "path";
1147
+ import path27 from "path";
1148
1148
  import chalk14 from "chalk";
1149
1149
  import fs33 from "fs";
1150
1150
 
1151
1151
  // src/fingerprint/index.ts
1152
- import fs9 from "fs";
1152
+ import fs8 from "fs";
1153
1153
  import path8 from "path";
1154
1154
 
1155
1155
  // src/fingerprint/git.ts
@@ -1258,8 +1258,8 @@ function scan(base, rel, depth, maxDepth, result) {
1258
1258
  }
1259
1259
 
1260
1260
  // src/fingerprint/existing-config.ts
1261
- import fs4 from "fs";
1262
- import path4 from "path";
1261
+ import fs2 from "fs";
1262
+ import path3 from "path";
1263
1263
 
1264
1264
  // src/constants.ts
1265
1265
  import path2 from "path";
@@ -1294,121 +1294,97 @@ var LEARNING_LAST_ERROR_FILE = "last-error.json";
1294
1294
  var MIN_SESSIONS_FOR_COMPARISON = 3;
1295
1295
 
1296
1296
  // src/fingerprint/existing-config.ts
1297
- init_builtin_skills();
1298
- init_pre_commit_block();
1299
- var MANAGED_CURSOR_RULES = /* @__PURE__ */ new Set([
1300
- getCursorPreCommitRule().filename,
1301
- getCursorLearningsRule().filename,
1302
- getCursorSyncRule().filename,
1303
- getCursorSetupRule().filename
1304
- ]);
1305
1297
  function readExistingConfigs(dir) {
1306
1298
  const configs = {};
1307
- const readmeMdPath = path4.join(dir, "README.md");
1308
- if (fs4.existsSync(readmeMdPath)) {
1309
- configs.readmeMd = fs4.readFileSync(readmeMdPath, "utf-8");
1299
+ const readmeMdPath = path3.join(dir, "README.md");
1300
+ if (fs2.existsSync(readmeMdPath)) {
1301
+ configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
1310
1302
  }
1311
- const agentsMdPath = path4.join(dir, "AGENTS.md");
1312
- if (fs4.existsSync(agentsMdPath)) {
1313
- configs.agentsMd = fs4.readFileSync(agentsMdPath, "utf-8");
1303
+ const agentsMdPath = path3.join(dir, "AGENTS.md");
1304
+ if (fs2.existsSync(agentsMdPath)) {
1305
+ configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
1314
1306
  }
1315
- const claudeMdPath = path4.join(dir, "CLAUDE.md");
1316
- if (fs4.existsSync(claudeMdPath)) {
1317
- configs.claudeMd = fs4.readFileSync(claudeMdPath, "utf-8");
1307
+ const claudeMdPath = path3.join(dir, "CLAUDE.md");
1308
+ if (fs2.existsSync(claudeMdPath)) {
1309
+ configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
1318
1310
  }
1319
- const claudeSettingsPath = path4.join(dir, ".claude", "settings.json");
1320
- if (fs4.existsSync(claudeSettingsPath)) {
1311
+ const claudeSettingsPath = path3.join(dir, ".claude", "settings.json");
1312
+ if (fs2.existsSync(claudeSettingsPath)) {
1321
1313
  try {
1322
- configs.claudeSettings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
1314
+ configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
1323
1315
  } catch {
1324
1316
  }
1325
1317
  }
1326
- const skillsDir = path4.join(dir, ".claude", "skills");
1327
- if (fs4.existsSync(skillsDir)) {
1318
+ const skillsDir = path3.join(dir, ".claude", "skills");
1319
+ if (fs2.existsSync(skillsDir)) {
1328
1320
  try {
1329
- const entries = fs4.readdirSync(skillsDir);
1321
+ const entries = fs2.readdirSync(skillsDir);
1330
1322
  const skills = [];
1331
1323
  for (const entry of entries) {
1332
- if (BUILTIN_SKILL_NAMES.has(entry)) continue;
1333
- const entryPath = path4.join(skillsDir, entry);
1334
- const skillMdPath = path4.join(entryPath, "SKILL.md");
1335
- if (fs4.statSync(entryPath).isDirectory() && fs4.existsSync(skillMdPath)) {
1336
- skills.push({ filename: `${entry}/SKILL.md`, content: fs4.readFileSync(skillMdPath, "utf-8") });
1324
+ const entryPath = path3.join(skillsDir, entry);
1325
+ const skillMdPath = path3.join(entryPath, "SKILL.md");
1326
+ if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
1327
+ skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
1337
1328
  } else if (entry.endsWith(".md")) {
1338
- skills.push({ filename: entry, content: fs4.readFileSync(entryPath, "utf-8") });
1329
+ skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
1339
1330
  }
1340
1331
  }
1341
1332
  if (skills.length > 0) configs.claudeSkills = skills;
1342
1333
  } catch {
1343
1334
  }
1344
1335
  }
1345
- const cursorrulesPath = path4.join(dir, ".cursorrules");
1346
- if (fs4.existsSync(cursorrulesPath)) {
1347
- configs.cursorrules = fs4.readFileSync(cursorrulesPath, "utf-8");
1336
+ const cursorrulesPath = path3.join(dir, ".cursorrules");
1337
+ if (fs2.existsSync(cursorrulesPath)) {
1338
+ configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
1348
1339
  }
1349
- const cursorRulesDir = path4.join(dir, ".cursor", "rules");
1350
- if (fs4.existsSync(cursorRulesDir)) {
1340
+ const cursorRulesDir = path3.join(dir, ".cursor", "rules");
1341
+ if (fs2.existsSync(cursorRulesDir)) {
1351
1342
  try {
1352
- const files = fs4.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc") && !MANAGED_CURSOR_RULES.has(f));
1343
+ const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
1353
1344
  configs.cursorRules = files.map((f) => ({
1354
1345
  filename: f,
1355
- content: fs4.readFileSync(path4.join(cursorRulesDir, f), "utf-8")
1346
+ content: fs2.readFileSync(path3.join(cursorRulesDir, f), "utf-8")
1356
1347
  }));
1357
1348
  } catch {
1358
1349
  }
1359
1350
  }
1360
- const cursorSkillsDir = path4.join(dir, ".cursor", "skills");
1361
- if (fs4.existsSync(cursorSkillsDir)) {
1351
+ const cursorSkillsDir = path3.join(dir, ".cursor", "skills");
1352
+ if (fs2.existsSync(cursorSkillsDir)) {
1362
1353
  try {
1363
- const slugs = fs4.readdirSync(cursorSkillsDir).filter((f) => {
1364
- return fs4.statSync(path4.join(cursorSkillsDir, f)).isDirectory() && !BUILTIN_SKILL_NAMES.has(f);
1354
+ const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
1355
+ return fs2.statSync(path3.join(cursorSkillsDir, f)).isDirectory();
1365
1356
  });
1366
- configs.cursorSkills = slugs.filter((slug) => fs4.existsSync(path4.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
1357
+ configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path3.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
1367
1358
  name,
1368
1359
  filename: "SKILL.md",
1369
- content: fs4.readFileSync(path4.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
1360
+ content: fs2.readFileSync(path3.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
1370
1361
  }));
1371
1362
  } catch {
1372
1363
  }
1373
1364
  }
1374
- const mcpJsonPath = path4.join(dir, ".mcp.json");
1375
- if (fs4.existsSync(mcpJsonPath)) {
1365
+ const mcpJsonPath = path3.join(dir, ".mcp.json");
1366
+ if (fs2.existsSync(mcpJsonPath)) {
1376
1367
  try {
1377
- const mcpJson = JSON.parse(fs4.readFileSync(mcpJsonPath, "utf-8"));
1368
+ const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
1378
1369
  if (mcpJson.mcpServers) {
1379
1370
  configs.claudeMcpServers = mcpJson.mcpServers;
1380
1371
  }
1381
1372
  } catch {
1382
1373
  }
1383
1374
  }
1384
- const cursorMcpPath = path4.join(dir, ".cursor", "mcp.json");
1385
- if (fs4.existsSync(cursorMcpPath)) {
1375
+ const cursorMcpPath = path3.join(dir, ".cursor", "mcp.json");
1376
+ if (fs2.existsSync(cursorMcpPath)) {
1386
1377
  try {
1387
- const cursorMcpJson = JSON.parse(fs4.readFileSync(cursorMcpPath, "utf-8"));
1378
+ const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
1388
1379
  if (cursorMcpJson.mcpServers) {
1389
1380
  configs.cursorMcpServers = cursorMcpJson.mcpServers;
1390
1381
  }
1391
1382
  } catch {
1392
1383
  }
1393
1384
  }
1394
- const copilotPath = path4.join(dir, ".github", "copilot-instructions.md");
1395
- if (fs4.existsSync(copilotPath)) {
1396
- configs.copilotInstructions = fs4.readFileSync(copilotPath, "utf-8");
1397
- }
1398
- const instructionsDir = path4.join(dir, ".github", "instructions");
1399
- if (fs4.existsSync(instructionsDir)) {
1400
- try {
1401
- const files = fs4.readdirSync(instructionsDir).filter((f) => f.endsWith(".md"));
1402
- configs.copilotInstructionFiles = files.map((f) => ({
1403
- filename: f,
1404
- content: fs4.readFileSync(path4.join(instructionsDir, f), "utf-8")
1405
- }));
1406
- } catch {
1407
- }
1408
- }
1409
- if (fs4.existsSync(PERSONAL_LEARNINGS_FILE)) {
1385
+ if (fs2.existsSync(PERSONAL_LEARNINGS_FILE)) {
1410
1386
  try {
1411
- const content = fs4.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
1387
+ const content = fs2.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
1412
1388
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
1413
1389
  if (bullets) configs.personalLearnings = bullets;
1414
1390
  } catch {
@@ -1418,11 +1394,26 @@ function readExistingConfigs(dir) {
1418
1394
  }
1419
1395
 
1420
1396
  // src/fingerprint/code-analysis.ts
1421
- import fs5 from "fs";
1397
+ import fs3 from "fs";
1422
1398
  import path5 from "path";
1423
- import { execSync as execSync4 } from "child_process";
1399
+ import { execSync as execSync3 } from "child_process";
1424
1400
 
1425
1401
  // src/lib/sanitize.ts
1402
+ import path4 from "path";
1403
+ function sanitizePath(component) {
1404
+ const cleaned = component.replace(/\.\./g, "").replace(/[/\\]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
1405
+ if (!cleaned) {
1406
+ throw new Error(`Invalid path component: ${component}`);
1407
+ }
1408
+ return cleaned;
1409
+ }
1410
+ function assertPathWithinDir(filePath, baseDir) {
1411
+ const resolved = path4.resolve(baseDir, filePath);
1412
+ const resolvedBase = path4.resolve(baseDir);
1413
+ if (!resolved.startsWith(resolvedBase + path4.sep) && resolved !== resolvedBase) {
1414
+ throw new Error(`Path traversal detected: ${filePath} escapes ${baseDir}`);
1415
+ }
1416
+ }
1426
1417
  var KNOWN_PREFIX_PATTERNS = [
1427
1418
  // Anthropic (before generic sk- pattern)
1428
1419
  [/sk-ant-[A-Za-z0-9_-]{20,}/g, "[REDACTED]"],
@@ -1446,9 +1437,13 @@ var KNOWN_PREFIX_PATTERNS = [
1446
1437
  // Bearer tokens
1447
1438
  [/[Bb]earer\s+[A-Za-z0-9_\-.]{20,}/g, "[REDACTED]"],
1448
1439
  // PEM private keys
1449
- [/-----BEGIN[A-Z ]+KEY-----[\s\S]+?-----END[A-Z ]+KEY-----/g, "[REDACTED]"]
1440
+ [/-----BEGIN[A-Z ]+KEY-----[\s\S]+?-----END[A-Z ]+KEY-----/g, "[REDACTED]"],
1441
+ // Database connection strings with credentials
1442
+ [/(postgresql|mysql|mongodb(\+srv)?|redis|amqp):\/\/[^@\s]+:[^@\s]+@[^\s'"]+/g, "[REDACTED]"],
1443
+ // GitLab tokens
1444
+ [/glpat-[a-zA-Z0-9_-]{20,}/g, "[REDACTED]"]
1450
1445
  ];
1451
- var SENSITIVE_ASSIGNMENT = /(?:api[_-]?key|secret[_-]?key|password|token|credential|auth[_-]?token|private[_-]?key)\s*[:=]\s*['"]?([^\s'"]{8,500})['"]?/gi;
1446
+ var SENSITIVE_ASSIGNMENT = /(?:api[_-]?key|secret[_-]?key|password|token|credential|auth[_-]?token|private[_-]?key|database[_-]?url|connection[_-]?string)\s*[:=]\s*['"]?([^\s'"]{8,500})['"]?/gi;
1452
1447
  function sanitizeSecrets(text) {
1453
1448
  let result = text;
1454
1449
  for (const [pattern, replacement] of KNOWN_PREFIX_PATTERNS) {
@@ -1567,12 +1562,15 @@ var SKIP_PATTERNS = [
1567
1562
  /\.d\.ts$/,
1568
1563
  /\.generated\./,
1569
1564
  /\.snap$/,
1570
- /^\.env($|\.)/
1565
+ /^\.env($|\.)/,
1566
+ /\.(pem|key|crt|cer|pfx|p12|jks|keystore)$/,
1567
+ /^id_(rsa|ed25519|ecdsa)$/,
1568
+ /^(known_hosts|authorized_keys)$/
1571
1569
  ];
1572
1570
  var COMMENT_LINE = {
1573
- "c": /^\s*\/\//,
1574
- "h": /^\s*#/,
1575
- "x": /^\s*<!--.*-->\s*$/
1571
+ c: /^\s*\/\//,
1572
+ h: /^\s*#/,
1573
+ x: /^\s*<!--.*-->\s*$/
1576
1574
  };
1577
1575
  var EXT_COMMENT = {
1578
1576
  ".ts": "c",
@@ -1678,7 +1676,6 @@ function extractSkeleton(content, ext) {
1678
1676
  const lines = content.split("\n");
1679
1677
  const result = [];
1680
1678
  let braceDepth = 0;
1681
- let inSignature = false;
1682
1679
  let skipBody = false;
1683
1680
  if ([".py", ".pyw", ".rb"].includes(ext)) {
1684
1681
  return extractSkeletonIndentBased(lines, ext);
@@ -1704,7 +1701,9 @@ function extractSkeleton(content, ext) {
1704
1701
  }
1705
1702
  continue;
1706
1703
  }
1707
- const isFnOrClass = /^\s*(export\s+)?(default\s+)?(async\s+)?(function|class|const\s+\w+\s*=\s*(async\s*)?\(|pub\s+fn|fn|func)\s/.test(trimmed) || /^\s*(def|func|fn|pub fn|pub async fn)\s/.test(trimmed);
1704
+ const isFnOrClass = /^\s*(export\s+)?(default\s+)?(async\s+)?(function|class|const\s+\w+\s*=\s*(async\s*)?\(|pub\s+fn|fn|func)\s/.test(
1705
+ trimmed
1706
+ ) || /^\s*(def|func|fn|pub fn|pub async fn)\s/.test(trimmed);
1708
1707
  if (isFnOrClass && braceDepth === 0) {
1709
1708
  result.push(line);
1710
1709
  const opens = (line.match(/{/g) || []).length;
@@ -1732,7 +1731,7 @@ function extractSkeleton(content, ext) {
1732
1731
  }
1733
1732
  return result.join("\n");
1734
1733
  }
1735
- function extractSkeletonIndentBased(lines, ext) {
1734
+ function extractSkeletonIndentBased(lines, _ext) {
1736
1735
  const result = [];
1737
1736
  let skipIndent = -1;
1738
1737
  for (const line of lines) {
@@ -1792,7 +1791,16 @@ function buildImportCounts(files) {
1792
1791
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
1793
1792
  const imports = extractImports(content, filePath);
1794
1793
  for (const imp of imports) {
1795
- const candidates = [imp, imp + ".ts", imp + ".js", imp + ".tsx", imp + ".jsx", imp + "/index.ts", imp + "/index.js", imp + ".py"];
1794
+ const candidates = [
1795
+ imp,
1796
+ imp + ".ts",
1797
+ imp + ".js",
1798
+ imp + ".tsx",
1799
+ imp + ".jsx",
1800
+ imp + "/index.ts",
1801
+ imp + "/index.js",
1802
+ imp + ".py"
1803
+ ];
1796
1804
  for (const candidate of candidates) {
1797
1805
  const normalized = candidate.replace(/\\/g, "/");
1798
1806
  if (files.has(normalized)) {
@@ -1807,7 +1815,7 @@ function buildImportCounts(files) {
1807
1815
  function getGitFrequency(dir) {
1808
1816
  const freq = /* @__PURE__ */ new Map();
1809
1817
  try {
1810
- const output = execSync4(
1818
+ const output = execSync3(
1811
1819
  'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
1812
1820
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
1813
1821
  );
@@ -1834,7 +1842,9 @@ function structuralFingerprint(content, ext) {
1834
1842
  const bucket = Math.floor(lines.length / 10) * 10;
1835
1843
  const first = (lines[0] || "").trim().slice(0, 50);
1836
1844
  const imports = lines.filter((l) => /^\s*(import |from |require\(|use )/.test(l)).length;
1837
- const fns = lines.filter((l) => /^\s*(export\s+)?(async\s+)?(function |def |func |fn |pub fn |class )/.test(l)).length;
1845
+ const fns = lines.filter(
1846
+ (l) => /^\s*(export\s+)?(async\s+)?(function |def |func |fn |pub fn |class )/.test(l)
1847
+ ).length;
1838
1848
  return `${ext}:${bucket}:${imports}:${fns}:${first}`;
1839
1849
  }
1840
1850
  function analyzeCode(dir) {
@@ -1843,14 +1853,14 @@ function analyzeCode(dir) {
1843
1853
  let totalChars = 0;
1844
1854
  for (const relPath of allPaths) {
1845
1855
  try {
1846
- totalChars += fs5.statSync(path5.join(dir, relPath)).size;
1856
+ totalChars += fs3.statSync(path5.join(dir, relPath)).size;
1847
1857
  } catch {
1848
1858
  }
1849
1859
  }
1850
1860
  const fileContents = /* @__PURE__ */ new Map();
1851
1861
  for (const relPath of allPaths) {
1852
1862
  try {
1853
- const content = fs5.readFileSync(path5.join(dir, relPath), "utf-8");
1863
+ const content = fs3.readFileSync(path5.join(dir, relPath), "utf-8");
1854
1864
  if (content.split("\n").length <= 500) fileContents.set(relPath, content);
1855
1865
  } catch {
1856
1866
  }
@@ -1881,7 +1891,12 @@ function analyzeCode(dir) {
1881
1891
  const repFP = structuralFingerprint(rep.compressed, rep.ext);
1882
1892
  const similar = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) === repFP);
1883
1893
  const unique = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) !== repFP);
1884
- const repEntry = { path: rep.path, content: sanitizeSecrets(rep.compressed), size: rep.compressed.length, priority: rep.score };
1894
+ const repEntry = {
1895
+ path: rep.path,
1896
+ content: sanitizeSecrets(rep.compressed),
1897
+ size: rep.compressed.length,
1898
+ priority: rep.score
1899
+ };
1885
1900
  const repSize = rep.path.length + rep.compressed.length + 10;
1886
1901
  if (includedChars + repSize <= CHAR_BUDGET) {
1887
1902
  result.push(repEntry);
@@ -1893,14 +1908,24 @@ function analyzeCode(dir) {
1893
1908
  const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
1894
1909
  const summarySize = summary.length + 30;
1895
1910
  if (includedChars + summarySize <= CHAR_BUDGET) {
1896
- result.push({ path: `[similar to ${rep.path}]`, content: summary, size: summary.length, priority: rep.score });
1911
+ result.push({
1912
+ path: `[similar to ${rep.path}]`,
1913
+ content: summary,
1914
+ size: summary.length,
1915
+ priority: rep.score
1916
+ });
1897
1917
  includedChars += summarySize;
1898
1918
  }
1899
1919
  }
1900
1920
  for (const f of unique) {
1901
1921
  const skeletonSize = f.path.length + f.skeleton.length + 10;
1902
1922
  if (includedChars + skeletonSize <= CHAR_BUDGET) {
1903
- result.push({ path: f.path, content: sanitizeSecrets(f.skeleton), size: f.skeleton.length, priority: f.score });
1923
+ result.push({
1924
+ path: f.path,
1925
+ content: sanitizeSecrets(f.skeleton),
1926
+ size: f.skeleton.length,
1927
+ priority: f.score
1928
+ });
1904
1929
  includedChars += skeletonSize;
1905
1930
  }
1906
1931
  }
@@ -1910,7 +1935,12 @@ function analyzeCode(dir) {
1910
1935
  if (includedPaths.has(f.path)) continue;
1911
1936
  const skeletonSize = f.path.length + f.skeleton.length + 10;
1912
1937
  if (includedChars + skeletonSize > CHAR_BUDGET) continue;
1913
- result.push({ path: f.path, content: sanitizeSecrets(f.skeleton), size: f.skeleton.length, priority: f.score });
1938
+ result.push({
1939
+ path: f.path,
1940
+ content: sanitizeSecrets(f.skeleton),
1941
+ size: f.skeleton.length,
1942
+ priority: f.score
1943
+ });
1914
1944
  includedChars += skeletonSize;
1915
1945
  }
1916
1946
  return {
@@ -1929,7 +1959,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
1929
1959
  const fullPath = path5.join(base, rel);
1930
1960
  let entries;
1931
1961
  try {
1932
- entries = fs5.readdirSync(fullPath, { withFileTypes: true });
1962
+ entries = fs3.readdirSync(fullPath, { withFileTypes: true });
1933
1963
  } catch {
1934
1964
  return;
1935
1965
  }
@@ -2082,7 +2112,7 @@ var AnthropicProvider = class {
2082
2112
  };
2083
2113
 
2084
2114
  // src/llm/vertex.ts
2085
- import fs7 from "fs";
2115
+ import fs5 from "fs";
2086
2116
  import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
2087
2117
  import { GoogleAuth } from "google-auth-library";
2088
2118
  var VertexProvider = class {
@@ -2105,7 +2135,7 @@ var VertexProvider = class {
2105
2135
  }
2106
2136
  } else {
2107
2137
  try {
2108
- creds = JSON.parse(fs7.readFileSync(raw, "utf-8"));
2138
+ creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
2109
2139
  } catch {
2110
2140
  throw new Error(`Cannot read credentials file: ${raw}`);
2111
2141
  }
@@ -2694,7 +2724,7 @@ function isClaudeCliAvailable() {
2694
2724
 
2695
2725
  // src/llm/utils.ts
2696
2726
  function extractJson(text) {
2697
- const startIdx = text.search(/[\[{]/);
2727
+ const startIdx = text.search(/[[{]/);
2698
2728
  if (startIdx === -1) return null;
2699
2729
  let depth = 0;
2700
2730
  let inString = false;
@@ -3025,7 +3055,6 @@ var SKILL_FORMAT_RULES = `All skills follow the OpenSkills standard (agentskills
3025
3055
 
3026
3056
  Skill field requirements:
3027
3057
  - "name": kebab-case (lowercase letters, numbers, hyphens only). Becomes the directory name.
3028
- - "name" MUST NOT be any of these reserved names (they are managed by Caliber automatically): "setup-caliber", "find-skills", "save-learning". Do NOT generate skills with these names.
3029
3058
  - "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
3030
3059
  - "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
3031
3060
 
@@ -3069,7 +3098,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
3069
3098
  Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
3070
3099
  README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
3071
3100
  var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
3072
- - CLAUDE.md / AGENTS.md: MUST be under 400 lines for maximum score. Aim for 200-350 lines. Be thorough \u2014 commands, architecture overview, key conventions, data flow, and important patterns. Use bullet points and tables, not prose.
3101
+ - CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
3073
3102
 
3074
3103
  Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
3075
3104
  GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
@@ -3197,7 +3226,7 @@ Structure:
3197
3226
  5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
3198
3227
 
3199
3228
  Rules:
3200
- - Max 400 lines. Focus on actionable instructions, not documentation prose.
3229
+ - Max 150 lines. Focus on actionable instructions, not documentation prose.
3201
3230
  - Study existing code in the project context to extract the real patterns being used. A skill for "create API route" should show the exact file structure, imports, error handling, and naming that existing routes use.
3202
3231
  - Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
3203
3232
  - Never use ambiguous language. Instead of "handle errors properly", write "Wrap the DB call in try/catch. On failure, return { error: string, code: number } matching the ErrorResponse type in \`src/types.ts\`."
@@ -3251,7 +3280,7 @@ Rules:
3251
3280
  - Update the "fileDescriptions" to reflect any changes you make.
3252
3281
 
3253
3282
  Quality constraints \u2014 your changes are scored, so do not break these:
3254
- - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
3283
+ - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
3255
3284
  - Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
3256
3285
  - Do NOT add directory tree listings in code blocks.
3257
3286
  - Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
@@ -3274,30 +3303,23 @@ CONSERVATIVE UPDATE means:
3274
3303
  - NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
3275
3304
  - NEVER replace specific paths/commands with generic prose
3276
3305
 
3277
- Cross-agent sync:
3278
- - When a change affects CLAUDE.md, apply the same semantic update to AGENTS.md and copilot instructions if they exist
3279
- - Each file uses its own format and conventions \u2014 do NOT copy content verbatim between them
3280
- - The goal is consistency: all agent config files should reflect the same project state after refresh
3281
-
3282
3306
  Quality constraints (the output is scored deterministically):
3283
- - CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
3307
+ - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
3284
3308
  - Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
3285
3309
  - Every file path, command, and identifier must be in backticks
3286
3310
  - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
3287
3311
  - Preserve the existing structure (headings, bullet style, formatting)
3288
3312
 
3289
3313
  Managed content:
3290
- - All blocks between <!-- caliber:managed:* --> and <!-- /caliber:managed:* --> are managed by Caliber \u2014 copy them verbatim, do NOT modify their contents
3291
- - This includes: pre-commit, sync, and learnings blocks
3314
+ - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
3292
3315
  - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
3293
- - Preserve any references to CALIBER_LEARNINGS.md
3316
+ - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
3294
3317
 
3295
3318
  Return a JSON object with this exact shape:
3296
3319
  {
3297
3320
  "updatedDocs": {
3298
3321
  "claudeMd": "<updated content or null>",
3299
3322
  "readmeMd": "<updated content or null>",
3300
- "agentsMd": "<updated content or null>",
3301
3323
  "cursorrules": "<updated content or null>",
3302
3324
  "cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
3303
3325
  "claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
@@ -3305,12 +3327,9 @@ Return a JSON object with this exact shape:
3305
3327
  "copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
3306
3328
  },
3307
3329
  "changesSummary": "<1-2 sentence summary of what was updated and why>",
3308
- "fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
3309
3330
  "docsUpdated": ["CLAUDE.md", "README.md"]
3310
3331
  }
3311
3332
 
3312
- The "fileChanges" array MUST include one entry per file that was updated (non-null in updatedDocs). Each entry describes what specifically changed in that file \u2014 be concrete (e.g. "added auth middleware section" not "updated docs").
3313
-
3314
3333
  Respond with ONLY the JSON object, no markdown fences or extra text.`;
3315
3334
  var LEARN_SYSTEM_PROMPT = `You are an expert developer experience engineer. You analyze raw tool call events from AI coding sessions to extract reusable operational lessons that will help future LLM sessions work more effectively in this project.
3316
3335
 
@@ -3459,7 +3478,7 @@ async function detectProjectStack(fileTree, suffixCounts) {
3459
3478
  init_config();
3460
3479
 
3461
3480
  // src/fingerprint/cache.ts
3462
- import fs8 from "fs";
3481
+ import fs7 from "fs";
3463
3482
  import path7 from "path";
3464
3483
  import crypto from "crypto";
3465
3484
  import { execSync as execSync7 } from "child_process";
@@ -3504,8 +3523,8 @@ function computeTreeSignature(fileTree, dir) {
3504
3523
  function loadFingerprintCache(dir, fileTree) {
3505
3524
  const cachePath = getCachePath(dir);
3506
3525
  try {
3507
- if (!fs8.existsSync(cachePath)) return null;
3508
- const raw = fs8.readFileSync(cachePath, "utf-8");
3526
+ if (!fs7.existsSync(cachePath)) return null;
3527
+ const raw = fs7.readFileSync(cachePath, "utf-8");
3509
3528
  const cache = JSON.parse(raw);
3510
3529
  if (cache.version !== CACHE_VERSION) return null;
3511
3530
  const currentHead = getGitHead(dir);
@@ -3527,8 +3546,8 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
3527
3546
  const cachePath = getCachePath(dir);
3528
3547
  try {
3529
3548
  const cacheDir = path7.dirname(cachePath);
3530
- if (!fs8.existsSync(cacheDir)) {
3531
- fs8.mkdirSync(cacheDir, { recursive: true });
3549
+ if (!fs7.existsSync(cacheDir)) {
3550
+ fs7.mkdirSync(cacheDir, { recursive: true });
3532
3551
  }
3533
3552
  const cache = {
3534
3553
  version: CACHE_VERSION,
@@ -3540,15 +3559,15 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
3540
3559
  tools,
3541
3560
  workspaces
3542
3561
  };
3543
- fs8.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
3562
+ fs7.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
3544
3563
  } catch {
3545
3564
  }
3546
3565
  }
3547
3566
  function getDetectedWorkspaces(dir) {
3548
3567
  const cachePath = getCachePath(dir);
3549
3568
  try {
3550
- if (!fs8.existsSync(cachePath)) return [];
3551
- const raw = fs8.readFileSync(cachePath, "utf-8");
3569
+ if (!fs7.existsSync(cachePath)) return [];
3570
+ const raw = fs7.readFileSync(cachePath, "utf-8");
3552
3571
  const cache = JSON.parse(raw);
3553
3572
  return cache.workspaces ?? [];
3554
3573
  } catch {
@@ -3601,8 +3620,8 @@ async function collectFingerprint(dir) {
3601
3620
  function readPackageName(dir) {
3602
3621
  try {
3603
3622
  const pkgPath = path8.join(dir, "package.json");
3604
- if (!fs9.existsSync(pkgPath)) return void 0;
3605
- const pkg3 = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
3623
+ if (!fs8.existsSync(pkgPath)) return void 0;
3624
+ const pkg3 = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
3606
3625
  return pkg3.name;
3607
3626
  } catch {
3608
3627
  return void 0;
@@ -3632,22 +3651,22 @@ async function enrichWithLLM(fingerprint) {
3632
3651
  }
3633
3652
 
3634
3653
  // src/scanner/index.ts
3635
- import fs10 from "fs";
3654
+ import fs9 from "fs";
3636
3655
  import path9 from "path";
3637
3656
  import crypto2 from "crypto";
3638
3657
  import os4 from "os";
3639
3658
  function detectPlatforms() {
3640
3659
  const home = os4.homedir();
3641
3660
  return {
3642
- claude: fs10.existsSync(path9.join(home, ".claude")),
3643
- cursor: fs10.existsSync(getCursorConfigDir()),
3644
- codex: fs10.existsSync(path9.join(home, ".codex"))
3661
+ claude: fs9.existsSync(path9.join(home, ".claude")),
3662
+ cursor: fs9.existsSync(getCursorConfigDir()),
3663
+ codex: fs9.existsSync(path9.join(home, ".codex"))
3645
3664
  };
3646
3665
  }
3647
3666
  function scanLocalState(dir) {
3648
3667
  const items = [];
3649
3668
  const claudeMdPath = path9.join(dir, "CLAUDE.md");
3650
- if (fs10.existsSync(claudeMdPath)) {
3669
+ if (fs9.existsSync(claudeMdPath)) {
3651
3670
  items.push({
3652
3671
  type: "rule",
3653
3672
  platform: "claude",
@@ -3657,8 +3676,8 @@ function scanLocalState(dir) {
3657
3676
  });
3658
3677
  }
3659
3678
  const skillsDir = path9.join(dir, ".claude", "skills");
3660
- if (fs10.existsSync(skillsDir)) {
3661
- for (const file of fs10.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
3679
+ if (fs9.existsSync(skillsDir)) {
3680
+ for (const file of fs9.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
3662
3681
  const filePath = path9.join(skillsDir, file);
3663
3682
  items.push({
3664
3683
  type: "skill",
@@ -3670,9 +3689,9 @@ function scanLocalState(dir) {
3670
3689
  }
3671
3690
  }
3672
3691
  const mcpJsonPath = path9.join(dir, ".mcp.json");
3673
- if (fs10.existsSync(mcpJsonPath)) {
3692
+ if (fs9.existsSync(mcpJsonPath)) {
3674
3693
  try {
3675
- const mcpJson = JSON.parse(fs10.readFileSync(mcpJsonPath, "utf-8"));
3694
+ const mcpJson = JSON.parse(fs9.readFileSync(mcpJsonPath, "utf-8"));
3676
3695
  if (mcpJson.mcpServers) {
3677
3696
  for (const name of Object.keys(mcpJson.mcpServers)) {
3678
3697
  items.push({
@@ -3689,7 +3708,7 @@ function scanLocalState(dir) {
3689
3708
  }
3690
3709
  }
3691
3710
  const agentsMdPath = path9.join(dir, "AGENTS.md");
3692
- if (fs10.existsSync(agentsMdPath)) {
3711
+ if (fs9.existsSync(agentsMdPath)) {
3693
3712
  items.push({
3694
3713
  type: "rule",
3695
3714
  platform: "codex",
@@ -3699,11 +3718,11 @@ function scanLocalState(dir) {
3699
3718
  });
3700
3719
  }
3701
3720
  const codexSkillsDir = path9.join(dir, ".agents", "skills");
3702
- if (fs10.existsSync(codexSkillsDir)) {
3721
+ if (fs9.existsSync(codexSkillsDir)) {
3703
3722
  try {
3704
- for (const name of fs10.readdirSync(codexSkillsDir)) {
3723
+ for (const name of fs9.readdirSync(codexSkillsDir)) {
3705
3724
  const skillFile = path9.join(codexSkillsDir, name, "SKILL.md");
3706
- if (fs10.existsSync(skillFile)) {
3725
+ if (fs9.existsSync(skillFile)) {
3707
3726
  items.push({
3708
3727
  type: "skill",
3709
3728
  platform: "codex",
@@ -3718,7 +3737,7 @@ function scanLocalState(dir) {
3718
3737
  }
3719
3738
  }
3720
3739
  const cursorrulesPath = path9.join(dir, ".cursorrules");
3721
- if (fs10.existsSync(cursorrulesPath)) {
3740
+ if (fs9.existsSync(cursorrulesPath)) {
3722
3741
  items.push({
3723
3742
  type: "rule",
3724
3743
  platform: "cursor",
@@ -3728,8 +3747,8 @@ function scanLocalState(dir) {
3728
3747
  });
3729
3748
  }
3730
3749
  const cursorRulesDir = path9.join(dir, ".cursor", "rules");
3731
- if (fs10.existsSync(cursorRulesDir)) {
3732
- for (const file of fs10.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
3750
+ if (fs9.existsSync(cursorRulesDir)) {
3751
+ for (const file of fs9.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
3733
3752
  const filePath = path9.join(cursorRulesDir, file);
3734
3753
  items.push({
3735
3754
  type: "rule",
@@ -3741,11 +3760,11 @@ function scanLocalState(dir) {
3741
3760
  }
3742
3761
  }
3743
3762
  const cursorSkillsDir = path9.join(dir, ".cursor", "skills");
3744
- if (fs10.existsSync(cursorSkillsDir)) {
3763
+ if (fs9.existsSync(cursorSkillsDir)) {
3745
3764
  try {
3746
- for (const name of fs10.readdirSync(cursorSkillsDir)) {
3765
+ for (const name of fs9.readdirSync(cursorSkillsDir)) {
3747
3766
  const skillFile = path9.join(cursorSkillsDir, name, "SKILL.md");
3748
- if (fs10.existsSync(skillFile)) {
3767
+ if (fs9.existsSync(skillFile)) {
3749
3768
  items.push({
3750
3769
  type: "skill",
3751
3770
  platform: "cursor",
@@ -3760,9 +3779,9 @@ function scanLocalState(dir) {
3760
3779
  }
3761
3780
  }
3762
3781
  const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
3763
- if (fs10.existsSync(cursorMcpPath)) {
3782
+ if (fs9.existsSync(cursorMcpPath)) {
3764
3783
  try {
3765
- const mcpJson = JSON.parse(fs10.readFileSync(cursorMcpPath, "utf-8"));
3784
+ const mcpJson = JSON.parse(fs9.readFileSync(cursorMcpPath, "utf-8"));
3766
3785
  if (mcpJson.mcpServers) {
3767
3786
  for (const name of Object.keys(mcpJson.mcpServers)) {
3768
3787
  items.push({
@@ -3781,7 +3800,7 @@ function scanLocalState(dir) {
3781
3800
  return items;
3782
3801
  }
3783
3802
  function hashFile(filePath) {
3784
- const text = fs10.readFileSync(filePath, "utf-8");
3803
+ const text = fs9.readFileSync(filePath, "utf-8");
3785
3804
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
3786
3805
  }
3787
3806
  function hashJson(obj) {
@@ -3804,7 +3823,7 @@ function getCursorConfigDir() {
3804
3823
 
3805
3824
  // src/lib/hooks.ts
3806
3825
  init_resolve_caliber();
3807
- import fs11 from "fs";
3826
+ import fs10 from "fs";
3808
3827
  import path10 from "path";
3809
3828
  import { execSync as execSync8 } from "child_process";
3810
3829
  var SETTINGS_PATH = path10.join(".claude", "settings.json");
@@ -3814,17 +3833,17 @@ function getHookCommand() {
3814
3833
  return `${resolveCaliber()} ${REFRESH_TAIL}`;
3815
3834
  }
3816
3835
  function readSettings() {
3817
- if (!fs11.existsSync(SETTINGS_PATH)) return {};
3836
+ if (!fs10.existsSync(SETTINGS_PATH)) return {};
3818
3837
  try {
3819
- return JSON.parse(fs11.readFileSync(SETTINGS_PATH, "utf-8"));
3838
+ return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
3820
3839
  } catch {
3821
3840
  return {};
3822
3841
  }
3823
3842
  }
3824
3843
  function writeSettings(settings) {
3825
3844
  const dir = path10.dirname(SETTINGS_PATH);
3826
- if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
3827
- fs11.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3845
+ if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
3846
+ fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3828
3847
  }
3829
3848
  function findHookIndex(sessionEnd) {
3830
3849
  return sessionEnd.findIndex(
@@ -3883,7 +3902,7 @@ if ${guard}; then
3883
3902
  echo "\\033[2mcaliber: refreshing docs...\\033[0m"
3884
3903
  ${invoke} refresh 2>/dev/null || true
3885
3904
  ${invoke} learn finalize 2>/dev/null || true
3886
- git diff --name-only -- CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
3905
+ git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
3887
3906
  fi
3888
3907
  ${PRECOMMIT_END}`;
3889
3908
  }
@@ -3901,8 +3920,8 @@ function getPreCommitPath() {
3901
3920
  }
3902
3921
  function isPreCommitHookInstalled() {
3903
3922
  const hookPath = getPreCommitPath();
3904
- if (!hookPath || !fs11.existsSync(hookPath)) return false;
3905
- const content = fs11.readFileSync(hookPath, "utf-8");
3923
+ if (!hookPath || !fs10.existsSync(hookPath)) return false;
3924
+ const content = fs10.readFileSync(hookPath, "utf-8");
3906
3925
  return content.includes(PRECOMMIT_START);
3907
3926
  }
3908
3927
  function installPreCommitHook() {
@@ -3912,40 +3931,40 @@ function installPreCommitHook() {
3912
3931
  const hookPath = getPreCommitPath();
3913
3932
  if (!hookPath) return { installed: false, alreadyInstalled: false };
3914
3933
  const hooksDir = path10.dirname(hookPath);
3915
- if (!fs11.existsSync(hooksDir)) fs11.mkdirSync(hooksDir, { recursive: true });
3934
+ if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
3916
3935
  let content = "";
3917
- if (fs11.existsSync(hookPath)) {
3918
- content = fs11.readFileSync(hookPath, "utf-8");
3936
+ if (fs10.existsSync(hookPath)) {
3937
+ content = fs10.readFileSync(hookPath, "utf-8");
3919
3938
  if (!content.endsWith("\n")) content += "\n";
3920
3939
  content += "\n" + getPrecommitBlock() + "\n";
3921
3940
  } else {
3922
3941
  content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
3923
3942
  }
3924
- fs11.writeFileSync(hookPath, content);
3925
- fs11.chmodSync(hookPath, 493);
3943
+ fs10.writeFileSync(hookPath, content);
3944
+ fs10.chmodSync(hookPath, 493);
3926
3945
  return { installed: true, alreadyInstalled: false };
3927
3946
  }
3928
3947
  function removePreCommitHook() {
3929
3948
  const hookPath = getPreCommitPath();
3930
- if (!hookPath || !fs11.existsSync(hookPath)) {
3949
+ if (!hookPath || !fs10.existsSync(hookPath)) {
3931
3950
  return { removed: false, notFound: true };
3932
3951
  }
3933
- let content = fs11.readFileSync(hookPath, "utf-8");
3952
+ let content = fs10.readFileSync(hookPath, "utf-8");
3934
3953
  if (!content.includes(PRECOMMIT_START)) {
3935
3954
  return { removed: false, notFound: true };
3936
3955
  }
3937
3956
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
3938
3957
  content = content.replace(regex, "\n");
3939
3958
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
3940
- fs11.unlinkSync(hookPath);
3959
+ fs10.unlinkSync(hookPath);
3941
3960
  } else {
3942
- fs11.writeFileSync(hookPath, content);
3961
+ fs10.writeFileSync(hookPath, content);
3943
3962
  }
3944
3963
  return { removed: true, notFound: false };
3945
3964
  }
3946
3965
 
3947
3966
  // src/fingerprint/sources.ts
3948
- import fs12 from "fs";
3967
+ import fs11 from "fs";
3949
3968
  import path11 from "path";
3950
3969
 
3951
3970
  // src/scoring/utils.ts
@@ -4013,12 +4032,14 @@ function isGitRepo2(dir) {
4013
4032
  function checkGitIgnored(dir, paths) {
4014
4033
  if (paths.length === 0) return /* @__PURE__ */ new Set();
4015
4034
  try {
4016
- const result = execFileSync(
4017
- "git",
4018
- ["check-ignore", ...paths],
4019
- { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
4035
+ const result = execFileSync("git", ["check-ignore", ...paths], {
4036
+ cwd: dir,
4037
+ encoding: "utf-8",
4038
+ stdio: ["pipe", "pipe", "pipe"]
4039
+ });
4040
+ return new Set(
4041
+ result.split("\n").map((l) => l.trim()).filter(Boolean)
4020
4042
  );
4021
- return new Set(result.split("\n").map((l) => l.trim()).filter(Boolean));
4022
4043
  } catch (err) {
4023
4044
  if (err && typeof err === "object" && "status" in err && err.status === 1) {
4024
4045
  return /* @__PURE__ */ new Set();
@@ -4042,7 +4063,10 @@ function collectProjectStructure(dir, maxDepth = 2) {
4042
4063
  if (name.startsWith(".") && IGNORED_DIRS.has(name)) continue;
4043
4064
  dirEntries.push({ name, rel: relative(dir, join(currentDir, name)) });
4044
4065
  }
4045
- const gitIgnored = useGit ? checkGitIgnored(dir, dirEntries.map((d) => d.rel)) : null;
4066
+ const gitIgnored = useGit ? checkGitIgnored(
4067
+ dir,
4068
+ dirEntries.map((d) => d.rel)
4069
+ ) : null;
4046
4070
  for (const entry of entries) {
4047
4071
  const name = entry.name;
4048
4072
  if (name.startsWith(".") && IGNORED_DIRS.has(name)) continue;
@@ -4225,7 +4249,9 @@ function countTreeLines(content) {
4225
4249
  return count;
4226
4250
  }
4227
4251
  function calculateDuplicatePercent(content1, content2) {
4228
- const lines1 = new Set(content1.split("\n").map((l) => l.trim()).filter((l) => l.length > 10));
4252
+ const lines1 = new Set(
4253
+ content1.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
4254
+ );
4229
4255
  const lines2 = content2.split("\n").map((l) => l.trim()).filter((l) => l.length > 10);
4230
4256
  const overlapping = lines2.filter((l) => lines1.has(l)).length;
4231
4257
  return lines2.length > 0 ? Math.round(overlapping / lines2.length * 100) : 0;
@@ -4244,7 +4270,9 @@ function isEntryMentioned(entry, contentLower) {
4244
4270
  if (lastSegment && lastSegment.length > 3) variants.push(lastSegment);
4245
4271
  return variants.some((v) => {
4246
4272
  const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4247
- return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(contentLower);
4273
+ return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(
4274
+ contentLower
4275
+ );
4248
4276
  });
4249
4277
  }
4250
4278
  function classifyLine(line, inCodeBlock) {
@@ -4255,7 +4283,8 @@ function classifyLine(line, inCodeBlock) {
4255
4283
  if (/`[^`]+`/.test(trimmed)) return "concrete";
4256
4284
  if (/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\.[a-zA-Z]{1,5}/.test(trimmed)) return "concrete";
4257
4285
  if (/[a-zA-Z0-9_]{4,}\/[a-zA-Z0-9_.-]/.test(trimmed)) return "concrete";
4258
- if (/\b[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}\b/.test(trimmed) && !/\b(e\.g|i\.e|vs|etc)\b/i.test(trimmed)) return "concrete";
4286
+ if (/\b[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}\b/.test(trimmed) && !/\b(e\.g|i\.e|vs|etc)\b/i.test(trimmed))
4287
+ return "concrete";
4259
4288
  return "abstract";
4260
4289
  }
4261
4290
 
@@ -4271,12 +4300,12 @@ function loadSourcesConfig(dir) {
4271
4300
  try {
4272
4301
  const parsed = JSON.parse(content);
4273
4302
  if (!Array.isArray(parsed.sources)) {
4274
- console.warn("Warning: .caliber/sources.json is malformed (missing sources array), skipping sources");
4303
+ console.warn(
4304
+ "Warning: .caliber/sources.json is malformed (missing sources array), skipping sources"
4305
+ );
4275
4306
  return [];
4276
4307
  }
4277
- return parsed.sources.filter(
4278
- (s) => s.type && (s.path || s.url)
4279
- );
4308
+ return parsed.sources.filter((s) => s.type && (s.path || s.url));
4280
4309
  } catch {
4281
4310
  console.warn("Warning: .caliber/sources.json is malformed, skipping sources");
4282
4311
  return [];
@@ -4284,15 +4313,15 @@ function loadSourcesConfig(dir) {
4284
4313
  }
4285
4314
  function writeSourcesConfig(dir, sources2) {
4286
4315
  const configDir = path11.join(dir, ".caliber");
4287
- if (!fs12.existsSync(configDir)) {
4288
- fs12.mkdirSync(configDir, { recursive: true });
4316
+ if (!fs11.existsSync(configDir)) {
4317
+ fs11.mkdirSync(configDir, { recursive: true });
4289
4318
  }
4290
4319
  const configPath = path11.join(configDir, "sources.json");
4291
- fs12.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
4320
+ fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
4292
4321
  }
4293
4322
  function detectSourceType(absPath) {
4294
4323
  try {
4295
- return fs12.statSync(absPath).isDirectory() ? "repo" : "file";
4324
+ return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
4296
4325
  } catch {
4297
4326
  return "file";
4298
4327
  }
@@ -4336,7 +4365,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
4336
4365
  for (const [absPath, resolved] of seen) {
4337
4366
  let stat;
4338
4367
  try {
4339
- stat = fs12.statSync(absPath);
4368
+ stat = fs11.statSync(absPath);
4340
4369
  } catch {
4341
4370
  console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
4342
4371
  continue;
@@ -4385,13 +4414,13 @@ function collectSourceSummary(resolved, projectDir) {
4385
4414
  }
4386
4415
  return collectRepoSummary(resolved, projectDir);
4387
4416
  }
4388
- function collectRepoSummary(resolved, projectDir) {
4417
+ function collectRepoSummary(resolved, _projectDir) {
4389
4418
  const { config, origin, absPath } = resolved;
4390
4419
  const packageName = readPackageName(absPath);
4391
4420
  let topLevelDirs;
4392
4421
  let keyFiles;
4393
4422
  try {
4394
- const entries = fs12.readdirSync(absPath, { withFileTypes: true });
4423
+ const entries = fs11.readdirSync(absPath, { withFileTypes: true });
4395
4424
  topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
4396
4425
  keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
4397
4426
  } catch {
@@ -4415,7 +4444,7 @@ function collectRepoSummary(resolved, projectDir) {
4415
4444
  packageName
4416
4445
  };
4417
4446
  }
4418
- function collectFileSummary(resolved, projectDir) {
4447
+ function collectFileSummary(resolved, _projectDir) {
4419
4448
  const { config, origin, absPath } = resolved;
4420
4449
  const content = readFileOrNull(absPath);
4421
4450
  return {
@@ -4463,15 +4492,15 @@ init_config();
4463
4492
  // src/utils/dependencies.ts
4464
4493
  import { readFileSync as readFileSync2 } from "fs";
4465
4494
  import { join as join2 } from "path";
4466
- function readFileOrNull2(path41) {
4495
+ function readFileOrNull2(path42) {
4467
4496
  try {
4468
- return readFileSync2(path41, "utf-8");
4497
+ return readFileSync2(path42, "utf-8");
4469
4498
  } catch {
4470
4499
  return null;
4471
4500
  }
4472
4501
  }
4473
- function readJsonOrNull(path41) {
4474
- const content = readFileOrNull2(path41);
4502
+ function readJsonOrNull(path42) {
4503
+ const content = readFileOrNull2(path42);
4475
4504
  if (!content) return null;
4476
4505
  try {
4477
4506
  return JSON.parse(content);
@@ -4513,18 +4542,24 @@ function extractNpmDeps(dir) {
4513
4542
  /^prettier-/,
4514
4543
  /^@typescript-eslint\//
4515
4544
  ];
4516
- return Object.keys(deps).filter((d) => !trivial.has(d) && !d.startsWith("@types/") && !trivialPatterns.some((p) => p.test(d))).slice(0, 30);
4545
+ return Object.keys(deps).filter(
4546
+ (d) => !trivial.has(d) && !d.startsWith("@types/") && !trivialPatterns.some((p) => p.test(d))
4547
+ ).slice(0, 30);
4517
4548
  }
4518
4549
  function extractPythonDeps(dir) {
4519
4550
  const reqTxt = readFileOrNull2(join2(dir, "requirements.txt"));
4520
4551
  if (reqTxt) {
4521
- return reqTxt.split("\n").map((l) => l.trim().split(/[=<>!~\[]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 30);
4552
+ return reqTxt.split("\n").map(
4553
+ (l) => l.trim().split(/[=<>!~[]/)[0].trim()
4554
+ ).filter((l) => l && !l.startsWith("#")).slice(0, 30);
4522
4555
  }
4523
4556
  const pyproject = readFileOrNull2(join2(dir, "pyproject.toml"));
4524
4557
  if (pyproject) {
4525
4558
  const depMatch = pyproject.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
4526
4559
  if (depMatch) {
4527
- return depMatch[1].split("\n").map((l) => l.trim().replace(/["',]/g, "").split(/[=<>!~\[]/)[0].trim()).filter((l) => l.length > 0).slice(0, 30);
4560
+ return depMatch[1].split("\n").map(
4561
+ (l) => l.trim().replace(/["',]/g, "").split(/[=<>!~[]/)[0].trim()
4562
+ ).filter((l) => l.length > 0).slice(0, 30);
4528
4563
  }
4529
4564
  }
4530
4565
  return [];
@@ -5062,16 +5097,16 @@ import fs19 from "fs";
5062
5097
 
5063
5098
  // src/writers/claude/index.ts
5064
5099
  init_pre_commit_block();
5065
- import fs13 from "fs";
5100
+ import fs12 from "fs";
5066
5101
  import path12 from "path";
5067
5102
  function writeClaudeConfig(config) {
5068
5103
  const written = [];
5069
- fs13.writeFileSync("CLAUDE.md", appendManagedBlocks(config.claudeMd, "claude"));
5104
+ fs12.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
5070
5105
  written.push("CLAUDE.md");
5071
5106
  if (config.skills?.length) {
5072
5107
  for (const skill of config.skills) {
5073
5108
  const skillDir = path12.join(".claude", "skills", skill.name);
5074
- if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
5109
+ if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
5075
5110
  const skillPath = path12.join(skillDir, "SKILL.md");
5076
5111
  const frontmatter = [
5077
5112
  "---",
@@ -5080,21 +5115,21 @@ function writeClaudeConfig(config) {
5080
5115
  "---",
5081
5116
  ""
5082
5117
  ].join("\n");
5083
- fs13.writeFileSync(skillPath, frontmatter + skill.content);
5118
+ fs12.writeFileSync(skillPath, frontmatter + skill.content);
5084
5119
  written.push(skillPath);
5085
5120
  }
5086
5121
  }
5087
5122
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
5088
5123
  let existingServers = {};
5089
5124
  try {
5090
- if (fs13.existsSync(".mcp.json")) {
5091
- const existing = JSON.parse(fs13.readFileSync(".mcp.json", "utf-8"));
5125
+ if (fs12.existsSync(".mcp.json")) {
5126
+ const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
5092
5127
  if (existing.mcpServers) existingServers = existing.mcpServers;
5093
5128
  }
5094
5129
  } catch {
5095
5130
  }
5096
5131
  const mergedServers = { ...existingServers, ...config.mcpServers };
5097
- fs13.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
5132
+ fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
5098
5133
  written.push(".mcp.json");
5099
5134
  }
5100
5135
  return written;
@@ -5102,30 +5137,28 @@ function writeClaudeConfig(config) {
5102
5137
 
5103
5138
  // src/writers/cursor/index.ts
5104
5139
  init_pre_commit_block();
5105
- import fs14 from "fs";
5140
+ import fs13 from "fs";
5106
5141
  import path13 from "path";
5107
5142
  function writeCursorConfig(config) {
5108
5143
  const written = [];
5109
5144
  if (config.cursorrules) {
5110
- fs14.writeFileSync(".cursorrules", config.cursorrules);
5145
+ fs13.writeFileSync(".cursorrules", config.cursorrules);
5111
5146
  written.push(".cursorrules");
5112
5147
  }
5113
5148
  const preCommitRule = getCursorPreCommitRule();
5114
5149
  const learningsRule = getCursorLearningsRule();
5115
- const syncRule = getCursorSyncRule();
5116
- const setupRule = getCursorSetupRule();
5117
- const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule, setupRule];
5150
+ const allRules = [...config.rules || [], preCommitRule, learningsRule];
5118
5151
  const rulesDir = path13.join(".cursor", "rules");
5119
- if (!fs14.existsSync(rulesDir)) fs14.mkdirSync(rulesDir, { recursive: true });
5152
+ if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
5120
5153
  for (const rule of allRules) {
5121
5154
  const rulePath = path13.join(rulesDir, rule.filename);
5122
- fs14.writeFileSync(rulePath, rule.content);
5155
+ fs13.writeFileSync(rulePath, rule.content);
5123
5156
  written.push(rulePath);
5124
5157
  }
5125
5158
  if (config.skills?.length) {
5126
5159
  for (const skill of config.skills) {
5127
5160
  const skillDir = path13.join(".cursor", "skills", skill.name);
5128
- if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
5161
+ if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
5129
5162
  const skillPath = path13.join(skillDir, "SKILL.md");
5130
5163
  const frontmatter = [
5131
5164
  "---",
@@ -5134,24 +5167,24 @@ function writeCursorConfig(config) {
5134
5167
  "---",
5135
5168
  ""
5136
5169
  ].join("\n");
5137
- fs14.writeFileSync(skillPath, frontmatter + skill.content);
5170
+ fs13.writeFileSync(skillPath, frontmatter + skill.content);
5138
5171
  written.push(skillPath);
5139
5172
  }
5140
5173
  }
5141
5174
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
5142
5175
  const cursorDir = ".cursor";
5143
- if (!fs14.existsSync(cursorDir)) fs14.mkdirSync(cursorDir, { recursive: true });
5176
+ if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
5144
5177
  const mcpPath = path13.join(cursorDir, "mcp.json");
5145
5178
  let existingServers = {};
5146
5179
  try {
5147
- if (fs14.existsSync(mcpPath)) {
5148
- const existing = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
5180
+ if (fs13.existsSync(mcpPath)) {
5181
+ const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
5149
5182
  if (existing.mcpServers) existingServers = existing.mcpServers;
5150
5183
  }
5151
5184
  } catch {
5152
5185
  }
5153
5186
  const mergedServers = { ...existingServers, ...config.mcpServers };
5154
- fs14.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
5187
+ fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
5155
5188
  written.push(mcpPath);
5156
5189
  }
5157
5190
  return written;
@@ -5159,16 +5192,16 @@ function writeCursorConfig(config) {
5159
5192
 
5160
5193
  // src/writers/codex/index.ts
5161
5194
  init_pre_commit_block();
5162
- import fs15 from "fs";
5195
+ import fs14 from "fs";
5163
5196
  import path14 from "path";
5164
5197
  function writeCodexConfig(config) {
5165
5198
  const written = [];
5166
- fs15.writeFileSync("AGENTS.md", appendManagedBlocks(config.agentsMd, "codex"));
5199
+ fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5167
5200
  written.push("AGENTS.md");
5168
5201
  if (config.skills?.length) {
5169
5202
  for (const skill of config.skills) {
5170
5203
  const skillDir = path14.join(".agents", "skills", skill.name);
5171
- if (!fs15.existsSync(skillDir)) fs15.mkdirSync(skillDir, { recursive: true });
5204
+ if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
5172
5205
  const skillPath = path14.join(skillDir, "SKILL.md");
5173
5206
  const frontmatter = [
5174
5207
  "---",
@@ -5177,7 +5210,7 @@ function writeCodexConfig(config) {
5177
5210
  "---",
5178
5211
  ""
5179
5212
  ].join("\n");
5180
- fs15.writeFileSync(skillPath, frontmatter + skill.content);
5213
+ fs14.writeFileSync(skillPath, frontmatter + skill.content);
5181
5214
  written.push(skillPath);
5182
5215
  }
5183
5216
  }
@@ -5186,20 +5219,20 @@ function writeCodexConfig(config) {
5186
5219
 
5187
5220
  // src/writers/github-copilot/index.ts
5188
5221
  init_pre_commit_block();
5189
- import fs16 from "fs";
5222
+ import fs15 from "fs";
5190
5223
  import path15 from "path";
5191
5224
  function writeGithubCopilotConfig(config) {
5192
5225
  const written = [];
5193
5226
  if (config.instructions) {
5194
- fs16.mkdirSync(".github", { recursive: true });
5195
- fs16.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendManagedBlocks(config.instructions, "copilot"));
5227
+ fs15.mkdirSync(".github", { recursive: true });
5228
+ fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
5196
5229
  written.push(".github/copilot-instructions.md");
5197
5230
  }
5198
5231
  if (config.instructionFiles?.length) {
5199
5232
  const instructionsDir = path15.join(".github", "instructions");
5200
- fs16.mkdirSync(instructionsDir, { recursive: true });
5233
+ fs15.mkdirSync(instructionsDir, { recursive: true });
5201
5234
  for (const file of config.instructionFiles) {
5202
- fs16.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
5235
+ fs15.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
5203
5236
  written.push(`.github/instructions/${file.filename}`);
5204
5237
  }
5205
5238
  }
@@ -5207,30 +5240,30 @@ function writeGithubCopilotConfig(config) {
5207
5240
  }
5208
5241
 
5209
5242
  // src/writers/backup.ts
5210
- import fs17 from "fs";
5243
+ import fs16 from "fs";
5211
5244
  import path16 from "path";
5212
5245
  function createBackup(files) {
5213
5246
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5214
5247
  const backupDir = path16.join(BACKUPS_DIR, timestamp);
5215
5248
  for (const file of files) {
5216
- if (!fs17.existsSync(file)) continue;
5249
+ if (!fs16.existsSync(file)) continue;
5217
5250
  const dest = path16.join(backupDir, file);
5218
5251
  const destDir = path16.dirname(dest);
5219
- if (!fs17.existsSync(destDir)) {
5220
- fs17.mkdirSync(destDir, { recursive: true });
5252
+ if (!fs16.existsSync(destDir)) {
5253
+ fs16.mkdirSync(destDir, { recursive: true });
5221
5254
  }
5222
- fs17.copyFileSync(file, dest);
5255
+ fs16.copyFileSync(file, dest);
5223
5256
  }
5224
5257
  return backupDir;
5225
5258
  }
5226
5259
  function restoreBackup(backupDir, file) {
5227
5260
  const backupFile = path16.join(backupDir, file);
5228
- if (!fs17.existsSync(backupFile)) return false;
5261
+ if (!fs16.existsSync(backupFile)) return false;
5229
5262
  const destDir = path16.dirname(file);
5230
- if (!fs17.existsSync(destDir)) {
5231
- fs17.mkdirSync(destDir, { recursive: true });
5263
+ if (!fs16.existsSync(destDir)) {
5264
+ fs16.mkdirSync(destDir, { recursive: true });
5232
5265
  }
5233
- fs17.copyFileSync(backupFile, file);
5266
+ fs16.copyFileSync(backupFile, file);
5234
5267
  return true;
5235
5268
  }
5236
5269
 
@@ -5263,10 +5296,7 @@ function fileChecksum(filePath) {
5263
5296
  function writeSetup(setup) {
5264
5297
  const filesToWrite = getFilesToWrite(setup);
5265
5298
  const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
5266
- const existingFiles = [
5267
- ...filesToWrite.filter((f) => fs19.existsSync(f)),
5268
- ...filesToDelete
5269
- ];
5299
+ const existingFiles = [...filesToWrite.filter((f) => fs19.existsSync(f)), ...filesToDelete];
5270
5300
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
5271
5301
  const written = [];
5272
5302
  if (setup.targetAgent.includes("claude") && setup.claude) {
@@ -5359,7 +5389,8 @@ function getFilesToWrite(setup) {
5359
5389
  if (setup.targetAgent.includes("github-copilot") && setup.copilot) {
5360
5390
  if (setup.copilot.instructions) files.push(".github/copilot-instructions.md");
5361
5391
  if (setup.copilot.instructionFiles) {
5362
- for (const f of setup.copilot.instructionFiles) files.push(`.github/instructions/${f.filename}`);
5392
+ for (const f of setup.copilot.instructionFiles)
5393
+ files.push(`.github/instructions/${f.filename}`);
5363
5394
  }
5364
5395
  }
5365
5396
  return files;
@@ -5378,10 +5409,10 @@ function ensureGitignore() {
5378
5409
 
5379
5410
  // src/writers/staging.ts
5380
5411
  import fs20 from "fs";
5381
- import path17 from "path";
5382
- var STAGED_DIR = path17.join(CALIBER_DIR, "staged");
5383
- var PROPOSED_DIR = path17.join(STAGED_DIR, "proposed");
5384
- var CURRENT_DIR = path17.join(STAGED_DIR, "current");
5412
+ import path18 from "path";
5413
+ var STAGED_DIR = path18.join(CALIBER_DIR, "staged");
5414
+ var PROPOSED_DIR = path18.join(STAGED_DIR, "proposed");
5415
+ var CURRENT_DIR = path18.join(STAGED_DIR, "current");
5385
5416
  function normalizeContent(content) {
5386
5417
  return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
5387
5418
  }
@@ -5391,22 +5422,29 @@ function stageFiles(files, projectDir) {
5391
5422
  let modifiedFiles = 0;
5392
5423
  const stagedFiles = [];
5393
5424
  for (const file of files) {
5394
- const originalPath = path17.join(projectDir, file.path);
5425
+ assertPathWithinDir(file.path, projectDir);
5426
+ const originalPath = path18.join(projectDir, file.path);
5395
5427
  if (fs20.existsSync(originalPath)) {
5396
5428
  const existing = fs20.readFileSync(originalPath, "utf-8");
5397
5429
  if (normalizeContent(existing) === normalizeContent(file.content)) {
5398
5430
  continue;
5399
5431
  }
5400
5432
  }
5401
- const proposedPath = path17.join(PROPOSED_DIR, file.path);
5402
- fs20.mkdirSync(path17.dirname(proposedPath), { recursive: true });
5433
+ const proposedPath = path18.join(PROPOSED_DIR, file.path);
5434
+ fs20.mkdirSync(path18.dirname(proposedPath), { recursive: true });
5403
5435
  fs20.writeFileSync(proposedPath, file.content);
5404
5436
  if (fs20.existsSync(originalPath)) {
5405
- const currentPath = path17.join(CURRENT_DIR, file.path);
5406
- fs20.mkdirSync(path17.dirname(currentPath), { recursive: true });
5437
+ const currentPath = path18.join(CURRENT_DIR, file.path);
5438
+ fs20.mkdirSync(path18.dirname(currentPath), { recursive: true });
5407
5439
  fs20.copyFileSync(originalPath, currentPath);
5408
5440
  modifiedFiles++;
5409
- stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
5441
+ stagedFiles.push({
5442
+ relativePath: file.path,
5443
+ proposedPath,
5444
+ currentPath,
5445
+ originalPath,
5446
+ isNew: false
5447
+ });
5410
5448
  } else {
5411
5449
  newFiles++;
5412
5450
  stagedFiles.push({ relativePath: file.path, proposedPath, isNew: true });
@@ -5433,11 +5471,17 @@ function collectSetupFiles(setup, targetAgent) {
5433
5471
  const skills = claude.skills;
5434
5472
  if (Array.isArray(skills)) {
5435
5473
  for (const skill of skills) {
5436
- files.push({ path: `.claude/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
5474
+ files.push({
5475
+ path: `.claude/skills/${sanitizePath(skill.name)}/SKILL.md`,
5476
+ content: buildSkillContent(skill)
5477
+ });
5437
5478
  }
5438
5479
  }
5439
5480
  for (const builtin of BUILTIN_SKILLS) {
5440
- files.push({ path: `.claude/skills/${builtin.name}/SKILL.md`, content: buildSkillContent(builtin) });
5481
+ files.push({
5482
+ path: `.claude/skills/${builtin.name}/SKILL.md`,
5483
+ content: buildSkillContent(builtin)
5484
+ });
5441
5485
  }
5442
5486
  }
5443
5487
  if (codex) {
@@ -5445,38 +5489,58 @@ function collectSetupFiles(setup, targetAgent) {
5445
5489
  const codexSkills = codex.skills;
5446
5490
  if (Array.isArray(codexSkills)) {
5447
5491
  for (const skill of codexSkills) {
5448
- files.push({ path: `.agents/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
5492
+ files.push({
5493
+ path: `.agents/skills/${sanitizePath(skill.name)}/SKILL.md`,
5494
+ content: buildSkillContent(skill)
5495
+ });
5449
5496
  }
5450
5497
  }
5451
5498
  for (const builtin of BUILTIN_SKILLS) {
5452
- files.push({ path: `.agents/skills/${builtin.name}/SKILL.md`, content: buildSkillContent(builtin) });
5499
+ files.push({
5500
+ path: `.agents/skills/${builtin.name}/SKILL.md`,
5501
+ content: buildSkillContent(builtin)
5502
+ });
5453
5503
  }
5454
5504
  }
5455
5505
  if (cursor) {
5456
- if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
5506
+ if (cursor.cursorrules)
5507
+ files.push({ path: ".cursorrules", content: cursor.cursorrules });
5457
5508
  const cursorSkills = cursor.skills;
5458
5509
  if (Array.isArray(cursorSkills)) {
5459
5510
  for (const skill of cursorSkills) {
5460
- files.push({ path: `.cursor/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
5511
+ files.push({
5512
+ path: `.cursor/skills/${sanitizePath(skill.name)}/SKILL.md`,
5513
+ content: buildSkillContent(skill)
5514
+ });
5461
5515
  }
5462
5516
  }
5463
5517
  for (const builtin of BUILTIN_SKILLS) {
5464
- files.push({ path: `.cursor/skills/${builtin.name}/SKILL.md`, content: buildSkillContent(builtin) });
5518
+ files.push({
5519
+ path: `.cursor/skills/${builtin.name}/SKILL.md`,
5520
+ content: buildSkillContent(builtin)
5521
+ });
5465
5522
  }
5466
5523
  const rules = cursor.rules;
5467
5524
  if (Array.isArray(rules)) {
5468
5525
  for (const rule of rules) {
5469
- files.push({ path: `.cursor/rules/${rule.filename}`, content: rule.content });
5526
+ files.push({ path: `.cursor/rules/${sanitizePath(rule.filename)}`, content: rule.content });
5470
5527
  }
5471
5528
  }
5472
5529
  }
5473
5530
  const copilot = setup.copilot;
5474
5531
  if (copilot) {
5475
- if (copilot.instructions) files.push({ path: ".github/copilot-instructions.md", content: copilot.instructions });
5532
+ if (copilot.instructions)
5533
+ files.push({
5534
+ path: ".github/copilot-instructions.md",
5535
+ content: copilot.instructions
5536
+ });
5476
5537
  const instructionFiles = copilot.instructionFiles;
5477
5538
  if (Array.isArray(instructionFiles)) {
5478
5539
  for (const file of instructionFiles) {
5479
- files.push({ path: `.github/instructions/${file.filename}`, content: file.content });
5540
+ files.push({
5541
+ path: `.github/instructions/${sanitizePath(file.filename)}`,
5542
+ content: file.content
5543
+ });
5480
5544
  }
5481
5545
  }
5482
5546
  }
@@ -5485,7 +5549,8 @@ function collectSetupFiles(setup, targetAgent) {
5485
5549
  const agentRefs = [];
5486
5550
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
5487
5551
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
5488
- if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
5552
+ if (agentRefs.length === 0)
5553
+ agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
5489
5554
  const stubContent = `# AGENTS.md
5490
5555
 
5491
5556
  This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
@@ -5500,8 +5565,8 @@ ${agentRefs.join(" ")}
5500
5565
  // src/lib/learning-hooks.ts
5501
5566
  init_resolve_caliber();
5502
5567
  import fs22 from "fs";
5503
- import path18 from "path";
5504
- var SETTINGS_PATH2 = path18.join(".claude", "settings.json");
5568
+ import path19 from "path";
5569
+ var SETTINGS_PATH2 = path19.join(".claude", "settings.json");
5505
5570
  var HOOK_TAILS = [
5506
5571
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
5507
5572
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
@@ -5526,7 +5591,7 @@ function readSettings2() {
5526
5591
  }
5527
5592
  }
5528
5593
  function writeSettings2(settings) {
5529
- const dir = path18.dirname(SETTINGS_PATH2);
5594
+ const dir = path19.dirname(SETTINGS_PATH2);
5530
5595
  if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5531
5596
  fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
5532
5597
  }
@@ -5562,7 +5627,7 @@ function installLearningHooks() {
5562
5627
  writeSettings2(settings);
5563
5628
  return { installed: true, alreadyInstalled: false };
5564
5629
  }
5565
- var CURSOR_HOOKS_PATH = path18.join(".cursor", "hooks.json");
5630
+ var CURSOR_HOOKS_PATH = path19.join(".cursor", "hooks.json");
5566
5631
  var CURSOR_HOOK_EVENTS = [
5567
5632
  { event: "postToolUse", tail: "learn observe" },
5568
5633
  { event: "postToolUseFailure", tail: "learn observe --failure" },
@@ -5578,7 +5643,7 @@ function readCursorHooks() {
5578
5643
  }
5579
5644
  }
5580
5645
  function writeCursorHooks(config) {
5581
- const dir = path18.dirname(CURSOR_HOOKS_PATH);
5646
+ const dir = path19.dirname(CURSOR_HOOKS_PATH);
5582
5647
  if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5583
5648
  fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
5584
5649
  }
@@ -5653,9 +5718,9 @@ init_resolve_caliber();
5653
5718
 
5654
5719
  // src/lib/state.ts
5655
5720
  import fs23 from "fs";
5656
- import path19 from "path";
5721
+ import path20 from "path";
5657
5722
  import { execSync as execSync9 } from "child_process";
5658
- var STATE_FILE = path19.join(CALIBER_DIR, ".caliber-state.json");
5723
+ var STATE_FILE = path20.join(CALIBER_DIR, ".caliber-state.json");
5659
5724
  function normalizeTargetAgent(value) {
5660
5725
  if (Array.isArray(value)) return value;
5661
5726
  if (typeof value === "string") {
@@ -5832,11 +5897,11 @@ var POINTS_LEARNED_CONTENT = 2;
5832
5897
  var POINTS_SOURCES_CONFIGURED = 3;
5833
5898
  var POINTS_SOURCES_REFERENCED = 3;
5834
5899
  var TOKEN_BUDGET_THRESHOLDS = [
5835
- { maxTokens: 5e3, points: 6 },
5836
- { maxTokens: 8e3, points: 5 },
5837
- { maxTokens: 12e3, points: 4 },
5838
- { maxTokens: 16e3, points: 2 },
5839
- { maxTokens: 24e3, points: 1 }
5900
+ { maxTokens: 2e3, points: 6 },
5901
+ { maxTokens: 3500, points: 5 },
5902
+ { maxTokens: 5e3, points: 4 },
5903
+ { maxTokens: 8e3, points: 2 },
5904
+ { maxTokens: 12e3, points: 1 }
5840
5905
  ];
5841
5906
  var CODE_BLOCK_THRESHOLDS = [
5842
5907
  { minBlocks: 3, points: 8 },
@@ -5866,8 +5931,8 @@ var SECRET_PATTERNS = [
5866
5931
  /AKIA[A-Z0-9]{16}/,
5867
5932
  /ghp_[a-zA-Z0-9]{36}/,
5868
5933
  /ghu_[a-zA-Z0-9]{36}/,
5869
- /glpat-[a-zA-Z0-9\-_]{20,}/,
5870
- /xox[bpors]-[a-zA-Z0-9\-]{10,}/,
5934
+ /glpat-[a-zA-Z0-9_-]{20,}/,
5935
+ /xox[bpors]-[a-zA-Z0-9-]{10,}/,
5871
5936
  /(?:password|secret|token|api_key)\s*[:=]\s*["'][^"']{8,}["']/i
5872
5937
  ];
5873
5938
  var SECRET_PLACEHOLDER_PATTERNS = [
@@ -5879,31 +5944,13 @@ var SECRET_PLACEHOLDER_PATTERNS = [
5879
5944
  /CHANGE[_-]?ME/i,
5880
5945
  /<[^>]+>/
5881
5946
  ];
5882
- var CURSOR_ONLY_CHECKS = /* @__PURE__ */ new Set([
5883
- "cursor_rules_exist",
5884
- "cursor_mdc_rules"
5885
- ]);
5886
- var CLAUDE_ONLY_CHECKS = /* @__PURE__ */ new Set([
5887
- "claude_md_exists",
5888
- "claude_md_freshness"
5889
- ]);
5890
- var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set([
5891
- "cross_platform_parity",
5892
- "no_duplicate_content"
5893
- ]);
5894
- var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set([
5895
- "codex_agents_md_exists"
5896
- ]);
5897
- var COPILOT_ONLY_CHECKS = /* @__PURE__ */ new Set([
5898
- "copilot_instructions_exists"
5899
- ]);
5900
- var NON_CODEX_CHECKS = /* @__PURE__ */ new Set([
5901
- "agents_md_exists"
5902
- ]);
5903
- var CLAUDE_OR_CODEX_CHECKS = /* @__PURE__ */ new Set([
5904
- "skills_exist",
5905
- "open_skills_format"
5906
- ]);
5947
+ var CURSOR_ONLY_CHECKS = /* @__PURE__ */ new Set(["cursor_rules_exist", "cursor_mdc_rules"]);
5948
+ var CLAUDE_ONLY_CHECKS = /* @__PURE__ */ new Set(["claude_md_exists", "claude_md_freshness"]);
5949
+ var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set(["cross_platform_parity", "no_duplicate_content"]);
5950
+ var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set(["codex_agents_md_exists"]);
5951
+ var COPILOT_ONLY_CHECKS = /* @__PURE__ */ new Set(["copilot_instructions_exists"]);
5952
+ var NON_CODEX_CHECKS = /* @__PURE__ */ new Set(["agents_md_exists"]);
5953
+ var CLAUDE_OR_CODEX_CHECKS = /* @__PURE__ */ new Set(["skills_exist", "open_skills_format"]);
5907
5954
  var GRADE_THRESHOLDS = [
5908
5955
  { minScore: 85, grade: "A" },
5909
5956
  { minScore: 70, grade: "B" },
@@ -6170,7 +6217,12 @@ function checkQuality(dir) {
6170
6217
  suggestion: concretenessPoints < 3 && totalMeaningful > 0 ? `${abstractCount} lines are generic prose \u2014 replace with specific instructions referencing project files` : void 0,
6171
6218
  fix: concretenessPoints < 3 && totalMeaningful > 0 ? {
6172
6219
  action: "replace_vague",
6173
- data: { abstractLines: abstractExamples, abstractCount, concreteCount, ratio: Math.round(concreteRatio * 100) },
6220
+ data: {
6221
+ abstractLines: abstractExamples,
6222
+ abstractCount,
6223
+ concreteCount,
6224
+ ratio: Math.round(concreteRatio * 100)
6225
+ },
6174
6226
  instruction: `Replace generic prose with specific references. Examples of vague lines: ${abstractExamples.join("; ")}`
6175
6227
  } : void 0
6176
6228
  });
@@ -6297,7 +6349,7 @@ function checkGrounding(dir) {
6297
6349
  }
6298
6350
 
6299
6351
  // src/scoring/checks/accuracy.ts
6300
- import { existsSync as existsSync4, statSync as statSync2 } from "fs";
6352
+ import { existsSync as existsSync4, statSync } from "fs";
6301
6353
  import { execSync as execSync10 } from "child_process";
6302
6354
  import { join as join5 } from "path";
6303
6355
  init_resolve_caliber();
@@ -6323,7 +6375,7 @@ function detectGitDrift(dir) {
6323
6375
  const filePath = join5(dir, file);
6324
6376
  if (!existsSync4(filePath)) continue;
6325
6377
  try {
6326
- const mtime = statSync2(filePath).mtime.getTime();
6378
+ const mtime = statSync(filePath).mtime.getTime();
6327
6379
  if (mtime > headTime) {
6328
6380
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: "uncommitted (recently modified)", isGitRepo: true };
6329
6381
  }
@@ -6436,7 +6488,7 @@ function checkAccuracy(dir) {
6436
6488
 
6437
6489
  // src/scoring/checks/freshness.ts
6438
6490
  init_resolve_caliber();
6439
- import { existsSync as existsSync5, statSync as statSync3 } from "fs";
6491
+ import { existsSync as existsSync5, statSync as statSync2 } from "fs";
6440
6492
  import { execSync as execSync11 } from "child_process";
6441
6493
  import { join as join6 } from "path";
6442
6494
  function getCommitsSinceConfigUpdate(dir) {
@@ -6451,7 +6503,7 @@ function getCommitsSinceConfigUpdate(dir) {
6451
6503
  const filePath = join6(dir, file);
6452
6504
  if (!existsSync5(filePath)) continue;
6453
6505
  try {
6454
- const mtime = statSync3(filePath).mtime.getTime();
6506
+ const mtime = statSync2(filePath).mtime.getTime();
6455
6507
  if (mtime > headTime) {
6456
6508
  return 0;
6457
6509
  }
@@ -6749,8 +6801,8 @@ function checkSources(dir) {
6749
6801
 
6750
6802
  // src/scoring/dismissed.ts
6751
6803
  import fs24 from "fs";
6752
- import path20 from "path";
6753
- var DISMISSED_FILE = path20.join(CALIBER_DIR, "dismissed-checks.json");
6804
+ import path21 from "path";
6805
+ var DISMISSED_FILE = path21.join(CALIBER_DIR, "dismissed-checks.json");
6754
6806
  function readDismissedChecks() {
6755
6807
  try {
6756
6808
  if (!fs24.existsSync(DISMISSED_FILE)) return [];
@@ -7019,12 +7071,12 @@ import chalk5 from "chalk";
7019
7071
 
7020
7072
  // src/telemetry/config.ts
7021
7073
  import fs25 from "fs";
7022
- import path21 from "path";
7074
+ import path22 from "path";
7023
7075
  import os5 from "os";
7024
7076
  import crypto4 from "crypto";
7025
7077
  import { execSync as execSync13 } from "child_process";
7026
- var CONFIG_DIR2 = path21.join(os5.homedir(), ".caliber");
7027
- var CONFIG_FILE2 = path21.join(CONFIG_DIR2, "config.json");
7078
+ var CONFIG_DIR2 = path22.join(os5.homedir(), ".caliber");
7079
+ var CONFIG_FILE2 = path22.join(CONFIG_DIR2, "config.json");
7028
7080
  var runtimeDisabled = false;
7029
7081
  function readConfig() {
7030
7082
  try {
@@ -7207,8 +7259,8 @@ function trackInitSkillsSearch(searched, installedCount) {
7207
7259
  function trackInitScoreRegression(oldScore, newScore) {
7208
7260
  trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
7209
7261
  }
7210
- function trackInitCompleted(path41, score) {
7211
- trackEvent("init_completed", { path: path41, score });
7262
+ function trackInitCompleted(path42, score) {
7263
+ trackEvent("init_completed", { path: path42, score });
7212
7264
  }
7213
7265
  function trackRegenerateCompleted(action, durationMs) {
7214
7266
  trackEvent("regenerate_completed", { action, duration_ms: durationMs });
@@ -7282,7 +7334,7 @@ function detectLocalPlatforms() {
7282
7334
  return platforms.size > 0 ? Array.from(platforms) : ["claude"];
7283
7335
  }
7284
7336
  function sanitizeSlug(slug) {
7285
- return slug.replace(/[^a-zA-Z0-9_\-]/g, "-").replace(/^-+|-+$/g, "");
7337
+ return slug.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
7286
7338
  }
7287
7339
  function getSkillPath(platform, slug) {
7288
7340
  const safe = sanitizeSlug(slug);
@@ -7320,9 +7372,12 @@ async function searchSkillsSh(technologies) {
7320
7372
  const bestBySlug = /* @__PURE__ */ new Map();
7321
7373
  for (const tech of technologies) {
7322
7374
  try {
7323
- const resp = await fetch(`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`, {
7324
- signal: AbortSignal.timeout(1e4)
7325
- });
7375
+ const resp = await fetch(
7376
+ `https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`,
7377
+ {
7378
+ signal: AbortSignal.timeout(1e4)
7379
+ }
7380
+ );
7326
7381
  if (!resp.ok) continue;
7327
7382
  const data = await resp.json();
7328
7383
  if (!data.skills?.length) continue;
@@ -7381,9 +7436,7 @@ async function searchAwesomeClaudeCode(technologies) {
7381
7436
  }
7382
7437
  }
7383
7438
  async function searchAllProviders(technologies, platform) {
7384
- const searches = [
7385
- searchSkillsSh(technologies)
7386
- ];
7439
+ const searches = [searchSkillsSh(technologies)];
7387
7440
  if (platform === "claude" || !platform) {
7388
7441
  searches.push(searchAwesomeClaudeCode(technologies));
7389
7442
  }
@@ -7443,18 +7496,24 @@ ${candidateList}`,
7443
7496
  function buildProjectContext(fingerprint, platforms) {
7444
7497
  const parts = [];
7445
7498
  if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
7446
- if (fingerprint.languages.length > 0) parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
7447
- if (fingerprint.frameworks.length > 0) parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
7499
+ if (fingerprint.languages.length > 0)
7500
+ parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
7501
+ if (fingerprint.frameworks.length > 0)
7502
+ parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
7448
7503
  if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
7449
7504
  if (fingerprint.fileTree.length > 0) {
7450
- parts.push(`
7505
+ parts.push(
7506
+ `
7451
7507
  File tree (${fingerprint.fileTree.length} files):
7452
- ${fingerprint.fileTree.slice(0, 50).join("\n")}`);
7508
+ ${fingerprint.fileTree.slice(0, 50).join("\n")}`
7509
+ );
7453
7510
  }
7454
7511
  if (fingerprint.existingConfigs.claudeMd) {
7455
- parts.push(`
7512
+ parts.push(
7513
+ `
7456
7514
  Existing CLAUDE.md (first 500 chars):
7457
- ${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`);
7515
+ ${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`
7516
+ );
7458
7517
  }
7459
7518
  const deps = extractTopDeps();
7460
7519
  if (deps.length > 0) {
@@ -7537,20 +7596,18 @@ function extractTopDeps() {
7537
7596
  /^@typescript-eslint\//,
7538
7597
  /^@commitlint\//
7539
7598
  ];
7540
- return deps.filter(
7541
- (d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
7542
- );
7599
+ return deps.filter((d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d)));
7543
7600
  } catch {
7544
7601
  return [];
7545
7602
  }
7546
7603
  }
7547
7604
  async function searchSkills(fingerprint, targetPlatforms, onStatus) {
7548
7605
  const installedSkills = getInstalledSkills(targetPlatforms);
7549
- const technologies = [...new Set([
7550
- ...fingerprint.languages,
7551
- ...fingerprint.frameworks,
7552
- ...extractTopDeps()
7553
- ].filter(Boolean))];
7606
+ const technologies = [
7607
+ ...new Set(
7608
+ [...fingerprint.languages, ...fingerprint.frameworks, ...extractTopDeps()].filter(Boolean)
7609
+ )
7610
+ ];
7554
7611
  if (technologies.length === 0) {
7555
7612
  return { results: [], contentMap: /* @__PURE__ */ new Map() };
7556
7613
  }
@@ -7582,10 +7639,12 @@ async function searchSkills(fingerprint, targetPlatforms, onStatus) {
7582
7639
  }
7583
7640
  onStatus?.("Fetching skill content...");
7584
7641
  const contentMap = /* @__PURE__ */ new Map();
7585
- await Promise.all(results.map(async (rec) => {
7586
- const content = await fetchSkillContent(rec);
7587
- if (content) contentMap.set(rec.slug, content);
7588
- }));
7642
+ await Promise.all(
7643
+ results.map(async (rec) => {
7644
+ const content = await fetchSkillContent(rec);
7645
+ if (content) contentMap.set(rec.slug, content);
7646
+ })
7647
+ );
7589
7648
  const available = results.filter((r) => contentMap.has(r.slug));
7590
7649
  return { results: available, contentMap };
7591
7650
  }
@@ -7631,10 +7690,12 @@ async function querySkills(query) {
7631
7690
  const top = results.slice(0, 5);
7632
7691
  const fetchSpinner = ora("Verifying availability...").start();
7633
7692
  const contentMap = /* @__PURE__ */ new Map();
7634
- await Promise.all(top.map(async (rec) => {
7635
- const content = await fetchSkillContent(rec);
7636
- if (content) contentMap.set(rec.slug, content);
7637
- }));
7693
+ await Promise.all(
7694
+ top.map(async (rec) => {
7695
+ const content = await fetchSkillContent(rec);
7696
+ if (content) contentMap.set(rec.slug, content);
7697
+ })
7698
+ );
7638
7699
  const available = top.filter((r) => contentMap.has(r.slug));
7639
7700
  fetchSpinner.succeed(`${available.length} available`);
7640
7701
  if (!available.length) {
@@ -7649,7 +7710,11 @@ async function querySkills(query) {
7649
7710
  console.log(` ${r.reason || r.name}`);
7650
7711
  }
7651
7712
  console.log("");
7652
- console.log(chalk6.dim(` Install with: ${resolveCaliber()} skills --install ${available.map((r) => r.slug).join(",")}`));
7713
+ console.log(
7714
+ chalk6.dim(
7715
+ ` Install with: ${resolveCaliber()} skills --install ${available.map((r) => r.slug).join(",")}`
7716
+ )
7717
+ );
7653
7718
  console.log("");
7654
7719
  }
7655
7720
  async function installBySlug(slugStr) {
@@ -7671,10 +7736,12 @@ async function installBySlug(slugStr) {
7671
7736
  return;
7672
7737
  }
7673
7738
  const contentMap = /* @__PURE__ */ new Map();
7674
- await Promise.all(matched.map(async (rec) => {
7675
- const content = await fetchSkillContent(rec);
7676
- if (content) contentMap.set(rec.slug, content);
7677
- }));
7739
+ await Promise.all(
7740
+ matched.map(async (rec) => {
7741
+ const content = await fetchSkillContent(rec);
7742
+ if (content) contentMap.set(rec.slug, content);
7743
+ })
7744
+ );
7678
7745
  const installable = matched.filter((r) => contentMap.has(r.slug));
7679
7746
  if (!installable.length) {
7680
7747
  spinner.fail("Could not fetch skill content.");
@@ -7711,13 +7778,17 @@ async function searchAndInstallSkills(targetPlatforms) {
7711
7778
  const fingerprint = await collectFingerprint(process.cwd());
7712
7779
  const platforms = targetPlatforms ?? detectLocalPlatforms();
7713
7780
  const installedSkills = getInstalledSkills(platforms);
7714
- const technologies = [...new Set([
7715
- ...fingerprint.languages,
7716
- ...fingerprint.frameworks,
7717
- ...extractTopDeps()
7718
- ].filter(Boolean))];
7781
+ const technologies = [
7782
+ ...new Set(
7783
+ [...fingerprint.languages, ...fingerprint.frameworks, ...extractTopDeps()].filter(Boolean)
7784
+ )
7785
+ ];
7719
7786
  if (technologies.length === 0) {
7720
- console.log(chalk6.yellow("Could not detect any languages or dependencies. Try running from a project root."));
7787
+ console.log(
7788
+ chalk6.yellow(
7789
+ "Could not detect any languages or dependencies. Try running from a project root."
7790
+ )
7791
+ );
7721
7792
  throw new Error("__exit__");
7722
7793
  }
7723
7794
  const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
@@ -7747,7 +7818,9 @@ async function searchAndInstallSkills(targetPlatforms) {
7747
7818
  scoreSpinner.succeed("No highly relevant skills found for your specific project.");
7748
7819
  return;
7749
7820
  }
7750
- scoreSpinner.succeed(`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`);
7821
+ scoreSpinner.succeed(
7822
+ `${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`
7823
+ );
7751
7824
  } catch {
7752
7825
  scoreSpinner.warn("Could not score relevance \u2014 showing top results");
7753
7826
  results = newCandidates.slice(0, 20);
@@ -7757,10 +7830,12 @@ async function searchAndInstallSkills(targetPlatforms) {
7757
7830
  }
7758
7831
  const fetchSpinner = ora("Verifying skill availability...").start();
7759
7832
  const contentMap = /* @__PURE__ */ new Map();
7760
- await Promise.all(results.map(async (rec) => {
7761
- const content = await fetchSkillContent(rec);
7762
- if (content) contentMap.set(rec.slug, content);
7763
- }));
7833
+ await Promise.all(
7834
+ results.map(async (rec) => {
7835
+ const content = await fetchSkillContent(rec);
7836
+ if (content) contentMap.set(rec.slug, content);
7837
+ })
7838
+ );
7764
7839
  const available = results.filter((r) => contentMap.has(r.slug));
7765
7840
  if (!available.length) {
7766
7841
  fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
@@ -7808,9 +7883,13 @@ async function interactiveSelect(recs) {
7808
7883
  if (hasScores) {
7809
7884
  const scoreColor = rec.score >= 90 ? chalk6.green : rec.score >= 70 ? chalk6.yellow : chalk6.dim;
7810
7885
  const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
7811
- lines.push(` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`);
7886
+ lines.push(
7887
+ ` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`
7888
+ );
7812
7889
  } else {
7813
- lines.push(` ${ptr} ${check} ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
7890
+ lines.push(
7891
+ ` ${ptr} ${check} ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`
7892
+ );
7814
7893
  }
7815
7894
  }
7816
7895
  lines.push("");
@@ -7866,7 +7945,9 @@ async function interactiveSelect(recs) {
7866
7945
  console.log(chalk6.dim("\n No skills selected.\n"));
7867
7946
  resolve3(null);
7868
7947
  } else {
7869
- resolve3(Array.from(selected).sort().map((i) => recs[i]));
7948
+ resolve3(
7949
+ Array.from(selected).sort().map((i) => recs[i])
7950
+ );
7870
7951
  }
7871
7952
  break;
7872
7953
  case "q":
@@ -7935,17 +8016,25 @@ function printSkills(recs) {
7935
8016
  const prefixWidth = 2;
7936
8017
  console.log(chalk6.bold("\n Skills\n"));
7937
8018
  if (hasScores) {
7938
- console.log(" ".repeat(prefixWidth) + chalk6.dim("Score".padEnd(scoreWidth)) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Why"));
8019
+ console.log(
8020
+ " ".repeat(prefixWidth) + chalk6.dim("Score".padEnd(scoreWidth)) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Why")
8021
+ );
7939
8022
  } else {
7940
- console.log(" ".repeat(prefixWidth) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Technology".padEnd(18)) + chalk6.dim("Source"));
8023
+ console.log(
8024
+ " ".repeat(prefixWidth) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Technology".padEnd(18)) + chalk6.dim("Source")
8025
+ );
7941
8026
  }
7942
8027
  console.log(chalk6.dim(" " + "\u2500".repeat(Math.min(cols - 4, 90))));
7943
8028
  for (const rec of recs) {
7944
8029
  if (hasScores) {
7945
8030
  const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
7946
- console.log(` ${String(rec.score).padStart(3)} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`);
8031
+ console.log(
8032
+ ` ${String(rec.score).padStart(3)} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`
8033
+ );
7947
8034
  } else {
7948
- console.log(` ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`);
8035
+ console.log(
8036
+ ` ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`
8037
+ );
7949
8038
  }
7950
8039
  }
7951
8040
  console.log("");
@@ -8165,11 +8254,11 @@ function countIssuePoints(issues) {
8165
8254
  }
8166
8255
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8167
8256
  const existsCache = /* @__PURE__ */ new Map();
8168
- const cachedExists = (path41) => {
8169
- const cached = existsCache.get(path41);
8257
+ const cachedExists = (path42) => {
8258
+ const cached = existsCache.get(path42);
8170
8259
  if (cached !== void 0) return cached;
8171
- const result = existsSync9(path41);
8172
- existsCache.set(path41, result);
8260
+ const result = existsSync9(path42);
8261
+ existsCache.set(path42, result);
8173
8262
  return result;
8174
8263
  };
8175
8264
  const projectStructure = collectProjectStructure(dir);
@@ -8309,7 +8398,7 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
8309
8398
 
8310
8399
  // src/lib/debug-report.ts
8311
8400
  import fs26 from "fs";
8312
- import path22 from "path";
8401
+ import path23 from "path";
8313
8402
  var DebugReport = class {
8314
8403
  sections = [];
8315
8404
  startTime;
@@ -8378,7 +8467,7 @@ var DebugReport = class {
8378
8467
  lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
8379
8468
  lines.push("");
8380
8469
  }
8381
- const dir = path22.dirname(outputPath);
8470
+ const dir = path23.dirname(outputPath);
8382
8471
  if (!fs26.existsSync(dir)) {
8383
8472
  fs26.mkdirSync(dir, { recursive: true });
8384
8473
  }
@@ -9045,7 +9134,11 @@ function formatWhatChanged(setup) {
9045
9134
  lines.push(`${action} AGENTS.md`);
9046
9135
  }
9047
9136
  const allSkills = [];
9048
- for (const [platform, obj] of [["claude", claude], ["codex", codex], ["cursor", cursor]]) {
9137
+ for (const [_platform, obj] of [
9138
+ ["claude", claude],
9139
+ ["codex", codex],
9140
+ ["cursor", cursor]
9141
+ ]) {
9049
9142
  const skills = obj?.skills;
9050
9143
  if (Array.isArray(skills)) {
9051
9144
  for (const s of skills) allSkills.push(s.name);
@@ -9062,7 +9155,9 @@ function formatWhatChanged(setup) {
9062
9155
  }
9063
9156
  const deletions = setup.deletions;
9064
9157
  if (Array.isArray(deletions) && deletions.length > 0) {
9065
- lines.push(`Removing ${deletions.length} file${deletions.length === 1 ? "" : "s"}: ${deletions.map((d) => d.filePath).join(", ")}`);
9158
+ lines.push(
9159
+ `Removing ${deletions.length} file${deletions.length === 1 ? "" : "s"}: ${deletions.map((d) => d.filePath).join(", ")}`
9160
+ );
9066
9161
  }
9067
9162
  return lines;
9068
9163
  }
@@ -9160,7 +9255,9 @@ function printSetupSummary(setup) {
9160
9255
  console.log("");
9161
9256
  }
9162
9257
  }
9163
- console.log(` ${chalk12.green("+")} ${chalk12.dim("new")} ${chalk12.yellow("~")} ${chalk12.dim("modified")} ${chalk12.red("-")} ${chalk12.dim("removed")}`);
9258
+ console.log(
9259
+ ` ${chalk12.green("+")} ${chalk12.dim("new")} ${chalk12.yellow("~")} ${chalk12.dim("modified")} ${chalk12.red("-")} ${chalk12.dim("removed")}`
9260
+ );
9164
9261
  console.log("");
9165
9262
  }
9166
9263
  function displayTokenUsage() {
@@ -9175,11 +9272,17 @@ function displayTokenUsage() {
9175
9272
  for (const m of summary) {
9176
9273
  totalIn += m.inputTokens;
9177
9274
  totalOut += m.outputTokens;
9178
- const cacheInfo = m.cacheReadTokens > 0 || m.cacheWriteTokens > 0 ? chalk12.dim(` (cache: ${m.cacheReadTokens.toLocaleString()} read, ${m.cacheWriteTokens.toLocaleString()} write)`) : "";
9179
- console.log(` ${chalk12.dim(m.model)}: ${m.inputTokens.toLocaleString()} in / ${m.outputTokens.toLocaleString()} out (${m.calls} call${m.calls === 1 ? "" : "s"})${cacheInfo}`);
9275
+ const cacheInfo = m.cacheReadTokens > 0 || m.cacheWriteTokens > 0 ? chalk12.dim(
9276
+ ` (cache: ${m.cacheReadTokens.toLocaleString()} read, ${m.cacheWriteTokens.toLocaleString()} write)`
9277
+ ) : "";
9278
+ console.log(
9279
+ ` ${chalk12.dim(m.model)}: ${m.inputTokens.toLocaleString()} in / ${m.outputTokens.toLocaleString()} out (${m.calls} call${m.calls === 1 ? "" : "s"})${cacheInfo}`
9280
+ );
9180
9281
  }
9181
9282
  if (summary.length > 1) {
9182
- console.log(` ${chalk12.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`);
9283
+ console.log(
9284
+ ` ${chalk12.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`
9285
+ );
9183
9286
  }
9184
9287
  console.log("");
9185
9288
  }
@@ -9188,9 +9291,9 @@ function displayTokenUsage() {
9188
9291
  init_config();
9189
9292
  import chalk13 from "chalk";
9190
9293
  import fs31 from "fs";
9191
- import path24 from "path";
9294
+ import path25 from "path";
9192
9295
  function isFirstRun(dir) {
9193
- const caliberDir = path24.join(dir, ".caliber");
9296
+ const caliberDir = path25.join(dir, ".caliber");
9194
9297
  try {
9195
9298
  const stat = fs31.statSync(caliberDir);
9196
9299
  return !stat.isDirectory();
@@ -9260,7 +9363,7 @@ function ensurePermissions(fingerprint) {
9260
9363
  }
9261
9364
  function writeErrorLog(config, rawOutput, error, stopReason) {
9262
9365
  try {
9263
- const logPath = path24.join(process.cwd(), ".caliber", "error-log.md");
9366
+ const logPath = path25.join(process.cwd(), ".caliber", "error-log.md");
9264
9367
  const lines = [
9265
9368
  `# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
9266
9369
  "",
@@ -9273,7 +9376,7 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
9273
9376
  lines.push("## Error", "```", error, "```", "");
9274
9377
  }
9275
9378
  lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
9276
- fs31.mkdirSync(path24.join(process.cwd(), ".caliber"), { recursive: true });
9379
+ fs31.mkdirSync(path25.join(process.cwd(), ".caliber"), { recursive: true });
9277
9380
  fs31.writeFileSync(logPath, lines.join("\n"));
9278
9381
  console.log(chalk13.dim(`
9279
9382
  Error log written to .caliber/error-log.md`));
@@ -9326,12 +9429,12 @@ ${JSON.stringify(checkList, null, 2)}`,
9326
9429
 
9327
9430
  // src/scoring/history.ts
9328
9431
  import fs32 from "fs";
9329
- import path25 from "path";
9432
+ import path26 from "path";
9330
9433
  var HISTORY_FILE = "score-history.jsonl";
9331
9434
  var MAX_ENTRIES = 500;
9332
9435
  var TRIM_THRESHOLD = MAX_ENTRIES + 50;
9333
9436
  function historyFilePath() {
9334
- return path25.join(CALIBER_DIR, HISTORY_FILE);
9437
+ return path26.join(CALIBER_DIR, HISTORY_FILE);
9335
9438
  }
9336
9439
  function recordScore(result, trigger) {
9337
9440
  const entry = {
@@ -9523,7 +9626,7 @@ async function initCommand(options) {
9523
9626
  trackInitLearnEnabled(true);
9524
9627
  }
9525
9628
  console.log("");
9526
- let baselineScore = computeLocalScore(process.cwd(), targetAgent);
9629
+ const baselineScore = computeLocalScore(process.cwd(), targetAgent);
9527
9630
  log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
9528
9631
  if (report) {
9529
9632
  report.markStep("Baseline scoring");
@@ -9570,7 +9673,7 @@ async function initCommand(options) {
9570
9673
  } catch {
9571
9674
  }
9572
9675
  if (!claudeContent) {
9573
- claudeContent = `# ${path26.basename(process.cwd())}
9676
+ claudeContent = `# ${path27.basename(process.cwd())}
9574
9677
  `;
9575
9678
  }
9576
9679
  const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
@@ -9579,15 +9682,15 @@ async function initCommand(options) {
9579
9682
  console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
9580
9683
  }
9581
9684
  if (targetAgent.includes("cursor")) {
9582
- const rulesDir = path26.join(".cursor", "rules");
9685
+ const rulesDir = path27.join(".cursor", "rules");
9583
9686
  if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
9584
9687
  for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
9585
- fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
9688
+ fs33.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
9586
9689
  }
9587
9690
  console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
9588
9691
  }
9589
9692
  if (targetAgent.includes("github-copilot")) {
9590
- const copilotPath = path26.join(".github", "copilot-instructions.md");
9693
+ const copilotPath = path27.join(".github", "copilot-instructions.md");
9591
9694
  let copilotContent = "";
9592
9695
  try {
9593
9696
  copilotContent = fs33.readFileSync(copilotPath, "utf-8");
@@ -9595,7 +9698,7 @@ async function initCommand(options) {
9595
9698
  }
9596
9699
  if (!copilotContent) {
9597
9700
  fs33.mkdirSync(".github", { recursive: true });
9598
- copilotContent = `# ${path26.basename(process.cwd())}
9701
+ copilotContent = `# ${path27.basename(process.cwd())}
9599
9702
  `;
9600
9703
  }
9601
9704
  const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
@@ -9616,8 +9719,6 @@ async function initCommand(options) {
9616
9719
  console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
9617
9720
  return;
9618
9721
  }
9619
- const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
9620
- const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
9621
9722
  const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
9622
9723
  console.log(chalk14.dim(genModelInfo + "\n"));
9623
9724
  if (report) report.markStep("Generation");
@@ -10004,9 +10105,9 @@ ${agentRefs.join(" ")}
10004
10105
  }
10005
10106
  if (report) {
10006
10107
  report.markStep("Finished");
10007
- const reportPath = path26.join(process.cwd(), ".caliber", "debug-report.md");
10108
+ const reportPath = path27.join(process.cwd(), ".caliber", "debug-report.md");
10008
10109
  report.write(reportPath);
10009
- console.log(chalk14.dim(` Debug report written to ${path26.relative(process.cwd(), reportPath)}
10110
+ console.log(chalk14.dim(` Debug report written to ${path27.relative(process.cwd(), reportPath)}
10010
10111
  `));
10011
10112
  }
10012
10113
  }
@@ -10233,30 +10334,39 @@ async function regenerateCommand(options) {
10233
10334
  // src/commands/score.ts
10234
10335
  import fs35 from "fs";
10235
10336
  import os7 from "os";
10236
- import path27 from "path";
10337
+ import path28 from "path";
10237
10338
  import { execFileSync as execFileSync2 } from "child_process";
10238
10339
  import chalk18 from "chalk";
10239
10340
  init_resolve_caliber();
10240
10341
  var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
10241
10342
  var CONFIG_DIRS = [".claude", ".cursor"];
10242
10343
  function scoreBaseRef(ref, target) {
10243
- if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
10244
- const tmpDir = fs35.mkdtempSync(path27.join(os7.tmpdir(), "caliber-compare-"));
10344
+ if (!/^[\w.\-/~^@{}]+$/.test(ref)) return null;
10345
+ const tmpDir = fs35.mkdtempSync(path28.join(os7.tmpdir(), "caliber-compare-"));
10245
10346
  try {
10246
10347
  for (const file of CONFIG_FILES) {
10247
10348
  try {
10248
- const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10249
- fs35.writeFileSync(path27.join(tmpDir, file), content);
10349
+ const content = execFileSync2("git", ["show", `${ref}:${file}`], {
10350
+ encoding: "utf-8",
10351
+ stdio: ["pipe", "pipe", "pipe"]
10352
+ });
10353
+ fs35.writeFileSync(path28.join(tmpDir, file), content);
10250
10354
  } catch {
10251
10355
  }
10252
10356
  }
10253
10357
  for (const dir of CONFIG_DIRS) {
10254
10358
  try {
10255
- const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
10359
+ const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], {
10360
+ encoding: "utf-8",
10361
+ stdio: ["pipe", "pipe", "pipe"]
10362
+ }).trim().split("\n").filter(Boolean);
10256
10363
  for (const file of files) {
10257
- const filePath = path27.join(tmpDir, file);
10258
- fs35.mkdirSync(path27.dirname(filePath), { recursive: true });
10259
- const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10364
+ const filePath = path28.join(tmpDir, file);
10365
+ fs35.mkdirSync(path28.dirname(filePath), { recursive: true });
10366
+ const content = execFileSync2("git", ["show", `${ref}:${file}`], {
10367
+ encoding: "utf-8",
10368
+ stdio: ["pipe", "pipe", "pipe"]
10369
+ });
10260
10370
  fs35.writeFileSync(filePath, content);
10261
10371
  }
10262
10372
  } catch {
@@ -10279,13 +10389,25 @@ async function scoreCommand(options) {
10279
10389
  if (options.compare) {
10280
10390
  const baseResult = scoreBaseRef(options.compare, target);
10281
10391
  if (!baseResult) {
10282
- console.error(chalk18.red(`Could not score ref "${options.compare}" \u2014 branch or ref not found.`));
10392
+ console.error(
10393
+ chalk18.red(`Could not score ref "${options.compare}" \u2014 branch or ref not found.`)
10394
+ );
10283
10395
  process.exitCode = 1;
10284
10396
  return;
10285
10397
  }
10286
10398
  const delta = result.score - baseResult.score;
10287
10399
  if (options.json) {
10288
- console.log(JSON.stringify({ current: result, base: { score: baseResult.score, grade: baseResult.grade, ref: options.compare }, delta }, null, 2));
10400
+ console.log(
10401
+ JSON.stringify(
10402
+ {
10403
+ current: result,
10404
+ base: { score: baseResult.score, grade: baseResult.grade, ref: options.compare },
10405
+ delta
10406
+ },
10407
+ null,
10408
+ 2
10409
+ )
10410
+ );
10289
10411
  return;
10290
10412
  }
10291
10413
  if (options.quiet) {
@@ -10297,9 +10419,13 @@ async function scoreCommand(options) {
10297
10419
  const separator2 = chalk18.gray(" " + "\u2500".repeat(53));
10298
10420
  console.log(separator2);
10299
10421
  if (delta > 0) {
10300
- console.log(chalk18.green(` +${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`));
10422
+ console.log(
10423
+ chalk18.green(` +${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`)
10424
+ );
10301
10425
  } else if (delta < 0) {
10302
- console.log(chalk18.red(` ${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`));
10426
+ console.log(
10427
+ chalk18.red(` ${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`)
10428
+ );
10303
10429
  } else {
10304
10430
  console.log(chalk18.gray(` No change from ${options.compare} (${baseResult.score}/100)`));
10305
10431
  }
@@ -10319,18 +10445,24 @@ async function scoreCommand(options) {
10319
10445
  console.log(separator);
10320
10446
  const bin = resolveCaliber();
10321
10447
  if (result.score < 40) {
10322
- console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to generate a complete, optimized config."));
10448
+ console.log(
10449
+ chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to generate a complete, optimized config.")
10450
+ );
10323
10451
  } else if (result.score < 70) {
10324
- console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to improve your config."));
10452
+ console.log(
10453
+ chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to improve your config.")
10454
+ );
10325
10455
  } else {
10326
- console.log(chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} regenerate`) + chalk18.gray(" to rebuild from scratch."));
10456
+ console.log(
10457
+ chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} regenerate`) + chalk18.gray(" to rebuild from scratch.")
10458
+ );
10327
10459
  }
10328
10460
  console.log("");
10329
10461
  }
10330
10462
 
10331
10463
  // src/commands/refresh.ts
10332
10464
  import fs39 from "fs";
10333
- import path31 from "path";
10465
+ import path32 from "path";
10334
10466
  import chalk19 from "chalk";
10335
10467
  import ora6 from "ora";
10336
10468
 
@@ -10340,24 +10472,14 @@ var MAX_DIFF_BYTES = 1e5;
10340
10472
  var DOC_PATTERNS = [
10341
10473
  "CLAUDE.md",
10342
10474
  "README.md",
10343
- "AGENTS.md",
10344
10475
  ".cursorrules",
10345
10476
  ".cursor/rules/",
10346
- ".cursor/skills/",
10347
10477
  ".claude/skills/",
10348
- ".agents/skills/",
10349
- ".github/copilot-instructions.md",
10350
- ".github/instructions/",
10351
10478
  "CALIBER_LEARNINGS.md"
10352
10479
  ];
10353
10480
  function excludeArgs() {
10354
10481
  return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
10355
10482
  }
10356
- function truncateAtLine(text, maxBytes) {
10357
- if (text.length <= maxBytes) return text;
10358
- const cut = text.lastIndexOf("\n", maxBytes);
10359
- return cut > 0 ? text.slice(0, cut) : text.slice(0, maxBytes);
10360
- }
10361
10483
  function safeExec(cmd) {
10362
10484
  try {
10363
10485
  return execSync15(cmd, {
@@ -10403,9 +10525,9 @@ function collectDiff(lastSha) {
10403
10525
  const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
10404
10526
  if (totalSize > MAX_DIFF_BYTES) {
10405
10527
  const ratio = MAX_DIFF_BYTES / totalSize;
10406
- committedDiff = truncateAtLine(committedDiff, Math.floor(committedDiff.length * ratio));
10407
- stagedDiff = truncateAtLine(stagedDiff, Math.floor(stagedDiff.length * ratio));
10408
- unstagedDiff = truncateAtLine(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
10528
+ committedDiff = committedDiff.slice(0, Math.floor(committedDiff.length * ratio));
10529
+ stagedDiff = stagedDiff.slice(0, Math.floor(stagedDiff.length * ratio));
10530
+ unstagedDiff = unstagedDiff.slice(0, Math.floor(unstagedDiff.length * ratio));
10409
10531
  }
10410
10532
  const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
10411
10533
  const parts = [];
@@ -10420,51 +10542,47 @@ function collectDiff(lastSha) {
10420
10542
  // src/writers/refresh.ts
10421
10543
  init_pre_commit_block();
10422
10544
  import fs36 from "fs";
10423
- import path28 from "path";
10545
+ import path29 from "path";
10424
10546
  function writeRefreshDocs(docs) {
10425
10547
  const written = [];
10426
10548
  if (docs.claudeMd) {
10427
- fs36.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd, "claude"));
10549
+ fs36.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10428
10550
  written.push("CLAUDE.md");
10429
10551
  }
10430
10552
  if (docs.readmeMd) {
10431
10553
  fs36.writeFileSync("README.md", docs.readmeMd);
10432
10554
  written.push("README.md");
10433
10555
  }
10434
- if (docs.agentsMd) {
10435
- fs36.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
10436
- written.push("AGENTS.md");
10437
- }
10438
10556
  if (docs.cursorrules) {
10439
10557
  fs36.writeFileSync(".cursorrules", docs.cursorrules);
10440
10558
  written.push(".cursorrules");
10441
10559
  }
10442
10560
  if (docs.cursorRules) {
10443
- const rulesDir = path28.join(".cursor", "rules");
10561
+ const rulesDir = path29.join(".cursor", "rules");
10444
10562
  if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
10445
10563
  for (const rule of docs.cursorRules) {
10446
- fs36.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
10564
+ fs36.writeFileSync(path29.join(rulesDir, rule.filename), rule.content);
10447
10565
  written.push(`.cursor/rules/${rule.filename}`);
10448
10566
  }
10449
10567
  }
10450
10568
  if (docs.claudeSkills) {
10451
- const skillsDir = path28.join(".claude", "skills");
10569
+ const skillsDir = path29.join(".claude", "skills");
10452
10570
  if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
10453
10571
  for (const skill of docs.claudeSkills) {
10454
- fs36.writeFileSync(path28.join(skillsDir, skill.filename), skill.content);
10572
+ fs36.writeFileSync(path29.join(skillsDir, skill.filename), skill.content);
10455
10573
  written.push(`.claude/skills/${skill.filename}`);
10456
10574
  }
10457
10575
  }
10458
10576
  if (docs.copilotInstructions) {
10459
10577
  fs36.mkdirSync(".github", { recursive: true });
10460
- fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendManagedBlocks(docs.copilotInstructions, "copilot"));
10578
+ fs36.writeFileSync(path29.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10461
10579
  written.push(".github/copilot-instructions.md");
10462
10580
  }
10463
10581
  if (docs.copilotInstructionFiles) {
10464
- const instructionsDir = path28.join(".github", "instructions");
10582
+ const instructionsDir = path29.join(".github", "instructions");
10465
10583
  fs36.mkdirSync(instructionsDir, { recursive: true });
10466
10584
  for (const file of docs.copilotInstructionFiles) {
10467
- fs36.writeFileSync(path28.join(instructionsDir, file.filename), file.content);
10585
+ fs36.writeFileSync(path29.join(instructionsDir, file.filename), file.content);
10468
10586
  written.push(`.github/instructions/${file.filename}`);
10469
10587
  }
10470
10588
  }
@@ -10476,18 +10594,10 @@ init_config();
10476
10594
  async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
10477
10595
  const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
10478
10596
  const fastModel = getFastModel();
10479
- const docCount = [
10480
- existingDocs.claudeMd,
10481
- existingDocs.readmeMd,
10482
- existingDocs.agentsMd,
10483
- existingDocs.cursorrules,
10484
- existingDocs.copilotInstructions
10485
- ].filter(Boolean).length + (existingDocs.cursorRules?.length ?? 0) + (existingDocs.claudeSkills?.length ?? 0);
10486
- const maxTokens = Math.min(32768, Math.max(8192, docCount * 4096));
10487
10597
  const raw = await llmCall({
10488
10598
  system: REFRESH_SYSTEM_PROMPT,
10489
10599
  prompt,
10490
- maxTokens,
10600
+ maxTokens: 16384,
10491
10601
  ...fastModel ? { model: fastModel } : {}
10492
10602
  });
10493
10603
  return parseJsonResponse(raw);
@@ -10546,21 +10656,6 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10546
10656
  parts.push(rule.content);
10547
10657
  }
10548
10658
  }
10549
- if (existingDocs.agentsMd) {
10550
- parts.push("\n[AGENTS.md]");
10551
- parts.push(existingDocs.agentsMd);
10552
- }
10553
- if (existingDocs.copilotInstructions) {
10554
- parts.push("\n[.github/copilot-instructions.md]");
10555
- parts.push(existingDocs.copilotInstructions);
10556
- }
10557
- if (existingDocs.copilotInstructionFiles?.length) {
10558
- for (const file of existingDocs.copilotInstructionFiles) {
10559
- parts.push(`
10560
- [.github/instructions/${file.filename}]`);
10561
- parts.push(file.content);
10562
- }
10563
- }
10564
10659
  if (learnedSection) {
10565
10660
  parts.push("\n--- Learned Patterns (from session learning) ---");
10566
10661
  parts.push("Consider these accumulated learnings when deciding what to update:");
@@ -10574,7 +10669,7 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10574
10669
 
10575
10670
  // src/learner/writer.ts
10576
10671
  import fs37 from "fs";
10577
- import path29 from "path";
10672
+ import path30 from "path";
10578
10673
 
10579
10674
  // src/learner/utils.ts
10580
10675
  var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
@@ -10699,9 +10794,9 @@ function writeLearnedSection(content) {
10699
10794
  return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
10700
10795
  }
10701
10796
  function writeLearnedSkill(skill) {
10702
- const skillDir = path29.join(".claude", "skills", skill.name);
10797
+ const skillDir = path30.join(".claude", "skills", skill.name);
10703
10798
  if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
10704
- const skillPath = path29.join(skillDir, "SKILL.md");
10799
+ const skillPath = path30.join(skillDir, "SKILL.md");
10705
10800
  if (!skill.isNew && fs37.existsSync(skillPath)) {
10706
10801
  const existing = fs37.readFileSync(skillPath, "utf-8");
10707
10802
  fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
@@ -10770,15 +10865,6 @@ function migrateInlineLearnings() {
10770
10865
  init_config();
10771
10866
  init_resolve_caliber();
10772
10867
  init_builtin_skills();
10773
- function detectSyncedAgents(writtenFiles) {
10774
- const agents = [];
10775
- const joined = writtenFiles.join(" ");
10776
- if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
10777
- if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
10778
- if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
10779
- if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
10780
- return agents;
10781
- }
10782
10868
  function log2(quiet, ...args) {
10783
10869
  if (!quiet) console.log(...args);
10784
10870
  }
@@ -10788,8 +10874,8 @@ function discoverGitRepos(parentDir) {
10788
10874
  const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
10789
10875
  for (const entry of entries) {
10790
10876
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
10791
- const childPath = path31.join(parentDir, entry.name);
10792
- if (fs39.existsSync(path31.join(childPath, ".git"))) {
10877
+ const childPath = path32.join(parentDir, entry.name);
10878
+ if (fs39.existsSync(path32.join(childPath, ".git"))) {
10793
10879
  repos.push(childPath);
10794
10880
  }
10795
10881
  }
@@ -10875,7 +10961,7 @@ async function refreshSingleRepo(repoDir, options) {
10875
10961
  const filesToWrite = response.docsUpdated || [];
10876
10962
  const preRefreshContents = /* @__PURE__ */ new Map();
10877
10963
  for (const filePath of filesToWrite) {
10878
- const fullPath = path31.resolve(repoDir, filePath);
10964
+ const fullPath = path32.resolve(repoDir, filePath);
10879
10965
  try {
10880
10966
  preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
10881
10967
  } catch {
@@ -10888,7 +10974,7 @@ async function refreshSingleRepo(repoDir, options) {
10888
10974
  const postScore = computeLocalScore(repoDir, targetAgent);
10889
10975
  if (postScore.score < preScore.score) {
10890
10976
  for (const [filePath, content] of preRefreshContents) {
10891
- const fullPath = path31.resolve(repoDir, filePath);
10977
+ const fullPath = path32.resolve(repoDir, filePath);
10892
10978
  if (content === null) {
10893
10979
  try {
10894
10980
  fs39.unlinkSync(fullPath);
@@ -10907,18 +10993,8 @@ async function refreshSingleRepo(repoDir, options) {
10907
10993
  }
10908
10994
  recordScore(postScore, "refresh");
10909
10995
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
10910
- const fileChangesMap = new Map(
10911
- (response.fileChanges || []).map((fc) => [fc.file, fc.description])
10912
- );
10913
10996
  for (const file of written) {
10914
- const desc = fileChangesMap.get(file);
10915
- const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
10916
- log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
10917
- }
10918
- const agents = detectSyncedAgents(written);
10919
- if (agents.length > 1) {
10920
- log2(quiet, chalk19.cyan(`
10921
- ${agents.length} agent formats in sync (${agents.join(", ")})`));
10997
+ log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
10922
10998
  }
10923
10999
  if (response.changesSummary) {
10924
11000
  log2(quiet, chalk19.dim(`
@@ -10960,7 +11036,7 @@ async function refreshCommand(options) {
10960
11036
  `));
10961
11037
  const originalDir = process.cwd();
10962
11038
  for (const repo of repos) {
10963
- const repoName = path31.basename(repo);
11039
+ const repoName = path32.basename(repo);
10964
11040
  try {
10965
11041
  process.chdir(repo);
10966
11042
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -11198,7 +11274,7 @@ async function configCommand() {
11198
11274
 
11199
11275
  // src/commands/learn.ts
11200
11276
  import fs44 from "fs";
11201
- import path35 from "path";
11277
+ import path36 from "path";
11202
11278
  import chalk23 from "chalk";
11203
11279
 
11204
11280
  // src/learner/stdin.ts
@@ -11230,7 +11306,7 @@ function readStdin() {
11230
11306
 
11231
11307
  // src/learner/storage.ts
11232
11308
  import fs41 from "fs";
11233
- import path32 from "path";
11309
+ import path33 from "path";
11234
11310
  var MAX_RESPONSE_LENGTH = 2e3;
11235
11311
  var DEFAULT_STATE = {
11236
11312
  sessionId: null,
@@ -11244,10 +11320,10 @@ function ensureLearningDir() {
11244
11320
  }
11245
11321
  }
11246
11322
  function sessionFilePath() {
11247
- return path32.join(getLearningDir(), LEARNING_SESSION_FILE);
11323
+ return path33.join(getLearningDir(), LEARNING_SESSION_FILE);
11248
11324
  }
11249
11325
  function stateFilePath() {
11250
- return path32.join(getLearningDir(), LEARNING_STATE_FILE);
11326
+ return path33.join(getLearningDir(), LEARNING_STATE_FILE);
11251
11327
  }
11252
11328
  function truncateResponse(response) {
11253
11329
  const str = JSON.stringify(response);
@@ -11319,7 +11395,7 @@ function resetState() {
11319
11395
  var LOCK_FILE2 = "finalize.lock";
11320
11396
  var LOCK_STALE_MS = 5 * 60 * 1e3;
11321
11397
  function lockFilePath() {
11322
- return path32.join(getLearningDir(), LOCK_FILE2);
11398
+ return path33.join(getLearningDir(), LOCK_FILE2);
11323
11399
  }
11324
11400
  function acquireFinalizeLock() {
11325
11401
  ensureLearningDir();
@@ -11365,10 +11441,10 @@ function releaseFinalizeLock() {
11365
11441
 
11366
11442
  // src/lib/notifications.ts
11367
11443
  import fs42 from "fs";
11368
- import path33 from "path";
11444
+ import path34 from "path";
11369
11445
  import chalk22 from "chalk";
11370
11446
  function notificationFilePath() {
11371
- return path33.join(getLearningDir(), "last-finalize-summary.json");
11447
+ return path34.join(getLearningDir(), "last-finalize-summary.json");
11372
11448
  }
11373
11449
  function writeFinalizeSummary(summary) {
11374
11450
  try {
@@ -11550,7 +11626,7 @@ init_config();
11550
11626
 
11551
11627
  // src/learner/roi.ts
11552
11628
  import fs43 from "fs";
11553
- import path34 from "path";
11629
+ import path35 from "path";
11554
11630
  var DEFAULT_TOTALS = {
11555
11631
  totalWasteTokens: 0,
11556
11632
  totalWasteSeconds: 0,
@@ -11564,7 +11640,7 @@ var DEFAULT_TOTALS = {
11564
11640
  lastSessionTimestamp: ""
11565
11641
  };
11566
11642
  function roiFilePath() {
11567
- return path34.join(getLearningDir(), LEARNING_ROI_FILE);
11643
+ return path35.join(getLearningDir(), LEARNING_ROI_FILE);
11568
11644
  }
11569
11645
  function readROIStats() {
11570
11646
  const filePath = roiFilePath();
@@ -11754,7 +11830,9 @@ Return a JSON object: {"matchedIndices": [0, 2]} or {"matchedIndices": []} if no
11754
11830
  const json = extractJson(raw);
11755
11831
  if (json) {
11756
11832
  const parsed = JSON.parse(json);
11757
- const indices = (parsed.matchedIndices || []).filter((i) => typeof i === "number" && i >= 0 && i < learnings.length);
11833
+ const indices = (parsed.matchedIndices || []).filter(
11834
+ (i) => typeof i === "number" && i >= 0 && i < learnings.length
11835
+ );
11758
11836
  return { matchedIndices: indices, unmatchedFailures: failureEvents.length - indices.length };
11759
11837
  }
11760
11838
  } catch {
@@ -11792,7 +11870,7 @@ var AUTO_SETTLE_MS = 200;
11792
11870
  var INCREMENTAL_INTERVAL = 50;
11793
11871
  function writeFinalizeError(message) {
11794
11872
  try {
11795
- const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11873
+ const errorPath = path36.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11796
11874
  if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
11797
11875
  fs44.writeFileSync(errorPath, JSON.stringify({
11798
11876
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11804,7 +11882,7 @@ function writeFinalizeError(message) {
11804
11882
  }
11805
11883
  function readFinalizeError() {
11806
11884
  try {
11807
- const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11885
+ const errorPath = path36.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11808
11886
  if (!fs44.existsSync(errorPath)) return null;
11809
11887
  return JSON.parse(fs44.readFileSync(errorPath, "utf-8"));
11810
11888
  } catch {
@@ -11853,7 +11931,7 @@ async function learnObserveCommand(options) {
11853
11931
  const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
11854
11932
  const bin = resolveCaliber2();
11855
11933
  const { spawn: spawn4 } = await import("child_process");
11856
- const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11934
+ const logPath = path36.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11857
11935
  if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
11858
11936
  const logFd = fs44.openSync(logPath, "a");
11859
11937
  spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
@@ -12143,7 +12221,7 @@ async function learnStatusCommand() {
12143
12221
  if (lastError) {
12144
12222
  console.log(`Last error: ${chalk23.red(lastError.error)}`);
12145
12223
  console.log(chalk23.dim(` at ${lastError.timestamp}`));
12146
- const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12224
+ const logPath = path36.join(getLearningDir(), LEARNING_FINALIZE_LOG);
12147
12225
  if (fs44.existsSync(logPath)) {
12148
12226
  console.log(chalk23.dim(` Full log: ${logPath}`));
12149
12227
  }
@@ -12425,7 +12503,7 @@ async function insightsCommand(options) {
12425
12503
 
12426
12504
  // src/commands/sources.ts
12427
12505
  import fs45 from "fs";
12428
- import path36 from "path";
12506
+ import path37 from "path";
12429
12507
  import chalk25 from "chalk";
12430
12508
  init_resolve_caliber();
12431
12509
  async function sourcesListCommand() {
@@ -12442,9 +12520,9 @@ async function sourcesListCommand() {
12442
12520
  if (configSources.length > 0) {
12443
12521
  for (const source of configSources) {
12444
12522
  const sourcePath = source.path || source.url || "";
12445
- const exists = source.path ? fs45.existsSync(path36.resolve(dir, source.path)) : false;
12523
+ const exists = source.path ? fs45.existsSync(path37.resolve(dir, source.path)) : false;
12446
12524
  const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
12447
- const hasSummary = source.path && fs45.existsSync(path36.join(path36.resolve(dir, source.path), ".caliber", "summary.json"));
12525
+ const hasSummary = source.path && fs45.existsSync(path37.join(path37.resolve(dir, source.path), ".caliber", "summary.json"));
12448
12526
  console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
12449
12527
  console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
12450
12528
  if (source.description) console.log(` ${chalk25.dim(source.description)}`);
@@ -12454,7 +12532,7 @@ async function sourcesListCommand() {
12454
12532
  if (workspaces.length > 0) {
12455
12533
  console.log(chalk25.dim(" Auto-detected workspaces:"));
12456
12534
  for (const ws of workspaces) {
12457
- const exists = fs45.existsSync(path36.resolve(dir, ws));
12535
+ const exists = fs45.existsSync(path37.resolve(dir, ws));
12458
12536
  console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
12459
12537
  }
12460
12538
  console.log("");
@@ -12462,7 +12540,7 @@ async function sourcesListCommand() {
12462
12540
  }
12463
12541
  async function sourcesAddCommand(sourcePath) {
12464
12542
  const dir = process.cwd();
12465
- const absPath = path36.resolve(dir, sourcePath);
12543
+ const absPath = path37.resolve(dir, sourcePath);
12466
12544
  if (!fs45.existsSync(absPath)) {
12467
12545
  console.log(chalk25.red(`
12468
12546
  Path not found: ${sourcePath}
@@ -12478,7 +12556,7 @@ async function sourcesAddCommand(sourcePath) {
12478
12556
  }
12479
12557
  const existing = loadSourcesConfig(dir);
12480
12558
  const alreadyConfigured = existing.some(
12481
- (s) => s.path && path36.resolve(dir, s.path) === absPath
12559
+ (s) => s.path && path37.resolve(dir, s.path) === absPath
12482
12560
  );
12483
12561
  if (alreadyConfigured) {
12484
12562
  console.log(chalk25.yellow(`
@@ -12527,7 +12605,7 @@ async function sourcesRemoveCommand(name) {
12527
12605
 
12528
12606
  // src/commands/publish.ts
12529
12607
  import fs46 from "fs";
12530
- import path37 from "path";
12608
+ import path38 from "path";
12531
12609
  import chalk26 from "chalk";
12532
12610
  import ora7 from "ora";
12533
12611
  init_config();
@@ -12542,10 +12620,10 @@ async function publishCommand() {
12542
12620
  const spinner = ora7("Generating project summary...").start();
12543
12621
  try {
12544
12622
  const fingerprint = await collectFingerprint(dir);
12545
- const claudeMd = readFileOrNull(path37.join(dir, "CLAUDE.md"));
12623
+ const claudeMd = readFileOrNull(path38.join(dir, "CLAUDE.md"));
12546
12624
  const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
12547
12625
  const summary = {
12548
- name: fingerprint.packageName || path37.basename(dir),
12626
+ name: fingerprint.packageName || path38.basename(dir),
12549
12627
  version: "1.0.0",
12550
12628
  description: fingerprint.description || "",
12551
12629
  languages: fingerprint.languages,
@@ -12557,7 +12635,7 @@ async function publishCommand() {
12557
12635
  summary.conventions = claudeMd.slice(0, 2e3);
12558
12636
  }
12559
12637
  try {
12560
- const pkgContent = readFileOrNull(path37.join(dir, "package.json"));
12638
+ const pkgContent = readFileOrNull(path38.join(dir, "package.json"));
12561
12639
  if (pkgContent) {
12562
12640
  const pkg3 = JSON.parse(pkgContent);
12563
12641
  if (pkg3.scripts) {
@@ -12570,14 +12648,14 @@ async function publishCommand() {
12570
12648
  }
12571
12649
  } catch {
12572
12650
  }
12573
- const outputDir = path37.join(dir, ".caliber");
12651
+ const outputDir = path38.join(dir, ".caliber");
12574
12652
  if (!fs46.existsSync(outputDir)) {
12575
12653
  fs46.mkdirSync(outputDir, { recursive: true });
12576
12654
  }
12577
- const outputPath = path37.join(outputDir, "summary.json");
12655
+ const outputPath = path38.join(outputDir, "summary.json");
12578
12656
  fs46.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
12579
12657
  spinner.succeed("Project summary published");
12580
- console.log(` ${chalk26.green("\u2713")} ${path37.relative(dir, outputPath)}`);
12658
+ console.log(` ${chalk26.green("\u2713")} ${path38.relative(dir, outputPath)}`);
12581
12659
  console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
12582
12660
  console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
12583
12661
  } catch (err) {
@@ -12630,7 +12708,7 @@ async function bootstrapCommand() {
12630
12708
 
12631
12709
  // src/commands/uninstall.ts
12632
12710
  import fs48 from "fs";
12633
- import path38 from "path";
12711
+ import path39 from "path";
12634
12712
  import chalk28 from "chalk";
12635
12713
  import confirm3 from "@inquirer/confirm";
12636
12714
  init_pre_commit_block();
@@ -12638,17 +12716,17 @@ init_builtin_skills();
12638
12716
  init_config();
12639
12717
  var MANAGED_DOC_FILES = [
12640
12718
  "CLAUDE.md",
12641
- path38.join(".github", "copilot-instructions.md"),
12719
+ path39.join(".github", "copilot-instructions.md"),
12642
12720
  "AGENTS.md"
12643
12721
  ];
12644
12722
  var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
12645
- var CURSOR_RULES_DIR = path38.join(".cursor", "rules");
12723
+ var CURSOR_RULES_DIR = path39.join(".cursor", "rules");
12646
12724
  function removeCaliberCursorRules() {
12647
12725
  const removed = [];
12648
12726
  if (!fs48.existsSync(CURSOR_RULES_DIR)) return removed;
12649
12727
  for (const file of fs48.readdirSync(CURSOR_RULES_DIR)) {
12650
12728
  if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
12651
- const fullPath = path38.join(CURSOR_RULES_DIR, file);
12729
+ const fullPath = path39.join(CURSOR_RULES_DIR, file);
12652
12730
  fs48.unlinkSync(fullPath);
12653
12731
  removed.push(fullPath);
12654
12732
  }
@@ -12660,7 +12738,7 @@ function removeBuiltinSkills() {
12660
12738
  for (const skillsDir of SKILL_DIRS) {
12661
12739
  if (!fs48.existsSync(skillsDir)) continue;
12662
12740
  for (const name of BUILTIN_SKILL_NAMES) {
12663
- const skillDir = path38.join(skillsDir, name);
12741
+ const skillDir = path39.join(skillsDir, name);
12664
12742
  if (fs48.existsSync(skillDir)) {
12665
12743
  fs48.rmSync(skillDir, { recursive: true });
12666
12744
  removed.push(skillDir);
@@ -12764,7 +12842,7 @@ async function uninstallCommand(options) {
12764
12842
  if (removeConfig) {
12765
12843
  fs48.unlinkSync(configPath);
12766
12844
  console.log(` ${chalk28.red("\u2717")} ${configPath}`);
12767
- const configDir = path38.dirname(configPath);
12845
+ const configDir = path39.dirname(configPath);
12768
12846
  try {
12769
12847
  const remaining = fs48.readdirSync(configDir);
12770
12848
  if (remaining.length === 0) fs48.rmdirSync(configDir);
@@ -12778,9 +12856,9 @@ async function uninstallCommand(options) {
12778
12856
  }
12779
12857
 
12780
12858
  // src/cli.ts
12781
- var __dirname = path39.dirname(fileURLToPath(import.meta.url));
12859
+ var __dirname = path40.dirname(fileURLToPath(import.meta.url));
12782
12860
  var pkg = JSON.parse(
12783
- fs49.readFileSync(path39.resolve(__dirname, "..", "package.json"), "utf-8")
12861
+ fs49.readFileSync(path40.resolve(__dirname, "..", "package.json"), "utf-8")
12784
12862
  );
12785
12863
  var program = new Command();
12786
12864
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -12872,16 +12950,14 @@ learn.command("add <content>").description("Add a learning directly (used by age
12872
12950
 
12873
12951
  // src/utils/version-check.ts
12874
12952
  import fs50 from "fs";
12875
- import path40 from "path";
12953
+ import path41 from "path";
12876
12954
  import { fileURLToPath as fileURLToPath2 } from "url";
12877
12955
  import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
12878
12956
  import chalk29 from "chalk";
12879
12957
  import ora8 from "ora";
12880
12958
  import confirm4 from "@inquirer/confirm";
12881
- var __dirname_vc = path40.dirname(fileURLToPath2(import.meta.url));
12882
- var pkg2 = JSON.parse(
12883
- fs50.readFileSync(path40.resolve(__dirname_vc, "..", "package.json"), "utf-8")
12884
- );
12959
+ var __dirname_vc = path41.dirname(fileURLToPath2(import.meta.url));
12960
+ var pkg2 = JSON.parse(fs50.readFileSync(path41.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
12885
12961
  function getChannel(version) {
12886
12962
  const match = version.match(/-(dev|next)\./);
12887
12963
  return match ? match[1] : "latest";
@@ -12904,8 +12980,11 @@ function isNewer(registry, current) {
12904
12980
  }
12905
12981
  function getInstalledVersion() {
12906
12982
  try {
12907
- const globalRoot = execSync16("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12908
- const pkgPath = path40.join(globalRoot, "@rely-ai", "caliber", "package.json");
12983
+ const globalRoot = execSync16("npm root -g", {
12984
+ encoding: "utf-8",
12985
+ stdio: ["pipe", "pipe", "pipe"]
12986
+ }).trim();
12987
+ const pkgPath = path41.join(globalRoot, "@rely-ai", "caliber", "package.json");
12909
12988
  return JSON.parse(fs50.readFileSync(pkgPath, "utf-8")).version;
12910
12989
  } catch {
12911
12990
  return null;
@@ -12940,17 +13019,18 @@ Run ${chalk29.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
12940
13019
  );
12941
13020
  return;
12942
13021
  }
12943
- console.log(
12944
- chalk29.yellow(`
12945
- Update available: ${current} -> ${latest}`)
12946
- );
12947
- const shouldUpdate = await confirm4({ message: "Would you like to update now? (Y/n)", default: true });
13022
+ console.log(chalk29.yellow(`
13023
+ Update available: ${current} -> ${latest}`));
13024
+ const shouldUpdate = await confirm4({
13025
+ message: "Would you like to update now? (Y/n)",
13026
+ default: true
13027
+ });
12948
13028
  if (!shouldUpdate) {
12949
13029
  console.log();
12950
13030
  return;
12951
13031
  }
12952
13032
  const tag = channel === "latest" ? latest : channel;
12953
- if (!/^[\w.\-]+$/.test(tag)) return;
13033
+ if (!/^[\w.-]+$/.test(tag)) return;
12954
13034
  const spinner = ora8("Updating caliber...").start();
12955
13035
  try {
12956
13036
  execFileSync3("npm", ["install", "-g", `@rely-ai/caliber@${tag}`], {
@@ -12961,8 +13041,10 @@ Update available: ${current} -> ${latest}`)
12961
13041
  const installed = getInstalledVersion();
12962
13042
  if (installed !== latest) {
12963
13043
  spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
12964
- console.log(chalk29.yellow(`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
12965
- `));
13044
+ console.log(
13045
+ chalk29.yellow(`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
13046
+ `)
13047
+ );
12966
13048
  return;
12967
13049
  }
12968
13050
  spinner.succeed(chalk29.green(`Updated to ${latest}`));