@rely-ai/caliber 1.32.0-dev.1774817730 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +1070 -1630
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -230,615 +230,21 @@ var init_types = __esm({
230
230
  }
231
231
  });
232
232
 
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
- }
262
- }
263
- function getPreCommitBlock(platform = "claude") {
264
- const bin = resolveCaliber();
265
- return `${BLOCK_START}
266
- ## Before Committing
267
-
268
- **IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
269
-
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
- \`\`\`
274
-
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
494
-
495
- User: "help me set up Docker for this project"
496
- -> "Would you like me to search for Docker-related skills?"
497
- -> If yes: run \`${bin} skills --query "docker deployment"\`
498
-
499
- User: "I need to write tests for this Python ML pipeline"
500
- -> "Would you like me to find skills for Python ML testing?"
501
- -> If yes: run \`${bin} skills --query "python machine-learning testing"\`
502
-
503
- ## When NOT to trigger
504
-
505
- - The user is working within an already well-configured area
506
- - You already suggested skills for this technology in this session
507
- - The user is in the middle of urgent debugging or time-sensitive work
508
- - The technology is too generic (e.g. just "code" or "programming")
509
- `;
510
- }
511
- function getSaveLearningContent() {
512
- const bin = resolveCaliber();
513
- return `# Save Learning
514
-
515
- Save a user's instruction or preference as a persistent learning that
516
- will be applied in all future sessions on this project.
517
-
518
- ## Instructions
519
-
520
- 1. Detect when the user gives an instruction to remember, such as:
521
- - "remember this", "save this", "always do X", "never do Y"
522
- - "from now on", "going forward", "in this project we..."
523
- - Any stated convention, preference, or rule
524
- 2. Refine the instruction into a clean, actionable learning bullet with
525
- an appropriate type prefix:
526
- - \`**[convention]**\` \u2014 coding style, workflow, git conventions
527
- - \`**[pattern]**\` \u2014 reusable code patterns
528
- - \`**[anti-pattern]**\` \u2014 things to avoid
529
- - \`**[preference]**\` \u2014 personal/team preferences
530
- - \`**[context]**\` \u2014 project-specific context
531
- 3. Show the refined learning to the user and ask for confirmation
532
- 4. If confirmed, run:
533
- \`\`\`bash
534
- ${bin} learn add "<refined learning>"
535
- \`\`\`
536
- For personal preferences (not project-level), add \`--personal\`:
537
- \`\`\`bash
538
- ${bin} learn add --personal "<refined learning>"
539
- \`\`\`
540
- 5. Stage the learnings file for the next commit:
541
- \`\`\`bash
542
- git add CALIBER_LEARNINGS.md
543
- \`\`\`
544
-
545
- ## Examples
546
-
547
- User: "when developing features, push to next branch not master, remember it"
548
- -> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
549
- -> "I'll save this as a project learning:
550
- **[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
551
- Save for future sessions?"
552
- -> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
553
- -> Run \`git add CALIBER_LEARNINGS.md\`
554
-
555
- User: "always use bun instead of npm"
556
- -> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
557
- -> Confirm and save
558
-
559
- User: "never use any in TypeScript, use unknown instead"
560
- -> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
561
- -> Confirm and save
562
-
563
- ## When NOT to trigger
564
-
565
- - The user is giving a one-time instruction for the current task only
566
- - The instruction is too vague to be actionable
567
- - The user explicitly says "just for now" or "only this time"
568
- `;
569
- }
570
- function getSetupCaliberContent() {
571
- return `# Setup Caliber
572
-
573
- Dynamic onboarding for Caliber \u2014 automatic AI agent context sync.
574
- Run all diagnostic steps below on every invocation to determine what's already
575
- set up and what still needs to be done.
576
-
577
- ## Instructions
578
-
579
- Run these checks in order. For each step, check the current state first,
580
- then only act if something is missing.
581
-
582
- ### Step 1: Check if Caliber is installed
583
-
584
- \`\`\`bash
585
- command -v caliber >/dev/null 2>&1 && caliber --version || echo "NOT_INSTALLED"
586
- \`\`\`
587
-
588
- - If a version prints \u2192 Caliber is installed globally. Set \`CALIBER="caliber"\` and move to Step 2.
589
- - If NOT_INSTALLED \u2192 Install it globally (faster for daily use since the pre-commit hook runs on every commit):
590
- \`\`\`bash
591
- npm install -g @rely-ai/caliber
592
- \`\`\`
593
- Set \`CALIBER="caliber"\`.
594
-
595
- If npm fails (permissions, no sudo, etc.), fall back to npx:
596
- \`\`\`bash
597
- npx @rely-ai/caliber --version 2>/dev/null || echo "NO_NODE"
598
- \`\`\`
599
- - If npx works \u2192 Set \`CALIBER="npx @rely-ai/caliber"\`. This works but adds ~500ms per invocation.
600
- - If NO_NODE \u2192 Tell the user: "Caliber requires Node.js >= 20. Install Node first, then run /setup-caliber again." Stop here.
601
-
602
- ### Step 2: Check if pre-commit hook is installed
603
-
604
- \`\`\`bash
605
- grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "HOOK_ACTIVE" || echo "NO_HOOK"
606
- \`\`\`
607
-
608
- - If HOOK_ACTIVE \u2192 Tell the user: "Pre-commit hook is active \u2014 configs sync on every commit." Move to Step 3.
609
- - If NO_HOOK \u2192 Tell the user: "I'll install the pre-commit hook so your agent configs sync automatically on every commit."
610
- \`\`\`bash
611
- $CALIBER hooks --install
612
- \`\`\`
613
-
614
- ### Step 3: Detect agents and check if configs exist
615
-
616
- First, detect which coding agents are configured in this project:
617
- \`\`\`bash
618
- AGENTS=""
619
- [ -d .claude ] && AGENTS="claude"
620
- [ -d .cursor ] && AGENTS="\${AGENTS:+$AGENTS,}cursor"
621
- [ -d .agents ] || [ -f AGENTS.md ] && AGENTS="\${AGENTS:+$AGENTS,}codex"
622
- [ -f .github/copilot-instructions.md ] && AGENTS="\${AGENTS:+$AGENTS,}github-copilot"
623
- echo "DETECTED_AGENTS=\${AGENTS:-none}"
624
- \`\`\`
625
-
626
- If no agents are detected, ask the user which coding agents they use (Claude Code, Cursor, Codex, GitHub Copilot).
627
- Build the agent list from their answer as a comma-separated string (e.g. "claude,cursor").
628
-
629
- Then check if agent configs exist:
630
- \`\`\`bash
631
- echo "CLAUDE_MD=$([ -f CLAUDE.md ] && echo exists || echo missing)"
632
- echo "CURSOR_RULES=$([ -d .cursor/rules ] && ls .cursor/rules/*.mdc 2>/dev/null | wc -l | tr -d ' ' || echo 0)"
633
- echo "AGENTS_MD=$([ -f AGENTS.md ] && echo exists || echo missing)"
634
- echo "COPILOT=$([ -f .github/copilot-instructions.md ] && echo exists || echo missing)"
635
- \`\`\`
636
-
637
- - If configs exist for the detected agents \u2192 Tell the user which configs are present. Move to Step 4.
638
- - If configs are missing \u2192 Tell the user: "No agent configs found. I'll generate them now."
639
- Use the detected or user-selected agent list:
640
- \`\`\`bash
641
- $CALIBER init --auto-approve --agent <comma-separated-agents>
642
- \`\`\`
643
- For example: \`$CALIBER init --auto-approve --agent claude,cursor\`
644
- This generates CLAUDE.md, Cursor rules, AGENTS.md, skills, and sync infrastructure for the specified agents.
645
-
646
- ### Step 4: Check if configs are fresh
647
-
648
- \`\`\`bash
649
- $CALIBER score --json --quiet 2>/dev/null | head -1
650
- \`\`\`
651
-
652
- - If score is 80+ \u2192 Tell the user: "Your configs are in good shape (score: X/100)."
653
- - If score is below 80 \u2192 Tell the user: "Your configs could be improved (score: X/100). Want me to run a refresh?"
654
- If yes:
655
- \`\`\`bash
656
- $CALIBER refresh
657
- \`\`\`
658
-
659
- ### Step 5: Ask about team setup
660
-
661
- Ask the user: "Are you setting up for yourself only, or for your team too?"
662
-
663
- - If **solo** \u2192 Continue with solo setup:
664
-
665
- Check if session learning is enabled:
666
- \`\`\`bash
667
- $CALIBER learn status 2>/dev/null | head -3
668
- \`\`\`
669
- - If learning is already enabled \u2192 note it in the summary.
670
- - If not enabled \u2192 ask the user: "Caliber can learn from your coding sessions \u2014 when you correct a mistake or fix a pattern, it remembers for next time. Enable session learning?"
671
- If yes:
672
- \`\`\`bash
673
- $CALIBER learn install
674
- \`\`\`
675
-
676
- Then tell the user:
677
- "You're all set! Here's what happens next:
678
- - Every time you commit, Caliber syncs your agent configs automatically
679
- - Your CLAUDE.md, Cursor rules, and AGENTS.md stay current with your code
680
- - Run \`$CALIBER skills\` anytime to discover community skills for your stack"
681
-
682
- Then show the summary (see below) and stop.
683
-
684
- - If **team** \u2192 Check if the GitHub Action already exists:
685
- \`\`\`bash
686
- [ -f .github/workflows/caliber-sync.yml ] && echo "ACTION_EXISTS" || echo "NO_ACTION"
687
- \`\`\`
688
- - If ACTION_EXISTS \u2192 Tell the user: "GitHub Action is already configured."
689
- - If NO_ACTION \u2192 Tell the user: "I'll create a GitHub Action that syncs configs nightly and on every PR."
690
- Write this file to \`.github/workflows/caliber-sync.yml\`:
691
- \`\`\`yaml
692
- name: Caliber Sync
693
- on:
694
- schedule:
695
- - cron: '0 3 * * 1-5'
696
- pull_request:
697
- types: [opened, synchronize]
698
- workflow_dispatch:
699
- jobs:
700
- sync:
701
- runs-on: ubuntu-latest
702
- steps:
703
- - uses: actions/checkout@v4
704
- - uses: caliber-ai-org/ai-setup@v1
705
- with:
706
- mode: sync
707
- auto-refresh: true
708
- comment: true
709
- github-token: \${{ secrets.GITHUB_TOKEN }}
710
- env:
711
- ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
712
- \`\`\`
713
- Now determine which LLM provider the team uses. Check the local Caliber config:
714
- \`\`\`bash
715
- $CALIBER config --show 2>/dev/null || echo "NO_CONFIG"
716
- \`\`\`
717
-
718
- Based on the provider, the GitHub Action needs the corresponding secret:
719
- - **anthropic** \u2192 \`ANTHROPIC_API_KEY\`
720
- - **openai** \u2192 \`OPENAI_API_KEY\`
721
- - **vertex** \u2192 \`VERTEX_PROJECT_ID\` and \`GOOGLE_APPLICATION_CREDENTIALS\` (service account JSON)
722
-
723
- Update the workflow env block to match the provider. For example, if using OpenAI:
724
- \`\`\`yaml
725
- env:
726
- OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
727
- \`\`\`
728
-
729
- Then check if the \`gh\` CLI is available to set the secret:
730
- \`\`\`bash
731
- command -v gh >/dev/null 2>&1 && echo "GH_AVAILABLE" || echo "NO_GH"
732
- \`\`\`
733
-
734
- - If GH_AVAILABLE \u2192 Ask the user for their API key and set it:
735
- \`\`\`bash
736
- gh secret set ANTHROPIC_API_KEY
737
- \`\`\`
738
- (This prompts for the value securely via stdin)
739
- - If NO_GH \u2192 Tell the user exactly what to do:
740
- "Go to your repo on GitHub \u2192 Settings \u2192 Secrets and variables \u2192 Actions \u2192 New repository secret.
741
- Name: ANTHROPIC_API_KEY (or OPENAI_API_KEY depending on provider)
742
- Value: your API key"
743
-
744
- Finally, offer to commit and push the workflow file:
745
- \`\`\`bash
746
- git add .github/workflows/caliber-sync.yml
747
- git commit -m "feat: add Caliber sync GitHub Action"
748
- git push
749
- \`\`\`
750
-
751
- ### Summary
752
-
753
- After completing all steps, show the user what's configured:
754
-
755
- \`\`\`
756
- Caliber Setup Complete:
757
- \u2713 Caliber installed (vX.X.X)
758
- \u2713 Pre-commit hook \u2014 configs sync on every commit
759
- \u2713 Agent configs \u2014 CLAUDE.md, Cursor rules, AGENTS.md
760
- \u2713 Config score: X/100
761
- \u2713 GitHub Action \u2014 nightly sync + PR checks (team only)
762
-
763
- From now on, every commit keeps all your agent configs in sync automatically.
764
- \`\`\`
765
-
766
- ## When to trigger
767
-
768
- - When a developer first opens this project
769
- - When the user asks about keeping agent configs up to date
770
- - When the user asks "how do I set up Caliber" or "what is Caliber"
771
- - When the user sees a message saying "Run /setup-caliber to get set up"
772
-
773
- ## When NOT to trigger
774
-
775
- - The user is in the middle of time-sensitive work
776
- `;
777
- }
778
- function ensureBuiltinSkills() {
779
- const written = [];
780
- for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
781
- if (!fs17.existsSync(platformDir)) continue;
782
- for (const skill of BUILTIN_SKILLS) {
783
- const skillPath = path17.join(skillsDir, skill.name, "SKILL.md");
784
- fs17.mkdirSync(path17.dirname(skillPath), { recursive: true });
785
- fs17.writeFileSync(skillPath, buildSkillContent(skill));
786
- written.push(skillPath);
787
- }
788
- }
789
- return written;
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"() {
794
- "use strict";
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
- }
825
- });
826
-
827
233
  // src/utils/editor.ts
828
- import { execSync as execSync14, spawn as spawn3 } from "child_process";
829
- import fs27 from "fs";
830
- import path24 from "path";
234
+ import { execSync as execSync13, spawn as spawn3 } from "child_process";
235
+ import fs26 from "fs";
236
+ import path23 from "path";
831
237
  import os6 from "os";
832
238
  function getEmptyFilePath(proposedPath) {
833
- fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
834
- const tempPath = path24.join(DIFF_TEMP_DIR, path24.basename(proposedPath));
835
- fs27.writeFileSync(tempPath, "");
239
+ fs26.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
240
+ const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
241
+ fs26.writeFileSync(tempPath, "");
836
242
  return tempPath;
837
243
  }
