@rely-ai/caliber 0.9.1 → 1.1.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 +18 -14
- package/dist/bin.js +258 -166
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/social-preview.svg" alt="Caliber" width="640">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://www.npmjs.com/package/@rely-ai/caliber"><img src="https://img.shields.io/npm/v/@rely-ai/caliber" alt="npm version"></a>
|
|
7
|
+
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/@rely-ai/caliber" alt="license"></a>
|
|
8
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@rely-ai/caliber" alt="node"></a>
|
|
9
|
+
</p>
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
<p align="center"><strong>Analyze your codebase. Generate optimized AI agent configs. One command.</strong></p>
|
|
8
12
|
|
|
9
13
|
Caliber scans your project — languages, frameworks, dependencies, file structure — and generates tailored config files for Claude Code and Cursor. If configs already exist, it audits them and suggests improvements.
|
|
10
14
|
|
|
@@ -13,7 +17,7 @@ Caliber scans your project — languages, frameworks, dependencies, file structu
|
|
|
13
17
|
## Quick Start
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
npx @rely-ai/caliber
|
|
20
|
+
npx @rely-ai/caliber onboard
|
|
17
21
|
```
|
|
18
22
|
|
|
19
23
|
That's it. On first run, Caliber walks you through provider setup interactively.
|
|
@@ -22,19 +26,19 @@ Or install globally:
|
|
|
22
26
|
|
|
23
27
|
```bash
|
|
24
28
|
npm install -g @rely-ai/caliber
|
|
25
|
-
caliber
|
|
29
|
+
caliber onboard
|
|
26
30
|
```
|
|
27
31
|
|
|
28
32
|
> **Already have an API key?** Skip the interactive setup:
|
|
29
33
|
> ```bash
|
|
30
34
|
> export ANTHROPIC_API_KEY=sk-ant-...
|
|
31
|
-
> npx @rely-ai/caliber
|
|
35
|
+
> npx @rely-ai/caliber onboard
|
|
32
36
|
> ```
|
|
33
37
|
|
|
34
38
|
## How It Works
|
|
35
39
|
|
|
36
40
|
```
|
|
37
|
-
caliber
|
|
41
|
+
caliber onboard
|
|
38
42
|
│
|
|
39
43
|
├─ 1. Scan Analyze languages, frameworks, dependencies, file structure,
|
|
40
44
|
│ and existing agent configs in your project
|
|
@@ -64,16 +68,16 @@ If these files already exist, Caliber audits them against your actual codebase a
|
|
|
64
68
|
|
|
65
69
|
| Command | Description |
|
|
66
70
|
|---------|-------------|
|
|
67
|
-
| `caliber
|
|
71
|
+
| `caliber onboard` | Onboard your project for AI-assisted development |
|
|
68
72
|
| `caliber score` | Score your config quality (deterministic, no LLM needed) |
|
|
69
73
|
| `caliber recommend` | Discover and install skills from [skills.sh](https://skills.sh) |
|
|
70
74
|
| `caliber config` | Configure LLM provider, API key, and model |
|
|
71
75
|
|
|
72
76
|
```bash
|
|
73
|
-
caliber
|
|
74
|
-
caliber
|
|
75
|
-
caliber
|
|
76
|
-
caliber
|
|
77
|
+
caliber onboard --agent claude # Target Claude Code only
|
|
78
|
+
caliber onboard --agent cursor # Target Cursor only
|
|
79
|
+
caliber onboard --agent both # Target both
|
|
80
|
+
caliber onboard --dry-run # Preview without writing files
|
|
77
81
|
caliber score --json # Machine-readable output
|
|
78
82
|
```
|
|
79
83
|
|
package/dist/bin.js
CHANGED
|
@@ -51,16 +51,16 @@ var init_constants = __esm({
|
|
|
51
51
|
|
|
52
52
|
// src/cli.ts
|
|
53
53
|
import { Command } from "commander";
|
|
54
|
-
import
|
|
55
|
-
import
|
|
54
|
+
import fs25 from "fs";
|
|
55
|
+
import path22 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
|
-
// src/commands/
|
|
58
|
+
// src/commands/onboard.ts
|
|
59
59
|
import chalk4 from "chalk";
|
|
60
60
|
import ora from "ora";
|
|
61
61
|
import readline3 from "readline";
|
|
62
62
|
import select2 from "@inquirer/select";
|
|
63
|
-
import
|
|
63
|
+
import fs18 from "fs";
|
|
64
64
|
|
|
65
65
|
// src/fingerprint/index.ts
|
|
66
66
|
import fs7 from "fs";
|
|
@@ -2127,16 +2127,25 @@ import path12 from "path";
|
|
|
2127
2127
|
var STAGED_DIR = path12.join(CALIBER_DIR, "staged");
|
|
2128
2128
|
var PROPOSED_DIR = path12.join(STAGED_DIR, "proposed");
|
|
2129
2129
|
var CURRENT_DIR = path12.join(STAGED_DIR, "current");
|
|
2130
|
+
function normalizeContent(content) {
|
|
2131
|
+
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2132
|
+
}
|
|
2130
2133
|
function stageFiles(files, projectDir) {
|
|
2131
2134
|
cleanupStaging();
|
|
2132
2135
|
let newFiles = 0;
|
|
2133
2136
|
let modifiedFiles = 0;
|
|
2134
2137
|
const stagedFiles = [];
|
|
2135
2138
|
for (const file of files) {
|
|
2139
|
+
const originalPath = path12.join(projectDir, file.path);
|
|
2140
|
+
if (fs13.existsSync(originalPath)) {
|
|
2141
|
+
const existing = fs13.readFileSync(originalPath, "utf-8");
|
|
2142
|
+
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2136
2146
|
const proposedPath = path12.join(PROPOSED_DIR, file.path);
|
|
2137
2147
|
fs13.mkdirSync(path12.dirname(proposedPath), { recursive: true });
|
|
2138
2148
|
fs13.writeFileSync(proposedPath, file.content);
|
|
2139
|
-
const originalPath = path12.join(projectDir, file.path);
|
|
2140
2149
|
if (fs13.existsSync(originalPath)) {
|
|
2141
2150
|
const currentPath = path12.join(CURRENT_DIR, file.path);
|
|
2142
2151
|
fs13.mkdirSync(path12.dirname(currentPath), { recursive: true });
|
|
@@ -2194,7 +2203,7 @@ function openDiffsInEditor(editor, files) {
|
|
|
2194
2203
|
}
|
|
2195
2204
|
}
|
|
2196
2205
|
|
|
2197
|
-
// src/commands/
|
|
2206
|
+
// src/commands/onboard.ts
|
|
2198
2207
|
import { createTwoFilesPatch } from "diff";
|
|
2199
2208
|
|
|
2200
2209
|
// src/lib/hooks.ts
|
|
@@ -2715,15 +2724,15 @@ function computeGrade(score) {
|
|
|
2715
2724
|
// src/scoring/checks/coverage.ts
|
|
2716
2725
|
import { readFileSync, readdirSync } from "fs";
|
|
2717
2726
|
import { join } from "path";
|
|
2718
|
-
function readFileOrNull(
|
|
2727
|
+
function readFileOrNull(path24) {
|
|
2719
2728
|
try {
|
|
2720
|
-
return readFileSync(
|
|
2729
|
+
return readFileSync(path24, "utf-8");
|
|
2721
2730
|
} catch {
|
|
2722
2731
|
return null;
|
|
2723
2732
|
}
|
|
2724
2733
|
}
|
|
2725
|
-
function readJsonOrNull(
|
|
2726
|
-
const content = readFileOrNull(
|
|
2734
|
+
function readJsonOrNull(path24) {
|
|
2735
|
+
const content = readFileOrNull(path24);
|
|
2727
2736
|
if (!content) return null;
|
|
2728
2737
|
try {
|
|
2729
2738
|
return JSON.parse(content);
|
|
@@ -3079,9 +3088,9 @@ function checkExistence(dir) {
|
|
|
3079
3088
|
// src/scoring/checks/quality.ts
|
|
3080
3089
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3081
3090
|
import { join as join3 } from "path";
|
|
3082
|
-
function readFileOrNull2(
|
|
3091
|
+
function readFileOrNull2(path24) {
|
|
3083
3092
|
try {
|
|
3084
|
-
return readFileSync3(
|
|
3093
|
+
return readFileSync3(path24, "utf-8");
|
|
3085
3094
|
} catch {
|
|
3086
3095
|
return null;
|
|
3087
3096
|
}
|
|
@@ -3232,15 +3241,15 @@ function checkQuality(dir) {
|
|
|
3232
3241
|
// src/scoring/checks/accuracy.ts
|
|
3233
3242
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3234
3243
|
import { join as join4 } from "path";
|
|
3235
|
-
function readFileOrNull3(
|
|
3244
|
+
function readFileOrNull3(path24) {
|
|
3236
3245
|
try {
|
|
3237
|
-
return readFileSync4(
|
|
3246
|
+
return readFileSync4(path24, "utf-8");
|
|
3238
3247
|
} catch {
|
|
3239
3248
|
return null;
|
|
3240
3249
|
}
|
|
3241
3250
|
}
|
|
3242
|
-
function readJsonOrNull2(
|
|
3243
|
-
const content = readFileOrNull3(
|
|
3251
|
+
function readJsonOrNull2(path24) {
|
|
3252
|
+
const content = readFileOrNull3(path24);
|
|
3244
3253
|
if (!content) return null;
|
|
3245
3254
|
try {
|
|
3246
3255
|
return JSON.parse(content);
|
|
@@ -3423,9 +3432,9 @@ function checkAccuracy(dir) {
|
|
|
3423
3432
|
// src/scoring/checks/freshness.ts
|
|
3424
3433
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3425
3434
|
import { join as join5 } from "path";
|
|
3426
|
-
function readFileOrNull4(
|
|
3435
|
+
function readFileOrNull4(path24) {
|
|
3427
3436
|
try {
|
|
3428
|
-
return readFileSync5(
|
|
3437
|
+
return readFileSync5(path24, "utf-8");
|
|
3429
3438
|
} catch {
|
|
3430
3439
|
return null;
|
|
3431
3440
|
}
|
|
@@ -3535,9 +3544,9 @@ function checkFreshness(dir) {
|
|
|
3535
3544
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3536
3545
|
import { execSync as execSync7 } from "child_process";
|
|
3537
3546
|
import { join as join6 } from "path";
|
|
3538
|
-
function readFileOrNull5(
|
|
3547
|
+
function readFileOrNull5(path24) {
|
|
3539
3548
|
try {
|
|
3540
|
-
return readFileSync6(
|
|
3549
|
+
return readFileSync6(path24, "utf-8");
|
|
3541
3550
|
} catch {
|
|
3542
3551
|
return null;
|
|
3543
3552
|
}
|
|
@@ -3630,6 +3639,29 @@ function checkBonus(dir) {
|
|
|
3630
3639
|
return checks;
|
|
3631
3640
|
}
|
|
3632
3641
|
|
|
3642
|
+
// src/scoring/dismissed.ts
|
|
3643
|
+
init_constants();
|
|
3644
|
+
import fs17 from "fs";
|
|
3645
|
+
import path16 from "path";
|
|
3646
|
+
var DISMISSED_FILE = path16.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3647
|
+
function readDismissedChecks() {
|
|
3648
|
+
try {
|
|
3649
|
+
if (!fs17.existsSync(DISMISSED_FILE)) return [];
|
|
3650
|
+
return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3651
|
+
} catch {
|
|
3652
|
+
return [];
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
function writeDismissedChecks(checks) {
|
|
3656
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
3657
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3658
|
+
}
|
|
3659
|
+
fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3660
|
+
}
|
|
3661
|
+
function getDismissedIds() {
|
|
3662
|
+
return new Set(readDismissedChecks().map((c) => c.id));
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3633
3665
|
// src/scoring/index.ts
|
|
3634
3666
|
function sumCategory(checks, category) {
|
|
3635
3667
|
const categoryChecks = checks.filter((c) => c.category === category);
|
|
@@ -3666,7 +3698,8 @@ function computeLocalScore(dir, targetAgent) {
|
|
|
3666
3698
|
...checkFreshness(dir),
|
|
3667
3699
|
...checkBonus(dir)
|
|
3668
3700
|
];
|
|
3669
|
-
const
|
|
3701
|
+
const dismissed = getDismissedIds();
|
|
3702
|
+
const checks = filterChecksForTarget(allChecks, target).filter((c) => !dismissed.has(c.id));
|
|
3670
3703
|
const maxPossible = checks.reduce((s, c) => s + c.maxPoints, 0);
|
|
3671
3704
|
const earned = checks.reduce((s, c) => s + c.earnedPoints, 0);
|
|
3672
3705
|
const score = maxPossible > 0 ? Math.min(100, Math.max(0, Math.round(earned / maxPossible * 100))) : 0;
|
|
@@ -3772,7 +3805,7 @@ function displayScoreSummary(result) {
|
|
|
3772
3805
|
const remaining = failing.length - shown.length;
|
|
3773
3806
|
const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
|
|
3774
3807
|
console.log(chalk3.dim(`
|
|
3775
|
-
Run ${chalk3.
|
|
3808
|
+
Run ${chalk3.hex("#83D1EB")("caliber score")} for details.${moreText}`));
|
|
3776
3809
|
}
|
|
3777
3810
|
console.log("");
|
|
3778
3811
|
}
|
|
@@ -3820,7 +3853,7 @@ function displayScoreDelta(before, after) {
|
|
|
3820
3853
|
}
|
|
3821
3854
|
}
|
|
3822
3855
|
|
|
3823
|
-
// src/commands/
|
|
3856
|
+
// src/commands/onboard.ts
|
|
3824
3857
|
async function initCommand(options) {
|
|
3825
3858
|
const brand = chalk4.hex("#EB9D83");
|
|
3826
3859
|
const title = chalk4.hex("#83D1EB");
|
|
@@ -3832,18 +3865,16 @@ async function initCommand(options) {
|
|
|
3832
3865
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3833
3866
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
3834
3867
|
`));
|
|
3835
|
-
console.log(chalk4.dim("
|
|
3836
|
-
console.log(title.bold("
|
|
3837
|
-
console.log(chalk4.dim(" Caliber
|
|
3838
|
-
console.log(chalk4.dim("
|
|
3839
|
-
console.log(
|
|
3840
|
-
console.log(chalk4.dim("
|
|
3841
|
-
console.log(
|
|
3842
|
-
console.log(chalk4.dim("
|
|
3843
|
-
console.log(chalk4.dim("
|
|
3844
|
-
console.log(
|
|
3845
|
-
console.log(chalk4.dim(" 4. Apply Config files are written with backups\n"));
|
|
3846
|
-
console.log(title.bold(" Step 1/4 \u2014 How do you want to use Caliber?\n"));
|
|
3868
|
+
console.log(chalk4.dim(" Onboard your project for AI-assisted development\n"));
|
|
3869
|
+
console.log(title.bold(" Welcome to Caliber\n"));
|
|
3870
|
+
console.log(chalk4.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
3871
|
+
console.log(chalk4.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
3872
|
+
console.log(title.bold(" How onboarding works:\n"));
|
|
3873
|
+
console.log(chalk4.dim(" 1. Connect Set up your LLM provider"));
|
|
3874
|
+
console.log(chalk4.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
3875
|
+
console.log(chalk4.dim(" 3. Generate Create config files tailored to your project"));
|
|
3876
|
+
console.log(chalk4.dim(" 4. Review Preview, refine, and apply the changes\n"));
|
|
3877
|
+
console.log(title.bold(" Step 1/4 \u2014 Connect your LLM\n"));
|
|
3847
3878
|
let config = loadConfig();
|
|
3848
3879
|
if (!config) {
|
|
3849
3880
|
console.log(chalk4.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
@@ -3860,14 +3891,14 @@ async function initCommand(options) {
|
|
|
3860
3891
|
console.log(chalk4.red(" Setup was cancelled or failed.\n"));
|
|
3861
3892
|
throw new Error("__exit__");
|
|
3862
3893
|
}
|
|
3863
|
-
console.log(chalk4.green(" \u2713 Provider saved.
|
|
3894
|
+
console.log(chalk4.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
3864
3895
|
}
|
|
3865
3896
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
3866
3897
|
const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
3867
3898
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
3868
3899
|
console.log(chalk4.dim(modelLine + "\n"));
|
|
3869
|
-
console.log(title.bold(" Step 2/4 \u2014
|
|
3870
|
-
console.log(chalk4.dim("
|
|
3900
|
+
console.log(title.bold(" Step 2/4 \u2014 Discover your project\n"));
|
|
3901
|
+
console.log(chalk4.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
3871
3902
|
const spinner = ora("Analyzing project...").start();
|
|
3872
3903
|
const fingerprint = collectFingerprint(process.cwd());
|
|
3873
3904
|
await enrichFingerprintWithLLM(fingerprint, process.cwd());
|
|
@@ -3876,12 +3907,23 @@ async function initCommand(options) {
|
|
|
3876
3907
|
console.log(chalk4.dim(` Files: ${fingerprint.fileTree.length} found
|
|
3877
3908
|
`));
|
|
3878
3909
|
const targetAgent = options.agent || await promptAgent();
|
|
3910
|
+
const preScore = computeLocalScore(process.cwd(), targetAgent);
|
|
3911
|
+
const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
3912
|
+
if (failingForDismissal.length > 0) {
|
|
3913
|
+
const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
|
|
3914
|
+
if (newDismissals.length > 0) {
|
|
3915
|
+
const existing = readDismissedChecks();
|
|
3916
|
+
const existingIds = new Set(existing.map((d) => d.id));
|
|
3917
|
+
const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
|
|
3918
|
+
writeDismissedChecks(merged);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3879
3921
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
3880
3922
|
displayScoreSummary(baselineScore);
|
|
3881
3923
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
3882
3924
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
3883
3925
|
console.log(chalk4.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
3884
|
-
console.log(chalk4.dim(" Run
|
|
3926
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber onboard --force") + chalk4.dim(" to regenerate anyway.\n"));
|
|
3885
3927
|
if (!options.force) return;
|
|
3886
3928
|
}
|
|
3887
3929
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
@@ -3896,8 +3938,8 @@ async function initCommand(options) {
|
|
|
3896
3938
|
passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
3897
3939
|
currentScore = baselineScore.score;
|
|
3898
3940
|
if (failingChecks.length > 0) {
|
|
3899
|
-
console.log(title.bold(" Step 3/4 \u2014
|
|
3900
|
-
console.log(chalk4.dim(`
|
|
3941
|
+
console.log(title.bold(" Step 3/4 \u2014 Fine-tuning\n"));
|
|
3942
|
+
console.log(chalk4.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
3901
3943
|
`));
|
|
3902
3944
|
for (const check of failingChecks) {
|
|
3903
3945
|
console.log(chalk4.dim(` \u2022 ${check.name}`));
|
|
@@ -3905,12 +3947,12 @@ async function initCommand(options) {
|
|
|
3905
3947
|
console.log("");
|
|
3906
3948
|
}
|
|
3907
3949
|
} else if (hasExistingConfig) {
|
|
3908
|
-
console.log(title.bold(" Step 3/4 \u2014
|
|
3909
|
-
console.log(chalk4.dim("
|
|
3910
|
-
console.log(chalk4.dim(" and
|
|
3950
|
+
console.log(title.bold(" Step 3/4 \u2014 Improve your setup\n"));
|
|
3951
|
+
console.log(chalk4.dim(" Reviewing your existing configs against your codebase"));
|
|
3952
|
+
console.log(chalk4.dim(" and preparing improvements.\n"));
|
|
3911
3953
|
} else {
|
|
3912
|
-
console.log(title.bold(" Step 3/4 \u2014
|
|
3913
|
-
console.log(chalk4.dim("
|
|
3954
|
+
console.log(title.bold(" Step 3/4 \u2014 Build your agent setup\n"));
|
|
3955
|
+
console.log(chalk4.dim(" Creating config files tailored to your project.\n"));
|
|
3914
3956
|
}
|
|
3915
3957
|
console.log(chalk4.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
3916
3958
|
const genStartTime = Date.now();
|
|
@@ -3970,7 +4012,7 @@ async function initCommand(options) {
|
|
|
3970
4012
|
role: "assistant",
|
|
3971
4013
|
content: summarizeSetup("Initial generation", generatedSetup)
|
|
3972
4014
|
});
|
|
3973
|
-
console.log(title.bold(" Step 4/4 \u2014 Review\n"));
|
|
4015
|
+
console.log(title.bold(" Step 4/4 \u2014 Review and apply\n"));
|
|
3974
4016
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
3975
4017
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
3976
4018
|
console.log(chalk4.dim(` ${chalk4.green(`${staged.newFiles} new`)} / ${chalk4.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
|
|
@@ -4029,11 +4071,6 @@ async function initCommand(options) {
|
|
|
4029
4071
|
console.error(chalk4.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4030
4072
|
throw new Error("__exit__");
|
|
4031
4073
|
}
|
|
4032
|
-
if (!fs17.existsSync("AGENTS.md")) {
|
|
4033
|
-
const agentsContent = "# AGENTS.md\n\nThis project uses AI coding agents. See CLAUDE.md for Claude Code configuration and .cursor/rules/ for Cursor rules.\n";
|
|
4034
|
-
fs17.writeFileSync("AGENTS.md", agentsContent);
|
|
4035
|
-
console.log(` ${chalk4.green("\u2713")} AGENTS.md`);
|
|
4036
|
-
}
|
|
4037
4074
|
ensurePermissions();
|
|
4038
4075
|
const sha = getCurrentHeadSha();
|
|
4039
4076
|
writeState({
|
|
@@ -4041,19 +4078,22 @@ async function initCommand(options) {
|
|
|
4041
4078
|
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4042
4079
|
targetAgent
|
|
4043
4080
|
});
|
|
4081
|
+
console.log("");
|
|
4082
|
+
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4083
|
+
console.log(chalk4.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4044
4084
|
const hookChoice = await promptHookType(targetAgent);
|
|
4045
4085
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4046
4086
|
const hookResult = installHook();
|
|
4047
4087
|
if (hookResult.installed) {
|
|
4048
4088
|
console.log(` ${chalk4.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
|
|
4049
|
-
console.log(chalk4.dim(" Run
|
|
4089
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber hooks remove") + chalk4.dim(" to disable"));
|
|
4050
4090
|
} else if (hookResult.alreadyInstalled) {
|
|
4051
4091
|
console.log(chalk4.dim(" Claude Code hook already installed"));
|
|
4052
4092
|
}
|
|
4053
4093
|
const learnResult = installLearningHooks();
|
|
4054
4094
|
if (learnResult.installed) {
|
|
4055
4095
|
console.log(` ${chalk4.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
4056
|
-
console.log(chalk4.dim(" Run
|
|
4096
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber learn remove") + chalk4.dim(" to disable"));
|
|
4057
4097
|
} else if (learnResult.alreadyInstalled) {
|
|
4058
4098
|
console.log(chalk4.dim(" Learning hooks already installed"));
|
|
4059
4099
|
}
|
|
@@ -4062,7 +4102,7 @@ async function initCommand(options) {
|
|
|
4062
4102
|
const precommitResult = installPreCommitHook();
|
|
4063
4103
|
if (precommitResult.installed) {
|
|
4064
4104
|
console.log(` ${chalk4.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
|
|
4065
|
-
console.log(chalk4.dim(" Run
|
|
4105
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber hooks remove-precommit") + chalk4.dim(" to disable"));
|
|
4066
4106
|
} else if (precommitResult.alreadyInstalled) {
|
|
4067
4107
|
console.log(chalk4.dim(" Pre-commit hook already installed"));
|
|
4068
4108
|
} else {
|
|
@@ -4070,7 +4110,7 @@ async function initCommand(options) {
|
|
|
4070
4110
|
}
|
|
4071
4111
|
}
|
|
4072
4112
|
if (hookChoice === "skip") {
|
|
4073
|
-
console.log(chalk4.dim(" Skipped auto-refresh hooks. Run
|
|
4113
|
+
console.log(chalk4.dim(" Skipped auto-refresh hooks. Run ") + chalk4.hex("#83D1EB")("caliber hooks install") + chalk4.dim(" later to enable."));
|
|
4074
4114
|
}
|
|
4075
4115
|
const afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4076
4116
|
if (afterScore.score < baselineScore.score) {
|
|
@@ -4083,13 +4123,15 @@ async function initCommand(options) {
|
|
|
4083
4123
|
}
|
|
4084
4124
|
} catch {
|
|
4085
4125
|
}
|
|
4086
|
-
console.log(chalk4.dim(" Run
|
|
4126
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber onboard --force") + chalk4.dim(" to override.\n"));
|
|
4087
4127
|
return;
|
|
4088
4128
|
}
|
|
4089
4129
|
displayScoreDelta(baselineScore, afterScore);
|
|
4090
|
-
console.log(chalk4.bold.green("
|
|
4091
|
-
console.log(chalk4.dim(" Run
|
|
4130
|
+
console.log(chalk4.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
|
|
4131
|
+
console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber undo") + chalk4.dim(" to revert changes.\n"));
|
|
4092
4132
|
console.log(chalk4.bold(" Next steps:\n"));
|
|
4133
|
+
console.log(` ${title("caliber score")} See your full config breakdown`);
|
|
4134
|
+
console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
|
|
4093
4135
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
4094
4136
|
console.log("");
|
|
4095
4137
|
}
|
|
@@ -4136,7 +4178,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4136
4178
|
}
|
|
4137
4179
|
function summarizeSetup(action, setup) {
|
|
4138
4180
|
const descriptions = setup.fileDescriptions;
|
|
4139
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4181
|
+
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4140
4182
|
return `${action}. Files:
|
|
4141
4183
|
${files}`;
|
|
4142
4184
|
}
|
|
@@ -4157,6 +4199,36 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
|
4157
4199
|
return true;
|
|
4158
4200
|
}
|
|
4159
4201
|
}
|
|
4202
|
+
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
4203
|
+
const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
4204
|
+
const checkList = failingChecks.map((c) => ({
|
|
4205
|
+
id: c.id,
|
|
4206
|
+
name: c.name,
|
|
4207
|
+
suggestion: c.suggestion
|
|
4208
|
+
}));
|
|
4209
|
+
try {
|
|
4210
|
+
const result = await llmJsonCall({
|
|
4211
|
+
system: `You evaluate whether scoring checks are applicable to a project.
|
|
4212
|
+
Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
|
|
4213
|
+
|
|
4214
|
+
Only dismiss checks that truly don't apply \u2014 e.g. "Build/test/lint commands" for a pure Terraform/HCL repo with no build system.
|
|
4215
|
+
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
4216
|
+
|
|
4217
|
+
Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
|
|
4218
|
+
prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
|
|
4219
|
+
Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
|
|
4220
|
+
|
|
4221
|
+
Failing checks:
|
|
4222
|
+
${JSON.stringify(checkList, null, 2)}`,
|
|
4223
|
+
maxTokens: 200,
|
|
4224
|
+
...fastModel ? { model: fastModel } : {}
|
|
4225
|
+
});
|
|
4226
|
+
if (!Array.isArray(result.dismissed)) return [];
|
|
4227
|
+
return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4228
|
+
} catch {
|
|
4229
|
+
return [];
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4160
4232
|
function promptInput2(question) {
|
|
4161
4233
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
4162
4234
|
return new Promise((resolve2) => {
|
|
@@ -4226,8 +4298,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4226
4298
|
return;
|
|
4227
4299
|
}
|
|
4228
4300
|
const fileInfos = stagedFiles.map((file) => {
|
|
4229
|
-
const proposed =
|
|
4230
|
-
const current = file.currentPath ?
|
|
4301
|
+
const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
|
|
4302
|
+
const current = file.currentPath ? fs18.readFileSync(file.currentPath, "utf-8") : "";
|
|
4231
4303
|
const patch = createTwoFilesPatch(
|
|
4232
4304
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
4233
4305
|
file.relativePath,
|
|
@@ -4410,7 +4482,7 @@ function printSetupSummary(setup) {
|
|
|
4410
4482
|
};
|
|
4411
4483
|
if (claude) {
|
|
4412
4484
|
if (claude.claudeMd) {
|
|
4413
|
-
const icon =
|
|
4485
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk4.yellow("~") : chalk4.green("+");
|
|
4414
4486
|
const desc = getDescription("CLAUDE.md");
|
|
4415
4487
|
console.log(` ${icon} ${chalk4.bold("CLAUDE.md")}`);
|
|
4416
4488
|
if (desc) console.log(chalk4.dim(` ${desc}`));
|
|
@@ -4420,7 +4492,7 @@ function printSetupSummary(setup) {
|
|
|
4420
4492
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4421
4493
|
for (const skill of skills) {
|
|
4422
4494
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4423
|
-
const icon =
|
|
4495
|
+
const icon = fs18.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
|
|
4424
4496
|
const desc = getDescription(skillPath);
|
|
4425
4497
|
console.log(` ${icon} ${chalk4.bold(skillPath)}`);
|
|
4426
4498
|
console.log(chalk4.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -4430,7 +4502,7 @@ function printSetupSummary(setup) {
|
|
|
4430
4502
|
}
|
|
4431
4503
|
if (cursor) {
|
|
4432
4504
|
if (cursor.cursorrules) {
|
|
4433
|
-
const icon =
|
|
4505
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk4.yellow("~") : chalk4.green("+");
|
|
4434
4506
|
const desc = getDescription(".cursorrules");
|
|
4435
4507
|
console.log(` ${icon} ${chalk4.bold(".cursorrules")}`);
|
|
4436
4508
|
if (desc) console.log(chalk4.dim(` ${desc}`));
|
|
@@ -4440,7 +4512,7 @@ function printSetupSummary(setup) {
|
|
|
4440
4512
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4441
4513
|
for (const skill of cursorSkills) {
|
|
4442
4514
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4443
|
-
const icon =
|
|
4515
|
+
const icon = fs18.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
|
|
4444
4516
|
const desc = getDescription(skillPath);
|
|
4445
4517
|
console.log(` ${icon} ${chalk4.bold(skillPath)}`);
|
|
4446
4518
|
console.log(chalk4.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -4451,7 +4523,7 @@ function printSetupSummary(setup) {
|
|
|
4451
4523
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
4452
4524
|
for (const rule of rules) {
|
|
4453
4525
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
4454
|
-
const icon =
|
|
4526
|
+
const icon = fs18.existsSync(rulePath) ? chalk4.yellow("~") : chalk4.green("+");
|
|
4455
4527
|
const desc = getDescription(rulePath);
|
|
4456
4528
|
console.log(` ${icon} ${chalk4.bold(rulePath)}`);
|
|
4457
4529
|
if (desc) {
|
|
@@ -4464,6 +4536,11 @@ function printSetupSummary(setup) {
|
|
|
4464
4536
|
}
|
|
4465
4537
|
}
|
|
4466
4538
|
}
|
|
4539
|
+
if (!fs18.existsSync("AGENTS.md")) {
|
|
4540
|
+
console.log(` ${chalk4.green("+")} ${chalk4.bold("AGENTS.md")}`);
|
|
4541
|
+
console.log(chalk4.dim(" Cross-agent coordination file"));
|
|
4542
|
+
console.log("");
|
|
4543
|
+
}
|
|
4467
4544
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
4468
4545
|
for (const del of deletions) {
|
|
4469
4546
|
console.log(` ${chalk4.red("-")} ${chalk4.bold(del.filePath)}`);
|
|
@@ -4487,8 +4564,8 @@ function ensurePermissions() {
|
|
|
4487
4564
|
const settingsPath = ".claude/settings.json";
|
|
4488
4565
|
let settings = {};
|
|
4489
4566
|
try {
|
|
4490
|
-
if (
|
|
4491
|
-
settings = JSON.parse(
|
|
4567
|
+
if (fs18.existsSync(settingsPath)) {
|
|
4568
|
+
settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
|
|
4492
4569
|
}
|
|
4493
4570
|
} catch {
|
|
4494
4571
|
}
|
|
@@ -4502,8 +4579,8 @@ function ensurePermissions() {
|
|
|
4502
4579
|
"Bash(git *)"
|
|
4503
4580
|
];
|
|
4504
4581
|
settings.permissions = permissions;
|
|
4505
|
-
if (!
|
|
4506
|
-
|
|
4582
|
+
if (!fs18.existsSync(".claude")) fs18.mkdirSync(".claude", { recursive: true });
|
|
4583
|
+
fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4507
4584
|
}
|
|
4508
4585
|
function collectSetupFiles(setup) {
|
|
4509
4586
|
const files = [];
|
|
@@ -4533,6 +4610,21 @@ function collectSetupFiles(setup) {
|
|
|
4533
4610
|
}
|
|
4534
4611
|
}
|
|
4535
4612
|
}
|
|
4613
|
+
if (!fs18.existsSync("AGENTS.md")) {
|
|
4614
|
+
const agentRefs = [];
|
|
4615
|
+
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
4616
|
+
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
4617
|
+
if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
4618
|
+
files.push({
|
|
4619
|
+
path: "AGENTS.md",
|
|
4620
|
+
content: `# AGENTS.md
|
|
4621
|
+
|
|
4622
|
+
This project uses AI coding agents configured by [Caliber](https://github.com/rely-ai-org/caliber).
|
|
4623
|
+
|
|
4624
|
+
${agentRefs.join(" ")}
|
|
4625
|
+
`
|
|
4626
|
+
});
|
|
4627
|
+
}
|
|
4536
4628
|
return files;
|
|
4537
4629
|
}
|
|
4538
4630
|
|
|
@@ -4569,7 +4661,7 @@ function undoCommand() {
|
|
|
4569
4661
|
|
|
4570
4662
|
// src/commands/status.ts
|
|
4571
4663
|
import chalk6 from "chalk";
|
|
4572
|
-
import
|
|
4664
|
+
import fs19 from "fs";
|
|
4573
4665
|
async function statusCommand(options) {
|
|
4574
4666
|
const config = loadConfig();
|
|
4575
4667
|
const manifest = readManifest();
|
|
@@ -4586,16 +4678,16 @@ async function statusCommand(options) {
|
|
|
4586
4678
|
if (config) {
|
|
4587
4679
|
console.log(` LLM: ${chalk6.green(config.provider)} (${config.model})`);
|
|
4588
4680
|
} else {
|
|
4589
|
-
console.log(` LLM: ${chalk6.yellow("Not configured")} \u2014 run
|
|
4681
|
+
console.log(` LLM: ${chalk6.yellow("Not configured")} \u2014 run ${chalk6.hex("#83D1EB")("caliber config")}`);
|
|
4590
4682
|
}
|
|
4591
4683
|
if (!manifest) {
|
|
4592
4684
|
console.log(` Setup: ${chalk6.dim("No setup applied")}`);
|
|
4593
|
-
console.log(chalk6.dim("\n Run
|
|
4685
|
+
console.log(chalk6.dim("\n Run ") + chalk6.hex("#83D1EB")("caliber onboard") + chalk6.dim(" to get started.\n"));
|
|
4594
4686
|
return;
|
|
4595
4687
|
}
|
|
4596
4688
|
console.log(` Files managed: ${chalk6.cyan(manifest.entries.length.toString())}`);
|
|
4597
4689
|
for (const entry of manifest.entries) {
|
|
4598
|
-
const exists =
|
|
4690
|
+
const exists = fs19.existsSync(entry.path);
|
|
4599
4691
|
const icon = exists ? chalk6.green("\u2713") : chalk6.red("\u2717");
|
|
4600
4692
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
4601
4693
|
}
|
|
@@ -4609,12 +4701,12 @@ import confirm from "@inquirer/confirm";
|
|
|
4609
4701
|
async function regenerateCommand(options) {
|
|
4610
4702
|
const config = loadConfig();
|
|
4611
4703
|
if (!config) {
|
|
4612
|
-
console.log(chalk7.red("No LLM provider configured. Run
|
|
4704
|
+
console.log(chalk7.red("No LLM provider configured. Run ") + chalk7.hex("#83D1EB")("caliber config") + chalk7.red(" (e.g. choose Cursor) or set ANTHROPIC_API_KEY."));
|
|
4613
4705
|
throw new Error("__exit__");
|
|
4614
4706
|
}
|
|
4615
4707
|
const manifest = readManifest();
|
|
4616
4708
|
if (!manifest) {
|
|
4617
|
-
console.log(chalk7.yellow("No existing setup found. Run
|
|
4709
|
+
console.log(chalk7.yellow("No existing setup found. Run ") + chalk7.hex("#83D1EB")("caliber onboard") + chalk7.yellow(" first."));
|
|
4618
4710
|
throw new Error("__exit__");
|
|
4619
4711
|
}
|
|
4620
4712
|
const spinner = ora3("Re-analyzing project...").start();
|
|
@@ -4682,13 +4774,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
|
|
|
4682
4774
|
import { join as join8, dirname as dirname2 } from "path";
|
|
4683
4775
|
|
|
4684
4776
|
// src/scanner/index.ts
|
|
4685
|
-
import
|
|
4686
|
-
import
|
|
4777
|
+
import fs20 from "fs";
|
|
4778
|
+
import path17 from "path";
|
|
4687
4779
|
import crypto2 from "crypto";
|
|
4688
4780
|
function scanLocalState(dir) {
|
|
4689
4781
|
const items = [];
|
|
4690
|
-
const claudeMdPath =
|
|
4691
|
-
if (
|
|
4782
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
4783
|
+
if (fs20.existsSync(claudeMdPath)) {
|
|
4692
4784
|
items.push({
|
|
4693
4785
|
type: "rule",
|
|
4694
4786
|
platform: "claude",
|
|
@@ -4697,10 +4789,10 @@ function scanLocalState(dir) {
|
|
|
4697
4789
|
path: claudeMdPath
|
|
4698
4790
|
});
|
|
4699
4791
|
}
|
|
4700
|
-
const skillsDir =
|
|
4701
|
-
if (
|
|
4702
|
-
for (const file of
|
|
4703
|
-
const filePath =
|
|
4792
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
4793
|
+
if (fs20.existsSync(skillsDir)) {
|
|
4794
|
+
for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
4795
|
+
const filePath = path17.join(skillsDir, file);
|
|
4704
4796
|
items.push({
|
|
4705
4797
|
type: "skill",
|
|
4706
4798
|
platform: "claude",
|
|
@@ -4710,10 +4802,10 @@ function scanLocalState(dir) {
|
|
|
4710
4802
|
});
|
|
4711
4803
|
}
|
|
4712
4804
|
}
|
|
4713
|
-
const mcpJsonPath =
|
|
4714
|
-
if (
|
|
4805
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
4806
|
+
if (fs20.existsSync(mcpJsonPath)) {
|
|
4715
4807
|
try {
|
|
4716
|
-
const mcpJson = JSON.parse(
|
|
4808
|
+
const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
|
|
4717
4809
|
if (mcpJson.mcpServers) {
|
|
4718
4810
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4719
4811
|
items.push({
|
|
@@ -4728,8 +4820,8 @@ function scanLocalState(dir) {
|
|
|
4728
4820
|
} catch {
|
|
4729
4821
|
}
|
|
4730
4822
|
}
|
|
4731
|
-
const cursorrulesPath =
|
|
4732
|
-
if (
|
|
4823
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
4824
|
+
if (fs20.existsSync(cursorrulesPath)) {
|
|
4733
4825
|
items.push({
|
|
4734
4826
|
type: "rule",
|
|
4735
4827
|
platform: "cursor",
|
|
@@ -4738,10 +4830,10 @@ function scanLocalState(dir) {
|
|
|
4738
4830
|
path: cursorrulesPath
|
|
4739
4831
|
});
|
|
4740
4832
|
}
|
|
4741
|
-
const cursorRulesDir =
|
|
4742
|
-
if (
|
|
4743
|
-
for (const file of
|
|
4744
|
-
const filePath =
|
|
4833
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
4834
|
+
if (fs20.existsSync(cursorRulesDir)) {
|
|
4835
|
+
for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
4836
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
4745
4837
|
items.push({
|
|
4746
4838
|
type: "rule",
|
|
4747
4839
|
platform: "cursor",
|
|
@@ -4751,12 +4843,12 @@ function scanLocalState(dir) {
|
|
|
4751
4843
|
});
|
|
4752
4844
|
}
|
|
4753
4845
|
}
|
|
4754
|
-
const cursorSkillsDir =
|
|
4755
|
-
if (
|
|
4846
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
4847
|
+
if (fs20.existsSync(cursorSkillsDir)) {
|
|
4756
4848
|
try {
|
|
4757
|
-
for (const name of
|
|
4758
|
-
const skillFile =
|
|
4759
|
-
if (
|
|
4849
|
+
for (const name of fs20.readdirSync(cursorSkillsDir)) {
|
|
4850
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
4851
|
+
if (fs20.existsSync(skillFile)) {
|
|
4760
4852
|
items.push({
|
|
4761
4853
|
type: "skill",
|
|
4762
4854
|
platform: "cursor",
|
|
@@ -4769,10 +4861,10 @@ function scanLocalState(dir) {
|
|
|
4769
4861
|
} catch {
|
|
4770
4862
|
}
|
|
4771
4863
|
}
|
|
4772
|
-
const cursorMcpPath =
|
|
4773
|
-
if (
|
|
4864
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
4865
|
+
if (fs20.existsSync(cursorMcpPath)) {
|
|
4774
4866
|
try {
|
|
4775
|
-
const mcpJson = JSON.parse(
|
|
4867
|
+
const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
|
|
4776
4868
|
if (mcpJson.mcpServers) {
|
|
4777
4869
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
4778
4870
|
items.push({
|
|
@@ -4790,7 +4882,7 @@ function scanLocalState(dir) {
|
|
|
4790
4882
|
return items;
|
|
4791
4883
|
}
|
|
4792
4884
|
function hashFile(filePath) {
|
|
4793
|
-
const text =
|
|
4885
|
+
const text = fs20.readFileSync(filePath, "utf-8");
|
|
4794
4886
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
4795
4887
|
}
|
|
4796
4888
|
function hashJson(obj) {
|
|
@@ -5356,18 +5448,18 @@ async function scoreCommand(options) {
|
|
|
5356
5448
|
const separator = chalk9.gray(" " + "\u2500".repeat(53));
|
|
5357
5449
|
console.log(separator);
|
|
5358
5450
|
if (result.score < 40) {
|
|
5359
|
-
console.log(chalk9.gray(" Run ") + chalk9.hex("#
|
|
5451
|
+
console.log(chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.gray(" to generate a complete, optimized setup."));
|
|
5360
5452
|
} else if (result.score < 70) {
|
|
5361
|
-
console.log(chalk9.gray(" Run ") + chalk9.hex("#
|
|
5453
|
+
console.log(chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.gray(" to improve your setup."));
|
|
5362
5454
|
} else {
|
|
5363
|
-
console.log(chalk9.green(" Looking good!") + chalk9.gray(" Run ") + chalk9.hex("#
|
|
5455
|
+
console.log(chalk9.green(" Looking good!") + chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber update") + chalk9.gray(" to keep it fresh."));
|
|
5364
5456
|
}
|
|
5365
5457
|
console.log("");
|
|
5366
5458
|
}
|
|
5367
5459
|
|
|
5368
5460
|
// src/commands/refresh.ts
|
|
5369
|
-
import
|
|
5370
|
-
import
|
|
5461
|
+
import fs22 from "fs";
|
|
5462
|
+
import path19 from "path";
|
|
5371
5463
|
import chalk10 from "chalk";
|
|
5372
5464
|
import ora5 from "ora";
|
|
5373
5465
|
|
|
@@ -5444,37 +5536,37 @@ function collectDiff(lastSha) {
|
|
|
5444
5536
|
}
|
|
5445
5537
|
|
|
5446
5538
|
// src/writers/refresh.ts
|
|
5447
|
-
import
|
|
5448
|
-
import
|
|
5539
|
+
import fs21 from "fs";
|
|
5540
|
+
import path18 from "path";
|
|
5449
5541
|
function writeRefreshDocs(docs) {
|
|
5450
5542
|
const written = [];
|
|
5451
5543
|
if (docs.claudeMd) {
|
|
5452
|
-
|
|
5544
|
+
fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
5453
5545
|
written.push("CLAUDE.md");
|
|
5454
5546
|
}
|
|
5455
5547
|
if (docs.readmeMd) {
|
|
5456
|
-
|
|
5548
|
+
fs21.writeFileSync("README.md", docs.readmeMd);
|
|
5457
5549
|
written.push("README.md");
|
|
5458
5550
|
}
|
|
5459
5551
|
if (docs.cursorrules) {
|
|
5460
|
-
|
|
5552
|
+
fs21.writeFileSync(".cursorrules", docs.cursorrules);
|
|
5461
5553
|
written.push(".cursorrules");
|
|
5462
5554
|
}
|
|
5463
5555
|
if (docs.cursorRules) {
|
|
5464
|
-
const rulesDir =
|
|
5465
|
-
if (!
|
|
5556
|
+
const rulesDir = path18.join(".cursor", "rules");
|
|
5557
|
+
if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
|
|
5466
5558
|
for (const rule of docs.cursorRules) {
|
|
5467
|
-
const filePath =
|
|
5468
|
-
|
|
5559
|
+
const filePath = path18.join(rulesDir, rule.filename);
|
|
5560
|
+
fs21.writeFileSync(filePath, rule.content);
|
|
5469
5561
|
written.push(filePath);
|
|
5470
5562
|
}
|
|
5471
5563
|
}
|
|
5472
5564
|
if (docs.claudeSkills) {
|
|
5473
|
-
const skillsDir =
|
|
5474
|
-
if (!
|
|
5565
|
+
const skillsDir = path18.join(".claude", "skills");
|
|
5566
|
+
if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
|
|
5475
5567
|
for (const skill of docs.claudeSkills) {
|
|
5476
|
-
const filePath =
|
|
5477
|
-
|
|
5568
|
+
const filePath = path18.join(skillsDir, skill.filename);
|
|
5569
|
+
fs21.writeFileSync(filePath, skill.content);
|
|
5478
5570
|
written.push(filePath);
|
|
5479
5571
|
}
|
|
5480
5572
|
}
|
|
@@ -5549,11 +5641,11 @@ function log(quiet, ...args) {
|
|
|
5549
5641
|
function discoverGitRepos(parentDir) {
|
|
5550
5642
|
const repos = [];
|
|
5551
5643
|
try {
|
|
5552
|
-
const entries =
|
|
5644
|
+
const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
|
|
5553
5645
|
for (const entry of entries) {
|
|
5554
5646
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
5555
|
-
const childPath =
|
|
5556
|
-
if (
|
|
5647
|
+
const childPath = path19.join(parentDir, entry.name);
|
|
5648
|
+
if (fs22.existsSync(path19.join(childPath, ".git"))) {
|
|
5557
5649
|
repos.push(childPath);
|
|
5558
5650
|
}
|
|
5559
5651
|
}
|
|
@@ -5631,7 +5723,7 @@ async function refreshCommand(options) {
|
|
|
5631
5723
|
const config = loadConfig();
|
|
5632
5724
|
if (!config) {
|
|
5633
5725
|
if (quiet) return;
|
|
5634
|
-
console.log(chalk10.red("No LLM provider configured. Run
|
|
5726
|
+
console.log(chalk10.red("No LLM provider configured. Run ") + chalk10.hex("#83D1EB")("caliber config") + chalk10.red(" (e.g. choose Cursor) or set an API key."));
|
|
5635
5727
|
throw new Error("__exit__");
|
|
5636
5728
|
}
|
|
5637
5729
|
if (isGitRepo()) {
|
|
@@ -5648,7 +5740,7 @@ async function refreshCommand(options) {
|
|
|
5648
5740
|
`));
|
|
5649
5741
|
const originalDir = process.cwd();
|
|
5650
5742
|
for (const repo of repos) {
|
|
5651
|
-
const repoName =
|
|
5743
|
+
const repoName = path19.basename(repo);
|
|
5652
5744
|
try {
|
|
5653
5745
|
process.chdir(repo);
|
|
5654
5746
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -5895,8 +5987,8 @@ function readStdin() {
|
|
|
5895
5987
|
|
|
5896
5988
|
// src/learner/storage.ts
|
|
5897
5989
|
init_constants();
|
|
5898
|
-
import
|
|
5899
|
-
import
|
|
5990
|
+
import fs23 from "fs";
|
|
5991
|
+
import path20 from "path";
|
|
5900
5992
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
5901
5993
|
var DEFAULT_STATE = {
|
|
5902
5994
|
sessionId: null,
|
|
@@ -5904,15 +5996,15 @@ var DEFAULT_STATE = {
|
|
|
5904
5996
|
lastAnalysisTimestamp: null
|
|
5905
5997
|
};
|
|
5906
5998
|
function ensureLearningDir() {
|
|
5907
|
-
if (!
|
|
5908
|
-
|
|
5999
|
+
if (!fs23.existsSync(LEARNING_DIR)) {
|
|
6000
|
+
fs23.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
5909
6001
|
}
|
|
5910
6002
|
}
|
|
5911
6003
|
function sessionFilePath() {
|
|
5912
|
-
return
|
|
6004
|
+
return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
5913
6005
|
}
|
|
5914
6006
|
function stateFilePath() {
|
|
5915
|
-
return
|
|
6007
|
+
return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
5916
6008
|
}
|
|
5917
6009
|
function truncateResponse(response) {
|
|
5918
6010
|
const str = JSON.stringify(response);
|
|
@@ -5923,50 +6015,50 @@ function appendEvent(event) {
|
|
|
5923
6015
|
ensureLearningDir();
|
|
5924
6016
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
5925
6017
|
const filePath = sessionFilePath();
|
|
5926
|
-
|
|
6018
|
+
fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
5927
6019
|
const count = getEventCount();
|
|
5928
6020
|
if (count > LEARNING_MAX_EVENTS) {
|
|
5929
|
-
const lines =
|
|
6021
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
5930
6022
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
5931
|
-
|
|
6023
|
+
fs23.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
5932
6024
|
}
|
|
5933
6025
|
}
|
|
5934
6026
|
function readAllEvents() {
|
|
5935
6027
|
const filePath = sessionFilePath();
|
|
5936
|
-
if (!
|
|
5937
|
-
const lines =
|
|
6028
|
+
if (!fs23.existsSync(filePath)) return [];
|
|
6029
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
5938
6030
|
return lines.map((line) => JSON.parse(line));
|
|
5939
6031
|
}
|
|
5940
6032
|
function getEventCount() {
|
|
5941
6033
|
const filePath = sessionFilePath();
|
|
5942
|
-
if (!
|
|
5943
|
-
const content =
|
|
6034
|
+
if (!fs23.existsSync(filePath)) return 0;
|
|
6035
|
+
const content = fs23.readFileSync(filePath, "utf-8");
|
|
5944
6036
|
return content.split("\n").filter(Boolean).length;
|
|
5945
6037
|
}
|
|
5946
6038
|
function clearSession() {
|
|
5947
6039
|
const filePath = sessionFilePath();
|
|
5948
|
-
if (
|
|
6040
|
+
if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
|
|
5949
6041
|
}
|
|
5950
6042
|
function readState2() {
|
|
5951
6043
|
const filePath = stateFilePath();
|
|
5952
|
-
if (!
|
|
6044
|
+
if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
5953
6045
|
try {
|
|
5954
|
-
return JSON.parse(
|
|
6046
|
+
return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
5955
6047
|
} catch {
|
|
5956
6048
|
return { ...DEFAULT_STATE };
|
|
5957
6049
|
}
|
|
5958
6050
|
}
|
|
5959
6051
|
function writeState2(state) {
|
|
5960
6052
|
ensureLearningDir();
|
|
5961
|
-
|
|
6053
|
+
fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
5962
6054
|
}
|
|
5963
6055
|
function resetState() {
|
|
5964
6056
|
writeState2({ ...DEFAULT_STATE });
|
|
5965
6057
|
}
|
|
5966
6058
|
|
|
5967
6059
|
// src/learner/writer.ts
|
|
5968
|
-
import
|
|
5969
|
-
import
|
|
6060
|
+
import fs24 from "fs";
|
|
6061
|
+
import path21 from "path";
|
|
5970
6062
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
5971
6063
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
5972
6064
|
function writeLearnedContent(update) {
|
|
@@ -5986,8 +6078,8 @@ function writeLearnedContent(update) {
|
|
|
5986
6078
|
function writeLearnedSection(content) {
|
|
5987
6079
|
const claudeMdPath = "CLAUDE.md";
|
|
5988
6080
|
let existing = "";
|
|
5989
|
-
if (
|
|
5990
|
-
existing =
|
|
6081
|
+
if (fs24.existsSync(claudeMdPath)) {
|
|
6082
|
+
existing = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
5991
6083
|
}
|
|
5992
6084
|
const section = `${LEARNED_START}
|
|
5993
6085
|
${content}
|
|
@@ -6001,15 +6093,15 @@ ${LEARNED_END}`;
|
|
|
6001
6093
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6002
6094
|
updated = existing + separator + "\n" + section + "\n";
|
|
6003
6095
|
}
|
|
6004
|
-
|
|
6096
|
+
fs24.writeFileSync(claudeMdPath, updated);
|
|
6005
6097
|
}
|
|
6006
6098
|
function writeLearnedSkill(skill) {
|
|
6007
|
-
const skillDir =
|
|
6008
|
-
if (!
|
|
6009
|
-
const skillPath =
|
|
6010
|
-
if (!skill.isNew &&
|
|
6011
|
-
const existing =
|
|
6012
|
-
|
|
6099
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
6100
|
+
if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
|
|
6101
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
6102
|
+
if (!skill.isNew && fs24.existsSync(skillPath)) {
|
|
6103
|
+
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
6104
|
+
fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6013
6105
|
} else {
|
|
6014
6106
|
const frontmatter = [
|
|
6015
6107
|
"---",
|
|
@@ -6018,14 +6110,14 @@ function writeLearnedSkill(skill) {
|
|
|
6018
6110
|
"---",
|
|
6019
6111
|
""
|
|
6020
6112
|
].join("\n");
|
|
6021
|
-
|
|
6113
|
+
fs24.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6022
6114
|
}
|
|
6023
6115
|
return skillPath;
|
|
6024
6116
|
}
|
|
6025
6117
|
function readLearnedSection() {
|
|
6026
6118
|
const claudeMdPath = "CLAUDE.md";
|
|
6027
|
-
if (!
|
|
6028
|
-
const content =
|
|
6119
|
+
if (!fs24.existsSync(claudeMdPath)) return null;
|
|
6120
|
+
const content = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
6029
6121
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6030
6122
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6031
6123
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6209,14 +6301,14 @@ Learned items in CLAUDE.md: ${chalk13.cyan(String(lineCount))}`);
|
|
|
6209
6301
|
}
|
|
6210
6302
|
|
|
6211
6303
|
// src/cli.ts
|
|
6212
|
-
var __dirname =
|
|
6304
|
+
var __dirname = path22.dirname(fileURLToPath(import.meta.url));
|
|
6213
6305
|
var pkg = JSON.parse(
|
|
6214
|
-
|
|
6306
|
+
fs25.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6215
6307
|
);
|
|
6216
6308
|
var program = new Command();
|
|
6217
6309
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
6218
6310
|
program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
|
|
6219
|
-
program.command("init").description("
|
|
6311
|
+
program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agent: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
|
|
6220
6312
|
program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
|
|
6221
6313
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
|
|
6222
6314
|
program.command("regenerate").alias("regen").alias("re").alias("update").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
|
|
@@ -6233,22 +6325,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
6233
6325
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
6234
6326
|
|
|
6235
6327
|
// src/utils/version-check.ts
|
|
6236
|
-
import
|
|
6237
|
-
import
|
|
6328
|
+
import fs26 from "fs";
|
|
6329
|
+
import path23 from "path";
|
|
6238
6330
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6239
6331
|
import { execSync as execSync9 } from "child_process";
|
|
6240
6332
|
import chalk14 from "chalk";
|
|
6241
6333
|
import ora6 from "ora";
|
|
6242
6334
|
import confirm2 from "@inquirer/confirm";
|
|
6243
|
-
var __dirname_vc =
|
|
6335
|
+
var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
|
|
6244
6336
|
var pkg2 = JSON.parse(
|
|
6245
|
-
|
|
6337
|
+
fs26.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6246
6338
|
);
|
|
6247
6339
|
function getInstalledVersion() {
|
|
6248
6340
|
try {
|
|
6249
6341
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6250
|
-
const pkgPath =
|
|
6251
|
-
return JSON.parse(
|
|
6342
|
+
const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6343
|
+
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
6252
6344
|
} catch {
|
|
6253
6345
|
return null;
|
|
6254
6346
|
}
|
package/package.json
CHANGED