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