838
244
  function commandExists(cmd) {
839
245
  try {
840
246
  const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
841
- execSync14(check, { stdio: "ignore" });
247
+ execSync13(check, { stdio: "ignore" });
842
248
  return true;
843
249
  } catch {
844
250
  return false;
@@ -872,7 +278,7 @@ var init_editor = __esm({
872
278
  "src/utils/editor.ts"() {
873
279
  "use strict";
874
280
  IS_WINDOWS3 = process.platform === "win32";
875
- DIFF_TEMP_DIR = path24.join(os6.tmpdir(), "caliber-diff");
281
+ DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
876
282
  }
877
283
  });
878
284
 
@@ -884,7 +290,7 @@ __export(review_exports, {
884
290
  promptWantsReview: () => promptWantsReview
885
291
  });
886
292
  import chalk10 from "chalk";
887
- import fs28 from "fs";
293
+ import fs27 from "fs";
888
294
  import select4 from "@inquirer/select";
889
295
  import { createTwoFilesPatch } from "diff";
890
296
  async function promptWantsReview() {
@@ -921,8 +327,8 @@ async function openReview(method, stagedFiles) {
921
327
  return;
922
328
  }
923
329
  const fileInfos = stagedFiles.map((file) => {
924
- const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
925
- const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
330
+ const proposed = fs27.readFileSync(file.proposedPath, "utf-8");
331
+ const current = file.currentPath ? fs27.readFileSync(file.currentPath, "utf-8") : "";
926
332
  const patch = createTwoFilesPatch(
927
333
  file.isNew ? "/dev/null" : file.relativePath,
928
334
  file.relativePath,
@@ -1097,13 +503,13 @@ __export(lock_exports, {
1097
503
  isCaliberRunning: () => isCaliberRunning,
1098
504
  releaseLock: () => releaseLock
1099
505
  });
1100
- import fs38 from "fs";
1101
- import path31 from "path";
506
+ import fs37 from "fs";
507
+ import path30 from "path";
1102
508
  import os8 from "os";
1103
509
  function isCaliberRunning() {
1104
510
  try {
1105
- if (!fs38.existsSync(LOCK_FILE)) return false;
1106
- const raw = fs38.readFileSync(LOCK_FILE, "utf-8").trim();
511
+ if (!fs37.existsSync(LOCK_FILE)) return false;
512
+ const raw = fs37.readFileSync(LOCK_FILE, "utf-8").trim();
1107
513
  const { pid, ts } = JSON.parse(raw);
1108
514
  if (Date.now() - ts > STALE_MS) return false;
1109
515
  try {
@@ -1118,13 +524,13 @@ function isCaliberRunning() {
1118
524
  }
1119
525
  function acquireLock() {
1120
526
  try {
1121
- fs38.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
527
+ fs37.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
1122
528
  } catch {
1123
529
  }
1124
530
  }
1125
531
  function releaseLock() {
1126
532
  try {
1127
- if (fs38.existsSync(LOCK_FILE)) fs38.unlinkSync(LOCK_FILE);
533
+ if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
1128
534
  } catch {
1129
535
  }
1130
536
  }
@@ -1132,21 +538,21 @@ var LOCK_FILE, STALE_MS;
1132
538
  var init_lock = __esm({
1133
539
  "src/lib/lock.ts"() {
1134
540
  "use strict";
1135
- LOCK_FILE = path31.join(os8.tmpdir(), ".caliber.lock");
541
+ LOCK_FILE = path30.join(os8.tmpdir(), ".caliber.lock");
1136
542
  STALE_MS = 10 * 60 * 1e3;
1137
543
  }
1138
544
  });
1139
545
 
1140
546
  // src/cli.ts
1141
547
  import { Command } from "commander";
1142
- import fs49 from "fs";
1143
- import path40 from "path";
548
+ import fs47 from "fs";
549
+ import path39 from "path";
1144
550
  import { fileURLToPath } from "url";
1145
551
 
1146
552
  // src/commands/init.ts
1147
- import path27 from "path";
553
+ import path26 from "path";
1148
554
  import chalk14 from "chalk";
1149
- import fs33 from "fs";
555
+ import fs32 from "fs";
1150
556
 
1151
557
  // src/fingerprint/index.ts
1152
558
  import fs8 from "fs";
@@ -3821,151 +3227,9 @@ function getCursorConfigDir() {
3821
3227
  return path9.join(home, ".config", "Cursor");
3822
3228
  }
3823
3229
 
3824
- // src/lib/hooks.ts
3825
- init_resolve_caliber();
3230
+ // src/fingerprint/sources.ts
3826
3231
  import fs10 from "fs";
3827
3232
  import path10 from "path";
3828
- import { execSync as execSync8 } from "child_process";
3829
- var SETTINGS_PATH = path10.join(".claude", "settings.json");
3830
- var REFRESH_TAIL = "refresh --quiet";
3831
- var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
3832
- function getHookCommand() {
3833
- return `${resolveCaliber()} ${REFRESH_TAIL}`;
3834
- }
3835
- function readSettings() {
3836
- if (!fs10.existsSync(SETTINGS_PATH)) return {};
3837
- try {
3838
- return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
3839
- } catch {
3840
- return {};
3841
- }
3842
- }
3843
- function writeSettings(settings) {
3844
- const dir = path10.dirname(SETTINGS_PATH);
3845
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
3846
- fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3847
- }
3848
- function findHookIndex(sessionEnd) {
3849
- return sessionEnd.findIndex(
3850
- (entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
3851
- );
3852
- }
3853
- function isHookInstalled() {
3854
- const settings = readSettings();
3855
- const sessionEnd = settings.hooks?.SessionEnd;
3856
- if (!Array.isArray(sessionEnd)) return false;
3857
- return findHookIndex(sessionEnd) !== -1;
3858
- }
3859
- function installHook() {
3860
- const settings = readSettings();
3861
- if (!settings.hooks) settings.hooks = {};
3862
- if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
3863
- if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
3864
- return { installed: false, alreadyInstalled: true };
3865
- }
3866
- settings.hooks.SessionEnd.push({
3867
- matcher: "",
3868
- hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
3869
- });
3870
- writeSettings(settings);
3871
- return { installed: true, alreadyInstalled: false };
3872
- }
3873
- function removeHook() {
3874
- const settings = readSettings();
3875
- const sessionEnd = settings.hooks?.SessionEnd;
3876
- if (!Array.isArray(sessionEnd)) {
3877
- return { removed: false, notFound: true };
3878
- }
3879
- const idx = findHookIndex(sessionEnd);
3880
- if (idx === -1) {
3881
- return { removed: false, notFound: true };
3882
- }
3883
- sessionEnd.splice(idx, 1);
3884
- if (sessionEnd.length === 0) {
3885
- delete settings.hooks.SessionEnd;
3886
- }
3887
- if (settings.hooks && Object.keys(settings.hooks).length === 0) {
3888
- delete settings.hooks;
3889
- }
3890
- writeSettings(settings);
3891
- return { removed: true, notFound: false };
3892
- }
3893
- var PRECOMMIT_START = "# caliber:pre-commit:start";
3894
- var PRECOMMIT_END = "# caliber:pre-commit:end";
3895
- function getPrecommitBlock() {
3896
- const bin = resolveCaliber();
3897
- const npx = isNpxResolution();
3898
- const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
3899
- const invoke = npx ? bin : `"${bin}"`;
3900
- return `${PRECOMMIT_START}
3901
- if ${guard}; then
3902
- echo "\\033[2mcaliber: refreshing docs...\\033[0m"
3903
- ${invoke} refresh 2>/dev/null || true
3904
- ${invoke} learn finalize 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
3906
- fi
3907
- ${PRECOMMIT_END}`;
3908
- }
3909
- function getGitHooksDir() {
3910
- try {
3911
- const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
3912
- return path10.join(gitDir, "hooks");
3913
- } catch {
3914
- return null;
3915
- }
3916
- }
3917
- function getPreCommitPath() {
3918
- const hooksDir = getGitHooksDir();
3919
- return hooksDir ? path10.join(hooksDir, "pre-commit") : null;
3920
- }
3921
- function isPreCommitHookInstalled() {
3922
- const hookPath = getPreCommitPath();
3923
- if (!hookPath || !fs10.existsSync(hookPath)) return false;
3924
- const content = fs10.readFileSync(hookPath, "utf-8");
3925
- return content.includes(PRECOMMIT_START);
3926
- }
3927
- function installPreCommitHook() {
3928
- if (isPreCommitHookInstalled()) {
3929
- return { installed: false, alreadyInstalled: true };
3930
- }
3931
- const hookPath = getPreCommitPath();
3932
- if (!hookPath) return { installed: false, alreadyInstalled: false };
3933
- const hooksDir = path10.dirname(hookPath);
3934
- if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
3935
- let content = "";
3936
- if (fs10.existsSync(hookPath)) {
3937
- content = fs10.readFileSync(hookPath, "utf-8");
3938
- if (!content.endsWith("\n")) content += "\n";
3939
- content += "\n" + getPrecommitBlock() + "\n";
3940
- } else {
3941
- content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
3942
- }
3943
- fs10.writeFileSync(hookPath, content);
3944
- fs10.chmodSync(hookPath, 493);
3945
- return { installed: true, alreadyInstalled: false };
3946
- }
3947
- function removePreCommitHook() {
3948
- const hookPath = getPreCommitPath();
3949
- if (!hookPath || !fs10.existsSync(hookPath)) {
3950
- return { removed: false, notFound: true };
3951
- }
3952
- let content = fs10.readFileSync(hookPath, "utf-8");
3953
- if (!content.includes(PRECOMMIT_START)) {
3954
- return { removed: false, notFound: true };
3955
- }
3956
- const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
3957
- content = content.replace(regex, "\n");
3958
- if (content.trim() === "#!/bin/sh" || content.trim() === "") {
3959
- fs10.unlinkSync(hookPath);
3960
- } else {
3961
- fs10.writeFileSync(hookPath, content);
3962
- }
3963
- return { removed: true, notFound: false };
3964
- }
3965
-
3966
- // src/fingerprint/sources.ts
3967
- import fs11 from "fs";
3968
- import path11 from "path";
3969
3233
 
3970
3234
  // src/scoring/utils.ts
3971
3235
  import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
@@ -4294,7 +3558,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
4294
3558
  var README_CONTENT_LIMIT = 1e3;
4295
3559
  var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
4296
3560
  function loadSourcesConfig(dir) {
4297
- const configPath = path11.join(dir, ".caliber", "sources.json");
3561
+ const configPath = path10.join(dir, ".caliber", "sources.json");
4298
3562
  const content = readFileOrNull(configPath);
4299
3563
  if (!content) return [];
4300
3564
  try {
@@ -4312,29 +3576,29 @@ function loadSourcesConfig(dir) {
4312
3576
  }
4313
3577
  }
4314
3578
  function writeSourcesConfig(dir, sources2) {
4315
- const configDir = path11.join(dir, ".caliber");
4316
- if (!fs11.existsSync(configDir)) {
4317
- fs11.mkdirSync(configDir, { recursive: true });
3579
+ const configDir = path10.join(dir, ".caliber");
3580
+ if (!fs10.existsSync(configDir)) {
3581
+ fs10.mkdirSync(configDir, { recursive: true });
4318
3582
  }
4319
- const configPath = path11.join(configDir, "sources.json");
4320
- fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
3583
+ const configPath = path10.join(configDir, "sources.json");
3584
+ fs10.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
4321
3585
  }
4322
3586
  function detectSourceType(absPath) {
4323
3587
  try {
4324
- return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
3588
+ return fs10.statSync(absPath).isDirectory() ? "repo" : "file";
4325
3589
  } catch {
4326
3590
  return "file";
4327
3591
  }
4328
3592
  }
4329
3593
  function isInsideDir(childPath, parentDir) {
4330
- const relative2 = path11.relative(parentDir, childPath);
4331
- return !relative2.startsWith("..") && !path11.isAbsolute(relative2);
3594
+ const relative2 = path10.relative(parentDir, childPath);
3595
+ return !relative2.startsWith("..") && !path10.isAbsolute(relative2);
4332
3596
  }
4333
3597
  function resolveAllSources(dir, cliSources, workspaces) {
4334
3598
  const seen = /* @__PURE__ */ new Map();
4335
- const projectRoot = path11.resolve(dir);
3599
+ const projectRoot = path10.resolve(dir);
4336
3600
  for (const src of cliSources) {
4337
- const absPath = path11.resolve(dir, src);
3601
+ const absPath = path10.resolve(dir, src);
4338
3602
  if (seen.has(absPath)) continue;
4339
3603
  const type = detectSourceType(absPath);
4340
3604
  seen.set(absPath, {
@@ -4347,12 +3611,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
4347
3611
  for (const cfg of configSources) {
4348
3612
  if (cfg.type === "url") continue;
4349
3613
  if (!cfg.path) continue;
4350
- const absPath = path11.resolve(dir, cfg.path);
3614
+ const absPath = path10.resolve(dir, cfg.path);
4351
3615
  if (seen.has(absPath)) continue;
4352
3616
  seen.set(absPath, { absPath, config: cfg, origin: "config" });
4353
3617
  }
4354
3618
  for (const ws of workspaces) {
4355
- const absPath = path11.resolve(dir, ws);
3619
+ const absPath = path10.resolve(dir, ws);
4356
3620
  if (seen.has(absPath)) continue;
4357
3621
  if (!isInsideDir(absPath, projectRoot)) continue;
4358
3622
  seen.set(absPath, {
@@ -4365,7 +3629,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
4365
3629
  for (const [absPath, resolved] of seen) {
4366
3630
  let stat;
4367
3631
  try {
4368
- stat = fs11.statSync(absPath);
3632
+ stat = fs10.statSync(absPath);
4369
3633
  } catch {
4370
3634
  console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
4371
3635
  continue;
@@ -4394,13 +3658,13 @@ function collectSourceSummary(resolved, projectDir) {
4394
3658
  if (config.type === "file") {
4395
3659
  return collectFileSummary(resolved, projectDir);
4396
3660
  }
4397
- const summaryPath = path11.join(absPath, ".caliber", "summary.json");
3661
+ const summaryPath = path10.join(absPath, ".caliber", "summary.json");
4398
3662
  const summaryContent = readFileOrNull(summaryPath);
4399
3663
  if (summaryContent) {
4400
3664
  try {
4401
3665
  const published = JSON.parse(summaryContent);
4402
3666
  return {
4403
- name: published.name || path11.basename(absPath),
3667
+ name: published.name || path10.basename(absPath),
4404
3668
  type: "repo",
4405
3669
  role: config.role || published.role || "related-repo",
4406
3670
  description: config.description || published.description || "",
@@ -4420,18 +3684,18 @@ function collectRepoSummary(resolved, _projectDir) {
4420
3684
  let topLevelDirs;
4421
3685
  let keyFiles;
4422
3686
  try {
4423
- const entries = fs11.readdirSync(absPath, { withFileTypes: true });
3687
+ const entries = fs10.readdirSync(absPath, { withFileTypes: true });
4424
3688
  topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
4425
3689
  keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
4426
3690
  } catch {
4427
3691
  }
4428
- const claudeMdContent = readFileOrNull(path11.join(absPath, "CLAUDE.md"));
3692
+ const claudeMdContent = readFileOrNull(path10.join(absPath, "CLAUDE.md"));
4429
3693
  const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
4430
- const readmeContent = readFileOrNull(path11.join(absPath, "README.md"));
3694
+ const readmeContent = readFileOrNull(path10.join(absPath, "README.md"));
4431
3695
  const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
4432
3696
  const gitRemoteUrl = getGitRemoteUrl(absPath);
4433
3697
  return {
4434
- name: packageName || path11.basename(absPath),
3698
+ name: packageName || path10.basename(absPath),
4435
3699
  type: "repo",
4436
3700
  role: config.role || "related-repo",
4437
3701
  description: config.description || "",
@@ -4448,7 +3712,7 @@ function collectFileSummary(resolved, _projectDir) {
4448
3712
  const { config, origin, absPath } = resolved;
4449
3713
  const content = readFileOrNull(absPath);
4450
3714
  return {
4451
- name: path11.basename(absPath),
3715
+ name: path10.basename(absPath),
4452
3716
  type: "file",
4453
3717
  role: config.role || "reference-doc",
4454
3718
  description: config.description || content?.slice(0, 100).split("\n")[0] || "",
@@ -4492,15 +3756,15 @@ init_config();
4492
3756
  // src/utils/dependencies.ts
4493
3757
  import { readFileSync as readFileSync2 } from "fs";
4494
3758
  import { join as join2 } from "path";
4495
- function readFileOrNull2(path42) {
3759
+ function readFileOrNull2(path41) {
4496
3760
  try {
4497
- return readFileSync2(path42, "utf-8");
3761
+ return readFileSync2(path41, "utf-8");
4498
3762
  } catch {
4499
3763
  return null;
4500
3764
  }
4501
3765
  }
4502
- function readJsonOrNull(path42) {
4503
- const content = readFileOrNull2(path42);
3766
+ function readJsonOrNull(path41) {
3767
+ const content = readFileOrNull2(path41);
4504
3768
  if (!content) return null;
4505
3769
  try {
4506
3770
  return JSON.parse(content);
@@ -5083,29 +4347,149 @@ ${f.content}
5083
4347
  header = `
5084
4348
  --- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
5085
4349
  }
5086
- parts.push(header);
5087
- parts.push(codeLines.join("\n"));
4350
+ parts.push(header);
4351
+ parts.push(codeLines.join("\n"));
4352
+ }
4353
+ if (fingerprint.sources?.length) {
4354
+ parts.push(formatSourcesForPrompt(fingerprint.sources));
4355
+ }
4356
+ return parts.join("\n");
4357
+ }
4358
+
4359
+ // src/writers/index.ts
4360
+ import fs18 from "fs";
4361
+
4362
+ // src/writers/claude/index.ts
4363
+ import fs11 from "fs";
4364
+ import path11 from "path";
4365
+
4366
+ // src/writers/pre-commit-block.ts
4367
+ init_resolve_caliber();
4368
+ var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
4369
+ var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
4370
+ var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
4371
+ function getPreCommitBlock() {
4372
+ const bin = resolveCaliber();
4373
+ return `${BLOCK_START}
4374
+ ## Before Committing
4375
+
4376
+ Run \`${bin} refresh\` before creating git commits to keep docs in sync with code changes.
4377
+ After it completes, stage any modified doc files before committing:
4378
+
4379
+ \`\`\`bash
4380
+ ${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null
4381
+ \`\`\`
4382
+ ${BLOCK_END}`;
4383
+ }
4384
+ var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
4385
+ function getCursorRuleContent() {
4386
+ const bin = resolveCaliber();
4387
+ return `---
4388
+ description: Run caliber refresh before git commits to keep docs in sync
4389
+ alwaysApply: true
4390
+ ---
4391
+ Before creating git commits, run \`${bin} refresh\` to update docs based on code changes,
4392
+ then stage modified doc files (${MANAGED_DOC_PATHS}).
4393
+ `;
4394
+ }
4395
+ function hasPreCommitBlock(content) {
4396
+ return content.includes(BLOCK_START);
4397
+ }
4398
+ function appendPreCommitBlock(content) {
4399
+ if (hasPreCommitBlock(content)) return content;
4400
+ const trimmed = content.trimEnd();
4401
+ return trimmed + "\n\n" + getPreCommitBlock() + "\n";
4402
+ }
4403
+ function getCursorPreCommitRule() {
4404
+ return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
4405
+ }
4406
+ var LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
4407
+ var LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
4408
+ var LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
4409
+ ## Session Learnings
4410
+
4411
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
4412
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
4413
+ ${LEARNINGS_BLOCK_END}`;
4414
+ var CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
4415
+ var CURSOR_LEARNINGS_CONTENT = `---
4416
+ description: Reference session-learned patterns from CALIBER_LEARNINGS.md
4417
+ alwaysApply: true
4418
+ ---
4419
+ Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
4420
+ These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
4421
+ `;
4422
+ function hasLearningsBlock(content) {
4423
+ return content.includes(LEARNINGS_BLOCK_START);
4424
+ }
4425
+ function appendLearningsBlock(content) {
4426
+ if (hasLearningsBlock(content)) return content;
4427
+ const trimmed = content.trimEnd();
4428
+ return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
4429
+ }
4430
+ function getCursorLearningsRule() {
4431
+ return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
4432
+ }
4433
+
4434
+ // src/writers/claude/index.ts
4435
+ function writeClaudeConfig(config) {
4436
+ const written = [];
4437
+ fs11.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
4438
+ written.push("CLAUDE.md");
4439
+ if (config.skills?.length) {
4440
+ for (const skill of config.skills) {
4441
+ const skillDir = path11.join(".claude", "skills", skill.name);
4442
+ if (!fs11.existsSync(skillDir)) fs11.mkdirSync(skillDir, { recursive: true });
4443
+ const skillPath = path11.join(skillDir, "SKILL.md");
4444
+ const frontmatter = [
4445
+ "---",
4446
+ `name: ${skill.name}`,
4447
+ `description: ${skill.description}`,
4448
+ "---",
4449
+ ""
4450
+ ].join("\n");
4451
+ fs11.writeFileSync(skillPath, frontmatter + skill.content);
4452
+ written.push(skillPath);
4453
+ }
5088
4454
  }
5089
- if (fingerprint.sources?.length) {
5090
- parts.push(formatSourcesForPrompt(fingerprint.sources));
4455
+ if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4456
+ let existingServers = {};
4457
+ try {
4458
+ if (fs11.existsSync(".mcp.json")) {
4459
+ const existing = JSON.parse(fs11.readFileSync(".mcp.json", "utf-8"));
4460
+ if (existing.mcpServers) existingServers = existing.mcpServers;
4461
+ }
4462
+ } catch {
4463
+ }
4464
+ const mergedServers = { ...existingServers, ...config.mcpServers };
4465
+ fs11.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
4466
+ written.push(".mcp.json");
5091
4467
  }
5092
- return parts.join("\n");
4468
+ return written;
5093
4469
  }
5094
4470
 
5095
- // src/writers/index.ts
5096
- import fs19 from "fs";
5097
-
5098
- // src/writers/claude/index.ts
5099
- init_pre_commit_block();
4471
+ // src/writers/cursor/index.ts
5100
4472
  import fs12 from "fs";
5101
4473
  import path12 from "path";
5102
- function writeClaudeConfig(config) {
4474
+ function writeCursorConfig(config) {
5103
4475
  const written = [];
5104
- fs12.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
5105
- written.push("CLAUDE.md");
4476
+ if (config.cursorrules) {
4477
+ fs12.writeFileSync(".cursorrules", config.cursorrules);
4478
+ written.push(".cursorrules");
4479
+ }
4480
+ const preCommitRule = getCursorPreCommitRule();
4481
+ const learningsRule = getCursorLearningsRule();
4482
+ const allRules = [...config.rules || [], preCommitRule, learningsRule];
4483
+ const rulesDir = path12.join(".cursor", "rules");
4484
+ if (!fs12.existsSync(rulesDir)) fs12.mkdirSync(rulesDir, { recursive: true });
4485
+ for (const rule of allRules) {
4486
+ const rulePath = path12.join(rulesDir, rule.filename);
4487
+ fs12.writeFileSync(rulePath, rule.content);
4488
+ written.push(rulePath);
4489
+ }
5106
4490
  if (config.skills?.length) {
5107
4491
  for (const skill of config.skills) {
5108
- const skillDir = path12.join(".claude", "skills", skill.name);
4492
+ const skillDir = path12.join(".cursor", "skills", skill.name);
5109
4493
  if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
5110
4494
  const skillPath = path12.join(skillDir, "SKILL.md");
5111
4495
  const frontmatter = [
@@ -5120,183 +4504,286 @@ function writeClaudeConfig(config) {
5120
4504
  }
5121
4505
  }
5122
4506
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
4507
+ const cursorDir = ".cursor";
4508
+ if (!fs12.existsSync(cursorDir)) fs12.mkdirSync(cursorDir, { recursive: true });
4509
+ const mcpPath = path12.join(cursorDir, "mcp.json");
5123
4510
  let existingServers = {};
5124
4511
  try {
5125
- if (fs12.existsSync(".mcp.json")) {
5126
- const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
4512
+ if (fs12.existsSync(mcpPath)) {
4513
+ const existing = JSON.parse(fs12.readFileSync(mcpPath, "utf-8"));
5127
4514
  if (existing.mcpServers) existingServers = existing.mcpServers;
5128
4515
  }
5129
4516
  } catch {
5130
4517
  }
5131
4518
  const mergedServers = { ...existingServers, ...config.mcpServers };
5132
- fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
5133
- written.push(".mcp.json");
4519
+ fs12.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
4520
+ written.push(mcpPath);
4521
+ }
4522
+ return written;
4523
+ }
4524
+
4525
+ // src/writers/codex/index.ts
4526
+ import fs13 from "fs";
4527
+ import path13 from "path";
4528
+ function writeCodexConfig(config) {
4529
+ const written = [];
4530
+ fs13.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4531
+ written.push("AGENTS.md");
4532
+ if (config.skills?.length) {
4533
+ for (const skill of config.skills) {
4534
+ const skillDir = path13.join(".agents", "skills", skill.name);
4535
+ if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
4536
+ const skillPath = path13.join(skillDir, "SKILL.md");
4537
+ const frontmatter = [
4538
+ "---",
4539
+ `name: ${skill.name}`,
4540
+ `description: ${skill.description}`,
4541
+ "---",
4542
+ ""
4543
+ ].join("\n");
4544
+ fs13.writeFileSync(skillPath, frontmatter + skill.content);
4545
+ written.push(skillPath);
4546
+ }
4547
+ }
4548
+ return written;
4549
+ }
4550
+
4551
+ // src/writers/github-copilot/index.ts
4552
+ import fs14 from "fs";
4553
+ import path14 from "path";
4554
+ function writeGithubCopilotConfig(config) {
4555
+ const written = [];
4556
+ if (config.instructions) {
4557
+ fs14.mkdirSync(".github", { recursive: true });
4558
+ fs14.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
4559
+ written.push(".github/copilot-instructions.md");
4560
+ }
4561
+ if (config.instructionFiles?.length) {
4562
+ const instructionsDir = path14.join(".github", "instructions");
4563
+ fs14.mkdirSync(instructionsDir, { recursive: true });
4564
+ for (const file of config.instructionFiles) {
4565
+ fs14.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
4566
+ written.push(`.github/instructions/${file.filename}`);
4567
+ }
5134
4568
  }
5135
4569
  return written;
5136
4570
  }
5137
-
5138
- // src/writers/cursor/index.ts
5139
- init_pre_commit_block();
5140
- import fs13 from "fs";
5141
- import path13 from "path";
5142
- function writeCursorConfig(config) {
5143
- const written = [];
5144
- if (config.cursorrules) {
5145
- fs13.writeFileSync(".cursorrules", config.cursorrules);
5146
- written.push(".cursorrules");
5147
- }
5148
- const preCommitRule = getCursorPreCommitRule();
5149
- const learningsRule = getCursorLearningsRule();
5150
- const allRules = [...config.rules || [], preCommitRule, learningsRule];
5151
- const rulesDir = path13.join(".cursor", "rules");
5152
- if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
5153
- for (const rule of allRules) {
5154
- const rulePath = path13.join(rulesDir, rule.filename);
5155
- fs13.writeFileSync(rulePath, rule.content);
5156
- written.push(rulePath);
4571
+
4572
+ // src/writers/backup.ts
4573
+ import fs15 from "fs";
4574
+ import path15 from "path";
4575
+ function createBackup(files) {
4576
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4577
+ const backupDir = path15.join(BACKUPS_DIR, timestamp);
4578
+ for (const file of files) {
4579
+ if (!fs15.existsSync(file)) continue;
4580
+ const dest = path15.join(backupDir, file);
4581
+ const destDir = path15.dirname(dest);
4582
+ if (!fs15.existsSync(destDir)) {
4583
+ fs15.mkdirSync(destDir, { recursive: true });
4584
+ }
4585
+ fs15.copyFileSync(file, dest);
4586
+ }
4587
+ return backupDir;
4588
+ }
4589
+ function restoreBackup(backupDir, file) {
4590
+ const backupFile = path15.join(backupDir, file);
4591
+ if (!fs15.existsSync(backupFile)) return false;
4592
+ const destDir = path15.dirname(file);
4593
+ if (!fs15.existsSync(destDir)) {
4594
+ fs15.mkdirSync(destDir, { recursive: true });
4595
+ }
4596
+ fs15.copyFileSync(backupFile, file);
4597
+ return true;
4598
+ }
4599
+
4600
+ // src/lib/builtin-skills.ts
4601
+ init_resolve_caliber();
4602
+ import fs16 from "fs";
4603
+ import path16 from "path";
4604
+ function buildSkillContent(skill) {
4605
+ const frontmatter = `---
4606
+ name: ${skill.name}
4607
+ description: ${skill.description}
4608
+ ---
4609
+
4610
+ `;
4611
+ return frontmatter + skill.content;
4612
+ }
4613
+ function getFindSkillsContent() {
4614
+ const bin = resolveCaliber();
4615
+ return `# Find Skills
4616
+
4617
+ Search the public skill registry for community-contributed skills
4618
+ relevant to the user's current task and install them into this project.
4619
+
4620
+ ## Instructions
4621
+
4622
+ 1. Identify the key technologies, frameworks, or task types from the
4623
+ user's request that might have community skills available
4624
+ 2. Ask the user: "Would you like me to search for community skills
4625
+ for [identified technologies]?"
4626
+ 3. If the user agrees, run:
4627
+ \`\`\`bash
4628
+ ${bin} skills --query "<relevant terms>"
4629
+ \`\`\`
4630
+ This outputs the top 5 matching skills with scores and descriptions.
4631
+ 4. Present the results to the user and ask which ones to install
4632
+ 5. Install the selected skills:
4633
+ \`\`\`bash
4634
+ ${bin} skills --install <slug1>,<slug2>
4635
+ \`\`\`
4636
+ 6. Read the installed SKILL.md files to load them into your current
4637
+ context so you can use them immediately in this session
4638
+ 7. Summarize what was installed and continue with the user's task
4639
+
4640
+ ## Examples
4641
+
4642
+ User: "let's build a web app using React"
4643
+ -> "I notice you want to work with React. Would you like me to search
4644
+ for community skills that could help with React development?"
4645
+ -> If yes: run \`${bin} skills --query "react frontend"\`
4646
+ -> Show the user the results, ask which to install
4647
+ -> Run \`${bin} skills --install <selected-slugs>\`
4648
+ -> Read the installed files and continue
4649
+
4650
+ User: "help me set up Docker for this project"
4651
+ -> "Would you like me to search for Docker-related skills?"
4652
+ -> If yes: run \`${bin} skills --query "docker deployment"\`
4653
+
4654
+ User: "I need to write tests for this Python ML pipeline"
4655
+ -> "Would you like me to find skills for Python ML testing?"
4656
+ -> If yes: run \`${bin} skills --query "python machine-learning testing"\`
4657
+
4658
+ ## When NOT to trigger
4659
+
4660
+ - The user is working within an already well-configured area
4661
+ - You already suggested skills for this technology in this session
4662
+ - The user is in the middle of urgent debugging or time-sensitive work
4663
+ - The technology is too generic (e.g. just "code" or "programming")
4664
+ `;
4665
+ }
4666
+ function getSaveLearningContent() {
4667
+ const bin = resolveCaliber();
4668
+ return `# Save Learning
4669
+
4670
+ Save a user's instruction or preference as a persistent learning that
4671
+ will be applied in all future sessions on this project.
4672
+
4673
+ ## Instructions
4674
+
4675
+ 1. Detect when the user gives an instruction to remember, such as:
4676
+ - "remember this", "save this", "always do X", "never do Y"
4677
+ - "from now on", "going forward", "in this project we..."
4678
+ - Any stated convention, preference, or rule
4679
+ 2. Refine the instruction into a clean, actionable learning bullet with
4680
+ an appropriate type prefix:
4681
+ - \`**[convention]**\` \u2014 coding style, workflow, git conventions
4682
+ - \`**[pattern]**\` \u2014 reusable code patterns
4683
+ - \`**[anti-pattern]**\` \u2014 things to avoid
4684
+ - \`**[preference]**\` \u2014 personal/team preferences
4685
+ - \`**[context]**\` \u2014 project-specific context
4686
+ 3. Show the refined learning to the user and ask for confirmation
4687
+ 4. If confirmed, run:
4688
+ \`\`\`bash
4689
+ ${bin} learn add "<refined learning>"
4690
+ \`\`\`
4691
+ For personal preferences (not project-level), add \`--personal\`:
4692
+ \`\`\`bash
4693
+ ${bin} learn add --personal "<refined learning>"
4694
+ \`\`\`
4695
+ 5. Stage the learnings file for the next commit:
4696
+ \`\`\`bash
4697
+ git add CALIBER_LEARNINGS.md
4698
+ \`\`\`
4699
+
4700
+ ## Examples
4701
+
4702
+ User: "when developing features, push to next branch not master, remember it"
4703
+ -> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
4704
+ -> "I'll save this as a project learning:
4705
+ **[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
4706
+ Save for future sessions?"
4707
+ -> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
4708
+ -> Run \`git add CALIBER_LEARNINGS.md\`
4709
+
4710
+ User: "always use bun instead of npm"
4711
+ -> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
4712
+ -> Confirm and save
4713
+
4714
+ User: "never use any in TypeScript, use unknown instead"
4715
+ -> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
4716
+ -> Confirm and save
4717
+
4718
+ ## When NOT to trigger
4719
+
4720
+ - The user is giving a one-time instruction for the current task only
4721
+ - The instruction is too vague to be actionable
4722
+ - The user explicitly says "just for now" or "only this time"
4723
+ `;
4724
+ }
4725
+ var FIND_SKILLS_SKILL = {
4726
+ name: "find-skills",
4727
+ 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.",
4728
+ get content() {
4729
+ return getFindSkillsContent();
5157
4730
  }
5158
- if (config.skills?.length) {
5159
- for (const skill of config.skills) {
5160
- const skillDir = path13.join(".cursor", "skills", skill.name);
5161
- if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
5162
- const skillPath = path13.join(skillDir, "SKILL.md");
5163
- const frontmatter = [
5164
- "---",
5165
- `name: ${skill.name}`,
5166
- `description: ${skill.description}`,
5167
- "---",
5168
- ""
5169
- ].join("\n");
5170
- fs13.writeFileSync(skillPath, frontmatter + skill.content);
5171
- written.push(skillPath);
5172
- }
5173
- }
5174
- if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
5175
- const cursorDir = ".cursor";
5176
- if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
5177
- const mcpPath = path13.join(cursorDir, "mcp.json");
5178
- let existingServers = {};
5179
- try {
5180
- if (fs13.existsSync(mcpPath)) {
5181
- const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
5182
- if (existing.mcpServers) existingServers = existing.mcpServers;
5183
- }
5184
- } catch {
5185
- }
5186
- const mergedServers = { ...existingServers, ...config.mcpServers };
5187
- fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
5188
- written.push(mcpPath);
4731
+ };
4732
+ var SAVE_LEARNING_SKILL = {
4733
+ name: "save-learning",
4734
+ 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.",
4735
+ get content() {
4736
+ return getSaveLearningContent();
5189
4737
  }
5190
- return written;
5191
- }
5192
-
5193
- // src/writers/codex/index.ts
5194
- init_pre_commit_block();
5195
- import fs14 from "fs";
5196
- import path14 from "path";
5197
- function writeCodexConfig(config) {
4738
+ };
4739
+ var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL];
4740
+ var PLATFORM_CONFIGS = [
4741
+ { platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
4742
+ { platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
4743
+ { platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
4744
+ ];
4745
+ function ensureBuiltinSkills() {
5198
4746
  const written = [];
5199
- fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
5200
- written.push("AGENTS.md");
5201
- if (config.skills?.length) {
5202
- for (const skill of config.skills) {
5203
- const skillDir = path14.join(".agents", "skills", skill.name);
5204
- if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
5205
- const skillPath = path14.join(skillDir, "SKILL.md");
5206
- const frontmatter = [
5207
- "---",
5208
- `name: ${skill.name}`,
5209
- `description: ${skill.description}`,
5210
- "---",
5211
- ""
5212
- ].join("\n");
5213
- fs14.writeFileSync(skillPath, frontmatter + skill.content);
4747
+ for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
4748
+ if (!fs16.existsSync(platformDir)) continue;
4749
+ for (const skill of BUILTIN_SKILLS) {
4750
+ const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
4751
+ if (fs16.existsSync(skillPath)) continue;
4752
+ fs16.mkdirSync(path16.dirname(skillPath), { recursive: true });
4753
+ fs16.writeFileSync(skillPath, buildSkillContent(skill));
5214
4754
  written.push(skillPath);
5215
4755
  }
5216
4756
  }
5217
4757
  return written;
5218
4758
  }
5219
4759
 
5220
- // src/writers/github-copilot/index.ts
5221
- init_pre_commit_block();
5222
- import fs15 from "fs";
5223
- import path15 from "path";
5224
- function writeGithubCopilotConfig(config) {
5225
- const written = [];
5226
- if (config.instructions) {
5227
- fs15.mkdirSync(".github", { recursive: true });
5228
- fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
5229
- written.push(".github/copilot-instructions.md");
5230
- }
5231
- if (config.instructionFiles?.length) {
5232
- const instructionsDir = path15.join(".github", "instructions");
5233
- fs15.mkdirSync(instructionsDir, { recursive: true });
5234
- for (const file of config.instructionFiles) {
5235
- fs15.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
5236
- written.push(`.github/instructions/${file.filename}`);
5237
- }
5238
- }
5239
- return written;
5240
- }
5241
-
5242
- // src/writers/backup.ts
5243
- import fs16 from "fs";
5244
- import path16 from "path";
5245
- function createBackup(files) {
5246
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5247
- const backupDir = path16.join(BACKUPS_DIR, timestamp);
5248
- for (const file of files) {
5249
- if (!fs16.existsSync(file)) continue;
5250
- const dest = path16.join(backupDir, file);
5251
- const destDir = path16.dirname(dest);
5252
- if (!fs16.existsSync(destDir)) {
5253
- fs16.mkdirSync(destDir, { recursive: true });
5254
- }
5255
- fs16.copyFileSync(file, dest);
5256
- }
5257
- return backupDir;
5258
- }
5259
- function restoreBackup(backupDir, file) {
5260
- const backupFile = path16.join(backupDir, file);
5261
- if (!fs16.existsSync(backupFile)) return false;
5262
- const destDir = path16.dirname(file);
5263
- if (!fs16.existsSync(destDir)) {
5264
- fs16.mkdirSync(destDir, { recursive: true });
5265
- }
5266
- fs16.copyFileSync(backupFile, file);
5267
- return true;
5268
- }
5269
-
5270
- // src/writers/index.ts
5271
- init_builtin_skills();
5272
-
5273
4760
  // src/writers/manifest.ts
5274
- import fs18 from "fs";
4761
+ import fs17 from "fs";
5275
4762
  import crypto3 from "crypto";
5276
4763
  function readManifest() {
5277
4764
  try {
5278
- if (!fs18.existsSync(MANIFEST_FILE)) return null;
5279
- return JSON.parse(fs18.readFileSync(MANIFEST_FILE, "utf-8"));
4765
+ if (!fs17.existsSync(MANIFEST_FILE)) return null;
4766
+ return JSON.parse(fs17.readFileSync(MANIFEST_FILE, "utf-8"));
5280
4767
  } catch {
5281
4768
  return null;
5282
4769
  }
5283
4770
  }
5284
4771
  function writeManifest(manifest) {
5285
- if (!fs18.existsSync(CALIBER_DIR)) {
5286
- fs18.mkdirSync(CALIBER_DIR, { recursive: true });
4772
+ if (!fs17.existsSync(CALIBER_DIR)) {
4773
+ fs17.mkdirSync(CALIBER_DIR, { recursive: true });
5287
4774
  }
5288
- fs18.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
4775
+ fs17.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
5289
4776
  }
5290
4777
  function fileChecksum(filePath) {
5291
- const content = fs18.readFileSync(filePath);
4778
+ const content = fs17.readFileSync(filePath);
5292
4779
  return crypto3.createHash("sha256").update(content).digest("hex");
5293
4780
  }
5294
4781
 
5295
4782
  // src/writers/index.ts
5296
4783
  function writeSetup(setup) {
5297
4784
  const filesToWrite = getFilesToWrite(setup);
5298
- const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
5299
- const existingFiles = [...filesToWrite.filter((f) => fs19.existsSync(f)), ...filesToDelete];
4785
+ const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs18.existsSync(f));
4786
+ const existingFiles = [...filesToWrite.filter((f) => fs18.existsSync(f)), ...filesToDelete];
5300
4787
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
5301
4788
  const written = [];
5302
4789
  if (setup.targetAgent.includes("claude") && setup.claude) {
@@ -5313,7 +4800,7 @@ function writeSetup(setup) {
5313
4800
  }
5314
4801
  const deleted = [];
5315
4802
  for (const filePath of filesToDelete) {
5316
- fs19.unlinkSync(filePath);
4803
+ fs18.unlinkSync(filePath);
5317
4804
  deleted.push(filePath);
5318
4805
  }
5319
4806
  written.push(...ensureBuiltinSkills());
@@ -5344,8 +4831,8 @@ function undoSetup() {
5344
4831
  const removed = [];
5345
4832
  for (const entry of manifest.entries) {
5346
4833
  if (entry.action === "created") {
5347
- if (fs19.existsSync(entry.path)) {
5348
- fs19.unlinkSync(entry.path);
4834
+ if (fs18.existsSync(entry.path)) {
4835
+ fs18.unlinkSync(entry.path);
5349
4836
  removed.push(entry.path);
5350
4837
  }
5351
4838
  } else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
@@ -5354,8 +4841,8 @@ function undoSetup() {
5354
4841
  }
5355
4842
  }
5356
4843
  }
5357
- if (fs19.existsSync(MANIFEST_FILE)) {
5358
- fs19.unlinkSync(MANIFEST_FILE);
4844
+ if (fs18.existsSync(MANIFEST_FILE)) {
4845
+ fs18.unlinkSync(MANIFEST_FILE);
5359
4846
  }
5360
4847
  return { restored, removed };
5361
4848
  }
@@ -5397,22 +4884,22 @@ function getFilesToWrite(setup) {
5397
4884
  }
5398
4885
  function ensureGitignore() {
5399
4886
  const gitignorePath = ".gitignore";
5400
- if (fs19.existsSync(gitignorePath)) {
5401
- const content = fs19.readFileSync(gitignorePath, "utf-8");
4887
+ if (fs18.existsSync(gitignorePath)) {
4888
+ const content = fs18.readFileSync(gitignorePath, "utf-8");
5402
4889
  if (!content.includes(".caliber/")) {
5403
- fs19.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
4890
+ fs18.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
5404
4891
  }
5405
4892
  } else {
5406
- fs19.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
4893
+ fs18.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
5407
4894
  }
5408
4895
  }
5409
4896
 
5410
4897
  // src/writers/staging.ts
5411
- import fs20 from "fs";
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");
4898
+ import fs19 from "fs";
4899
+ import path17 from "path";
4900
+ var STAGED_DIR = path17.join(CALIBER_DIR, "staged");
4901
+ var PROPOSED_DIR = path17.join(STAGED_DIR, "proposed");
4902
+ var CURRENT_DIR = path17.join(STAGED_DIR, "current");
5416
4903
  function normalizeContent(content) {
5417
4904
  return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
5418
4905
  }
@@ -5423,20 +4910,20 @@ function stageFiles(files, projectDir) {
5423
4910
  const stagedFiles = [];
5424
4911
  for (const file of files) {
5425
4912
  assertPathWithinDir(file.path, projectDir);
5426
- const originalPath = path18.join(projectDir, file.path);
5427
- if (fs20.existsSync(originalPath)) {
5428
- const existing = fs20.readFileSync(originalPath, "utf-8");
4913
+ const originalPath = path17.join(projectDir, file.path);
4914
+ if (fs19.existsSync(originalPath)) {
4915
+ const existing = fs19.readFileSync(originalPath, "utf-8");
5429
4916
  if (normalizeContent(existing) === normalizeContent(file.content)) {
5430
4917
  continue;
5431
4918
  }
5432
4919
  }
5433
- const proposedPath = path18.join(PROPOSED_DIR, file.path);
5434
- fs20.mkdirSync(path18.dirname(proposedPath), { recursive: true });
5435
- fs20.writeFileSync(proposedPath, file.content);
5436
- if (fs20.existsSync(originalPath)) {
5437
- const currentPath = path18.join(CURRENT_DIR, file.path);
5438
- fs20.mkdirSync(path18.dirname(currentPath), { recursive: true });
5439
- fs20.copyFileSync(originalPath, currentPath);
4920
+ const proposedPath = path17.join(PROPOSED_DIR, file.path);
4921
+ fs19.mkdirSync(path17.dirname(proposedPath), { recursive: true });
4922
+ fs19.writeFileSync(proposedPath, file.content);
4923
+ if (fs19.existsSync(originalPath)) {
4924
+ const currentPath = path17.join(CURRENT_DIR, file.path);
4925
+ fs19.mkdirSync(path17.dirname(currentPath), { recursive: true });
4926
+ fs19.copyFileSync(originalPath, currentPath);
5440
4927
  modifiedFiles++;
5441
4928
  stagedFiles.push({
5442
4929
  relativePath: file.path,
@@ -5453,14 +4940,13 @@ function stageFiles(files, projectDir) {
5453
4940
  return { newFiles, modifiedFiles, stagedFiles };
5454
4941
  }
5455
4942
  function cleanupStaging() {
5456
- if (fs20.existsSync(STAGED_DIR)) {
5457
- fs20.rmSync(STAGED_DIR, { recursive: true, force: true });
4943
+ if (fs19.existsSync(STAGED_DIR)) {
4944
+ fs19.rmSync(STAGED_DIR, { recursive: true, force: true });
5458
4945
  }
5459
4946
  }
5460
4947
 
5461
4948
  // src/commands/setup-files.ts
5462
- init_builtin_skills();
5463
- import fs21 from "fs";
4949
+ import fs20 from "fs";
5464
4950
  function collectSetupFiles(setup, targetAgent) {
5465
4951
  const files = [];
5466
4952
  const claude = setup.claude;
@@ -5545,7 +5031,7 @@ function collectSetupFiles(setup, targetAgent) {
5545
5031
  }
5546
5032
  }
5547
5033
  const codexTargeted = targetAgent ? targetAgent.includes("codex") : false;
5548
- if (codexTargeted && !fs21.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
5034
+ if (codexTargeted && !fs20.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
5549
5035
  const agentRefs = [];
5550
5036
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
5551
5037
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
@@ -5564,9 +5050,9 @@ ${agentRefs.join(" ")}
5564
5050
 
5565
5051
  // src/lib/learning-hooks.ts
5566
5052
  init_resolve_caliber();
5567
- import fs22 from "fs";
5568
- import path19 from "path";
5569
- var SETTINGS_PATH2 = path19.join(".claude", "settings.json");
5053
+ import fs21 from "fs";
5054
+ import path18 from "path";
5055
+ var SETTINGS_PATH = path18.join(".claude", "settings.json");
5570
5056
  var HOOK_TAILS = [
5571
5057
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
5572
5058
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
@@ -5582,24 +5068,24 @@ function getHookConfigs() {
5582
5068
  description
5583
5069
  }));
5584
5070
  }
5585
- function readSettings2() {
5586
- if (!fs22.existsSync(SETTINGS_PATH2)) return {};
5071
+ function readSettings() {
5072
+ if (!fs21.existsSync(SETTINGS_PATH)) return {};
5587
5073
  try {
5588
- return JSON.parse(fs22.readFileSync(SETTINGS_PATH2, "utf-8"));
5074
+ return JSON.parse(fs21.readFileSync(SETTINGS_PATH, "utf-8"));
5589
5075
  } catch {
5590
5076
  return {};
5591
5077
  }
5592
5078
  }
5593
- function writeSettings2(settings) {
5594
- const dir = path19.dirname(SETTINGS_PATH2);
5595
- if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5596
- fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
5079
+ function writeSettings(settings) {
5080
+ const dir = path18.dirname(SETTINGS_PATH);
5081
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
5082
+ fs21.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
5597
5083
  }
5598
5084
  function hasLearningHook(matchers, tail) {
5599
5085
  return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
5600
5086
  }
5601
5087
  function areLearningHooksInstalled() {
5602
- const settings = readSettings2();
5088
+ const settings = readSettings();
5603
5089
  if (!settings.hooks) return false;
5604
5090
  return HOOK_TAILS.every((cfg) => {
5605
5091
  const matchers = settings.hooks[cfg.event];
@@ -5610,7 +5096,7 @@ function installLearningHooks() {
5610
5096
  if (areLearningHooksInstalled()) {
5611
5097
  return { installed: false, alreadyInstalled: true };
5612
5098
  }
5613
- const settings = readSettings2();
5099
+ const settings = readSettings();
5614
5100
  if (!settings.hooks) settings.hooks = {};
5615
5101
  const configs = getHookConfigs();
5616
5102
  for (const cfg of configs) {
@@ -5624,10 +5110,10 @@ function installLearningHooks() {
5624
5110
  });
5625
5111
  }
5626
5112
  }
5627
- writeSettings2(settings);
5113
+ writeSettings(settings);
5628
5114
  return { installed: true, alreadyInstalled: false };
5629
5115
  }
5630
- var CURSOR_HOOKS_PATH = path19.join(".cursor", "hooks.json");
5116
+ var CURSOR_HOOKS_PATH = path18.join(".cursor", "hooks.json");
5631
5117
  var CURSOR_HOOK_EVENTS = [
5632
5118
  { event: "postToolUse", tail: "learn observe" },
5633
5119
  { event: "postToolUseFailure", tail: "learn observe --failure" },
@@ -5635,17 +5121,17 @@ var CURSOR_HOOK_EVENTS = [
5635
5121
  { event: "sessionEnd", tail: "learn finalize --auto" }
5636
5122
  ];
5637
5123
  function readCursorHooks() {
5638
- if (!fs22.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
5124
+ if (!fs21.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
5639
5125
  try {
5640
- return JSON.parse(fs22.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
5126
+ return JSON.parse(fs21.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
5641
5127
  } catch {
5642
5128
  return { version: 1, hooks: {} };
5643
5129
  }
5644
5130
  }
5645
5131
  function writeCursorHooks(config) {
5646
- const dir = path19.dirname(CURSOR_HOOKS_PATH);
5647
- if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
5648
- fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
5132
+ const dir = path18.dirname(CURSOR_HOOKS_PATH);
5133
+ if (!fs21.existsSync(dir)) fs21.mkdirSync(dir, { recursive: true });
5134
+ fs21.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
5649
5135
  }
5650
5136
  function hasCursorHook(entries, tail) {
5651
5137
  return entries.some((e) => isCaliberCommand(e.command, tail));
@@ -5692,7 +5178,7 @@ function removeCursorLearningHooks() {
5692
5178
  return { removed: true, notFound: false };
5693
5179
  }
5694
5180
  function removeLearningHooks() {
5695
- const settings = readSettings2();
5181
+ const settings = readSettings();
5696
5182
  if (!settings.hooks) return { removed: false, notFound: true };
5697
5183
  let removedAny = false;
5698
5184
  for (const cfg of HOOK_TAILS) {
@@ -5709,7 +5195,7 @@ function removeLearningHooks() {
5709
5195
  delete settings.hooks;
5710
5196
  }
5711
5197
  if (!removedAny) return { removed: false, notFound: true };
5712
- writeSettings2(settings);
5198
+ writeSettings(settings);
5713
5199
  return { removed: true, notFound: false };
5714
5200
  }
5715
5201
 
@@ -5717,10 +5203,10 @@ function removeLearningHooks() {
5717
5203
  init_resolve_caliber();
5718
5204
 
5719
5205
  // src/lib/state.ts
5720
- import fs23 from "fs";
5721
- import path20 from "path";
5722
- import { execSync as execSync9 } from "child_process";
5723
- var STATE_FILE = path20.join(CALIBER_DIR, ".caliber-state.json");
5206
+ import fs22 from "fs";
5207
+ import path19 from "path";
5208
+ import { execSync as execSync8 } from "child_process";
5209
+ var STATE_FILE = path19.join(CALIBER_DIR, ".caliber-state.json");
5724
5210
  function normalizeTargetAgent(value) {
5725
5211
  if (Array.isArray(value)) return value;
5726
5212
  if (typeof value === "string") {
@@ -5731,8 +5217,8 @@ function normalizeTargetAgent(value) {
5731
5217
  }
5732
5218
  function readState() {
5733
5219
  try {
5734
- if (!fs23.existsSync(STATE_FILE)) return null;
5735
- const raw = JSON.parse(fs23.readFileSync(STATE_FILE, "utf-8"));
5220
+ if (!fs22.existsSync(STATE_FILE)) return null;
5221
+ const raw = JSON.parse(fs22.readFileSync(STATE_FILE, "utf-8"));
5736
5222
  if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
5737
5223
  return raw;
5738
5224
  } catch {
@@ -5740,14 +5226,14 @@ function readState() {
5740
5226
  }
5741
5227
  }
5742
5228
  function writeState(state) {
5743
- if (!fs23.existsSync(CALIBER_DIR)) {
5744
- fs23.mkdirSync(CALIBER_DIR, { recursive: true });
5229
+ if (!fs22.existsSync(CALIBER_DIR)) {
5230
+ fs22.mkdirSync(CALIBER_DIR, { recursive: true });
5745
5231
  }
5746
- fs23.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
5232
+ fs22.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
5747
5233
  }
5748
5234
  function getCurrentHeadSha() {
5749
5235
  try {
5750
- return execSync9("git rev-parse HEAD", {
5236
+ return execSync8("git rev-parse HEAD", {
5751
5237
  encoding: "utf-8",
5752
5238
  stdio: ["pipe", "pipe", "pipe"]
5753
5239
  }).trim();
@@ -5857,9 +5343,6 @@ async function runInteractiveProviderSetup(options) {
5857
5343
  return config;
5858
5344
  }
5859
5345
 
5860
- // src/commands/init.ts
5861
- import confirm2 from "@inquirer/confirm";
5862
-
5863
5346
  // src/scoring/index.ts
5864
5347
  import { existsSync as existsSync7 } from "fs";
5865
5348
  import { join as join9 } from "path";
@@ -6350,7 +5833,7 @@ function checkGrounding(dir) {
6350
5833
 
6351
5834
  // src/scoring/checks/accuracy.ts
6352
5835
  import { existsSync as existsSync4, statSync } from "fs";
6353
- import { execSync as execSync10 } from "child_process";
5836
+ import { execSync as execSync9 } from "child_process";
6354
5837
  import { join as join5 } from "path";
6355
5838
  init_resolve_caliber();
6356
5839
  function validateReferences(dir) {
@@ -6360,13 +5843,13 @@ function validateReferences(dir) {
6360
5843
  }
6361
5844
  function detectGitDrift(dir) {
6362
5845
  try {
6363
- execSync10("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
5846
+ execSync9("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
6364
5847
  } catch {
6365
5848
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
6366
5849
  }
6367
5850
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
6368
5851
  try {
6369
- const headTimestamp = execSync10(
5852
+ const headTimestamp = execSync9(
6370
5853
  "git log -1 --format=%ct HEAD",
6371
5854
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6372
5855
  ).trim();
@@ -6387,7 +5870,7 @@ function detectGitDrift(dir) {
6387
5870
  let latestConfigCommitHash = null;
6388
5871
  for (const file of configFiles) {
6389
5872
  try {
6390
- const hash = execSync10(
5873
+ const hash = execSync9(
6391
5874
  `git log -1 --format=%H -- "${file}"`,
6392
5875
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6393
5876
  ).trim();
@@ -6396,7 +5879,7 @@ function detectGitDrift(dir) {
6396
5879
  latestConfigCommitHash = hash;
6397
5880
  } else {
6398
5881
  try {
6399
- execSync10(
5882
+ execSync9(
6400
5883
  `git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
6401
5884
  { cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
6402
5885
  );
@@ -6411,12 +5894,12 @@ function detectGitDrift(dir) {
6411
5894
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
6412
5895
  }
6413
5896
  try {
6414
- const countStr = execSync10(
5897
+ const countStr = execSync9(
6415
5898
  `git rev-list --count ${latestConfigCommitHash}..HEAD`,
6416
5899
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6417
5900
  ).trim();
6418
5901
  const commitsSince = parseInt(countStr, 10) || 0;
6419
- const lastDate = execSync10(
5902
+ const lastDate = execSync9(
6420
5903
  `git log -1 --format=%ci ${latestConfigCommitHash}`,
6421
5904
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6422
5905
  ).trim();
@@ -6489,12 +5972,12 @@ function checkAccuracy(dir) {
6489
5972
  // src/scoring/checks/freshness.ts
6490
5973
  init_resolve_caliber();
6491
5974
  import { existsSync as existsSync5, statSync as statSync2 } from "fs";
6492
- import { execSync as execSync11 } from "child_process";
5975
+ import { execSync as execSync10 } from "child_process";
6493
5976
  import { join as join6 } from "path";
6494
5977
  function getCommitsSinceConfigUpdate(dir) {
6495
5978
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
6496
5979
  try {
6497
- const headTimestamp = execSync11(
5980
+ const headTimestamp = execSync10(
6498
5981
  "git log -1 --format=%ct HEAD",
6499
5982
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6500
5983
  ).trim();
@@ -6514,12 +5997,12 @@ function getCommitsSinceConfigUpdate(dir) {
6514
5997
  }
6515
5998
  for (const file of configFiles) {
6516
5999
  try {
6517
- const hash = execSync11(
6000
+ const hash = execSync10(
6518
6001
  `git log -1 --format=%H -- "${file}"`,
6519
6002
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6520
6003
  ).trim();
6521
6004
  if (hash) {
6522
- const countStr = execSync11(
6005
+ const countStr = execSync10(
6523
6006
  `git rev-list --count ${hash}..HEAD`,
6524
6007
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6525
6008
  ).trim();
@@ -6637,13 +6120,12 @@ function checkFreshness(dir) {
6637
6120
 
6638
6121
  // src/scoring/checks/bonus.ts
6639
6122
  import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
6640
- import { execSync as execSync12 } from "child_process";
6123
+ import { execSync as execSync11 } from "child_process";
6641
6124
  import { join as join7 } from "path";
6642
6125
  init_resolve_caliber();
6643
- init_pre_commit_block();
6644
6126
  function hasPreCommitHook(dir) {
6645
6127
  try {
6646
- const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6128
+ const gitDir = execSync11("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6647
6129
  const hookPath = join7(gitDir, "hooks", "pre-commit");
6648
6130
  const content = readFileOrNull(hookPath);
6649
6131
  return content ? content.includes("caliber") : false;
@@ -6800,22 +6282,22 @@ function checkSources(dir) {
6800
6282
  }
6801
6283
 
6802
6284
  // src/scoring/dismissed.ts
6803
- import fs24 from "fs";
6804
- import path21 from "path";
6805
- var DISMISSED_FILE = path21.join(CALIBER_DIR, "dismissed-checks.json");
6285
+ import fs23 from "fs";
6286
+ import path20 from "path";
6287
+ var DISMISSED_FILE = path20.join(CALIBER_DIR, "dismissed-checks.json");
6806
6288
  function readDismissedChecks() {
6807
6289
  try {
6808
- if (!fs24.existsSync(DISMISSED_FILE)) return [];
6809
- return JSON.parse(fs24.readFileSync(DISMISSED_FILE, "utf-8"));
6290
+ if (!fs23.existsSync(DISMISSED_FILE)) return [];
6291
+ return JSON.parse(fs23.readFileSync(DISMISSED_FILE, "utf-8"));
6810
6292
  } catch {
6811
6293
  return [];
6812
6294
  }
6813
6295
  }
6814
6296
  function writeDismissedChecks(checks) {
6815
- if (!fs24.existsSync(CALIBER_DIR)) {
6816
- fs24.mkdirSync(CALIBER_DIR, { recursive: true });
6297
+ if (!fs23.existsSync(CALIBER_DIR)) {
6298
+ fs23.mkdirSync(CALIBER_DIR, { recursive: true });
6817
6299
  }
6818
- fs24.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
6300
+ fs23.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
6819
6301
  }
6820
6302
  function getDismissedIds() {
6821
6303
  return new Set(readDismissedChecks().map((c) => c.id));
@@ -6845,8 +6327,8 @@ function detectTargetAgent(dir) {
6845
6327
  const agents = [];
6846
6328
  if (existsSync7(join9(dir, "CLAUDE.md")) || existsSync7(join9(dir, ".claude", "skills"))) agents.push("claude");
6847
6329
  if (existsSync7(join9(dir, ".cursorrules")) || existsSync7(join9(dir, ".cursor", "rules"))) agents.push("cursor");
6848
- if (existsSync7(join9(dir, ".codex")) || existsSync7(join9(dir, ".agents", "skills")) || existsSync7(join9(dir, "AGENTS.md"))) agents.push("codex");
6849
- if (existsSync7(join9(dir, ".github", "copilot-instructions.md")) || existsSync7(join9(dir, ".github", "instructions"))) agents.push("github-copilot");
6330
+ if (existsSync7(join9(dir, ".codex")) || existsSync7(join9(dir, ".agents", "skills"))) agents.push("codex");
6331
+ if (existsSync7(join9(dir, ".github", "copilot-instructions.md"))) agents.push("github-copilot");
6850
6332
  return agents.length > 0 ? agents : ["claude"];
6851
6333
  }
6852
6334
  function computeLocalScore(dir, targetAgent) {
@@ -7070,27 +6552,27 @@ import { PostHog } from "posthog-node";
7070
6552
  import chalk5 from "chalk";
7071
6553
 
7072
6554
  // src/telemetry/config.ts
7073
- import fs25 from "fs";
7074
- import path22 from "path";
6555
+ import fs24 from "fs";
6556
+ import path21 from "path";
7075
6557
  import os5 from "os";
7076
6558
  import crypto4 from "crypto";
7077
- import { execSync as execSync13 } from "child_process";
7078
- var CONFIG_DIR2 = path22.join(os5.homedir(), ".caliber");
7079
- var CONFIG_FILE2 = path22.join(CONFIG_DIR2, "config.json");
6559
+ import { execSync as execSync12 } from "child_process";
6560
+ var CONFIG_DIR2 = path21.join(os5.homedir(), ".caliber");
6561
+ var CONFIG_FILE2 = path21.join(CONFIG_DIR2, "config.json");
7080
6562
  var runtimeDisabled = false;
7081
6563
  function readConfig() {
7082
6564
  try {
7083
- if (!fs25.existsSync(CONFIG_FILE2)) return {};
7084
- return JSON.parse(fs25.readFileSync(CONFIG_FILE2, "utf-8"));
6565
+ if (!fs24.existsSync(CONFIG_FILE2)) return {};
6566
+ return JSON.parse(fs24.readFileSync(CONFIG_FILE2, "utf-8"));
7085
6567
  } catch {
7086
6568
  return {};
7087
6569
  }
7088
6570
  }
7089
6571
  function writeConfig(config) {
7090
- if (!fs25.existsSync(CONFIG_DIR2)) {
7091
- fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
6572
+ if (!fs24.existsSync(CONFIG_DIR2)) {
6573
+ fs24.mkdirSync(CONFIG_DIR2, { recursive: true });
7092
6574
  }
7093
- fs25.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
6575
+ fs24.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
7094
6576
  }
7095
6577
  function getMachineId() {
7096
6578
  const config = readConfig();
@@ -7100,57 +6582,11 @@ function getMachineId() {
7100
6582
  return machineId;
7101
6583
  }
7102
6584
  var EMAIL_HASH_KEY = "caliber-telemetry-v1";
7103
- var PERSONAL_DOMAINS = /* @__PURE__ */ new Set([
7104
- "gmail.com",
7105
- "googlemail.com",
7106
- "outlook.com",
7107
- "hotmail.com",
7108
- "live.com",
7109
- "yahoo.com",
7110
- "yahoo.co.uk",
7111
- "icloud.com",
7112
- "me.com",
7113
- "mac.com",
7114
- "aol.com",
7115
- "protonmail.com",
7116
- "proton.me",
7117
- "pm.me",
7118
- "mail.com",
7119
- "zoho.com",
7120
- "yandex.com",
7121
- "gmx.com",
7122
- "gmx.net",
7123
- "fastmail.com",
7124
- "tutanota.com",
7125
- "tuta.io"
7126
- ]);
7127
- function getGitEmail() {
7128
- try {
7129
- const email = execSync13("git config user.email", { encoding: "utf-8" }).trim();
7130
- return email || void 0;
7131
- } catch {
7132
- return void 0;
7133
- }
7134
- }
7135
- function getGitEmailInfo() {
7136
- const email = getGitEmail();
7137
- if (!email) return {};
7138
- const hash = crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
7139
- let domain;
7140
- if (email.includes("@")) {
7141
- const d = email.split("@")[1].toLowerCase();
7142
- if (!PERSONAL_DOMAINS.has(d)) domain = d;
7143
- }
7144
- return { hash, domain };
7145
- }
7146
- function getRepoHash() {
6585
+ function getGitEmailHash() {
7147
6586
  try {
7148
- const remote = execSync13("git remote get-url origin || git rev-parse --show-toplevel", {
7149
- encoding: "utf-8",
7150
- stdio: ["pipe", "pipe", "pipe"]
7151
- }).trim();
7152
- if (!remote) return void 0;
7153
- return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(remote).digest("hex").slice(0, 16);
6587
+ const email = execSync12("git config user.email", { encoding: "utf-8" }).trim();
6588
+ if (!email) return void 0;
6589
+ return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
7154
6590
  } catch {
7155
6591
  return void 0;
7156
6592
  }
@@ -7175,7 +6611,6 @@ function markNoticeShown() {
7175
6611
  var POSTHOG_KEY = "phc_XXrV0pSX4s2QVxVoOaeuyXDvtlRwPAjovt1ttMGVMPp";
7176
6612
  var client = null;
7177
6613
  var distinctId = null;
7178
- var superProperties = {};
7179
6614
  function initTelemetry() {
7180
6615
  if (isTelemetryDisabled()) return;
7181
6616
  const machineId = getMachineId();
@@ -7191,17 +6626,11 @@ function initTelemetry() {
7191
6626
  );
7192
6627
  markNoticeShown();
7193
6628
  }
7194
- const { hash: gitEmailHash, domain: emailDomain } = getGitEmailInfo();
7195
- const repoHash = getRepoHash();
7196
- superProperties = {
7197
- ...repoHash ? { repo_hash: repoHash } : {},
7198
- ...emailDomain ? { email_domain: emailDomain } : {}
7199
- };
6629
+ const gitEmailHash = getGitEmailHash();
7200
6630
  client.identify({
7201
6631
  distinctId: machineId,
7202
6632
  properties: {
7203
- ...gitEmailHash ? { git_email_hash: gitEmailHash } : {},
7204
- ...emailDomain ? { email_domain: emailDomain } : {}
6633
+ ...gitEmailHash ? { git_email_hash: gitEmailHash } : {}
7205
6634
  }
7206
6635
  });
7207
6636
  }
@@ -7210,7 +6639,7 @@ function trackEvent(name, properties) {
7210
6639
  client.capture({
7211
6640
  distinctId,
7212
6641
  event: name,
7213
- properties: { ...superProperties, ...properties }
6642
+ properties: properties ?? {}
7214
6643
  });
7215
6644
  }
7216
6645
  async function flushTelemetry() {
@@ -7259,14 +6688,11 @@ function trackInitSkillsSearch(searched, installedCount) {
7259
6688
  function trackInitScoreRegression(oldScore, newScore) {
7260
6689
  trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
7261
6690
  }
7262
- function trackInitCompleted(path42, score) {
7263
- trackEvent("init_completed", { path: path42, score });
7264
- }
7265
6691
  function trackRegenerateCompleted(action, durationMs) {
7266
6692
  trackEvent("regenerate_completed", { action, duration_ms: durationMs });
7267
6693
  }
7268
- function trackRefreshCompleted(changesCount, durationMs, trigger) {
7269
- trackEvent("refresh_completed", { changes_count: changesCount, duration_ms: durationMs, trigger: trigger ?? "manual" });
6694
+ function trackRefreshCompleted(changesCount, durationMs) {
6695
+ trackEvent("refresh_completed", { changes_count: changesCount, duration_ms: durationMs });
7270
6696
  }
7271
6697
  function trackScoreComputed(score, agent) {
7272
6698
  trackEvent("score_computed", { score, agent });
@@ -7280,9 +6706,6 @@ function trackSkillsInstalled(count) {
7280
6706
  function trackUndoExecuted() {
7281
6707
  trackEvent("undo_executed");
7282
6708
  }
7283
- function trackUninstallExecuted() {
7284
- trackEvent("uninstall_executed");
7285
- }
7286
6709
  function trackInitLearnEnabled(enabled) {
7287
6710
  trackEvent("init_learn_enabled", { enabled });
7288
6711
  }
@@ -8254,11 +7677,11 @@ function countIssuePoints(issues) {
8254
7677
  }
8255
7678
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
8256
7679
  const existsCache = /* @__PURE__ */ new Map();
8257
- const cachedExists = (path42) => {
8258
- const cached = existsCache.get(path42);
7680
+ const cachedExists = (path41) => {
7681
+ const cached = existsCache.get(path41);
8259
7682
  if (cached !== void 0) return cached;
8260
- const result = existsSync9(path42);
8261
- existsCache.set(path42, result);
7683
+ const result = existsSync9(path41);
7684
+ existsCache.set(path41, result);
8262
7685
  return result;
8263
7686
  };
8264
7687
  const projectStructure = collectProjectStructure(dir);
@@ -8397,8 +7820,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
8397
7820
  }
8398
7821
 
8399
7822
  // src/lib/debug-report.ts
8400
- import fs26 from "fs";
8401
- import path23 from "path";
7823
+ import fs25 from "fs";
7824
+ import path22 from "path";
8402
7825
  var DebugReport = class {
8403
7826
  sections = [];
8404
7827
  startTime;
@@ -8467,11 +7890,11 @@ var DebugReport = class {
8467
7890
  lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
8468
7891
  lines.push("");
8469
7892
  }
8470
- const dir = path23.dirname(outputPath);
8471
- if (!fs26.existsSync(dir)) {
8472
- fs26.mkdirSync(dir, { recursive: true });
7893
+ const dir = path22.dirname(outputPath);
7894
+ if (!fs25.existsSync(dir)) {
7895
+ fs25.mkdirSync(dir, { recursive: true });
8473
7896
  }
8474
- fs26.writeFileSync(outputPath, lines.join("\n"));
7897
+ fs25.writeFileSync(outputPath, lines.join("\n"));
8475
7898
  }
8476
7899
  };
8477
7900
  function formatMs(ms) {
@@ -8872,7 +8295,7 @@ import chalk11 from "chalk";
8872
8295
  import ora3 from "ora";
8873
8296
  import select5 from "@inquirer/select";
8874
8297
  import checkbox from "@inquirer/checkbox";
8875
- import fs29 from "fs";
8298
+ import fs28 from "fs";
8876
8299
 
8877
8300
  // src/ai/refine.ts
8878
8301
  async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
@@ -9013,10 +8436,10 @@ init_config();
9013
8436
  init_review();
9014
8437
  function detectAgents(dir) {
9015
8438
  const agents = [];
9016
- if (fs29.existsSync(`${dir}/.claude`)) agents.push("claude");
9017
- if (fs29.existsSync(`${dir}/.cursor`)) agents.push("cursor");
9018
- if (fs29.existsSync(`${dir}/.agents`) || fs29.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
9019
- if (fs29.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
8439
+ if (fs28.existsSync(`${dir}/.claude`)) agents.push("claude");
8440
+ if (fs28.existsSync(`${dir}/.cursor`)) agents.push("cursor");
8441
+ if (fs28.existsSync(`${dir}/.agents`) || fs28.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
8442
+ if (fs28.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
9020
8443
  return agents;
9021
8444
  }
9022
8445
  async function promptAgent(detected) {
@@ -9038,6 +8461,25 @@ async function promptAgent(detected) {
9038
8461
  });
9039
8462
  return selected;
9040
8463
  }
8464
+ async function promptLearnInstall(targetAgent) {
8465
+ const hasClaude = targetAgent.includes("claude");
8466
+ const hasCursor = targetAgent.includes("cursor");
8467
+ const agentName = hasClaude && hasCursor ? "Claude and Cursor" : hasClaude ? "Claude" : "Cursor";
8468
+ console.log(chalk11.bold(`
8469
+ Session Learning
8470
+ `));
8471
+ console.log(chalk11.dim(` Caliber can learn from your ${agentName} sessions \u2014 when a tool fails`));
8472
+ console.log(chalk11.dim(` or you correct a mistake, it captures the lesson so it won't`));
8473
+ console.log(chalk11.dim(` happen again. Runs once at session end using the fast model.
8474
+ `));
8475
+ return select5({
8476
+ message: "Enable session learning?",
8477
+ choices: [
8478
+ { name: "Enable session learning (recommended)", value: true },
8479
+ { name: "Skip for now", value: false }
8480
+ ]
8481
+ });
8482
+ }
9041
8483
  async function promptReviewAction(hasSkillResults, hasChanges, staged) {
9042
8484
  if (!hasChanges && !hasSkillResults) return "accept";
9043
8485
  const choices = [];
@@ -9119,18 +8561,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
9119
8561
 
9120
8562
  // src/commands/init-display.ts
9121
8563
  import chalk12 from "chalk";
9122
- import fs30 from "fs";
8564
+ import fs29 from "fs";
9123
8565
  function formatWhatChanged(setup) {
9124
8566
  const lines = [];
9125
8567
  const claude = setup.claude;
9126
8568
  const codex = setup.codex;
9127
8569
  const cursor = setup.cursor;
9128
8570
  if (claude?.claudeMd) {
9129
- const action = fs30.existsSync("CLAUDE.md") ? "Updated" : "Created";
8571
+ const action = fs29.existsSync("CLAUDE.md") ? "Updated" : "Created";
9130
8572
  lines.push(`${action} CLAUDE.md`);
9131
8573
  }
9132
8574
  if (codex?.agentsMd) {
9133
- const action = fs30.existsSync("AGENTS.md") ? "Updated" : "Created";
8575
+ const action = fs29.existsSync("AGENTS.md") ? "Updated" : "Created";
9134
8576
  lines.push(`${action} AGENTS.md`);
9135
8577
  }
9136
8578
  const allSkills = [];
@@ -9173,7 +8615,7 @@ function printSetupSummary(setup) {
9173
8615
  };
9174
8616
  if (claude) {
9175
8617
  if (claude.claudeMd) {
9176
- const icon = fs30.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
8618
+ const icon = fs29.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
9177
8619
  const desc = getDescription("CLAUDE.md");
9178
8620
  console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
9179
8621
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -9183,7 +8625,7 @@ function printSetupSummary(setup) {
9183
8625
  if (Array.isArray(skills) && skills.length > 0) {
9184
8626
  for (const skill of skills) {
9185
8627
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
9186
- const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8628
+ const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
9187
8629
  const desc = getDescription(skillPath);
9188
8630
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
9189
8631
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -9194,7 +8636,7 @@ function printSetupSummary(setup) {
9194
8636
  const codex = setup.codex;
9195
8637
  if (codex) {
9196
8638
  if (codex.agentsMd) {
9197
- const icon = fs30.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
8639
+ const icon = fs29.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
9198
8640
  const desc = getDescription("AGENTS.md");
9199
8641
  console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
9200
8642
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -9204,7 +8646,7 @@ function printSetupSummary(setup) {
9204
8646
  if (Array.isArray(codexSkills) && codexSkills.length > 0) {
9205
8647
  for (const skill of codexSkills) {
9206
8648
  const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
9207
- const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8649
+ const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
9208
8650
  const desc = getDescription(skillPath);
9209
8651
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
9210
8652
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -9214,7 +8656,7 @@ function printSetupSummary(setup) {
9214
8656
  }
9215
8657
  if (cursor) {
9216
8658
  if (cursor.cursorrules) {
9217
- const icon = fs30.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
8659
+ const icon = fs29.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
9218
8660
  const desc = getDescription(".cursorrules");
9219
8661
  console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
9220
8662
  if (desc) console.log(chalk12.dim(` ${desc}`));
@@ -9224,7 +8666,7 @@ function printSetupSummary(setup) {
9224
8666
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
9225
8667
  for (const skill of cursorSkills) {
9226
8668
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
9227
- const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
8669
+ const icon = fs29.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
9228
8670
  const desc = getDescription(skillPath);
9229
8671
  console.log(` ${icon} ${chalk12.bold(skillPath)}`);
9230
8672
  console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
@@ -9235,7 +8677,7 @@ function printSetupSummary(setup) {
9235
8677
  if (Array.isArray(rulesArr) && rulesArr.length > 0) {
9236
8678
  for (const rule of rulesArr) {
9237
8679
  const rulePath = `.cursor/rules/${rule.filename}`;
9238
- const icon = fs30.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
8680
+ const icon = fs29.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
9239
8681
  const desc = getDescription(rulePath);
9240
8682
  console.log(` ${icon} ${chalk12.bold(rulePath)}`);
9241
8683
  if (desc) {
@@ -9290,12 +8732,12 @@ function displayTokenUsage() {
9290
8732
  // src/commands/init-helpers.ts
9291
8733
  init_config();
9292
8734
  import chalk13 from "chalk";
9293
- import fs31 from "fs";
9294
- import path25 from "path";
8735
+ import fs30 from "fs";
8736
+ import path24 from "path";
9295
8737
  function isFirstRun(dir) {
9296
- const caliberDir = path25.join(dir, ".caliber");
8738
+ const caliberDir = path24.join(dir, ".caliber");
9297
8739
  try {
9298
- const stat = fs31.statSync(caliberDir);
8740
+ const stat = fs30.statSync(caliberDir);
9299
8741
  return !stat.isDirectory();
9300
8742
  } catch {
9301
8743
  return true;
@@ -9348,8 +8790,8 @@ function ensurePermissions(fingerprint) {
9348
8790
  const settingsPath = ".claude/settings.json";
9349
8791
  let settings = {};
9350
8792
  try {
9351
- if (fs31.existsSync(settingsPath)) {
9352
- settings = JSON.parse(fs31.readFileSync(settingsPath, "utf-8"));
8793
+ if (fs30.existsSync(settingsPath)) {
8794
+ settings = JSON.parse(fs30.readFileSync(settingsPath, "utf-8"));
9353
8795
  }
9354
8796
  } catch {
9355
8797
  }
@@ -9358,12 +8800,12 @@ function ensurePermissions(fingerprint) {
9358
8800
  if (Array.isArray(allow) && allow.length > 0) return;
9359
8801
  permissions.allow = derivePermissions(fingerprint);
9360
8802
  settings.permissions = permissions;
9361
- if (!fs31.existsSync(".claude")) fs31.mkdirSync(".claude", { recursive: true });
9362
- fs31.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
8803
+ if (!fs30.existsSync(".claude")) fs30.mkdirSync(".claude", { recursive: true });
8804
+ fs30.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
9363
8805
  }
9364
8806
  function writeErrorLog(config, rawOutput, error, stopReason) {
9365
8807
  try {
9366
- const logPath = path25.join(process.cwd(), ".caliber", "error-log.md");
8808
+ const logPath = path24.join(process.cwd(), ".caliber", "error-log.md");
9367
8809
  const lines = [
9368
8810
  `# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
9369
8811
  "",
@@ -9376,8 +8818,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
9376
8818
  lines.push("## Error", "```", error, "```", "");
9377
8819
  }
9378
8820
  lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
9379
- fs31.mkdirSync(path25.join(process.cwd(), ".caliber"), { recursive: true });
9380
- fs31.writeFileSync(logPath, lines.join("\n"));
8821
+ fs30.mkdirSync(path24.join(process.cwd(), ".caliber"), { recursive: true });
8822
+ fs30.writeFileSync(logPath, lines.join("\n"));
9381
8823
  console.log(chalk13.dim(`
9382
8824
  Error log written to .caliber/error-log.md`));
9383
8825
  } catch {
@@ -9428,13 +8870,13 @@ ${JSON.stringify(checkList, null, 2)}`,
9428
8870
  }
9429
8871
 
9430
8872
  // src/scoring/history.ts
9431
- import fs32 from "fs";
9432
- import path26 from "path";
8873
+ import fs31 from "fs";
8874
+ import path25 from "path";
9433
8875
  var HISTORY_FILE = "score-history.jsonl";
9434
8876
  var MAX_ENTRIES = 500;
9435
8877
  var TRIM_THRESHOLD = MAX_ENTRIES + 50;
9436
8878
  function historyFilePath() {
9437
- return path26.join(CALIBER_DIR, HISTORY_FILE);
8879
+ return path25.join(CALIBER_DIR, HISTORY_FILE);
9438
8880
  }
9439
8881
  function recordScore(result, trigger) {
9440
8882
  const entry = {
@@ -9445,14 +8887,14 @@ function recordScore(result, trigger) {
9445
8887
  trigger
9446
8888
  };
9447
8889
  try {
9448
- fs32.mkdirSync(CALIBER_DIR, { recursive: true });
8890
+ fs31.mkdirSync(CALIBER_DIR, { recursive: true });
9449
8891
  const filePath = historyFilePath();
9450
- fs32.appendFileSync(filePath, JSON.stringify(entry) + "\n");
9451
- const stat = fs32.statSync(filePath);
8892
+ fs31.appendFileSync(filePath, JSON.stringify(entry) + "\n");
8893
+ const stat = fs31.statSync(filePath);
9452
8894
  if (stat.size > TRIM_THRESHOLD * 120) {
9453
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
8895
+ const lines = fs31.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9454
8896
  if (lines.length > MAX_ENTRIES) {
9455
- fs32.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
8897
+ fs31.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
9456
8898
  }
9457
8899
  }
9458
8900
  } catch {
@@ -9461,7 +8903,7 @@ function recordScore(result, trigger) {
9461
8903
  function readScoreHistory() {
9462
8904
  const filePath = historyFilePath();
9463
8905
  try {
9464
- const content = fs32.readFileSync(filePath, "utf-8");
8906
+ const content = fs31.readFileSync(filePath, "utf-8");
9465
8907
  const entries = [];
9466
8908
  for (const line of content.split("\n")) {
9467
8909
  if (!line) continue;
@@ -9499,73 +8941,49 @@ async function initCommand(options) {
9499
8941
  const bin = resolveCaliber();
9500
8942
  const firstRun = isFirstRun(process.cwd());
9501
8943
  if (firstRun) {
9502
- console.log(brand.bold(`
8944
+ console.log(
8945
+ brand.bold(`
9503
8946
  \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
9504
8947
  \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
9505
8948
  \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
9506
8949
  \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
9507
8950
  \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
9508
8951
  \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
9509
- `));
9510
- console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
9511
- console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
8952
+ `)
8953
+ );
8954
+ console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
8955
+ console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
9512
8956
  console.log(title.bold(" How it works:\n"));
9513
- console.log(chalk14.dim(" 1. Connect Auto-detect your LLM provider and agents"));
9514
- console.log(chalk14.dim(" 2. Build Install sync, scan your project, generate configs"));
9515
- console.log(chalk14.dim(" 3. Done Review score and start syncing\n"));
8957
+ console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
8958
+ console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
8959
+ console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
8960
+ console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
9516
8961
  } else {
9517
- console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 setting up continuous sync\n"));
8962
+ console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
9518
8963
  }
9519
8964
  const platforms = detectPlatforms();
9520
8965
  if (!platforms.claude && !platforms.cursor && !platforms.codex) {
9521
8966
  console.log(chalk14.yellow(" \u26A0 No supported AI platforms detected (Claude, Cursor, Codex)."));
9522
- console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
8967
+ console.log(
8968
+ chalk14.yellow(
8969
+ " Caliber will still generate config files, but they won't be auto-installed.\n"
8970
+ )
8971
+ );
9523
8972
  }
9524
8973
  const report = options.debugReport ? new DebugReport() : null;
9525
- console.log(title.bold(" Step 1/3 \u2014 Connect\n"));
8974
+ console.log(title.bold(" Step 1/4 \u2014 Connect\n"));
9526
8975
  let config = loadConfig();
9527
- if (!config && !options.autoApprove) {
9528
- if (isClaudeCliAvailable()) {
9529
- console.log(chalk14.dim(" Detected: Claude Code CLI (uses your Pro/Max/Team subscription)\n"));
9530
- const useIt = await confirm2({ message: "Use Claude Code as your LLM provider?" });
9531
- if (useIt) {
9532
- const autoConfig = { provider: "claude-cli", model: "default" };
9533
- writeConfigFile(autoConfig);
9534
- config = autoConfig;
9535
- }
9536
- } else if (isCursorAgentAvailable() && isCursorLoggedIn()) {
9537
- console.log(chalk14.dim(" Detected: Cursor (uses your existing subscription)\n"));
9538
- const useIt = await confirm2({ message: "Use Cursor as your LLM provider?" });
9539
- if (useIt) {
9540
- const autoConfig = { provider: "cursor", model: "sonnet-4.6" };
9541
- writeConfigFile(autoConfig);
9542
- config = autoConfig;
9543
- }
9544
- }
9545
- }
9546
8976
  if (!config) {
9547
- if (options.autoApprove) {
9548
- if (isClaudeCliAvailable()) {
9549
- const autoConfig = { provider: "claude-cli", model: "default" };
9550
- writeConfigFile(autoConfig);
9551
- config = autoConfig;
9552
- } else if (isCursorAgentAvailable() && isCursorLoggedIn()) {
9553
- const autoConfig = { provider: "cursor", model: "sonnet-4.6" };
9554
- writeConfigFile(autoConfig);
9555
- config = autoConfig;
9556
- }
9557
- }
8977
+ console.log(chalk14.dim(" No LLM provider configured yet.\n"));
8978
+ await runInteractiveProviderSetup({
8979
+ selectMessage: "How do you want to use Caliber? (choose LLM provider)"
8980
+ });
8981
+ config = loadConfig();
9558
8982
  if (!config) {
9559
- console.log(chalk14.dim(" No LLM provider detected.\n"));
9560
- await runInteractiveProviderSetup({
9561
- selectMessage: "How do you want to use Caliber? (choose LLM provider)"
9562
- });
9563
- config = loadConfig();
9564
- if (!config) {
9565
- console.log(chalk14.red(" Configuration cancelled or failed.\n"));
9566
- throw new Error("__exit__");
9567
- }
8983
+ console.log(chalk14.red(" Configuration cancelled or failed.\n"));
8984
+ throw new Error("__exit__");
9568
8985
  }
8986
+ console.log(chalk14.green(" \u2713 Provider saved\n"));
9569
8987
  }
9570
8988
  trackInitProviderSelected(config.provider, config.model, firstRun);
9571
8989
  const displayModel = getDisplayModel(config);
@@ -9574,67 +8992,51 @@ async function initCommand(options) {
9574
8992
  console.log(chalk14.dim(modelLine + "\n"));
9575
8993
  if (report) {
9576
8994
  report.markStep("Provider connection");
9577
- report.addSection("LLM Provider", `- **Provider**: ${config.provider}
8995
+ report.addSection(
8996
+ "LLM Provider",
8997
+ `- **Provider**: ${config.provider}
9578
8998
  - **Model**: ${displayModel}
9579
- - **Fast model**: ${fastModel || "none"}`);
8999
+ - **Fast model**: ${fastModel || "none"}`
9000
+ );
9580
9001
  }
9581
9002
  await validateModel({ fast: true });
9582
9003
  let targetAgent;
9583
9004
  const agentAutoDetected = !options.agent;
9584
9005
  if (options.agent) {
9585
9006
  targetAgent = options.agent;
9007
+ } else if (options.autoApprove) {
9008
+ targetAgent = ["claude"];
9009
+ log(options.verbose, "Auto-approve: defaulting to claude agent");
9586
9010
  } else {
9587
9011
  const detected = detectAgents(process.cwd());
9588
- if (detected.length > 0 && (options.autoApprove || firstRun)) {
9589
- targetAgent = detected;
9590
- console.log(chalk14.dim(` Coding agents in this repo: ${detected.join(", ")}
9591
- `));
9592
- } else if (detected.length > 0) {
9593
- console.log(chalk14.dim(` Coding agents in this repo: ${detected.join(", ")}
9594
- `));
9595
- const useDetected = await confirm2({ message: "Generate configs for these agents?" });
9596
- targetAgent = useDetected ? detected : await promptAgent();
9597
- } else {
9598
- targetAgent = options.autoApprove ? ["claude"] : await promptAgent();
9599
- }
9012
+ targetAgent = await promptAgent(detected.length > 0 ? detected : void 0);
9600
9013
  }
9601
9014
  console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
9602
9015
  `));
9603
9016
  trackInitAgentSelected(targetAgent, agentAutoDetected);
9604
- console.log(title.bold(" Step 2/3 \u2014 Build\n"));
9605
- const hookResult = installPreCommitHook();
9606
- if (hookResult.installed) {
9607
- console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed`);
9608
- } else if (hookResult.alreadyInstalled) {
9609
- console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 active`);
9610
- }
9611
- const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
9612
- for (const agent of targetAgent) {
9613
- if (agent === "claude" && !fs33.existsSync(".claude")) fs33.mkdirSync(".claude", { recursive: true });
9614
- if (agent === "cursor" && !fs33.existsSync(".cursor")) fs33.mkdirSync(".cursor", { recursive: true });
9615
- if (agent === "codex" && !fs33.existsSync(".agents")) fs33.mkdirSync(".agents", { recursive: true });
9616
- }
9617
- const skillsWritten = ensureBuiltinSkills2();
9618
- if (skillsWritten.length > 0) {
9619
- console.log(` ${chalk14.green("\u2713")} Agent skills installed`);
9620
- }
9621
- const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
9622
- if (hasLearnableAgent) {
9623
- if (targetAgent.includes("claude")) installLearningHooks();
9624
- if (targetAgent.includes("cursor")) installCursorLearningHooks();
9625
- console.log(` ${chalk14.green("\u2713")} Session learning enabled`);
9626
- trackInitLearnEnabled(true);
9627
- }
9628
- console.log("");
9629
9017
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
9630
- log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
9018
+ console.log(chalk14.dim("\n Current config score:"));
9019
+ displayScoreSummary(baselineScore);
9020
+ if (options.verbose) {
9021
+ for (const c of baselineScore.checks) {
9022
+ log(
9023
+ options.verbose,
9024
+ ` ${c.passed ? "\u2713" : "\u2717"} ${c.name}: ${c.earnedPoints}/${c.maxPoints}${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
9025
+ );
9026
+ }
9027
+ }
9631
9028
  if (report) {
9632
9029
  report.markStep("Baseline scoring");
9633
- report.addSection("Scoring: Baseline", `**Score**: ${baselineScore.score}/100
9030
+ report.addSection(
9031
+ "Scoring: Baseline",
9032
+ `**Score**: ${baselineScore.score}/100
9634
9033
 
9635
9034
  | Check | Passed | Points | Max |
9636
9035
  |-------|--------|--------|-----|
9637
- ` + baselineScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
9036
+ ` + baselineScore.checks.map(
9037
+ (c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
9038
+ ).join("\n")
9039
+ );
9638
9040
  report.addSection("Generation: Target Agents", targetAgent.join(", "));
9639
9041
  }
9640
9042
  const hasExistingConfig = !!(baselineScore.checks.some((c) => c.id === "claude_md_exists" && c.passed) || baselineScore.checks.some((c) => c.id === "cursorrules_exists" && c.passed));
@@ -9648,77 +9050,33 @@ async function initCommand(options) {
9648
9050
  ]);
9649
9051
  const passingCount = baselineScore.checks.filter((c) => c.passed).length;
9650
9052
  const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
9053
+ if (hasExistingConfig && baselineScore.score === 100) {
9054
+ trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
9055
+ console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
9056
+ console.log(
9057
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n")
9058
+ );
9059
+ if (!options.force) return;
9060
+ }
9061
+ const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
9062
+ const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
9651
9063
  trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
9652
- let skipGeneration = false;
9653
- if (hasExistingConfig && baselineScore.score === 100 && !options.force) {
9654
- skipGeneration = true;
9655
- } else if (hasExistingConfig && !options.force && !options.autoApprove) {
9656
- console.log(chalk14.dim(` Config score: ${baselineScore.score}/100 \u2014 Caliber can improve this.
9657
- `));
9658
- const improveAnswer = await confirm2({ message: "Improve your existing configs?" });
9659
- skipGeneration = !improveAnswer;
9660
- }
9661
- if (skipGeneration) {
9662
- const {
9663
- appendManagedBlocks: appendManagedBlocks2,
9664
- getCursorPreCommitRule: getCursorPreCommitRule2,
9665
- getCursorLearningsRule: getCursorLearningsRule2,
9666
- getCursorSyncRule: getCursorSyncRule2,
9667
- getCursorSetupRule: getCursorSetupRule2
9668
- } = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
9669
- const claudeMdPath = "CLAUDE.md";
9670
- let claudeContent = "";
9671
- try {
9672
- claudeContent = fs33.readFileSync(claudeMdPath, "utf-8");
9673
- } catch {
9674
- }
9675
- if (!claudeContent) {
9676
- claudeContent = `# ${path27.basename(process.cwd())}
9677
- `;
9678
- }
9679
- const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
9680
- if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
9681
- fs33.writeFileSync(claudeMdPath, updatedClaude);
9682
- console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
9683
- }
9684
- if (targetAgent.includes("cursor")) {
9685
- const rulesDir = path27.join(".cursor", "rules");
9686
- if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
9687
- for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
9688
- fs33.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
9689
- }
9690
- console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
9691
- }
9692
- if (targetAgent.includes("github-copilot")) {
9693
- const copilotPath = path27.join(".github", "copilot-instructions.md");
9694
- let copilotContent = "";
9695
- try {
9696
- copilotContent = fs33.readFileSync(copilotPath, "utf-8");
9697
- } catch {
9698
- }
9699
- if (!copilotContent) {
9700
- fs33.mkdirSync(".github", { recursive: true });
9701
- copilotContent = `# ${path27.basename(process.cwd())}
9702
- `;
9703
- }
9704
- const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
9705
- if (updatedCopilot !== copilotContent) {
9706
- fs33.writeFileSync(copilotPath, updatedCopilot);
9707
- console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
9064
+ if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
9065
+ console.log(chalk14.bold.green("\n Your config is fully optimized for LLM generation.\n"));
9066
+ console.log(chalk14.dim(" Remaining items need CLI actions:\n"));
9067
+ for (const check of allFailingChecks) {
9068
+ console.log(chalk14.dim(` \u2022 ${check.name}`));
9069
+ if (check.suggestion) {
9070
+ console.log(` ${chalk14.hex("#83D1EB")(check.suggestion)}`);
9708
9071
  }
9709
9072
  }
9710
- const sha2 = getCurrentHeadSha();
9711
- writeState({
9712
- lastRefreshSha: sha2 ?? "",
9713
- lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
9714
- targetAgent
9715
- });
9716
- trackInitCompleted("sync-only", baselineScore.score);
9717
- console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
9718
- console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
9719
- console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
9073
+ console.log("");
9074
+ console.log(
9075
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n")
9076
+ );
9720
9077
  return;
9721
9078
  }
9079
+ console.log(title.bold(" Step 2/4 \u2014 Engine\n"));
9722
9080
  const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
9723
9081
  console.log(chalk14.dim(genModelInfo + "\n"));
9724
9082
  if (report) report.markStep("Generation");
@@ -9734,8 +9092,14 @@ async function initCommand(options) {
9734
9092
  const TASK_STACK = display.add("Detecting project stack", { pipelineLabel: "Scan" });
9735
9093
  const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
9736
9094
  const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
9737
- const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
9738
- const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
9095
+ const TASK_SKILLS_SEARCH = display.add("Searching community skills", {
9096
+ depth: 1,
9097
+ pipelineLabel: "Search",
9098
+ pipelineRow: 1
9099
+ });
9100
+ const TASK_SCORE_REFINE = display.add("Validating & refining config", {
9101
+ pipelineLabel: "Validate"
9102
+ });
9739
9103
  display.start();
9740
9104
  display.enableWaitingContent();
9741
9105
  try {
@@ -9745,21 +9109,39 @@ async function initCommand(options) {
9745
9109
  const stackSummary = stackParts.join(", ") || "no languages";
9746
9110
  const largeRepoNote = fingerprint.fileTree.length > 5e3 ? ` (${fingerprint.fileTree.length.toLocaleString()} files, smart sampling active)` : "";
9747
9111
  display.update(TASK_STACK, "done", stackSummary + largeRepoNote);
9748
- trackInitProjectDiscovered(fingerprint.languages.length, fingerprint.frameworks.length, fingerprint.fileTree.length);
9749
- log(options.verbose, `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`);
9112
+ trackInitProjectDiscovered(
9113
+ fingerprint.languages.length,
9114
+ fingerprint.frameworks.length,
9115
+ fingerprint.fileTree.length
9116
+ );
9117
+ log(
9118
+ options.verbose,
9119
+ `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`
9120
+ );
9750
9121
  const cliSources = options.source || [];
9751
9122
  const workspaces = getDetectedWorkspaces(process.cwd());
9752
9123
  const sources2 = resolveAllSources(process.cwd(), cliSources, workspaces);
9753
9124
  if (sources2.length > 0) {
9754
9125
  fingerprint.sources = sources2;
9755
- log(options.verbose, `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`);
9126
+ log(
9127
+ options.verbose,
9128
+ `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`
9129
+ );
9756
9130
  }
9757
9131
  if (report) {
9758
- report.addJson("Fingerprint: Git", { remote: fingerprint.gitRemoteUrl, packageName: fingerprint.packageName });
9132
+ report.addJson("Fingerprint: Git", {
9133
+ remote: fingerprint.gitRemoteUrl,
9134
+ packageName: fingerprint.packageName
9135
+ });
9759
9136
  report.addCodeBlock("Fingerprint: File Tree", fingerprint.fileTree.join("\n"));
9760
- report.addJson("Fingerprint: Detected Stack", { languages: fingerprint.languages, frameworks: fingerprint.frameworks, tools: fingerprint.tools });
9137
+ report.addJson("Fingerprint: Detected Stack", {
9138
+ languages: fingerprint.languages,
9139
+ frameworks: fingerprint.frameworks,
9140
+ tools: fingerprint.tools
9141
+ });
9761
9142
  report.addJson("Fingerprint: Existing Configs", fingerprint.existingConfigs);
9762
- if (fingerprint.codeAnalysis) report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
9143
+ if (fingerprint.codeAnalysis)
9144
+ report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
9763
9145
  }
9764
9146
  const isEmpty = fingerprint.fileTree.length < 3;
9765
9147
  if (isEmpty) {
@@ -9790,13 +9172,26 @@ async function initCommand(options) {
9790
9172
  let passingChecks;
9791
9173
  let currentScore;
9792
9174
  if (hasExistingConfig && localBaseline.score >= 95 && !options.force) {
9793
- const currentLlmFixable = localBaseline.checks.filter((c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id));
9794
- failingChecks = currentLlmFixable.map((c) => ({ name: c.name, suggestion: c.suggestion, fix: c.fix }));
9175
+ const currentLlmFixable = localBaseline.checks.filter(
9176
+ (c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id)
9177
+ );
9178
+ failingChecks = currentLlmFixable.map((c) => ({
9179
+ name: c.name,
9180
+ suggestion: c.suggestion,
9181
+ fix: c.fix
9182
+ }));
9795
9183
  passingChecks = localBaseline.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
9796
9184
  currentScore = localBaseline.score;
9797
9185
  }
9798
9186
  if (report) {
9799
- const fullPrompt = buildGeneratePrompt(fingerprint, targetAgent, fingerprint.description, failingChecks, currentScore, passingChecks);
9187
+ const fullPrompt = buildGeneratePrompt(
9188
+ fingerprint,
9189
+ targetAgent,
9190
+ fingerprint.description,
9191
+ failingChecks,
9192
+ currentScore,
9193
+ passingChecks
9194
+ );
9800
9195
  report.addCodeBlock("Generation: Full LLM Prompt", fullPrompt);
9801
9196
  }
9802
9197
  const result = await generateSetup(
@@ -9916,8 +9311,11 @@ async function initCommand(options) {
9916
9311
  if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
9917
9312
  report.addJson("Generation: Parsed Config", generatedSetup);
9918
9313
  }
9919
- log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
9920
- console.log(title.bold(" Step 3/3 \u2014 Done\n"));
9314
+ log(
9315
+ options.verbose,
9316
+ `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`
9317
+ );
9318
+ console.log(title.bold(" Step 3/4 \u2014 Review\n"));
9921
9319
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
9922
9320
  const staged = stageFiles(setupFiles, process.cwd());
9923
9321
  const totalChanges = staged.newFiles + staged.modifiedFiles;
@@ -9928,10 +9326,18 @@ async function initCommand(options) {
9928
9326
  }
9929
9327
  console.log("");
9930
9328
  }
9931
- console.log(chalk14.dim(` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`));
9329
+ console.log(
9330
+ chalk14.dim(
9331
+ ` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`
9332
+ )
9333
+ );
9932
9334
  if (skillSearchResult.results.length > 0) {
9933
- console.log(chalk14.dim(` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
9934
- `));
9335
+ console.log(
9336
+ chalk14.dim(
9337
+ ` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
9338
+ `
9339
+ )
9340
+ );
9935
9341
  } else {
9936
9342
  console.log("");
9937
9343
  }
@@ -9967,8 +9373,12 @@ async function initCommand(options) {
9967
9373
  }
9968
9374
  const updatedFiles = collectSetupFiles(generatedSetup, targetAgent);
9969
9375
  const restaged = stageFiles(updatedFiles, process.cwd());
9970
- console.log(chalk14.dim(` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
9971
- `));
9376
+ console.log(
9377
+ chalk14.dim(
9378
+ ` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
9379
+ `
9380
+ )
9381
+ );
9972
9382
  printSetupSummary(generatedSetup);
9973
9383
  const { openReview: openRev } = await Promise.resolve().then(() => (init_review(), review_exports));
9974
9384
  await openRev("terminal", restaged.stagedFiles);
@@ -9980,6 +9390,7 @@ async function initCommand(options) {
9980
9390
  console.log(chalk14.dim("Declined. No files were modified."));
9981
9391
  return;
9982
9392
  }
9393
+ console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
9983
9394
  if (options.dryRun) {
9984
9395
  console.log(chalk14.yellow("\n[Dry run] Would write the following files:"));
9985
9396
  console.log(JSON.stringify(generatedSetup, null, 2));
@@ -9988,13 +9399,14 @@ async function initCommand(options) {
9988
9399
  const { default: ora9 } = await import("ora");
9989
9400
  const writeSpinner = ora9("Writing config files...").start();
9990
9401
  try {
9991
- if (targetAgent.includes("codex") && !fs33.existsSync("AGENTS.md") && !generatedSetup.codex) {
9402
+ if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
9992
9403
  const claude = generatedSetup.claude;
9993
9404
  const cursor = generatedSetup.cursor;
9994
9405
  const agentRefs = [];
9995
9406
  if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
9996
9407
  if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
9997
- if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
9408
+ if (agentRefs.length === 0)
9409
+ agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
9998
9410
  const stubContent = `# AGENTS.md
9999
9411
 
10000
9412
  This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
@@ -10041,32 +9453,49 @@ ${agentRefs.join(" ")}
10041
9453
  if (afterScore.score < baselineScore.score) {
10042
9454
  trackInitScoreRegression(baselineScore.score, afterScore.score);
10043
9455
  console.log("");
10044
- console.log(chalk14.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
9456
+ console.log(
9457
+ chalk14.yellow(
9458
+ ` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`
9459
+ )
9460
+ );
10045
9461
  try {
10046
9462
  const { restored, removed } = undoSetup();
10047
9463
  if (restored.length > 0 || removed.length > 0) {
10048
- console.log(chalk14.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
9464
+ console.log(
9465
+ chalk14.dim(
9466
+ ` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`
9467
+ )
9468
+ );
10049
9469
  }
10050
9470
  } catch {
10051
9471
  }
10052
- console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n"));
9472
+ console.log(
9473
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n")
9474
+ );
10053
9475
  return;
10054
9476
  }
10055
9477
  if (report) {
10056
9478
  report.markStep("Post-write scoring");
10057
- report.addSection("Scoring: Post-Write", `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
9479
+ report.addSection(
9480
+ "Scoring: Post-Write",
9481
+ `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
10058
9482
 
10059
9483
  | Check | Passed | Points | Max |
10060
9484
  |-------|--------|--------|-----|
10061
- ` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
9485
+ ` + afterScore.checks.map(
9486
+ (c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
9487
+ ).join("\n")
9488
+ );
10062
9489
  }
10063
9490
  recordScore(afterScore, "init");
10064
- trackInitCompleted("full-generation", afterScore.score);
10065
9491
  displayScoreDelta(baselineScore, afterScore);
10066
9492
  if (options.verbose) {
10067
9493
  log(options.verbose, `Final score: ${afterScore.score}/100`);
10068
9494
  for (const c of afterScore.checks.filter((ch) => !ch.passed)) {
10069
- log(options.verbose, ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`);
9495
+ log(
9496
+ options.verbose,
9497
+ ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
9498
+ );
10070
9499
  }
10071
9500
  }
10072
9501
  let communitySkillsInstalled = 0;
@@ -10079,36 +9508,99 @@ ${agentRefs.join(" ")}
10079
9508
  communitySkillsInstalled = selected.length;
10080
9509
  }
10081
9510
  }
9511
+ console.log("");
9512
+ console.log(
9513
+ ` ${chalk14.green("\u2713")} Docs auto-refresh ${chalk14.dim(`agents run ${resolveCaliber()} refresh before commits`)}`
9514
+ );
10082
9515
  trackInitHookSelected("config-instructions");
9516
+ const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
9517
+ let enableLearn = false;
9518
+ if (hasLearnableAgent) {
9519
+ if (!options.autoApprove) {
9520
+ enableLearn = await promptLearnInstall(targetAgent);
9521
+ trackInitLearnEnabled(enableLearn);
9522
+ if (enableLearn) {
9523
+ if (targetAgent.includes("claude")) {
9524
+ const r = installLearningHooks();
9525
+ if (r.installed)
9526
+ console.log(` ${chalk14.green("\u2713")} Learning hooks installed for Claude Code`);
9527
+ else if (r.alreadyInstalled)
9528
+ console.log(chalk14.dim(" Claude Code learning hooks already installed"));
9529
+ }
9530
+ if (targetAgent.includes("cursor")) {
9531
+ const r = installCursorLearningHooks();
9532
+ if (r.installed) console.log(` ${chalk14.green("\u2713")} Learning hooks installed for Cursor`);
9533
+ else if (r.alreadyInstalled)
9534
+ console.log(chalk14.dim(" Cursor learning hooks already installed"));
9535
+ }
9536
+ console.log(
9537
+ chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} learn status`) + chalk14.dim(" to see insights")
9538
+ );
9539
+ } else {
9540
+ console.log(
9541
+ chalk14.dim(" Skipped. Run ") + chalk14.hex("#83D1EB")(`${bin} learn install`) + chalk14.dim(" later to enable.")
9542
+ );
9543
+ }
9544
+ } else {
9545
+ enableLearn = true;
9546
+ if (targetAgent.includes("claude")) installLearningHooks();
9547
+ if (targetAgent.includes("cursor")) installCursorLearningHooks();
9548
+ }
9549
+ }
9550
+ console.log(chalk14.bold.green("\n Configuration complete!"));
9551
+ console.log(
9552
+ chalk14.dim(" Your AI agents now understand your project's architecture, build commands,")
9553
+ );
9554
+ console.log(
9555
+ chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n")
9556
+ );
10083
9557
  const done = chalk14.green("\u2713");
10084
- console.log(chalk14.bold.green("\n Caliber is set up!\n"));
10085
- console.log(chalk14.bold(" What's configured:\n"));
10086
- console.log(` ${done} Continuous sync ${chalk14.dim("pre-commit hook keeps all agent configs in sync")}`);
10087
- console.log(` ${done} Config generated ${chalk14.dim(`score: ${afterScore.score}/100`)}`);
10088
- console.log(` ${done} Agent skills ${chalk14.dim("/setup-caliber for new team members")}`);
9558
+ const skip = chalk14.dim("\u2013");
9559
+ console.log(chalk14.bold(" What was configured:\n"));
9560
+ console.log(
9561
+ ` ${done} Config generated ${title(`${bin} score`)} ${chalk14.dim("for full breakdown")}`
9562
+ );
9563
+ console.log(
9564
+ ` ${done} Docs auto-refresh ${chalk14.dim(`agents run ${bin} refresh before commits`)}`
9565
+ );
10089
9566
  if (hasLearnableAgent) {
10090
- console.log(` ${done} Session learning ${chalk14.dim("learns from your corrections")}`);
9567
+ if (enableLearn) {
9568
+ console.log(
9569
+ ` ${done} Session learning ${chalk14.dim("agent learns from your feedback")}`
9570
+ );
9571
+ } else {
9572
+ console.log(
9573
+ ` ${skip} Session learning ${title(`${bin} learn install`)} to enable later`
9574
+ );
9575
+ }
10091
9576
  }
10092
9577
  if (communitySkillsInstalled > 0) {
10093
- console.log(` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} installed for your stack`)}`);
10094
- }
10095
- console.log(chalk14.bold("\n What happens next:\n"));
10096
- console.log(chalk14.dim(" Every commit syncs your agent configs automatically."));
10097
- console.log(chalk14.dim(" New team members run /setup-caliber to get set up instantly.\n"));
10098
- console.log(` ${title(`${bin} score`)} Full scoring breakdown`);
10099
- console.log(` ${title(`${bin} skills`)} Find community skills`);
10100
- console.log(` ${title(`${bin} undo`)} Revert changes`);
10101
- console.log(` ${title(`${bin} uninstall`)} Remove Caliber completely`);
9578
+ console.log(
9579
+ ` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} skill${communitySkillsInstalled > 1 ? "s" : ""} installed for your stack`)}`
9580
+ );
9581
+ } else if (skillSearchResult.results.length > 0) {
9582
+ console.log(` ${skip} Community skills ${chalk14.dim("available but skipped")}`);
9583
+ }
9584
+ console.log(chalk14.bold("\n Explore next:\n"));
9585
+ console.log(
9586
+ ` ${title(`${bin} skills`)} Find more community skills as your codebase evolves`
9587
+ );
9588
+ console.log(
9589
+ ` ${title(`${bin} score`)} See the full scoring breakdown with improvement tips`
9590
+ );
9591
+ console.log(` ${title(`${bin} undo`)} Revert all changes from this run`);
10102
9592
  console.log("");
10103
9593
  if (options.showTokens) {
10104
9594
  displayTokenUsage();
10105
9595
  }
10106
9596
  if (report) {
10107
9597
  report.markStep("Finished");
10108
- const reportPath = path27.join(process.cwd(), ".caliber", "debug-report.md");
9598
+ const reportPath = path26.join(process.cwd(), ".caliber", "debug-report.md");
10109
9599
  report.write(reportPath);
10110
- console.log(chalk14.dim(` Debug report written to ${path27.relative(process.cwd(), reportPath)}
10111
- `));
9600
+ console.log(
9601
+ chalk14.dim(` Debug report written to ${path26.relative(process.cwd(), reportPath)}
9602
+ `)
9603
+ );
10112
9604
  }
10113
9605
  }
10114
9606
 
@@ -10146,7 +9638,7 @@ function undoCommand() {
10146
9638
 
10147
9639
  // src/commands/status.ts
10148
9640
  import chalk16 from "chalk";
10149
- import fs34 from "fs";
9641
+ import fs33 from "fs";
10150
9642
  init_config();
10151
9643
  init_resolve_caliber();
10152
9644
  async function statusCommand(options) {
@@ -10175,7 +9667,7 @@ async function statusCommand(options) {
10175
9667
  }
10176
9668
  console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
10177
9669
  for (const entry of manifest.entries) {
10178
- const exists = fs34.existsSync(entry.path);
9670
+ const exists = fs33.existsSync(entry.path);
10179
9671
  const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
10180
9672
  console.log(` ${icon} ${entry.path} (${entry.action})`);
10181
9673
  }
@@ -10332,9 +9824,9 @@ async function regenerateCommand(options) {
10332
9824
  }
10333
9825
 
10334
9826
  // src/commands/score.ts
10335
- import fs35 from "fs";
9827
+ import fs34 from "fs";
10336
9828
  import os7 from "os";
10337
- import path28 from "path";
9829
+ import path27 from "path";
10338
9830
  import { execFileSync as execFileSync2 } from "child_process";
10339
9831
  import chalk18 from "chalk";
10340
9832
  init_resolve_caliber();
@@ -10342,7 +9834,7 @@ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS
10342
9834
  var CONFIG_DIRS = [".claude", ".cursor"];
10343
9835
  function scoreBaseRef(ref, target) {
10344
9836
  if (!/^[\w.\-/~^@{}]+$/.test(ref)) return null;
10345
- const tmpDir = fs35.mkdtempSync(path28.join(os7.tmpdir(), "caliber-compare-"));
9837
+ const tmpDir = fs34.mkdtempSync(path27.join(os7.tmpdir(), "caliber-compare-"));
10346
9838
  try {
10347
9839
  for (const file of CONFIG_FILES) {
10348
9840
  try {
@@ -10350,7 +9842,7 @@ function scoreBaseRef(ref, target) {
10350
9842
  encoding: "utf-8",
10351
9843
  stdio: ["pipe", "pipe", "pipe"]
10352
9844
  });
10353
- fs35.writeFileSync(path28.join(tmpDir, file), content);
9845
+ fs34.writeFileSync(path27.join(tmpDir, file), content);
10354
9846
  } catch {
10355
9847
  }
10356
9848
  }
@@ -10361,13 +9853,13 @@ function scoreBaseRef(ref, target) {
10361
9853
  stdio: ["pipe", "pipe", "pipe"]
10362
9854
  }).trim().split("\n").filter(Boolean);
10363
9855
  for (const file of files) {
10364
- const filePath = path28.join(tmpDir, file);
10365
- fs35.mkdirSync(path28.dirname(filePath), { recursive: true });
9856
+ const filePath = path27.join(tmpDir, file);
9857
+ fs34.mkdirSync(path27.dirname(filePath), { recursive: true });
10366
9858
  const content = execFileSync2("git", ["show", `${ref}:${file}`], {
10367
9859
  encoding: "utf-8",
10368
9860
  stdio: ["pipe", "pipe", "pipe"]
10369
9861
  });
10370
- fs35.writeFileSync(filePath, content);
9862
+ fs34.writeFileSync(filePath, content);
10371
9863
  }
10372
9864
  } catch {
10373
9865
  }
@@ -10377,7 +9869,7 @@ function scoreBaseRef(ref, target) {
10377
9869
  } catch {
10378
9870
  return null;
10379
9871
  } finally {
10380
- fs35.rmSync(tmpDir, { recursive: true, force: true });
9872
+ fs34.rmSync(tmpDir, { recursive: true, force: true });
10381
9873
  }
10382
9874
  }
10383
9875
  async function scoreCommand(options) {
@@ -10461,13 +9953,13 @@ async function scoreCommand(options) {
10461
9953
  }
10462
9954
 
10463
9955
  // src/commands/refresh.ts
10464
- import fs39 from "fs";
10465
- import path32 from "path";
9956
+ import fs38 from "fs";
9957
+ import path31 from "path";
10466
9958
  import chalk19 from "chalk";
10467
9959
  import ora6 from "ora";
10468
9960
 
10469
9961
  // src/lib/git-diff.ts
10470
- import { execSync as execSync15 } from "child_process";
9962
+ import { execSync as execSync14 } from "child_process";
10471
9963
  var MAX_DIFF_BYTES = 1e5;
10472
9964
  var DOC_PATTERNS = [
10473
9965
  "CLAUDE.md",
@@ -10482,7 +9974,7 @@ function excludeArgs() {
10482
9974
  }
10483
9975
  function safeExec(cmd) {
10484
9976
  try {
10485
- return execSync15(cmd, {
9977
+ return execSync14(cmd, {
10486
9978
  encoding: "utf-8",
10487
9979
  stdio: ["pipe", "pipe", "pipe"],
10488
9980
  maxBuffer: 10 * 1024 * 1024
@@ -10540,49 +10032,48 @@ function collectDiff(lastSha) {
10540
10032
  }
10541
10033
 
10542
10034
  // src/writers/refresh.ts
10543
- init_pre_commit_block();
10544
- import fs36 from "fs";
10545
- import path29 from "path";
10035
+ import fs35 from "fs";
10036
+ import path28 from "path";
10546
10037
  function writeRefreshDocs(docs) {
10547
10038
  const written = [];
10548
10039
  if (docs.claudeMd) {
10549
- fs36.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10040
+ fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
10550
10041
  written.push("CLAUDE.md");
10551
10042
  }
10552
10043
  if (docs.readmeMd) {
10553
- fs36.writeFileSync("README.md", docs.readmeMd);
10044
+ fs35.writeFileSync("README.md", docs.readmeMd);
10554
10045
  written.push("README.md");
10555
10046
  }
10556
10047
  if (docs.cursorrules) {
10557
- fs36.writeFileSync(".cursorrules", docs.cursorrules);
10048
+ fs35.writeFileSync(".cursorrules", docs.cursorrules);
10558
10049
  written.push(".cursorrules");
10559
10050
  }
10560
10051
  if (docs.cursorRules) {
10561
- const rulesDir = path29.join(".cursor", "rules");
10562
- if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
10052
+ const rulesDir = path28.join(".cursor", "rules");
10053
+ if (!fs35.existsSync(rulesDir)) fs35.mkdirSync(rulesDir, { recursive: true });
10563
10054
  for (const rule of docs.cursorRules) {
10564
- fs36.writeFileSync(path29.join(rulesDir, rule.filename), rule.content);
10055
+ fs35.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
10565
10056
  written.push(`.cursor/rules/${rule.filename}`);
10566
10057
  }
10567
10058
  }
10568
10059
  if (docs.claudeSkills) {
10569
- const skillsDir = path29.join(".claude", "skills");
10570
- if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
10060
+ const skillsDir = path28.join(".claude", "skills");
10061
+ if (!fs35.existsSync(skillsDir)) fs35.mkdirSync(skillsDir, { recursive: true });
10571
10062
  for (const skill of docs.claudeSkills) {
10572
- fs36.writeFileSync(path29.join(skillsDir, skill.filename), skill.content);
10063
+ fs35.writeFileSync(path28.join(skillsDir, skill.filename), skill.content);
10573
10064
  written.push(`.claude/skills/${skill.filename}`);
10574
10065
  }
10575
10066
  }
10576
10067
  if (docs.copilotInstructions) {
10577
- fs36.mkdirSync(".github", { recursive: true });
10578
- fs36.writeFileSync(path29.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10068
+ fs35.mkdirSync(".github", { recursive: true });
10069
+ fs35.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
10579
10070
  written.push(".github/copilot-instructions.md");
10580
10071
  }
10581
10072
  if (docs.copilotInstructionFiles) {
10582
- const instructionsDir = path29.join(".github", "instructions");
10583
- fs36.mkdirSync(instructionsDir, { recursive: true });
10073
+ const instructionsDir = path28.join(".github", "instructions");
10074
+ fs35.mkdirSync(instructionsDir, { recursive: true });
10584
10075
  for (const file of docs.copilotInstructionFiles) {
10585
- fs36.writeFileSync(path29.join(instructionsDir, file.filename), file.content);
10076
+ fs35.writeFileSync(path28.join(instructionsDir, file.filename), file.content);
10586
10077
  written.push(`.github/instructions/${file.filename}`);
10587
10078
  }
10588
10079
  }
@@ -10668,8 +10159,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
10668
10159
  }
10669
10160
 
10670
10161
  // src/learner/writer.ts
10671
- import fs37 from "fs";
10672
- import path30 from "path";
10162
+ import fs36 from "fs";
10163
+ import path29 from "path";
10673
10164
 
10674
10165
  // src/learner/utils.ts
10675
10166
  var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
@@ -10786,20 +10277,20 @@ function deduplicateLearnedItems(existing, incoming) {
10786
10277
  }
10787
10278
  function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
10788
10279
  const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
10789
- fs37.writeFileSync(filePath, header + merged + "\n");
10790
- if (mode) fs37.chmodSync(filePath, mode);
10280
+ fs36.writeFileSync(filePath, header + merged + "\n");
10281
+ if (mode) fs36.chmodSync(filePath, mode);
10791
10282
  return { newCount, newItems };
10792
10283
  }
10793
10284
  function writeLearnedSection(content) {
10794
10285
  return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
10795
10286
  }
10796
10287
  function writeLearnedSkill(skill) {
10797
- const skillDir = path30.join(".claude", "skills", skill.name);
10798
- if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
10799
- const skillPath = path30.join(skillDir, "SKILL.md");
10800
- if (!skill.isNew && fs37.existsSync(skillPath)) {
10801
- const existing = fs37.readFileSync(skillPath, "utf-8");
10802
- fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
10288
+ const skillDir = path29.join(".claude", "skills", skill.name);
10289
+ if (!fs36.existsSync(skillDir)) fs36.mkdirSync(skillDir, { recursive: true });
10290
+ const skillPath = path29.join(skillDir, "SKILL.md");
10291
+ if (!skill.isNew && fs36.existsSync(skillPath)) {
10292
+ const existing = fs36.readFileSync(skillPath, "utf-8");
10293
+ fs36.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
10803
10294
  } else {
10804
10295
  const frontmatter = [
10805
10296
  "---",
@@ -10808,12 +10299,12 @@ function writeLearnedSkill(skill) {
10808
10299
  "---",
10809
10300
  ""
10810
10301
  ].join("\n");
10811
- fs37.writeFileSync(skillPath, frontmatter + skill.content);
10302
+ fs36.writeFileSync(skillPath, frontmatter + skill.content);
10812
10303
  }
10813
10304
  return skillPath;
10814
10305
  }
10815
10306
  function writePersonalLearnedSection(content) {
10816
- if (!fs37.existsSync(AUTH_DIR)) fs37.mkdirSync(AUTH_DIR, { recursive: true });
10307
+ if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
10817
10308
  return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
10818
10309
  }
10819
10310
  function addLearning(bullet, scope = "project") {
@@ -10826,56 +10317,55 @@ function addLearning(bullet, scope = "project") {
10826
10317
  return { file: LEARNINGS_FILE, added: result.newCount > 0 };
10827
10318
  }
10828
10319
  function readPersonalLearnings() {
10829
- if (!fs37.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
10830
- const content = fs37.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
10320
+ if (!fs36.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
10321
+ const content = fs36.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
10831
10322
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
10832
10323
  return bullets || null;
10833
10324
  }
10834
10325
  function readLearnedSection() {
10835
- if (fs37.existsSync(LEARNINGS_FILE)) {
10836
- const content2 = fs37.readFileSync(LEARNINGS_FILE, "utf-8");
10326
+ if (fs36.existsSync(LEARNINGS_FILE)) {
10327
+ const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
10837
10328
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
10838
10329
  return bullets || null;
10839
10330
  }
10840
10331
  const claudeMdPath = "CLAUDE.md";
10841
- if (!fs37.existsSync(claudeMdPath)) return null;
10842
- const content = fs37.readFileSync(claudeMdPath, "utf-8");
10332
+ if (!fs36.existsSync(claudeMdPath)) return null;
10333
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
10843
10334
  const startIdx = content.indexOf(LEARNED_START);
10844
10335
  const endIdx = content.indexOf(LEARNED_END);
10845
10336
  if (startIdx === -1 || endIdx === -1) return null;
10846
10337
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
10847
10338
  }
10848
10339
  function migrateInlineLearnings() {
10849
- if (fs37.existsSync(LEARNINGS_FILE)) return false;
10340
+ if (fs36.existsSync(LEARNINGS_FILE)) return false;
10850
10341
  const claudeMdPath = "CLAUDE.md";
10851
- if (!fs37.existsSync(claudeMdPath)) return false;
10852
- const content = fs37.readFileSync(claudeMdPath, "utf-8");
10342
+ if (!fs36.existsSync(claudeMdPath)) return false;
10343
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
10853
10344
  const startIdx = content.indexOf(LEARNED_START);
10854
10345
  const endIdx = content.indexOf(LEARNED_END);
10855
10346
  if (startIdx === -1 || endIdx === -1) return false;
10856
10347
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
10857
10348
  if (!section) return false;
10858
- fs37.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
10349
+ fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
10859
10350
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
10860
- fs37.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
10351
+ fs36.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
10861
10352
  return true;
10862
10353
  }
10863
10354
 
10864
10355
  // src/commands/refresh.ts
10865
10356
  init_config();
10866
10357
  init_resolve_caliber();
10867
- init_builtin_skills();
10868
10358
  function log2(quiet, ...args) {
10869
10359
  if (!quiet) console.log(...args);
10870
10360
  }
10871
10361
  function discoverGitRepos(parentDir) {
10872
10362
  const repos = [];
10873
10363
  try {
10874
- const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
10364
+ const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
10875
10365
  for (const entry of entries) {
10876
10366
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
10877
- const childPath = path32.join(parentDir, entry.name);
10878
- if (fs39.existsSync(path32.join(childPath, ".git"))) {
10367
+ const childPath = path31.join(parentDir, entry.name);
10368
+ if (fs38.existsSync(path31.join(childPath, ".git"))) {
10879
10369
  repos.push(childPath);
10880
10370
  }
10881
10371
  }
@@ -10961,27 +10451,26 @@ async function refreshSingleRepo(repoDir, options) {
10961
10451
  const filesToWrite = response.docsUpdated || [];
10962
10452
  const preRefreshContents = /* @__PURE__ */ new Map();
10963
10453
  for (const filePath of filesToWrite) {
10964
- const fullPath = path32.resolve(repoDir, filePath);
10454
+ const fullPath = path31.resolve(repoDir, filePath);
10965
10455
  try {
10966
- preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
10456
+ preRefreshContents.set(filePath, fs38.readFileSync(fullPath, "utf-8"));
10967
10457
  } catch {
10968
10458
  preRefreshContents.set(filePath, null);
10969
10459
  }
10970
10460
  }
10971
10461
  const written = writeRefreshDocs(response.updatedDocs);
10972
- const trigger = quiet ? "hook" : "manual";
10973
- trackRefreshCompleted(written.length, Date.now(), trigger);
10462
+ trackRefreshCompleted(written.length, Date.now());
10974
10463
  const postScore = computeLocalScore(repoDir, targetAgent);
10975
10464
  if (postScore.score < preScore.score) {
10976
10465
  for (const [filePath, content] of preRefreshContents) {
10977
- const fullPath = path32.resolve(repoDir, filePath);
10466
+ const fullPath = path31.resolve(repoDir, filePath);
10978
10467
  if (content === null) {
10979
10468
  try {
10980
- fs39.unlinkSync(fullPath);
10469
+ fs38.unlinkSync(fullPath);
10981
10470
  } catch {
10982
10471
  }
10983
10472
  } else {
10984
- fs39.writeFileSync(fullPath, content);
10473
+ fs38.writeFileSync(fullPath, content);
10985
10474
  }
10986
10475
  }
10987
10476
  spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
@@ -11036,7 +10525,7 @@ async function refreshCommand(options) {
11036
10525
  `));
11037
10526
  const originalDir = process.cwd();
11038
10527
  for (const repo of repos) {
11039
- const repoName = path32.basename(repo);
10528
+ const repoName = path31.basename(repo);
11040
10529
  try {
11041
10530
  process.chdir(repo);
11042
10531
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -11058,6 +10547,150 @@ async function refreshCommand(options) {
11058
10547
  // src/commands/hooks.ts
11059
10548
  import chalk20 from "chalk";
11060
10549
  import fs40 from "fs";
10550
+
10551
+ // src/lib/hooks.ts
10552
+ init_resolve_caliber();
10553
+ import fs39 from "fs";
10554
+ import path32 from "path";
10555
+ import { execSync as execSync15 } from "child_process";
10556
+ var SETTINGS_PATH2 = path32.join(".claude", "settings.json");
10557
+ var REFRESH_TAIL = "refresh --quiet";
10558
+ var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
10559
+ function getHookCommand() {
10560
+ return `${resolveCaliber()} ${REFRESH_TAIL}`;
10561
+ }
10562
+ function readSettings2() {
10563
+ if (!fs39.existsSync(SETTINGS_PATH2)) return {};
10564
+ try {
10565
+ return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
10566
+ } catch {
10567
+ return {};
10568
+ }
10569
+ }
10570
+ function writeSettings2(settings) {
10571
+ const dir = path32.dirname(SETTINGS_PATH2);
10572
+ if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
10573
+ fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
10574
+ }
10575
+ function findHookIndex(sessionEnd) {
10576
+ return sessionEnd.findIndex(
10577
+ (entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
10578
+ );
10579
+ }
10580
+ function isHookInstalled() {
10581
+ const settings = readSettings2();
10582
+ const sessionEnd = settings.hooks?.SessionEnd;
10583
+ if (!Array.isArray(sessionEnd)) return false;
10584
+ return findHookIndex(sessionEnd) !== -1;
10585
+ }
10586
+ function installHook() {
10587
+ const settings = readSettings2();
10588
+ if (!settings.hooks) settings.hooks = {};
10589
+ if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
10590
+ if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
10591
+ return { installed: false, alreadyInstalled: true };
10592
+ }
10593
+ settings.hooks.SessionEnd.push({
10594
+ matcher: "",
10595
+ hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
10596
+ });
10597
+ writeSettings2(settings);
10598
+ return { installed: true, alreadyInstalled: false };
10599
+ }
10600
+ function removeHook() {
10601
+ const settings = readSettings2();
10602
+ const sessionEnd = settings.hooks?.SessionEnd;
10603
+ if (!Array.isArray(sessionEnd)) {
10604
+ return { removed: false, notFound: true };
10605
+ }
10606
+ const idx = findHookIndex(sessionEnd);
10607
+ if (idx === -1) {
10608
+ return { removed: false, notFound: true };
10609
+ }
10610
+ sessionEnd.splice(idx, 1);
10611
+ if (sessionEnd.length === 0) {
10612
+ delete settings.hooks.SessionEnd;
10613
+ }
10614
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
10615
+ delete settings.hooks;
10616
+ }
10617
+ writeSettings2(settings);
10618
+ return { removed: true, notFound: false };
10619
+ }
10620
+ var PRECOMMIT_START = "# caliber:pre-commit:start";
10621
+ var PRECOMMIT_END = "# caliber:pre-commit:end";
10622
+ function getPrecommitBlock() {
10623
+ const bin = resolveCaliber();
10624
+ const npx = isNpxResolution();
10625
+ const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
10626
+ const invoke = npx ? bin : `"${bin}"`;
10627
+ return `${PRECOMMIT_START}
10628
+ if ${guard}; then
10629
+ echo "\\033[2mcaliber: refreshing docs...\\033[0m"
10630
+ ${invoke} refresh 2>/dev/null || true
10631
+ ${invoke} learn finalize 2>/dev/null || true
10632
+ git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
10633
+ fi
10634
+ ${PRECOMMIT_END}`;
10635
+ }
10636
+ function getGitHooksDir() {
10637
+ try {
10638
+ const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10639
+ return path32.join(gitDir, "hooks");
10640
+ } catch {
10641
+ return null;
10642
+ }
10643
+ }
10644
+ function getPreCommitPath() {
10645
+ const hooksDir = getGitHooksDir();
10646
+ return hooksDir ? path32.join(hooksDir, "pre-commit") : null;
10647
+ }
10648
+ function isPreCommitHookInstalled() {
10649
+ const hookPath = getPreCommitPath();
10650
+ if (!hookPath || !fs39.existsSync(hookPath)) return false;
10651
+ const content = fs39.readFileSync(hookPath, "utf-8");
10652
+ return content.includes(PRECOMMIT_START);
10653
+ }
10654
+ function installPreCommitHook() {
10655
+ if (isPreCommitHookInstalled()) {
10656
+ return { installed: false, alreadyInstalled: true };
10657
+ }
10658
+ const hookPath = getPreCommitPath();
10659
+ if (!hookPath) return { installed: false, alreadyInstalled: false };
10660
+ const hooksDir = path32.dirname(hookPath);
10661
+ if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
10662
+ let content = "";
10663
+ if (fs39.existsSync(hookPath)) {
10664
+ content = fs39.readFileSync(hookPath, "utf-8");
10665
+ if (!content.endsWith("\n")) content += "\n";
10666
+ content += "\n" + getPrecommitBlock() + "\n";
10667
+ } else {
10668
+ content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
10669
+ }
10670
+ fs39.writeFileSync(hookPath, content);
10671
+ fs39.chmodSync(hookPath, 493);
10672
+ return { installed: true, alreadyInstalled: false };
10673
+ }
10674
+ function removePreCommitHook() {
10675
+ const hookPath = getPreCommitPath();
10676
+ if (!hookPath || !fs39.existsSync(hookPath)) {
10677
+ return { removed: false, notFound: true };
10678
+ }
10679
+ let content = fs39.readFileSync(hookPath, "utf-8");
10680
+ if (!content.includes(PRECOMMIT_START)) {
10681
+ return { removed: false, notFound: true };
10682
+ }
10683
+ const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
10684
+ content = content.replace(regex, "\n");
10685
+ if (content.trim() === "#!/bin/sh" || content.trim() === "") {
10686
+ fs39.unlinkSync(hookPath);
10687
+ } else {
10688
+ fs39.writeFileSync(hookPath, content);
10689
+ }
10690
+ return { removed: true, notFound: false };
10691
+ }
10692
+
10693
+ // src/commands/hooks.ts
11061
10694
  var HOOKS = [
11062
10695
  {
11063
10696
  id: "session-end",
@@ -12666,199 +12299,10 @@ async function publishCommand() {
12666
12299
  }
12667
12300
  }
12668
12301
 
12669
- // src/commands/bootstrap.ts
12670
- init_builtin_skills();
12671
- import fs47 from "fs";
12672
- import chalk27 from "chalk";
12673
- var PLATFORM_SKILL_DIRS = {
12674
- claude: ".claude/skills",
12675
- cursor: ".cursor/skills",
12676
- codex: ".agents/skills"
12677
- };
12678
- async function bootstrapCommand() {
12679
- const platforms = detectPlatforms();
12680
- const detected = [];
12681
- if (platforms.claude) detected.push("claude");
12682
- if (platforms.cursor) detected.push("cursor");
12683
- if (platforms.codex) detected.push("codex");
12684
- if (detected.length === 0) detected.push("claude");
12685
- const written = [];
12686
- for (const platform of detected) {
12687
- const skillsDir = PLATFORM_SKILL_DIRS[platform];
12688
- if (!skillsDir) continue;
12689
- for (const skill of BUILTIN_SKILLS) {
12690
- const skillDir = `${skillsDir}/${skill.name}`;
12691
- const skillPath = `${skillDir}/SKILL.md`;
12692
- fs47.mkdirSync(skillDir, { recursive: true });
12693
- fs47.writeFileSync(skillPath, buildSkillContent(skill));
12694
- written.push(skillPath);
12695
- }
12696
- }
12697
- if (written.length === 0) {
12698
- console.log(chalk27.yellow("No skills were written."));
12699
- return;
12700
- }
12701
- console.log(chalk27.bold.green("\n Caliber skills installed!\n"));
12702
- for (const file of written) {
12703
- console.log(` ${chalk27.green("\u2713")} ${file}`);
12704
- }
12705
- console.log(chalk27.dim("\n Your agent can now run /setup-caliber to complete the setup."));
12706
- console.log(chalk27.dim(' Just tell your agent: "Run /setup-caliber"\n'));
12707
- }
12708
-
12709
- // src/commands/uninstall.ts
12710
- import fs48 from "fs";
12711
- import path39 from "path";
12712
- import chalk28 from "chalk";
12713
- import confirm3 from "@inquirer/confirm";
12714
- init_pre_commit_block();
12715
- init_builtin_skills();
12716
- init_config();
12717
- var MANAGED_DOC_FILES = [
12718
- "CLAUDE.md",
12719
- path39.join(".github", "copilot-instructions.md"),
12720
- "AGENTS.md"
12721
- ];
12722
- var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
12723
- var CURSOR_RULES_DIR = path39.join(".cursor", "rules");
12724
- function removeCaliberCursorRules() {
12725
- const removed = [];
12726
- if (!fs48.existsSync(CURSOR_RULES_DIR)) return removed;
12727
- for (const file of fs48.readdirSync(CURSOR_RULES_DIR)) {
12728
- if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
12729
- const fullPath = path39.join(CURSOR_RULES_DIR, file);
12730
- fs48.unlinkSync(fullPath);
12731
- removed.push(fullPath);
12732
- }
12733
- }
12734
- return removed;
12735
- }
12736
- function removeBuiltinSkills() {
12737
- const removed = [];
12738
- for (const skillsDir of SKILL_DIRS) {
12739
- if (!fs48.existsSync(skillsDir)) continue;
12740
- for (const name of BUILTIN_SKILL_NAMES) {
12741
- const skillDir = path39.join(skillsDir, name);
12742
- if (fs48.existsSync(skillDir)) {
12743
- fs48.rmSync(skillDir, { recursive: true });
12744
- removed.push(skillDir);
12745
- }
12746
- }
12747
- }
12748
- return removed;
12749
- }
12750
- function stripManagedBlocksFromFiles() {
12751
- const modified = [];
12752
- for (const filePath of MANAGED_DOC_FILES) {
12753
- if (!fs48.existsSync(filePath)) continue;
12754
- const original = fs48.readFileSync(filePath, "utf-8");
12755
- const stripped = stripManagedBlocks(original);
12756
- if (stripped !== original) {
12757
- const trimmed = stripped.trim();
12758
- if (!trimmed || /^#\s*\S*$/.test(trimmed)) {
12759
- fs48.unlinkSync(filePath);
12760
- } else {
12761
- fs48.writeFileSync(filePath, stripped);
12762
- }
12763
- modified.push(filePath);
12764
- }
12765
- }
12766
- return modified;
12767
- }
12768
- function removeDirectory(dir) {
12769
- if (!fs48.existsSync(dir)) return false;
12770
- fs48.rmSync(dir, { recursive: true });
12771
- return true;
12772
- }
12773
- async function uninstallCommand(options) {
12774
- console.log(chalk28.bold("\n Caliber Uninstall\n"));
12775
- console.log(chalk28.dim(" This will remove all Caliber resources from this project:\n"));
12776
- console.log(chalk28.dim(" \u2022 Pre-commit hook"));
12777
- console.log(chalk28.dim(" \u2022 Session learning hooks"));
12778
- console.log(chalk28.dim(" \u2022 Managed blocks in CLAUDE.md, AGENTS.md, copilot-instructions.md"));
12779
- console.log(chalk28.dim(" \u2022 Cursor rules (caliber-*.mdc)"));
12780
- console.log(chalk28.dim(" \u2022 Built-in skills (setup-caliber, find-skills, save-learning)"));
12781
- console.log(chalk28.dim(" \u2022 CALIBER_LEARNINGS.md"));
12782
- console.log(chalk28.dim(" \u2022 .caliber/ directory (backups, cache, state)\n"));
12783
- if (!options.force) {
12784
- const proceed = await confirm3({ message: "Continue with uninstall?" });
12785
- if (!proceed) {
12786
- console.log(chalk28.dim("\n Cancelled.\n"));
12787
- return;
12788
- }
12789
- }
12790
- console.log("");
12791
- const actions = [];
12792
- const hookResult = removePreCommitHook();
12793
- if (hookResult.removed) {
12794
- console.log(` ${chalk28.red("\u2717")} Pre-commit hook removed`);
12795
- actions.push("pre-commit hook");
12796
- }
12797
- const learnResult = removeLearningHooks();
12798
- if (learnResult.removed) {
12799
- console.log(` ${chalk28.red("\u2717")} Claude Code learning hooks removed`);
12800
- actions.push("claude learning hooks");
12801
- }
12802
- const cursorLearnResult = removeCursorLearningHooks();
12803
- if (cursorLearnResult.removed) {
12804
- console.log(` ${chalk28.red("\u2717")} Cursor learning hooks removed`);
12805
- actions.push("cursor learning hooks");
12806
- }
12807
- const strippedFiles = stripManagedBlocksFromFiles();
12808
- for (const file of strippedFiles) {
12809
- console.log(` ${chalk28.yellow("~")} ${file} \u2014 managed blocks removed`);
12810
- actions.push(file);
12811
- }
12812
- const removedRules = removeCaliberCursorRules();
12813
- for (const rule of removedRules) {
12814
- console.log(` ${chalk28.red("\u2717")} ${rule}`);
12815
- }
12816
- if (removedRules.length > 0) actions.push("cursor rules");
12817
- const removedSkills = removeBuiltinSkills();
12818
- for (const skill of removedSkills) {
12819
- console.log(` ${chalk28.red("\u2717")} ${skill}/`);
12820
- }
12821
- if (removedSkills.length > 0) actions.push("builtin skills");
12822
- if (fs48.existsSync("CALIBER_LEARNINGS.md")) {
12823
- fs48.unlinkSync("CALIBER_LEARNINGS.md");
12824
- console.log(` ${chalk28.red("\u2717")} CALIBER_LEARNINGS.md`);
12825
- actions.push("learnings file");
12826
- }
12827
- if (removeDirectory(CALIBER_DIR)) {
12828
- console.log(` ${chalk28.red("\u2717")} .caliber/ directory`);
12829
- actions.push(".caliber directory");
12830
- }
12831
- if (actions.length === 0) {
12832
- console.log(chalk28.dim(" Nothing to remove \u2014 Caliber is not installed in this project.\n"));
12833
- return;
12834
- }
12835
- trackUninstallExecuted();
12836
- const configPath = getConfigFilePath();
12837
- if (fs48.existsSync(configPath)) {
12838
- console.log("");
12839
- const removeConfig = options.force || await confirm3({
12840
- message: `Remove global config (~/.caliber/config.json)? This affects all projects.`
12841
- });
12842
- if (removeConfig) {
12843
- fs48.unlinkSync(configPath);
12844
- console.log(` ${chalk28.red("\u2717")} ${configPath}`);
12845
- const configDir = path39.dirname(configPath);
12846
- try {
12847
- const remaining = fs48.readdirSync(configDir);
12848
- if (remaining.length === 0) fs48.rmdirSync(configDir);
12849
- } catch {
12850
- }
12851
- }
12852
- }
12853
- console.log(chalk28.bold.green(`
12854
- Caliber has been removed from this project.`));
12855
- console.log(chalk28.dim(" Your code is untouched \u2014 only Caliber config files were removed.\n"));
12856
- }
12857
-
12858
12302
  // src/cli.ts
12859
- var __dirname = path40.dirname(fileURLToPath(import.meta.url));
12303
+ var __dirname = path39.dirname(fileURLToPath(import.meta.url));
12860
12304
  var pkg = JSON.parse(
12861
- fs49.readFileSync(path40.resolve(__dirname, "..", "package.json"), "utf-8")
12305
+ fs47.readFileSync(path39.resolve(__dirname, "..", "package.json"), "utf-8")
12862
12306
  );
12863
12307
  var program = new Command();
12864
12308
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -12866,11 +12310,9 @@ program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI co
12866
12310
  function tracked(commandName, handler) {
12867
12311
  const wrapper = async (...args) => {
12868
12312
  const start = Date.now();
12869
- const isCI = !!(process.env.CI || process.env.GITHUB_ACTIONS);
12870
12313
  trackEvent("command_started", {
12871
12314
  command: commandName,
12872
- cli_version: pkg.version,
12873
- is_ci: isCI
12315
+ cli_version: pkg.version
12874
12316
  });
12875
12317
  try {
12876
12318
  await handler(...args);
@@ -12922,9 +12364,7 @@ function parseAgentOption(value) {
12922
12364
  return agents;
12923
12365
  }
12924
12366
  program.command("init").description("Initialize your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, github-copilot", parseAgentOption).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").action(tracked("init", initCommand));
12925
- program.command("bootstrap").description("Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init").action(tracked("bootstrap", bootstrapCommand));
12926
12367
  program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
12927
- program.command("uninstall").description("Remove all Caliber resources from this project").option("--force", "Skip confirmation prompt").action(tracked("uninstall", (options) => uninstallCommand(options)));
12928
12368
  program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
12929
12369
  program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate config").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
12930
12370
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
@@ -12949,15 +12389,15 @@ learn.command("delete <index>").description("Delete a learning by its index numb
12949
12389
  learn.command("add <content>").description("Add a learning directly (used by agent skills)").option("--personal", "Save as a personal learning instead of project-level").action(tracked("learn:add", learnAddCommand));
12950
12390
 
12951
12391
  // src/utils/version-check.ts
12952
- import fs50 from "fs";
12953
- import path41 from "path";
12392
+ import fs48 from "fs";
12393
+ import path40 from "path";
12954
12394
  import { fileURLToPath as fileURLToPath2 } from "url";
12955
12395
  import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
12956
- import chalk29 from "chalk";
12396
+ import chalk27 from "chalk";
12957
12397
  import ora8 from "ora";
12958
- import confirm4 from "@inquirer/confirm";
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"));
12398
+ import confirm2 from "@inquirer/confirm";
12399
+ var __dirname_vc = path40.dirname(fileURLToPath2(import.meta.url));
12400
+ var pkg2 = JSON.parse(fs48.readFileSync(path40.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
12961
12401
  function getChannel(version) {
12962
12402
  const match = version.match(/-(dev|next)\./);
12963
12403
  return match ? match[1] : "latest";
@@ -12984,8 +12424,8 @@ function getInstalledVersion() {
12984
12424
  encoding: "utf-8",
12985
12425
  stdio: ["pipe", "pipe", "pipe"]
12986
12426
  }).trim();
12987
- const pkgPath = path41.join(globalRoot, "@rely-ai", "caliber", "package.json");
12988
- return JSON.parse(fs50.readFileSync(pkgPath, "utf-8")).version;
12427
+ const pkgPath = path40.join(globalRoot, "@rely-ai", "caliber", "package.json");
12428
+ return JSON.parse(fs48.readFileSync(pkgPath, "utf-8")).version;
12989
12429
  } catch {
12990
12430
  return null;
12991
12431
  }
@@ -13010,18 +12450,18 @@ async function checkForUpdates() {
13010
12450
  if (!isInteractive) {
13011
12451
  const installTag = channel === "latest" ? "" : `@${channel}`;
13012
12452
  console.log(
13013
- chalk29.yellow(
12453
+ chalk27.yellow(
13014
12454
  `
13015
12455
  Update available: ${current} -> ${latest}
13016
- Run ${chalk29.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
12456
+ Run ${chalk27.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
13017
12457
  `
13018
12458
  )
13019
12459
  );
13020
12460
  return;
13021
12461
  }
13022
- console.log(chalk29.yellow(`
12462
+ console.log(chalk27.yellow(`
13023
12463
  Update available: ${current} -> ${latest}`));
13024
- const shouldUpdate = await confirm4({
12464
+ const shouldUpdate = await confirm2({
13025
12465
  message: "Would you like to update now? (Y/n)",
13026
12466
  default: true
13027
12467
  });
@@ -13042,14 +12482,14 @@ Update available: ${current} -> ${latest}`));
13042
12482
  if (installed !== latest) {
13043
12483
  spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
13044
12484
  console.log(
13045
- chalk29.yellow(`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
12485
+ chalk27.yellow(`Run ${chalk27.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
13046
12486
  `)
13047
12487
  );
13048
12488
  return;
13049
12489
  }
13050
- spinner.succeed(chalk29.green(`Updated to ${latest}`));
12490
+ spinner.succeed(chalk27.green(`Updated to ${latest}`));
13051
12491
  const args = process.argv.slice(2);
13052
- console.log(chalk29.dim(`
12492
+ console.log(chalk27.dim(`
13053
12493
  Restarting: caliber ${args.join(" ")}
13054
12494
  `));
13055
12495
  execFileSync3("caliber", args, {
@@ -13062,11 +12502,11 @@ Restarting: caliber ${args.join(" ")}
13062
12502
  if (err instanceof Error) {
13063
12503
  const stderr = err.stderr;
13064
12504
  const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
13065
- if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk29.dim(` ${errMsg}`));
12505
+ if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk27.dim(` ${errMsg}`));
13066
12506
  }
13067
12507
  console.log(
13068
- chalk29.yellow(
13069
- `Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
12508
+ chalk27.yellow(
12509
+ `Run ${chalk27.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
13070
12510
  `
13071
12511
  )
13072
12512
  );