@rely-ai/caliber 1.23.2 → 1.24.0-dev.1773821102

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 +596 -221
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -216,6 +216,56 @@ var init_constants = __esm({
216
216
  }
217
217
  });
218
218
 
219
+ // src/lib/resolve-caliber.ts
220
+ var resolve_caliber_exports = {};
221
+ __export(resolve_caliber_exports, {
222
+ isCaliberCommand: () => isCaliberCommand,
223
+ resolveCaliber: () => resolveCaliber
224
+ });
225
+ import fs18 from "fs";
226
+ import { execSync as execSync7 } from "child_process";
227
+ function resolveCaliber() {
228
+ if (_resolved) return _resolved;
229
+ const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
230
+ if (isNpx) {
231
+ _resolved = "npx --yes @rely-ai/caliber";
232
+ return _resolved;
233
+ }
234
+ try {
235
+ const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
236
+ const found = execSync7(whichCmd, {
237
+ encoding: "utf-8",
238
+ stdio: ["pipe", "pipe", "pipe"]
239
+ }).trim();
240
+ if (found) {
241
+ _resolved = found;
242
+ return _resolved;
243
+ }
244
+ } catch {
245
+ }
246
+ const binPath = process.argv[1];
247
+ if (binPath && fs18.existsSync(binPath)) {
248
+ _resolved = binPath;
249
+ return _resolved;
250
+ }
251
+ _resolved = "caliber";
252
+ return _resolved;
253
+ }
254
+ function isCaliberCommand(command, subcommandTail) {
255
+ if (command === `caliber ${subcommandTail}`) return true;
256
+ if (command.endsWith(`/caliber ${subcommandTail}`)) return true;
257
+ if (command === `npx --yes @rely-ai/caliber ${subcommandTail}`) return true;
258
+ if (command === `npx @rely-ai/caliber ${subcommandTail}`) return true;
259
+ return false;
260
+ }
261
+ var _resolved;
262
+ var init_resolve_caliber = __esm({
263
+ "src/lib/resolve-caliber.ts"() {
264
+ "use strict";
265
+ _resolved = null;
266
+ }
267
+ });
268
+
219
269
  // src/lib/lock.ts
220
270
  var lock_exports = {};
