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