@rely-ai/caliber 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/bin.js +169 -116
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,8 +43,10 @@ If you already have these files, Caliber audits them against your actual codebas
|
|
|
43
43
|
| `caliber recommend` | Discover skills from [skills.sh](https://skills.sh) |
|
|
44
44
|
| `caliber undo` | Revert all changes made by Caliber |
|
|
45
45
|
| `caliber status` | Show current setup status |
|
|
46
|
-
| `caliber hooks install` | Install auto-refresh hook
|
|
47
|
-
| `caliber hooks remove` | Remove auto-refresh hook |
|
|
46
|
+
| `caliber hooks install` | Install Claude Code auto-refresh hook |
|
|
47
|
+
| `caliber hooks remove` | Remove Claude Code auto-refresh hook |
|
|
48
|
+
| `caliber hooks install-precommit` | Install git pre-commit hook for auto-refresh |
|
|
49
|
+
| `caliber hooks remove-precommit` | Remove git pre-commit hook |
|
|
48
50
|
| `caliber hooks status` | Show installed hooks |
|
|
49
51
|
| `caliber learn install` | Install session learning hooks |
|
|
50
52
|
| `caliber learn status` | Show learned insights from sessions |
|
|
@@ -109,11 +111,19 @@ During `caliber init`, a before/after score is displayed so you can see the impr
|
|
|
109
111
|
|
|
110
112
|
### Auto-refresh
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
During `caliber init`, you'll be prompted to choose how docs auto-refresh:
|
|
115
|
+
|
|
116
|
+
- **Claude Code hook** — refreshes docs when Claude Code sessions end
|
|
117
|
+
- **Git pre-commit hook** — refreshes docs before each commit
|
|
118
|
+
- **Both** — enables both hooks
|
|
119
|
+
- **Skip** — install later with `caliber hooks install` or `caliber hooks install-precommit`
|
|
113
120
|
|
|
114
121
|
```bash
|
|
115
|
-
caliber hooks install
|
|
116
|
-
caliber hooks
|
|
122
|
+
caliber hooks install # Install Claude Code hook
|
|
123
|
+
caliber hooks install-precommit # Install git pre-commit hook
|
|
124
|
+
caliber hooks remove # Remove Claude Code hook
|
|
125
|
+
caliber hooks remove-precommit # Remove pre-commit hook
|
|
126
|
+
caliber hooks status # Show installed hooks
|
|
117
127
|
```
|
|
118
128
|
|
|
119
129
|
### Session Learning
|
package/dist/bin.js
CHANGED
|
@@ -95,98 +95,21 @@ function isGitRepo() {
|
|
|
95
95
|
import fs from "fs";
|
|
96
96
|
import path from "path";
|
|
97
97
|
import { globSync } from "glob";
|
|
98
|
-
var NODE_FRAMEWORK_DEPS = {
|
|
99
|
-
react: "React",
|
|
100
|
-
next: "Next.js",
|
|
101
|
-
vue: "Vue",
|
|
102
|
-
nuxt: "Nuxt",
|
|
103
|
-
svelte: "Svelte",
|
|
104
|
-
"@sveltejs/kit": "SvelteKit",
|
|
105
|
-
angular: "Angular",
|
|
106
|
-
"@angular/core": "Angular",
|
|
107
|
-
express: "Express",
|
|
108
|
-
fastify: "Fastify",
|
|
109
|
-
hono: "Hono",
|
|
110
|
-
nestjs: "NestJS",
|
|
111
|
-
"@nestjs/core": "NestJS",
|
|
112
|
-
tailwindcss: "Tailwind CSS",
|
|
113
|
-
prisma: "Prisma",
|
|
114
|
-
drizzle: "Drizzle",
|
|
115
|
-
"drizzle-orm": "Drizzle",
|
|
116
|
-
"@supabase/supabase-js": "Supabase",
|
|
117
|
-
mongoose: "MongoDB",
|
|
118
|
-
typeorm: "TypeORM",
|
|
119
|
-
sequelize: "Sequelize",
|
|
120
|
-
"better-auth": "Better Auth"
|
|
121
|
-
};
|
|
122
|
-
var PYTHON_FRAMEWORK_DEPS = {
|
|
123
|
-
fastapi: "FastAPI",
|
|
124
|
-
django: "Django",
|
|
125
|
-
flask: "Flask",
|
|
126
|
-
sqlalchemy: "SQLAlchemy",
|
|
127
|
-
pydantic: "Pydantic",
|
|
128
|
-
celery: "Celery",
|
|
129
|
-
pytest: "pytest",
|
|
130
|
-
uvicorn: "Uvicorn",
|
|
131
|
-
starlette: "Starlette",
|
|
132
|
-
httpx: "HTTPX",
|
|
133
|
-
alembic: "Alembic",
|
|
134
|
-
tortoise: "Tortoise ORM",
|
|
135
|
-
"google-cloud-pubsub": "Google Pub/Sub",
|
|
136
|
-
stripe: "Stripe",
|
|
137
|
-
redis: "Redis"
|
|
138
|
-
};
|
|
139
98
|
var WORKSPACE_GLOBS = [
|
|
140
99
|
"apps/*/package.json",
|
|
141
100
|
"packages/*/package.json",
|
|
142
101
|
"services/*/package.json",
|
|
143
102
|
"libs/*/package.json"
|
|
144
103
|
];
|
|
145
|
-
function detectNodeFrameworks(pkgPath) {
|
|
146
|
-
try {
|
|
147
|
-
const pkg3 = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
148
|
-
const allDeps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
149
|
-
const frameworks = [];
|
|
150
|
-
for (const [dep, framework] of Object.entries(NODE_FRAMEWORK_DEPS)) {
|
|
151
|
-
if (allDeps[dep]) frameworks.push(framework);
|
|
152
|
-
}
|
|
153
|
-
return frameworks;
|
|
154
|
-
} catch {
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function detectPythonFrameworks(dir) {
|
|
159
|
-
const frameworks = [];
|
|
160
|
-
const candidates = [
|
|
161
|
-
path.join(dir, "pyproject.toml"),
|
|
162
|
-
path.join(dir, "requirements.txt"),
|
|
163
|
-
...globSync("apps/*/pyproject.toml", { cwd: dir, absolute: true }),
|
|
164
|
-
...globSync("apps/*/requirements.txt", { cwd: dir, absolute: true }),
|
|
165
|
-
...globSync("services/*/pyproject.toml", { cwd: dir, absolute: true })
|
|
166
|
-
];
|
|
167
|
-
for (const filePath of candidates) {
|
|
168
|
-
if (!fs.existsSync(filePath)) continue;
|
|
169
|
-
try {
|
|
170
|
-
const content = fs.readFileSync(filePath, "utf-8").toLowerCase();
|
|
171
|
-
for (const [dep, framework] of Object.entries(PYTHON_FRAMEWORK_DEPS)) {
|
|
172
|
-
if (content.includes(dep)) frameworks.push(framework);
|
|
173
|
-
}
|
|
174
|
-
} catch {
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return frameworks;
|
|
178
|
-
}
|
|
179
104
|
function analyzePackageJson(dir) {
|
|
180
105
|
const rootPkgPath = path.join(dir, "package.json");
|
|
181
106
|
let name;
|
|
182
|
-
const allFrameworks = [];
|
|
183
107
|
const languages = [];
|
|
184
108
|
if (fs.existsSync(rootPkgPath)) {
|
|
185
109
|
try {
|
|
186
110
|
const pkg3 = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
187
111
|
name = pkg3.name;
|
|
188
112
|
const allDeps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
189
|
-
allFrameworks.push(...detectNodeFrameworks(rootPkgPath));
|
|
190
113
|
if (allDeps.typescript || allDeps["@types/node"]) {
|
|
191
114
|
languages.push("TypeScript");
|
|
192
115
|
}
|
|
@@ -197,7 +120,6 @@ function analyzePackageJson(dir) {
|
|
|
197
120
|
for (const glob of WORKSPACE_GLOBS) {
|
|
198
121
|
const matches = globSync(glob, { cwd: dir, absolute: true });
|
|
199
122
|
for (const pkgPath of matches) {
|
|
200
|
-
allFrameworks.push(...detectNodeFrameworks(pkgPath));
|
|
201
123
|
try {
|
|
202
124
|
const pkg3 = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
203
125
|
const deps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
@@ -208,10 +130,8 @@ function analyzePackageJson(dir) {
|
|
|
208
130
|
}
|
|
209
131
|
}
|
|
210
132
|
}
|
|
211
|
-
allFrameworks.push(...detectPythonFrameworks(dir));
|
|
212
133
|
return {
|
|
213
134
|
name,
|
|
214
|
-
frameworks: [...new Set(allFrameworks)],
|
|
215
135
|
languages: [...new Set(languages)]
|
|
216
136
|
};
|
|
217
137
|
}
|
|
@@ -1277,7 +1197,7 @@ function collectFingerprint(dir) {
|
|
|
1277
1197
|
gitRemoteUrl,
|
|
1278
1198
|
packageName: pkgInfo.name,
|
|
1279
1199
|
languages,
|
|
1280
|
-
frameworks:
|
|
1200
|
+
frameworks: [],
|
|
1281
1201
|
fileTree,
|
|
1282
1202
|
existingConfigs,
|
|
1283
1203
|
codeAnalysis
|
|
@@ -1937,6 +1857,7 @@ import { createTwoFilesPatch } from "diff";
|
|
|
1937
1857
|
// src/lib/hooks.ts
|
|
1938
1858
|
import fs14 from "fs";
|
|
1939
1859
|
import path13 from "path";
|
|
1860
|
+
import { execSync as execSync3 } from "child_process";
|
|
1940
1861
|
var SETTINGS_PATH = path13.join(".claude", "settings.json");
|
|
1941
1862
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
1942
1863
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
@@ -1998,6 +1919,70 @@ function removeHook() {
|
|
|
1998
1919
|
writeSettings(settings);
|
|
1999
1920
|
return { removed: true, notFound: false };
|
|
2000
1921
|
}
|
|
1922
|
+
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
1923
|
+
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
1924
|
+
var PRECOMMIT_BLOCK = `${PRECOMMIT_START}
|
|
1925
|
+
if command -v caliber >/dev/null 2>&1; then
|
|
1926
|
+
caliber refresh --quiet 2>/dev/null || true
|
|
1927
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
1928
|
+
fi
|
|
1929
|
+
${PRECOMMIT_END}`;
|
|
1930
|
+
function getGitHooksDir() {
|
|
1931
|
+
try {
|
|
1932
|
+
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1933
|
+
return path13.join(gitDir, "hooks");
|
|
1934
|
+
} catch {
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
function getPreCommitPath() {
|
|
1939
|
+
const hooksDir = getGitHooksDir();
|
|
1940
|
+
return hooksDir ? path13.join(hooksDir, "pre-commit") : null;
|
|
1941
|
+
}
|
|
1942
|
+
function isPreCommitHookInstalled() {
|
|
1943
|
+
const hookPath = getPreCommitPath();
|
|
1944
|
+
if (!hookPath || !fs14.existsSync(hookPath)) return false;
|
|
1945
|
+
const content = fs14.readFileSync(hookPath, "utf-8");
|
|
1946
|
+
return content.includes(PRECOMMIT_START);
|
|
1947
|
+
}
|
|
1948
|
+
function installPreCommitHook() {
|
|
1949
|
+
if (isPreCommitHookInstalled()) {
|
|
1950
|
+
return { installed: false, alreadyInstalled: true };
|
|
1951
|
+
}
|
|
1952
|
+
const hookPath = getPreCommitPath();
|
|
1953
|
+
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
1954
|
+
const hooksDir = path13.dirname(hookPath);
|
|
1955
|
+
if (!fs14.existsSync(hooksDir)) fs14.mkdirSync(hooksDir, { recursive: true });
|
|
1956
|
+
let content = "";
|
|
1957
|
+
if (fs14.existsSync(hookPath)) {
|
|
1958
|
+
content = fs14.readFileSync(hookPath, "utf-8");
|
|
1959
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
1960
|
+
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
1961
|
+
} else {
|
|
1962
|
+
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
1963
|
+
}
|
|
1964
|
+
fs14.writeFileSync(hookPath, content);
|
|
1965
|
+
fs14.chmodSync(hookPath, 493);
|
|
1966
|
+
return { installed: true, alreadyInstalled: false };
|
|
1967
|
+
}
|
|
1968
|
+
function removePreCommitHook() {
|
|
1969
|
+
const hookPath = getPreCommitPath();
|
|
1970
|
+
if (!hookPath || !fs14.existsSync(hookPath)) {
|
|
1971
|
+
return { removed: false, notFound: true };
|
|
1972
|
+
}
|
|
1973
|
+
let content = fs14.readFileSync(hookPath, "utf-8");
|
|
1974
|
+
if (!content.includes(PRECOMMIT_START)) {
|
|
1975
|
+
return { removed: false, notFound: true };
|
|
1976
|
+
}
|
|
1977
|
+
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
1978
|
+
content = content.replace(regex, "\n");
|
|
1979
|
+
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
1980
|
+
fs14.unlinkSync(hookPath);
|
|
1981
|
+
} else {
|
|
1982
|
+
fs14.writeFileSync(hookPath, content);
|
|
1983
|
+
}
|
|
1984
|
+
return { removed: true, notFound: false };
|
|
1985
|
+
}
|
|
2001
1986
|
|
|
2002
1987
|
// src/lib/learning-hooks.ts
|
|
2003
1988
|
import fs15 from "fs";
|
|
@@ -2090,7 +2075,7 @@ function removeLearningHooks() {
|
|
|
2090
2075
|
init_constants();
|
|
2091
2076
|
import fs16 from "fs";
|
|
2092
2077
|
import path15 from "path";
|
|
2093
|
-
import { execSync as
|
|
2078
|
+
import { execSync as execSync4 } from "child_process";
|
|
2094
2079
|
var STATE_FILE = path15.join(CALIBER_DIR, ".caliber-state.json");
|
|
2095
2080
|
function readState() {
|
|
2096
2081
|
try {
|
|
@@ -2108,7 +2093,7 @@ function writeState(state) {
|
|
|
2108
2093
|
}
|
|
2109
2094
|
function getCurrentHeadSha() {
|
|
2110
2095
|
try {
|
|
2111
|
-
return
|
|
2096
|
+
return execSync4("git rev-parse HEAD", {
|
|
2112
2097
|
encoding: "utf-8",
|
|
2113
2098
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2114
2099
|
}).trim();
|
|
@@ -3334,17 +3319,26 @@ function displayScoreDelta(before, after) {
|
|
|
3334
3319
|
const deltaColor = delta >= 0 ? chalk2.green : chalk2.red;
|
|
3335
3320
|
const beforeGc = gradeColor(before.grade);
|
|
3336
3321
|
const afterGc = gradeColor(after.grade);
|
|
3322
|
+
const BOX_INNER = 51;
|
|
3323
|
+
const scorePart = `Score: ${before.score} > ${after.score}`;
|
|
3324
|
+
const deltaPart = `${deltaStr} pts`;
|
|
3325
|
+
const gradePart = `${before.grade} > ${after.grade}`;
|
|
3326
|
+
const contentLen = 3 + scorePart.length + deltaPart.length + gradePart.length + 8;
|
|
3327
|
+
const totalPad = BOX_INNER - contentLen;
|
|
3328
|
+
const pad1 = Math.max(2, Math.ceil(totalPad / 2));
|
|
3329
|
+
const pad2 = Math.max(1, totalPad - pad1);
|
|
3330
|
+
const scoreLineFormatted = " Score: " + beforeGc(`${before.score}`) + chalk2.gray(" \u2192 ") + afterGc(`${after.score}`) + " ".repeat(pad1) + deltaColor(deltaPart) + " ".repeat(pad2) + beforeGc(before.grade) + chalk2.gray(" \u2192 ") + afterGc(after.grade);
|
|
3331
|
+
const visibleLen = 3 + scorePart.length + pad1 + deltaPart.length + pad2 + gradePart.length;
|
|
3332
|
+
const trailingPad = Math.max(0, BOX_INNER - visibleLen);
|
|
3333
|
+
const barWidth = Math.floor((BOX_INNER - 12) / 2);
|
|
3334
|
+
const barLine = ` ${progressBar(before.score, before.maxScore, barWidth)}` + chalk2.gray(" \u2192 ") + progressBar(after.score, after.maxScore, barWidth) + " ";
|
|
3337
3335
|
console.log("");
|
|
3338
|
-
console.log(chalk2.gray(" \u256D\u2500\
|
|
3339
|
-
console.log(chalk2.gray(" \u2502") + "
|
|
3340
|
-
console.log(
|
|
3341
|
-
|
|
3342
|
-
);
|
|
3343
|
-
console.log(
|
|
3344
|
-
chalk2.gray(" \u2502") + ` ${progressBar(before.score, before.maxScore, 18)}` + chalk2.gray(" \u2192 ") + `${progressBar(after.score, after.maxScore, 18)} ` + chalk2.gray("\u2502")
|
|
3345
|
-
);
|
|
3346
|
-
console.log(chalk2.gray(" \u2502") + " " + chalk2.gray("\u2502"));
|
|
3347
|
-
console.log(chalk2.gray(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
3336
|
+
console.log(chalk2.gray(" \u256D" + "\u2500".repeat(BOX_INNER) + "\u256E"));
|
|
3337
|
+
console.log(chalk2.gray(" \u2502") + " ".repeat(BOX_INNER) + chalk2.gray("\u2502"));
|
|
3338
|
+
console.log(chalk2.gray(" \u2502") + scoreLineFormatted + " ".repeat(trailingPad) + chalk2.gray("\u2502"));
|
|
3339
|
+
console.log(chalk2.gray(" \u2502") + barLine + chalk2.gray("\u2502"));
|
|
3340
|
+
console.log(chalk2.gray(" \u2502") + " ".repeat(BOX_INNER) + chalk2.gray("\u2502"));
|
|
3341
|
+
console.log(chalk2.gray(" \u2570" + "\u2500".repeat(BOX_INNER) + "\u256F"));
|
|
3348
3342
|
console.log("");
|
|
3349
3343
|
const improved = after.checks.filter((ac) => {
|
|
3350
3344
|
const bc = before.checks.find((b) => b.id === ac.id);
|
|
@@ -3401,10 +3395,9 @@ async function initCommand(options) {
|
|
|
3401
3395
|
console.log(chalk3.dim(" Detecting languages, frameworks, file structure, and existing configs.\n"));
|
|
3402
3396
|
const spinner = ora("Analyzing project...").start();
|
|
3403
3397
|
const fingerprint = collectFingerprint(process.cwd());
|
|
3398
|
+
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
3404
3399
|
spinner.succeed("Project analyzed");
|
|
3405
|
-
const enrichmentPromise = enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
3406
3400
|
console.log(chalk3.dim(` Languages: ${fingerprint.languages.join(", ") || "none detected"}`));
|
|
3407
|
-
console.log(chalk3.dim(` Frameworks: ${fingerprint.frameworks.join(", ") || "none detected"}`));
|
|
3408
3401
|
console.log(chalk3.dim(` Files: ${fingerprint.fileTree.length} found
|
|
3409
3402
|
`));
|
|
3410
3403
|
const targetAgent = options.agent || await promptAgent();
|
|
@@ -3413,7 +3406,6 @@ async function initCommand(options) {
|
|
|
3413
3406
|
if (isEmpty) {
|
|
3414
3407
|
fingerprint.description = await promptInput("What will you build in this project?");
|
|
3415
3408
|
}
|
|
3416
|
-
await enrichmentPromise;
|
|
3417
3409
|
console.log(chalk3.hex("#6366f1").bold(" Step 3/4 \u2014 Auditing your configs\n"));
|
|
3418
3410
|
console.log(chalk3.dim(" AI is auditing your CLAUDE.md, skills, and rules against your"));
|
|
3419
3411
|
console.log(chalk3.dim(" project's actual codebase and conventions.\n"));
|
|
@@ -3543,13 +3535,14 @@ async function initCommand(options) {
|
|
|
3543
3535
|
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3544
3536
|
targetAgent
|
|
3545
3537
|
});
|
|
3546
|
-
|
|
3538
|
+
const hookChoice = await promptHookType(targetAgent);
|
|
3539
|
+
if (hookChoice === "claude" || hookChoice === "both") {
|
|
3547
3540
|
const hookResult = installHook();
|
|
3548
3541
|
if (hookResult.installed) {
|
|
3549
|
-
console.log(` ${chalk3.green("\u2713")}
|
|
3542
|
+
console.log(` ${chalk3.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
3550
3543
|
console.log(chalk3.dim(" Run `caliber hooks remove` to disable"));
|
|
3551
3544
|
} else if (hookResult.alreadyInstalled) {
|
|
3552
|
-
console.log(chalk3.dim("
|
|
3545
|
+
console.log(chalk3.dim(" Claude Code hook already installed"));
|
|
3553
3546
|
}
|
|
3554
3547
|
const learnResult = installLearningHooks();
|
|
3555
3548
|
if (learnResult.installed) {
|
|
@@ -3559,6 +3552,20 @@ async function initCommand(options) {
|
|
|
3559
3552
|
console.log(chalk3.dim(" Learning hooks already installed"));
|
|
3560
3553
|
}
|
|
3561
3554
|
}
|
|
3555
|
+
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
3556
|
+
const precommitResult = installPreCommitHook();
|
|
3557
|
+
if (precommitResult.installed) {
|
|
3558
|
+
console.log(` ${chalk3.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
3559
|
+
console.log(chalk3.dim(" Run `caliber hooks remove-precommit` to disable"));
|
|
3560
|
+
} else if (precommitResult.alreadyInstalled) {
|
|
3561
|
+
console.log(chalk3.dim(" Pre-commit hook already installed"));
|
|
3562
|
+
} else {
|
|
3563
|
+
console.log(chalk3.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
if (hookChoice === "skip") {
|
|
3567
|
+
console.log(chalk3.dim(" Skipped auto-refresh hooks. Run `caliber hooks install` later to enable."));
|
|
3568
|
+
}
|
|
3562
3569
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
3563
3570
|
displayScoreDelta(baselineScore, afterScore);
|
|
3564
3571
|
console.log(chalk3.bold.green(" Setup complete! Your coding agent is now configured."));
|
|
@@ -3618,6 +3625,21 @@ async function promptAgent() {
|
|
|
3618
3625
|
]
|
|
3619
3626
|
});
|
|
3620
3627
|
}
|
|
3628
|
+
async function promptHookType(targetAgent) {
|
|
3629
|
+
const choices = [];
|
|
3630
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
3631
|
+
choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
|
|
3632
|
+
}
|
|
3633
|
+
choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
|
|
3634
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
3635
|
+
choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
|
|
3636
|
+
}
|
|
3637
|
+
choices.push({ name: "Skip for now", value: "skip" });
|
|
3638
|
+
return select({
|
|
3639
|
+
message: "How would you like to auto-refresh your docs?",
|
|
3640
|
+
choices
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3621
3643
|
async function promptWantsReview() {
|
|
3622
3644
|
const answer = await select({
|
|
3623
3645
|
message: "Would you like to review the diffs before deciding?",
|
|
@@ -4328,7 +4350,7 @@ import chalk9 from "chalk";
|
|
|
4328
4350
|
import ora5 from "ora";
|
|
4329
4351
|
|
|
4330
4352
|
// src/lib/git-diff.ts
|
|
4331
|
-
import { execSync as
|
|
4353
|
+
import { execSync as execSync5 } from "child_process";
|
|
4332
4354
|
var MAX_DIFF_BYTES = 1e5;
|
|
4333
4355
|
var DOC_PATTERNS = [
|
|
4334
4356
|
"CLAUDE.md",
|
|
@@ -4342,7 +4364,7 @@ function excludeArgs() {
|
|
|
4342
4364
|
}
|
|
4343
4365
|
function safeExec(cmd) {
|
|
4344
4366
|
try {
|
|
4345
|
-
return
|
|
4367
|
+
return execSync5(cmd, {
|
|
4346
4368
|
encoding: "utf-8",
|
|
4347
4369
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4348
4370
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -4628,7 +4650,7 @@ import chalk10 from "chalk";
|
|
|
4628
4650
|
async function hooksInstallCommand() {
|
|
4629
4651
|
const result = installHook();
|
|
4630
4652
|
if (result.alreadyInstalled) {
|
|
4631
|
-
console.log(chalk10.dim("
|
|
4653
|
+
console.log(chalk10.dim("Claude Code hook already installed."));
|
|
4632
4654
|
return;
|
|
4633
4655
|
}
|
|
4634
4656
|
console.log(chalk10.green("\u2713") + " SessionEnd hook installed in .claude/settings.json");
|
|
@@ -4637,18 +4659,47 @@ async function hooksInstallCommand() {
|
|
|
4637
4659
|
async function hooksRemoveCommand() {
|
|
4638
4660
|
const result = removeHook();
|
|
4639
4661
|
if (result.notFound) {
|
|
4640
|
-
console.log(chalk10.dim("
|
|
4662
|
+
console.log(chalk10.dim("Claude Code hook not found."));
|
|
4641
4663
|
return;
|
|
4642
4664
|
}
|
|
4643
4665
|
console.log(chalk10.green("\u2713") + " SessionEnd hook removed from .claude/settings.json");
|
|
4644
4666
|
}
|
|
4667
|
+
async function hooksInstallPrecommitCommand() {
|
|
4668
|
+
const result = installPreCommitHook();
|
|
4669
|
+
if (result.alreadyInstalled) {
|
|
4670
|
+
console.log(chalk10.dim("Pre-commit hook already installed."));
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
if (!result.installed) {
|
|
4674
|
+
console.log(chalk10.red("Failed to install pre-commit hook (not a git repository?)."));
|
|
4675
|
+
return;
|
|
4676
|
+
}
|
|
4677
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook installed in .git/hooks/pre-commit");
|
|
4678
|
+
console.log(chalk10.dim(" Docs will auto-refresh before each commit via LLM."));
|
|
4679
|
+
}
|
|
4680
|
+
async function hooksRemovePrecommitCommand() {
|
|
4681
|
+
const result = removePreCommitHook();
|
|
4682
|
+
if (result.notFound) {
|
|
4683
|
+
console.log(chalk10.dim("Pre-commit hook not found."));
|
|
4684
|
+
return;
|
|
4685
|
+
}
|
|
4686
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook removed from .git/hooks/pre-commit");
|
|
4687
|
+
}
|
|
4645
4688
|
async function hooksStatusCommand() {
|
|
4646
|
-
const
|
|
4647
|
-
|
|
4648
|
-
|
|
4689
|
+
const claudeInstalled = isHookInstalled();
|
|
4690
|
+
const precommitInstalled = isPreCommitHookInstalled();
|
|
4691
|
+
if (claudeInstalled) {
|
|
4692
|
+
console.log(chalk10.green("\u2713") + " Claude Code hook is " + chalk10.green("installed"));
|
|
4649
4693
|
} else {
|
|
4650
|
-
console.log(chalk10.dim("\u2717") + "
|
|
4651
|
-
|
|
4694
|
+
console.log(chalk10.dim("\u2717") + " Claude Code hook is " + chalk10.yellow("not installed"));
|
|
4695
|
+
}
|
|
4696
|
+
if (precommitInstalled) {
|
|
4697
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook is " + chalk10.green("installed"));
|
|
4698
|
+
} else {
|
|
4699
|
+
console.log(chalk10.dim("\u2717") + " Pre-commit hook is " + chalk10.yellow("not installed"));
|
|
4700
|
+
}
|
|
4701
|
+
if (!claudeInstalled && !precommitInstalled) {
|
|
4702
|
+
console.log(chalk10.dim("\n Run `caliber hooks install` or `caliber hooks install-precommit` to enable auto-refresh."));
|
|
4652
4703
|
}
|
|
4653
4704
|
}
|
|
4654
4705
|
|
|
@@ -5095,10 +5146,12 @@ program.command("config").description("Configure LLM provider, API key, and mode
|
|
|
5095
5146
|
program.command("recommend").description("Discover and install skill recommendations").option("--generate", "Force fresh recommendation search").action(recommendCommand);
|
|
5096
5147
|
program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agent: claude, cursor, or both").action(scoreCommand);
|
|
5097
5148
|
program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
|
|
5098
|
-
var hooks = program.command("hooks").description("Manage Claude Code
|
|
5099
|
-
hooks.command("install").description("Install auto-refresh
|
|
5100
|
-
hooks.command("remove").description("Remove auto-refresh
|
|
5101
|
-
hooks.command("
|
|
5149
|
+
var hooks = program.command("hooks").description("Manage auto-refresh hooks (Claude Code and git pre-commit)");
|
|
5150
|
+
hooks.command("install").description("Install Claude Code SessionEnd auto-refresh hook").action(hooksInstallCommand);
|
|
5151
|
+
hooks.command("remove").description("Remove Claude Code SessionEnd auto-refresh hook").action(hooksRemoveCommand);
|
|
5152
|
+
hooks.command("install-precommit").description("Install git pre-commit hook for auto-refresh").action(hooksInstallPrecommitCommand);
|
|
5153
|
+
hooks.command("remove-precommit").description("Remove git pre-commit hook").action(hooksRemovePrecommitCommand);
|
|
5154
|
+
hooks.command("status").description("Check installed hooks status").action(hooksStatusCommand);
|
|
5102
5155
|
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
5103
5156
|
learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").action(learnObserveCommand);
|
|
5104
5157
|
learn.command("finalize").description("Analyze session events and update CLAUDE.md (called on SessionEnd)").action(learnFinalizeCommand);
|
|
@@ -5110,7 +5163,7 @@ learn.command("status").description("Show learning system status").action(learnS
|
|
|
5110
5163
|
import fs25 from "fs";
|
|
5111
5164
|
import path22 from "path";
|
|
5112
5165
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5113
|
-
import { execSync as
|
|
5166
|
+
import { execSync as execSync6 } from "child_process";
|
|
5114
5167
|
import chalk13 from "chalk";
|
|
5115
5168
|
import ora6 from "ora";
|
|
5116
5169
|
import confirm2 from "@inquirer/confirm";
|
|
@@ -5120,7 +5173,7 @@ var pkg2 = JSON.parse(
|
|
|
5120
5173
|
);
|
|
5121
5174
|
function getInstalledVersion() {
|
|
5122
5175
|
try {
|
|
5123
|
-
const globalRoot =
|
|
5176
|
+
const globalRoot = execSync6("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5124
5177
|
const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
5125
5178
|
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
5126
5179
|
} catch {
|
|
@@ -5165,7 +5218,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
5165
5218
|
}
|
|
5166
5219
|
const spinner = ora6("Updating caliber...").start();
|
|
5167
5220
|
try {
|
|
5168
|
-
|
|
5221
|
+
execSync6(`npm install -g @rely-ai/caliber@${latest} --prefer-online`, { stdio: "pipe", timeout: 6e4 });
|
|
5169
5222
|
const installed = getInstalledVersion();
|
|
5170
5223
|
if (installed !== latest) {
|
|
5171
5224
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
@@ -5178,7 +5231,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
5178
5231
|
console.log(chalk13.dim(`
|
|
5179
5232
|
Restarting: caliber ${args.join(" ")}
|
|
5180
5233
|
`));
|
|
5181
|
-
|
|
5234
|
+
execSync6(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
5182
5235
|
stdio: "inherit",
|
|
5183
5236
|
env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
|
|
5184
5237
|
});
|
package/package.json
CHANGED