@caliber-ai/cli 0.23.0 → 0.24.0
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 +189 -182
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -92,7 +92,7 @@ import chalk3 from "chalk";
|
|
|
92
92
|
import ora2 from "ora";
|
|
93
93
|
import readline from "readline";
|
|
94
94
|
import select from "@inquirer/select";
|
|
95
|
-
import
|
|
95
|
+
import fs18 from "fs";
|
|
96
96
|
|
|
97
97
|
// src/auth/token-store.ts
|
|
98
98
|
init_constants();
|
|
@@ -1518,25 +1518,112 @@ function removeHook() {
|
|
|
1518
1518
|
return { removed: true, notFound: false };
|
|
1519
1519
|
}
|
|
1520
1520
|
|
|
1521
|
-
// src/lib/
|
|
1522
|
-
init_constants();
|
|
1521
|
+
// src/lib/learning-hooks.ts
|
|
1523
1522
|
import fs16 from "fs";
|
|
1524
1523
|
import path15 from "path";
|
|
1524
|
+
var SETTINGS_PATH2 = path15.join(".claude", "settings.json");
|
|
1525
|
+
var HOOK_CONFIGS = [
|
|
1526
|
+
{
|
|
1527
|
+
event: "PostToolUse",
|
|
1528
|
+
command: "caliber learn observe",
|
|
1529
|
+
description: "Caliber: recording tool usage for session learning"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
event: "PostToolUseFailure",
|
|
1533
|
+
command: "caliber learn observe --failure",
|
|
1534
|
+
description: "Caliber: recording tool failure for session learning"
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
event: "SessionEnd",
|
|
1538
|
+
command: "caliber learn finalize",
|
|
1539
|
+
description: "Caliber: finalizing session learnings"
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1542
|
+
function readSettings2() {
|
|
1543
|
+
if (!fs16.existsSync(SETTINGS_PATH2)) return {};
|
|
1544
|
+
try {
|
|
1545
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
1546
|
+
} catch {
|
|
1547
|
+
return {};
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function writeSettings2(settings) {
|
|
1551
|
+
const dir = path15.dirname(SETTINGS_PATH2);
|
|
1552
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
1553
|
+
fs16.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
1554
|
+
}
|
|
1555
|
+
function hasLearningHook(matchers, command) {
|
|
1556
|
+
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
1557
|
+
}
|
|
1558
|
+
function areLearningHooksInstalled() {
|
|
1559
|
+
const settings = readSettings2();
|
|
1560
|
+
if (!settings.hooks) return false;
|
|
1561
|
+
return HOOK_CONFIGS.every((cfg) => {
|
|
1562
|
+
const matchers = settings.hooks[cfg.event];
|
|
1563
|
+
return Array.isArray(matchers) && hasLearningHook(matchers, cfg.command);
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
function installLearningHooks() {
|
|
1567
|
+
if (areLearningHooksInstalled()) {
|
|
1568
|
+
return { installed: false, alreadyInstalled: true };
|
|
1569
|
+
}
|
|
1570
|
+
const settings = readSettings2();
|
|
1571
|
+
if (!settings.hooks) settings.hooks = {};
|
|
1572
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
1573
|
+
if (!Array.isArray(settings.hooks[cfg.event])) {
|
|
1574
|
+
settings.hooks[cfg.event] = [];
|
|
1575
|
+
}
|
|
1576
|
+
if (!hasLearningHook(settings.hooks[cfg.event], cfg.command)) {
|
|
1577
|
+
settings.hooks[cfg.event].push({
|
|
1578
|
+
matcher: "",
|
|
1579
|
+
hooks: [{ type: "command", command: cfg.command, description: cfg.description }]
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
writeSettings2(settings);
|
|
1584
|
+
return { installed: true, alreadyInstalled: false };
|
|
1585
|
+
}
|
|
1586
|
+
function removeLearningHooks() {
|
|
1587
|
+
const settings = readSettings2();
|
|
1588
|
+
if (!settings.hooks) return { removed: false, notFound: true };
|
|
1589
|
+
let removedAny = false;
|
|
1590
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
1591
|
+
const matchers = settings.hooks[cfg.event];
|
|
1592
|
+
if (!Array.isArray(matchers)) continue;
|
|
1593
|
+
const idx = matchers.findIndex((entry) => entry.hooks?.some((h) => h.command === cfg.command));
|
|
1594
|
+
if (idx !== -1) {
|
|
1595
|
+
matchers.splice(idx, 1);
|
|
1596
|
+
removedAny = true;
|
|
1597
|
+
if (matchers.length === 0) delete settings.hooks[cfg.event];
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
1601
|
+
delete settings.hooks;
|
|
1602
|
+
}
|
|
1603
|
+
if (!removedAny) return { removed: false, notFound: true };
|
|
1604
|
+
writeSettings2(settings);
|
|
1605
|
+
return { removed: true, notFound: false };
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// src/lib/state.ts
|
|
1609
|
+
init_constants();
|
|
1610
|
+
import fs17 from "fs";
|
|
1611
|
+
import path16 from "path";
|
|
1525
1612
|
import { execSync as execSync3 } from "child_process";
|
|
1526
|
-
var STATE_FILE =
|
|
1613
|
+
var STATE_FILE = path16.join(CALIBER_DIR, ".caliber-state.json");
|
|
1527
1614
|
function readState() {
|
|
1528
1615
|
try {
|
|
1529
|
-
if (!
|
|
1530
|
-
return JSON.parse(
|
|
1616
|
+
if (!fs17.existsSync(STATE_FILE)) return null;
|
|
1617
|
+
return JSON.parse(fs17.readFileSync(STATE_FILE, "utf-8"));
|
|
1531
1618
|
} catch {
|
|
1532
1619
|
return null;
|
|
1533
1620
|
}
|
|
1534
1621
|
}
|
|
1535
1622
|
function writeState(state) {
|
|
1536
|
-
if (!
|
|
1537
|
-
|
|
1623
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
1624
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1538
1625
|
}
|
|
1539
|
-
|
|
1626
|
+
fs17.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
1540
1627
|
}
|
|
1541
1628
|
function getCurrentHeadSha() {
|
|
1542
1629
|
try {
|
|
@@ -1893,6 +1980,13 @@ async function initCommand(options) {
|
|
|
1893
1980
|
} else if (hookResult.alreadyInstalled) {
|
|
1894
1981
|
console.log(chalk3.dim(" Auto-refresh hook already installed"));
|
|
1895
1982
|
}
|
|
1983
|
+
const learnResult = installLearningHooks();
|
|
1984
|
+
if (learnResult.installed) {
|
|
1985
|
+
console.log(` ${chalk3.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
1986
|
+
console.log(chalk3.dim(" Run `caliber learn remove` to disable"));
|
|
1987
|
+
} else if (learnResult.alreadyInstalled) {
|
|
1988
|
+
console.log(chalk3.dim(" Learning hooks already installed"));
|
|
1989
|
+
}
|
|
1896
1990
|
}
|
|
1897
1991
|
try {
|
|
1898
1992
|
let projectId = existingProjectId;
|
|
@@ -2024,8 +2118,8 @@ function openReview(method, stagedFiles) {
|
|
|
2024
2118
|
} else {
|
|
2025
2119
|
for (const file of stagedFiles) {
|
|
2026
2120
|
if (file.currentPath) {
|
|
2027
|
-
const currentLines =
|
|
2028
|
-
const proposedLines =
|
|
2121
|
+
const currentLines = fs18.readFileSync(file.currentPath, "utf-8").split("\n");
|
|
2122
|
+
const proposedLines = fs18.readFileSync(file.proposedPath, "utf-8").split("\n");
|
|
2029
2123
|
const patch = createTwoFilesPatch(file.relativePath, file.relativePath, currentLines.join("\n"), proposedLines.join("\n"));
|
|
2030
2124
|
let added = 0, removed = 0;
|
|
2031
2125
|
for (const line of patch.split("\n")) {
|
|
@@ -2034,7 +2128,7 @@ function openReview(method, stagedFiles) {
|
|
|
2034
2128
|
}
|
|
2035
2129
|
console.log(` ${chalk3.yellow("~")} ${file.relativePath} ${chalk3.green(`+${added}`)} ${chalk3.red(`-${removed}`)}`);
|
|
2036
2130
|
} else {
|
|
2037
|
-
const lines =
|
|
2131
|
+
const lines = fs18.readFileSync(file.proposedPath, "utf-8").split("\n").length;
|
|
2038
2132
|
console.log(` ${chalk3.green("+")} ${file.relativePath} ${chalk3.dim(`${lines} lines`)}`);
|
|
2039
2133
|
}
|
|
2040
2134
|
}
|
|
@@ -2065,7 +2159,7 @@ function printSetupSummary(setup) {
|
|
|
2065
2159
|
};
|
|
2066
2160
|
if (claude) {
|
|
2067
2161
|
if (claude.claudeMd) {
|
|
2068
|
-
const icon =
|
|
2162
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
|
|
2069
2163
|
const desc = getDescription("CLAUDE.md");
|
|
2070
2164
|
console.log(` ${icon} ${chalk3.bold("CLAUDE.md")}`);
|
|
2071
2165
|
if (desc) {
|
|
@@ -2077,7 +2171,7 @@ function printSetupSummary(setup) {
|
|
|
2077
2171
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
2078
2172
|
for (const skill of skills) {
|
|
2079
2173
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
2080
|
-
const icon =
|
|
2174
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2081
2175
|
const desc = getDescription(skillPath);
|
|
2082
2176
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2083
2177
|
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -2087,7 +2181,7 @@ function printSetupSummary(setup) {
|
|
|
2087
2181
|
}
|
|
2088
2182
|
if (cursor) {
|
|
2089
2183
|
if (cursor.cursorrules) {
|
|
2090
|
-
const icon =
|
|
2184
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
|
|
2091
2185
|
const desc = getDescription(".cursorrules");
|
|
2092
2186
|
console.log(` ${icon} ${chalk3.bold(".cursorrules")}`);
|
|
2093
2187
|
if (desc) console.log(chalk3.dim(` ${desc}`));
|
|
@@ -2097,7 +2191,7 @@ function printSetupSummary(setup) {
|
|
|
2097
2191
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
2098
2192
|
for (const skill of cursorSkills) {
|
|
2099
2193
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
2100
|
-
const icon =
|
|
2194
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2101
2195
|
const desc = getDescription(skillPath);
|
|
2102
2196
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2103
2197
|
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -2108,7 +2202,7 @@ function printSetupSummary(setup) {
|
|
|
2108
2202
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
2109
2203
|
for (const rule of rules) {
|
|
2110
2204
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
2111
|
-
const icon =
|
|
2205
|
+
const icon = fs18.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2112
2206
|
const desc = getDescription(rulePath);
|
|
2113
2207
|
console.log(` ${icon} ${chalk3.bold(rulePath)}`);
|
|
2114
2208
|
if (desc) {
|
|
@@ -2205,16 +2299,16 @@ function undoCommand() {
|
|
|
2205
2299
|
|
|
2206
2300
|
// src/commands/status.ts
|
|
2207
2301
|
import chalk5 from "chalk";
|
|
2208
|
-
import
|
|
2302
|
+
import fs20 from "fs";
|
|
2209
2303
|
|
|
2210
2304
|
// src/scanner/index.ts
|
|
2211
|
-
import
|
|
2212
|
-
import
|
|
2305
|
+
import fs19 from "fs";
|
|
2306
|
+
import path17 from "path";
|
|
2213
2307
|
import crypto4 from "crypto";
|
|
2214
2308
|
function scanLocalState(dir) {
|
|
2215
2309
|
const items = [];
|
|
2216
|
-
const claudeMdPath =
|
|
2217
|
-
if (
|
|
2310
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
2311
|
+
if (fs19.existsSync(claudeMdPath)) {
|
|
2218
2312
|
items.push({
|
|
2219
2313
|
type: "rule",
|
|
2220
2314
|
platform: "claude",
|
|
@@ -2223,10 +2317,10 @@ function scanLocalState(dir) {
|
|
|
2223
2317
|
path: claudeMdPath
|
|
2224
2318
|
});
|
|
2225
2319
|
}
|
|
2226
|
-
const skillsDir =
|
|
2227
|
-
if (
|
|
2228
|
-
for (const file of
|
|
2229
|
-
const filePath =
|
|
2320
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
2321
|
+
if (fs19.existsSync(skillsDir)) {
|
|
2322
|
+
for (const file of fs19.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
2323
|
+
const filePath = path17.join(skillsDir, file);
|
|
2230
2324
|
items.push({
|
|
2231
2325
|
type: "skill",
|
|
2232
2326
|
platform: "claude",
|
|
@@ -2236,10 +2330,10 @@ function scanLocalState(dir) {
|
|
|
2236
2330
|
});
|
|
2237
2331
|
}
|
|
2238
2332
|
}
|
|
2239
|
-
const mcpJsonPath =
|
|
2240
|
-
if (
|
|
2333
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
2334
|
+
if (fs19.existsSync(mcpJsonPath)) {
|
|
2241
2335
|
try {
|
|
2242
|
-
const mcpJson = JSON.parse(
|
|
2336
|
+
const mcpJson = JSON.parse(fs19.readFileSync(mcpJsonPath, "utf-8"));
|
|
2243
2337
|
if (mcpJson.mcpServers) {
|
|
2244
2338
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
2245
2339
|
items.push({
|
|
@@ -2254,8 +2348,8 @@ function scanLocalState(dir) {
|
|
|
2254
2348
|
} catch {
|
|
2255
2349
|
}
|
|
2256
2350
|
}
|
|
2257
|
-
const cursorrulesPath =
|
|
2258
|
-
if (
|
|
2351
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
2352
|
+
if (fs19.existsSync(cursorrulesPath)) {
|
|
2259
2353
|
items.push({
|
|
2260
2354
|
type: "rule",
|
|
2261
2355
|
platform: "cursor",
|
|
@@ -2264,10 +2358,10 @@ function scanLocalState(dir) {
|
|
|
2264
2358
|
path: cursorrulesPath
|
|
2265
2359
|
});
|
|
2266
2360
|
}
|
|
2267
|
-
const cursorRulesDir =
|
|
2268
|
-
if (
|
|
2269
|
-
for (const file of
|
|
2270
|
-
const filePath =
|
|
2361
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
2362
|
+
if (fs19.existsSync(cursorRulesDir)) {
|
|
2363
|
+
for (const file of fs19.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
2364
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
2271
2365
|
items.push({
|
|
2272
2366
|
type: "rule",
|
|
2273
2367
|
platform: "cursor",
|
|
@@ -2277,12 +2371,12 @@ function scanLocalState(dir) {
|
|
|
2277
2371
|
});
|
|
2278
2372
|
}
|
|
2279
2373
|
}
|
|
2280
|
-
const cursorSkillsDir =
|
|
2281
|
-
if (
|
|
2374
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
2375
|
+
if (fs19.existsSync(cursorSkillsDir)) {
|
|
2282
2376
|
try {
|
|
2283
|
-
for (const name of
|
|
2284
|
-
const skillFile =
|
|
2285
|
-
if (
|
|
2377
|
+
for (const name of fs19.readdirSync(cursorSkillsDir)) {
|
|
2378
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
2379
|
+
if (fs19.existsSync(skillFile)) {
|
|
2286
2380
|
items.push({
|
|
2287
2381
|
type: "skill",
|
|
2288
2382
|
platform: "cursor",
|
|
@@ -2295,10 +2389,10 @@ function scanLocalState(dir) {
|
|
|
2295
2389
|
} catch {
|
|
2296
2390
|
}
|
|
2297
2391
|
}
|
|
2298
|
-
const cursorMcpPath =
|
|
2299
|
-
if (
|
|
2392
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
2393
|
+
if (fs19.existsSync(cursorMcpPath)) {
|
|
2300
2394
|
try {
|
|
2301
|
-
const mcpJson = JSON.parse(
|
|
2395
|
+
const mcpJson = JSON.parse(fs19.readFileSync(cursorMcpPath, "utf-8"));
|
|
2302
2396
|
if (mcpJson.mcpServers) {
|
|
2303
2397
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
2304
2398
|
items.push({
|
|
@@ -2342,7 +2436,7 @@ function compareState(serverItems, localItems) {
|
|
|
2342
2436
|
return { installed, missing, outdated, extra };
|
|
2343
2437
|
}
|
|
2344
2438
|
function hashFile(filePath) {
|
|
2345
|
-
const text =
|
|
2439
|
+
const text = fs19.readFileSync(filePath, "utf-8");
|
|
2346
2440
|
return crypto4.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
2347
2441
|
}
|
|
2348
2442
|
function hashJson(obj) {
|
|
@@ -2374,7 +2468,7 @@ async function statusCommand(options) {
|
|
|
2374
2468
|
}
|
|
2375
2469
|
console.log(` Files managed: ${chalk5.cyan(manifest.entries.length.toString())}`);
|
|
2376
2470
|
for (const entry of manifest.entries) {
|
|
2377
|
-
const exists =
|
|
2471
|
+
const exists = fs20.existsSync(entry.path);
|
|
2378
2472
|
const icon = exists ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
2379
2473
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
2380
2474
|
}
|
|
@@ -3009,8 +3103,8 @@ async function diffCommand(options) {
|
|
|
3009
3103
|
}
|
|
3010
3104
|
|
|
3011
3105
|
// src/commands/refresh.ts
|
|
3012
|
-
import
|
|
3013
|
-
import
|
|
3106
|
+
import fs22 from "fs";
|
|
3107
|
+
import path19 from "path";
|
|
3014
3108
|
import chalk11 from "chalk";
|
|
3015
3109
|
import ora8 from "ora";
|
|
3016
3110
|
|
|
@@ -3087,37 +3181,37 @@ function collectDiff(lastSha) {
|
|
|
3087
3181
|
}
|
|
3088
3182
|
|
|
3089
3183
|
// src/writers/refresh.ts
|
|
3090
|
-
import
|
|
3091
|
-
import
|
|
3184
|
+
import fs21 from "fs";
|
|
3185
|
+
import path18 from "path";
|
|
3092
3186
|
function writeRefreshDocs(docs) {
|
|
3093
3187
|
const written = [];
|
|
3094
3188
|
if (docs.claudeMd) {
|
|
3095
|
-
|
|
3189
|
+
fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
3096
3190
|
written.push("CLAUDE.md");
|
|
3097
3191
|
}
|
|
3098
3192
|
if (docs.readmeMd) {
|
|
3099
|
-
|
|
3193
|
+
fs21.writeFileSync("README.md", docs.readmeMd);
|
|
3100
3194
|
written.push("README.md");
|
|
3101
3195
|
}
|
|
3102
3196
|
if (docs.cursorrules) {
|
|
3103
|
-
|
|
3197
|
+
fs21.writeFileSync(".cursorrules", docs.cursorrules);
|
|
3104
3198
|
written.push(".cursorrules");
|
|
3105
3199
|
}
|
|
3106
3200
|
if (docs.cursorRules) {
|
|
3107
|
-
const rulesDir =
|
|
3108
|
-
if (!
|
|
3201
|
+
const rulesDir = path18.join(".cursor", "rules");
|
|
3202
|
+
if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
|
|
3109
3203
|
for (const rule of docs.cursorRules) {
|
|
3110
|
-
const filePath =
|
|
3111
|
-
|
|
3204
|
+
const filePath = path18.join(rulesDir, rule.filename);
|
|
3205
|
+
fs21.writeFileSync(filePath, rule.content);
|
|
3112
3206
|
written.push(filePath);
|
|
3113
3207
|
}
|
|
3114
3208
|
}
|
|
3115
3209
|
if (docs.claudeSkills) {
|
|
3116
|
-
const skillsDir =
|
|
3117
|
-
if (!
|
|
3210
|
+
const skillsDir = path18.join(".claude", "skills");
|
|
3211
|
+
if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
|
|
3118
3212
|
for (const skill of docs.claudeSkills) {
|
|
3119
|
-
const filePath =
|
|
3120
|
-
|
|
3213
|
+
const filePath = path18.join(skillsDir, skill.filename);
|
|
3214
|
+
fs21.writeFileSync(filePath, skill.content);
|
|
3121
3215
|
written.push(filePath);
|
|
3122
3216
|
}
|
|
3123
3217
|
}
|
|
@@ -3131,11 +3225,11 @@ function log(quiet, ...args) {
|
|
|
3131
3225
|
function discoverGitRepos(parentDir) {
|
|
3132
3226
|
const repos = [];
|
|
3133
3227
|
try {
|
|
3134
|
-
const entries =
|
|
3228
|
+
const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
|
|
3135
3229
|
for (const entry of entries) {
|
|
3136
3230
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3137
|
-
const childPath =
|
|
3138
|
-
if (
|
|
3231
|
+
const childPath = path19.join(parentDir, entry.name);
|
|
3232
|
+
if (fs22.existsSync(path19.join(childPath, ".git"))) {
|
|
3139
3233
|
repos.push(childPath);
|
|
3140
3234
|
}
|
|
3141
3235
|
}
|
|
@@ -3238,7 +3332,7 @@ async function refreshCommand(options) {
|
|
|
3238
3332
|
`));
|
|
3239
3333
|
const originalDir = process.cwd();
|
|
3240
3334
|
for (const repo of repos) {
|
|
3241
|
-
const repoName =
|
|
3335
|
+
const repoName = path19.basename(repo);
|
|
3242
3336
|
try {
|
|
3243
3337
|
process.chdir(repo);
|
|
3244
3338
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -3411,8 +3505,8 @@ function readStdin() {
|
|
|
3411
3505
|
|
|
3412
3506
|
// src/learner/storage.ts
|
|
3413
3507
|
init_constants();
|
|
3414
|
-
import
|
|
3415
|
-
import
|
|
3508
|
+
import fs23 from "fs";
|
|
3509
|
+
import path20 from "path";
|
|
3416
3510
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
3417
3511
|
var DEFAULT_STATE = {
|
|
3418
3512
|
sessionId: null,
|
|
@@ -3420,15 +3514,15 @@ var DEFAULT_STATE = {
|
|
|
3420
3514
|
lastAnalysisTimestamp: null
|
|
3421
3515
|
};
|
|
3422
3516
|
function ensureLearningDir() {
|
|
3423
|
-
if (!
|
|
3424
|
-
|
|
3517
|
+
if (!fs23.existsSync(LEARNING_DIR)) {
|
|
3518
|
+
fs23.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
3425
3519
|
}
|
|
3426
3520
|
}
|
|
3427
3521
|
function sessionFilePath() {
|
|
3428
|
-
return
|
|
3522
|
+
return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
3429
3523
|
}
|
|
3430
3524
|
function stateFilePath() {
|
|
3431
|
-
return
|
|
3525
|
+
return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
3432
3526
|
}
|
|
3433
3527
|
function truncateResponse(response) {
|
|
3434
3528
|
const str = JSON.stringify(response);
|
|
@@ -3439,50 +3533,50 @@ function appendEvent(event) {
|
|
|
3439
3533
|
ensureLearningDir();
|
|
3440
3534
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
3441
3535
|
const filePath = sessionFilePath();
|
|
3442
|
-
|
|
3536
|
+
fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
3443
3537
|
const count = getEventCount();
|
|
3444
3538
|
if (count > LEARNING_MAX_EVENTS) {
|
|
3445
|
-
const lines =
|
|
3539
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3446
3540
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
3447
|
-
|
|
3541
|
+
fs23.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
3448
3542
|
}
|
|
3449
3543
|
}
|
|
3450
3544
|
function readAllEvents() {
|
|
3451
3545
|
const filePath = sessionFilePath();
|
|
3452
|
-
if (!
|
|
3453
|
-
const lines =
|
|
3546
|
+
if (!fs23.existsSync(filePath)) return [];
|
|
3547
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3454
3548
|
return lines.map((line) => JSON.parse(line));
|
|
3455
3549
|
}
|
|
3456
3550
|
function getEventCount() {
|
|
3457
3551
|
const filePath = sessionFilePath();
|
|
3458
|
-
if (!
|
|
3459
|
-
const content =
|
|
3552
|
+
if (!fs23.existsSync(filePath)) return 0;
|
|
3553
|
+
const content = fs23.readFileSync(filePath, "utf-8");
|
|
3460
3554
|
return content.split("\n").filter(Boolean).length;
|
|
3461
3555
|
}
|
|
3462
3556
|
function clearSession() {
|
|
3463
3557
|
const filePath = sessionFilePath();
|
|
3464
|
-
if (
|
|
3558
|
+
if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
|
|
3465
3559
|
}
|
|
3466
3560
|
function readState2() {
|
|
3467
3561
|
const filePath = stateFilePath();
|
|
3468
|
-
if (!
|
|
3562
|
+
if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
3469
3563
|
try {
|
|
3470
|
-
return JSON.parse(
|
|
3564
|
+
return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
3471
3565
|
} catch {
|
|
3472
3566
|
return { ...DEFAULT_STATE };
|
|
3473
3567
|
}
|
|
3474
3568
|
}
|
|
3475
3569
|
function writeState2(state) {
|
|
3476
3570
|
ensureLearningDir();
|
|
3477
|
-
|
|
3571
|
+
fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
3478
3572
|
}
|
|
3479
3573
|
function resetState() {
|
|
3480
3574
|
writeState2({ ...DEFAULT_STATE });
|
|
3481
3575
|
}
|
|
3482
3576
|
|
|
3483
3577
|
// src/learner/writer.ts
|
|
3484
|
-
import
|
|
3485
|
-
import
|
|
3578
|
+
import fs24 from "fs";
|
|
3579
|
+
import path21 from "path";
|
|
3486
3580
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
3487
3581
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
3488
3582
|
function writeLearnedContent(update) {
|
|
@@ -3502,8 +3596,8 @@ function writeLearnedContent(update) {
|
|
|
3502
3596
|
function writeLearnedSection(content) {
|
|
3503
3597
|
const claudeMdPath = "CLAUDE.md";
|
|
3504
3598
|
let existing = "";
|
|
3505
|
-
if (
|
|
3506
|
-
existing =
|
|
3599
|
+
if (fs24.existsSync(claudeMdPath)) {
|
|
3600
|
+
existing = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
3507
3601
|
}
|
|
3508
3602
|
const section = `${LEARNED_START}
|
|
3509
3603
|
${content}
|
|
@@ -3517,15 +3611,15 @@ ${LEARNED_END}`;
|
|
|
3517
3611
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
3518
3612
|
updated = existing + separator + "\n" + section + "\n";
|
|
3519
3613
|
}
|
|
3520
|
-
|
|
3614
|
+
fs24.writeFileSync(claudeMdPath, updated);
|
|
3521
3615
|
}
|
|
3522
3616
|
function writeLearnedSkill(skill) {
|
|
3523
|
-
const skillDir =
|
|
3524
|
-
if (!
|
|
3525
|
-
const skillPath =
|
|
3526
|
-
if (!skill.isNew &&
|
|
3527
|
-
const existing =
|
|
3528
|
-
|
|
3617
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
3618
|
+
if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
|
|
3619
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
3620
|
+
if (!skill.isNew && fs24.existsSync(skillPath)) {
|
|
3621
|
+
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
3622
|
+
fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
3529
3623
|
} else {
|
|
3530
3624
|
const frontmatter = [
|
|
3531
3625
|
"---",
|
|
@@ -3534,107 +3628,20 @@ function writeLearnedSkill(skill) {
|
|
|
3534
3628
|
"---",
|
|
3535
3629
|
""
|
|
3536
3630
|
].join("\n");
|
|
3537
|
-
|
|
3631
|
+
fs24.writeFileSync(skillPath, frontmatter + skill.content);
|
|
3538
3632
|
}
|
|
3539
3633
|
return skillPath;
|
|
3540
3634
|
}
|
|
3541
3635
|
function readLearnedSection() {
|
|
3542
3636
|
const claudeMdPath = "CLAUDE.md";
|
|
3543
|
-
if (!
|
|
3544
|
-
const content =
|
|
3637
|
+
if (!fs24.existsSync(claudeMdPath)) return null;
|
|
3638
|
+
const content = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
3545
3639
|
const startIdx = content.indexOf(LEARNED_START);
|
|
3546
3640
|
const endIdx = content.indexOf(LEARNED_END);
|
|
3547
3641
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
3548
3642
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
3549
3643
|
}
|
|
3550
3644
|
|
|
3551
|
-
// src/lib/learning-hooks.ts
|
|
3552
|
-
import fs24 from "fs";
|
|
3553
|
-
import path21 from "path";
|
|
3554
|
-
var SETTINGS_PATH2 = path21.join(".claude", "settings.json");
|
|
3555
|
-
var HOOK_CONFIGS = [
|
|
3556
|
-
{
|
|
3557
|
-
event: "PostToolUse",
|
|
3558
|
-
command: "caliber learn observe",
|
|
3559
|
-
description: "Caliber: recording tool usage for session learning"
|
|
3560
|
-
},
|
|
3561
|
-
{
|
|
3562
|
-
event: "PostToolUseFailure",
|
|
3563
|
-
command: "caliber learn observe --failure",
|
|
3564
|
-
description: "Caliber: recording tool failure for session learning"
|
|
3565
|
-
},
|
|
3566
|
-
{
|
|
3567
|
-
event: "SessionEnd",
|
|
3568
|
-
command: "caliber learn finalize",
|
|
3569
|
-
description: "Caliber: finalizing session learnings"
|
|
3570
|
-
}
|
|
3571
|
-
];
|
|
3572
|
-
function readSettings2() {
|
|
3573
|
-
if (!fs24.existsSync(SETTINGS_PATH2)) return {};
|
|
3574
|
-
try {
|
|
3575
|
-
return JSON.parse(fs24.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
3576
|
-
} catch {
|
|
3577
|
-
return {};
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
function writeSettings2(settings) {
|
|
3581
|
-
const dir = path21.dirname(SETTINGS_PATH2);
|
|
3582
|
-
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
3583
|
-
fs24.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
3584
|
-
}
|
|
3585
|
-
function hasLearningHook(matchers, command) {
|
|
3586
|
-
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
3587
|
-
}
|
|
3588
|
-
function areLearningHooksInstalled() {
|
|
3589
|
-
const settings = readSettings2();
|
|
3590
|
-
if (!settings.hooks) return false;
|
|
3591
|
-
return HOOK_CONFIGS.every((cfg) => {
|
|
3592
|
-
const matchers = settings.hooks[cfg.event];
|
|
3593
|
-
return Array.isArray(matchers) && hasLearningHook(matchers, cfg.command);
|
|
3594
|
-
});
|
|
3595
|
-
}
|
|
3596
|
-
function installLearningHooks() {
|
|
3597
|
-
if (areLearningHooksInstalled()) {
|
|
3598
|
-
return { installed: false, alreadyInstalled: true };
|
|
3599
|
-
}
|
|
3600
|
-
const settings = readSettings2();
|
|
3601
|
-
if (!settings.hooks) settings.hooks = {};
|
|
3602
|
-
for (const cfg of HOOK_CONFIGS) {
|
|
3603
|
-
if (!Array.isArray(settings.hooks[cfg.event])) {
|
|
3604
|
-
settings.hooks[cfg.event] = [];
|
|
3605
|
-
}
|
|
3606
|
-
if (!hasLearningHook(settings.hooks[cfg.event], cfg.command)) {
|
|
3607
|
-
settings.hooks[cfg.event].push({
|
|
3608
|
-
matcher: "",
|
|
3609
|
-
hooks: [{ type: "command", command: cfg.command, description: cfg.description }]
|
|
3610
|
-
});
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
writeSettings2(settings);
|
|
3614
|
-
return { installed: true, alreadyInstalled: false };
|
|
3615
|
-
}
|
|
3616
|
-
function removeLearningHooks() {
|
|
3617
|
-
const settings = readSettings2();
|
|
3618
|
-
if (!settings.hooks) return { removed: false, notFound: true };
|
|
3619
|
-
let removedAny = false;
|
|
3620
|
-
for (const cfg of HOOK_CONFIGS) {
|
|
3621
|
-
const matchers = settings.hooks[cfg.event];
|
|
3622
|
-
if (!Array.isArray(matchers)) continue;
|
|
3623
|
-
const idx = matchers.findIndex((entry) => entry.hooks?.some((h) => h.command === cfg.command));
|
|
3624
|
-
if (idx !== -1) {
|
|
3625
|
-
matchers.splice(idx, 1);
|
|
3626
|
-
removedAny = true;
|
|
3627
|
-
if (matchers.length === 0) delete settings.hooks[cfg.event];
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
3631
|
-
delete settings.hooks;
|
|
3632
|
-
}
|
|
3633
|
-
if (!removedAny) return { removed: false, notFound: true };
|
|
3634
|
-
writeSettings2(settings);
|
|
3635
|
-
return { removed: true, notFound: false };
|
|
3636
|
-
}
|
|
3637
|
-
|
|
3638
3645
|
// src/commands/learn.ts
|
|
3639
3646
|
async function learnObserveCommand(options) {
|
|
3640
3647
|
try {
|