@rely-ai/caliber 1.23.0 → 1.24.0-dev.1773820755

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 +562 -214
  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);
@@ -8126,7 +8140,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
8126
8140
  }
8127
8141
  function summarizeSetup(action, setup) {
8128
8142
  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(", ");
8143
+ const files = descriptions ? Object.entries(descriptions).map(([path32, desc]) => ` ${path32}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
8130
8144
  return `${action}. Files:
8131
8145
  ${files}`;
8132
8146
  }
@@ -8231,6 +8245,7 @@ async function promptLearnInstall(targetAgent) {
8231
8245
  `));
8232
8246
  return select5({
8233
8247
  message: "Enable session learning?",
8248
+ default: true,
8234
8249
  choices: [
8235
8250
  { name: "Enable session learning (recommended)", value: true },
8236
8251
  { name: "Skip for now", value: false }
@@ -8657,12 +8672,79 @@ async function regenerateCommand(options) {
8657
8672
  }
8658
8673
 
8659
8674
  // src/commands/score.ts
8675
+ import fs28 from "fs";
8676
+ import os6 from "os";
8677
+ import path22 from "path";
8678
+ import { execFileSync } from "child_process";
8660
8679
  import chalk15 from "chalk";
8680
+ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
8681
+ var CONFIG_DIRS = [".claude", ".cursor"];
8682
+ function scoreBaseRef(ref, target) {
8683
+ if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
8684
+ const tmpDir = fs28.mkdtempSync(path22.join(os6.tmpdir(), "caliber-compare-"));
8685
+ try {
8686
+ for (const file of CONFIG_FILES) {
8687
+ try {
8688
+ const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
8689
+ fs28.writeFileSync(path22.join(tmpDir, file), content);
8690
+ } catch {
8691
+ }
8692
+ }
8693
+ for (const dir of CONFIG_DIRS) {
8694
+ try {
8695
+ const files = execFileSync("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
8696
+ for (const file of files) {
8697
+ const filePath = path22.join(tmpDir, file);
8698
+ fs28.mkdirSync(path22.dirname(filePath), { recursive: true });
8699
+ const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
8700
+ fs28.writeFileSync(filePath, content);
8701
+ }
8702
+ } catch {
8703
+ }
8704
+ }
8705
+ const result = computeLocalScore(tmpDir, target);
8706
+ return { score: result.score, grade: result.grade };
8707
+ } catch {
8708
+ return null;
8709
+ } finally {
8710
+ fs28.rmSync(tmpDir, { recursive: true, force: true });
8711
+ }
8712
+ }
8661
8713
  async function scoreCommand(options) {
8662
8714
  const dir = process.cwd();
8663
8715
  const target = options.agent ?? readState()?.targetAgent;
8664
8716
  const result = computeLocalScore(dir, target);
8665
8717
  trackScoreComputed(result.score, target);
8718
+ if (options.compare) {
8719
+ const baseResult = scoreBaseRef(options.compare, target);
8720
+ if (!baseResult) {
8721
+ console.error(chalk15.red(`Could not score ref "${options.compare}" \u2014 branch or ref not found.`));
8722
+ process.exitCode = 1;
8723
+ return;
8724
+ }
8725
+ const delta = result.score - baseResult.score;
8726
+ if (options.json) {
8727
+ console.log(JSON.stringify({ current: result, base: { score: baseResult.score, grade: baseResult.grade, ref: options.compare }, delta }, null, 2));
8728
+ return;
8729
+ }
8730
+ if (options.quiet) {
8731
+ const sign = delta > 0 ? "+" : "";
8732
+ console.log(`${result.score}/100 (${result.grade}) ${sign}${delta} from ${options.compare}`);
8733
+ return;
8734
+ }
8735
+ displayScore(result);
8736
+ const separator2 = chalk15.gray(" " + "\u2500".repeat(53));
8737
+ console.log(separator2);
8738
+ if (delta > 0) {
8739
+ console.log(chalk15.green(` +${delta}`) + chalk15.gray(` from ${options.compare} (${baseResult.score}/100)`));
8740
+ } else if (delta < 0) {
8741
+ console.log(chalk15.red(` ${delta}`) + chalk15.gray(` from ${options.compare} (${baseResult.score}/100)`));
8742
+ } else {
8743
+ console.log(chalk15.gray(` No change from ${options.compare} (${baseResult.score}/100)`));
8744
+ }
8745
+ console.log("");
8746
+ return;
8747
+ }
8666
8748
  if (options.json) {
8667
8749
  console.log(JSON.stringify(result, null, 2));
8668
8750
  return;
@@ -8685,8 +8767,8 @@ async function scoreCommand(options) {
8685
8767
  }
8686
8768
 
8687
8769
  // src/commands/refresh.ts
8688
- import fs31 from "fs";
8689
- import path25 from "path";
8770
+ import fs32 from "fs";
8771
+ import path26 from "path";
8690
8772
  import chalk16 from "chalk";
8691
8773
  import ora6 from "ora";
8692
8774
 
@@ -8764,37 +8846,37 @@ function collectDiff(lastSha) {
8764
8846
  }
8765
8847
 
8766
8848
  // src/writers/refresh.ts
8767
- import fs28 from "fs";
8768
- import path22 from "path";
8849
+ import fs29 from "fs";
8850
+ import path23 from "path";
8769
8851
  function writeRefreshDocs(docs) {
8770
8852
  const written = [];
8771
8853
  if (docs.claudeMd) {
8772
- fs28.writeFileSync("CLAUDE.md", docs.claudeMd);
8854
+ fs29.writeFileSync("CLAUDE.md", docs.claudeMd);
8773
8855
  written.push("CLAUDE.md");
8774
8856
  }
8775
8857
  if (docs.readmeMd) {
8776
- fs28.writeFileSync("README.md", docs.readmeMd);
8858
+ fs29.writeFileSync("README.md", docs.readmeMd);
8777
8859
  written.push("README.md");
8778
8860
  }
8779
8861
  if (docs.cursorrules) {
8780
- fs28.writeFileSync(".cursorrules", docs.cursorrules);
8862
+ fs29.writeFileSync(".cursorrules", docs.cursorrules);
8781
8863
  written.push(".cursorrules");
8782
8864
  }
8783
8865
  if (docs.cursorRules) {
8784
- const rulesDir = path22.join(".cursor", "rules");
8785
- if (!fs28.existsSync(rulesDir)) fs28.mkdirSync(rulesDir, { recursive: true });
8866
+ const rulesDir = path23.join(".cursor", "rules");
8867
+ if (!fs29.existsSync(rulesDir)) fs29.mkdirSync(rulesDir, { recursive: true });
8786
8868
  for (const rule of docs.cursorRules) {
8787
- const filePath = path22.join(rulesDir, rule.filename);
8788
- fs28.writeFileSync(filePath, rule.content);
8869
+ const filePath = path23.join(rulesDir, rule.filename);
8870
+ fs29.writeFileSync(filePath, rule.content);
8789
8871
  written.push(filePath);
8790
8872
  }
8791
8873
  }
8792
8874
  if (docs.claudeSkills) {
8793
- const skillsDir = path22.join(".claude", "skills");
8794
- if (!fs28.existsSync(skillsDir)) fs28.mkdirSync(skillsDir, { recursive: true });
8875
+ const skillsDir = path23.join(".claude", "skills");
8876
+ if (!fs29.existsSync(skillsDir)) fs29.mkdirSync(skillsDir, { recursive: true });
8795
8877
  for (const skill of docs.claudeSkills) {
8796
- const filePath = path22.join(skillsDir, skill.filename);
8797
- fs28.writeFileSync(filePath, skill.content);
8878
+ const filePath = path23.join(skillsDir, skill.filename);
8879
+ fs29.writeFileSync(filePath, skill.content);
8798
8880
  written.push(filePath);
8799
8881
  }
8800
8882
  }
@@ -8871,8 +8953,29 @@ Changed files: ${diff.changedFiles.join(", ")}`);
8871
8953
  }
8872
8954
 
8873
8955
  // src/learner/writer.ts
8874
- import fs29 from "fs";
8875
- import path23 from "path";
8956
+ import fs30 from "fs";
8957
+ import path24 from "path";
8958
+
8959
+ // src/learner/utils.ts
8960
+ var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
8961
+ function normalizeBullet(bullet) {
8962
+ return bullet.replace(/^- /, "").replace(TYPE_PREFIX_RE, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
8963
+ }
8964
+ function hasTypePrefix(bullet) {
8965
+ return TYPE_PREFIX_RE.test(bullet.replace(/^- /, ""));
8966
+ }
8967
+ var SIMILARITY_THRESHOLD = 0.7;
8968
+ function isSimilarLearning(a, b) {
8969
+ const normA = normalizeBullet(a);
8970
+ const normB = normalizeBullet(b);
8971
+ if (!normA || !normB) return false;
8972
+ const shorter = Math.min(normA.length, normB.length);
8973
+ const longer = Math.max(normA.length, normB.length);
8974
+ if (!(normA.includes(normB) || normB.includes(normA))) return false;
8975
+ return shorter / longer > SIMILARITY_THRESHOLD;
8976
+ }
8977
+
8978
+ // src/learner/writer.ts
8876
8979
  var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
8877
8980
  var LEARNINGS_HEADER = `# Caliber Learnings
8878
8981
 
@@ -8919,13 +9022,6 @@ function parseBullets(content) {
8919
9022
  if (current) bullets.push(current);
8920
9023
  return bullets;
8921
9024
  }
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
9025
  function deduplicateLearnedItems(existing, incoming) {
8930
9026
  const existingBullets = existing ? parseBullets(existing) : [];
8931
9027
  const incomingBullets = parseBullets(incoming);
@@ -8934,13 +9030,7 @@ function deduplicateLearnedItems(existing, incoming) {
8934
9030
  for (const bullet of incomingBullets) {
8935
9031
  const norm = normalizeBullet(bullet);
8936
9032
  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
- });
9033
+ const dupIdx = merged.findIndex((e) => isSimilarLearning(bullet, e));
8944
9034
  if (dupIdx !== -1) {
8945
9035
  if (hasTypePrefix(bullet) && !hasTypePrefix(merged[dupIdx])) {
8946
9036
  merged[dupIdx] = bullet;
@@ -8956,16 +9046,16 @@ function deduplicateLearnedItems(existing, incoming) {
8956
9046
  function writeLearnedSection(content) {
8957
9047
  const existingSection = readLearnedSection();
8958
9048
  const { merged, newCount, newItems } = deduplicateLearnedItems(existingSection, content);
8959
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
9049
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
8960
9050
  return { newCount, newItems };
8961
9051
  }
8962
9052
  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);
9053
+ const skillDir = path24.join(".claude", "skills", skill.name);
9054
+ if (!fs30.existsSync(skillDir)) fs30.mkdirSync(skillDir, { recursive: true });
9055
+ const skillPath = path24.join(skillDir, "SKILL.md");
9056
+ if (!skill.isNew && fs30.existsSync(skillPath)) {
9057
+ const existing = fs30.readFileSync(skillPath, "utf-8");
9058
+ fs30.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
8969
9059
  } else {
8970
9060
  const frontmatter = [
8971
9061
  "---",
@@ -8974,37 +9064,37 @@ function writeLearnedSkill(skill) {
8974
9064
  "---",
8975
9065
  ""
8976
9066
  ].join("\n");
8977
- fs29.writeFileSync(skillPath, frontmatter + skill.content);
9067
+ fs30.writeFileSync(skillPath, frontmatter + skill.content);
8978
9068
  }
8979
9069
  return skillPath;
8980
9070
  }
8981
9071
  function readLearnedSection() {
8982
- if (fs29.existsSync(LEARNINGS_FILE)) {
8983
- const content2 = fs29.readFileSync(LEARNINGS_FILE, "utf-8");
9072
+ if (fs30.existsSync(LEARNINGS_FILE)) {
9073
+ const content2 = fs30.readFileSync(LEARNINGS_FILE, "utf-8");
8984
9074
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
8985
9075
  return bullets || null;
8986
9076
  }
8987
9077
  const claudeMdPath = "CLAUDE.md";
8988
- if (!fs29.existsSync(claudeMdPath)) return null;
8989
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9078
+ if (!fs30.existsSync(claudeMdPath)) return null;
9079
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
8990
9080
  const startIdx = content.indexOf(LEARNED_START);
8991
9081
  const endIdx = content.indexOf(LEARNED_END);
8992
9082
  if (startIdx === -1 || endIdx === -1) return null;
8993
9083
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
8994
9084
  }
8995
9085
  function migrateInlineLearnings() {
8996
- if (fs29.existsSync(LEARNINGS_FILE)) return false;
9086
+ if (fs30.existsSync(LEARNINGS_FILE)) return false;
8997
9087
  const claudeMdPath = "CLAUDE.md";
8998
- if (!fs29.existsSync(claudeMdPath)) return false;
8999
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9088
+ if (!fs30.existsSync(claudeMdPath)) return false;
9089
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
9000
9090
  const startIdx = content.indexOf(LEARNED_START);
9001
9091
  const endIdx = content.indexOf(LEARNED_END);
9002
9092
  if (startIdx === -1 || endIdx === -1) return false;
9003
9093
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
9004
9094
  if (!section) return false;
9005
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9095
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9006
9096
  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");
9097
+ fs30.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
9008
9098
  return true;
9009
9099
  }
9010
9100
 
@@ -9016,11 +9106,11 @@ function log2(quiet, ...args) {
9016
9106
  function discoverGitRepos(parentDir) {
9017
9107
  const repos = [];
9018
9108
  try {
9019
- const entries = fs31.readdirSync(parentDir, { withFileTypes: true });
9109
+ const entries = fs32.readdirSync(parentDir, { withFileTypes: true });
9020
9110
  for (const entry of entries) {
9021
9111
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
9022
- const childPath = path25.join(parentDir, entry.name);
9023
- if (fs31.existsSync(path25.join(childPath, ".git"))) {
9112
+ const childPath = path26.join(parentDir, entry.name);
9113
+ if (fs32.existsSync(path26.join(childPath, ".git"))) {
9024
9114
  repos.push(childPath);
9025
9115
  }
9026
9116
  }
@@ -9123,7 +9213,7 @@ async function refreshCommand(options) {
9123
9213
  `));
9124
9214
  const originalDir = process.cwd();
9125
9215
  for (const repo of repos) {
9126
- const repoName = path25.basename(repo);
9216
+ const repoName = path26.basename(repo);
9127
9217
  try {
9128
9218
  process.chdir(repo);
9129
9219
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -9144,6 +9234,7 @@ async function refreshCommand(options) {
9144
9234
 
9145
9235
  // src/commands/hooks.ts
9146
9236
  import chalk17 from "chalk";
9237
+ import fs33 from "fs";
9147
9238
  var HOOKS = [
9148
9239
  {
9149
9240
  id: "session-end",
@@ -9183,6 +9274,14 @@ async function hooksCommand(options) {
9183
9274
  console.log(chalk17.green(" \u2713") + ` ${hook.label} enabled`);
9184
9275
  }
9185
9276
  }
9277
+ if (fs33.existsSync(".claude")) {
9278
+ const r = installLearningHooks();
9279
+ if (r.installed) console.log(chalk17.green(" \u2713") + " Claude Code learning hooks enabled");
9280
+ }
9281
+ if (fs33.existsSync(".cursor")) {
9282
+ const r = installCursorLearningHooks();
9283
+ if (r.installed) console.log(chalk17.green(" \u2713") + " Cursor learning hooks enabled");
9284
+ }
9186
9285
  return;
9187
9286
  }
9188
9287
  if (options.remove) {
@@ -9347,8 +9446,8 @@ async function configCommand() {
9347
9446
  }
9348
9447
 
9349
9448
  // src/commands/learn.ts
9350
- import fs34 from "fs";
9351
- import chalk19 from "chalk";
9449
+ import fs37 from "fs";
9450
+ import chalk20 from "chalk";
9352
9451
 
9353
9452
  // src/learner/stdin.ts
9354
9453
  var STDIN_TIMEOUT_MS = 5e3;
@@ -9379,24 +9478,25 @@ function readStdin() {
9379
9478
 
9380
9479
  // src/learner/storage.ts
9381
9480
  init_constants();
9382
- import fs32 from "fs";
9383
- import path26 from "path";
9481
+ import fs34 from "fs";
9482
+ import path27 from "path";
9384
9483
  var MAX_RESPONSE_LENGTH = 2e3;
9385
9484
  var DEFAULT_STATE = {
9386
9485
  sessionId: null,
9387
9486
  eventCount: 0,
9388
- lastAnalysisTimestamp: null
9487
+ lastAnalysisTimestamp: null,
9488
+ lastAnalysisEventCount: 0
9389
9489
  };
9390
9490
  function ensureLearningDir() {
9391
- if (!fs32.existsSync(LEARNING_DIR)) {
9392
- fs32.mkdirSync(LEARNING_DIR, { recursive: true });
9491
+ if (!fs34.existsSync(LEARNING_DIR)) {
9492
+ fs34.mkdirSync(LEARNING_DIR, { recursive: true });
9393
9493
  }
9394
9494
  }
9395
9495
  function sessionFilePath() {
9396
- return path26.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9496
+ return path27.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9397
9497
  }
9398
9498
  function stateFilePath() {
9399
- return path26.join(LEARNING_DIR, LEARNING_STATE_FILE);
9499
+ return path27.join(LEARNING_DIR, LEARNING_STATE_FILE);
9400
9500
  }
9401
9501
  function truncateResponse(response) {
9402
9502
  const str = JSON.stringify(response);
@@ -9407,29 +9507,29 @@ function appendEvent(event) {
9407
9507
  ensureLearningDir();
9408
9508
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
9409
9509
  const filePath = sessionFilePath();
9410
- fs32.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9510
+ fs34.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9411
9511
  const count = getEventCount();
9412
9512
  if (count > LEARNING_MAX_EVENTS) {
9413
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9513
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9414
9514
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9415
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9515
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9416
9516
  }
9417
9517
  }
9418
9518
  function appendPromptEvent(event) {
9419
9519
  ensureLearningDir();
9420
9520
  const filePath = sessionFilePath();
9421
- fs32.appendFileSync(filePath, JSON.stringify(event) + "\n");
9521
+ fs34.appendFileSync(filePath, JSON.stringify(event) + "\n");
9422
9522
  const count = getEventCount();
9423
9523
  if (count > LEARNING_MAX_EVENTS) {
9424
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9524
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9425
9525
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9426
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9526
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9427
9527
  }
9428
9528
  }
9429
9529
  function readAllEvents() {
9430
9530
  const filePath = sessionFilePath();
9431
- if (!fs32.existsSync(filePath)) return [];
9432
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9531
+ if (!fs34.existsSync(filePath)) return [];
9532
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9433
9533
  const events = [];
9434
9534
  for (const line of lines) {
9435
9535
  try {
@@ -9441,26 +9541,26 @@ function readAllEvents() {
9441
9541
  }
9442
9542
  function getEventCount() {
9443
9543
  const filePath = sessionFilePath();
9444
- if (!fs32.existsSync(filePath)) return 0;
9445
- const content = fs32.readFileSync(filePath, "utf-8");
9544
+ if (!fs34.existsSync(filePath)) return 0;
9545
+ const content = fs34.readFileSync(filePath, "utf-8");
9446
9546
  return content.split("\n").filter(Boolean).length;
9447
9547
  }
9448
9548
  function clearSession() {
9449
9549
  const filePath = sessionFilePath();
9450
- if (fs32.existsSync(filePath)) fs32.unlinkSync(filePath);
9550
+ if (fs34.existsSync(filePath)) fs34.unlinkSync(filePath);
9451
9551
  }
9452
9552
  function readState2() {
9453
9553
  const filePath = stateFilePath();
9454
- if (!fs32.existsSync(filePath)) return { ...DEFAULT_STATE };
9554
+ if (!fs34.existsSync(filePath)) return { ...DEFAULT_STATE };
9455
9555
  try {
9456
- return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
9556
+ return JSON.parse(fs34.readFileSync(filePath, "utf-8"));
9457
9557
  } catch {
9458
9558
  return { ...DEFAULT_STATE };
9459
9559
  }
9460
9560
  }
9461
9561
  function writeState2(state) {
9462
9562
  ensureLearningDir();
9463
- fs32.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9563
+ fs34.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9464
9564
  }
9465
9565
  function resetState() {
9466
9566
  writeState2({ ...DEFAULT_STATE });
@@ -9468,14 +9568,14 @@ function resetState() {
9468
9568
  var LOCK_FILE2 = "finalize.lock";
9469
9569
  var LOCK_STALE_MS = 5 * 60 * 1e3;
9470
9570
  function lockFilePath() {
9471
- return path26.join(LEARNING_DIR, LOCK_FILE2);
9571
+ return path27.join(LEARNING_DIR, LOCK_FILE2);
9472
9572
  }
9473
9573
  function acquireFinalizeLock() {
9474
9574
  ensureLearningDir();
9475
9575
  const lockPath = lockFilePath();
9476
- if (fs32.existsSync(lockPath)) {
9576
+ if (fs34.existsSync(lockPath)) {
9477
9577
  try {
9478
- const stat = fs32.statSync(lockPath);
9578
+ const stat = fs34.statSync(lockPath);
9479
9579
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
9480
9580
  return false;
9481
9581
  }
@@ -9483,7 +9583,7 @@ function acquireFinalizeLock() {
9483
9583
  }
9484
9584
  }
9485
9585
  try {
9486
- fs32.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9586
+ fs34.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9487
9587
  return true;
9488
9588
  } catch {
9489
9589
  return false;
@@ -9492,7 +9592,7 @@ function acquireFinalizeLock() {
9492
9592
  function releaseFinalizeLock() {
9493
9593
  const lockPath = lockFilePath();
9494
9594
  try {
9495
- if (fs32.existsSync(lockPath)) fs32.unlinkSync(lockPath);
9595
+ if (fs34.existsSync(lockPath)) fs34.unlinkSync(lockPath);
9496
9596
  } catch {
9497
9597
  }
9498
9598
  }
@@ -9536,6 +9636,45 @@ function sanitizeSecrets(text) {
9536
9636
  return result;
9537
9637
  }
9538
9638
 
9639
+ // src/lib/notifications.ts
9640
+ init_constants();
9641
+ import fs35 from "fs";
9642
+ import path28 from "path";
9643
+ import chalk19 from "chalk";
9644
+ var NOTIFICATION_FILE = path28.join(LEARNING_DIR, "last-finalize-summary.json");
9645
+ function writeFinalizeSummary(summary) {
9646
+ try {
9647
+ ensureLearningDir();
9648
+ fs35.writeFileSync(NOTIFICATION_FILE, JSON.stringify(summary, null, 2));
9649
+ } catch {
9650
+ }
9651
+ }
9652
+ function checkPendingNotifications() {
9653
+ try {
9654
+ if (!fs35.existsSync(NOTIFICATION_FILE)) return;
9655
+ const raw = fs35.readFileSync(NOTIFICATION_FILE, "utf-8");
9656
+ fs35.unlinkSync(NOTIFICATION_FILE);
9657
+ const summary = JSON.parse(raw);
9658
+ if (!summary.newItemCount || summary.newItemCount === 0) return;
9659
+ const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
9660
+ console.log(
9661
+ chalk19.dim(`caliber: learned ${summary.newItemCount} new pattern${summary.newItemCount === 1 ? "" : "s"} from your last session${wasteLabel}`)
9662
+ );
9663
+ for (const item of summary.newItems.slice(0, 3)) {
9664
+ console.log(chalk19.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
9665
+ }
9666
+ if (summary.newItems.length > 3) {
9667
+ console.log(chalk19.dim(` ... and ${summary.newItems.length - 3} more`));
9668
+ }
9669
+ console.log("");
9670
+ } catch {
9671
+ try {
9672
+ fs35.unlinkSync(NOTIFICATION_FILE);
9673
+ } catch {
9674
+ }
9675
+ }
9676
+ }
9677
+
9539
9678
  // src/ai/learn.ts
9540
9679
  init_config();
9541
9680
  var MAX_PROMPT_TOKENS = 1e5;
@@ -9605,14 +9744,29 @@ ${existingLearnedSection}`);
9605
9744
 
9606
9745
  ${skillsSummary}`);
9607
9746
  }
9608
- const prompt = `${contextParts.length ? contextParts.join("\n\n---\n\n") + "\n\n---\n\n" : ""}## Tool Events from Session (${fittedEvents.length} events)
9747
+ contextParts.push(`## Task Segmentation & Attribution Instructions
9748
+
9749
+ 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).
9750
+
9751
+ For each task, determine:
9752
+ - "summary": what the user was trying to accomplish (1 sentence)
9753
+ - "outcome": "success" (completed without issues), "corrected" (user had to redirect the agent), or "failed" (task was abandoned or produced errors)
9754
+ - "startEventIdx" and "endEventIdx": 0-based indices in the event list
9755
+ - "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).
9756
+
9757
+ Include the "tasks" array in your JSON response alongside "claudeMdLearnedSection", "skills", and "explanations".`);
9758
+ const prompt = `${contextParts.join("\n\n---\n\n")}
9759
+
9760
+ ---
9761
+
9762
+ ## Tool Events from Session (${fittedEvents.length} events)
9609
9763
 
9610
9764
  ${eventsText}`;
9611
9765
  const fastModel = getFastModel();
9612
9766
  const raw = await llmCall({
9613
9767
  system: LEARN_SYSTEM_PROMPT,
9614
9768
  prompt,
9615
- maxTokens: 4096,
9769
+ maxTokens: 8192,
9616
9770
  ...fastModel ? { model: fastModel } : {}
9617
9771
  });
9618
9772
  return parseAnalysisResponse(raw);
@@ -9648,8 +9802,8 @@ init_config();
9648
9802
 
9649
9803
  // src/learner/roi.ts
9650
9804
  init_constants();
9651
- import fs33 from "fs";
9652
- import path27 from "path";
9805
+ import fs36 from "fs";
9806
+ import path29 from "path";
9653
9807
  var DEFAULT_TOTALS = {
9654
9808
  totalWasteTokens: 0,
9655
9809
  totalWasteSeconds: 0,
@@ -9663,22 +9817,22 @@ var DEFAULT_TOTALS = {
9663
9817
  lastSessionTimestamp: ""
9664
9818
  };
9665
9819
  function roiFilePath() {
9666
- return path27.join(LEARNING_DIR, LEARNING_ROI_FILE);
9820
+ return path29.join(LEARNING_DIR, LEARNING_ROI_FILE);
9667
9821
  }
9668
9822
  function readROIStats() {
9669
9823
  const filePath = roiFilePath();
9670
- if (!fs33.existsSync(filePath)) {
9824
+ if (!fs36.existsSync(filePath)) {
9671
9825
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9672
9826
  }
9673
9827
  try {
9674
- return JSON.parse(fs33.readFileSync(filePath, "utf-8"));
9828
+ return JSON.parse(fs36.readFileSync(filePath, "utf-8"));
9675
9829
  } catch {
9676
9830
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9677
9831
  }
9678
9832
  }
9679
9833
  function writeROIStats(stats) {
9680
9834
  ensureLearningDir();
9681
- fs33.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9835
+ fs36.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9682
9836
  }
9683
9837
  function recalculateTotals(stats) {
9684
9838
  const totals = stats.totals;
@@ -9711,7 +9865,16 @@ function recordSession(summary, learnings) {
9711
9865
  const stats = readROIStats();
9712
9866
  stats.sessions.push(summary);
9713
9867
  if (learnings?.length) {
9714
- stats.learnings.push(...learnings);
9868
+ for (const entry of learnings) {
9869
+ const existingIdx = stats.learnings.findIndex((e) => isSimilarLearning(e.summary, entry.summary));
9870
+ if (existingIdx !== -1) {
9871
+ stats.learnings[existingIdx].occurrences = (stats.learnings[existingIdx].occurrences || 1) + 1;
9872
+ stats.learnings[existingIdx].timestamp = entry.timestamp;
9873
+ } else {
9874
+ entry.occurrences = 1;
9875
+ stats.learnings.push(entry);
9876
+ }
9877
+ }
9715
9878
  }
9716
9879
  if (stats.sessions.length > MAX_SESSIONS) {
9717
9880
  stats.sessions = stats.sessions.slice(-MAX_SESSIONS);
@@ -9760,6 +9923,9 @@ function formatROISummary(stats) {
9760
9923
 
9761
9924
  // src/commands/learn.ts
9762
9925
  var MIN_EVENTS_FOR_ANALYSIS = 25;
9926
+ var MIN_EVENTS_AUTO = 10;
9927
+ var AUTO_SETTLE_MS = 200;
9928
+ var INCREMENTAL_INTERVAL = 50;
9763
9929
  async function learnObserveCommand(options) {
9764
9930
  try {
9765
9931
  const raw = await readStdin();
@@ -9796,33 +9962,53 @@ async function learnObserveCommand(options) {
9796
9962
  state.eventCount++;
9797
9963
  if (!state.sessionId) state.sessionId = sessionId;
9798
9964
  writeState2(state);
9965
+ const eventsSinceLastAnalysis = state.eventCount - (state.lastAnalysisEventCount || 0);
9966
+ if (eventsSinceLastAnalysis >= INCREMENTAL_INTERVAL) {
9967
+ try {
9968
+ const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
9969
+ const bin = resolveCaliber2();
9970
+ const { spawn: spawn4 } = await import("child_process");
9971
+ spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
9972
+ detached: true,
9973
+ stdio: "ignore"
9974
+ }).unref();
9975
+ } catch {
9976
+ }
9977
+ }
9799
9978
  } catch {
9800
9979
  }
9801
9980
  }
9802
9981
  async function learnFinalizeCommand(options) {
9803
- if (!options?.force) {
9982
+ const isAuto = options?.auto === true;
9983
+ const isIncremental = options?.incremental === true;
9984
+ if (!options?.force && !isAuto) {
9804
9985
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
9805
9986
  if (isCaliberRunning2()) {
9806
- console.log(chalk19.dim("caliber: skipping finalize \u2014 another caliber process is running"));
9987
+ if (!isAuto) console.log(chalk20.dim("caliber: skipping finalize \u2014 another caliber process is running"));
9807
9988
  return;
9808
9989
  }
9809
9990
  }
9991
+ if (isAuto) {
9992
+ await new Promise((r) => setTimeout(r, AUTO_SETTLE_MS));
9993
+ }
9810
9994
  if (!acquireFinalizeLock()) {
9811
- console.log(chalk19.dim("caliber: skipping finalize \u2014 another finalize is in progress"));
9995
+ if (!isAuto) console.log(chalk20.dim("caliber: skipping finalize \u2014 another finalize is in progress"));
9812
9996
  return;
9813
9997
  }
9814
9998
  let analyzed = false;
9815
9999
  try {
9816
10000
  const config = loadConfig();
9817
10001
  if (!config) {
9818
- console.log(chalk19.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
10002
+ if (isAuto) return;
10003
+ console.log(chalk20.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
9819
10004
  clearSession();
9820
10005
  resetState();
9821
10006
  return;
9822
10007
  }
9823
10008
  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`));
10009
+ const threshold = isAuto ? MIN_EVENTS_AUTO : MIN_EVENTS_FOR_ANALYSIS;
10010
+ if (events.length < threshold) {
10011
+ if (!isAuto) console.log(chalk20.dim(`caliber: ${events.length}/${threshold} events recorded \u2014 need more before analysis`));
9826
10012
  return;
9827
10013
  }
9828
10014
  await validateModel({ fast: true });
@@ -9849,10 +10035,19 @@ async function learnFinalizeCommand(options) {
9849
10035
  });
9850
10036
  newLearningsProduced = result.newItemCount;
9851
10037
  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)}`));
10038
+ if (isAuto) {
10039
+ writeFinalizeSummary({
10040
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
10041
+ newItemCount: result.newItemCount,
10042
+ newItems: result.newItems,
10043
+ wasteTokens: waste.totalWasteTokens
10044
+ });
10045
+ } else {
10046
+ const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
10047
+ console.log(chalk20.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
10048
+ for (const item of result.newItems) {
10049
+ console.log(chalk20.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
10050
+ }
9856
10051
  }
9857
10052
  const wastePerLearning = Math.round(waste.totalWasteTokens / result.newItemCount);
9858
10053
  const TYPE_RE = /^\*\*\[([^\]]+)\]\*\*/;
@@ -9877,6 +10072,15 @@ async function learnFinalizeCommand(options) {
9877
10072
  roiLearningEntries = learningEntries;
9878
10073
  }
9879
10074
  }
10075
+ const tasks = response.tasks || [];
10076
+ let taskSuccessCount = 0;
10077
+ let taskCorrectionCount = 0;
10078
+ let taskFailureCount = 0;
10079
+ for (const t2 of tasks) {
10080
+ if (t2.outcome === "success") taskSuccessCount++;
10081
+ else if (t2.outcome === "corrected") taskCorrectionCount++;
10082
+ else if (t2.outcome === "failed") taskFailureCount++;
10083
+ }
9880
10084
  const sessionSummary = {
9881
10085
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9882
10086
  sessionId: readState2().sessionId || "unknown",
@@ -9886,7 +10090,11 @@ async function learnFinalizeCommand(options) {
9886
10090
  wasteSeconds: Math.round(waste.totalWasteSeconds),
9887
10091
  hadLearningsAvailable: hadLearnings,
9888
10092
  learningsCount: existingLearnedItems,
9889
- newLearningsProduced
10093
+ newLearningsProduced,
10094
+ taskCount: tasks.length > 0 ? tasks.length : void 0,
10095
+ taskSuccessCount: tasks.length > 0 ? taskSuccessCount : void 0,
10096
+ taskCorrectionCount: tasks.length > 0 ? taskCorrectionCount : void 0,
10097
+ taskFailureCount: tasks.length > 0 ? taskFailureCount : void 0
9890
10098
  };
9891
10099
  const roiStats = recordSession(sessionSummary, roiLearningEntries);
9892
10100
  trackLearnSessionAnalyzed({
@@ -9913,66 +10121,73 @@ async function learnFinalizeCommand(options) {
9913
10121
  estimatedSavingsSeconds: t.estimatedSavingsSeconds,
9914
10122
  learningCount: roiStats.learnings.length
9915
10123
  });
9916
- if (t.estimatedSavingsTokens > 0) {
10124
+ if (!isAuto && t.estimatedSavingsTokens > 0) {
9917
10125
  const totalLearnings = existingLearnedItems + newLearningsProduced;
9918
- console.log(chalk19.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
10126
+ console.log(chalk20.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
9919
10127
  }
9920
10128
  } catch (err) {
9921
- if (options?.force) {
9922
- console.error(chalk19.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
10129
+ if (options?.force && !isAuto) {
10130
+ console.error(chalk20.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
9923
10131
  }
9924
10132
  } finally {
9925
10133
  if (analyzed) {
9926
- clearSession();
9927
- resetState();
10134
+ if (isIncremental) {
10135
+ const state = readState2();
10136
+ state.lastAnalysisEventCount = state.eventCount;
10137
+ state.lastAnalysisTimestamp = (/* @__PURE__ */ new Date()).toISOString();
10138
+ writeState2(state);
10139
+ } else {
10140
+ clearSession();
10141
+ resetState();
10142
+ }
9928
10143
  }
9929
10144
  releaseFinalizeLock();
9930
10145
  }
9931
10146
  }
9932
10147
  async function learnInstallCommand() {
9933
10148
  let anyInstalled = false;
9934
- if (fs34.existsSync(".claude")) {
10149
+ if (fs37.existsSync(".claude")) {
9935
10150
  const r = installLearningHooks();
9936
10151
  if (r.installed) {
9937
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks installed");
10152
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks installed");
9938
10153
  anyInstalled = true;
9939
10154
  } else if (r.alreadyInstalled) {
9940
- console.log(chalk19.dim(" Claude Code hooks already installed"));
10155
+ console.log(chalk20.dim(" Claude Code hooks already installed"));
9941
10156
  }
9942
10157
  }
9943
- if (fs34.existsSync(".cursor")) {
10158
+ if (fs37.existsSync(".cursor")) {
9944
10159
  const r = installCursorLearningHooks();
9945
10160
  if (r.installed) {
9946
- console.log(chalk19.green("\u2713") + " Cursor learning hooks installed");
10161
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks installed");
9947
10162
  anyInstalled = true;
9948
10163
  } else if (r.alreadyInstalled) {
9949
- console.log(chalk19.dim(" Cursor hooks already installed"));
10164
+ console.log(chalk20.dim(" Cursor hooks already installed"));
9950
10165
  }
9951
10166
  }
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."));
10167
+ if (!fs37.existsSync(".claude") && !fs37.existsSync(".cursor")) {
10168
+ console.log(chalk20.yellow("No .claude/ or .cursor/ directory found."));
10169
+ console.log(chalk20.dim(" Run `caliber init` first, or create the directory manually."));
9955
10170
  return;
9956
10171
  }
9957
10172
  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."));
10173
+ console.log(chalk20.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
10174
+ console.log(chalk20.dim(" Learnings written to CALIBER_LEARNINGS.md."));
9960
10175
  }
9961
10176
  }
9962
10177
  async function learnRemoveCommand() {
9963
10178
  let anyRemoved = false;
9964
10179
  const r1 = removeLearningHooks();
9965
10180
  if (r1.removed) {
9966
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks removed");
10181
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks removed");
9967
10182
  anyRemoved = true;
9968
10183
  }
9969
10184
  const r2 = removeCursorLearningHooks();
9970
10185
  if (r2.removed) {
9971
- console.log(chalk19.green("\u2713") + " Cursor learning hooks removed");
10186
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks removed");
9972
10187
  anyRemoved = true;
9973
10188
  }
9974
10189
  if (!anyRemoved) {
9975
- console.log(chalk19.dim("No learning hooks found."));
10190
+ console.log(chalk20.dim("No learning hooks found."));
9976
10191
  }
9977
10192
  }
9978
10193
  async function learnStatusCommand() {
@@ -9980,50 +10195,178 @@ async function learnStatusCommand() {
9980
10195
  const cursorInstalled = areCursorLearningHooksInstalled();
9981
10196
  const state = readState2();
9982
10197
  const eventCount = getEventCount();
9983
- console.log(chalk19.bold("Session Learning Status"));
10198
+ console.log(chalk20.bold("Session Learning Status"));
9984
10199
  console.log();
9985
10200
  if (claudeInstalled) {
9986
- console.log(chalk19.green("\u2713") + " Claude Code hooks " + chalk19.green("installed"));
10201
+ console.log(chalk20.green("\u2713") + " Claude Code hooks " + chalk20.green("installed"));
9987
10202
  } else {
9988
- console.log(chalk19.dim("\u2717") + " Claude Code hooks " + chalk19.dim("not installed"));
10203
+ console.log(chalk20.dim("\u2717") + " Claude Code hooks " + chalk20.dim("not installed"));
9989
10204
  }
9990
10205
  if (cursorInstalled) {
9991
- console.log(chalk19.green("\u2713") + " Cursor hooks " + chalk19.green("installed"));
10206
+ console.log(chalk20.green("\u2713") + " Cursor hooks " + chalk20.green("installed"));
9992
10207
  } else {
9993
- console.log(chalk19.dim("\u2717") + " Cursor hooks " + chalk19.dim("not installed"));
10208
+ console.log(chalk20.dim("\u2717") + " Cursor hooks " + chalk20.dim("not installed"));
9994
10209
  }
9995
10210
  if (!claudeInstalled && !cursorInstalled) {
9996
- console.log(chalk19.dim(" Run `caliber learn install` to enable session learning."));
10211
+ console.log(chalk20.dim(" Run `caliber learn install` to enable session learning."));
9997
10212
  }
9998
10213
  console.log();
9999
- console.log(`Events recorded: ${chalk19.cyan(String(eventCount))}`);
10000
- console.log(`Threshold for analysis: ${chalk19.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
10214
+ console.log(`Events recorded: ${chalk20.cyan(String(eventCount))}`);
10215
+ console.log(`Threshold for analysis: ${chalk20.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
10001
10216
  if (state.lastAnalysisTimestamp) {
10002
- console.log(`Last analysis: ${chalk19.cyan(state.lastAnalysisTimestamp)}`);
10217
+ console.log(`Last analysis: ${chalk20.cyan(state.lastAnalysisTimestamp)}`);
10003
10218
  } else {
10004
- console.log(`Last analysis: ${chalk19.dim("none")}`);
10219
+ console.log(`Last analysis: ${chalk20.dim("none")}`);
10005
10220
  }
10006
10221
  const learnedSection = readLearnedSection();
10007
10222
  if (learnedSection) {
10008
10223
  const lineCount = learnedSection.split("\n").filter(Boolean).length;
10009
10224
  console.log(`
10010
- Learned items in CALIBER_LEARNINGS.md: ${chalk19.cyan(String(lineCount))}`);
10225
+ Learned items in CALIBER_LEARNINGS.md: ${chalk20.cyan(String(lineCount))}`);
10011
10226
  }
10012
10227
  const roiStats = readROIStats();
10013
10228
  const roiSummary = formatROISummary(roiStats);
10014
10229
  if (roiSummary) {
10015
10230
  console.log();
10016
- console.log(chalk19.bold(roiSummary.split("\n")[0]));
10231
+ console.log(chalk20.bold(roiSummary.split("\n")[0]));
10017
10232
  for (const line of roiSummary.split("\n").slice(1)) {
10018
10233
  console.log(line);
10019
10234
  }
10020
10235
  }
10021
10236
  }
10022
10237
 
10238
+ // src/commands/insights.ts
10239
+ import chalk21 from "chalk";
10240
+ var MIN_SESSIONS_FULL = 20;
10241
+ function buildInsightsData(stats) {
10242
+ const t = stats.totals;
10243
+ const totalSessions = t.totalSessionsWithLearnings + t.totalSessionsWithoutLearnings;
10244
+ const failureRateWith = t.totalSessionsWithLearnings > 0 ? t.totalFailuresWithLearnings / t.totalSessionsWithLearnings : null;
10245
+ const failureRateWithout = t.totalSessionsWithoutLearnings > 0 ? t.totalFailuresWithoutLearnings / t.totalSessionsWithoutLearnings : null;
10246
+ const failureRateImprovement = failureRateWith !== null && failureRateWithout !== null && failureRateWithout > 0 ? Math.round((1 - failureRateWith / failureRateWithout) * 100) : null;
10247
+ let taskCount = 0;
10248
+ let taskSuccessCount = 0;
10249
+ let taskCorrectionCount = 0;
10250
+ let taskFailureCount = 0;
10251
+ for (const s of stats.sessions) {
10252
+ if (s.taskCount) {
10253
+ taskCount += s.taskCount;
10254
+ taskSuccessCount += s.taskSuccessCount || 0;
10255
+ taskCorrectionCount += s.taskCorrectionCount || 0;
10256
+ taskFailureCount += s.taskFailureCount || 0;
10257
+ }
10258
+ }
10259
+ const taskSuccessRate = taskCount > 0 ? Math.round(taskSuccessCount / taskCount * 100) : null;
10260
+ return {
10261
+ totalSessions,
10262
+ learningCount: stats.learnings.length,
10263
+ failureRateWith,
10264
+ failureRateWithout,
10265
+ failureRateImprovement,
10266
+ taskCount,
10267
+ taskSuccessCount,
10268
+ taskCorrectionCount,
10269
+ taskFailureCount,
10270
+ taskSuccessRate,
10271
+ totalWasteTokens: t.totalWasteTokens,
10272
+ totalWasteSeconds: t.totalWasteSeconds,
10273
+ estimatedSavingsTokens: t.estimatedSavingsTokens,
10274
+ estimatedSavingsSeconds: t.estimatedSavingsSeconds
10275
+ };
10276
+ }
10277
+ function displayColdStart(score) {
10278
+ console.log(chalk21.bold("\n Agent Insights\n"));
10279
+ const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
10280
+ if (!hooksInstalled) {
10281
+ console.log(chalk21.yellow(" No learning hooks installed."));
10282
+ console.log(chalk21.dim(" Run ") + chalk21.cyan("caliber learn install") + chalk21.dim(" to start tracking agent performance."));
10283
+ } else {
10284
+ console.log(chalk21.dim(" No session data yet. Use your AI agent and insights will appear here."));
10285
+ console.log(chalk21.dim(" Learnings are extracted automatically at the end of each session."));
10286
+ }
10287
+ console.log(chalk21.dim(`
10288
+ Config score: ${score.score}/100 (${score.grade})`));
10289
+ console.log("");
10290
+ }
10291
+ function displayEarlyData(data, score) {
10292
+ console.log(chalk21.bold("\n Agent Insights") + chalk21.yellow(" (early data)\n"));
10293
+ console.log(chalk21.dim(" Still collecting data. Insights become more reliable after 20+ sessions.\n"));
10294
+ console.log(` Sessions tracked: ${chalk21.cyan(String(data.totalSessions))}`);
10295
+ console.log(` Learnings accumulated: ${chalk21.cyan(String(data.learningCount))}`);
10296
+ if (data.totalWasteTokens > 0) {
10297
+ console.log(` Waste captured: ${chalk21.cyan(data.totalWasteTokens.toLocaleString())} tokens`);
10298
+ }
10299
+ if (data.failureRateImprovement !== null && data.failureRateImprovement > 0) {
10300
+ console.log(` Failure rate trend: ${chalk21.green(`${data.failureRateImprovement}% fewer`)} failures with learnings ${chalk21.dim("(early signal)")}`);
10301
+ }
10302
+ if (data.taskSuccessRate !== null) {
10303
+ console.log(` Task success rate: ${chalk21.cyan(`${data.taskSuccessRate}%`)} ${chalk21.dim(`(${data.taskCount} tasks)`)}`);
10304
+ }
10305
+ console.log(` Config score: ${chalk21.cyan(`${score.score}/100`)} (${score.grade})`);
10306
+ console.log("");
10307
+ }
10308
+ function displayFullInsights(data, score) {
10309
+ console.log(chalk21.bold("\n Agent Insights\n"));
10310
+ console.log(chalk21.bold(" Agent Health"));
10311
+ if (data.taskSuccessRate !== null) {
10312
+ const color = data.taskSuccessRate >= 80 ? chalk21.green : data.taskSuccessRate >= 60 ? chalk21.yellow : chalk21.red;
10313
+ console.log(` Task success rate: ${color(`${data.taskSuccessRate}%`)} across ${data.taskCount} tasks`);
10314
+ if (data.taskCorrectionCount > 0) {
10315
+ console.log(` Corrections needed: ${chalk21.yellow(String(data.taskCorrectionCount))} tasks required user correction`);
10316
+ }
10317
+ }
10318
+ console.log(` Sessions tracked: ${chalk21.cyan(String(data.totalSessions))}`);
10319
+ console.log(chalk21.bold("\n Learning Impact"));
10320
+ console.log(` Learnings active: ${chalk21.cyan(String(data.learningCount))}`);
10321
+ if (data.failureRateWith !== null && data.failureRateWithout !== null) {
10322
+ console.log(` Failure rate: ${chalk21.red(data.failureRateWithout.toFixed(1))}/session ${chalk21.dim("\u2192")} ${chalk21.green(data.failureRateWith.toFixed(1))}/session with learnings`);
10323
+ if (data.failureRateImprovement !== null && data.failureRateImprovement > 0) {
10324
+ console.log(` Improvement: ${chalk21.green(`${data.failureRateImprovement}%`)} fewer failures`);
10325
+ }
10326
+ }
10327
+ if (data.totalWasteTokens > 0 || data.estimatedSavingsTokens > 0) {
10328
+ console.log(chalk21.bold("\n Efficiency"));
10329
+ if (data.totalWasteTokens > 0) {
10330
+ console.log(` Waste captured: ${chalk21.cyan(data.totalWasteTokens.toLocaleString())} tokens`);
10331
+ }
10332
+ if (data.estimatedSavingsTokens > 0) {
10333
+ console.log(` Estimated savings: ~${chalk21.green(data.estimatedSavingsTokens.toLocaleString())} tokens`);
10334
+ }
10335
+ if (data.estimatedSavingsSeconds > 0) {
10336
+ console.log(` Time saved: ~${chalk21.green(formatDuration(data.estimatedSavingsSeconds))}`);
10337
+ }
10338
+ }
10339
+ console.log(chalk21.bold("\n Config Quality"));
10340
+ console.log(` Score: ${chalk21.cyan(`${score.score}/100`)} (${score.grade})`);
10341
+ console.log("");
10342
+ }
10343
+ async function insightsCommand(options) {
10344
+ const stats = readROIStats();
10345
+ const data = buildInsightsData(stats);
10346
+ const score = computeLocalScore(process.cwd(), readState()?.targetAgent);
10347
+ trackInsightsViewed(data.totalSessions, data.learningCount);
10348
+ if (options.json) {
10349
+ console.log(JSON.stringify({
10350
+ ...data,
10351
+ tier: data.totalSessions === 0 ? "cold-start" : data.totalSessions < MIN_SESSIONS_FULL ? "early" : "full",
10352
+ configScore: score.score,
10353
+ configGrade: score.grade
10354
+ }, null, 2));
10355
+ return;
10356
+ }
10357
+ if (data.totalSessions === 0) {
10358
+ displayColdStart(score);
10359
+ } else if (data.totalSessions < MIN_SESSIONS_FULL) {
10360
+ displayEarlyData(data, score);
10361
+ } else {
10362
+ displayFullInsights(data, score);
10363
+ }
10364
+ }
10365
+
10023
10366
  // src/cli.ts
10024
- var __dirname = path28.dirname(fileURLToPath(import.meta.url));
10367
+ var __dirname = path30.dirname(fileURLToPath(import.meta.url));
10025
10368
  var pkg = JSON.parse(
10026
- fs35.readFileSync(path28.resolve(__dirname, "..", "package.json"), "utf-8")
10369
+ fs38.readFileSync(path30.resolve(__dirname, "..", "package.json"), "utf-8")
10027
10370
  );
10028
10371
  var program = new Command();
10029
10372
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -10068,6 +10411,10 @@ program.hook("preAction", (thisCommand) => {
10068
10411
  setTelemetryDisabled(true);
10069
10412
  }
10070
10413
  initTelemetry();
10414
+ const cmdName = thisCommand.name();
10415
+ if (cmdName !== "learn" && cmdName !== "observe" && cmdName !== "finalize") {
10416
+ checkPendingNotifications();
10417
+ }
10071
10418
  });
10072
10419
  function parseAgentOption(value) {
10073
10420
  if (value === "both") return ["claude", "cursor"];
@@ -10086,27 +10433,28 @@ program.command("status").description("Show current Caliber setup status").optio
10086
10433
  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
10434
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
10088
10435
  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));
10436
+ 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
10437
  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
10438
  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));
10439
+ program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
10092
10440
  var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
10093
10441
  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)));
10442
+ 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
10443
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
10096
10444
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
10097
10445
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
10098
10446
 
10099
10447
  // src/utils/version-check.ts
10100
- import fs36 from "fs";
10101
- import path29 from "path";
10448
+ import fs39 from "fs";
10449
+ import path31 from "path";
10102
10450
  import { fileURLToPath as fileURLToPath2 } from "url";
10103
10451
  import { execSync as execSync15 } from "child_process";
10104
- import chalk20 from "chalk";
10452
+ import chalk22 from "chalk";
10105
10453
  import ora7 from "ora";
10106
10454
  import confirm2 from "@inquirer/confirm";
10107
- var __dirname_vc = path29.dirname(fileURLToPath2(import.meta.url));
10455
+ var __dirname_vc = path31.dirname(fileURLToPath2(import.meta.url));
10108
10456
  var pkg2 = JSON.parse(
10109
- fs36.readFileSync(path29.resolve(__dirname_vc, "..", "package.json"), "utf-8")
10457
+ fs39.readFileSync(path31.resolve(__dirname_vc, "..", "package.json"), "utf-8")
10110
10458
  );
10111
10459
  function getChannel(version) {
10112
10460
  const match = version.match(/-(dev|next)\./);
@@ -10131,8 +10479,8 @@ function isNewer(registry, current) {
10131
10479
  function getInstalledVersion() {
10132
10480
  try {
10133
10481
  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;
10482
+ const pkgPath = path31.join(globalRoot, "@rely-ai", "caliber", "package.json");
10483
+ return JSON.parse(fs39.readFileSync(pkgPath, "utf-8")).version;
10136
10484
  } catch {
10137
10485
  return null;
10138
10486
  }
@@ -10157,17 +10505,17 @@ async function checkForUpdates() {
10157
10505
  if (!isInteractive) {
10158
10506
  const installTag = channel === "latest" ? "" : `@${channel}`;
10159
10507
  console.log(
10160
- chalk20.yellow(
10508
+ chalk22.yellow(
10161
10509
  `
10162
10510
  Update available: ${current} -> ${latest}
10163
- Run ${chalk20.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
10511
+ Run ${chalk22.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
10164
10512
  `
10165
10513
  )
10166
10514
  );
10167
10515
  return;
10168
10516
  }
10169
10517
  console.log(
10170
- chalk20.yellow(`
10518
+ chalk22.yellow(`
10171
10519
  Update available: ${current} -> ${latest}`)
10172
10520
  );
10173
10521
  const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
@@ -10186,13 +10534,13 @@ Update available: ${current} -> ${latest}`)
10186
10534
  const installed = getInstalledVersion();
10187
10535
  if (installed !== latest) {
10188
10536
  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.
10537
+ console.log(chalk22.yellow(`Run ${chalk22.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
10190
10538
  `));
10191
10539
  return;
10192
10540
  }
10193
- spinner.succeed(chalk20.green(`Updated to ${latest}`));
10541
+ spinner.succeed(chalk22.green(`Updated to ${latest}`));
10194
10542
  const args = process.argv.slice(2);
10195
- console.log(chalk20.dim(`
10543
+ console.log(chalk22.dim(`
10196
10544
  Restarting: caliber ${args.join(" ")}
10197
10545
  `));
10198
10546
  execSync15(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
@@ -10205,11 +10553,11 @@ Restarting: caliber ${args.join(" ")}
10205
10553
  if (err instanceof Error) {
10206
10554
  const stderr = err.stderr;
10207
10555
  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}`));
10556
+ if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk22.dim(` ${errMsg}`));
10209
10557
  }
10210
10558
  console.log(
10211
- chalk20.yellow(
10212
- `Run ${chalk20.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
10559
+ chalk22.yellow(
10560
+ `Run ${chalk22.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
10213
10561
  `
10214
10562
  )
10215
10563
  );