@rely-ai/caliber 1.29.7 → 1.30.0-dev.1774285227

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 +442 -288
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -495,13 +495,13 @@ __export(lock_exports, {
495
495
  isCaliberRunning: () => isCaliberRunning,
496
496
  releaseLock: () => releaseLock
497
497
  });
498
- import fs36 from "fs";
499
- import path28 from "path";
498
+ import fs37 from "fs";
499
+ import path29 from "path";
500
500
  import os8 from "os";
501
501
  function isCaliberRunning() {
502
502
  try {
503
- if (!fs36.existsSync(LOCK_FILE)) return false;
504
- const raw = fs36.readFileSync(LOCK_FILE, "utf-8").trim();
503
+ if (!fs37.existsSync(LOCK_FILE)) return false;
504
+ const raw = fs37.readFileSync(LOCK_FILE, "utf-8").trim();
505
505
  const { pid, ts } = JSON.parse(raw);
506
506
  if (Date.now() - ts > STALE_MS) return false;
507
507
  try {
@@ -516,13 +516,13 @@ function isCaliberRunning() {
516
516
  }
517
517
  function acquireLock() {
518
518
  try {
519
- fs36.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
519
+ fs37.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
520
520
  } catch {
521
521
  }
522
522
  }
523
523
  function releaseLock() {
524
524
  try {
525
- if (fs36.existsSync(LOCK_FILE)) fs36.unlinkSync(LOCK_FILE);
525
+ if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
526
526
  } catch {
527
527
  }
528
528
  }
@@ -530,21 +530,21 @@ var LOCK_FILE, STALE_MS;
530
530
  var init_lock = __esm({
531
531
  "src/lib/lock.ts"() {
532
532
  "use strict";
533
- LOCK_FILE = path28.join(os8.tmpdir(), ".caliber.lock");
533
+ LOCK_FILE = path29.join(os8.tmpdir(), ".caliber.lock");
534
534
  STALE_MS = 10 * 60 * 1e3;
535
535
  }
536
536
  });
537
537
 
538
538
  // src/cli.ts
539
539
  import { Command } from "commander";
540
- import fs46 from "fs";
541
- import path37 from "path";
540
+ import fs47 from "fs";
541
+ import path38 from "path";
542
542
  import { fileURLToPath } from "url";
543
543
 
544
544
  // src/commands/init.ts
545
- import path24 from "path";
545
+ import path25 from "path";
546
546
  import chalk14 from "chalk";
547
- import fs31 from "fs";
547
+ import fs32 from "fs";
548
548
 
549
549
  // src/fingerprint/index.ts
550
550
  import fs7 from "fs";
@@ -2307,7 +2307,7 @@ init_config();
2307
2307
  var ROLE_AND_CONTEXT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, Codex, and GitHub Copilot).
2308
2308
 
2309
2309
  Your job depends on context:
2310
- - If no existing configs exist \u2192 generate an initial setup from scratch.
2310
+ - If no existing configs exist \u2192 generate an initial configuration from scratch.
2311
2311
  - If existing configs are provided \u2192 audit them and suggest targeted improvements. Preserve accurate content \u2014 don't rewrite what's already correct.`;
2312
2312
  var CONFIG_FILE_TYPES = `You understand these config files:
2313
2313
  - CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
@@ -2320,6 +2320,12 @@ var CONFIG_FILE_TYPES = `You understand these config files:
2320
2320
  - .github/copilot-instructions.md: Always-on repository-wide instructions for GitHub Copilot \u2014 same purpose as CLAUDE.md but for Copilot. Plain markdown, no frontmatter.
2321
2321
  - .github/instructions/*.instructions.md: Path-specific instruction files for GitHub Copilot with YAML frontmatter containing an \`applyTo\` glob pattern (e.g. \`applyTo: "**/*.ts,**/*.tsx"\`). Only loaded when Copilot is working on matching files.`;
2322
2322
  var EXCLUSIONS = `Do NOT generate .claude/settings.json, .claude/settings.local.json, or mcpServers \u2014 those are managed separately.`;
2323
+ var AUDIT_CHECKLIST = `Audit checklist (when existing configs are provided):
2324
+ 1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
2325
+ 2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
2326
+ 3. Duplicate or overlapping skills \u2014 can any be merged or removed?
2327
+ 4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
2328
+ 5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?`;
2323
2329
  var OUTPUT_FORMAT = `Your output MUST follow this exact format (no markdown fences):
2324
2330
 
2325
2331
  1. Exactly 6 short status lines (one per line, prefixed with "STATUS: "). Each should be a creative, specific description of what you're analyzing for THIS project \u2014 reference the project's actual languages, frameworks, or tools.
@@ -2383,7 +2389,10 @@ Accuracy (15 pts) \u2014 CRITICAL:
2383
2389
 
2384
2390
  Safety: Never include API keys, tokens, or credentials in config files.
2385
2391
 
2386
- Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.`;
2392
+ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more than raw token count. A 2500-token config that references 50%+ of the project's directories scores higher than a 1500-token config that only mentions 3 paths. Pack references densely using the inline path style shown in OUTPUT SIZE CONSTRAINTS.
2393
+
2394
+ Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
2395
+ README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
2387
2396
  var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
2388
2397
  - CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
2389
2398
 
@@ -2399,12 +2408,7 @@ var GENERATION_SYSTEM_PROMPT = `${ROLE_AND_CONTEXT}
2399
2408
 
2400
2409
  ${CONFIG_FILE_TYPES}
2401
2410
 
2402
- Audit checklist (when existing configs are provided):
2403
- 1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
2404
- 2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
2405
- 3. Duplicate or overlapping skills \u2014 can any be merged or removed?
2406
- 4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
2407
- 5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?
2411
+ ${AUDIT_CHECKLIST}
2408
2412
 
2409
2413
  ${EXCLUSIONS}
2410
2414
 
@@ -2449,6 +2453,8 @@ var CORE_GENERATION_PROMPT = `${ROLE_AND_CONTEXT}
2449
2453
 
2450
2454
  ${CONFIG_FILE_TYPES}
2451
2455
 
2456
+ ${AUDIT_CHECKLIST}
2457
+
2452
2458
  ${EXCLUSIONS}
2453
2459
 
2454
2460
  ${OUTPUT_FORMAT}
@@ -2468,10 +2474,10 @@ CoreSetup schema:
2468
2474
  },
2469
2475
  "codex": {
2470
2476
  "agentsMd": "string (markdown content for AGENTS.md)",
2471
- "skillTopics": [{ "name": "string (kebab-case)", "description": "string" }]
2477
+ "skillTopics": [{ "name": "string (kebab-case)", "description": "string (what this skill does and WHEN to use it \u2014 include trigger phrases)" }]
2472
2478
  },
2473
2479
  "cursor": {
2474
- "skillTopics": [{ "name": "string (kebab-case)", "description": "string" }],
2480
+ "skillTopics": [{ "name": "string (kebab-case)", "description": "string (what this skill does and WHEN to use it \u2014 include trigger phrases)" }],
2475
2481
  "rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
2476
2482
  },
2477
2483
  "copilot": {
@@ -2523,6 +2529,7 @@ Rules:
2523
2529
  - Reference actual commands, paths, and packages from the project context provided.
2524
2530
  - Do NOT include YAML frontmatter \u2014 it will be generated separately.
2525
2531
  - Be specific to THIS project \u2014 avoid generic advice. The skill should produce code that looks identical to what's already in the codebase.
2532
+ - If the project context does not contain enough code examples for this skill topic, generate the skill based on the detected frameworks and conventions rather than inventing patterns. Prefer fewer, grounded instructions over many speculative ones.
2526
2533
 
2527
2534
  Description field formula: [What it does] + [When to use it with trigger phrases] + [Key capabilities]. Include negative triggers ("Do NOT use for X") to prevent over-triggering.
2528
2535
 
@@ -2569,32 +2576,40 @@ Rules:
2569
2576
  - Update the "fileDescriptions" to reflect any changes you make.
2570
2577
 
2571
2578
  Quality constraints \u2014 your changes are scored, so do not break these:
2572
- - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget.
2579
+ - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
2573
2580
  - Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
2574
2581
  - Do NOT add directory tree listings in code blocks.
2582
+ - Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
2575
2583
  - Use backticks for every file path, command, and identifier.
2576
2584
  - Keep skill content under 150 lines, focused on actionable instructions.
2577
2585
  - Only reference file paths that actually exist in the project.`;
2578
- var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project documentation. Your job is to update existing documentation files based on code changes (git diffs).
2586
+ var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project documentation. Your job is to apply minimal, surgical updates to existing documentation files based on code changes (git diffs).
2579
2587
 
2580
2588
  You will receive:
2581
2589
  1. Git diffs showing what code changed
2582
- 2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules)
2583
- 3. Project context (languages, frameworks)
2584
-
2585
- Rules:
2586
- - Only update docs where the diffs clearly warrant a change
2587
- - Preserve existing style, tone, structure, and formatting
2588
- - Be conservative \u2014 don't rewrite sections that aren't affected by the changes
2589
- - Don't add speculative or aspirational content
2590
+ 2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules, copilot instructions)
2591
+ 3. Project context (languages, frameworks, file tree)
2592
+
2593
+ CONSERVATIVE UPDATE means:
2594
+ - Touch ONLY the specific lines/sections affected by the diff
2595
+ - If a command was renamed in the diff, update that command in the docs \u2014 don't rewrite the surrounding section
2596
+ - If a file was added/removed/renamed, update the architecture section \u2014 don't restructure it
2597
+ - If nothing in the diff affects a doc file, return null for it
2598
+ - NEVER add new sections, new prose, or new explanations that weren't in the original
2599
+ - NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
2600
+ - NEVER replace specific paths/commands with generic prose
2601
+
2602
+ Quality constraints (the output is scored deterministically):
2603
+ - CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
2604
+ - Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
2605
+ - Every file path, command, and identifier must be in backticks
2606
+ - ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
2607
+ - Preserve the existing structure (headings, bullet style, formatting)
2608
+
2609
+ Managed content:
2590
2610
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
2591
- - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately by the learning system
2611
+ - Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
2592
2612
  - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
2593
- - If a doc doesn't need updating, return null for it
2594
- - For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them. Keep under 150 lines.
2595
- - For README.md: update setup instructions, API docs, or feature descriptions if affected
2596
- - Only reference file paths that exist in the project
2597
- - Use backticks for all file paths, commands, and identifiers
2598
2613
 
2599
2614
  Return a JSON object with this exact shape:
2600
2615
  {
@@ -2618,15 +2633,8 @@ You receive a chronological sequence of events from a Claude Code session. Most
2618
2633
 
2619
2634
  Your job is to find OPERATIONAL patterns \u2014 things that went wrong and how they were fixed, commands that required specific flags or configuration, APIs that needed a particular approach to work. Focus on the WORKFLOW, not the code logic.
2620
2635
 
2621
- Look for:
2622
-
2623
- 1. **Failure \u2192 Recovery sequences**: A tool call failed, then a different approach succeeded. Document what works and what doesn't. Example: an API call failed with one config but succeeded with different headers or parameters.
2624
- 2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
2625
- 3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
2626
- 4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
2627
- 5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
2628
- 6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
2629
- 7. **User corrections**: The user explicitly told the AI what's wrong, what to use instead, or what to avoid. Look for phrases like "no, use X instead of Y", "don't touch/edit/modify X", "that's wrong, you need to...", "always/never do X in this project", "stop, that file is...". These are the HIGHEST VALUE signals \u2014 they represent direct human feedback about project-specific requirements. If a user correction contradicts a pattern you'd otherwise extract, the correction wins.
2636
+ CRITICAL FILTER \u2014 apply this to every potential learning before including it:
2637
+ The litmus test: "Would a different developer, working on a DIFFERENT task in this same repo next week, benefit from knowing this?" If the answer is no \u2014 if it only matters for the exact problem being debugged today \u2014 do NOT include it.
2630
2638
 
2631
2639
  DO NOT extract:
2632
2640
  - Descriptions of what the code does or how features work (e.g. "compression removes comments" or "skeleton extraction creates outlines")
@@ -2637,7 +2645,15 @@ DO NOT extract:
2637
2645
  - **Session-specific file paths, worktree locations, or branch names** \u2014 these are ephemeral and won't apply to future sessions
2638
2646
  - **Implementation details of a feature being built** \u2014 the learning should be about HOW to work in this project, not WHAT was built
2639
2647
 
2640
- The litmus test for every learning: "Would a different developer, working on a DIFFERENT task in this same repo next week, benefit from knowing this?" If the answer is no \u2014 if it only matters for the exact problem being debugged today \u2014 do NOT include it.
2648
+ Look for:
2649
+
2650
+ 1. **Failure \u2192 Recovery sequences**: A tool call failed, then a different approach succeeded. Document what works and what doesn't. Example: an API call failed with one config but succeeded with different headers or parameters.
2651
+ 2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
2652
+ 3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
2653
+ 4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
2654
+ 5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
2655
+ 6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
2656
+ 7. **User corrections**: The user explicitly told the AI what's wrong, what to use instead, or what to avoid. Look for phrases like "no, use X instead of Y", "don't touch/edit/modify X", "that's wrong, you need to...", "always/never do X in this project", "stop, that file is...". These are the HIGHEST VALUE signals \u2014 they represent direct human feedback about project-specific requirements. If a user correction contradicts a pattern you'd otherwise extract, the correction wins.
2641
2657
 
2642
2658
  From these observations, produce:
2643
2659
 
@@ -2716,6 +2732,10 @@ Be thorough \u2014 reason from:
2716
2732
  - File extensions and their frequency distribution
2717
2733
  - Directory structure and naming conventions
2718
2734
  - Configuration files (e.g. next.config.js implies Next.js, .tf files imply Terraform + cloud providers)
2735
+ - Package manager lockfiles (pnpm-lock.yaml \u2192 pnpm, yarn.lock \u2192 yarn, bun.lockb \u2192 bun, package-lock.json \u2192 npm)
2736
+ - Database/ORM files (schema.prisma \u2192 Prisma, drizzle.config.ts \u2192 Drizzle, knexfile \u2192 Knex, .sql files)
2737
+ - Test framework configs (vitest.config.ts \u2192 Vitest, jest.config.js \u2192 Jest, .mocharc \u2192 Mocha, cypress.config \u2192 Cypress)
2738
+ - Monorepo tools (nx.json \u2192 Nx, turbo.json \u2192 Turborepo, lerna.json \u2192 Lerna)
2719
2739
  - Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
2720
2740
  - CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
2721
2741
 
@@ -3616,15 +3636,15 @@ init_config();
3616
3636
  // src/utils/dependencies.ts
3617
3637
  import { readFileSync as readFileSync2 } from "fs";
3618
3638
  import { join as join2 } from "path";
3619
- function readFileOrNull2(path39) {
3639
+ function readFileOrNull2(path40) {
3620
3640
  try {
3621
- return readFileSync2(path39, "utf-8");
3641
+ return readFileSync2(path40, "utf-8");
3622
3642
  } catch {
3623
3643
  return null;
3624
3644
  }
3625
3645
  }
3626
- function readJsonOrNull(path39) {
3627
- const content = readFileOrNull2(path39);
3646
+ function readJsonOrNull(path40) {
3647
+ const content = readFileOrNull2(path40);
3628
3648
  if (!content) return null;
3629
3649
  try {
3630
3650
  return JSON.parse(content);
@@ -6181,7 +6201,7 @@ var AGENT_DISPLAY_NAMES = {
6181
6201
  codex: "Codex"
6182
6202
  };
6183
6203
  var CATEGORY_LABELS = {
6184
- existence: { icon: "\u{1F4C1}", label: "FILES & SETUP" },
6204
+ existence: { icon: "\u{1F4C1}", label: "FILES & CONFIG" },
6185
6205
  quality: { icon: "\u26A1", label: "QUALITY" },
6186
6206
  grounding: { icon: "\u{1F3AF}", label: "GROUNDING" },
6187
6207
  accuracy: { icon: "\u{1F50D}", label: "ACCURACY" },
@@ -7440,11 +7460,11 @@ function countIssuePoints(issues) {
7440
7460
  }
7441
7461
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7442
7462
  const existsCache = /* @__PURE__ */ new Map();
7443
- const cachedExists = (path39) => {
7444
- const cached = existsCache.get(path39);
7463
+ const cachedExists = (path40) => {
7464
+ const cached = existsCache.get(path40);
7445
7465
  if (cached !== void 0) return cached;
7446
- const result = existsSync9(path39);
7447
- existsCache.set(path39, result);
7466
+ const result = existsSync9(path40);
7467
+ existsCache.set(path40, result);
7448
7468
  return result;
7449
7469
  };
7450
7470
  const projectStructure = collectProjectStructure(dir);
@@ -7459,7 +7479,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7459
7479
  bestLostPoints = lostPoints;
7460
7480
  }
7461
7481
  if (lostPoints === 0) {
7462
- if (callbacks?.onStatus) callbacks.onStatus("Setup passes all scoring checks");
7482
+ if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
7463
7483
  return bestSetup;
7464
7484
  }
7465
7485
  const pointIssues = issues.filter((i) => i.pointsLost > 0);
@@ -7469,7 +7489,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7469
7489
  }
7470
7490
  const refined = await applyTargetedFixes(currentSetup, issues);
7471
7491
  if (!refined) {
7472
- if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current setup");
7492
+ if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current config");
7473
7493
  return bestSetup;
7474
7494
  }
7475
7495
  sessionHistory.push({
@@ -7562,7 +7582,7 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
7562
7582
  }
7563
7583
  }
7564
7584
  async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
7565
- const spinner = ora2("Validating setup against scoring criteria...").start();
7585
+ const spinner = ora2("Validating config against scoring criteria...").start();
7566
7586
  try {
7567
7587
  const refined = await scoreAndRefine(setup, dir, sessionHistory, {
7568
7588
  onStatus: (msg) => {
@@ -7570,9 +7590,9 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
7570
7590
  }
7571
7591
  });
7572
7592
  if (refined !== setup) {
7573
- spinner.succeed("Setup refined based on scoring feedback");
7593
+ spinner.succeed("Config refined based on scoring feedback");
7574
7594
  } else {
7575
- spinner.succeed("Setup passes scoring validation");
7595
+ spinner.succeed("Config passes scoring validation");
7576
7596
  }
7577
7597
  return refined;
7578
7598
  } catch (err) {
@@ -8127,7 +8147,7 @@ var REFINE_MESSAGES = [
8127
8147
  "Rebalancing permissions and tool settings...",
8128
8148
  "Refining skills and workflows...",
8129
8149
  "Updating rules to match your preferences...",
8130
- "Finalizing the revised setup..."
8150
+ "Finalizing the revised config..."
8131
8151
  ];
8132
8152
  var SpinnerMessages = class {
8133
8153
  spinner;
@@ -8297,10 +8317,10 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
8297
8317
  if (!isValid) {
8298
8318
  console.log(chalk11.dim(" This doesn't look like a config change request."));
8299
8319
  console.log(chalk11.dim(" Describe what to add, remove, or modify in your configs."));
8300
- console.log(chalk11.dim(' Type "done" to accept the current setup.\n'));
8320
+ console.log(chalk11.dim(' Type "done" to accept the current config.\n'));
8301
8321
  continue;
8302
8322
  }
8303
- const refineSpinner = ora3("Refining setup...").start();
8323
+ const refineSpinner = ora3("Refining config...").start();
8304
8324
  const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
8305
8325
  refineMessages.start();
8306
8326
  const refined = await refineSetup(setup, message, sessionHistory);
@@ -8312,12 +8332,12 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
8312
8332
  role: "assistant",
8313
8333
  content: summarizeSetup2("Applied changes", refined)
8314
8334
  });
8315
- refineSpinner.succeed("Setup updated");
8335
+ refineSpinner.succeed("Config updated");
8316
8336
  printSummary(refined);
8317
8337
  console.log(chalk11.dim('Type "done" to accept, or describe more changes.'));
8318
8338
  } else {
8319
8339
  refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
8320
- console.log(chalk11.dim('Try rephrasing your request, or type "done" to keep the current setup.'));
8340
+ console.log(chalk11.dim('Try rephrasing your request, or type "done" to keep the current config.'));
8321
8341
  }
8322
8342
  }
8323
8343
  }
@@ -8366,7 +8386,7 @@ function printSetupSummary(setup) {
8366
8386
  const fileDescriptions = setup.fileDescriptions;
8367
8387
  const deletions = setup.deletions;
8368
8388
  console.log("");
8369
- console.log(chalk12.bold(" Your tailored setup:\n"));
8389
+ console.log(chalk12.bold(" Your tailored config:\n"));
8370
8390
  const getDescription = (filePath) => {
8371
8391
  return fileDescriptions?.[filePath];
8372
8392
  };
@@ -8595,6 +8615,7 @@ Only dismiss checks that truly don't apply. Examples:
8595
8615
  - "Build/test/lint commands" for a GitOps/Helm/Terraform/config repo with no build system
8596
8616
  - "Build/test/lint commands" for a repo with only YAML, HCL, or config files and no package.json/Makefile
8597
8617
  - "Dependency coverage" for a repo with no package manager
8618
+ - "Skills configured" for a documentation-only or data-science notebook repo with no repeating code patterns
8598
8619
 
8599
8620
  Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
8600
8621
 
@@ -8607,7 +8628,7 @@ Top files: ${topFiles}
8607
8628
 
8608
8629
  Failing checks:
8609
8630
  ${JSON.stringify(checkList, null, 2)}`,
8610
- maxTokens: 300,
8631
+ maxTokens: 500,
8611
8632
  ...fastModel ? { model: fastModel } : {}
8612
8633
  });