221
271
  __export(lock_exports, {
@@ -223,13 +273,13 @@ __export(lock_exports, {
223
273
  isCaliberRunning: () => isCaliberRunning,
224
274
  releaseLock: () => releaseLock
225
275
  });
226
- import fs30 from "fs";
227
- import path24 from "path";
228
- import os6 from "os";
276
+ import fs31 from "fs";
277
+ import path25 from "path";
278
+ import os7 from "os";
229
279
  function isCaliberRunning() {
230
280
  try {
231
- if (!fs30.existsSync(LOCK_FILE)) return false;
232
- const raw = fs30.readFileSync(LOCK_FILE, "utf-8").trim();
281
+ if (!fs31.existsSync(LOCK_FILE)) return false;
282
+ const raw = fs31.readFileSync(LOCK_FILE, "utf-8").trim();
233
283
  const { pid, ts } = JSON.parse(raw);
234
284
  if (Date.now() - ts > STALE_MS) return false;
235
285
  try {
@@ -244,13 +294,13 @@ function isCaliberRunning() {
244
294
  }
245
295
  function acquireLock() {
246
296
  try {
247
- fs30.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
297
+ fs31.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
248
298
  } catch {
249
299
  }
250
300
  }
251
301
  function releaseLock() {
252
302
  try {
253
- if (fs30.existsSync(LOCK_FILE)) fs30.unlinkSync(LOCK_FILE);
303
+ if (fs31.existsSync(LOCK_FILE)) fs31.unlinkSync(LOCK_FILE);
254
304
  } catch {
255
305
  }
256
306
  }
@@ -258,15 +308,15 @@ var LOCK_FILE, STALE_MS;
258
308
  var init_lock = __esm({
259
309
  "src/lib/lock.ts"() {
260
310
  "use strict";
261
- LOCK_FILE = path24.join(os6.tmpdir(), ".caliber.lock");
311
+ LOCK_FILE = path25.join(os7.tmpdir(), ".caliber.lock");
262
312
  STALE_MS = 10 * 60 * 1e3;
263
313
  }
264
314
  });
265
315
 
266
316
  // src/cli.ts
267
317
  import { Command } from "commander";
268
- import fs35 from "fs";
269
- import path28 from "path";
318
+ import fs38 from "fs";
319
+ import path30 from "path";
270
320
  import { fileURLToPath } from "url";
271
321
 
272
322
  // src/commands/init.ts
@@ -2568,15 +2618,15 @@ init_config();
2568
2618
  // src/utils/dependencies.ts
2569
2619
  import { readFileSync } from "fs";
2570
2620
  import { join } from "path";
2571
- function readFileOrNull(path30) {
2621
+ function readFileOrNull(path32) {
2572
2622
  try {
2573
- return readFileSync(path30, "utf-8");
2623
+ return readFileSync(path32, "utf-8");
2574
2624
  } catch {
2575
2625
  return null;
2576
2626
  }
2577
2627
  }
2578
- function readJsonOrNull(path30) {
2579
- const content = readFileOrNull(path30);
2628
+ function readJsonOrNull(path32) {
2629
+ const content = readFileOrNull(path32);
2580
2630
  if (!content) return null;
2581
2631
  try {
2582
2632
  return JSON.parse(content);
@@ -3843,50 +3893,10 @@ ${agentRefs.join(" ")}
3843
3893
  }
3844
3894
 
3845
3895
  // src/lib/hooks.ts
3896
+ init_resolve_caliber();
3846
3897
  import fs19 from "fs";
3847
3898
  import path14 from "path";
3848
3899
  import { execSync as execSync8 } from "child_process";
3849
-
3850
- // src/lib/resolve-caliber.ts
3851
- import fs18 from "fs";
3852
- import { execSync as execSync7 } from "child_process";
3853
- var _resolved = null;
3854
- function resolveCaliber() {
3855
- if (_resolved) return _resolved;
3856
- const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
3857
- if (isNpx) {
3858
- _resolved = "npx --yes @rely-ai/caliber";
3859
- return _resolved;
3860
- }
3861
- try {
3862
- const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
3863
- const found = execSync7(whichCmd, {
3864
- encoding: "utf-8",
3865
- stdio: ["pipe", "pipe", "pipe"]
3866
- }).trim();
3867
- if (found) {
3868
- _resolved = found;
3869
- return _resolved;
3870
- }
3871
- } catch {
3872
- }
3873
- const binPath = process.argv[1];
3874
- if (binPath && fs18.existsSync(binPath)) {
3875
- _resolved = binPath;
3876
- return _resolved;
3877
- }
3878
- _resolved = "caliber";
3879
- return _resolved;
3880
- }
3881
- function isCaliberCommand(command, subcommandTail) {
3882
- if (command === `caliber ${subcommandTail}`) return true;
3883
- if (command.endsWith(`/caliber ${subcommandTail}`)) return true;
3884
- if (command === `npx --yes @rely-ai/caliber ${subcommandTail}`) return true;
3885
- if (command === `npx @rely-ai/caliber ${subcommandTail}`) return true;
3886
- return false;
3887
- }
3888
-
3889
- // src/lib/hooks.ts
3890
3900
  var SETTINGS_PATH = path14.join(".claude", "settings.json");
3891
3901
  var REFRESH_TAIL = "refresh --quiet";
3892
3902
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
@@ -4022,6 +4032,7 @@ function removePreCommitHook() {
4022
4032
  }
4023
4033
 
4024
4034
  // src/lib/learning-hooks.ts
4035
+ init_resolve_caliber();
4025
4036
  import fs20 from "fs";
4026
4037
  import path15 from "path";
4027
4038
  var SETTINGS_PATH2 = path15.join(".claude", "settings.json");
@@ -4029,7 +4040,7 @@ var HOOK_TAILS = [
4029
4040
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
4030
4041
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
4031
4042
  { event: "UserPromptSubmit", tail: "learn observe --prompt", description: "Caliber: recording user prompt for correction detection" },
4032
- { event: "SessionEnd", tail: "learn finalize", description: "Caliber: finalizing session learnings" }
4043
+ { event: "SessionEnd", tail: "learn finalize --auto", description: "Caliber: finalizing session learnings" }
4033
4044
  ];
4034
4045
  function getHookConfigs() {
4035
4046
  const bin = resolveCaliber();
@@ -4090,7 +4101,7 @@ var CURSOR_HOOK_EVENTS = [
4090
4101
  { event: "postToolUse", tail: "learn observe" },
4091
4102
  { event: "postToolUseFailure", tail: "learn observe --failure" },
4092
4103
  { event: "userPromptSubmit", tail: "learn observe --prompt" },
4093
- { event: "sessionEnd", tail: "learn finalize" }
4104
+ { event: "sessionEnd", tail: "learn finalize --auto" }
4094
4105
  ];
4095
4106
  function readCursorHooks() {
4096
4107
  if (!fs20.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
@@ -6154,6 +6165,9 @@ function trackLearnNewLearning(props) {
6154
6165
  source_event_count: props.sourceEventCount
6155
6166
  });
6156
6167
  }
6168
+ function trackInsightsViewed(totalSessions, learningCount) {
6169
+ trackEvent("insights_viewed", { total_sessions: totalSessions, learning_count: learningCount });
6170
+ }
6157
6171
 
6158
6172
  // src/commands/recommend.ts
6159
6173
  function detectLocalPlatforms() {
@@ -6941,11 +6955,11 @@ function countIssuePoints(issues) {
6941
6955
  }
6942
6956
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
6943
6957
  const existsCache = /* @__PURE__ */ new Map();
6944
- const cachedExists = (path30) => {
6945
- const cached = existsCache.get(path30);
6958
+ const cachedExists = (path32) => {
6959
+ const cached = existsCache.get(path32);
6946
6960
  if (cached !== void 0) return cached;
6947
- const result = existsSync9(path30);
6948
- existsCache.set(path30, result);
6961
+ const result = existsSync9(path32);
6962
+ existsCache.set(path32, result);
6949
6963
  return result;
6950
6964
  };
6951
6965
  const projectStructure = collectProjectStructure(dir);
@@ -7182,9 +7196,9 @@ var waiting_cards_default = [
7182
7196
  title: "Welcome to Caliber!",
7183
7197
  icon: "?",
7184
7198
  lines: [
7185
- "Caliber is your agentic development companion.",
7186
- "It analyzes your repository, understands how you work,",
7187
- "and adapts your AI agent to fit you and your project."
7199
+ "Caliber is your AI agent's memory. It analyzes your repo,",
7200
+ "learns from every session, and keeps your agent context",
7201
+ "fresh, grounded, and personal to your project."
7188
7202
  ]
7189
7203
  },
7190
7204
  {
@@ -7214,6 +7228,24 @@ var waiting_cards_default = [
7214
7228
  "shaped to your project. Browse more with `caliber skills`."
7215
7229
  ]
7216
7230
  },
7231
+ {
7232
+ title: "Learns from Every Session",
7233
+ icon: "\u2B50",
7234
+ lines: [
7235
+ "Caliber watches your agent work. When something goes wrong",
7236
+ "\u2014 a tool fails, you correct a mistake \u2014 it captures the",
7237
+ "lesson so the same mistake never happens twice."
7238
+ ]
7239
+ },
7240
+ {
7241
+ title: "Gets Smarter Over Time",
7242
+ icon: "\u{1F9E0}",
7243
+ lines: [
7244
+ "Learnings accumulate in CALIBER_LEARNINGS.md and feed back",
7245
+ "into your configs automatically. The more you use your agent,",
7246
+ "the better it gets. Your whole team benefits."
7247
+ ]
7248
+ },
7217
7249
  {
7218
7250
  title: "Stays in Sync",
7219
7251
  icon: "\u21BB",
@@ -7224,12 +7256,21 @@ var waiting_cards_default = [
7224
7256
  ]
7225
7257
  },
7226
7258
  {
7227
- title: "Measures Itself",
7259
+ title: "Runs in CI",
7260
+ icon: "\u2713",
7261
+ lines: [
7262
+ "Add Caliber to your GitHub pipeline. It scores every PR,",
7263
+ "catches config drift, and blocks merges when agent quality",
7264
+ "drops. `caliber score --compare main` shows the delta."
7265
+ ]
7266
+ },
7267
+ {
7268
+ title: "Measures What Matters",
7228
7269
  icon: "\u2605",
7229
7270
  lines: [
7230
- "`caliber score` checks how well your setup serves the agent \u2014",
7231
- "grounding, accuracy, freshness \u2014 and gives a letter grade.",
7232
- "Aim for an A to get the most out of your AI agent."
7271
+ "`caliber score` checks config quality \u2014 grounding, accuracy,",
7272
+ "freshness. `caliber insights` goes further: task success rate,",
7273
+ "failure trends, and proof that your agent is improving."
7233
7274
  ]
7234
7275
  }
7235
7276
  ];
@@ -8126,7 +8167,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
8126
8167
  }
8127
8168
  function summarizeSetup(action, setup) {
8128
8169
  const descriptions = setup.fileDescriptions;
8129
- const files = descriptions ? Object.entries(descriptions).map(([path30, desc]) => ` ${path30}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
8170
+ const files = descriptions ? Object.entries(descriptions).map(([path32, desc]) => ` ${path32}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
8130
8171
  return `${action}. Files:
8131
8172
  ${files}`;
8132
8173
  }
@@ -8231,6 +8272,7 @@ async function promptLearnInstall(targetAgent) {
8231
8272
  `));
8232
8273
  return select5({
8233
8274
  message: "Enable session learning?",
8275
+ default: true,
8234
8276
  choices: [
8235
8277
  { name: "Enable session learning (recommended)", value: true },
8236
8278
  { name: "Skip for now", value: false }
@@ -8657,12 +8699,79 @@ async function regenerateCommand(options) {
8657
8699
  }
8658
8700
 
8659
8701
  // src/commands/score.ts
8702
+ import fs28 from "fs";
8703
+ import os6 from "os";
8704
+ import path22 from "path";
8705
+ import { execFileSync } from "child_process";
8660
8706
  import chalk15 from "chalk";
8707
+ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
8708
+ var CONFIG_DIRS = [".claude", ".cursor"];
8709
+ function scoreBaseRef(ref, target) {
8710
+ if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
8711
+ const tmpDir = fs28.mkdtempSync(path22.join(os6.tmpdir(), "caliber-compare-"));
8712
+ try {
8713
+ for (const file of CONFIG_FILES) {
8714
+ try {
8715
+ const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
8716
+ fs28.writeFileSync(path22.join(tmpDir, file), content);
8717
+ } catch {
8718
+ }
8719
+ }
8720
+ for (const dir of CONFIG_DIRS) {
8721
+ try {
8722
+ const files = execFileSync("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
8723
+ for (const file of files) {
8724
+ const filePath = path22.join(tmpDir, file);
8725
+ fs28.mkdirSync(path22.dirname(filePath), { recursive: true });
8726
+ const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
8727
+ fs28.writeFileSync(filePath, content);
8728
+ }
8729
+ } catch {
8730
+ }
8731
+ }
8732
+ const result = computeLocalScore(tmpDir, target);
8733
+ return { score: result.score, grade: result.grade };
8734
+ } catch {
8735
+ return null;
8736
+ } finally {
8737
+ fs28.rmSync(tmpDir, { recursive: true, force: true });
8738
+ }
8739
+ }
8661
8740
  async function scoreCommand(options) {
8662
8741
  const dir = process.cwd();
8663
8742
  const target = options.agent ?? readState()?.targetAgent;
8664
8743
  const result = computeLocalScore(dir, target);
8665
8744
  trackScoreComputed(result.score, target);
8745
+ if (options.compare) {
8746
+ const baseResult = scoreBaseRef(options.compare, target);
8747
+ if (!baseResult) {
8748
+ console.error(chalk15.red(`Could not score ref "${options.compare}" \u2014 branch or ref not found.`));
8749
+ process.exitCode = 1;
8750
+ return;
8751
+ }
8752
+ const delta = result.score - baseResult.score;
8753
+ if (options.json) {
8754
+ console.log(JSON.stringify({ current: result, base: { score: baseResult.score, grade: baseResult.grade, ref: options.compare }, delta }, null, 2));
8755
+ return;
8756
+ }
8757
+ if (options.quiet) {
8758
+ const sign = delta > 0 ? "+" : "";
8759
+ console.log(`${result.score}/100 (${result.grade}) ${sign}${delta} from ${options.compare}`);
8760
+ return;
8761
+ }
8762
+ displayScore(result);
8763
+ const separator2 = chalk15.gray(" " + "\u2500".repeat(53));
8764
+ console.log(separator2);
8765
+ if (delta > 0) {
8766
+ console.log(chalk15.green(` +${delta}`) + chalk15.gray(` from ${options.compare} (${baseResult.score}/100)`));
8767
+ } else if (delta < 0) {
8768
+ console.log(chalk15.red(` ${delta}`) + chalk15.gray(` from ${options.compare} (${baseResult.score}/100)`));
8769
+ } else {
8770
+ console.log(chalk15.gray(` No change from ${options.compare} (${baseResult.score}/100)`));
8771
+ }
8772
+ console.log("");
8773
+ return;
8774
+ }
8666
8775
  if (options.json) {
8667
8776
  console.log(JSON.stringify(result, null, 2));
8668
8777
  return;
@@ -8685,8 +8794,8 @@ async function scoreCommand(options) {
8685
8794
  }
8686
8795
 
8687
8796
  // src/commands/refresh.ts
8688
- import fs31 from "fs";
8689
- import path25 from "path";
8797
+ import fs32 from "fs";
8798
+ import path26 from "path";
8690
8799
  import chalk16 from "chalk";
8691
8800
  import ora6 from "ora";
8692
8801
 
@@ -8764,37 +8873,37 @@ function collectDiff(lastSha) {
8764
8873
  }
8765
8874
 
8766
8875
  // src/writers/refresh.ts
8767
- import fs28 from "fs";
8768
- import path22 from "path";
8876
+ import fs29 from "fs";
8877
+ import path23 from "path";
8769
8878
  function writeRefreshDocs(docs) {
8770
8879
  const written = [];
8771
8880
  if (docs.claudeMd) {
8772
- fs28.writeFileSync("CLAUDE.md", docs.claudeMd);
8881
+ fs29.writeFileSync("CLAUDE.md", docs.claudeMd);
8773
8882
  written.push("CLAUDE.md");
8774
8883
  }
8775
8884
  if (docs.readmeMd) {
8776
- fs28.writeFileSync("README.md", docs.readmeMd);
8885
+ fs29.writeFileSync("README.md", docs.readmeMd);
8777
8886
  written.push("README.md");
8778
8887
  }
8779
8888
  if (docs.cursorrules) {
8780
- fs28.writeFileSync(".cursorrules", docs.cursorrules);
8889
+ fs29.writeFileSync(".cursorrules", docs.cursorrules);
8781
8890
  written.push(".cursorrules");
8782
8891
  }
8783
8892
  if (docs.cursorRules) {
8784
- const rulesDir = path22.join(".cursor", "rules");
8785
- if (!fs28.existsSync(rulesDir)) fs28.mkdirSync(rulesDir, { recursive: true });
8893
+ const rulesDir = path23.join(".cursor", "rules");
8894
+ if (!fs29.existsSync(rulesDir)) fs29.mkdirSync(rulesDir, { recursive: true });
8786
8895
  for (const rule of docs.cursorRules) {
8787
- const filePath = path22.join(rulesDir, rule.filename);
8788
- fs28.writeFileSync(filePath, rule.content);
8896
+ const filePath = path23.join(rulesDir, rule.filename);
8897
+ fs29.writeFileSync(filePath, rule.content);
8789
8898
  written.push(filePath);
8790
8899
  }
8791
8900
  }
8792
8901
  if (docs.claudeSkills) {
8793
- const skillsDir = path22.join(".claude", "skills");
8794
- if (!fs28.existsSync(skillsDir)) fs28.mkdirSync(skillsDir, { recursive: true });
8902
+ const skillsDir = path23.join(".claude", "skills");
8903
+ if (!fs29.existsSync(skillsDir)) fs29.mkdirSync(skillsDir, { recursive: true });
8795
8904
  for (const skill of docs.claudeSkills) {
8796
- const filePath = path22.join(skillsDir, skill.filename);
8797
- fs28.writeFileSync(filePath, skill.content);
8905
+ const filePath = path23.join(skillsDir, skill.filename);
8906
+ fs29.writeFileSync(filePath, skill.content);
8798
8907
  written.push(filePath);
8799
8908
  }
8800
8909
  }
@@ -8871,8 +8980,29 @@ Changed files: ${diff.changedFiles.join(", ")}`);
8871
8980
  }
8872
8981
 
8873
8982
  // src/learner/writer.ts
8874
- import fs29 from "fs";
8875
- import path23 from "path";
8983
+ import fs30 from "fs";
8984
+ import path24 from "path";
8985
+
8986
+ // src/learner/utils.ts
8987
+ var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
8988
+ function normalizeBullet(bullet) {
8989
+ return bullet.replace(/^- /, "").replace(TYPE_PREFIX_RE, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
8990
+ }
8991
+ function hasTypePrefix(bullet) {
8992
+ return TYPE_PREFIX_RE.test(bullet.replace(/^- /, ""));
8993
+ }
8994
+ var SIMILARITY_THRESHOLD = 0.7;
8995
+ function isSimilarLearning(a, b) {
8996
+ const normA = normalizeBullet(a);
8997
+ const normB = normalizeBullet(b);
8998
+ if (!normA || !normB) return false;
8999
+ const shorter = Math.min(normA.length, normB.length);
9000
+ const longer = Math.max(normA.length, normB.length);
9001
+ if (!(normA.includes(normB) || normB.includes(normA))) return false;
9002
+ return shorter / longer > SIMILARITY_THRESHOLD;
9003
+ }
9004
+
9005
+ // src/learner/writer.ts
8876
9006
  var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
8877
9007
  var LEARNINGS_HEADER = `# Caliber Learnings
8878
9008
 
@@ -8919,13 +9049,6 @@ function parseBullets(content) {
8919
9049
  if (current) bullets.push(current);
8920
9050
  return bullets;
8921
9051
  }
8922
- var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
8923
- function normalizeBullet(bullet) {
8924
- return bullet.replace(/^- /, "").replace(TYPE_PREFIX_RE, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
8925
- }
8926
- function hasTypePrefix(bullet) {
8927
- return TYPE_PREFIX_RE.test(bullet.replace(/^- /, ""));
8928
- }
8929
9052
  function deduplicateLearnedItems(existing, incoming) {
8930
9053
  const existingBullets = existing ? parseBullets(existing) : [];
8931
9054
  const incomingBullets = parseBullets(incoming);
@@ -8934,13 +9057,7 @@ function deduplicateLearnedItems(existing, incoming) {
8934
9057
  for (const bullet of incomingBullets) {
8935
9058
  const norm = normalizeBullet(bullet);
8936
9059
  if (!norm) continue;
8937
- const dupIdx = merged.findIndex((e) => {
8938
- const eNorm = normalizeBullet(e);
8939
- const shorter = Math.min(norm.length, eNorm.length);
8940
- const longer = Math.max(norm.length, eNorm.length);
8941
- if (!(eNorm.includes(norm) || norm.includes(eNorm))) return false;
8942
- return shorter / longer > 0.7;
8943
- });
9060
+ const dupIdx = merged.findIndex((e) => isSimilarLearning(bullet, e));
8944
9061
  if (dupIdx !== -1) {
8945
9062
  if (hasTypePrefix(bullet) && !hasTypePrefix(merged[dupIdx])) {
8946
9063
  merged[dupIdx] = bullet;
@@ -8956,16 +9073,16 @@ function deduplicateLearnedItems(existing, incoming) {
8956
9073
  function writeLearnedSection(content) {
8957
9074
  const existingSection = readLearnedSection();
8958
9075
  const { merged, newCount, newItems } = deduplicateLearnedItems(existingSection, content);
8959
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
9076
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
8960
9077
  return { newCount, newItems };
8961
9078
  }
8962
9079
  function writeLearnedSkill(skill) {
8963
- const skillDir = path23.join(".claude", "skills", skill.name);
8964
- if (!fs29.existsSync(skillDir)) fs29.mkdirSync(skillDir, { recursive: true });
8965
- const skillPath = path23.join(skillDir, "SKILL.md");
8966
- if (!skill.isNew && fs29.existsSync(skillPath)) {
8967
- const existing = fs29.readFileSync(skillPath, "utf-8");
8968
- fs29.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
9080
+ const skillDir = path24.join(".claude", "skills", skill.name);
9081
+ if (!fs30.existsSync(skillDir)) fs30.mkdirSync(skillDir, { recursive: true });
9082
+ const skillPath = path24.join(skillDir, "SKILL.md");
9083
+ if (!skill.isNew && fs30.existsSync(skillPath)) {
9084
+ const existing = fs30.readFileSync(skillPath, "utf-8");
9085
+ fs30.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
8969
9086
  } else {
8970
9087
  const frontmatter = [
8971
9088
  "---",
@@ -8974,37 +9091,37 @@ function writeLearnedSkill(skill) {
8974
9091
  "---",
8975
9092
  ""
8976
9093
  ].join("\n");
8977
- fs29.writeFileSync(skillPath, frontmatter + skill.content);
9094
+ fs30.writeFileSync(skillPath, frontmatter + skill.content);
8978
9095
  }
8979
9096
  return skillPath;
8980
9097
  }
8981
9098
  function readLearnedSection() {
8982
- if (fs29.existsSync(LEARNINGS_FILE)) {
8983
- const content2 = fs29.readFileSync(LEARNINGS_FILE, "utf-8");
9099
+ if (fs30.existsSync(LEARNINGS_FILE)) {
9100
+ const content2 = fs30.readFileSync(LEARNINGS_FILE, "utf-8");
8984
9101
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
8985
9102
  return bullets || null;
8986
9103
  }
8987
9104
  const claudeMdPath = "CLAUDE.md";
8988
- if (!fs29.existsSync(claudeMdPath)) return null;
8989
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9105
+ if (!fs30.existsSync(claudeMdPath)) return null;
9106
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
8990
9107
  const startIdx = content.indexOf(LEARNED_START);
8991
9108
  const endIdx = content.indexOf(LEARNED_END);
8992
9109
  if (startIdx === -1 || endIdx === -1) return null;
8993
9110
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
8994
9111
  }
8995
9112
  function migrateInlineLearnings() {
8996
- if (fs29.existsSync(LEARNINGS_FILE)) return false;
9113
+ if (fs30.existsSync(LEARNINGS_FILE)) return false;
8997
9114
  const claudeMdPath = "CLAUDE.md";
8998
- if (!fs29.existsSync(claudeMdPath)) return false;
8999
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9115
+ if (!fs30.existsSync(claudeMdPath)) return false;
9116
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
9000
9117
  const startIdx = content.indexOf(LEARNED_START);
9001
9118
  const endIdx = content.indexOf(LEARNED_END);
9002
9119
  if (startIdx === -1 || endIdx === -1) return false;
9003
9120
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
9004
9121
  if (!section) return false;
9005
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9122
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9006
9123
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
9007
- fs29.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
9124
+ fs30.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
9008
9125
  return true;
9009
9126
  }
9010
9127
 
@@ -9016,11 +9133,11 @@ function log2(quiet, ...args) {
9016
9133
  function discoverGitRepos(parentDir) {
9017
9134
  const repos = [];
9018
9135
  try {
9019
- const entries = fs31.readdirSync(parentDir, { withFileTypes: true });
9136
+ const entries = fs32.readdirSync(parentDir, { withFileTypes: true });
9020
9137
  for (const entry of entries) {
9021
9138
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
9022
- const childPath = path25.join(parentDir, entry.name);
9023
- if (fs31.existsSync(path25.join(childPath, ".git"))) {
9139
+ const childPath = path26.join(parentDir, entry.name);
9140
+ if (fs32.existsSync(path26.join(childPath, ".git"))) {
9024
9141
  repos.push(childPath);
9025
9142
  }
9026
9143
  }
@@ -9123,7 +9240,7 @@ async function refreshCommand(options) {
9123
9240
  `));
9124
9241
  const originalDir = process.cwd();
9125
9242
  for (const repo of repos) {
9126
- const repoName = path25.basename(repo);
9243
+ const repoName = path26.basename(repo);
9127
9244
  try {
9128
9245
  process.chdir(repo);
9129
9246
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -9144,6 +9261,7 @@ async function refreshCommand(options) {
9144
9261
 
9145
9262
  // src/commands/hooks.ts
9146
9263
  import chalk17 from "chalk";
9264
+ import fs33 from "fs";
9147
9265
  var HOOKS = [
9148
9266
  {
9149
9267
  id: "session-end",
@@ -9183,6 +9301,14 @@ async function hooksCommand(options) {
9183
9301
  console.log(chalk17.green(" \u2713") + ` ${hook.label} enabled`);
9184
9302
  }
9185
9303
  }
9304
+ if (fs33.existsSync(".claude")) {
9305
+ const r = installLearningHooks();
9306
+ if (r.installed) console.log(chalk17.green(" \u2713") + " Claude Code learning hooks enabled");
9307
+ }
9308
+ if (fs33.existsSync(".cursor")) {
9309
+ const r = installCursorLearningHooks();
9310
+ if (r.installed) console.log(chalk17.green(" \u2713") + " Cursor learning hooks enabled");
9311
+ }
9186
9312
  return;
9187
9313
  }
9188
9314
  if (options.remove) {
@@ -9347,8 +9473,8 @@ async function configCommand() {
9347
9473
  }
9348
9474
 
9349
9475
  // src/commands/learn.ts
9350
- import fs34 from "fs";
9351
- import chalk19 from "chalk";
9476
+ import fs37 from "fs";
9477
+ import chalk20 from "chalk";
9352
9478
 
9353
9479
  // src/learner/stdin.ts
9354
9480
  var STDIN_TIMEOUT_MS = 5e3;
@@ -9379,24 +9505,25 @@ function readStdin() {
9379
9505
 
9380
9506
  // src/learner/storage.ts
9381
9507
  init_constants();
9382
- import fs32 from "fs";
9383
- import path26 from "path";
9508
+ import fs34 from "fs";
9509
+ import path27 from "path";
9384
9510
  var MAX_RESPONSE_LENGTH = 2e3;
9385
9511
  var DEFAULT_STATE = {
9386
9512
  sessionId: null,
9387
9513
  eventCount: 0,
9388
- lastAnalysisTimestamp: null
9514
+ lastAnalysisTimestamp: null,
9515
+ lastAnalysisEventCount: 0
9389
9516
  };
9390
9517
  function ensureLearningDir() {
9391
- if (!fs32.existsSync(LEARNING_DIR)) {
9392
- fs32.mkdirSync(LEARNING_DIR, { recursive: true });
9518
+ if (!fs34.existsSync(LEARNING_DIR)) {
9519
+ fs34.mkdirSync(LEARNING_DIR, { recursive: true });
9393
9520
  }
9394
9521
  }
9395
9522
  function sessionFilePath() {
9396
- return path26.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9523
+ return path27.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9397
9524
  }
9398
9525
  function stateFilePath() {
9399
- return path26.join(LEARNING_DIR, LEARNING_STATE_FILE);
9526
+ return path27.join(LEARNING_DIR, LEARNING_STATE_FILE);
9400
9527
  }
9401
9528
  function truncateResponse(response) {
9402
9529
  const str = JSON.stringify(response);
@@ -9407,29 +9534,29 @@ function appendEvent(event) {
9407
9534
  ensureLearningDir();
9408
9535
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
9409
9536
  const filePath = sessionFilePath();
9410
- fs32.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9537
+ fs34.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9411
9538
  const count = getEventCount();
9412
9539
  if (count > LEARNING_MAX_EVENTS) {
9413
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9540
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9414
9541
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9415
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9542
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9416
9543
  }
9417
9544
  }
9418
9545
  function appendPromptEvent(event) {
9419
9546
  ensureLearningDir();
9420
9547
  const filePath = sessionFilePath();
9421
- fs32.appendFileSync(filePath, JSON.stringify(event) + "\n");
9548
+ fs34.appendFileSync(filePath, JSON.stringify(event) + "\n");
9422
9549
  const count = getEventCount();
9423
9550
  if (count > LEARNING_MAX_EVENTS) {
9424
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9551
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9425
9552
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9426
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9553
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9427
9554
  }
9428
9555
  }
9429
9556
  function readAllEvents() {
9430
9557
  const filePath = sessionFilePath();
9431
- if (!fs32.existsSync(filePath)) return [];
9432
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9558
+ if (!fs34.existsSync(filePath)) return [];
9559
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9433
9560
  const events = [];
9434
9561
  for (const line of lines) {
9435
9562
  try {
@@ -9441,26 +9568,26 @@ function readAllEvents() {
9441
9568
  }
9442
9569
  function getEventCount() {
9443
9570
  const filePath = sessionFilePath();
9444
- if (!fs32.existsSync(filePath)) return 0;
9445
- const content = fs32.readFileSync(filePath, "utf-8");
9571
+ if (!fs34.existsSync(filePath)) return 0;
9572
+ const content = fs34.readFileSync(filePath, "utf-8");
9446
9573
  return content.split("\n").filter(Boolean).length;
9447
9574
  }
9448
9575
  function clearSession() {
9449
9576
  const filePath = sessionFilePath();
9450
- if (fs32.existsSync(filePath)) fs32.unlinkSync(filePath);
9577
+ if (fs34.existsSync(filePath)) fs34.unlinkSync(filePath);
9451
9578
  }
9452
9579
  function readState2() {
9453
9580
  const filePath = stateFilePath();
9454
- if (!fs32.existsSync(filePath)) return { ...DEFAULT_STATE };
9581
+ if (!fs34.existsSync(filePath)) return { ...DEFAULT_STATE };
9455
9582
  try {
9456
- return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
9583
+ return JSON.parse(fs34.readFileSync(filePath, "utf-8"));
9457
9584
  } catch {
9458
9585
  return { ...DEFAULT_STATE };
9459
9586
  }
9460
9587
  }
9461
9588
  function writeState2(state) {
9462
9589
  ensureLearningDir();
9463
- fs32.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9590
+ fs34.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9464
9591
  }
9465
9592
  function resetState() {
9466
9593
  writeState2({ ...DEFAULT_STATE });
@@ -9468,14 +9595,14 @@ function resetState() {
9468
9595
  var LOCK_FILE2 = "finalize.lock";
9469
9596
  var LOCK_STALE_MS = 5 * 60 * 1e3;
9470
9597
  function lockFilePath() {
9471
- return path26.join(LEARNING_DIR, LOCK_FILE2);
9598
+ return path27.join(LEARNING_DIR, LOCK_FILE2);
9472
9599
  }
9473
9600
  function acquireFinalizeLock() {
9474
9601
  ensureLearningDir();
9475
9602
  const lockPath = lockFilePath();
9476
- if (fs32.existsSync(lockPath)) {
9603
+ if (fs34.existsSync(lockPath)) {
9477
9604
  try {
9478
- const stat = fs32.statSync(lockPath);
9605
+ const stat = fs34.statSync(lockPath);
9479
9606
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
9480
9607
  return false;
9481
9608
  }
@@ -9483,7 +9610,7 @@ function acquireFinalizeLock() {
9483
9610
  }
9484
9611
  }
9485
9612
  try {
9486
- fs32.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9613
+ fs34.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9487
9614
  return true;
9488
9615
  } catch {
9489
9616
  return false;
@@ -9492,7 +9619,7 @@ function acquireFinalizeLock() {
9492
9619
  function releaseFinalizeLock() {
9493
9620
  const lockPath = lockFilePath();
9494
9621
  try {
9495
- if (fs32.existsSync(lockPath)) fs32.unlinkSync(lockPath);
9622
+ if (fs34.existsSync(lockPath)) fs34.unlinkSync(lockPath);
9496
9623
  } catch {
9497
9624
  }
9498
9625
  }
@@ -9536,6 +9663,45 @@ function sanitizeSecrets(text) {
9536
9663
  return result;
9537
9664
  }
9538
9665
 
9666
+ // src/lib/notifications.ts
9667
+ init_constants();
9668
+ import fs35 from "fs";
9669
+ import path28 from "path";
9670
+ import chalk19 from "chalk";
9671
+ var NOTIFICATION_FILE = path28.join(LEARNING_DIR, "last-finalize-summary.json");
9672
+ function writeFinalizeSummary(summary) {
9673
+ try {
9674
+ ensureLearningDir();
9675
+ fs35.writeFileSync(NOTIFICATION_FILE, JSON.stringify(summary, null, 2));
9676
+ } catch {
9677
+ }
9678
+ }
9679
+ function checkPendingNotifications() {
9680
+ try {
9681
+ if (!fs35.existsSync(NOTIFICATION_FILE)) return;
9682
+ const raw = fs35.readFileSync(NOTIFICATION_FILE, "utf-8");
9683
+ fs35.unlinkSync(NOTIFICATION_FILE);
9684
+ const summary = JSON.parse(raw);
9685
+ if (!summary.newItemCount || summary.newItemCount === 0) return;
9686
+ const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
9687
+ console.log(
9688
+ chalk19.dim(`caliber: learned ${summary.newItemCount} new pattern${summary.newItemCount === 1 ? "" : "s"} from your last session${wasteLabel}`)
9689
+ );
9690
+ for (const item of summary.newItems.slice(0, 3)) {
9691
+ console.log(chalk19.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
9692
+ }
9693
+ if (summary.newItems.length > 3) {
9694
+ console.log(chalk19.dim(` ... and ${summary.newItems.length - 3} more`));
9695
+ }
9696
+ console.log("");
9697
+ } catch {
9698
+ try {
9699
+ fs35.unlinkSync(NOTIFICATION_FILE);
9700
+ } catch {
9701
+ }
9702
+ }
9703
+ }
9704
+
9539
9705
  // src/ai/learn.ts
9540
9706
  init_config();
9541
9707
  var MAX_PROMPT_TOKENS = 1e5;
@@ -9605,14 +9771,29 @@ ${existingLearnedSection}`);
9605
9771
 
9606
9772
  ${skillsSummary}`);
9607
9773
  }
9608
- const prompt = `${contextParts.length ? contextParts.join("\n\n---\n\n") + "\n\n---\n\n" : ""}## Tool Events from Session (${fittedEvents.length} events)
9774
+ contextParts.push(`## Task Segmentation & Attribution Instructions
9775
+
9776
+ Analyze the event timeline and identify logical tasks (a task = one user intent, from their prompt through the agent's work until the next user prompt or session end).
9777
+
9778
+ For each task, determine:
9779
+ - "summary": what the user was trying to accomplish (1 sentence)
9780
+ - "outcome": "success" (completed without issues), "corrected" (user had to redirect the agent), or "failed" (task was abandoned or produced errors)
9781
+ - "startEventIdx" and "endEventIdx": 0-based indices in the event list
9782
+ - "attribution": if the task was corrected or failed, identify which section of CLAUDE.md (by ## heading) SHOULD have contained guidance that would have prevented the issue. Include "configSection" (the heading text) and "relevance" (0-1).
9783
+
9784
+ Include the "tasks" array in your JSON response alongside "claudeMdLearnedSection", "skills", and "explanations".`);
9785
+ const prompt = `${contextParts.join("\n\n---\n\n")}
9786
+
9787
+ ---
9788
+
9789
+ ## Tool Events from Session (${fittedEvents.length} events)
9609
9790
 
9610
9791
  ${eventsText}`;
9611
9792
  const fastModel = getFastModel();
9612
9793
  const raw = await llmCall({
9613
9794
  system: LEARN_SYSTEM_PROMPT,
9614
9795
  prompt,
9615
- maxTokens: 4096,
9796
+ maxTokens: 8192,
9616
9797
  ...fastModel ? { model: fastModel } : {}
9617
9798
  });
9618
9799
  return parseAnalysisResponse(raw);
@@ -9648,8 +9829,8 @@ init_config();
9648
9829
 
9649
9830
  // src/learner/roi.ts
9650
9831
  init_constants();
9651
- import fs33 from "fs";
9652
- import path27 from "path";
9832
+ import fs36 from "fs";
9833
+ import path29 from "path";
9653
9834
  var DEFAULT_TOTALS = {
9654
9835
  totalWasteTokens: 0,
9655
9836
  totalWasteSeconds: 0,
@@ -9663,22 +9844,22 @@ var DEFAULT_TOTALS = {
9663
9844
  lastSessionTimestamp: ""
9664
9845
  };
9665
9846
  function roiFilePath() {
9666
- return path27.join(LEARNING_DIR, LEARNING_ROI_FILE);
9847
+ return path29.join(LEARNING_DIR, LEARNING_ROI_FILE);
9667
9848
  }
9668
9849
  function readROIStats() {
9669
9850
  const filePath = roiFilePath();
9670
- if (!fs33.existsSync(filePath)) {
9851
+ if (!fs36.existsSync(filePath)) {
9671
9852
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9672
9853
  }
9673
9854
  try {
9674
- return JSON.parse(fs33.readFileSync(filePath, "utf-8"));
9855
+ return JSON.parse(fs36.readFileSync(filePath, "utf-8"));
9675
9856
  } catch {
9676
9857
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9677
9858
  }
9678
9859
  }
9679
9860
  function writeROIStats(stats) {
9680
9861
  ensureLearningDir();
9681
- fs33.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9862
+ fs36.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9682
9863
  }
9683
9864
  function recalculateTotals(stats) {
9684
9865
  const totals = stats.totals;
@@ -9711,7 +9892,16 @@ function recordSession(summary, learnings) {
9711
9892
  const stats = readROIStats();
9712
9893
  stats.sessions.push(summary);
9713
9894
  if (learnings?.length) {
9714
- stats.learnings.push(...learnings);
9895
+ for (const entry of learnings) {
9896
+ const existingIdx = stats.learnings.findIndex((e) => isSimilarLearning(e.summary, entry.summary));
9897
+ if (existingIdx !== -1) {
9898
+ stats.learnings[existingIdx].occurrences = (stats.learnings[existingIdx].occurrences || 1) + 1;
9899
+ stats.learnings[existingIdx].timestamp = entry.timestamp;
9900
+ } else {
9901
+ entry.occurrences = 1;
9902
+ stats.learnings.push(entry);
9903
+ }
9904
+ }
9715
9905
  }
9716
9906
  if (stats.sessions.length > MAX_SESSIONS) {
9717
9907
  stats.sessions = stats.sessions.slice(-MAX_SESSIONS);
@@ -9760,6 +9950,9 @@ function formatROISummary(stats) {
9760
9950
 
9761
9951
  // src/commands/learn.ts
9762
9952
  var MIN_EVENTS_FOR_ANALYSIS = 25;
9953
+ var MIN_EVENTS_AUTO = 10;
9954
+ var AUTO_SETTLE_MS = 200;
9955
+ var INCREMENTAL_INTERVAL = 50;
9763
9956
  async function learnObserveCommand(options) {
9764
9957
  try {
9765
9958
  const raw = await readStdin();
@@ -9796,33 +9989,53 @@ async function learnObserveCommand(options) {
9796
9989
  state.eventCount++;
9797
9990
  if (!state.sessionId) state.sessionId = sessionId;
9798
9991
  writeState2(state);
9992
+ const eventsSinceLastAnalysis = state.eventCount - (state.lastAnalysisEventCount || 0);
9993
+ if (eventsSinceLastAnalysis >= INCREMENTAL_INTERVAL) {
9994
+ try {
9995
+ const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
9996
+ const bin = resolveCaliber2();
9997
+ const { spawn: spawn4 } = await import("child_process");
9998
+ spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
9999
+ detached: true,
10000
+ stdio: "ignore"
10001
+ }).unref();
10002
+ } catch {
10003
+ }
10004
+ }
9799
10005
  } catch {
9800
10006
  }
9801
10007
  }
9802
10008
  async function learnFinalizeCommand(options) {
9803
- if (!options?.force) {
10009
+ const isAuto = options?.auto === true;
10010
+ const isIncremental = options?.incremental === true;
10011
+ if (!options?.force && !isAuto) {
9804
10012
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
9805
10013
  if (isCaliberRunning2()) {
9806
- console.log(chalk19.dim("caliber: skipping finalize \u2014 another caliber process is running"));
10014
+ if (!isAuto) console.log(chalk20.dim("caliber: skipping finalize \u2014 another caliber process is running"));
9807
10015
  return;
9808
10016
  }
9809
10017
  }
10018
+ if (isAuto) {
10019
+ await new Promise((r) => setTimeout(r, AUTO_SETTLE_MS));
10020
+ }
9810
10021
  if (!acquireFinalizeLock()) {
9811
- console.log(chalk19.dim("caliber: skipping finalize \u2014 another finalize is in progress"));
10022
+ if (!isAuto) console.log(chalk20.dim("caliber: skipping finalize \u2014 another finalize is in progress"));
9812
10023
  return;
9813
10024
  }
9814
10025
  let analyzed = false;
9815
10026
  try {
9816
10027
  const config = loadConfig();
9817
10028
  if (!config) {
9818
- console.log(chalk19.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
10029
+ if (isAuto) return;
10030
+ console.log(chalk20.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
9819
10031
  clearSession();
9820
10032
  resetState();
9821
10033
  return;
9822
10034
  }
9823
10035
  const events = readAllEvents();
9824
- if (events.length < MIN_EVENTS_FOR_ANALYSIS) {
9825
- console.log(chalk19.dim(`caliber: ${events.length}/${MIN_EVENTS_FOR_ANALYSIS} events recorded \u2014 need more before analysis`));
10036
+ const threshold = isAuto ? MIN_EVENTS_AUTO : MIN_EVENTS_FOR_ANALYSIS;
10037
+ if (events.length < threshold) {
10038
+ if (!isAuto) console.log(chalk20.dim(`caliber: ${events.length}/${threshold} events recorded \u2014 need more before analysis`));
9826
10039
  return;
9827
10040
  }
9828
10041
  await validateModel({ fast: true });
@@ -9849,10 +10062,19 @@ async function learnFinalizeCommand(options) {
9849
10062
  });
9850
10063
  newLearningsProduced = result.newItemCount;
9851
10064
  if (result.newItemCount > 0) {
9852
- const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
9853
- console.log(chalk19.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
9854
- for (const item of result.newItems) {
9855
- console.log(chalk19.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
10065
+ if (isAuto) {
10066
+ writeFinalizeSummary({
10067
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10068
+ newItemCount: result.newItemCount,
10069
+ newItems: result.newItems,
10070
+ wasteTokens: waste.totalWasteTokens
10071
+ });
10072
+ } else {
10073
+ const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
10074
+ console.log(chalk20.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
10075
+ for (const item of result.newItems) {
10076
+ console.log(chalk20.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
10077
+ }
9856
10078
  }
9857
10079
  const wastePerLearning = Math.round(waste.totalWasteTokens / result.newItemCount);
9858
10080
  const TYPE_RE = /^\*\*\[([^\]]+)\]\*\*/;
@@ -9877,6 +10099,15 @@ async function learnFinalizeCommand(options) {
9877
10099
  roiLearningEntries = learningEntries;
9878
10100
  }
9879
10101
  }
10102
+ const tasks = response.tasks || [];
10103
+ let taskSuccessCount = 0;
10104
+ let taskCorrectionCount = 0;
10105
+ let taskFailureCount = 0;
10106
+ for (const t2 of tasks) {
10107
+ if (t2.outcome === "success") taskSuccessCount++;
10108
+ else if (t2.outcome === "corrected") taskCorrectionCount++;
10109
+ else if (t2.outcome === "failed") taskFailureCount++;
10110
+ }
9880
10111
  const sessionSummary = {
9881
10112
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9882
10113
  sessionId: readState2().sessionId || "unknown",
@@ -9886,7 +10117,11 @@ async function learnFinalizeCommand(options) {
9886
10117
  wasteSeconds: Math.round(waste.totalWasteSeconds),
9887
10118
  hadLearningsAvailable: hadLearnings,
9888
10119
  learningsCount: existingLearnedItems,
9889
- newLearningsProduced
10120
+ newLearningsProduced,
10121
+ taskCount: tasks.length > 0 ? tasks.length : void 0,
10122
+ taskSuccessCount: tasks.length > 0 ? taskSuccessCount : void 0,
10123
+ taskCorrectionCount: tasks.length > 0 ? taskCorrectionCount : void 0,
10124
+ taskFailureCount: tasks.length > 0 ? taskFailureCount : void 0
9890
10125
  };
9891
10126
  const roiStats = recordSession(sessionSummary, roiLearningEntries);
9892
10127
  trackLearnSessionAnalyzed({
@@ -9913,66 +10148,73 @@ async function learnFinalizeCommand(options) {
9913
10148
  estimatedSavingsSeconds: t.estimatedSavingsSeconds,
9914
10149
  learningCount: roiStats.learnings.length
9915
10150
  });
9916
- if (t.estimatedSavingsTokens > 0) {
10151
+ if (!isAuto && t.estimatedSavingsTokens > 0) {
9917
10152
  const totalLearnings = existingLearnedItems + newLearningsProduced;
9918
- console.log(chalk19.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
10153
+ console.log(chalk20.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
9919
10154
  }
9920
10155
  } catch (err) {
9921
- if (options?.force) {
9922
- console.error(chalk19.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
10156
+ if (options?.force && !isAuto) {
10157
+ console.error(chalk20.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
9923
10158
  }
9924
10159
  } finally {
9925
10160
  if (analyzed) {
9926
- clearSession();
9927
- resetState();
10161
+ if (isIncremental) {
10162
+ const state = readState2();
10163
+ state.lastAnalysisEventCount = state.eventCount;
10164
+ state.lastAnalysisTimestamp = (/* @__PURE__ */ new Date()).toISOString();
10165
+ writeState2(state);
10166
+ } else {
10167
+ clearSession();
10168
+ resetState();
10169
+ }
9928
10170
  }
9929
10171
  releaseFinalizeLock();
9930
10172
  }
9931
10173
  }
9932
10174
  async function learnInstallCommand() {
9933
10175
  let anyInstalled = false;
9934
- if (fs34.existsSync(".claude")) {
10176
+ if (fs37.existsSync(".claude")) {
9935
10177
  const r = installLearningHooks();
9936
10178
  if (r.installed) {
9937
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks installed");
10179
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks installed");
9938
10180
  anyInstalled = true;
9939
10181
  } else if (r.alreadyInstalled) {
9940
- console.log(chalk19.dim(" Claude Code hooks already installed"));
10182
+ console.log(chalk20.dim(" Claude Code hooks already installed"));
9941
10183
  }
9942
10184
  }
9943
- if (fs34.existsSync(".cursor")) {
10185
+ if (fs37.existsSync(".cursor")) {
9944
10186
  const r = installCursorLearningHooks();
9945
10187
  if (r.installed) {
9946
- console.log(chalk19.green("\u2713") + " Cursor learning hooks installed");
10188
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks installed");
9947
10189
  anyInstalled = true;
9948
10190
  } else if (r.alreadyInstalled) {
9949
- console.log(chalk19.dim(" Cursor hooks already installed"));
10191
+ console.log(chalk20.dim(" Cursor hooks already installed"));
9950
10192
  }
9951
10193
  }
9952
- if (!fs34.existsSync(".claude") && !fs34.existsSync(".cursor")) {
9953
- console.log(chalk19.yellow("No .claude/ or .cursor/ directory found."));
9954
- console.log(chalk19.dim(" Run `caliber init` first, or create the directory manually."));
10194
+ if (!fs37.existsSync(".claude") && !fs37.existsSync(".cursor")) {
10195
+ console.log(chalk20.yellow("No .claude/ or .cursor/ directory found."));
10196
+ console.log(chalk20.dim(" Run `caliber init` first, or create the directory manually."));
9955
10197
  return;
9956
10198
  }
9957
10199
  if (anyInstalled) {
9958
- console.log(chalk19.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
9959
- console.log(chalk19.dim(" Learnings written to CALIBER_LEARNINGS.md."));
10200
+ console.log(chalk20.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
10201
+ console.log(chalk20.dim(" Learnings written to CALIBER_LEARNINGS.md."));
9960
10202
  }
9961
10203
  }
9962
10204
  async function learnRemoveCommand() {
9963
10205
  let anyRemoved = false;
9964
10206
  const r1 = removeLearningHooks();
9965
10207
  if (r1.removed) {
9966
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks removed");
10208
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks removed");
9967
10209
  anyRemoved = true;
9968
10210
  }
9969
10211
  const r2 = removeCursorLearningHooks();
9970
10212
  if (r2.removed) {
9971
- console.log(chalk19.green("\u2713") + " Cursor learning hooks removed");
10213
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks removed");
9972
10214
  anyRemoved = true;
9973
10215
  }
9974
10216
  if (!anyRemoved) {
9975
- console.log(chalk19.dim("No learning hooks found."));
10217
+ console.log(chalk20.dim("No learning hooks found."));
9976
10218
  }
9977
10219
  }
9978
10220
  async function learnStatusCommand() {
@@ -9980,50 +10222,178 @@ async function learnStatusCommand() {
9980
10222
  const cursorInstalled = areCursorLearningHooksInstalled();
9981
10223
  const state = readState2();
9982
10224
  const eventCount = getEventCount();
9983
- console.log(chalk19.bold("Session Learning Status"));
10225
+ console.log(chalk20.bold("Session Learning Status"));
9984
10226
  console.log();
9985
10227
  if (claudeInstalled) {
9986
- console.log(chalk19.green("\u2713") + " Claude Code hooks " + chalk19.green("installed"));
10228
+ console.log(chalk20.green("\u2713") + " Claude Code hooks " + chalk20.green("installed"));
9987
10229
  } else {
9988
- console.log(chalk19.dim("\u2717") + " Claude Code hooks " + chalk19.dim("not installed"));
10230
+ console.log(chalk20.dim("\u2717") + " Claude Code hooks " + chalk20.dim("not installed"));
9989
10231
  }
9990
10232
  if (cursorInstalled) {
9991
- console.log(chalk19.green("\u2713") + " Cursor hooks " + chalk19.green("installed"));
10233
+ console.log(chalk20.green("\u2713") + " Cursor hooks " + chalk20.green("installed"));
9992
10234
  } else {
9993
- console.log(chalk19.dim("\u2717") + " Cursor hooks " + chalk19.dim("not installed"));
10235
+ console.log(chalk20.dim("\u2717") + " Cursor hooks " + chalk20.dim("not installed"));
9994
10236
  }
9995
10237
  if (!claudeInstalled && !cursorInstalled) {
9996
- console.log(chalk19.dim(" Run `caliber learn install` to enable session learning."));
10238
+ console.log(chalk20.dim(" Run `caliber learn install` to enable session learning."));
9997
10239
  }
9998
10240
  console.log();
9999
- console.log(`Events recorded: ${chalk19.cyan(String(eventCount))}`);
10000
- console.log(`Threshold for analysis: ${chalk19.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
10241
+ console.log(`Events recorded: ${chalk20.cyan(String(eventCount))}`);
10242
+ console.log(`Threshold for analysis: ${chalk20.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
10001
10243
  if (state.lastAnalysisTimestamp) {
10002
- console.log(`Last analysis: ${chalk19.cyan(state.lastAnalysisTimestamp)}`);
10244
+ console.log(`Last analysis: ${chalk20.cyan(state.lastAnalysisTimestamp)}`);
10003
10245
  } else {
10004
- console.log(`Last analysis: ${chalk19.dim("none")}`);
10246
+ console.log(`Last analysis: ${chalk20.dim("none")}`);
10005
10247
  }
10006
10248
  const learnedSection = readLearnedSection();
10007
10249
  if (learnedSection) {
10008
10250
  const lineCount = learnedSection.split("\n").filter(Boolean).length;
10009
10251
  console.log(`
10010
- Learned items in CALIBER_LEARNINGS.md: ${chalk19.cyan(String(lineCount))}`);
10252
+ Learned items in CALIBER_LEARNINGS.md: ${chalk20.cyan(String(lineCount))}`);
10011
10253
  }
10012
10254
  const roiStats = readROIStats();
10013
10255
  const roiSummary = formatROISummary(roiStats);
10014
10256
  if (roiSummary) {
10015
10257
  console.log();
10016
- console.log(chalk19.bold(roiSummary.split("\n")[0]));
10258
+ console.log(chalk20.bold(roiSummary.split("\n")[0]));
10017
10259
  for (const line of roiSummary.split("\n").slice(1)) {
10018
10260
  console.log(line);
10019
10261
  }
10020
10262
  }
10021
10263
  }
10022
10264
 
10265
+ // src/commands/insights.ts
10266
+ import chalk21 from "chalk";
10267
+ var MIN_SESSIONS_FULL = 20;
10268
+ function buildInsightsData(stats) {
10269
+ const t = stats.totals;
10270
+ const totalSessions = t.totalSessionsWithLearnings + t.totalSessionsWithoutLearnings;
10271
+ const failureRateWith = t.totalSessionsWithLearnings > 0 ? t.totalFailuresWithLearnings / t.totalSessionsWithLearnings : null;
10272
+ const failureRateWithout = t.totalSessionsWithoutLearnings > 0 ? t.totalFailuresWithoutLearnings / t.totalSessionsWithoutLearnings : null;
10273
+ const failureRateImprovement = failureRateWith !== null && failureRateWithout !== null && failureRateWithout > 0 ? Math.round((1 - failureRateWith / failureRateWithout) * 100) : null;
10274
+ let taskCount = 0;
10275
+ let taskSuccessCount = 0;
10276
+ let taskCorrectionCount = 0;
10277
+ let taskFailureCount = 0;
10278
+ for (const s of stats.sessions) {
10279
+ if (s.taskCount) {
10280
+ taskCount += s.taskCount;
10281
+ taskSuccessCount += s.taskSuccessCount || 0;
10282
+ taskCorrectionCount += s.taskCorrectionCount || 0;
10283
+ taskFailureCount += s.taskFailureCount || 0;
10284
+ }
10285
+ }
10286
+ const taskSuccessRate = taskCount > 0 ? Math.round(taskSuccessCount / taskCount * 100) : null;
10287
+ return {
10288
+ totalSessions,
10289
+ learningCount: stats.learnings.length,
10290
+ failureRateWith,
10291
+ failureRateWithout,
10292
+ failureRateImprovement,
10293
+ taskCount,
10294
+ taskSuccessCount,
10295
+ taskCorrectionCount,
10296
+ taskFailureCount,
10297
+ taskSuccessRate,
10298
+ totalWasteTokens: t.totalWasteTokens,
10299
+ totalWasteSeconds: t.totalWasteSeconds,
10300
+ estimatedSavingsTokens: t.estimatedSavingsTokens,
10301
+ estimatedSavingsSeconds: t.estimatedSavingsSeconds
10302
+ };
10303
+ }
10304
+ function displayColdStart(score) {
10305
+ console.log(chalk21.bold("\n Agent Insights\n"));
10306
+ const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
10307
+ if (!hooksInstalled) {
10308
+ console.log(chalk21.yellow(" No learning hooks installed."));
10309
+ console.log(chalk21.dim(" Run ") + chalk21.cyan("caliber learn install") + chalk21.dim(" to start tracking agent performance."));
10310
+ } else {
10311
+ console.log(chalk21.dim(" No session data yet. Use your AI agent and insights will appear here."));
10312
+ console.log(chalk21.dim(" Learnings are extracted automatically at the end of each session."));
10313
+ }
10314
+ console.log(chalk21.dim(`
10315
+ Config score: ${score.score}/100 (${score.grade})`));
10316
+ console.log("");
10317
+ }
10318
+ function displayEarlyData(data, score) {
10319
+ console.log(chalk21.bold("\n Agent Insights") + chalk21.yellow(" (early data)\n"));
10320
+ console.log(chalk21.dim(" Still collecting data. Insights become more reliable after 20+ sessions.\n"));
10321
+ console.log(` Sessions tracked: ${chalk21.cyan(String(data.totalSessions))}`);
10322
+ console.log(` Learnings accumulated: ${chalk21.cyan(String(data.learningCount))}`);
10323
+ if (data.totalWasteTokens > 0) {
10324
+ console.log(` Waste captured: ${chalk21.cyan(data.totalWasteTokens.toLocaleString())} tokens`);
10325
+ }
10326
+ if (data.failureRateImprovement !== null && data.failureRateImprovement > 0) {
10327
+ console.log(` Failure rate trend: ${chalk21.green(`${data.failureRateImprovement}% fewer`)} failures with learnings ${chalk21.dim("(early signal)")}`);
10328
+ }
10329
+ if (data.taskSuccessRate !== null) {
10330
+ console.log(` Task success rate: ${chalk21.cyan(`${data.taskSuccessRate}%`)} ${chalk21.dim(`(${data.taskCount} tasks)`)}`);
10331
+ }
10332
+ console.log(` Config score: ${chalk21.cyan(`${score.score}/100`)} (${score.grade})`);
10333
+ console.log("");
10334
+ }
10335
+ function displayFullInsights(data, score) {
10336
+ console.log(chalk21.bold("\n Agent Insights\n"));
10337
+ console.log(chalk21.bold(" Agent Health"));
10338
+ if (data.taskSuccessRate !== null) {
10339
+ const color = data.taskSuccessRate >= 80 ? chalk21.green : data.taskSuccessRate >= 60 ? chalk21.yellow : chalk21.red;
10340
+ console.log(` Task success rate: ${color(`${data.taskSuccessRate}%`)} across ${data.taskCount} tasks`);
10341
+ if (data.taskCorrectionCount > 0) {
10342
+ console.log(` Corrections needed: ${chalk21.yellow(String(data.taskCorrectionCount))} tasks required user correction`);
10343
+ }
10344
+ }
10345
+ console.log(` Sessions tracked: ${chalk21.cyan(String(data.totalSessions))}`);
10346
+ console.log(chalk21.bold("\n Learning Impact"));
10347
+ console.log(` Learnings active: ${chalk21.cyan(String(data.learningCount))}`);
10348
+ if (data.failureRateWith !== null && data.failureRateWithout !== null) {
10349
+ console.log(` Failure rate: ${chalk21.red(data.failureRateWithout.toFixed(1))}/session ${chalk21.dim("\u2192")} ${chalk21.green(data.failureRateWith.toFixed(1))}/session with learnings`);
10350
+ if (data.failureRateImprovement !== null && data.failureRateImprovement > 0) {
10351
+ console.log(` Improvement: ${chalk21.green(`${data.failureRateImprovement}%`)} fewer failures`);
10352
+ }
10353
+ }
10354
+ if (data.totalWasteTokens > 0 || data.estimatedSavingsTokens > 0) {
10355
+ console.log(chalk21.bold("\n Efficiency"));
10356
+ if (data.totalWasteTokens > 0) {
10357
+ console.log(` Waste captured: ${chalk21.cyan(data.totalWasteTokens.toLocaleString())} tokens`);
10358
+ }
10359
+ if (data.estimatedSavingsTokens > 0) {
10360
+ console.log(` Estimated savings: ~${chalk21.green(data.estimatedSavingsTokens.toLocaleString())} tokens`);
10361
+ }
10362
+ if (data.estimatedSavingsSeconds > 0) {
10363
+ console.log(` Time saved: ~${chalk21.green(formatDuration(data.estimatedSavingsSeconds))}`);
10364
+ }
10365
+ }
10366
+ console.log(chalk21.bold("\n Config Quality"));
10367
+ console.log(` Score: ${chalk21.cyan(`${score.score}/100`)} (${score.grade})`);
10368
+ console.log("");
10369
+ }
10370
+ async function insightsCommand(options) {
10371
+ const stats = readROIStats();
10372
+ const data = buildInsightsData(stats);
10373
+ const score = computeLocalScore(process.cwd(), readState()?.targetAgent);
10374
+ trackInsightsViewed(data.totalSessions, data.learningCount);
10375
+ if (options.json) {
10376
+ console.log(JSON.stringify({
10377
+ ...data,
10378
+ tier: data.totalSessions === 0 ? "cold-start" : data.totalSessions < MIN_SESSIONS_FULL ? "early" : "full",
10379
+ configScore: score.score,
10380
+ configGrade: score.grade
10381
+ }, null, 2));
10382
+ return;
10383
+ }
10384
+ if (data.totalSessions === 0) {
10385
+ displayColdStart(score);
10386
+ } else if (data.totalSessions < MIN_SESSIONS_FULL) {
10387
+ displayEarlyData(data, score);
10388
+ } else {
10389
+ displayFullInsights(data, score);
10390
+ }
10391
+ }
10392
+
10023
10393
  // src/cli.ts
10024
- var __dirname = path28.dirname(fileURLToPath(import.meta.url));
10394
+ var __dirname = path30.dirname(fileURLToPath(import.meta.url));
10025
10395
  var pkg = JSON.parse(
10026
- fs35.readFileSync(path28.resolve(__dirname, "..", "package.json"), "utf-8")
10396
+ fs38.readFileSync(path30.resolve(__dirname, "..", "package.json"), "utf-8")
10027
10397
  );
10028
10398
  var program = new Command();
10029
10399
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -10068,6 +10438,10 @@ program.hook("preAction", (thisCommand) => {
10068
10438
  setTelemetryDisabled(true);
10069
10439
  }
10070
10440
  initTelemetry();
10441
+ const cmdName = thisCommand.name();
10442
+ if (cmdName !== "learn" && cmdName !== "observe" && cmdName !== "finalize") {
10443
+ checkPendingNotifications();
10444
+ }
10071
10445
  });
10072
10446
  function parseAgentOption(value) {
10073
10447
  if (value === "both") return ["claude", "cursor"];
@@ -10086,27 +10460,28 @@ program.command("status").description("Show current Caliber setup status").optio
10086
10460
  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));
10087
10461
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
10088
10462
  program.command("skills").description("Discover and install community skills for your project").action(tracked("skills", recommendCommand));
10089
- 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", parseAgentOption).action(tracked("score", scoreCommand));
10463
+ 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", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
10090
10464
  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));
10091
10465
  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));
10466
+ program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
10092
10467
  var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
10093
10468
  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));
10094
- 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)").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
10469
+ 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)));
10095
10470
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
10096
10471
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
10097
10472
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
10098
10473
 
10099
10474
  // src/utils/version-check.ts
10100
- import fs36 from "fs";
10101
- import path29 from "path";
10475
+ import fs39 from "fs";
10476
+ import path31 from "path";
10102
10477
  import { fileURLToPath as fileURLToPath2 } from "url";
10103
10478
  import { execSync as execSync15 } from "child_process";
10104
- import chalk20 from "chalk";
10479
+ import chalk22 from "chalk";
10105
10480
  import ora7 from "ora";
10106
10481
  import confirm2 from "@inquirer/confirm";
10107
- var __dirname_vc = path29.dirname(fileURLToPath2(import.meta.url));
10482
+ var __dirname_vc = path31.dirname(fileURLToPath2(import.meta.url));
10108
10483
  var pkg2 = JSON.parse(
10109
- fs36.readFileSync(path29.resolve(__dirname_vc, "..", "package.json"), "utf-8")
10484
+ fs39.readFileSync(path31.resolve(__dirname_vc, "..", "package.json"), "utf-8")
10110
10485
  );
10111
10486
  function getChannel(version) {
10112
10487
  const match = version.match(/-(dev|next)\./);
@@ -10131,8 +10506,8 @@ function isNewer(registry, current) {
10131
10506
  function getInstalledVersion() {
10132
10507
  try {
10133
10508
  const globalRoot = execSync15("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
10134
- const pkgPath = path29.join(globalRoot, "@rely-ai", "caliber", "package.json");
10135
- return JSON.parse(fs36.readFileSync(pkgPath, "utf-8")).version;
10509
+ const pkgPath = path31.join(globalRoot, "@rely-ai", "caliber", "package.json");
10510
+ return JSON.parse(fs39.readFileSync(pkgPath, "utf-8")).version;
10136
10511
  } catch {
10137
10512
  return null;
10138
10513
  }
@@ -10157,17 +10532,17 @@ async function checkForUpdates() {
10157
10532
  if (!isInteractive) {
10158
10533
  const installTag = channel === "latest" ? "" : `@${channel}`;
10159
10534
  console.log(
10160
- chalk20.yellow(
10535
+ chalk22.yellow(
10161
10536
  `
10162
10537
  Update available: ${current} -> ${latest}
10163
- Run ${chalk20.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
10538
+ Run ${chalk22.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
10164
10539
  `
10165
10540
  )
10166
10541
  );
10167
10542
  return;
10168
10543
  }
10169
10544
  console.log(
10170
- chalk20.yellow(`
10545
+ chalk22.yellow(`
10171
10546
  Update available: ${current} -> ${latest}`)
10172
10547
  );
10173
10548
  const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
@@ -10186,13 +10561,13 @@ Update available: ${current} -> ${latest}`)
10186
10561
  const installed = getInstalledVersion();
10187
10562
  if (installed !== latest) {
10188
10563
  spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
10189
- console.log(chalk20.yellow(`Run ${chalk20.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
10564
+ console.log(chalk22.yellow(`Run ${chalk22.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
10190
10565
  `));
10191
10566
  return;
10192
10567
  }
10193
- spinner.succeed(chalk20.green(`Updated to ${latest}`));
10568
+ spinner.succeed(chalk22.green(`Updated to ${latest}`));
10194
10569
  const args = process.argv.slice(2);
10195
- console.log(chalk20.dim(`
10570
+ console.log(chalk22.dim(`
10196
10571
  Restarting: caliber ${args.join(" ")}
10197
10572
  `));
10198
10573
  execSync15(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
@@ -10205,11 +10580,11 @@ Restarting: caliber ${args.join(" ")}
10205
10580
  if (err instanceof Error) {
10206
10581
  const stderr = err.stderr;
10207
10582
  const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
10208
- if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk20.dim(` ${errMsg}`));
10583
+ if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk22.dim(` ${errMsg}`));
10209
10584
  }
10210
10585
  console.log(
10211
- chalk20.yellow(
10212
- `Run ${chalk20.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
10586
+ chalk22.yellow(
10587
+ `Run ${chalk22.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
10213
10588
  `
10214
10589
  )
10215
10590
  );