@rely-ai/caliber 1.18.9 → 1.19.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 +393 -148
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -177,13 +177,13 @@ __export(lock_exports, {
|
|
|
177
177
|
isCaliberRunning: () => isCaliberRunning,
|
|
178
178
|
releaseLock: () => releaseLock
|
|
179
179
|
});
|
|
180
|
-
import
|
|
181
|
-
import
|
|
180
|
+
import fs28 from "fs";
|
|
181
|
+
import path22 from "path";
|
|
182
182
|
import os4 from "os";
|
|
183
183
|
function isCaliberRunning() {
|
|
184
184
|
try {
|
|
185
|
-
if (!
|
|
186
|
-
const raw =
|
|
185
|
+
if (!fs28.existsSync(LOCK_FILE)) return false;
|
|
186
|
+
const raw = fs28.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
187
187
|
const { pid, ts } = JSON.parse(raw);
|
|
188
188
|
if (Date.now() - ts > STALE_MS) return false;
|
|
189
189
|
try {
|
|
@@ -198,13 +198,13 @@ function isCaliberRunning() {
|
|
|
198
198
|
}
|
|
199
199
|
function acquireLock() {
|
|
200
200
|
try {
|
|
201
|
-
|
|
201
|
+
fs28.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
202
202
|
} catch {
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
function releaseLock() {
|
|
206
206
|
try {
|
|
207
|
-
if (
|
|
207
|
+
if (fs28.existsSync(LOCK_FILE)) fs28.unlinkSync(LOCK_FILE);
|
|
208
208
|
} catch {
|
|
209
209
|
}
|
|
210
210
|
}
|
|
@@ -212,14 +212,14 @@ var LOCK_FILE, STALE_MS;
|
|
|
212
212
|
var init_lock = __esm({
|
|
213
213
|
"src/lib/lock.ts"() {
|
|
214
214
|
"use strict";
|
|
215
|
-
LOCK_FILE =
|
|
215
|
+
LOCK_FILE = path22.join(os4.tmpdir(), ".caliber.lock");
|
|
216
216
|
STALE_MS = 10 * 60 * 1e3;
|
|
217
217
|
}
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
// src/cli.ts
|
|
221
221
|
import { Command } from "commander";
|
|
222
|
-
import
|
|
222
|
+
import fs32 from "fs";
|
|
223
223
|
import path25 from "path";
|
|
224
224
|
import { fileURLToPath } from "url";
|
|
225
225
|
|
|
@@ -1382,11 +1382,15 @@ var ClaudeCliProvider = class {
|
|
|
1382
1382
|
const combined = this.buildCombinedPrompt(options);
|
|
1383
1383
|
const args = ["-p"];
|
|
1384
1384
|
if (options.model) args.push("--model", options.model);
|
|
1385
|
-
const child = spawn2(CLAUDE_CLI_BIN, args, {
|
|
1385
|
+
const child = IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
|
|
1386
1386
|
cwd: process.cwd(),
|
|
1387
1387
|
stdio: ["pipe", "pipe", "inherit"],
|
|
1388
1388
|
env: process.env,
|
|
1389
|
-
|
|
1389
|
+
shell: true
|
|
1390
|
+
}) : spawn2(CLAUDE_CLI_BIN, args, {
|
|
1391
|
+
cwd: process.cwd(),
|
|
1392
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
1393
|
+
env: process.env
|
|
1390
1394
|
});
|
|
1391
1395
|
child.stdin.end(combined);
|
|
1392
1396
|
let settled = false;
|
|
@@ -1446,11 +1450,15 @@ ${msg.content}
|
|
|
1446
1450
|
return new Promise((resolve2, reject) => {
|
|
1447
1451
|
const args = ["-p"];
|
|
1448
1452
|
if (model) args.push("--model", model);
|
|
1449
|
-
const child = spawn2(CLAUDE_CLI_BIN, args, {
|
|
1453
|
+
const child = IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
|
|
1450
1454
|
cwd: process.cwd(),
|
|
1451
1455
|
stdio: ["pipe", "pipe", "inherit"],
|
|
1452
1456
|
env: process.env,
|
|
1453
|
-
|
|
1457
|
+
shell: true
|
|
1458
|
+
}) : spawn2(CLAUDE_CLI_BIN, args, {
|
|
1459
|
+
cwd: process.cwd(),
|
|
1460
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
1461
|
+
env: process.env
|
|
1454
1462
|
});
|
|
1455
1463
|
child.stdin.end(combinedPrompt);
|
|
1456
1464
|
const chunks = [];
|
|
@@ -2030,6 +2038,8 @@ Rules:
|
|
|
2030
2038
|
- Be conservative \u2014 don't rewrite sections that aren't affected by the changes
|
|
2031
2039
|
- Don't add speculative or aspirational content
|
|
2032
2040
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
2041
|
+
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately by the learning system
|
|
2042
|
+
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
2033
2043
|
- If a doc doesn't need updating, return null for it
|
|
2034
2044
|
- For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them. Keep under 150 lines.
|
|
2035
2045
|
- For README.md: update setup instructions, API docs, or feature descriptions if affected
|
|
@@ -2061,20 +2071,25 @@ Your job is to reason deeply about these events and identify:
|
|
|
2061
2071
|
3. **Workarounds**: When the agent had to abandon one approach entirely and use a different strategy
|
|
2062
2072
|
4. **Repeated struggles**: The same tool being called many times against the same target, indicating confusion or trial-and-error
|
|
2063
2073
|
5. **Project-specific conventions**: Commands, paths, patterns, or configurations that are specific to this project and would help future sessions
|
|
2074
|
+
6. **Anti-patterns**: Commands, approaches, or configurations that consistently fail or cause problems \u2014 things future sessions should explicitly avoid
|
|
2064
2075
|
|
|
2065
2076
|
From these observations, produce:
|
|
2066
2077
|
|
|
2067
2078
|
### claudeMdLearnedSection
|
|
2068
|
-
A markdown section with concise, actionable bullet points
|
|
2079
|
+
A markdown section with concise, actionable bullet points. Your output will be written to CALIBER_LEARNINGS.md \u2014 a standalone file that all AI coding agents (Claude Code, Cursor, Codex) reference for project-specific patterns and anti-patterns. Each bullet should be a concrete instruction that prevents a past mistake or encodes a discovered convention. Examples:
|
|
2069
2080
|
- "Always run \`npm install\` before \`npm run build\` in this project"
|
|
2070
2081
|
- "The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
|
|
2071
2082
|
- "TypeScript strict mode is enabled \u2014 never use \`any\`, use \`unknown\` with type guards"
|
|
2072
2083
|
- "Use \`pnpm\` not \`npm\` \u2014 the lockfile is pnpm-lock.yaml"
|
|
2084
|
+
- "Never use \`npm\` in this project \u2014 pnpm-lock.yaml is the lockfile"
|
|
2085
|
+
- "Do NOT run \`jest\` directly \u2014 always use \`npm run test\` which sets the correct env"
|
|
2086
|
+
- "Avoid modifying files in \`src/generated/\` \u2014 they are auto-generated by the build step"
|
|
2073
2087
|
|
|
2074
2088
|
Rules for the learned section:
|
|
2075
2089
|
- Be additive: keep all existing learned items, add new ones, remove duplicates
|
|
2076
2090
|
- Never repeat instructions already present in the main CLAUDE.md
|
|
2077
2091
|
- Each bullet must be specific and actionable \u2014 no vague advice
|
|
2092
|
+
- Include both positive directives ('Always do X') and negative rules ('Never do Y because Z') when the session evidence supports them
|
|
2078
2093
|
- Maximum ~30 bullet items total
|
|
2079
2094
|
- Group related items under subheadings if there are many
|
|
2080
2095
|
- If there's nothing meaningful to learn, return null
|
|
@@ -3193,18 +3208,13 @@ function openDiffsInEditor(editor, files) {
|
|
|
3193
3208
|
const cmd = editor === "cursor" ? "cursor" : "code";
|
|
3194
3209
|
for (const file of files) {
|
|
3195
3210
|
try {
|
|
3196
|
-
if (
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
...IS_WINDOWS3 && { shell: true }
|
|
3201
|
-
}).unref();
|
|
3211
|
+
if (IS_WINDOWS3) {
|
|
3212
|
+
const quote = (s) => `"${s}"`;
|
|
3213
|
+
const parts = file.originalPath ? [cmd, "--diff", quote(file.originalPath), quote(file.proposedPath)] : [cmd, quote(file.proposedPath)];
|
|
3214
|
+
spawn3(parts.join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
|
|
3202
3215
|
} else {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
detached: true,
|
|
3206
|
-
...IS_WINDOWS3 && { shell: true }
|
|
3207
|
-
}).unref();
|
|
3216
|
+
const args = file.originalPath ? ["--diff", file.originalPath, file.proposedPath] : [file.proposedPath];
|
|
3217
|
+
spawn3(cmd, args, { stdio: "ignore", detached: true }).unref();
|
|
3208
3218
|
}
|
|
3209
3219
|
} catch {
|
|
3210
3220
|
continue;
|
|
@@ -3593,7 +3603,8 @@ function getPrecommitBlock() {
|
|
|
3593
3603
|
if [ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1; then
|
|
3594
3604
|
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
3595
3605
|
"${bin}" refresh 2>/dev/null || true
|
|
3596
|
-
|
|
3606
|
+
"${bin}" learn finalize 2>/dev/null || true
|
|
3607
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
3597
3608
|
fi
|
|
3598
3609
|
${PRECOMMIT_END}`;
|
|
3599
3610
|
}
|
|
@@ -3717,6 +3728,69 @@ function installLearningHooks() {
|
|
|
3717
3728
|
writeSettings2(settings);
|
|
3718
3729
|
return { installed: true, alreadyInstalled: false };
|
|
3719
3730
|
}
|
|
3731
|
+
var CURSOR_HOOKS_PATH = path13.join(".cursor", "hooks.json");
|
|
3732
|
+
var CURSOR_HOOK_EVENTS = [
|
|
3733
|
+
{ event: "postToolUse", tail: "learn observe" },
|
|
3734
|
+
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
3735
|
+
{ event: "sessionEnd", tail: "learn finalize" }
|
|
3736
|
+
];
|
|
3737
|
+
function readCursorHooks() {
|
|
3738
|
+
if (!fs18.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
3739
|
+
try {
|
|
3740
|
+
return JSON.parse(fs18.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
|
|
3741
|
+
} catch {
|
|
3742
|
+
return { version: 1, hooks: {} };
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
function writeCursorHooks(config) {
|
|
3746
|
+
const dir = path13.dirname(CURSOR_HOOKS_PATH);
|
|
3747
|
+
if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
|
|
3748
|
+
fs18.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
3749
|
+
}
|
|
3750
|
+
function hasCursorHook(entries, tail) {
|
|
3751
|
+
return entries.some((e) => isCaliberCommand(e.command, tail));
|
|
3752
|
+
}
|
|
3753
|
+
function areCursorLearningHooksInstalled() {
|
|
3754
|
+
const config = readCursorHooks();
|
|
3755
|
+
return CURSOR_HOOK_EVENTS.every((cfg) => {
|
|
3756
|
+
const entries = config.hooks[cfg.event];
|
|
3757
|
+
return Array.isArray(entries) && hasCursorHook(entries, cfg.tail);
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
function installCursorLearningHooks() {
|
|
3761
|
+
if (areCursorLearningHooksInstalled()) {
|
|
3762
|
+
return { installed: false, alreadyInstalled: true };
|
|
3763
|
+
}
|
|
3764
|
+
const config = readCursorHooks();
|
|
3765
|
+
const bin = resolveCaliber();
|
|
3766
|
+
for (const cfg of CURSOR_HOOK_EVENTS) {
|
|
3767
|
+
if (!Array.isArray(config.hooks[cfg.event])) {
|
|
3768
|
+
config.hooks[cfg.event] = [];
|
|
3769
|
+
}
|
|
3770
|
+
if (!hasCursorHook(config.hooks[cfg.event], cfg.tail)) {
|
|
3771
|
+
config.hooks[cfg.event].push({ command: `${bin} ${cfg.tail}` });
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
writeCursorHooks(config);
|
|
3775
|
+
return { installed: true, alreadyInstalled: false };
|
|
3776
|
+
}
|
|
3777
|
+
function removeCursorLearningHooks() {
|
|
3778
|
+
const config = readCursorHooks();
|
|
3779
|
+
let removedAny = false;
|
|
3780
|
+
for (const cfg of CURSOR_HOOK_EVENTS) {
|
|
3781
|
+
const entries = config.hooks[cfg.event];
|
|
3782
|
+
if (!Array.isArray(entries)) continue;
|
|
3783
|
+
const idx = entries.findIndex((e) => isCaliberCommand(e.command, cfg.tail));
|
|
3784
|
+
if (idx !== -1) {
|
|
3785
|
+
entries.splice(idx, 1);
|
|
3786
|
+
removedAny = true;
|
|
3787
|
+
if (entries.length === 0) delete config.hooks[cfg.event];
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
if (!removedAny) return { removed: false, notFound: true };
|
|
3791
|
+
writeCursorHooks(config);
|
|
3792
|
+
return { removed: true, notFound: false };
|
|
3793
|
+
}
|
|
3720
3794
|
function removeLearningHooks() {
|
|
3721
3795
|
const settings = readSettings2();
|
|
3722
3796
|
if (!settings.hooks) return { removed: false, notFound: true };
|
|
@@ -3998,6 +4072,7 @@ var POINTS_PERMISSIONS = 2;
|
|
|
3998
4072
|
var POINTS_HOOKS = 2;
|
|
3999
4073
|
var POINTS_AGENTS_MD = 1;
|
|
4000
4074
|
var POINTS_OPEN_SKILLS_FORMAT = 2;
|
|
4075
|
+
var POINTS_LEARNED_CONTENT = 2;
|
|
4001
4076
|
var TOKEN_BUDGET_THRESHOLDS = [
|
|
4002
4077
|
{ maxTokens: 2e3, points: 6 },
|
|
4003
4078
|
{ maxTokens: 3500, points: 5 },
|
|
@@ -5106,6 +5181,18 @@ function checkBonus(dir) {
|
|
|
5106
5181
|
instruction: "Migrate flat skill files to .claude/skills/{name}/SKILL.md with YAML frontmatter."
|
|
5107
5182
|
} : void 0
|
|
5108
5183
|
});
|
|
5184
|
+
const learningsContent = readFileOrNull2(join7(dir, "CALIBER_LEARNINGS.md"));
|
|
5185
|
+
const hasLearned = learningsContent ? learningsContent.split("\n").filter((l) => l.startsWith("- ")).length > 0 : false;
|
|
5186
|
+
checks.push({
|
|
5187
|
+
id: "learned_content",
|
|
5188
|
+
name: "Learned content present",
|
|
5189
|
+
category: "bonus",
|
|
5190
|
+
maxPoints: POINTS_LEARNED_CONTENT,
|
|
5191
|
+
earnedPoints: hasLearned ? POINTS_LEARNED_CONTENT : 0,
|
|
5192
|
+
passed: hasLearned,
|
|
5193
|
+
detail: hasLearned ? "Session learnings found in CALIBER_LEARNINGS.md" : "No learned content",
|
|
5194
|
+
suggestion: hasLearned ? void 0 : "Install learning hooks: `caliber learn install`"
|
|
5195
|
+
});
|
|
5109
5196
|
return checks;
|
|
5110
5197
|
}
|
|
5111
5198
|
|
|
@@ -7314,8 +7401,8 @@ async function scoreCommand(options) {
|
|
|
7314
7401
|
}
|
|
7315
7402
|
|
|
7316
7403
|
// src/commands/refresh.ts
|
|
7317
|
-
import
|
|
7318
|
-
import
|
|
7404
|
+
import fs29 from "fs";
|
|
7405
|
+
import path23 from "path";
|
|
7319
7406
|
import chalk14 from "chalk";
|
|
7320
7407
|
import ora5 from "ora";
|
|
7321
7408
|
|
|
@@ -7327,7 +7414,8 @@ var DOC_PATTERNS = [
|
|
|
7327
7414
|
"README.md",
|
|
7328
7415
|
".cursorrules",
|
|
7329
7416
|
".cursor/rules/",
|
|
7330
|
-
".claude/skills/"
|
|
7417
|
+
".claude/skills/",
|
|
7418
|
+
"CALIBER_LEARNINGS.md"
|
|
7331
7419
|
];
|
|
7332
7420
|
function excludeArgs() {
|
|
7333
7421
|
return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
|
|
@@ -7431,8 +7519,8 @@ function writeRefreshDocs(docs) {
|
|
|
7431
7519
|
|
|
7432
7520
|
// src/ai/refresh.ts
|
|
7433
7521
|
init_config();
|
|
7434
|
-
async function refreshDocs(diff, existingDocs, projectContext) {
|
|
7435
|
-
const prompt = buildRefreshPrompt(diff, existingDocs, projectContext);
|
|
7522
|
+
async function refreshDocs(diff, existingDocs, projectContext, learnedSection) {
|
|
7523
|
+
const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection);
|
|
7436
7524
|
const fastModel = getFastModel();
|
|
7437
7525
|
const raw = await llmCall({
|
|
7438
7526
|
system: REFRESH_SYSTEM_PROMPT,
|
|
@@ -7442,7 +7530,7 @@ async function refreshDocs(diff, existingDocs, projectContext) {
|
|
|
7442
7530
|
});
|
|
7443
7531
|
return parseJsonResponse(raw);
|
|
7444
7532
|
}
|
|
7445
|
-
function buildRefreshPrompt(diff, existingDocs, projectContext) {
|
|
7533
|
+
function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection) {
|
|
7446
7534
|
const parts = [];
|
|
7447
7535
|
parts.push("Update documentation based on the following code changes.\n");
|
|
7448
7536
|
if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
|
|
@@ -7490,9 +7578,144 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
7490
7578
|
parts.push(rule.content);
|
|
7491
7579
|
}
|
|
7492
7580
|
}
|
|
7581
|
+
if (learnedSection) {
|
|
7582
|
+
parts.push("\n--- Learned Patterns (from session learning) ---");
|
|
7583
|
+
parts.push("Consider these accumulated learnings when deciding what to update:");
|
|
7584
|
+
parts.push(learnedSection);
|
|
7585
|
+
}
|
|
7493
7586
|
return parts.join("\n");
|
|
7494
7587
|
}
|
|
7495
7588
|
|
|
7589
|
+
// src/learner/writer.ts
|
|
7590
|
+
import fs27 from "fs";
|
|
7591
|
+
import path21 from "path";
|
|
7592
|
+
var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
|
|
7593
|
+
var LEARNINGS_HEADER = `# Caliber Learnings
|
|
7594
|
+
|
|
7595
|
+
Accumulated patterns and anti-patterns from development sessions.
|
|
7596
|
+
Auto-managed by [caliber](https://github.com/rely-ai-org/caliber) \u2014 do not edit manually.
|
|
7597
|
+
|
|
7598
|
+
`;
|
|
7599
|
+
var LEARNED_START = "<!-- caliber:learned -->";
|
|
7600
|
+
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
7601
|
+
var MAX_LEARNED_ITEMS = 30;
|
|
7602
|
+
function writeLearnedContent(update) {
|
|
7603
|
+
const written = [];
|
|
7604
|
+
let newItemCount = 0;
|
|
7605
|
+
let newItems = [];
|
|
7606
|
+
if (update.claudeMdLearnedSection) {
|
|
7607
|
+
const result = writeLearnedSection(update.claudeMdLearnedSection);
|
|
7608
|
+
newItemCount = result.newCount;
|
|
7609
|
+
newItems = result.newItems;
|
|
7610
|
+
written.push(LEARNINGS_FILE);
|
|
7611
|
+
}
|
|
7612
|
+
if (update.skills?.length) {
|
|
7613
|
+
for (const skill of update.skills) {
|
|
7614
|
+
const skillPath = writeLearnedSkill(skill);
|
|
7615
|
+
written.push(skillPath);
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
return { written, newItemCount, newItems };
|
|
7619
|
+
}
|
|
7620
|
+
function parseBullets(content) {
|
|
7621
|
+
const lines = content.split("\n");
|
|
7622
|
+
const bullets = [];
|
|
7623
|
+
let current = "";
|
|
7624
|
+
for (const line of lines) {
|
|
7625
|
+
if (line.startsWith("- ")) {
|
|
7626
|
+
if (current) bullets.push(current);
|
|
7627
|
+
current = line;
|
|
7628
|
+
} else if (current && line.trim() && !line.startsWith("#")) {
|
|
7629
|
+
current += "\n" + line;
|
|
7630
|
+
} else {
|
|
7631
|
+
if (current) bullets.push(current);
|
|
7632
|
+
current = "";
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
if (current) bullets.push(current);
|
|
7636
|
+
return bullets;
|
|
7637
|
+
}
|
|
7638
|
+
function normalizeBullet(bullet) {
|
|
7639
|
+
return bullet.replace(/^- /, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
|
|
7640
|
+
}
|
|
7641
|
+
function deduplicateLearnedItems(existing, incoming) {
|
|
7642
|
+
const existingBullets = existing ? parseBullets(existing) : [];
|
|
7643
|
+
const incomingBullets = parseBullets(incoming);
|
|
7644
|
+
const merged = [...existingBullets];
|
|
7645
|
+
const newItems = [];
|
|
7646
|
+
for (const bullet of incomingBullets) {
|
|
7647
|
+
const norm = normalizeBullet(bullet);
|
|
7648
|
+
if (!norm) continue;
|
|
7649
|
+
const isDup = merged.some((e) => {
|
|
7650
|
+
const eNorm = normalizeBullet(e);
|
|
7651
|
+
const shorter = Math.min(norm.length, eNorm.length);
|
|
7652
|
+
const longer = Math.max(norm.length, eNorm.length);
|
|
7653
|
+
if (!(eNorm.includes(norm) || norm.includes(eNorm))) return false;
|
|
7654
|
+
return shorter / longer > 0.7;
|
|
7655
|
+
});
|
|
7656
|
+
if (!isDup) {
|
|
7657
|
+
merged.push(bullet);
|
|
7658
|
+
newItems.push(bullet);
|
|
7659
|
+
}
|
|
7660
|
+
}
|
|
7661
|
+
const capped = merged.length > MAX_LEARNED_ITEMS ? merged.slice(-MAX_LEARNED_ITEMS) : merged;
|
|
7662
|
+
return { merged: capped.join("\n"), newCount: newItems.length, newItems };
|
|
7663
|
+
}
|
|
7664
|
+
function writeLearnedSection(content) {
|
|
7665
|
+
const existingSection = readLearnedSection();
|
|
7666
|
+
const { merged, newCount, newItems } = deduplicateLearnedItems(existingSection, content);
|
|
7667
|
+
fs27.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + merged + "\n");
|
|
7668
|
+
return { newCount, newItems };
|
|
7669
|
+
}
|
|
7670
|
+
function writeLearnedSkill(skill) {
|
|
7671
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
7672
|
+
if (!fs27.existsSync(skillDir)) fs27.mkdirSync(skillDir, { recursive: true });
|
|
7673
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
7674
|
+
if (!skill.isNew && fs27.existsSync(skillPath)) {
|
|
7675
|
+
const existing = fs27.readFileSync(skillPath, "utf-8");
|
|
7676
|
+
fs27.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
7677
|
+
} else {
|
|
7678
|
+
const frontmatter = [
|
|
7679
|
+
"---",
|
|
7680
|
+
`name: ${skill.name}`,
|
|
7681
|
+
`description: ${skill.description}`,
|
|
7682
|
+
"---",
|
|
7683
|
+
""
|
|
7684
|
+
].join("\n");
|
|
7685
|
+
fs27.writeFileSync(skillPath, frontmatter + skill.content);
|
|
7686
|
+
}
|
|
7687
|
+
return skillPath;
|
|
7688
|
+
}
|
|
7689
|
+
function readLearnedSection() {
|
|
7690
|
+
if (fs27.existsSync(LEARNINGS_FILE)) {
|
|
7691
|
+
const content2 = fs27.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
7692
|
+
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
7693
|
+
return bullets || null;
|
|
7694
|
+
}
|
|
7695
|
+
const claudeMdPath = "CLAUDE.md";
|
|
7696
|
+
if (!fs27.existsSync(claudeMdPath)) return null;
|
|
7697
|
+
const content = fs27.readFileSync(claudeMdPath, "utf-8");
|
|
7698
|
+
const startIdx = content.indexOf(LEARNED_START);
|
|
7699
|
+
const endIdx = content.indexOf(LEARNED_END);
|
|
7700
|
+
if (startIdx === -1 || endIdx === -1) return null;
|
|
7701
|
+
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
7702
|
+
}
|
|
7703
|
+
function migrateInlineLearnings() {
|
|
7704
|
+
if (fs27.existsSync(LEARNINGS_FILE)) return false;
|
|
7705
|
+
const claudeMdPath = "CLAUDE.md";
|
|
7706
|
+
if (!fs27.existsSync(claudeMdPath)) return false;
|
|
7707
|
+
const content = fs27.readFileSync(claudeMdPath, "utf-8");
|
|
7708
|
+
const startIdx = content.indexOf(LEARNED_START);
|
|
7709
|
+
const endIdx = content.indexOf(LEARNED_END);
|
|
7710
|
+
if (startIdx === -1 || endIdx === -1) return false;
|
|
7711
|
+
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
7712
|
+
if (!section) return false;
|
|
7713
|
+
fs27.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
7714
|
+
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
7715
|
+
fs27.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
7716
|
+
return true;
|
|
7717
|
+
}
|
|
7718
|
+
|
|
7496
7719
|
// src/commands/refresh.ts
|
|
7497
7720
|
init_config();
|
|
7498
7721
|
function log2(quiet, ...args) {
|
|
@@ -7501,11 +7724,11 @@ function log2(quiet, ...args) {
|
|
|
7501
7724
|
function discoverGitRepos(parentDir) {
|
|
7502
7725
|
const repos = [];
|
|
7503
7726
|
try {
|
|
7504
|
-
const entries =
|
|
7727
|
+
const entries = fs29.readdirSync(parentDir, { withFileTypes: true });
|
|
7505
7728
|
for (const entry of entries) {
|
|
7506
7729
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
7507
|
-
const childPath =
|
|
7508
|
-
if (
|
|
7730
|
+
const childPath = path23.join(parentDir, entry.name);
|
|
7731
|
+
if (fs29.existsSync(path23.join(childPath, ".git"))) {
|
|
7509
7732
|
repos.push(childPath);
|
|
7510
7733
|
}
|
|
7511
7734
|
}
|
|
@@ -7529,6 +7752,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
7529
7752
|
}
|
|
7530
7753
|
const spinner = quiet ? null : ora5(`${prefix}Analyzing changes...`).start();
|
|
7531
7754
|
const existingDocs = readExistingConfigs(repoDir);
|
|
7755
|
+
const learnedSection = readLearnedSection();
|
|
7532
7756
|
const fingerprint = await collectFingerprint(repoDir);
|
|
7533
7757
|
const projectContext = {
|
|
7534
7758
|
languages: fingerprint.languages,
|
|
@@ -7544,7 +7768,8 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
7544
7768
|
summary: diff.summary
|
|
7545
7769
|
},
|
|
7546
7770
|
existingDocs,
|
|
7547
|
-
projectContext
|
|
7771
|
+
projectContext,
|
|
7772
|
+
learnedSection
|
|
7548
7773
|
);
|
|
7549
7774
|
if (!response.docsUpdated || response.docsUpdated.length === 0) {
|
|
7550
7775
|
spinner?.succeed(`${prefix}No doc updates needed`);
|
|
@@ -7606,7 +7831,7 @@ async function refreshCommand(options) {
|
|
|
7606
7831
|
`));
|
|
7607
7832
|
const originalDir = process.cwd();
|
|
7608
7833
|
for (const repo of repos) {
|
|
7609
|
-
const repoName =
|
|
7834
|
+
const repoName = path23.basename(repo);
|
|
7610
7835
|
try {
|
|
7611
7836
|
process.chdir(repo);
|
|
7612
7837
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -7830,6 +8055,7 @@ async function configCommand() {
|
|
|
7830
8055
|
}
|
|
7831
8056
|
|
|
7832
8057
|
// src/commands/learn.ts
|
|
8058
|
+
import fs31 from "fs";
|
|
7833
8059
|
import chalk17 from "chalk";
|
|
7834
8060
|
|
|
7835
8061
|
// src/learner/stdin.ts
|
|
@@ -7861,8 +8087,8 @@ function readStdin() {
|
|
|
7861
8087
|
|
|
7862
8088
|
// src/learner/storage.ts
|
|
7863
8089
|
init_constants();
|
|
7864
|
-
import
|
|
7865
|
-
import
|
|
8090
|
+
import fs30 from "fs";
|
|
8091
|
+
import path24 from "path";
|
|
7866
8092
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
7867
8093
|
var DEFAULT_STATE = {
|
|
7868
8094
|
sessionId: null,
|
|
@@ -7870,15 +8096,15 @@ var DEFAULT_STATE = {
|
|
|
7870
8096
|
lastAnalysisTimestamp: null
|
|
7871
8097
|
};
|
|
7872
8098
|
function ensureLearningDir() {
|
|
7873
|
-
if (!
|
|
7874
|
-
|
|
8099
|
+
if (!fs30.existsSync(LEARNING_DIR)) {
|
|
8100
|
+
fs30.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
7875
8101
|
}
|
|
7876
8102
|
}
|
|
7877
8103
|
function sessionFilePath() {
|
|
7878
|
-
return
|
|
8104
|
+
return path24.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
7879
8105
|
}
|
|
7880
8106
|
function stateFilePath() {
|
|
7881
|
-
return
|
|
8107
|
+
return path24.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
7882
8108
|
}
|
|
7883
8109
|
function truncateResponse(response) {
|
|
7884
8110
|
const str = JSON.stringify(response);
|
|
@@ -7889,113 +8115,84 @@ function appendEvent(event) {
|
|
|
7889
8115
|
ensureLearningDir();
|
|
7890
8116
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
7891
8117
|
const filePath = sessionFilePath();
|
|
7892
|
-
|
|
8118
|
+
fs30.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
7893
8119
|
const count = getEventCount();
|
|
7894
8120
|
if (count > LEARNING_MAX_EVENTS) {
|
|
7895
|
-
const lines =
|
|
8121
|
+
const lines = fs30.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
7896
8122
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
7897
|
-
|
|
8123
|
+
fs30.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
7898
8124
|
}
|
|
7899
8125
|
}
|
|
7900
8126
|
function readAllEvents() {
|
|
7901
8127
|
const filePath = sessionFilePath();
|
|
7902
|
-
if (!
|
|
7903
|
-
const lines =
|
|
8128
|
+
if (!fs30.existsSync(filePath)) return [];
|
|
8129
|
+
const lines = fs30.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
7904
8130
|
return lines.map((line) => JSON.parse(line));
|
|
7905
8131
|
}
|
|
7906
8132
|
function getEventCount() {
|
|
7907
8133
|
const filePath = sessionFilePath();
|
|
7908
|
-
if (!
|
|
7909
|
-
const content =
|
|
8134
|
+
if (!fs30.existsSync(filePath)) return 0;
|
|
8135
|
+
const content = fs30.readFileSync(filePath, "utf-8");
|
|
7910
8136
|
return content.split("\n").filter(Boolean).length;
|
|
7911
8137
|
}
|
|
7912
8138
|
function clearSession() {
|
|
7913
8139
|
const filePath = sessionFilePath();
|
|
7914
|
-
if (
|
|
8140
|
+
if (fs30.existsSync(filePath)) fs30.unlinkSync(filePath);
|
|
7915
8141
|
}
|
|
7916
8142
|
function readState2() {
|
|
7917
8143
|
const filePath = stateFilePath();
|
|
7918
|
-
if (!
|
|
8144
|
+
if (!fs30.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
7919
8145
|
try {
|
|
7920
|
-
return JSON.parse(
|
|
8146
|
+
return JSON.parse(fs30.readFileSync(filePath, "utf-8"));
|
|
7921
8147
|
} catch {
|
|
7922
8148
|
return { ...DEFAULT_STATE };
|
|
7923
8149
|
}
|
|
7924
8150
|
}
|
|
7925
8151
|
function writeState2(state) {
|
|
7926
8152
|
ensureLearningDir();
|
|
7927
|
-
|
|
8153
|
+
fs30.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
7928
8154
|
}
|
|
7929
8155
|
function resetState() {
|
|
7930
8156
|
writeState2({ ...DEFAULT_STATE });
|
|
7931
8157
|
}
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
const
|
|
7940
|
-
if (
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
written.push(skillPath);
|
|
8158
|
+
var LOCK_FILE2 = "finalize.lock";
|
|
8159
|
+
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
8160
|
+
function lockFilePath() {
|
|
8161
|
+
return path24.join(LEARNING_DIR, LOCK_FILE2);
|
|
8162
|
+
}
|
|
8163
|
+
function acquireFinalizeLock() {
|
|
8164
|
+
ensureLearningDir();
|
|
8165
|
+
const lockPath = lockFilePath();
|
|
8166
|
+
if (fs30.existsSync(lockPath)) {
|
|
8167
|
+
try {
|
|
8168
|
+
const stat = fs30.statSync(lockPath);
|
|
8169
|
+
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
|
|
8170
|
+
return false;
|
|
8171
|
+
}
|
|
8172
|
+
} catch {
|
|
7948
8173
|
}
|
|
7949
8174
|
}
|
|
7950
|
-
|
|
7951
|
-
}
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
let updated;
|
|
7964
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
7965
|
-
updated = existing.slice(0, startIdx) + section + existing.slice(endIdx + LEARNED_END.length);
|
|
7966
|
-
} else {
|
|
7967
|
-
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
7968
|
-
updated = existing + separator + "\n" + section + "\n";
|
|
8175
|
+
try {
|
|
8176
|
+
fs30.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
8177
|
+
return true;
|
|
8178
|
+
} catch {
|
|
8179
|
+
try {
|
|
8180
|
+
const stat = fs30.statSync(lockPath);
|
|
8181
|
+
if (Date.now() - stat.mtimeMs >= LOCK_STALE_MS) {
|
|
8182
|
+
fs30.writeFileSync(lockPath, String(process.pid));
|
|
8183
|
+
return true;
|
|
8184
|
+
}
|
|
8185
|
+
} catch {
|
|
8186
|
+
}
|
|
8187
|
+
return false;
|
|
7969
8188
|
}
|
|
7970
|
-
fs30.writeFileSync(claudeMdPath, updated);
|
|
7971
8189
|
}
|
|
7972
|
-
function
|
|
7973
|
-
const
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
const existing = fs30.readFileSync(skillPath, "utf-8");
|
|
7978
|
-
fs30.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
7979
|
-
} else {
|
|
7980
|
-
const frontmatter = [
|
|
7981
|
-
"---",
|
|
7982
|
-
`name: ${skill.name}`,
|
|
7983
|
-
`description: ${skill.description}`,
|
|
7984
|
-
"---",
|
|
7985
|
-
""
|
|
7986
|
-
].join("\n");
|
|
7987
|
-
fs30.writeFileSync(skillPath, frontmatter + skill.content);
|
|
8190
|
+
function releaseFinalizeLock() {
|
|
8191
|
+
const lockPath = lockFilePath();
|
|
8192
|
+
try {
|
|
8193
|
+
if (fs30.existsSync(lockPath)) fs30.unlinkSync(lockPath);
|
|
8194
|
+
} catch {
|
|
7988
8195
|
}
|
|
7989
|
-
return skillPath;
|
|
7990
|
-
}
|
|
7991
|
-
function readLearnedSection() {
|
|
7992
|
-
const claudeMdPath = "CLAUDE.md";
|
|
7993
|
-
if (!fs30.existsSync(claudeMdPath)) return null;
|
|
7994
|
-
const content = fs30.readFileSync(claudeMdPath, "utf-8");
|
|
7995
|
-
const startIdx = content.indexOf(LEARNED_START);
|
|
7996
|
-
const endIdx = content.indexOf(LEARNED_END);
|
|
7997
|
-
if (startIdx === -1 || endIdx === -1) return null;
|
|
7998
|
-
return content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
7999
8196
|
}
|
|
8000
8197
|
|
|
8001
8198
|
// src/ai/learn.ts
|
|
@@ -8074,6 +8271,7 @@ ${eventsText}`;
|
|
|
8074
8271
|
|
|
8075
8272
|
// src/commands/learn.ts
|
|
8076
8273
|
init_config();
|
|
8274
|
+
var MIN_EVENTS_FOR_ANALYSIS = 50;
|
|
8077
8275
|
async function learnObserveCommand(options) {
|
|
8078
8276
|
try {
|
|
8079
8277
|
const raw = await readStdin();
|
|
@@ -8081,11 +8279,11 @@ async function learnObserveCommand(options) {
|
|
|
8081
8279
|
const hookData = JSON.parse(raw);
|
|
8082
8280
|
const event = {
|
|
8083
8281
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8084
|
-
session_id: hookData.session_id || "unknown",
|
|
8282
|
+
session_id: hookData.session_id || hookData.conversation_id || "unknown",
|
|
8085
8283
|
hook_event_name: options.failure ? "PostToolUseFailure" : "PostToolUse",
|
|
8086
8284
|
tool_name: hookData.tool_name || "unknown",
|
|
8087
8285
|
tool_input: hookData.tool_input || {},
|
|
8088
|
-
tool_response: hookData.tool_response || {},
|
|
8286
|
+
tool_response: hookData.tool_response || hookData.tool_output || {},
|
|
8089
8287
|
tool_use_id: hookData.tool_use_id || "",
|
|
8090
8288
|
cwd: hookData.cwd || process.cwd()
|
|
8091
8289
|
};
|
|
@@ -8100,6 +8298,8 @@ async function learnObserveCommand(options) {
|
|
|
8100
8298
|
async function learnFinalizeCommand() {
|
|
8101
8299
|
const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
|
|
8102
8300
|
if (isCaliberRunning2()) return;
|
|
8301
|
+
if (!acquireFinalizeLock()) return;
|
|
8302
|
+
let analyzed = false;
|
|
8103
8303
|
try {
|
|
8104
8304
|
const config = loadConfig();
|
|
8105
8305
|
if (!config) {
|
|
@@ -8108,12 +8308,9 @@ async function learnFinalizeCommand() {
|
|
|
8108
8308
|
return;
|
|
8109
8309
|
}
|
|
8110
8310
|
const events = readAllEvents();
|
|
8111
|
-
if (
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
return;
|
|
8115
|
-
}
|
|
8116
|
-
await validateModel();
|
|
8311
|
+
if (events.length < MIN_EVENTS_FOR_ANALYSIS) return;
|
|
8312
|
+
await validateModel({ fast: true });
|
|
8313
|
+
migrateInlineLearnings();
|
|
8117
8314
|
const existingConfigs = readExistingConfigs(process.cwd());
|
|
8118
8315
|
const existingLearnedSection = readLearnedSection();
|
|
8119
8316
|
const existingSkills = existingConfigs.claudeSkills || [];
|
|
@@ -8123,51 +8320,97 @@ async function learnFinalizeCommand() {
|
|
|
8123
8320
|
existingLearnedSection,
|
|
8124
8321
|
existingSkills
|
|
8125
8322
|
);
|
|
8323
|
+
analyzed = true;
|
|
8126
8324
|
if (response.claudeMdLearnedSection || response.skills?.length) {
|
|
8127
|
-
writeLearnedContent({
|
|
8325
|
+
const result = writeLearnedContent({
|
|
8128
8326
|
claudeMdLearnedSection: response.claudeMdLearnedSection,
|
|
8129
8327
|
skills: response.skills
|
|
8130
8328
|
});
|
|
8329
|
+
if (result.newItemCount > 0) {
|
|
8330
|
+
console.log(chalk17.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}`));
|
|
8331
|
+
for (const item of result.newItems) {
|
|
8332
|
+
console.log(chalk17.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
8131
8335
|
}
|
|
8132
8336
|
} catch {
|
|
8133
8337
|
} finally {
|
|
8134
|
-
|
|
8135
|
-
|
|
8338
|
+
if (analyzed) {
|
|
8339
|
+
clearSession();
|
|
8340
|
+
resetState();
|
|
8341
|
+
}
|
|
8342
|
+
releaseFinalizeLock();
|
|
8136
8343
|
}
|
|
8137
8344
|
}
|
|
8138
8345
|
async function learnInstallCommand() {
|
|
8139
|
-
|
|
8140
|
-
if (
|
|
8141
|
-
|
|
8346
|
+
let anyInstalled = false;
|
|
8347
|
+
if (fs31.existsSync(".claude")) {
|
|
8348
|
+
const r = installLearningHooks();
|
|
8349
|
+
if (r.installed) {
|
|
8350
|
+
console.log(chalk17.green("\u2713") + " Claude Code learning hooks installed");
|
|
8351
|
+
anyInstalled = true;
|
|
8352
|
+
} else if (r.alreadyInstalled) {
|
|
8353
|
+
console.log(chalk17.dim(" Claude Code hooks already installed"));
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
if (fs31.existsSync(".cursor")) {
|
|
8357
|
+
const r = installCursorLearningHooks();
|
|
8358
|
+
if (r.installed) {
|
|
8359
|
+
console.log(chalk17.green("\u2713") + " Cursor learning hooks installed");
|
|
8360
|
+
anyInstalled = true;
|
|
8361
|
+
} else if (r.alreadyInstalled) {
|
|
8362
|
+
console.log(chalk17.dim(" Cursor hooks already installed"));
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
8365
|
+
if (!fs31.existsSync(".claude") && !fs31.existsSync(".cursor")) {
|
|
8366
|
+
console.log(chalk17.yellow("No .claude/ or .cursor/ directory found."));
|
|
8367
|
+
console.log(chalk17.dim(" Run `caliber init` first, or create the directory manually."));
|
|
8142
8368
|
return;
|
|
8143
8369
|
}
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8370
|
+
if (anyInstalled) {
|
|
8371
|
+
console.log(chalk17.dim(` Tool usage will be recorded and learnings extracted after \u2265${MIN_EVENTS_FOR_ANALYSIS} events.`));
|
|
8372
|
+
console.log(chalk17.dim(" Learnings written to CALIBER_LEARNINGS.md."));
|
|
8373
|
+
}
|
|
8147
8374
|
}
|
|
8148
8375
|
async function learnRemoveCommand() {
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8376
|
+
let anyRemoved = false;
|
|
8377
|
+
const r1 = removeLearningHooks();
|
|
8378
|
+
if (r1.removed) {
|
|
8379
|
+
console.log(chalk17.green("\u2713") + " Claude Code learning hooks removed");
|
|
8380
|
+
anyRemoved = true;
|
|
8381
|
+
}
|
|
8382
|
+
const r2 = removeCursorLearningHooks();
|
|
8383
|
+
if (r2.removed) {
|
|
8384
|
+
console.log(chalk17.green("\u2713") + " Cursor learning hooks removed");
|
|
8385
|
+
anyRemoved = true;
|
|
8386
|
+
}
|
|
8387
|
+
if (!anyRemoved) {
|
|
8388
|
+
console.log(chalk17.dim("No learning hooks found."));
|
|
8153
8389
|
}
|
|
8154
|
-
console.log(chalk17.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
8155
8390
|
}
|
|
8156
8391
|
async function learnStatusCommand() {
|
|
8157
|
-
const
|
|
8392
|
+
const claudeInstalled = areLearningHooksInstalled();
|
|
8393
|
+
const cursorInstalled = areCursorLearningHooksInstalled();
|
|
8158
8394
|
const state = readState2();
|
|
8159
8395
|
const eventCount = getEventCount();
|
|
8160
8396
|
console.log(chalk17.bold("Session Learning Status"));
|
|
8161
8397
|
console.log();
|
|
8162
|
-
if (
|
|
8163
|
-
console.log(chalk17.green("\u2713") + "
|
|
8398
|
+
if (claudeInstalled) {
|
|
8399
|
+
console.log(chalk17.green("\u2713") + " Claude Code hooks " + chalk17.green("installed"));
|
|
8400
|
+
} else {
|
|
8401
|
+
console.log(chalk17.dim("\u2717") + " Claude Code hooks " + chalk17.dim("not installed"));
|
|
8402
|
+
}
|
|
8403
|
+
if (cursorInstalled) {
|
|
8404
|
+
console.log(chalk17.green("\u2713") + " Cursor hooks " + chalk17.green("installed"));
|
|
8164
8405
|
} else {
|
|
8165
|
-
console.log(chalk17.dim("\u2717") + "
|
|
8406
|
+
console.log(chalk17.dim("\u2717") + " Cursor hooks " + chalk17.dim("not installed"));
|
|
8407
|
+
}
|
|
8408
|
+
if (!claudeInstalled && !cursorInstalled) {
|
|
8166
8409
|
console.log(chalk17.dim(" Run `caliber learn install` to enable session learning."));
|
|
8167
8410
|
}
|
|
8168
8411
|
console.log();
|
|
8169
8412
|
console.log(`Events recorded: ${chalk17.cyan(String(eventCount))}`);
|
|
8170
|
-
console.log(`
|
|
8413
|
+
console.log(`Threshold for analysis: ${chalk17.cyan(String(MIN_EVENTS_FOR_ANALYSIS))}`);
|
|
8171
8414
|
if (state.lastAnalysisTimestamp) {
|
|
8172
8415
|
console.log(`Last analysis: ${chalk17.cyan(state.lastAnalysisTimestamp)}`);
|
|
8173
8416
|
} else {
|
|
@@ -8177,14 +8420,14 @@ async function learnStatusCommand() {
|
|
|
8177
8420
|
if (learnedSection) {
|
|
8178
8421
|
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
8179
8422
|
console.log(`
|
|
8180
|
-
Learned items in
|
|
8423
|
+
Learned items in CALIBER_LEARNINGS.md: ${chalk17.cyan(String(lineCount))}`);
|
|
8181
8424
|
}
|
|
8182
8425
|
}
|
|
8183
8426
|
|
|
8184
8427
|
// src/cli.ts
|
|
8185
8428
|
var __dirname = path25.dirname(fileURLToPath(import.meta.url));
|
|
8186
8429
|
var pkg = JSON.parse(
|
|
8187
|
-
|
|
8430
|
+
fs32.readFileSync(path25.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
8188
8431
|
);
|
|
8189
8432
|
var program = new Command();
|
|
8190
8433
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -8258,7 +8501,7 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
8258
8501
|
learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
|
|
8259
8502
|
|
|
8260
8503
|
// src/utils/version-check.ts
|
|
8261
|
-
import
|
|
8504
|
+
import fs33 from "fs";
|
|
8262
8505
|
import path26 from "path";
|
|
8263
8506
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8264
8507
|
import { execSync as execSync14 } from "child_process";
|
|
@@ -8267,13 +8510,13 @@ import ora6 from "ora";
|
|
|
8267
8510
|
import confirm2 from "@inquirer/confirm";
|
|
8268
8511
|
var __dirname_vc = path26.dirname(fileURLToPath2(import.meta.url));
|
|
8269
8512
|
var pkg2 = JSON.parse(
|
|
8270
|
-
|
|
8513
|
+
fs33.readFileSync(path26.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
8271
8514
|
);
|
|
8272
8515
|
function getInstalledVersion() {
|
|
8273
8516
|
try {
|
|
8274
8517
|
const globalRoot = execSync14("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8275
8518
|
const pkgPath = path26.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
8276
|
-
return JSON.parse(
|
|
8519
|
+
return JSON.parse(fs33.readFileSync(pkgPath, "utf-8")).version;
|
|
8277
8520
|
} catch {
|
|
8278
8521
|
return null;
|
|
8279
8522
|
}
|
|
@@ -8362,7 +8605,9 @@ acquireLock();
|
|
|
8362
8605
|
if (process.env.CALIBER_LOCAL) {
|
|
8363
8606
|
process.env.CALIBER_SKIP_UPDATE_CHECK = "1";
|
|
8364
8607
|
}
|
|
8365
|
-
var
|
|
8608
|
+
var userArgs = process.argv.slice(2);
|
|
8609
|
+
var hasCommand = userArgs.some((a) => !a.startsWith("-"));
|
|
8610
|
+
var isQuickExit = !hasCommand || ["--version", "-V", "--help", "-h"].some((f) => userArgs.includes(f));
|
|
8366
8611
|
if (!isQuickExit) {
|
|
8367
8612
|
await checkForUpdates();
|
|
8368
8613
|
}
|
package/package.json
CHANGED