@rely-ai/caliber 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +461 -743
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -32,17 +32,17 @@ __export(constants_exports, {
|
|
|
32
32
|
LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
|
|
33
33
|
MANIFEST_FILE: () => MANIFEST_FILE
|
|
34
34
|
});
|
|
35
|
-
import
|
|
35
|
+
import path8 from "path";
|
|
36
36
|
import os2 from "os";
|
|
37
37
|
var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
|
|
38
38
|
var init_constants = __esm({
|
|
39
39
|
"src/constants.ts"() {
|
|
40
40
|
"use strict";
|
|
41
|
-
AUTH_DIR =
|
|
41
|
+
AUTH_DIR = path8.join(os2.homedir(), ".caliber");
|
|
42
42
|
CALIBER_DIR = ".caliber";
|
|
43
|
-
MANIFEST_FILE =
|
|
44
|
-
BACKUPS_DIR =
|
|
45
|
-
LEARNING_DIR =
|
|
43
|
+
MANIFEST_FILE = path8.join(CALIBER_DIR, "manifest.json");
|
|
44
|
+
BACKUPS_DIR = path8.join(CALIBER_DIR, "backups");
|
|
45
|
+
LEARNING_DIR = path8.join(CALIBER_DIR, "learning");
|
|
46
46
|
LEARNING_SESSION_FILE = "current-session.jsonl";
|
|
47
47
|
LEARNING_STATE_FILE = "state.json";
|
|
48
48
|
LEARNING_MAX_EVENTS = 500;
|
|
@@ -51,8 +51,8 @@ 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 path21 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
@@ -60,11 +60,11 @@ import chalk5 from "chalk";
|
|
|
60
60
|
import ora2 from "ora";
|
|
61
61
|
import readline4 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
|
-
import
|
|
67
|
-
import
|
|
66
|
+
import fs6 from "fs";
|
|
67
|
+
import path5 from "path";
|
|
68
68
|
|
|
69
69
|
// src/fingerprint/git.ts
|
|
70
70
|
import { execSync } from "child_process";
|
|
@@ -91,54 +91,9 @@ function isGitRepo() {
|
|
|
91
91
|
return isInsideGitRepo();
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// src/fingerprint/
|
|
94
|
+
// src/fingerprint/file-tree.ts
|
|
95
95
|
import fs from "fs";
|
|
96
96
|
import path from "path";
|
|
97
|
-
import { globSync } from "glob";
|
|
98
|
-
var WORKSPACE_GLOBS = [
|
|
99
|
-
"apps/*/package.json",
|
|
100
|
-
"packages/*/package.json",
|
|
101
|
-
"services/*/package.json",
|
|
102
|
-
"libs/*/package.json"
|
|
103
|
-
];
|
|
104
|
-
function analyzePackageJson(dir) {
|
|
105
|
-
const rootPkgPath = path.join(dir, "package.json");
|
|
106
|
-
let name;
|
|
107
|
-
const languages = [];
|
|
108
|
-
if (fs.existsSync(rootPkgPath)) {
|
|
109
|
-
try {
|
|
110
|
-
const pkg3 = JSON.parse(fs.readFileSync(rootPkgPath, "utf-8"));
|
|
111
|
-
name = pkg3.name;
|
|
112
|
-
const allDeps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
113
|
-
if (allDeps.typescript || allDeps["@types/node"]) {
|
|
114
|
-
languages.push("TypeScript");
|
|
115
|
-
}
|
|
116
|
-
languages.push("JavaScript");
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
for (const glob of WORKSPACE_GLOBS) {
|
|
121
|
-
const matches = globSync(glob, { cwd: dir, absolute: true });
|
|
122
|
-
for (const pkgPath of matches) {
|
|
123
|
-
try {
|
|
124
|
-
const pkg3 = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
125
|
-
const deps = { ...pkg3.dependencies, ...pkg3.devDependencies };
|
|
126
|
-
if (deps.typescript || deps["@types/node"]) {
|
|
127
|
-
languages.push("TypeScript");
|
|
128
|
-
}
|
|
129
|
-
} catch {
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
name,
|
|
135
|
-
languages: [...new Set(languages)]
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/fingerprint/file-tree.ts
|
|
140
|
-
import fs2 from "fs";
|
|
141
|
-
import path2 from "path";
|
|
142
97
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
143
98
|
"node_modules",
|
|
144
99
|
".git",
|
|
@@ -161,10 +116,10 @@ function getFileTree(dir, maxDepth = 3) {
|
|
|
161
116
|
}
|
|
162
117
|
function scan(base, rel, depth, maxDepth, result) {
|
|
163
118
|
if (depth > maxDepth) return;
|
|
164
|
-
const fullPath =
|
|
119
|
+
const fullPath = path.join(base, rel);
|
|
165
120
|
let entries;
|
|
166
121
|
try {
|
|
167
|
-
entries =
|
|
122
|
+
entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
168
123
|
} catch {
|
|
169
124
|
return;
|
|
170
125
|
}
|
|
@@ -181,122 +136,87 @@ function scan(base, rel, depth, maxDepth, result) {
|
|
|
181
136
|
}
|
|
182
137
|
}
|
|
183
138
|
|
|
184
|
-
// src/fingerprint/languages.ts
|
|
185
|
-
import path3 from "path";
|
|
186
|
-
var EXT_TO_LANG = {
|
|
187
|
-
".ts": "TypeScript",
|
|
188
|
-
".tsx": "TypeScript",
|
|
189
|
-
".js": "JavaScript",
|
|
190
|
-
".jsx": "JavaScript",
|
|
191
|
-
".py": "Python",
|
|
192
|
-
".go": "Go",
|
|
193
|
-
".rs": "Rust",
|
|
194
|
-
".rb": "Ruby",
|
|
195
|
-
".java": "Java",
|
|
196
|
-
".kt": "Kotlin",
|
|
197
|
-
".swift": "Swift",
|
|
198
|
-
".cs": "C#",
|
|
199
|
-
".cpp": "C++",
|
|
200
|
-
".c": "C",
|
|
201
|
-
".php": "PHP",
|
|
202
|
-
".dart": "Dart",
|
|
203
|
-
".ex": "Elixir",
|
|
204
|
-
".exs": "Elixir",
|
|
205
|
-
".scala": "Scala",
|
|
206
|
-
".zig": "Zig"
|
|
207
|
-
};
|
|
208
|
-
function detectLanguages(fileTree) {
|
|
209
|
-
const langs = /* @__PURE__ */ new Set();
|
|
210
|
-
for (const file of fileTree) {
|
|
211
|
-
const ext = path3.extname(file).toLowerCase();
|
|
212
|
-
if (EXT_TO_LANG[ext]) {
|
|
213
|
-
langs.add(EXT_TO_LANG[ext]);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return [...langs];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
139
|
// src/fingerprint/existing-config.ts
|
|
220
|
-
import
|
|
221
|
-
import
|
|
140
|
+
import fs2 from "fs";
|
|
141
|
+
import path2 from "path";
|
|
222
142
|
function readExistingConfigs(dir) {
|
|
223
143
|
const configs = {};
|
|
224
|
-
const readmeMdPath =
|
|
225
|
-
if (
|
|
226
|
-
configs.readmeMd =
|
|
144
|
+
const readmeMdPath = path2.join(dir, "README.md");
|
|
145
|
+
if (fs2.existsSync(readmeMdPath)) {
|
|
146
|
+
configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
|
|
227
147
|
}
|
|
228
|
-
const claudeMdPath =
|
|
229
|
-
if (
|
|
230
|
-
configs.claudeMd =
|
|
148
|
+
const claudeMdPath = path2.join(dir, "CLAUDE.md");
|
|
149
|
+
if (fs2.existsSync(claudeMdPath)) {
|
|
150
|
+
configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
231
151
|
}
|
|
232
|
-
const claudeSettingsPath =
|
|
233
|
-
if (
|
|
152
|
+
const claudeSettingsPath = path2.join(dir, ".claude", "settings.json");
|
|
153
|
+
if (fs2.existsSync(claudeSettingsPath)) {
|
|
234
154
|
try {
|
|
235
|
-
configs.claudeSettings = JSON.parse(
|
|
155
|
+
configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
|
|
236
156
|
} catch {
|
|
237
157
|
}
|
|
238
158
|
}
|
|
239
|
-
const skillsDir =
|
|
240
|
-
if (
|
|
159
|
+
const skillsDir = path2.join(dir, ".claude", "skills");
|
|
160
|
+
if (fs2.existsSync(skillsDir)) {
|
|
241
161
|
try {
|
|
242
|
-
const entries =
|
|
162
|
+
const entries = fs2.readdirSync(skillsDir);
|
|
243
163
|
const skills = [];
|
|
244
164
|
for (const entry of entries) {
|
|
245
|
-
const entryPath =
|
|
246
|
-
const skillMdPath =
|
|
247
|
-
if (
|
|
248
|
-
skills.push({ filename: `${entry}/SKILL.md`, content:
|
|
165
|
+
const entryPath = path2.join(skillsDir, entry);
|
|
166
|
+
const skillMdPath = path2.join(entryPath, "SKILL.md");
|
|
167
|
+
if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
|
|
168
|
+
skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
|
|
249
169
|
} else if (entry.endsWith(".md")) {
|
|
250
|
-
skills.push({ filename: entry, content:
|
|
170
|
+
skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
|
|
251
171
|
}
|
|
252
172
|
}
|
|
253
173
|
if (skills.length > 0) configs.claudeSkills = skills;
|
|
254
174
|
} catch {
|
|
255
175
|
}
|
|
256
176
|
}
|
|
257
|
-
const cursorrulesPath =
|
|
258
|
-
if (
|
|
259
|
-
configs.cursorrules =
|
|
177
|
+
const cursorrulesPath = path2.join(dir, ".cursorrules");
|
|
178
|
+
if (fs2.existsSync(cursorrulesPath)) {
|
|
179
|
+
configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
|
|
260
180
|
}
|
|
261
|
-
const cursorRulesDir =
|
|
262
|
-
if (
|
|
181
|
+
const cursorRulesDir = path2.join(dir, ".cursor", "rules");
|
|
182
|
+
if (fs2.existsSync(cursorRulesDir)) {
|
|
263
183
|
try {
|
|
264
|
-
const files =
|
|
184
|
+
const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
|
|
265
185
|
configs.cursorRules = files.map((f) => ({
|
|
266
186
|
filename: f,
|
|
267
|
-
content:
|
|
187
|
+
content: fs2.readFileSync(path2.join(cursorRulesDir, f), "utf-8")
|
|
268
188
|
}));
|
|
269
189
|
} catch {
|
|
270
190
|
}
|
|
271
191
|
}
|
|
272
|
-
const cursorSkillsDir =
|
|
273
|
-
if (
|
|
192
|
+
const cursorSkillsDir = path2.join(dir, ".cursor", "skills");
|
|
193
|
+
if (fs2.existsSync(cursorSkillsDir)) {
|
|
274
194
|
try {
|
|
275
|
-
const slugs =
|
|
276
|
-
return
|
|
195
|
+
const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
|
|
196
|
+
return fs2.statSync(path2.join(cursorSkillsDir, f)).isDirectory();
|
|
277
197
|
});
|
|
278
|
-
configs.cursorSkills = slugs.filter((slug) =>
|
|
198
|
+
configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path2.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
279
199
|
name,
|
|
280
200
|
filename: "SKILL.md",
|
|
281
|
-
content:
|
|
201
|
+
content: fs2.readFileSync(path2.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
282
202
|
}));
|
|
283
203
|
} catch {
|
|
284
204
|
}
|
|
285
205
|
}
|
|
286
|
-
const mcpJsonPath =
|
|
287
|
-
if (
|
|
206
|
+
const mcpJsonPath = path2.join(dir, ".mcp.json");
|
|
207
|
+
if (fs2.existsSync(mcpJsonPath)) {
|
|
288
208
|
try {
|
|
289
|
-
const mcpJson = JSON.parse(
|
|
209
|
+
const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
|
|
290
210
|
if (mcpJson.mcpServers) {
|
|
291
211
|
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
292
212
|
}
|
|
293
213
|
} catch {
|
|
294
214
|
}
|
|
295
215
|
}
|
|
296
|
-
const cursorMcpPath =
|
|
297
|
-
if (
|
|
216
|
+
const cursorMcpPath = path2.join(dir, ".cursor", "mcp.json");
|
|
217
|
+
if (fs2.existsSync(cursorMcpPath)) {
|
|
298
218
|
try {
|
|
299
|
-
const cursorMcpJson = JSON.parse(
|
|
219
|
+
const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
|
|
300
220
|
if (cursorMcpJson.mcpServers) {
|
|
301
221
|
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
302
222
|
}
|
|
@@ -307,8 +227,8 @@ function readExistingConfigs(dir) {
|
|
|
307
227
|
}
|
|
308
228
|
|
|
309
229
|
// src/fingerprint/code-analysis.ts
|
|
310
|
-
import
|
|
311
|
-
import
|
|
230
|
+
import fs3 from "fs";
|
|
231
|
+
import path3 from "path";
|
|
312
232
|
var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
|
|
313
233
|
"node_modules",
|
|
314
234
|
".git",
|
|
@@ -379,16 +299,16 @@ function analyzeCode(dir) {
|
|
|
379
299
|
let truncated = false;
|
|
380
300
|
const fileSummaries = [];
|
|
381
301
|
for (const relPath of sourceFiles) {
|
|
382
|
-
const fullPath =
|
|
302
|
+
const fullPath = path3.join(dir, relPath);
|
|
383
303
|
let content;
|
|
384
304
|
try {
|
|
385
|
-
content =
|
|
305
|
+
content = fs3.readFileSync(fullPath, "utf-8");
|
|
386
306
|
} catch {
|
|
387
307
|
continue;
|
|
388
308
|
}
|
|
389
309
|
const lineCount = content.split("\n").length;
|
|
390
310
|
if (lineCount > 500) continue;
|
|
391
|
-
const ext =
|
|
311
|
+
const ext = path3.extname(relPath);
|
|
392
312
|
const language = resolveLanguage(ext);
|
|
393
313
|
if (!language) continue;
|
|
394
314
|
const summary = language === "py" ? extractPython(relPath, content) : extractTypeScriptJavaScript(relPath, content, language);
|
|
@@ -404,10 +324,10 @@ function analyzeCode(dir) {
|
|
|
404
324
|
}
|
|
405
325
|
function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir) {
|
|
406
326
|
if (depth > maxDepth) return;
|
|
407
|
-
const fullPath =
|
|
327
|
+
const fullPath = path3.join(base, rel);
|
|
408
328
|
let entries;
|
|
409
329
|
try {
|
|
410
|
-
entries =
|
|
330
|
+
entries = fs3.readdirSync(fullPath, { withFileTypes: true });
|
|
411
331
|
} catch {
|
|
412
332
|
return;
|
|
413
333
|
}
|
|
@@ -426,12 +346,12 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
|
|
|
426
346
|
} else {
|
|
427
347
|
if (CONFIG_FILE_NAMES.has(entry.name)) {
|
|
428
348
|
try {
|
|
429
|
-
const content =
|
|
349
|
+
const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
|
|
430
350
|
configFiles.push({ path: relPath, content });
|
|
431
351
|
} catch {
|
|
432
352
|
}
|
|
433
353
|
}
|
|
434
|
-
const ext =
|
|
354
|
+
const ext = path3.extname(entry.name);
|
|
435
355
|
if (SOURCE_EXTENSIONS.has(ext) && !entry.name.endsWith(".d.ts")) {
|
|
436
356
|
sourceFiles.push(relPath);
|
|
437
357
|
}
|
|
@@ -439,10 +359,10 @@ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir)
|
|
|
439
359
|
}
|
|
440
360
|
}
|
|
441
361
|
function collectConfigsFromDir(base, relDir, pattern, configFiles) {
|
|
442
|
-
const fullDir =
|
|
362
|
+
const fullDir = path3.join(base, relDir);
|
|
443
363
|
let entries;
|
|
444
364
|
try {
|
|
445
|
-
entries =
|
|
365
|
+
entries = fs3.readdirSync(fullDir, { withFileTypes: true });
|
|
446
366
|
} catch {
|
|
447
367
|
return;
|
|
448
368
|
}
|
|
@@ -450,7 +370,7 @@ function collectConfigsFromDir(base, relDir, pattern, configFiles) {
|
|
|
450
370
|
if (entry.isFile() && pattern.test(entry.name)) {
|
|
451
371
|
const relPath = `${relDir}/${entry.name}`;
|
|
452
372
|
try {
|
|
453
|
-
const content =
|
|
373
|
+
const content = fs3.readFileSync(path3.join(base, relPath), "utf-8");
|
|
454
374
|
configFiles.push({ path: relPath, content });
|
|
455
375
|
} catch {
|
|
456
376
|
}
|
|
@@ -464,7 +384,7 @@ function sortByPriority(files) {
|
|
|
464
384
|
const servicePattern = /(service|lib|utils)/i;
|
|
465
385
|
const testPattern = /(test|spec|__tests__)/i;
|
|
466
386
|
function priority(filePath) {
|
|
467
|
-
const base =
|
|
387
|
+
const base = path3.basename(filePath);
|
|
468
388
|
if (entryPointNames.has(base)) return 0;
|
|
469
389
|
if (routePattern.test(filePath)) return 1;
|
|
470
390
|
if (schemaPattern.test(filePath)) return 2;
|
|
@@ -583,11 +503,11 @@ function estimateSummarySize(summary) {
|
|
|
583
503
|
}
|
|
584
504
|
|
|
585
505
|
// src/llm/config.ts
|
|
586
|
-
import
|
|
587
|
-
import
|
|
506
|
+
import fs4 from "fs";
|
|
507
|
+
import path4 from "path";
|
|
588
508
|
import os from "os";
|
|
589
|
-
var CONFIG_DIR =
|
|
590
|
-
var CONFIG_FILE =
|
|
509
|
+
var CONFIG_DIR = path4.join(os.homedir(), ".caliber");
|
|
510
|
+
var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
|
|
591
511
|
var DEFAULT_MODELS = {
|
|
592
512
|
anthropic: "claude-sonnet-4-6",
|
|
593
513
|
vertex: "claude-sonnet-4-6",
|
|
@@ -641,8 +561,8 @@ function resolveFromEnv() {
|
|
|
641
561
|
}
|
|
642
562
|
function readConfigFile() {
|
|
643
563
|
try {
|
|
644
|
-
if (!
|
|
645
|
-
const raw =
|
|
564
|
+
if (!fs4.existsSync(CONFIG_FILE)) return null;
|
|
565
|
+
const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
|
|
646
566
|
const parsed = JSON.parse(raw);
|
|
647
567
|
if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
|
|
648
568
|
return null;
|
|
@@ -653,18 +573,21 @@ function readConfigFile() {
|
|
|
653
573
|
}
|
|
654
574
|
}
|
|
655
575
|
function writeConfigFile(config) {
|
|
656
|
-
if (!
|
|
657
|
-
|
|
576
|
+
if (!fs4.existsSync(CONFIG_DIR)) {
|
|
577
|
+
fs4.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
658
578
|
}
|
|
659
579
|
const sanitized = { ...config };
|
|
660
580
|
if (sanitized.apiKey) {
|
|
661
581
|
sanitized.apiKey = sanitized.apiKey.trim();
|
|
662
582
|
}
|
|
663
|
-
|
|
583
|
+
fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
|
|
664
584
|
}
|
|
665
585
|
function getConfigFilePath() {
|
|
666
586
|
return CONFIG_FILE;
|
|
667
587
|
}
|
|
588
|
+
function getFastModel() {
|
|
589
|
+
return process.env.CALIBER_FAST_MODEL || process.env.ANTHROPIC_SMALL_FAST_MODEL || void 0;
|
|
590
|
+
}
|
|
668
591
|
|
|
669
592
|
// src/llm/anthropic.ts
|
|
670
593
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -710,7 +633,7 @@ var AnthropicProvider = class {
|
|
|
710
633
|
};
|
|
711
634
|
|
|
712
635
|
// src/llm/vertex.ts
|
|
713
|
-
import
|
|
636
|
+
import fs5 from "fs";
|
|
714
637
|
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
|
|
715
638
|
import { GoogleAuth } from "google-auth-library";
|
|
716
639
|
var VertexProvider = class {
|
|
@@ -733,7 +656,7 @@ var VertexProvider = class {
|
|
|
733
656
|
}
|
|
734
657
|
} else {
|
|
735
658
|
try {
|
|
736
|
-
creds = JSON.parse(
|
|
659
|
+
creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
|
|
737
660
|
} catch {
|
|
738
661
|
throw new Error(`Cannot read credentials file: ${raw}`);
|
|
739
662
|
}
|
|
@@ -1470,22 +1393,26 @@ All markdown content inside string values must be properly escaped for JSON (new
|
|
|
1470
1393
|
|
|
1471
1394
|
If there's nothing worth learning from the events (routine successful operations), return:
|
|
1472
1395
|
{"claudeMdLearnedSection": null, "skills": null, "explanations": ["No actionable patterns found in these events."]}`;
|
|
1473
|
-
var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages and
|
|
1396
|
+
var FINGERPRINT_SYSTEM_PROMPT = `You are an expert at detecting programming languages, frameworks, and external tools/services from project file trees and dependency files.
|
|
1474
1397
|
|
|
1475
1398
|
Analyze the provided file tree and dependency file contents. Return a JSON object with:
|
|
1476
|
-
- "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust")
|
|
1477
|
-
- "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js")
|
|
1399
|
+
- "languages": array of programming languages used (e.g. "TypeScript", "Python", "Go", "Rust", "HCL")
|
|
1400
|
+
- "frameworks": array of frameworks and key libraries detected (e.g. "FastAPI", "React", "Celery", "Django", "Express", "Next.js", "Terraform")
|
|
1401
|
+
- "tools": array of external tools, services, and platforms the project integrates with \u2014 things that could have an MCP server or API integration (e.g. "PostgreSQL", "Redis", "Stripe", "Sentry", "AWS", "GCP", "GitHub", "Slack", "Docker", "Kubernetes", "Datadog", "PagerDuty", "MongoDB", "Elasticsearch")
|
|
1478
1402
|
|
|
1479
1403
|
Be thorough \u2014 look for signals in:
|
|
1480
1404
|
- Dependency files (package.json, pyproject.toml, requirements.txt, go.mod, Cargo.toml, etc.)
|
|
1481
1405
|
- File extensions and directory structure
|
|
1482
|
-
- Configuration files (e.g. next.config.js implies Next.js)
|
|
1406
|
+
- Configuration files (e.g. next.config.js implies Next.js, .tf files imply Terraform + cloud providers)
|
|
1407
|
+
- Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
|
|
1408
|
+
- CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
|
|
1409
|
+
- Environment variable patterns and service references in code
|
|
1483
1410
|
|
|
1484
|
-
Only include
|
|
1411
|
+
Only include items you're confident about. Return ONLY the JSON object.`;
|
|
1485
1412
|
|
|
1486
1413
|
// src/ai/detect.ts
|
|
1487
|
-
async function
|
|
1488
|
-
const parts = ["Analyze this project and detect languages and
|
|
1414
|
+
async function detectProjectStack(fileTree, fileContents) {
|
|
1415
|
+
const parts = ["Analyze this project and detect languages, frameworks, and external tools/services.\n"];
|
|
1489
1416
|
if (fileTree.length > 0) {
|
|
1490
1417
|
parts.push("File tree:");
|
|
1491
1418
|
parts.push(fileTree.join("\n"));
|
|
@@ -1498,7 +1425,7 @@ async function detectFrameworks(fileTree, fileContents) {
|
|
|
1498
1425
|
parts.push(content);
|
|
1499
1426
|
}
|
|
1500
1427
|
}
|
|
1501
|
-
const fastModel =
|
|
1428
|
+
const fastModel = getFastModel();
|
|
1502
1429
|
const result = await llmJsonCall({
|
|
1503
1430
|
system: FINGERPRINT_SYSTEM_PROMPT,
|
|
1504
1431
|
prompt: parts.join("\n"),
|
|
@@ -1506,29 +1433,39 @@ async function detectFrameworks(fileTree, fileContents) {
|
|
|
1506
1433
|
});
|
|
1507
1434
|
return {
|
|
1508
1435
|
languages: Array.isArray(result.languages) ? result.languages : [],
|
|
1509
|
-
frameworks: Array.isArray(result.frameworks) ? result.frameworks : []
|
|
1436
|
+
frameworks: Array.isArray(result.frameworks) ? result.frameworks : [],
|
|
1437
|
+
tools: Array.isArray(result.tools) ? result.tools : []
|
|
1510
1438
|
};
|
|
1511
1439
|
}
|
|
1512
1440
|
|
|
1513
1441
|
// src/fingerprint/index.ts
|
|
1514
1442
|
function collectFingerprint(dir) {
|
|
1515
1443
|
const gitRemoteUrl = getGitRemoteUrl();
|
|
1516
|
-
const pkgInfo = analyzePackageJson(dir);
|
|
1517
1444
|
const fileTree = getFileTree(dir);
|
|
1518
|
-
const fileLangs = detectLanguages(fileTree);
|
|
1519
1445
|
const existingConfigs = readExistingConfigs(dir);
|
|
1520
1446
|
const codeAnalysis = analyzeCode(dir);
|
|
1521
|
-
const
|
|
1447
|
+
const packageName = readPackageName(dir);
|
|
1522
1448
|
return {
|
|
1523
1449
|
gitRemoteUrl,
|
|
1524
|
-
packageName
|
|
1525
|
-
languages,
|
|
1450
|
+
packageName,
|
|
1451
|
+
languages: [],
|
|
1526
1452
|
frameworks: [],
|
|
1453
|
+
tools: [],
|
|
1527
1454
|
fileTree,
|
|
1528
1455
|
existingConfigs,
|
|
1529
1456
|
codeAnalysis
|
|
1530
1457
|
};
|
|
1531
1458
|
}
|
|
1459
|
+
function readPackageName(dir) {
|
|
1460
|
+
try {
|
|
1461
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
1462
|
+
if (!fs6.existsSync(pkgPath)) return void 0;
|
|
1463
|
+
const pkg3 = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
1464
|
+
return pkg3.name;
|
|
1465
|
+
} catch {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1532
1469
|
var DEP_FILE_PATTERNS = [
|
|
1533
1470
|
"package.json",
|
|
1534
1471
|
"pyproject.toml",
|
|
@@ -1550,12 +1487,12 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1550
1487
|
const fileContents = {};
|
|
1551
1488
|
let totalSize = 0;
|
|
1552
1489
|
for (const treePath of fingerprint.fileTree) {
|
|
1553
|
-
const basename =
|
|
1490
|
+
const basename = path5.basename(treePath);
|
|
1554
1491
|
if (!DEP_FILE_PATTERNS.includes(basename)) continue;
|
|
1555
|
-
const fullPath =
|
|
1556
|
-
if (!
|
|
1492
|
+
const fullPath = path5.join(dir, treePath);
|
|
1493
|
+
if (!fs6.existsSync(fullPath)) continue;
|
|
1557
1494
|
try {
|
|
1558
|
-
const content =
|
|
1495
|
+
const content = fs6.readFileSync(fullPath, "utf-8");
|
|
1559
1496
|
if (totalSize + content.length > MAX_CONTENT_SIZE) break;
|
|
1560
1497
|
fileContents[treePath] = content;
|
|
1561
1498
|
totalSize += content.length;
|
|
@@ -1564,7 +1501,7 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1564
1501
|
}
|
|
1565
1502
|
}
|
|
1566
1503
|
if (Object.keys(fileContents).length === 0 && fingerprint.fileTree.length === 0) return;
|
|
1567
|
-
const result = await
|
|
1504
|
+
const result = await detectProjectStack(fingerprint.fileTree, fileContents);
|
|
1568
1505
|
if (result.languages?.length) {
|
|
1569
1506
|
const langSet = new Set(fingerprint.languages);
|
|
1570
1507
|
for (const lang of result.languages) langSet.add(lang);
|
|
@@ -1575,6 +1512,11 @@ async function enrichFingerprintWithLLM(fingerprint, dir) {
|
|
|
1575
1512
|
for (const fw of result.frameworks) fwSet.add(fw);
|
|
1576
1513
|
fingerprint.frameworks = [...fwSet];
|
|
1577
1514
|
}
|
|
1515
|
+
if (result.tools?.length) {
|
|
1516
|
+
const toolSet = new Set(fingerprint.tools);
|
|
1517
|
+
for (const tool of result.tools) toolSet.add(tool);
|
|
1518
|
+
fingerprint.tools = [...toolSet];
|
|
1519
|
+
}
|
|
1578
1520
|
} catch {
|
|
1579
1521
|
}
|
|
1580
1522
|
}
|
|
@@ -1875,20 +1817,20 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
|
|
|
1875
1817
|
}
|
|
1876
1818
|
|
|
1877
1819
|
// src/writers/index.ts
|
|
1878
|
-
import
|
|
1820
|
+
import fs11 from "fs";
|
|
1879
1821
|
|
|
1880
1822
|
// src/writers/claude/index.ts
|
|
1881
|
-
import
|
|
1882
|
-
import
|
|
1823
|
+
import fs7 from "fs";
|
|
1824
|
+
import path6 from "path";
|
|
1883
1825
|
function writeClaudeConfig(config) {
|
|
1884
1826
|
const written = [];
|
|
1885
|
-
|
|
1827
|
+
fs7.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
1886
1828
|
written.push("CLAUDE.md");
|
|
1887
1829
|
if (config.skills?.length) {
|
|
1888
1830
|
for (const skill of config.skills) {
|
|
1889
|
-
const skillDir =
|
|
1890
|
-
if (!
|
|
1891
|
-
const skillPath =
|
|
1831
|
+
const skillDir = path6.join(".claude", "skills", skill.name);
|
|
1832
|
+
if (!fs7.existsSync(skillDir)) fs7.mkdirSync(skillDir, { recursive: true });
|
|
1833
|
+
const skillPath = path6.join(skillDir, "SKILL.md");
|
|
1892
1834
|
const frontmatter = [
|
|
1893
1835
|
"---",
|
|
1894
1836
|
`name: ${skill.name}`,
|
|
@@ -1896,49 +1838,49 @@ function writeClaudeConfig(config) {
|
|
|
1896
1838
|
"---",
|
|
1897
1839
|
""
|
|
1898
1840
|
].join("\n");
|
|
1899
|
-
|
|
1841
|
+
fs7.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1900
1842
|
written.push(skillPath);
|
|
1901
1843
|
}
|
|
1902
1844
|
}
|
|
1903
1845
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1904
1846
|
let existingServers = {};
|
|
1905
1847
|
try {
|
|
1906
|
-
if (
|
|
1907
|
-
const existing = JSON.parse(
|
|
1848
|
+
if (fs7.existsSync(".mcp.json")) {
|
|
1849
|
+
const existing = JSON.parse(fs7.readFileSync(".mcp.json", "utf-8"));
|
|
1908
1850
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
1909
1851
|
}
|
|
1910
1852
|
} catch {
|
|
1911
1853
|
}
|
|
1912
1854
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1913
|
-
|
|
1855
|
+
fs7.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1914
1856
|
written.push(".mcp.json");
|
|
1915
1857
|
}
|
|
1916
1858
|
return written;
|
|
1917
1859
|
}
|
|
1918
1860
|
|
|
1919
1861
|
// src/writers/cursor/index.ts
|
|
1920
|
-
import
|
|
1921
|
-
import
|
|
1862
|
+
import fs8 from "fs";
|
|
1863
|
+
import path7 from "path";
|
|
1922
1864
|
function writeCursorConfig(config) {
|
|
1923
1865
|
const written = [];
|
|
1924
1866
|
if (config.cursorrules) {
|
|
1925
|
-
|
|
1867
|
+
fs8.writeFileSync(".cursorrules", config.cursorrules);
|
|
1926
1868
|
written.push(".cursorrules");
|
|
1927
1869
|
}
|
|
1928
1870
|
if (config.rules?.length) {
|
|
1929
|
-
const rulesDir =
|
|
1930
|
-
if (!
|
|
1871
|
+
const rulesDir = path7.join(".cursor", "rules");
|
|
1872
|
+
if (!fs8.existsSync(rulesDir)) fs8.mkdirSync(rulesDir, { recursive: true });
|
|
1931
1873
|
for (const rule of config.rules) {
|
|
1932
|
-
const rulePath =
|
|
1933
|
-
|
|
1874
|
+
const rulePath = path7.join(rulesDir, rule.filename);
|
|
1875
|
+
fs8.writeFileSync(rulePath, rule.content);
|
|
1934
1876
|
written.push(rulePath);
|
|
1935
1877
|
}
|
|
1936
1878
|
}
|
|
1937
1879
|
if (config.skills?.length) {
|
|
1938
1880
|
for (const skill of config.skills) {
|
|
1939
|
-
const skillDir =
|
|
1940
|
-
if (!
|
|
1941
|
-
const skillPath =
|
|
1881
|
+
const skillDir = path7.join(".cursor", "skills", skill.name);
|
|
1882
|
+
if (!fs8.existsSync(skillDir)) fs8.mkdirSync(skillDir, { recursive: true });
|
|
1883
|
+
const skillPath = path7.join(skillDir, "SKILL.md");
|
|
1942
1884
|
const frontmatter = [
|
|
1943
1885
|
"---",
|
|
1944
1886
|
`name: ${skill.name}`,
|
|
@@ -1946,24 +1888,24 @@ function writeCursorConfig(config) {
|
|
|
1946
1888
|
"---",
|
|
1947
1889
|
""
|
|
1948
1890
|
].join("\n");
|
|
1949
|
-
|
|
1891
|
+
fs8.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1950
1892
|
written.push(skillPath);
|
|
1951
1893
|
}
|
|
1952
1894
|
}
|
|
1953
1895
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
1954
1896
|
const cursorDir = ".cursor";
|
|
1955
|
-
if (!
|
|
1956
|
-
const mcpPath =
|
|
1897
|
+
if (!fs8.existsSync(cursorDir)) fs8.mkdirSync(cursorDir, { recursive: true });
|
|
1898
|
+
const mcpPath = path7.join(cursorDir, "mcp.json");
|
|
1957
1899
|
let existingServers = {};
|
|
1958
1900
|
try {
|
|
1959
|
-
if (
|
|
1960
|
-
const existing = JSON.parse(
|
|
1901
|
+
if (fs8.existsSync(mcpPath)) {
|
|
1902
|
+
const existing = JSON.parse(fs8.readFileSync(mcpPath, "utf-8"));
|
|
1961
1903
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
1962
1904
|
}
|
|
1963
1905
|
} catch {
|
|
1964
1906
|
}
|
|
1965
1907
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
1966
|
-
|
|
1908
|
+
fs8.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
1967
1909
|
written.push(mcpPath);
|
|
1968
1910
|
}
|
|
1969
1911
|
return written;
|
|
@@ -1971,62 +1913,62 @@ function writeCursorConfig(config) {
|
|
|
1971
1913
|
|
|
1972
1914
|
// src/writers/backup.ts
|
|
1973
1915
|
init_constants();
|
|
1974
|
-
import
|
|
1975
|
-
import
|
|
1916
|
+
import fs9 from "fs";
|
|
1917
|
+
import path9 from "path";
|
|
1976
1918
|
function createBackup(files) {
|
|
1977
1919
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1978
|
-
const backupDir =
|
|
1920
|
+
const backupDir = path9.join(BACKUPS_DIR, timestamp);
|
|
1979
1921
|
for (const file of files) {
|
|
1980
|
-
if (!
|
|
1981
|
-
const dest =
|
|
1982
|
-
const destDir =
|
|
1983
|
-
if (!
|
|
1984
|
-
|
|
1922
|
+
if (!fs9.existsSync(file)) continue;
|
|
1923
|
+
const dest = path9.join(backupDir, file);
|
|
1924
|
+
const destDir = path9.dirname(dest);
|
|
1925
|
+
if (!fs9.existsSync(destDir)) {
|
|
1926
|
+
fs9.mkdirSync(destDir, { recursive: true });
|
|
1985
1927
|
}
|
|
1986
|
-
|
|
1928
|
+
fs9.copyFileSync(file, dest);
|
|
1987
1929
|
}
|
|
1988
1930
|
return backupDir;
|
|
1989
1931
|
}
|
|
1990
1932
|
function restoreBackup(backupDir, file) {
|
|
1991
|
-
const backupFile =
|
|
1992
|
-
if (!
|
|
1993
|
-
const destDir =
|
|
1994
|
-
if (!
|
|
1995
|
-
|
|
1933
|
+
const backupFile = path9.join(backupDir, file);
|
|
1934
|
+
if (!fs9.existsSync(backupFile)) return false;
|
|
1935
|
+
const destDir = path9.dirname(file);
|
|
1936
|
+
if (!fs9.existsSync(destDir)) {
|
|
1937
|
+
fs9.mkdirSync(destDir, { recursive: true });
|
|
1996
1938
|
}
|
|
1997
|
-
|
|
1939
|
+
fs9.copyFileSync(backupFile, file);
|
|
1998
1940
|
return true;
|
|
1999
1941
|
}
|
|
2000
1942
|
|
|
2001
1943
|
// src/writers/manifest.ts
|
|
2002
1944
|
init_constants();
|
|
2003
|
-
import
|
|
1945
|
+
import fs10 from "fs";
|
|
2004
1946
|
import crypto from "crypto";
|
|
2005
1947
|
function readManifest() {
|
|
2006
1948
|
try {
|
|
2007
|
-
if (!
|
|
2008
|
-
return JSON.parse(
|
|
1949
|
+
if (!fs10.existsSync(MANIFEST_FILE)) return null;
|
|
1950
|
+
return JSON.parse(fs10.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
2009
1951
|
} catch {
|
|
2010
1952
|
return null;
|
|
2011
1953
|
}
|
|
2012
1954
|
}
|
|
2013
1955
|
function writeManifest(manifest) {
|
|
2014
|
-
if (!
|
|
2015
|
-
|
|
1956
|
+
if (!fs10.existsSync(CALIBER_DIR)) {
|
|
1957
|
+
fs10.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2016
1958
|
}
|
|
2017
|
-
|
|
1959
|
+
fs10.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
2018
1960
|
}
|
|
2019
1961
|
function fileChecksum(filePath) {
|
|
2020
|
-
const content =
|
|
1962
|
+
const content = fs10.readFileSync(filePath);
|
|
2021
1963
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
2022
1964
|
}
|
|
2023
1965
|
|
|
2024
1966
|
// src/writers/index.ts
|
|
2025
1967
|
function writeSetup(setup) {
|
|
2026
1968
|
const filesToWrite = getFilesToWrite(setup);
|
|
2027
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
1969
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs11.existsSync(f));
|
|
2028
1970
|
const existingFiles = [
|
|
2029
|
-
...filesToWrite.filter((f) =>
|
|
1971
|
+
...filesToWrite.filter((f) => fs11.existsSync(f)),
|
|
2030
1972
|
...filesToDelete
|
|
2031
1973
|
];
|
|
2032
1974
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
@@ -2039,7 +1981,7 @@ function writeSetup(setup) {
|
|
|
2039
1981
|
}
|
|
2040
1982
|
const deleted = [];
|
|
2041
1983
|
for (const filePath of filesToDelete) {
|
|
2042
|
-
|
|
1984
|
+
fs11.unlinkSync(filePath);
|
|
2043
1985
|
deleted.push(filePath);
|
|
2044
1986
|
}
|
|
2045
1987
|
ensureGitignore();
|
|
@@ -2069,8 +2011,8 @@ function undoSetup() {
|
|
|
2069
2011
|
const removed = [];
|
|
2070
2012
|
for (const entry of manifest.entries) {
|
|
2071
2013
|
if (entry.action === "created") {
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2014
|
+
if (fs11.existsSync(entry.path)) {
|
|
2015
|
+
fs11.unlinkSync(entry.path);
|
|
2074
2016
|
removed.push(entry.path);
|
|
2075
2017
|
}
|
|
2076
2018
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -2080,8 +2022,8 @@ function undoSetup() {
|
|
|
2080
2022
|
}
|
|
2081
2023
|
}
|
|
2082
2024
|
const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
|
|
2083
|
-
if (
|
|
2084
|
-
|
|
2025
|
+
if (fs11.existsSync(MANIFEST_FILE2)) {
|
|
2026
|
+
fs11.unlinkSync(MANIFEST_FILE2);
|
|
2085
2027
|
}
|
|
2086
2028
|
return { restored, removed };
|
|
2087
2029
|
}
|
|
@@ -2110,23 +2052,23 @@ function getFilesToWrite(setup) {
|
|
|
2110
2052
|
}
|
|
2111
2053
|
function ensureGitignore() {
|
|
2112
2054
|
const gitignorePath = ".gitignore";
|
|
2113
|
-
if (
|
|
2114
|
-
const content =
|
|
2055
|
+
if (fs11.existsSync(gitignorePath)) {
|
|
2056
|
+
const content = fs11.readFileSync(gitignorePath, "utf-8");
|
|
2115
2057
|
if (!content.includes(".caliber/")) {
|
|
2116
|
-
|
|
2058
|
+
fs11.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
2117
2059
|
}
|
|
2118
2060
|
} else {
|
|
2119
|
-
|
|
2061
|
+
fs11.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
2120
2062
|
}
|
|
2121
2063
|
}
|
|
2122
2064
|
|
|
2123
2065
|
// src/writers/staging.ts
|
|
2124
2066
|
init_constants();
|
|
2125
|
-
import
|
|
2126
|
-
import
|
|
2127
|
-
var STAGED_DIR =
|
|
2128
|
-
var PROPOSED_DIR =
|
|
2129
|
-
var CURRENT_DIR =
|
|
2067
|
+
import fs12 from "fs";
|
|
2068
|
+
import path10 from "path";
|
|
2069
|
+
var STAGED_DIR = path10.join(CALIBER_DIR, "staged");
|
|
2070
|
+
var PROPOSED_DIR = path10.join(STAGED_DIR, "proposed");
|
|
2071
|
+
var CURRENT_DIR = path10.join(STAGED_DIR, "current");
|
|
2130
2072
|
function normalizeContent(content) {
|
|
2131
2073
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2132
2074
|
}
|
|
@@ -2136,20 +2078,20 @@ function stageFiles(files, projectDir) {
|
|
|
2136
2078
|
let modifiedFiles = 0;
|
|
2137
2079
|
const stagedFiles = [];
|
|
2138
2080
|
for (const file of files) {
|
|
2139
|
-
const originalPath =
|
|
2140
|
-
if (
|
|
2141
|
-
const existing =
|
|
2081
|
+
const originalPath = path10.join(projectDir, file.path);
|
|
2082
|
+
if (fs12.existsSync(originalPath)) {
|
|
2083
|
+
const existing = fs12.readFileSync(originalPath, "utf-8");
|
|
2142
2084
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2143
2085
|
continue;
|
|
2144
2086
|
}
|
|
2145
2087
|
}
|
|
2146
|
-
const proposedPath =
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
if (
|
|
2150
|
-
const currentPath =
|
|
2151
|
-
|
|
2152
|
-
|
|
2088
|
+
const proposedPath = path10.join(PROPOSED_DIR, file.path);
|
|
2089
|
+
fs12.mkdirSync(path10.dirname(proposedPath), { recursive: true });
|
|
2090
|
+
fs12.writeFileSync(proposedPath, file.content);
|
|
2091
|
+
if (fs12.existsSync(originalPath)) {
|
|
2092
|
+
const currentPath = path10.join(CURRENT_DIR, file.path);
|
|
2093
|
+
fs12.mkdirSync(path10.dirname(currentPath), { recursive: true });
|
|
2094
|
+
fs12.copyFileSync(originalPath, currentPath);
|
|
2153
2095
|
modifiedFiles++;
|
|
2154
2096
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
2155
2097
|
} else {
|
|
@@ -2160,8 +2102,8 @@ function stageFiles(files, projectDir) {
|
|
|
2160
2102
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
2161
2103
|
}
|
|
2162
2104
|
function cleanupStaging() {
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
2105
|
+
if (fs12.existsSync(STAGED_DIR)) {
|
|
2106
|
+
fs12.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
2165
2107
|
}
|
|
2166
2108
|
}
|
|
2167
2109
|
|
|
@@ -2207,24 +2149,24 @@ function openDiffsInEditor(editor, files) {
|
|
|
2207
2149
|
import { createTwoFilesPatch } from "diff";
|
|
2208
2150
|
|
|
2209
2151
|
// src/lib/hooks.ts
|
|
2210
|
-
import
|
|
2211
|
-
import
|
|
2152
|
+
import fs13 from "fs";
|
|
2153
|
+
import path11 from "path";
|
|
2212
2154
|
import { execSync as execSync5 } from "child_process";
|
|
2213
|
-
var SETTINGS_PATH =
|
|
2155
|
+
var SETTINGS_PATH = path11.join(".claude", "settings.json");
|
|
2214
2156
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
2215
2157
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
2216
2158
|
function readSettings() {
|
|
2217
|
-
if (!
|
|
2159
|
+
if (!fs13.existsSync(SETTINGS_PATH)) return {};
|
|
2218
2160
|
try {
|
|
2219
|
-
return JSON.parse(
|
|
2161
|
+
return JSON.parse(fs13.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2220
2162
|
} catch {
|
|
2221
2163
|
return {};
|
|
2222
2164
|
}
|
|
2223
2165
|
}
|
|
2224
2166
|
function writeSettings(settings) {
|
|
2225
|
-
const dir =
|
|
2226
|
-
if (!
|
|
2227
|
-
|
|
2167
|
+
const dir = path11.dirname(SETTINGS_PATH);
|
|
2168
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
2169
|
+
fs13.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
2228
2170
|
}
|
|
2229
2171
|
function findHookIndex(sessionEnd) {
|
|
2230
2172
|
return sessionEnd.findIndex(
|
|
@@ -2282,19 +2224,19 @@ ${PRECOMMIT_END}`;
|
|
|
2282
2224
|
function getGitHooksDir() {
|
|
2283
2225
|
try {
|
|
2284
2226
|
const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2285
|
-
return
|
|
2227
|
+
return path11.join(gitDir, "hooks");
|
|
2286
2228
|
} catch {
|
|
2287
2229
|
return null;
|
|
2288
2230
|
}
|
|
2289
2231
|
}
|
|
2290
2232
|
function getPreCommitPath() {
|
|
2291
2233
|
const hooksDir = getGitHooksDir();
|
|
2292
|
-
return hooksDir ?
|
|
2234
|
+
return hooksDir ? path11.join(hooksDir, "pre-commit") : null;
|
|
2293
2235
|
}
|
|
2294
2236
|
function isPreCommitHookInstalled() {
|
|
2295
2237
|
const hookPath = getPreCommitPath();
|
|
2296
|
-
if (!hookPath || !
|
|
2297
|
-
const content =
|
|
2238
|
+
if (!hookPath || !fs13.existsSync(hookPath)) return false;
|
|
2239
|
+
const content = fs13.readFileSync(hookPath, "utf-8");
|
|
2298
2240
|
return content.includes(PRECOMMIT_START);
|
|
2299
2241
|
}
|
|
2300
2242
|
function installPreCommitHook() {
|
|
@@ -2303,43 +2245,43 @@ function installPreCommitHook() {
|
|
|
2303
2245
|
}
|
|
2304
2246
|
const hookPath = getPreCommitPath();
|
|
2305
2247
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
2306
|
-
const hooksDir =
|
|
2307
|
-
if (!
|
|
2248
|
+
const hooksDir = path11.dirname(hookPath);
|
|
2249
|
+
if (!fs13.existsSync(hooksDir)) fs13.mkdirSync(hooksDir, { recursive: true });
|
|
2308
2250
|
let content = "";
|
|
2309
|
-
if (
|
|
2310
|
-
content =
|
|
2251
|
+
if (fs13.existsSync(hookPath)) {
|
|
2252
|
+
content = fs13.readFileSync(hookPath, "utf-8");
|
|
2311
2253
|
if (!content.endsWith("\n")) content += "\n";
|
|
2312
2254
|
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
2313
2255
|
} else {
|
|
2314
2256
|
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
2315
2257
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2258
|
+
fs13.writeFileSync(hookPath, content);
|
|
2259
|
+
fs13.chmodSync(hookPath, 493);
|
|
2318
2260
|
return { installed: true, alreadyInstalled: false };
|
|
2319
2261
|
}
|
|
2320
2262
|
function removePreCommitHook() {
|
|
2321
2263
|
const hookPath = getPreCommitPath();
|
|
2322
|
-
if (!hookPath || !
|
|
2264
|
+
if (!hookPath || !fs13.existsSync(hookPath)) {
|
|
2323
2265
|
return { removed: false, notFound: true };
|
|
2324
2266
|
}
|
|
2325
|
-
let content =
|
|
2267
|
+
let content = fs13.readFileSync(hookPath, "utf-8");
|
|
2326
2268
|
if (!content.includes(PRECOMMIT_START)) {
|
|
2327
2269
|
return { removed: false, notFound: true };
|
|
2328
2270
|
}
|
|
2329
2271
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
2330
2272
|
content = content.replace(regex, "\n");
|
|
2331
2273
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
2332
|
-
|
|
2274
|
+
fs13.unlinkSync(hookPath);
|
|
2333
2275
|
} else {
|
|
2334
|
-
|
|
2276
|
+
fs13.writeFileSync(hookPath, content);
|
|
2335
2277
|
}
|
|
2336
2278
|
return { removed: true, notFound: false };
|
|
2337
2279
|
}
|
|
2338
2280
|
|
|
2339
2281
|
// src/lib/learning-hooks.ts
|
|
2340
|
-
import
|
|
2341
|
-
import
|
|
2342
|
-
var SETTINGS_PATH2 =
|
|
2282
|
+
import fs14 from "fs";
|
|
2283
|
+
import path12 from "path";
|
|
2284
|
+
var SETTINGS_PATH2 = path12.join(".claude", "settings.json");
|
|
2343
2285
|
var HOOK_CONFIGS = [
|
|
2344
2286
|
{
|
|
2345
2287
|
event: "PostToolUse",
|
|
@@ -2358,17 +2300,17 @@ var HOOK_CONFIGS = [
|
|
|
2358
2300
|
}
|
|
2359
2301
|
];
|
|
2360
2302
|
function readSettings2() {
|
|
2361
|
-
if (!
|
|
2303
|
+
if (!fs14.existsSync(SETTINGS_PATH2)) return {};
|
|
2362
2304
|
try {
|
|
2363
|
-
return JSON.parse(
|
|
2305
|
+
return JSON.parse(fs14.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
2364
2306
|
} catch {
|
|
2365
2307
|
return {};
|
|
2366
2308
|
}
|
|
2367
2309
|
}
|
|
2368
2310
|
function writeSettings2(settings) {
|
|
2369
|
-
const dir =
|
|
2370
|
-
if (!
|
|
2371
|
-
|
|
2311
|
+
const dir = path12.dirname(SETTINGS_PATH2);
|
|
2312
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
2313
|
+
fs14.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
2372
2314
|
}
|
|
2373
2315
|
function hasLearningHook(matchers, command) {
|
|
2374
2316
|
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
@@ -2425,23 +2367,23 @@ function removeLearningHooks() {
|
|
|
2425
2367
|
|
|
2426
2368
|
// src/lib/state.ts
|
|
2427
2369
|
init_constants();
|
|
2428
|
-
import
|
|
2429
|
-
import
|
|
2370
|
+
import fs15 from "fs";
|
|
2371
|
+
import path13 from "path";
|
|
2430
2372
|
import { execSync as execSync6 } from "child_process";
|
|
2431
|
-
var STATE_FILE =
|
|
2373
|
+
var STATE_FILE = path13.join(CALIBER_DIR, ".caliber-state.json");
|
|
2432
2374
|
function readState() {
|
|
2433
2375
|
try {
|
|
2434
|
-
if (!
|
|
2435
|
-
return JSON.parse(
|
|
2376
|
+
if (!fs15.existsSync(STATE_FILE)) return null;
|
|
2377
|
+
return JSON.parse(fs15.readFileSync(STATE_FILE, "utf-8"));
|
|
2436
2378
|
} catch {
|
|
2437
2379
|
return null;
|
|
2438
2380
|
}
|
|
2439
2381
|
}
|
|
2440
2382
|
function writeState(state) {
|
|
2441
|
-
if (!
|
|
2442
|
-
|
|
2383
|
+
if (!fs15.existsSync(CALIBER_DIR)) {
|
|
2384
|
+
fs15.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2443
2385
|
}
|
|
2444
|
-
|
|
2386
|
+
fs15.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
2445
2387
|
}
|
|
2446
2388
|
function getCurrentHeadSha() {
|
|
2447
2389
|
try {
|
|
@@ -2724,15 +2666,15 @@ function computeGrade(score) {
|
|
|
2724
2666
|
// src/scoring/checks/coverage.ts
|
|
2725
2667
|
import { readFileSync, readdirSync } from "fs";
|
|
2726
2668
|
import { join } from "path";
|
|
2727
|
-
function readFileOrNull(
|
|
2669
|
+
function readFileOrNull(path23) {
|
|
2728
2670
|
try {
|
|
2729
|
-
return readFileSync(
|
|
2671
|
+
return readFileSync(path23, "utf-8");
|
|
2730
2672
|
} catch {
|
|
2731
2673
|
return null;
|
|
2732
2674
|
}
|
|
2733
2675
|
}
|
|
2734
|
-
function readJsonOrNull(
|
|
2735
|
-
const content = readFileOrNull(
|
|
2676
|
+
function readJsonOrNull(path23) {
|
|
2677
|
+
const content = readFileOrNull(path23);
|
|
2736
2678
|
if (!content) return null;
|
|
2737
2679
|
try {
|
|
2738
2680
|
return JSON.parse(content);
|
|
@@ -3088,9 +3030,9 @@ function checkExistence(dir) {
|
|
|
3088
3030
|
// src/scoring/checks/quality.ts
|
|
3089
3031
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3090
3032
|
import { join as join3 } from "path";
|
|
3091
|
-
function readFileOrNull2(
|
|
3033
|
+
function readFileOrNull2(path23) {
|
|
3092
3034
|
try {
|
|
3093
|
-
return readFileSync3(
|
|
3035
|
+
return readFileSync3(path23, "utf-8");
|
|
3094
3036
|
} catch {
|
|
3095
3037
|
return null;
|
|
3096
3038
|
}
|
|
@@ -3241,15 +3183,15 @@ function checkQuality(dir) {
|
|
|
3241
3183
|
// src/scoring/checks/accuracy.ts
|
|
3242
3184
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3243
3185
|
import { join as join4 } from "path";
|
|
3244
|
-
function readFileOrNull3(
|
|
3186
|
+
function readFileOrNull3(path23) {
|
|
3245
3187
|
try {
|
|
3246
|
-
return readFileSync4(
|
|
3188
|
+
return readFileSync4(path23, "utf-8");
|
|
3247
3189
|
} catch {
|
|
3248
3190
|
return null;
|
|
3249
3191
|
}
|
|
3250
3192
|
}
|
|
3251
|
-
function readJsonOrNull2(
|
|
3252
|
-
const content = readFileOrNull3(
|
|
3193
|
+
function readJsonOrNull2(path23) {
|
|
3194
|
+
const content = readFileOrNull3(path23);
|
|
3253
3195
|
if (!content) return null;
|
|
3254
3196
|
try {
|
|
3255
3197
|
return JSON.parse(content);
|
|
@@ -3432,9 +3374,9 @@ function checkAccuracy(dir) {
|
|
|
3432
3374
|
// src/scoring/checks/freshness.ts
|
|
3433
3375
|
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3434
3376
|
import { join as join5 } from "path";
|
|
3435
|
-
function readFileOrNull4(
|
|
3377
|
+
function readFileOrNull4(path23) {
|
|
3436
3378
|
try {
|
|
3437
|
-
return readFileSync5(
|
|
3379
|
+
return readFileSync5(path23, "utf-8");
|
|
3438
3380
|
} catch {
|
|
3439
3381
|
return null;
|
|
3440
3382
|
}
|
|
@@ -3544,9 +3486,9 @@ function checkFreshness(dir) {
|
|
|
3544
3486
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3545
3487
|
import { execSync as execSync7 } from "child_process";
|
|
3546
3488
|
import { join as join6 } from "path";
|
|
3547
|
-
function readFileOrNull5(
|
|
3489
|
+
function readFileOrNull5(path23) {
|
|
3548
3490
|
try {
|
|
3549
|
-
return readFileSync6(
|
|
3491
|
+
return readFileSync6(path23, "utf-8");
|
|
3550
3492
|
} catch {
|
|
3551
3493
|
return null;
|
|
3552
3494
|
}
|
|
@@ -3641,22 +3583,22 @@ function checkBonus(dir) {
|
|
|
3641
3583
|
|
|
3642
3584
|
// src/scoring/dismissed.ts
|
|
3643
3585
|
init_constants();
|
|
3644
|
-
import
|
|
3645
|
-
import
|
|
3646
|
-
var DISMISSED_FILE =
|
|
3586
|
+
import fs16 from "fs";
|
|
3587
|
+
import path14 from "path";
|
|
3588
|
+
var DISMISSED_FILE = path14.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3647
3589
|
function readDismissedChecks() {
|
|
3648
3590
|
try {
|
|
3649
|
-
if (!
|
|
3650
|
-
return JSON.parse(
|
|
3591
|
+
if (!fs16.existsSync(DISMISSED_FILE)) return [];
|
|
3592
|
+
return JSON.parse(fs16.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3651
3593
|
} catch {
|
|
3652
3594
|
return [];
|
|
3653
3595
|
}
|
|
3654
3596
|
}
|
|
3655
3597
|
function writeDismissedChecks(checks) {
|
|
3656
|
-
if (!
|
|
3657
|
-
|
|
3598
|
+
if (!fs16.existsSync(CALIBER_DIR)) {
|
|
3599
|
+
fs16.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3658
3600
|
}
|
|
3659
|
-
|
|
3601
|
+
fs16.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3660
3602
|
}
|
|
3661
3603
|
function getDismissedIds() {
|
|
3662
3604
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -3857,283 +3799,8 @@ function displayScoreDelta(before, after) {
|
|
|
3857
3799
|
import chalk4 from "chalk";
|
|
3858
3800
|
import ora from "ora";
|
|
3859
3801
|
import readline3 from "readline";
|
|
3860
|
-
import
|
|
3861
|
-
import
|
|
3862
|
-
|
|
3863
|
-
// src/mcp/deps.ts
|
|
3864
|
-
import fs18 from "fs";
|
|
3865
|
-
import path17 from "path";
|
|
3866
|
-
function extractAllDeps(dir) {
|
|
3867
|
-
const deps = /* @__PURE__ */ new Set();
|
|
3868
|
-
parsePackageJson(dir, deps);
|
|
3869
|
-
parseRequirementsTxt(dir, deps);
|
|
3870
|
-
parsePyprojectToml(dir, deps);
|
|
3871
|
-
parseGoMod(dir, deps);
|
|
3872
|
-
parseCargoToml(dir, deps);
|
|
3873
|
-
parseGemfile(dir, deps);
|
|
3874
|
-
parseComposerJson(dir, deps);
|
|
3875
|
-
return Array.from(deps);
|
|
3876
|
-
}
|
|
3877
|
-
function readFileSafe(filePath) {
|
|
3878
|
-
try {
|
|
3879
|
-
if (fs18.existsSync(filePath)) {
|
|
3880
|
-
return fs18.readFileSync(filePath, "utf-8");
|
|
3881
|
-
}
|
|
3882
|
-
} catch {
|
|
3883
|
-
}
|
|
3884
|
-
return null;
|
|
3885
|
-
}
|
|
3886
|
-
function parsePackageJson(dir, deps) {
|
|
3887
|
-
const content = readFileSafe(path17.join(dir, "package.json"));
|
|
3888
|
-
if (!content) return;
|
|
3889
|
-
try {
|
|
3890
|
-
const pkg3 = JSON.parse(content);
|
|
3891
|
-
const allDeps = {
|
|
3892
|
-
...pkg3.dependencies,
|
|
3893
|
-
...pkg3.devDependencies
|
|
3894
|
-
};
|
|
3895
|
-
for (const name of Object.keys(allDeps)) {
|
|
3896
|
-
deps.add(name);
|
|
3897
|
-
}
|
|
3898
|
-
} catch {
|
|
3899
|
-
}
|
|
3900
|
-
}
|
|
3901
|
-
function parseRequirementsTxt(dir, deps) {
|
|
3902
|
-
const content = readFileSafe(path17.join(dir, "requirements.txt"));
|
|
3903
|
-
if (!content) return;
|
|
3904
|
-
for (const line of content.split("\n")) {
|
|
3905
|
-
const trimmed = line.trim();
|
|
3906
|
-
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
3907
|
-
const match = trimmed.match(/^([a-zA-Z0-9_-]+(?:\[[^\]]*\])?)/);
|
|
3908
|
-
if (match) {
|
|
3909
|
-
deps.add(match[1].replace(/\[.*\]/, "").toLowerCase());
|
|
3910
|
-
}
|
|
3911
|
-
}
|
|
3912
|
-
}
|
|
3913
|
-
function parsePyprojectToml(dir, deps) {
|
|
3914
|
-
const content = readFileSafe(path17.join(dir, "pyproject.toml"));
|
|
3915
|
-
if (!content) return;
|
|
3916
|
-
const depsMatch = content.match(/\bdependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
3917
|
-
if (depsMatch) {
|
|
3918
|
-
const items = depsMatch[1].matchAll(/"([a-zA-Z0-9_-]+)/g);
|
|
3919
|
-
for (const m of items) {
|
|
3920
|
-
deps.add(m[1].toLowerCase());
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
function parseGoMod(dir, deps) {
|
|
3925
|
-
const content = readFileSafe(path17.join(dir, "go.mod"));
|
|
3926
|
-
if (!content) return;
|
|
3927
|
-
const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/g);
|
|
3928
|
-
if (requireBlock) {
|
|
3929
|
-
for (const block of requireBlock) {
|
|
3930
|
-
const lines = block.split("\n");
|
|
3931
|
-
for (const line of lines) {
|
|
3932
|
-
const match = line.trim().match(/^([a-zA-Z0-9./\-_]+)\s/);
|
|
3933
|
-
if (match && !match[1].startsWith("//") && match[1].includes("/")) {
|
|
3934
|
-
const parts = match[1].split("/");
|
|
3935
|
-
deps.add(parts[parts.length - 1]);
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
function parseCargoToml(dir, deps) {
|
|
3942
|
-
const content = readFileSafe(path17.join(dir, "Cargo.toml"));
|
|
3943
|
-
if (!content) return;
|
|
3944
|
-
const sections = content.split(/\[/);
|
|
3945
|
-
for (const section of sections) {
|
|
3946
|
-
if (section.startsWith("dependencies]") || section.startsWith("dev-dependencies]")) {
|
|
3947
|
-
const lines = section.split("\n").slice(1);
|
|
3948
|
-
for (const line of lines) {
|
|
3949
|
-
if (line.startsWith("[")) break;
|
|
3950
|
-
const match = line.match(/^([a-zA-Z0-9_-]+)\s*=/);
|
|
3951
|
-
if (match) {
|
|
3952
|
-
deps.add(match[1]);
|
|
3953
|
-
}
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
}
|
|
3957
|
-
}
|
|
3958
|
-
function parseGemfile(dir, deps) {
|
|
3959
|
-
const content = readFileSafe(path17.join(dir, "Gemfile"));
|
|
3960
|
-
if (!content) return;
|
|
3961
|
-
const gemPattern = /gem\s+['"]([a-zA-Z0-9_-]+)['"]/g;
|
|
3962
|
-
let match;
|
|
3963
|
-
while ((match = gemPattern.exec(content)) !== null) {
|
|
3964
|
-
deps.add(match[1]);
|
|
3965
|
-
}
|
|
3966
|
-
}
|
|
3967
|
-
function parseComposerJson(dir, deps) {
|
|
3968
|
-
const content = readFileSafe(path17.join(dir, "composer.json"));
|
|
3969
|
-
if (!content) return;
|
|
3970
|
-
try {
|
|
3971
|
-
const composer = JSON.parse(content);
|
|
3972
|
-
const allDeps = {
|
|
3973
|
-
...composer.require,
|
|
3974
|
-
...composer["require-dev"]
|
|
3975
|
-
};
|
|
3976
|
-
for (const name of Object.keys(allDeps)) {
|
|
3977
|
-
if (name !== "php" && !name.startsWith("ext-")) {
|
|
3978
|
-
deps.add(name);
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
} catch {
|
|
3982
|
-
}
|
|
3983
|
-
}
|
|
3984
|
-
|
|
3985
|
-
// src/mcp/prompts.ts
|
|
3986
|
-
var CLASSIFY_DEPS_PROMPT = `You classify software dependencies into two categories:
|
|
3987
|
-
|
|
3988
|
-
**Tools** (MCP-worthy): Services, platforms, APIs, databases, SaaS products, and cloud services that have their own web dashboards, APIs, or external infrastructure. Examples: supabase, stripe, sentry, datadog, firebase, mongodb, redis, slack, linear, github, vercel, aws-sdk, twilio, sendgrid, algolia, elasticsearch, prisma, planetscale, neon, clerk, auth0.
|
|
3989
|
-
|
|
3990
|
-
**Libraries** (skip): Utility packages, frameworks, build tools, test runners, and local-only code that does NOT connect to an external service. Examples: lodash, react, express, vitest, webpack, zod, chalk, commander, typescript, eslint, prettier, axios, dayjs, uuid.
|
|
3991
|
-
|
|
3992
|
-
Given a list of dependencies, return ONLY the tool dependencies as a JSON array of strings.
|
|
3993
|
-
Return ONLY the JSON array, no explanation.`;
|
|
3994
|
-
var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
|
|
3995
|
-
|
|
3996
|
-
Score each candidate from 0-100 based on:
|
|
3997
|
-
- **Relevance** (40%): How directly does it match the project's detected tool dependencies?
|
|
3998
|
-
- **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
|
|
3999
|
-
- **Quality signals** (30%): Stars, recent activity, vendor/official status
|
|
4000
|
-
|
|
4001
|
-
Return a JSON array where each element has:
|
|
4002
|
-
- "index": the candidate's index number
|
|
4003
|
-
- "score": relevance score 0-100
|
|
4004
|
-
- "reason": one-liner explaining the score (max 80 chars)
|
|
4005
|
-
|
|
4006
|
-
Be selective. Only score candidates that would genuinely help developers working on this project.
|
|
4007
|
-
Return ONLY the JSON array.`;
|
|
4008
|
-
var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
|
|
4009
|
-
|
|
4010
|
-
Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
|
|
4011
|
-
|
|
4012
|
-
Return a JSON object with:
|
|
4013
|
-
- "command": the executable command (e.g., "npx", "uvx", "node", "docker")
|
|
4014
|
-
- "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
|
|
4015
|
-
- "env": array of objects, each with:
|
|
4016
|
-
- "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
|
|
4017
|
-
- "description": brief description of what this value is (e.g., "Personal access token from dashboard")
|
|
4018
|
-
- "required": boolean
|
|
4019
|
-
|
|
4020
|
-
If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
|
|
4021
|
-
If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
|
|
4022
|
-
Return ONLY the JSON object.`;
|
|
4023
|
-
|
|
4024
|
-
// src/mcp/classify.ts
|
|
4025
|
-
async function classifyDeps(allDeps) {
|
|
4026
|
-
if (allDeps.length === 0) return [];
|
|
4027
|
-
try {
|
|
4028
|
-
const result = await llmJsonCall({
|
|
4029
|
-
system: CLASSIFY_DEPS_PROMPT,
|
|
4030
|
-
prompt: `Dependencies:
|
|
4031
|
-
${JSON.stringify(allDeps)}`,
|
|
4032
|
-
maxTokens: 2e3
|
|
4033
|
-
});
|
|
4034
|
-
if (!Array.isArray(result)) return [];
|
|
4035
|
-
const inputLower = new Set(allDeps.map((d) => d.toLowerCase()));
|
|
4036
|
-
return result.filter((d) => typeof d === "string" && inputLower.has(d.toLowerCase()));
|
|
4037
|
-
} catch {
|
|
4038
|
-
return fallbackClassify(allDeps);
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
function fallbackClassify(deps) {
|
|
4042
|
-
const utilityPatterns = [
|
|
4043
|
-
/^@types\//,
|
|
4044
|
-
/^eslint/,
|
|
4045
|
-
/^prettier/,
|
|
4046
|
-
/^@typescript-eslint\//,
|
|
4047
|
-
/^@commitlint\//,
|
|
4048
|
-
/^@eslint\//,
|
|
4049
|
-
/^webpack/,
|
|
4050
|
-
/^rollup/,
|
|
4051
|
-
/^babel/,
|
|
4052
|
-
/^@babel\//,
|
|
4053
|
-
/^postcss/,
|
|
4054
|
-
/^tailwindcss/,
|
|
4055
|
-
/^autoprefixer/
|
|
4056
|
-
];
|
|
4057
|
-
const utilities = /* @__PURE__ */ new Set([
|
|
4058
|
-
"typescript",
|
|
4059
|
-
"tslib",
|
|
4060
|
-
"ts-node",
|
|
4061
|
-
"tsx",
|
|
4062
|
-
"prettier",
|
|
4063
|
-
"eslint",
|
|
4064
|
-
"rimraf",
|
|
4065
|
-
"cross-env",
|
|
4066
|
-
"dotenv",
|
|
4067
|
-
"nodemon",
|
|
4068
|
-
"husky",
|
|
4069
|
-
"lint-staged",
|
|
4070
|
-
"commitlint",
|
|
4071
|
-
"chalk",
|
|
4072
|
-
"ora",
|
|
4073
|
-
"commander",
|
|
4074
|
-
"yargs",
|
|
4075
|
-
"meow",
|
|
4076
|
-
"inquirer",
|
|
4077
|
-
"glob",
|
|
4078
|
-
"minimatch",
|
|
4079
|
-
"micromatch",
|
|
4080
|
-
"diff",
|
|
4081
|
-
"semver",
|
|
4082
|
-
"uuid",
|
|
4083
|
-
"nanoid",
|
|
4084
|
-
"debug",
|
|
4085
|
-
"ms",
|
|
4086
|
-
"lodash",
|
|
4087
|
-
"underscore",
|
|
4088
|
-
"ramda",
|
|
4089
|
-
"tsup",
|
|
4090
|
-
"esbuild",
|
|
4091
|
-
"rollup",
|
|
4092
|
-
"webpack",
|
|
4093
|
-
"vite",
|
|
4094
|
-
"parcel",
|
|
4095
|
-
"vitest",
|
|
4096
|
-
"jest",
|
|
4097
|
-
"mocha",
|
|
4098
|
-
"chai",
|
|
4099
|
-
"ava",
|
|
4100
|
-
"tap",
|
|
4101
|
-
"fs-extra",
|
|
4102
|
-
"mkdirp",
|
|
4103
|
-
"del",
|
|
4104
|
-
"path-to-regexp",
|
|
4105
|
-
"strip-ansi",
|
|
4106
|
-
"ansi-colors",
|
|
4107
|
-
"react",
|
|
4108
|
-
"react-dom",
|
|
4109
|
-
"next",
|
|
4110
|
-
"vue",
|
|
4111
|
-
"angular",
|
|
4112
|
-
"svelte",
|
|
4113
|
-
"express",
|
|
4114
|
-
"fastify",
|
|
4115
|
-
"koa",
|
|
4116
|
-
"hapi",
|
|
4117
|
-
"zod",
|
|
4118
|
-
"joi",
|
|
4119
|
-
"yup",
|
|
4120
|
-
"ajv",
|
|
4121
|
-
"axios",
|
|
4122
|
-
"node-fetch",
|
|
4123
|
-
"got",
|
|
4124
|
-
"undici",
|
|
4125
|
-
"moment",
|
|
4126
|
-
"dayjs",
|
|
4127
|
-
"date-fns",
|
|
4128
|
-
"luxon"
|
|
4129
|
-
]);
|
|
4130
|
-
return deps.filter((d) => {
|
|
4131
|
-
const lower = d.toLowerCase();
|
|
4132
|
-
if (utilities.has(lower)) return false;
|
|
4133
|
-
if (utilityPatterns.some((p) => p.test(lower))) return false;
|
|
4134
|
-
return true;
|
|
4135
|
-
});
|
|
4136
|
-
}
|
|
3802
|
+
import fs17 from "fs";
|
|
3803
|
+
import path15 from "path";
|
|
4137
3804
|
|
|
4138
3805
|
// src/mcp/search.ts
|
|
4139
3806
|
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
@@ -4285,6 +3952,37 @@ async function searchVendorOrg(dep) {
|
|
|
4285
3952
|
}
|
|
4286
3953
|
}
|
|
4287
3954
|
|
|
3955
|
+
// src/mcp/prompts.ts
|
|
3956
|
+
var SCORE_MCP_PROMPT = `You evaluate MCP (Model Context Protocol) server candidates for relevance to a software project.
|
|
3957
|
+
|
|
3958
|
+
Score each candidate from 0-100 based on:
|
|
3959
|
+
- **Relevance** (40%): How directly does it match the project's detected tool dependencies?
|
|
3960
|
+
- **Capabilities** (30%): Does it provide meaningful read+write operations (not just read-only)?
|
|
3961
|
+
- **Quality signals** (30%): Stars, recent activity, vendor/official status
|
|
3962
|
+
|
|
3963
|
+
Return a JSON array where each element has:
|
|
3964
|
+
- "index": the candidate's index number
|
|
3965
|
+
- "score": relevance score 0-100
|
|
3966
|
+
- "reason": one-liner explaining the score (max 80 chars)
|
|
3967
|
+
|
|
3968
|
+
Be selective. Only score candidates that would genuinely help developers working on this project.
|
|
3969
|
+
Return ONLY the JSON array.`;
|
|
3970
|
+
var EXTRACT_CONFIG_PROMPT = `You extract MCP server configuration from a README file.
|
|
3971
|
+
|
|
3972
|
+
Look for the MCP server's configuration block \u2014 typically a JSON snippet showing how to add it to claude_desktop_config.json, .mcp.json, or similar.
|
|
3973
|
+
|
|
3974
|
+
Return a JSON object with:
|
|
3975
|
+
- "command": the executable command (e.g., "npx", "uvx", "node", "docker")
|
|
3976
|
+
- "args": array of arguments (e.g., ["-y", "@supabase/mcp-server"])
|
|
3977
|
+
- "env": array of objects, each with:
|
|
3978
|
+
- "key": environment variable name (e.g., "SUPABASE_ACCESS_TOKEN")
|
|
3979
|
+
- "description": brief description of what this value is (e.g., "Personal access token from dashboard")
|
|
3980
|
+
- "required": boolean
|
|
3981
|
+
|
|
3982
|
+
If the README shows multiple configuration methods, prefer npx > uvx > node > docker.
|
|
3983
|
+
If you cannot determine the configuration, return {"command": "", "args": [], "env": []}.
|
|
3984
|
+
Return ONLY the JSON object.`;
|
|
3985
|
+
|
|
4288
3986
|
// src/mcp/validate.ts
|
|
4289
3987
|
var MIN_STARS = 100;
|
|
4290
3988
|
var MAX_AGE_DAYS = 180;
|
|
@@ -4377,40 +4075,28 @@ ${truncated}`,
|
|
|
4377
4075
|
// src/mcp/index.ts
|
|
4378
4076
|
async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
4379
4077
|
console.log(chalk4.hex("#6366f1").bold("\n MCP Server Discovery\n"));
|
|
4380
|
-
const
|
|
4381
|
-
const allDeps = extractAllDeps(dir);
|
|
4382
|
-
if (allDeps.length === 0) {
|
|
4383
|
-
spinner.succeed(chalk4.dim("No dependencies found \u2014 skipping MCP discovery"));
|
|
4384
|
-
return { installed: 0, names: [] };
|
|
4385
|
-
}
|
|
4386
|
-
const toolDeps = await classifyDeps(allDeps);
|
|
4078
|
+
const toolDeps = fingerprint.tools;
|
|
4387
4079
|
if (toolDeps.length === 0) {
|
|
4388
|
-
|
|
4389
|
-
console.log(chalk4.dim(` All deps (${allDeps.length}): ${allDeps.slice(0, 10).join(", ")}${allDeps.length > 10 ? "..." : ""}`));
|
|
4080
|
+
console.log(chalk4.dim(" No external tools or services detected \u2014 skipping MCP discovery"));
|
|
4390
4081
|
return { installed: 0, names: [] };
|
|
4391
4082
|
}
|
|
4392
|
-
spinner
|
|
4393
|
-
console.log(chalk4.dim(`
|
|
4394
|
-
console.log(chalk4.dim(` Classified as tools: ${toolDeps.join(", ")}`));
|
|
4083
|
+
const spinner = ora(`Searching MCP servers for ${toolDeps.length} detected tool${toolDeps.length === 1 ? "" : "s"}...`).start();
|
|
4084
|
+
console.log(chalk4.dim(` Detected: ${toolDeps.join(", ")}`));
|
|
4395
4085
|
const existingMcps = getExistingMcpNames(fingerprint, targetAgent);
|
|
4396
4086
|
const filteredDeps = toolDeps.filter((d) => {
|
|
4397
|
-
const lower = d.toLowerCase()
|
|
4087
|
+
const lower = d.toLowerCase();
|
|
4398
4088
|
return !existingMcps.some((name) => name.includes(lower) || lower.includes(name));
|
|
4399
4089
|
});
|
|
4400
4090
|
if (filteredDeps.length === 0) {
|
|
4401
|
-
|
|
4402
|
-
console.log(chalk4.dim(` Existing MCPs: ${existingMcps.join(", ")}`));
|
|
4091
|
+
spinner.succeed(chalk4.dim("All detected tools already have MCP servers configured"));
|
|
4403
4092
|
return { installed: 0, names: [] };
|
|
4404
4093
|
}
|
|
4405
|
-
const searchSpinner = ora("Searching for MCP servers...").start();
|
|
4406
4094
|
const candidates = await searchAllMcpSources(filteredDeps);
|
|
4407
4095
|
if (candidates.length === 0) {
|
|
4408
|
-
|
|
4409
|
-
console.log(chalk4.dim(` Searched for: ${filteredDeps.join(", ")}`));
|
|
4096
|
+
spinner.succeed(chalk4.dim("No MCP servers found for detected tools"));
|
|
4410
4097
|
return { installed: 0, names: [] };
|
|
4411
4098
|
}
|
|
4412
|
-
|
|
4413
|
-
console.log(chalk4.dim(` Sources: ${candidates.map((c) => c.repoFullName).join(", ")}`));
|
|
4099
|
+
spinner.succeed(`Found ${candidates.length} candidate${candidates.length === 1 ? "" : "s"} for ${filteredDeps.join(", ")}`);
|
|
4414
4100
|
const scoreSpinner = ora("Scoring MCP candidates...").start();
|
|
4415
4101
|
const scored = await validateAndScore(candidates, filteredDeps);
|
|
4416
4102
|
if (scored.length === 0) {
|
|
@@ -4462,26 +4148,26 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4462
4148
|
return { installed: 0, names: [] };
|
|
4463
4149
|
}
|
|
4464
4150
|
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4465
|
-
writeMcpJson(
|
|
4151
|
+
writeMcpJson(path15.join(dir, ".mcp.json"), mcpServers);
|
|
4466
4152
|
}
|
|
4467
4153
|
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4468
|
-
const cursorDir =
|
|
4469
|
-
if (!
|
|
4470
|
-
writeMcpJson(
|
|
4154
|
+
const cursorDir = path15.join(dir, ".cursor");
|
|
4155
|
+
if (!fs17.existsSync(cursorDir)) fs17.mkdirSync(cursorDir, { recursive: true });
|
|
4156
|
+
writeMcpJson(path15.join(cursorDir, "mcp.json"), mcpServers);
|
|
4471
4157
|
}
|
|
4472
4158
|
return { installed: installedNames.length, names: installedNames };
|
|
4473
4159
|
}
|
|
4474
4160
|
function writeMcpJson(filePath, mcpServers) {
|
|
4475
4161
|
let existing = {};
|
|
4476
4162
|
try {
|
|
4477
|
-
if (
|
|
4478
|
-
const parsed = JSON.parse(
|
|
4163
|
+
if (fs17.existsSync(filePath)) {
|
|
4164
|
+
const parsed = JSON.parse(fs17.readFileSync(filePath, "utf-8"));
|
|
4479
4165
|
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4480
4166
|
}
|
|
4481
4167
|
} catch {
|
|
4482
4168
|
}
|
|
4483
4169
|
const merged = { ...existing, ...mcpServers };
|
|
4484
|
-
|
|
4170
|
+
fs17.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4485
4171
|
}
|
|
4486
4172
|
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4487
4173
|
const names = [];
|
|
@@ -4618,11 +4304,12 @@ async function initCommand(options) {
|
|
|
4618
4304
|
console.log(chalk5.dim(" Caliber analyzes your codebase and creates tailored config files"));
|
|
4619
4305
|
console.log(chalk5.dim(" so your AI coding agents understand your project from day one.\n"));
|
|
4620
4306
|
console.log(title.bold(" How onboarding works:\n"));
|
|
4621
|
-
console.log(chalk5.dim(" 1. Connect
|
|
4622
|
-
console.log(chalk5.dim(" 2. Discover
|
|
4623
|
-
console.log(chalk5.dim(" 3. Generate
|
|
4624
|
-
console.log(chalk5.dim(" 4. Review
|
|
4625
|
-
console.log(
|
|
4307
|
+
console.log(chalk5.dim(" 1. Connect Set up your LLM provider"));
|
|
4308
|
+
console.log(chalk5.dim(" 2. Discover Analyze your code, dependencies, and structure"));
|
|
4309
|
+
console.log(chalk5.dim(" 3. Generate Create config files tailored to your project"));
|
|
4310
|
+
console.log(chalk5.dim(" 4. Review Preview, refine, and apply the changes"));
|
|
4311
|
+
console.log(chalk5.dim(" 5. Enhance Discover MCP servers for your tools\n"));
|
|
4312
|
+
console.log(title.bold(" Step 1/5 \u2014 Connect your LLM\n"));
|
|
4626
4313
|
let config = loadConfig();
|
|
4627
4314
|
if (!config) {
|
|
4628
4315
|
console.log(chalk5.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
|
|
@@ -4642,10 +4329,10 @@ async function initCommand(options) {
|
|
|
4642
4329
|
console.log(chalk5.green(" \u2713 Provider saved. Let's continue.\n"));
|
|
4643
4330
|
}
|
|
4644
4331
|
const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
|
|
4645
|
-
const fastModel =
|
|
4332
|
+
const fastModel = getFastModel();
|
|
4646
4333
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
4647
4334
|
console.log(chalk5.dim(modelLine + "\n"));
|
|
4648
|
-
console.log(title.bold(" Step 2/
|
|
4335
|
+
console.log(title.bold(" Step 2/5 \u2014 Discover your project\n"));
|
|
4649
4336
|
console.log(chalk5.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
|
|
4650
4337
|
const spinner = ora2("Analyzing project...").start();
|
|
4651
4338
|
const fingerprint = collectFingerprint(process.cwd());
|
|
@@ -4669,11 +4356,27 @@ async function initCommand(options) {
|
|
|
4669
4356
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4670
4357
|
displayScoreSummary(baselineScore);
|
|
4671
4358
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
4359
|
+
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
4672
4360
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
4673
4361
|
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
4674
4362
|
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
4675
4363
|
if (!options.force) return;
|
|
4676
4364
|
}
|
|
4365
|
+
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
4366
|
+
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
4367
|
+
if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
|
|
4368
|
+
console.log(chalk5.bold.green("\n Your config is fully optimized for LLM generation.\n"));
|
|
4369
|
+
console.log(chalk5.dim(" Remaining items need CLI actions:\n"));
|
|
4370
|
+
for (const check of allFailingChecks) {
|
|
4371
|
+
console.log(chalk5.dim(` \u2022 ${check.name}`));
|
|
4372
|
+
if (check.suggestion) {
|
|
4373
|
+
console.log(` ${chalk5.hex("#83D1EB")(check.suggestion)}`);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
console.log("");
|
|
4377
|
+
console.log(chalk5.dim(" Run ") + chalk5.hex("#83D1EB")("caliber onboard --force") + chalk5.dim(" to regenerate anyway.\n"));
|
|
4378
|
+
return;
|
|
4379
|
+
}
|
|
4677
4380
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
4678
4381
|
if (isEmpty) {
|
|
4679
4382
|
fingerprint.description = await promptInput3("What will you build in this project?");
|
|
@@ -4682,11 +4385,11 @@ async function initCommand(options) {
|
|
|
4682
4385
|
let passingChecks;
|
|
4683
4386
|
let currentScore;
|
|
4684
4387
|
if (hasExistingConfig && baselineScore.score >= 95 && !options.force) {
|
|
4685
|
-
failingChecks =
|
|
4388
|
+
failingChecks = llmFixableChecks.map((c) => ({ name: c.name, suggestion: c.suggestion }));
|
|
4686
4389
|
passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
4687
4390
|
currentScore = baselineScore.score;
|
|
4688
4391
|
if (failingChecks.length > 0) {
|
|
4689
|
-
console.log(title.bold(" Step 3/
|
|
4392
|
+
console.log(title.bold(" Step 3/5 \u2014 Fine-tuning\n"));
|
|
4690
4393
|
console.log(chalk5.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
|
|
4691
4394
|
`));
|
|
4692
4395
|
for (const check of failingChecks) {
|
|
@@ -4695,11 +4398,11 @@ async function initCommand(options) {
|
|
|
4695
4398
|
console.log("");
|
|
4696
4399
|
}
|
|
4697
4400
|
} else if (hasExistingConfig) {
|
|
4698
|
-
console.log(title.bold(" Step 3/
|
|
4401
|
+
console.log(title.bold(" Step 3/5 \u2014 Improve your setup\n"));
|
|
4699
4402
|
console.log(chalk5.dim(" Reviewing your existing configs against your codebase"));
|
|
4700
4403
|
console.log(chalk5.dim(" and preparing improvements.\n"));
|
|
4701
4404
|
} else {
|
|
4702
|
-
console.log(title.bold(" Step 3/
|
|
4405
|
+
console.log(title.bold(" Step 3/5 \u2014 Build your agent setup\n"));
|
|
4703
4406
|
console.log(chalk5.dim(" Creating config files tailored to your project.\n"));
|
|
4704
4407
|
}
|
|
4705
4408
|
console.log(chalk5.dim(" This can take a couple of minutes depending on your model and provider.\n"));
|
|
@@ -4760,17 +4463,25 @@ async function initCommand(options) {
|
|
|
4760
4463
|
role: "assistant",
|
|
4761
4464
|
content: summarizeSetup("Initial generation", generatedSetup)
|
|
4762
4465
|
});
|
|
4763
|
-
console.log(title.bold(" Step 4/
|
|
4466
|
+
console.log(title.bold(" Step 4/5 \u2014 Review and apply\n"));
|
|
4764
4467
|
const setupFiles = collectSetupFiles(generatedSetup);
|
|
4765
4468
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
4766
|
-
|
|
4469
|
+
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
4470
|
+
console.log(chalk5.dim(` ${chalk5.green(`${staged.newFiles} new`)} / ${chalk5.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}
|
|
4767
4471
|
`));
|
|
4768
|
-
|
|
4769
|
-
if (
|
|
4770
|
-
|
|
4771
|
-
|
|
4472
|
+
let action;
|
|
4473
|
+
if (totalChanges === 0) {
|
|
4474
|
+
console.log(chalk5.dim(" No changes needed \u2014 your configs are already up to date.\n"));
|
|
4475
|
+
cleanupStaging();
|
|
4476
|
+
action = "accept";
|
|
4477
|
+
} else {
|
|
4478
|
+
const wantsReview = await promptWantsReview();
|
|
4479
|
+
if (wantsReview) {
|
|
4480
|
+
const reviewMethod = await promptReviewMethod();
|
|
4481
|
+
await openReview(reviewMethod, staged.stagedFiles);
|
|
4482
|
+
}
|
|
4483
|
+
action = await promptReviewAction();
|
|
4772
4484
|
}
|
|
4773
|
-
let action = await promptReviewAction();
|
|
4774
4485
|
while (action === "refine") {
|
|
4775
4486
|
generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
|
|
4776
4487
|
if (!generatedSetup) {
|
|
@@ -4819,17 +4530,24 @@ async function initCommand(options) {
|
|
|
4819
4530
|
console.error(chalk5.red(err instanceof Error ? err.message : "Unknown error"));
|
|
4820
4531
|
throw new Error("__exit__");
|
|
4821
4532
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4533
|
+
console.log(title.bold("\n Step 5/5 \u2014 Enhance with MCP servers\n"));
|
|
4534
|
+
console.log(chalk5.dim(" MCP servers connect your AI agents to external tools and services"));
|
|
4535
|
+
console.log(chalk5.dim(" like databases, APIs, and platforms your project depends on.\n"));
|
|
4536
|
+
if (fingerprint.tools.length > 0) {
|
|
4537
|
+
try {
|
|
4538
|
+
const mcpResult = await discoverAndInstallMcps(targetAgent, fingerprint, process.cwd());
|
|
4539
|
+
if (mcpResult.installed > 0) {
|
|
4540
|
+
console.log(chalk5.bold(`
|
|
4826
4541
|
${mcpResult.installed} MCP server${mcpResult.installed > 1 ? "s" : ""} configured`));
|
|
4827
|
-
|
|
4828
|
-
|
|
4542
|
+
for (const name of mcpResult.names) {
|
|
4543
|
+
console.log(` ${chalk5.green("\u2713")} ${name}`);
|
|
4544
|
+
}
|
|
4829
4545
|
}
|
|
4546
|
+
} catch (err) {
|
|
4547
|
+
console.log(chalk5.dim(" MCP discovery skipped: " + (err instanceof Error ? err.message : "unknown error")));
|
|
4830
4548
|
}
|
|
4831
|
-
}
|
|
4832
|
-
console.log(chalk5.dim("
|
|
4549
|
+
} else {
|
|
4550
|
+
console.log(chalk5.dim(" No external tools or services detected \u2014 skipping MCP discovery.\n"));
|
|
4833
4551
|
}
|
|
4834
4552
|
ensurePermissions();
|
|
4835
4553
|
const sha = getCurrentHeadSha();
|
|
@@ -4938,12 +4656,12 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4938
4656
|
}
|
|
4939
4657
|
function summarizeSetup(action, setup) {
|
|
4940
4658
|
const descriptions = setup.fileDescriptions;
|
|
4941
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4659
|
+
const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4942
4660
|
return `${action}. Files:
|
|
4943
4661
|
${files}`;
|
|
4944
4662
|
}
|
|
4945
4663
|
async function classifyRefineIntent(message) {
|
|
4946
|
-
const fastModel =
|
|
4664
|
+
const fastModel = getFastModel();
|
|
4947
4665
|
try {
|
|
4948
4666
|
const result = await llmJsonCall({
|
|
4949
4667
|
system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
|
|
@@ -4960,7 +4678,7 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
|
|
|
4960
4678
|
}
|
|
4961
4679
|
}
|
|
4962
4680
|
async function evaluateDismissals(failingChecks, fingerprint) {
|
|
4963
|
-
const fastModel =
|
|
4681
|
+
const fastModel = getFastModel();
|
|
4964
4682
|
const checkList = failingChecks.map((c) => ({
|
|
4965
4683
|
id: c.id,
|
|
4966
4684
|
name: c.name,
|
|
@@ -5058,8 +4776,8 @@ async function openReview(method, stagedFiles) {
|
|
|
5058
4776
|
return;
|
|
5059
4777
|
}
|
|
5060
4778
|
const fileInfos = stagedFiles.map((file) => {
|
|
5061
|
-
const proposed =
|
|
5062
|
-
const current = file.currentPath ?
|
|
4779
|
+
const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
|
|
4780
|
+
const current = file.currentPath ? fs18.readFileSync(file.currentPath, "utf-8") : "";
|
|
5063
4781
|
const patch = createTwoFilesPatch(
|
|
5064
4782
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
5065
4783
|
file.relativePath,
|
|
@@ -5242,7 +4960,7 @@ function printSetupSummary(setup) {
|
|
|
5242
4960
|
};
|
|
5243
4961
|
if (claude) {
|
|
5244
4962
|
if (claude.claudeMd) {
|
|
5245
|
-
const icon =
|
|
4963
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
5246
4964
|
const desc = getDescription("CLAUDE.md");
|
|
5247
4965
|
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
5248
4966
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -5252,7 +4970,7 @@ function printSetupSummary(setup) {
|
|
|
5252
4970
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
5253
4971
|
for (const skill of skills) {
|
|
5254
4972
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
5255
|
-
const icon =
|
|
4973
|
+
const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5256
4974
|
const desc = getDescription(skillPath);
|
|
5257
4975
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5258
4976
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -5262,7 +4980,7 @@ function printSetupSummary(setup) {
|
|
|
5262
4980
|
}
|
|
5263
4981
|
if (cursor) {
|
|
5264
4982
|
if (cursor.cursorrules) {
|
|
5265
|
-
const icon =
|
|
4983
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
5266
4984
|
const desc = getDescription(".cursorrules");
|
|
5267
4985
|
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
5268
4986
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -5272,7 +4990,7 @@ function printSetupSummary(setup) {
|
|
|
5272
4990
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
5273
4991
|
for (const skill of cursorSkills) {
|
|
5274
4992
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
5275
|
-
const icon =
|
|
4993
|
+
const icon = fs18.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5276
4994
|
const desc = getDescription(skillPath);
|
|
5277
4995
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5278
4996
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -5283,7 +5001,7 @@ function printSetupSummary(setup) {
|
|
|
5283
5001
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
5284
5002
|
for (const rule of rules) {
|
|
5285
5003
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5286
|
-
const icon =
|
|
5004
|
+
const icon = fs18.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5287
5005
|
const desc = getDescription(rulePath);
|
|
5288
5006
|
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
5289
5007
|
if (desc) {
|
|
@@ -5296,7 +5014,7 @@ function printSetupSummary(setup) {
|
|
|
5296
5014
|
}
|
|
5297
5015
|
}
|
|
5298
5016
|
}
|
|
5299
|
-
if (!
|
|
5017
|
+
if (!fs18.existsSync("AGENTS.md")) {
|
|
5300
5018
|
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
5301
5019
|
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
5302
5020
|
console.log("");
|
|
@@ -5324,8 +5042,8 @@ function ensurePermissions() {
|
|
|
5324
5042
|
const settingsPath = ".claude/settings.json";
|
|
5325
5043
|
let settings = {};
|
|
5326
5044
|
try {
|
|
5327
|
-
if (
|
|
5328
|
-
settings = JSON.parse(
|
|
5045
|
+
if (fs18.existsSync(settingsPath)) {
|
|
5046
|
+
settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
|
|
5329
5047
|
}
|
|
5330
5048
|
} catch {
|
|
5331
5049
|
}
|
|
@@ -5339,8 +5057,8 @@ function ensurePermissions() {
|
|
|
5339
5057
|
"Bash(git *)"
|
|
5340
5058
|
];
|
|
5341
5059
|
settings.permissions = permissions;
|
|
5342
|
-
if (!
|
|
5343
|
-
|
|
5060
|
+
if (!fs18.existsSync(".claude")) fs18.mkdirSync(".claude", { recursive: true });
|
|
5061
|
+
fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5344
5062
|
}
|
|
5345
5063
|
function collectSetupFiles(setup) {
|
|
5346
5064
|
const files = [];
|
|
@@ -5370,7 +5088,7 @@ function collectSetupFiles(setup) {
|
|
|
5370
5088
|
}
|
|
5371
5089
|
}
|
|
5372
5090
|
}
|
|
5373
|
-
if (!
|
|
5091
|
+
if (!fs18.existsSync("AGENTS.md")) {
|
|
5374
5092
|
const agentRefs = [];
|
|
5375
5093
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
5376
5094
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -5421,7 +5139,7 @@ function undoCommand() {
|
|
|
5421
5139
|
|
|
5422
5140
|
// src/commands/status.ts
|
|
5423
5141
|
import chalk7 from "chalk";
|
|
5424
|
-
import
|
|
5142
|
+
import fs19 from "fs";
|
|
5425
5143
|
async function statusCommand(options) {
|
|
5426
5144
|
const config = loadConfig();
|
|
5427
5145
|
const manifest = readManifest();
|
|
@@ -5447,7 +5165,7 @@ async function statusCommand(options) {
|
|
|
5447
5165
|
}
|
|
5448
5166
|
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
5449
5167
|
for (const entry of manifest.entries) {
|
|
5450
|
-
const exists =
|
|
5168
|
+
const exists = fs19.existsSync(entry.path);
|
|
5451
5169
|
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
5452
5170
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
5453
5171
|
}
|
|
@@ -5534,13 +5252,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
|
|
|
5534
5252
|
import { join as join8, dirname as dirname2 } from "path";
|
|
5535
5253
|
|
|
5536
5254
|
// src/scanner/index.ts
|
|
5537
|
-
import
|
|
5538
|
-
import
|
|
5255
|
+
import fs20 from "fs";
|
|
5256
|
+
import path16 from "path";
|
|
5539
5257
|
import crypto2 from "crypto";
|
|
5540
5258
|
function scanLocalState(dir) {
|
|
5541
5259
|
const items = [];
|
|
5542
|
-
const claudeMdPath =
|
|
5543
|
-
if (
|
|
5260
|
+
const claudeMdPath = path16.join(dir, "CLAUDE.md");
|
|
5261
|
+
if (fs20.existsSync(claudeMdPath)) {
|
|
5544
5262
|
items.push({
|
|
5545
5263
|
type: "rule",
|
|
5546
5264
|
platform: "claude",
|
|
@@ -5549,10 +5267,10 @@ function scanLocalState(dir) {
|
|
|
5549
5267
|
path: claudeMdPath
|
|
5550
5268
|
});
|
|
5551
5269
|
}
|
|
5552
|
-
const skillsDir =
|
|
5553
|
-
if (
|
|
5554
|
-
for (const file of
|
|
5555
|
-
const filePath =
|
|
5270
|
+
const skillsDir = path16.join(dir, ".claude", "skills");
|
|
5271
|
+
if (fs20.existsSync(skillsDir)) {
|
|
5272
|
+
for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5273
|
+
const filePath = path16.join(skillsDir, file);
|
|
5556
5274
|
items.push({
|
|
5557
5275
|
type: "skill",
|
|
5558
5276
|
platform: "claude",
|
|
@@ -5562,10 +5280,10 @@ function scanLocalState(dir) {
|
|
|
5562
5280
|
});
|
|
5563
5281
|
}
|
|
5564
5282
|
}
|
|
5565
|
-
const mcpJsonPath =
|
|
5566
|
-
if (
|
|
5283
|
+
const mcpJsonPath = path16.join(dir, ".mcp.json");
|
|
5284
|
+
if (fs20.existsSync(mcpJsonPath)) {
|
|
5567
5285
|
try {
|
|
5568
|
-
const mcpJson = JSON.parse(
|
|
5286
|
+
const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
|
|
5569
5287
|
if (mcpJson.mcpServers) {
|
|
5570
5288
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5571
5289
|
items.push({
|
|
@@ -5580,8 +5298,8 @@ function scanLocalState(dir) {
|
|
|
5580
5298
|
} catch {
|
|
5581
5299
|
}
|
|
5582
5300
|
}
|
|
5583
|
-
const cursorrulesPath =
|
|
5584
|
-
if (
|
|
5301
|
+
const cursorrulesPath = path16.join(dir, ".cursorrules");
|
|
5302
|
+
if (fs20.existsSync(cursorrulesPath)) {
|
|
5585
5303
|
items.push({
|
|
5586
5304
|
type: "rule",
|
|
5587
5305
|
platform: "cursor",
|
|
@@ -5590,10 +5308,10 @@ function scanLocalState(dir) {
|
|
|
5590
5308
|
path: cursorrulesPath
|
|
5591
5309
|
});
|
|
5592
5310
|
}
|
|
5593
|
-
const cursorRulesDir =
|
|
5594
|
-
if (
|
|
5595
|
-
for (const file of
|
|
5596
|
-
const filePath =
|
|
5311
|
+
const cursorRulesDir = path16.join(dir, ".cursor", "rules");
|
|
5312
|
+
if (fs20.existsSync(cursorRulesDir)) {
|
|
5313
|
+
for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5314
|
+
const filePath = path16.join(cursorRulesDir, file);
|
|
5597
5315
|
items.push({
|
|
5598
5316
|
type: "rule",
|
|
5599
5317
|
platform: "cursor",
|
|
@@ -5603,12 +5321,12 @@ function scanLocalState(dir) {
|
|
|
5603
5321
|
});
|
|
5604
5322
|
}
|
|
5605
5323
|
}
|
|
5606
|
-
const cursorSkillsDir =
|
|
5607
|
-
if (
|
|
5324
|
+
const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
|
|
5325
|
+
if (fs20.existsSync(cursorSkillsDir)) {
|
|
5608
5326
|
try {
|
|
5609
|
-
for (const name of
|
|
5610
|
-
const skillFile =
|
|
5611
|
-
if (
|
|
5327
|
+
for (const name of fs20.readdirSync(cursorSkillsDir)) {
|
|
5328
|
+
const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
|
|
5329
|
+
if (fs20.existsSync(skillFile)) {
|
|
5612
5330
|
items.push({
|
|
5613
5331
|
type: "skill",
|
|
5614
5332
|
platform: "cursor",
|
|
@@ -5621,10 +5339,10 @@ function scanLocalState(dir) {
|
|
|
5621
5339
|
} catch {
|
|
5622
5340
|
}
|
|
5623
5341
|
}
|
|
5624
|
-
const cursorMcpPath =
|
|
5625
|
-
if (
|
|
5342
|
+
const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
|
|
5343
|
+
if (fs20.existsSync(cursorMcpPath)) {
|
|
5626
5344
|
try {
|
|
5627
|
-
const mcpJson = JSON.parse(
|
|
5345
|
+
const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
|
|
5628
5346
|
if (mcpJson.mcpServers) {
|
|
5629
5347
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5630
5348
|
items.push({
|
|
@@ -5642,7 +5360,7 @@ function scanLocalState(dir) {
|
|
|
5642
5360
|
return items;
|
|
5643
5361
|
}
|
|
5644
5362
|
function hashFile(filePath) {
|
|
5645
|
-
const text =
|
|
5363
|
+
const text = fs20.readFileSync(filePath, "utf-8");
|
|
5646
5364
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
5647
5365
|
}
|
|
5648
5366
|
function hashJson(obj) {
|
|
@@ -6218,8 +5936,8 @@ async function scoreCommand(options) {
|
|
|
6218
5936
|
}
|
|
6219
5937
|
|
|
6220
5938
|
// src/commands/refresh.ts
|
|
6221
|
-
import
|
|
6222
|
-
import
|
|
5939
|
+
import fs22 from "fs";
|
|
5940
|
+
import path18 from "path";
|
|
6223
5941
|
import chalk11 from "chalk";
|
|
6224
5942
|
import ora6 from "ora";
|
|
6225
5943
|
|
|
@@ -6296,37 +6014,37 @@ function collectDiff(lastSha) {
|
|
|
6296
6014
|
}
|
|
6297
6015
|
|
|
6298
6016
|
// src/writers/refresh.ts
|
|
6299
|
-
import
|
|
6300
|
-
import
|
|
6017
|
+
import fs21 from "fs";
|
|
6018
|
+
import path17 from "path";
|
|
6301
6019
|
function writeRefreshDocs(docs) {
|
|
6302
6020
|
const written = [];
|
|
6303
6021
|
if (docs.claudeMd) {
|
|
6304
|
-
|
|
6022
|
+
fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6305
6023
|
written.push("CLAUDE.md");
|
|
6306
6024
|
}
|
|
6307
6025
|
if (docs.readmeMd) {
|
|
6308
|
-
|
|
6026
|
+
fs21.writeFileSync("README.md", docs.readmeMd);
|
|
6309
6027
|
written.push("README.md");
|
|
6310
6028
|
}
|
|
6311
6029
|
if (docs.cursorrules) {
|
|
6312
|
-
|
|
6030
|
+
fs21.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6313
6031
|
written.push(".cursorrules");
|
|
6314
6032
|
}
|
|
6315
6033
|
if (docs.cursorRules) {
|
|
6316
|
-
const rulesDir =
|
|
6317
|
-
if (!
|
|
6034
|
+
const rulesDir = path17.join(".cursor", "rules");
|
|
6035
|
+
if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
|
|
6318
6036
|
for (const rule of docs.cursorRules) {
|
|
6319
|
-
const filePath =
|
|
6320
|
-
|
|
6037
|
+
const filePath = path17.join(rulesDir, rule.filename);
|
|
6038
|
+
fs21.writeFileSync(filePath, rule.content);
|
|
6321
6039
|
written.push(filePath);
|
|
6322
6040
|
}
|
|
6323
6041
|
}
|
|
6324
6042
|
if (docs.claudeSkills) {
|
|
6325
|
-
const skillsDir =
|
|
6326
|
-
if (!
|
|
6043
|
+
const skillsDir = path17.join(".claude", "skills");
|
|
6044
|
+
if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
|
|
6327
6045
|
for (const skill of docs.claudeSkills) {
|
|
6328
|
-
const filePath =
|
|
6329
|
-
|
|
6046
|
+
const filePath = path17.join(skillsDir, skill.filename);
|
|
6047
|
+
fs21.writeFileSync(filePath, skill.content);
|
|
6330
6048
|
written.push(filePath);
|
|
6331
6049
|
}
|
|
6332
6050
|
}
|
|
@@ -6401,11 +6119,11 @@ function log(quiet, ...args) {
|
|
|
6401
6119
|
function discoverGitRepos(parentDir) {
|
|
6402
6120
|
const repos = [];
|
|
6403
6121
|
try {
|
|
6404
|
-
const entries =
|
|
6122
|
+
const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
|
|
6405
6123
|
for (const entry of entries) {
|
|
6406
6124
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
6407
|
-
const childPath =
|
|
6408
|
-
if (
|
|
6125
|
+
const childPath = path18.join(parentDir, entry.name);
|
|
6126
|
+
if (fs22.existsSync(path18.join(childPath, ".git"))) {
|
|
6409
6127
|
repos.push(childPath);
|
|
6410
6128
|
}
|
|
6411
6129
|
}
|
|
@@ -6500,7 +6218,7 @@ async function refreshCommand(options) {
|
|
|
6500
6218
|
`));
|
|
6501
6219
|
const originalDir = process.cwd();
|
|
6502
6220
|
for (const repo of repos) {
|
|
6503
|
-
const repoName =
|
|
6221
|
+
const repoName = path18.basename(repo);
|
|
6504
6222
|
try {
|
|
6505
6223
|
process.chdir(repo);
|
|
6506
6224
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -6747,8 +6465,8 @@ function readStdin() {
|
|
|
6747
6465
|
|
|
6748
6466
|
// src/learner/storage.ts
|
|
6749
6467
|
init_constants();
|
|
6750
|
-
import
|
|
6751
|
-
import
|
|
6468
|
+
import fs23 from "fs";
|
|
6469
|
+
import path19 from "path";
|
|
6752
6470
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
6753
6471
|
var DEFAULT_STATE = {
|
|
6754
6472
|
sessionId: null,
|
|
@@ -6756,15 +6474,15 @@ var DEFAULT_STATE = {
|
|
|
6756
6474
|
lastAnalysisTimestamp: null
|
|
6757
6475
|
};
|
|
6758
6476
|
function ensureLearningDir() {
|
|
6759
|
-
if (!
|
|
6760
|
-
|
|
6477
|
+
if (!fs23.existsSync(LEARNING_DIR)) {
|
|
6478
|
+
fs23.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6761
6479
|
}
|
|
6762
6480
|
}
|
|
6763
6481
|
function sessionFilePath() {
|
|
6764
|
-
return
|
|
6482
|
+
return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6765
6483
|
}
|
|
6766
6484
|
function stateFilePath() {
|
|
6767
|
-
return
|
|
6485
|
+
return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6768
6486
|
}
|
|
6769
6487
|
function truncateResponse(response) {
|
|
6770
6488
|
const str = JSON.stringify(response);
|
|
@@ -6775,50 +6493,50 @@ function appendEvent(event) {
|
|
|
6775
6493
|
ensureLearningDir();
|
|
6776
6494
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6777
6495
|
const filePath = sessionFilePath();
|
|
6778
|
-
|
|
6496
|
+
fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6779
6497
|
const count = getEventCount();
|
|
6780
6498
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6781
|
-
const lines =
|
|
6499
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6782
6500
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6783
|
-
|
|
6501
|
+
fs23.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6784
6502
|
}
|
|
6785
6503
|
}
|
|
6786
6504
|
function readAllEvents() {
|
|
6787
6505
|
const filePath = sessionFilePath();
|
|
6788
|
-
if (!
|
|
6789
|
-
const lines =
|
|
6506
|
+
if (!fs23.existsSync(filePath)) return [];
|
|
6507
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6790
6508
|
return lines.map((line) => JSON.parse(line));
|
|
6791
6509
|
}
|
|
6792
6510
|
function getEventCount() {
|
|
6793
6511
|
const filePath = sessionFilePath();
|
|
6794
|
-
if (!
|
|
6795
|
-
const content =
|
|
6512
|
+
if (!fs23.existsSync(filePath)) return 0;
|
|
6513
|
+
const content = fs23.readFileSync(filePath, "utf-8");
|
|
6796
6514
|
return content.split("\n").filter(Boolean).length;
|
|
6797
6515
|
}
|
|
6798
6516
|
function clearSession() {
|
|
6799
6517
|
const filePath = sessionFilePath();
|
|
6800
|
-
if (
|
|
6518
|
+
if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
|
|
6801
6519
|
}
|
|
6802
6520
|
function readState2() {
|
|
6803
6521
|
const filePath = stateFilePath();
|
|
6804
|
-
if (!
|
|
6522
|
+
if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6805
6523
|
try {
|
|
6806
|
-
return JSON.parse(
|
|
6524
|
+
return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
6807
6525
|
} catch {
|
|
6808
6526
|
return { ...DEFAULT_STATE };
|
|
6809
6527
|
}
|
|
6810
6528
|
}
|
|
6811
6529
|
function writeState2(state) {
|
|
6812
6530
|
ensureLearningDir();
|
|
6813
|
-
|
|
6531
|
+
fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6814
6532
|
}
|
|
6815
6533
|
function resetState() {
|
|
6816
6534
|
writeState2({ ...DEFAULT_STATE });
|
|
6817
6535
|
}
|
|
6818
6536
|
|
|
6819
6537
|
// src/learner/writer.ts
|
|
6820
|
-
import
|
|
6821
|
-
import
|
|
6538
|
+
import fs24 from "fs";
|
|
6539
|
+
import path20 from "path";
|
|
6822
6540
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6823
6541
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6824
6542
|
function writeLearnedContent(update) {
|
|
@@ -6838,8 +6556,8 @@ function writeLearnedContent(update) {
|
|
|
6838
6556
|
function writeLearnedSection(content) {
|
|
6839
6557
|
const claudeMdPath = "CLAUDE.md";
|
|
6840
6558
|
let existing = "";
|
|
6841
|
-
if (
|
|
6842
|
-
existing =
|
|
6559
|
+
if (fs24.existsSync(claudeMdPath)) {
|
|
6560
|
+
existing = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
6843
6561
|
}
|
|
6844
6562
|
const section = `${LEARNED_START}
|
|
6845
6563
|
${content}
|
|
@@ -6853,15 +6571,15 @@ ${LEARNED_END}`;
|
|
|
6853
6571
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6854
6572
|
updated = existing + separator + "\n" + section + "\n";
|
|
6855
6573
|
}
|
|
6856
|
-
|
|
6574
|
+
fs24.writeFileSync(claudeMdPath, updated);
|
|
6857
6575
|
}
|
|
6858
6576
|
function writeLearnedSkill(skill) {
|
|
6859
|
-
const skillDir =
|
|
6860
|
-
if (!
|
|
6861
|
-
const skillPath =
|
|
6862
|
-
if (!skill.isNew &&
|
|
6863
|
-
const existing =
|
|
6864
|
-
|
|
6577
|
+
const skillDir = path20.join(".claude", "skills", skill.name);
|
|
6578
|
+
if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
|
|
6579
|
+
const skillPath = path20.join(skillDir, "SKILL.md");
|
|
6580
|
+
if (!skill.isNew && fs24.existsSync(skillPath)) {
|
|
6581
|
+
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
6582
|
+
fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6865
6583
|
} else {
|
|
6866
6584
|
const frontmatter = [
|
|
6867
6585
|
"---",
|
|
@@ -6870,14 +6588,14 @@ function writeLearnedSkill(skill) {
|
|
|
6870
6588
|
"---",
|
|
6871
6589
|
""
|
|
6872
6590
|
].join("\n");
|
|
6873
|
-
|
|
6591
|
+
fs24.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6874
6592
|
}
|
|
6875
6593
|
return skillPath;
|
|
6876
6594
|
}
|
|
6877
6595
|
function readLearnedSection() {
|
|
6878
6596
|
const claudeMdPath = "CLAUDE.md";
|
|
6879
|
-
if (!
|
|
6880
|
-
const content =
|
|
6597
|
+
if (!fs24.existsSync(claudeMdPath)) return null;
|
|
6598
|
+
const content = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
6881
6599
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6882
6600
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6883
6601
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -7061,9 +6779,9 @@ Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
|
7061
6779
|
}
|
|
7062
6780
|
|
|
7063
6781
|
// src/cli.ts
|
|
7064
|
-
var __dirname =
|
|
6782
|
+
var __dirname = path21.dirname(fileURLToPath(import.meta.url));
|
|
7065
6783
|
var pkg = JSON.parse(
|
|
7066
|
-
|
|
6784
|
+
fs25.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
7067
6785
|
);
|
|
7068
6786
|
var program = new Command();
|
|
7069
6787
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -7085,22 +6803,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
7085
6803
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
7086
6804
|
|
|
7087
6805
|
// src/utils/version-check.ts
|
|
7088
|
-
import
|
|
7089
|
-
import
|
|
6806
|
+
import fs26 from "fs";
|
|
6807
|
+
import path22 from "path";
|
|
7090
6808
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7091
6809
|
import { execSync as execSync9 } from "child_process";
|
|
7092
6810
|
import chalk15 from "chalk";
|
|
7093
6811
|
import ora7 from "ora";
|
|
7094
6812
|
import confirm2 from "@inquirer/confirm";
|
|
7095
|
-
var __dirname_vc =
|
|
6813
|
+
var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
|
|
7096
6814
|
var pkg2 = JSON.parse(
|
|
7097
|
-
|
|
6815
|
+
fs26.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
7098
6816
|
);
|
|
7099
6817
|
function getInstalledVersion() {
|
|
7100
6818
|
try {
|
|
7101
6819
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7102
|
-
const pkgPath =
|
|
7103
|
-
return JSON.parse(
|
|
6820
|
+
const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6821
|
+
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
7104
6822
|
} catch {
|
|
7105
6823
|
return null;
|
|
7106
6824
|
}
|