@rely-ai/caliber 1.23.0-dev.1773793983 → 1.23.0-dev.1773820608

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 +790 -279
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -169,6 +169,22 @@ var init_config = __esm({
169
169
  }
170
170
  });
171
171
 
172
+ // src/llm/types.ts
173
+ var types_exports = {};
174
+ __export(types_exports, {
175
+ isSeatBased: () => isSeatBased
176
+ });
177
+ function isSeatBased(provider) {
178
+ return SEAT_BASED_PROVIDERS.has(provider);
179
+ }
180
+ var SEAT_BASED_PROVIDERS;
181
+ var init_types = __esm({
182
+ "src/llm/types.ts"() {
183
+ "use strict";
184
+ SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
185
+ }
186
+ });
187
+
172
188
  // src/constants.ts
173
189
  var constants_exports = {};
174
190
  __export(constants_exports, {
@@ -200,6 +216,56 @@ var init_constants = __esm({
200
216
  }
201
217
  });
202
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
+
203
269
  // src/lib/lock.ts
204
270
  var lock_exports = {};
205
271
  __export(lock_exports, {
@@ -207,13 +273,13 @@ __export(lock_exports, {
207
273
  isCaliberRunning: () => isCaliberRunning,
208
274
  releaseLock: () => releaseLock
209
275
  });
210
- import fs30 from "fs";
211
- import path24 from "path";
212
- import os6 from "os";
276
+ import fs31 from "fs";
277
+ import path25 from "path";
278
+ import os7 from "os";
213
279
  function isCaliberRunning() {
214
280
  try {
215
- if (!fs30.existsSync(LOCK_FILE)) return false;
216
- 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();
217
283
  const { pid, ts } = JSON.parse(raw);
218
284
  if (Date.now() - ts > STALE_MS) return false;
219
285
  try {
@@ -228,13 +294,13 @@ function isCaliberRunning() {
228
294
  }
229
295
  function acquireLock() {
230
296
  try {
231
- 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() }));
232
298
  } catch {
233
299
  }
234
300
  }
235
301
  function releaseLock() {
236
302
  try {
237
- if (fs30.existsSync(LOCK_FILE)) fs30.unlinkSync(LOCK_FILE);
303
+ if (fs31.existsSync(LOCK_FILE)) fs31.unlinkSync(LOCK_FILE);
238
304
  } catch {
239
305
  }
240
306
  }
@@ -242,15 +308,15 @@ var LOCK_FILE, STALE_MS;
242
308
  var init_lock = __esm({
243
309
  "src/lib/lock.ts"() {
244
310
  "use strict";
245
- LOCK_FILE = path24.join(os6.tmpdir(), ".caliber.lock");
311
+ LOCK_FILE = path25.join(os7.tmpdir(), ".caliber.lock");
246
312
  STALE_MS = 10 * 60 * 1e3;
247
313
  }
248
314
  });
249
315
 
250
316
  // src/cli.ts
251
317
  import { Command } from "commander";
252
- import fs35 from "fs";
253
- import path28 from "path";
318
+ import fs38 from "fs";
319
+ import path30 from "path";
254
320
  import { fileURLToPath } from "url";
255
321
 
256
322
  // src/commands/init.ts
@@ -1036,8 +1102,8 @@ var AnthropicProvider = class {
1036
1102
  cacheWriteTokens: u.cache_creation_input_tokens
1037
1103
  });
1038
1104
  }
1039
- const block = response.content[0];
1040
- return block.type === "text" ? block.text : "";
1105
+ const block = response.content?.[0];
1106
+ return block?.type === "text" ? block.text : "";
1041
1107
  }
1042
1108
  async listModels() {
1043
1109
  const models = [];
@@ -1140,8 +1206,8 @@ var VertexProvider = class {
1140
1206
  cacheWriteTokens: u.cache_creation_input_tokens
1141
1207
  });
1142
1208
  }
1143
- const block = response.content[0];
1144
- return block.type === "text" ? block.text : "";
1209
+ const block = response.content?.[0];
1210
+ return block?.type === "text" ? block.text : "";
1145
1211
  }
