@rely-ai/caliber 1.23.0-dev.1773820608 → 1.23.1
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 +214 -562
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -216,56 +216,6 @@ 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
|
-
|
|
269
219
|
// src/lib/lock.ts
|
|
270
220
|
var lock_exports = {};
|
|
271
221
|
__export(lock_exports, {
|
|
@@ -273,13 +223,13 @@ __export(lock_exports, {
|
|
|
273
223
|
isCaliberRunning: () => isCaliberRunning,
|
|
274
224
|
releaseLock: () => releaseLock
|
|
275
225
|
});
|
|
276
|
-
import
|
|
277
|
-
import
|
|
278
|
-
import
|
|
226
|
+
import fs30 from "fs";
|
|
227
|
+
import path24 from "path";
|
|
228
|
+
import os6 from "os";
|
|
279
229
|
function isCaliberRunning() {
|
|
280
230
|
try {
|
|
281
|
-
if (!
|
|
282
|
-
const raw =
|
|
231
|
+
if (!fs30.existsSync(LOCK_FILE)) return false;
|
|
232
|
+
const raw = fs30.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
283
233
|
const { pid, ts } = JSON.parse(raw);
|
|
284
234
|
if (Date.now() - ts > STALE_MS) return false;
|
|
285
235
|
try {
|
|
@@ -294,13 +244,13 @@ function isCaliberRunning() {
|
|
|
294
244
|
}
|
|
295
245
|
function acquireLock() {
|
|
296
246
|
try {
|
|
297
|
-
|
|
247
|
+
fs30.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
298
248
|
} catch {
|
|
299
249
|
}
|
|
300
250
|
}
|
|
301
251
|
function releaseLock() {
|
|
302
252
|
try {
|
|
303
|
-
if (
|
|
253
|
+
if (fs30.existsSync(LOCK_FILE)) fs30.unlinkSync(LOCK_FILE);
|
|
304
254
|
} catch {
|
|
305
255
|
}
|
|
306
256
|
}
|
|
@@ -308,15 +258,15 @@ var LOCK_FILE, STALE_MS;
|
|
|
308
258
|
var init_lock = __esm({
|
|
309
259
|
"src/lib/lock.ts"() {
|
|
310
260
|
"use strict";
|
|
311
|
-
LOCK_FILE =
|
|
261
|
+
LOCK_FILE = path24.join(os6.tmpdir(), ".caliber.lock");
|
|
312
262
|
STALE_MS = 10 * 60 * 1e3;
|
|
313
263
|
}
|
|
314
264
|
});
|
|
315
265
|
|
|
316
266
|
// src/cli.ts
|
|
317
267
|
import { Command } from "commander";
|
|
318
|
-
import
|
|
319
|
-
import
|
|
268
|
+
import fs35 from "fs";
|
|
269
|
+
import path28 from "path";
|
|
320
270
|
import { fileURLToPath } from "url";
|
|
321
271
|
|
|
322
272
|
// src/commands/init.ts
|
|
@@ -2618,15 +2568,15 @@ init_config();
|
|
|
2618
2568
|
// src/utils/dependencies.ts
|
|
2619
2569
|
import { readFileSync } from "fs";
|
|
2620
2570
|
import { join } from "path";
|
|
2621
|
-
function readFileOrNull(
|
|
2571
|
+
function readFileOrNull(path30) {
|
|
2622
2572
|
try {
|
|
2623
|
-
return readFileSync(
|
|
2573
|
+
return readFileSync(path30, "utf-8");
|
|
2624
2574
|
} catch {
|
|
2625
2575
|
return null;
|
|
2626
2576
|
}
|
|
2627
2577
|
}
|
|
2628
|
-
function readJsonOrNull(
|
|
2629
|
-
const content = readFileOrNull(
|
|
2578
|
+
function readJsonOrNull(path30) {
|
|
2579
|
+
const content = readFileOrNull(path30);
|
|
2630
2580
|
if (!content) return null;
|
|
2631
2581
|
try {
|
|
2632
2582
|
return JSON.parse(content);
|
|
@@ -3893,10 +3843,50 @@ ${agentRefs.join(" ")}
|
|
|
3893
3843
|
}
|
|
3894
3844
|
|
|
3895
3845
|
// src/lib/hooks.ts
|
|
3896
|
-
init_resolve_caliber();
|
|
3897
3846
|
import fs19 from "fs";
|
|
3898
3847
|
import path14 from "path";
|
|
3899
3848
|
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
|
|
3900
3890
|
var SETTINGS_PATH = path14.join(".claude", "settings.json");
|
|
3901
3891
|
var REFRESH_TAIL = "refresh --quiet";
|
|
3902
3892
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
@@ -4032,7 +4022,6 @@ function removePreCommitHook() {
|
|
|
4032
4022
|
}
|
|
4033
4023
|
|
|
4034
4024
|
// src/lib/learning-hooks.ts
|
|
4035
|
-
init_resolve_caliber();
|
|
4036
4025
|
import fs20 from "fs";
|
|
4037
4026
|
import path15 from "path";
|
|
4038
4027
|
var SETTINGS_PATH2 = path15.join(".claude", "settings.json");
|
|
@@ -4040,7 +4029,7 @@ var HOOK_TAILS = [
|
|
|
4040
4029
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
4041
4030
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
4042
4031
|
{ event: "UserPromptSubmit", tail: "learn observe --prompt", description: "Caliber: recording user prompt for correction detection" },
|
|
4043
|
-
{ event: "SessionEnd", tail: "learn finalize
|
|
4032
|
+
{ event: "SessionEnd", tail: "learn finalize", description: "Caliber: finalizing session learnings" }
|
|
4044
4033
|
];
|
|
4045
4034
|
function getHookConfigs() {
|
|
4046
4035
|
const bin = resolveCaliber();
|
|
@@ -4101,7 +4090,7 @@ var CURSOR_HOOK_EVENTS = [
|
|
|
4101
4090
|
{ event: "postToolUse", tail: "learn observe" },
|
|
4102
4091
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
4103
4092
|
{ event: "userPromptSubmit", tail: "learn observe --prompt" },
|
|
4104
|
-
{ event: "sessionEnd", tail: "learn finalize
|
|
4093
|
+
{ event: "sessionEnd", tail: "learn finalize" }
|
|
4105
4094
|
];
|
|
4106
4095
|
function readCursorHooks() {
|
|
4107
4096
|
if (!fs20.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
@@ -6165,9 +6154,6 @@ function trackLearnNewLearning(props) {
|
|
|
6165
6154
|
source_event_count: props.sourceEventCount
|
|
6166
6155
|
});
|
|
6167
6156
|
}
|
|
6168
|
-
function trackInsightsViewed(totalSessions, learningCount) {
|
|
6169
|
-
trackEvent("insights_viewed", { total_sessions: totalSessions, learning_count: learningCount });
|
|
6170
|
-
}
|
|
6171
6157
|
|
|
6172
6158
|
// src/commands/recommend.ts
|
|
6173
6159
|
function detectLocalPlatforms() {
|
|
@@ -6955,11 +6941,11 @@ function countIssuePoints(issues) {
|
|
|
6955
6941
|
}
|
|
6956
6942
|
async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
6957
6943
|
const existsCache = /* @__PURE__ */ new Map();
|
|
6958
|
-
const cachedExists = (
|
|
6959
|
-
const cached = existsCache.get(
|
|
6944
|
+
const cachedExists = (path30) => {
|
|
6945
|
+
const cached = existsCache.get(path30);
|
|
6960
6946
|
if (cached !== void 0) return cached;
|
|
6961
|
-
const result = existsSync9(
|
|
6962
|
-
existsCache.set(
|
|
6947
|
+
const result = existsSync9(path30);
|
|
6948
|
+
existsCache.set(path30, result);
|
|
6963
6949
|
return result;
|
|
6964
6950
|
};
|
|
6965
6951
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -8140,7 +8126,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
8140
8126
|
}
|
|
8141
8127
|
function summarizeSetup(action, setup) {
|
|
8142
8128
|
const descriptions = setup.fileDescriptions;
|
|
8143
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
8129
|
+
const files = descriptions ? Object.entries(descriptions).map(([path30, desc]) => ` ${path30}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
8144
8130
|
return `${action}. Files:
|
|
8145
8131
|
${files}`;
|
|
8146
8132
|
}
|
|
@@ -8245,7 +8231,6 @@ async function promptLearnInstall(targetAgent) {
|
|
|
8245
8231
|
`));
|
|
8246
8232
|
return select5({
|
|
8247
8233
|
message: "Enable session learning?",
|
|
8248
|
-
default: true,
|
|
8249
8234
|
choices: [
|
|
8250
8235
|
{ name: "Enable session learning (recommended)", value: true },
|
|
8251
8236
|
{ name: "Skip for now", value: false }
|
|
@@ -8672,79 +8657,12 @@ async function regenerateCommand(options) {
|
|
|
8672
8657
|
}
|
|
8673
8658
|
|
|
8674
8659
|
// 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";
|
|
8679
8660
|
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
|
-
}
|
|
8713
8661
|
async function scoreCommand(options) {
|
|
8714
8662
|
const dir = process.cwd();
|
|
8715
8663
|
const target = options.agent ?? readState()?.targetAgent;
|
|
8716
8664
|
const result = computeLocalScore(dir, target);
|
|
8717
8665
|
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
|
-
}
|
|
8748
8666
|
if (options.json) {
|
|
8749
8667
|
console.log(JSON.stringify(result, null, 2));
|
|
8750
8668
|
return;
|
|
@@ -8767,8 +8685,8 @@ async function scoreCommand(options) {
|
|
|
8767
8685
|
}
|
|
8768
8686
|
|
|
8769
8687
|
// src/commands/refresh.ts
|
|
8770
|
-
import
|
|
8771
|
-
import
|
|
8688
|
+
import fs31 from "fs";
|
|
8689
|
+
import path25 from "path";
|
|
8772
8690
|
import chalk16 from "chalk";
|
|
8773
8691
|
import ora6 from "ora";
|
|
8774
8692
|
|
|
@@ -8846,37 +8764,37 @@ function collectDiff(lastSha) {
|
|
|
8846
8764
|
}
|
|
8847
8765
|
|
|
8848
8766
|
// src/writers/refresh.ts
|
|
8849
|
-
import
|
|
8850
|
-
import
|
|
8767
|
+
import fs28 from "fs";
|
|
8768
|
+
import path22 from "path";
|
|
8851
8769
|
function writeRefreshDocs(docs) {
|
|
8852
8770
|
const written = [];
|
|
8853
8771
|
if (docs.claudeMd) {
|
|
8854
|
-
|
|
8772
|
+
fs28.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
8855
8773
|
written.push("CLAUDE.md");
|
|
8856
8774
|
}
|
|
8857
8775
|
if (docs.readmeMd) {
|
|
8858
|
-
|
|
8776
|
+
fs28.writeFileSync("README.md", docs.readmeMd);
|
|
8859
8777
|
written.push("README.md");
|
|
8860
8778
|
}
|
|
8861
8779
|
if (docs.cursorrules) {
|
|
8862
|
-
|
|
8780
|
+
fs28.writeFileSync(".cursorrules", docs.cursorrules);
|
|
8863
8781
|
written.push(".cursorrules");
|
|
8864
8782
|
}
|
|
8865
8783
|
if (docs.cursorRules) {
|
|
8866
|
-
const rulesDir =
|
|
8867
|
-
if (!
|
|
8784
|
+
const rulesDir = path22.join(".cursor", "rules");
|
|
8785
|
+
if (!fs28.existsSync(rulesDir)) fs28.mkdirSync(rulesDir, { recursive: true });
|
|
8868
8786
|
for (const rule of docs.cursorRules) {
|
|
8869
|
-
const filePath =
|
|
8870
|
-
|
|
8787
|
+
const filePath = path22.join(rulesDir, rule.filename);
|
|
8788
|
+
fs28.writeFileSync(filePath, rule.content);
|
|
8871
8789
|
written.push(filePath);
|
|
8872
8790
|
}
|
|
8873
8791
|
}
|
|
8874
8792
|
if (docs.claudeSkills) {
|
|
8875
|
-
const skillsDir =
|
|
8876
|
-
if (!
|
|
8793
|
+
const skillsDir = path22.join(".claude", "skills");
|
|
8794
|
+
if (!fs28.existsSync(skillsDir)) fs28.mkdirSync(skillsDir, { recursive: true });
|
|
8877
8795
|
for (const skill of docs.claudeSkills) {
|
|
8878
|
-
const filePath =
|
|
8879
|
-
|
|
8796
|
+
const filePath = path22.join(skillsDir, skill.filename);
|
|
8797
|
+
fs28.writeFileSync(filePath, skill.content);
|
|
8880
8798
|
written.push(filePath);
|
|
8881
8799
|
}
|
|
8882
8800
|
}
|
|
@@ -8953,29 +8871,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
8953
8871
|
}
|
|
8954
8872
|
|
|
8955
8873
|
// src/learner/writer.ts
|
|
8956
|
-
import
|
|
8957
|
-
import
|
|
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
|
|
8874
|
+
import fs29 from "fs";
|
|
8875
|
+
import path23 from "path";
|
|
8979
8876
|
var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
|
|
8980
8877
|
var LEARNINGS_HEADER = `# Caliber Learnings
|
|
8981
8878
|
|
|
@@ -9022,6 +8919,13 @@ function parseBullets(content) {
|
|
|
9022
8919
|
if (current) bullets.push(current);
|
|
9023
8920
|
return bullets;
|
|
9024
8921
|
}
|
|
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
|
+
}
|
|
9025
8929
|
function deduplicateLearnedItems(existing, incoming) {
|
|
9026
8930
|
const existingBullets = existing ? parseBullets(existing) : [];
|
|
9027
8931
|
const incomingBullets = parseBullets(incoming);
|
|
@@ -9030,7 +8934,13 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9030
8934
|
for (const bullet of incomingBullets) {
|
|
9031
8935
|
const norm = normalizeBullet(bullet);
|
|
9032
8936
|
if (!norm) continue;
|
|
9033
|
-
const dupIdx = merged.findIndex((e) =>
|
|
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
|
+
});
|
|
9034
8944
|
if (dupIdx !== -1) {
|
|
9035
8945
|
if (hasTypePrefix(bullet) && !hasTypePrefix(merged[dupIdx])) {
|
|
9036
8946
|
merged[dupIdx] = bullet;
|
|
@@ -9046,16 +8956,16 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9046
8956
|
function writeLearnedSection(content) {
|
|
9047
8957
|
const existingSection = readLearnedSection();
|
|
9048
8958
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existingSection, content);
|
|
9049
|
-
|
|
8959
|
+
fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
|
|
9050
8960
|
return { newCount, newItems };
|
|
9051
8961
|
}
|
|
9052
8962
|
function writeLearnedSkill(skill) {
|
|
9053
|
-
const skillDir =
|
|
9054
|
-
if (!
|
|
9055
|
-
const skillPath =
|
|
9056
|
-
if (!skill.isNew &&
|
|
9057
|
-
const existing =
|
|
9058
|
-
|
|
8963
|
+
const skillDir = path23.join(".claude", "skills", skill.name);
|
|
8964
|
+
if (!fs29.existsSync(skillDir)) fs29.mkdirSync(skillDir, { recursive: true });
|
|
8965
|
+
const skillPath = path23.join(skillDir, "SKILL.md");
|
|
8966
|
+
if (!skill.isNew && fs29.existsSync(skillPath)) {
|
|
8967
|
+
const existing = fs29.readFileSync(skillPath, "utf-8");
|
|
8968
|
+
fs29.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
9059
8969
|
} else {
|
|
9060
8970
|
const frontmatter = [
|
|
9061
8971
|
"---",
|
|
@@ -9064,37 +8974,37 @@ function writeLearnedSkill(skill) {
|
|
|
9064
8974
|
"---",
|
|
9065
8975
|
""
|
|
9066
8976
|
].join("\n");
|
|
9067
|
-
|
|
8977
|
+
fs29.writeFileSync(skillPath, frontmatter + skill.content);
|
|
9068
8978
|
}
|
|
9069
8979
|
return skillPath;
|
|
9070
8980
|
}
|
|
9071
8981
|
function readLearnedSection() {
|
|
9072
|
-
if (
|
|
9073
|
-
const content2 =
|
|
8982
|
+
if (fs29.existsSync(LEARNINGS_FILE)) {
|
|
8983
|
+
const content2 = fs29.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
9074
8984
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9075
8985
|
return bullets || null;
|
|
9076
8986
|
}
|
|
9077
8987
|
const claudeMdPath = "CLAUDE.md";
|
|
9078
|
-
if (!
|
|
9079
|
-
const content =
|
|
8988
|
+
if (!fs29.existsSync(claudeMdPath)) return null;
|
|
8989
|
+
const content = fs29.readFileSync(claudeMdPath, "utf-8");
|
|
9080
8990
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9081
8991
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9082
8992
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
9083
8993
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
9084
8994
|
}
|
|
9085
8995
|
function migrateInlineLearnings() {
|
|
9086
|
-
if (
|
|
8996
|
+
if (fs29.existsSync(LEARNINGS_FILE)) return false;
|
|
9087
8997
|
const claudeMdPath = "CLAUDE.md";
|
|
9088
|
-
if (!
|
|
9089
|
-
const content =
|
|
8998
|
+
if (!fs29.existsSync(claudeMdPath)) return false;
|
|
8999
|
+
const content = fs29.readFileSync(claudeMdPath, "utf-8");
|
|
9090
9000
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9091
9001
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9092
9002
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
9093
9003
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
9094
9004
|
if (!section) return false;
|
|
9095
|
-
|
|
9005
|
+
fs29.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
9096
9006
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
9097
|
-
|
|
9007
|
+
fs29.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
9098
9008
|
return true;
|
|
9099
9009
|
}
|
|
9100
9010
|
|
|
@@ -9106,11 +9016,11 @@ function log2(quiet, ...args) {
|
|
|
9106
9016
|
function discoverGitRepos(parentDir) {
|
|
9107
9017
|
const repos = [];
|
|
9108
9018
|
try {
|
|
9109
|
-
const entries =
|
|
9019
|
+
const entries = fs31.readdirSync(parentDir, { withFileTypes: true });
|
|
9110
9020
|
for (const entry of entries) {
|
|
9111
9021
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
9112
|
-
const childPath =
|
|
9113
|
-
if (
|
|
9022
|
+
const childPath = path25.join(parentDir, entry.name);
|
|
9023
|
+
if (fs31.existsSync(path25.join(childPath, ".git"))) {
|
|
9114
9024
|
repos.push(childPath);
|
|
9115
9025
|
}
|
|
9116
9026
|
}
|
|
@@ -9213,7 +9123,7 @@ async function refreshCommand(options) {
|
|
|
9213
9123
|
`));
|
|
9214
9124
|
const originalDir = process.cwd();
|
|
9215
9125
|
for (const repo of repos) {
|
|
9216
|
-
const repoName =
|
|
9126
|
+
const repoName = path25.basename(repo);
|
|
9217
9127
|
try {
|
|
9218
9128
|
process.chdir(repo);
|
|
9219
9129
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -9234,7 +9144,6 @@ async function refreshCommand(options) {
|
|
|
9234
9144
|
|
|
9235
9145
|
// src/commands/hooks.ts
|
|
9236
9146
|
import chalk17 from "chalk";
|
|
9237
|
-
import fs33 from "fs";
|
|
9238
9147
|
var HOOKS = [
|
|
9239
9148
|
{
|
|
9240
9149
|
id: "session-end",
|
|
@@ -9274,14 +9183,6 @@ async function hooksCommand(options) {
|
|
|
9274
9183
|
console.log(chalk17.green(" \u2713") + ` ${hook.label} enabled`);
|
|
9275
9184
|
}
|
|
9276
9185
|
}
|
|
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
|
-
}
|
|
9285
9186
|
return;
|
|
9286
9187
|
}
|
|
9287
9188
|
if (options.remove) {
|
|
@@ -9446,8 +9347,8 @@ async function configCommand() {
|
|
|
9446
9347
|
}
|
|
9447
9348
|
|
|
9448
9349
|
// src/commands/learn.ts
|
|
9449
|
-
import
|
|
9450
|
-
import
|
|
9350
|
+
import fs34 from "fs";
|
|
9351
|
+
import chalk19 from "chalk";
|
|
9451
9352
|
|
|
9452
9353
|
// src/learner/stdin.ts
|
|
9453
9354
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -9478,25 +9379,24 @@ function readStdin() {
|
|
|
9478
9379
|
|
|
9479
9380
|
// src/learner/storage.ts
|
|
9480
9381
|
init_constants();
|
|
9481
|
-
import
|
|
9482
|
-
import
|
|
9382
|
+
import fs32 from "fs";
|
|
9383
|
+
import path26 from "path";
|
|
9483
9384
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
9484
9385
|
var DEFAULT_STATE = {
|
|
9485
9386
|
sessionId: null,
|
|
9486
9387
|
eventCount: 0,
|
|
9487
|
-
lastAnalysisTimestamp: null
|
|
9488
|
-
lastAnalysisEventCount: 0
|
|
9388
|
+
lastAnalysisTimestamp: null
|
|
9489
9389
|
};
|
|
9490
9390
|
function ensureLearningDir() {
|
|
9491
|
-
if (!
|
|
9492
|
-
|
|
9391
|
+
if (!fs32.existsSync(LEARNING_DIR)) {
|
|
9392
|
+
fs32.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
9493
9393
|
}
|
|
9494
9394
|
}
|
|
9495
9395
|
function sessionFilePath() {
|
|
9496
|
-
return
|
|
9396
|
+
return path26.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
9497
9397
|
}
|
|
9498
9398
|
function stateFilePath() {
|
|
9499
|
-
return
|
|
9399
|
+
return path26.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
9500
9400
|
}
|
|
9501
9401
|
function truncateResponse(response) {
|
|
9502
9402
|
const str = JSON.stringify(response);
|
|
@@ -9507,29 +9407,29 @@ function appendEvent(event) {
|
|
|
9507
9407
|
ensureLearningDir();
|
|
9508
9408
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
9509
9409
|
const filePath = sessionFilePath();
|
|
9510
|
-
|
|
9410
|
+
fs32.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
9511
9411
|
const count = getEventCount();
|
|
9512
9412
|
if (count > LEARNING_MAX_EVENTS) {
|
|
9513
|
-
const lines =
|
|
9413
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9514
9414
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
9515
|
-
|
|
9415
|
+
fs32.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
9516
9416
|
}
|
|
9517
9417
|
}
|
|
9518
9418
|
function appendPromptEvent(event) {
|
|
9519
9419
|
ensureLearningDir();
|
|
9520
9420
|
const filePath = sessionFilePath();
|
|
9521
|
-
|
|
9421
|
+
fs32.appendFileSync(filePath, JSON.stringify(event) + "\n");
|
|
9522
9422
|
const count = getEventCount();
|
|
9523
9423
|
if (count > LEARNING_MAX_EVENTS) {
|
|
9524
|
-
const lines =
|
|
9424
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9525
9425
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
9526
|
-
|
|
9426
|
+
fs32.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
9527
9427
|
}
|
|
9528
9428
|
}
|
|
9529
9429
|
function readAllEvents() {
|
|
9530
9430
|
const filePath = sessionFilePath();
|
|
9531
|
-
if (!
|
|
9532
|
-
const lines =
|
|
9431
|
+
if (!fs32.existsSync(filePath)) return [];
|
|
9432
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9533
9433
|
const events = [];
|
|
9534
9434
|
for (const line of lines) {
|
|
9535
9435
|
try {
|
|
@@ -9541,26 +9441,26 @@ function readAllEvents() {
|
|
|
9541
9441
|
}
|
|
9542
9442
|
function getEventCount() {
|
|
9543
9443
|
const filePath = sessionFilePath();
|
|
9544
|
-
if (!
|
|
9545
|
-
const content =
|
|
9444
|
+
if (!fs32.existsSync(filePath)) return 0;
|
|
9445
|
+
const content = fs32.readFileSync(filePath, "utf-8");
|
|
9546
9446
|
return content.split("\n").filter(Boolean).length;
|
|
9547
9447
|
}
|
|
9548
9448
|
function clearSession() {
|
|
9549
9449
|
const filePath = sessionFilePath();
|
|
9550
|
-
if (
|
|
9450
|
+
if (fs32.existsSync(filePath)) fs32.unlinkSync(filePath);
|
|
9551
9451
|
}
|
|
9552
9452
|
function readState2() {
|
|
9553
9453
|
const filePath = stateFilePath();
|
|
9554
|
-
if (!
|
|
9454
|
+
if (!fs32.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
9555
9455
|
try {
|
|
9556
|
-
return JSON.parse(
|
|
9456
|
+
return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
|
|
9557
9457
|
} catch {
|
|
9558
9458
|
return { ...DEFAULT_STATE };
|
|
9559
9459
|
}
|
|
9560
9460
|
}
|
|
9561
9461
|
function writeState2(state) {
|
|
9562
9462
|
ensureLearningDir();
|
|
9563
|
-
|
|
9463
|
+
fs32.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
9564
9464
|
}
|
|
9565
9465
|
function resetState() {
|
|
9566
9466
|
writeState2({ ...DEFAULT_STATE });
|
|
@@ -9568,14 +9468,14 @@ function resetState() {
|
|
|
9568
9468
|
var LOCK_FILE2 = "finalize.lock";
|
|
9569
9469
|
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
9570
9470
|
function lockFilePath() {
|
|
9571
|
-
return
|
|
9471
|
+
return path26.join(LEARNING_DIR, LOCK_FILE2);
|
|
9572
9472
|
}
|
|
9573
9473
|
function acquireFinalizeLock() {
|
|
9574
9474
|
ensureLearningDir();
|
|
9575
9475
|
const lockPath = lockFilePath();
|
|
9576
|
-
if (
|
|
9476
|
+
if (fs32.existsSync(lockPath)) {
|
|
9577
9477
|
try {
|
|
9578
|
-
const stat =
|
|
9478
|
+
const stat = fs32.statSync(lockPath);
|
|
9579
9479
|
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
|
|
9580
9480
|
return false;
|
|
9581
9481
|
}
|
|
@@ -9583,7 +9483,7 @@ function acquireFinalizeLock() {
|
|
|
9583
9483
|
}
|
|
9584
9484
|
}
|
|
9585
9485
|
try {
|
|
9586
|
-
|
|
9486
|
+
fs32.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
9587
9487
|
return true;
|
|
9588
9488
|
} catch {
|
|
9589
9489
|
return false;
|
|
@@ -9592,7 +9492,7 @@ function acquireFinalizeLock() {
|
|
|
9592
9492
|
function releaseFinalizeLock() {
|
|
9593
9493
|
const lockPath = lockFilePath();
|
|
9594
9494
|
try {
|
|
9595
|
-
if (
|
|
9495
|
+
if (fs32.existsSync(lockPath)) fs32.unlinkSync(lockPath);
|
|
9596
9496
|
} catch {
|
|
9597
9497
|
}
|
|
9598
9498
|
}
|
|
@@ -9636,45 +9536,6 @@ function sanitizeSecrets(text) {
|
|
|
9636
9536
|
return result;
|
|
9637
9537
|
}
|
|
9638
9538
|
|
|
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
|
-
|
|
9678
9539
|
// src/ai/learn.ts
|
|
9679
9540
|
init_config();
|
|
9680
9541
|
var MAX_PROMPT_TOKENS = 1e5;
|
|
@@ -9744,29 +9605,14 @@ ${existingLearnedSection}`);
|
|
|
9744
9605
|
|
|
9745
9606
|
${skillsSummary}`);
|
|
9746
9607
|
}
|
|
9747
|
-
contextParts.
|
|
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)
|
|
9608
|
+
const prompt = `${contextParts.length ? contextParts.join("\n\n---\n\n") + "\n\n---\n\n" : ""}## Tool Events from Session (${fittedEvents.length} events)
|
|
9763
9609
|
|
|
9764
9610
|
${eventsText}`;
|
|
9765
9611
|
const fastModel = getFastModel();
|
|
9766
9612
|
const raw = await llmCall({
|
|
9767
9613
|
system: LEARN_SYSTEM_PROMPT,
|
|
9768
9614
|
prompt,
|
|
9769
|
-
maxTokens:
|
|
9615
|
+
maxTokens: 4096,
|
|
9770
9616
|
...fastModel ? { model: fastModel } : {}
|
|
9771
9617
|
});
|
|
9772
9618
|
return parseAnalysisResponse(raw);
|
|
@@ -9802,8 +9648,8 @@ init_config();
|
|
|
9802
9648
|
|
|
9803
9649
|
// src/learner/roi.ts
|
|
9804
9650
|
init_constants();
|
|
9805
|
-
import
|
|
9806
|
-
import
|
|
9651
|
+
import fs33 from "fs";
|
|
9652
|
+
import path27 from "path";
|
|
9807
9653
|
var DEFAULT_TOTALS = {
|
|
9808
9654
|
totalWasteTokens: 0,
|
|
9809
9655
|
totalWasteSeconds: 0,
|
|
@@ -9817,22 +9663,22 @@ var DEFAULT_TOTALS = {
|
|
|
9817
9663
|
lastSessionTimestamp: ""
|
|
9818
9664
|
};
|
|
9819
9665
|
function roiFilePath() {
|
|
9820
|
-
return
|
|
9666
|
+
return path27.join(LEARNING_DIR, LEARNING_ROI_FILE);
|
|
9821
9667
|
}
|
|
9822
9668
|
function readROIStats() {
|
|
9823
9669
|
const filePath = roiFilePath();
|
|
9824
|
-
if (!
|
|
9670
|
+
if (!fs33.existsSync(filePath)) {
|
|
9825
9671
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
9826
9672
|
}
|
|
9827
9673
|
try {
|
|
9828
|
-
return JSON.parse(
|
|
9674
|
+
return JSON.parse(fs33.readFileSync(filePath, "utf-8"));
|
|
9829
9675
|
} catch {
|
|
9830
9676
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
9831
9677
|
}
|
|
9832
9678
|
}
|
|
9833
9679
|
function writeROIStats(stats) {
|
|
9834
9680
|
ensureLearningDir();
|
|
9835
|
-
|
|
9681
|
+
fs33.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
|
|
9836
9682
|
}
|
|
9837
9683
|
function recalculateTotals(stats) {
|
|
9838
9684
|
const totals = stats.totals;
|
|
@@ -9865,16 +9711,7 @@ function recordSession(summary, learnings) {
|
|
|
9865
9711
|
const stats = readROIStats();
|
|
9866
9712
|
stats.sessions.push(summary);
|
|
9867
9713
|
if (learnings?.length) {
|
|
9868
|
-
|
|
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
|
-
}
|
|
9714
|
+
stats.learnings.push(...learnings);
|
|
9878
9715
|
}
|
|
9879
9716
|
if (stats.sessions.length > MAX_SESSIONS) {
|
|
9880
9717
|
stats.sessions = stats.sessions.slice(-MAX_SESSIONS);
|
|
@@ -9923,9 +9760,6 @@ function formatROISummary(stats) {
|
|
|
9923
9760
|
|
|
9924
9761
|
// src/commands/learn.ts
|
|
9925
9762
|
var MIN_EVENTS_FOR_ANALYSIS = 25;
|
|
9926
|
-
var MIN_EVENTS_AUTO = 10;
|
|
9927
|
-
var AUTO_SETTLE_MS = 200;
|
|
9928
|
-
var INCREMENTAL_INTERVAL = 50;
|
|
9929
9763
|
async function learnObserveCommand(options) {
|
|
9930
9764
|
try {
|
|
9931
9765
|
const raw = await readStdin();
|
|
@@ -9962,53 +9796,33 @@ async function learnObserveCommand(options) {
|
|
|
9962
9796
|
state.eventCount++;
|
|
9963
9797
|
if (!state.sessionId) state.sessionId = sessionId;
|
|
9964
9798
|
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
|
-
}
|
|
9978
9799
|
} catch {
|
|
9979
9800
|
}
|
|
9980
9801
|
}
|
|
9981
9802
|
async function learnFinalizeCommand(options) {
|
|
9982
|
-
|
|
9983
|
-
const isIncremental = options?.incremental === true;
|
|
9984
|
-
if (!options?.force && !isAuto) {
|
|
9803
|
+
if (!options?.force) {
|
|
9985
9804
|
const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
|
|
9986
9805
|
if (isCaliberRunning2()) {
|
|
9987
|
-
|
|
9806
|
+
console.log(chalk19.dim("caliber: skipping finalize \u2014 another caliber process is running"));
|
|
9988
9807
|
return;
|
|
9989
9808
|
}
|
|
9990
9809
|
}
|
|
9991
|
-
if (isAuto) {
|
|
9992
|
-
await new Promise((r) => setTimeout(r, AUTO_SETTLE_MS));
|
|
9993
|
-
}
|
|
9994
9810
|
if (!acquireFinalizeLock()) {
|
|
9995
|
-
|
|
9811
|
+
console.log(chalk19.dim("caliber: skipping finalize \u2014 another finalize is in progress"));
|
|
9996
9812
|
return;
|
|
9997
9813
|
}
|
|
9998
9814
|
let analyzed = false;
|
|
9999
9815
|
try {
|
|
10000
9816
|
const config = loadConfig();
|
|
10001
9817
|
if (!config) {
|
|
10002
|
-
|
|
10003
|
-
console.log(chalk20.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
|
|
9818
|
+
console.log(chalk19.yellow("caliber: no LLM provider configured \u2014 run `caliber config` first"));
|
|
10004
9819
|
clearSession();
|
|
10005
9820
|
resetState();
|
|
10006
9821
|
return;
|
|
10007
9822
|
}
|
|
10008
9823
|
const events = readAllEvents();
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
if (!isAuto) console.log(chalk20.dim(`caliber: ${events.length}/${threshold} events recorded \u2014 need more before analysis`));
|
|
9824
|
+
if (events.length < MIN_EVENTS_FOR_ANALYSIS) {
|
|
9825
|
+
console.log(chalk19.dim(`caliber: ${events.length}/${MIN_EVENTS_FOR_ANALYSIS} events recorded \u2014 need more before analysis`));
|
|
10012
9826
|
return;
|
|
10013
9827
|
}
|
|
10014
9828
|
await validateModel({ fast: true });
|
|
@@ -10035,19 +9849,10 @@ async function learnFinalizeCommand(options) {
|
|
|
10035
9849
|
});
|
|
10036
9850
|
newLearningsProduced = result.newItemCount;
|
|
10037
9851
|
if (result.newItemCount > 0) {
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
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
|
-
}
|
|
9852
|
+
const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
|
|
9853
|
+
console.log(chalk19.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
|
|
9854
|
+
for (const item of result.newItems) {
|
|
9855
|
+
console.log(chalk19.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
|
|
10051
9856
|
}
|
|
10052
9857
|
const wastePerLearning = Math.round(waste.totalWasteTokens / result.newItemCount);
|
|
10053
9858
|
const TYPE_RE = /^\*\*\[([^\]]+)\]\*\*/;
|
|
@@ -10072,15 +9877,6 @@ async function learnFinalizeCommand(options) {
|
|
|
10072
9877
|
roiLearningEntries = learningEntries;
|
|
10073
9878
|
}
|
|
10074
9879
|
}
|
|
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
|
-
}
|
|
10084
9880
|
const sessionSummary = {
|
|
10085
9881
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10086
9882
|
sessionId: readState2().sessionId || "unknown",
|
|
@@ -10090,11 +9886,7 @@ async function learnFinalizeCommand(options) {
|
|
|
10090
9886
|
wasteSeconds: Math.round(waste.totalWasteSeconds),
|
|
10091
9887
|
hadLearningsAvailable: hadLearnings,
|
|
10092
9888
|
learningsCount: existingLearnedItems,
|
|
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
|
|
9889
|
+
newLearningsProduced
|
|
10098
9890
|
};
|
|
10099
9891
|
const roiStats = recordSession(sessionSummary, roiLearningEntries);
|
|
10100
9892
|
trackLearnSessionAnalyzed({
|
|
@@ -10121,73 +9913,66 @@ async function learnFinalizeCommand(options) {
|
|
|
10121
9913
|
estimatedSavingsSeconds: t.estimatedSavingsSeconds,
|
|
10122
9914
|
learningCount: roiStats.learnings.length
|
|
10123
9915
|
});
|
|
10124
|
-
if (
|
|
9916
|
+
if (t.estimatedSavingsTokens > 0) {
|
|
10125
9917
|
const totalLearnings = existingLearnedItems + newLearningsProduced;
|
|
10126
|
-
console.log(
|
|
9918
|
+
console.log(chalk19.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
|
|
10127
9919
|
}
|
|
10128
9920
|
} catch (err) {
|
|
10129
|
-
if (options?.force
|
|
10130
|
-
console.error(
|
|
9921
|
+
if (options?.force) {
|
|
9922
|
+
console.error(chalk19.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
|
|
10131
9923
|
}
|
|
10132
9924
|
} finally {
|
|
10133
9925
|
if (analyzed) {
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
state.lastAnalysisEventCount = state.eventCount;
|
|
10137
|
-
state.lastAnalysisTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10138
|
-
writeState2(state);
|
|
10139
|
-
} else {
|
|
10140
|
-
clearSession();
|
|
10141
|
-
resetState();
|
|
10142
|
-
}
|
|
9926
|
+
clearSession();
|
|
9927
|
+
resetState();
|
|
10143
9928
|
}
|
|
10144
9929
|
releaseFinalizeLock();
|
|
10145
9930
|
}
|
|
10146
9931
|
}
|
|
10147
9932
|
async function learnInstallCommand() {
|
|
10148
9933
|
let anyInstalled = false;
|
|
10149
|
-
if (
|
|
9934
|
+
if (fs34.existsSync(".claude")) {
|
|
10150
9935
|
const r = installLearningHooks();
|
|
10151
9936
|
if (r.installed) {
|
|
10152
|
-
console.log(
|
|
9937
|
+
console.log(chalk19.green("\u2713") + " Claude Code learning hooks installed");
|
|
10153
9938
|
anyInstalled = true;
|
|
10154
9939
|
} else if (r.alreadyInstalled) {
|
|
10155
|
-
console.log(
|
|
9940
|
+
console.log(chalk19.dim(" Claude Code hooks already installed"));
|
|
10156
9941
|
}
|
|
10157
9942
|
}
|
|
10158
|
-
if (
|
|
9943
|
+
if (fs34.existsSync(".cursor")) {
|
|
10159
9944
|
const r = installCursorLearningHooks();
|
|
10160
9945
|
if (r.installed) {
|
|
10161
|
-
console.log(
|
|
9946
|
+
console.log(chalk19.green("\u2713") + " Cursor learning hooks installed");
|
|
10162
9947
|
anyInstalled = true;
|
|
10163
9948
|
} else if (r.alreadyInstalled) {
|
|
10164
|
-
console.log(
|
|
9949
|
+
console.log(chalk19.dim(" Cursor hooks already installed"));
|
|
10165
9950
|
}
|
|
10166
9951
|
}
|
|
10167
|
-
if (!
|
|
10168
|
-
console.log(
|
|
10169
|
-
console.log(
|
|
9952
|
+
if (!fs34.existsSync(".claude") && !fs34.existsSync(".cursor")) {
|
|
9953
|
+
console.log(chalk19.yellow("No .claude/ or .cursor/ directory found."));
|
|
9954
|
+
console.log(chalk19.dim(" Run `caliber init` first, or create the directory manually."));
|
|
10170
9955
|
return;
|
|
10171
9956
|
}
|
|
10172
9957
|
if (anyInstalled) {
|
|
10173
|
-
console.log(
|
|
10174
|
-
console.log(
|
|
9958
|
+
console.log(chalk19.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
|
|
9959
|
+
console.log(chalk19.dim(" Learnings written to CALIBER_LEARNINGS.md."));
|
|
10175
9960
|
}
|
|
10176
9961
|
}
|
|
10177
9962
|
async function learnRemoveCommand() {
|
|
10178
9963
|
let anyRemoved = false;
|
|
10179
9964
|
const r1 = removeLearningHooks();
|
|
10180
9965
|
if (r1.removed) {
|
|
10181
|
-
console.log(
|
|
9966
|
+
console.log(chalk19.green("\u2713") + " Claude Code learning hooks removed");
|
|
10182
9967
|
anyRemoved = true;
|
|
10183
9968
|
}
|
|
10184
9969
|
const r2 = removeCursorLearningHooks();
|
|
10185
9970
|
if (r2.removed) {
|
|
10186
|
-
console.log(
|
|
9971
|
+
console.log(chalk19.green("\u2713") + " Cursor learning hooks removed");
|
|
10187
9972
|
anyRemoved = true;
|
|
10188
9973
|
}
|
|
10189
9974
|
if (!anyRemoved) {
|
|
10190
|
-
console.log(
|
|
9975
|
+
console.log(chalk19.dim("No learning hooks found."));
|
|
10191
9976
|
}
|
|
10192
9977
|
}
|
|
10193
9978
|
async function learnStatusCommand() {
|
|
@@ -10195,178 +9980,50 @@ async function learnStatusCommand() {
|
|
|
10195
9980
|
const cursorInstalled = areCursorLearningHooksInstalled();
|
|
10196
9981
|
const state = readState2();
|
|
10197
9982
|
const eventCount = getEventCount();
|
|
10198
|
-
console.log(
|
|
9983
|
+
console.log(chalk19.bold("Session Learning Status"));
|
|
10199
9984
|
console.log();
|
|
10200
9985
|
if (claudeInstalled) {
|
|
10201
|
-
console.log(
|
|
9986
|
+
console.log(chalk19.green("\u2713") + " Claude Code hooks " + chalk19.green("installed"));
|
|
10202
9987
|
} else {
|
|
10203
|
-
console.log(
|
|
9988
|
+
console.log(chalk19.dim("\u2717") + " Claude Code hooks " + chalk19.dim("not installed"));
|
|
10204
9989
|
}
|
|
10205
9990
|
if (cursorInstalled) {
|
|
10206
|
-
console.log(
|
|
9991
|
+
console.log(chalk19.green("\u2713") + " Cursor hooks " + chalk19.green("installed"));
|
|
10207
9992
|
} else {
|
|
10208
|
-
console.log(
|
|
9993
|
+
console.log(chalk19.dim("\u2717") + " Cursor hooks " + chalk19.dim("not installed"));
|
|
10209
9994
|
}
|
|
10210
9995
|
if (!claudeInstalled && !cursorInstalled) {
|
|
10211
|
-
console.log(
|
|
9996
|
+
console.log(chalk19.dim(" Run `caliber learn install` to enable session learning."));
|
|
10212
9997
|
}
|
|
10213
9998
|
console.log();
|
|
10214
|
-
console.log(`Events recorded: ${
|
|
10215
|
-
console.log(`Threshold for analysis: ${
|
|
9999
|
+
console.log(`Events recorded: ${chalk19.cyan(String(eventCount))}`);
|
|
10000
|
+
console.log(`Threshold for analysis: ${chalk19.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
|
|
10216
10001
|
if (state.lastAnalysisTimestamp) {
|
|
10217
|
-
console.log(`Last analysis: ${
|
|
10002
|
+
console.log(`Last analysis: ${chalk19.cyan(state.lastAnalysisTimestamp)}`);
|
|
10218
10003
|
} else {
|
|
10219
|
-
console.log(`Last analysis: ${
|
|
10004
|
+
console.log(`Last analysis: ${chalk19.dim("none")}`);
|
|
10220
10005
|
}
|
|
10221
10006
|
const learnedSection = readLearnedSection();
|
|
10222
10007
|
if (learnedSection) {
|
|
10223
10008
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
10224
10009
|
console.log(`
|
|
10225
|
-
Learned items in CALIBER_LEARNINGS.md: ${
|
|
10010
|
+
Learned items in CALIBER_LEARNINGS.md: ${chalk19.cyan(String(lineCount))}`);
|
|
10226
10011
|
}
|
|
10227
10012
|
const roiStats = readROIStats();
|
|
10228
10013
|
const roiSummary = formatROISummary(roiStats);
|
|
10229
10014
|
if (roiSummary) {
|
|
10230
10015
|
console.log();
|
|
10231
|
-
console.log(
|
|
10016
|
+
console.log(chalk19.bold(roiSummary.split("\n")[0]));
|
|
10232
10017
|
for (const line of roiSummary.split("\n").slice(1)) {
|
|
10233
10018
|
console.log(line);
|
|
10234
10019
|
}
|
|
10235
10020
|
}
|
|
10236
10021
|
}
|
|
10237
10022
|
|
|
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
|
-
|
|
10366
10023
|
// src/cli.ts
|
|
10367
|
-
var __dirname =
|
|
10024
|
+
var __dirname = path28.dirname(fileURLToPath(import.meta.url));
|
|
10368
10025
|
var pkg = JSON.parse(
|
|
10369
|
-
|
|
10026
|
+
fs35.readFileSync(path28.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
10370
10027
|
);
|
|
10371
10028
|
var program = new Command();
|
|
10372
10029
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -10411,10 +10068,6 @@ program.hook("preAction", (thisCommand) => {
|
|
|
10411
10068
|
setTelemetryDisabled(true);
|
|
10412
10069
|
}
|
|
10413
10070
|
initTelemetry();
|
|
10414
|
-
const cmdName = thisCommand.name();
|
|
10415
|
-
if (cmdName !== "learn" && cmdName !== "observe" && cmdName !== "finalize") {
|
|
10416
|
-
checkPendingNotifications();
|
|
10417
|
-
}
|
|
10418
10071
|
});
|
|
10419
10072
|
function parseAgentOption(value) {
|
|
10420
10073
|
if (value === "both") return ["claude", "cursor"];
|
|
@@ -10433,28 +10086,27 @@ program.command("status").description("Show current Caliber setup status").optio
|
|
|
10433
10086
|
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));
|
|
10434
10087
|
program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
|
|
10435
10088
|
program.command("skills").description("Discover and install community skills for your project").action(tracked("skills", recommendCommand));
|
|
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).
|
|
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));
|
|
10437
10090
|
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));
|
|
10438
10091
|
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));
|
|
10440
10092
|
var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
10441
10093
|
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));
|
|
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)").
|
|
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)));
|
|
10443
10095
|
learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
|
|
10444
10096
|
learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
|
|
10445
10097
|
learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
|
|
10446
10098
|
|
|
10447
10099
|
// src/utils/version-check.ts
|
|
10448
|
-
import
|
|
10449
|
-
import
|
|
10100
|
+
import fs36 from "fs";
|
|
10101
|
+
import path29 from "path";
|
|
10450
10102
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10451
10103
|
import { execSync as execSync15 } from "child_process";
|
|
10452
|
-
import
|
|
10104
|
+
import chalk20 from "chalk";
|
|
10453
10105
|
import ora7 from "ora";
|
|
10454
10106
|
import confirm2 from "@inquirer/confirm";
|
|
10455
|
-
var __dirname_vc =
|
|
10107
|
+
var __dirname_vc = path29.dirname(fileURLToPath2(import.meta.url));
|
|
10456
10108
|
var pkg2 = JSON.parse(
|
|
10457
|
-
|
|
10109
|
+
fs36.readFileSync(path29.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
10458
10110
|
);
|
|
10459
10111
|
function getChannel(version) {
|
|
10460
10112
|
const match = version.match(/-(dev|next)\./);
|
|
@@ -10479,8 +10131,8 @@ function isNewer(registry, current) {
|
|
|
10479
10131
|
function getInstalledVersion() {
|
|
10480
10132
|
try {
|
|
10481
10133
|
const globalRoot = execSync15("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10482
|
-
const pkgPath =
|
|
10483
|
-
return JSON.parse(
|
|
10134
|
+
const pkgPath = path29.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
10135
|
+
return JSON.parse(fs36.readFileSync(pkgPath, "utf-8")).version;
|
|
10484
10136
|
} catch {
|
|
10485
10137
|
return null;
|
|
10486
10138
|
}
|
|
@@ -10505,17 +10157,17 @@ async function checkForUpdates() {
|
|
|
10505
10157
|
if (!isInteractive) {
|
|
10506
10158
|
const installTag = channel === "latest" ? "" : `@${channel}`;
|
|
10507
10159
|
console.log(
|
|
10508
|
-
|
|
10160
|
+
chalk20.yellow(
|
|
10509
10161
|
`
|
|
10510
10162
|
Update available: ${current} -> ${latest}
|
|
10511
|
-
Run ${
|
|
10163
|
+
Run ${chalk20.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
|
|
10512
10164
|
`
|
|
10513
10165
|
)
|
|
10514
10166
|
);
|
|
10515
10167
|
return;
|
|
10516
10168
|
}
|
|
10517
10169
|
console.log(
|
|
10518
|
-
|
|
10170
|
+
chalk20.yellow(`
|
|
10519
10171
|
Update available: ${current} -> ${latest}`)
|
|
10520
10172
|
);
|
|
10521
10173
|
const shouldUpdate = await confirm2({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -10534,13 +10186,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
10534
10186
|
const installed = getInstalledVersion();
|
|
10535
10187
|
if (installed !== latest) {
|
|
10536
10188
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
10537
|
-
console.log(
|
|
10189
|
+
console.log(chalk20.yellow(`Run ${chalk20.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
|
|
10538
10190
|
`));
|
|
10539
10191
|
return;
|
|
10540
10192
|
}
|
|
10541
|
-
spinner.succeed(
|
|
10193
|
+
spinner.succeed(chalk20.green(`Updated to ${latest}`));
|
|
10542
10194
|
const args = process.argv.slice(2);
|
|
10543
|
-
console.log(
|
|
10195
|
+
console.log(chalk20.dim(`
|
|
10544
10196
|
Restarting: caliber ${args.join(" ")}
|
|
10545
10197
|
`));
|
|
10546
10198
|
execSync15(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -10553,11 +10205,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
10553
10205
|
if (err instanceof Error) {
|
|
10554
10206
|
const stderr = err.stderr;
|
|
10555
10207
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
10556
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
10208
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk20.dim(` ${errMsg}`));
|
|
10557
10209
|
}
|
|
10558
10210
|
console.log(
|
|
10559
|
-
|
|
10560
|
-
`Run ${
|
|
10211
|
+
chalk20.yellow(
|
|
10212
|
+
`Run ${chalk20.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
|
|
10561
10213
|
`
|
|
10562
10214
|
)
|
|
10563
10215
|
);
|