@rely-ai/caliber 1.30.0-dev.1774189509 → 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 +523 -380
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -181,7 +181,7 @@ __export(resolve_caliber_exports, {
181
181
  resolveCaliber: () => resolveCaliber
182
182
  });
183
183
  import fs20 from "fs";
184
- import { execSync as execSync6 } from "child_process";
184
+ import { execSync as execSync7 } from "child_process";
185
185
  function resolveCaliber() {
186
186
  if (_resolved) return _resolved;
187
187
  const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
@@ -191,14 +191,12 @@ function resolveCaliber() {
191
191
  }
192
192
  try {
193
193
  const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
194
- const found = execSync6(whichCmd, {
194
+ execSync7(whichCmd, {
195
195
  encoding: "utf-8",
196
196
  stdio: ["pipe", "pipe", "pipe"]
197
- }).trim();
198
- if (found) {
199
- _resolved = found;
200
- return _resolved;
201
- }
197
+ });
198
+ _resolved = "caliber";
199
+ return _resolved;
202
200
  } catch {
203
201
  }
204
202
  const binPath = process.argv[1];
@@ -225,7 +223,7 @@ var init_resolve_caliber = __esm({
225
223
  });
226
224
 
227
225
  // src/utils/editor.ts
228
- import { execSync as execSync12, spawn as spawn3 } from "child_process";
226
+ import { execSync as execSync13, spawn as spawn3 } from "child_process";
229
227
  import fs26 from "fs";
230
228
  import path22 from "path";
231
229
  import os6 from "os";
@@ -238,7 +236,7 @@ function getEmptyFilePath(proposedPath) {
238
236
  function commandExists(cmd) {
239
237
  try {
240
238
  const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
241
- execSync12(check, { stdio: "ignore" });
239
+ execSync13(check, { stdio: "ignore" });
242
240
  return true;
243
241
  } catch {
244
242
  return false;
@@ -497,13 +495,13 @@ __export(lock_exports, {
497
495
  isCaliberRunning: () => isCaliberRunning,
498
496
  releaseLock: () => releaseLock
499
497
  });
500
- import fs36 from "fs";
501
- import path28 from "path";
498
+ import fs37 from "fs";
499
+ import path29 from "path";
502
500
  import os8 from "os";
503
501
  function isCaliberRunning() {
504
502
  try {
505
- if (!fs36.existsSync(LOCK_FILE)) return false;
506
- 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();
507
505
  const { pid, ts } = JSON.parse(raw);
508
506
  if (Date.now() - ts > STALE_MS) return false;
509
507
  try {
@@ -518,13 +516,13 @@ function isCaliberRunning() {
518
516
  }
519
517
  function acquireLock() {
520
518
  try {
521
- 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() }));
522
520
  } catch {
523
521
  }
524
522
  }
525
523
  function releaseLock() {
526
524
  try {
527
- if (fs36.existsSync(LOCK_FILE)) fs36.unlinkSync(LOCK_FILE);
525
+ if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
528
526
  } catch {
529
527
  }
530
528
  }
@@ -532,21 +530,21 @@ var LOCK_FILE, STALE_MS;
532
530
  var init_lock = __esm({
533
531
  "src/lib/lock.ts"() {
534
532
  "use strict";
535
- LOCK_FILE = path28.join(os8.tmpdir(), ".caliber.lock");
533
+ LOCK_FILE = path29.join(os8.tmpdir(), ".caliber.lock");
536
534
  STALE_MS = 10 * 60 * 1e3;
537
535
  }
538
536
  });
539
537
 
540
538
  // src/cli.ts
541
539
  import { Command } from "commander";
542
- import fs46 from "fs";
543
- import path37 from "path";
540
+ import fs47 from "fs";
541
+ import path38 from "path";
544
542
  import { fileURLToPath } from "url";
545
543
 
546
544
  // src/commands/init.ts
547
- import path24 from "path";
545
+ import path25 from "path";
548
546
  import chalk14 from "chalk";
549
- import fs31 from "fs";
547
+ import fs32 from "fs";
550
548
 
551
549
  // src/fingerprint/index.ts
552
550
  import fs7 from "fs";
@@ -664,11 +662,26 @@ import path3 from "path";
664
662
  // src/constants.ts
665
663
  import path2 from "path";
666
664
  import os from "os";
665
+ import { execSync as execSync2 } from "child_process";
667
666
  var AUTH_DIR = path2.join(os.homedir(), ".caliber");
668
667
  var CALIBER_DIR = ".caliber";
669
668
  var MANIFEST_FILE = path2.join(CALIBER_DIR, "manifest.json");
670
669
  var BACKUPS_DIR = path2.join(CALIBER_DIR, "backups");
671
- var LEARNING_DIR = path2.join(CALIBER_DIR, "learning");
670
+ var _learningDirCache = null;
671
+ function getLearningDir() {
672
+ if (_learningDirCache) return _learningDirCache;
673
+ try {
674
+ const gitCommonDir = execSync2("git rev-parse --git-common-dir", {
675
+ encoding: "utf-8",
676
+ stdio: ["pipe", "pipe", "pipe"]
677
+ }).trim();
678
+ const mainRoot = path2.dirname(path2.resolve(gitCommonDir));
679
+ _learningDirCache = path2.join(mainRoot, CALIBER_DIR, "learning");
680
+ } catch {
681
+ _learningDirCache = path2.join(CALIBER_DIR, "learning");
682
+ }
683
+ return _learningDirCache;
684
+ }
672
685
  var LEARNING_SESSION_FILE = "current-session.jsonl";
673
686
  var LEARNING_STATE_FILE = "state.json";
674
687
  var LEARNING_MAX_EVENTS = 500;
@@ -781,7 +794,7 @@ function readExistingConfigs(dir) {
781
794
  // src/fingerprint/code-analysis.ts
782
795
  import fs3 from "fs";
783
796
  import path4 from "path";
784
- import { execSync as execSync2 } from "child_process";
797
+ import { execSync as execSync3 } from "child_process";
785
798
  var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
786
799
  "node_modules",
787
800
  ".git",
@@ -1128,7 +1141,7 @@ function buildImportCounts(files) {
1128
1141
  function getGitFrequency(dir) {
1129
1142
  const freq = /* @__PURE__ */ new Map();
1130
1143
  try {
1131
- const output = execSync2(
1144
+ const output = execSync3(
1132
1145
  'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
1133
1146
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
1134
1147
  );
@@ -1577,7 +1590,7 @@ var OpenAICompatProvider = class {
1577
1590
  };
1578
1591
 
1579
1592
  // src/llm/cursor-acp.ts
1580
- import { spawn, execSync as execSync3 } from "child_process";
1593
+ import { spawn, execSync as execSync4 } from "child_process";
1581
1594
  import os3 from "os";
1582
1595
 
1583
1596
  // src/llm/seat-based-errors.ts
@@ -1853,7 +1866,7 @@ var CursorAcpProvider = class {
1853
1866
  function isCursorAgentAvailable() {
1854
1867
  try {
1855
1868
  const cmd = IS_WINDOWS ? `where ${AGENT_BIN}` : `which ${AGENT_BIN}`;
1856
- execSync3(cmd, { stdio: "ignore" });
1869
+ execSync4(cmd, { stdio: "ignore" });
1857
1870
  return true;
1858
1871
  } catch {
1859
1872
  return false;
@@ -1861,7 +1874,7 @@ function isCursorAgentAvailable() {
1861
1874
  }
1862
1875
  function isCursorLoggedIn() {
1863
1876
  try {
1864
- const result = execSync3(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
1877
+ const result = execSync4(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
1865
1878
  return !result.toString().includes("not logged in");
1866
1879
  } catch {
1867
1880
  return false;
@@ -1869,7 +1882,7 @@ function isCursorLoggedIn() {
1869
1882
  }
1870
1883
 
1871
1884
  // src/llm/claude-cli.ts
1872
- import { spawn as spawn2, execSync as execSync4 } from "child_process";
1885
+ import { spawn as spawn2, execSync as execSync5 } from "child_process";
1873
1886
  var CLAUDE_CLI_BIN = "claude";
1874
1887
  var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
1875
1888
  var IS_WINDOWS2 = process.platform === "win32";
@@ -2004,7 +2017,7 @@ var ClaudeCliProvider = class {
2004
2017
  function isClaudeCliAvailable() {
2005
2018
  try {
2006
2019
  const cmd = process.platform === "win32" ? `where ${CLAUDE_CLI_BIN}` : `which ${CLAUDE_CLI_BIN}`;
2007
- execSync4(cmd, { stdio: "ignore" });
2020
+ execSync5(cmd, { stdio: "ignore" });
2008
2021
  return true;
2009
2022
  } catch {
2010
2023
  return false;
@@ -2294,7 +2307,7 @@ init_config();
2294
2307
  var ROLE_AND_CONTEXT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, Codex, and GitHub Copilot).
2295
2308
 
2296
2309
  Your job depends on context:
2297
- - 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.
2298
2311
  - If existing configs are provided \u2192 audit them and suggest targeted improvements. Preserve accurate content \u2014 don't rewrite what's already correct.`;
2299
2312
  var CONFIG_FILE_TYPES = `You understand these config files:
2300
2313
  - CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
@@ -2307,6 +2320,12 @@ var CONFIG_FILE_TYPES = `You understand these config files:
2307
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.
2308
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.`;
2309
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?`;
2310
2329
  var OUTPUT_FORMAT = `Your output MUST follow this exact format (no markdown fences):
2311
2330
 
2312
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.
@@ -2370,7 +2389,10 @@ Accuracy (15 pts) \u2014 CRITICAL:
2370
2389
 
2371
2390
  Safety: Never include API keys, tokens, or credentials in config files.
2372
2391
 
2373
- 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.`;
2374
2396
  var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
2375
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.
2376
2398
 
@@ -2381,26 +2403,12 @@ For command sections, use code blocks with one command per line.
2381
2403
 
2382
2404
  - Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
2383
2405
  - Cursor rules: max 5 .mdc files.
2384
- - If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.
2385
-
2386
- CONVENTIONS SECTION PRIORITY \u2014 this is the highest-value section for agent performance:
2387
- The Conventions section determines whether agents choose correct, project-consistent solutions vs technically clever but fragile ones. Prioritize:
2388
- 1. Error handling patterns \u2014 does the project use try/except, Result types, or assertions? Show the actual pattern with a code example.
2389
- 2. Backward compatibility approach \u2014 does the project prefer duck typing (try/except TypeError) over introspection (inspect.signature)? Defensive patterns over clever ones?
2390
- 3. Testing infrastructure \u2014 test runner, key fixtures, how to add a new test. Agents that understand the test infra write tests that pass.
2391
- 4. "Prefer X over Y" guidelines \u2014 for common trade-offs in this codebase (e.g. "prefer composition over inheritance", "prefer simple try/except over type checking").
2392
- 5. Anti-patterns \u2014 2-3 specific things NOT to do in this codebase. These prevent agents from choosing overengineered solutions.
2393
- Keep this section tight \u2014 bullet points, not prose. Each line should be a decision rule an agent can act on.`;
2406
+ - If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.`;
2394
2407
  var GENERATION_SYSTEM_PROMPT = `${ROLE_AND_CONTEXT}
2395
2408
 
2396
2409
  ${CONFIG_FILE_TYPES}
2397
2410
 
2398
- Audit checklist (when existing configs are provided):
2399
- 1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
2400
- 2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
2401
- 3. Duplicate or overlapping skills \u2014 can any be merged or removed?
2402
- 4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
2403
- 5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?
2411
+ ${AUDIT_CHECKLIST}
2404
2412
 
2405
2413
  ${EXCLUSIONS}
2406
2414
 
@@ -2445,6 +2453,8 @@ var CORE_GENERATION_PROMPT = `${ROLE_AND_CONTEXT}
2445
2453
 
2446
2454
  ${CONFIG_FILE_TYPES}
2447
2455
 
2456
+ ${AUDIT_CHECKLIST}
2457
+
2448
2458
  ${EXCLUSIONS}
2449
2459
 
2450
2460
  ${OUTPUT_FORMAT}
@@ -2464,10 +2474,10 @@ CoreSetup schema:
2464
2474
  },
2465
2475
  "codex": {
2466
2476
  "agentsMd": "string (markdown content for AGENTS.md)",
2467
- "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)" }]
2468
2478
  },
2469
2479
  "cursor": {
2470
- "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)" }],
2471
2481
  "rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
2472
2482
  },
2473
2483
  "copilot": {
@@ -2509,8 +2519,7 @@ Structure:
2509
2519
  - Have a validation gate: "Verify X before proceeding to the next step"
2510
2520
  - Specify dependencies: "This step uses the output from Step N"
2511
2521
  4. "## Examples" \u2014 at least one example showing: User says \u2192 Actions taken \u2192 Result. The example should mirror how existing code in the project is structured.
2512
- 5. "## Anti-patterns" (required) \u2014 2-3 specific approaches to AVOID, with the correct alternative. Focus on cases where the "clever" solution breaks things. Example: "Do NOT use inspect.signature() to check function parameters \u2014 use try/except TypeError instead. The introspection approach breaks with decorated functions and functools.partial."
2513
- 6. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
2522
+ 5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
2514
2523
 
2515
2524
  Rules:
2516
2525
  - Max 150 lines. Focus on actionable instructions, not documentation prose.
@@ -2520,6 +2529,7 @@ Rules:
2520
2529
  - Reference actual commands, paths, and packages from the project context provided.
2521
2530
  - Do NOT include YAML frontmatter \u2014 it will be generated separately.
2522
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.
2523
2533
 
2524
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.
2525
2535
 
@@ -2566,32 +2576,40 @@ Rules:
2566
2576
  - Update the "fileDescriptions" to reflect any changes you make.
2567
2577
 
2568
2578
  Quality constraints \u2014 your changes are scored, so do not break these:
2569
- - 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.
2570
2580
  - Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
2571
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.
2572
2583
  - Use backticks for every file path, command, and identifier.
2573
2584
  - Keep skill content under 150 lines, focused on actionable instructions.
2574
2585
  - Only reference file paths that actually exist in the project.`;
2575
- 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).
2576
2587
 
2577
2588
  You will receive:
2578
2589
  1. Git diffs showing what code changed
2579
- 2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules)
2580
- 3. Project context (languages, frameworks)
2581
-
2582
- Rules:
2583
- - Only update docs where the diffs clearly warrant a change
2584
- - Preserve existing style, tone, structure, and formatting
2585
- - Be conservative \u2014 don't rewrite sections that aren't affected by the changes
2586
- - 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:
2587
2610
  - Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
2588
- - 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
2589
2612
  - Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
2590
- - If a doc doesn't need updating, return null for it
2591
- - For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them. Keep under 150 lines.
2592
- - For README.md: update setup instructions, API docs, or feature descriptions if affected
2593
- - Only reference file paths that exist in the project
2594
- - Use backticks for all file paths, commands, and identifiers
2595
2613
 
2596
2614
  Return a JSON object with this exact shape:
2597
2615
  {
@@ -2615,15 +2633,8 @@ You receive a chronological sequence of events from a Claude Code session. Most
2615
2633
 
2616
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.
2617
2635
 
2618
- Look for:
2619
-
2620
- 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.
2621
- 2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
2622
- 3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
2623
- 4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
2624
- 5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
2625
- 6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
2626
- 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.
2627
2638
 
2628
2639
  DO NOT extract:
2629
2640
  - Descriptions of what the code does or how features work (e.g. "compression removes comments" or "skeleton extraction creates outlines")
@@ -2634,7 +2645,15 @@ DO NOT extract:
2634
2645
  - **Session-specific file paths, worktree locations, or branch names** \u2014 these are ephemeral and won't apply to future sessions
2635
2646
  - **Implementation details of a feature being built** \u2014 the learning should be about HOW to work in this project, not WHAT was built
2636
2647
 
2637
- 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.
2638
2657
 
2639
2658
  From these observations, produce:
2640
2659
 
@@ -2713,6 +2732,10 @@ Be thorough \u2014 reason from:
2713
2732
  - File extensions and their frequency distribution
2714
2733
  - Directory structure and naming conventions
2715
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)
2716
2739
  - Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
2717
2740
  - CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
2718
2741
 
@@ -2754,7 +2777,7 @@ init_config();
2754
2777
  import fs6 from "fs";
2755
2778
  import path6 from "path";
2756
2779
  import crypto from "crypto";
2757
- import { execSync as execSync5 } from "child_process";
2780
+ import { execSync as execSync6 } from "child_process";
2758
2781
  var CACHE_VERSION = 1;
2759
2782
  var CACHE_DIR = ".caliber/cache";
2760
2783
  var CACHE_FILE = "fingerprint.json";
@@ -2763,7 +2786,7 @@ function getCachePath(dir) {
2763
2786
  }
2764
2787
  function getGitHead(dir) {
2765
2788
  try {
2766
- return execSync5("git rev-parse HEAD", {
2789
+ return execSync6("git rev-parse HEAD", {
2767
2790
  cwd: dir,
2768
2791
  encoding: "utf-8",
2769
2792
  stdio: ["pipe", "pipe", "pipe"],
@@ -2775,7 +2798,7 @@ function getGitHead(dir) {
2775
2798
  }
2776
2799
  function getDirtySignature(dir) {
2777
2800
  try {
2778
- const output = execSync5("git diff --name-only HEAD", {
2801
+ const output = execSync6("git diff --name-only HEAD", {
2779
2802
  cwd: dir,
2780
2803
  encoding: "utf-8",
2781
2804
  stdio: ["pipe", "pipe", "pipe"],
@@ -3613,15 +3636,15 @@ init_config();
3613
3636
  // src/utils/dependencies.ts
3614
3637
  import { readFileSync as readFileSync2 } from "fs";
3615
3638
  import { join as join2 } from "path";
3616
- function readFileOrNull2(path39) {
3639
+ function readFileOrNull2(path40) {
3617
3640
  try {
3618
- return readFileSync2(path39, "utf-8");
3641
+ return readFileSync2(path40, "utf-8");
3619
3642
  } catch {
3620
3643
  return null;
3621
3644
  }
3622
3645
  }
3623
- function readJsonOrNull(path39) {
3624
- const content = readFileOrNull2(path39);
3646
+ function readJsonOrNull(path40) {
3647
+ const content = readFileOrNull2(path40);
3625
3648
  if (!content) return null;
3626
3649
  try {
3627
3650
  return JSON.parse(content);
@@ -4274,38 +4297,11 @@ function appendLearningsBlock(content) {
4274
4297
  function getCursorLearningsRule() {
4275
4298
  return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
4276
4299
  }
4277
- var SKILLS_HEADING = "## Available Skills";
4278
- function truncateDescription(description) {
4279
- const match = description.match(/^[^.]*\.\s/);
4280
- if (match) return match[0].trim();
4281
- if (description.length > 80) return description.slice(0, 77) + "...";
4282
- return description;
4283
- }
4284
- function appendSkillListing(content, skills, pathPrefix) {
4285
- if (skills.length === 0) return content;
4286
- if (content.includes(SKILLS_HEADING)) return content;
4287
- const lines = [
4288
- "",
4289
- SKILLS_HEADING,
4290
- "",
4291
- "The following skill files are available in this repo. Read them when working on related areas:",
4292
- ""
4293
- ];
4294
- for (const skill of skills) {
4295
- const shortDesc = truncateDescription(skill.description);
4296
- lines.push(`- \`${pathPrefix}${skill.name}/SKILL.md\` \u2014 ${shortDesc}`);
4297
- }
4298
- return content.trimEnd() + "\n" + lines.join("\n") + "\n";
4299
- }
4300
4300
 
4301
4301
  // src/writers/claude/index.ts
4302
4302
  function writeClaudeConfig(config) {
4303
4303
  const written = [];
4304
- let claudeMd = config.claudeMd;
4305
- if (config.skills?.length) {
4306
- claudeMd = appendSkillListing(claudeMd, config.skills, ".claude/skills/");
4307
- }
4308
- fs10.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(claudeMd)));
4304
+ fs10.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
4309
4305
  written.push("CLAUDE.md");
4310
4306
  if (config.skills?.length) {
4311
4307
  for (const skill of config.skills) {
@@ -4398,11 +4394,7 @@ import fs12 from "fs";
4398
4394
  import path12 from "path";
4399
4395
  function writeCodexConfig(config) {
4400
4396
  const written = [];
4401
- let agentsMd = config.agentsMd;
4402
- if (config.skills?.length) {
4403
- agentsMd = appendSkillListing(agentsMd, config.skills, ".agents/skills/");
4404
- }
4405
- fs12.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(agentsMd)));
4397
+ fs12.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
4406
4398
  written.push("AGENTS.md");
4407
4399
  if (config.skills?.length) {
4408
4400
  for (const skill of config.skills) {
@@ -5032,7 +5024,7 @@ function removeLearningHooks() {
5032
5024
  // src/lib/state.ts
5033
5025
  import fs22 from "fs";
5034
5026
  import path18 from "path";
5035
- import { execSync as execSync7 } from "child_process";
5027
+ import { execSync as execSync8 } from "child_process";
5036
5028
  var STATE_FILE = path18.join(CALIBER_DIR, ".caliber-state.json");
5037
5029
  function normalizeTargetAgent(value) {
5038
5030
  if (Array.isArray(value)) return value;
@@ -5060,7 +5052,7 @@ function writeState(state) {
5060
5052
  }
5061
5053
  function getCurrentHeadSha() {
5062
5054
  try {
5063
- return execSync7("git rev-parse HEAD", {
5055
+ return execSync8("git rev-parse HEAD", {
5064
5056
  encoding: "utf-8",
5065
5057
  stdio: ["pipe", "pipe", "pipe"]
5066
5058
  }).trim();
@@ -5673,7 +5665,7 @@ function checkGrounding(dir) {
5673
5665
 
5674
5666
  // src/scoring/checks/accuracy.ts
5675
5667
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
5676
- import { execSync as execSync8 } from "child_process";
5668
+ import { execSync as execSync9 } from "child_process";
5677
5669
  import { join as join5 } from "path";
5678
5670
  function validateReferences(dir) {
5679
5671
  const configContent = collectPrimaryConfigContent(dir);
@@ -5682,13 +5674,13 @@ function validateReferences(dir) {
5682
5674
  }
5683
5675
  function detectGitDrift(dir) {
5684
5676
  try {
5685
- execSync8("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
5677
+ execSync9("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
5686
5678
  } catch {
5687
5679
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
5688
5680
  }
5689
5681
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
5690
5682
  try {
5691
- const headTimestamp = execSync8(
5683
+ const headTimestamp = execSync9(
5692
5684
  "git log -1 --format=%ct HEAD",
5693
5685
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5694
5686
  ).trim();
@@ -5709,7 +5701,7 @@ function detectGitDrift(dir) {
5709
5701
  let latestConfigCommitHash = null;
5710
5702
  for (const file of configFiles) {
5711
5703
  try {
5712
- const hash = execSync8(
5704
+ const hash = execSync9(
5713
5705
  `git log -1 --format=%H -- "${file}"`,
5714
5706
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5715
5707
  ).trim();
@@ -5718,7 +5710,7 @@ function detectGitDrift(dir) {
5718
5710
  latestConfigCommitHash = hash;
5719
5711
  } else {
5720
5712
  try {
5721
- execSync8(
5713
+ execSync9(
5722
5714
  `git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
5723
5715
  { cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
5724
5716
  );
@@ -5733,12 +5725,12 @@ function detectGitDrift(dir) {
5733
5725
  return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
5734
5726
  }
5735
5727
  try {
5736
- const countStr = execSync8(
5728
+ const countStr = execSync9(
5737
5729
  `git rev-list --count ${latestConfigCommitHash}..HEAD`,
5738
5730
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5739
5731
  ).trim();
5740
5732
  const commitsSince = parseInt(countStr, 10) || 0;
5741
- const lastDate = execSync8(
5733
+ const lastDate = execSync9(
5742
5734
  `git log -1 --format=%ci ${latestConfigCommitHash}`,
5743
5735
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5744
5736
  ).trim();
@@ -5810,12 +5802,12 @@ function checkAccuracy(dir) {
5810
5802
 
5811
5803
  // src/scoring/checks/freshness.ts
5812
5804
  import { existsSync as existsSync5, statSync as statSync3 } from "fs";
5813
- import { execSync as execSync9 } from "child_process";
5805
+ import { execSync as execSync10 } from "child_process";
5814
5806
  import { join as join6 } from "path";
5815
5807
  function getCommitsSinceConfigUpdate(dir) {
5816
5808
  const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
5817
5809
  try {
5818
- const headTimestamp = execSync9(
5810
+ const headTimestamp = execSync10(
5819
5811
  "git log -1 --format=%ct HEAD",
5820
5812
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5821
5813
  ).trim();
@@ -5835,12 +5827,12 @@ function getCommitsSinceConfigUpdate(dir) {
5835
5827
  }
5836
5828
  for (const file of configFiles) {
5837
5829
  try {
5838
- const hash = execSync9(
5830
+ const hash = execSync10(
5839
5831
  `git log -1 --format=%H -- "${file}"`,
5840
5832
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5841
5833
  ).trim();
5842
5834
  if (hash) {
5843
- const countStr = execSync9(
5835
+ const countStr = execSync10(
5844
5836
  `git rev-list --count ${hash}..HEAD`,
5845
5837
  { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5846
5838
  ).trim();
@@ -5958,11 +5950,11 @@ function checkFreshness(dir) {
5958
5950
 
5959
5951
  // src/scoring/checks/bonus.ts
5960
5952
  import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
5961
- import { execSync as execSync10 } from "child_process";
5953
+ import { execSync as execSync11 } from "child_process";
5962
5954
  import { join as join7 } from "path";
5963
5955
  function hasPreCommitHook(dir) {
5964
5956
  try {
5965
- const gitDir = execSync10("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
5957
+ const gitDir = execSync11("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
5966
5958
  const hookPath = join7(gitDir, "hooks", "pre-commit");
5967
5959
  const content = readFileOrNull(hookPath);
5968
5960
  return content ? content.includes("caliber") : false;
@@ -6209,7 +6201,7 @@ var AGENT_DISPLAY_NAMES = {
6209
6201
  codex: "Codex"
6210
6202
  };
6211
6203
  var CATEGORY_LABELS = {
6212
- existence: { icon: "\u{1F4C1}", label: "FILES & SETUP" },
6204
+ existence: { icon: "\u{1F4C1}", label: "FILES & CONFIG" },
6213
6205
  quality: { icon: "\u26A1", label: "QUALITY" },
6214
6206
  grounding: { icon: "\u{1F3AF}", label: "GROUNDING" },
6215
6207
  accuracy: { icon: "\u{1F50D}", label: "ACCURACY" },
@@ -6391,7 +6383,7 @@ import fs24 from "fs";
6391
6383
  import path20 from "path";
6392
6384
  import os5 from "os";
6393
6385
  import crypto4 from "crypto";
6394
- import { execSync as execSync11 } from "child_process";
6386
+ import { execSync as execSync12 } from "child_process";
6395
6387
  var CONFIG_DIR2 = path20.join(os5.homedir(), ".caliber");
6396
6388
  var CONFIG_FILE2 = path20.join(CONFIG_DIR2, "config.json");
6397
6389
  var runtimeDisabled = false;
@@ -6418,7 +6410,7 @@ function getMachineId() {
6418
6410
  }
6419
6411
  function getGitEmailHash() {
6420
6412
  try {
6421
- const email = execSync11("git config user.email", { encoding: "utf-8" }).trim();
6413
+ const email = execSync12("git config user.email", { encoding: "utf-8" }).trim();
6422
6414
  if (!email) return void 0;
6423
6415
  return crypto4.createHash("sha256").update(email).digest("hex");
6424
6416
  } catch {
@@ -7468,11 +7460,11 @@ function countIssuePoints(issues) {
7468
7460
  }
7469
7461
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7470
7462
  const existsCache = /* @__PURE__ */ new Map();
7471
- const cachedExists = (path39) => {
7472
- const cached = existsCache.get(path39);
7463
+ const cachedExists = (path40) => {
7464
+ const cached = existsCache.get(path40);
7473
7465
  if (cached !== void 0) return cached;
7474
- const result = existsSync9(path39);
7475
- existsCache.set(path39, result);
7466
+ const result = existsSync9(path40);
7467
+ existsCache.set(path40, result);
7476
7468
  return result;
7477
7469
  };
7478
7470
  const projectStructure = collectProjectStructure(dir);
@@ -7487,7 +7479,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7487
7479
  bestLostPoints = lostPoints;
7488
7480
  }
7489
7481
  if (lostPoints === 0) {
7490
- if (callbacks?.onStatus) callbacks.onStatus("Setup passes all scoring checks");
7482
+ if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
7491
7483
  return bestSetup;
7492
7484
  }
7493
7485
  const pointIssues = issues.filter((i) => i.pointsLost > 0);
@@ -7497,7 +7489,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
7497
7489
  }
7498
7490
  const refined = await applyTargetedFixes(currentSetup, issues);
7499
7491
  if (!refined) {
7500
- if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current setup");
7492
+ if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current config");
7501
7493
  return bestSetup;
7502
7494
  }
7503
7495
  sessionHistory.push({
@@ -7590,7 +7582,7 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
7590
7582
  }
7591
7583
  }
7592
7584
  async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
7593
- const spinner = ora2("Validating setup against scoring criteria...").start();
7585
+ const spinner = ora2("Validating config against scoring criteria...").start();
7594
7586
  try {
7595
7587
  const refined = await scoreAndRefine(setup, dir, sessionHistory, {
7596
7588
  onStatus: (msg) => {
@@ -7598,9 +7590,9 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
7598
7590
  }
7599
7591
  });
7600
7592
  if (refined !== setup) {
7601
- spinner.succeed("Setup refined based on scoring feedback");
7593
+ spinner.succeed("Config refined based on scoring feedback");
7602
7594
  } else {
7603
- spinner.succeed("Setup passes scoring validation");
7595
+ spinner.succeed("Config passes scoring validation");
7604
7596
  }
7605
7597
  return refined;
7606
7598
  } catch (err) {
@@ -8155,7 +8147,7 @@ var REFINE_MESSAGES = [
8155
8147
  "Rebalancing permissions and tool settings...",
8156
8148
  "Refining skills and workflows...",
8157
8149
  "Updating rules to match your preferences...",
8158
- "Finalizing the revised setup..."
8150
+ "Finalizing the revised config..."
8159
8151
  ];
8160
8152
  var SpinnerMessages = class {
8161
8153
  spinner;
@@ -8325,10 +8317,10 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
8325
8317
  if (!isValid) {
8326
8318
  console.log(chalk11.dim(" This doesn't look like a config change request."));
8327
8319
  console.log(chalk11.dim(" Describe what to add, remove, or modify in your configs."));
8328
- 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'));
8329
8321
  continue;
8330
8322
  }
8331
- const refineSpinner = ora3("Refining setup...").start();
8323
+ const refineSpinner = ora3("Refining config...").start();
8332
8324
  const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
8333
8325
  refineMessages.start();
8334
8326
  const refined = await refineSetup(setup, message, sessionHistory);
@@ -8340,12 +8332,12 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
8340
8332
  role: "assistant",
8341
8333
  content: summarizeSetup2("Applied changes", refined)
8342
8334
  });
8343
- refineSpinner.succeed("Setup updated");
8335
+ refineSpinner.succeed("Config updated");
8344
8336
  printSummary(refined);
8345
8337
  console.log(chalk11.dim('Type "done" to accept, or describe more changes.'));
8346
8338
  } else {
8347
8339
  refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
8348
- 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.'));
8349
8341
  }
8350
8342
  }
8351
8343
  }
@@ -8394,7 +8386,7 @@ function printSetupSummary(setup) {
8394
8386
  const fileDescriptions = setup.fileDescriptions;
8395
8387
  const deletions = setup.deletions;
8396
8388
  console.log("");
8397
- console.log(chalk12.bold(" Your tailored setup:\n"));
8389
+ console.log(chalk12.bold(" Your tailored config:\n"));
8398
8390
  const getDescription = (filePath) => {
8399
8391
  return fileDescriptions?.[filePath];
8400
8392
  };
@@ -8623,6 +8615,7 @@ Only dismiss checks that truly don't apply. Examples:
8623
8615
  - "Build/test/lint commands" for a GitOps/Helm/Terraform/config repo with no build system
8624
8616
  - "Build/test/lint commands" for a repo with only YAML, HCL, or config files and no package.json/Makefile
8625
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
8626
8619
 
8627
8620
  Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
8628
8621
 
@@ -8635,7 +8628,7 @@ Top files: ${topFiles}
8635
8628
 
8636
8629
  Failing checks:
8637
8630
  ${JSON.stringify(checkList, null, 2)}`,
8638
- maxTokens: 300,
8631
+ maxTokens: 500,
8639
8632
  ...fastModel ? { model: fastModel } : {}
8640
8633
  });
8641
8634
  if (!Array.isArray(result.dismissed)) return [];
@@ -8645,6 +8638,68 @@ ${JSON.stringify(checkList, null, 2)}`,
8645
8638
  }
8646
8639
  }
8647
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
+
8648
8703
  // src/commands/init.ts
8649
8704
  function log(verbose, ...args) {
8650
8705
  if (verbose) console.log(chalk14.dim(` [verbose] ${args.map(String).join(" ")}`));
@@ -8665,7 +8720,7 @@ async function initCommand(options) {
8665
8720
  console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
8666
8721
  console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
8667
8722
  console.log(title.bold(" How it works:\n"));
8668
- 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"));
8669
8724
  console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
8670
8725
  console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
8671
8726
  console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
@@ -8678,7 +8733,7 @@ async function initCommand(options) {
8678
8733
  console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
8679
8734
  }
8680
8735
  const report = options.debugReport ? new DebugReport() : null;
8681
- console.log(title.bold(" Step 1/4 \u2014 Setup\n"));
8736
+ console.log(title.bold(" Step 1/4 \u2014 Connect\n"));
8682
8737
  let config = loadConfig();
8683
8738
  if (!config) {
8684
8739
  console.log(chalk14.dim(" No LLM provider configured yet.\n"));
@@ -8687,7 +8742,7 @@ async function initCommand(options) {
8687
8742
  });
8688
8743
  config = loadConfig();
8689
8744
  if (!config) {
8690
- console.log(chalk14.red(" Setup was cancelled or failed.\n"));
8745
+ console.log(chalk14.red(" Configuration cancelled or failed.\n"));
8691
8746
  throw new Error("__exit__");
8692
8747
  }
8693
8748
  console.log(chalk14.green(" \u2713 Provider saved\n"));
@@ -8698,7 +8753,7 @@ async function initCommand(options) {
8698
8753
  const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
8699
8754
  console.log(chalk14.dim(modelLine + "\n"));
8700
8755
  if (report) {
8701
- report.markStep("Provider setup");
8756
+ report.markStep("Provider connection");
8702
8757
  report.addSection("LLM Provider", `- **Provider**: ${config.provider}
8703
8758
  - **Model**: ${displayModel}
8704
8759
  - **Fast model**: ${fastModel || "none"}`);
@@ -8719,7 +8774,7 @@ async function initCommand(options) {
8719
8774
  `));
8720
8775
  trackInitAgentSelected(targetAgent, agentAutoDetected);
8721
8776
  let baselineScore = computeLocalScore(process.cwd(), targetAgent);
8722
- console.log(chalk14.dim("\n Current setup score:"));
8777
+ console.log(chalk14.dim("\n Current config score:"));
8723
8778
  displayScoreSummary(baselineScore);
8724
8779
  if (options.verbose) {
8725
8780
  for (const c of baselineScore.checks) {
@@ -8748,7 +8803,7 @@ async function initCommand(options) {
8748
8803
  const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
8749
8804
  if (hasExistingConfig && baselineScore.score === 100) {
8750
8805
  trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
8751
- 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"));
8752
8807
  console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber init --force") + chalk14.dim(" to regenerate anyway.\n"));
8753
8808
  if (!options.force) return;
8754
8809
  }
@@ -8785,7 +8840,7 @@ async function initCommand(options) {
8785
8840
  const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
8786
8841
  const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
8787
8842
  const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
8788
- const TASK_SCORE_REFINE = display.add("Validating & refining setup", { pipelineLabel: "Validate" });
8843
+ const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
8789
8844
  display.start();
8790
8845
  display.enableWaitingContent();
8791
8846
  try {
@@ -8933,7 +8988,7 @@ async function initCommand(options) {
8933
8988
  display.update(TASK_SCORE_REFINE, "done", "Skipped");
8934
8989
  }
8935
8990
  } else {
8936
- display.update(TASK_SCORE_REFINE, "failed", "No setup to validate");
8991
+ display.update(TASK_SCORE_REFINE, "failed", "No config to validate");
8937
8992
  }
8938
8993
  } catch (err) {
8939
8994
  display.stop();
@@ -8954,7 +9009,7 @@ async function initCommand(options) {
8954
9009
  Done in ${timeStr}
8955
9010
  `));
8956
9011
  if (!generatedSetup) {
8957
- console.log(chalk14.red(" Failed to generate setup."));
9012
+ console.log(chalk14.red(" Failed to generate config."));
8958
9013
  writeErrorLog(config, rawOutput, void 0, genStopReason);
8959
9014
  if (rawOutput) {
8960
9015
  console.log(chalk14.dim("\nRaw LLM output (JSON parse failed):"));
@@ -8964,7 +9019,7 @@ async function initCommand(options) {
8964
9019
  }
8965
9020
  if (report) {
8966
9021
  if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
8967
- report.addJson("Generation: Parsed Setup", generatedSetup);
9022
+ report.addJson("Generation: Parsed Config", generatedSetup);
8968
9023
  }
8969
9024
  log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
8970
9025
  console.log(title.bold(" Step 3/4 \u2014 Review\n"));
@@ -9027,7 +9082,7 @@ async function initCommand(options) {
9027
9082
  }
9028
9083
  cleanupStaging();
9029
9084
  if (action === "decline") {
9030
- console.log(chalk14.dim("Setup declined. No files were modified."));
9085
+ console.log(chalk14.dim("Declined. No files were modified."));
9031
9086
  return;
9032
9087
  }
9033
9088
  console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
@@ -9039,7 +9094,7 @@ async function initCommand(options) {
9039
9094
  const { default: ora9 } = await import("ora");
9040
9095
  const writeSpinner = ora9("Writing config files...").start();
9041
9096
  try {
9042
- if (targetAgent.includes("codex") && !fs31.existsSync("AGENTS.md") && !generatedSetup.codex) {
9097
+ if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
9043
9098
  const claude = generatedSetup.claude;
9044
9099
  const cursor = generatedSetup.cursor;
9045
9100
  const agentRefs = [];
@@ -9111,6 +9166,7 @@ ${agentRefs.join(" ")}
9111
9166
  |-------|--------|--------|-----|
9112
9167
  ` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
9113
9168
  }
9169
+ recordScore(afterScore, "init");
9114
9170
  displayScoreDelta(baselineScore, afterScore);
9115
9171
  if (options.verbose) {
9116
9172
  log(options.verbose, `Final score: ${afterScore.score}/100`);
@@ -9158,12 +9214,12 @@ ${agentRefs.join(" ")}
9158
9214
  if (targetAgent.includes("cursor")) installCursorLearningHooks();
9159
9215
  }
9160
9216
  }
9161
- console.log(chalk14.bold.green("\n Setup complete!"));
9217
+ console.log(chalk14.bold.green("\n Configuration complete!"));
9162
9218
  console.log(chalk14.dim(" Your AI agents now understand your project's architecture, build commands,"));
9163
9219
  console.log(chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n"));
9164
9220
  const done = chalk14.green("\u2713");
9165
9221
  const skip = chalk14.dim("\u2013");
9166
- console.log(chalk14.bold(" What was set up:\n"));
9222
+ console.log(chalk14.bold(" What was configured:\n"));
9167
9223
  console.log(` ${done} Config generated ${title("caliber score")} ${chalk14.dim("for full breakdown")}`);
9168
9224
  console.log(` ${done} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
9169
9225
  if (hasLearnableAgent) {
@@ -9188,9 +9244,9 @@ ${agentRefs.join(" ")}
9188
9244
  }
9189
9245
  if (report) {
9190
9246
  report.markStep("Finished");
9191
- const reportPath = path24.join(process.cwd(), ".caliber", "debug-report.md");
9247
+ const reportPath = path25.join(process.cwd(), ".caliber", "debug-report.md");
9192
9248
  report.write(reportPath);
9193
- 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)}
9194
9250
  `));
9195
9251
  }
9196
9252
  }
@@ -9199,7 +9255,7 @@ ${agentRefs.join(" ")}
9199
9255
  import chalk15 from "chalk";
9200
9256
  import ora4 from "ora";
9201
9257
  function undoCommand() {
9202
- const spinner = ora4("Reverting setup...").start();
9258
+ const spinner = ora4("Reverting config changes...").start();
9203
9259
  try {
9204
9260
  const { restored, removed } = undoSetup();
9205
9261
  if (restored.length === 0 && removed.length === 0) {
@@ -9207,7 +9263,7 @@ function undoCommand() {
9207
9263
  return;
9208
9264
  }
9209
9265
  trackUndoExecuted();
9210
- spinner.succeed("Setup reverted successfully.\n");
9266
+ spinner.succeed("Config reverted successfully.\n");
9211
9267
  if (restored.length > 0) {
9212
9268
  console.log(chalk15.cyan(" Restored from backup:"));
9213
9269
  for (const file of restored) {
@@ -9229,7 +9285,7 @@ function undoCommand() {
9229
9285
 
9230
9286
  // src/commands/status.ts
9231
9287
  import chalk16 from "chalk";
9232
- import fs32 from "fs";
9288
+ import fs33 from "fs";
9233
9289
  init_config();
9234
9290
  async function statusCommand(options) {
9235
9291
  const config = loadConfig();
@@ -9250,13 +9306,13 @@ async function statusCommand(options) {
9250
9306
  console.log(` LLM: ${chalk16.yellow("Not configured")} \u2014 run ${chalk16.hex("#83D1EB")("caliber config")}`);
9251
9307
  }
9252
9308
  if (!manifest) {
9253
- console.log(` Setup: ${chalk16.dim("No setup applied")}`);
9309
+ console.log(` Config: ${chalk16.dim("No config applied")}`);
9254
9310
  console.log(chalk16.dim("\n Run ") + chalk16.hex("#83D1EB")("caliber init") + chalk16.dim(" to get started.\n"));
9255
9311
  return;
9256
9312
  }
9257
9313
  console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
9258
9314
  for (const entry of manifest.entries) {
9259
- const exists = fs32.existsSync(entry.path);
9315
+ const exists = fs33.existsSync(entry.path);
9260
9316
  const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
9261
9317
  console.log(` ${icon} ${entry.path} (${entry.action})`);
9262
9318
  }
@@ -9277,7 +9333,7 @@ async function regenerateCommand(options) {
9277
9333
  }
9278
9334
  const manifest = readManifest();
9279
9335
  if (!manifest) {
9280
- 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."));
9281
9337
  throw new Error("__exit__");
9282
9338
  }
9283
9339
  const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
@@ -9288,10 +9344,10 @@ async function regenerateCommand(options) {
9288
9344
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
9289
9345
  displayScoreSummary(baselineScore);
9290
9346
  if (baselineScore.score === 100) {
9291
- 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"));
9292
9348
  return;
9293
9349
  }
9294
- const genSpinner = ora5("Regenerating setup...").start();
9350
+ const genSpinner = ora5("Regenerating config...").start();
9295
9351
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
9296
9352
  genMessages.start();
9297
9353
  let generatedSetup = null;
@@ -9322,10 +9378,10 @@ async function regenerateCommand(options) {
9322
9378
  }
9323
9379
  genMessages.stop();
9324
9380
  if (!generatedSetup) {
9325
- genSpinner.fail("Failed to regenerate setup.");
9381
+ genSpinner.fail("Failed to regenerate config.");
9326
9382
  throw new Error("__exit__");
9327
9383
  }
9328
- genSpinner.succeed("Setup regenerated");
9384
+ genSpinner.succeed("Config regenerated");
9329
9385
  generatedSetup = await runScoreRefineWithSpinner(generatedSetup, process.cwd(), []);
9330
9386
  const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
9331
9387
  const staged = stageFiles(setupFiles, process.cwd());
@@ -9352,7 +9408,7 @@ async function regenerateCommand(options) {
9352
9408
  await openReview(reviewMethod, staged.stagedFiles);
9353
9409
  }
9354
9410
  const action = await select6({
9355
- message: "Apply regenerated setup?",
9411
+ message: "Apply regenerated config?",
9356
9412
  choices: [
9357
9413
  { name: "Accept and apply", value: "accept" },
9358
9414
  { name: "Decline", value: "decline" }
@@ -9411,21 +9467,21 @@ async function regenerateCommand(options) {
9411
9467
  }
9412
9468
 
9413
9469
  // src/commands/score.ts
9414
- import fs33 from "fs";
9470
+ import fs34 from "fs";
9415
9471
  import os7 from "os";
9416
- import path25 from "path";
9472
+ import path26 from "path";
9417
9473
  import { execFileSync as execFileSync2 } from "child_process";
9418
9474
  import chalk18 from "chalk";
9419
9475
  var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
9420
9476
  var CONFIG_DIRS = [".claude", ".cursor"];
9421
9477
  function scoreBaseRef(ref, target) {
9422
9478
  if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
9423
- const tmpDir = fs33.mkdtempSync(path25.join(os7.tmpdir(), "caliber-compare-"));
9479
+ const tmpDir = fs34.mkdtempSync(path26.join(os7.tmpdir(), "caliber-compare-"));
9424
9480
  try {
9425
9481
  for (const file of CONFIG_FILES) {
9426
9482
  try {
9427
9483
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9428
- fs33.writeFileSync(path25.join(tmpDir, file), content);
9484
+ fs34.writeFileSync(path26.join(tmpDir, file), content);
9429
9485
  } catch {
9430
9486
  }
9431
9487
  }
@@ -9433,10 +9489,10 @@ function scoreBaseRef(ref, target) {
9433
9489
  try {
9434
9490
  const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
9435
9491
  for (const file of files) {
9436
- const filePath = path25.join(tmpDir, file);
9437
- fs33.mkdirSync(path25.dirname(filePath), { recursive: true });
9492
+ const filePath = path26.join(tmpDir, file);
9493
+ fs34.mkdirSync(path26.dirname(filePath), { recursive: true });
9438
9494
  const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
9439
- fs33.writeFileSync(filePath, content);
9495
+ fs34.writeFileSync(filePath, content);
9440
9496
  }
9441
9497
  } catch {
9442
9498
  }
@@ -9446,7 +9502,7 @@ function scoreBaseRef(ref, target) {
9446
9502
  } catch {
9447
9503
  return null;
9448
9504
  } finally {
9449
- fs33.rmSync(tmpDir, { recursive: true, force: true });
9505
+ fs34.rmSync(tmpDir, { recursive: true, force: true });
9450
9506
  }
9451
9507
  }
9452
9508
  async function scoreCommand(options) {
@@ -9454,6 +9510,7 @@ async function scoreCommand(options) {
9454
9510
  const target = options.agent ?? readState()?.targetAgent;
9455
9511
  const result = computeLocalScore(dir, target);
9456
9512
  trackScoreComputed(result.score, target);
9513
+ recordScore(result, "score");
9457
9514
  if (options.compare) {
9458
9515
  const baseResult = scoreBaseRef(options.compare, target);
9459
9516
  if (!baseResult) {
@@ -9496,9 +9553,9 @@ async function scoreCommand(options) {
9496
9553
  const separator = chalk18.gray(" " + "\u2500".repeat(53));
9497
9554
  console.log(separator);
9498
9555
  if (result.score < 40) {
9499
- 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."));
9500
9557
  } else if (result.score < 70) {
9501
- 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."));
9502
9559
  } else {
9503
9560
  console.log(chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber regenerate") + chalk18.gray(" to rebuild from scratch."));
9504
9561
  }
@@ -9506,13 +9563,13 @@ async function scoreCommand(options) {
9506
9563
  }
9507
9564
 
9508
9565
  // src/commands/refresh.ts
9509
- import fs37 from "fs";
9510
- import path29 from "path";
9566
+ import fs38 from "fs";
9567
+ import path30 from "path";
9511
9568
  import chalk19 from "chalk";
9512
9569
  import ora6 from "ora";
9513
9570
 
9514
9571
  // src/lib/git-diff.ts
9515
- import { execSync as execSync13 } from "child_process";
9572
+ import { execSync as execSync14 } from "child_process";
9516
9573
  var MAX_DIFF_BYTES = 1e5;
9517
9574
  var DOC_PATTERNS = [
9518
9575
  "CLAUDE.md",
@@ -9527,7 +9584,7 @@ function excludeArgs() {
9527
9584
  }
9528
9585
  function safeExec(cmd) {
9529
9586
  try {
9530
- return execSync13(cmd, {
9587
+ return execSync14(cmd, {
9531
9588
  encoding: "utf-8",
9532
9589
  stdio: ["pipe", "pipe", "pipe"],
9533
9590
  maxBuffer: 10 * 1024 * 1024
@@ -9585,48 +9642,48 @@ function collectDiff(lastSha) {
9585
9642
  }
9586
9643
 
9587
9644
  // src/writers/refresh.ts
9588
- import fs34 from "fs";
9589
- import path26 from "path";
9645
+ import fs35 from "fs";
9646
+ import path27 from "path";
9590
9647
  function writeRefreshDocs(docs) {
9591
9648
  const written = [];
9592
9649
  if (docs.claudeMd) {
9593
- fs34.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
9650
+ fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
9594
9651
  written.push("CLAUDE.md");
9595
9652
  }
9596
9653
  if (docs.readmeMd) {
9597
- fs34.writeFileSync("README.md", docs.readmeMd);
9654
+ fs35.writeFileSync("README.md", docs.readmeMd);
9598
9655
  written.push("README.md");
9599
9656
  }
9600
9657
  if (docs.cursorrules) {
9601
- fs34.writeFileSync(".cursorrules", docs.cursorrules);
9658
+ fs35.writeFileSync(".cursorrules", docs.cursorrules);
9602
9659
  written.push(".cursorrules");
9603
9660
  }
9604
9661
  if (docs.cursorRules) {
9605
- const rulesDir = path26.join(".cursor", "rules");
9606
- 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 });
9607
9664
  for (const rule of docs.cursorRules) {
9608
- fs34.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
9665
+ fs35.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
9609
9666
  written.push(`.cursor/rules/${rule.filename}`);
9610
9667
  }
9611
9668
  }
9612
9669
  if (docs.claudeSkills) {
9613
- const skillsDir = path26.join(".claude", "skills");
9614
- 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 });
9615
9672
  for (const skill of docs.claudeSkills) {
9616
- fs34.writeFileSync(path26.join(skillsDir, skill.filename), skill.content);
9673
+ fs35.writeFileSync(path27.join(skillsDir, skill.filename), skill.content);
9617
9674
  written.push(`.claude/skills/${skill.filename}`);
9618
9675
  }
9619
9676
  }
9620
9677
  if (docs.copilotInstructions) {
9621
- fs34.mkdirSync(".github", { recursive: true });
9622
- 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)));
9623
9680
  written.push(".github/copilot-instructions.md");
9624
9681
  }
9625
9682
  if (docs.copilotInstructionFiles) {
9626
- const instructionsDir = path26.join(".github", "instructions");
9627
- fs34.mkdirSync(instructionsDir, { recursive: true });
9683
+ const instructionsDir = path27.join(".github", "instructions");
9684
+ fs35.mkdirSync(instructionsDir, { recursive: true });
9628
9685
  for (const file of docs.copilotInstructionFiles) {
9629
- fs34.writeFileSync(path26.join(instructionsDir, file.filename), file.content);
9686
+ fs35.writeFileSync(path27.join(instructionsDir, file.filename), file.content);
9630
9687
  written.push(`.github/instructions/${file.filename}`);
9631
9688
  }
9632
9689
  }
@@ -9652,6 +9709,12 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
9652
9709
  if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
9653
9710
  if (projectContext.languages?.length) parts.push(`Languages: ${projectContext.languages.join(", ")}`);
9654
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
+ }
9655
9718
  parts.push(`
9656
9719
  Changed files: ${diff.changedFiles.join(", ")}`);
9657
9720
  parts.push(`Summary: ${diff.summary}`);
@@ -9706,8 +9769,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
9706
9769
  }
9707
9770
 
9708
9771
  // src/learner/writer.ts
9709
- import fs35 from "fs";
9710
- import path27 from "path";
9772
+ import fs36 from "fs";
9773
+ import path28 from "path";
9711
9774
 
9712
9775
  // src/learner/utils.ts
9713
9776
  var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
@@ -9824,20 +9887,20 @@ function deduplicateLearnedItems(existing, incoming) {
9824
9887
  }
9825
9888
  function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
9826
9889
  const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
9827
- fs35.writeFileSync(filePath, header + merged + "\n");
9828
- if (mode) fs35.chmodSync(filePath, mode);
9890
+ fs36.writeFileSync(filePath, header + merged + "\n");
9891
+ if (mode) fs36.chmodSync(filePath, mode);
9829
9892
  return { newCount, newItems };
9830
9893
  }
9831
9894
  function writeLearnedSection(content) {
9832
9895
  return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
9833
9896
  }
9834
9897
  function writeLearnedSkill(skill) {
9835
- const skillDir = path27.join(".claude", "skills", skill.name);
9836
- if (!fs35.existsSync(skillDir)) fs35.mkdirSync(skillDir, { recursive: true });
9837
- const skillPath = path27.join(skillDir, "SKILL.md");
9838
- if (!skill.isNew && fs35.existsSync(skillPath)) {
9839
- const existing = fs35.readFileSync(skillPath, "utf-8");
9840
- 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);
9841
9904
  } else {
9842
9905
  const frontmatter = [
9843
9906
  "---",
@@ -9846,12 +9909,12 @@ function writeLearnedSkill(skill) {
9846
9909
  "---",
9847
9910
  ""
9848
9911
  ].join("\n");
9849
- fs35.writeFileSync(skillPath, frontmatter + skill.content);
9912
+ fs36.writeFileSync(skillPath, frontmatter + skill.content);
9850
9913
  }
9851
9914
  return skillPath;
9852
9915
  }
9853
9916
  function writePersonalLearnedSection(content) {
9854
- if (!fs35.existsSync(AUTH_DIR)) fs35.mkdirSync(AUTH_DIR, { recursive: true });
9917
+ if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
9855
9918
  return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
9856
9919
  }
9857
9920
  function addLearning(bullet, scope = "project") {
@@ -9864,38 +9927,38 @@ function addLearning(bullet, scope = "project") {
9864
9927
  return { file: LEARNINGS_FILE, added: result.newCount > 0 };
9865
9928
  }
9866
9929
  function readPersonalLearnings() {
9867
- if (!fs35.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
9868
- 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");
9869
9932
  const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
9870
9933
  return bullets || null;
9871
9934
  }
9872
9935
  function readLearnedSection() {
9873
- if (fs35.existsSync(LEARNINGS_FILE)) {
9874
- const content2 = fs35.readFileSync(LEARNINGS_FILE, "utf-8");
9936
+ if (fs36.existsSync(LEARNINGS_FILE)) {
9937
+ const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
9875
9938
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
9876
9939
  return bullets || null;
9877
9940
  }
9878
9941
  const claudeMdPath = "CLAUDE.md";
9879
- if (!fs35.existsSync(claudeMdPath)) return null;
9880
- const content = fs35.readFileSync(claudeMdPath, "utf-8");
9942
+ if (!fs36.existsSync(claudeMdPath)) return null;
9943
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
9881
9944
  const startIdx = content.indexOf(LEARNED_START);
9882
9945
  const endIdx = content.indexOf(LEARNED_END);
9883
9946
  if (startIdx === -1 || endIdx === -1) return null;
9884
9947
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
9885
9948
  }
9886
9949
  function migrateInlineLearnings() {
9887
- if (fs35.existsSync(LEARNINGS_FILE)) return false;
9950
+ if (fs36.existsSync(LEARNINGS_FILE)) return false;
9888
9951
  const claudeMdPath = "CLAUDE.md";
9889
- if (!fs35.existsSync(claudeMdPath)) return false;
9890
- const content = fs35.readFileSync(claudeMdPath, "utf-8");
9952
+ if (!fs36.existsSync(claudeMdPath)) return false;
9953
+ const content = fs36.readFileSync(claudeMdPath, "utf-8");
9891
9954
  const startIdx = content.indexOf(LEARNED_START);
9892
9955
  const endIdx = content.indexOf(LEARNED_END);
9893
9956
  if (startIdx === -1 || endIdx === -1) return false;
9894
9957
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
9895
9958
  if (!section) return false;
9896
- fs35.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9959
+ fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9897
9960
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
9898
- 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");
9899
9962
  return true;
9900
9963
  }
9901
9964
 
@@ -9907,11 +9970,11 @@ function log2(quiet, ...args) {
9907
9970
  function discoverGitRepos(parentDir) {
9908
9971
  const repos = [];
9909
9972
  try {
9910
- const entries = fs37.readdirSync(parentDir, { withFileTypes: true });
9973
+ const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
9911
9974
  for (const entry of entries) {
9912
9975
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
9913
- const childPath = path29.join(parentDir, entry.name);
9914
- if (fs37.existsSync(path29.join(childPath, ".git"))) {
9976
+ const childPath = path30.join(parentDir, entry.name);
9977
+ if (fs38.existsSync(path30.join(childPath, ".git"))) {
9915
9978
  repos.push(childPath);
9916
9979
  }
9917
9980
  }
@@ -9919,11 +9982,19 @@ function discoverGitRepos(parentDir) {
9919
9982
  }
9920
9983
  return repos.sort();
9921
9984
  }
9985
+ var REFRESH_COOLDOWN_MS = 3e4;
9922
9986
  async function refreshSingleRepo(repoDir, options) {
9923
9987
  const quiet = !!options.quiet;
9924
9988
  const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
9925
9989
  const state = readState();
9926
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
+ }
9927
9998
  const diff = collectDiff(lastSha);
9928
9999
  const currentSha = getCurrentHeadSha();
9929
10000
  if (!diff.hasChanges) {
@@ -9940,23 +10011,32 @@ async function refreshSingleRepo(repoDir, options) {
9940
10011
  const projectContext = {
9941
10012
  languages: fingerprint.languages,
9942
10013
  frameworks: fingerprint.frameworks,
9943
- packageName: fingerprint.packageName
10014
+ packageName: fingerprint.packageName,
10015
+ fileTree: fingerprint.fileTree
9944
10016
  };
9945
10017
  const workspaces = getDetectedWorkspaces(repoDir);
9946
10018
  const sources2 = resolveAllSources(repoDir, [], workspaces);
9947
- const response = await refreshDocs(
9948
- {
9949
- committed: diff.committedDiff,
9950
- staged: diff.stagedDiff,
9951
- unstaged: diff.unstagedDiff,
9952
- changedFiles: diff.changedFiles,
9953
- summary: diff.summary
9954
- },
9955
- existingDocs,
9956
- projectContext,
9957
- learnedSection,
9958
- sources2.length > 0 ? sources2 : void 0
9959
- );
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
+ }
9960
10040
  if (!response.docsUpdated || response.docsUpdated.length === 0) {
9961
10041
  spinner?.succeed(`${prefix}No doc updates needed`);
9962
10042
  if (currentSha) {
@@ -9975,8 +10055,41 @@ async function refreshSingleRepo(repoDir, options) {
9975
10055
  }
9976
10056
  return;
9977
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
+ }
9978
10070
  const written = writeRefreshDocs(response.updatedDocs);
9979
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");
9980
10093
  spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
9981
10094
  for (const file of written) {
9982
10095
  log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
@@ -10021,7 +10134,7 @@ async function refreshCommand(options) {
10021
10134
  `));
10022
10135
  const originalDir = process.cwd();
10023
10136
  for (const repo of repos) {
10024
- const repoName = path29.basename(repo);
10137
+ const repoName = path30.basename(repo);
10025
10138
  try {
10026
10139
  process.chdir(repo);
10027
10140
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -10042,31 +10155,31 @@ async function refreshCommand(options) {
10042
10155
 
10043
10156
  // src/commands/hooks.ts
10044
10157
  import chalk20 from "chalk";
10045
- import fs39 from "fs";
10158
+ import fs40 from "fs";
10046
10159
 
10047
10160
  // src/lib/hooks.ts
10048
10161
  init_resolve_caliber();
10049
- import fs38 from "fs";
10050
- import path30 from "path";
10051
- import { execSync as execSync14 } from "child_process";
10052
- var SETTINGS_PATH2 = path30.join(".claude", "settings.json");
10162
+ import fs39 from "fs";
10163
+ import path31 from "path";
10164
+ import { execSync as execSync15 } from "child_process";
10165
+ var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
10053
10166
  var REFRESH_TAIL = "refresh --quiet";
10054
10167
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
10055
10168
  function getHookCommand() {
10056
10169
  return `${resolveCaliber()} ${REFRESH_TAIL}`;
10057
10170
  }
10058
10171
  function readSettings2() {
10059
- if (!fs38.existsSync(SETTINGS_PATH2)) return {};
10172
+ if (!fs39.existsSync(SETTINGS_PATH2)) return {};
10060
10173
  try {
10061
- return JSON.parse(fs38.readFileSync(SETTINGS_PATH2, "utf-8"));
10174
+ return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
10062
10175
  } catch {
10063
10176
  return {};
10064
10177
  }
10065
10178
  }
10066
10179
  function writeSettings2(settings) {
10067
- const dir = path30.dirname(SETTINGS_PATH2);
10068
- if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
10069
- 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));
10070
10183
  }
10071
10184
  function findHookIndex(sessionEnd) {
10072
10185
  return sessionEnd.findIndex(
@@ -10128,20 +10241,20 @@ ${PRECOMMIT_END}`;
10128
10241
  }
10129
10242
  function getGitHooksDir() {
10130
10243
  try {
10131
- const gitDir = execSync14("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10132
- return path30.join(gitDir, "hooks");
10244
+ const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10245
+ return path31.join(gitDir, "hooks");
10133
10246
  } catch {
10134
10247
  return null;
10135
10248
  }
10136
10249
  }
10137
10250
  function getPreCommitPath() {
10138
10251
  const hooksDir = getGitHooksDir();
10139
- return hooksDir ? path30.join(hooksDir, "pre-commit") : null;
10252
+ return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
10140
10253
  }
10141
10254
  function isPreCommitHookInstalled() {
10142
10255
  const hookPath = getPreCommitPath();
10143
- if (!hookPath || !fs38.existsSync(hookPath)) return false;
10144
- const content = fs38.readFileSync(hookPath, "utf-8");
10256
+ if (!hookPath || !fs39.existsSync(hookPath)) return false;
10257
+ const content = fs39.readFileSync(hookPath, "utf-8");
10145
10258
  return content.includes(PRECOMMIT_START);
10146
10259
  }
10147
10260
  function installPreCommitHook() {
@@ -10150,35 +10263,35 @@ function installPreCommitHook() {
10150
10263
  }
10151
10264
  const hookPath = getPreCommitPath();
10152
10265
  if (!hookPath) return { installed: false, alreadyInstalled: false };
10153
- const hooksDir = path30.dirname(hookPath);
10154
- 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 });
10155
10268
  let content = "";
10156
- if (fs38.existsSync(hookPath)) {
10157
- content = fs38.readFileSync(hookPath, "utf-8");
10269
+ if (fs39.existsSync(hookPath)) {
10270
+ content = fs39.readFileSync(hookPath, "utf-8");
10158
10271
  if (!content.endsWith("\n")) content += "\n";
10159
10272
  content += "\n" + getPrecommitBlock() + "\n";
10160
10273
  } else {
10161
10274
  content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
10162
10275
  }
10163
- fs38.writeFileSync(hookPath, content);
10164
- fs38.chmodSync(hookPath, 493);
10276
+ fs39.writeFileSync(hookPath, content);
10277
+ fs39.chmodSync(hookPath, 493);
10165
10278
  return { installed: true, alreadyInstalled: false };
10166
10279
  }
10167
10280
  function removePreCommitHook() {
10168
10281
  const hookPath = getPreCommitPath();
10169
- if (!hookPath || !fs38.existsSync(hookPath)) {
10282
+ if (!hookPath || !fs39.existsSync(hookPath)) {
10170
10283
  return { removed: false, notFound: true };
10171
10284
  }
10172
- let content = fs38.readFileSync(hookPath, "utf-8");
10285
+ let content = fs39.readFileSync(hookPath, "utf-8");
10173
10286
  if (!content.includes(PRECOMMIT_START)) {
10174
10287
  return { removed: false, notFound: true };
10175
10288
  }
10176
10289
  const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
10177
10290
  content = content.replace(regex, "\n");
10178
10291
  if (content.trim() === "#!/bin/sh" || content.trim() === "") {
10179
- fs38.unlinkSync(hookPath);
10292
+ fs39.unlinkSync(hookPath);
10180
10293
  } else {
10181
- fs38.writeFileSync(hookPath, content);
10294
+ fs39.writeFileSync(hookPath, content);
10182
10295
  }
10183
10296
  return { removed: true, notFound: false };
10184
10297
  }
@@ -10227,11 +10340,11 @@ async function hooksCommand(options) {
10227
10340
  console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
10228
10341
  }
10229
10342
  }
10230
- if (fs39.existsSync(".claude")) {
10343
+ if (fs40.existsSync(".claude")) {
10231
10344
  const r = installLearningHooks();
10232
10345
  if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
10233
10346
  }
10234
- if (fs39.existsSync(".cursor")) {
10347
+ if (fs40.existsSync(".cursor")) {
10235
10348
  const r = installCursorLearningHooks();
10236
10349
  if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
10237
10350
  }
@@ -10399,8 +10512,8 @@ async function configCommand() {
10399
10512
  }
10400
10513
 
10401
10514
  // src/commands/learn.ts
10402
- import fs43 from "fs";
10403
- import path34 from "path";
10515
+ import fs44 from "fs";
10516
+ import path35 from "path";
10404
10517
  import chalk23 from "chalk";
10405
10518
 
10406
10519
  // src/learner/stdin.ts
@@ -10431,8 +10544,8 @@ function readStdin() {
10431
10544
  }
10432
10545
 
10433
10546
  // src/learner/storage.ts
10434
- import fs40 from "fs";
10435
- import path31 from "path";
10547
+ import fs41 from "fs";
10548
+ import path32 from "path";
10436
10549
  var MAX_RESPONSE_LENGTH = 2e3;
10437
10550
  var DEFAULT_STATE = {
10438
10551
  sessionId: null,
@@ -10441,15 +10554,15 @@ var DEFAULT_STATE = {
10441
10554
  lastAnalysisEventCount: 0
10442
10555
  };
10443
10556
  function ensureLearningDir() {
10444
- if (!fs40.existsSync(LEARNING_DIR)) {
10445
- fs40.mkdirSync(LEARNING_DIR, { recursive: true });
10557
+ if (!fs41.existsSync(getLearningDir())) {
10558
+ fs41.mkdirSync(getLearningDir(), { recursive: true });
10446
10559
  }
10447
10560
  }
10448
10561
  function sessionFilePath() {
10449
- return path31.join(LEARNING_DIR, LEARNING_SESSION_FILE);
10562
+ return path32.join(getLearningDir(), LEARNING_SESSION_FILE);
10450
10563
  }
10451
10564
  function stateFilePath() {
10452
- return path31.join(LEARNING_DIR, LEARNING_STATE_FILE);
10565
+ return path32.join(getLearningDir(), LEARNING_STATE_FILE);
10453
10566
  }
10454
10567
  function truncateResponse(response) {
10455
10568
  const str = JSON.stringify(response);
@@ -10459,10 +10572,10 @@ function truncateResponse(response) {
10459
10572
  function trimSessionFileIfNeeded(filePath) {
10460
10573
  const state = readState2();
10461
10574
  if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
10462
- const lines = fs40.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10575
+ const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
10463
10576
  if (lines.length > LEARNING_MAX_EVENTS) {
10464
10577
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
10465
- fs40.writeFileSync(filePath, kept.join("\n") + "\n");
10578
+ fs41.writeFileSync(filePath, kept.join("\n") + "\n");
10466
10579
  }
10467
10580
  }
10468
10581
  }
@@ -10470,19 +10583,19 @@ function appendEvent(event) {
10470
10583
  ensureLearningDir();
10471
10584
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
10472
10585
  const filePath = sessionFilePath();
10473
- fs40.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
10586
+ fs41.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
10474
10587
  trimSessionFileIfNeeded(filePath);
10475
10588
  }
10476
10589
  function appendPromptEvent(event) {
10477
10590
  ensureLearningDir();
10478
10591
  const filePath = sessionFilePath();
10479
- fs40.appendFileSync(filePath, JSON.stringify(event) + "\n");
10592
+ fs41.appendFileSync(filePath, JSON.stringify(event) + "\n");
10480
10593
  trimSessionFileIfNeeded(filePath);
10481
10594
  }
10482
10595
  function readAllEvents() {
10483
10596
  const filePath = sessionFilePath();
10484
- if (!fs40.existsSync(filePath)) return [];
10485
- 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);
10486
10599
  const events = [];
10487
10600
  for (const line of lines) {
10488
10601
  try {
@@ -10494,26 +10607,26 @@ function readAllEvents() {
10494
10607
  }
10495
10608
  function getEventCount() {
10496
10609
  const filePath = sessionFilePath();
10497
- if (!fs40.existsSync(filePath)) return 0;
10498
- const content = fs40.readFileSync(filePath, "utf-8");
10610
+ if (!fs41.existsSync(filePath)) return 0;
10611
+ const content = fs41.readFileSync(filePath, "utf-8");
10499
10612
  return content.split("\n").filter(Boolean).length;
10500
10613
  }
10501
10614
  function clearSession() {
10502
10615
  const filePath = sessionFilePath();
10503
- if (fs40.existsSync(filePath)) fs40.unlinkSync(filePath);
10616
+ if (fs41.existsSync(filePath)) fs41.unlinkSync(filePath);
10504
10617
  }
10505
10618
  function readState2() {
10506
10619
  const filePath = stateFilePath();
10507
- if (!fs40.existsSync(filePath)) return { ...DEFAULT_STATE };
10620
+ if (!fs41.existsSync(filePath)) return { ...DEFAULT_STATE };
10508
10621
  try {
10509
- return JSON.parse(fs40.readFileSync(filePath, "utf-8"));
10622
+ return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
10510
10623
  } catch {
10511
10624
  return { ...DEFAULT_STATE };
10512
10625
  }
10513
10626
  }
10514
10627
  function writeState2(state) {
10515
10628
  ensureLearningDir();
10516
- fs40.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
10629
+ fs41.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
10517
10630
  }
10518
10631
  function resetState() {
10519
10632
  writeState2({ ...DEFAULT_STATE });
@@ -10521,16 +10634,16 @@ function resetState() {
10521
10634
  var LOCK_FILE2 = "finalize.lock";
10522
10635
  var LOCK_STALE_MS = 5 * 60 * 1e3;
10523
10636
  function lockFilePath() {
10524
- return path31.join(LEARNING_DIR, LOCK_FILE2);
10637
+ return path32.join(getLearningDir(), LOCK_FILE2);
10525
10638
  }
10526
10639
  function acquireFinalizeLock() {
10527
10640
  ensureLearningDir();
10528
10641
  const lockPath = lockFilePath();
10529
- if (fs40.existsSync(lockPath)) {
10642
+ if (fs41.existsSync(lockPath)) {
10530
10643
  try {
10531
- const stat = fs40.statSync(lockPath);
10644
+ const stat = fs41.statSync(lockPath);
10532
10645
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
10533
- const pid = parseInt(fs40.readFileSync(lockPath, "utf-8").trim(), 10);
10646
+ const pid = parseInt(fs41.readFileSync(lockPath, "utf-8").trim(), 10);
10534
10647
  if (!isNaN(pid) && isProcessAlive(pid)) {
10535
10648
  return false;
10536
10649
  }
@@ -10538,12 +10651,12 @@ function acquireFinalizeLock() {
10538
10651
  } catch {
10539
10652
  }
10540
10653
  try {
10541
- fs40.unlinkSync(lockPath);
10654
+ fs41.unlinkSync(lockPath);
10542
10655
  } catch {
10543
10656
  }
10544
10657
  }
10545
10658
  try {
10546
- fs40.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
10659
+ fs41.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
10547
10660
  return true;
10548
10661
  } catch {
10549
10662
  return false;
@@ -10560,7 +10673,7 @@ function isProcessAlive(pid) {
10560
10673
  function releaseFinalizeLock() {
10561
10674
  const lockPath = lockFilePath();
10562
10675
  try {
10563
- if (fs40.existsSync(lockPath)) fs40.unlinkSync(lockPath);
10676
+ if (fs41.existsSync(lockPath)) fs41.unlinkSync(lockPath);
10564
10677
  } catch {
10565
10678
  }
10566
10679
  }
@@ -10605,22 +10718,24 @@ function sanitizeSecrets(text) {
10605
10718
  }
10606
10719
 
10607
10720
  // src/lib/notifications.ts
10608
- import fs41 from "fs";
10609
- import path32 from "path";
10721
+ import fs42 from "fs";
10722
+ import path33 from "path";
10610
10723
  import chalk22 from "chalk";
10611
- var NOTIFICATION_FILE = path32.join(LEARNING_DIR, "last-finalize-summary.json");
10724
+ function notificationFilePath() {
10725
+ return path33.join(getLearningDir(), "last-finalize-summary.json");
10726
+ }
10612
10727
  function writeFinalizeSummary(summary) {
10613
10728
  try {
10614
10729
  ensureLearningDir();
10615
- fs41.writeFileSync(NOTIFICATION_FILE, JSON.stringify(summary, null, 2));
10730
+ fs42.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
10616
10731
  } catch {
10617
10732
  }
10618
10733
  }
10619
10734
  function checkPendingNotifications() {
10620
10735
  try {
10621
- if (!fs41.existsSync(NOTIFICATION_FILE)) return;
10622
- const raw = fs41.readFileSync(NOTIFICATION_FILE, "utf-8");
10623
- fs41.unlinkSync(NOTIFICATION_FILE);
10736
+ if (!fs42.existsSync(notificationFilePath())) return;
10737
+ const raw = fs42.readFileSync(notificationFilePath(), "utf-8");
10738
+ fs42.unlinkSync(notificationFilePath());
10624
10739
  const summary = JSON.parse(raw);
10625
10740
  if (!summary.newItemCount || summary.newItemCount === 0) return;
10626
10741
  const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
@@ -10636,7 +10751,7 @@ function checkPendingNotifications() {
10636
10751
  console.log("");
10637
10752
  } catch {
10638
10753
  try {
10639
- fs41.unlinkSync(NOTIFICATION_FILE);
10754
+ fs42.unlinkSync(notificationFilePath());
10640
10755
  } catch {
10641
10756
  }
10642
10757
  }
@@ -10675,10 +10790,25 @@ function trimEventsToFit(events, maxTokens) {
10675
10790
  if (estimateTokens(formatted) <= maxTokens) return kept;
10676
10791
  return kept.slice(-50);
10677
10792
  }
10793
+ function normalizeAnalysisResult(parsed) {
10794
+ let section = parsed.claudeMdLearnedSection ?? null;
10795
+ if (Array.isArray(section)) {
10796
+ section = section.join("\n");
10797
+ }
10798
+ if (section !== null && typeof section !== "string") {
10799
+ section = String(section);
10800
+ }
10801
+ return {
10802
+ ...parsed,
10803
+ claudeMdLearnedSection: section,
10804
+ skills: parsed.skills ?? null,
10805
+ explanations: parsed.explanations ?? []
10806
+ };
10807
+ }
10678
10808
  function parseAnalysisResponse(raw) {
10679
10809
  const cleaned = stripMarkdownFences(raw);
10680
10810
  try {
10681
- return JSON.parse(cleaned);
10811
+ return normalizeAnalysisResult(JSON.parse(cleaned));
10682
10812
  } catch {
10683
10813
  }
10684
10814
  const json = extractJson(cleaned);
@@ -10686,7 +10816,7 @@ function parseAnalysisResponse(raw) {
10686
10816
  return { claudeMdLearnedSection: null, skills: null, explanations: ["LLM response could not be parsed."] };
10687
10817
  }
10688
10818
  try {
10689
- return JSON.parse(json);
10819
+ return normalizeAnalysisResult(JSON.parse(json));
10690
10820
  } catch {
10691
10821
  return { claudeMdLearnedSection: null, skills: null, explanations: ["LLM response contained invalid JSON."] };
10692
10822
  }
@@ -10773,8 +10903,8 @@ function calculateSessionWaste(events) {
10773
10903
  init_config();
10774
10904
 
10775
10905
  // src/learner/roi.ts
10776
- import fs42 from "fs";
10777
- import path33 from "path";
10906
+ import fs43 from "fs";
10907
+ import path34 from "path";
10778
10908
  var DEFAULT_TOTALS = {
10779
10909
  totalWasteTokens: 0,
10780
10910
  totalWasteSeconds: 0,
@@ -10788,19 +10918,19 @@ var DEFAULT_TOTALS = {
10788
10918
  lastSessionTimestamp: ""
10789
10919
  };
10790
10920
  function roiFilePath() {
10791
- return path33.join(LEARNING_DIR, LEARNING_ROI_FILE);
10921
+ return path34.join(getLearningDir(), LEARNING_ROI_FILE);
10792
10922
  }
10793
10923
  function readROIStats() {
10794
10924
  const filePath = roiFilePath();
10795
- if (!fs42.existsSync(filePath)) {
10925
+ if (!fs43.existsSync(filePath)) {
10796
10926
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
10797
10927
  }
10798
10928
  try {
10799
- return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
10929
+ return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
10800
10930
  } catch {
10801
10931
  try {
10802
10932
  const corruptPath = filePath + ".corrupt";
10803
- fs42.renameSync(filePath, corruptPath);
10933
+ fs43.renameSync(filePath, corruptPath);
10804
10934
  console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
10805
10935
  } catch {
10806
10936
  }
@@ -10809,7 +10939,7 @@ function readROIStats() {
10809
10939
  }
10810
10940
  function writeROIStats(stats) {
10811
10941
  ensureLearningDir();
10812
- fs42.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
10942
+ fs43.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
10813
10943
  }
10814
10944
  function recalculateTotals(stats) {
10815
10945
  const totals = stats.totals;
@@ -11015,9 +11145,9 @@ var AUTO_SETTLE_MS = 200;
11015
11145
  var INCREMENTAL_INTERVAL = 50;
11016
11146
  function writeFinalizeError(message) {
11017
11147
  try {
11018
- const errorPath = path34.join(LEARNING_DIR, LEARNING_LAST_ERROR_FILE);
11019
- if (!fs43.existsSync(LEARNING_DIR)) fs43.mkdirSync(LEARNING_DIR, { recursive: true });
11020
- 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({
11021
11151
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11022
11152
  error: message,
11023
11153
  pid: process.pid
@@ -11027,9 +11157,9 @@ function writeFinalizeError(message) {
11027
11157
  }
11028
11158
  function readFinalizeError() {
11029
11159
  try {
11030
- const errorPath = path34.join(LEARNING_DIR, LEARNING_LAST_ERROR_FILE);
11031
- if (!fs43.existsSync(errorPath)) return null;
11032
- 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"));
11033
11163
  } catch {
11034
11164
  return null;
11035
11165
  }
@@ -11076,14 +11206,14 @@ async function learnObserveCommand(options) {
11076
11206
  const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
11077
11207
  const bin = resolveCaliber2();
11078
11208
  const { spawn: spawn4 } = await import("child_process");
11079
- const logPath = path34.join(LEARNING_DIR, LEARNING_FINALIZE_LOG);
11080
- if (!fs43.existsSync(LEARNING_DIR)) fs43.mkdirSync(LEARNING_DIR, { recursive: true });
11081
- 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");
11082
11212
  spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
11083
11213
  detached: true,
11084
11214
  stdio: ["ignore", logFd, logFd]
11085
11215
  }).unref();
11086
- fs43.closeSync(logFd);
11216
+ fs44.closeSync(logFd);
11087
11217
  } catch {
11088
11218
  }
11089
11219
  }
@@ -11290,7 +11420,7 @@ async function learnFinalizeCommand(options) {
11290
11420
  }
11291
11421
  async function learnInstallCommand() {
11292
11422
  let anyInstalled = false;
11293
- if (fs43.existsSync(".claude")) {
11423
+ if (fs44.existsSync(".claude")) {
11294
11424
  const r = installLearningHooks();
11295
11425
  if (r.installed) {
11296
11426
  console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
@@ -11299,7 +11429,7 @@ async function learnInstallCommand() {
11299
11429
  console.log(chalk23.dim(" Claude Code hooks already installed"));
11300
11430
  }
11301
11431
  }
11302
- if (fs43.existsSync(".cursor")) {
11432
+ if (fs44.existsSync(".cursor")) {
11303
11433
  const r = installCursorLearningHooks();
11304
11434
  if (r.installed) {
11305
11435
  console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
@@ -11308,7 +11438,7 @@ async function learnInstallCommand() {
11308
11438
  console.log(chalk23.dim(" Cursor hooks already installed"));
11309
11439
  }
11310
11440
  }
11311
- if (!fs43.existsSync(".claude") && !fs43.existsSync(".cursor")) {
11441
+ if (!fs44.existsSync(".claude") && !fs44.existsSync(".cursor")) {
11312
11442
  console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
11313
11443
  console.log(chalk23.dim(" Run `caliber init` first, or create the directory manually."));
11314
11444
  return;
@@ -11366,8 +11496,8 @@ async function learnStatusCommand() {
11366
11496
  if (lastError) {
11367
11497
  console.log(`Last error: ${chalk23.red(lastError.error)}`);
11368
11498
  console.log(chalk23.dim(` at ${lastError.timestamp}`));
11369
- const logPath = path34.join(LEARNING_DIR, LEARNING_FINALIZE_LOG);
11370
- if (fs43.existsSync(logPath)) {
11499
+ const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
11500
+ if (fs44.existsSync(logPath)) {
11371
11501
  console.log(chalk23.dim(` Full log: ${logPath}`));
11372
11502
  }
11373
11503
  }
@@ -11447,11 +11577,11 @@ async function learnDeleteCommand(indexStr) {
11447
11577
  }
11448
11578
  const item = items[targetIdx];
11449
11579
  const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
11450
- if (!fs43.existsSync(filePath)) {
11580
+ if (!fs44.existsSync(filePath)) {
11451
11581
  console.log(chalk23.red("Learnings file not found."));
11452
11582
  return;
11453
11583
  }
11454
- const content = fs43.readFileSync(filePath, "utf-8");
11584
+ const content = fs44.readFileSync(filePath, "utf-8");
11455
11585
  const lines = content.split("\n");
11456
11586
  const bulletsOfSource = items.filter((i) => i.source === item.source);
11457
11587
  const posInFile = bulletsOfSource.indexOf(item);
@@ -11472,9 +11602,9 @@ async function learnDeleteCommand(indexStr) {
11472
11602
  }
11473
11603
  const bulletToRemove = lines[lineToRemove];
11474
11604
  const newLines = lines.filter((_, i) => i !== lineToRemove);
11475
- fs43.writeFileSync(filePath, newLines.join("\n"));
11605
+ fs44.writeFileSync(filePath, newLines.join("\n"));
11476
11606
  if (item.source === "personal") {
11477
- fs43.chmodSync(filePath, 384);
11607
+ fs44.chmodSync(filePath, 384);
11478
11608
  }
11479
11609
  const roiStats = readROIStats();
11480
11610
  const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
@@ -11543,11 +11673,14 @@ function displayColdStart(score) {
11543
11673
  console.log(chalk24.bold("\n Agent Insights\n"));
11544
11674
  const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
11545
11675
  if (!hooksInstalled) {
11546
- console.log(chalk24.yellow(" No learning hooks installed."));
11547
- 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."));
11548
11680
  } else {
11549
- console.log(chalk24.dim(" No session data yet. Use your AI agent and insights will appear here."));
11550
- 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}`));
11551
11684
  }
11552
11685
  console.log(chalk24.dim(`
11553
11686
  Config score: ${score.score}/100 (${score.grade})`));
@@ -11555,7 +11688,9 @@ function displayColdStart(score) {
11555
11688
  }
11556
11689
  function displayEarlyData(data, score) {
11557
11690
  console.log(chalk24.bold("\n Agent Insights") + chalk24.yellow(" (early data)\n"));
11558
- 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
+ `));
11559
11694
  console.log(` Sessions tracked: ${chalk24.cyan(String(data.totalSessions))}`);
11560
11695
  console.log(` Learnings accumulated: ${chalk24.cyan(String(data.learningCount))}`);
11561
11696
  if (data.totalWasteTokens > 0) {
@@ -11607,6 +11742,14 @@ function displayFullInsights(data, score) {
11607
11742
  }
11608
11743
  console.log(chalk24.bold("\n Config Quality"));
11609
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
+ }
11610
11753
  console.log("");
11611
11754
  }
11612
11755
  async function insightsCommand(options) {
@@ -11633,8 +11776,8 @@ async function insightsCommand(options) {
11633
11776
  }
11634
11777
 
11635
11778
  // src/commands/sources.ts
11636
- import fs44 from "fs";
11637
- import path35 from "path";
11779
+ import fs45 from "fs";
11780
+ import path36 from "path";
11638
11781
  import chalk25 from "chalk";
11639
11782
  async function sourcesListCommand() {
11640
11783
  const dir = process.cwd();
@@ -11650,9 +11793,9 @@ async function sourcesListCommand() {
11650
11793
  if (configSources.length > 0) {
11651
11794
  for (const source of configSources) {
11652
11795
  const sourcePath = source.path || source.url || "";
11653
- 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;
11654
11797
  const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
11655
- 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"));
11656
11799
  console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
11657
11800
  console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
11658
11801
  if (source.description) console.log(` ${chalk25.dim(source.description)}`);
@@ -11662,7 +11805,7 @@ async function sourcesListCommand() {
11662
11805
  if (workspaces.length > 0) {
11663
11806
  console.log(chalk25.dim(" Auto-detected workspaces:"));
11664
11807
  for (const ws of workspaces) {
11665
- const exists = fs44.existsSync(path35.resolve(dir, ws));
11808
+ const exists = fs45.existsSync(path36.resolve(dir, ws));
11666
11809
  console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
11667
11810
  }
11668
11811
  console.log("");
@@ -11670,8 +11813,8 @@ async function sourcesListCommand() {
11670
11813
  }
11671
11814
  async function sourcesAddCommand(sourcePath) {
11672
11815
  const dir = process.cwd();
11673
- const absPath = path35.resolve(dir, sourcePath);
11674
- if (!fs44.existsSync(absPath)) {
11816
+ const absPath = path36.resolve(dir, sourcePath);
11817
+ if (!fs45.existsSync(absPath)) {
11675
11818
  console.log(chalk25.red(`
11676
11819
  Path not found: ${sourcePath}
11677
11820
  `));
@@ -11686,7 +11829,7 @@ async function sourcesAddCommand(sourcePath) {
11686
11829
  }
11687
11830
  const existing = loadSourcesConfig(dir);
11688
11831
  const alreadyConfigured = existing.some(
11689
- (s) => s.path && path35.resolve(dir, s.path) === absPath
11832
+ (s) => s.path && path36.resolve(dir, s.path) === absPath
11690
11833
  );
11691
11834
  if (alreadyConfigured) {
11692
11835
  console.log(chalk25.yellow(`
@@ -11734,8 +11877,8 @@ async function sourcesRemoveCommand(name) {
11734
11877
  }
11735
11878
 
11736
11879
  // src/commands/publish.ts
11737
- import fs45 from "fs";
11738
- import path36 from "path";
11880
+ import fs46 from "fs";
11881
+ import path37 from "path";
11739
11882
  import chalk26 from "chalk";
11740
11883
  import ora7 from "ora";
11741
11884
  init_config();
@@ -11749,10 +11892,10 @@ async function publishCommand() {
11749
11892
  const spinner = ora7("Generating project summary...").start();
11750
11893
  try {
11751
11894
  const fingerprint = await collectFingerprint(dir);
11752
- const claudeMd = readFileOrNull(path36.join(dir, "CLAUDE.md"));
11895
+ const claudeMd = readFileOrNull(path37.join(dir, "CLAUDE.md"));
11753
11896
  const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
11754
11897
  const summary = {
11755
- name: fingerprint.packageName || path36.basename(dir),
11898
+ name: fingerprint.packageName || path37.basename(dir),
11756
11899
  version: "1.0.0",
11757
11900
  description: fingerprint.description || "",
11758
11901
  languages: fingerprint.languages,
@@ -11764,7 +11907,7 @@ async function publishCommand() {
11764
11907
  summary.conventions = claudeMd.slice(0, 2e3);
11765
11908
  }
11766
11909
  try {
11767
- const pkgContent = readFileOrNull(path36.join(dir, "package.json"));
11910
+ const pkgContent = readFileOrNull(path37.join(dir, "package.json"));
11768
11911
  if (pkgContent) {
11769
11912
  const pkg3 = JSON.parse(pkgContent);
11770
11913
  if (pkg3.scripts) {
@@ -11777,14 +11920,14 @@ async function publishCommand() {
11777
11920
  }
11778
11921
  } catch {
11779
11922
  }
11780
- const outputDir = path36.join(dir, ".caliber");
11781
- if (!fs45.existsSync(outputDir)) {
11782
- fs45.mkdirSync(outputDir, { recursive: true });
11923
+ const outputDir = path37.join(dir, ".caliber");
11924
+ if (!fs46.existsSync(outputDir)) {
11925
+ fs46.mkdirSync(outputDir, { recursive: true });
11783
11926
  }
11784
- const outputPath = path36.join(outputDir, "summary.json");
11785
- 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");
11786
11929
  spinner.succeed("Project summary published");
11787
- console.log(` ${chalk26.green("\u2713")} ${path36.relative(dir, outputPath)}`);
11930
+ console.log(` ${chalk26.green("\u2713")} ${path37.relative(dir, outputPath)}`);
11788
11931
  console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
11789
11932
  console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
11790
11933
  } catch (err) {
@@ -11796,13 +11939,13 @@ async function publishCommand() {
11796
11939
  }
11797
11940
 
11798
11941
  // src/cli.ts
11799
- var __dirname = path37.dirname(fileURLToPath(import.meta.url));
11942
+ var __dirname = path38.dirname(fileURLToPath(import.meta.url));
11800
11943
  var pkg = JSON.parse(
11801
- fs46.readFileSync(path37.resolve(__dirname, "..", "package.json"), "utf-8")
11944
+ fs47.readFileSync(path38.resolve(__dirname, "..", "package.json"), "utf-8")
11802
11945
  );
11803
11946
  var program = new Command();
11804
11947
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
11805
- 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");
11806
11949
  function tracked(commandName, handler) {
11807
11950
  const wrapper = async (...args) => {
11808
11951
  const start = Date.now();
@@ -11859,13 +12002,13 @@ function parseAgentOption(value) {
11859
12002
  }
11860
12003
  return agents;
11861
12004
  }
11862
- 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));
11863
12006
  program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
11864
- program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
11865
- 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));
11866
12009
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
11867
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));
11868
- 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));
11869
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));
11870
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));
11871
12014
  program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
@@ -11874,7 +12017,7 @@ sources.command("list").description("Show configured and auto-detected sources")
11874
12017
  sources.command("add").description("Add an external source").argument("<path>", "Path to repo directory or file").action(tracked("sources:add", sourcesAddCommand));
11875
12018
  sources.command("remove").description("Remove a configured source").argument("<name>", "Source path or role to remove").action(tracked("sources:remove", sourcesRemoveCommand));
11876
12019
  program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
11877
- 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");
11878
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));
11879
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)));
11880
12023
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
@@ -11885,16 +12028,16 @@ learn.command("delete <index>").description("Delete a learning by its index numb
11885
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));
11886
12029
 
11887
12030
  // src/utils/version-check.ts
11888
- import fs47 from "fs";
11889
- import path38 from "path";
12031
+ import fs48 from "fs";
12032
+ import path39 from "path";
11890
12033
  import { fileURLToPath as fileURLToPath2 } from "url";
11891
- import { execSync as execSync15 } from "child_process";
12034
+ import { execSync as execSync16 } from "child_process";
11892
12035
  import chalk27 from "chalk";
11893
12036
  import ora8 from "ora";
11894
12037
  import confirm2 from "@inquirer/confirm";
11895
- var __dirname_vc = path38.dirname(fileURLToPath2(import.meta.url));
12038
+ var __dirname_vc = path39.dirname(fileURLToPath2(import.meta.url));
11896
12039
  var pkg2 = JSON.parse(
11897
- fs47.readFileSync(path38.resolve(__dirname_vc, "..", "package.json"), "utf-8")
12040
+ fs48.readFileSync(path39.resolve(__dirname_vc, "..", "package.json"), "utf-8")
11898
12041
  );
11899
12042
  function getChannel(version) {
11900
12043
  const match = version.match(/-(dev|next)\./);
@@ -11918,9 +12061,9 @@ function isNewer(registry, current) {
11918
12061
  }
11919
12062
  function getInstalledVersion() {
11920
12063
  try {
11921
- const globalRoot = execSync15("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
11922
- const pkgPath = path38.join(globalRoot, "@rely-ai", "caliber", "package.json");
11923
- return JSON.parse(fs47.readFileSync(pkgPath, "utf-8")).version;
12064
+ const globalRoot = execSync16("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
12065
+ const pkgPath = path39.join(globalRoot, "@rely-ai", "caliber", "package.json");
12066
+ return JSON.parse(fs48.readFileSync(pkgPath, "utf-8")).version;
11924
12067
  } catch {
11925
12068
  return null;
11926
12069
  }
@@ -11966,7 +12109,7 @@ Update available: ${current} -> ${latest}`)
11966
12109
  const tag = channel === "latest" ? latest : channel;
11967
12110
  const spinner = ora8("Updating caliber...").start();
11968
12111
  try {
11969
- execSync15(`npm install -g @rely-ai/caliber@${tag}`, {
12112
+ execSync16(`npm install -g @rely-ai/caliber@${tag}`, {
11970
12113
  stdio: "pipe",
11971
12114
  timeout: 12e4,
11972
12115
  env: { ...process.env, npm_config_fund: "false", npm_config_audit: "false" }
@@ -11983,7 +12126,7 @@ Update available: ${current} -> ${latest}`)
11983
12126
  console.log(chalk27.dim(`
11984
12127
  Restarting: caliber ${args.join(" ")}
11985
12128
  `));
11986
- execSync15(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
12129
+ execSync16(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
11987
12130
  stdio: "inherit",
11988
12131
  env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
11989
12132
  });