8613
8634
  if (!Array.isArray(result.dismissed)) return [];
@@ -8617,6 +8638,68 @@ ${JSON.stringify(checkList, null, 2)}`,
8617
8638
  }
8618
8639
  }
8619
8640
 
8641
+ // src/scoring/history.ts
8642
+ import fs31 from "fs";
8643
+ import path24 from "path";
8644
+ var HISTORY_FILE = "score-history.jsonl";
8645
+ var MAX_ENTRIES = 500;
8646
+ var TRIM_THRESHOLD = MAX_ENTRIES + 50;
8647
+ function historyFilePath() {
8648
+ return path24.join(CALIBER_DIR, HISTORY_FILE);
8649
+ }
8650
+ function recordScore(result, trigger) {
8651
+ const entry = {
8652
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8653
+ score: result.score,
8654
+ grade: result.grade,
8655
+ targetAgent: [...result.targetAgent],
8656
+ trigger
8657
+ };
8658
+ try {
8659
+ fs31.mkdirSync(CALIBER_DIR, { recursive: true });
8660
+ const filePath = historyFilePath();
8661
+ fs31.appendFileSync(filePath, JSON.stringify(entry) + "\n");
8662
+ const stat = fs31.statSync(filePath);
8663
+ if (stat.size > TRIM_THRESHOLD * 120) {
8664
+ const lines = fs31.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
8665
+ if (lines.length > MAX_ENTRIES) {
8666
+ fs31.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
8667
+ }
8668
+ }
8669
+ } catch {
8670
+ }
8671
+ }
8672
+ function readScoreHistory() {
8673
+ const filePath = historyFilePath();
8674
+ try {
8675
+ const content = fs31.readFileSync(filePath, "utf-8");
8676
+ const entries = [];
8677
+ for (const line of content.split("\n")) {
8678
+ if (!line) continue;
8679
+ try {
8680
+ entries.push(JSON.parse(line));
8681
+ } catch {
8682
+ }
8683
+ }
8684
+ return entries;
8685
+ } catch {
8686
+ return [];
8687
+ }
8688
+ }
8689
+ function getScoreTrend(entries) {
8690
+ if (entries.length < 2) return null;
8691
+ const first = entries[0];
8692
+ const last = entries[entries.length - 1];
8693
+ const delta = last.score - first.score;
8694
+ return {
8695
+ direction: delta > 0 ? "up" : delta < 0 ? "down" : "stable",
8696
+ delta,
8697
+ entries: entries.length,
8698
+ firstScore: first.score,
8699
+ lastScore: last.score
8700
+ };
8701
+ }
8702
+
8620
8703
  // src/commands/init.ts
8621
8704
  function log(verbose, ...args) {
8622
8705
  if (verbose) console.log(chalk14.dim(` [verbose] ${args.map(String).join(" ")}`));
@@ -8637,7 +8720,7 @@ async function initCommand(options) {
8637
8720
  console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
8638
8721
  console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
8639
8722
  console.log(title.bold(" How it works:\n"));
8640
- console.log(chalk14.dim(" 1. Setup Connect your LLM provider and select your agents"));
8723
+ console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
8641
8724
  console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
8642
8725
  console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
8643
8726
  console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
@@ -8650,7 +8733,7 @@ async function initCommand(options) {
8650
8733
  console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
8651
8734
  }
8652
8735
  const report = options.debugReport ? new DebugReport() : null;
8653
- console.log(title.bold(" Step 1/4 \u2014 Setup\n"));
8736
+ console.log(title.bold(" Step 1/4 \u2014 Connect\n"));
8654
8737
  let config = loadConfig();
8655
8738
  if (!config) {
8656
8739
  console.log(chalk14.dim(" No LLM provider configured yet.\n"));
@@ -8659,7 +8742,7 @@ async function initCommand(options) {
8659
8742
  });
8660
8743
  config = loadConfig();
8661
8744
  if (!config) {
8662
- console.log(chalk14.red(" Setup was cancelled or failed.\n"));
8745
+ console.log(chalk14.red(" Configuration cancelled or failed.\n"));
8663
8746
  throw new Error("__exit__");
8664
8747
  }
8665
8748
  console.log(chalk14.green(" \u2713 Provider saved\n"));
@@ -8670,7 +8753,7 @@ async function initCommand(options) {
8670
8753
  const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
8671
8754
  console.log(chalk14.dim(modelLine + "\n"));
8672
8755
  if (report) {
8673
- report.markStep("Provider setup");
8756
+ report.markStep("Provider connection");
8674
8757
  report.addSection("LLM Provider", `- **Provider**: ${config.provider}