1146
1212
  async stream(options, callbacks) {
1147
1213
  const messages = options.messages ? [
@@ -1261,14 +1327,44 @@ var OpenAICompatProvider = class {
1261
1327
  // src/llm/cursor-acp.ts
1262
1328
  import { spawn, execSync as execSync3 } from "child_process";
1263
1329
  import os2 from "os";
1330
+
1331
+ // src/llm/seat-based-errors.ts
1332
+ var ERROR_PATTERNS = [
1333
+ { pattern: /not logged in|not authenticated|login required|unauthorized/i, message: "Authentication required. Run the login command for your provider to re-authenticate." },
1334
+ { pattern: /rate limit|too many requests|429/i, message: "Rate limit exceeded. Retrying..." },
1335
+ { pattern: /model.*not found|invalid model|model.*unavailable/i, message: "The requested model is not available. Run `caliber config` to select a different model." }
1336
+ ];
1337
+ function parseSeatBasedError(stderr, exitCode) {
1338
+ if (!stderr && exitCode === 0) return null;
1339
+ for (const { pattern, message } of ERROR_PATTERNS) {
1340
+ if (pattern.test(stderr)) return message;
1341
+ }
1342
+ return null;
1343
+ }
1344
+ function isRateLimitError(stderr) {
1345
+ return /rate limit|too many requests|429/i.test(stderr);
1346
+ }
1347
+
1348
+ // src/llm/cursor-acp.ts
1264
1349
  var AGENT_BIN = "agent";
1265
1350
  var IS_WINDOWS = process.platform === "win32";
1351
+ var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
1352
+ var SIGKILL_DELAY_MS = 5e3;
1353
+ var STDERR_MAX_BYTES = 10 * 1024;
1266
1354
  var CursorAcpProvider = class {
1267
1355
  defaultModel;
1268
1356
  cursorApiKey;
1357
+ timeoutMs;
1358
+ warmProcess = null;
1359
+ warmModel = null;
1269
1360
  constructor(config) {
1270
1361
  this.defaultModel = config.model || "sonnet-4.6";
1271
1362
  this.cursorApiKey = process.env.CURSOR_API_KEY ?? process.env.CURSOR_AUTH_TOKEN;
1363
+ const envTimeout = process.env.CALIBER_CURSOR_TIMEOUT_MS;
1364
+ this.timeoutMs = envTimeout ? parseInt(envTimeout, 10) : DEFAULT_TIMEOUT_MS;
1365
+ if (!Number.isFinite(this.timeoutMs) || this.timeoutMs < 1e3) {
1366
+ this.timeoutMs = DEFAULT_TIMEOUT_MS;
1367
+ }
1272
1368
  }
1273
1369
  async call(options) {
1274
1370
  const prompt = this.buildPrompt(options);
@@ -1280,6 +1376,29 @@ var CursorAcpProvider = class {
1280
1376
  const model = options.model || this.defaultModel;
1281
1377
  return this.runPrintStream(model, prompt, callbacks);
1282
1378
  }
1379
+ /**
1380
+ * Pre-spawn an agent process so it's ready when the first call comes.
1381
+ * Call this during fingerprint collection to hide spawn latency.
1382
+ */
1383
+ prewarm(model) {
1384
+ const targetModel = model || this.defaultModel;
1385
+ if (this.warmProcess && !this.warmProcess.killed && this.warmModel === targetModel) return;
1386
+ const args = this.buildArgs(targetModel, false);
1387
+ this.warmProcess = spawn(AGENT_BIN, args, {
1388
+ stdio: ["pipe", "pipe", "pipe"],
1389
+ env: { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } },
1390
+ ...IS_WINDOWS && { shell: true }
1391
+ });
1392
+ this.warmModel = targetModel;
1393
+ this.warmProcess.on("error", () => {
1394
+ this.warmProcess = null;
1395
+ this.warmModel = null;
1396
+ });
1397
+ this.warmProcess.on("close", () => {
1398
+ this.warmProcess = null;
1399
+ this.warmModel = null;
1400
+ });
1401
+ }
1283
1402
  buildArgs(model, streaming) {
1284
1403
  const args = ["--print", "--trust", "--workspace", os2.tmpdir()];
1285
1404
  if (model && model !== "auto" && model !== "default") {
@@ -1293,23 +1412,78 @@ var CursorAcpProvider = class {
1293
1412
  }
1294
1413
  return args;
1295
1414
  }
1415
+ takeWarmProcess(model, streaming) {
1416
+ if (!streaming && this.warmProcess && !this.warmProcess.killed && this.warmModel === model) {
1417
+ const proc = this.warmProcess;
1418
+ this.warmProcess = null;
1419
+ this.warmModel = null;
1420
+ return proc;
1421
+ }
1422
+ return null;
1423
+ }
1424
+ spawnAgent(model, streaming) {
1425
+ const warm = this.takeWarmProcess(model, streaming);
1426
+ if (warm) {
1427
+ const stderrChunks2 = [];
1428
+ warm.stderr?.on("data", (chunk) => {
1429
+ if (Buffer.concat(stderrChunks2).length < STDERR_MAX_BYTES) stderrChunks2.push(chunk);
1430
+ });
1431
+ return { child: warm, stderrChunks: stderrChunks2 };
1432
+ }
1433
+ const args = this.buildArgs(model, streaming);
1434
+ const child = spawn(AGENT_BIN, args, {
1435
+ stdio: ["pipe", "pipe", "pipe"],
1436
+ env: { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } },
1437
+ ...IS_WINDOWS && { shell: true }
1438
+ });
1439
+ const stderrChunks = [];
1440
+ child.stderr.on("data", (chunk) => {
1441
+ if (Buffer.concat(stderrChunks).length < STDERR_MAX_BYTES) stderrChunks.push(chunk);
1442
+ });
1443
+ return { child, stderrChunks };
1444
+ }
1445
+ killWithEscalation(child) {
1446
+ child.kill("SIGTERM");
1447
+ const killTimer = setTimeout(() => {
1448
+ if (!child.killed) child.kill("SIGKILL");
1449
+ }, SIGKILL_DELAY_MS);
1450
+ killTimer.unref();
1451
+ }
1452
+ buildErrorMessage(code, stderrChunks) {
1453
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
1454
+ const parsed = parseSeatBasedError(stderr, code);
1455
+ if (parsed) return parsed;
1456
+ const base = `Cursor agent exited with code ${code}`;
1457
+ return stderr ? `${base}: ${stderr.slice(0, 200)}` : base;
1458
+ }
1296
1459
  runPrint(model, prompt) {
1297
1460
  return new Promise((resolve2, reject) => {
1298
- const args = this.buildArgs(model, false);
1299
- const child = spawn(AGENT_BIN, args, {
1300
- stdio: ["pipe", "pipe", "ignore"],
1301
- env: { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } },
1302
- ...IS_WINDOWS && { shell: true }
1303
- });
1461
+ const { child, stderrChunks } = this.spawnAgent(model, false);
1462
+ let settled = false;
1304
1463
  const chunks = [];
1305
- child.stdout.on("data", (data) => {
1306
- chunks.push(data);
1464
+ child.stdout.on("data", (data) => chunks.push(data));
1465
+ const timer = setTimeout(() => {
1466
+ this.killWithEscalation(child);
1467
+ if (!settled) {
1468
+ settled = true;
1469
+ reject(new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`));
1470
+ }
1471
+ }, this.timeoutMs);
1472
+ timer.unref();
1473
+ child.on("error", (err) => {
1474
+ clearTimeout(timer);
1475
+ if (!settled) {
1476
+ settled = true;
1477
+ reject(err);
1478
+ }
1307
1479
  });
1308
- child.on("error", reject);
1309
1480
  child.on("close", (code) => {
1481
+ clearTimeout(timer);
1482
+ if (settled) return;
1483
+ settled = true;
1310
1484
  const output = Buffer.concat(chunks).toString("utf-8").trim();
1311
1485
  if (code !== 0 && !output) {
1312
- reject(new Error(`Cursor agent exited with code ${code}`));
1486
+ reject(new Error(this.buildErrorMessage(code, stderrChunks)));
1313
1487
  } else {
1314
1488
  resolve2(output);
1315
1489
  }
@@ -1320,14 +1494,20 @@ var CursorAcpProvider = class {
1320
1494
  }
1321
1495
  runPrintStream(model, prompt, callbacks) {
1322
1496
  return new Promise((resolve2, reject) => {
1323
- const args = this.buildArgs(model, true);
1324
- const child = spawn(AGENT_BIN, args, {
1325
- stdio: ["pipe", "pipe", "ignore"],
1326
- env: { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } },
1327
- ...IS_WINDOWS && { shell: true }
1328
- });
1497
+ const { child, stderrChunks } = this.spawnAgent(model, true);
1329
1498
  let buffer = "";
1330
1499
  let endCalled = false;
1500
+ let settled = false;
1501
+ const timer = setTimeout(() => {
1502
+ this.killWithEscalation(child);
1503
+ if (!settled) {
1504
+ settled = true;
1505
+ const err = new Error(`Cursor agent timed out after ${this.timeoutMs / 1e3}s. Set CALIBER_CURSOR_TIMEOUT_MS to increase.`);
1506
+ callbacks.onError(err);
1507
+ reject(err);
1508
+ }
1509
+ }, this.timeoutMs);
1510
+ timer.unref();
1331
1511
  child.stdout.on("data", (data) => {
1332
1512
  buffer += data.toString("utf-8");
1333
1513
  const lines = buffer.split("\n");
@@ -1352,16 +1532,26 @@ var CursorAcpProvider = class {
1352
1532
  }
1353
1533
  });
1354
1534
  child.on("error", (err) => {
1355
- callbacks.onError(err);
1356
- reject(err);
1535
+ clearTimeout(timer);
1536
+ if (!settled) {
1537
+ settled = true;
1538
+ callbacks.onError(err);
1539
+ reject(err);
1540
+ }
1357
1541
  });
1358
1542
  child.on("close", (code) => {
1543
+ clearTimeout(timer);
1544
+ if (settled) return;
1545
+ settled = true;
1359
1546
  if (buffer.trim()) {
1360
1547
  try {
1361
1548
  const event = JSON.parse(buffer);
1362
1549
  if (event.type === "assistant") {
1363
- const text = event.message?.content?.[0]?.text || event.content;
1364
- if (text) callbacks.onText(text);
1550
+ const isDelta = "timestamp_ms" in event;
1551
+ if (isDelta) {
1552
+ const text = event.message?.content?.[0]?.text || event.content;
1553
+ if (text) callbacks.onText(text);
1554
+ }
1365
1555
  } else if (event.type === "result") {
1366
1556
  endCalled = true;
1367
1557
  callbacks.onEnd({ stopReason: event.is_error ? "error" : "end_turn" });
@@ -1374,9 +1564,16 @@ var CursorAcpProvider = class {
1374
1564
  callbacks.onEnd({ stopReason: code === 0 ? "end_turn" : "error" });
1375
1565
  }
1376
1566
  if (code !== 0 && code !== null) {
1377
- const err = new Error(`Cursor agent exited with code ${code}`);
1378
- callbacks.onError(err);
1379
- reject(err);
1567
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
1568
+ if (isRateLimitError(stderr)) {
1569
+ const err = new Error("Rate limit exceeded");
1570
+ callbacks.onError(err);
1571
+ reject(err);
1572
+ } else {
1573
+ const err = new Error(this.buildErrorMessage(code, stderrChunks));
1574
+ callbacks.onError(err);
1575
+ reject(err);
1576
+ }
1380
1577
  } else {
1381
1578
  resolve2();
1382
1579
  }
@@ -1403,28 +1600,48 @@ var CursorAcpProvider = class {
1403
1600
  };
1404
1601
  function isCursorAgentAvailable() {
1405
1602
  try {
1406
- const cmd = process.platform === "win32" ? `where ${AGENT_BIN}` : `which ${AGENT_BIN}`;
1603
+ const cmd = IS_WINDOWS ? `where ${AGENT_BIN}` : `which ${AGENT_BIN}`;
1407
1604
  execSync3(cmd, { stdio: "ignore" });
1408
1605
  return true;
1409
1606
  } catch {
1410
1607
  return false;
1411
1608
  }
1412
1609
  }
1610
+ function isCursorLoggedIn() {
1611
+ try {
1612
+ const result = execSync3(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
1613
+ return !result.toString().includes("not logged in");
1614
+ } catch {
1615
+ return false;
1616
+ }
1617
+ }
1413
1618
 
1414
1619
  // src/llm/claude-cli.ts
1415
1620
  import { spawn as spawn2, execSync as execSync4 } from "child_process";
1416
1621
  var CLAUDE_CLI_BIN = "claude";
1417
- var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
1622
+ var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
1418
1623
  var IS_WINDOWS2 = process.platform === "win32";
1624
+ function spawnClaude(args) {
1625
+ return IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
1626
+ cwd: process.cwd(),
1627
+ stdio: ["pipe", "pipe", "pipe"],
1628
+ env: process.env,
1629
+ shell: true
1630
+ }) : spawn2(CLAUDE_CLI_BIN, args, {
1631
+ cwd: process.cwd(),
1632
+ stdio: ["pipe", "pipe", "pipe"],
1633
+ env: process.env
1634
+ });
1635
+ }
1419
1636
  var ClaudeCliProvider = class {
1420
1637
  defaultModel;
1421
1638
  timeoutMs;
1422
1639
  constructor(config) {
1423
1640
  this.defaultModel = config.model || "default";
1424
1641
  const envTimeout = process.env.CALIBER_CLAUDE_CLI_TIMEOUT_MS;
1425
- this.timeoutMs = envTimeout ? parseInt(envTimeout, 10) : DEFAULT_TIMEOUT_MS;
1642
+ this.timeoutMs = envTimeout ? parseInt(envTimeout, 10) : DEFAULT_TIMEOUT_MS2;
1426
1643
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs < 1e3) {
1427
- this.timeoutMs = DEFAULT_TIMEOUT_MS;
1644
+ this.timeoutMs = DEFAULT_TIMEOUT_MS2;
1428
1645
  }
1429
1646
  }
1430
1647
  async call(options) {
@@ -1435,23 +1652,16 @@ var ClaudeCliProvider = class {
1435
1652
  const combined = this.buildCombinedPrompt(options);
1436
1653
  const args = ["-p"];
1437
1654
  if (options.model) args.push("--model", options.model);
1438
- const child = IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
1439
- cwd: process.cwd(),
1440
- stdio: ["pipe", "pipe", "inherit"],
1441
- env: process.env,
1442
- shell: true
1443
- }) : spawn2(CLAUDE_CLI_BIN, args, {
1444
- cwd: process.cwd(),
1445
- stdio: ["pipe", "pipe", "inherit"],
1446
- env: process.env
1447
- });
1655
+ const child = spawnClaude(args);
1448
1656
  child.stdin.end(combined);
1449
1657
  let settled = false;
1450
1658
  const chunks = [];
1659
+ const stderrChunks = [];
1451
1660
  child.stdout.on("data", (chunk) => {
1452
1661
  chunks.push(chunk);
1453
1662
  callbacks.onText(chunk.toString("utf-8"));
1454
1663
  });
1664
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
1455
1665
  const timer = setTimeout(() => {
1456
1666
  child.kill("SIGTERM");
1457
1667
  if (!settled) {
@@ -1477,45 +1687,40 @@ var ClaudeCliProvider = class {
1477
1687
  if (code === 0) {
1478
1688
  callbacks.onEnd({ stopReason: "end_turn" });
1479
1689
  } else {
1690
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
1691
+ const friendly = parseSeatBasedError(stderr, code);
1480
1692
  const stdout = Buffer.concat(chunks).toString("utf-8").trim();
1481
- const msg = signal ? `Claude CLI killed (${signal})` : code != null ? `Claude CLI exited with code ${code}` : "Claude CLI exited";
1482
- callbacks.onError(new Error(stdout ? `${msg}. Output: ${stdout.slice(0, 200)}` : msg));
1693
+ const base = signal ? `Claude CLI killed (${signal})` : code != null ? `Claude CLI exited with code ${code}` : "Claude CLI exited";
1694
+ const detail = friendly || stderr || (stdout ? stdout.slice(0, 200) : "");
1695
+ callbacks.onError(new Error(detail ? `${base}. ${detail}` : base));
1483
1696
  }
1484
1697
  });
1485
1698
  }
1486
1699
  buildCombinedPrompt(options) {
1487
1700
  const streamOpts = options;
1488
1701
  const hasHistory = streamOpts.messages && streamOpts.messages.length > 0;
1489
- let combined = "";
1490
- combined += "[[System]]\n" + options.system + "\n\n";
1702
+ let combined = options.system + "\n\n";
1491
1703
  if (hasHistory) {
1492
1704
  for (const msg of streamOpts.messages) {
1493
- combined += `[[${msg.role === "user" ? "User" : "Assistant"}]]
1494
- ${msg.content}
1705
+ const label = msg.role === "user" ? "User" : "Assistant";
1706
+ combined += `${label}: ${msg.content}
1495
1707
 
1496
1708
  `;
1497
1709
  }
1498
1710
  }
1499
- combined += "[[User]]\n" + options.prompt;
1711
+ combined += options.prompt;
1500
1712
  return combined;
1501
1713
  }
1502
1714
  runClaudePrint(combinedPrompt, model) {
1503
1715
  return new Promise((resolve2, reject) => {
1504
1716
  const args = ["-p"];
1505
1717
  if (model) args.push("--model", model);
1506
- const child = IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
1507
- cwd: process.cwd(),
1508
- stdio: ["pipe", "pipe", "inherit"],
1509
- env: process.env,
1510
- shell: true
1511
- }) : spawn2(CLAUDE_CLI_BIN, args, {
1512
- cwd: process.cwd(),
1513
- stdio: ["pipe", "pipe", "inherit"],
1514
- env: process.env
1515
- });
1718
+ const child = spawnClaude(args);
1516
1719
  child.stdin.end(combinedPrompt);
1517
1720
  const chunks = [];
1721
+ const stderrChunks = [];
1518
1722
  child.stdout.on("data", (chunk) => chunks.push(chunk));
1723
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
1519
1724
  child.on("error", (err) => {
1520
1725
  clearTimeout(timer);
1521
1726
  reject(err);
@@ -1526,8 +1731,11 @@ ${msg.content}
1526
1731
  if (code === 0) {
1527
1732
  resolve2(stdout);
1528
1733
  } else {
1529
- const msg = signal ? `Claude CLI killed (${signal})` : code != null ? `Claude CLI exited with code ${code}` : "Claude CLI exited";
1530
- reject(new Error(stdout ? `${msg}. Output: ${stdout.slice(0, 200)}` : msg));
1734
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
1735
+ const friendly = parseSeatBasedError(stderr, code);
1736
+ const base = signal ? `Claude CLI killed (${signal})` : code != null ? `Claude CLI exited with code ${code}` : "Claude CLI exited";
1737
+ const detail = friendly || stderr || (stdout ? stdout.slice(0, 200) : "");
1738
+ reject(new Error(detail ? `${base}. ${detail}` : base));
1531
1739
  }
1532
1740
  });
1533
1741
  const timer = setTimeout(() => {
@@ -1701,6 +1909,7 @@ async function handleModelNotAvailable(failedModel, provider, config) {
1701
1909
  }
1702
1910
 
1703
1911
  // src/llm/index.ts
1912
+ init_types();
1704
1913
  init_config();
1705
1914
  var cachedProvider = null;
1706
1915
  var cachedConfig = null;
@@ -1776,11 +1985,11 @@ async function llmCall(options) {
1776
1985
  throw error;
1777
1986
  }
1778
1987
  if (isOverloaded(error) && attempt < MAX_RETRIES) {
1779
- await new Promise((r) => setTimeout(r, 2e3));
1988
+ await new Promise((r) => setTimeout(r, 1e3 * Math.pow(2, attempt - 1)));
1780
1989
  continue;
1781
1990
  }
1782
1991
  if (isTransientError(error) && attempt < MAX_RETRIES) {
1783
- await new Promise((r) => setTimeout(r, 2e3));
1992
+ await new Promise((r) => setTimeout(r, 1e3 * Math.pow(2, attempt - 1)));
1784
1993
  continue;
1785
1994
  }
1786
1995
  throw error;
@@ -1796,7 +2005,8 @@ async function validateModel(options) {
1796
2005
  const provider = getProvider();
1797
2006
  const config = cachedConfig;
1798
2007
  if (!config) return;
1799
- if (config.provider === "cursor" || config.provider === "claude-cli") return;
2008
+ const { isSeatBased: isSeatBased2 } = await Promise.resolve().then(() => (init_types(), types_exports));
2009
+ if (isSeatBased2(config.provider)) return;
1800
2010
  const modelsToCheck = [config.model];
1801
2011
  if (options?.fast) {
1802
2012
  const { getFastModel: getFastModel2 } = await Promise.resolve().then(() => (init_config(), config_exports));
@@ -2408,15 +2618,15 @@ init_config();
2408
2618
  // src/utils/dependencies.ts
2409
2619
  import { readFileSync } from "fs";
2410
2620
  import { join } from "path";
2411
- function readFileOrNull(path30) {
2621
+ function readFileOrNull(path32) {
2412
2622
  try {
2413
- return readFileSync(path30, "utf-8");
2623
+ return readFileSync(path32, "utf-8");
2414
2624
  } catch {
2415
2625
  return null;
2416
2626
  }
2417
2627
  }
2418
- function readJsonOrNull(path30) {
2419
- const content = readFileOrNull(path30);
2628
+ function readJsonOrNull(path32) {
2629
+ const content = readFileOrNull(path32);
2420
2630
  if (!content) return null;
2421
2631
  try {
2422
2632
  return JSON.parse(content);
@@ -3683,50 +3893,10 @@ ${agentRefs.join(" ")}
3683
3893
  }
3684
3894
 
3685
3895
  // src/lib/hooks.ts
3896
+ init_resolve_caliber();
3686
3897
  import fs19 from "fs";
3687
3898
  import path14 from "path";
3688
3899
  import { execSync as execSync8 } from "child_process";
3689
-
3690
- // src/lib/resolve-caliber.ts
3691
- import fs18 from "fs";
3692
- import { execSync as execSync7 } from "child_process";
3693
- var _resolved = null;
3694
- function resolveCaliber() {
3695
- if (_resolved) return _resolved;
3696
- const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
3697
- if (isNpx) {
3698
- _resolved = "npx --yes @rely-ai/caliber";
3699
- return _resolved;
3700
- }
3701
- try {
3702
- const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
3703
- const found = execSync7(whichCmd, {
3704
- encoding: "utf-8",
3705
- stdio: ["pipe", "pipe", "pipe"]
3706
- }).trim();
3707
- if (found) {
3708
- _resolved = found;
3709
- return _resolved;
3710
- }
3711
- } catch {
3712
- }
3713
- const binPath = process.argv[1];
3714
- if (binPath && fs18.existsSync(binPath)) {
3715
- _resolved = binPath;
3716
- return _resolved;
3717
- }
3718
- _resolved = "caliber";
3719
- return _resolved;
3720
- }
3721
- function isCaliberCommand(command, subcommandTail) {
3722
- if (command === `caliber ${subcommandTail}`) return true;
3723
- if (command.endsWith(`/caliber ${subcommandTail}`)) return true;
3724
- if (command === `npx --yes @rely-ai/caliber ${subcommandTail}`) return true;
3725
- if (command === `npx @rely-ai/caliber ${subcommandTail}`) return true;
3726
- return false;
3727
- }
3728
-
3729
- // src/lib/hooks.ts
3730
3900
  var SETTINGS_PATH = path14.join(".claude", "settings.json");
3731
3901
  var REFRESH_TAIL = "refresh --quiet";
3732
3902
  var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
@@ -3862,6 +4032,7 @@ function removePreCommitHook() {
3862
4032
  }
3863
4033
 
3864
4034
  // src/lib/learning-hooks.ts
4035
+ init_resolve_caliber();
3865
4036
  import fs20 from "fs";
3866
4037
  import path15 from "path";
3867
4038
  var SETTINGS_PATH2 = path15.join(".claude", "settings.json");
@@ -3869,7 +4040,7 @@ var HOOK_TAILS = [
3869
4040
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
3870
4041
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
3871
4042
  { event: "UserPromptSubmit", tail: "learn observe --prompt", description: "Caliber: recording user prompt for correction detection" },
3872
- { event: "SessionEnd", tail: "learn finalize", description: "Caliber: finalizing session learnings" }
4043
+ { event: "SessionEnd", tail: "learn finalize --auto", description: "Caliber: finalizing session learnings" }
3873
4044
  ];
3874
4045
  function getHookConfigs() {
3875
4046
  const bin = resolveCaliber();
@@ -3930,7 +4101,7 @@ var CURSOR_HOOK_EVENTS = [
3930
4101
  { event: "postToolUse", tail: "learn observe" },
3931
4102
  { event: "postToolUseFailure", tail: "learn observe --failure" },
3932
4103
  { event: "userPromptSubmit", tail: "learn observe --prompt" },
3933
- { event: "sessionEnd", tail: "learn finalize" }
4104
+ { event: "sessionEnd", tail: "learn finalize --auto" }
3934
4105
  ];
3935
4106
  function readCursorHooks() {
3936
4107
  if (!fs20.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
@@ -4196,8 +4367,11 @@ async function runInteractiveProviderSetup(options) {
4196
4367
  console.log(chalk5.dim(" Then run ") + chalk5.hex("#83D1EB")("agent login") + chalk5.dim(" to authenticate.\n"));
4197
4368
  const proceed = await confirm({ message: "Continue anyway?" });
4198
4369
  if (!proceed) throw new Error("__exit__");
4199
- } else {
4200
- console.log(chalk5.dim(" Run `agent login` if you haven't, or set CURSOR_API_KEY."));
4370
+ } else if (!isCursorLoggedIn()) {
4371
+ console.log(chalk5.yellow("\n Cursor Agent CLI found but not logged in."));
4372
+ console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("agent login") + chalk5.dim(" to authenticate.\n"));
4373
+ const proceed = await confirm({ message: "Continue anyway?" });
4374
+ if (!proceed) throw new Error("__exit__");
4201
4375
  }
4202
4376
  break;
4203
4377
  }
@@ -5991,6 +6165,9 @@ function trackLearnNewLearning(props) {
5991
6165
  source_event_count: props.sourceEventCount
5992
6166
  });
5993
6167
  }
6168
+ function trackInsightsViewed(totalSessions, learningCount) {
6169
+ trackEvent("insights_viewed", { total_sessions: totalSessions, learning_count: learningCount });
6170
+ }
5994
6171
 
5995
6172
  // src/commands/recommend.ts
5996
6173
  function detectLocalPlatforms() {
@@ -6778,11 +6955,11 @@ function countIssuePoints(issues) {
6778
6955
  }
6779
6956
  async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
6780
6957
  const existsCache = /* @__PURE__ */ new Map();
6781
- const cachedExists = (path30) => {
6782
- const cached = existsCache.get(path30);
6958
+ const cachedExists = (path32) => {
6959
+ const cached = existsCache.get(path32);
6783
6960
  if (cached !== void 0) return cached;
6784
- const result = existsSync9(path30);
6785
- existsCache.set(path30, result);
6961
+ const result = existsSync9(path32);
6962
+ existsCache.set(path32, result);
6786
6963
  return result;
6787
6964
  };
6788
6965
  const projectStructure = collectProjectStructure(dir);
@@ -7963,7 +8140,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
7963
8140
  }
7964
8141
  function summarizeSetup(action, setup) {
7965
8142
  const descriptions = setup.fileDescriptions;
7966
- 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(", ");
7967
8144
  return `${action}. Files:
7968
8145
  ${files}`;
7969
8146
  }
@@ -8068,6 +8245,7 @@ async function promptLearnInstall(targetAgent) {
8068
8245
  `));
8069
8246
  return select5({
8070
8247
  message: "Enable session learning?",
8248
+ default: true,
8071
8249
  choices: [
8072
8250
  { name: "Enable session learning (recommended)", value: true },
8073
8251
  { name: "Skip for now", value: false }
@@ -8494,12 +8672,79 @@ async function regenerateCommand(options) {
8494
8672
  }
8495
8673
 
8496
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";
8497
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
+ }
8498
8713
  async function scoreCommand(options) {
8499
8714
  const dir = process.cwd();
8500
8715
  const target = options.agent ?? readState()?.targetAgent;
8501
8716
  const result = computeLocalScore(dir, target);
8502
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
+ }
8503
8748
  if (options.json) {
8504
8749
  console.log(JSON.stringify(result, null, 2));
8505
8750
  return;
@@ -8522,8 +8767,8 @@ async function scoreCommand(options) {
8522
8767
  }
8523
8768
 
8524
8769
  // src/commands/refresh.ts
8525
- import fs31 from "fs";
8526
- import path25 from "path";
8770
+ import fs32 from "fs";
8771
+ import path26 from "path";
8527
8772
  import chalk16 from "chalk";
8528
8773
  import ora6 from "ora";
8529
8774
 
@@ -8601,37 +8846,37 @@ function collectDiff(lastSha) {
8601
8846
  }
8602
8847
 
8603
8848
  // src/writers/refresh.ts
8604
- import fs28 from "fs";
8605
- import path22 from "path";
8849
+ import fs29 from "fs";
8850
+ import path23 from "path";
8606
8851
  function writeRefreshDocs(docs) {
8607
8852
  const written = [];
8608
8853
  if (docs.claudeMd) {
8609
- fs28.writeFileSync("CLAUDE.md", docs.claudeMd);
8854
+ fs29.writeFileSync("CLAUDE.md", docs.claudeMd);
8610
8855
  written.push("CLAUDE.md");
8611
8856
  }
8612
8857
  if (docs.readmeMd) {
8613
- fs28.writeFileSync("README.md", docs.readmeMd);
8858
+ fs29.writeFileSync("README.md", docs.readmeMd);
8614
8859
  written.push("README.md");
8615
8860
  }
8616
8861
  if (docs.cursorrules) {
8617
- fs28.writeFileSync(".cursorrules", docs.cursorrules);
8862
+ fs29.writeFileSync(".cursorrules", docs.cursorrules);
8618
8863
  written.push(".cursorrules");
8619
8864
  }
8620
8865
  if (docs.cursorRules) {
8621
- const rulesDir = path22.join(".cursor", "rules");
8622
- 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 });
8623
8868
  for (const rule of docs.cursorRules) {
8624
- const filePath = path22.join(rulesDir, rule.filename);
8625
- fs28.writeFileSync(filePath, rule.content);
8869
+ const filePath = path23.join(rulesDir, rule.filename);
8870
+ fs29.writeFileSync(filePath, rule.content);
8626
8871
  written.push(filePath);
8627
8872
  }
8628
8873
  }
8629
8874
  if (docs.claudeSkills) {
8630
- const skillsDir = path22.join(".claude", "skills");
8631
- 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 });
8632
8877
  for (const skill of docs.claudeSkills) {
8633
- const filePath = path22.join(skillsDir, skill.filename);
8634
- fs28.writeFileSync(filePath, skill.content);
8878
+ const filePath = path23.join(skillsDir, skill.filename);
8879
+ fs29.writeFileSync(filePath, skill.content);
8635
8880
  written.push(filePath);
8636
8881
  }
8637
8882
  }
@@ -8708,8 +8953,29 @@ Changed files: ${diff.changedFiles.join(", ")}`);
8708
8953
  }
8709
8954
 
8710
8955
  // src/learner/writer.ts
8711
- import fs29 from "fs";
8712
- 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
8713
8979
  var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
8714
8980
  var LEARNINGS_HEADER = `# Caliber Learnings
8715
8981
 
@@ -8756,13 +9022,6 @@ function parseBullets(content) {
8756
9022
  if (current) bullets.push(current);
8757
9023
  return bullets;
8758
9024
  }
8759
- var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
8760
- function normalizeBullet(bullet) {
8761
- return bullet.replace(/^- /, "").replace(TYPE_PREFIX_RE, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
8762
- }
8763
- function hasTypePrefix(bullet) {
8764
- return TYPE_PREFIX_RE.test(bullet.replace(/^- /, ""));
8765
- }
8766
9025
  function deduplicateLearnedItems(existing, incoming) {
8767
9026
  const existingBullets = existing ? parseBullets(existing) : [];
8768
9027
  const incomingBullets = parseBullets(incoming);
@@ -8771,13 +9030,7 @@ function deduplicateLearnedItems(existing, incoming) {
8771
9030
  for (const bullet of incomingBullets) {
8772
9031
  const norm = normalizeBullet(bullet);
8773
9032
  if (!norm) continue;
8774
- const dupIdx = merged.findIndex((e) => {
8775
- const eNorm = normalizeBullet(e);
8776
- const shorter = Math.min(norm.length, eNorm.length);
8777
- const longer = Math.max(norm.length, eNorm.length);
8778
- if (!(eNorm.includes(norm) || norm.includes(eNorm))) return false;
8779
- return shorter / longer > 0.7;
8780
- });
9033
+ const dupIdx = merged.findIndex((e) => isSimilarLearning(bullet, e));
8781
9034
  if (dupIdx !== -1) {
8782
9035
  if (hasTypePrefix(bullet) && !hasTypePrefix(merged[dupIdx])) {
8783
9036
  merged[dupIdx] = bullet;
@@ -8793,16 +9046,16 @@ function deduplicateLearnedItems(existing, incoming) {
8793
9046
  function writeLearnedSection(content) {
8794
9047
  const existingSection = readLearnedSection();
8795
9048
  const { merged, newCount, newItems } = deduplicateLearnedItems(existingSection, content);
8796
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
9049
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
8797
9050
  return { newCount, newItems };
8798
9051
  }
8799
9052
  function writeLearnedSkill(skill) {
8800
- const skillDir = path23.join(".claude", "skills", skill.name);
8801
- if (!fs29.existsSync(skillDir)) fs29.mkdirSync(skillDir, { recursive: true });
8802
- const skillPath = path23.join(skillDir, "SKILL.md");
8803
- if (!skill.isNew && fs29.existsSync(skillPath)) {
8804
- const existing = fs29.readFileSync(skillPath, "utf-8");
8805
- 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);
8806
9059
  } else {
8807
9060
  const frontmatter = [
8808
9061
  "---",
@@ -8811,37 +9064,37 @@ function writeLearnedSkill(skill) {
8811
9064
  "---",
8812
9065
  ""
8813
9066
  ].join("\n");
8814
- fs29.writeFileSync(skillPath, frontmatter + skill.content);
9067
+ fs30.writeFileSync(skillPath, frontmatter + skill.content);
8815
9068
  }
8816
9069
  return skillPath;
8817
9070
  }
8818
9071
  function readLearnedSection() {
8819
- if (fs29.existsSync(LEARNINGS_FILE)) {
8820
- const content2 = fs29.readFileSync(LEARNINGS_FILE, "utf-8");
9072
+ if (fs30.existsSync(LEARNINGS_FILE)) {
9073
+ const content2 = fs30.readFileSync(LEARNINGS_FILE, "utf-8");
8821
9074
  const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
8822
9075
  return bullets || null;
8823
9076
  }
8824
9077
  const claudeMdPath = "CLAUDE.md";
8825
- if (!fs29.existsSync(claudeMdPath)) return null;
8826
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9078
+ if (!fs30.existsSync(claudeMdPath)) return null;
9079
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
8827
9080
  const startIdx = content.indexOf(LEARNED_START);
8828
9081
  const endIdx = content.indexOf(LEARNED_END);
8829
9082
  if (startIdx === -1 || endIdx === -1) return null;
8830
9083
  return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
8831
9084
  }
8832
9085
  function migrateInlineLearnings() {
8833
- if (fs29.existsSync(LEARNINGS_FILE)) return false;
9086
+ if (fs30.existsSync(LEARNINGS_FILE)) return false;
8834
9087
  const claudeMdPath = "CLAUDE.md";
8835
- if (!fs29.existsSync(claudeMdPath)) return false;
8836
- const content = fs29.readFileSync(claudeMdPath, "utf-8");
9088
+ if (!fs30.existsSync(claudeMdPath)) return false;
9089
+ const content = fs30.readFileSync(claudeMdPath, "utf-8");
8837
9090
  const startIdx = content.indexOf(LEARNED_START);
8838
9091
  const endIdx = content.indexOf(LEARNED_END);
8839
9092
  if (startIdx === -1 || endIdx === -1) return false;
8840
9093
  const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
8841
9094
  if (!section) return false;
8842
- fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
9095
+ fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
8843
9096
  const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
8844
- 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");
8845
9098
  return true;
8846
9099
  }
8847
9100
 
@@ -8853,11 +9106,11 @@ function log2(quiet, ...args) {
8853
9106
  function discoverGitRepos(parentDir) {
8854
9107
  const repos = [];
8855
9108
  try {
8856
- const entries = fs31.readdirSync(parentDir, { withFileTypes: true });
9109
+ const entries = fs32.readdirSync(parentDir, { withFileTypes: true });
8857
9110
  for (const entry of entries) {
8858
9111
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
8859
- const childPath = path25.join(parentDir, entry.name);
8860
- if (fs31.existsSync(path25.join(childPath, ".git"))) {
9112
+ const childPath = path26.join(parentDir, entry.name);
9113
+ if (fs32.existsSync(path26.join(childPath, ".git"))) {
8861
9114
  repos.push(childPath);
8862
9115
  }
8863
9116
  }
@@ -8960,7 +9213,7 @@ async function refreshCommand(options) {
8960
9213
  `));
8961
9214
  const originalDir = process.cwd();
8962
9215
  for (const repo of repos) {
8963
- const repoName = path25.basename(repo);
9216
+ const repoName = path26.basename(repo);
8964
9217
  try {
8965
9218
  process.chdir(repo);
8966
9219
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -8981,6 +9234,7 @@ async function refreshCommand(options) {
8981
9234
 
8982
9235
  // src/commands/hooks.ts
8983
9236
  import chalk17 from "chalk";
9237
+ import fs33 from "fs";
8984
9238
  var HOOKS = [
8985
9239
  {
8986
9240
  id: "session-end",
@@ -9020,6 +9274,14 @@ async function hooksCommand(options) {
9020
9274
  console.log(chalk17.green(" \u2713") + ` ${hook.label} enabled`);
9021
9275
  }
9022
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
+ }
9023
9285
  return;
9024
9286
  }
9025
9287
  if (options.remove) {
@@ -9184,8 +9446,8 @@ async function configCommand() {
9184
9446
  }
9185
9447
 
9186
9448
  // src/commands/learn.ts
9187
- import fs34 from "fs";
9188
- import chalk19 from "chalk";
9449
+ import fs37 from "fs";
9450
+ import chalk20 from "chalk";
9189
9451
 
9190
9452
  // src/learner/stdin.ts
9191
9453
  var STDIN_TIMEOUT_MS = 5e3;
@@ -9216,24 +9478,25 @@ function readStdin() {
9216
9478
 
9217
9479
  // src/learner/storage.ts
9218
9480
  init_constants();
9219
- import fs32 from "fs";
9220
- import path26 from "path";
9481
+ import fs34 from "fs";
9482
+ import path27 from "path";
9221
9483
  var MAX_RESPONSE_LENGTH = 2e3;
9222
9484
  var DEFAULT_STATE = {
9223
9485
  sessionId: null,
9224
9486
  eventCount: 0,
9225
- lastAnalysisTimestamp: null
9487
+ lastAnalysisTimestamp: null,
9488
+ lastAnalysisEventCount: 0
9226
9489
  };
9227
9490
  function ensureLearningDir() {
9228
- if (!fs32.existsSync(LEARNING_DIR)) {
9229
- fs32.mkdirSync(LEARNING_DIR, { recursive: true });
9491
+ if (!fs34.existsSync(LEARNING_DIR)) {
9492
+ fs34.mkdirSync(LEARNING_DIR, { recursive: true });
9230
9493
  }
9231
9494
  }
9232
9495
  function sessionFilePath() {
9233
- return path26.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9496
+ return path27.join(LEARNING_DIR, LEARNING_SESSION_FILE);
9234
9497
  }
9235
9498
  function stateFilePath() {
9236
- return path26.join(LEARNING_DIR, LEARNING_STATE_FILE);
9499
+ return path27.join(LEARNING_DIR, LEARNING_STATE_FILE);
9237
9500
  }
9238
9501
  function truncateResponse(response) {
9239
9502
  const str = JSON.stringify(response);
@@ -9244,29 +9507,29 @@ function appendEvent(event) {
9244
9507
  ensureLearningDir();
9245
9508
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
9246
9509
  const filePath = sessionFilePath();
9247
- fs32.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9510
+ fs34.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
9248
9511
  const count = getEventCount();
9249
9512
  if (count > LEARNING_MAX_EVENTS) {
9250
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9513
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9251
9514
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9252
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9515
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9253
9516
  }
9254
9517
  }
9255
9518
  function appendPromptEvent(event) {
9256
9519
  ensureLearningDir();
9257
9520
  const filePath = sessionFilePath();
9258
- fs32.appendFileSync(filePath, JSON.stringify(event) + "\n");
9521
+ fs34.appendFileSync(filePath, JSON.stringify(event) + "\n");
9259
9522
  const count = getEventCount();
9260
9523
  if (count > LEARNING_MAX_EVENTS) {
9261
- const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9524
+ const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
9262
9525
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
9263
- fs32.writeFileSync(filePath, kept.join("\n") + "\n");
9526
+ fs34.writeFileSync(filePath, kept.join("\n") + "\n");
9264
9527
  }
9265
9528
  }
9266
9529
  function readAllEvents() {
9267
9530
  const filePath = sessionFilePath();
9268
- if (!fs32.existsSync(filePath)) return [];
9269
- 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);
9270
9533
  const events = [];
9271
9534
  for (const line of lines) {
9272
9535
  try {
@@ -9278,26 +9541,26 @@ function readAllEvents() {
9278
9541
  }
9279
9542
  function getEventCount() {
9280
9543
  const filePath = sessionFilePath();
9281
- if (!fs32.existsSync(filePath)) return 0;
9282
- const content = fs32.readFileSync(filePath, "utf-8");
9544
+ if (!fs34.existsSync(filePath)) return 0;
9545
+ const content = fs34.readFileSync(filePath, "utf-8");
9283
9546
  return content.split("\n").filter(Boolean).length;
9284
9547
  }
9285
9548
  function clearSession() {
9286
9549
  const filePath = sessionFilePath();
9287
- if (fs32.existsSync(filePath)) fs32.unlinkSync(filePath);
9550
+ if (fs34.existsSync(filePath)) fs34.unlinkSync(filePath);
9288
9551
  }
9289
9552
  function readState2() {
9290
9553
  const filePath = stateFilePath();
9291
- if (!fs32.existsSync(filePath)) return { ...DEFAULT_STATE };
9554
+ if (!fs34.existsSync(filePath)) return { ...DEFAULT_STATE };
9292
9555
  try {
9293
- return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
9556
+ return JSON.parse(fs34.readFileSync(filePath, "utf-8"));
9294
9557
  } catch {
9295
9558
  return { ...DEFAULT_STATE };
9296
9559
  }
9297
9560
  }
9298
9561
  function writeState2(state) {
9299
9562
  ensureLearningDir();
9300
- fs32.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9563
+ fs34.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
9301
9564
  }
9302
9565
  function resetState() {
9303
9566
  writeState2({ ...DEFAULT_STATE });
@@ -9305,14 +9568,14 @@ function resetState() {
9305
9568
  var LOCK_FILE2 = "finalize.lock";
9306
9569
  var LOCK_STALE_MS = 5 * 60 * 1e3;
9307
9570
  function lockFilePath() {
9308
- return path26.join(LEARNING_DIR, LOCK_FILE2);
9571
+ return path27.join(LEARNING_DIR, LOCK_FILE2);
9309
9572
  }
9310
9573
  function acquireFinalizeLock() {
9311
9574
  ensureLearningDir();
9312
9575
  const lockPath = lockFilePath();
9313
- if (fs32.existsSync(lockPath)) {
9576
+ if (fs34.existsSync(lockPath)) {
9314
9577
  try {
9315
- const stat = fs32.statSync(lockPath);
9578
+ const stat = fs34.statSync(lockPath);
9316
9579
  if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
9317
9580
  return false;
9318
9581
  }
@@ -9320,7 +9583,7 @@ function acquireFinalizeLock() {
9320
9583
  }
9321
9584
  }
9322
9585
  try {
9323
- fs32.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9586
+ fs34.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
9324
9587
  return true;
9325
9588
  } catch {
9326
9589
  return false;
@@ -9329,7 +9592,7 @@ function acquireFinalizeLock() {
9329
9592
  function releaseFinalizeLock() {
9330
9593
  const lockPath = lockFilePath();
9331
9594
  try {
9332
- if (fs32.existsSync(lockPath)) fs32.unlinkSync(lockPath);
9595
+ if (fs34.existsSync(lockPath)) fs34.unlinkSync(lockPath);
9333
9596
  } catch {
9334
9597
  }
9335
9598
  }
@@ -9373,6 +9636,45 @@ function sanitizeSecrets(text) {
9373
9636
  return result;
9374
9637
  }
9375
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
+
9376
9678
  // src/ai/learn.ts
9377
9679
  init_config();
9378
9680
  var MAX_PROMPT_TOKENS = 1e5;
@@ -9442,14 +9744,29 @@ ${existingLearnedSection}`);
9442
9744
 
9443
9745
  ${skillsSummary}`);
9444
9746
  }
9445
- 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)
9446
9763
 
9447
9764
  ${eventsText}`;
9448
9765
  const fastModel = getFastModel();
9449
9766
  const raw = await llmCall({
9450
9767
  system: LEARN_SYSTEM_PROMPT,
9451
9768
  prompt,
9452
- maxTokens: 4096,
9769
+ maxTokens: 8192,
9453
9770
  ...fastModel ? { model: fastModel } : {}
9454
9771
  });
9455
9772
  return parseAnalysisResponse(raw);
@@ -9485,8 +9802,8 @@ init_config();
9485
9802
 
9486
9803
  // src/learner/roi.ts
9487
9804
  init_constants();
9488
- import fs33 from "fs";
9489
- import path27 from "path";
9805
+ import fs36 from "fs";
9806
+ import path29 from "path";
9490
9807
  var DEFAULT_TOTALS = {
9491
9808
  totalWasteTokens: 0,
9492
9809
  totalWasteSeconds: 0,
@@ -9500,22 +9817,22 @@ var DEFAULT_TOTALS = {
9500
9817
  lastSessionTimestamp: ""
9501
9818
  };
9502
9819
  function roiFilePath() {
9503
- return path27.join(LEARNING_DIR, LEARNING_ROI_FILE);
9820
+ return path29.join(LEARNING_DIR, LEARNING_ROI_FILE);
9504
9821
  }
9505
9822
  function readROIStats() {
9506
9823
  const filePath = roiFilePath();
9507
- if (!fs33.existsSync(filePath)) {
9824
+ if (!fs36.existsSync(filePath)) {
9508
9825
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9509
9826
  }
9510
9827
  try {
9511
- return JSON.parse(fs33.readFileSync(filePath, "utf-8"));
9828
+ return JSON.parse(fs36.readFileSync(filePath, "utf-8"));
9512
9829
  } catch {
9513
9830
  return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
9514
9831
  }
9515
9832
  }
9516
9833
  function writeROIStats(stats) {
9517
9834
  ensureLearningDir();
9518
- fs33.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9835
+ fs36.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
9519
9836
  }
9520
9837
  function recalculateTotals(stats) {
9521
9838
  const totals = stats.totals;
@@ -9548,7 +9865,16 @@ function recordSession(summary, learnings) {
9548
9865
  const stats = readROIStats();
9549
9866
  stats.sessions.push(summary);
9550
9867
  if (learnings?.length) {
9551
- 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
+ }
9552
9878
  }
9553
9879
  if (stats.sessions.length > MAX_SESSIONS) {
9554
9880
  stats.sessions = stats.sessions.slice(-MAX_SESSIONS);
@@ -9597,6 +9923,9 @@ function formatROISummary(stats) {
9597
9923
 
9598
9924
  // src/commands/learn.ts
9599
9925
  var MIN_EVENTS_FOR_ANALYSIS = 25;
9926
+ var MIN_EVENTS_AUTO = 10;
9927
+ var AUTO_SETTLE_MS = 200;
9928
+ var INCREMENTAL_INTERVAL = 50;
9600
9929
  async function learnObserveCommand(options) {
9601
9930
  try {
9602
9931
  const raw = await readStdin();
@@ -9633,33 +9962,53 @@ async function learnObserveCommand(options) {
9633
9962
  state.eventCount++;
9634
9963
  if (!state.sessionId) state.sessionId = sessionId;
9635
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
+ }
9636
9978
  } catch {
9637
9979
  }
9638
9980
  }
9639
9981
  async function learnFinalizeCommand(options) {
9640
- if (!options?.force) {
9982
+ const isAuto = options?.auto === true;
9983
+ const isIncremental = options?.incremental === true;
9984
+ if (!options?.force && !isAuto) {
9641
9985
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
9642
9986
  if (isCaliberRunning2()) {
9643
- 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"));
9644
9988
  return;
9645
9989
  }
9646
9990
  }
9991
+ if (isAuto) {
9992
+ await new Promise((r) => setTimeout(r, AUTO_SETTLE_MS));
9993
+ }
9647
9994
  if (!acquireFinalizeLock()) {
9648
- 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"));
9649
9996
  return;
9650
9997
  }
9651
9998
  let analyzed = false;
9652
9999
  try {
9653
10000
  const config = loadConfig();
9654
10001
  if (!config) {
9655
- 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"));
9656
10004
  clearSession();
9657
10005
  resetState();
9658
10006
  return;
9659
10007
  }
9660
10008
  const events = readAllEvents();
9661
- if (events.length < MIN_EVENTS_FOR_ANALYSIS) {
9662
- 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`));
9663
10012
  return;
9664
10013
  }
9665
10014
  await validateModel({ fast: true });
@@ -9686,10 +10035,19 @@ async function learnFinalizeCommand(options) {
9686
10035
  });
9687
10036
  newLearningsProduced = result.newItemCount;
9688
10037
  if (result.newItemCount > 0) {
9689
- const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
9690
- console.log(chalk19.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
9691
- for (const item of result.newItems) {
9692
- 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
+ }
9693
10051
  }
9694
10052
  const wastePerLearning = Math.round(waste.totalWasteTokens / result.newItemCount);
9695
10053
  const TYPE_RE = /^\*\*\[([^\]]+)\]\*\*/;
@@ -9714,6 +10072,15 @@ async function learnFinalizeCommand(options) {
9714
10072
  roiLearningEntries = learningEntries;
9715
10073
  }
9716
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
+ }
9717
10084
  const sessionSummary = {
9718
10085
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9719
10086
  sessionId: readState2().sessionId || "unknown",
@@ -9723,7 +10090,11 @@ async function learnFinalizeCommand(options) {
9723
10090
  wasteSeconds: Math.round(waste.totalWasteSeconds),
9724
10091
  hadLearningsAvailable: hadLearnings,
9725
10092
  learningsCount: existingLearnedItems,
9726
- 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
9727
10098
  };
9728
10099
  const roiStats = recordSession(sessionSummary, roiLearningEntries);
9729
10100
  trackLearnSessionAnalyzed({
@@ -9750,66 +10121,73 @@ async function learnFinalizeCommand(options) {
9750
10121
  estimatedSavingsSeconds: t.estimatedSavingsSeconds,
9751
10122
  learningCount: roiStats.learnings.length
9752
10123
  });
9753
- if (t.estimatedSavingsTokens > 0) {
10124
+ if (!isAuto && t.estimatedSavingsTokens > 0) {
9754
10125
  const totalLearnings = existingLearnedItems + newLearningsProduced;
9755
- 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`));
9756
10127
  }
9757
10128
  } catch (err) {
9758
- if (options?.force) {
9759
- 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);
9760
10131
  }
9761
10132
  } finally {
9762
10133
  if (analyzed) {
9763
- clearSession();
9764
- 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
+ }
9765
10143
  }
9766
10144
  releaseFinalizeLock();
9767
10145
  }
9768
10146
  }
9769
10147
  async function learnInstallCommand() {
9770
10148
  let anyInstalled = false;
9771
- if (fs34.existsSync(".claude")) {
10149
+ if (fs37.existsSync(".claude")) {
9772
10150
  const r = installLearningHooks();
9773
10151
  if (r.installed) {
9774
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks installed");
10152
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks installed");
9775
10153
  anyInstalled = true;
9776
10154
  } else if (r.alreadyInstalled) {
9777
- console.log(chalk19.dim(" Claude Code hooks already installed"));
10155
+ console.log(chalk20.dim(" Claude Code hooks already installed"));
9778
10156
  }
9779
10157
  }
9780
- if (fs34.existsSync(".cursor")) {
10158
+ if (fs37.existsSync(".cursor")) {
9781
10159
  const r = installCursorLearningHooks();
9782
10160
  if (r.installed) {
9783
- console.log(chalk19.green("\u2713") + " Cursor learning hooks installed");
10161
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks installed");
9784
10162
  anyInstalled = true;
9785
10163
  } else if (r.alreadyInstalled) {
9786
- console.log(chalk19.dim(" Cursor hooks already installed"));
10164
+ console.log(chalk20.dim(" Cursor hooks already installed"));
9787
10165
  }
9788
10166
  }
9789
- if (!fs34.existsSync(".claude") && !fs34.existsSync(".cursor")) {
9790
- console.log(chalk19.yellow("No .claude/ or .cursor/ directory found."));
9791
- 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."));
9792
10170
  return;
9793
10171
  }
9794
10172
  if (anyInstalled) {
9795
- console.log(chalk19.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
9796
- 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."));
9797
10175
  }
9798
10176
  }
9799
10177
  async function learnRemoveCommand() {
9800
10178
  let anyRemoved = false;
9801
10179
  const r1 = removeLearningHooks();
9802
10180
  if (r1.removed) {
9803
- console.log(chalk19.green("\u2713") + " Claude Code learning hooks removed");
10181
+ console.log(chalk20.green("\u2713") + " Claude Code learning hooks removed");
9804
10182
  anyRemoved = true;
9805
10183
  }
9806
10184
  const r2 = removeCursorLearningHooks();
9807
10185
  if (r2.removed) {
9808
- console.log(chalk19.green("\u2713") + " Cursor learning hooks removed");
10186
+ console.log(chalk20.green("\u2713") + " Cursor learning hooks removed");
9809
10187
  anyRemoved = true;
9810
10188
  }
9811
10189
  if (!anyRemoved) {
9812
- console.log(chalk19.dim("No learning hooks found."));
10190
+ console.log(chalk20.dim("No learning hooks found."));
9813
10191
  }
9814
10192
  }
9815
10193
  async function learnStatusCommand() {
@@ -9817,50 +10195,178 @@ async function learnStatusCommand() {
9817
10195
  const cursorInstalled = areCursorLearningHooksInstalled();
9818
10196
  const state = readState2();
9819
10197
  const eventCount = getEventCount();
9820
- console.log(chalk19.bold("Session Learning Status"));
10198
+ console.log(chalk20.bold("Session Learning Status"));
9821
10199
  console.log();
9822
10200
  if (claudeInstalled) {
9823
- console.log(chalk19.green("\u2713") + " Claude Code hooks " + chalk19.green("installed"));
10201
+ console.log(chalk20.green("\u2713") + " Claude Code hooks " + chalk20.green("installed"));
9824
10202
  } else {
9825
- 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"));
9826
10204
  }
9827
10205
  if (cursorInstalled) {
9828
- console.log(chalk19.green("\u2713") + " Cursor hooks " + chalk19.green("installed"));
10206
+ console.log(chalk20.green("\u2713") + " Cursor hooks " + chalk20.green("installed"));
9829
10207
  } else {
9830
- console.log(chalk19.dim("\u2717") + " Cursor hooks " + chalk19.dim("not installed"));
10208
+ console.log(chalk20.dim("\u2717") + " Cursor hooks " + chalk20.dim("not installed"));
9831
10209
  }
9832
10210
  if (!claudeInstalled && !cursorInstalled) {
9833
- 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."));
9834
10212
  }
9835
10213
  console.log();
9836
- console.log(`Events recorded: ${chalk19.cyan(String(eventCount))}`);
9837
- 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))}`);
9838
10216
  if (state.lastAnalysisTimestamp) {
9839
- console.log(`Last analysis: ${chalk19.cyan(state.lastAnalysisTimestamp)}`);
10217
+ console.log(`Last analysis: ${chalk20.cyan(state.lastAnalysisTimestamp)}`);
9840
10218
  } else {
9841
- console.log(`Last analysis: ${chalk19.dim("none")}`);
10219
+ console.log(`Last analysis: ${chalk20.dim("none")}`);
9842
10220
  }
9843
10221
  const learnedSection = readLearnedSection();
9844
10222
  if (learnedSection) {
9845
10223
  const lineCount = learnedSection.split("\n").filter(Boolean).length;
9846
10224
  console.log(`
9847
- Learned items in CALIBER_LEARNINGS.md: ${chalk19.cyan(String(lineCount))}`);
10225
+ Learned items in CALIBER_LEARNINGS.md: ${chalk20.cyan(String(lineCount))}`);
9848
10226
  }
9849
10227
  const roiStats = readROIStats();
9850
10228
  const roiSummary = formatROISummary(roiStats);
9851
10229
  if (roiSummary) {
9852
10230
  console.log();
9853
- console.log(chalk19.bold(roiSummary.split("\n")[0]));
10231
+ console.log(chalk20.bold(roiSummary.split("\n")[0]));
9854
10232
  for (const line of roiSummary.split("\n").slice(1)) {
9855
10233
  console.log(line);
9856
10234
  }
9857
10235
  }
9858
10236
  }
9859
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
+
9860
10366
  // src/cli.ts
9861
- var __dirname = path28.dirname(fileURLToPath(import.meta.url));
10367
+ var __dirname = path30.dirname(fileURLToPath(import.meta.url));
9862
10368
  var pkg = JSON.parse(
9863
- fs35.readFileSync(path28.resolve(__dirname, "..", "package.json"), "utf-8")
10369
+ fs38.readFileSync(path30.resolve(__dirname, "..", "package.json"), "utf-8")
9864
10370
  );
9865
10371
  var program = new Command();
9866
10372
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -9905,6 +10411,10 @@ program.hook("preAction", (thisCommand) => {
9905
10411
  setTelemetryDisabled(true);
9906
10412
  }
9907
10413
  initTelemetry();
10414
+ const cmdName = thisCommand.name();
10415
+ if (cmdName !== "learn" && cmdName !== "observe" && cmdName !== "finalize") {
10416
+ checkPendingNotifications();
10417
+ }
9908
10418
  });
9909
10419
  function parseAgentOption(value) {
9910
10420
  if (value === "both") return ["claude", "cursor"];
@@ -9923,27 +10433,28 @@ program.command("status").description("Show current Caliber setup status").optio
9923
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));
9924
10434
  program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
9925
10435
  program.command("skills").description("Discover and install community skills for your project").action(tracked("skills", recommendCommand));
9926
- 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));
9927
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));
9928
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));
9929
10440
  var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
9930
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));
9931
- 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)));
9932
10443
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
9933
10444
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
9934
10445
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
9935
10446
 
9936
10447
  // src/utils/version-check.ts
9937
- import fs36 from "fs";
9938
- import path29 from "path";
10448
+ import fs39 from "fs";
10449
+ import path31 from "path";
9939
10450
  import { fileURLToPath as fileURLToPath2 } from "url";
9940
10451
  import { execSync as execSync15 } from "child_process";
9941
- import chalk20 from "chalk";
10452
+ import chalk22 from "chalk";
9942
10453
  import ora7 from "ora";
9943
10454
  import confirm2 from "@inquirer/confirm";
9944
- var __dirname_vc = path29.dirname(fileURLToPath2(import.meta.url));
10455
+ var __dirname_vc = path31.dirname(fileURLToPath2(import.meta.url));
9945
10456
  var pkg2 = JSON.parse(
9946
- fs36.readFileSync(path29.resolve(__dirname_vc, "..", "package.json"), "utf-8")
10457
+ fs39.readFileSync(path31.resolve(__dirname_vc, "..", "package.json"), "utf-8")
9947
10458
  );
9948
10459
  function getChannel(version) {
9949
10460
  const match = version.match(/-(dev|next)\./);
@@ -9968,8 +10479,8 @@ function isNewer(registry, current) {
9968
10479
  function getInstalledVersion() {
9969
10480
  try {
9970
10481
  const globalRoot = execSync15("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9971
- const pkgPath = path29.join(globalRoot, "@rely-ai", "caliber", "package.json");
9972
- 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;
9973
10484
  } catch {
9974
10485
  return null;
9975
10486
  }
@@ -9994,17 +10505,17 @@ async function checkForUpdates() {
9994
10505
  if (!isInteractive) {
9995
10506
  const installTag = channel === "latest" ? "" : `@${channel}`;
9996
10507
  console.log(
9997
- chalk20.yellow(
10508
+ chalk22.yellow(
9998
10509
  `
9999
10510
  Update available: ${current} -> ${latest}
10000
- 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.
10001
10512
  `
10002
10513
  )
10003
10514
  );
10004
10515
  return;
10005
10516
  }
10006
10517
  console.log(
10007
- chalk20.yellow(`
10518
+ chalk22.yellow(`
10008
10519
  Update available: ${current} -> ${latest}`)
10009
10520
  );
10010
10521
  const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
@@ -10023,13 +10534,13 @@ Update available: ${current} -> ${latest}`)
10023
10534
  const installed = getInstalledVersion();
10024
10535
  if (installed !== latest) {
10025
10536
  spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
10026
- 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.
10027
10538
  `));
10028
10539
  return;
10029
10540
  }
10030
- spinner.succeed(chalk20.green(`Updated to ${latest}`));
10541
+ spinner.succeed(chalk22.green(`Updated to ${latest}`));
10031
10542
  const args = process.argv.slice(2);
10032
- console.log(chalk20.dim(`
10543
+ console.log(chalk22.dim(`
10033
10544
  Restarting: caliber ${args.join(" ")}
10034
10545
  `));
10035
10546
  execSync15(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
@@ -10042,11 +10553,11 @@ Restarting: caliber ${args.join(" ")}
10042
10553
  if (err instanceof Error) {
10043
10554
  const stderr = err.stderr;
10044
10555
  const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
10045
- if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk20.dim(` ${errMsg}`));
10556
+ if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk22.dim(` ${errMsg}`));
10046
10557
  }
10047
10558
  console.log(
10048
- chalk20.yellow(
10049
- `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.
10050
10561
  `
10051
10562
  )
10052
10563
  );