@rely-ai/caliber 1.25.0-dev.1773915517 → 1.25.0-dev.1773925407
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 +409 -440
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -271,20 +271,20 @@ var init_resolve_caliber = __esm({
|
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
// src/utils/editor.ts
|
|
274
|
-
import { execSync as
|
|
275
|
-
import
|
|
276
|
-
import
|
|
274
|
+
import { execSync as execSync12, spawn as spawn3 } from "child_process";
|
|
275
|
+
import fs25 from "fs";
|
|
276
|
+
import path21 from "path";
|
|
277
277
|
import os5 from "os";
|
|
278
278
|
function getEmptyFilePath(proposedPath) {
|
|
279
|
-
|
|
280
|
-
const tempPath =
|
|
281
|
-
|
|
279
|
+
fs25.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
280
|
+
const tempPath = path21.join(DIFF_TEMP_DIR, path21.basename(proposedPath));
|
|
281
|
+
fs25.writeFileSync(tempPath, "");
|
|
282
282
|
return tempPath;
|
|
283
283
|
}
|
|
284
284
|
function commandExists(cmd) {
|
|
285
285
|
try {
|
|
286
286
|
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
287
|
-
|
|
287
|
+
execSync12(check, { stdio: "ignore" });
|
|
288
288
|
return true;
|
|
289
289
|
} catch {
|
|
290
290
|
return false;
|
|
@@ -318,7 +318,7 @@ var init_editor = __esm({
|
|
|
318
318
|
"src/utils/editor.ts"() {
|
|
319
319
|
"use strict";
|
|
320
320
|
IS_WINDOWS3 = process.platform === "win32";
|
|
321
|
-
DIFF_TEMP_DIR =
|
|
321
|
+
DIFF_TEMP_DIR = path21.join(os5.tmpdir(), "caliber-diff");
|
|
322
322
|
}
|
|
323
323
|
});
|
|
324
324
|
|
|
@@ -330,7 +330,7 @@ __export(review_exports, {
|
|
|
330
330
|
promptWantsReview: () => promptWantsReview
|
|
331
331
|
});
|
|
332
332
|
import chalk10 from "chalk";
|
|
333
|
-
import
|
|
333
|
+
import fs26 from "fs";
|
|
334
334
|
import select4 from "@inquirer/select";
|
|
335
335
|
import { createTwoFilesPatch } from "diff";
|
|
336
336
|
async function promptWantsReview() {
|
|
@@ -367,8 +367,8 @@ async function openReview(method, stagedFiles) {
|
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
369
|
const fileInfos = stagedFiles.map((file) => {
|
|
370
|
-
const proposed =
|
|
371
|
-
const current = file.currentPath ?
|
|
370
|
+
const proposed = fs26.readFileSync(file.proposedPath, "utf-8");
|
|
371
|
+
const current = file.currentPath ? fs26.readFileSync(file.currentPath, "utf-8") : "";
|
|
372
372
|
const patch = createTwoFilesPatch(
|
|
373
373
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
374
374
|
file.relativePath,
|
|
@@ -543,13 +543,13 @@ __export(lock_exports, {
|
|
|
543
543
|
isCaliberRunning: () => isCaliberRunning,
|
|
544
544
|
releaseLock: () => releaseLock
|
|
545
545
|
});
|
|
546
|
-
import
|
|
547
|
-
import
|
|
546
|
+
import fs35 from "fs";
|
|
547
|
+
import path27 from "path";
|
|
548
548
|
import os7 from "os";
|
|
549
549
|
function isCaliberRunning() {
|
|
550
550
|
try {
|
|
551
|
-
if (!
|
|
552
|
-
const raw =
|
|
551
|
+
if (!fs35.existsSync(LOCK_FILE)) return false;
|
|
552
|
+
const raw = fs35.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
553
553
|
const { pid, ts } = JSON.parse(raw);
|
|
554
554
|
if (Date.now() - ts > STALE_MS) return false;
|
|
555
555
|
try {
|
|
@@ -564,13 +564,13 @@ function isCaliberRunning() {
|
|
|
564
564
|
}
|
|
565
565
|
function acquireLock() {
|
|
566
566
|
try {
|
|
567
|
-
|
|
567
|
+
fs35.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
568
568
|
} catch {
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
function releaseLock() {
|
|
572
572
|
try {
|
|
573
|
-
if (
|
|
573
|
+
if (fs35.existsSync(LOCK_FILE)) fs35.unlinkSync(LOCK_FILE);
|
|
574
574
|
} catch {
|
|
575
575
|
}
|
|
576
576
|
}
|
|
@@ -578,7 +578,7 @@ var LOCK_FILE, STALE_MS;
|
|
|
578
578
|
var init_lock = __esm({
|
|
579
579
|
"src/lib/lock.ts"() {
|
|
580
580
|
"use strict";
|
|
581
|
-
LOCK_FILE =
|
|
581
|
+
LOCK_FILE = path27.join(os7.tmpdir(), ".caliber.lock");
|
|
582
582
|
STALE_MS = 10 * 60 * 1e3;
|
|
583
583
|
}
|
|
584
584
|
});
|
|
@@ -590,9 +590,9 @@ import path35 from "path";
|
|
|
590
590
|
import { fileURLToPath } from "url";
|
|
591
591
|
|
|
592
592
|
// src/commands/init.ts
|
|
593
|
-
import
|
|
593
|
+
import path23 from "path";
|
|
594
594
|
import chalk14 from "chalk";
|
|
595
|
-
import
|
|
595
|
+
import fs30 from "fs";
|
|
596
596
|
|
|
597
597
|
// src/fingerprint/index.ts
|
|
598
598
|
import fs7 from "fs";
|
|
@@ -4012,9 +4012,45 @@ import fs15 from "fs";
|
|
|
4012
4012
|
// src/writers/claude/index.ts
|
|
4013
4013
|
import fs9 from "fs";
|
|
4014
4014
|
import path9 from "path";
|
|
4015
|
+
|
|
4016
|
+
// src/writers/pre-commit-block.ts
|
|
4017
|
+
var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
4018
|
+
var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
4019
|
+
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
|
|
4020
|
+
var PRE_COMMIT_BLOCK = `${BLOCK_START}
|
|
4021
|
+
## Before Committing
|
|
4022
|
+
|
|
4023
|
+
Run \`caliber refresh\` before creating git commits to keep docs in sync with code changes.
|
|
4024
|
+
After it completes, stage any modified doc files before committing:
|
|
4025
|
+
|
|
4026
|
+
\`\`\`bash
|
|
4027
|
+
caliber refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
4028
|
+
\`\`\`
|
|
4029
|
+
${BLOCK_END}`;
|
|
4030
|
+
var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
4031
|
+
var CURSOR_RULE_CONTENT = `---
|
|
4032
|
+
description: Run caliber refresh before git commits to keep docs in sync
|
|
4033
|
+
alwaysApply: true
|
|
4034
|
+
---
|
|
4035
|
+
Before creating git commits, run \`caliber refresh\` to update docs based on code changes,
|
|
4036
|
+
then stage modified doc files (${MANAGED_DOC_PATHS}).
|
|
4037
|
+
`;
|
|
4038
|
+
function hasPreCommitBlock(content) {
|
|
4039
|
+
return content.includes(BLOCK_START);
|
|
4040
|
+
}
|
|
4041
|
+
function appendPreCommitBlock(content) {
|
|
4042
|
+
if (hasPreCommitBlock(content)) return content;
|
|
4043
|
+
const trimmed = content.trimEnd();
|
|
4044
|
+
return trimmed + "\n\n" + PRE_COMMIT_BLOCK + "\n";
|
|
4045
|
+
}
|
|
4046
|
+
function getCursorPreCommitRule() {
|
|
4047
|
+
return { filename: CURSOR_RULE_FILENAME, content: CURSOR_RULE_CONTENT };
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
// src/writers/claude/index.ts
|
|
4015
4051
|
function writeClaudeConfig(config) {
|
|
4016
4052
|
const written = [];
|
|
4017
|
-
fs9.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
4053
|
+
fs9.writeFileSync("CLAUDE.md", appendPreCommitBlock(config.claudeMd));
|
|
4018
4054
|
written.push("CLAUDE.md");
|
|
4019
4055
|
if (config.skills?.length) {
|
|
4020
4056
|
for (const skill of config.skills) {
|
|
@@ -4057,14 +4093,14 @@ function writeCursorConfig(config) {
|
|
|
4057
4093
|
fs10.writeFileSync(".cursorrules", config.cursorrules);
|
|
4058
4094
|
written.push(".cursorrules");
|
|
4059
4095
|
}
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4096
|
+
const preCommitRule = getCursorPreCommitRule();
|
|
4097
|
+
const allRules = [...config.rules || [], preCommitRule];
|
|
4098
|
+
const rulesDir = path10.join(".cursor", "rules");
|
|
4099
|
+
if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
|
|
4100
|
+
for (const rule of allRules) {
|
|
4101
|
+
const rulePath = path10.join(rulesDir, rule.filename);
|
|
4102
|
+
fs10.writeFileSync(rulePath, rule.content);
|
|
4103
|
+
written.push(rulePath);
|
|
4068
4104
|
}
|
|
4069
4105
|
if (config.skills?.length) {
|
|
4070
4106
|
for (const skill of config.skills) {
|
|
@@ -4106,7 +4142,7 @@ import fs11 from "fs";
|
|
|
4106
4142
|
import path11 from "path";
|
|
4107
4143
|
function writeCodexConfig(config) {
|
|
4108
4144
|
const written = [];
|
|
4109
|
-
fs11.writeFileSync("AGENTS.md", config.agentsMd);
|
|
4145
|
+
fs11.writeFileSync("AGENTS.md", appendPreCommitBlock(config.agentsMd));
|
|
4110
4146
|
written.push("AGENTS.md");
|
|
4111
4147
|
if (config.skills?.length) {
|
|
4112
4148
|
for (const skill of config.skills) {
|
|
@@ -4134,7 +4170,7 @@ function writeGithubCopilotConfig(config) {
|
|
|
4134
4170
|
const written = [];
|
|
4135
4171
|
if (config.instructions) {
|
|
4136
4172
|
fs12.mkdirSync(".github", { recursive: true });
|
|
4137
|
-
fs12.writeFileSync(path12.join(".github", "copilot-instructions.md"), config.instructions);
|
|
4173
|
+
fs12.writeFileSync(path12.join(".github", "copilot-instructions.md"), appendPreCommitBlock(config.instructions));
|
|
4138
4174
|
written.push(".github/copilot-instructions.md");
|
|
4139
4175
|
}
|
|
4140
4176
|
if (config.instructionFiles?.length) {
|
|
@@ -4438,150 +4474,11 @@ ${agentRefs.join(" ")}
|
|
|
4438
4474
|
return files;
|
|
4439
4475
|
}
|
|
4440
4476
|
|
|
4441
|
-
// src/lib/hooks.ts
|
|
4477
|
+
// src/lib/learning-hooks.ts
|
|
4442
4478
|
init_resolve_caliber();
|
|
4443
4479
|
import fs19 from "fs";
|
|
4444
4480
|
import path15 from "path";
|
|
4445
|
-
import { execSync as execSync7 } from "child_process";
|
|
4446
4481
|
var SETTINGS_PATH = path15.join(".claude", "settings.json");
|
|
4447
|
-
var REFRESH_TAIL = "refresh --quiet";
|
|
4448
|
-
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
4449
|
-
function getHookCommand() {
|
|
4450
|
-
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
4451
|
-
}
|
|
4452
|
-
function readSettings() {
|
|
4453
|
-
if (!fs19.existsSync(SETTINGS_PATH)) return {};
|
|
4454
|
-
try {
|
|
4455
|
-
return JSON.parse(fs19.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
4456
|
-
} catch {
|
|
4457
|
-
return {};
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
function writeSettings(settings) {
|
|
4461
|
-
const dir = path15.dirname(SETTINGS_PATH);
|
|
4462
|
-
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
4463
|
-
fs19.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
4464
|
-
}
|
|
4465
|
-
function findHookIndex(sessionEnd) {
|
|
4466
|
-
return sessionEnd.findIndex(
|
|
4467
|
-
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
4468
|
-
);
|
|
4469
|
-
}
|
|
4470
|
-
function isHookInstalled() {
|
|
4471
|
-
const settings = readSettings();
|
|
4472
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
4473
|
-
if (!Array.isArray(sessionEnd)) return false;
|
|
4474
|
-
return findHookIndex(sessionEnd) !== -1;
|
|
4475
|
-
}
|
|
4476
|
-
function installHook() {
|
|
4477
|
-
const settings = readSettings();
|
|
4478
|
-
if (!settings.hooks) settings.hooks = {};
|
|
4479
|
-
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
4480
|
-
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
4481
|
-
return { installed: false, alreadyInstalled: true };
|
|
4482
|
-
}
|
|
4483
|
-
settings.hooks.SessionEnd.push({
|
|
4484
|
-
matcher: "",
|
|
4485
|
-
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
4486
|
-
});
|
|
4487
|
-
writeSettings(settings);
|
|
4488
|
-
return { installed: true, alreadyInstalled: false };
|
|
4489
|
-
}
|
|
4490
|
-
function removeHook() {
|
|
4491
|
-
const settings = readSettings();
|
|
4492
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
4493
|
-
if (!Array.isArray(sessionEnd)) {
|
|
4494
|
-
return { removed: false, notFound: true };
|
|
4495
|
-
}
|
|
4496
|
-
const idx = findHookIndex(sessionEnd);
|
|
4497
|
-
if (idx === -1) {
|
|
4498
|
-
return { removed: false, notFound: true };
|
|
4499
|
-
}
|
|
4500
|
-
sessionEnd.splice(idx, 1);
|
|
4501
|
-
if (sessionEnd.length === 0) {
|
|
4502
|
-
delete settings.hooks.SessionEnd;
|
|
4503
|
-
}
|
|
4504
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
4505
|
-
delete settings.hooks;
|
|
4506
|
-
}
|
|
4507
|
-
writeSettings(settings);
|
|
4508
|
-
return { removed: true, notFound: false };
|
|
4509
|
-
}
|
|
4510
|
-
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
4511
|
-
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
4512
|
-
function getPrecommitBlock() {
|
|
4513
|
-
const bin = resolveCaliber();
|
|
4514
|
-
return `${PRECOMMIT_START}
|
|
4515
|
-
if [ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1; then
|
|
4516
|
-
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
4517
|
-
"${bin}" refresh 2>/dev/null || true
|
|
4518
|
-
"${bin}" learn finalize 2>/dev/null || true
|
|
4519
|
-
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
4520
|
-
fi
|
|
4521
|
-
${PRECOMMIT_END}`;
|
|
4522
|
-
}
|
|
4523
|
-
function getGitHooksDir() {
|
|
4524
|
-
try {
|
|
4525
|
-
const gitDir = execSync7("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
4526
|
-
return path15.join(gitDir, "hooks");
|
|
4527
|
-
} catch {
|
|
4528
|
-
return null;
|
|
4529
|
-
}
|
|
4530
|
-
}
|
|
4531
|
-
function getPreCommitPath() {
|
|
4532
|
-
const hooksDir = getGitHooksDir();
|
|
4533
|
-
return hooksDir ? path15.join(hooksDir, "pre-commit") : null;
|
|
4534
|
-
}
|
|
4535
|
-
function isPreCommitHookInstalled() {
|
|
4536
|
-
const hookPath = getPreCommitPath();
|
|
4537
|
-
if (!hookPath || !fs19.existsSync(hookPath)) return false;
|
|
4538
|
-
const content = fs19.readFileSync(hookPath, "utf-8");
|
|
4539
|
-
return content.includes(PRECOMMIT_START);
|
|
4540
|
-
}
|
|
4541
|
-
function installPreCommitHook() {
|
|
4542
|
-
if (isPreCommitHookInstalled()) {
|
|
4543
|
-
return { installed: false, alreadyInstalled: true };
|
|
4544
|
-
}
|
|
4545
|
-
const hookPath = getPreCommitPath();
|
|
4546
|
-
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
4547
|
-
const hooksDir = path15.dirname(hookPath);
|
|
4548
|
-
if (!fs19.existsSync(hooksDir)) fs19.mkdirSync(hooksDir, { recursive: true });
|
|
4549
|
-
let content = "";
|
|
4550
|
-
if (fs19.existsSync(hookPath)) {
|
|
4551
|
-
content = fs19.readFileSync(hookPath, "utf-8");
|
|
4552
|
-
if (!content.endsWith("\n")) content += "\n";
|
|
4553
|
-
content += "\n" + getPrecommitBlock() + "\n";
|
|
4554
|
-
} else {
|
|
4555
|
-
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
4556
|
-
}
|
|
4557
|
-
fs19.writeFileSync(hookPath, content);
|
|
4558
|
-
fs19.chmodSync(hookPath, 493);
|
|
4559
|
-
return { installed: true, alreadyInstalled: false };
|
|
4560
|
-
}
|
|
4561
|
-
function removePreCommitHook() {
|
|
4562
|
-
const hookPath = getPreCommitPath();
|
|
4563
|
-
if (!hookPath || !fs19.existsSync(hookPath)) {
|
|
4564
|
-
return { removed: false, notFound: true };
|
|
4565
|
-
}
|
|
4566
|
-
let content = fs19.readFileSync(hookPath, "utf-8");
|
|
4567
|
-
if (!content.includes(PRECOMMIT_START)) {
|
|
4568
|
-
return { removed: false, notFound: true };
|
|
4569
|
-
}
|
|
4570
|
-
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
4571
|
-
content = content.replace(regex, "\n");
|
|
4572
|
-
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
4573
|
-
fs19.unlinkSync(hookPath);
|
|
4574
|
-
} else {
|
|
4575
|
-
fs19.writeFileSync(hookPath, content);
|
|
4576
|
-
}
|
|
4577
|
-
return { removed: true, notFound: false };
|
|
4578
|
-
}
|
|
4579
|
-
|
|
4580
|
-
// src/lib/learning-hooks.ts
|
|
4581
|
-
init_resolve_caliber();
|
|
4582
|
-
import fs20 from "fs";
|
|
4583
|
-
import path16 from "path";
|
|
4584
|
-
var SETTINGS_PATH2 = path16.join(".claude", "settings.json");
|
|
4585
4482
|
var HOOK_TAILS = [
|
|
4586
4483
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
4587
4484
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
@@ -4597,24 +4494,24 @@ function getHookConfigs() {
|
|
|
4597
4494
|
description
|
|
4598
4495
|
}));
|
|
4599
4496
|
}
|
|
4600
|
-
function
|
|
4601
|
-
if (!
|
|
4497
|
+
function readSettings() {
|
|
4498
|
+
if (!fs19.existsSync(SETTINGS_PATH)) return {};
|
|
4602
4499
|
try {
|
|
4603
|
-
return JSON.parse(
|
|
4500
|
+
return JSON.parse(fs19.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
4604
4501
|
} catch {
|
|
4605
4502
|
return {};
|
|
4606
4503
|
}
|
|
4607
4504
|
}
|
|
4608
|
-
function
|
|
4609
|
-
const dir =
|
|
4610
|
-
if (!
|
|
4611
|
-
|
|
4505
|
+
function writeSettings(settings) {
|
|
4506
|
+
const dir = path15.dirname(SETTINGS_PATH);
|
|
4507
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
4508
|
+
fs19.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
4612
4509
|
}
|
|
4613
4510
|
function hasLearningHook(matchers, tail) {
|
|
4614
4511
|
return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
|
|
4615
4512
|
}
|
|
4616
4513
|
function areLearningHooksInstalled() {
|
|
4617
|
-
const settings =
|
|
4514
|
+
const settings = readSettings();
|
|
4618
4515
|
if (!settings.hooks) return false;
|
|
4619
4516
|
return HOOK_TAILS.every((cfg) => {
|
|
4620
4517
|
const matchers = settings.hooks[cfg.event];
|
|
@@ -4625,7 +4522,7 @@ function installLearningHooks() {
|
|
|
4625
4522
|
if (areLearningHooksInstalled()) {
|
|
4626
4523
|
return { installed: false, alreadyInstalled: true };
|
|
4627
4524
|
}
|
|
4628
|
-
const settings =
|
|
4525
|
+
const settings = readSettings();
|
|
4629
4526
|
if (!settings.hooks) settings.hooks = {};
|
|
4630
4527
|
const configs = getHookConfigs();
|
|
4631
4528
|
for (const cfg of configs) {
|
|
@@ -4639,10 +4536,10 @@ function installLearningHooks() {
|
|
|
4639
4536
|
});
|
|
4640
4537
|
}
|
|
4641
4538
|
}
|
|
4642
|
-
|
|
4539
|
+
writeSettings(settings);
|
|
4643
4540
|
return { installed: true, alreadyInstalled: false };
|
|
4644
4541
|
}
|
|
4645
|
-
var CURSOR_HOOKS_PATH =
|
|
4542
|
+
var CURSOR_HOOKS_PATH = path15.join(".cursor", "hooks.json");
|
|
4646
4543
|
var CURSOR_HOOK_EVENTS = [
|
|
4647
4544
|
{ event: "postToolUse", tail: "learn observe" },
|
|
4648
4545
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
@@ -4650,17 +4547,17 @@ var CURSOR_HOOK_EVENTS = [
|
|
|
4650
4547
|
{ event: "sessionEnd", tail: "learn finalize --auto" }
|
|
4651
4548
|
];
|
|
4652
4549
|
function readCursorHooks() {
|
|
4653
|
-
if (!
|
|
4550
|
+
if (!fs19.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
4654
4551
|
try {
|
|
4655
|
-
return JSON.parse(
|
|
4552
|
+
return JSON.parse(fs19.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
|
|
4656
4553
|
} catch {
|
|
4657
4554
|
return { version: 1, hooks: {} };
|
|
4658
4555
|
}
|
|
4659
4556
|
}
|
|
4660
4557
|
function writeCursorHooks(config) {
|
|
4661
|
-
const dir =
|
|
4662
|
-
if (!
|
|
4663
|
-
|
|
4558
|
+
const dir = path15.dirname(CURSOR_HOOKS_PATH);
|
|
4559
|
+
if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
|
|
4560
|
+
fs19.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
4664
4561
|
}
|
|
4665
4562
|
function hasCursorHook(entries, tail) {
|
|
4666
4563
|
return entries.some((e) => isCaliberCommand(e.command, tail));
|
|
@@ -4707,7 +4604,7 @@ function removeCursorLearningHooks() {
|
|
|
4707
4604
|
return { removed: true, notFound: false };
|
|
4708
4605
|
}
|
|
4709
4606
|
function removeLearningHooks() {
|
|
4710
|
-
const settings =
|
|
4607
|
+
const settings = readSettings();
|
|
4711
4608
|
if (!settings.hooks) return { removed: false, notFound: true };
|
|
4712
4609
|
let removedAny = false;
|
|
4713
4610
|
for (const cfg of HOOK_TAILS) {
|
|
@@ -4724,16 +4621,16 @@ function removeLearningHooks() {
|
|
|
4724
4621
|
delete settings.hooks;
|
|
4725
4622
|
}
|
|
4726
4623
|
if (!removedAny) return { removed: false, notFound: true };
|
|
4727
|
-
|
|
4624
|
+
writeSettings(settings);
|
|
4728
4625
|
return { removed: true, notFound: false };
|
|
4729
4626
|
}
|
|
4730
4627
|
|
|
4731
4628
|
// src/lib/state.ts
|
|
4732
4629
|
init_constants();
|
|
4733
|
-
import
|
|
4734
|
-
import
|
|
4735
|
-
import { execSync as
|
|
4736
|
-
var STATE_FILE =
|
|
4630
|
+
import fs20 from "fs";
|
|
4631
|
+
import path16 from "path";
|
|
4632
|
+
import { execSync as execSync7 } from "child_process";
|
|
4633
|
+
var STATE_FILE = path16.join(CALIBER_DIR, ".caliber-state.json");
|
|
4737
4634
|
function normalizeTargetAgent(value) {
|
|
4738
4635
|
if (Array.isArray(value)) return value;
|
|
4739
4636
|
if (typeof value === "string") {
|
|
@@ -4744,8 +4641,8 @@ function normalizeTargetAgent(value) {
|
|
|
4744
4641
|
}
|
|
4745
4642
|
function readState() {
|
|
4746
4643
|
try {
|
|
4747
|
-
if (!
|
|
4748
|
-
const raw = JSON.parse(
|
|
4644
|
+
if (!fs20.existsSync(STATE_FILE)) return null;
|
|
4645
|
+
const raw = JSON.parse(fs20.readFileSync(STATE_FILE, "utf-8"));
|
|
4749
4646
|
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
4750
4647
|
return raw;
|
|
4751
4648
|
} catch {
|
|
@@ -4753,14 +4650,14 @@ function readState() {
|
|
|
4753
4650
|
}
|
|
4754
4651
|
}
|
|
4755
4652
|
function writeState(state) {
|
|
4756
|
-
if (!
|
|
4757
|
-
|
|
4653
|
+
if (!fs20.existsSync(CALIBER_DIR)) {
|
|
4654
|
+
fs20.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
4758
4655
|
}
|
|
4759
|
-
|
|
4656
|
+
fs20.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
4760
4657
|
}
|
|
4761
4658
|
function getCurrentHeadSha() {
|
|
4762
4659
|
try {
|
|
4763
|
-
return
|
|
4660
|
+
return execSync7("git rev-parse HEAD", {
|
|
4764
4661
|
encoding: "utf-8",
|
|
4765
4662
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4766
4663
|
}).trim();
|
|
@@ -5369,7 +5266,7 @@ function checkGrounding(dir) {
|
|
|
5369
5266
|
|
|
5370
5267
|
// src/scoring/checks/accuracy.ts
|
|
5371
5268
|
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
5372
|
-
import { execSync as
|
|
5269
|
+
import { execSync as execSync8 } from "child_process";
|
|
5373
5270
|
import { join as join5 } from "path";
|
|
5374
5271
|
function validateReferences(dir) {
|
|
5375
5272
|
const configContent = collectPrimaryConfigContent(dir);
|
|
@@ -5378,13 +5275,13 @@ function validateReferences(dir) {
|
|
|
5378
5275
|
}
|
|
5379
5276
|
function detectGitDrift(dir) {
|
|
5380
5277
|
try {
|
|
5381
|
-
|
|
5278
|
+
execSync8("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
|
|
5382
5279
|
} catch {
|
|
5383
5280
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
|
|
5384
5281
|
}
|
|
5385
5282
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
|
|
5386
5283
|
try {
|
|
5387
|
-
const headTimestamp =
|
|
5284
|
+
const headTimestamp = execSync8(
|
|
5388
5285
|
"git log -1 --format=%ct HEAD",
|
|
5389
5286
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5390
5287
|
).trim();
|
|
@@ -5405,7 +5302,7 @@ function detectGitDrift(dir) {
|
|
|
5405
5302
|
let latestConfigCommitHash = null;
|
|
5406
5303
|
for (const file of configFiles) {
|
|
5407
5304
|
try {
|
|
5408
|
-
const hash =
|
|
5305
|
+
const hash = execSync8(
|
|
5409
5306
|
`git log -1 --format=%H -- "${file}"`,
|
|
5410
5307
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5411
5308
|
).trim();
|
|
@@ -5414,7 +5311,7 @@ function detectGitDrift(dir) {
|
|
|
5414
5311
|
latestConfigCommitHash = hash;
|
|
5415
5312
|
} else {
|
|
5416
5313
|
try {
|
|
5417
|
-
|
|
5314
|
+
execSync8(
|
|
5418
5315
|
`git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
|
|
5419
5316
|
{ cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
|
|
5420
5317
|
);
|
|
@@ -5429,12 +5326,12 @@ function detectGitDrift(dir) {
|
|
|
5429
5326
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
|
|
5430
5327
|
}
|
|
5431
5328
|
try {
|
|
5432
|
-
const countStr =
|
|
5329
|
+
const countStr = execSync8(
|
|
5433
5330
|
`git rev-list --count ${latestConfigCommitHash}..HEAD`,
|
|
5434
5331
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5435
5332
|
).trim();
|
|
5436
5333
|
const commitsSince = parseInt(countStr, 10) || 0;
|
|
5437
|
-
const lastDate =
|
|
5334
|
+
const lastDate = execSync8(
|
|
5438
5335
|
`git log -1 --format=%ci ${latestConfigCommitHash}`,
|
|
5439
5336
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5440
5337
|
).trim();
|
|
@@ -5506,12 +5403,12 @@ function checkAccuracy(dir) {
|
|
|
5506
5403
|
|
|
5507
5404
|
// src/scoring/checks/freshness.ts
|
|
5508
5405
|
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
5509
|
-
import { execSync as
|
|
5406
|
+
import { execSync as execSync9 } from "child_process";
|
|
5510
5407
|
import { join as join6 } from "path";
|
|
5511
5408
|
function getCommitsSinceConfigUpdate(dir) {
|
|
5512
5409
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
|
|
5513
5410
|
try {
|
|
5514
|
-
const headTimestamp =
|
|
5411
|
+
const headTimestamp = execSync9(
|
|
5515
5412
|
"git log -1 --format=%ct HEAD",
|
|
5516
5413
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5517
5414
|
).trim();
|
|
@@ -5531,12 +5428,12 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
5531
5428
|
}
|
|
5532
5429
|
for (const file of configFiles) {
|
|
5533
5430
|
try {
|
|
5534
|
-
const hash =
|
|
5431
|
+
const hash = execSync9(
|
|
5535
5432
|
`git log -1 --format=%H -- "${file}"`,
|
|
5536
5433
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5537
5434
|
).trim();
|
|
5538
5435
|
if (hash) {
|
|
5539
|
-
const countStr =
|
|
5436
|
+
const countStr = execSync9(
|
|
5540
5437
|
`git rev-list --count ${hash}..HEAD`,
|
|
5541
5438
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5542
5439
|
).trim();
|
|
@@ -5654,11 +5551,11 @@ function checkFreshness(dir) {
|
|
|
5654
5551
|
|
|
5655
5552
|
// src/scoring/checks/bonus.ts
|
|
5656
5553
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
5657
|
-
import { execSync as
|
|
5554
|
+
import { execSync as execSync10 } from "child_process";
|
|
5658
5555
|
import { join as join7 } from "path";
|
|
5659
5556
|
function hasPreCommitHook(dir) {
|
|
5660
5557
|
try {
|
|
5661
|
-
const gitDir =
|
|
5558
|
+
const gitDir = execSync10("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5662
5559
|
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
5663
5560
|
const content = readFileOrNull(hookPath);
|
|
5664
5561
|
return content ? content.includes("caliber") : false;
|
|
@@ -5687,7 +5584,12 @@ function checkBonus(dir) {
|
|
|
5687
5584
|
if (hasPrecommit) {
|
|
5688
5585
|
hookSources.push("git pre-commit");
|
|
5689
5586
|
}
|
|
5690
|
-
const
|
|
5587
|
+
const claudeMd = readFileOrNull(join7(dir, "CLAUDE.md"));
|
|
5588
|
+
const hasPreCommitBlock2 = claudeMd ? hasPreCommitBlock(claudeMd) : false;
|
|
5589
|
+
if (hasPreCommitBlock2) {
|
|
5590
|
+
hookSources.push("config pre-commit instruction");
|
|
5591
|
+
}
|
|
5592
|
+
const hasHooks = hasClaudeHooks || hasPrecommit || hasPreCommitBlock2;
|
|
5691
5593
|
checks.push({
|
|
5692
5594
|
id: "hooks_configured",
|
|
5693
5595
|
name: "Hooks configured",
|
|
@@ -5696,11 +5598,11 @@ function checkBonus(dir) {
|
|
|
5696
5598
|
earnedPoints: hasHooks ? POINTS_HOOKS : 0,
|
|
5697
5599
|
passed: hasHooks,
|
|
5698
5600
|
detail: hasHooks ? hookSources.join(", ") : "No hooks configured",
|
|
5699
|
-
suggestion: hasHooks ? void 0 : "Run `caliber
|
|
5601
|
+
suggestion: hasHooks ? void 0 : "Run `caliber init` to add pre-commit instructions",
|
|
5700
5602
|
fix: hasHooks ? void 0 : {
|
|
5701
5603
|
action: "install_hooks",
|
|
5702
5604
|
data: {},
|
|
5703
|
-
instruction: "
|
|
5605
|
+
instruction: "Run caliber init to add pre-commit refresh instructions to config files."
|
|
5704
5606
|
}
|
|
5705
5607
|
});
|
|
5706
5608
|
const agentsMdExists = existsSync6(join7(dir, "AGENTS.md"));
|
|
@@ -5810,22 +5712,22 @@ function checkSources(dir) {
|
|
|
5810
5712
|
|
|
5811
5713
|
// src/scoring/dismissed.ts
|
|
5812
5714
|
init_constants();
|
|
5813
|
-
import
|
|
5814
|
-
import
|
|
5815
|
-
var DISMISSED_FILE =
|
|
5715
|
+
import fs21 from "fs";
|
|
5716
|
+
import path17 from "path";
|
|
5717
|
+
var DISMISSED_FILE = path17.join(CALIBER_DIR, "dismissed-checks.json");
|
|
5816
5718
|
function readDismissedChecks() {
|
|
5817
5719
|
try {
|
|
5818
|
-
if (!
|
|
5819
|
-
return JSON.parse(
|
|
5720
|
+
if (!fs21.existsSync(DISMISSED_FILE)) return [];
|
|
5721
|
+
return JSON.parse(fs21.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
5820
5722
|
} catch {
|
|
5821
5723
|
return [];
|
|
5822
5724
|
}
|
|
5823
5725
|
}
|
|
5824
5726
|
function writeDismissedChecks(checks) {
|
|
5825
|
-
if (!
|
|
5826
|
-
|
|
5727
|
+
if (!fs21.existsSync(CALIBER_DIR)) {
|
|
5728
|
+
fs21.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
5827
5729
|
}
|
|
5828
|
-
|
|
5730
|
+
fs21.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
5829
5731
|
}
|
|
5830
5732
|
function getDismissedIds() {
|
|
5831
5733
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -6073,13 +5975,13 @@ import { mkdirSync, readFileSync as readFileSync4, readdirSync as readdirSync4,
|
|
|
6073
5975
|
import { join as join10, dirname as dirname2 } from "path";
|
|
6074
5976
|
|
|
6075
5977
|
// src/scanner/index.ts
|
|
6076
|
-
import
|
|
6077
|
-
import
|
|
5978
|
+
import fs22 from "fs";
|
|
5979
|
+
import path18 from "path";
|
|
6078
5980
|
import crypto3 from "crypto";
|
|
6079
5981
|
function scanLocalState(dir) {
|
|
6080
5982
|
const items = [];
|
|
6081
|
-
const claudeMdPath =
|
|
6082
|
-
if (
|
|
5983
|
+
const claudeMdPath = path18.join(dir, "CLAUDE.md");
|
|
5984
|
+
if (fs22.existsSync(claudeMdPath)) {
|
|
6083
5985
|
items.push({
|
|
6084
5986
|
type: "rule",
|
|
6085
5987
|
platform: "claude",
|
|
@@ -6088,10 +5990,10 @@ function scanLocalState(dir) {
|
|
|
6088
5990
|
path: claudeMdPath
|
|
6089
5991
|
});
|
|
6090
5992
|
}
|
|
6091
|
-
const skillsDir =
|
|
6092
|
-
if (
|
|
6093
|
-
for (const file of
|
|
6094
|
-
const filePath =
|
|
5993
|
+
const skillsDir = path18.join(dir, ".claude", "skills");
|
|
5994
|
+
if (fs22.existsSync(skillsDir)) {
|
|
5995
|
+
for (const file of fs22.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5996
|
+
const filePath = path18.join(skillsDir, file);
|
|
6095
5997
|
items.push({
|
|
6096
5998
|
type: "skill",
|
|
6097
5999
|
platform: "claude",
|
|
@@ -6101,10 +6003,10 @@ function scanLocalState(dir) {
|
|
|
6101
6003
|
});
|
|
6102
6004
|
}
|
|
6103
6005
|
}
|
|
6104
|
-
const mcpJsonPath =
|
|
6105
|
-
if (
|
|
6006
|
+
const mcpJsonPath = path18.join(dir, ".mcp.json");
|
|
6007
|
+
if (fs22.existsSync(mcpJsonPath)) {
|
|
6106
6008
|
try {
|
|
6107
|
-
const mcpJson = JSON.parse(
|
|
6009
|
+
const mcpJson = JSON.parse(fs22.readFileSync(mcpJsonPath, "utf-8"));
|
|
6108
6010
|
if (mcpJson.mcpServers) {
|
|
6109
6011
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
6110
6012
|
items.push({
|
|
@@ -6119,8 +6021,8 @@ function scanLocalState(dir) {
|
|
|
6119
6021
|
} catch {
|
|
6120
6022
|
}
|
|
6121
6023
|
}
|
|
6122
|
-
const agentsMdPath =
|
|
6123
|
-
if (
|
|
6024
|
+
const agentsMdPath = path18.join(dir, "AGENTS.md");
|
|
6025
|
+
if (fs22.existsSync(agentsMdPath)) {
|
|
6124
6026
|
items.push({
|
|
6125
6027
|
type: "rule",
|
|
6126
6028
|
platform: "codex",
|
|
@@ -6129,12 +6031,12 @@ function scanLocalState(dir) {
|
|
|
6129
6031
|
path: agentsMdPath
|
|
6130
6032
|
});
|
|
6131
6033
|
}
|
|
6132
|
-
const codexSkillsDir =
|
|
6133
|
-
if (
|
|
6034
|
+
const codexSkillsDir = path18.join(dir, ".agents", "skills");
|
|
6035
|
+
if (fs22.existsSync(codexSkillsDir)) {
|
|
6134
6036
|
try {
|
|
6135
|
-
for (const name of
|
|
6136
|
-
const skillFile =
|
|
6137
|
-
if (
|
|
6037
|
+
for (const name of fs22.readdirSync(codexSkillsDir)) {
|
|
6038
|
+
const skillFile = path18.join(codexSkillsDir, name, "SKILL.md");
|
|
6039
|
+
if (fs22.existsSync(skillFile)) {
|
|
6138
6040
|
items.push({
|
|
6139
6041
|
type: "skill",
|
|
6140
6042
|
platform: "codex",
|
|
@@ -6147,8 +6049,8 @@ function scanLocalState(dir) {
|
|
|
6147
6049
|
} catch {
|
|
6148
6050
|
}
|
|
6149
6051
|
}
|
|
6150
|
-
const cursorrulesPath =
|
|
6151
|
-
if (
|
|
6052
|
+
const cursorrulesPath = path18.join(dir, ".cursorrules");
|
|
6053
|
+
if (fs22.existsSync(cursorrulesPath)) {
|
|
6152
6054
|
items.push({
|
|
6153
6055
|
type: "rule",
|
|
6154
6056
|
platform: "cursor",
|
|
@@ -6157,10 +6059,10 @@ function scanLocalState(dir) {
|
|
|
6157
6059
|
path: cursorrulesPath
|
|
6158
6060
|
});
|
|
6159
6061
|
}
|
|
6160
|
-
const cursorRulesDir =
|
|
6161
|
-
if (
|
|
6162
|
-
for (const file of
|
|
6163
|
-
const filePath =
|
|
6062
|
+
const cursorRulesDir = path18.join(dir, ".cursor", "rules");
|
|
6063
|
+
if (fs22.existsSync(cursorRulesDir)) {
|
|
6064
|
+
for (const file of fs22.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
6065
|
+
const filePath = path18.join(cursorRulesDir, file);
|
|
6164
6066
|
items.push({
|
|
6165
6067
|
type: "rule",
|
|
6166
6068
|
platform: "cursor",
|
|
@@ -6170,12 +6072,12 @@ function scanLocalState(dir) {
|
|
|
6170
6072
|
});
|
|
6171
6073
|
}
|
|
6172
6074
|
}
|
|
6173
|
-
const cursorSkillsDir =
|
|
6174
|
-
if (
|
|
6075
|
+
const cursorSkillsDir = path18.join(dir, ".cursor", "skills");
|
|
6076
|
+
if (fs22.existsSync(cursorSkillsDir)) {
|
|
6175
6077
|
try {
|
|
6176
|
-
for (const name of
|
|
6177
|
-
const skillFile =
|
|
6178
|
-
if (
|
|
6078
|
+
for (const name of fs22.readdirSync(cursorSkillsDir)) {
|
|
6079
|
+
const skillFile = path18.join(cursorSkillsDir, name, "SKILL.md");
|
|
6080
|
+
if (fs22.existsSync(skillFile)) {
|
|
6179
6081
|
items.push({
|
|
6180
6082
|
type: "skill",
|
|
6181
6083
|
platform: "cursor",
|
|
@@ -6188,10 +6090,10 @@ function scanLocalState(dir) {
|
|
|
6188
6090
|
} catch {
|
|
6189
6091
|
}
|
|
6190
6092
|
}
|
|
6191
|
-
const cursorMcpPath =
|
|
6192
|
-
if (
|
|
6093
|
+
const cursorMcpPath = path18.join(dir, ".cursor", "mcp.json");
|
|
6094
|
+
if (fs22.existsSync(cursorMcpPath)) {
|
|
6193
6095
|
try {
|
|
6194
|
-
const mcpJson = JSON.parse(
|
|
6096
|
+
const mcpJson = JSON.parse(fs22.readFileSync(cursorMcpPath, "utf-8"));
|
|
6195
6097
|
if (mcpJson.mcpServers) {
|
|
6196
6098
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
6197
6099
|
items.push({
|
|
@@ -6209,7 +6111,7 @@ function scanLocalState(dir) {
|
|
|
6209
6111
|
return items;
|
|
6210
6112
|
}
|
|
6211
6113
|
function hashFile(filePath) {
|
|
6212
|
-
const text =
|
|
6114
|
+
const text = fs22.readFileSync(filePath, "utf-8");
|
|
6213
6115
|
return crypto3.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
6214
6116
|
}
|
|
6215
6117
|
function hashJson(obj) {
|
|
@@ -6224,27 +6126,27 @@ import { PostHog } from "posthog-node";
|
|
|
6224
6126
|
import chalk5 from "chalk";
|
|
6225
6127
|
|
|
6226
6128
|
// src/telemetry/config.ts
|
|
6227
|
-
import
|
|
6228
|
-
import
|
|
6129
|
+
import fs23 from "fs";
|
|
6130
|
+
import path19 from "path";
|
|
6229
6131
|
import os4 from "os";
|
|
6230
6132
|
import crypto4 from "crypto";
|
|
6231
|
-
import { execSync as
|
|
6232
|
-
var CONFIG_DIR2 =
|
|
6233
|
-
var CONFIG_FILE2 =
|
|
6133
|
+
import { execSync as execSync11 } from "child_process";
|
|
6134
|
+
var CONFIG_DIR2 = path19.join(os4.homedir(), ".caliber");
|
|
6135
|
+
var CONFIG_FILE2 = path19.join(CONFIG_DIR2, "config.json");
|
|
6234
6136
|
var runtimeDisabled = false;
|
|
6235
6137
|
function readConfig() {
|
|
6236
6138
|
try {
|
|
6237
|
-
if (!
|
|
6238
|
-
return JSON.parse(
|
|
6139
|
+
if (!fs23.existsSync(CONFIG_FILE2)) return {};
|
|
6140
|
+
return JSON.parse(fs23.readFileSync(CONFIG_FILE2, "utf-8"));
|
|
6239
6141
|
} catch {
|
|
6240
6142
|
return {};
|
|
6241
6143
|
}
|
|
6242
6144
|
}
|
|
6243
6145
|
function writeConfig(config) {
|
|
6244
|
-
if (!
|
|
6245
|
-
|
|
6146
|
+
if (!fs23.existsSync(CONFIG_DIR2)) {
|
|
6147
|
+
fs23.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
6246
6148
|
}
|
|
6247
|
-
|
|
6149
|
+
fs23.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
6248
6150
|
}
|
|
6249
6151
|
function getMachineId() {
|
|
6250
6152
|
const config = readConfig();
|
|
@@ -6255,7 +6157,7 @@ function getMachineId() {
|
|
|
6255
6157
|
}
|
|
6256
6158
|
function getGitEmailHash() {
|
|
6257
6159
|
try {
|
|
6258
|
-
const email =
|
|
6160
|
+
const email = execSync11("git config user.email", { encoding: "utf-8" }).trim();
|
|
6259
6161
|
if (!email) return void 0;
|
|
6260
6162
|
return crypto4.createHash("sha256").update(email).digest("hex");
|
|
6261
6163
|
} catch {
|
|
@@ -7346,8 +7248,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7346
7248
|
}
|
|
7347
7249
|
|
|
7348
7250
|
// src/lib/debug-report.ts
|
|
7349
|
-
import
|
|
7350
|
-
import
|
|
7251
|
+
import fs24 from "fs";
|
|
7252
|
+
import path20 from "path";
|
|
7351
7253
|
var DebugReport = class {
|
|
7352
7254
|
sections = [];
|
|
7353
7255
|
startTime;
|
|
@@ -7416,11 +7318,11 @@ var DebugReport = class {
|
|
|
7416
7318
|
lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
|
|
7417
7319
|
lines.push("");
|
|
7418
7320
|
}
|
|
7419
|
-
const dir =
|
|
7420
|
-
if (!
|
|
7421
|
-
|
|
7321
|
+
const dir = path20.dirname(outputPath);
|
|
7322
|
+
if (!fs24.existsSync(dir)) {
|
|
7323
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
7422
7324
|
}
|
|
7423
|
-
|
|
7325
|
+
fs24.writeFileSync(outputPath, lines.join("\n"));
|
|
7424
7326
|
}
|
|
7425
7327
|
};
|
|
7426
7328
|
function formatMs(ms) {
|
|
@@ -7821,7 +7723,7 @@ import chalk11 from "chalk";
|
|
|
7821
7723
|
import ora3 from "ora";
|
|
7822
7724
|
import select5 from "@inquirer/select";
|
|
7823
7725
|
import checkbox from "@inquirer/checkbox";
|
|
7824
|
-
import
|
|
7726
|
+
import fs27 from "fs";
|
|
7825
7727
|
|
|
7826
7728
|
// src/ai/refine.ts
|
|
7827
7729
|
async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
|
|
@@ -7962,10 +7864,10 @@ init_config();
|
|
|
7962
7864
|
init_review();
|
|
7963
7865
|
function detectAgents(dir) {
|
|
7964
7866
|
const agents = [];
|
|
7965
|
-
if (
|
|
7966
|
-
if (
|
|
7967
|
-
if (
|
|
7968
|
-
if (
|
|
7867
|
+
if (fs27.existsSync(`${dir}/.claude`)) agents.push("claude");
|
|
7868
|
+
if (fs27.existsSync(`${dir}/.cursor`)) agents.push("cursor");
|
|
7869
|
+
if (fs27.existsSync(`${dir}/.agents`) || fs27.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
|
|
7870
|
+
if (fs27.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
|
|
7969
7871
|
return agents;
|
|
7970
7872
|
}
|
|
7971
7873
|
async function promptAgent(detected) {
|
|
@@ -7987,20 +7889,6 @@ async function promptAgent(detected) {
|
|
|
7987
7889
|
});
|
|
7988
7890
|
return selected;
|
|
7989
7891
|
}
|
|
7990
|
-
async function promptHookType(targetAgent) {
|
|
7991
|
-
const choices = [];
|
|
7992
|
-
const hasClaude = targetAgent.includes("claude");
|
|
7993
|
-
choices.push({ name: "Git pre-commit hook \u2014 refresh before each commit (recommended)", value: "precommit" });
|
|
7994
|
-
if (hasClaude) {
|
|
7995
|
-
choices.push({ name: "Claude Code hook \u2014 auto-refresh on session end", value: "claude" });
|
|
7996
|
-
choices.push({ name: "Both (pre-commit + Claude Code)", value: "both" });
|
|
7997
|
-
}
|
|
7998
|
-
choices.push({ name: "Skip for now", value: "skip" });
|
|
7999
|
-
return select5({
|
|
8000
|
-
message: "Keep your AI docs & skills in sync as your code evolves?",
|
|
8001
|
-
choices
|
|
8002
|
-
});
|
|
8003
|
-
}
|
|
8004
7892
|
async function promptLearnInstall(targetAgent) {
|
|
8005
7893
|
const hasClaude = targetAgent.includes("claude");
|
|
8006
7894
|
const hasCursor = targetAgent.includes("cursor");
|
|
@@ -8101,18 +7989,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8101
7989
|
|
|
8102
7990
|
// src/commands/init-display.ts
|
|
8103
7991
|
import chalk12 from "chalk";
|
|
8104
|
-
import
|
|
7992
|
+
import fs28 from "fs";
|
|
8105
7993
|
function formatWhatChanged(setup) {
|
|
8106
7994
|
const lines = [];
|
|
8107
7995
|
const claude = setup.claude;
|
|
8108
7996
|
const codex = setup.codex;
|
|
8109
7997
|
const cursor = setup.cursor;
|
|
8110
7998
|
if (claude?.claudeMd) {
|
|
8111
|
-
const action =
|
|
7999
|
+
const action = fs28.existsSync("CLAUDE.md") ? "Updated" : "Created";
|
|
8112
8000
|
lines.push(`${action} CLAUDE.md`);
|
|
8113
8001
|
}
|
|
8114
8002
|
if (codex?.agentsMd) {
|
|
8115
|
-
const action =
|
|
8003
|
+
const action = fs28.existsSync("AGENTS.md") ? "Updated" : "Created";
|
|
8116
8004
|
lines.push(`${action} AGENTS.md`);
|
|
8117
8005
|
}
|
|
8118
8006
|
const allSkills = [];
|
|
@@ -8149,7 +8037,7 @@ function printSetupSummary(setup) {
|
|
|
8149
8037
|
};
|
|
8150
8038
|
if (claude) {
|
|
8151
8039
|
if (claude.claudeMd) {
|
|
8152
|
-
const icon =
|
|
8040
|
+
const icon = fs28.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8153
8041
|
const desc = getDescription("CLAUDE.md");
|
|
8154
8042
|
console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
|
|
8155
8043
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8159,7 +8047,7 @@ function printSetupSummary(setup) {
|
|
|
8159
8047
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
8160
8048
|
for (const skill of skills) {
|
|
8161
8049
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
8162
|
-
const icon =
|
|
8050
|
+
const icon = fs28.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8163
8051
|
const desc = getDescription(skillPath);
|
|
8164
8052
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8165
8053
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8170,7 +8058,7 @@ function printSetupSummary(setup) {
|
|
|
8170
8058
|
const codex = setup.codex;
|
|
8171
8059
|
if (codex) {
|
|
8172
8060
|
if (codex.agentsMd) {
|
|
8173
|
-
const icon =
|
|
8061
|
+
const icon = fs28.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8174
8062
|
const desc = getDescription("AGENTS.md");
|
|
8175
8063
|
console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
|
|
8176
8064
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8180,7 +8068,7 @@ function printSetupSummary(setup) {
|
|
|
8180
8068
|
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
8181
8069
|
for (const skill of codexSkills) {
|
|
8182
8070
|
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
8183
|
-
const icon =
|
|
8071
|
+
const icon = fs28.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8184
8072
|
const desc = getDescription(skillPath);
|
|
8185
8073
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8186
8074
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8190,7 +8078,7 @@ function printSetupSummary(setup) {
|
|
|
8190
8078
|
}
|
|
8191
8079
|
if (cursor) {
|
|
8192
8080
|
if (cursor.cursorrules) {
|
|
8193
|
-
const icon =
|
|
8081
|
+
const icon = fs28.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8194
8082
|
const desc = getDescription(".cursorrules");
|
|
8195
8083
|
console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
|
|
8196
8084
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8200,7 +8088,7 @@ function printSetupSummary(setup) {
|
|
|
8200
8088
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
8201
8089
|
for (const skill of cursorSkills) {
|
|
8202
8090
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
8203
|
-
const icon =
|
|
8091
|
+
const icon = fs28.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8204
8092
|
const desc = getDescription(skillPath);
|
|
8205
8093
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8206
8094
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8211,7 +8099,7 @@ function printSetupSummary(setup) {
|
|
|
8211
8099
|
if (Array.isArray(rulesArr) && rulesArr.length > 0) {
|
|
8212
8100
|
for (const rule of rulesArr) {
|
|
8213
8101
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
8214
|
-
const icon =
|
|
8102
|
+
const icon = fs28.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8215
8103
|
const desc = getDescription(rulePath);
|
|
8216
8104
|
console.log(` ${icon} ${chalk12.bold(rulePath)}`);
|
|
8217
8105
|
if (desc) {
|
|
@@ -8258,12 +8146,12 @@ function displayTokenUsage() {
|
|
|
8258
8146
|
// src/commands/init-helpers.ts
|
|
8259
8147
|
init_config();
|
|
8260
8148
|
import chalk13 from "chalk";
|
|
8261
|
-
import
|
|
8262
|
-
import
|
|
8149
|
+
import fs29 from "fs";
|
|
8150
|
+
import path22 from "path";
|
|
8263
8151
|
function isFirstRun(dir) {
|
|
8264
|
-
const caliberDir =
|
|
8152
|
+
const caliberDir = path22.join(dir, ".caliber");
|
|
8265
8153
|
try {
|
|
8266
|
-
const stat =
|
|
8154
|
+
const stat = fs29.statSync(caliberDir);
|
|
8267
8155
|
return !stat.isDirectory();
|
|
8268
8156
|
} catch {
|
|
8269
8157
|
return true;
|
|
@@ -8316,8 +8204,8 @@ function ensurePermissions(fingerprint) {
|
|
|
8316
8204
|
const settingsPath = ".claude/settings.json";
|
|
8317
8205
|
let settings = {};
|
|
8318
8206
|
try {
|
|
8319
|
-
if (
|
|
8320
|
-
settings = JSON.parse(
|
|
8207
|
+
if (fs29.existsSync(settingsPath)) {
|
|
8208
|
+
settings = JSON.parse(fs29.readFileSync(settingsPath, "utf-8"));
|
|
8321
8209
|
}
|
|
8322
8210
|
} catch {
|
|
8323
8211
|
}
|
|
@@ -8326,12 +8214,12 @@ function ensurePermissions(fingerprint) {
|
|
|
8326
8214
|
if (Array.isArray(allow) && allow.length > 0) return;
|
|
8327
8215
|
permissions.allow = derivePermissions(fingerprint);
|
|
8328
8216
|
settings.permissions = permissions;
|
|
8329
|
-
if (!
|
|
8330
|
-
|
|
8217
|
+
if (!fs29.existsSync(".claude")) fs29.mkdirSync(".claude", { recursive: true });
|
|
8218
|
+
fs29.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8331
8219
|
}
|
|
8332
8220
|
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
8333
8221
|
try {
|
|
8334
|
-
const logPath =
|
|
8222
|
+
const logPath = path22.join(process.cwd(), ".caliber", "error-log.md");
|
|
8335
8223
|
const lines = [
|
|
8336
8224
|
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
8337
8225
|
"",
|
|
@@ -8344,8 +8232,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
|
8344
8232
|
lines.push("## Error", "```", error, "```", "");
|
|
8345
8233
|
}
|
|
8346
8234
|
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
8347
|
-
|
|
8348
|
-
|
|
8235
|
+
fs29.mkdirSync(path22.join(process.cwd(), ".caliber"), { recursive: true });
|
|
8236
|
+
fs29.writeFileSync(logPath, lines.join("\n"));
|
|
8349
8237
|
console.log(chalk13.dim(`
|
|
8350
8238
|
Error log written to .caliber/error-log.md`));
|
|
8351
8239
|
} catch {
|
|
@@ -8783,7 +8671,7 @@ async function initCommand(options) {
|
|
|
8783
8671
|
const { default: ora9 } = await import("ora");
|
|
8784
8672
|
const writeSpinner = ora9("Writing config files...").start();
|
|
8785
8673
|
try {
|
|
8786
|
-
if (targetAgent.includes("codex") && !
|
|
8674
|
+
if (targetAgent.includes("codex") && !fs30.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
8787
8675
|
const claude = generatedSetup.claude;
|
|
8788
8676
|
const cursor = generatedSetup.cursor;
|
|
8789
8677
|
const agentRefs = [];
|
|
@@ -8873,66 +8761,8 @@ ${agentRefs.join(" ")}
|
|
|
8873
8761
|
}
|
|
8874
8762
|
}
|
|
8875
8763
|
console.log("");
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
hookChoice = "skip";
|
|
8879
|
-
log(options.verbose, "Auto-approve: skipping hook installation");
|
|
8880
|
-
} else if (firstRun) {
|
|
8881
|
-
const precommitResult = installPreCommitHook();
|
|
8882
|
-
if (precommitResult.installed) {
|
|
8883
|
-
console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
8884
|
-
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber hooks --remove") + chalk14.dim(" to disable"));
|
|
8885
|
-
hookChoice = "precommit";
|
|
8886
|
-
} else if (precommitResult.alreadyInstalled) {
|
|
8887
|
-
console.log(chalk14.dim(" Pre-commit hook already installed"));
|
|
8888
|
-
hookChoice = "precommit";
|
|
8889
|
-
} else {
|
|
8890
|
-
hookChoice = "skip";
|
|
8891
|
-
}
|
|
8892
|
-
if (targetAgent.includes("claude")) {
|
|
8893
|
-
const { default: select7 } = await import("@inquirer/select");
|
|
8894
|
-
const wantsClaude = await select7({
|
|
8895
|
-
message: "Also install Claude Code session hook? (auto-refresh on session end)",
|
|
8896
|
-
choices: [
|
|
8897
|
-
{ name: "Yes", value: true },
|
|
8898
|
-
{ name: "No", value: false }
|
|
8899
|
-
]
|
|
8900
|
-
});
|
|
8901
|
-
if (wantsClaude) {
|
|
8902
|
-
const hookResult = installHook();
|
|
8903
|
-
if (hookResult.installed) {
|
|
8904
|
-
console.log(` ${chalk14.green("\u2713")} Claude Code hook installed`);
|
|
8905
|
-
}
|
|
8906
|
-
hookChoice = hookChoice === "precommit" ? "both" : "claude";
|
|
8907
|
-
}
|
|
8908
|
-
}
|
|
8909
|
-
} else {
|
|
8910
|
-
hookChoice = await promptHookType(targetAgent);
|
|
8911
|
-
if (hookChoice === "claude" || hookChoice === "both") {
|
|
8912
|
-
const hookResult = installHook();
|
|
8913
|
-
if (hookResult.installed) {
|
|
8914
|
-
console.log(` ${chalk14.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
8915
|
-
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber hooks --remove") + chalk14.dim(" to disable"));
|
|
8916
|
-
} else if (hookResult.alreadyInstalled) {
|
|
8917
|
-
console.log(chalk14.dim(" Claude Code hook already installed"));
|
|
8918
|
-
}
|
|
8919
|
-
}
|
|
8920
|
-
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
8921
|
-
const precommitResult = installPreCommitHook();
|
|
8922
|
-
if (precommitResult.installed) {
|
|
8923
|
-
console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
8924
|
-
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber hooks --remove") + chalk14.dim(" to disable"));
|
|
8925
|
-
} else if (precommitResult.alreadyInstalled) {
|
|
8926
|
-
console.log(chalk14.dim(" Pre-commit hook already installed"));
|
|
8927
|
-
} else {
|
|
8928
|
-
console.log(chalk14.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
8929
|
-
}
|
|
8930
|
-
}
|
|
8931
|
-
if (hookChoice === "skip") {
|
|
8932
|
-
console.log(chalk14.dim(" Skipped auto-sync hooks. Run ") + chalk14.hex("#83D1EB")("caliber hooks --install") + chalk14.dim(" later to enable."));
|
|
8933
|
-
}
|
|
8934
|
-
}
|
|
8935
|
-
trackInitHookSelected(hookChoice);
|
|
8764
|
+
console.log(` ${chalk14.green("\u2713")} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
|
|
8765
|
+
trackInitHookSelected("config-instructions");
|
|
8936
8766
|
const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
|
|
8937
8767
|
let enableLearn = false;
|
|
8938
8768
|
if (hasLearnableAgent) {
|
|
@@ -8967,13 +8797,7 @@ ${agentRefs.join(" ")}
|
|
|
8967
8797
|
const skip = chalk14.dim("\u2013");
|
|
8968
8798
|
console.log(chalk14.bold(" What was set up:\n"));
|
|
8969
8799
|
console.log(` ${done} Config generated ${title("caliber score")} ${chalk14.dim("for full breakdown")}`);
|
|
8970
|
-
|
|
8971
|
-
if (hooksInstalled) {
|
|
8972
|
-
const hookLabel = hookChoice === "both" ? "pre-commit + Claude Code" : hookChoice === "precommit" ? "pre-commit" : "Claude Code";
|
|
8973
|
-
console.log(` ${done} Auto-sync hooks ${chalk14.dim(hookLabel + " \u2014 docs stay fresh automatically")}`);
|
|
8974
|
-
} else {
|
|
8975
|
-
console.log(` ${skip} Auto-sync hooks ${title("caliber hooks --install")} to enable later`);
|
|
8976
|
-
}
|
|
8800
|
+
console.log(` ${done} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
|
|
8977
8801
|
if (hasLearnableAgent) {
|
|
8978
8802
|
if (enableLearn) {
|
|
8979
8803
|
console.log(` ${done} Session learning ${chalk14.dim("agent learns from your feedback")}`);
|
|
@@ -8996,9 +8820,9 @@ ${agentRefs.join(" ")}
|
|
|
8996
8820
|
}
|
|
8997
8821
|
if (report) {
|
|
8998
8822
|
report.markStep("Finished");
|
|
8999
|
-
const reportPath =
|
|
8823
|
+
const reportPath = path23.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9000
8824
|
report.write(reportPath);
|
|
9001
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
8825
|
+
console.log(chalk14.dim(` Debug report written to ${path23.relative(process.cwd(), reportPath)}
|
|
9002
8826
|
`));
|
|
9003
8827
|
}
|
|
9004
8828
|
}
|
|
@@ -9037,7 +8861,7 @@ function undoCommand() {
|
|
|
9037
8861
|
|
|
9038
8862
|
// src/commands/status.ts
|
|
9039
8863
|
import chalk16 from "chalk";
|
|
9040
|
-
import
|
|
8864
|
+
import fs31 from "fs";
|
|
9041
8865
|
init_config();
|
|
9042
8866
|
async function statusCommand(options) {
|
|
9043
8867
|
const config = loadConfig();
|
|
@@ -9064,7 +8888,7 @@ async function statusCommand(options) {
|
|
|
9064
8888
|
}
|
|
9065
8889
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9066
8890
|
for (const entry of manifest.entries) {
|
|
9067
|
-
const exists =
|
|
8891
|
+
const exists = fs31.existsSync(entry.path);
|
|
9068
8892
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9069
8893
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9070
8894
|
}
|
|
@@ -9219,21 +9043,21 @@ async function regenerateCommand(options) {
|
|
|
9219
9043
|
}
|
|
9220
9044
|
|
|
9221
9045
|
// src/commands/score.ts
|
|
9222
|
-
import
|
|
9046
|
+
import fs32 from "fs";
|
|
9223
9047
|
import os6 from "os";
|
|
9224
|
-
import
|
|
9048
|
+
import path24 from "path";
|
|
9225
9049
|
import { execFileSync } from "child_process";
|
|
9226
9050
|
import chalk18 from "chalk";
|
|
9227
9051
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
|
|
9228
9052
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9229
9053
|
function scoreBaseRef(ref, target) {
|
|
9230
9054
|
if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
|
|
9231
|
-
const tmpDir =
|
|
9055
|
+
const tmpDir = fs32.mkdtempSync(path24.join(os6.tmpdir(), "caliber-compare-"));
|
|
9232
9056
|
try {
|
|
9233
9057
|
for (const file of CONFIG_FILES) {
|
|
9234
9058
|
try {
|
|
9235
9059
|
const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9236
|
-
|
|
9060
|
+
fs32.writeFileSync(path24.join(tmpDir, file), content);
|
|
9237
9061
|
} catch {
|
|
9238
9062
|
}
|
|
9239
9063
|
}
|
|
@@ -9241,10 +9065,10 @@ function scoreBaseRef(ref, target) {
|
|
|
9241
9065
|
try {
|
|
9242
9066
|
const files = execFileSync("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
|
|
9243
9067
|
for (const file of files) {
|
|
9244
|
-
const filePath =
|
|
9245
|
-
|
|
9068
|
+
const filePath = path24.join(tmpDir, file);
|
|
9069
|
+
fs32.mkdirSync(path24.dirname(filePath), { recursive: true });
|
|
9246
9070
|
const content = execFileSync("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9247
|
-
|
|
9071
|
+
fs32.writeFileSync(filePath, content);
|
|
9248
9072
|
}
|
|
9249
9073
|
} catch {
|
|
9250
9074
|
}
|
|
@@ -9254,7 +9078,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9254
9078
|
} catch {
|
|
9255
9079
|
return null;
|
|
9256
9080
|
} finally {
|
|
9257
|
-
|
|
9081
|
+
fs32.rmSync(tmpDir, { recursive: true, force: true });
|
|
9258
9082
|
}
|
|
9259
9083
|
}
|
|
9260
9084
|
async function scoreCommand(options) {
|
|
@@ -9314,13 +9138,13 @@ async function scoreCommand(options) {
|
|
|
9314
9138
|
}
|
|
9315
9139
|
|
|
9316
9140
|
// src/commands/refresh.ts
|
|
9317
|
-
import
|
|
9318
|
-
import
|
|
9141
|
+
import fs36 from "fs";
|
|
9142
|
+
import path28 from "path";
|
|
9319
9143
|
import chalk19 from "chalk";
|
|
9320
9144
|
import ora6 from "ora";
|
|
9321
9145
|
|
|
9322
9146
|
// src/lib/git-diff.ts
|
|
9323
|
-
import { execSync as
|
|
9147
|
+
import { execSync as execSync13 } from "child_process";
|
|
9324
9148
|
var MAX_DIFF_BYTES = 1e5;
|
|
9325
9149
|
var DOC_PATTERNS = [
|
|
9326
9150
|
"CLAUDE.md",
|
|
@@ -9335,7 +9159,7 @@ function excludeArgs() {
|
|
|
9335
9159
|
}
|
|
9336
9160
|
function safeExec(cmd) {
|
|
9337
9161
|
try {
|
|
9338
|
-
return
|
|
9162
|
+
return execSync13(cmd, {
|
|
9339
9163
|
encoding: "utf-8",
|
|
9340
9164
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9341
9165
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -9393,48 +9217,48 @@ function collectDiff(lastSha) {
|
|
|
9393
9217
|
}
|
|
9394
9218
|
|
|
9395
9219
|
// src/writers/refresh.ts
|
|
9396
|
-
import
|
|
9397
|
-
import
|
|
9220
|
+
import fs33 from "fs";
|
|
9221
|
+
import path25 from "path";
|
|
9398
9222
|
function writeRefreshDocs(docs) {
|
|
9399
9223
|
const written = [];
|
|
9400
9224
|
if (docs.claudeMd) {
|
|
9401
|
-
|
|
9225
|
+
fs33.writeFileSync("CLAUDE.md", appendPreCommitBlock(docs.claudeMd));
|
|
9402
9226
|
written.push("CLAUDE.md");
|
|
9403
9227
|
}
|
|
9404
9228
|
if (docs.readmeMd) {
|
|
9405
|
-
|
|
9229
|
+
fs33.writeFileSync("README.md", docs.readmeMd);
|
|
9406
9230
|
written.push("README.md");
|
|
9407
9231
|
}
|
|
9408
9232
|
if (docs.cursorrules) {
|
|
9409
|
-
|
|
9233
|
+
fs33.writeFileSync(".cursorrules", docs.cursorrules);
|
|
9410
9234
|
written.push(".cursorrules");
|
|
9411
9235
|
}
|
|
9412
9236
|
if (docs.cursorRules) {
|
|
9413
|
-
const rulesDir =
|
|
9414
|
-
if (!
|
|
9237
|
+
const rulesDir = path25.join(".cursor", "rules");
|
|
9238
|
+
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9415
9239
|
for (const rule of docs.cursorRules) {
|
|
9416
|
-
|
|
9240
|
+
fs33.writeFileSync(path25.join(rulesDir, rule.filename), rule.content);
|
|
9417
9241
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
9418
9242
|
}
|
|
9419
9243
|
}
|
|
9420
9244
|
if (docs.claudeSkills) {
|
|
9421
|
-
const skillsDir =
|
|
9422
|
-
if (!
|
|
9245
|
+
const skillsDir = path25.join(".claude", "skills");
|
|
9246
|
+
if (!fs33.existsSync(skillsDir)) fs33.mkdirSync(skillsDir, { recursive: true });
|
|
9423
9247
|
for (const skill of docs.claudeSkills) {
|
|
9424
|
-
|
|
9248
|
+
fs33.writeFileSync(path25.join(skillsDir, skill.filename), skill.content);
|
|
9425
9249
|
written.push(`.claude/skills/${skill.filename}`);
|
|
9426
9250
|
}
|
|
9427
9251
|
}
|
|
9428
9252
|
if (docs.copilotInstructions) {
|
|
9429
|
-
|
|
9430
|
-
|
|
9253
|
+
fs33.mkdirSync(".github", { recursive: true });
|
|
9254
|
+
fs33.writeFileSync(path25.join(".github", "copilot-instructions.md"), appendPreCommitBlock(docs.copilotInstructions));
|
|
9431
9255
|
written.push(".github/copilot-instructions.md");
|
|
9432
9256
|
}
|
|
9433
9257
|
if (docs.copilotInstructionFiles) {
|
|
9434
|
-
const instructionsDir =
|
|
9435
|
-
|
|
9258
|
+
const instructionsDir = path25.join(".github", "instructions");
|
|
9259
|
+
fs33.mkdirSync(instructionsDir, { recursive: true });
|
|
9436
9260
|
for (const file of docs.copilotInstructionFiles) {
|
|
9437
|
-
|
|
9261
|
+
fs33.writeFileSync(path25.join(instructionsDir, file.filename), file.content);
|
|
9438
9262
|
written.push(`.github/instructions/${file.filename}`);
|
|
9439
9263
|
}
|
|
9440
9264
|
}
|
|
@@ -9514,8 +9338,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
9514
9338
|
}
|
|
9515
9339
|
|
|
9516
9340
|
// src/learner/writer.ts
|
|
9517
|
-
import
|
|
9518
|
-
import
|
|
9341
|
+
import fs34 from "fs";
|
|
9342
|
+
import path26 from "path";
|
|
9519
9343
|
|
|
9520
9344
|
// src/learner/utils.ts
|
|
9521
9345
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -9633,20 +9457,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9633
9457
|
}
|
|
9634
9458
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
9635
9459
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
9636
|
-
|
|
9637
|
-
if (mode)
|
|
9460
|
+
fs34.writeFileSync(filePath, header + merged + "\n");
|
|
9461
|
+
if (mode) fs34.chmodSync(filePath, mode);
|
|
9638
9462
|
return { newCount, newItems };
|
|
9639
9463
|
}
|
|
9640
9464
|
function writeLearnedSection(content) {
|
|
9641
9465
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
9642
9466
|
}
|
|
9643
9467
|
function writeLearnedSkill(skill) {
|
|
9644
|
-
const skillDir =
|
|
9645
|
-
if (!
|
|
9646
|
-
const skillPath =
|
|
9647
|
-
if (!skill.isNew &&
|
|
9648
|
-
const existing =
|
|
9649
|
-
|
|
9468
|
+
const skillDir = path26.join(".claude", "skills", skill.name);
|
|
9469
|
+
if (!fs34.existsSync(skillDir)) fs34.mkdirSync(skillDir, { recursive: true });
|
|
9470
|
+
const skillPath = path26.join(skillDir, "SKILL.md");
|
|
9471
|
+
if (!skill.isNew && fs34.existsSync(skillPath)) {
|
|
9472
|
+
const existing = fs34.readFileSync(skillPath, "utf-8");
|
|
9473
|
+
fs34.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
9650
9474
|
} else {
|
|
9651
9475
|
const frontmatter = [
|
|
9652
9476
|
"---",
|
|
@@ -9655,47 +9479,47 @@ function writeLearnedSkill(skill) {
|
|
|
9655
9479
|
"---",
|
|
9656
9480
|
""
|
|
9657
9481
|
].join("\n");
|
|
9658
|
-
|
|
9482
|
+
fs34.writeFileSync(skillPath, frontmatter + skill.content);
|
|
9659
9483
|
}
|
|
9660
9484
|
return skillPath;
|
|
9661
9485
|
}
|
|
9662
9486
|
function writePersonalLearnedSection(content) {
|
|
9663
|
-
if (!
|
|
9487
|
+
if (!fs34.existsSync(AUTH_DIR)) fs34.mkdirSync(AUTH_DIR, { recursive: true });
|
|
9664
9488
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
9665
9489
|
}
|
|
9666
9490
|
function readPersonalLearnings() {
|
|
9667
|
-
if (!
|
|
9668
|
-
const content =
|
|
9491
|
+
if (!fs34.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
9492
|
+
const content = fs34.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
9669
9493
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9670
9494
|
return bullets || null;
|
|
9671
9495
|
}
|
|
9672
9496
|
function readLearnedSection() {
|
|
9673
|
-
if (
|
|
9674
|
-
const content2 =
|
|
9497
|
+
if (fs34.existsSync(LEARNINGS_FILE)) {
|
|
9498
|
+
const content2 = fs34.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
9675
9499
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9676
9500
|
return bullets || null;
|
|
9677
9501
|
}
|
|
9678
9502
|
const claudeMdPath = "CLAUDE.md";
|
|
9679
|
-
if (!
|
|
9680
|
-
const content =
|
|
9503
|
+
if (!fs34.existsSync(claudeMdPath)) return null;
|
|
9504
|
+
const content = fs34.readFileSync(claudeMdPath, "utf-8");
|
|
9681
9505
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9682
9506
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9683
9507
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
9684
9508
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
9685
9509
|
}
|
|
9686
9510
|
function migrateInlineLearnings() {
|
|
9687
|
-
if (
|
|
9511
|
+
if (fs34.existsSync(LEARNINGS_FILE)) return false;
|
|
9688
9512
|
const claudeMdPath = "CLAUDE.md";
|
|
9689
|
-
if (!
|
|
9690
|
-
const content =
|
|
9513
|
+
if (!fs34.existsSync(claudeMdPath)) return false;
|
|
9514
|
+
const content = fs34.readFileSync(claudeMdPath, "utf-8");
|
|
9691
9515
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9692
9516
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9693
9517
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
9694
9518
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
9695
9519
|
if (!section) return false;
|
|
9696
|
-
|
|
9520
|
+
fs34.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
9697
9521
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
9698
|
-
|
|
9522
|
+
fs34.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
9699
9523
|
return true;
|
|
9700
9524
|
}
|
|
9701
9525
|
|
|
@@ -9707,11 +9531,11 @@ function log2(quiet, ...args) {
|
|
|
9707
9531
|
function discoverGitRepos(parentDir) {
|
|
9708
9532
|
const repos = [];
|
|
9709
9533
|
try {
|
|
9710
|
-
const entries =
|
|
9534
|
+
const entries = fs36.readdirSync(parentDir, { withFileTypes: true });
|
|
9711
9535
|
for (const entry of entries) {
|
|
9712
9536
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
9713
|
-
const childPath =
|
|
9714
|
-
if (
|
|
9537
|
+
const childPath = path28.join(parentDir, entry.name);
|
|
9538
|
+
if (fs36.existsSync(path28.join(childPath, ".git"))) {
|
|
9715
9539
|
repos.push(childPath);
|
|
9716
9540
|
}
|
|
9717
9541
|
}
|
|
@@ -9817,7 +9641,7 @@ async function refreshCommand(options) {
|
|
|
9817
9641
|
`));
|
|
9818
9642
|
const originalDir = process.cwd();
|
|
9819
9643
|
for (const repo of repos) {
|
|
9820
|
-
const repoName =
|
|
9644
|
+
const repoName = path28.basename(repo);
|
|
9821
9645
|
try {
|
|
9822
9646
|
process.chdir(repo);
|
|
9823
9647
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -9839,6 +9663,147 @@ async function refreshCommand(options) {
|
|
|
9839
9663
|
// src/commands/hooks.ts
|
|
9840
9664
|
import chalk20 from "chalk";
|
|
9841
9665
|
import fs38 from "fs";
|
|
9666
|
+
|
|
9667
|
+
// src/lib/hooks.ts
|
|
9668
|
+
init_resolve_caliber();
|
|
9669
|
+
import fs37 from "fs";
|
|
9670
|
+
import path29 from "path";
|
|
9671
|
+
import { execSync as execSync14 } from "child_process";
|
|
9672
|
+
var SETTINGS_PATH2 = path29.join(".claude", "settings.json");
|
|
9673
|
+
var REFRESH_TAIL = "refresh --quiet";
|
|
9674
|
+
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
9675
|
+
function getHookCommand() {
|
|
9676
|
+
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
9677
|
+
}
|
|
9678
|
+
function readSettings2() {
|
|
9679
|
+
if (!fs37.existsSync(SETTINGS_PATH2)) return {};
|
|
9680
|
+
try {
|
|
9681
|
+
return JSON.parse(fs37.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
9682
|
+
} catch {
|
|
9683
|
+
return {};
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
function writeSettings2(settings) {
|
|
9687
|
+
const dir = path29.dirname(SETTINGS_PATH2);
|
|
9688
|
+
if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
|
|
9689
|
+
fs37.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
9690
|
+
}
|
|
9691
|
+
function findHookIndex(sessionEnd) {
|
|
9692
|
+
return sessionEnd.findIndex(
|
|
9693
|
+
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
9694
|
+
);
|
|
9695
|
+
}
|
|
9696
|
+
function isHookInstalled() {
|
|
9697
|
+
const settings = readSettings2();
|
|
9698
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
9699
|
+
if (!Array.isArray(sessionEnd)) return false;
|
|
9700
|
+
return findHookIndex(sessionEnd) !== -1;
|
|
9701
|
+
}
|
|
9702
|
+
function installHook() {
|
|
9703
|
+
const settings = readSettings2();
|
|
9704
|
+
if (!settings.hooks) settings.hooks = {};
|
|
9705
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
9706
|
+
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
9707
|
+
return { installed: false, alreadyInstalled: true };
|
|
9708
|
+
}
|
|
9709
|
+
settings.hooks.SessionEnd.push({
|
|
9710
|
+
matcher: "",
|
|
9711
|
+
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
9712
|
+
});
|
|
9713
|
+
writeSettings2(settings);
|
|
9714
|
+
return { installed: true, alreadyInstalled: false };
|
|
9715
|
+
}
|
|
9716
|
+
function removeHook() {
|
|
9717
|
+
const settings = readSettings2();
|
|
9718
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
9719
|
+
if (!Array.isArray(sessionEnd)) {
|
|
9720
|
+
return { removed: false, notFound: true };
|
|
9721
|
+
}
|
|
9722
|
+
const idx = findHookIndex(sessionEnd);
|
|
9723
|
+
if (idx === -1) {
|
|
9724
|
+
return { removed: false, notFound: true };
|
|
9725
|
+
}
|
|
9726
|
+
sessionEnd.splice(idx, 1);
|
|
9727
|
+
if (sessionEnd.length === 0) {
|
|
9728
|
+
delete settings.hooks.SessionEnd;
|
|
9729
|
+
}
|
|
9730
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
9731
|
+
delete settings.hooks;
|
|
9732
|
+
}
|
|
9733
|
+
writeSettings2(settings);
|
|
9734
|
+
return { removed: true, notFound: false };
|
|
9735
|
+
}
|
|
9736
|
+
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
9737
|
+
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
9738
|
+
function getPrecommitBlock() {
|
|
9739
|
+
const bin = resolveCaliber();
|
|
9740
|
+
return `${PRECOMMIT_START}
|
|
9741
|
+
if [ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1; then
|
|
9742
|
+
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
9743
|
+
"${bin}" refresh 2>/dev/null || true
|
|
9744
|
+
"${bin}" learn finalize 2>/dev/null || true
|
|
9745
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
9746
|
+
fi
|
|
9747
|
+
${PRECOMMIT_END}`;
|
|
9748
|
+
}
|
|
9749
|
+
function getGitHooksDir() {
|
|
9750
|
+
try {
|
|
9751
|
+
const gitDir = execSync14("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
9752
|
+
return path29.join(gitDir, "hooks");
|
|
9753
|
+
} catch {
|
|
9754
|
+
return null;
|
|
9755
|
+
}
|
|
9756
|
+
}
|
|
9757
|
+
function getPreCommitPath() {
|
|
9758
|
+
const hooksDir = getGitHooksDir();
|
|
9759
|
+
return hooksDir ? path29.join(hooksDir, "pre-commit") : null;
|
|
9760
|
+
}
|
|
9761
|
+
function isPreCommitHookInstalled() {
|
|
9762
|
+
const hookPath = getPreCommitPath();
|
|
9763
|
+
if (!hookPath || !fs37.existsSync(hookPath)) return false;
|
|
9764
|
+
const content = fs37.readFileSync(hookPath, "utf-8");
|
|
9765
|
+
return content.includes(PRECOMMIT_START);
|
|
9766
|
+
}
|
|
9767
|
+
function installPreCommitHook() {
|
|
9768
|
+
if (isPreCommitHookInstalled()) {
|
|
9769
|
+
return { installed: false, alreadyInstalled: true };
|
|
9770
|
+
}
|
|
9771
|
+
const hookPath = getPreCommitPath();
|
|
9772
|
+
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
9773
|
+
const hooksDir = path29.dirname(hookPath);
|
|
9774
|
+
if (!fs37.existsSync(hooksDir)) fs37.mkdirSync(hooksDir, { recursive: true });
|
|
9775
|
+
let content = "";
|
|
9776
|
+
if (fs37.existsSync(hookPath)) {
|
|
9777
|
+
content = fs37.readFileSync(hookPath, "utf-8");
|
|
9778
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
9779
|
+
content += "\n" + getPrecommitBlock() + "\n";
|
|
9780
|
+
} else {
|
|
9781
|
+
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
9782
|
+
}
|
|
9783
|
+
fs37.writeFileSync(hookPath, content);
|
|
9784
|
+
fs37.chmodSync(hookPath, 493);
|
|
9785
|
+
return { installed: true, alreadyInstalled: false };
|
|
9786
|
+
}
|
|
9787
|
+
function removePreCommitHook() {
|
|
9788
|
+
const hookPath = getPreCommitPath();
|
|
9789
|
+
if (!hookPath || !fs37.existsSync(hookPath)) {
|
|
9790
|
+
return { removed: false, notFound: true };
|
|
9791
|
+
}
|
|
9792
|
+
let content = fs37.readFileSync(hookPath, "utf-8");
|
|
9793
|
+
if (!content.includes(PRECOMMIT_START)) {
|
|
9794
|
+
return { removed: false, notFound: true };
|
|
9795
|
+
}
|
|
9796
|
+
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
9797
|
+
content = content.replace(regex, "\n");
|
|
9798
|
+
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
9799
|
+
fs37.unlinkSync(hookPath);
|
|
9800
|
+
} else {
|
|
9801
|
+
fs37.writeFileSync(hookPath, content);
|
|
9802
|
+
}
|
|
9803
|
+
return { removed: true, notFound: false };
|
|
9804
|
+
}
|
|
9805
|
+
|
|
9806
|
+
// src/commands/hooks.ts
|
|
9842
9807
|
var HOOKS = [
|
|
9843
9808
|
{
|
|
9844
9809
|
id: "session-end",
|
|
@@ -9869,6 +9834,10 @@ function printStatus() {
|
|
|
9869
9834
|
console.log("");
|
|
9870
9835
|
}
|
|
9871
9836
|
async function hooksCommand(options) {
|
|
9837
|
+
if (!options.install && !options.remove) {
|
|
9838
|
+
console.log(chalk20.dim("\n Note: caliber now adds refresh instructions directly to config files."));
|
|
9839
|
+
console.log(chalk20.dim(" These hooks are available for non-agent workflows (manual commits).\n"));
|
|
9840
|
+
}
|
|
9872
9841
|
if (options.install) {
|
|
9873
9842
|
for (const hook of HOOKS) {
|
|
9874
9843
|
const result = hook.install();
|