8675
8758
  - **Model**: ${displayModel}
8676
8759
  - **Fast model**: ${fastModel || "none"}`);
@@ -8691,7 +8774,7 @@ async function initCommand(options) {
8691
8774
  `));
8692
8775
  trackInitAgentSelected(targetAgent, agentAutoDetected);
8693
8776
  let baselineScore = computeLocalScore(process.cwd(), targetAgent);
8694
- console.log(chalk14.dim("\n Current setup score:"));
8777
+ console.log(chalk14.dim("\n Current config score:"));
8695
8778
  displayScoreSummary(baselineScore);
8696
8779
  if (options.verbose) {
8697
8780
  for (const c of baselineScore.checks) {
@@ -8720,7 +8803,7 @@ async function initCommand(options) {
8720
8803
  const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
8721
8804
  if (hasExistingConfig && baselineScore.score === 100) {
8722
8805
  trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
8723
- console.log(chalk14.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
8806
+ console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
8724
8807
  console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber init --force") + chalk14.dim(" to regenerate anyway.\n"));
8725
8808
  if (!options.force) return;
8726
8809
  }
@@ -8757,7 +8840,7 @@ async function initCommand(options) {
8757
8840
  const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
8758
8841
  const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
8759
8842
  const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
8760
- const TASK_SCORE_REFINE = display.add("Validating & refining setup", { pipelineLabel: "Validate" });
8843
+ const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
8761
8844
  display.start();
8762
8845
  display.enableWaitingContent();
8763
8846
  try {
@@ -8905,7 +8988,7 @@ async function initCommand(options) {
8905
8988
  display.update(TASK_SCORE_REFINE, "done", "Skipped");
8906
8989
  }
8907
8990
  } else {
8908
- display.update(TASK_SCORE_REFINE, "failed", "No setup to validate");
8991
+ display.update(TASK_SCORE_REFINE, "failed", "No config to validate");
8909
8992
  }
8910
8993
  } catch (err) {
8911
8994
  display.stop();
@@ -8926,7 +9009,7 @@ async function initCommand(options) {
8926
9009
  Done in ${timeStr}
8927
9010
  `));
8928
9011
  if (!generatedSetup) {
8929
- console.log(chalk14.red(" Failed to generate setup."));
9012
+ console.log(chalk14.red(" Failed to generate config."));
8930
9013
  writeErrorLog(config, rawOutput, void 0, genStopReason);
8931
9014
  if (rawOutput) {
8932
9015
  console.log(chalk14.dim("\nRaw LLM output (JSON parse failed):"));
@@ -8936,7 +9019,7 @@ async function initCommand(options) {
8936
9019
  }
8937
9020
  if (report) {
8938
9021
  if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
8939
- report.addJson("Generation: Parsed Setup", generatedSetup);
9022
+ report.addJson("Generation: Parsed Config", generatedSetup);
8940
9023
  }
8941
9024
  log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
8942
9025
  console.log(title.bold(" Step 3/4 \u2014 Review\n"));
@@ -8999,7 +9082,7 @@ async function initCommand(options) {
8999
9082
  }
9000
9083
  cleanupStaging();
9001
9084
  if (action === "decline") {
9002
- console.log(chalk14.dim("Setup declined. No files were modified."));
9085
+ console.log(chalk14.dim("Declined. No files were modified."));
9003
9086
  return;
9004
9087
  }
9005
9088
  console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
@@ -9011,7 +9094,7 @@ async function initCommand(options) {
9011
9094
  const { default: ora9 } = await import("ora");
9012
9095
  const writeSpinner = ora9("Writing config files...").start();
9013
9096
  try {
9014
- if (targetAgent.includes("codex") && !fs31.existsSync("AGENTS.md") && !generatedSetup.codex) {
9097
+ if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
9015
9098
  const claude = generatedSetup.claude;
9016
9099
  const cursor = generatedSetup.cursor;
9017
9100
  const agentRefs = [];
@@ -9083,6 +9166,7 @@ ${agentRefs.join(" ")}
9083
9166
  |-------|--------|--------|-----|
9084
9167
  ` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
9085
9168
  }
9169
+ recordScore(afterScore, "init");
9086
9170
  displayScoreDelta(baselineScore, afterScore);
9087
9171
  if (options.verbose) {
9088
9172
  log(options.verbose, `Final score: ${afterScore.score}/100`);
@@ -9130,12 +9214,12 @@ ${agentRefs.join(" ")}
9130
9214
  if (targetAgent.includes("cursor")) installCursorLearningHooks();
9131
9215
  }
9132
9216
  }
9133
- console.log(chalk14.bold.green("\n Setup complete!"));
9217
+ console.log(chalk14.bold.green("\n Configuration complete!"));
9134
9218
  console.log(chalk14.dim(" Your AI agents now understand your project's architecture, build commands,"));
9135
9219
  console.log(chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n"));
9136
9220
  const done = chalk14.green("\u2713");
9137
9221
  const skip = chalk14.dim("\u2013");
9138
- console.log(chalk14.bold(" What was set up:\n"));
9222
+ console.log(chalk14.bold(" What was configured:\n"));
9139
9223
  console.log(` ${done} Config generated ${title("caliber score")} ${chalk14.dim("for full breakdown")}`);
9140
9224
  console.log(` ${done} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
9141
9225
  if (hasLearnableAgent) {
@@ -9160,9 +9244,9 @@ ${agentRefs.join(" ")}
9160
9244
  }
9161
9245
  if (report) {
9162
9246
  report.markStep("Finished");
9163
- const reportPath = path24.join(process.cwd(), ".caliber", "debug-report.md");
9247
+ const reportPath = path25.join(process.cwd(), ".caliber", "debug-report.md");
9164
9248
  report.write(reportPath);
9165
- console.log(chalk14.dim(` Debug report written to ${path24.relative(process.cwd(), reportPath)}
9249
+ console.log(chalk14.dim(` Debug report written to ${path25.relative(process.cwd(), reportPath)}
9166
9250
  `));
9167
9251
  }
9168
9252
  }
@@ -9171,7 +9255,7 @@ ${agentRefs.join(" ")}
9171
9255
  import chalk15 from "chalk";
9172
9256
  import ora4 from "ora";
9173
9257
  function undoCommand() {
9174
- const spinner = ora4("Reverting setup...").start();
9258
+ const spinner = ora4("Reverting config changes...").start();
9175
9259
  try {
9176
9260
  const { restored, removed } = undoSetup();
9177
9261
  if (restored.length === 0 && removed.length === 0) {
@@ -9179,7 +9263,7 @@ function undoCommand() {
9179
9263
  return;
9180
9264
  }
9181
9265
  trackUndoExecuted();
9182
- spinner.succeed("Setup reverted successfully.\n");
9266
+ spinner.succeed("Config reverted successfully.\n");
9183
9267
  if (restored.length > 0) {
9184
9268
  console.log(chalk15.cyan(" Restored from backup:"));
9185
9269
  for (const file of restored) {
@@ -9201,7 +9285,7 @@ function undoCommand() {
9201
9285
 
9202
9286
  // src/commands/status.ts
9203
9287
  import chalk16 from "chalk";
9204
- import fs32 from "fs";
9288
+ import fs33 from "fs";
9205
9289
  init_config();
9206
9290
  async function statusCommand(options) {
9207
9291
  const config = loadConfig();
@@ -9222,13 +9306,13 @@ async function statusCommand(options) {
9222
9306
  console.log(` LLM: ${chalk16.yellow("Not configured")} \u2014 run ${chalk16.hex("#83D1EB")("caliber config")}`);
9223
9307
  }
9224
9308
  if (!manifest) {
9225
- console.log(` Setup: ${chalk16.dim("No setup applied")}`);
9309
+ console.log(` Config: ${chalk16.dim("No config applied")}`);
9226
9310
  console.log(chalk16.dim("\n Run ") + chalk16.hex("#83D1EB")("caliber init") + chalk16.dim(" to get started.\n"));
9227
9311
  return;
9228
9312
  }
9229
9313
  console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
9230
9314
  for (const entry of manifest.entries) {
9231
- const exists = fs32.existsSync(entry.path);
9315
+ const exists = fs33.existsSync(entry.path);
9232
9316
  const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
9233
9317
  console.log(` ${icon} ${entry.path} (${entry.action})`);
9234
9318
  }
@@ -9249,7 +9333,7 @@ async function regenerateCommand(options) {
9249
9333
  }
9250
9334
  const manifest = readManifest();
9251
9335
  if (!manifest) {
9252
- console.log(chalk17.yellow("No existing setup found. Run ") + chalk17.hex("#83D1EB")("caliber init") + chalk17.yellow(" first."));
9336
+ console.log(chalk17.yellow("No existing config found. Run ") + chalk17.hex("#83D1EB")("caliber init") + chalk17.yellow(" first."));
9253
9337
  throw new Error("__exit__");
9254
9338
  }
9255
9339
  const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
@@ -9260,10 +9344,10 @@ async function regenerateCommand(options) {
9260
9344
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
9261
9345
  displayScoreSummary(baselineScore);
9262
9346
  if (baselineScore.score === 100) {
9263
- console.log(chalk17.green(" Your setup is already at 100/100 \u2014 nothing to regenerate.\n"));
9347
+ console.log(chalk17.green(" Your config is already at 100/100 \u2014 nothing to regenerate.\n"));
9264
9348
  return;
9265
9349
  }
9266
- const genSpinner = ora5("Regenerating setup...").start();
9350
+ const genSpinner = ora5("Regenerating config...").start();
9267
9351
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
9268
9352
  genMessages.start();
9269
9353
  let generatedSetup = null;
@@ -9294,10 +9378,10 @@ async function regenerateCommand(options) {
9294
9378
  }
9295
9379
  genMessages.stop();
9296
9380
  if (!generatedSetup) {
9297
- genSpinner.fail("Failed to regenerate setup.");
9381
+ genSpinner.fail("Failed to regenerate config.");
9298
9382
  throw new Error("__exit__");
9299
9383
  }
9300
- genSpinner.succeed("Setup regenerated");
9384
+ genSpinner.succeed("Config regenerated");
9301
9385
  generatedSetup = await runScoreRefineWithSpinner(generatedSetup, process.cwd(), []);
9302
9386
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
9303
9387
  const staged = stageFiles(setupFiles, process.cwd());
@@ -9324,7 +9408,7 @@ async function regenerateCommand(options) {
9324
9408
  await openReview(reviewMethod, staged.stagedFiles);
9325
9409
  }
9326
9410
  const action = await select6({
9327
- message: "Apply regenerated setup?",
9411
+ message: "Apply regenerated config?",
9328
9412
  choices: [
9329
9413
  { name: "Accept and apply", value: "accept" },
9330
9414
  { name: "Decline", value: "decline" }
@@ -9383,21 +9467,21 @@ async function regenerateCommand(options) {
9383
9467
  }
9384
9468
 
9385
9469
  // src/commands/score.ts
9386
- import fs33 from "fs";
9470
+ import fs34 from "fs";
9387
9471
  import os7 from "os";
9388
- import path25 from "path";
9472
+ import path26 from "path";
9389
9473
  import { execFileSync as execFileSync2 } from "child_process";
9390
9474
  import chalk18 from "chalk";
9391
9475
  var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
9392
9476
  var CONFIG_DIRS = [".claude", ".cursor"];
9393
9477
  function scoreBaseRef(ref, target) {
9394
9478
  if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
9395
- const tmpDir = fs33.mkdtempSync(path25.join(os7.tmpdir(), "caliber-compare-"));
9479
+ const tmpDir = fs34.mkdtempSync(path26.join(os7.tmpdir(), "caliber-compare-"));
9396
9480
  try {
9397
9481
  for (const file of CONFIG_FILES) {
9398
9482
  try {
9399
9483
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9400
- fs33.writeFileSync(path25.join(tmpDir, file), content);
9484
+ fs34.writeFileSync(path26.join(tmpDir, file), content);
9401
9485
  } catch {
9402
9486
  }
9403
9487
  }
@@ -9405,10 +9489,10 @@ function scoreBaseRef(ref, target) {
9405
9489
  try {
9406
9490
  const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
9407
9491
  for (const file of files) {
9408
- const filePath = path25.join(tmpDir, file);
9409
- fs33.mkdirSync(path25.dirname(filePath), { recursive: true });
9492
+ const filePath = path26.join(tmpDir, file);
9493
+ fs34.mkdirSync(path26.dirname(filePath), { recursive: true });
9410
9494
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9411
- fs33.writeFileSync(filePath, content);
9495
+ fs34.writeFileSync(filePath, content);
9412
9496
  }
9413
9497
  } catch {
9414
9498
  }
@@ -9418,7 +9502,7 @@ function scoreBaseRef(ref, target) {
9418
9502
  } catch {
9419
9503
  return null;
9420
9504
  } finally {
9421
- fs33.rmSync(tmpDir, { recursive: true, force: true });
9505
+ fs34.rmSync(tmpDir, { recursive: true, force: true });
9422
9506
  }
9423
9507
  }
9424
9508
  async function scoreCommand(options) {
@@ -9426,6 +9510,7 @@ async function scoreCommand(options) {
9426
9510
  const target = options.agent ?? readState()?.targetAgent;
9427
9511
  const result = computeLocalScore(dir, target);
9428
9512
  trackScoreComputed(result.score, target);
9513
+ recordScore(result, "score");
9429
9514
  if (options.compare) {
9430
9515
  const baseResult = scoreBaseRef(options.compare, target);
9431
9516
  if (!baseResult) {
@@ -9468,9 +9553,9 @@ async function scoreCommand(options) {
9468
9553
  const separator = chalk18.gray(" " + "\u2500".repeat(53));
9469
9554
  console.log(separator);
9470
9555
  if (result.score < 40) {
9471
- console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to generate a complete, optimized setup."));
9556
+ console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to generate a complete, optimized config."));
9472
9557
  } else if (result.score < 70) {
9473
- console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to improve your setup."));
9558
+ console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to improve your config."));
9474
9559
  } else {
9475
9560
  console.log(chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber regenerate") + chalk18.gray(" to rebuild from scratch."));
9476
9561
  }
@@ -9478,8 +9563,8 @@ async function scoreCommand(options) {
9478
9563
  }
9479
9564
 
9480
9565
  // src/commands/refresh.ts
9481
- import fs37 from "fs";
9482
- import path29 from "path";
9566
+ import fs38 from "fs";
9567
+ import path30 from "path";
9483
9568
  import chalk19 from "chalk";
9484
9569
  import ora6 from "ora";
9485
9570
 
@@ -9557,48 +9642,48 @@ function collectDiff(lastSha) {
9557
9642
  }
9558
9643
 
9559
9644
  // src/writers/refresh.ts
9560
- import fs34 from "fs";
9561
- import path26 from "path";
9645
+ import fs35 from "fs";
9646
+ import path27 from "path";
9562
9647
  function writeRefreshDocs(docs) {
9563
9648
  const written = [];
9564
9649
  if (docs.claudeMd) {
9565
- fs34.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
9650
+ fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
9566
9651
  written.push("CLAUDE.md");
9567
9652
  }
9568
9653
  if (docs.readmeMd) {
9569
- fs34.writeFileSync("README.md", docs.readmeMd);
9654
+ fs35.writeFileSync("README.md", docs.readmeMd);
9570
9655
  written.push("README.md");
9571
9656
  }
9572
9657
  if (docs.cursorrules) {
9573
- fs34.writeFileSync(".cursorrules", docs.cursorrules);
9658
+ fs35.writeFileSync(".cursorrules", docs.cursorrules);
9574
9659
  written.push(".cursorrules");
9575
9660
  }
9576
9661
  if (docs.cursorRules) {
9577
- const rulesDir = path26.join(".cursor", "rules");
9578
- if (!fs34.existsSync(rulesDir)) fs34.mkdirSync(rulesDir, { recursive: true });
9662
+ const rulesDir = path27.join(".cursor", "rules");
9663
+ if (!fs35.existsSync(rulesDir)) fs35.mkdirSync(rulesDir, { recursive: true });
9579
9664
  for (const rule of docs.cursorRules) {
9580
- fs34.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
9665
+ fs35.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
9581
9666
  written.push(`.cursor/rules/${rule.filename}`);
9582
9667
  }
9583
9668
  }
9584
9669
  if (docs.claudeSkills) {
9585
- const skillsDir = path26.join(".claude", "skills");
9586
- if (!fs34.existsSync(skillsDir)) fs34.mkdirSync(skillsDir, { recursive: true });
9670
+ const skillsDir = path27.join(".claude", "skills");
9671
+ if (!fs35.existsSync(skillsDir)) fs35.mkdirSync(skillsDir, { recursive: true });
9587
9672
  for (const skill of docs.claudeSkills) {
9588
- fs34.writeFileSync(path26.join(skillsDir, skill.filename), skill.content);
9673
+ fs35.writeFileSync(path27.join(skillsDir, skill.filename), skill.content);
9589
9674
  written.push(`.claude/skills/${skill.filename}`);
9590
9675
  }
9591
9676
  }
9592
9677
  if (docs.copilotInstructions) {
9593
- fs34.mkdirSync(".github", { recursive: true });
9594
- fs34.writeFileSync(path26.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
9678
+ fs35.mkdirSync(".github", { recursive: true });
9679
+ fs35.writeFileSync(path27.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
9595
9680
  written.push(".github/copilot-instructions.md");
9596
9681
  }
9597
9682
  if (docs.copilotInstructionFiles) {
9598
- const instructionsDir = path26.join(".github", "instructions");
9599
- fs34.mkdirSync(instructionsDir, { recursive: true });
9683
+ const instructionsDir = path27.join(".github", "instructions");
9684
+ fs35.mkdirSync(instructionsDir, { recursive: true });
9600
9685
  for (const file of docs.copilotInstructionFiles) {
9601
- fs34.writeFileSync(path26.join(instructionsDir, file.filename), file.content);
9686
+ fs35.writeFileSync(path27.join(instructionsDir, file.filename), file.content);
9602
9687
  written.push(`.github/instructions/${file.filename}`);
9603
9688
  }
9604
9689
  }
@@ -9624,6 +9709,12 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
9624
9709
  if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
9625
9710
  if (projectContext.languages?.length) parts.push(`Languages: ${projectContext.languages.join(", ")}`);
9626
9711
  if (projectContext.frameworks?.length) parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
9712
+ if (projectContext.fileTree?.length) {
9713
+ const tree = projectContext.fileTree.slice(0, 200);
9714
+ parts.push(`
9715
+ File tree (${tree.length}/${projectContext.fileTree.length} \u2014 only reference paths from this list):
9716
+ ${tree.join("\n")}`);
9717
+ }
9627
9718
  parts.push(`
9628
9719
  Changed files: ${diff.changedFiles.join(", ")}`);
9629
9720
  parts.push(`Summary: ${diff.summary}`);
@@ -9678,8 +9769,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
9678
9769
  }
9679
9770
 
9680
9771
  // src/learner/writer.ts
9681
- import fs35 from "fs";
9682
- import path27 from "path";
9772
+ import fs36 from "fs";
9773
+ import path28 from "path";
9683
9774
 
9684
9775
  // src/learner/utils.ts
9685
9776
  var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
@@ -9796,20 +9887,20 @@ function deduplicateLearnedItems(existing, incoming) {
9796
9887
  }
9797
9888
  function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
9798
9889
  const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
9799
- fs35.writeFileSync(filePath, header + merged + "\n");
9800
- if (mode) fs35.chmodSync(filePath, mode);
9890
+ fs36.writeFileSync(filePath, header + merged + "\n");
9891
+ if (mode) fs36.chmodSync(filePath, mode);
9801
9892
  return { newCount, newItems };
9802
9893
  }
9803
9894
  function writeLearnedSection(content) {
9804
9895
  return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
9805
9896
  }
9806
9897
  function writeLearnedSkill(skill) {
9807
- const skillDir = path27.join(".claude", "skills", skill.name);
9808
- if (!fs35.existsSync(skillDir)) fs35.mkdirSync(skillDir, { recursive: true });
9809
- const skillPath = path27.join(skillDir, "SKILL.md");
9810
- if (!skill.isNew && fs35.existsSync(skillPath)) {
9811
- const existing = fs35.readFileSync(skillPath, "utf-8");
9812
- fs35.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
9898
+ const skillDir = path28.join(".claude", "skills", skill.name);
9899
+ if (!fs36.existsSync(skillDir)) fs36.mkdirSync(skillDir, { recursive: true });
9900
+ const skillPath = path28.join(skillDir, "SKILL.md");
9901
+ if (!skill.isNew && fs36.existsSync(skillPath)) {
9902
+ const existing = fs36.readFileSync(skillPath, "utf-8");
9903
+ fs36.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
9813
9904
  } else {
9814
9905
  const frontmatter = [
9815
9906
  "---",
@@ -9818,12 +9909,12 @@ function writeLearnedSkill(skill) {
9818
9909
  "---",
9819
9910
  ""
9820
9911
  ].join("\n");
9821
- fs35.writeFileSync(skillPath, frontmatter + skill.content);
9912
+ fs36.writeFileSync(skillPath, frontmatter + skill.content);
9822
9913
  }
9823
9914
  return skillPath;
9824
9915
  }
9825
9916
  function writePersonalLearnedSection(content) {
9826
- if (!fs35.existsSync(AUTH_DIR)) fs35.mkdirSync(AUTH_DIR, { recursive: true });
9917
+ if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
9827
9918
  return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
9828
9919
  }
9829
9920
  function addLearning(bullet, scope = "project") {
@@ -9836,38 +9927,38 @@ function addLearning(bullet, scope = "project") {
9836
9927
  return { file: LEARNINGS_FILE, added: result.newCount > 0 };
9837
9928
  }
9838
9929
  function readPersonalLearnings() {
9839
- if (!fs35.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
9840
- const content = fs35.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
9930
+ if (!fs36.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
9931
+ const content = fs36.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
9841
9932
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
9842
9933
  return bullets || null;
9843
9934
  }
9844
9935
  function readLearnedSection() {
9845
- if (fs35.existsSync(LEARNINGS_FILE)) {
9846
- const content2 = fs35.readFileSync(LEARNINGS_FILE, "utf-8");
9936
+ if (fs36.existsSync(LEARNINGS_FILE)) {
9937
+ const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
9847
9938
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
9848
9939
  return bullets || null;
9849
9940
  }
9850
9941
  const claudeMdPath = "CLAUDE.md";
9851
- if (!fs35.existsSync(claudeMdPath)) return null;
9852
- const content = fs35.readFileSync(claudeMdPath, "utf-8");
9942
+ if (!fs36.existsSync(claudeMdPath)) return null;
9943
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
9853
9944
  const startIdx = content.indexOf(LEARNED_START);
9854
9945
  const endIdx = content.indexOf(LEARNED_END);
9855
9946
  if (startIdx === -1 || endIdx === -1) return null;
9856
9947
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
9857
9948
  }
9858
9949
  function migrateInlineLearnings() {
9859
- if (fs35.existsSync(LEARNINGS_FILE)) return false;
9950
+ if (fs36.existsSync(LEARNINGS_FILE)) return false;
9860
9951
  const claudeMdPath = "CLAUDE.md";
9861
- if (!fs35.existsSync(claudeMdPath)) return false;
9862
- const content = fs35.readFileSync(claudeMdPath, "utf-8");
9952
+ if (!fs36.existsSync(claudeMdPath)) return false;
9953
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
9863
9954
  const startIdx = content.indexOf(LEARNED_START);
9864
9955
  const endIdx = content.indexOf(LEARNED_END);
9865
9956
  if (startIdx === -1 || endIdx === -1) return false;
9866
9957
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
9867
9958
  if (!section) return false;
9868
- fs35.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9959
+ fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9869
9960
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
9870
- fs35.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
9961
+ fs36.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
9871
9962
  return true;
9872
9963
  }
9873
9964
 
@@ -9879,11 +9970,11 @@ function log2(quiet, ...args) {
9879
9970
  function discoverGitRepos(parentDir) {
9880
9971
  const repos = [];
9881
9972
  try {
9882
- const entries = fs37.readdirSync(parentDir, { withFileTypes: true });
9973
+ const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
9883
9974
  for (const entry of entries) {
9884
9975
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
9885
- const childPath = path29.join(parentDir, entry.name);
9886
- if (fs37.existsSync(path29.join(childPath, ".git"))) {
9976
+ const childPath = path30.join(parentDir, entry.name);
9977
+ if (fs38.existsSync(path30.join(childPath, ".git"))) {
9887
9978
  repos.push(childPath);
9888
9979
  }
9889
9980
  }
@@ -9891,11 +9982,19 @@ function discoverGitRepos(parentDir) {
9891
9982
  }
9892
9983
  return repos.sort();
9893
9984
  }
9985
+ var REFRESH_COOLDOWN_MS = 3e4;
9894
9986
  async function refreshSingleRepo(repoDir, options) {
9895
9987
  const quiet = !!options.quiet;
9896
9988
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
9897
9989
  const state = readState();
9898
9990
  const lastSha = state?.lastRefreshSha ?? null;
9991
+ if (state?.lastRefreshTimestamp) {
9992
+ const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
9993
+ if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
9994
+ log2(quiet, chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`));
9995
+ return;
9996
+ }
9997
+ }
9899
9998
  const diff = collectDiff(lastSha);
9900
9999
  const currentSha = getCurrentHeadSha();
9901
10000
  if (!diff.hasChanges) {
@@ -9912,23 +10011,32 @@ async function refreshSingleRepo(repoDir, options) {
9912
10011
  const projectContext = {
9913
10012
  languages: fingerprint.languages,
9914
10013
  frameworks: fingerprint.frameworks,
9915
- packageName: fingerprint.packageName
10014
+ packageName: fingerprint.packageName,
10015
+ fileTree: fingerprint.fileTree
9916
10016
  };
9917
10017
  const workspaces = getDetectedWorkspaces(repoDir);
9918
10018
  const sources2 = resolveAllSources(repoDir, [], workspaces);
9919
- const response = await refreshDocs(
9920
- {
9921
- committed: diff.committedDiff,
9922
- staged: diff.stagedDiff,
9923
- unstaged: diff.unstagedDiff,
9924
- changedFiles: diff.changedFiles,
9925
- summary: diff.summary
9926
- },
9927
- existingDocs,
9928
- projectContext,
9929
- learnedSection,
9930
- sources2.length > 0 ? sources2 : void 0
9931
- );
10019
+ const diffPayload = {
10020
+ committed: diff.committedDiff,
10021
+ staged: diff.stagedDiff,
10022
+ unstaged: diff.unstagedDiff,
10023
+ changedFiles: diff.changedFiles,
10024
+ summary: diff.summary
10025
+ };
10026
+ const sourcesPayload = sources2.length > 0 ? sources2 : void 0;
10027
+ let response;
10028
+ try {
10029
+ response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
10030
+ } catch (firstErr) {
10031
+ const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
10032
+ if (!isTransient) throw firstErr;
10033
+ try {
10034
+ response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
10035
+ } catch {
10036
+ spinner?.fail(`${prefix}Refresh failed after retry`);
10037
+ throw firstErr;
10038
+ }
10039
+ }
9932
10040
  if (!response.docsUpdated || response.docsUpdated.length === 0) {
9933
10041
  spinner?.succeed(`${prefix}No doc updates needed`);
9934
10042
  if (currentSha) {
@@ -9947,8 +10055,41 @@ async function refreshSingleRepo(repoDir, options) {
9947
10055
  }
9948
10056
  return;
9949
10057
  }
10058
+ const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
10059
+ const preScore = computeLocalScore(repoDir, targetAgent);
10060
+ const filesToWrite = response.docsUpdated || [];
10061
+ const preRefreshContents = /* @__PURE__ */ new Map();
10062
+ for (const filePath of filesToWrite) {
10063
+ const fullPath = path30.resolve(repoDir, filePath);
10064
+ try {
10065
+ preRefreshContents.set(filePath, fs38.readFileSync(fullPath, "utf-8"));
10066
+ } catch {
10067
+ preRefreshContents.set(filePath, null);
10068
+ }
10069
+ }
9950
10070
  const written = writeRefreshDocs(response.updatedDocs);
9951
10071
  trackRefreshCompleted(written.length, Date.now());
10072
+ const postScore = computeLocalScore(repoDir, targetAgent);
10073
+ if (postScore.score < preScore.score) {
10074
+ for (const [filePath, content] of preRefreshContents) {
10075
+ const fullPath = path30.resolve(repoDir, filePath);
10076
+ if (content === null) {
10077
+ try {
10078
+ fs38.unlinkSync(fullPath);
10079
+ } catch {
10080
+ }
10081
+ } else {
10082
+ fs38.writeFileSync(fullPath, content);
10083
+ }
10084
+ }
10085
+ spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
10086
+ log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
10087
+ if (currentSha) {
10088
+ writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
10089
+ }
10090
+ return;
10091
+ }
10092
+ recordScore(postScore, "refresh");
9952
10093
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
9953
10094
  for (const file of written) {
9954
10095
  log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
@@ -9993,7 +10134,7 @@ async function refreshCommand(options) {
9993
10134
  `));
9994
10135
  const originalDir = process.cwd();
9995
10136
  for (const repo of repos) {
9996
- const repoName = path29.basename(repo);
10137
+ const repoName = path30.basename(repo);
9997
10138
  try {
9998
10139
  process.chdir(repo);
9999
10140
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -10014,31 +10155,31 @@ async function refreshCommand(options) {
10014
10155
 
10015
10156
  // src/commands/hooks.ts
10016
10157
  import chalk20 from "chalk";
10017
- import fs39 from "fs";
10158
+ import fs40 from "fs";
10018
10159
 
10019
10160
  // src/lib/hooks.ts
10020
10161
  init_resolve_caliber();
10021
- import fs38 from "fs";
10022
- import path30 from "path";
10162
+ import fs39 from "fs";
10163
+ import path31 from "path";
10023
10164
  import { execSync as execSync15 } from "child_process";
10024
- var SETTINGS_PATH2 = path30.join(".claude", "settings.json");
10165
+ var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
10025
10166
  var REFRESH_TAIL = "refresh --quiet";
10026
10167
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
10027
10168
  function getHookCommand() {
10028
10169
  return `${resolveCaliber()} ${REFRESH_TAIL}`;
10029
10170
  }
10030
10171
  function readSettings2() {
10031
- if (!fs38.existsSync(SETTINGS_PATH2)) return {};
10172
+ if (!fs39.existsSync(SETTINGS_PATH2)) return {};
10032
10173
  try {
10033
- return JSON.parse(fs38.readFileSync(SETTINGS_PATH2, "utf-8"));
10174
+ return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
10034
10175
  } catch {
10035
10176
  return {};
10036
10177
  }
10037
10178
  }
10038
10179
  function writeSettings2(settings) {
10039
- const dir = path30.dirname(SETTINGS_PATH2);
10040
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
10041
- fs38.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
10180
+ const dir = path31.dirname(SETTINGS_PATH2);
10181
+ if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
10182
+ fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
10042
10183
  }
10043
10184
  function findHookIndex(sessionEnd) {
10044
10185
  return sessionEnd.findIndex(
@@ -10101,19 +10242,19 @@ ${PRECOMMIT_END}`;
10101
10242
  function getGitHooksDir() {
10102
10243
  try {
10103
10244
  const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10104
- return path30.join(gitDir, "hooks");
10245
+ return path31.join(gitDir, "hooks");
10105
10246
  } catch {
10106
10247
  return null;
10107
10248
  }
10108
10249
  }
10109
10250
  function getPreCommitPath() {
10110
10251
  const hooksDir = getGitHooksDir();
10111
- return hooksDir ? path30.join(hooksDir, "pre-commit") : null;
10252
+ return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
10112
10253
  }
10113
10254
  function isPreCommitHookInstalled() {
10114
10255
  const hookPath = getPreCommitPath();
10115
- if (!hookPath || !fs38.existsSync(hookPath)) return false;
10116
- const content = fs38.readFileSync(hookPath, "utf-8");
10256
+ if (!hookPath || !fs39.existsSync(hookPath)) return false;
10257
+ const content = fs39.readFileSync(hookPath, "utf-8");
10117
10258
  return content.includes(PRECOMMIT_START);
10118
10259
  }
10119
10260
  function installPreCommitHook() {
@@ -10122,35 +10263,35 @@ function installPreCommitHook() {
10122
10263
  }
10123
10264
  const hookPath = getPreCommitPath();
10124
10265
  if (!hookPath) return { installed: false, alreadyInstalled: false };
10125
- const hooksDir = path30.dirname(hookPath);
10126
- if (!fs38.existsSync(hooksDir)) fs38.mkdirSync(hooksDir, { recursive: true });
10266
+ const hooksDir = path31.dirname(hookPath);
10267
+ if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
10127
10268
  let content = "";
10128
- if (fs38.existsSync(hookPath)) {
10129
- content = fs38.readFileSync(hookPath, "utf-8");
10269
+ if (fs39.existsSync(hookPath)) {
10270
+ content = fs39.readFileSync(hookPath, "utf-8");
10130
10271
  if (!content.endsWith("\n")) content += "\n";
10131
10272
  content += "\n" + getPrecommitBlock() + "\n";
10132
10273
  } else {
10133
10274
  content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
10134
10275
  }
10135
- fs38.writeFileSync(hookPath, content);
10136
- fs38.chmodSync(hookPath, 493);
10276
+ fs39.writeFileSync(hookPath, content);
10277
+ fs39.chmodSync(hookPath, 493);
10137
10278
  return { installed: true, alreadyInstalled: false };
10138
10279
  }
10139
10280
  function removePreCommitHook() {
10140
10281
  const hookPath = getPreCommitPath();
10141
- if (!hookPath || !fs38.existsSync(hookPath)) {
10282
+ if (!hookPath || !fs39.existsSync(hookPath)) {
10142
10283
  return { removed: false, notFound: true };
10143
10284
  }
10144
- let content = fs38.readFileSync(hookPath, "utf-8");
10285
+ let content = fs39.readFileSync(hookPath, "utf-8");
10145
10286
  if (!content.includes(PRECOMMIT_START)) {
10146
10287
  return { removed: false, notFound: true };
10147
10288
  }
10148
10289
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
10149
10290
  content = content.replace(regex, "\n");
10150
10291
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
10151
- fs38.unlinkSync(hookPath);
10292
+ fs39.unlinkSync(hookPath);
10152
10293
  } else {
10153
- fs38.writeFileSync(hookPath, content);
10294
+ fs39.writeFileSync(hookPath, content);
10154
10295
  }
10155
10296
  return { removed: true, notFound: false };
10156
10297
  }
@@ -10199,11 +10340,11 @@ async function hooksCommand(options) {
10199
10340
  console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
10200
10341
  }
10201
10342
  }
10202
- if (fs39.existsSync(".claude")) {
10343
+ if (fs40.existsSync(".claude")) {
10203
10344
  const r = installLearningHooks();
10204
10345
  if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
10205
10346
  }
10206
- if (fs39.existsSync(".cursor")) {
10347
+ if (fs40.existsSync(".cursor")) {
10207
10348
  const r = installCursorLearningHooks();
10208
10349
  if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
10209
10350
  }
@@ -10371,8 +10512,8 @@ async function configCommand() {
10371
10512
  }
10372
10513
 
10373
10514
  // src/commands/learn.ts
10374
- import fs43 from "fs";
10375
- import path34 from "path";
10515
+ import fs44 from "fs";
10516
+ import path35 from "path";
10376
10517
  import chalk23 from "chalk";
10377
10518
 
10378
10519
  // src/learner/stdin.ts
@@ -10403,8 +10544,8 @@ function readStdin() {
10403
10544
  }
10404
10545
 
10405
10546
  // src/learner/storage.ts
10406
- import fs40 from "fs";
10407
- import path31 from "path";
10547
+ import fs41 from "fs";
10548
+ import path32 from "path";
10408
10549
  var MAX_RESPONSE_LENGTH = 2e3;
10409
10550
  var DEFAULT_STATE = {
10410
10551
  sessionId: null,
@@ -10413,15 +10554,15 @@ var DEFAULT_STATE = {
10413
10554
  lastAnalysisEventCount: 0
10414
10555
  };
10415
10556
  function ensureLearningDir() {
10416
- if (!fs40.existsSync(getLearningDir())) {
10417
- fs40.mkdirSync(getLearningDir(), { recursive: true });
10557
+ if (!fs41.existsSync(getLearningDir())) {
10558
+ fs41.mkdirSync(getLearningDir(), { recursive: true });
10418
10559
  }
10419
10560
  }
10420
10561
  function sessionFilePath() {
10421
- return path31.join(getLearningDir(), LEARNING_SESSION_FILE);
10562
+ return path32.join(getLearningDir(), LEARNING_SESSION_FILE);
10422
10563
  }
10423
10564
  function stateFilePath() {
10424
- return path31.join(getLearningDir(), LEARNING_STATE_FILE);
10565
+ return path32.join(getLearningDir(), LEARNING_STATE_FILE);
10425
10566
  }
10426
10567
  function truncateResponse(response) {
10427
10568
  const str = JSON.stringify(response);
@@ -10431,10 +10572,10 @@ function truncateResponse(response) {
10431
10572
  function trimSessionFileIfNeeded(filePath) {
10432
10573
  const state = readState2();
10433
10574
  if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
10434
- const lines = fs40.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10575
+ const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10435
10576
  if (lines.length > LEARNING_MAX_EVENTS) {
10436
10577
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
10437
- fs40.writeFileSync(filePath, kept.join("\n") + "\n");
10578
+ fs41.writeFileSync(filePath, kept.join("\n") + "\n");
10438
10579
  }
10439
10580
  }
10440
10581
  }
@@ -10442,19 +10583,19 @@ function appendEvent(event) {
10442
10583
  ensureLearningDir();
10443
10584
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
10444
10585
  const filePath = sessionFilePath();
10445
- fs40.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
10586
+ fs41.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
10446
10587
  trimSessionFileIfNeeded(filePath);
10447
10588
  }
10448
10589
  function appendPromptEvent(event) {
10449
10590
  ensureLearningDir();
10450
10591
  const filePath = sessionFilePath();
10451
- fs40.appendFileSync(filePath, JSON.stringify(event) + "\n");
10592
+ fs41.appendFileSync(filePath, JSON.stringify(event) + "\n");
10452
10593
  trimSessionFileIfNeeded(filePath);
10453
10594
  }
10454
10595
  function readAllEvents() {
10455
10596
  const filePath = sessionFilePath();
10456
- if (!fs40.existsSync(filePath)) return [];
10457
- const lines = fs40.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10597
+ if (!fs41.existsSync(filePath)) return [];
10598
+ const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10458
10599
  const events = [];
10459
10600
  for (const line of lines) {
10460
10601
  try {
@@ -10466,26 +10607,26 @@ function readAllEvents() {
10466
10607
  }
10467
10608
  function getEventCount() {
10468
10609
  const filePath = sessionFilePath();
10469
- if (!fs40.existsSync(filePath)) return 0;
10470
- const content = fs40.readFileSync(filePath, "utf-8");
10610
+ if (!fs41.existsSync(filePath)) return 0;
10611
+ const content = fs41.readFileSync(filePath, "utf-8");
10471
10612
  return content.split("\n").filter(Boolean).length;
10472
10613
  }
10473
10614
  function clearSession() {
10474
10615
  const filePath = sessionFilePath();
10475
- if (fs40.existsSync(filePath)) fs40.unlinkSync(filePath);
10616
+ if (fs41.existsSync(filePath)) fs41.unlinkSync(filePath);
10476
10617
  }
10477
10618
  function readState2() {
10478
10619
  const filePath = stateFilePath();
10479
- if (!fs40.existsSync(filePath)) return { ...DEFAULT_STATE };
10620
+ if (!fs41.existsSync(filePath)) return { ...DEFAULT_STATE };
10480
10621
  try {
10481
- return JSON.parse(fs40.readFileSync(filePath, "utf-8"));
10622
+ return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
10482
10623
  } catch {
10483
10624
  return { ...DEFAULT_STATE };
10484
10625
  }
10485
10626
  }
10486
10627
  function writeState2(state) {
10487
10628
  ensureLearningDir();
10488
- fs40.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
10629
+ fs41.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
10489
10630
  }
10490
10631
  function resetState() {
10491
10632
  writeState2({ ...DEFAULT_STATE });
@@ -10493,16 +10634,16 @@ function resetState() {
10493
10634
  var LOCK_FILE2 = "finalize.lock";
10494
10635
  var LOCK_STALE_MS = 5 * 60 * 1e3;
10495
10636
  function lockFilePath() {
10496
- return path31.join(getLearningDir(), LOCK_FILE2);
10637
+ return path32.join(getLearningDir(), LOCK_FILE2);
10497
10638
  }
10498
10639
  function acquireFinalizeLock() {
10499
10640
  ensureLearningDir();
10500
10641
  const lockPath = lockFilePath();
10501
- if (fs40.existsSync(lockPath)) {
10642
+ if (fs41.existsSync(lockPath)) {
10502
10643
  try {
10503
- const stat = fs40.statSync(lockPath);
10644
+ const stat = fs41.statSync(lockPath);
10504
10645
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
10505
- const pid = parseInt(fs40.readFileSync(lockPath, "utf-8").trim(), 10);
10646
+ const pid = parseInt(fs41.readFileSync(lockPath, "utf-8").trim(), 10);
10506
10647
  if (!isNaN(pid) && isProcessAlive(pid)) {
10507
10648
  return false;
10508
10649
  }
@@ -10510,12 +10651,12 @@ function acquireFinalizeLock() {
10510
10651
  } catch {
10511
10652
  }
10512
10653
  try {
10513
- fs40.unlinkSync(lockPath);
10654
+ fs41.unlinkSync(lockPath);
10514
10655
  } catch {
10515
10656
  }
10516
10657
  }
10517
10658
  try {
10518
- fs40.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
10659
+ fs41.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
10519
10660
  return true;
10520
10661
  } catch {
10521
10662
  return false;
@@ -10532,7 +10673,7 @@ function isProcessAlive(pid) {
10532
10673
  function releaseFinalizeLock() {
10533
10674
  const lockPath = lockFilePath();
10534
10675
  try {
10535
- if (fs40.existsSync(lockPath)) fs40.unlinkSync(lockPath);
10676
+ if (fs41.existsSync(lockPath)) fs41.unlinkSync(lockPath);
10536
10677
  } catch {
10537
10678
  }
10538
10679
  }
@@ -10577,24 +10718,24 @@ function sanitizeSecrets(text) {
10577
10718
  }
10578
10719
 
10579
10720
  // src/lib/notifications.ts
10580
- import fs41 from "fs";
10581
- import path32 from "path";
10721
+ import fs42 from "fs";
10722
+ import path33 from "path";
10582
10723
  import chalk22 from "chalk";
10583
10724
  function notificationFilePath() {
10584
- return path32.join(getLearningDir(), "last-finalize-summary.json");
10725
+ return path33.join(getLearningDir(), "last-finalize-summary.json");
10585
10726
  }
10586
10727
  function writeFinalizeSummary(summary) {
10587
10728
  try {
10588
10729
  ensureLearningDir();
10589
- fs41.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
10730
+ fs42.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
10590
10731
  } catch {
10591
10732
  }
10592
10733
  }
10593
10734
  function checkPendingNotifications() {
10594
10735
  try {
10595
- if (!fs41.existsSync(notificationFilePath())) return;
10596
- const raw = fs41.readFileSync(notificationFilePath(), "utf-8");
10597
- fs41.unlinkSync(notificationFilePath());
10736
+ if (!fs42.existsSync(notificationFilePath())) return;
10737
+ const raw = fs42.readFileSync(notificationFilePath(), "utf-8");
10738
+ fs42.unlinkSync(notificationFilePath());
10598
10739
  const summary = JSON.parse(raw);
10599
10740
  if (!summary.newItemCount || summary.newItemCount === 0) return;
10600
10741
  const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
@@ -10610,7 +10751,7 @@ function checkPendingNotifications() {
10610
10751
  console.log("");
10611
10752
  } catch {
10612
10753
  try {
10613
- fs41.unlinkSync(notificationFilePath());
10754
+ fs42.unlinkSync(notificationFilePath());
10614
10755
  } catch {
10615
10756
  }
10616
10757
  }
@@ -10762,8 +10903,8 @@ function calculateSessionWaste(events) {
10762
10903
  init_config();
10763
10904
 
10764
10905
  // src/learner/roi.ts
10765
- import fs42 from "fs";
10766
- import path33 from "path";
10906
+ import fs43 from "fs";
10907
+ import path34 from "path";
10767
10908
  var DEFAULT_TOTALS = {
10768
10909
  totalWasteTokens: 0,
10769
10910
  totalWasteSeconds: 0,
@@ -10777,19 +10918,19 @@ var DEFAULT_TOTALS = {
10777
10918
  lastSessionTimestamp: ""
10778
10919
  };
10779
10920
  function roiFilePath() {
10780
- return path33.join(getLearningDir(), LEARNING_ROI_FILE);
10921
+ return path34.join(getLearningDir(), LEARNING_ROI_FILE);
10781
10922
  }
10782
10923
  function readROIStats() {
10783
10924
  const filePath = roiFilePath();
10784
- if (!fs42.existsSync(filePath)) {
10925
+ if (!fs43.existsSync(filePath)) {
10785
10926
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
10786
10927
  }
10787
10928
  try {
10788
- return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
10929
+ return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
10789
10930
  } catch {
10790
10931
  try {
10791
10932
  const corruptPath = filePath + ".corrupt";
10792
- fs42.renameSync(filePath, corruptPath);
10933
+ fs43.renameSync(filePath, corruptPath);
10793
10934
  console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
10794
10935
  } catch {
10795
10936
  }
@@ -10798,7 +10939,7 @@ function readROIStats() {
10798
10939
  }
10799
10940
  function writeROIStats(stats) {
10800
10941
  ensureLearningDir();
10801
- fs42.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
10942
+ fs43.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
10802
10943
  }
10803
10944
  function recalculateTotals(stats) {
10804
10945
  const totals = stats.totals;
@@ -11004,9 +11145,9 @@ var AUTO_SETTLE_MS = 200;
11004
11145
  var INCREMENTAL_INTERVAL = 50;
11005
11146
  function writeFinalizeError(message) {
11006
11147
  try {
11007
- const errorPath = path34.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11008
- if (!fs43.existsSync(getLearningDir())) fs43.mkdirSync(getLearningDir(), { recursive: true });
11009
- fs43.writeFileSync(errorPath, JSON.stringify({
11148
+ const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11149
+ if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
11150
+ fs44.writeFileSync(errorPath, JSON.stringify({
11010
11151
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11011
11152
  error: message,
11012
11153
  pid: process.pid
@@ -11016,9 +11157,9 @@ function writeFinalizeError(message) {
11016
11157
  }
11017
11158
  function readFinalizeError() {
11018
11159
  try {
11019
- const errorPath = path34.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11020
- if (!fs43.existsSync(errorPath)) return null;
11021
- return JSON.parse(fs43.readFileSync(errorPath, "utf-8"));
11160
+ const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
11161
+ if (!fs44.existsSync(errorPath)) return null;
11162
+ return JSON.parse(fs44.readFileSync(errorPath, "utf-8"));
11022
11163
  } catch {
11023
11164
  return null;
11024
11165
  }
@@ -11065,14 +11206,14 @@ async function learnObserveCommand(options) {
11065
11206
  const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
11066
11207
  const bin = resolveCaliber2();
11067
11208
  const { spawn: spawn4 } = await import("child_process");
11068
- const logPath = path34.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11069
- if (!fs43.existsSync(getLearningDir())) fs43.mkdirSync(getLearningDir(), { recursive: true });
11070
- const logFd = fs43.openSync(logPath, "a");
11209
+ const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11210
+ if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
11211
+ const logFd = fs44.openSync(logPath, "a");
11071
11212
  spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
11072
11213
  detached: true,
11073
11214
  stdio: ["ignore", logFd, logFd]
11074
11215
  }).unref();
11075
- fs43.closeSync(logFd);
11216
+ fs44.closeSync(logFd);
11076
11217
  } catch {
11077
11218
  }
11078
11219
  }
@@ -11279,7 +11420,7 @@ async function learnFinalizeCommand(options) {
11279
11420
  }
11280
11421
  async function learnInstallCommand() {
11281
11422
  let anyInstalled = false;
11282
- if (fs43.existsSync(".claude")) {
11423
+ if (fs44.existsSync(".claude")) {
11283
11424
  const r = installLearningHooks();
11284
11425
  if (r.installed) {
11285
11426
  console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
@@ -11288,7 +11429,7 @@ async function learnInstallCommand() {
11288
11429
  console.log(chalk23.dim(" Claude Code hooks already installed"));
11289
11430
  }
11290
11431
  }
11291
- if (fs43.existsSync(".cursor")) {
11432
+ if (fs44.existsSync(".cursor")) {
11292
11433
  const r = installCursorLearningHooks();
11293
11434
  if (r.installed) {
11294
11435
  console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
@@ -11297,7 +11438,7 @@ async function learnInstallCommand() {
11297
11438
  console.log(chalk23.dim(" Cursor hooks already installed"));
11298
11439
  }
11299
11440
  }
11300
- if (!fs43.existsSync(".claude") && !fs43.existsSync(".cursor")) {
11441
+ if (!fs44.existsSync(".claude") && !fs44.existsSync(".cursor")) {
11301
11442
  console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
11302
11443
  console.log(chalk23.dim(" Run `caliber init` first, or create the directory manually."));
11303
11444
  return;
@@ -11355,8 +11496,8 @@ async function learnStatusCommand() {
11355
11496
  if (lastError) {
11356
11497
  console.log(`Last error: ${chalk23.red(lastError.error)}`);
11357
11498
  console.log(chalk23.dim(` at ${lastError.timestamp}`));
11358
- const logPath = path34.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11359
- if (fs43.existsSync(logPath)) {
11499
+ const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11500
+ if (fs44.existsSync(logPath)) {
11360
11501
  console.log(chalk23.dim(` Full log: ${logPath}`));
11361
11502
  }
11362
11503
  }
@@ -11436,11 +11577,11 @@ async function learnDeleteCommand(indexStr) {
11436
11577
  }
11437
11578
  const item = items[targetIdx];
11438
11579
  const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
11439
- if (!fs43.existsSync(filePath)) {
11580
+ if (!fs44.existsSync(filePath)) {
11440
11581
  console.log(chalk23.red("Learnings file not found."));
11441
11582
  return;
11442
11583
  }
11443
- const content = fs43.readFileSync(filePath, "utf-8");
11584
+ const content = fs44.readFileSync(filePath, "utf-8");
11444
11585
  const lines = content.split("\n");
11445
11586
  const bulletsOfSource = items.filter((i) => i.source === item.source);
11446
11587
  const posInFile = bulletsOfSource.indexOf(item);
@@ -11461,9 +11602,9 @@ async function learnDeleteCommand(indexStr) {
11461
11602
  }
11462
11603
  const bulletToRemove = lines[lineToRemove];
11463
11604
  const newLines = lines.filter((_, i) => i !== lineToRemove);
11464
- fs43.writeFileSync(filePath, newLines.join("\n"));
11605
+ fs44.writeFileSync(filePath, newLines.join("\n"));
11465
11606
  if (item.source === "personal") {
11466
- fs43.chmodSync(filePath, 384);
11607
+ fs44.chmodSync(filePath, 384);
11467
11608
  }
11468
11609
  const roiStats = readROIStats();
11469
11610
  const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
@@ -11532,11 +11673,14 @@ function displayColdStart(score) {
11532
11673
  console.log(chalk24.bold("\n Agent Insights\n"));
11533
11674
  const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
11534
11675
  if (!hooksInstalled) {
11535
- console.log(chalk24.yellow(" No learning hooks installed."));
11536
- console.log(chalk24.dim(" Run ") + chalk24.cyan("caliber learn install") + chalk24.dim(" to start tracking agent performance."));
11676
+ console.log(chalk24.yellow(" Learning hooks not installed."));
11677
+ console.log(chalk24.dim(" Session learning captures patterns from your AI coding sessions \u2014 what"));
11678
+ console.log(chalk24.dim(" fails, what works, corrections you make \u2014 so your agents improve over time.\n"));
11679
+ console.log(chalk24.dim(" Run ") + chalk24.cyan("caliber learn install") + chalk24.dim(" to enable."));
11537
11680
  } else {
11538
- console.log(chalk24.dim(" No session data yet. Use your AI agent and insights will appear here."));
11539
- console.log(chalk24.dim(" Learnings are extracted automatically at the end of each session."));
11681
+ console.log(chalk24.dim(" Learning hooks are active. Use your AI agent and insights"));
11682
+ console.log(chalk24.dim(" will appear automatically after each session.\n"));
11683
+ console.log(chalk24.dim(` Progress: 0/${MIN_SESSIONS_FULL} sessions \u2014 full insights unlock at ${MIN_SESSIONS_FULL}`));
11540
11684
  }
11541
11685
  console.log(chalk24.dim(`
11542
11686
  Config score: ${score.score}/100 (${score.grade})`));
@@ -11544,7 +11688,9 @@ function displayColdStart(score) {
11544
11688
  }
11545
11689
  function displayEarlyData(data, score) {
11546
11690
  console.log(chalk24.bold("\n Agent Insights") + chalk24.yellow(" (early data)\n"));
11547
- console.log(chalk24.dim(" Still collecting data. Insights become more reliable after 20+ sessions.\n"));
11691
+ const remaining = MIN_SESSIONS_FULL - data.totalSessions;
11692
+ console.log(chalk24.dim(` ${data.totalSessions}/${MIN_SESSIONS_FULL} sessions tracked \u2014 ${remaining} more for full insights.
11693
+ `));
11548
11694
  console.log(` Sessions tracked: ${chalk24.cyan(String(data.totalSessions))}`);
11549
11695
  console.log(` Learnings accumulated: ${chalk24.cyan(String(data.learningCount))}`);
11550
11696
  if (data.totalWasteTokens > 0) {
@@ -11596,6 +11742,14 @@ function displayFullInsights(data, score) {
11596
11742
  }
11597
11743
  console.log(chalk24.bold("\n Config Quality"));
11598
11744
  console.log(` Score: ${chalk24.cyan(`${score.score}/100`)} (${score.grade})`);
11745
+ const history = readScoreHistory();
11746
+ const trend = getScoreTrend(history);
11747
+ if (trend) {
11748
+ const trendColor = trend.direction === "up" ? chalk24.green : trend.direction === "down" ? chalk24.red : chalk24.gray;
11749
+ const arrow = trend.direction === "up" ? "\u2191" : trend.direction === "down" ? "\u2193" : "\u2192";
11750
+ const sign = trend.delta > 0 ? "+" : "";
11751
+ console.log(` Trend: ${trendColor(`${arrow} ${sign}${trend.delta} pts`)} ${chalk24.dim(`over ${trend.entries} checks`)}`);
11752
+ }
11599
11753
  console.log("");
11600
11754
  }
11601
11755
  async function insightsCommand(options) {
@@ -11622,8 +11776,8 @@ async function insightsCommand(options) {
11622
11776
  }
11623
11777
 
11624
11778
  // src/commands/sources.ts
11625
- import fs44 from "fs";
11626
- import path35 from "path";
11779
+ import fs45 from "fs";
11780
+ import path36 from "path";
11627
11781
  import chalk25 from "chalk";
11628
11782
  async function sourcesListCommand() {
11629
11783
  const dir = process.cwd();
@@ -11639,9 +11793,9 @@ async function sourcesListCommand() {
11639
11793
  if (configSources.length > 0) {
11640
11794
  for (const source of configSources) {
11641
11795
  const sourcePath = source.path || source.url || "";
11642
- const exists = source.path ? fs44.existsSync(path35.resolve(dir, source.path)) : false;
11796
+ const exists = source.path ? fs45.existsSync(path36.resolve(dir, source.path)) : false;
11643
11797
  const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
11644
- const hasSummary = source.path && fs44.existsSync(path35.join(path35.resolve(dir, source.path), ".caliber", "summary.json"));
11798
+ const hasSummary = source.path && fs45.existsSync(path36.join(path36.resolve(dir, source.path), ".caliber", "summary.json"));
11645
11799
  console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
11646
11800
  console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
11647
11801
  if (source.description) console.log(` ${chalk25.dim(source.description)}`);
@@ -11651,7 +11805,7 @@ async function sourcesListCommand() {
11651
11805
  if (workspaces.length > 0) {
11652
11806
  console.log(chalk25.dim(" Auto-detected workspaces:"));
11653
11807
  for (const ws of workspaces) {
11654
- const exists = fs44.existsSync(path35.resolve(dir, ws));
11808
+ const exists = fs45.existsSync(path36.resolve(dir, ws));
11655
11809
  console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
11656
11810
  }
11657
11811
  console.log("");
@@ -11659,8 +11813,8 @@ async function sourcesListCommand() {
11659
11813
  }
11660
11814
  async function sourcesAddCommand(sourcePath) {
11661
11815
  const dir = process.cwd();
11662
- const absPath = path35.resolve(dir, sourcePath);
11663
- if (!fs44.existsSync(absPath)) {
11816
+ const absPath = path36.resolve(dir, sourcePath);
11817
+ if (!fs45.existsSync(absPath)) {
11664
11818
  console.log(chalk25.red(`
11665
11819
  Path not found: ${sourcePath}
11666
11820
  `));
@@ -11675,7 +11829,7 @@ async function sourcesAddCommand(sourcePath) {
11675
11829
  }
11676
11830
  const existing = loadSourcesConfig(dir);
11677
11831
  const alreadyConfigured = existing.some(
11678
- (s) => s.path && path35.resolve(dir, s.path) === absPath
11832
+ (s) => s.path && path36.resolve(dir, s.path) === absPath
11679
11833
  );
11680
11834
  if (alreadyConfigured) {
11681
11835
  console.log(chalk25.yellow(`
@@ -11723,8 +11877,8 @@ async function sourcesRemoveCommand(name) {
11723
11877
  }
11724
11878
 
11725
11879
  // src/commands/publish.ts
11726
- import fs45 from "fs";
11727
- import path36 from "path";
11880
+ import fs46 from "fs";
11881
+ import path37 from "path";
11728
11882
  import chalk26 from "chalk";
11729
11883
  import ora7 from "ora";
11730
11884
  init_config();
@@ -11738,10 +11892,10 @@ async function publishCommand() {
11738
11892
  const spinner = ora7("Generating project summary...").start();
11739
11893
  try {
11740
11894
  const fingerprint = await collectFingerprint(dir);
11741
- const claudeMd = readFileOrNull(path36.join(dir, "CLAUDE.md"));
11895
+ const claudeMd = readFileOrNull(path37.join(dir, "CLAUDE.md"));
11742
11896
  const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
11743
11897
  const summary = {
11744
- name: fingerprint.packageName || path36.basename(dir),
11898
+ name: fingerprint.packageName || path37.basename(dir),
11745
11899
  version: "1.0.0",
11746
11900
  description: fingerprint.description || "",
11747
11901
  languages: fingerprint.languages,
@@ -11753,7 +11907,7 @@ async function publishCommand() {
11753
11907
  summary.conventions = claudeMd.slice(0, 2e3);
11754
11908
  }
11755
11909
  try {
11756
- const pkgContent = readFileOrNull(path36.join(dir, "package.json"));
11910
+ const pkgContent = readFileOrNull(path37.join(dir, "package.json"));
11757
11911
  if (pkgContent) {
11758
11912
  const pkg3 = JSON.parse(pkgContent);
11759
11913
  if (pkg3.scripts) {
@@ -11766,14 +11920,14 @@ async function publishCommand() {
11766
11920
  }
11767
11921
  } catch {
11768
11922
  }
11769
- const outputDir = path36.join(dir, ".caliber");
11770
- if (!fs45.existsSync(outputDir)) {
11771
- fs45.mkdirSync(outputDir, { recursive: true });
11923
+ const outputDir = path37.join(dir, ".caliber");
11924
+ if (!fs46.existsSync(outputDir)) {
11925
+ fs46.mkdirSync(outputDir, { recursive: true });
11772
11926
  }
11773
- const outputPath = path36.join(outputDir, "summary.json");
11774
- fs45.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
11927
+ const outputPath = path37.join(outputDir, "summary.json");
11928
+ fs46.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
11775
11929
  spinner.succeed("Project summary published");
11776
- console.log(` ${chalk26.green("\u2713")} ${path36.relative(dir, outputPath)}`);
11930
+ console.log(` ${chalk26.green("\u2713")} ${path37.relative(dir, outputPath)}`);
11777
11931
  console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
11778
11932
  console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
11779
11933
  } catch (err) {
@@ -11785,13 +11939,13 @@ async function publishCommand() {
11785
11939
  }
11786
11940
 
11787
11941
  // src/cli.ts
11788
- var __dirname = path37.dirname(fileURLToPath(import.meta.url));
11942
+ var __dirname = path38.dirname(fileURLToPath(import.meta.url));
11789
11943
  var pkg = JSON.parse(
11790
- fs46.readFileSync(path37.resolve(__dirname, "..", "package.json"), "utf-8")
11944
+ fs47.readFileSync(path38.resolve(__dirname, "..", "package.json"), "utf-8")
11791
11945
  );
11792
11946
  var program = new Command();
11793
11947
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
11794
- program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion).option("--no-traces", "Disable anonymous telemetry for this run");
11948
+ program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI context infrastructure for coding agents").version(displayVersion).option("--no-traces", "Disable anonymous telemetry for this run");
11795
11949
  function tracked(commandName, handler) {
11796
11950
  const wrapper = async (...args) => {
11797
11951
  const start = Date.now();
@@ -11848,13 +12002,13 @@ function parseAgentOption(value) {
11848
12002
  }
11849
12003
  return agents;
11850
12004
  }
11851
- 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 setup 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));
12005
+ 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));
11852
12006
  program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
11853
- program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
11854
- program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
12007
+ program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
12008
+ 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));
11855
12009
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
11856
12010
  program.command("skills").description("Discover and install community skills for your project").option("--query <terms>", 'Search for skills by topic (e.g. "react frontend")').option("--install <slugs>", "Install specific skills by slug (comma-separated)").action(tracked("skills", recommendCommand));
11857
- program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, github-copilot", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
12011
+ program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, github-copilot", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
11858
12012
  program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(tracked("refresh", refreshCommand));
11859
12013
  program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(tracked("hooks", hooksCommand));
11860
12014
  program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
@@ -11863,7 +12017,7 @@ sources.command("list").description("Show configured and auto-detected sources")
11863
12017
  sources.command("add").description("Add an external source").argument("<path>", "Path to repo directory or file").action(tracked("sources:add", sourcesAddCommand));
11864
12018
  sources.command("remove").description("Remove a configured source").argument("<name>", "Source path or role to remove").action(tracked("sources:remove", sourcesRemoveCommand));
11865
12019
  program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
11866
- var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
12020
+ var learn = program.command("learn").description("Manage session learning \u2014 extract patterns from your AI coding sessions");
11867
12021
  learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").option("--prompt", "Record a user prompt event").action(tracked("learn:observe", learnObserveCommand));
11868
12022
  learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
11869
12023
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
@@ -11874,16 +12028,16 @@ learn.command("delete <index>").description("Delete a learning by its index numb
11874
12028
  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));
11875
12029
 
11876
12030
  // src/utils/version-check.ts
11877
- import fs47 from "fs";
11878
- import path38 from "path";
12031
+ import fs48 from "fs";
12032
+ import path39 from "path";
11879
12033
  import { fileURLToPath as fileURLToPath2 } from "url";
11880
12034
  import { execSync as execSync16 } from "child_process";
11881
12035
  import chalk27 from "chalk";
11882
12036
  import ora8 from "ora";
11883
12037
  import confirm2 from "@inquirer/confirm";
11884
- var __dirname_vc = path38.dirname(fileURLToPath2(import.meta.url));
12038
+ var __dirname_vc = path39.dirname(fileURLToPath2(import.meta.url));
11885
12039
  var pkg2 = JSON.parse(
11886
- fs47.readFileSync(path38.resolve(__dirname_vc, "..", "package.json"), "utf-8")
12040
+ fs48.readFileSync(path39.resolve(__dirname_vc, "..", "package.json"), "utf-8")
11887
12041
  );
11888
12042
  function getChannel(version) {
11889
12043
  const match = version.match(/-(dev|next)\./);
@@ -11908,8 +12062,8 @@ function isNewer(registry, current) {
11908
12062
  function getInstalledVersion() {
11909
12063
  try {
11910
12064
  const globalRoot = execSync16("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
11911
- const pkgPath = path38.join(globalRoot, "@rely-ai", "caliber", "package.json");
11912
- return JSON.parse(fs47.readFileSync(pkgPath, "utf-8")).version;
12065
+ const pkgPath = path39.join(globalRoot, "@rely-ai", "caliber", "package.json");
12066
+ return JSON.parse(fs48.readFileSync(pkgPath, "utf-8")).version;
11913
12067
  } catch {
11914
12068
  return null;
11915
12069
  }