@rely-ai/caliber 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/bin.js +201 -131
- 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);
|
|
@@ -3380,10 +3374,10 @@ async function initCommand(options) {
|
|
|
3380
3374
|
console.log(chalk3.dim(" against your actual codebase \u2014 keeping what works, fixing"));
|
|
3381
3375
|
console.log(chalk3.dim(" what's stale, and adding what's missing.\n"));
|
|
3382
3376
|
console.log(chalk3.bold(" How it works:\n"));
|
|
3383
|
-
console.log(chalk3.dim(" 1. Scan Analyze your code, dependencies, and
|
|
3384
|
-
console.log(chalk3.dim(" 2. Generate AI creates config files
|
|
3385
|
-
console.log(chalk3.dim(" 3. Review You accept, refine, or decline the
|
|
3386
|
-
console.log(chalk3.dim(" 4. Apply Config files are written
|
|
3377
|
+
console.log(chalk3.dim(" 1. Scan Analyze your code, dependencies, and existing configs"));
|
|
3378
|
+
console.log(chalk3.dim(" 2. Generate AI creates or improves config files for your project"));
|
|
3379
|
+
console.log(chalk3.dim(" 3. Review You accept, refine, or decline the proposed changes"));
|
|
3380
|
+
console.log(chalk3.dim(" 4. Apply Config files are written with backups\n"));
|
|
3387
3381
|
console.log(chalk3.hex("#6366f1").bold(" Step 1/4 \u2014 Check LLM provider\n"));
|
|
3388
3382
|
const config = loadConfig();
|
|
3389
3383
|
if (!config) {
|
|
@@ -3398,13 +3392,12 @@ async function initCommand(options) {
|
|
|
3398
3392
|
console.log(chalk3.dim(` Provider: ${config.provider} | Model: ${config.model}
|
|
3399
3393
|
`));
|
|
3400
3394
|
console.log(chalk3.hex("#6366f1").bold(" Step 2/4 \u2014 Scan project\n"));
|
|
3401
|
-
console.log(chalk3.dim(" Detecting languages,
|
|
3395
|
+
console.log(chalk3.dim(" Detecting languages, dependencies, 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,12 +3406,16 @@ 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
|
-
console.log(chalk3.hex("#6366f1").bold(" Step 3/4 \u2014 Auditing your configs\n"));
|
|
3418
|
-
console.log(chalk3.dim(" AI is auditing your CLAUDE.md, skills, and rules against your"));
|
|
3419
|
-
console.log(chalk3.dim(" project's actual codebase and conventions.\n"));
|
|
3420
|
-
console.log(chalk3.dim(" This usually takes 1\u20133 minutes on first run.\n"));
|
|
3421
3409
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
3410
|
+
if (hasExistingConfig) {
|
|
3411
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 3/4 \u2014 Auditing your configs\n"));
|
|
3412
|
+
console.log(chalk3.dim(" AI is reviewing your existing configs against your codebase"));
|
|
3413
|
+
console.log(chalk3.dim(" and suggesting improvements.\n"));
|
|
3414
|
+
} else {
|
|
3415
|
+
console.log(chalk3.hex("#6366f1").bold(" Step 3/4 \u2014 Generating configs\n"));
|
|
3416
|
+
console.log(chalk3.dim(" AI is creating agent config files tailored to your project.\n"));
|
|
3417
|
+
}
|
|
3418
|
+
console.log(chalk3.dim(" This usually takes 1\u20133 minutes.\n"));
|
|
3422
3419
|
const genStartTime = Date.now();
|
|
3423
3420
|
const genSpinner = ora("Generating setup...").start();
|
|
3424
3421
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
@@ -3543,13 +3540,14 @@ async function initCommand(options) {
|
|
|
3543
3540
|
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3544
3541
|
targetAgent
|
|
3545
3542
|
});
|
|
3546
|
-
|
|
3543
|
+
const hookChoice = await promptHookType(targetAgent);
|
|
3544
|
+
if (hookChoice === "claude" || hookChoice === "both") {
|
|
3547
3545
|
const hookResult = installHook();
|
|
3548
3546
|
if (hookResult.installed) {
|
|
3549
|
-
console.log(` ${chalk3.green("\u2713")}
|
|
3547
|
+
console.log(` ${chalk3.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
3550
3548
|
console.log(chalk3.dim(" Run `caliber hooks remove` to disable"));
|
|
3551
3549
|
} else if (hookResult.alreadyInstalled) {
|
|
3552
|
-
console.log(chalk3.dim("
|
|
3550
|
+
console.log(chalk3.dim(" Claude Code hook already installed"));
|
|
3553
3551
|
}
|
|
3554
3552
|
const learnResult = installLearningHooks();
|
|
3555
3553
|
if (learnResult.installed) {
|
|
@@ -3559,6 +3557,20 @@ async function initCommand(options) {
|
|
|
3559
3557
|
console.log(chalk3.dim(" Learning hooks already installed"));
|
|
3560
3558
|
}
|
|
3561
3559
|
}
|
|
3560
|
+
if (hookChoice === "precommit" || hookChoice === "both") {
|
|
3561
|
+
const precommitResult = installPreCommitHook();
|
|
3562
|
+
if (precommitResult.installed) {
|
|
3563
|
+
console.log(` ${chalk3.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
3564
|
+
console.log(chalk3.dim(" Run `caliber hooks remove-precommit` to disable"));
|
|
3565
|
+
} else if (precommitResult.alreadyInstalled) {
|
|
3566
|
+
console.log(chalk3.dim(" Pre-commit hook already installed"));
|
|
3567
|
+
} else {
|
|
3568
|
+
console.log(chalk3.yellow(" Could not install pre-commit hook (not a git repository?)"));
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
if (hookChoice === "skip") {
|
|
3572
|
+
console.log(chalk3.dim(" Skipped auto-refresh hooks. Run `caliber hooks install` later to enable."));
|
|
3573
|
+
}
|
|
3562
3574
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
3563
3575
|
displayScoreDelta(baselineScore, afterScore);
|
|
3564
3576
|
console.log(chalk3.bold.green(" Setup complete! Your coding agent is now configured."));
|
|
@@ -3618,6 +3630,21 @@ async function promptAgent() {
|
|
|
3618
3630
|
]
|
|
3619
3631
|
});
|
|
3620
3632
|
}
|
|
3633
|
+
async function promptHookType(targetAgent) {
|
|
3634
|
+
const choices = [];
|
|
3635
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
3636
|
+
choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
|
|
3637
|
+
}
|
|
3638
|
+
choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
|
|
3639
|
+
if (targetAgent === "claude" || targetAgent === "both") {
|
|
3640
|
+
choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
|
|
3641
|
+
}
|
|
3642
|
+
choices.push({ name: "Skip for now", value: "skip" });
|
|
3643
|
+
return select({
|
|
3644
|
+
message: "How would you like to auto-refresh your docs?",
|
|
3645
|
+
choices
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3621
3648
|
async function promptWantsReview() {
|
|
3622
3649
|
const answer = await select({
|
|
3623
3650
|
message: "Would you like to review the diffs before deciding?",
|
|
@@ -3909,9 +3936,10 @@ async function regenerateCommand(options) {
|
|
|
3909
3936
|
genMessages.start();
|
|
3910
3937
|
let generatedSetup = null;
|
|
3911
3938
|
try {
|
|
3939
|
+
const targetAgent = readState()?.targetAgent ?? "both";
|
|
3912
3940
|
const result2 = await generateSetup(
|
|
3913
3941
|
fingerprint,
|
|
3914
|
-
|
|
3942
|
+
targetAgent,
|
|
3915
3943
|
void 0,
|
|
3916
3944
|
{
|
|
3917
3945
|
onStatus: (status) => {
|
|
@@ -3961,7 +3989,7 @@ async function regenerateCommand(options) {
|
|
|
3961
3989
|
// src/commands/recommend.ts
|
|
3962
3990
|
import chalk7 from "chalk";
|
|
3963
3991
|
import ora4 from "ora";
|
|
3964
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
3992
|
+
import { mkdirSync, readFileSync as readFileSync7, existsSync as existsSync9, writeFileSync } from "fs";
|
|
3965
3993
|
import { join as join8, dirname as dirname2 } from "path";
|
|
3966
3994
|
|
|
3967
3995
|
// src/scanner/index.ts
|
|
@@ -4123,15 +4151,26 @@ async function searchSkills(technologies) {
|
|
|
4123
4151
|
}
|
|
4124
4152
|
return results;
|
|
4125
4153
|
}
|
|
4154
|
+
function extractTopDeps() {
|
|
4155
|
+
const pkgPath = join8(process.cwd(), "package.json");
|
|
4156
|
+
if (!existsSync9(pkgPath)) return [];
|
|
4157
|
+
try {
|
|
4158
|
+
const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
4159
|
+
return Object.keys(pkg3.dependencies ?? {});
|
|
4160
|
+
} catch {
|
|
4161
|
+
return [];
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4126
4164
|
async function recommendCommand(options) {
|
|
4127
4165
|
const fingerprint = collectFingerprint(process.cwd());
|
|
4128
4166
|
const platforms = detectLocalPlatforms();
|
|
4129
|
-
const technologies = [
|
|
4167
|
+
const technologies = [...new Set([
|
|
4130
4168
|
...fingerprint.languages,
|
|
4131
|
-
...fingerprint.frameworks
|
|
4132
|
-
|
|
4169
|
+
...fingerprint.frameworks,
|
|
4170
|
+
...extractTopDeps()
|
|
4171
|
+
].filter(Boolean))];
|
|
4133
4172
|
if (technologies.length === 0) {
|
|
4134
|
-
console.log(chalk7.yellow("Could not detect any languages or
|
|
4173
|
+
console.log(chalk7.yellow("Could not detect any languages or dependencies. Try running from a project root."));
|
|
4135
4174
|
throw new Error("__exit__");
|
|
4136
4175
|
}
|
|
4137
4176
|
const spinner = ora4("Searching for skills...").start();
|
|
@@ -4328,7 +4367,7 @@ import chalk9 from "chalk";
|
|
|
4328
4367
|
import ora5 from "ora";
|
|
4329
4368
|
|
|
4330
4369
|
// src/lib/git-diff.ts
|
|
4331
|
-
import { execSync as
|
|
4370
|
+
import { execSync as execSync5 } from "child_process";
|
|
4332
4371
|
var MAX_DIFF_BYTES = 1e5;
|
|
4333
4372
|
var DOC_PATTERNS = [
|
|
4334
4373
|
"CLAUDE.md",
|
|
@@ -4342,7 +4381,7 @@ function excludeArgs() {
|
|
|
4342
4381
|
}
|
|
4343
4382
|
function safeExec(cmd) {
|
|
4344
4383
|
try {
|
|
4345
|
-
return
|
|
4384
|
+
return execSync5(cmd, {
|
|
4346
4385
|
encoding: "utf-8",
|
|
4347
4386
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4348
4387
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -4628,7 +4667,7 @@ import chalk10 from "chalk";
|
|
|
4628
4667
|
async function hooksInstallCommand() {
|
|
4629
4668
|
const result = installHook();
|
|
4630
4669
|
if (result.alreadyInstalled) {
|
|
4631
|
-
console.log(chalk10.dim("
|
|
4670
|
+
console.log(chalk10.dim("Claude Code hook already installed."));
|
|
4632
4671
|
return;
|
|
4633
4672
|
}
|
|
4634
4673
|
console.log(chalk10.green("\u2713") + " SessionEnd hook installed in .claude/settings.json");
|
|
@@ -4637,18 +4676,47 @@ async function hooksInstallCommand() {
|
|
|
4637
4676
|
async function hooksRemoveCommand() {
|
|
4638
4677
|
const result = removeHook();
|
|
4639
4678
|
if (result.notFound) {
|
|
4640
|
-
console.log(chalk10.dim("
|
|
4679
|
+
console.log(chalk10.dim("Claude Code hook not found."));
|
|
4641
4680
|
return;
|
|
4642
4681
|
}
|
|
4643
4682
|
console.log(chalk10.green("\u2713") + " SessionEnd hook removed from .claude/settings.json");
|
|
4644
4683
|
}
|
|
4684
|
+
async function hooksInstallPrecommitCommand() {
|
|
4685
|
+
const result = installPreCommitHook();
|
|
4686
|
+
if (result.alreadyInstalled) {
|
|
4687
|
+
console.log(chalk10.dim("Pre-commit hook already installed."));
|
|
4688
|
+
return;
|
|
4689
|
+
}
|
|
4690
|
+
if (!result.installed) {
|
|
4691
|
+
console.log(chalk10.red("Failed to install pre-commit hook (not a git repository?)."));
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook installed in .git/hooks/pre-commit");
|
|
4695
|
+
console.log(chalk10.dim(" Docs will auto-refresh before each commit via LLM."));
|
|
4696
|
+
}
|
|
4697
|
+
async function hooksRemovePrecommitCommand() {
|
|
4698
|
+
const result = removePreCommitHook();
|
|
4699
|
+
if (result.notFound) {
|
|
4700
|
+
console.log(chalk10.dim("Pre-commit hook not found."));
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook removed from .git/hooks/pre-commit");
|
|
4704
|
+
}
|
|
4645
4705
|
async function hooksStatusCommand() {
|
|
4646
|
-
const
|
|
4647
|
-
|
|
4648
|
-
|
|
4706
|
+
const claudeInstalled = isHookInstalled();
|
|
4707
|
+
const precommitInstalled = isPreCommitHookInstalled();
|
|
4708
|
+
if (claudeInstalled) {
|
|
4709
|
+
console.log(chalk10.green("\u2713") + " Claude Code hook is " + chalk10.green("installed"));
|
|
4710
|
+
} else {
|
|
4711
|
+
console.log(chalk10.dim("\u2717") + " Claude Code hook is " + chalk10.yellow("not installed"));
|
|
4712
|
+
}
|
|
4713
|
+
if (precommitInstalled) {
|
|
4714
|
+
console.log(chalk10.green("\u2713") + " Pre-commit hook is " + chalk10.green("installed"));
|
|
4649
4715
|
} else {
|
|
4650
|
-
console.log(chalk10.dim("\u2717") + "
|
|
4651
|
-
|
|
4716
|
+
console.log(chalk10.dim("\u2717") + " Pre-commit hook is " + chalk10.yellow("not installed"));
|
|
4717
|
+
}
|
|
4718
|
+
if (!claudeInstalled && !precommitInstalled) {
|
|
4719
|
+
console.log(chalk10.dim("\n Run `caliber hooks install` or `caliber hooks install-precommit` to enable auto-refresh."));
|
|
4652
4720
|
}
|
|
4653
4721
|
}
|
|
4654
4722
|
|
|
@@ -5095,10 +5163,12 @@ program.command("config").description("Configure LLM provider, API key, and mode
|
|
|
5095
5163
|
program.command("recommend").description("Discover and install skill recommendations").option("--generate", "Force fresh recommendation search").action(recommendCommand);
|
|
5096
5164
|
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
5165
|
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("
|
|
5166
|
+
var hooks = program.command("hooks").description("Manage auto-refresh hooks (Claude Code and git pre-commit)");
|
|
5167
|
+
hooks.command("install").description("Install Claude Code SessionEnd auto-refresh hook").action(hooksInstallCommand);
|
|
5168
|
+
hooks.command("remove").description("Remove Claude Code SessionEnd auto-refresh hook").action(hooksRemoveCommand);
|
|
5169
|
+
hooks.command("install-precommit").description("Install git pre-commit hook for auto-refresh").action(hooksInstallPrecommitCommand);
|
|
5170
|
+
hooks.command("remove-precommit").description("Remove git pre-commit hook").action(hooksRemovePrecommitCommand);
|
|
5171
|
+
hooks.command("status").description("Check installed hooks status").action(hooksStatusCommand);
|
|
5102
5172
|
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
5103
5173
|
learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").action(learnObserveCommand);
|
|
5104
5174
|
learn.command("finalize").description("Analyze session events and update CLAUDE.md (called on SessionEnd)").action(learnFinalizeCommand);
|
|
@@ -5110,7 +5180,7 @@ learn.command("status").description("Show learning system status").action(learnS
|
|
|
5110
5180
|
import fs25 from "fs";
|
|
5111
5181
|
import path22 from "path";
|
|
5112
5182
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5113
|
-
import { execSync as
|
|
5183
|
+
import { execSync as execSync6 } from "child_process";
|
|
5114
5184
|
import chalk13 from "chalk";
|
|
5115
5185
|
import ora6 from "ora";
|
|
5116
5186
|
import confirm2 from "@inquirer/confirm";
|
|
@@ -5120,7 +5190,7 @@ var pkg2 = JSON.parse(
|
|
|
5120
5190
|
);
|
|
5121
5191
|
function getInstalledVersion() {
|
|
5122
5192
|
try {
|
|
5123
|
-
const globalRoot =
|
|
5193
|
+
const globalRoot = execSync6("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5124
5194
|
const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
5125
5195
|
return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
|
|
5126
5196
|
} catch {
|
|
@@ -5165,7 +5235,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
5165
5235
|
}
|
|
5166
5236
|
const spinner = ora6("Updating caliber...").start();
|
|
5167
5237
|
try {
|
|
5168
|
-
|
|
5238
|
+
execSync6(`npm install -g @rely-ai/caliber@${latest} --prefer-online`, { stdio: "pipe", timeout: 6e4 });
|
|
5169
5239
|
const installed = getInstalledVersion();
|
|
5170
5240
|
if (installed !== latest) {
|
|
5171
5241
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
@@ -5178,7 +5248,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
5178
5248
|
console.log(chalk13.dim(`
|
|
5179
5249
|
Restarting: caliber ${args.join(" ")}
|
|
5180
5250
|
`));
|
|
5181
|
-
|
|
5251
|
+
execSync6(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
5182
5252
|
stdio: "inherit",
|
|
5183
5253
|
env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
|
|
5184
5254
|
});
|
package/package.json
CHANGED