@rely-ai/caliber 1.30.6 → 1.31.0-dev.1774710550
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 +666 -483
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -231,20 +231,20 @@ var init_types = __esm({
|
|
|
231
231
|
});
|
|
232
232
|
|
|
233
233
|
// src/utils/editor.ts
|
|
234
|
-
import { execSync as
|
|
235
|
-
import
|
|
236
|
-
import
|
|
234
|
+
import { execSync as execSync14, spawn as spawn3 } from "child_process";
|
|
235
|
+
import fs27 from "fs";
|
|
236
|
+
import path23 from "path";
|
|
237
237
|
import os6 from "os";
|
|
238
238
|
function getEmptyFilePath(proposedPath) {
|
|
239
|
-
|
|
240
|
-
const tempPath =
|
|
241
|
-
|
|
239
|
+
fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
240
|
+
const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
|
|
241
|
+
fs27.writeFileSync(tempPath, "");
|
|
242
242
|
return tempPath;
|
|
243
243
|
}
|
|
244
244
|
function commandExists(cmd) {
|
|
245
245
|
try {
|
|
246
246
|
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
247
|
-
|
|
247
|
+
execSync14(check, { stdio: "ignore" });
|
|
248
248
|
return true;
|
|
249
249
|
} catch {
|
|
250
250
|
return false;
|
|
@@ -278,7 +278,7 @@ var init_editor = __esm({
|
|
|
278
278
|
"src/utils/editor.ts"() {
|
|
279
279
|
"use strict";
|
|
280
280
|
IS_WINDOWS3 = process.platform === "win32";
|
|
281
|
-
DIFF_TEMP_DIR =
|
|
281
|
+
DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
|
|
282
282
|
}
|
|
283
283
|
});
|
|
284
284
|
|
|
@@ -290,7 +290,7 @@ __export(review_exports, {
|
|
|
290
290
|
promptWantsReview: () => promptWantsReview
|
|
291
291
|
});
|
|
292
292
|
import chalk10 from "chalk";
|
|
293
|
-
import
|
|
293
|
+
import fs28 from "fs";
|
|
294
294
|
import select4 from "@inquirer/select";
|
|
295
295
|
import { createTwoFilesPatch } from "diff";
|
|
296
296
|
async function promptWantsReview() {
|
|
@@ -327,8 +327,8 @@ async function openReview(method, stagedFiles) {
|
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
const fileInfos = stagedFiles.map((file) => {
|
|
330
|
-
const proposed =
|
|
331
|
-
const current = file.currentPath ?
|
|
330
|
+
const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
|
|
331
|
+
const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
|
|
332
332
|
const patch = createTwoFilesPatch(
|
|
333
333
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
334
334
|
file.relativePath,
|
|
@@ -503,13 +503,13 @@ __export(lock_exports, {
|
|
|
503
503
|
isCaliberRunning: () => isCaliberRunning,
|
|
504
504
|
releaseLock: () => releaseLock
|
|
505
505
|
});
|
|
506
|
-
import
|
|
507
|
-
import
|
|
506
|
+
import fs38 from "fs";
|
|
507
|
+
import path30 from "path";
|
|
508
508
|
import os8 from "os";
|
|
509
509
|
function isCaliberRunning() {
|
|
510
510
|
try {
|
|
511
|
-
if (!
|
|
512
|
-
const raw =
|
|
511
|
+
if (!fs38.existsSync(LOCK_FILE)) return false;
|
|
512
|
+
const raw = fs38.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
513
513
|
const { pid, ts } = JSON.parse(raw);
|
|
514
514
|
if (Date.now() - ts > STALE_MS) return false;
|
|
515
515
|
try {
|
|
@@ -524,13 +524,13 @@ function isCaliberRunning() {
|
|
|
524
524
|
}
|
|
525
525
|
function acquireLock() {
|
|
526
526
|
try {
|
|
527
|
-
|
|
527
|
+
fs38.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
528
528
|
} catch {
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
function releaseLock() {
|
|
532
532
|
try {
|
|
533
|
-
if (
|
|
533
|
+
if (fs38.existsSync(LOCK_FILE)) fs38.unlinkSync(LOCK_FILE);
|
|
534
534
|
} catch {
|
|
535
535
|
}
|
|
536
536
|
}
|
|
@@ -538,7 +538,7 @@ var LOCK_FILE, STALE_MS;
|
|
|
538
538
|
var init_lock = __esm({
|
|
539
539
|
"src/lib/lock.ts"() {
|
|
540
540
|
"use strict";
|
|
541
|
-
LOCK_FILE =
|
|
541
|
+
LOCK_FILE = path30.join(os8.tmpdir(), ".caliber.lock");
|
|
542
542
|
STALE_MS = 10 * 60 * 1e3;
|
|
543
543
|
}
|
|
544
544
|
});
|
|
@@ -550,9 +550,9 @@ import path38 from "path";
|
|
|
550
550
|
import { fileURLToPath } from "url";
|
|
551
551
|
|
|
552
552
|
// src/commands/init.ts
|
|
553
|
-
import
|
|
553
|
+
import path26 from "path";
|
|
554
554
|
import chalk14 from "chalk";
|
|
555
|
-
import
|
|
555
|
+
import fs33 from "fs";
|
|
556
556
|
|
|
557
557
|
// src/fingerprint/index.ts
|
|
558
558
|
import fs8 from "fs";
|
|
@@ -2450,7 +2450,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
|
|
|
2450
2450
|
Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
|
|
2451
2451
|
README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
|
|
2452
2452
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
2453
|
-
- CLAUDE.md / AGENTS.md: MUST be under
|
|
2453
|
+
- CLAUDE.md / AGENTS.md: MUST be under 400 lines for maximum score. Aim for 200-350 lines. Be thorough \u2014 commands, architecture overview, key conventions, data flow, and important patterns. Use bullet points and tables, not prose.
|
|
2454
2454
|
|
|
2455
2455
|
Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
|
|
2456
2456
|
GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
|
|
@@ -2578,7 +2578,7 @@ Structure:
|
|
|
2578
2578
|
5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
|
|
2579
2579
|
|
|
2580
2580
|
Rules:
|
|
2581
|
-
- Max
|
|
2581
|
+
- Max 400 lines. Focus on actionable instructions, not documentation prose.
|
|
2582
2582
|
- Study existing code in the project context to extract the real patterns being used. A skill for "create API route" should show the exact file structure, imports, error handling, and naming that existing routes use.
|
|
2583
2583
|
- Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
|
|
2584
2584
|
- Never use ambiguous language. Instead of "handle errors properly", write "Wrap the DB call in try/catch. On failure, return { error: string, code: number } matching the ErrorResponse type in \`src/types.ts\`."
|
|
@@ -2632,7 +2632,7 @@ Rules:
|
|
|
2632
2632
|
- Update the "fileDescriptions" to reflect any changes you make.
|
|
2633
2633
|
|
|
2634
2634
|
Quality constraints \u2014 your changes are scored, so do not break these:
|
|
2635
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
2635
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
|
|
2636
2636
|
- Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
|
|
2637
2637
|
- Do NOT add directory tree listings in code blocks.
|
|
2638
2638
|
- Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
|
|
@@ -2656,7 +2656,7 @@ CONSERVATIVE UPDATE means:
|
|
|
2656
2656
|
- NEVER replace specific paths/commands with generic prose
|
|
2657
2657
|
|
|
2658
2658
|
Quality constraints (the output is scored deterministically):
|
|
2659
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
2659
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
2660
2660
|
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
2661
2661
|
- Every file path, command, and identifier must be in backticks
|
|
2662
2662
|
- ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
|
|
@@ -2664,6 +2664,7 @@ Quality constraints (the output is scored deterministically):
|
|
|
2664
2664
|
|
|
2665
2665
|
Managed content:
|
|
2666
2666
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
2667
|
+
- Keep context sync blocks (<!-- caliber:managed:sync --> ... <!-- /caliber:managed:sync -->) intact
|
|
2667
2668
|
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2668
2669
|
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
2669
2670
|
|
|
@@ -2679,9 +2680,12 @@ Return a JSON object with this exact shape:
|
|
|
2679
2680
|
"copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
|
|
2680
2681
|
},
|
|
2681
2682
|
"changesSummary": "<1-2 sentence summary of what was updated and why>",
|
|
2683
|
+
"fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
|
|
2682
2684
|
"docsUpdated": ["CLAUDE.md", "README.md"]
|
|
2683
2685
|
}
|
|
2684
2686
|
|
|
2687
|
+
The "fileChanges" array MUST include one entry per file that was updated (non-null in updatedDocs). Each entry describes what specifically changed in that file \u2014 be concrete (e.g. "added auth middleware section" not "updated docs").
|
|
2688
|
+
|
|
2685
2689
|
Respond with ONLY the JSON object, no markdown fences or extra text.`;
|
|
2686
2690
|
var LEARN_SYSTEM_PROMPT = `You are an expert developer experience engineer. You analyze raw tool call events from AI coding sessions to extract reusable operational lessons that will help future LLM sessions work more effectively in this project.
|
|
2687
2691
|
|
|
@@ -3173,9 +3177,151 @@ function getCursorConfigDir() {
|
|
|
3173
3177
|
return path8.join(home, ".config", "Cursor");
|
|
3174
3178
|
}
|
|
3175
3179
|
|
|
3176
|
-
// src/
|
|
3180
|
+
// src/lib/hooks.ts
|
|
3181
|
+
init_resolve_caliber();
|
|
3177
3182
|
import fs10 from "fs";
|
|
3178
3183
|
import path9 from "path";
|
|
3184
|
+
import { execSync as execSync8 } from "child_process";
|
|
3185
|
+
var SETTINGS_PATH = path9.join(".claude", "settings.json");
|
|
3186
|
+
var REFRESH_TAIL = "refresh --quiet";
|
|
3187
|
+
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
3188
|
+
function getHookCommand() {
|
|
3189
|
+
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3190
|
+
}
|
|
3191
|
+
function readSettings() {
|
|
3192
|
+
if (!fs10.existsSync(SETTINGS_PATH)) return {};
|
|
3193
|
+
try {
|
|
3194
|
+
return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3195
|
+
} catch {
|
|
3196
|
+
return {};
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
function writeSettings(settings) {
|
|
3200
|
+
const dir = path9.dirname(SETTINGS_PATH);
|
|
3201
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3202
|
+
fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
3203
|
+
}
|
|
3204
|
+
function findHookIndex(sessionEnd) {
|
|
3205
|
+
return sessionEnd.findIndex(
|
|
3206
|
+
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
3207
|
+
);
|
|
3208
|
+
}
|
|
3209
|
+
function isHookInstalled() {
|
|
3210
|
+
const settings = readSettings();
|
|
3211
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3212
|
+
if (!Array.isArray(sessionEnd)) return false;
|
|
3213
|
+
return findHookIndex(sessionEnd) !== -1;
|
|
3214
|
+
}
|
|
3215
|
+
function installHook() {
|
|
3216
|
+
const settings = readSettings();
|
|
3217
|
+
if (!settings.hooks) settings.hooks = {};
|
|
3218
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
3219
|
+
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
3220
|
+
return { installed: false, alreadyInstalled: true };
|
|
3221
|
+
}
|
|
3222
|
+
settings.hooks.SessionEnd.push({
|
|
3223
|
+
matcher: "",
|
|
3224
|
+
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
3225
|
+
});
|
|
3226
|
+
writeSettings(settings);
|
|
3227
|
+
return { installed: true, alreadyInstalled: false };
|
|
3228
|
+
}
|
|
3229
|
+
function removeHook() {
|
|
3230
|
+
const settings = readSettings();
|
|
3231
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3232
|
+
if (!Array.isArray(sessionEnd)) {
|
|
3233
|
+
return { removed: false, notFound: true };
|
|
3234
|
+
}
|
|
3235
|
+
const idx = findHookIndex(sessionEnd);
|
|
3236
|
+
if (idx === -1) {
|
|
3237
|
+
return { removed: false, notFound: true };
|
|
3238
|
+
}
|
|
3239
|
+
sessionEnd.splice(idx, 1);
|
|
3240
|
+
if (sessionEnd.length === 0) {
|
|
3241
|
+
delete settings.hooks.SessionEnd;
|
|
3242
|
+
}
|
|
3243
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
3244
|
+
delete settings.hooks;
|
|
3245
|
+
}
|
|
3246
|
+
writeSettings(settings);
|
|
3247
|
+
return { removed: true, notFound: false };
|
|
3248
|
+
}
|
|
3249
|
+
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
3250
|
+
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
3251
|
+
function getPrecommitBlock() {
|
|
3252
|
+
const bin = resolveCaliber();
|
|
3253
|
+
const npx = isNpxResolution();
|
|
3254
|
+
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
3255
|
+
const invoke = npx ? bin : `"${bin}"`;
|
|
3256
|
+
return `${PRECOMMIT_START}
|
|
3257
|
+
if ${guard}; then
|
|
3258
|
+
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
3259
|
+
${invoke} refresh 2>/dev/null || true
|
|
3260
|
+
${invoke} learn finalize 2>/dev/null || true
|
|
3261
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
3262
|
+
fi
|
|
3263
|
+
${PRECOMMIT_END}`;
|
|
3264
|
+
}
|
|
3265
|
+
function getGitHooksDir() {
|
|
3266
|
+
try {
|
|
3267
|
+
const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3268
|
+
return path9.join(gitDir, "hooks");
|
|
3269
|
+
} catch {
|
|
3270
|
+
return null;
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
function getPreCommitPath() {
|
|
3274
|
+
const hooksDir = getGitHooksDir();
|
|
3275
|
+
return hooksDir ? path9.join(hooksDir, "pre-commit") : null;
|
|
3276
|
+
}
|
|
3277
|
+
function isPreCommitHookInstalled() {
|
|
3278
|
+
const hookPath = getPreCommitPath();
|
|
3279
|
+
if (!hookPath || !fs10.existsSync(hookPath)) return false;
|
|
3280
|
+
const content = fs10.readFileSync(hookPath, "utf-8");
|
|
3281
|
+
return content.includes(PRECOMMIT_START);
|
|
3282
|
+
}
|
|
3283
|
+
function installPreCommitHook() {
|
|
3284
|
+
if (isPreCommitHookInstalled()) {
|
|
3285
|
+
return { installed: false, alreadyInstalled: true };
|
|
3286
|
+
}
|
|
3287
|
+
const hookPath = getPreCommitPath();
|
|
3288
|
+
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3289
|
+
const hooksDir = path9.dirname(hookPath);
|
|
3290
|
+
if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
|
|
3291
|
+
let content = "";
|
|
3292
|
+
if (fs10.existsSync(hookPath)) {
|
|
3293
|
+
content = fs10.readFileSync(hookPath, "utf-8");
|
|
3294
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
3295
|
+
content += "\n" + getPrecommitBlock() + "\n";
|
|
3296
|
+
} else {
|
|
3297
|
+
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3298
|
+
}
|
|
3299
|
+
fs10.writeFileSync(hookPath, content);
|
|
3300
|
+
fs10.chmodSync(hookPath, 493);
|
|
3301
|
+
return { installed: true, alreadyInstalled: false };
|
|
3302
|
+
}
|
|
3303
|
+
function removePreCommitHook() {
|
|
3304
|
+
const hookPath = getPreCommitPath();
|
|
3305
|
+
if (!hookPath || !fs10.existsSync(hookPath)) {
|
|
3306
|
+
return { removed: false, notFound: true };
|
|
3307
|
+
}
|
|
3308
|
+
let content = fs10.readFileSync(hookPath, "utf-8");
|
|
3309
|
+
if (!content.includes(PRECOMMIT_START)) {
|
|
3310
|
+
return { removed: false, notFound: true };
|
|
3311
|
+
}
|
|
3312
|
+
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3313
|
+
content = content.replace(regex, "\n");
|
|
3314
|
+
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3315
|
+
fs10.unlinkSync(hookPath);
|
|
3316
|
+
} else {
|
|
3317
|
+
fs10.writeFileSync(hookPath, content);
|
|
3318
|
+
}
|
|
3319
|
+
return { removed: true, notFound: false };
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
// src/fingerprint/sources.ts
|
|
3323
|
+
import fs11 from "fs";
|
|
3324
|
+
import path10 from "path";
|
|
3179
3325
|
|
|
3180
3326
|
// src/scoring/utils.ts
|
|
3181
3327
|
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
@@ -3494,7 +3640,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
|
|
|
3494
3640
|
var README_CONTENT_LIMIT = 1e3;
|
|
3495
3641
|
var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
|
|
3496
3642
|
function loadSourcesConfig(dir) {
|
|
3497
|
-
const configPath =
|
|
3643
|
+
const configPath = path10.join(dir, ".caliber", "sources.json");
|
|
3498
3644
|
const content = readFileOrNull(configPath);
|
|
3499
3645
|
if (!content) return [];
|
|
3500
3646
|
try {
|
|
@@ -3512,29 +3658,29 @@ function loadSourcesConfig(dir) {
|
|
|
3512
3658
|
}
|
|
3513
3659
|
}
|
|
3514
3660
|
function writeSourcesConfig(dir, sources2) {
|
|
3515
|
-
const configDir =
|
|
3516
|
-
if (!
|
|
3517
|
-
|
|
3661
|
+
const configDir = path10.join(dir, ".caliber");
|
|
3662
|
+
if (!fs11.existsSync(configDir)) {
|
|
3663
|
+
fs11.mkdirSync(configDir, { recursive: true });
|
|
3518
3664
|
}
|
|
3519
|
-
const configPath =
|
|
3520
|
-
|
|
3665
|
+
const configPath = path10.join(configDir, "sources.json");
|
|
3666
|
+
fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
3521
3667
|
}
|
|
3522
3668
|
function detectSourceType(absPath) {
|
|
3523
3669
|
try {
|
|
3524
|
-
return
|
|
3670
|
+
return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
3525
3671
|
} catch {
|
|
3526
3672
|
return "file";
|
|
3527
3673
|
}
|
|
3528
3674
|
}
|
|
3529
3675
|
function isInsideDir(childPath, parentDir) {
|
|
3530
|
-
const relative2 =
|
|
3531
|
-
return !relative2.startsWith("..") && !
|
|
3676
|
+
const relative2 = path10.relative(parentDir, childPath);
|
|
3677
|
+
return !relative2.startsWith("..") && !path10.isAbsolute(relative2);
|
|
3532
3678
|
}
|
|
3533
3679
|
function resolveAllSources(dir, cliSources, workspaces) {
|
|
3534
3680
|
const seen = /* @__PURE__ */ new Map();
|
|
3535
|
-
const projectRoot =
|
|
3681
|
+
const projectRoot = path10.resolve(dir);
|
|
3536
3682
|
for (const src of cliSources) {
|
|
3537
|
-
const absPath =
|
|
3683
|
+
const absPath = path10.resolve(dir, src);
|
|
3538
3684
|
if (seen.has(absPath)) continue;
|
|
3539
3685
|
const type = detectSourceType(absPath);
|
|
3540
3686
|
seen.set(absPath, {
|
|
@@ -3547,12 +3693,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3547
3693
|
for (const cfg of configSources) {
|
|
3548
3694
|
if (cfg.type === "url") continue;
|
|
3549
3695
|
if (!cfg.path) continue;
|
|
3550
|
-
const absPath =
|
|
3696
|
+
const absPath = path10.resolve(dir, cfg.path);
|
|
3551
3697
|
if (seen.has(absPath)) continue;
|
|
3552
3698
|
seen.set(absPath, { absPath, config: cfg, origin: "config" });
|
|
3553
3699
|
}
|
|
3554
3700
|
for (const ws of workspaces) {
|
|
3555
|
-
const absPath =
|
|
3701
|
+
const absPath = path10.resolve(dir, ws);
|
|
3556
3702
|
if (seen.has(absPath)) continue;
|
|
3557
3703
|
if (!isInsideDir(absPath, projectRoot)) continue;
|
|
3558
3704
|
seen.set(absPath, {
|
|
@@ -3565,7 +3711,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3565
3711
|
for (const [absPath, resolved] of seen) {
|
|
3566
3712
|
let stat;
|
|
3567
3713
|
try {
|
|
3568
|
-
stat =
|
|
3714
|
+
stat = fs11.statSync(absPath);
|
|
3569
3715
|
} catch {
|
|
3570
3716
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
3571
3717
|
continue;
|
|
@@ -3594,13 +3740,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
3594
3740
|
if (config.type === "file") {
|
|
3595
3741
|
return collectFileSummary(resolved, projectDir);
|
|
3596
3742
|
}
|
|
3597
|
-
const summaryPath =
|
|
3743
|
+
const summaryPath = path10.join(absPath, ".caliber", "summary.json");
|
|
3598
3744
|
const summaryContent = readFileOrNull(summaryPath);
|
|
3599
3745
|
if (summaryContent) {
|
|
3600
3746
|
try {
|
|
3601
3747
|
const published = JSON.parse(summaryContent);
|
|
3602
3748
|
return {
|
|
3603
|
-
name: published.name ||
|
|
3749
|
+
name: published.name || path10.basename(absPath),
|
|
3604
3750
|
type: "repo",
|
|
3605
3751
|
role: config.role || published.role || "related-repo",
|
|
3606
3752
|
description: config.description || published.description || "",
|
|
@@ -3620,18 +3766,18 @@ function collectRepoSummary(resolved, projectDir) {
|
|
|
3620
3766
|
let topLevelDirs;
|
|
3621
3767
|
let keyFiles;
|
|
3622
3768
|
try {
|
|
3623
|
-
const entries =
|
|
3769
|
+
const entries = fs11.readdirSync(absPath, { withFileTypes: true });
|
|
3624
3770
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
3625
3771
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
3626
3772
|
} catch {
|
|
3627
3773
|
}
|
|
3628
|
-
const claudeMdContent = readFileOrNull(
|
|
3774
|
+
const claudeMdContent = readFileOrNull(path10.join(absPath, "CLAUDE.md"));
|
|
3629
3775
|
const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
|
|
3630
|
-
const readmeContent = readFileOrNull(
|
|
3776
|
+
const readmeContent = readFileOrNull(path10.join(absPath, "README.md"));
|
|
3631
3777
|
const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
|
|
3632
3778
|
const gitRemoteUrl = getGitRemoteUrl(absPath);
|
|
3633
3779
|
return {
|
|
3634
|
-
name: packageName ||
|
|
3780
|
+
name: packageName || path10.basename(absPath),
|
|
3635
3781
|
type: "repo",
|
|
3636
3782
|
role: config.role || "related-repo",
|
|
3637
3783
|
description: config.description || "",
|
|
@@ -3648,7 +3794,7 @@ function collectFileSummary(resolved, projectDir) {
|
|
|
3648
3794
|
const { config, origin, absPath } = resolved;
|
|
3649
3795
|
const content = readFileOrNull(absPath);
|
|
3650
3796
|
return {
|
|
3651
|
-
name:
|
|
3797
|
+
name: path10.basename(absPath),
|
|
3652
3798
|
type: "file",
|
|
3653
3799
|
role: config.role || "reference-doc",
|
|
3654
3800
|
description: config.description || content?.slice(0, 100).split("\n")[0] || "",
|
|
@@ -4287,39 +4433,52 @@ ${f.content}
|
|
|
4287
4433
|
}
|
|
4288
4434
|
|
|
4289
4435
|
// src/writers/index.ts
|
|
4290
|
-
import
|
|
4436
|
+
import fs19 from "fs";
|
|
4291
4437
|
|
|
4292
4438
|
// src/writers/claude/index.ts
|
|
4293
|
-
import
|
|
4294
|
-
import
|
|
4439
|
+
import fs12 from "fs";
|
|
4440
|
+
import path11 from "path";
|
|
4295
4441
|
|
|
4296
4442
|
// src/writers/pre-commit-block.ts
|
|
4297
4443
|
init_resolve_caliber();
|
|
4298
4444
|
var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
4299
4445
|
var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
4300
|
-
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
|
|
4446
|
+
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
4301
4447
|
function getPreCommitBlock() {
|
|
4302
4448
|
const bin = resolveCaliber();
|
|
4303
4449
|
return `${BLOCK_START}
|
|
4304
4450
|
## Before Committing
|
|
4305
4451
|
|
|
4306
|
-
|
|
4307
|
-
After it completes, stage any modified doc files before committing:
|
|
4452
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
4308
4453
|
|
|
4454
|
+
First, check if the pre-commit hook is already installed:
|
|
4309
4455
|
\`\`\`bash
|
|
4310
|
-
|
|
4456
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
4311
4457
|
\`\`\`
|
|
4458
|
+
|
|
4459
|
+
- If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
|
|
4460
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
4461
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
4462
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
4463
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
4464
|
+
|
|
4465
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
|
|
4312
4466
|
${BLOCK_END}`;
|
|
4313
4467
|
}
|
|
4314
4468
|
var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
4315
4469
|
function getCursorRuleContent() {
|
|
4316
4470
|
const bin = resolveCaliber();
|
|
4317
4471
|
return `---
|
|
4318
|
-
description: Run
|
|
4472
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
4319
4473
|
alwaysApply: true
|
|
4320
4474
|
---
|
|
4321
|
-
Before
|
|
4322
|
-
|
|
4475
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
4476
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
4477
|
+
|
|
4478
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
4479
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
4480
|
+
|
|
4481
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4323
4482
|
`;
|
|
4324
4483
|
}
|
|
4325
4484
|
function hasPreCommitBlock(content) {
|
|
@@ -4360,17 +4519,52 @@ function appendLearningsBlock(content) {
|
|
|
4360
4519
|
function getCursorLearningsRule() {
|
|
4361
4520
|
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
4362
4521
|
}
|
|
4522
|
+
var SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
4523
|
+
var SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
4524
|
+
function getSyncBlock() {
|
|
4525
|
+
const bin = resolveCaliber();
|
|
4526
|
+
return `${SYNC_BLOCK_START}
|
|
4527
|
+
## Context Sync
|
|
4528
|
+
|
|
4529
|
+
This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
4530
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
4531
|
+
If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
|
|
4532
|
+
${SYNC_BLOCK_END}`;
|
|
4533
|
+
}
|
|
4534
|
+
function hasSyncBlock(content) {
|
|
4535
|
+
return content.includes(SYNC_BLOCK_START);
|
|
4536
|
+
}
|
|
4537
|
+
function appendSyncBlock(content) {
|
|
4538
|
+
if (hasSyncBlock(content)) return content;
|
|
4539
|
+
const trimmed = content.trimEnd();
|
|
4540
|
+
return trimmed + "\n\n" + getSyncBlock() + "\n";
|
|
4541
|
+
}
|
|
4542
|
+
var CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
4543
|
+
function getCursorSyncContent() {
|
|
4544
|
+
const bin = resolveCaliber();
|
|
4545
|
+
return `---
|
|
4546
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
4547
|
+
alwaysApply: true
|
|
4548
|
+
---
|
|
4549
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
4550
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
4551
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4552
|
+
`;
|
|
4553
|
+
}
|
|
4554
|
+
function getCursorSyncRule() {
|
|
4555
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
4556
|
+
}
|
|
4363
4557
|
|
|
4364
4558
|
// src/writers/claude/index.ts
|
|
4365
4559
|
function writeClaudeConfig(config) {
|
|
4366
4560
|
const written = [];
|
|
4367
|
-
|
|
4561
|
+
fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
|
|
4368
4562
|
written.push("CLAUDE.md");
|
|
4369
4563
|
if (config.skills?.length) {
|
|
4370
4564
|
for (const skill of config.skills) {
|
|
4371
|
-
const skillDir =
|
|
4372
|
-
if (!
|
|
4373
|
-
const skillPath =
|
|
4565
|
+
const skillDir = path11.join(".claude", "skills", skill.name);
|
|
4566
|
+
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
4567
|
+
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
4374
4568
|
const frontmatter = [
|
|
4375
4569
|
"---",
|
|
4376
4570
|
`name: ${skill.name}`,
|
|
@@ -4378,50 +4572,51 @@ function writeClaudeConfig(config) {
|
|
|
4378
4572
|
"---",
|
|
4379
4573
|
""
|
|
4380
4574
|
].join("\n");
|
|
4381
|
-
|
|
4575
|
+
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4382
4576
|
written.push(skillPath);
|
|
4383
4577
|
}
|
|
4384
4578
|
}
|
|
4385
4579
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4386
4580
|
let existingServers = {};
|
|
4387
4581
|
try {
|
|
4388
|
-
if (
|
|
4389
|
-
const existing = JSON.parse(
|
|
4582
|
+
if (fs12.existsSync(".mcp.json")) {
|
|
4583
|
+
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
4390
4584
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4391
4585
|
}
|
|
4392
4586
|
} catch {
|
|
4393
4587
|
}
|
|
4394
4588
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4395
|
-
|
|
4589
|
+
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4396
4590
|
written.push(".mcp.json");
|
|
4397
4591
|
}
|
|
4398
4592
|
return written;
|
|
4399
4593
|
}
|
|
4400
4594
|
|
|
4401
4595
|
// src/writers/cursor/index.ts
|
|
4402
|
-
import
|
|
4403
|
-
import
|
|
4596
|
+
import fs13 from "fs";
|
|
4597
|
+
import path12 from "path";
|
|
4404
4598
|
function writeCursorConfig(config) {
|
|
4405
4599
|
const written = [];
|
|
4406
4600
|
if (config.cursorrules) {
|
|
4407
|
-
|
|
4601
|
+
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
4408
4602
|
written.push(".cursorrules");
|
|
4409
4603
|
}
|
|
4410
4604
|
const preCommitRule = getCursorPreCommitRule();
|
|
4411
4605
|
const learningsRule = getCursorLearningsRule();
|
|
4412
|
-
const
|
|
4413
|
-
const
|
|
4414
|
-
|
|
4606
|
+
const syncRule = getCursorSyncRule();
|
|
4607
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
|
|
4608
|
+
const rulesDir = path12.join(".cursor", "rules");
|
|
4609
|
+
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
4415
4610
|
for (const rule of allRules) {
|
|
4416
|
-
const rulePath =
|
|
4417
|
-
|
|
4611
|
+
const rulePath = path12.join(rulesDir, rule.filename);
|
|
4612
|
+
fs13.writeFileSync(rulePath, rule.content);
|
|
4418
4613
|
written.push(rulePath);
|
|
4419
4614
|
}
|
|
4420
4615
|
if (config.skills?.length) {
|
|
4421
4616
|
for (const skill of config.skills) {
|
|
4422
|
-
const skillDir =
|
|
4423
|
-
if (!
|
|
4424
|
-
const skillPath =
|
|
4617
|
+
const skillDir = path12.join(".cursor", "skills", skill.name);
|
|
4618
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
4619
|
+
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4425
4620
|
const frontmatter = [
|
|
4426
4621
|
"---",
|
|
4427
4622
|
`name: ${skill.name}`,
|
|
@@ -4429,41 +4624,41 @@ function writeCursorConfig(config) {
|
|
|
4429
4624
|
"---",
|
|
4430
4625
|
""
|
|
4431
4626
|
].join("\n");
|
|
4432
|
-
|
|
4627
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4433
4628
|
written.push(skillPath);
|
|
4434
4629
|
}
|
|
4435
4630
|
}
|
|
4436
4631
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4437
4632
|
const cursorDir = ".cursor";
|
|
4438
|
-
if (!
|
|
4439
|
-
const mcpPath =
|
|
4633
|
+
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
4634
|
+
const mcpPath = path12.join(cursorDir, "mcp.json");
|
|
4440
4635
|
let existingServers = {};
|
|
4441
4636
|
try {
|
|
4442
|
-
if (
|
|
4443
|
-
const existing = JSON.parse(
|
|
4637
|
+
if (fs13.existsSync(mcpPath)) {
|
|
4638
|
+
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
4444
4639
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4445
4640
|
}
|
|
4446
4641
|
} catch {
|
|
4447
4642
|
}
|
|
4448
4643
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4449
|
-
|
|
4644
|
+
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4450
4645
|
written.push(mcpPath);
|
|
4451
4646
|
}
|
|
4452
4647
|
return written;
|
|
4453
4648
|
}
|
|
4454
4649
|
|
|
4455
4650
|
// src/writers/codex/index.ts
|
|
4456
|
-
import
|
|
4457
|
-
import
|
|
4651
|
+
import fs14 from "fs";
|
|
4652
|
+
import path13 from "path";
|
|
4458
4653
|
function writeCodexConfig(config) {
|
|
4459
4654
|
const written = [];
|
|
4460
|
-
|
|
4655
|
+
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4461
4656
|
written.push("AGENTS.md");
|
|
4462
4657
|
if (config.skills?.length) {
|
|
4463
4658
|
for (const skill of config.skills) {
|
|
4464
|
-
const skillDir =
|
|
4465
|
-
if (!
|
|
4466
|
-
const skillPath =
|
|
4659
|
+
const skillDir = path13.join(".agents", "skills", skill.name);
|
|
4660
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
4661
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
4467
4662
|
const frontmatter = [
|
|
4468
4663
|
"---",
|
|
4469
4664
|
`name: ${skill.name}`,
|
|
@@ -4471,7 +4666,7 @@ function writeCodexConfig(config) {
|
|
|
4471
4666
|
"---",
|
|
4472
4667
|
""
|
|
4473
4668
|
].join("\n");
|
|
4474
|
-
|
|
4669
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4475
4670
|
written.push(skillPath);
|
|
4476
4671
|
}
|
|
4477
4672
|
}
|
|
@@ -4479,20 +4674,20 @@ function writeCodexConfig(config) {
|
|
|
4479
4674
|
}
|
|
4480
4675
|
|
|
4481
4676
|
// src/writers/github-copilot/index.ts
|
|
4482
|
-
import
|
|
4483
|
-
import
|
|
4677
|
+
import fs15 from "fs";
|
|
4678
|
+
import path14 from "path";
|
|
4484
4679
|
function writeGithubCopilotConfig(config) {
|
|
4485
4680
|
const written = [];
|
|
4486
4681
|
if (config.instructions) {
|
|
4487
|
-
|
|
4488
|
-
|
|
4682
|
+
fs15.mkdirSync(".github", { recursive: true });
|
|
4683
|
+
fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
|
|
4489
4684
|
written.push(".github/copilot-instructions.md");
|
|
4490
4685
|
}
|
|
4491
4686
|
if (config.instructionFiles?.length) {
|
|
4492
|
-
const instructionsDir =
|
|
4493
|
-
|
|
4687
|
+
const instructionsDir = path14.join(".github", "instructions");
|
|
4688
|
+
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
4494
4689
|
for (const file of config.instructionFiles) {
|
|
4495
|
-
|
|
4690
|
+
fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
|
|
4496
4691
|
written.push(`.github/instructions/${file.filename}`);
|
|
4497
4692
|
}
|
|
4498
4693
|
}
|
|
@@ -4500,37 +4695,37 @@ function writeGithubCopilotConfig(config) {
|
|
|
4500
4695
|
}
|
|
4501
4696
|
|
|
4502
4697
|
// src/writers/backup.ts
|
|
4503
|
-
import
|
|
4504
|
-
import
|
|
4698
|
+
import fs16 from "fs";
|
|
4699
|
+
import path15 from "path";
|
|
4505
4700
|
function createBackup(files) {
|
|
4506
4701
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4507
|
-
const backupDir =
|
|
4702
|
+
const backupDir = path15.join(BACKUPS_DIR, timestamp);
|
|
4508
4703
|
for (const file of files) {
|
|
4509
|
-
if (!
|
|
4510
|
-
const dest =
|
|
4511
|
-
const destDir =
|
|
4512
|
-
if (!
|
|
4513
|
-
|
|
4704
|
+
if (!fs16.existsSync(file)) continue;
|
|
4705
|
+
const dest = path15.join(backupDir, file);
|
|
4706
|
+
const destDir = path15.dirname(dest);
|
|
4707
|
+
if (!fs16.existsSync(destDir)) {
|
|
4708
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
4514
4709
|
}
|
|
4515
|
-
|
|
4710
|
+
fs16.copyFileSync(file, dest);
|
|
4516
4711
|
}
|
|
4517
4712
|
return backupDir;
|
|
4518
4713
|
}
|
|
4519
4714
|
function restoreBackup(backupDir, file) {
|
|
4520
|
-
const backupFile =
|
|
4521
|
-
if (!
|
|
4522
|
-
const destDir =
|
|
4523
|
-
if (!
|
|
4524
|
-
|
|
4715
|
+
const backupFile = path15.join(backupDir, file);
|
|
4716
|
+
if (!fs16.existsSync(backupFile)) return false;
|
|
4717
|
+
const destDir = path15.dirname(file);
|
|
4718
|
+
if (!fs16.existsSync(destDir)) {
|
|
4719
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
4525
4720
|
}
|
|
4526
|
-
|
|
4721
|
+
fs16.copyFileSync(backupFile, file);
|
|
4527
4722
|
return true;
|
|
4528
4723
|
}
|
|
4529
4724
|
|
|
4530
4725
|
// src/lib/builtin-skills.ts
|
|
4531
4726
|
init_resolve_caliber();
|
|
4532
|
-
import
|
|
4533
|
-
import
|
|
4727
|
+
import fs17 from "fs";
|
|
4728
|
+
import path16 from "path";
|
|
4534
4729
|
function buildSkillContent(skill) {
|
|
4535
4730
|
const frontmatter = `---
|
|
4536
4731
|
name: ${skill.name}
|
|
@@ -4666,21 +4861,128 @@ var SAVE_LEARNING_SKILL = {
|
|
|
4666
4861
|
return getSaveLearningContent();
|
|
4667
4862
|
}
|
|
4668
4863
|
};
|
|
4669
|
-
|
|
4864
|
+
function getSetupCaliberContent() {
|
|
4865
|
+
const bin = resolveCaliber();
|
|
4866
|
+
return `# Setup Caliber
|
|
4867
|
+
|
|
4868
|
+
Set up Caliber for automatic AI agent context sync on this project.
|
|
4869
|
+
Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
|
|
4870
|
+
in sync with your codebase \u2014 automatically on every commit.
|
|
4871
|
+
|
|
4872
|
+
## Instructions
|
|
4873
|
+
|
|
4874
|
+
1. Check if Caliber is already set up locally:
|
|
4875
|
+
\`\`\`bash
|
|
4876
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
4877
|
+
\`\`\`
|
|
4878
|
+
|
|
4879
|
+
2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
|
|
4880
|
+
Your agent configs update automatically on every commit." Then stop.
|
|
4881
|
+
|
|
4882
|
+
3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
|
|
4883
|
+
or for your team?"
|
|
4884
|
+
|
|
4885
|
+
### Solo Setup
|
|
4886
|
+
|
|
4887
|
+
a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
|
|
4888
|
+
on every commit."
|
|
4889
|
+
b. Install the pre-commit hook:
|
|
4890
|
+
\`\`\`bash
|
|
4891
|
+
${bin} hooks --install
|
|
4892
|
+
\`\`\`
|
|
4893
|
+
c. Run a refresh to make sure configs are current:
|
|
4894
|
+
\`\`\`bash
|
|
4895
|
+
${bin} refresh
|
|
4896
|
+
\`\`\`
|
|
4897
|
+
d. Tell the user what happened and that configs will auto-sync on every commit.
|
|
4898
|
+
|
|
4899
|
+
### Team Setup
|
|
4900
|
+
|
|
4901
|
+
a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
|
|
4902
|
+
plus a GitHub Action for automatic sync across the team."
|
|
4903
|
+
b. Install the local pre-commit hook:
|
|
4904
|
+
\`\`\`bash
|
|
4905
|
+
${bin} hooks --install
|
|
4906
|
+
\`\`\`
|
|
4907
|
+
c. Run a refresh:
|
|
4908
|
+
\`\`\`bash
|
|
4909
|
+
${bin} refresh
|
|
4910
|
+
\`\`\`
|
|
4911
|
+
d. Create the GitHub Action workflow for nightly sync. Write this file to
|
|
4912
|
+
\`.github/workflows/caliber-sync.yml\`:
|
|
4913
|
+
\`\`\`yaml
|
|
4914
|
+
name: Caliber Sync
|
|
4915
|
+
on:
|
|
4916
|
+
schedule:
|
|
4917
|
+
- cron: '0 3 * * 1-5'
|
|
4918
|
+
pull_request:
|
|
4919
|
+
types: [opened, synchronize]
|
|
4920
|
+
workflow_dispatch:
|
|
4921
|
+
jobs:
|
|
4922
|
+
sync:
|
|
4923
|
+
runs-on: ubuntu-latest
|
|
4924
|
+
steps:
|
|
4925
|
+
- uses: actions/checkout@v4
|
|
4926
|
+
- uses: caliber-ai-org/ai-setup@v1
|
|
4927
|
+
with:
|
|
4928
|
+
mode: sync
|
|
4929
|
+
auto-refresh: true
|
|
4930
|
+
comment: true
|
|
4931
|
+
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
4932
|
+
env:
|
|
4933
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
4934
|
+
\`\`\`
|
|
4935
|
+
e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
|
|
4936
|
+
to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
|
|
4937
|
+
workflow file. From now on, every PR gets a Caliber sync check and configs
|
|
4938
|
+
refresh nightly."
|
|
4939
|
+
f. Offer to commit the workflow file and the updated configs.
|
|
4940
|
+
|
|
4941
|
+
### If \`${bin}\` is not found
|
|
4942
|
+
|
|
4943
|
+
Install it first:
|
|
4944
|
+
\`\`\`bash
|
|
4945
|
+
npm install -g @rely-ai/caliber
|
|
4946
|
+
\`\`\`
|
|
4947
|
+
Or use npx (no install needed):
|
|
4948
|
+
\`\`\`bash
|
|
4949
|
+
npx @rely-ai/caliber hooks --install
|
|
4950
|
+
\`\`\`
|
|
4951
|
+
|
|
4952
|
+
## When to trigger
|
|
4953
|
+
|
|
4954
|
+
- When a developer first opens this project and Caliber hooks are not installed
|
|
4955
|
+
- When the user asks about keeping agent configs up to date
|
|
4956
|
+
- When the user asks "how do I set up Caliber" or "what is Caliber"
|
|
4957
|
+
|
|
4958
|
+
## When NOT to trigger
|
|
4959
|
+
|
|
4960
|
+
- Hooks are already installed and working
|
|
4961
|
+
- The user is in the middle of time-sensitive work
|
|
4962
|
+
`;
|
|
4963
|
+
}
|
|
4964
|
+
var SETUP_CALIBER_SKILL = {
|
|
4965
|
+
name: "setup-caliber",
|
|
4966
|
+
description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
|
|
4967
|
+
get content() {
|
|
4968
|
+
return getSetupCaliberContent();
|
|
4969
|
+
}
|
|
4970
|
+
};
|
|
4971
|
+
var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
4670
4972
|
var PLATFORM_CONFIGS = [
|
|
4671
|
-
{ platformDir: ".claude", skillsDir:
|
|
4672
|
-
{ platformDir: ".cursor", skillsDir:
|
|
4673
|
-
{ platformDir: ".agents", skillsDir:
|
|
4973
|
+
{ platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
|
|
4974
|
+
{ platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
|
|
4975
|
+
{ platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
|
|
4674
4976
|
];
|
|
4675
4977
|
function ensureBuiltinSkills() {
|
|
4676
4978
|
const written = [];
|
|
4677
4979
|
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
4678
|
-
if (!
|
|
4980
|
+
if (!fs17.existsSync(platformDir)) continue;
|
|
4679
4981
|
for (const skill of BUILTIN_SKILLS) {
|
|
4680
|
-
const skillPath =
|
|
4681
|
-
if (
|
|
4682
|
-
|
|
4683
|
-
|
|
4982
|
+
const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
|
|
4983
|
+
if (fs17.existsSync(skillPath)) continue;
|
|
4984
|
+
fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
|
|
4985
|
+
fs17.writeFileSync(skillPath, buildSkillContent(skill));
|
|
4684
4986
|
written.push(skillPath);
|
|
4685
4987
|
}
|
|
4686
4988
|
}
|
|
@@ -4688,33 +4990,33 @@ function ensureBuiltinSkills() {
|
|
|
4688
4990
|
}
|
|
4689
4991
|
|
|
4690
4992
|
// src/writers/manifest.ts
|
|
4691
|
-
import
|
|
4993
|
+
import fs18 from "fs";
|
|
4692
4994
|
import crypto3 from "crypto";
|
|
4693
4995
|
function readManifest() {
|
|
4694
4996
|
try {
|
|
4695
|
-
if (!
|
|
4696
|
-
return JSON.parse(
|
|
4997
|
+
if (!fs18.existsSync(MANIFEST_FILE)) return null;
|
|
4998
|
+
return JSON.parse(fs18.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
4697
4999
|
} catch {
|
|
4698
5000
|
return null;
|
|
4699
5001
|
}
|
|
4700
5002
|
}
|
|
4701
5003
|
function writeManifest(manifest) {
|
|
4702
|
-
if (!
|
|
4703
|
-
|
|
5004
|
+
if (!fs18.existsSync(CALIBER_DIR)) {
|
|
5005
|
+
fs18.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
4704
5006
|
}
|
|
4705
|
-
|
|
5007
|
+
fs18.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
4706
5008
|
}
|
|
4707
5009
|
function fileChecksum(filePath) {
|
|
4708
|
-
const content =
|
|
5010
|
+
const content = fs18.readFileSync(filePath);
|
|
4709
5011
|
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
4710
5012
|
}
|
|
4711
5013
|
|
|
4712
5014
|
// src/writers/index.ts
|
|
4713
5015
|
function writeSetup(setup) {
|
|
4714
5016
|
const filesToWrite = getFilesToWrite(setup);
|
|
4715
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
5017
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
|
|
4716
5018
|
const existingFiles = [
|
|
4717
|
-
...filesToWrite.filter((f) =>
|
|
5019
|
+
...filesToWrite.filter((f) => fs19.existsSync(f)),
|
|
4718
5020
|
...filesToDelete
|
|
4719
5021
|
];
|
|
4720
5022
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
@@ -4733,7 +5035,7 @@ function writeSetup(setup) {
|
|
|
4733
5035
|
}
|
|
4734
5036
|
const deleted = [];
|
|
4735
5037
|
for (const filePath of filesToDelete) {
|
|
4736
|
-
|
|
5038
|
+
fs19.unlinkSync(filePath);
|
|
4737
5039
|
deleted.push(filePath);
|
|
4738
5040
|
}
|
|
4739
5041
|
written.push(...ensureBuiltinSkills());
|
|
@@ -4764,8 +5066,8 @@ function undoSetup() {
|
|
|
4764
5066
|
const removed = [];
|
|
4765
5067
|
for (const entry of manifest.entries) {
|
|
4766
5068
|
if (entry.action === "created") {
|
|
4767
|
-
if (
|
|
4768
|
-
|
|
5069
|
+
if (fs19.existsSync(entry.path)) {
|
|
5070
|
+
fs19.unlinkSync(entry.path);
|
|
4769
5071
|
removed.push(entry.path);
|
|
4770
5072
|
}
|
|
4771
5073
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -4774,8 +5076,8 @@ function undoSetup() {
|
|
|
4774
5076
|
}
|
|
4775
5077
|
}
|
|
4776
5078
|
}
|
|
4777
|
-
if (
|
|
4778
|
-
|
|
5079
|
+
if (fs19.existsSync(MANIFEST_FILE)) {
|
|
5080
|
+
fs19.unlinkSync(MANIFEST_FILE);
|
|
4779
5081
|
}
|
|
4780
5082
|
return { restored, removed };
|
|
4781
5083
|
}
|
|
@@ -4816,22 +5118,22 @@ function getFilesToWrite(setup) {
|
|
|
4816
5118
|
}
|
|
4817
5119
|
function ensureGitignore() {
|
|
4818
5120
|
const gitignorePath = ".gitignore";
|
|
4819
|
-
if (
|
|
4820
|
-
const content =
|
|
5121
|
+
if (fs19.existsSync(gitignorePath)) {
|
|
5122
|
+
const content = fs19.readFileSync(gitignorePath, "utf-8");
|
|
4821
5123
|
if (!content.includes(".caliber/")) {
|
|
4822
|
-
|
|
5124
|
+
fs19.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
4823
5125
|
}
|
|
4824
5126
|
} else {
|
|
4825
|
-
|
|
5127
|
+
fs19.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
4826
5128
|
}
|
|
4827
5129
|
}
|
|
4828
5130
|
|
|
4829
5131
|
// src/writers/staging.ts
|
|
4830
|
-
import
|
|
4831
|
-
import
|
|
4832
|
-
var STAGED_DIR =
|
|
4833
|
-
var PROPOSED_DIR =
|
|
4834
|
-
var CURRENT_DIR =
|
|
5132
|
+
import fs20 from "fs";
|
|
5133
|
+
import path17 from "path";
|
|
5134
|
+
var STAGED_DIR = path17.join(CALIBER_DIR, "staged");
|
|
5135
|
+
var PROPOSED_DIR = path17.join(STAGED_DIR, "proposed");
|
|
5136
|
+
var CURRENT_DIR = path17.join(STAGED_DIR, "current");
|
|
4835
5137
|
function normalizeContent(content) {
|
|
4836
5138
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4837
5139
|
}
|
|
@@ -4841,20 +5143,20 @@ function stageFiles(files, projectDir) {
|
|
|
4841
5143
|
let modifiedFiles = 0;
|
|
4842
5144
|
const stagedFiles = [];
|
|
4843
5145
|
for (const file of files) {
|
|
4844
|
-
const originalPath =
|
|
4845
|
-
if (
|
|
4846
|
-
const existing =
|
|
5146
|
+
const originalPath = path17.join(projectDir, file.path);
|
|
5147
|
+
if (fs20.existsSync(originalPath)) {
|
|
5148
|
+
const existing = fs20.readFileSync(originalPath, "utf-8");
|
|
4847
5149
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
4848
5150
|
continue;
|
|
4849
5151
|
}
|
|
4850
5152
|
}
|
|
4851
|
-
const proposedPath =
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
if (
|
|
4855
|
-
const currentPath =
|
|
4856
|
-
|
|
4857
|
-
|
|
5153
|
+
const proposedPath = path17.join(PROPOSED_DIR, file.path);
|
|
5154
|
+
fs20.mkdirSync(path17.dirname(proposedPath), { recursive: true });
|
|
5155
|
+
fs20.writeFileSync(proposedPath, file.content);
|
|
5156
|
+
if (fs20.existsSync(originalPath)) {
|
|
5157
|
+
const currentPath = path17.join(CURRENT_DIR, file.path);
|
|
5158
|
+
fs20.mkdirSync(path17.dirname(currentPath), { recursive: true });
|
|
5159
|
+
fs20.copyFileSync(originalPath, currentPath);
|
|
4858
5160
|
modifiedFiles++;
|
|
4859
5161
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
4860
5162
|
} else {
|
|
@@ -4865,13 +5167,13 @@ function stageFiles(files, projectDir) {
|
|
|
4865
5167
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
4866
5168
|
}
|
|
4867
5169
|
function cleanupStaging() {
|
|
4868
|
-
if (
|
|
4869
|
-
|
|
5170
|
+
if (fs20.existsSync(STAGED_DIR)) {
|
|
5171
|
+
fs20.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
4870
5172
|
}
|
|
4871
5173
|
}
|
|
4872
5174
|
|
|
4873
5175
|
// src/commands/setup-files.ts
|
|
4874
|
-
import
|
|
5176
|
+
import fs21 from "fs";
|
|
4875
5177
|
function collectSetupFiles(setup, targetAgent) {
|
|
4876
5178
|
const files = [];
|
|
4877
5179
|
const claude = setup.claude;
|
|
@@ -4930,7 +5232,7 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
4930
5232
|
}
|
|
4931
5233
|
}
|
|
4932
5234
|
const codexTargeted = targetAgent ? targetAgent.includes("codex") : false;
|
|
4933
|
-
if (codexTargeted && !
|
|
5235
|
+
if (codexTargeted && !fs21.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
|
|
4934
5236
|
const agentRefs = [];
|
|
4935
5237
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
4936
5238
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -4948,9 +5250,9 @@ ${agentRefs.join(" ")}
|
|
|
4948
5250
|
|
|
4949
5251
|
// src/lib/learning-hooks.ts
|
|
4950
5252
|
init_resolve_caliber();
|
|
4951
|
-
import
|
|
4952
|
-
import
|
|
4953
|
-
var
|
|
5253
|
+
import fs22 from "fs";
|
|
5254
|
+
import path18 from "path";
|
|
5255
|
+
var SETTINGS_PATH2 = path18.join(".claude", "settings.json");
|
|
4954
5256
|
var HOOK_TAILS = [
|
|
4955
5257
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
4956
5258
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
@@ -4966,24 +5268,24 @@ function getHookConfigs() {
|
|
|
4966
5268
|
description
|
|
4967
5269
|
}));
|
|
4968
5270
|
}
|
|
4969
|
-
function
|
|
4970
|
-
if (!
|
|
5271
|
+
function readSettings2() {
|
|
5272
|
+
if (!fs22.existsSync(SETTINGS_PATH2)) return {};
|
|
4971
5273
|
try {
|
|
4972
|
-
return JSON.parse(
|
|
5274
|
+
return JSON.parse(fs22.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
4973
5275
|
} catch {
|
|
4974
5276
|
return {};
|
|
4975
5277
|
}
|
|
4976
5278
|
}
|
|
4977
|
-
function
|
|
4978
|
-
const dir =
|
|
4979
|
-
if (!
|
|
4980
|
-
|
|
5279
|
+
function writeSettings2(settings) {
|
|
5280
|
+
const dir = path18.dirname(SETTINGS_PATH2);
|
|
5281
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5282
|
+
fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
4981
5283
|
}
|
|
4982
5284
|
function hasLearningHook(matchers, tail) {
|
|
4983
5285
|
return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
|
|
4984
5286
|
}
|
|
4985
5287
|
function areLearningHooksInstalled() {
|
|
4986
|
-
const settings =
|
|
5288
|
+
const settings = readSettings2();
|
|
4987
5289
|
if (!settings.hooks) return false;
|
|
4988
5290
|
return HOOK_TAILS.every((cfg) => {
|
|
4989
5291
|
const matchers = settings.hooks[cfg.event];
|
|
@@ -4994,7 +5296,7 @@ function installLearningHooks() {
|
|
|
4994
5296
|
if (areLearningHooksInstalled()) {
|
|
4995
5297
|
return { installed: false, alreadyInstalled: true };
|
|
4996
5298
|
}
|
|
4997
|
-
const settings =
|
|
5299
|
+
const settings = readSettings2();
|
|
4998
5300
|
if (!settings.hooks) settings.hooks = {};
|
|
4999
5301
|
const configs = getHookConfigs();
|
|
5000
5302
|
for (const cfg of configs) {
|
|
@@ -5008,10 +5310,10 @@ function installLearningHooks() {
|
|
|
5008
5310
|
});
|
|
5009
5311
|
}
|
|
5010
5312
|
}
|
|
5011
|
-
|
|
5313
|
+
writeSettings2(settings);
|
|
5012
5314
|
return { installed: true, alreadyInstalled: false };
|
|
5013
5315
|
}
|
|
5014
|
-
var CURSOR_HOOKS_PATH =
|
|
5316
|
+
var CURSOR_HOOKS_PATH = path18.join(".cursor", "hooks.json");
|
|
5015
5317
|
var CURSOR_HOOK_EVENTS = [
|
|
5016
5318
|
{ event: "postToolUse", tail: "learn observe" },
|
|
5017
5319
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
@@ -5019,17 +5321,17 @@ var CURSOR_HOOK_EVENTS = [
|
|
|
5019
5321
|
{ event: "sessionEnd", tail: "learn finalize --auto" }
|
|
5020
5322
|
];
|
|
5021
5323
|
function readCursorHooks() {
|
|
5022
|
-
if (!
|
|
5324
|
+
if (!fs22.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
5023
5325
|
try {
|
|
5024
|
-
return JSON.parse(
|
|
5326
|
+
return JSON.parse(fs22.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
|
|
5025
5327
|
} catch {
|
|
5026
5328
|
return { version: 1, hooks: {} };
|
|
5027
5329
|
}
|
|
5028
5330
|
}
|
|
5029
5331
|
function writeCursorHooks(config) {
|
|
5030
|
-
const dir =
|
|
5031
|
-
if (!
|
|
5032
|
-
|
|
5332
|
+
const dir = path18.dirname(CURSOR_HOOKS_PATH);
|
|
5333
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5334
|
+
fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
5033
5335
|
}
|
|
5034
5336
|
function hasCursorHook(entries, tail) {
|
|
5035
5337
|
return entries.some((e) => isCaliberCommand(e.command, tail));
|
|
@@ -5076,7 +5378,7 @@ function removeCursorLearningHooks() {
|
|
|
5076
5378
|
return { removed: true, notFound: false };
|
|
5077
5379
|
}
|
|
5078
5380
|
function removeLearningHooks() {
|
|
5079
|
-
const settings =
|
|
5381
|
+
const settings = readSettings2();
|
|
5080
5382
|
if (!settings.hooks) return { removed: false, notFound: true };
|
|
5081
5383
|
let removedAny = false;
|
|
5082
5384
|
for (const cfg of HOOK_TAILS) {
|
|
@@ -5093,7 +5395,7 @@ function removeLearningHooks() {
|
|
|
5093
5395
|
delete settings.hooks;
|
|
5094
5396
|
}
|
|
5095
5397
|
if (!removedAny) return { removed: false, notFound: true };
|
|
5096
|
-
|
|
5398
|
+
writeSettings2(settings);
|
|
5097
5399
|
return { removed: true, notFound: false };
|
|
5098
5400
|
}
|
|
5099
5401
|
|
|
@@ -5101,10 +5403,10 @@ function removeLearningHooks() {
|
|
|
5101
5403
|
init_resolve_caliber();
|
|
5102
5404
|
|
|
5103
5405
|
// src/lib/state.ts
|
|
5104
|
-
import
|
|
5105
|
-
import
|
|
5106
|
-
import { execSync as
|
|
5107
|
-
var STATE_FILE =
|
|
5406
|
+
import fs23 from "fs";
|
|
5407
|
+
import path19 from "path";
|
|
5408
|
+
import { execSync as execSync9 } from "child_process";
|
|
5409
|
+
var STATE_FILE = path19.join(CALIBER_DIR, ".caliber-state.json");
|
|
5108
5410
|
function normalizeTargetAgent(value) {
|
|
5109
5411
|
if (Array.isArray(value)) return value;
|
|
5110
5412
|
if (typeof value === "string") {
|
|
@@ -5115,8 +5417,8 @@ function normalizeTargetAgent(value) {
|
|
|
5115
5417
|
}
|
|
5116
5418
|
function readState() {
|
|
5117
5419
|
try {
|
|
5118
|
-
if (!
|
|
5119
|
-
const raw = JSON.parse(
|
|
5420
|
+
if (!fs23.existsSync(STATE_FILE)) return null;
|
|
5421
|
+
const raw = JSON.parse(fs23.readFileSync(STATE_FILE, "utf-8"));
|
|
5120
5422
|
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
5121
5423
|
return raw;
|
|
5122
5424
|
} catch {
|
|
@@ -5124,14 +5426,14 @@ function readState() {
|
|
|
5124
5426
|
}
|
|
5125
5427
|
}
|
|
5126
5428
|
function writeState(state) {
|
|
5127
|
-
if (!
|
|
5128
|
-
|
|
5429
|
+
if (!fs23.existsSync(CALIBER_DIR)) {
|
|
5430
|
+
fs23.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
5129
5431
|
}
|
|
5130
|
-
|
|
5432
|
+
fs23.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
5131
5433
|
}
|
|
5132
5434
|
function getCurrentHeadSha() {
|
|
5133
5435
|
try {
|
|
5134
|
-
return
|
|
5436
|
+
return execSync9("git rev-parse HEAD", {
|
|
5135
5437
|
encoding: "utf-8",
|
|
5136
5438
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5137
5439
|
}).trim();
|
|
@@ -5278,11 +5580,11 @@ var POINTS_LEARNED_CONTENT = 2;
|
|
|
5278
5580
|
var POINTS_SOURCES_CONFIGURED = 3;
|
|
5279
5581
|
var POINTS_SOURCES_REFERENCED = 3;
|
|
5280
5582
|
var TOKEN_BUDGET_THRESHOLDS = [
|
|
5281
|
-
{ maxTokens:
|
|
5282
|
-
{ maxTokens:
|
|
5283
|
-
{ maxTokens:
|
|
5284
|
-
{ maxTokens:
|
|
5285
|
-
{ maxTokens:
|
|
5583
|
+
{ maxTokens: 5e3, points: 6 },
|
|
5584
|
+
{ maxTokens: 8e3, points: 5 },
|
|
5585
|
+
{ maxTokens: 12e3, points: 4 },
|
|
5586
|
+
{ maxTokens: 16e3, points: 2 },
|
|
5587
|
+
{ maxTokens: 24e3, points: 1 }
|
|
5286
5588
|
];
|
|
5287
5589
|
var CODE_BLOCK_THRESHOLDS = [
|
|
5288
5590
|
{ minBlocks: 3, points: 8 },
|
|
@@ -5744,7 +6046,7 @@ function checkGrounding(dir) {
|
|
|
5744
6046
|
|
|
5745
6047
|
// src/scoring/checks/accuracy.ts
|
|
5746
6048
|
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
5747
|
-
import { execSync as
|
|
6049
|
+
import { execSync as execSync10 } from "child_process";
|
|
5748
6050
|
import { join as join5 } from "path";
|
|
5749
6051
|
init_resolve_caliber();
|
|
5750
6052
|
function validateReferences(dir) {
|
|
@@ -5754,13 +6056,13 @@ function validateReferences(dir) {
|
|
|
5754
6056
|
}
|
|
5755
6057
|
function detectGitDrift(dir) {
|
|
5756
6058
|
try {
|
|
5757
|
-
|
|
6059
|
+
execSync10("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
|
|
5758
6060
|
} catch {
|
|
5759
6061
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
|
|
5760
6062
|
}
|
|
5761
6063
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
|
|
5762
6064
|
try {
|
|
5763
|
-
const headTimestamp =
|
|
6065
|
+
const headTimestamp = execSync10(
|
|
5764
6066
|
"git log -1 --format=%ct HEAD",
|
|
5765
6067
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5766
6068
|
).trim();
|
|
@@ -5781,7 +6083,7 @@ function detectGitDrift(dir) {
|
|
|
5781
6083
|
let latestConfigCommitHash = null;
|
|
5782
6084
|
for (const file of configFiles) {
|
|
5783
6085
|
try {
|
|
5784
|
-
const hash =
|
|
6086
|
+
const hash = execSync10(
|
|
5785
6087
|
`git log -1 --format=%H -- "${file}"`,
|
|
5786
6088
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5787
6089
|
).trim();
|
|
@@ -5790,7 +6092,7 @@ function detectGitDrift(dir) {
|
|
|
5790
6092
|
latestConfigCommitHash = hash;
|
|
5791
6093
|
} else {
|
|
5792
6094
|
try {
|
|
5793
|
-
|
|
6095
|
+
execSync10(
|
|
5794
6096
|
`git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
|
|
5795
6097
|
{ cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
|
|
5796
6098
|
);
|
|
@@ -5805,12 +6107,12 @@ function detectGitDrift(dir) {
|
|
|
5805
6107
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
|
|
5806
6108
|
}
|
|
5807
6109
|
try {
|
|
5808
|
-
const countStr =
|
|
6110
|
+
const countStr = execSync10(
|
|
5809
6111
|
`git rev-list --count ${latestConfigCommitHash}..HEAD`,
|
|
5810
6112
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5811
6113
|
).trim();
|
|
5812
6114
|
const commitsSince = parseInt(countStr, 10) || 0;
|
|
5813
|
-
const lastDate =
|
|
6115
|
+
const lastDate = execSync10(
|
|
5814
6116
|
`git log -1 --format=%ci ${latestConfigCommitHash}`,
|
|
5815
6117
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5816
6118
|
).trim();
|
|
@@ -5883,12 +6185,12 @@ function checkAccuracy(dir) {
|
|
|
5883
6185
|
// src/scoring/checks/freshness.ts
|
|
5884
6186
|
init_resolve_caliber();
|
|
5885
6187
|
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
5886
|
-
import { execSync as
|
|
6188
|
+
import { execSync as execSync11 } from "child_process";
|
|
5887
6189
|
import { join as join6 } from "path";
|
|
5888
6190
|
function getCommitsSinceConfigUpdate(dir) {
|
|
5889
6191
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
|
|
5890
6192
|
try {
|
|
5891
|
-
const headTimestamp =
|
|
6193
|
+
const headTimestamp = execSync11(
|
|
5892
6194
|
"git log -1 --format=%ct HEAD",
|
|
5893
6195
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5894
6196
|
).trim();
|
|
@@ -5908,12 +6210,12 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
5908
6210
|
}
|
|
5909
6211
|
for (const file of configFiles) {
|
|
5910
6212
|
try {
|
|
5911
|
-
const hash =
|
|
6213
|
+
const hash = execSync11(
|
|
5912
6214
|
`git log -1 --format=%H -- "${file}"`,
|
|
5913
6215
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5914
6216
|
).trim();
|
|
5915
6217
|
if (hash) {
|
|
5916
|
-
const countStr =
|
|
6218
|
+
const countStr = execSync11(
|
|
5917
6219
|
`git rev-list --count ${hash}..HEAD`,
|
|
5918
6220
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5919
6221
|
).trim();
|
|
@@ -6031,12 +6333,12 @@ function checkFreshness(dir) {
|
|
|
6031
6333
|
|
|
6032
6334
|
// src/scoring/checks/bonus.ts
|
|
6033
6335
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
6034
|
-
import { execSync as
|
|
6336
|
+
import { execSync as execSync12 } from "child_process";
|
|
6035
6337
|
import { join as join7 } from "path";
|
|
6036
6338
|
init_resolve_caliber();
|
|
6037
6339
|
function hasPreCommitHook(dir) {
|
|
6038
6340
|
try {
|
|
6039
|
-
const gitDir =
|
|
6341
|
+
const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6040
6342
|
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
6041
6343
|
const content = readFileOrNull(hookPath);
|
|
6042
6344
|
return content ? content.includes("caliber") : false;
|
|
@@ -6193,22 +6495,22 @@ function checkSources(dir) {
|
|
|
6193
6495
|
}
|
|
6194
6496
|
|
|
6195
6497
|
// src/scoring/dismissed.ts
|
|
6196
|
-
import
|
|
6197
|
-
import
|
|
6198
|
-
var DISMISSED_FILE =
|
|
6498
|
+
import fs24 from "fs";
|
|
6499
|
+
import path20 from "path";
|
|
6500
|
+
var DISMISSED_FILE = path20.join(CALIBER_DIR, "dismissed-checks.json");
|
|
6199
6501
|
function readDismissedChecks() {
|
|
6200
6502
|
try {
|
|
6201
|
-
if (!
|
|
6202
|
-
return JSON.parse(
|
|
6503
|
+
if (!fs24.existsSync(DISMISSED_FILE)) return [];
|
|
6504
|
+
return JSON.parse(fs24.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
6203
6505
|
} catch {
|
|
6204
6506
|
return [];
|
|
6205
6507
|
}
|
|
6206
6508
|
}
|
|
6207
6509
|
function writeDismissedChecks(checks) {
|
|
6208
|
-
if (!
|
|
6209
|
-
|
|
6510
|
+
if (!fs24.existsSync(CALIBER_DIR)) {
|
|
6511
|
+
fs24.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
6210
6512
|
}
|
|
6211
|
-
|
|
6513
|
+
fs24.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
6212
6514
|
}
|
|
6213
6515
|
function getDismissedIds() {
|
|
6214
6516
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -6463,27 +6765,27 @@ import { PostHog } from "posthog-node";
|
|
|
6463
6765
|
import chalk5 from "chalk";
|
|
6464
6766
|
|
|
6465
6767
|
// src/telemetry/config.ts
|
|
6466
|
-
import
|
|
6467
|
-
import
|
|
6768
|
+
import fs25 from "fs";
|
|
6769
|
+
import path21 from "path";
|
|
6468
6770
|
import os5 from "os";
|
|
6469
6771
|
import crypto4 from "crypto";
|
|
6470
|
-
import { execSync as
|
|
6471
|
-
var CONFIG_DIR2 =
|
|
6472
|
-
var CONFIG_FILE2 =
|
|
6772
|
+
import { execSync as execSync13 } from "child_process";
|
|
6773
|
+
var CONFIG_DIR2 = path21.join(os5.homedir(), ".caliber");
|
|
6774
|
+
var CONFIG_FILE2 = path21.join(CONFIG_DIR2, "config.json");
|
|
6473
6775
|
var runtimeDisabled = false;
|
|
6474
6776
|
function readConfig() {
|
|
6475
6777
|
try {
|
|
6476
|
-
if (!
|
|
6477
|
-
return JSON.parse(
|
|
6778
|
+
if (!fs25.existsSync(CONFIG_FILE2)) return {};
|
|
6779
|
+
return JSON.parse(fs25.readFileSync(CONFIG_FILE2, "utf-8"));
|
|
6478
6780
|
} catch {
|
|
6479
6781
|
return {};
|
|
6480
6782
|
}
|
|
6481
6783
|
}
|
|
6482
6784
|
function writeConfig(config) {
|
|
6483
|
-
if (!
|
|
6484
|
-
|
|
6785
|
+
if (!fs25.existsSync(CONFIG_DIR2)) {
|
|
6786
|
+
fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
6485
6787
|
}
|
|
6486
|
-
|
|
6788
|
+
fs25.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
6487
6789
|
}
|
|
6488
6790
|
function getMachineId() {
|
|
6489
6791
|
const config = readConfig();
|
|
@@ -6495,7 +6797,7 @@ function getMachineId() {
|
|
|
6495
6797
|
var EMAIL_HASH_KEY = "caliber-telemetry-v1";
|
|
6496
6798
|
function getGitEmailHash() {
|
|
6497
6799
|
try {
|
|
6498
|
-
const email =
|
|
6800
|
+
const email = execSync13("git config user.email", { encoding: "utf-8" }).trim();
|
|
6499
6801
|
if (!email) return void 0;
|
|
6500
6802
|
return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
|
|
6501
6803
|
} catch {
|
|
@@ -7694,8 +7996,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7694
7996
|
}
|
|
7695
7997
|
|
|
7696
7998
|
// src/lib/debug-report.ts
|
|
7697
|
-
import
|
|
7698
|
-
import
|
|
7999
|
+
import fs26 from "fs";
|
|
8000
|
+
import path22 from "path";
|
|
7699
8001
|
var DebugReport = class {
|
|
7700
8002
|
sections = [];
|
|
7701
8003
|
startTime;
|
|
@@ -7764,11 +8066,11 @@ var DebugReport = class {
|
|
|
7764
8066
|
lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
|
|
7765
8067
|
lines.push("");
|
|
7766
8068
|
}
|
|
7767
|
-
const dir =
|
|
7768
|
-
if (!
|
|
7769
|
-
|
|
8069
|
+
const dir = path22.dirname(outputPath);
|
|
8070
|
+
if (!fs26.existsSync(dir)) {
|
|
8071
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
7770
8072
|
}
|
|
7771
|
-
|
|
8073
|
+
fs26.writeFileSync(outputPath, lines.join("\n"));
|
|
7772
8074
|
}
|
|
7773
8075
|
};
|
|
7774
8076
|
function formatMs(ms) {
|
|
@@ -8169,7 +8471,7 @@ import chalk11 from "chalk";
|
|
|
8169
8471
|
import ora3 from "ora";
|
|
8170
8472
|
import select5 from "@inquirer/select";
|
|
8171
8473
|
import checkbox from "@inquirer/checkbox";
|
|
8172
|
-
import
|
|
8474
|
+
import fs29 from "fs";
|
|
8173
8475
|
|
|
8174
8476
|
// src/ai/refine.ts
|
|
8175
8477
|
async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
|
|
@@ -8310,10 +8612,10 @@ init_config();
|
|
|
8310
8612
|
init_review();
|
|
8311
8613
|
function detectAgents(dir) {
|
|
8312
8614
|
const agents = [];
|
|
8313
|
-
if (
|
|
8314
|
-
if (
|
|
8315
|
-
if (
|
|
8316
|
-
if (
|
|
8615
|
+
if (fs29.existsSync(`${dir}/.claude`)) agents.push("claude");
|
|
8616
|
+
if (fs29.existsSync(`${dir}/.cursor`)) agents.push("cursor");
|
|
8617
|
+
if (fs29.existsSync(`${dir}/.agents`) || fs29.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
|
|
8618
|
+
if (fs29.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
|
|
8317
8619
|
return agents;
|
|
8318
8620
|
}
|
|
8319
8621
|
async function promptAgent(detected) {
|
|
@@ -8435,18 +8737,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8435
8737
|
|
|
8436
8738
|
// src/commands/init-display.ts
|
|
8437
8739
|
import chalk12 from "chalk";
|
|
8438
|
-
import
|
|
8740
|
+
import fs30 from "fs";
|
|
8439
8741
|
function formatWhatChanged(setup) {
|
|
8440
8742
|
const lines = [];
|
|
8441
8743
|
const claude = setup.claude;
|
|
8442
8744
|
const codex = setup.codex;
|
|
8443
8745
|
const cursor = setup.cursor;
|
|
8444
8746
|
if (claude?.claudeMd) {
|
|
8445
|
-
const action =
|
|
8747
|
+
const action = fs30.existsSync("CLAUDE.md") ? "Updated" : "Created";
|
|
8446
8748
|
lines.push(`${action} CLAUDE.md`);
|
|
8447
8749
|
}
|
|
8448
8750
|
if (codex?.agentsMd) {
|
|
8449
|
-
const action =
|
|
8751
|
+
const action = fs30.existsSync("AGENTS.md") ? "Updated" : "Created";
|
|
8450
8752
|
lines.push(`${action} AGENTS.md`);
|
|
8451
8753
|
}
|
|
8452
8754
|
const allSkills = [];
|
|
@@ -8483,7 +8785,7 @@ function printSetupSummary(setup) {
|
|
|
8483
8785
|
};
|
|
8484
8786
|
if (claude) {
|
|
8485
8787
|
if (claude.claudeMd) {
|
|
8486
|
-
const icon =
|
|
8788
|
+
const icon = fs30.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8487
8789
|
const desc = getDescription("CLAUDE.md");
|
|
8488
8790
|
console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
|
|
8489
8791
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8493,7 +8795,7 @@ function printSetupSummary(setup) {
|
|
|
8493
8795
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
8494
8796
|
for (const skill of skills) {
|
|
8495
8797
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
8496
|
-
const icon =
|
|
8798
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8497
8799
|
const desc = getDescription(skillPath);
|
|
8498
8800
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8499
8801
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8504,7 +8806,7 @@ function printSetupSummary(setup) {
|
|
|
8504
8806
|
const codex = setup.codex;
|
|
8505
8807
|
if (codex) {
|
|
8506
8808
|
if (codex.agentsMd) {
|
|
8507
|
-
const icon =
|
|
8809
|
+
const icon = fs30.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8508
8810
|
const desc = getDescription("AGENTS.md");
|
|
8509
8811
|
console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
|
|
8510
8812
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8514,7 +8816,7 @@ function printSetupSummary(setup) {
|
|
|
8514
8816
|
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
8515
8817
|
for (const skill of codexSkills) {
|
|
8516
8818
|
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
8517
|
-
const icon =
|
|
8819
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8518
8820
|
const desc = getDescription(skillPath);
|
|
8519
8821
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8520
8822
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8524,7 +8826,7 @@ function printSetupSummary(setup) {
|
|
|
8524
8826
|
}
|
|
8525
8827
|
if (cursor) {
|
|
8526
8828
|
if (cursor.cursorrules) {
|
|
8527
|
-
const icon =
|
|
8829
|
+
const icon = fs30.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8528
8830
|
const desc = getDescription(".cursorrules");
|
|
8529
8831
|
console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
|
|
8530
8832
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8534,7 +8836,7 @@ function printSetupSummary(setup) {
|
|
|
8534
8836
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
8535
8837
|
for (const skill of cursorSkills) {
|
|
8536
8838
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
8537
|
-
const icon =
|
|
8839
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8538
8840
|
const desc = getDescription(skillPath);
|
|
8539
8841
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8540
8842
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8545,7 +8847,7 @@ function printSetupSummary(setup) {
|
|
|
8545
8847
|
if (Array.isArray(rulesArr) && rulesArr.length > 0) {
|
|
8546
8848
|
for (const rule of rulesArr) {
|
|
8547
8849
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
8548
|
-
const icon =
|
|
8850
|
+
const icon = fs30.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8549
8851
|
const desc = getDescription(rulePath);
|
|
8550
8852
|
console.log(` ${icon} ${chalk12.bold(rulePath)}`);
|
|
8551
8853
|
if (desc) {
|
|
@@ -8592,12 +8894,12 @@ function displayTokenUsage() {
|
|
|
8592
8894
|
// src/commands/init-helpers.ts
|
|
8593
8895
|
init_config();
|
|
8594
8896
|
import chalk13 from "chalk";
|
|
8595
|
-
import
|
|
8596
|
-
import
|
|
8897
|
+
import fs31 from "fs";
|
|
8898
|
+
import path24 from "path";
|
|
8597
8899
|
function isFirstRun(dir) {
|
|
8598
|
-
const caliberDir =
|
|
8900
|
+
const caliberDir = path24.join(dir, ".caliber");
|
|
8599
8901
|
try {
|
|
8600
|
-
const stat =
|
|
8902
|
+
const stat = fs31.statSync(caliberDir);
|
|
8601
8903
|
return !stat.isDirectory();
|
|
8602
8904
|
} catch {
|
|
8603
8905
|
return true;
|
|
@@ -8650,8 +8952,8 @@ function ensurePermissions(fingerprint) {
|
|
|
8650
8952
|
const settingsPath = ".claude/settings.json";
|
|
8651
8953
|
let settings = {};
|
|
8652
8954
|
try {
|
|
8653
|
-
if (
|
|
8654
|
-
settings = JSON.parse(
|
|
8955
|
+
if (fs31.existsSync(settingsPath)) {
|
|
8956
|
+
settings = JSON.parse(fs31.readFileSync(settingsPath, "utf-8"));
|
|
8655
8957
|
}
|
|
8656
8958
|
} catch {
|
|
8657
8959
|
}
|
|
@@ -8660,12 +8962,12 @@ function ensurePermissions(fingerprint) {
|
|
|
8660
8962
|
if (Array.isArray(allow) && allow.length > 0) return;
|
|
8661
8963
|
permissions.allow = derivePermissions(fingerprint);
|
|
8662
8964
|
settings.permissions = permissions;
|
|
8663
|
-
if (!
|
|
8664
|
-
|
|
8965
|
+
if (!fs31.existsSync(".claude")) fs31.mkdirSync(".claude", { recursive: true });
|
|
8966
|
+
fs31.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8665
8967
|
}
|
|
8666
8968
|
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
8667
8969
|
try {
|
|
8668
|
-
const logPath =
|
|
8970
|
+
const logPath = path24.join(process.cwd(), ".caliber", "error-log.md");
|
|
8669
8971
|
const lines = [
|
|
8670
8972
|
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
8671
8973
|
"",
|
|
@@ -8678,8 +8980,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
|
8678
8980
|
lines.push("## Error", "```", error, "```", "");
|
|
8679
8981
|
}
|
|
8680
8982
|
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
8681
|
-
|
|
8682
|
-
|
|
8983
|
+
fs31.mkdirSync(path24.join(process.cwd(), ".caliber"), { recursive: true });
|
|
8984
|
+
fs31.writeFileSync(logPath, lines.join("\n"));
|
|
8683
8985
|
console.log(chalk13.dim(`
|
|
8684
8986
|
Error log written to .caliber/error-log.md`));
|
|
8685
8987
|
} catch {
|
|
@@ -8730,13 +9032,13 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
8730
9032
|
}
|
|
8731
9033
|
|
|
8732
9034
|
// src/scoring/history.ts
|
|
8733
|
-
import
|
|
8734
|
-
import
|
|
9035
|
+
import fs32 from "fs";
|
|
9036
|
+
import path25 from "path";
|
|
8735
9037
|
var HISTORY_FILE = "score-history.jsonl";
|
|
8736
9038
|
var MAX_ENTRIES = 500;
|
|
8737
9039
|
var TRIM_THRESHOLD = MAX_ENTRIES + 50;
|
|
8738
9040
|
function historyFilePath() {
|
|
8739
|
-
return
|
|
9041
|
+
return path25.join(CALIBER_DIR, HISTORY_FILE);
|
|
8740
9042
|
}
|
|
8741
9043
|
function recordScore(result, trigger) {
|
|
8742
9044
|
const entry = {
|
|
@@ -8747,14 +9049,14 @@ function recordScore(result, trigger) {
|
|
|
8747
9049
|
trigger
|
|
8748
9050
|
};
|
|
8749
9051
|
try {
|
|
8750
|
-
|
|
9052
|
+
fs32.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
8751
9053
|
const filePath = historyFilePath();
|
|
8752
|
-
|
|
8753
|
-
const stat =
|
|
9054
|
+
fs32.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
9055
|
+
const stat = fs32.statSync(filePath);
|
|
8754
9056
|
if (stat.size > TRIM_THRESHOLD * 120) {
|
|
8755
|
-
const lines =
|
|
9057
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
8756
9058
|
if (lines.length > MAX_ENTRIES) {
|
|
8757
|
-
|
|
9059
|
+
fs32.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
|
|
8758
9060
|
}
|
|
8759
9061
|
}
|
|
8760
9062
|
} catch {
|
|
@@ -8763,7 +9065,7 @@ function recordScore(result, trigger) {
|
|
|
8763
9065
|
function readScoreHistory() {
|
|
8764
9066
|
const filePath = historyFilePath();
|
|
8765
9067
|
try {
|
|
8766
|
-
const content =
|
|
9068
|
+
const content = fs32.readFileSync(filePath, "utf-8");
|
|
8767
9069
|
const entries = [];
|
|
8768
9070
|
for (const line of content.split("\n")) {
|
|
8769
9071
|
if (!line) continue;
|
|
@@ -8809,13 +9111,13 @@ async function initCommand(options) {
|
|
|
8809
9111
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
8810
9112
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
8811
9113
|
`));
|
|
8812
|
-
console.log(chalk14.dim("
|
|
8813
|
-
console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
9114
|
+
console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
|
|
9115
|
+
console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
8814
9116
|
console.log(title.bold(" How it works:\n"));
|
|
8815
9117
|
console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
|
|
8816
9118
|
console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
|
|
8817
9119
|
console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
|
|
8818
|
-
console.log(chalk14.dim(" 4. Finalize
|
|
9120
|
+
console.log(chalk14.dim(" 4. Finalize Install sync hooks and score your setup\n"));
|
|
8819
9121
|
} else {
|
|
8820
9122
|
console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
|
|
8821
9123
|
}
|
|
@@ -9186,7 +9488,7 @@ async function initCommand(options) {
|
|
|
9186
9488
|
const { default: ora9 } = await import("ora");
|
|
9187
9489
|
const writeSpinner = ora9("Writing config files...").start();
|
|
9188
9490
|
try {
|
|
9189
|
-
if (targetAgent.includes("codex") && !
|
|
9491
|
+
if (targetAgent.includes("codex") && !fs33.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
9190
9492
|
const claude = generatedSetup.claude;
|
|
9191
9493
|
const cursor = generatedSetup.cursor;
|
|
9192
9494
|
const agentRefs = [];
|
|
@@ -9229,6 +9531,12 @@ ${agentRefs.join(" ")}
|
|
|
9229
9531
|
throw new Error("__exit__");
|
|
9230
9532
|
}
|
|
9231
9533
|
if (fingerprint) ensurePermissions(fingerprint);
|
|
9534
|
+
const hookResult = installPreCommitHook();
|
|
9535
|
+
if (hookResult.installed) {
|
|
9536
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 configs sync on every commit`);
|
|
9537
|
+
} else if (hookResult.alreadyInstalled) {
|
|
9538
|
+
console.log(chalk14.dim(" Pre-commit hook already installed"));
|
|
9539
|
+
}
|
|
9232
9540
|
const sha = getCurrentHeadSha();
|
|
9233
9541
|
writeState({
|
|
9234
9542
|
lastRefreshSha: sha ?? "",
|
|
@@ -9306,9 +9614,9 @@ ${agentRefs.join(" ")}
|
|
|
9306
9614
|
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9307
9615
|
}
|
|
9308
9616
|
}
|
|
9309
|
-
console.log(chalk14.bold.green("\n
|
|
9310
|
-
console.log(chalk14.dim(" Your AI
|
|
9311
|
-
console.log(chalk14.dim("
|
|
9617
|
+
console.log(chalk14.bold.green("\n Caliber is set up!"));
|
|
9618
|
+
console.log(chalk14.dim(" Your AI agent configs will now stay in sync with your codebase automatically."));
|
|
9619
|
+
console.log(chalk14.dim(" Every commit refreshes configs for all your agents. All changes are backed up.\n"));
|
|
9312
9620
|
const done = chalk14.green("\u2713");
|
|
9313
9621
|
const skip = chalk14.dim("\u2013");
|
|
9314
9622
|
console.log(chalk14.bold(" What was configured:\n"));
|
|
@@ -9336,9 +9644,9 @@ ${agentRefs.join(" ")}
|
|
|
9336
9644
|
}
|
|
9337
9645
|
if (report) {
|
|
9338
9646
|
report.markStep("Finished");
|
|
9339
|
-
const reportPath =
|
|
9647
|
+
const reportPath = path26.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9340
9648
|
report.write(reportPath);
|
|
9341
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
9649
|
+
console.log(chalk14.dim(` Debug report written to ${path26.relative(process.cwd(), reportPath)}
|
|
9342
9650
|
`));
|
|
9343
9651
|
}
|
|
9344
9652
|
}
|
|
@@ -9377,7 +9685,7 @@ function undoCommand() {
|
|
|
9377
9685
|
|
|
9378
9686
|
// src/commands/status.ts
|
|
9379
9687
|
import chalk16 from "chalk";
|
|
9380
|
-
import
|
|
9688
|
+
import fs34 from "fs";
|
|
9381
9689
|
init_config();
|
|
9382
9690
|
init_resolve_caliber();
|
|
9383
9691
|
async function statusCommand(options) {
|
|
@@ -9406,7 +9714,7 @@ async function statusCommand(options) {
|
|
|
9406
9714
|
}
|
|
9407
9715
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9408
9716
|
for (const entry of manifest.entries) {
|
|
9409
|
-
const exists =
|
|
9717
|
+
const exists = fs34.existsSync(entry.path);
|
|
9410
9718
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9411
9719
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9412
9720
|
}
|
|
@@ -9563,9 +9871,9 @@ async function regenerateCommand(options) {
|
|
|
9563
9871
|
}
|
|
9564
9872
|
|
|
9565
9873
|
// src/commands/score.ts
|
|
9566
|
-
import
|
|
9874
|
+
import fs35 from "fs";
|
|
9567
9875
|
import os7 from "os";
|
|
9568
|
-
import
|
|
9876
|
+
import path27 from "path";
|
|
9569
9877
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9570
9878
|
import chalk18 from "chalk";
|
|
9571
9879
|
init_resolve_caliber();
|
|
@@ -9573,12 +9881,12 @@ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS
|
|
|
9573
9881
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9574
9882
|
function scoreBaseRef(ref, target) {
|
|
9575
9883
|
if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
|
|
9576
|
-
const tmpDir =
|
|
9884
|
+
const tmpDir = fs35.mkdtempSync(path27.join(os7.tmpdir(), "caliber-compare-"));
|
|
9577
9885
|
try {
|
|
9578
9886
|
for (const file of CONFIG_FILES) {
|
|
9579
9887
|
try {
|
|
9580
9888
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9581
|
-
|
|
9889
|
+
fs35.writeFileSync(path27.join(tmpDir, file), content);
|
|
9582
9890
|
} catch {
|
|
9583
9891
|
}
|
|
9584
9892
|
}
|
|
@@ -9586,10 +9894,10 @@ function scoreBaseRef(ref, target) {
|
|
|
9586
9894
|
try {
|
|
9587
9895
|
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
|
|
9588
9896
|
for (const file of files) {
|
|
9589
|
-
const filePath =
|
|
9590
|
-
|
|
9897
|
+
const filePath = path27.join(tmpDir, file);
|
|
9898
|
+
fs35.mkdirSync(path27.dirname(filePath), { recursive: true });
|
|
9591
9899
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9592
|
-
|
|
9900
|
+
fs35.writeFileSync(filePath, content);
|
|
9593
9901
|
}
|
|
9594
9902
|
} catch {
|
|
9595
9903
|
}
|
|
@@ -9599,7 +9907,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9599
9907
|
} catch {
|
|
9600
9908
|
return null;
|
|
9601
9909
|
} finally {
|
|
9602
|
-
|
|
9910
|
+
fs35.rmSync(tmpDir, { recursive: true, force: true });
|
|
9603
9911
|
}
|
|
9604
9912
|
}
|
|
9605
9913
|
async function scoreCommand(options) {
|
|
@@ -9661,13 +9969,13 @@ async function scoreCommand(options) {
|
|
|
9661
9969
|
}
|
|
9662
9970
|
|
|
9663
9971
|
// src/commands/refresh.ts
|
|
9664
|
-
import
|
|
9665
|
-
import
|
|
9972
|
+
import fs39 from "fs";
|
|
9973
|
+
import path31 from "path";
|
|
9666
9974
|
import chalk19 from "chalk";
|
|
9667
9975
|
import ora6 from "ora";
|
|
9668
9976
|
|
|
9669
9977
|
// src/lib/git-diff.ts
|
|
9670
|
-
import { execSync as
|
|
9978
|
+
import { execSync as execSync15 } from "child_process";
|
|
9671
9979
|
var MAX_DIFF_BYTES = 1e5;
|
|
9672
9980
|
var DOC_PATTERNS = [
|
|
9673
9981
|
"CLAUDE.md",
|
|
@@ -9682,7 +9990,7 @@ function excludeArgs() {
|
|
|
9682
9990
|
}
|
|
9683
9991
|
function safeExec(cmd) {
|
|
9684
9992
|
try {
|
|
9685
|
-
return
|
|
9993
|
+
return execSync15(cmd, {
|
|
9686
9994
|
encoding: "utf-8",
|
|
9687
9995
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9688
9996
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -9740,48 +10048,48 @@ function collectDiff(lastSha) {
|
|
|
9740
10048
|
}
|
|
9741
10049
|
|
|
9742
10050
|
// src/writers/refresh.ts
|
|
9743
|
-
import
|
|
9744
|
-
import
|
|
10051
|
+
import fs36 from "fs";
|
|
10052
|
+
import path28 from "path";
|
|
9745
10053
|
function writeRefreshDocs(docs) {
|
|
9746
10054
|
const written = [];
|
|
9747
10055
|
if (docs.claudeMd) {
|
|
9748
|
-
|
|
10056
|
+
fs36.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.claudeMd))));
|
|
9749
10057
|
written.push("CLAUDE.md");
|
|
9750
10058
|
}
|
|
9751
10059
|
if (docs.readmeMd) {
|
|
9752
|
-
|
|
10060
|
+
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
9753
10061
|
written.push("README.md");
|
|
9754
10062
|
}
|
|
9755
10063
|
if (docs.cursorrules) {
|
|
9756
|
-
|
|
10064
|
+
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
9757
10065
|
written.push(".cursorrules");
|
|
9758
10066
|
}
|
|
9759
10067
|
if (docs.cursorRules) {
|
|
9760
|
-
const rulesDir =
|
|
9761
|
-
if (!
|
|
10068
|
+
const rulesDir = path28.join(".cursor", "rules");
|
|
10069
|
+
if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
|
|
9762
10070
|
for (const rule of docs.cursorRules) {
|
|
9763
|
-
|
|
10071
|
+
fs36.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
|
|
9764
10072
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
9765
10073
|
}
|
|
9766
10074
|
}
|
|
9767
10075
|
if (docs.claudeSkills) {
|
|
9768
|
-
const skillsDir =
|
|
9769
|
-
if (!
|
|
10076
|
+
const skillsDir = path28.join(".claude", "skills");
|
|
10077
|
+
if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
|
|
9770
10078
|
for (const skill of docs.claudeSkills) {
|
|
9771
|
-
|
|
10079
|
+
fs36.writeFileSync(path28.join(skillsDir, skill.filename), skill.content);
|
|
9772
10080
|
written.push(`.claude/skills/${skill.filename}`);
|
|
9773
10081
|
}
|
|
9774
10082
|
}
|
|
9775
10083
|
if (docs.copilotInstructions) {
|
|
9776
|
-
|
|
9777
|
-
|
|
10084
|
+
fs36.mkdirSync(".github", { recursive: true });
|
|
10085
|
+
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions))));
|
|
9778
10086
|
written.push(".github/copilot-instructions.md");
|
|
9779
10087
|
}
|
|
9780
10088
|
if (docs.copilotInstructionFiles) {
|
|
9781
|
-
const instructionsDir =
|
|
9782
|
-
|
|
10089
|
+
const instructionsDir = path28.join(".github", "instructions");
|
|
10090
|
+
fs36.mkdirSync(instructionsDir, { recursive: true });
|
|
9783
10091
|
for (const file of docs.copilotInstructionFiles) {
|
|
9784
|
-
|
|
10092
|
+
fs36.writeFileSync(path28.join(instructionsDir, file.filename), file.content);
|
|
9785
10093
|
written.push(`.github/instructions/${file.filename}`);
|
|
9786
10094
|
}
|
|
9787
10095
|
}
|
|
@@ -9867,8 +10175,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
9867
10175
|
}
|
|
9868
10176
|
|
|
9869
10177
|
// src/learner/writer.ts
|
|
9870
|
-
import
|
|
9871
|
-
import
|
|
10178
|
+
import fs37 from "fs";
|
|
10179
|
+
import path29 from "path";
|
|
9872
10180
|
|
|
9873
10181
|
// src/learner/utils.ts
|
|
9874
10182
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -9985,20 +10293,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9985
10293
|
}
|
|
9986
10294
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
9987
10295
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
9988
|
-
|
|
9989
|
-
if (mode)
|
|
10296
|
+
fs37.writeFileSync(filePath, header + merged + "\n");
|
|
10297
|
+
if (mode) fs37.chmodSync(filePath, mode);
|
|
9990
10298
|
return { newCount, newItems };
|
|
9991
10299
|
}
|
|
9992
10300
|
function writeLearnedSection(content) {
|
|
9993
10301
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
9994
10302
|
}
|
|
9995
10303
|
function writeLearnedSkill(skill) {
|
|
9996
|
-
const skillDir =
|
|
9997
|
-
if (!
|
|
9998
|
-
const skillPath =
|
|
9999
|
-
if (!skill.isNew &&
|
|
10000
|
-
const existing =
|
|
10001
|
-
|
|
10304
|
+
const skillDir = path29.join(".claude", "skills", skill.name);
|
|
10305
|
+
if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
|
|
10306
|
+
const skillPath = path29.join(skillDir, "SKILL.md");
|
|
10307
|
+
if (!skill.isNew && fs37.existsSync(skillPath)) {
|
|
10308
|
+
const existing = fs37.readFileSync(skillPath, "utf-8");
|
|
10309
|
+
fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
10002
10310
|
} else {
|
|
10003
10311
|
const frontmatter = [
|
|
10004
10312
|
"---",
|
|
@@ -10007,12 +10315,12 @@ function writeLearnedSkill(skill) {
|
|
|
10007
10315
|
"---",
|
|
10008
10316
|
""
|
|
10009
10317
|
].join("\n");
|
|
10010
|
-
|
|
10318
|
+
fs37.writeFileSync(skillPath, frontmatter + skill.content);
|
|
10011
10319
|
}
|
|
10012
10320
|
return skillPath;
|
|
10013
10321
|
}
|
|
10014
10322
|
function writePersonalLearnedSection(content) {
|
|
10015
|
-
if (!
|
|
10323
|
+
if (!fs37.existsSync(AUTH_DIR)) fs37.mkdirSync(AUTH_DIR, { recursive: true });
|
|
10016
10324
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
10017
10325
|
}
|
|
10018
10326
|
function addLearning(bullet, scope = "project") {
|
|
@@ -10025,55 +10333,64 @@ function addLearning(bullet, scope = "project") {
|
|
|
10025
10333
|
return { file: LEARNINGS_FILE, added: result.newCount > 0 };
|
|
10026
10334
|
}
|
|
10027
10335
|
function readPersonalLearnings() {
|
|
10028
|
-
if (!
|
|
10029
|
-
const content =
|
|
10336
|
+
if (!fs37.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
10337
|
+
const content = fs37.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
10030
10338
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10031
10339
|
return bullets || null;
|
|
10032
10340
|
}
|
|
10033
10341
|
function readLearnedSection() {
|
|
10034
|
-
if (
|
|
10035
|
-
const content2 =
|
|
10342
|
+
if (fs37.existsSync(LEARNINGS_FILE)) {
|
|
10343
|
+
const content2 = fs37.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
10036
10344
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10037
10345
|
return bullets || null;
|
|
10038
10346
|
}
|
|
10039
10347
|
const claudeMdPath = "CLAUDE.md";
|
|
10040
|
-
if (!
|
|
10041
|
-
const content =
|
|
10348
|
+
if (!fs37.existsSync(claudeMdPath)) return null;
|
|
10349
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10042
10350
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10043
10351
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10044
10352
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
10045
10353
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
10046
10354
|
}
|
|
10047
10355
|
function migrateInlineLearnings() {
|
|
10048
|
-
if (
|
|
10356
|
+
if (fs37.existsSync(LEARNINGS_FILE)) return false;
|
|
10049
10357
|
const claudeMdPath = "CLAUDE.md";
|
|
10050
|
-
if (!
|
|
10051
|
-
const content =
|
|
10358
|
+
if (!fs37.existsSync(claudeMdPath)) return false;
|
|
10359
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10052
10360
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10053
10361
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10054
10362
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
10055
10363
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
10056
10364
|
if (!section) return false;
|
|
10057
|
-
|
|
10365
|
+
fs37.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
10058
10366
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
10059
|
-
|
|
10367
|
+
fs37.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
10060
10368
|
return true;
|
|
10061
10369
|
}
|
|
10062
10370
|
|
|
10063
10371
|
// src/commands/refresh.ts
|
|
10064
10372
|
init_config();
|
|
10065
10373
|
init_resolve_caliber();
|
|
10374
|
+
function detectSyncedAgents(writtenFiles) {
|
|
10375
|
+
const agents = [];
|
|
10376
|
+
const joined = writtenFiles.join(" ");
|
|
10377
|
+
if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
|
|
10378
|
+
if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
|
|
10379
|
+
if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
|
|
10380
|
+
if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
|
|
10381
|
+
return agents;
|
|
10382
|
+
}
|
|
10066
10383
|
function log2(quiet, ...args) {
|
|
10067
10384
|
if (!quiet) console.log(...args);
|
|
10068
10385
|
}
|
|
10069
10386
|
function discoverGitRepos(parentDir) {
|
|
10070
10387
|
const repos = [];
|
|
10071
10388
|
try {
|
|
10072
|
-
const entries =
|
|
10389
|
+
const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
|
|
10073
10390
|
for (const entry of entries) {
|
|
10074
10391
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
10075
|
-
const childPath =
|
|
10076
|
-
if (
|
|
10392
|
+
const childPath = path31.join(parentDir, entry.name);
|
|
10393
|
+
if (fs39.existsSync(path31.join(childPath, ".git"))) {
|
|
10077
10394
|
repos.push(childPath);
|
|
10078
10395
|
}
|
|
10079
10396
|
}
|
|
@@ -10159,9 +10476,9 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10159
10476
|
const filesToWrite = response.docsUpdated || [];
|
|
10160
10477
|
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
10161
10478
|
for (const filePath of filesToWrite) {
|
|
10162
|
-
const fullPath =
|
|
10479
|
+
const fullPath = path31.resolve(repoDir, filePath);
|
|
10163
10480
|
try {
|
|
10164
|
-
preRefreshContents.set(filePath,
|
|
10481
|
+
preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
|
|
10165
10482
|
} catch {
|
|
10166
10483
|
preRefreshContents.set(filePath, null);
|
|
10167
10484
|
}
|
|
@@ -10171,14 +10488,14 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10171
10488
|
const postScore = computeLocalScore(repoDir, targetAgent);
|
|
10172
10489
|
if (postScore.score < preScore.score) {
|
|
10173
10490
|
for (const [filePath, content] of preRefreshContents) {
|
|
10174
|
-
const fullPath =
|
|
10491
|
+
const fullPath = path31.resolve(repoDir, filePath);
|
|
10175
10492
|
if (content === null) {
|
|
10176
10493
|
try {
|
|
10177
|
-
|
|
10494
|
+
fs39.unlinkSync(fullPath);
|
|
10178
10495
|
} catch {
|
|
10179
10496
|
}
|
|
10180
10497
|
} else {
|
|
10181
|
-
|
|
10498
|
+
fs39.writeFileSync(fullPath, content);
|
|
10182
10499
|
}
|
|
10183
10500
|
}
|
|
10184
10501
|
spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
|
|
@@ -10190,8 +10507,18 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10190
10507
|
}
|
|
10191
10508
|
recordScore(postScore, "refresh");
|
|
10192
10509
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
10510
|
+
const fileChangesMap = new Map(
|
|
10511
|
+
(response.fileChanges || []).map((fc) => [fc.file, fc.description])
|
|
10512
|
+
);
|
|
10193
10513
|
for (const file of written) {
|
|
10194
|
-
|
|
10514
|
+
const desc = fileChangesMap.get(file);
|
|
10515
|
+
const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
|
|
10516
|
+
log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
|
|
10517
|
+
}
|
|
10518
|
+
const agents = detectSyncedAgents(written);
|
|
10519
|
+
if (agents.length > 1) {
|
|
10520
|
+
log2(quiet, chalk19.cyan(`
|
|
10521
|
+
${agents.length} agent formats in sync (${agents.join(", ")})`));
|
|
10195
10522
|
}
|
|
10196
10523
|
if (response.changesSummary) {
|
|
10197
10524
|
log2(quiet, chalk19.dim(`
|
|
@@ -10233,7 +10560,7 @@ async function refreshCommand(options) {
|
|
|
10233
10560
|
`));
|
|
10234
10561
|
const originalDir = process.cwd();
|
|
10235
10562
|
for (const repo of repos) {
|
|
10236
|
-
const repoName =
|
|
10563
|
+
const repoName = path31.basename(repo);
|
|
10237
10564
|
try {
|
|
10238
10565
|
process.chdir(repo);
|
|
10239
10566
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -10255,150 +10582,6 @@ async function refreshCommand(options) {
|
|
|
10255
10582
|
// src/commands/hooks.ts
|
|
10256
10583
|
import chalk20 from "chalk";
|
|
10257
10584
|
import fs40 from "fs";
|
|
10258
|
-
|
|
10259
|
-
// src/lib/hooks.ts
|
|
10260
|
-
init_resolve_caliber();
|
|
10261
|
-
import fs39 from "fs";
|
|
10262
|
-
import path31 from "path";
|
|
10263
|
-
import { execSync as execSync15 } from "child_process";
|
|
10264
|
-
var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
|
|
10265
|
-
var REFRESH_TAIL = "refresh --quiet";
|
|
10266
|
-
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
10267
|
-
function getHookCommand() {
|
|
10268
|
-
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
10269
|
-
}
|
|
10270
|
-
function readSettings2() {
|
|
10271
|
-
if (!fs39.existsSync(SETTINGS_PATH2)) return {};
|
|
10272
|
-
try {
|
|
10273
|
-
return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
10274
|
-
} catch {
|
|
10275
|
-
return {};
|
|
10276
|
-
}
|
|
10277
|
-
}
|
|
10278
|
-
function writeSettings2(settings) {
|
|
10279
|
-
const dir = path31.dirname(SETTINGS_PATH2);
|
|
10280
|
-
if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
|
|
10281
|
-
fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
10282
|
-
}
|
|
10283
|
-
function findHookIndex(sessionEnd) {
|
|
10284
|
-
return sessionEnd.findIndex(
|
|
10285
|
-
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
10286
|
-
);
|
|
10287
|
-
}
|
|
10288
|
-
function isHookInstalled() {
|
|
10289
|
-
const settings = readSettings2();
|
|
10290
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10291
|
-
if (!Array.isArray(sessionEnd)) return false;
|
|
10292
|
-
return findHookIndex(sessionEnd) !== -1;
|
|
10293
|
-
}
|
|
10294
|
-
function installHook() {
|
|
10295
|
-
const settings = readSettings2();
|
|
10296
|
-
if (!settings.hooks) settings.hooks = {};
|
|
10297
|
-
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
10298
|
-
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
10299
|
-
return { installed: false, alreadyInstalled: true };
|
|
10300
|
-
}
|
|
10301
|
-
settings.hooks.SessionEnd.push({
|
|
10302
|
-
matcher: "",
|
|
10303
|
-
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
10304
|
-
});
|
|
10305
|
-
writeSettings2(settings);
|
|
10306
|
-
return { installed: true, alreadyInstalled: false };
|
|
10307
|
-
}
|
|
10308
|
-
function removeHook() {
|
|
10309
|
-
const settings = readSettings2();
|
|
10310
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10311
|
-
if (!Array.isArray(sessionEnd)) {
|
|
10312
|
-
return { removed: false, notFound: true };
|
|
10313
|
-
}
|
|
10314
|
-
const idx = findHookIndex(sessionEnd);
|
|
10315
|
-
if (idx === -1) {
|
|
10316
|
-
return { removed: false, notFound: true };
|
|
10317
|
-
}
|
|
10318
|
-
sessionEnd.splice(idx, 1);
|
|
10319
|
-
if (sessionEnd.length === 0) {
|
|
10320
|
-
delete settings.hooks.SessionEnd;
|
|
10321
|
-
}
|
|
10322
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
10323
|
-
delete settings.hooks;
|
|
10324
|
-
}
|
|
10325
|
-
writeSettings2(settings);
|
|
10326
|
-
return { removed: true, notFound: false };
|
|
10327
|
-
}
|
|
10328
|
-
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
10329
|
-
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
10330
|
-
function getPrecommitBlock() {
|
|
10331
|
-
const bin = resolveCaliber();
|
|
10332
|
-
const npx = isNpxResolution();
|
|
10333
|
-
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
10334
|
-
const invoke = npx ? bin : `"${bin}"`;
|
|
10335
|
-
return `${PRECOMMIT_START}
|
|
10336
|
-
if ${guard}; then
|
|
10337
|
-
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
10338
|
-
${invoke} refresh 2>/dev/null || true
|
|
10339
|
-
${invoke} learn finalize 2>/dev/null || true
|
|
10340
|
-
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
10341
|
-
fi
|
|
10342
|
-
${PRECOMMIT_END}`;
|
|
10343
|
-
}
|
|
10344
|
-
function getGitHooksDir() {
|
|
10345
|
-
try {
|
|
10346
|
-
const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10347
|
-
return path31.join(gitDir, "hooks");
|
|
10348
|
-
} catch {
|
|
10349
|
-
return null;
|
|
10350
|
-
}
|
|
10351
|
-
}
|
|
10352
|
-
function getPreCommitPath() {
|
|
10353
|
-
const hooksDir = getGitHooksDir();
|
|
10354
|
-
return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
|
|
10355
|
-
}
|
|
10356
|
-
function isPreCommitHookInstalled() {
|
|
10357
|
-
const hookPath = getPreCommitPath();
|
|
10358
|
-
if (!hookPath || !fs39.existsSync(hookPath)) return false;
|
|
10359
|
-
const content = fs39.readFileSync(hookPath, "utf-8");
|
|
10360
|
-
return content.includes(PRECOMMIT_START);
|
|
10361
|
-
}
|
|
10362
|
-
function installPreCommitHook() {
|
|
10363
|
-
if (isPreCommitHookInstalled()) {
|
|
10364
|
-
return { installed: false, alreadyInstalled: true };
|
|
10365
|
-
}
|
|
10366
|
-
const hookPath = getPreCommitPath();
|
|
10367
|
-
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
10368
|
-
const hooksDir = path31.dirname(hookPath);
|
|
10369
|
-
if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
|
|
10370
|
-
let content = "";
|
|
10371
|
-
if (fs39.existsSync(hookPath)) {
|
|
10372
|
-
content = fs39.readFileSync(hookPath, "utf-8");
|
|
10373
|
-
if (!content.endsWith("\n")) content += "\n";
|
|
10374
|
-
content += "\n" + getPrecommitBlock() + "\n";
|
|
10375
|
-
} else {
|
|
10376
|
-
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
10377
|
-
}
|
|
10378
|
-
fs39.writeFileSync(hookPath, content);
|
|
10379
|
-
fs39.chmodSync(hookPath, 493);
|
|
10380
|
-
return { installed: true, alreadyInstalled: false };
|
|
10381
|
-
}
|
|
10382
|
-
function removePreCommitHook() {
|
|
10383
|
-
const hookPath = getPreCommitPath();
|
|
10384
|
-
if (!hookPath || !fs39.existsSync(hookPath)) {
|
|
10385
|
-
return { removed: false, notFound: true };
|
|
10386
|
-
}
|
|
10387
|
-
let content = fs39.readFileSync(hookPath, "utf-8");
|
|
10388
|
-
if (!content.includes(PRECOMMIT_START)) {
|
|
10389
|
-
return { removed: false, notFound: true };
|
|
10390
|
-
}
|
|
10391
|
-
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
10392
|
-
content = content.replace(regex, "\n");
|
|
10393
|
-
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
10394
|
-
fs39.unlinkSync(hookPath);
|
|
10395
|
-
} else {
|
|
10396
|
-
fs39.writeFileSync(hookPath, content);
|
|
10397
|
-
}
|
|
10398
|
-
return { removed: true, notFound: false };
|
|
10399
|
-
}
|
|
10400
|
-
|
|
10401
|
-
// src/commands/hooks.ts
|
|
10402
10585
|
var HOOKS = [
|
|
10403
10586
|
{
|
|
10404
10587
|
id: "session-end",
|