@rely-ai/caliber 1.31.0-dev.1774710550 → 1.31.0-dev.1774711826

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +854 -744
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -230,144 +230,560 @@ var init_types = __esm({
230
230
  }
231
231
  });
232
232
 
233
- // src/utils/editor.ts
234
- import { execSync as execSync14, spawn as spawn3 } from "child_process";
235
- import fs27 from "fs";
236
- import path23 from "path";
237
- import os6 from "os";
238
- function getEmptyFilePath(proposedPath) {
239
- fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
240
- const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
241
- fs27.writeFileSync(tempPath, "");
242
- return tempPath;
233
+ // src/writers/pre-commit-block.ts
234
+ var pre_commit_block_exports = {};
235
+ __export(pre_commit_block_exports, {
236
+ appendLearningsBlock: () => appendLearningsBlock,
237
+ appendPreCommitBlock: () => appendPreCommitBlock,
238
+ appendSyncBlock: () => appendSyncBlock,
239
+ getCursorLearningsRule: () => getCursorLearningsRule,
240
+ getCursorPreCommitRule: () => getCursorPreCommitRule,
241
+ getCursorSyncRule: () => getCursorSyncRule,
242
+ hasLearningsBlock: () => hasLearningsBlock,
243
+ hasPreCommitBlock: () => hasPreCommitBlock,
244
+ hasSyncBlock: () => hasSyncBlock
245
+ });
246
+ function getPreCommitBlock() {
247
+ const bin = resolveCaliber();
248
+ return `${BLOCK_START}
249
+ ## Before Committing
250
+
251
+ **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
252
+
253
+ First, check if the pre-commit hook is already installed:
254
+ \`\`\`bash
255
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
256
+ \`\`\`
257
+
258
+ - If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
259
+ - If **no-hook**: run Caliber manually before committing:
260
+ 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
261
+ 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
262
+ 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
263
+
264
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
265
+ ${BLOCK_END}`;
243
266
  }
244
- function commandExists(cmd) {
245
- try {
246
- const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
247
- execSync14(check, { stdio: "ignore" });
248
- return true;
249
- } catch {
250
- return false;
251
- }
267
+ function getCursorRuleContent() {
268
+ const bin = resolveCaliber();
269
+ return `---
270
+ description: Run Caliber sync before git commits to keep agent configs up to date
271
+ alwaysApply: true
272
+ ---
273
+ Before every git commit, check if the Caliber pre-commit hook is installed:
274
+ Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
275
+
276
+ If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
277
+ If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
278
+
279
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
280
+ `;
252
281
  }
253
- function detectAvailableEditors() {
254
- const methods = [];
255
- if (commandExists("cursor")) methods.push("cursor");
256
- if (commandExists("code")) methods.push("vscode");
257
- methods.push("terminal");
258
- return methods;
282
+ function hasPreCommitBlock(content) {
283
+ return content.includes(BLOCK_START);
259
284
  }
260
- function openDiffsInEditor(editor, files) {
261
- const cmd = editor === "cursor" ? "cursor" : "code";
262
- for (const file of files) {
263
- try {
264
- const leftPath = file.originalPath ?? getEmptyFilePath(file.proposedPath);
265
- if (IS_WINDOWS3) {
266
- const quote = (s) => `"${s}"`;
267
- spawn3([cmd, "--diff", quote(leftPath), quote(file.proposedPath)].join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
268
- } else {
269
- spawn3(cmd, ["--diff", leftPath, file.proposedPath], { stdio: "ignore", detached: true }).unref();
270
- }
271
- } catch {
272
- continue;
273
- }
274
- }
285
+ function appendPreCommitBlock(content) {
286
+ if (hasPreCommitBlock(content)) return content;
287
+ const trimmed = content.trimEnd();
288
+ return trimmed + "\n\n" + getPreCommitBlock() + "\n";
275
289
  }
276
- var IS_WINDOWS3, DIFF_TEMP_DIR;
277
- var init_editor = __esm({
278
- "src/utils/editor.ts"() {
290
+ function getCursorPreCommitRule() {
291
+ return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
292
+ }
293
+ function hasLearningsBlock(content) {
294
+ return content.includes(LEARNINGS_BLOCK_START);
295
+ }
296
+ function appendLearningsBlock(content) {
297
+ if (hasLearningsBlock(content)) return content;
298
+ const trimmed = content.trimEnd();
299
+ return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
300
+ }
301
+ function getCursorLearningsRule() {
302
+ return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
303
+ }
304
+ function getSyncBlock() {
305
+ const bin = resolveCaliber();
306
+ return `${SYNC_BLOCK_START}
307
+ ## Context Sync
308
+
309
+ This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
310
+ Configs update automatically before each commit via \`${bin} refresh\`.
311
+ If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
312
+ ${SYNC_BLOCK_END}`;
313
+ }
314
+ function hasSyncBlock(content) {
315
+ return content.includes(SYNC_BLOCK_START);
316
+ }
317
+ function appendSyncBlock(content) {
318
+ if (hasSyncBlock(content)) return content;
319
+ const trimmed = content.trimEnd();
320
+ return trimmed + "\n\n" + getSyncBlock() + "\n";
321
+ }
322
+ function getCursorSyncContent() {
323
+ const bin = resolveCaliber();
324
+ return `---
325
+ description: This project uses Caliber for automatic AI agent context sync
326
+ alwaysApply: true
327
+ ---
328
+ This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
329
+ in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
330
+ If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
331
+ `;
332
+ }
333
+ function getCursorSyncRule() {
334
+ return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
335
+ }
336
+ var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME;
337
+ var init_pre_commit_block = __esm({
338
+ "src/writers/pre-commit-block.ts"() {
279
339
  "use strict";
280
- IS_WINDOWS3 = process.platform === "win32";
281
- DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
340
+ init_resolve_caliber();
341
+ BLOCK_START = "<!-- caliber:managed:pre-commit -->";
342
+ BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
343
+ MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
344
+ CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
345
+ LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
346
+ LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
347
+ LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
348
+ ## Session Learnings
349
+
350
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
351
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
352
+ ${LEARNINGS_BLOCK_END}`;
353
+ CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
354
+ CURSOR_LEARNINGS_CONTENT = `---
355
+ description: Reference session-learned patterns from CALIBER_LEARNINGS.md
356
+ alwaysApply: true
357
+ ---
358
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
359
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
360
+ `;
361
+ SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
362
+ SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
363
+ CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
282
364
  }
283
365
  });
284
366
 
285
- // src/utils/review.ts
286
- var review_exports = {};
287
- __export(review_exports, {
288
- openReview: () => openReview,
289
- promptReviewMethod: () => promptReviewMethod,
290
- promptWantsReview: () => promptWantsReview
367
+ // src/lib/builtin-skills.ts
368
+ var builtin_skills_exports = {};
369
+ __export(builtin_skills_exports, {
370
+ BUILTIN_SKILLS: () => BUILTIN_SKILLS,
371
+ FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
372
+ SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
373
+ SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
374
+ buildSkillContent: () => buildSkillContent,
375
+ ensureBuiltinSkills: () => ensureBuiltinSkills
291
376
  });
292
- import chalk10 from "chalk";
293
- import fs28 from "fs";
294
- import select4 from "@inquirer/select";
295
- import { createTwoFilesPatch } from "diff";
296
- async function promptWantsReview() {
297
- return select4({
298
- message: "Would you like to review the diffs before deciding?",
299
- choices: [
300
- { name: "Yes, show me the diffs", value: true },
301
- { name: "No, continue", value: false }
302
- ]
303
- });
377
+ import fs17 from "fs";
378
+ import path16 from "path";
379
+ function buildSkillContent(skill) {
380
+ const frontmatter = `---
381
+ name: ${skill.name}
382
+ description: ${skill.description}
383
+ ---
384
+
385
+ `;
386
+ return frontmatter + skill.content;
304
387
  }
305
- async function promptReviewMethod() {
306
- const available = detectAvailableEditors();
307
- if (available.length === 1) return "terminal";
308
- const choices = available.map((method) => {
309
- switch (method) {
310
- case "cursor":
311
- return { name: "Cursor (diff view)", value: "cursor" };
312
- case "vscode":
313
- return { name: "VS Code (diff view)", value: "vscode" };
314
- case "terminal":
315
- return { name: "Terminal", value: "terminal" };
316
- }
317
- });
318
- return select4({ message: "How would you like to review the changes?", choices });
388
+ function getFindSkillsContent() {
389
+ const bin = resolveCaliber();
390
+ return `# Find Skills
391
+
392
+ Search the public skill registry for community-contributed skills
393
+ relevant to the user's current task and install them into this project.
394
+
395
+ ## Instructions
396
+
397
+ 1. Identify the key technologies, frameworks, or task types from the
398
+ user's request that might have community skills available
399
+ 2. Ask the user: "Would you like me to search for community skills
400
+ for [identified technologies]?"
401
+ 3. If the user agrees, run:
402
+ \`\`\`bash
403
+ ${bin} skills --query "<relevant terms>"
404
+ \`\`\`
405
+ This outputs the top 5 matching skills with scores and descriptions.
406
+ 4. Present the results to the user and ask which ones to install
407
+ 5. Install the selected skills:
408
+ \`\`\`bash
409
+ ${bin} skills --install <slug1>,<slug2>
410
+ \`\`\`
411
+ 6. Read the installed SKILL.md files to load them into your current
412
+ context so you can use them immediately in this session
413
+ 7. Summarize what was installed and continue with the user's task
414
+
415
+ ## Examples
416
+
417
+ User: "let's build a web app using React"
418
+ -> "I notice you want to work with React. Would you like me to search
419
+ for community skills that could help with React development?"
420
+ -> If yes: run \`${bin} skills --query "react frontend"\`
421
+ -> Show the user the results, ask which to install
422
+ -> Run \`${bin} skills --install <selected-slugs>\`
423
+ -> Read the installed files and continue
424
+
425
+ User: "help me set up Docker for this project"
426
+ -> "Would you like me to search for Docker-related skills?"
427
+ -> If yes: run \`${bin} skills --query "docker deployment"\`
428
+
429
+ User: "I need to write tests for this Python ML pipeline"
430
+ -> "Would you like me to find skills for Python ML testing?"
431
+ -> If yes: run \`${bin} skills --query "python machine-learning testing"\`
432
+
433
+ ## When NOT to trigger
434
+
435
+ - The user is working within an already well-configured area
436
+ - You already suggested skills for this technology in this session
437
+ - The user is in the middle of urgent debugging or time-sensitive work
438
+ - The technology is too generic (e.g. just "code" or "programming")
439
+ `;
319
440
  }
320
- async function openReview(method, stagedFiles) {
321
- if (method === "cursor" || method === "vscode") {
322
- openDiffsInEditor(method, stagedFiles.map((f) => ({
323
- originalPath: f.originalPath,
324
- proposedPath: f.proposedPath
325
- })));
326
- console.log(chalk10.dim(" Diffs opened in your editor.\n"));
327
- return;
328
- }
329
- const fileInfos = stagedFiles.map((file) => {
330
- const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
331
- const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
332
- const patch = createTwoFilesPatch(
333
- file.isNew ? "/dev/null" : file.relativePath,
334
- file.relativePath,
335
- current,
336
- proposed
337
- );
338
- let added = 0, removed = 0;
339
- for (const line of patch.split("\n")) {
340
- if (line.startsWith("+") && !line.startsWith("+++")) added++;
341
- if (line.startsWith("-") && !line.startsWith("---")) removed++;
441
+ function getSaveLearningContent() {
442
+ const bin = resolveCaliber();
443
+ return `# Save Learning
444
+
445
+ Save a user's instruction or preference as a persistent learning that
446
+ will be applied in all future sessions on this project.
447
+
448
+ ## Instructions
449
+
450
+ 1. Detect when the user gives an instruction to remember, such as:
451
+ - "remember this", "save this", "always do X", "never do Y"
452
+ - "from now on", "going forward", "in this project we..."
453
+ - Any stated convention, preference, or rule
454
+ 2. Refine the instruction into a clean, actionable learning bullet with
455
+ an appropriate type prefix:
456
+ - \`**[convention]**\` \u2014 coding style, workflow, git conventions
457
+ - \`**[pattern]**\` \u2014 reusable code patterns
458
+ - \`**[anti-pattern]**\` \u2014 things to avoid
459
+ - \`**[preference]**\` \u2014 personal/team preferences
460
+ - \`**[context]**\` \u2014 project-specific context
461
+ 3. Show the refined learning to the user and ask for confirmation
462
+ 4. If confirmed, run:
463
+ \`\`\`bash
464
+ ${bin} learn add "<refined learning>"
465
+ \`\`\`
466
+ For personal preferences (not project-level), add \`--personal\`:
467
+ \`\`\`bash
468
+ ${bin} learn add --personal "<refined learning>"
469
+ \`\`\`
470
+ 5. Stage the learnings file for the next commit:
471
+ \`\`\`bash
472
+ git add CALIBER_LEARNINGS.md
473
+ \`\`\`
474
+
475
+ ## Examples
476
+
477
+ User: "when developing features, push to next branch not master, remember it"
478
+ -> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
479
+ -> "I'll save this as a project learning:
480
+ **[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
481
+ Save for future sessions?"
482
+ -> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
483
+ -> Run \`git add CALIBER_LEARNINGS.md\`
484
+
485
+ User: "always use bun instead of npm"
486
+ -> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
487
+ -> Confirm and save
488
+
489
+ User: "never use any in TypeScript, use unknown instead"
490
+ -> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
491
+ -> Confirm and save
492
+
493
+ ## When NOT to trigger
494
+
495
+ - The user is giving a one-time instruction for the current task only
496
+ - The instruction is too vague to be actionable
497
+ - The user explicitly says "just for now" or "only this time"
498
+ `;
499
+ }
500
+ function getSetupCaliberContent() {
501
+ const bin = resolveCaliber();
502
+ return `# Setup Caliber
503
+
504
+ Set up Caliber for automatic AI agent context sync on this project.
505
+ Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
506
+ in sync with your codebase \u2014 automatically on every commit.
507
+
508
+ ## Instructions
509
+
510
+ 1. Check if Caliber is already set up locally:
511
+ \`\`\`bash
512
+ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
513
+ \`\`\`
514
+
515
+ 2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
516
+ Your agent configs update automatically on every commit." Then stop.
517
+
518
+ 3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
519
+ or for your team?"
520
+
521
+ ### Solo Setup
522
+
523
+ a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
524
+ on every commit."
525
+ b. Install the pre-commit hook:
526
+ \`\`\`bash
527
+ ${bin} hooks --install
528
+ \`\`\`
529
+ c. Run a refresh to make sure configs are current:
530
+ \`\`\`bash
531
+ ${bin} refresh
532
+ \`\`\`
533
+ d. Tell the user what happened and that configs will auto-sync on every commit.
534
+
535
+ ### Team Setup
536
+
537
+ a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
538
+ plus a GitHub Action for automatic sync across the team."
539
+ b. Install the local pre-commit hook:
540
+ \`\`\`bash
541
+ ${bin} hooks --install
542
+ \`\`\`
543
+ c. Run a refresh:
544
+ \`\`\`bash
545
+ ${bin} refresh
546
+ \`\`\`
547
+ d. Create the GitHub Action workflow for nightly sync. Write this file to
548
+ \`.github/workflows/caliber-sync.yml\`:
549
+ \`\`\`yaml
550
+ name: Caliber Sync
551
+ on:
552
+ schedule:
553
+ - cron: '0 3 * * 1-5'
554
+ pull_request:
555
+ types: [opened, synchronize]
556
+ workflow_dispatch:
557
+ jobs:
558
+ sync:
559
+ runs-on: ubuntu-latest
560
+ steps:
561
+ - uses: actions/checkout@v4
562
+ - uses: caliber-ai-org/ai-setup@v1
563
+ with:
564
+ mode: sync
565
+ auto-refresh: true
566
+ comment: true
567
+ github-token: \${{ secrets.GITHUB_TOKEN }}
568
+ env:
569
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
570
+ \`\`\`
571
+ e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
572
+ to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
573
+ workflow file. From now on, every PR gets a Caliber sync check and configs
574
+ refresh nightly."
575
+ f. Offer to commit the workflow file and the updated configs.
576
+
577
+ ### If \`${bin}\` is not found
578
+
579
+ Install it first:
580
+ \`\`\`bash
581
+ npm install -g @rely-ai/caliber
582
+ \`\`\`
583
+ Or use npx (no install needed):
584
+ \`\`\`bash
585
+ npx @rely-ai/caliber hooks --install
586
+ \`\`\`
587
+
588
+ ## When to trigger
589
+
590
+ - When a developer first opens this project and Caliber hooks are not installed
591
+ - When the user asks about keeping agent configs up to date
592
+ - When the user asks "how do I set up Caliber" or "what is Caliber"
593
+
594
+ ## When NOT to trigger
595
+
596
+ - Hooks are already installed and working
597
+ - The user is in the middle of time-sensitive work
598
+ `;
599
+ }
600
+ function ensureBuiltinSkills() {
601
+ const written = [];
602
+ for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
603
+ if (!fs17.existsSync(platformDir)) continue;
604
+ for (const skill of BUILTIN_SKILLS) {
605
+ const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
606
+ if (fs17.existsSync(skillPath)) continue;
607
+ fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
608
+ fs17.writeFileSync(skillPath, buildSkillContent(skill));
609
+ written.push(skillPath);
342
610
  }
343
- return {
344
- relativePath: file.relativePath,
345
- isNew: file.isNew,
346
- added,
347
- removed,
348
- lines: proposed.split("\n").length,
349
- patch
611
+ }
612
+ return written;
613
+ }
614
+ var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS;
615
+ var init_builtin_skills = __esm({
616
+ "src/lib/builtin-skills.ts"() {
617
+ "use strict";
618
+ init_resolve_caliber();
619
+ FIND_SKILLS_SKILL = {
620
+ name: "find-skills",
621
+ 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.",
622
+ get content() {
623
+ return getFindSkillsContent();
624
+ }
350
625
  };
351
- });
352
- await interactiveDiffExplorer(fileInfos);
626
+ SAVE_LEARNING_SKILL = {
627
+ name: "save-learning",
628
+ 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.",
629
+ get content() {
630
+ return getSaveLearningContent();
631
+ }
632
+ };
633
+ SETUP_CALIBER_SKILL = {
634
+ name: "setup-caliber",
635
+ 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.",
636
+ get content() {
637
+ return getSetupCaliberContent();
638
+ }
639
+ };
640
+ BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
641
+ PLATFORM_CONFIGS = [
642
+ { platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
643
+ { platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
644
+ { platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
645
+ ];
646
+ }
647
+ });
648
+
649
+ // src/utils/editor.ts
650
+ import { execSync as execSync14, spawn as spawn3 } from "child_process";
651
+ import fs27 from "fs";
652
+ import path23 from "path";
653
+ import os6 from "os";
654
+ function getEmptyFilePath(proposedPath) {
655
+ fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
656
+ const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
657
+ fs27.writeFileSync(tempPath, "");
658
+ return tempPath;
353
659
  }
354
- async function interactiveDiffExplorer(files) {
355
- if (!process.stdin.isTTY) {
356
- for (const f of files) {
357
- const icon = f.isNew ? chalk10.green("+") : chalk10.yellow("~");
358
- const stats = f.isNew ? chalk10.dim(`${f.lines} lines`) : `${chalk10.green(`+${f.added}`)} ${chalk10.red(`-${f.removed}`)}`;
359
- console.log(` ${icon} ${f.relativePath} ${stats}`);
660
+ function commandExists(cmd) {
661
+ try {
662
+ const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
663
+ execSync14(check, { stdio: "ignore" });
664
+ return true;
665
+ } catch {
666
+ return false;
667
+ }
668
+ }
669
+ function detectAvailableEditors() {
670
+ const methods = [];
671
+ if (commandExists("cursor")) methods.push("cursor");
672
+ if (commandExists("code")) methods.push("vscode");
673
+ methods.push("terminal");
674
+ return methods;
675
+ }
676
+ function openDiffsInEditor(editor, files) {
677
+ const cmd = editor === "cursor" ? "cursor" : "code";
678
+ for (const file of files) {
679
+ try {
680
+ const leftPath = file.originalPath ?? getEmptyFilePath(file.proposedPath);
681
+ if (IS_WINDOWS3) {
682
+ const quote = (s) => `"${s}"`;
683
+ spawn3([cmd, "--diff", quote(leftPath), quote(file.proposedPath)].join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
684
+ } else {
685
+ spawn3(cmd, ["--diff", leftPath, file.proposedPath], { stdio: "ignore", detached: true }).unref();
686
+ }
687
+ } catch {
688
+ continue;
360
689
  }
361
- console.log("");
362
- return;
363
690
  }
364
- const { stdin, stdout } = process;
365
- let cursor = 0;
366
- let viewing = null;
367
- let scrollOffset = 0;
368
- let lineCount = 0;
369
- function getTermHeight() {
370
- return (stdout.rows || 24) - 4;
691
+ }
692
+ var IS_WINDOWS3, DIFF_TEMP_DIR;
693
+ var init_editor = __esm({
694
+ "src/utils/editor.ts"() {
695
+ "use strict";
696
+ IS_WINDOWS3 = process.platform === "win32";
697
+ DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
698
+ }
699
+ });
700
+
701
+ // src/utils/review.ts
702
+ var review_exports = {};
703
+ __export(review_exports, {
704
+ openReview: () => openReview,
705
+ promptReviewMethod: () => promptReviewMethod,
706
+ promptWantsReview: () => promptWantsReview
707
+ });
708
+ import chalk10 from "chalk";
709
+ import fs28 from "fs";
710
+ import select4 from "@inquirer/select";
711
+ import { createTwoFilesPatch } from "diff";
712
+ async function promptWantsReview() {
713
+ return select4({
714
+ message: "Would you like to review the diffs before deciding?",
715
+ choices: [
716
+ { name: "Yes, show me the diffs", value: true },
717
+ { name: "No, continue", value: false }
718
+ ]
719
+ });
720
+ }
721
+ async function promptReviewMethod() {
722
+ const available = detectAvailableEditors();
723
+ if (available.length === 1) return "terminal";
724
+ const choices = available.map((method) => {
725
+ switch (method) {
726
+ case "cursor":
727
+ return { name: "Cursor (diff view)", value: "cursor" };
728
+ case "vscode":
729
+ return { name: "VS Code (diff view)", value: "vscode" };
730
+ case "terminal":
731
+ return { name: "Terminal", value: "terminal" };
732
+ }
733
+ });
734
+ return select4({ message: "How would you like to review the changes?", choices });
735
+ }
736
+ async function openReview(method, stagedFiles) {
737
+ if (method === "cursor" || method === "vscode") {
738
+ openDiffsInEditor(method, stagedFiles.map((f) => ({
739
+ originalPath: f.originalPath,
740
+ proposedPath: f.proposedPath
741
+ })));
742
+ console.log(chalk10.dim(" Diffs opened in your editor.\n"));
743
+ return;
744
+ }
745
+ const fileInfos = stagedFiles.map((file) => {
746
+ const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
747
+ const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
748
+ const patch = createTwoFilesPatch(
749
+ file.isNew ? "/dev/null" : file.relativePath,
750
+ file.relativePath,
751
+ current,
752
+ proposed
753
+ );
754
+ let added = 0, removed = 0;
755
+ for (const line of patch.split("\n")) {
756
+ if (line.startsWith("+") && !line.startsWith("+++")) added++;
757
+ if (line.startsWith("-") && !line.startsWith("---")) removed++;
758
+ }
759
+ return {
760
+ relativePath: file.relativePath,
761
+ isNew: file.isNew,
762
+ added,
763
+ removed,
764
+ lines: proposed.split("\n").length,
765
+ patch
766
+ };
767
+ });
768
+ await interactiveDiffExplorer(fileInfos);
769
+ }
770
+ async function interactiveDiffExplorer(files) {
771
+ if (!process.stdin.isTTY) {
772
+ for (const f of files) {
773
+ const icon = f.isNew ? chalk10.green("+") : chalk10.yellow("~");
774
+ const stats = f.isNew ? chalk10.dim(`${f.lines} lines`) : `${chalk10.green(`+${f.added}`)} ${chalk10.red(`-${f.removed}`)}`;
775
+ console.log(` ${icon} ${f.relativePath} ${stats}`);
776
+ }
777
+ console.log("");
778
+ return;
779
+ }
780
+ const { stdin, stdout } = process;
781
+ let cursor = 0;
782
+ let viewing = null;
783
+ let scrollOffset = 0;
784
+ let lineCount = 0;
785
+ function getTermHeight() {
786
+ return (stdout.rows || 24) - 4;
371
787
  }
372
788
  function renderFileList() {
373
789
  const lines = [];
@@ -4385,610 +4801,232 @@ Project dependencies (${allDeps.length}):`);
4385
4801
  }
4386
4802
  if (prompt) parts.push(`
4387
4803
  User instructions: ${prompt}`);
4388
- if (fingerprint.codeAnalysis) {
4389
- const ca = fingerprint.codeAnalysis;
4390
- const basePrompt = parts.join("\n");
4391
- const maxPromptTokens = getMaxPromptTokens();
4392
- const baseTokens = estimateTokens(basePrompt);
4393
- const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
4394
- const codeLines = [];
4395
- let codeChars = 0;
4396
- const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
4397
- codeLines.push(introLine);
4398
- let runningCodeLen = introLine.length;
4399
- const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
4400
- let includedFiles = 0;
4401
- for (const f of sortedFiles) {
4402
- const entry = `[${f.path}]
4403
- ${f.content}
4404
- `;
4405
- const projectedLen = runningCodeLen + 1 + entry.length;
4406
- if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
4407
- codeLines.push(entry);
4408
- codeChars += f.content.length;
4409
- runningCodeLen = projectedLen;
4410
- includedFiles++;
4411
- }
4412
- const includedTokens = Math.ceil(codeChars / 4);
4413
- let header;
4414
- if (includedFiles < ca.files.length) {
4415
- const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
4416
- header = `
4417
- --- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
4418
- } else if (ca.truncated) {
4419
- const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
4420
- header = `
4421
- --- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
4422
- } else {
4423
- header = `
4424
- --- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
4425
- }
4426
- parts.push(header);
4427
- parts.push(codeLines.join("\n"));
4428
- }
4429
- if (fingerprint.sources?.length) {
4430
- parts.push(formatSourcesForPrompt(fingerprint.sources));
4431
- }
4432
- return parts.join("\n");
4433
- }
4434
-
4435
- // src/writers/index.ts
4436
- import fs19 from "fs";
4437
-
4438
- // src/writers/claude/index.ts
4439
- import fs12 from "fs";
4440
- import path11 from "path";
4441
-
4442
- // src/writers/pre-commit-block.ts
4443
- init_resolve_caliber();
4444
- var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
4445
- var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
4446
- var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
4447
- function getPreCommitBlock() {
4448
- const bin = resolveCaliber();
4449
- return `${BLOCK_START}
4450
- ## Before Committing
4451
-
4452
- **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
4453
-
4454
- First, check if the pre-commit hook is already installed:
4455
- \`\`\`bash
4456
- grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
4457
- \`\`\`
4458
-
4459
- - 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."
4460
- - If **no-hook**: run Caliber manually before committing:
4461
- 1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
4462
- 2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
4463
- 3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
4464
-
4465
- If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
4466
- ${BLOCK_END}`;
4467
- }
4468
- var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
4469
- function getCursorRuleContent() {
4470
- const bin = resolveCaliber();
4471
- return `---
4472
- description: Run Caliber sync before git commits to keep agent configs up to date
4473
- alwaysApply: true
4474
- ---
4475
- Before every git commit, check if the Caliber pre-commit hook is installed:
4476
- Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
4477
-
4478
- If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
4479
- If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
4480
-
4481
- If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
4482
- `;
4483
- }
4484
- function hasPreCommitBlock(content) {
4485
- return content.includes(BLOCK_START);
4486
- }
4487
- function appendPreCommitBlock(content) {
4488
- if (hasPreCommitBlock(content)) return content;
4489
- const trimmed = content.trimEnd();
4490
- return trimmed + "\n\n" + getPreCommitBlock() + "\n";
4491
- }
4492
- function getCursorPreCommitRule() {
4493
- return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
4494
- }
4495
- var LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
4496
- var LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
4497
- var LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
4498
- ## Session Learnings
4499
-
4500
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
4501
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
4502
- ${LEARNINGS_BLOCK_END}`;
4503
- var CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
4504
- var CURSOR_LEARNINGS_CONTENT = `---
4505
- description: Reference session-learned patterns from CALIBER_LEARNINGS.md
4506
- alwaysApply: true
4507
- ---
4508
- Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
4509
- These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
4510
- `;
4511
- function hasLearningsBlock(content) {
4512
- return content.includes(LEARNINGS_BLOCK_START);
4513
- }
4514
- function appendLearningsBlock(content) {
4515
- if (hasLearningsBlock(content)) return content;
4516
- const trimmed = content.trimEnd();
4517
- return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
4518
- }
4519
- function getCursorLearningsRule() {
4520
- return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
4521
- }
4522
- var SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
4523
- var SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
4524
- function getSyncBlock() {
4525
- const bin = resolveCaliber();
4526
- return `${SYNC_BLOCK_START}
4527
- ## Context Sync
4528
-
4529
- This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
4530
- Configs update automatically before each commit via \`${bin} refresh\`.
4531
- If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
4532
- ${SYNC_BLOCK_END}`;
4533
- }
4534
- function hasSyncBlock(content) {
4535
- return content.includes(SYNC_BLOCK_START);
4536
- }
4537
- function appendSyncBlock(content) {
4538
- if (hasSyncBlock(content)) return content;
4539
- const trimmed = content.trimEnd();
4540
- return trimmed + "\n\n" + getSyncBlock() + "\n";
4541
- }
4542
- var CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
4543
- function getCursorSyncContent() {
4544
- const bin = resolveCaliber();
4545
- return `---
4546
- description: This project uses Caliber for automatic AI agent context sync
4547
- alwaysApply: true
4548
- ---
4549
- This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
4550
- in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
4551
- If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
4552
- `;
4553
- }
4554
- function getCursorSyncRule() {
4555
- return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
4556
- }
4557
-
4558
- // src/writers/claude/index.ts
4559
- function writeClaudeConfig(config) {
4560
- const written = [];
4561
- fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
4562
- written.push("CLAUDE.md");
4563
- if (config.skills?.length) {
4564
- for (const skill of config.skills) {
4565
- const skillDir = path11.join(".claude", "skills", skill.name);
4566
- if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
4567
- const skillPath = path11.join(skillDir, "SKILL.md");
4568
- const frontmatter = [
4569
- "---",
4570
- `name: ${skill.name}`,
4571
- `description: ${skill.description}`,
4572
- "---",
4573
- ""
4574
- ].join("\n");
4575
- fs12.writeFileSync(skillPath, frontmatter + skill.content);
4576
- written.push(skillPath);
4577
- }
4578
- }
4579
- if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4580
- let existingServers = {};
4581
- try {
4582
- if (fs12.existsSync(".mcp.json")) {
4583
- const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
4584
- if (existing.mcpServers) existingServers = existing.mcpServers;
4585
- }
4586
- } catch {
4587
- }
4588
- const mergedServers = { ...existingServers, ...config.mcpServers };
4589
- fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4590
- written.push(".mcp.json");
4591
- }
4592
- return written;
4593
- }
4594
-
4595
- // src/writers/cursor/index.ts
4596
- import fs13 from "fs";
4597
- import path12 from "path";
4598
- function writeCursorConfig(config) {
4599
- const written = [];
4600
- if (config.cursorrules) {
4601
- fs13.writeFileSync(".cursorrules", config.cursorrules);
4602
- written.push(".cursorrules");
4603
- }
4604
- const preCommitRule = getCursorPreCommitRule();
4605
- const learningsRule = getCursorLearningsRule();
4606
- const syncRule = getCursorSyncRule();
4607
- const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
4608
- const rulesDir = path12.join(".cursor", "rules");
4609
- if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
4610
- for (const rule of allRules) {
4611
- const rulePath = path12.join(rulesDir, rule.filename);
4612
- fs13.writeFileSync(rulePath, rule.content);
4613
- written.push(rulePath);
4614
- }
4615
- if (config.skills?.length) {
4616
- for (const skill of config.skills) {
4617
- const skillDir = path12.join(".cursor", "skills", skill.name);
4618
- if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
4619
- const skillPath = path12.join(skillDir, "SKILL.md");
4620
- const frontmatter = [
4621
- "---",
4622
- `name: ${skill.name}`,
4623
- `description: ${skill.description}`,
4624
- "---",
4625
- ""
4626
- ].join("\n");
4627
- fs13.writeFileSync(skillPath, frontmatter + skill.content);
4628
- written.push(skillPath);
4629
- }
4630
- }
4631
- if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4632
- const cursorDir = ".cursor";
4633
- if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
4634
- const mcpPath = path12.join(cursorDir, "mcp.json");
4635
- let existingServers = {};
4636
- try {
4637
- if (fs13.existsSync(mcpPath)) {
4638
- const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
4639
- if (existing.mcpServers) existingServers = existing.mcpServers;
4640
- }
4641
- } catch {
4642
- }
4643
- const mergedServers = { ...existingServers, ...config.mcpServers };
4644
- fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
4645
- written.push(mcpPath);
4646
- }
4647
- return written;
4648
- }
4649
-
4650
- // src/writers/codex/index.ts
4651
- import fs14 from "fs";
4652
- import path13 from "path";
4653
- function writeCodexConfig(config) {
4654
- const written = [];
4655
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4656
- written.push("AGENTS.md");
4657
- if (config.skills?.length) {
4658
- for (const skill of config.skills) {
4659
- const skillDir = path13.join(".agents", "skills", skill.name);
4660
- if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
4661
- const skillPath = path13.join(skillDir, "SKILL.md");
4662
- const frontmatter = [
4663
- "---",
4664
- `name: ${skill.name}`,
4665
- `description: ${skill.description}`,
4666
- "---",
4667
- ""
4668
- ].join("\n");
4669
- fs14.writeFileSync(skillPath, frontmatter + skill.content);
4670
- written.push(skillPath);
4671
- }
4672
- }
4673
- return written;
4674
- }
4675
-
4676
- // src/writers/github-copilot/index.ts
4677
- import fs15 from "fs";
4678
- import path14 from "path";
4679
- function writeGithubCopilotConfig(config) {
4680
- const written = [];
4681
- if (config.instructions) {
4682
- fs15.mkdirSync(".github", { recursive: true });
4683
- fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
4684
- written.push(".github/copilot-instructions.md");
4685
- }
4686
- if (config.instructionFiles?.length) {
4687
- const instructionsDir = path14.join(".github", "instructions");
4688
- fs15.mkdirSync(instructionsDir, { recursive: true });
4689
- for (const file of config.instructionFiles) {
4690
- fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
4691
- written.push(`.github/instructions/${file.filename}`);
4692
- }
4693
- }
4694
- return written;
4695
- }
4696
-
4697
- // src/writers/backup.ts
4698
- import fs16 from "fs";
4699
- import path15 from "path";
4700
- function createBackup(files) {
4701
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4702
- const backupDir = path15.join(BACKUPS_DIR, timestamp);
4703
- for (const file of files) {
4704
- if (!fs16.existsSync(file)) continue;
4705
- const dest = path15.join(backupDir, file);
4706
- const destDir = path15.dirname(dest);
4707
- if (!fs16.existsSync(destDir)) {
4708
- fs16.mkdirSync(destDir, { recursive: true });
4709
- }
4710
- fs16.copyFileSync(file, dest);
4711
- }
4712
- return backupDir;
4713
- }
4714
- function restoreBackup(backupDir, file) {
4715
- const backupFile = path15.join(backupDir, file);
4716
- if (!fs16.existsSync(backupFile)) return false;
4717
- const destDir = path15.dirname(file);
4718
- if (!fs16.existsSync(destDir)) {
4719
- fs16.mkdirSync(destDir, { recursive: true });
4720
- }
4721
- fs16.copyFileSync(backupFile, file);
4722
- return true;
4723
- }
4724
-
4725
- // src/lib/builtin-skills.ts
4726
- init_resolve_caliber();
4727
- import fs17 from "fs";
4728
- import path16 from "path";
4729
- function buildSkillContent(skill) {
4730
- const frontmatter = `---
4731
- name: ${skill.name}
4732
- description: ${skill.description}
4733
- ---
4734
-
4735
- `;
4736
- return frontmatter + skill.content;
4737
- }
4738
- function getFindSkillsContent() {
4739
- const bin = resolveCaliber();
4740
- return `# Find Skills
4741
-
4742
- Search the public skill registry for community-contributed skills
4743
- relevant to the user's current task and install them into this project.
4744
-
4745
- ## Instructions
4746
-
4747
- 1. Identify the key technologies, frameworks, or task types from the
4748
- user's request that might have community skills available
4749
- 2. Ask the user: "Would you like me to search for community skills
4750
- for [identified technologies]?"
4751
- 3. If the user agrees, run:
4752
- \`\`\`bash
4753
- ${bin} skills --query "<relevant terms>"
4754
- \`\`\`
4755
- This outputs the top 5 matching skills with scores and descriptions.
4756
- 4. Present the results to the user and ask which ones to install
4757
- 5. Install the selected skills:
4758
- \`\`\`bash
4759
- ${bin} skills --install <slug1>,<slug2>
4760
- \`\`\`
4761
- 6. Read the installed SKILL.md files to load them into your current
4762
- context so you can use them immediately in this session
4763
- 7. Summarize what was installed and continue with the user's task
4764
-
4765
- ## Examples
4766
-
4767
- User: "let's build a web app using React"
4768
- -> "I notice you want to work with React. Would you like me to search
4769
- for community skills that could help with React development?"
4770
- -> If yes: run \`${bin} skills --query "react frontend"\`
4771
- -> Show the user the results, ask which to install
4772
- -> Run \`${bin} skills --install <selected-slugs>\`
4773
- -> Read the installed files and continue
4774
-
4775
- User: "help me set up Docker for this project"
4776
- -> "Would you like me to search for Docker-related skills?"
4777
- -> If yes: run \`${bin} skills --query "docker deployment"\`
4778
-
4779
- User: "I need to write tests for this Python ML pipeline"
4780
- -> "Would you like me to find skills for Python ML testing?"
4781
- -> If yes: run \`${bin} skills --query "python machine-learning testing"\`
4782
-
4783
- ## When NOT to trigger
4784
-
4785
- - The user is working within an already well-configured area
4786
- - You already suggested skills for this technology in this session
4787
- - The user is in the middle of urgent debugging or time-sensitive work
4788
- - The technology is too generic (e.g. just "code" or "programming")
4789
- `;
4790
- }
4791
- function getSaveLearningContent() {
4792
- const bin = resolveCaliber();
4793
- return `# Save Learning
4794
-
4795
- Save a user's instruction or preference as a persistent learning that
4796
- will be applied in all future sessions on this project.
4797
-
4798
- ## Instructions
4799
-
4800
- 1. Detect when the user gives an instruction to remember, such as:
4801
- - "remember this", "save this", "always do X", "never do Y"
4802
- - "from now on", "going forward", "in this project we..."
4803
- - Any stated convention, preference, or rule
4804
- 2. Refine the instruction into a clean, actionable learning bullet with
4805
- an appropriate type prefix:
4806
- - \`**[convention]**\` \u2014 coding style, workflow, git conventions
4807
- - \`**[pattern]**\` \u2014 reusable code patterns
4808
- - \`**[anti-pattern]**\` \u2014 things to avoid
4809
- - \`**[preference]**\` \u2014 personal/team preferences
4810
- - \`**[context]**\` \u2014 project-specific context
4811
- 3. Show the refined learning to the user and ask for confirmation
4812
- 4. If confirmed, run:
4813
- \`\`\`bash
4814
- ${bin} learn add "<refined learning>"
4815
- \`\`\`
4816
- For personal preferences (not project-level), add \`--personal\`:
4817
- \`\`\`bash
4818
- ${bin} learn add --personal "<refined learning>"
4819
- \`\`\`
4820
- 5. Stage the learnings file for the next commit:
4821
- \`\`\`bash
4822
- git add CALIBER_LEARNINGS.md
4823
- \`\`\`
4824
-
4825
- ## Examples
4826
-
4827
- User: "when developing features, push to next branch not master, remember it"
4828
- -> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
4829
- -> "I'll save this as a project learning:
4830
- **[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
4831
- Save for future sessions?"
4832
- -> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
4833
- -> Run \`git add CALIBER_LEARNINGS.md\`
4834
-
4835
- User: "always use bun instead of npm"
4836
- -> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
4837
- -> Confirm and save
4838
-
4839
- User: "never use any in TypeScript, use unknown instead"
4840
- -> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
4841
- -> Confirm and save
4842
-
4843
- ## When NOT to trigger
4844
-
4845
- - The user is giving a one-time instruction for the current task only
4846
- - The instruction is too vague to be actionable
4847
- - The user explicitly says "just for now" or "only this time"
4848
- `;
4849
- }
4850
- var FIND_SKILLS_SKILL = {
4851
- name: "find-skills",
4852
- 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.",
4853
- get content() {
4854
- return getFindSkillsContent();
4855
- }
4856
- };
4857
- var SAVE_LEARNING_SKILL = {
4858
- name: "save-learning",
4859
- 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.",
4860
- get content() {
4861
- return getSaveLearningContent();
4862
- }
4863
- };
4864
- function getSetupCaliberContent() {
4865
- const bin = resolveCaliber();
4866
- return `# Setup Caliber
4867
-
4868
- Set up Caliber for automatic AI agent context sync on this project.
4869
- Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
4870
- in sync with your codebase \u2014 automatically on every commit.
4871
-
4872
- ## Instructions
4873
-
4874
- 1. Check if Caliber is already set up locally:
4875
- \`\`\`bash
4876
- grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
4877
- \`\`\`
4878
-
4879
- 2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
4880
- Your agent configs update automatically on every commit." Then stop.
4881
-
4882
- 3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
4883
- or for your team?"
4884
-
4885
- ### Solo Setup
4886
-
4887
- a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
4888
- on every commit."
4889
- b. Install the pre-commit hook:
4890
- \`\`\`bash
4891
- ${bin} hooks --install
4892
- \`\`\`
4893
- c. Run a refresh to make sure configs are current:
4894
- \`\`\`bash
4895
- ${bin} refresh
4896
- \`\`\`
4897
- d. Tell the user what happened and that configs will auto-sync on every commit.
4898
-
4899
- ### Team Setup
4900
-
4901
- a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
4902
- plus a GitHub Action for automatic sync across the team."
4903
- b. Install the local pre-commit hook:
4904
- \`\`\`bash
4905
- ${bin} hooks --install
4906
- \`\`\`
4907
- c. Run a refresh:
4908
- \`\`\`bash
4909
- ${bin} refresh
4910
- \`\`\`
4911
- d. Create the GitHub Action workflow for nightly sync. Write this file to
4912
- \`.github/workflows/caliber-sync.yml\`:
4913
- \`\`\`yaml
4914
- name: Caliber Sync
4915
- on:
4916
- schedule:
4917
- - cron: '0 3 * * 1-5'
4918
- pull_request:
4919
- types: [opened, synchronize]
4920
- workflow_dispatch:
4921
- jobs:
4922
- sync:
4923
- runs-on: ubuntu-latest
4924
- steps:
4925
- - uses: actions/checkout@v4
4926
- - uses: caliber-ai-org/ai-setup@v1
4927
- with:
4928
- mode: sync
4929
- auto-refresh: true
4930
- comment: true
4931
- github-token: \${{ secrets.GITHUB_TOKEN }}
4932
- env:
4933
- ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
4934
- \`\`\`
4935
- e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
4936
- to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
4937
- workflow file. From now on, every PR gets a Caliber sync check and configs
4938
- refresh nightly."
4939
- f. Offer to commit the workflow file and the updated configs.
4940
-
4941
- ### If \`${bin}\` is not found
4942
-
4943
- Install it first:
4944
- \`\`\`bash
4945
- npm install -g @rely-ai/caliber
4946
- \`\`\`
4947
- Or use npx (no install needed):
4948
- \`\`\`bash
4949
- npx @rely-ai/caliber hooks --install
4950
- \`\`\`
4951
-
4952
- ## When to trigger
4953
-
4954
- - When a developer first opens this project and Caliber hooks are not installed
4955
- - When the user asks about keeping agent configs up to date
4956
- - When the user asks "how do I set up Caliber" or "what is Caliber"
4804
+ if (fingerprint.codeAnalysis) {
4805
+ const ca = fingerprint.codeAnalysis;
4806
+ const basePrompt = parts.join("\n");
4807
+ const maxPromptTokens = getMaxPromptTokens();
4808
+ const baseTokens = estimateTokens(basePrompt);
4809
+ const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
4810
+ const codeLines = [];
4811
+ let codeChars = 0;
4812
+ const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
4813
+ codeLines.push(introLine);
4814
+ let runningCodeLen = introLine.length;
4815
+ const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
4816
+ let includedFiles = 0;
4817
+ for (const f of sortedFiles) {
4818
+ const entry = `[${f.path}]
4819
+ ${f.content}
4820
+ `;
4821
+ const projectedLen = runningCodeLen + 1 + entry.length;
4822
+ if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
4823
+ codeLines.push(entry);
4824
+ codeChars += f.content.length;
4825
+ runningCodeLen = projectedLen;
4826
+ includedFiles++;
4827
+ }
4828
+ const includedTokens = Math.ceil(codeChars / 4);
4829
+ let header;
4830
+ if (includedFiles < ca.files.length) {
4831
+ const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
4832
+ header = `
4833
+ --- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
4834
+ } else if (ca.truncated) {
4835
+ const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
4836
+ header = `
4837
+ --- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
4838
+ } else {
4839
+ header = `
4840
+ --- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
4841
+ }
4842
+ parts.push(header);
4843
+ parts.push(codeLines.join("\n"));
4844
+ }
4845
+ if (fingerprint.sources?.length) {
4846
+ parts.push(formatSourcesForPrompt(fingerprint.sources));
4847
+ }
4848
+ return parts.join("\n");
4849
+ }
4957
4850
 
4958
- ## When NOT to trigger
4851
+ // src/writers/index.ts
4852
+ import fs19 from "fs";
4959
4853
 
4960
- - Hooks are already installed and working
4961
- - The user is in the middle of time-sensitive work
4962
- `;
4854
+ // src/writers/claude/index.ts
4855
+ init_pre_commit_block();
4856
+ import fs12 from "fs";
4857
+ import path11 from "path";
4858
+ function writeClaudeConfig(config) {
4859
+ const written = [];
4860
+ fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
4861
+ written.push("CLAUDE.md");
4862
+ if (config.skills?.length) {
4863
+ for (const skill of config.skills) {
4864
+ const skillDir = path11.join(".claude", "skills", skill.name);
4865
+ if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
4866
+ const skillPath = path11.join(skillDir, "SKILL.md");
4867
+ const frontmatter = [
4868
+ "---",
4869
+ `name: ${skill.name}`,
4870
+ `description: ${skill.description}`,
4871
+ "---",
4872
+ ""
4873
+ ].join("\n");
4874
+ fs12.writeFileSync(skillPath, frontmatter + skill.content);
4875
+ written.push(skillPath);
4876
+ }
4877
+ }
4878
+ if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4879
+ let existingServers = {};
4880
+ try {
4881
+ if (fs12.existsSync(".mcp.json")) {
4882
+ const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
4883
+ if (existing.mcpServers) existingServers = existing.mcpServers;
4884
+ }
4885
+ } catch {
4886
+ }
4887
+ const mergedServers = { ...existingServers, ...config.mcpServers };
4888
+ fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4889
+ written.push(".mcp.json");
4890
+ }
4891
+ return written;
4963
4892
  }
4964
- var SETUP_CALIBER_SKILL = {
4965
- name: "setup-caliber",
4966
- 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.",
4967
- get content() {
4968
- return getSetupCaliberContent();
4893
+
4894
+ // src/writers/cursor/index.ts
4895
+ init_pre_commit_block();
4896
+ import fs13 from "fs";
4897
+ import path12 from "path";
4898
+ function writeCursorConfig(config) {
4899
+ const written = [];
4900
+ if (config.cursorrules) {
4901
+ fs13.writeFileSync(".cursorrules", config.cursorrules);
4902
+ written.push(".cursorrules");
4969
4903
  }
4970
- };
4971
- var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
4972
- var PLATFORM_CONFIGS = [
4973
- { platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
4974
- { platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
4975
- { platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
4976
- ];
4977
- function ensureBuiltinSkills() {
4904
+ const preCommitRule = getCursorPreCommitRule();
4905
+ const learningsRule = getCursorLearningsRule();
4906
+ const syncRule = getCursorSyncRule();
4907
+ const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
4908
+ const rulesDir = path12.join(".cursor", "rules");
4909
+ if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
4910
+ for (const rule of allRules) {
4911
+ const rulePath = path12.join(rulesDir, rule.filename);
4912
+ fs13.writeFileSync(rulePath, rule.content);
4913
+ written.push(rulePath);
4914
+ }
4915
+ if (config.skills?.length) {
4916
+ for (const skill of config.skills) {
4917
+ const skillDir = path12.join(".cursor", "skills", skill.name);
4918
+ if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
4919
+ const skillPath = path12.join(skillDir, "SKILL.md");
4920
+ const frontmatter = [
4921
+ "---",
4922
+ `name: ${skill.name}`,
4923
+ `description: ${skill.description}`,
4924
+ "---",
4925
+ ""
4926
+ ].join("\n");
4927
+ fs13.writeFileSync(skillPath, frontmatter + skill.content);
4928
+ written.push(skillPath);
4929
+ }
4930
+ }
4931
+ if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4932
+ const cursorDir = ".cursor";
4933
+ if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
4934
+ const mcpPath = path12.join(cursorDir, "mcp.json");
4935
+ let existingServers = {};
4936
+ try {
4937
+ if (fs13.existsSync(mcpPath)) {
4938
+ const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
4939
+ if (existing.mcpServers) existingServers = existing.mcpServers;
4940
+ }
4941
+ } catch {
4942
+ }
4943
+ const mergedServers = { ...existingServers, ...config.mcpServers };
4944
+ fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
4945
+ written.push(mcpPath);
4946
+ }
4947
+ return written;
4948
+ }
4949
+
4950
+ // src/writers/codex/index.ts
4951
+ init_pre_commit_block();
4952
+ import fs14 from "fs";
4953
+ import path13 from "path";
4954
+ function writeCodexConfig(config) {
4978
4955
  const written = [];
4979
- for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
4980
- if (!fs17.existsSync(platformDir)) continue;
4981
- for (const skill of BUILTIN_SKILLS) {
4982
- const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
4983
- if (fs17.existsSync(skillPath)) continue;
4984
- fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
4985
- fs17.writeFileSync(skillPath, buildSkillContent(skill));
4956
+ fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4957
+ written.push("AGENTS.md");
4958
+ if (config.skills?.length) {
4959
+ for (const skill of config.skills) {
4960
+ const skillDir = path13.join(".agents", "skills", skill.name);
4961
+ if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
4962
+ const skillPath = path13.join(skillDir, "SKILL.md");
4963
+ const frontmatter = [
4964
+ "---",
4965
+ `name: ${skill.name}`,
4966
+ `description: ${skill.description}`,
4967
+ "---",
4968
+ ""
4969
+ ].join("\n");
4970
+ fs14.writeFileSync(skillPath, frontmatter + skill.content);
4986
4971
  written.push(skillPath);
4987
4972
  }
4988
4973
  }
4989
4974
  return written;
4990
4975
  }
4991
4976
 
4977
+ // src/writers/github-copilot/index.ts
4978
+ init_pre_commit_block();
4979
+ import fs15 from "fs";
4980
+ import path14 from "path";
4981
+ function writeGithubCopilotConfig(config) {
4982
+ const written = [];
4983
+ if (config.instructions) {
4984
+ fs15.mkdirSync(".github", { recursive: true });
4985
+ fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
4986
+ written.push(".github/copilot-instructions.md");
4987
+ }
4988
+ if (config.instructionFiles?.length) {
4989
+ const instructionsDir = path14.join(".github", "instructions");
4990
+ fs15.mkdirSync(instructionsDir, { recursive: true });
4991
+ for (const file of config.instructionFiles) {
4992
+ fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
4993
+ written.push(`.github/instructions/${file.filename}`);
4994
+ }
4995
+ }
4996
+ return written;
4997
+ }
4998
+
4999
+ // src/writers/backup.ts
5000
+ import fs16 from "fs";
5001
+ import path15 from "path";
5002
+ function createBackup(files) {
5003
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5004
+ const backupDir = path15.join(BACKUPS_DIR, timestamp);
5005
+ for (const file of files) {
5006
+ if (!fs16.existsSync(file)) continue;
5007
+ const dest = path15.join(backupDir, file);
5008
+ const destDir = path15.dirname(dest);
5009
+ if (!fs16.existsSync(destDir)) {
5010
+ fs16.mkdirSync(destDir, { recursive: true });
5011
+ }
5012
+ fs16.copyFileSync(file, dest);
5013
+ }
5014
+ return backupDir;
5015
+ }
5016
+ function restoreBackup(backupDir, file) {
5017
+ const backupFile = path15.join(backupDir, file);
5018
+ if (!fs16.existsSync(backupFile)) return false;
5019
+ const destDir = path15.dirname(file);
5020
+ if (!fs16.existsSync(destDir)) {
5021
+ fs16.mkdirSync(destDir, { recursive: true });
5022
+ }
5023
+ fs16.copyFileSync(backupFile, file);
5024
+ return true;
5025
+ }
5026
+
5027
+ // src/writers/index.ts
5028
+ init_builtin_skills();
5029
+
4992
5030
  // src/writers/manifest.ts
4993
5031
  import fs18 from "fs";
4994
5032
  import crypto3 from "crypto";
@@ -5173,6 +5211,7 @@ function cleanupStaging() {
5173
5211
  }
5174
5212
 
5175
5213
  // src/commands/setup-files.ts
5214
+ init_builtin_skills();
5176
5215
  import fs21 from "fs";
5177
5216
  function collectSetupFiles(setup, targetAgent) {
5178
5217
  const files = [];
@@ -6336,6 +6375,7 @@ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
6336
6375
  import { execSync as execSync12 } from "child_process";
6337
6376
  import { join as join7 } from "path";
6338
6377
  init_resolve_caliber();
6378
+ init_pre_commit_block();
6339
6379
  function hasPreCommitHook(dir) {
6340
6380
  try {
6341
6381
  const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
@@ -9115,9 +9155,9 @@ async function initCommand(options) {
9115
9155
  console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
9116
9156
  console.log(title.bold(" How it works:\n"));
9117
9157
  console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
9118
- console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
9119
- console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
9120
- console.log(chalk14.dim(" 4. Finalize Install sync hooks and score your setup\n"));
9158
+ console.log(chalk14.dim(" 2. Setup Detect stack, install sync hooks & skills"));
9159
+ console.log(chalk14.dim(" 3. Generate Audit existing config or generate from scratch"));
9160
+ console.log(chalk14.dim(" 4. Finalize Review changes and score your setup\n"));
9121
9161
  } else {
9122
9162
  console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
9123
9163
  }
@@ -9167,8 +9207,26 @@ async function initCommand(options) {
9167
9207
  console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
9168
9208
  `));
9169
9209
  trackInitAgentSelected(targetAgent, agentAutoDetected);
9210
+ console.log(title.bold(" Step 2/4 \u2014 Setup\n"));
9211
+ const hookResult = installPreCommitHook();
9212
+ if (hookResult.installed) {
9213
+ console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 configs sync on every commit`);
9214
+ } else if (hookResult.alreadyInstalled) {
9215
+ console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 already installed`);
9216
+ }
9217
+ const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
9218
+ for (const agent of targetAgent) {
9219
+ if (agent === "claude" && !fs33.existsSync(".claude")) fs33.mkdirSync(".claude", { recursive: true });
9220
+ if (agent === "cursor" && !fs33.existsSync(".cursor")) fs33.mkdirSync(".cursor", { recursive: true });
9221
+ if (agent === "codex" && !fs33.existsSync(".agents")) fs33.mkdirSync(".agents", { recursive: true });
9222
+ }
9223
+ const skillsWritten = ensureBuiltinSkills2();
9224
+ if (skillsWritten.length > 0) {
9225
+ console.log(` ${chalk14.green("\u2713")} Agent skills \u2014 ${skillsWritten.length} skills installed (setup-caliber, find-skills, save-learning)`);
9226
+ }
9227
+ console.log("");
9170
9228
  let baselineScore = computeLocalScore(process.cwd(), targetAgent);
9171
- console.log(chalk14.dim("\n Current config score:"));
9229
+ console.log(chalk14.dim(" Current config score:"));
9172
9230
  displayScoreSummary(baselineScore);
9173
9231
  if (options.verbose) {
9174
9232
  for (const c of baselineScore.checks) {
@@ -9195,29 +9253,86 @@ async function initCommand(options) {
9195
9253
  ]);
9196
9254
  const passingCount = baselineScore.checks.filter((c) => c.passed).length;
9197
9255
  const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
9256
+ let skipGeneration = false;
9198
9257
  if (hasExistingConfig && baselineScore.score === 100) {
9199
9258
  trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
9200
- console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
9201
- console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n"));
9202
- if (!options.force) return;
9203
- }
9204
- const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
9205
- const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
9206
- trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9207
- if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
9208
- console.log(chalk14.bold.green("\n Your config is fully optimized for LLM generation.\n"));
9209
- console.log(chalk14.dim(" Remaining items need CLI actions:\n"));
9210
- for (const check of allFailingChecks) {
9211
- console.log(chalk14.dim(` \u2022 ${check.name}`));
9212
- if (check.suggestion) {
9213
- console.log(` ${chalk14.hex("#83D1EB")(check.suggestion)}`);
9259
+ console.log(chalk14.bold.green("\n Your config is already optimal.\n"));
9260
+ skipGeneration = !options.force;
9261
+ } else if (hasExistingConfig && !options.force && !options.autoApprove) {
9262
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9263
+ const auditAnswer = await promptInput(" Want Caliber to audit and improve your existing config? (Y/n) ");
9264
+ skipGeneration = auditAnswer.toLowerCase() === "n";
9265
+ } else if (!hasExistingConfig && !options.force && !options.autoApprove) {
9266
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9267
+ const generateAnswer = await promptInput(" Want Caliber to generate agent configs for your project? (Y/n) ");
9268
+ skipGeneration = generateAnswer.toLowerCase() === "n";
9269
+ } else {
9270
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9271
+ }
9272
+ if (skipGeneration) {
9273
+ const {
9274
+ appendPreCommitBlock: appendPreCommitBlock2,
9275
+ appendLearningsBlock: appendLearningsBlock2,
9276
+ appendSyncBlock: appendSyncBlock2,
9277
+ getCursorPreCommitRule: getCursorPreCommitRule2,
9278
+ getCursorLearningsRule: getCursorLearningsRule2,
9279
+ getCursorSyncRule: getCursorSyncRule2
9280
+ } = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
9281
+ const claudeMdPath = "CLAUDE.md";
9282
+ let claudeContent = "";
9283
+ try {
9284
+ claudeContent = fs33.readFileSync(claudeMdPath, "utf-8");
9285
+ } catch {
9286
+ }
9287
+ if (!claudeContent) {
9288
+ claudeContent = `# ${path26.basename(process.cwd())}
9289
+ `;
9290
+ }
9291
+ const updatedClaude = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(claudeContent)));
9292
+ if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
9293
+ fs33.writeFileSync(claudeMdPath, updatedClaude);
9294
+ console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
9295
+ }
9296
+ if (targetAgent.includes("cursor")) {
9297
+ const rulesDir = path26.join(".cursor", "rules");
9298
+ if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
9299
+ for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2()]) {
9300
+ fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
9214
9301
  }
9302
+ console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
9215
9303
  }
9216
- console.log("");
9217
- console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n"));
9304
+ if (targetAgent.includes("github-copilot")) {
9305
+ const copilotPath = path26.join(".github", "copilot-instructions.md");
9306
+ let copilotContent = "";
9307
+ try {
9308
+ copilotContent = fs33.readFileSync(copilotPath, "utf-8");
9309
+ } catch {
9310
+ }
9311
+ if (!copilotContent) {
9312
+ fs33.mkdirSync(".github", { recursive: true });
9313
+ copilotContent = `# ${path26.basename(process.cwd())}
9314
+ `;
9315
+ }
9316
+ const updatedCopilot = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(copilotContent)));
9317
+ if (updatedCopilot !== copilotContent) {
9318
+ fs33.writeFileSync(copilotPath, updatedCopilot);
9319
+ console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
9320
+ }
9321
+ }
9322
+ const sha2 = getCurrentHeadSha();
9323
+ writeState({
9324
+ lastRefreshSha: sha2 ?? "",
9325
+ lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
9326
+ targetAgent
9327
+ });
9328
+ console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
9329
+ console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
9330
+ console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
9218
9331
  return;
9219
9332
  }
9220
- console.log(title.bold(" Step 2/4 \u2014 Engine\n"));
9333
+ const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
9334
+ const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
9335
+ console.log(title.bold("\n Step 3/4 \u2014 Generate\n"));
9221
9336
  const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
9222
9337
  console.log(chalk14.dim(genModelInfo + "\n"));
9223
9338
  if (report) report.markStep("Generation");
@@ -9416,7 +9531,7 @@ async function initCommand(options) {
9416
9531
  report.addJson("Generation: Parsed Config", generatedSetup);
9417
9532
  }
9418
9533
  log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
9419
- console.log(title.bold(" Step 3/4 \u2014 Review\n"));
9534
+ console.log(title.bold(" Step 4/4 \u2014 Finalize\n"));
9420
9535
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
9421
9536
  const staged = stageFiles(setupFiles, process.cwd());
9422
9537
  const totalChanges = staged.newFiles + staged.modifiedFiles;
@@ -9479,7 +9594,6 @@ async function initCommand(options) {
9479
9594
  console.log(chalk14.dim("Declined. No files were modified."));
9480
9595
  return;
9481
9596
  }
9482
- console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
9483
9597
  if (options.dryRun) {
9484
9598
  console.log(chalk14.yellow("\n[Dry run] Would write the following files:"));
9485
9599
  console.log(JSON.stringify(generatedSetup, null, 2));
@@ -9531,12 +9645,6 @@ ${agentRefs.join(" ")}
9531
9645
  throw new Error("__exit__");
9532
9646
  }
9533
9647
  if (fingerprint) ensurePermissions(fingerprint);
9534
- const hookResult = installPreCommitHook();
9535
- if (hookResult.installed) {
9536
- console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 configs sync on every commit`);
9537
- } else if (hookResult.alreadyInstalled) {
9538
- console.log(chalk14.dim(" Pre-commit hook already installed"));
9539
- }
9540
9648
  const sha = getCurrentHeadSha();
9541
9649
  writeState({
9542
9650
  lastRefreshSha: sha ?? "",
@@ -10048,6 +10156,7 @@ function collectDiff(lastSha) {
10048
10156
  }
10049
10157
 
10050
10158
  // src/writers/refresh.ts
10159
+ init_pre_commit_block();
10051
10160
  import fs36 from "fs";
10052
10161
  import path28 from "path";
10053
10162
  function writeRefreshDocs(docs) {
@@ -10371,6 +10480,7 @@ function migrateInlineLearnings() {
10371
10480
  // src/commands/refresh.ts
10372
10481
  init_config();
10373
10482
  init_resolve_caliber();
10483
+ init_builtin_skills();
10374
10484
  function detectSyncedAgents(writtenFiles) {
10375
10485
  const agents = [];
10376
10486
  const joined = writtenFiles.join(" ");