@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.
- package/dist/bin.js +790 -279
- 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
|
|
211
|
-
import
|
|
212
|
-
import
|
|
276
|
+
import fs31 from "fs";
|
|
277
|
+
import path25 from "path";
|
|
278
|
+
import os7 from "os";
|
|
213
279
|
function isCaliberRunning() {
|
|
214
280
|
try {
|
|
215
|
-
if (!
|
|
216
|
-
const raw =
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
|
253
|
-
import
|
|
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
|
|
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
|
|
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
|
|
1299
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
1356
|
-
|
|
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
|
|
1364
|
-
if (
|
|
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
|
|
1378
|
-
|
|
1379
|
-
|
|
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 =
|
|
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
|
|
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) :
|
|
1642
|
+
this.timeoutMs = envTimeout ? parseInt(envTimeout, 10) : DEFAULT_TIMEOUT_MS2;
|
|
1426
1643
|
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs < 1e3) {
|
|
1427
|
-
this.timeoutMs =
|
|
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 =
|
|
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
|
|
1482
|
-
|
|
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
|
-
|
|
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 +=
|
|
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 =
|
|
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
|
|
1530
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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(
|
|
2621
|
+
function readFileOrNull(path32) {
|
|
2412
2622
|
try {
|
|
2413
|
-
return readFileSync(
|
|
2623
|
+
return readFileSync(path32, "utf-8");
|
|
2414
2624
|
} catch {
|
|
2415
2625
|
return null;
|
|
2416
2626
|
}
|
|
2417
2627
|
}
|
|
2418
|
-
function readJsonOrNull(
|
|
2419
|
-
const content = readFileOrNull(
|
|
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.
|
|
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 = (
|
|
6782
|
-
const cached = existsCache.get(
|
|
6958
|
+
const cachedExists = (path32) => {
|
|
6959
|
+
const cached = existsCache.get(path32);
|
|
6783
6960
|
if (cached !== void 0) return cached;
|
|
6784
|
-
const result = existsSync9(
|
|
6785
|
-
existsCache.set(
|
|
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(([
|
|
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
|
|
8526
|
-
import
|
|
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
|
|
8605
|
-
import
|
|
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
|
-
|
|
8854
|
+
fs29.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
8610
8855
|
written.push("CLAUDE.md");
|
|
8611
8856
|
}
|
|
8612
8857
|
if (docs.readmeMd) {
|
|
8613
|
-
|
|
8858
|
+
fs29.writeFileSync("README.md", docs.readmeMd);
|
|
8614
8859
|
written.push("README.md");
|
|
8615
8860
|
}
|
|
8616
8861
|
if (docs.cursorrules) {
|
|
8617
|
-
|
|
8862
|
+
fs29.writeFileSync(".cursorrules", docs.cursorrules);
|
|
8618
8863
|
written.push(".cursorrules");
|
|
8619
8864
|
}
|
|
8620
8865
|
if (docs.cursorRules) {
|
|
8621
|
-
const rulesDir =
|
|
8622
|
-
if (!
|
|
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 =
|
|
8625
|
-
|
|
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 =
|
|
8631
|
-
if (!
|
|
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 =
|
|
8634
|
-
|
|
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
|
|
8712
|
-
import
|
|
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
|
-
|
|
9049
|
+
fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
|
|
8797
9050
|
return { newCount, newItems };
|
|
8798
9051
|
}
|
|
8799
9052
|
function writeLearnedSkill(skill) {
|
|
8800
|
-
const skillDir =
|
|
8801
|
-
if (!
|
|
8802
|
-
const skillPath =
|
|
8803
|
-
if (!skill.isNew &&
|
|
8804
|
-
const existing =
|
|
8805
|
-
|
|
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
|
-
|
|
9067
|
+
fs30.writeFileSync(skillPath, frontmatter + skill.content);
|
|
8815
9068
|
}
|
|
8816
9069
|
return skillPath;
|
|
8817
9070
|
}
|
|
8818
9071
|
function readLearnedSection() {
|
|
8819
|
-
if (
|
|
8820
|
-
const content2 =
|
|
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 (!
|
|
8826
|
-
const content =
|
|
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 (
|
|
9086
|
+
if (fs30.existsSync(LEARNINGS_FILE)) return false;
|
|
8834
9087
|
const claudeMdPath = "CLAUDE.md";
|
|
8835
|
-
if (!
|
|
8836
|
-
const content =
|
|
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
|
-
|
|
9095
|
+
fs30.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
8843
9096
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
8844
|
-
|
|
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 =
|
|
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 =
|
|
8860
|
-
if (
|
|
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 =
|
|
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
|
|
9188
|
-
import
|
|
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
|
|
9220
|
-
import
|
|
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 (!
|
|
9229
|
-
|
|
9491
|
+
if (!fs34.existsSync(LEARNING_DIR)) {
|
|
9492
|
+
fs34.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
9230
9493
|
}
|
|
9231
9494
|
}
|
|
9232
9495
|
function sessionFilePath() {
|
|
9233
|
-
return
|
|
9496
|
+
return path27.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
9234
9497
|
}
|
|
9235
9498
|
function stateFilePath() {
|
|
9236
|
-
return
|
|
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
|
-
|
|
9510
|
+
fs34.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
9248
9511
|
const count = getEventCount();
|
|
9249
9512
|
if (count > LEARNING_MAX_EVENTS) {
|
|
9250
|
-
const lines =
|
|
9513
|
+
const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9251
9514
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
9252
|
-
|
|
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
|
-
|
|
9521
|
+
fs34.appendFileSync(filePath, JSON.stringify(event) + "\n");
|
|
9259
9522
|
const count = getEventCount();
|
|
9260
9523
|
if (count > LEARNING_MAX_EVENTS) {
|
|
9261
|
-
const lines =
|
|
9524
|
+
const lines = fs34.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9262
9525
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
9263
|
-
|
|
9526
|
+
fs34.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
9264
9527
|
}
|
|
9265
9528
|
}
|
|
9266
9529
|
function readAllEvents() {
|
|
9267
9530
|
const filePath = sessionFilePath();
|
|
9268
|
-
if (!
|
|
9269
|
-
const lines =
|
|
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 (!
|
|
9282
|
-
const content =
|
|
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 (
|
|
9550
|
+
if (fs34.existsSync(filePath)) fs34.unlinkSync(filePath);
|
|
9288
9551
|
}
|
|
9289
9552
|
function readState2() {
|
|
9290
9553
|
const filePath = stateFilePath();
|
|
9291
|
-
if (!
|
|
9554
|
+
if (!fs34.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
9292
9555
|
try {
|
|
9293
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
|
9571
|
+
return path27.join(LEARNING_DIR, LOCK_FILE2);
|
|
9309
9572
|
}
|
|
9310
9573
|
function acquireFinalizeLock() {
|
|
9311
9574
|
ensureLearningDir();
|
|
9312
9575
|
const lockPath = lockFilePath();
|
|
9313
|
-
if (
|
|
9576
|
+
if (fs34.existsSync(lockPath)) {
|
|
9314
9577
|
try {
|
|
9315
|
-
const stat =
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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:
|
|
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
|
|
9489
|
-
import
|
|
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
|
|
9820
|
+
return path29.join(LEARNING_DIR, LEARNING_ROI_FILE);
|
|
9504
9821
|
}
|
|
9505
9822
|
function readROIStats() {
|
|
9506
9823
|
const filePath = roiFilePath();
|
|
9507
|
-
if (!
|
|
9824
|
+
if (!fs36.existsSync(filePath)) {
|
|
9508
9825
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
9509
9826
|
}
|
|
9510
9827
|
try {
|
|
9511
|
-
return JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
9662
|
-
|
|
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
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
9764
|
-
|
|
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 (
|
|
10149
|
+
if (fs37.existsSync(".claude")) {
|
|
9772
10150
|
const r = installLearningHooks();
|
|
9773
10151
|
if (r.installed) {
|
|
9774
|
-
console.log(
|
|
10152
|
+
console.log(chalk20.green("\u2713") + " Claude Code learning hooks installed");
|
|
9775
10153
|
anyInstalled = true;
|
|
9776
10154
|
} else if (r.alreadyInstalled) {
|
|
9777
|
-
console.log(
|
|
10155
|
+
console.log(chalk20.dim(" Claude Code hooks already installed"));
|
|
9778
10156
|
}
|
|
9779
10157
|
}
|
|
9780
|
-
if (
|
|
10158
|
+
if (fs37.existsSync(".cursor")) {
|
|
9781
10159
|
const r = installCursorLearningHooks();
|
|
9782
10160
|
if (r.installed) {
|
|
9783
|
-
console.log(
|
|
10161
|
+
console.log(chalk20.green("\u2713") + " Cursor learning hooks installed");
|
|
9784
10162
|
anyInstalled = true;
|
|
9785
10163
|
} else if (r.alreadyInstalled) {
|
|
9786
|
-
console.log(
|
|
10164
|
+
console.log(chalk20.dim(" Cursor hooks already installed"));
|
|
9787
10165
|
}
|
|
9788
10166
|
}
|
|
9789
|
-
if (!
|
|
9790
|
-
console.log(
|
|
9791
|
-
console.log(
|
|
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(
|
|
9796
|
-
console.log(
|
|
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(
|
|
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(
|
|
10186
|
+
console.log(chalk20.green("\u2713") + " Cursor learning hooks removed");
|
|
9809
10187
|
anyRemoved = true;
|
|
9810
10188
|
}
|
|
9811
10189
|
if (!anyRemoved) {
|
|
9812
|
-
console.log(
|
|
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(
|
|
10198
|
+
console.log(chalk20.bold("Session Learning Status"));
|
|
9821
10199
|
console.log();
|
|
9822
10200
|
if (claudeInstalled) {
|
|
9823
|
-
console.log(
|
|
10201
|
+
console.log(chalk20.green("\u2713") + " Claude Code hooks " + chalk20.green("installed"));
|
|
9824
10202
|
} else {
|
|
9825
|
-
console.log(
|
|
10203
|
+
console.log(chalk20.dim("\u2717") + " Claude Code hooks " + chalk20.dim("not installed"));
|
|
9826
10204
|
}
|
|
9827
10205
|
if (cursorInstalled) {
|
|
9828
|
-
console.log(
|
|
10206
|
+
console.log(chalk20.green("\u2713") + " Cursor hooks " + chalk20.green("installed"));
|
|
9829
10207
|
} else {
|
|
9830
|
-
console.log(
|
|
10208
|
+
console.log(chalk20.dim("\u2717") + " Cursor hooks " + chalk20.dim("not installed"));
|
|
9831
10209
|
}
|
|
9832
10210
|
if (!claudeInstalled && !cursorInstalled) {
|
|
9833
|
-
console.log(
|
|
10211
|
+
console.log(chalk20.dim(" Run `caliber learn install` to enable session learning."));
|
|
9834
10212
|
}
|
|
9835
10213
|
console.log();
|
|
9836
|
-
console.log(`Events recorded: ${
|
|
9837
|
-
console.log(`Threshold 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: ${
|
|
10217
|
+
console.log(`Last analysis: ${chalk20.cyan(state.lastAnalysisTimestamp)}`);
|
|
9840
10218
|
} else {
|
|
9841
|
-
console.log(`Last analysis: ${
|
|
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: ${
|
|
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(
|
|
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 =
|
|
10367
|
+
var __dirname = path30.dirname(fileURLToPath(import.meta.url));
|
|
9862
10368
|
var pkg = JSON.parse(
|
|
9863
|
-
|
|
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
|
|
9938
|
-
import
|
|
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
|
|
10452
|
+
import chalk22 from "chalk";
|
|
9942
10453
|
import ora7 from "ora";
|
|
9943
10454
|
import confirm2 from "@inquirer/confirm";
|
|
9944
|
-
var __dirname_vc =
|
|
10455
|
+
var __dirname_vc = path31.dirname(fileURLToPath2(import.meta.url));
|
|
9945
10456
|
var pkg2 = JSON.parse(
|
|
9946
|
-
|
|
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 =
|
|
9972
|
-
return JSON.parse(
|
|
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
|
-
|
|
10508
|
+
chalk22.yellow(
|
|
9998
10509
|
`
|
|
9999
10510
|
Update available: ${current} -> ${latest}
|
|
10000
|
-
Run ${
|
|
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
|
-
|
|
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(
|
|
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(
|
|
10541
|
+
spinner.succeed(chalk22.green(`Updated to ${latest}`));
|
|
10031
10542
|
const args = process.argv.slice(2);
|
|
10032
|
-
console.log(
|
|
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(
|
|
10556
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk22.dim(` ${errMsg}`));
|
|
10046
10557
|
}
|
|
10047
10558
|
console.log(
|
|
10048
|
-
|
|
10049
|
-
`Run ${
|
|
10559
|
+
chalk22.yellow(
|
|
10560
|
+
`Run ${chalk22.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
|
|
10050
10561
|
`
|
|
10051
10562
|
)
|
|
10052
10563
|
);
|