@rely-ai/caliber 1.31.0-dev.1774741028 → 1.31.0-dev.1774743306
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 +721 -585
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -9,155 +9,6 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/llm/config.ts
|
|
13
|
-
var config_exports = {};
|
|
14
|
-
__export(config_exports, {
|
|
15
|
-
DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
|
|
16
|
-
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
17
|
-
MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
|
|
18
|
-
getConfigFilePath: () => getConfigFilePath,
|
|
19
|
-
getDisplayModel: () => getDisplayModel,
|
|
20
|
-
getFastModel: () => getFastModel,
|
|
21
|
-
getMaxPromptTokens: () => getMaxPromptTokens,
|
|
22
|
-
loadConfig: () => loadConfig,
|
|
23
|
-
readConfigFile: () => readConfigFile,
|
|
24
|
-
resolveFromEnv: () => resolveFromEnv,
|
|
25
|
-
writeConfigFile: () => writeConfigFile
|
|
26
|
-
});
|
|
27
|
-
import fs4 from "fs";
|
|
28
|
-
import path5 from "path";
|
|
29
|
-
import os2 from "os";
|
|
30
|
-
function getMaxPromptTokens() {
|
|
31
|
-
const config = loadConfig();
|
|
32
|
-
const model = process.env.CALIBER_MODEL || config?.model;
|
|
33
|
-
const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
|
|
34
|
-
const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
|
|
35
|
-
return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
|
|
36
|
-
}
|
|
37
|
-
function loadConfig() {
|
|
38
|
-
const envConfig = resolveFromEnv();
|
|
39
|
-
if (envConfig) return envConfig;
|
|
40
|
-
return readConfigFile();
|
|
41
|
-
}
|
|
42
|
-
function resolveFromEnv() {
|
|
43
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
44
|
-
return {
|
|
45
|
-
provider: "anthropic",
|
|
46
|
-
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
47
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
|
|
51
|
-
return {
|
|
52
|
-
provider: "vertex",
|
|
53
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
|
|
54
|
-
vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
|
|
55
|
-
vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
|
|
56
|
-
vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
if (process.env.OPENAI_API_KEY) {
|
|
60
|
-
return {
|
|
61
|
-
provider: "openai",
|
|
62
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
63
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
|
|
64
|
-
baseUrl: process.env.OPENAI_BASE_URL
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
|
|
68
|
-
return {
|
|
69
|
-
provider: "cursor",
|
|
70
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
|
|
74
|
-
return {
|
|
75
|
-
provider: "claude-cli",
|
|
76
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
function readConfigFile() {
|
|
82
|
-
try {
|
|
83
|
-
if (!fs4.existsSync(CONFIG_FILE)) return null;
|
|
84
|
-
const raw = fs4.readFileSync(CONFIG_FILE, "utf-8");
|
|
85
|
-
const parsed = JSON.parse(raw);
|
|
86
|
-
if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
return parsed;
|
|
90
|
-
} catch {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
function writeConfigFile(config) {
|
|
95
|
-
if (!fs4.existsSync(CONFIG_DIR)) {
|
|
96
|
-
fs4.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
97
|
-
}
|
|
98
|
-
const sanitized = { ...config };
|
|
99
|
-
if (sanitized.apiKey) {
|
|
100
|
-
sanitized.apiKey = sanitized.apiKey.trim();
|
|
101
|
-
}
|
|
102
|
-
fs4.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
|
|
103
|
-
}
|
|
104
|
-
function getConfigFilePath() {
|
|
105
|
-
return CONFIG_FILE;
|
|
106
|
-
}
|
|
107
|
-
function getDisplayModel(config) {
|
|
108
|
-
if (config.model === "default" && config.provider === "claude-cli") {
|
|
109
|
-
return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
|
|
110
|
-
}
|
|
111
|
-
return config.model;
|
|
112
|
-
}
|
|
113
|
-
function getFastModel() {
|
|
114
|
-
if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
|
|
115
|
-
const config = loadConfig();
|
|
116
|
-
const provider = config?.provider;
|
|
117
|
-
if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
|
|
118
|
-
return process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
119
|
-
}
|
|
120
|
-
if (config?.fastModel) return config.fastModel;
|
|
121
|
-
if (provider) return DEFAULT_FAST_MODELS[provider];
|
|
122
|
-
return void 0;
|
|
123
|
-
}
|
|
124
|
-
var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, MODEL_CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, INPUT_BUDGET_FRACTION, MAX_PROMPT_TOKENS_CAP, MIN_PROMPT_TOKENS, DEFAULT_FAST_MODELS;
|
|
125
|
-
var init_config = __esm({
|
|
126
|
-
"src/llm/config.ts"() {
|
|
127
|
-
"use strict";
|
|
128
|
-
CONFIG_DIR = path5.join(os2.homedir(), ".caliber");
|
|
129
|
-
CONFIG_FILE = path5.join(CONFIG_DIR, "config.json");
|
|
130
|
-
DEFAULT_MODELS = {
|
|
131
|
-
anthropic: "claude-sonnet-4-6",
|
|
132
|
-
vertex: "claude-sonnet-4-6",
|
|
133
|
-
openai: "gpt-4.1",
|
|
134
|
-
cursor: "sonnet-4.6",
|
|
135
|
-
"claude-cli": "default"
|
|
136
|
-
};
|
|
137
|
-
MODEL_CONTEXT_WINDOWS = {
|
|
138
|
-
"claude-sonnet-4-6": 2e5,
|
|
139
|
-
"claude-opus-4-6": 2e5,
|
|
140
|
-
"claude-haiku-4-5-20251001": 2e5,
|
|
141
|
-
"claude-sonnet-4-5-20250514": 2e5,
|
|
142
|
-
"gpt-4.1": 1e6,
|
|
143
|
-
"gpt-4.1-mini": 1e6,
|
|
144
|
-
"gpt-4o": 128e3,
|
|
145
|
-
"gpt-4o-mini": 128e3,
|
|
146
|
-
"sonnet-4.6": 2e5
|
|
147
|
-
};
|
|
148
|
-
DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
149
|
-
INPUT_BUDGET_FRACTION = 0.6;
|
|
150
|
-
MAX_PROMPT_TOKENS_CAP = 3e5;
|
|
151
|
-
MIN_PROMPT_TOKENS = 3e4;
|
|
152
|
-
DEFAULT_FAST_MODELS = {
|
|
153
|
-
anthropic: "claude-haiku-4-5-20251001",
|
|
154
|
-
vertex: "claude-haiku-4-5-20251001",
|
|
155
|
-
openai: "gpt-4.1-mini",
|
|
156
|
-
cursor: "gpt-5.3-codex-fast"
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
12
|
// src/lib/resolve-caliber.ts
|
|
162
13
|
var resolve_caliber_exports = {};
|
|
163
14
|
__export(resolve_caliber_exports, {
|
|
@@ -166,8 +17,8 @@ __export(resolve_caliber_exports, {
|
|
|
166
17
|
resetResolvedCaliber: () => resetResolvedCaliber,
|
|
167
18
|
resolveCaliber: () => resolveCaliber
|
|
168
19
|
});
|
|
169
|
-
import
|
|
170
|
-
import { execSync as
|
|
20
|
+
import fs2 from "fs";
|
|
21
|
+
import { execSync as execSync3 } from "child_process";
|
|
171
22
|
function resolveCaliber() {
|
|
172
23
|
if (_resolved) return _resolved;
|
|
173
24
|
const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
|
|
@@ -177,7 +28,7 @@ function resolveCaliber() {
|
|
|
177
28
|
}
|
|
178
29
|
try {
|
|
179
30
|
const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
|
|
180
|
-
|
|
31
|
+
execSync3(whichCmd, {
|
|
181
32
|
encoding: "utf-8",
|
|
182
33
|
stdio: ["pipe", "pipe", "pipe"]
|
|
183
34
|
});
|
|
@@ -186,7 +37,7 @@ function resolveCaliber() {
|
|
|
186
37
|
} catch {
|
|
187
38
|
}
|
|
188
39
|
const binPath = process.argv[1];
|
|
189
|
-
if (binPath && /caliber/.test(binPath) &&
|
|
40
|
+
if (binPath && /caliber/.test(binPath) && fs2.existsSync(binPath)) {
|
|
190
41
|
_resolved = binPath;
|
|
191
42
|
return _resolved;
|
|
192
43
|
}
|
|
@@ -214,156 +65,6 @@ var init_resolve_caliber = __esm({
|
|
|
214
65
|
}
|
|
215
66
|
});
|
|
216
67
|
|
|
217
|
-
// src/llm/types.ts
|
|
218
|
-
var types_exports = {};
|
|
219
|
-
__export(types_exports, {
|
|
220
|
-
isSeatBased: () => isSeatBased
|
|
221
|
-
});
|
|
222
|
-
function isSeatBased(provider) {
|
|
223
|
-
return SEAT_BASED_PROVIDERS.has(provider);
|
|
224
|
-
}
|
|
225
|
-
var SEAT_BASED_PROVIDERS;
|
|
226
|
-
var init_types = __esm({
|
|
227
|
-
"src/llm/types.ts"() {
|
|
228
|
-
"use strict";
|
|
229
|
-
SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// src/writers/pre-commit-block.ts
|
|
234
|
-
var pre_commit_block_exports = {};
|
|
235
|
-
__export(pre_commit_block_exports, {
|
|
236
|
-
appendLearningsBlock: () => appendLearningsBlock,
|
|
237
|
-
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
238
|
-
appendSyncBlock: () => appendSyncBlock,
|
|
239
|
-
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
240
|
-
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
241
|
-
getCursorSyncRule: () => getCursorSyncRule,
|
|
242
|
-
hasLearningsBlock: () => hasLearningsBlock,
|
|
243
|
-
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
244
|
-
hasSyncBlock: () => hasSyncBlock
|
|
245
|
-
});
|
|
246
|
-
function getPreCommitBlock() {
|
|
247
|
-
const bin = resolveCaliber();
|
|
248
|
-
return `${BLOCK_START}
|
|
249
|
-
## Before Committing
|
|
250
|
-
|
|
251
|
-
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
252
|
-
|
|
253
|
-
First, check if the pre-commit hook is already installed:
|
|
254
|
-
\`\`\`bash
|
|
255
|
-
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
256
|
-
\`\`\`
|
|
257
|
-
|
|
258
|
-
- If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
|
|
259
|
-
- If **no-hook**: run Caliber manually before committing:
|
|
260
|
-
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
261
|
-
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
262
|
-
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
263
|
-
|
|
264
|
-
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."
|
|
265
|
-
${BLOCK_END}`;
|
|
266
|
-
}
|
|
267
|
-
function getCursorRuleContent() {
|
|
268
|
-
const bin = resolveCaliber();
|
|
269
|
-
return `---
|
|
270
|
-
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
271
|
-
alwaysApply: true
|
|
272
|
-
---
|
|
273
|
-
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
274
|
-
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
275
|
-
|
|
276
|
-
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
277
|
-
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
278
|
-
|
|
279
|
-
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
280
|
-
`;
|
|
281
|
-
}
|
|
282
|
-
function hasPreCommitBlock(content) {
|
|
283
|
-
return content.includes(BLOCK_START);
|
|
284
|
-
}
|
|
285
|
-
function appendPreCommitBlock(content) {
|
|
286
|
-
if (hasPreCommitBlock(content)) return content;
|
|
287
|
-
const trimmed = content.trimEnd();
|
|
288
|
-
return trimmed + "\n\n" + getPreCommitBlock() + "\n";
|
|
289
|
-
}
|
|
290
|
-
function getCursorPreCommitRule() {
|
|
291
|
-
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
292
|
-
}
|
|
293
|
-
function hasLearningsBlock(content) {
|
|
294
|
-
return content.includes(LEARNINGS_BLOCK_START);
|
|
295
|
-
}
|
|
296
|
-
function appendLearningsBlock(content) {
|
|
297
|
-
if (hasLearningsBlock(content)) return content;
|
|
298
|
-
const trimmed = content.trimEnd();
|
|
299
|
-
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
300
|
-
}
|
|
301
|
-
function getCursorLearningsRule() {
|
|
302
|
-
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
303
|
-
}
|
|
304
|
-
function getSyncBlock() {
|
|
305
|
-
const bin = resolveCaliber();
|
|
306
|
-
return `${SYNC_BLOCK_START}
|
|
307
|
-
## Context Sync
|
|
308
|
-
|
|
309
|
-
This project uses [Caliber](https://github.com/caliber-ai-org/ai-setup) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
310
|
-
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
311
|
-
If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
|
|
312
|
-
${SYNC_BLOCK_END}`;
|
|
313
|
-
}
|
|
314
|
-
function hasSyncBlock(content) {
|
|
315
|
-
return content.includes(SYNC_BLOCK_START);
|
|
316
|
-
}
|
|
317
|
-
function appendSyncBlock(content) {
|
|
318
|
-
if (hasSyncBlock(content)) return content;
|
|
319
|
-
const trimmed = content.trimEnd();
|
|
320
|
-
return trimmed + "\n\n" + getSyncBlock() + "\n";
|
|
321
|
-
}
|
|
322
|
-
function getCursorSyncContent() {
|
|
323
|
-
const bin = resolveCaliber();
|
|
324
|
-
return `---
|
|
325
|
-
description: This project uses Caliber for automatic AI agent context sync
|
|
326
|
-
alwaysApply: true
|
|
327
|
-
---
|
|
328
|
-
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
329
|
-
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
330
|
-
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
331
|
-
`;
|
|
332
|
-
}
|
|
333
|
-
function getCursorSyncRule() {
|
|
334
|
-
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
335
|
-
}
|
|
336
|
-
var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME;
|
|
337
|
-
var init_pre_commit_block = __esm({
|
|
338
|
-
"src/writers/pre-commit-block.ts"() {
|
|
339
|
-
"use strict";
|
|
340
|
-
init_resolve_caliber();
|
|
341
|
-
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
342
|
-
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
343
|
-
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
344
|
-
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
345
|
-
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
346
|
-
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
347
|
-
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
348
|
-
## Session Learnings
|
|
349
|
-
|
|
350
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
351
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
352
|
-
${LEARNINGS_BLOCK_END}`;
|
|
353
|
-
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
354
|
-
CURSOR_LEARNINGS_CONTENT = `---
|
|
355
|
-
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
356
|
-
alwaysApply: true
|
|
357
|
-
---
|
|
358
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
359
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
360
|
-
`;
|
|
361
|
-
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
362
|
-
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
363
|
-
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
|
|
367
68
|
// src/lib/builtin-skills.ts
|
|
368
69
|
var builtin_skills_exports = {};
|
|
369
70
|
__export(builtin_skills_exports, {
|
|
@@ -375,8 +76,8 @@ __export(builtin_skills_exports, {
|
|
|
375
76
|
buildSkillContent: () => buildSkillContent,
|
|
376
77
|
ensureBuiltinSkills: () => ensureBuiltinSkills
|
|
377
78
|
});
|
|
378
|
-
import
|
|
379
|
-
import
|
|
79
|
+
import fs3 from "fs";
|
|
80
|
+
import path3 from "path";
|
|
380
81
|
function buildSkillContent(skill) {
|
|
381
82
|
const frontmatter = `---
|
|
382
83
|
name: ${skill.name}
|
|
@@ -541,20 +242,37 @@ grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "HOOK_ACTIVE" || ech
|
|
|
541
242
|
caliber hooks --install
|
|
542
243
|
\`\`\`
|
|
543
244
|
|
|
544
|
-
### Step 3:
|
|
245
|
+
### Step 3: Detect agents and check if configs exist
|
|
246
|
+
|
|
247
|
+
First, detect which coding agents are configured in this project:
|
|
248
|
+
\`\`\`bash
|
|
249
|
+
AGENTS=""
|
|
250
|
+
[ -d .claude ] && AGENTS="claude"
|
|
251
|
+
[ -d .cursor ] && AGENTS="\${AGENTS:+$AGENTS,}cursor"
|
|
252
|
+
[ -d .agents ] || [ -f AGENTS.md ] && AGENTS="\${AGENTS:+$AGENTS,}codex"
|
|
253
|
+
[ -f .github/copilot-instructions.md ] && AGENTS="\${AGENTS:+$AGENTS,}github-copilot"
|
|
254
|
+
echo "DETECTED_AGENTS=\${AGENTS:-none}"
|
|
255
|
+
\`\`\`
|
|
545
256
|
|
|
257
|
+
If no agents are detected, ask the user which coding agents they use (Claude Code, Cursor, Codex, GitHub Copilot).
|
|
258
|
+
Build the agent list from their answer as a comma-separated string (e.g. "claude,cursor").
|
|
259
|
+
|
|
260
|
+
Then check if agent configs exist:
|
|
546
261
|
\`\`\`bash
|
|
547
262
|
echo "CLAUDE_MD=$([ -f CLAUDE.md ] && echo exists || echo missing)"
|
|
548
263
|
echo "CURSOR_RULES=$([ -d .cursor/rules ] && ls .cursor/rules/*.mdc 2>/dev/null | wc -l | tr -d ' ' || echo 0)"
|
|
549
264
|
echo "AGENTS_MD=$([ -f AGENTS.md ] && echo exists || echo missing)"
|
|
265
|
+
echo "COPILOT=$([ -f .github/copilot-instructions.md ] && echo exists || echo missing)"
|
|
550
266
|
\`\`\`
|
|
551
267
|
|
|
552
|
-
- If configs exist \u2192 Tell the user which configs are present. Move to Step 4.
|
|
268
|
+
- If configs exist for the detected agents \u2192 Tell the user which configs are present. Move to Step 4.
|
|
553
269
|
- If configs are missing \u2192 Tell the user: "No agent configs found. I'll generate them now."
|
|
270
|
+
Use the detected or user-selected agent list:
|
|
554
271
|
\`\`\`bash
|
|
555
|
-
caliber init --auto-approve
|
|
272
|
+
caliber init --auto-approve --agent <comma-separated-agents>
|
|
556
273
|
\`\`\`
|
|
557
|
-
|
|
274
|
+
For example: \`caliber init --auto-approve --agent claude,cursor\`
|
|
275
|
+
This generates CLAUDE.md, Cursor rules, AGENTS.md, skills, and sync infrastructure for the specified agents.
|
|
558
276
|
|
|
559
277
|
### Step 4: Check if configs are fresh
|
|
560
278
|
|
|
@@ -688,52 +406,402 @@ From now on, every commit keeps all your agent configs in sync automatically.
|
|
|
688
406
|
- The user is in the middle of time-sensitive work
|
|
689
407
|
`;
|
|
690
408
|
}
|
|
691
|
-
function ensureBuiltinSkills() {
|
|
692
|
-
const written = [];
|
|
693
|
-
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
694
|
-
if (!
|
|
695
|
-
for (const skill of BUILTIN_SKILLS) {
|
|
696
|
-
const skillPath =
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
written.push(skillPath);
|
|
409
|
+
function ensureBuiltinSkills() {
|
|
410
|
+
const written = [];
|
|
411
|
+
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
412
|
+
if (!fs3.existsSync(platformDir)) continue;
|
|
413
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
414
|
+
const skillPath = path3.join(skillsDir, skill.name, "SKILL.md");
|
|
415
|
+
fs3.mkdirSync(path3.dirname(skillPath), { recursive: true });
|
|
416
|
+
fs3.writeFileSync(skillPath, buildSkillContent(skill));
|
|
417
|
+
written.push(skillPath);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return written;
|
|
421
|
+
}
|
|
422
|
+
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
|
|
423
|
+
var init_builtin_skills = __esm({
|
|
424
|
+
"src/lib/builtin-skills.ts"() {
|
|
425
|
+
"use strict";
|
|
426
|
+
init_resolve_caliber();
|
|
427
|
+
FIND_SKILLS_SKILL = {
|
|
428
|
+
name: "find-skills",
|
|
429
|
+
description: "Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.",
|
|
430
|
+
get content() {
|
|
431
|
+
return getFindSkillsContent();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
SAVE_LEARNING_SKILL = {
|
|
435
|
+
name: "save-learning",
|
|
436
|
+
description: "Saves user instructions as persistent learnings for future sessions. Use when the user says 'remember this', 'always do X', 'from now on', 'never do Y', or gives any instruction they want persisted across sessions. Proactively suggest when the user states a preference, convention, or rule they clearly want followed in the future.",
|
|
437
|
+
get content() {
|
|
438
|
+
return getSaveLearningContent();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
SETUP_CALIBER_SKILL = {
|
|
442
|
+
name: "setup-caliber",
|
|
443
|
+
description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
|
|
444
|
+
get content() {
|
|
445
|
+
return getSetupCaliberContent();
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
449
|
+
PLATFORM_CONFIGS = [
|
|
450
|
+
{ platformDir: ".claude", skillsDir: path3.join(".claude", "skills") },
|
|
451
|
+
{ platformDir: ".cursor", skillsDir: path3.join(".cursor", "skills") },
|
|
452
|
+
{ platformDir: ".agents", skillsDir: path3.join(".agents", "skills") }
|
|
453
|
+
];
|
|
454
|
+
BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// src/writers/pre-commit-block.ts
|
|
459
|
+
var pre_commit_block_exports = {};
|
|
460
|
+
__export(pre_commit_block_exports, {
|
|
461
|
+
appendLearningsBlock: () => appendLearningsBlock,
|
|
462
|
+
appendManagedBlocks: () => appendManagedBlocks,
|
|
463
|
+
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
464
|
+
appendSyncBlock: () => appendSyncBlock,
|
|
465
|
+
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
466
|
+
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
467
|
+
getCursorSetupRule: () => getCursorSetupRule,
|
|
468
|
+
getCursorSyncRule: () => getCursorSyncRule,
|
|
469
|
+
hasLearningsBlock: () => hasLearningsBlock,
|
|
470
|
+
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
471
|
+
hasSyncBlock: () => hasSyncBlock
|
|
472
|
+
});
|
|
473
|
+
function getCaliberNotFoundFallback(platform, bin) {
|
|
474
|
+
switch (platform) {
|
|
475
|
+
case "claude":
|
|
476
|
+
return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
|
|
477
|
+
case "codex":
|
|
478
|
+
return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
|
|
479
|
+
case "copilot":
|
|
480
|
+
return `If \`${bin}\` is not found, install it by running \`npm install -g @rely-ai/caliber\`, then run \`caliber hooks --install\` and \`caliber refresh\`.`;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function getPreCommitBlock(platform = "claude") {
|
|
484
|
+
const bin = resolveCaliber();
|
|
485
|
+
return `${BLOCK_START}
|
|
486
|
+
## Before Committing
|
|
487
|
+
|
|
488
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
489
|
+
|
|
490
|
+
First, check if the pre-commit hook is already installed:
|
|
491
|
+
\`\`\`bash
|
|
492
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
493
|
+
\`\`\`
|
|
494
|
+
|
|
495
|
+
- If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
|
|
496
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
497
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
498
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
499
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
500
|
+
|
|
501
|
+
${getCaliberNotFoundFallback(platform, bin)}
|
|
502
|
+
${BLOCK_END}`;
|
|
503
|
+
}
|
|
504
|
+
function getCursorRuleContent() {
|
|
505
|
+
const bin = resolveCaliber();
|
|
506
|
+
return `---
|
|
507
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
508
|
+
alwaysApply: true
|
|
509
|
+
---
|
|
510
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
511
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
512
|
+
|
|
513
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
514
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
515
|
+
|
|
516
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
517
|
+
`;
|
|
518
|
+
}
|
|
519
|
+
function hasPreCommitBlock(content) {
|
|
520
|
+
return content.includes(BLOCK_START);
|
|
521
|
+
}
|
|
522
|
+
function appendPreCommitBlock(content, platform = "claude") {
|
|
523
|
+
if (hasPreCommitBlock(content)) return content;
|
|
524
|
+
const trimmed = content.trimEnd();
|
|
525
|
+
return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
|
|
526
|
+
}
|
|
527
|
+
function getCursorPreCommitRule() {
|
|
528
|
+
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
529
|
+
}
|
|
530
|
+
function hasLearningsBlock(content) {
|
|
531
|
+
return content.includes(LEARNINGS_BLOCK_START);
|
|
532
|
+
}
|
|
533
|
+
function appendLearningsBlock(content) {
|
|
534
|
+
if (hasLearningsBlock(content)) return content;
|
|
535
|
+
const trimmed = content.trimEnd();
|
|
536
|
+
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
537
|
+
}
|
|
538
|
+
function getCursorLearningsRule() {
|
|
539
|
+
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
540
|
+
}
|
|
541
|
+
function getSyncSetupInstruction(platform, bin) {
|
|
542
|
+
switch (platform) {
|
|
543
|
+
case "claude":
|
|
544
|
+
return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
|
|
545
|
+
case "codex":
|
|
546
|
+
return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
|
|
547
|
+
case "copilot":
|
|
548
|
+
return `If the pre-commit hook is not set up, install it by running:
|
|
549
|
+
\`\`\`bash
|
|
550
|
+
npm install -g @rely-ai/caliber
|
|
551
|
+
caliber hooks --install
|
|
552
|
+
caliber refresh
|
|
553
|
+
git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
554
|
+
\`\`\``;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function getSyncBlock(platform = "claude") {
|
|
558
|
+
const bin = resolveCaliber();
|
|
559
|
+
return `${SYNC_BLOCK_START}
|
|
560
|
+
## Context Sync
|
|
561
|
+
|
|
562
|
+
This project uses [Caliber](https://github.com/caliber-ai-org/ai-setup) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
563
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
564
|
+
${getSyncSetupInstruction(platform, bin)}
|
|
565
|
+
${SYNC_BLOCK_END}`;
|
|
566
|
+
}
|
|
567
|
+
function hasSyncBlock(content) {
|
|
568
|
+
return content.includes(SYNC_BLOCK_START);
|
|
569
|
+
}
|
|
570
|
+
function appendSyncBlock(content, platform = "claude") {
|
|
571
|
+
if (hasSyncBlock(content)) return content;
|
|
572
|
+
const trimmed = content.trimEnd();
|
|
573
|
+
return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
|
|
574
|
+
}
|
|
575
|
+
function appendManagedBlocks(content, platform = "claude") {
|
|
576
|
+
return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
|
|
577
|
+
}
|
|
578
|
+
function getCursorSyncContent() {
|
|
579
|
+
const bin = resolveCaliber();
|
|
580
|
+
return `---
|
|
581
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
582
|
+
alwaysApply: true
|
|
583
|
+
---
|
|
584
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
585
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
586
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
function getCursorSyncRule() {
|
|
590
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
591
|
+
}
|
|
592
|
+
function getCursorSetupContent() {
|
|
593
|
+
return `---
|
|
594
|
+
description: Check if Caliber agent config sync is set up and offer installation on new sessions
|
|
595
|
+
alwaysApply: true
|
|
596
|
+
---
|
|
597
|
+
This project uses Caliber to keep AI agent configs in sync automatically.
|
|
598
|
+
At the start of each session, silently check if sync is active:
|
|
599
|
+
|
|
600
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
|
|
601
|
+
|
|
602
|
+
If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
|
|
603
|
+
If NO_SYNC: Tell the user: "This project uses Caliber for automatic agent config sync, but it's not set up on your machine yet. Want me to set it up? It takes about 30 seconds."
|
|
604
|
+
|
|
605
|
+
If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
|
|
606
|
+
`;
|
|
607
|
+
}
|
|
608
|
+
function getCursorSetupRule() {
|
|
609
|
+
return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
|
|
610
|
+
}
|
|
611
|
+
var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME, CURSOR_SETUP_FILENAME;
|
|
612
|
+
var init_pre_commit_block = __esm({
|
|
613
|
+
"src/writers/pre-commit-block.ts"() {
|
|
614
|
+
"use strict";
|
|
615
|
+
init_resolve_caliber();
|
|
616
|
+
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
617
|
+
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
618
|
+
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
619
|
+
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
620
|
+
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
621
|
+
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
622
|
+
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
623
|
+
## Session Learnings
|
|
624
|
+
|
|
625
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
626
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
627
|
+
${LEARNINGS_BLOCK_END}`;
|
|
628
|
+
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
629
|
+
CURSOR_LEARNINGS_CONTENT = `---
|
|
630
|
+
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
631
|
+
alwaysApply: true
|
|
632
|
+
---
|
|
633
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
634
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
635
|
+
`;
|
|
636
|
+
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
637
|
+
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
638
|
+
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
639
|
+
CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// src/llm/config.ts
|
|
644
|
+
var config_exports = {};
|
|
645
|
+
__export(config_exports, {
|
|
646
|
+
DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
|
|
647
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
648
|
+
MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
|
|
649
|
+
getConfigFilePath: () => getConfigFilePath,
|
|
650
|
+
getDisplayModel: () => getDisplayModel,
|
|
651
|
+
getFastModel: () => getFastModel,
|
|
652
|
+
getMaxPromptTokens: () => getMaxPromptTokens,
|
|
653
|
+
loadConfig: () => loadConfig,
|
|
654
|
+
readConfigFile: () => readConfigFile,
|
|
655
|
+
resolveFromEnv: () => resolveFromEnv,
|
|
656
|
+
writeConfigFile: () => writeConfigFile
|
|
657
|
+
});
|
|
658
|
+
import fs6 from "fs";
|
|
659
|
+
import path6 from "path";
|
|
660
|
+
import os2 from "os";
|
|
661
|
+
function getMaxPromptTokens() {
|
|
662
|
+
const config = loadConfig();
|
|
663
|
+
const model = process.env.CALIBER_MODEL || config?.model;
|
|
664
|
+
const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
|
|
665
|
+
const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
|
|
666
|
+
return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
|
|
667
|
+
}
|
|
668
|
+
function loadConfig() {
|
|
669
|
+
const envConfig = resolveFromEnv();
|
|
670
|
+
if (envConfig) return envConfig;
|
|
671
|
+
return readConfigFile();
|
|
672
|
+
}
|
|
673
|
+
function resolveFromEnv() {
|
|
674
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
675
|
+
return {
|
|
676
|
+
provider: "anthropic",
|
|
677
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
678
|
+
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
|
|
682
|
+
return {
|
|
683
|
+
provider: "vertex",
|
|
684
|
+
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
|
|
685
|
+
vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
|
|
686
|
+
vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
|
|
687
|
+
vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
if (process.env.OPENAI_API_KEY) {
|
|
691
|
+
return {
|
|
692
|
+
provider: "openai",
|
|
693
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
694
|
+
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
|
|
695
|
+
baseUrl: process.env.OPENAI_BASE_URL
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
|
|
699
|
+
return {
|
|
700
|
+
provider: "cursor",
|
|
701
|
+
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
|
|
705
|
+
return {
|
|
706
|
+
provider: "claude-cli",
|
|
707
|
+
model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
function readConfigFile() {
|
|
713
|
+
try {
|
|
714
|
+
if (!fs6.existsSync(CONFIG_FILE)) return null;
|
|
715
|
+
const raw = fs6.readFileSync(CONFIG_FILE, "utf-8");
|
|
716
|
+
const parsed = JSON.parse(raw);
|
|
717
|
+
if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
|
|
718
|
+
return null;
|
|
700
719
|
}
|
|
720
|
+
return parsed;
|
|
721
|
+
} catch {
|
|
722
|
+
return null;
|
|
701
723
|
}
|
|
702
|
-
return written;
|
|
703
724
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
725
|
+
function writeConfigFile(config) {
|
|
726
|
+
if (!fs6.existsSync(CONFIG_DIR)) {
|
|
727
|
+
fs6.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
728
|
+
}
|
|
729
|
+
const sanitized = { ...config };
|
|
730
|
+
if (sanitized.apiKey) {
|
|
731
|
+
sanitized.apiKey = sanitized.apiKey.trim();
|
|
732
|
+
}
|
|
733
|
+
fs6.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
|
|
734
|
+
}
|
|
735
|
+
function getConfigFilePath() {
|
|
736
|
+
return CONFIG_FILE;
|
|
737
|
+
}
|
|
738
|
+
function getDisplayModel(config) {
|
|
739
|
+
if (config.model === "default" && config.provider === "claude-cli") {
|
|
740
|
+
return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
|
|
741
|
+
}
|
|
742
|
+
return config.model;
|
|
743
|
+
}
|
|
744
|
+
function getFastModel() {
|
|
745
|
+
if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
|
|
746
|
+
const config = loadConfig();
|
|
747
|
+
const provider = config?.provider;
|
|
748
|
+
if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
|
|
749
|
+
return process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
750
|
+
}
|
|
751
|
+
if (config?.fastModel) return config.fastModel;
|
|
752
|
+
if (provider) return DEFAULT_FAST_MODELS[provider];
|
|
753
|
+
return void 0;
|
|
754
|
+
}
|
|
755
|
+
var CONFIG_DIR, CONFIG_FILE, DEFAULT_MODELS, MODEL_CONTEXT_WINDOWS, DEFAULT_CONTEXT_WINDOW, INPUT_BUDGET_FRACTION, MAX_PROMPT_TOKENS_CAP, MIN_PROMPT_TOKENS, DEFAULT_FAST_MODELS;
|
|
756
|
+
var init_config = __esm({
|
|
757
|
+
"src/llm/config.ts"() {
|
|
707
758
|
"use strict";
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
759
|
+
CONFIG_DIR = path6.join(os2.homedir(), ".caliber");
|
|
760
|
+
CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
|
|
761
|
+
DEFAULT_MODELS = {
|
|
762
|
+
anthropic: "claude-sonnet-4-6",
|
|
763
|
+
vertex: "claude-sonnet-4-6",
|
|
764
|
+
openai: "gpt-4.1",
|
|
765
|
+
cursor: "sonnet-4.6",
|
|
766
|
+
"claude-cli": "default"
|
|
715
767
|
};
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
768
|
+
MODEL_CONTEXT_WINDOWS = {
|
|
769
|
+
"claude-sonnet-4-6": 2e5,
|
|
770
|
+
"claude-opus-4-6": 2e5,
|
|
771
|
+
"claude-haiku-4-5-20251001": 2e5,
|
|
772
|
+
"claude-sonnet-4-5-20250514": 2e5,
|
|
773
|
+
"gpt-4.1": 1e6,
|
|
774
|
+
"gpt-4.1-mini": 1e6,
|
|
775
|
+
"gpt-4o": 128e3,
|
|
776
|
+
"gpt-4o-mini": 128e3,
|
|
777
|
+
"sonnet-4.6": 2e5
|
|
722
778
|
};
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
779
|
+
DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
780
|
+
INPUT_BUDGET_FRACTION = 0.6;
|
|
781
|
+
MAX_PROMPT_TOKENS_CAP = 3e5;
|
|
782
|
+
MIN_PROMPT_TOKENS = 3e4;
|
|
783
|
+
DEFAULT_FAST_MODELS = {
|
|
784
|
+
anthropic: "claude-haiku-4-5-20251001",
|
|
785
|
+
vertex: "claude-haiku-4-5-20251001",
|
|
786
|
+
openai: "gpt-4.1-mini",
|
|
787
|
+
cursor: "gpt-5.3-codex-fast"
|
|
729
788
|
};
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
// src/llm/types.ts
|
|
793
|
+
var types_exports = {};
|
|
794
|
+
__export(types_exports, {
|
|
795
|
+
isSeatBased: () => isSeatBased
|
|
796
|
+
});
|
|
797
|
+
function isSeatBased(provider) {
|
|
798
|
+
return SEAT_BASED_PROVIDERS.has(provider);
|
|
799
|
+
}
|
|
800
|
+
var SEAT_BASED_PROVIDERS;
|
|
801
|
+
var init_types = __esm({
|
|
802
|
+
"src/llm/types.ts"() {
|
|
803
|
+
"use strict";
|
|
804
|
+
SEAT_BASED_PROVIDERS = /* @__PURE__ */ new Set(["cursor", "claude-cli"]);
|
|
737
805
|
}
|
|
738
806
|
});
|
|
739
807
|
|
|
@@ -1062,8 +1130,8 @@ import chalk14 from "chalk";
|
|
|
1062
1130
|
import fs33 from "fs";
|
|
1063
1131
|
|
|
1064
1132
|
// src/fingerprint/index.ts
|
|
1065
|
-
import
|
|
1066
|
-
import
|
|
1133
|
+
import fs9 from "fs";
|
|
1134
|
+
import path8 from "path";
|
|
1067
1135
|
|
|
1068
1136
|
// src/fingerprint/git.ts
|
|
1069
1137
|
import { execSync } from "child_process";
|
|
@@ -1171,8 +1239,8 @@ function scan(base, rel, depth, maxDepth, result) {
|
|
|
1171
1239
|
}
|
|
1172
1240
|
|
|
1173
1241
|
// src/fingerprint/existing-config.ts
|
|
1174
|
-
import
|
|
1175
|
-
import
|
|
1242
|
+
import fs4 from "fs";
|
|
1243
|
+
import path4 from "path";
|
|
1176
1244
|
|
|
1177
1245
|
// src/constants.ts
|
|
1178
1246
|
import path2 from "path";
|
|
@@ -1207,97 +1275,121 @@ var LEARNING_LAST_ERROR_FILE = "last-error.json";
|
|
|
1207
1275
|
var MIN_SESSIONS_FOR_COMPARISON = 3;
|
|
1208
1276
|
|
|
1209
1277
|
// src/fingerprint/existing-config.ts
|
|
1278
|
+
init_builtin_skills();
|
|
1279
|
+
init_pre_commit_block();
|
|
1280
|
+
var MANAGED_CURSOR_RULES = /* @__PURE__ */ new Set([
|
|
1281
|
+
getCursorPreCommitRule().filename,
|
|
1282
|
+
getCursorLearningsRule().filename,
|
|
1283
|
+
getCursorSyncRule().filename,
|
|
1284
|
+
getCursorSetupRule().filename
|
|
1285
|
+
]);
|
|
1210
1286
|
function readExistingConfigs(dir) {
|
|
1211
1287
|
const configs = {};
|
|
1212
|
-
const readmeMdPath =
|
|
1213
|
-
if (
|
|
1214
|
-
configs.readmeMd =
|
|
1288
|
+
const readmeMdPath = path4.join(dir, "README.md");
|
|
1289
|
+
if (fs4.existsSync(readmeMdPath)) {
|
|
1290
|
+
configs.readmeMd = fs4.readFileSync(readmeMdPath, "utf-8");
|
|
1215
1291
|
}
|
|
1216
|
-
const agentsMdPath =
|
|
1217
|
-
if (
|
|
1218
|
-
configs.agentsMd =
|
|
1292
|
+
const agentsMdPath = path4.join(dir, "AGENTS.md");
|
|
1293
|
+
if (fs4.existsSync(agentsMdPath)) {
|
|
1294
|
+
configs.agentsMd = fs4.readFileSync(agentsMdPath, "utf-8");
|
|
1219
1295
|
}
|
|
1220
|
-
const claudeMdPath =
|
|
1221
|
-
if (
|
|
1222
|
-
configs.claudeMd =
|
|
1296
|
+
const claudeMdPath = path4.join(dir, "CLAUDE.md");
|
|
1297
|
+
if (fs4.existsSync(claudeMdPath)) {
|
|
1298
|
+
configs.claudeMd = fs4.readFileSync(claudeMdPath, "utf-8");
|
|
1223
1299
|
}
|
|
1224
|
-
const claudeSettingsPath =
|
|
1225
|
-
if (
|
|
1300
|
+
const claudeSettingsPath = path4.join(dir, ".claude", "settings.json");
|
|
1301
|
+
if (fs4.existsSync(claudeSettingsPath)) {
|
|
1226
1302
|
try {
|
|
1227
|
-
configs.claudeSettings = JSON.parse(
|
|
1303
|
+
configs.claudeSettings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
|
|
1228
1304
|
} catch {
|
|
1229
1305
|
}
|
|
1230
1306
|
}
|
|
1231
|
-
const skillsDir =
|
|
1232
|
-
if (
|
|
1307
|
+
const skillsDir = path4.join(dir, ".claude", "skills");
|
|
1308
|
+
if (fs4.existsSync(skillsDir)) {
|
|
1233
1309
|
try {
|
|
1234
|
-
const entries =
|
|
1310
|
+
const entries = fs4.readdirSync(skillsDir);
|
|
1235
1311
|
const skills = [];
|
|
1236
1312
|
for (const entry of entries) {
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1313
|
+
if (BUILTIN_SKILL_NAMES.has(entry)) continue;
|
|
1314
|
+
const entryPath = path4.join(skillsDir, entry);
|
|
1315
|
+
const skillMdPath = path4.join(entryPath, "SKILL.md");
|
|
1316
|
+
if (fs4.statSync(entryPath).isDirectory() && fs4.existsSync(skillMdPath)) {
|
|
1317
|
+
skills.push({ filename: `${entry}/SKILL.md`, content: fs4.readFileSync(skillMdPath, "utf-8") });
|
|
1241
1318
|
} else if (entry.endsWith(".md")) {
|
|
1242
|
-
skills.push({ filename: entry, content:
|
|
1319
|
+
skills.push({ filename: entry, content: fs4.readFileSync(entryPath, "utf-8") });
|
|
1243
1320
|
}
|
|
1244
1321
|
}
|
|
1245
1322
|
if (skills.length > 0) configs.claudeSkills = skills;
|
|
1246
1323
|
} catch {
|
|
1247
1324
|
}
|
|
1248
1325
|
}
|
|
1249
|
-
const cursorrulesPath =
|
|
1250
|
-
if (
|
|
1251
|
-
configs.cursorrules =
|
|
1326
|
+
const cursorrulesPath = path4.join(dir, ".cursorrules");
|
|
1327
|
+
if (fs4.existsSync(cursorrulesPath)) {
|
|
1328
|
+
configs.cursorrules = fs4.readFileSync(cursorrulesPath, "utf-8");
|
|
1252
1329
|
}
|
|
1253
|
-
const cursorRulesDir =
|
|
1254
|
-
if (
|
|
1330
|
+
const cursorRulesDir = path4.join(dir, ".cursor", "rules");
|
|
1331
|
+
if (fs4.existsSync(cursorRulesDir)) {
|
|
1255
1332
|
try {
|
|
1256
|
-
const files =
|
|
1333
|
+
const files = fs4.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc") && !MANAGED_CURSOR_RULES.has(f));
|
|
1257
1334
|
configs.cursorRules = files.map((f) => ({
|
|
1258
1335
|
filename: f,
|
|
1259
|
-
content:
|
|
1336
|
+
content: fs4.readFileSync(path4.join(cursorRulesDir, f), "utf-8")
|
|
1260
1337
|
}));
|
|
1261
1338
|
} catch {
|
|
1262
1339
|
}
|
|
1263
1340
|
}
|
|
1264
|
-
const cursorSkillsDir =
|
|
1265
|
-
if (
|
|
1341
|
+
const cursorSkillsDir = path4.join(dir, ".cursor", "skills");
|
|
1342
|
+
if (fs4.existsSync(cursorSkillsDir)) {
|
|
1266
1343
|
try {
|
|
1267
|
-
const slugs =
|
|
1268
|
-
return
|
|
1344
|
+
const slugs = fs4.readdirSync(cursorSkillsDir).filter((f) => {
|
|
1345
|
+
return fs4.statSync(path4.join(cursorSkillsDir, f)).isDirectory() && !BUILTIN_SKILL_NAMES.has(f);
|
|
1269
1346
|
});
|
|
1270
|
-
configs.cursorSkills = slugs.filter((slug) =>
|
|
1347
|
+
configs.cursorSkills = slugs.filter((slug) => fs4.existsSync(path4.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
1271
1348
|
name,
|
|
1272
1349
|
filename: "SKILL.md",
|
|
1273
|
-
content:
|
|
1350
|
+
content: fs4.readFileSync(path4.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
1274
1351
|
}));
|
|
1275
1352
|
} catch {
|
|
1276
1353
|
}
|
|
1277
1354
|
}
|
|
1278
|
-
const mcpJsonPath =
|
|
1279
|
-
if (
|
|
1355
|
+
const mcpJsonPath = path4.join(dir, ".mcp.json");
|
|
1356
|
+
if (fs4.existsSync(mcpJsonPath)) {
|
|
1280
1357
|
try {
|
|
1281
|
-
const mcpJson = JSON.parse(
|
|
1358
|
+
const mcpJson = JSON.parse(fs4.readFileSync(mcpJsonPath, "utf-8"));
|
|
1282
1359
|
if (mcpJson.mcpServers) {
|
|
1283
1360
|
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
1284
1361
|
}
|
|
1285
1362
|
} catch {
|
|
1286
1363
|
}
|
|
1287
1364
|
}
|
|
1288
|
-
const cursorMcpPath =
|
|
1289
|
-
if (
|
|
1365
|
+
const cursorMcpPath = path4.join(dir, ".cursor", "mcp.json");
|
|
1366
|
+
if (fs4.existsSync(cursorMcpPath)) {
|
|
1290
1367
|
try {
|
|
1291
|
-
const cursorMcpJson = JSON.parse(
|
|
1368
|
+
const cursorMcpJson = JSON.parse(fs4.readFileSync(cursorMcpPath, "utf-8"));
|
|
1292
1369
|
if (cursorMcpJson.mcpServers) {
|
|
1293
1370
|
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
1294
1371
|
}
|
|
1295
1372
|
} catch {
|
|
1296
1373
|
}
|
|
1297
1374
|
}
|
|
1298
|
-
|
|
1375
|
+
const copilotPath = path4.join(dir, ".github", "copilot-instructions.md");
|
|
1376
|
+
if (fs4.existsSync(copilotPath)) {
|
|
1377
|
+
configs.copilotInstructions = fs4.readFileSync(copilotPath, "utf-8");
|
|
1378
|
+
}
|
|
1379
|
+
const instructionsDir = path4.join(dir, ".github", "instructions");
|
|
1380
|
+
if (fs4.existsSync(instructionsDir)) {
|
|
1381
|
+
try {
|
|
1382
|
+
const files = fs4.readdirSync(instructionsDir).filter((f) => f.endsWith(".md"));
|
|
1383
|
+
configs.copilotInstructionFiles = files.map((f) => ({
|
|
1384
|
+
filename: f,
|
|
1385
|
+
content: fs4.readFileSync(path4.join(instructionsDir, f), "utf-8")
|
|
1386
|
+
}));
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
if (fs4.existsSync(PERSONAL_LEARNINGS_FILE)) {
|
|
1299
1391
|
try {
|
|
1300
|
-
const content =
|
|
1392
|
+
const content = fs4.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
1301
1393
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
1302
1394
|
if (bullets) configs.personalLearnings = bullets;
|
|
1303
1395
|
} catch {
|
|
@@ -1307,9 +1399,9 @@ function readExistingConfigs(dir) {
|
|
|
1307
1399
|
}
|
|
1308
1400
|
|
|
1309
1401
|
// src/fingerprint/code-analysis.ts
|
|
1310
|
-
import
|
|
1311
|
-
import
|
|
1312
|
-
import { execSync as
|
|
1402
|
+
import fs5 from "fs";
|
|
1403
|
+
import path5 from "path";
|
|
1404
|
+
import { execSync as execSync4 } from "child_process";
|
|
1313
1405
|
|
|
1314
1406
|
// src/lib/sanitize.ts
|
|
1315
1407
|
var KNOWN_PREFIX_PATTERNS = [
|
|
@@ -1653,18 +1745,18 @@ function extractSkeletonIndentBased(lines, ext) {
|
|
|
1653
1745
|
}
|
|
1654
1746
|
function extractImports(content, filePath) {
|
|
1655
1747
|
const imports = [];
|
|
1656
|
-
const dir =
|
|
1748
|
+
const dir = path5.dirname(filePath);
|
|
1657
1749
|
for (const line of content.split("\n")) {
|
|
1658
1750
|
const trimmed = line.trim();
|
|
1659
1751
|
const jsMatch = trimmed.match(/(?:from|require\()\s*['"]([^'"]+)['"]/);
|
|
1660
1752
|
if (jsMatch && jsMatch[1].startsWith(".")) {
|
|
1661
|
-
imports.push(
|
|
1753
|
+
imports.push(path5.normalize(path5.join(dir, jsMatch[1])));
|
|
1662
1754
|
continue;
|
|
1663
1755
|
}
|
|
1664
1756
|
const pyMatch = trimmed.match(/^from\s+(\.[.\w]*)\s+import/);
|
|
1665
1757
|
if (pyMatch) {
|
|
1666
1758
|
const modulePath = pyMatch[1].replace(/\./g, "/");
|
|
1667
|
-
imports.push(
|
|
1759
|
+
imports.push(path5.normalize(path5.join(dir, modulePath)));
|
|
1668
1760
|
continue;
|
|
1669
1761
|
}
|
|
1670
1762
|
const goMatch = trimmed.match(/^\s*"([^"]+)"/);
|
|
@@ -1677,7 +1769,7 @@ function extractImports(content, filePath) {
|
|
|
1677
1769
|
function buildImportCounts(files) {
|
|
1678
1770
|
const counts = /* @__PURE__ */ new Map();
|
|
1679
1771
|
for (const [filePath, content] of files) {
|
|
1680
|
-
const ext =
|
|
1772
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
1681
1773
|
if (!SOURCE_EXTENSIONS.has(ext)) continue;
|
|
1682
1774
|
const imports = extractImports(content, filePath);
|
|
1683
1775
|
for (const imp of imports) {
|
|
@@ -1696,7 +1788,7 @@ function buildImportCounts(files) {
|
|
|
1696
1788
|
function getGitFrequency(dir) {
|
|
1697
1789
|
const freq = /* @__PURE__ */ new Map();
|
|
1698
1790
|
try {
|
|
1699
|
-
const output =
|
|
1791
|
+
const output = execSync4(
|
|
1700
1792
|
'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
|
|
1701
1793
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
1702
1794
|
);
|
|
@@ -1711,7 +1803,7 @@ function getGitFrequency(dir) {
|
|
|
1711
1803
|
function groupByDirectory(files) {
|
|
1712
1804
|
const groups = /* @__PURE__ */ new Map();
|
|
1713
1805
|
for (const f of files) {
|
|
1714
|
-
const dir =
|
|
1806
|
+
const dir = path5.dirname(f.path);
|
|
1715
1807
|
const group = groups.get(dir) || [];
|
|
1716
1808
|
group.push(f);
|
|
1717
1809
|
groups.set(dir, group);
|
|
@@ -1732,14 +1824,14 @@ function analyzeCode(dir) {
|
|
|
1732
1824
|
let totalChars = 0;
|
|
1733
1825
|
for (const relPath of allPaths) {
|
|
1734
1826
|
try {
|
|
1735
|
-
totalChars +=
|
|
1827
|
+
totalChars += fs5.statSync(path5.join(dir, relPath)).size;
|
|
1736
1828
|
} catch {
|
|
1737
1829
|
}
|
|
1738
1830
|
}
|
|
1739
1831
|
const fileContents = /* @__PURE__ */ new Map();
|
|
1740
1832
|
for (const relPath of allPaths) {
|
|
1741
1833
|
try {
|
|
1742
|
-
const content =
|
|
1834
|
+
const content = fs5.readFileSync(path5.join(dir, relPath), "utf-8");
|
|
1743
1835
|
if (content.split("\n").length <= 500) fileContents.set(relPath, content);
|
|
1744
1836
|
} catch {
|
|
1745
1837
|
}
|
|
@@ -1749,7 +1841,7 @@ function analyzeCode(dir) {
|
|
|
1749
1841
|
const scored = [];
|
|
1750
1842
|
let compressedChars = 0;
|
|
1751
1843
|
for (const [relPath, rawContent] of fileContents) {
|
|
1752
|
-
const ext =
|
|
1844
|
+
const ext = path5.extname(relPath).toLowerCase();
|
|
1753
1845
|
const compressed = compressContent(rawContent, ext);
|
|
1754
1846
|
const skeleton = extractSkeleton(compressed, ext);
|
|
1755
1847
|
compressedChars += compressed.length;
|
|
@@ -1778,7 +1870,7 @@ function analyzeCode(dir) {
|
|
|
1778
1870
|
}
|
|
1779
1871
|
if (similar.length > 0) {
|
|
1780
1872
|
dupGroups++;
|
|
1781
|
-
const names = similar.map((f) =>
|
|
1873
|
+
const names = similar.map((f) => path5.basename(f.path));
|
|
1782
1874
|
const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
|
|
1783
1875
|
const summarySize = summary.length + 30;
|
|
1784
1876
|
if (includedChars + summarySize <= CHAR_BUDGET) {
|
|
@@ -1815,10 +1907,10 @@ function analyzeCode(dir) {
|
|
|
1815
1907
|
}
|
|
1816
1908
|
function walkDir(base, rel, depth, maxDepth, files) {
|
|
1817
1909
|
if (depth > maxDepth) return;
|
|
1818
|
-
const fullPath =
|
|
1910
|
+
const fullPath = path5.join(base, rel);
|
|
1819
1911
|
let entries;
|
|
1820
1912
|
try {
|
|
1821
|
-
entries =
|
|
1913
|
+
entries = fs5.readdirSync(fullPath, { withFileTypes: true });
|
|
1822
1914
|
} catch {
|
|
1823
1915
|
return;
|
|
1824
1916
|
}
|
|
@@ -1831,7 +1923,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
|
|
|
1831
1923
|
} else if (entry.isFile()) {
|
|
1832
1924
|
if (SKIP_FILES.has(entry.name)) continue;
|
|
1833
1925
|
if (SKIP_PATTERNS.some((p) => p.test(entry.name))) continue;
|
|
1834
|
-
const ext =
|
|
1926
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
1835
1927
|
if (TEXT_EXTENSIONS.has(ext) || depth === 0 && !ext && !entry.name.startsWith(".")) {
|
|
1836
1928
|
files.push(relPath);
|
|
1837
1929
|
}
|
|
@@ -1839,7 +1931,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
|
|
|
1839
1931
|
}
|
|
1840
1932
|
}
|
|
1841
1933
|
function filePriority(filePath) {
|
|
1842
|
-
const base =
|
|
1934
|
+
const base = path5.basename(filePath);
|
|
1843
1935
|
const entryPoints = /* @__PURE__ */ new Set([
|
|
1844
1936
|
"index.ts",
|
|
1845
1937
|
"index.js",
|
|
@@ -1971,7 +2063,7 @@ var AnthropicProvider = class {
|
|
|
1971
2063
|
};
|
|
1972
2064
|
|
|
1973
2065
|
// src/llm/vertex.ts
|
|
1974
|
-
import
|
|
2066
|
+
import fs7 from "fs";
|
|
1975
2067
|
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
|
|
1976
2068
|
import { GoogleAuth } from "google-auth-library";
|
|
1977
2069
|
var VertexProvider = class {
|
|
@@ -1994,7 +2086,7 @@ var VertexProvider = class {
|
|
|
1994
2086
|
}
|
|
1995
2087
|
} else {
|
|
1996
2088
|
try {
|
|
1997
|
-
creds = JSON.parse(
|
|
2089
|
+
creds = JSON.parse(fs7.readFileSync(raw, "utf-8"));
|
|
1998
2090
|
} catch {
|
|
1999
2091
|
throw new Error(`Cannot read credentials file: ${raw}`);
|
|
2000
2092
|
}
|
|
@@ -3163,6 +3255,11 @@ CONSERVATIVE UPDATE means:
|
|
|
3163
3255
|
- NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
|
|
3164
3256
|
- NEVER replace specific paths/commands with generic prose
|
|
3165
3257
|
|
|
3258
|
+
Cross-agent sync:
|
|
3259
|
+
- When a change affects CLAUDE.md, apply the same semantic update to AGENTS.md and copilot instructions if they exist
|
|
3260
|
+
- Each file uses its own format and conventions \u2014 do NOT copy content verbatim between them
|
|
3261
|
+
- The goal is consistency: all agent config files should reflect the same project state after refresh
|
|
3262
|
+
|
|
3166
3263
|
Quality constraints (the output is scored deterministically):
|
|
3167
3264
|
- CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
3168
3265
|
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
@@ -3171,16 +3268,17 @@ Quality constraints (the output is scored deterministically):
|
|
|
3171
3268
|
- Preserve the existing structure (headings, bullet style, formatting)
|
|
3172
3269
|
|
|
3173
3270
|
Managed content:
|
|
3174
|
-
-
|
|
3175
|
-
-
|
|
3271
|
+
- All blocks between <!-- caliber:managed:* --> and <!-- /caliber:managed:* --> are managed by Caliber \u2014 copy them verbatim, do NOT modify their contents
|
|
3272
|
+
- This includes: pre-commit, sync, and learnings blocks
|
|
3176
3273
|
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
3177
|
-
- Preserve any references to CALIBER_LEARNINGS.md
|
|
3274
|
+
- Preserve any references to CALIBER_LEARNINGS.md
|
|
3178
3275
|
|
|
3179
3276
|
Return a JSON object with this exact shape:
|
|
3180
3277
|
{
|
|
3181
3278
|
"updatedDocs": {
|
|
3182
3279
|
"claudeMd": "<updated content or null>",
|
|
3183
3280
|
"readmeMd": "<updated content or null>",
|
|
3281
|
+
"agentsMd": "<updated content or null>",
|
|
3184
3282
|
"cursorrules": "<updated content or null>",
|
|
3185
3283
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
3186
3284
|
"claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
|
|
@@ -3342,15 +3440,15 @@ async function detectProjectStack(fileTree, suffixCounts) {
|
|
|
3342
3440
|
init_config();
|
|
3343
3441
|
|
|
3344
3442
|
// src/fingerprint/cache.ts
|
|
3345
|
-
import
|
|
3346
|
-
import
|
|
3443
|
+
import fs8 from "fs";
|
|
3444
|
+
import path7 from "path";
|
|
3347
3445
|
import crypto from "crypto";
|
|
3348
3446
|
import { execSync as execSync7 } from "child_process";
|
|
3349
3447
|
var CACHE_VERSION = 1;
|
|
3350
3448
|
var CACHE_DIR = ".caliber/cache";
|
|
3351
3449
|
var CACHE_FILE = "fingerprint.json";
|
|
3352
3450
|
function getCachePath(dir) {
|
|
3353
|
-
return
|
|
3451
|
+
return path7.join(dir, CACHE_DIR, CACHE_FILE);
|
|
3354
3452
|
}
|
|
3355
3453
|
function getGitHead(dir) {
|
|
3356
3454
|
try {
|
|
@@ -3387,8 +3485,8 @@ function computeTreeSignature(fileTree, dir) {
|
|
|
3387
3485
|
function loadFingerprintCache(dir, fileTree) {
|
|
3388
3486
|
const cachePath = getCachePath(dir);
|
|
3389
3487
|
try {
|
|
3390
|
-
if (!
|
|
3391
|
-
const raw =
|
|
3488
|
+
if (!fs8.existsSync(cachePath)) return null;
|
|
3489
|
+
const raw = fs8.readFileSync(cachePath, "utf-8");
|
|
3392
3490
|
const cache = JSON.parse(raw);
|
|
3393
3491
|
if (cache.version !== CACHE_VERSION) return null;
|
|
3394
3492
|
const currentHead = getGitHead(dir);
|
|
@@ -3409,9 +3507,9 @@ function loadFingerprintCache(dir, fileTree) {
|
|
|
3409
3507
|
function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks, tools, workspaces) {
|
|
3410
3508
|
const cachePath = getCachePath(dir);
|
|
3411
3509
|
try {
|
|
3412
|
-
const cacheDir =
|
|
3413
|
-
if (!
|
|
3414
|
-
|
|
3510
|
+
const cacheDir = path7.dirname(cachePath);
|
|
3511
|
+
if (!fs8.existsSync(cacheDir)) {
|
|
3512
|
+
fs8.mkdirSync(cacheDir, { recursive: true });
|
|
3415
3513
|
}
|
|
3416
3514
|
const cache = {
|
|
3417
3515
|
version: CACHE_VERSION,
|
|
@@ -3423,15 +3521,15 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
|
|
|
3423
3521
|
tools,
|
|
3424
3522
|
workspaces
|
|
3425
3523
|
};
|
|
3426
|
-
|
|
3524
|
+
fs8.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
|
|
3427
3525
|
} catch {
|
|
3428
3526
|
}
|
|
3429
3527
|
}
|
|
3430
3528
|
function getDetectedWorkspaces(dir) {
|
|
3431
3529
|
const cachePath = getCachePath(dir);
|
|
3432
3530
|
try {
|
|
3433
|
-
if (!
|
|
3434
|
-
const raw =
|
|
3531
|
+
if (!fs8.existsSync(cachePath)) return [];
|
|
3532
|
+
const raw = fs8.readFileSync(cachePath, "utf-8");
|
|
3435
3533
|
const cache = JSON.parse(raw);
|
|
3436
3534
|
return cache.workspaces ?? [];
|
|
3437
3535
|
} catch {
|
|
@@ -3483,9 +3581,9 @@ async function collectFingerprint(dir) {
|
|
|
3483
3581
|
}
|
|
3484
3582
|
function readPackageName(dir) {
|
|
3485
3583
|
try {
|
|
3486
|
-
const pkgPath =
|
|
3487
|
-
if (!
|
|
3488
|
-
const pkg3 = JSON.parse(
|
|
3584
|
+
const pkgPath = path8.join(dir, "package.json");
|
|
3585
|
+
if (!fs9.existsSync(pkgPath)) return void 0;
|
|
3586
|
+
const pkg3 = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
3489
3587
|
return pkg3.name;
|
|
3490
3588
|
} catch {
|
|
3491
3589
|
return void 0;
|
|
@@ -3499,7 +3597,7 @@ async function enrichWithLLM(fingerprint) {
|
|
|
3499
3597
|
const suffixCounts = {};
|
|
3500
3598
|
for (const entry of fingerprint.fileTree) {
|
|
3501
3599
|
if (entry.endsWith("/")) continue;
|
|
3502
|
-
const ext =
|
|
3600
|
+
const ext = path8.extname(entry).toLowerCase();
|
|
3503
3601
|
if (ext) {
|
|
3504
3602
|
suffixCounts[ext] = (suffixCounts[ext] || 0) + 1;
|
|
3505
3603
|
}
|
|
@@ -3515,22 +3613,22 @@ async function enrichWithLLM(fingerprint) {
|
|
|
3515
3613
|
}
|
|
3516
3614
|
|
|
3517
3615
|
// src/scanner/index.ts
|
|
3518
|
-
import
|
|
3519
|
-
import
|
|
3616
|
+
import fs10 from "fs";
|
|
3617
|
+
import path9 from "path";
|
|
3520
3618
|
import crypto2 from "crypto";
|
|
3521
3619
|
import os4 from "os";
|
|
3522
3620
|
function detectPlatforms() {
|
|
3523
3621
|
const home = os4.homedir();
|
|
3524
3622
|
return {
|
|
3525
|
-
claude:
|
|
3526
|
-
cursor:
|
|
3527
|
-
codex:
|
|
3623
|
+
claude: fs10.existsSync(path9.join(home, ".claude")),
|
|
3624
|
+
cursor: fs10.existsSync(getCursorConfigDir()),
|
|
3625
|
+
codex: fs10.existsSync(path9.join(home, ".codex"))
|
|
3528
3626
|
};
|
|
3529
3627
|
}
|
|
3530
3628
|
function scanLocalState(dir) {
|
|
3531
3629
|
const items = [];
|
|
3532
|
-
const claudeMdPath =
|
|
3533
|
-
if (
|
|
3630
|
+
const claudeMdPath = path9.join(dir, "CLAUDE.md");
|
|
3631
|
+
if (fs10.existsSync(claudeMdPath)) {
|
|
3534
3632
|
items.push({
|
|
3535
3633
|
type: "rule",
|
|
3536
3634
|
platform: "claude",
|
|
@@ -3539,10 +3637,10 @@ function scanLocalState(dir) {
|
|
|
3539
3637
|
path: claudeMdPath
|
|
3540
3638
|
});
|
|
3541
3639
|
}
|
|
3542
|
-
const skillsDir =
|
|
3543
|
-
if (
|
|
3544
|
-
for (const file of
|
|
3545
|
-
const filePath =
|
|
3640
|
+
const skillsDir = path9.join(dir, ".claude", "skills");
|
|
3641
|
+
if (fs10.existsSync(skillsDir)) {
|
|
3642
|
+
for (const file of fs10.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
3643
|
+
const filePath = path9.join(skillsDir, file);
|
|
3546
3644
|
items.push({
|
|
3547
3645
|
type: "skill",
|
|
3548
3646
|
platform: "claude",
|
|
@@ -3552,10 +3650,10 @@ function scanLocalState(dir) {
|
|
|
3552
3650
|
});
|
|
3553
3651
|
}
|
|
3554
3652
|
}
|
|
3555
|
-
const mcpJsonPath =
|
|
3556
|
-
if (
|
|
3653
|
+
const mcpJsonPath = path9.join(dir, ".mcp.json");
|
|
3654
|
+
if (fs10.existsSync(mcpJsonPath)) {
|
|
3557
3655
|
try {
|
|
3558
|
-
const mcpJson = JSON.parse(
|
|
3656
|
+
const mcpJson = JSON.parse(fs10.readFileSync(mcpJsonPath, "utf-8"));
|
|
3559
3657
|
if (mcpJson.mcpServers) {
|
|
3560
3658
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3561
3659
|
items.push({
|
|
@@ -3571,8 +3669,8 @@ function scanLocalState(dir) {
|
|
|
3571
3669
|
warnScanSkip(".mcp.json", error);
|
|
3572
3670
|
}
|
|
3573
3671
|
}
|
|
3574
|
-
const agentsMdPath =
|
|
3575
|
-
if (
|
|
3672
|
+
const agentsMdPath = path9.join(dir, "AGENTS.md");
|
|
3673
|
+
if (fs10.existsSync(agentsMdPath)) {
|
|
3576
3674
|
items.push({
|
|
3577
3675
|
type: "rule",
|
|
3578
3676
|
platform: "codex",
|
|
@@ -3581,12 +3679,12 @@ function scanLocalState(dir) {
|
|
|
3581
3679
|
path: agentsMdPath
|
|
3582
3680
|
});
|
|
3583
3681
|
}
|
|
3584
|
-
const codexSkillsDir =
|
|
3585
|
-
if (
|
|
3682
|
+
const codexSkillsDir = path9.join(dir, ".agents", "skills");
|
|
3683
|
+
if (fs10.existsSync(codexSkillsDir)) {
|
|
3586
3684
|
try {
|
|
3587
|
-
for (const name of
|
|
3588
|
-
const skillFile =
|
|
3589
|
-
if (
|
|
3685
|
+
for (const name of fs10.readdirSync(codexSkillsDir)) {
|
|
3686
|
+
const skillFile = path9.join(codexSkillsDir, name, "SKILL.md");
|
|
3687
|
+
if (fs10.existsSync(skillFile)) {
|
|
3590
3688
|
items.push({
|
|
3591
3689
|
type: "skill",
|
|
3592
3690
|
platform: "codex",
|
|
@@ -3600,8 +3698,8 @@ function scanLocalState(dir) {
|
|
|
3600
3698
|
warnScanSkip(".agents/skills", error);
|
|
3601
3699
|
}
|
|
3602
3700
|
}
|
|
3603
|
-
const cursorrulesPath =
|
|
3604
|
-
if (
|
|
3701
|
+
const cursorrulesPath = path9.join(dir, ".cursorrules");
|
|
3702
|
+
if (fs10.existsSync(cursorrulesPath)) {
|
|
3605
3703
|
items.push({
|
|
3606
3704
|
type: "rule",
|
|
3607
3705
|
platform: "cursor",
|
|
@@ -3610,10 +3708,10 @@ function scanLocalState(dir) {
|
|
|
3610
3708
|
path: cursorrulesPath
|
|
3611
3709
|
});
|
|
3612
3710
|
}
|
|
3613
|
-
const cursorRulesDir =
|
|
3614
|
-
if (
|
|
3615
|
-
for (const file of
|
|
3616
|
-
const filePath =
|
|
3711
|
+
const cursorRulesDir = path9.join(dir, ".cursor", "rules");
|
|
3712
|
+
if (fs10.existsSync(cursorRulesDir)) {
|
|
3713
|
+
for (const file of fs10.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
3714
|
+
const filePath = path9.join(cursorRulesDir, file);
|
|
3617
3715
|
items.push({
|
|
3618
3716
|
type: "rule",
|
|
3619
3717
|
platform: "cursor",
|
|
@@ -3623,12 +3721,12 @@ function scanLocalState(dir) {
|
|
|
3623
3721
|
});
|
|
3624
3722
|
}
|
|
3625
3723
|
}
|
|
3626
|
-
const cursorSkillsDir =
|
|
3627
|
-
if (
|
|
3724
|
+
const cursorSkillsDir = path9.join(dir, ".cursor", "skills");
|
|
3725
|
+
if (fs10.existsSync(cursorSkillsDir)) {
|
|
3628
3726
|
try {
|
|
3629
|
-
for (const name of
|
|
3630
|
-
const skillFile =
|
|
3631
|
-
if (
|
|
3727
|
+
for (const name of fs10.readdirSync(cursorSkillsDir)) {
|
|
3728
|
+
const skillFile = path9.join(cursorSkillsDir, name, "SKILL.md");
|
|
3729
|
+
if (fs10.existsSync(skillFile)) {
|
|
3632
3730
|
items.push({
|
|
3633
3731
|
type: "skill",
|
|
3634
3732
|
platform: "cursor",
|
|
@@ -3642,10 +3740,10 @@ function scanLocalState(dir) {
|
|
|
3642
3740
|
warnScanSkip(".cursor/skills", error);
|
|
3643
3741
|
}
|
|
3644
3742
|
}
|
|
3645
|
-
const cursorMcpPath =
|
|
3646
|
-
if (
|
|
3743
|
+
const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
|
|
3744
|
+
if (fs10.existsSync(cursorMcpPath)) {
|
|
3647
3745
|
try {
|
|
3648
|
-
const mcpJson = JSON.parse(
|
|
3746
|
+
const mcpJson = JSON.parse(fs10.readFileSync(cursorMcpPath, "utf-8"));
|
|
3649
3747
|
if (mcpJson.mcpServers) {
|
|
3650
3748
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3651
3749
|
items.push({
|
|
@@ -3664,7 +3762,7 @@ function scanLocalState(dir) {
|
|
|
3664
3762
|
return items;
|
|
3665
3763
|
}
|
|
3666
3764
|
function hashFile(filePath) {
|
|
3667
|
-
const text =
|
|
3765
|
+
const text = fs10.readFileSync(filePath, "utf-8");
|
|
3668
3766
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
3669
3767
|
}
|
|
3670
3768
|
function hashJson(obj) {
|
|
@@ -3677,37 +3775,37 @@ function warnScanSkip(target, error) {
|
|
|
3677
3775
|
function getCursorConfigDir() {
|
|
3678
3776
|
const home = os4.homedir();
|
|
3679
3777
|
if (process.platform === "darwin") {
|
|
3680
|
-
return
|
|
3778
|
+
return path9.join(home, "Library", "Application Support", "Cursor");
|
|
3681
3779
|
}
|
|
3682
3780
|
if (process.platform === "win32") {
|
|
3683
|
-
return
|
|
3781
|
+
return path9.join(home, "AppData", "Roaming", "Cursor");
|
|
3684
3782
|
}
|
|
3685
|
-
return
|
|
3783
|
+
return path9.join(home, ".config", "Cursor");
|
|
3686
3784
|
}
|
|
3687
3785
|
|
|
3688
3786
|
// src/lib/hooks.ts
|
|
3689
3787
|
init_resolve_caliber();
|
|
3690
|
-
import
|
|
3691
|
-
import
|
|
3788
|
+
import fs11 from "fs";
|
|
3789
|
+
import path10 from "path";
|
|
3692
3790
|
import { execSync as execSync8 } from "child_process";
|
|
3693
|
-
var SETTINGS_PATH =
|
|
3791
|
+
var SETTINGS_PATH = path10.join(".claude", "settings.json");
|
|
3694
3792
|
var REFRESH_TAIL = "refresh --quiet";
|
|
3695
3793
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
3696
3794
|
function getHookCommand() {
|
|
3697
3795
|
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3698
3796
|
}
|
|
3699
3797
|
function readSettings() {
|
|
3700
|
-
if (!
|
|
3798
|
+
if (!fs11.existsSync(SETTINGS_PATH)) return {};
|
|
3701
3799
|
try {
|
|
3702
|
-
return JSON.parse(
|
|
3800
|
+
return JSON.parse(fs11.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3703
3801
|
} catch {
|
|
3704
3802
|
return {};
|
|
3705
3803
|
}
|
|
3706
3804
|
}
|
|
3707
3805
|
function writeSettings(settings) {
|
|
3708
|
-
const dir =
|
|
3709
|
-
if (!
|
|
3710
|
-
|
|
3806
|
+
const dir = path10.dirname(SETTINGS_PATH);
|
|
3807
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
3808
|
+
fs11.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
3711
3809
|
}
|
|
3712
3810
|
function findHookIndex(sessionEnd) {
|
|
3713
3811
|
return sessionEnd.findIndex(
|
|
@@ -3773,19 +3871,19 @@ ${PRECOMMIT_END}`;
|
|
|
3773
3871
|
function getGitHooksDir() {
|
|
3774
3872
|
try {
|
|
3775
3873
|
const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3776
|
-
return
|
|
3874
|
+
return path10.join(gitDir, "hooks");
|
|
3777
3875
|
} catch {
|
|
3778
3876
|
return null;
|
|
3779
3877
|
}
|
|
3780
3878
|
}
|
|
3781
3879
|
function getPreCommitPath() {
|
|
3782
3880
|
const hooksDir = getGitHooksDir();
|
|
3783
|
-
return hooksDir ?
|
|
3881
|
+
return hooksDir ? path10.join(hooksDir, "pre-commit") : null;
|
|
3784
3882
|
}
|
|
3785
3883
|
function isPreCommitHookInstalled() {
|
|
3786
3884
|
const hookPath = getPreCommitPath();
|
|
3787
|
-
if (!hookPath || !
|
|
3788
|
-
const content =
|
|
3885
|
+
if (!hookPath || !fs11.existsSync(hookPath)) return false;
|
|
3886
|
+
const content = fs11.readFileSync(hookPath, "utf-8");
|
|
3789
3887
|
return content.includes(PRECOMMIT_START);
|
|
3790
3888
|
}
|
|
3791
3889
|
function installPreCommitHook() {
|
|
@@ -3794,42 +3892,42 @@ function installPreCommitHook() {
|
|
|
3794
3892
|
}
|
|
3795
3893
|
const hookPath = getPreCommitPath();
|
|
3796
3894
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3797
|
-
const hooksDir =
|
|
3798
|
-
if (!
|
|
3895
|
+
const hooksDir = path10.dirname(hookPath);
|
|
3896
|
+
if (!fs11.existsSync(hooksDir)) fs11.mkdirSync(hooksDir, { recursive: true });
|
|
3799
3897
|
let content = "";
|
|
3800
|
-
if (
|
|
3801
|
-
content =
|
|
3898
|
+
if (fs11.existsSync(hookPath)) {
|
|
3899
|
+
content = fs11.readFileSync(hookPath, "utf-8");
|
|
3802
3900
|
if (!content.endsWith("\n")) content += "\n";
|
|
3803
3901
|
content += "\n" + getPrecommitBlock() + "\n";
|
|
3804
3902
|
} else {
|
|
3805
3903
|
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3806
3904
|
}
|
|
3807
|
-
|
|
3808
|
-
|
|
3905
|
+
fs11.writeFileSync(hookPath, content);
|
|
3906
|
+
fs11.chmodSync(hookPath, 493);
|
|
3809
3907
|
return { installed: true, alreadyInstalled: false };
|
|
3810
3908
|
}
|
|
3811
3909
|
function removePreCommitHook() {
|
|
3812
3910
|
const hookPath = getPreCommitPath();
|
|
3813
|
-
if (!hookPath || !
|
|
3911
|
+
if (!hookPath || !fs11.existsSync(hookPath)) {
|
|
3814
3912
|
return { removed: false, notFound: true };
|
|
3815
3913
|
}
|
|
3816
|
-
let content =
|
|
3914
|
+
let content = fs11.readFileSync(hookPath, "utf-8");
|
|
3817
3915
|
if (!content.includes(PRECOMMIT_START)) {
|
|
3818
3916
|
return { removed: false, notFound: true };
|
|
3819
3917
|
}
|
|
3820
3918
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3821
3919
|
content = content.replace(regex, "\n");
|
|
3822
3920
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3823
|
-
|
|
3921
|
+
fs11.unlinkSync(hookPath);
|
|
3824
3922
|
} else {
|
|
3825
|
-
|
|
3923
|
+
fs11.writeFileSync(hookPath, content);
|
|
3826
3924
|
}
|
|
3827
3925
|
return { removed: true, notFound: false };
|
|
3828
3926
|
}
|
|
3829
3927
|
|
|
3830
3928
|
// src/fingerprint/sources.ts
|
|
3831
|
-
import
|
|
3832
|
-
import
|
|
3929
|
+
import fs12 from "fs";
|
|
3930
|
+
import path11 from "path";
|
|
3833
3931
|
|
|
3834
3932
|
// src/scoring/utils.ts
|
|
3835
3933
|
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
@@ -4148,7 +4246,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
|
|
|
4148
4246
|
var README_CONTENT_LIMIT = 1e3;
|
|
4149
4247
|
var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
|
|
4150
4248
|
function loadSourcesConfig(dir) {
|
|
4151
|
-
const configPath =
|
|
4249
|
+
const configPath = path11.join(dir, ".caliber", "sources.json");
|
|
4152
4250
|
const content = readFileOrNull(configPath);
|
|
4153
4251
|
if (!content) return [];
|
|
4154
4252
|
try {
|
|
@@ -4166,29 +4264,29 @@ function loadSourcesConfig(dir) {
|
|
|
4166
4264
|
}
|
|
4167
4265
|
}
|
|
4168
4266
|
function writeSourcesConfig(dir, sources2) {
|
|
4169
|
-
const configDir =
|
|
4170
|
-
if (!
|
|
4171
|
-
|
|
4267
|
+
const configDir = path11.join(dir, ".caliber");
|
|
4268
|
+
if (!fs12.existsSync(configDir)) {
|
|
4269
|
+
fs12.mkdirSync(configDir, { recursive: true });
|
|
4172
4270
|
}
|
|
4173
|
-
const configPath =
|
|
4174
|
-
|
|
4271
|
+
const configPath = path11.join(configDir, "sources.json");
|
|
4272
|
+
fs12.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
4175
4273
|
}
|
|
4176
4274
|
function detectSourceType(absPath) {
|
|
4177
4275
|
try {
|
|
4178
|
-
return
|
|
4276
|
+
return fs12.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
4179
4277
|
} catch {
|
|
4180
4278
|
return "file";
|
|
4181
4279
|
}
|
|
4182
4280
|
}
|
|
4183
4281
|
function isInsideDir(childPath, parentDir) {
|
|
4184
|
-
const relative2 =
|
|
4185
|
-
return !relative2.startsWith("..") && !
|
|
4282
|
+
const relative2 = path11.relative(parentDir, childPath);
|
|
4283
|
+
return !relative2.startsWith("..") && !path11.isAbsolute(relative2);
|
|
4186
4284
|
}
|
|
4187
4285
|
function resolveAllSources(dir, cliSources, workspaces) {
|
|
4188
4286
|
const seen = /* @__PURE__ */ new Map();
|
|
4189
|
-
const projectRoot =
|
|
4287
|
+
const projectRoot = path11.resolve(dir);
|
|
4190
4288
|
for (const src of cliSources) {
|
|
4191
|
-
const absPath =
|
|
4289
|
+
const absPath = path11.resolve(dir, src);
|
|
4192
4290
|
if (seen.has(absPath)) continue;
|
|
4193
4291
|
const type = detectSourceType(absPath);
|
|
4194
4292
|
seen.set(absPath, {
|
|
@@ -4201,12 +4299,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
4201
4299
|
for (const cfg of configSources) {
|
|
4202
4300
|
if (cfg.type === "url") continue;
|
|
4203
4301
|
if (!cfg.path) continue;
|
|
4204
|
-
const absPath =
|
|
4302
|
+
const absPath = path11.resolve(dir, cfg.path);
|
|
4205
4303
|
if (seen.has(absPath)) continue;
|
|
4206
4304
|
seen.set(absPath, { absPath, config: cfg, origin: "config" });
|
|
4207
4305
|
}
|
|
4208
4306
|
for (const ws of workspaces) {
|
|
4209
|
-
const absPath =
|
|
4307
|
+
const absPath = path11.resolve(dir, ws);
|
|
4210
4308
|
if (seen.has(absPath)) continue;
|
|
4211
4309
|
if (!isInsideDir(absPath, projectRoot)) continue;
|
|
4212
4310
|
seen.set(absPath, {
|
|
@@ -4219,7 +4317,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
4219
4317
|
for (const [absPath, resolved] of seen) {
|
|
4220
4318
|
let stat;
|
|
4221
4319
|
try {
|
|
4222
|
-
stat =
|
|
4320
|
+
stat = fs12.statSync(absPath);
|
|
4223
4321
|
} catch {
|
|
4224
4322
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
4225
4323
|
continue;
|
|
@@ -4248,13 +4346,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
4248
4346
|
if (config.type === "file") {
|
|
4249
4347
|
return collectFileSummary(resolved, projectDir);
|
|
4250
4348
|
}
|
|
4251
|
-
const summaryPath =
|
|
4349
|
+
const summaryPath = path11.join(absPath, ".caliber", "summary.json");
|
|
4252
4350
|
const summaryContent = readFileOrNull(summaryPath);
|
|
4253
4351
|
if (summaryContent) {
|
|
4254
4352
|
try {
|
|
4255
4353
|
const published = JSON.parse(summaryContent);
|
|
4256
4354
|
return {
|
|
4257
|
-
name: published.name ||
|
|
4355
|
+
name: published.name || path11.basename(absPath),
|
|
4258
4356
|
type: "repo",
|
|
4259
4357
|
role: config.role || published.role || "related-repo",
|
|
4260
4358
|
description: config.description || published.description || "",
|
|
@@ -4274,18 +4372,18 @@ function collectRepoSummary(resolved, projectDir) {
|
|
|
4274
4372
|
let topLevelDirs;
|
|
4275
4373
|
let keyFiles;
|
|
4276
4374
|
try {
|
|
4277
|
-
const entries =
|
|
4375
|
+
const entries = fs12.readdirSync(absPath, { withFileTypes: true });
|
|
4278
4376
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
4279
4377
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
4280
4378
|
} catch {
|
|
4281
4379
|
}
|
|
4282
|
-
const claudeMdContent = readFileOrNull(
|
|
4380
|
+
const claudeMdContent = readFileOrNull(path11.join(absPath, "CLAUDE.md"));
|
|
4283
4381
|
const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
|
|
4284
|
-
const readmeContent = readFileOrNull(
|
|
4382
|
+
const readmeContent = readFileOrNull(path11.join(absPath, "README.md"));
|
|
4285
4383
|
const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
|
|
4286
4384
|
const gitRemoteUrl = getGitRemoteUrl(absPath);
|
|
4287
4385
|
return {
|
|
4288
|
-
name: packageName ||
|
|
4386
|
+
name: packageName || path11.basename(absPath),
|
|
4289
4387
|
type: "repo",
|
|
4290
4388
|
role: config.role || "related-repo",
|
|
4291
4389
|
description: config.description || "",
|
|
@@ -4302,7 +4400,7 @@ function collectFileSummary(resolved, projectDir) {
|
|
|
4302
4400
|
const { config, origin, absPath } = resolved;
|
|
4303
4401
|
const content = readFileOrNull(absPath);
|
|
4304
4402
|
return {
|
|
4305
|
-
name:
|
|
4403
|
+
name: path11.basename(absPath),
|
|
4306
4404
|
type: "file",
|
|
4307
4405
|
role: config.role || "reference-doc",
|
|
4308
4406
|
description: config.description || content?.slice(0, 100).split("\n")[0] || "",
|
|
@@ -4945,17 +5043,17 @@ import fs19 from "fs";
|
|
|
4945
5043
|
|
|
4946
5044
|
// src/writers/claude/index.ts
|
|
4947
5045
|
init_pre_commit_block();
|
|
4948
|
-
import
|
|
4949
|
-
import
|
|
5046
|
+
import fs13 from "fs";
|
|
5047
|
+
import path12 from "path";
|
|
4950
5048
|
function writeClaudeConfig(config) {
|
|
4951
5049
|
const written = [];
|
|
4952
|
-
|
|
5050
|
+
fs13.writeFileSync("CLAUDE.md", appendManagedBlocks(config.claudeMd, "claude"));
|
|
4953
5051
|
written.push("CLAUDE.md");
|
|
4954
5052
|
if (config.skills?.length) {
|
|
4955
5053
|
for (const skill of config.skills) {
|
|
4956
|
-
const skillDir =
|
|
4957
|
-
if (!
|
|
4958
|
-
const skillPath =
|
|
5054
|
+
const skillDir = path12.join(".claude", "skills", skill.name);
|
|
5055
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
5056
|
+
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4959
5057
|
const frontmatter = [
|
|
4960
5058
|
"---",
|
|
4961
5059
|
`name: ${skill.name}`,
|
|
@@ -4963,21 +5061,21 @@ function writeClaudeConfig(config) {
|
|
|
4963
5061
|
"---",
|
|
4964
5062
|
""
|
|
4965
5063
|
].join("\n");
|
|
4966
|
-
|
|
5064
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4967
5065
|
written.push(skillPath);
|
|
4968
5066
|
}
|
|
4969
5067
|
}
|
|
4970
5068
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4971
5069
|
let existingServers = {};
|
|
4972
5070
|
try {
|
|
4973
|
-
if (
|
|
4974
|
-
const existing = JSON.parse(
|
|
5071
|
+
if (fs13.existsSync(".mcp.json")) {
|
|
5072
|
+
const existing = JSON.parse(fs13.readFileSync(".mcp.json", "utf-8"));
|
|
4975
5073
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4976
5074
|
}
|
|
4977
5075
|
} catch {
|
|
4978
5076
|
}
|
|
4979
5077
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4980
|
-
|
|
5078
|
+
fs13.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4981
5079
|
written.push(".mcp.json");
|
|
4982
5080
|
}
|
|
4983
5081
|
return written;
|
|
@@ -4985,30 +5083,31 @@ function writeClaudeConfig(config) {
|
|
|
4985
5083
|
|
|
4986
5084
|
// src/writers/cursor/index.ts
|
|
4987
5085
|
init_pre_commit_block();
|
|
4988
|
-
import
|
|
4989
|
-
import
|
|
5086
|
+
import fs14 from "fs";
|
|
5087
|
+
import path13 from "path";
|
|
4990
5088
|
function writeCursorConfig(config) {
|
|
4991
5089
|
const written = [];
|
|
4992
5090
|
if (config.cursorrules) {
|
|
4993
|
-
|
|
5091
|
+
fs14.writeFileSync(".cursorrules", config.cursorrules);
|
|
4994
5092
|
written.push(".cursorrules");
|
|
4995
5093
|
}
|
|
4996
5094
|
const preCommitRule = getCursorPreCommitRule();
|
|
4997
5095
|
const learningsRule = getCursorLearningsRule();
|
|
4998
5096
|
const syncRule = getCursorSyncRule();
|
|
4999
|
-
const
|
|
5000
|
-
const
|
|
5001
|
-
|
|
5097
|
+
const setupRule = getCursorSetupRule();
|
|
5098
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule, setupRule];
|
|
5099
|
+
const rulesDir = path13.join(".cursor", "rules");
|
|
5100
|
+
if (!fs14.existsSync(rulesDir)) fs14.mkdirSync(rulesDir, { recursive: true });
|
|
5002
5101
|
for (const rule of allRules) {
|
|
5003
|
-
const rulePath =
|
|
5004
|
-
|
|
5102
|
+
const rulePath = path13.join(rulesDir, rule.filename);
|
|
5103
|
+
fs14.writeFileSync(rulePath, rule.content);
|
|
5005
5104
|
written.push(rulePath);
|
|
5006
5105
|
}
|
|
5007
5106
|
if (config.skills?.length) {
|
|
5008
5107
|
for (const skill of config.skills) {
|
|
5009
|
-
const skillDir =
|
|
5010
|
-
if (!
|
|
5011
|
-
const skillPath =
|
|
5108
|
+
const skillDir = path13.join(".cursor", "skills", skill.name);
|
|
5109
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
5110
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
5012
5111
|
const frontmatter = [
|
|
5013
5112
|
"---",
|
|
5014
5113
|
`name: ${skill.name}`,
|
|
@@ -5016,24 +5115,24 @@ function writeCursorConfig(config) {
|
|
|
5016
5115
|
"---",
|
|
5017
5116
|
""
|
|
5018
5117
|
].join("\n");
|
|
5019
|
-
|
|
5118
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5020
5119
|
written.push(skillPath);
|
|
5021
5120
|
}
|
|
5022
5121
|
}
|
|
5023
5122
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
5024
5123
|
const cursorDir = ".cursor";
|
|
5025
|
-
if (!
|
|
5026
|
-
const mcpPath =
|
|
5124
|
+
if (!fs14.existsSync(cursorDir)) fs14.mkdirSync(cursorDir, { recursive: true });
|
|
5125
|
+
const mcpPath = path13.join(cursorDir, "mcp.json");
|
|
5027
5126
|
let existingServers = {};
|
|
5028
5127
|
try {
|
|
5029
|
-
if (
|
|
5030
|
-
const existing = JSON.parse(
|
|
5128
|
+
if (fs14.existsSync(mcpPath)) {
|
|
5129
|
+
const existing = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
|
|
5031
5130
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
5032
5131
|
}
|
|
5033
5132
|
} catch {
|
|
5034
5133
|
}
|
|
5035
5134
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
5036
|
-
|
|
5135
|
+
fs14.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
5037
5136
|
written.push(mcpPath);
|
|
5038
5137
|
}
|
|
5039
5138
|
return written;
|
|
@@ -5041,17 +5140,17 @@ function writeCursorConfig(config) {
|
|
|
5041
5140
|
|
|
5042
5141
|
// src/writers/codex/index.ts
|
|
5043
5142
|
init_pre_commit_block();
|
|
5044
|
-
import
|
|
5045
|
-
import
|
|
5143
|
+
import fs15 from "fs";
|
|
5144
|
+
import path14 from "path";
|
|
5046
5145
|
function writeCodexConfig(config) {
|
|
5047
5146
|
const written = [];
|
|
5048
|
-
|
|
5147
|
+
fs15.writeFileSync("AGENTS.md", appendManagedBlocks(config.agentsMd, "codex"));
|
|
5049
5148
|
written.push("AGENTS.md");
|
|
5050
5149
|
if (config.skills?.length) {
|
|
5051
5150
|
for (const skill of config.skills) {
|
|
5052
|
-
const skillDir =
|
|
5053
|
-
if (!
|
|
5054
|
-
const skillPath =
|
|
5151
|
+
const skillDir = path14.join(".agents", "skills", skill.name);
|
|
5152
|
+
if (!fs15.existsSync(skillDir)) fs15.mkdirSync(skillDir, { recursive: true });
|
|
5153
|
+
const skillPath = path14.join(skillDir, "SKILL.md");
|
|
5055
5154
|
const frontmatter = [
|
|
5056
5155
|
"---",
|
|
5057
5156
|
`name: ${skill.name}`,
|
|
@@ -5059,7 +5158,7 @@ function writeCodexConfig(config) {
|
|
|
5059
5158
|
"---",
|
|
5060
5159
|
""
|
|
5061
5160
|
].join("\n");
|
|
5062
|
-
|
|
5161
|
+
fs15.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5063
5162
|
written.push(skillPath);
|
|
5064
5163
|
}
|
|
5065
5164
|
}
|
|
@@ -5068,20 +5167,20 @@ function writeCodexConfig(config) {
|
|
|
5068
5167
|
|
|
5069
5168
|
// src/writers/github-copilot/index.ts
|
|
5070
5169
|
init_pre_commit_block();
|
|
5071
|
-
import
|
|
5072
|
-
import
|
|
5170
|
+
import fs16 from "fs";
|
|
5171
|
+
import path15 from "path";
|
|
5073
5172
|
function writeGithubCopilotConfig(config) {
|
|
5074
5173
|
const written = [];
|
|
5075
5174
|
if (config.instructions) {
|
|
5076
|
-
|
|
5077
|
-
|
|
5175
|
+
fs16.mkdirSync(".github", { recursive: true });
|
|
5176
|
+
fs16.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendManagedBlocks(config.instructions, "copilot"));
|
|
5078
5177
|
written.push(".github/copilot-instructions.md");
|
|
5079
5178
|
}
|
|
5080
5179
|
if (config.instructionFiles?.length) {
|
|
5081
|
-
const instructionsDir =
|
|
5082
|
-
|
|
5180
|
+
const instructionsDir = path15.join(".github", "instructions");
|
|
5181
|
+
fs16.mkdirSync(instructionsDir, { recursive: true });
|
|
5083
5182
|
for (const file of config.instructionFiles) {
|
|
5084
|
-
|
|
5183
|
+
fs16.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
|
|
5085
5184
|
written.push(`.github/instructions/${file.filename}`);
|
|
5086
5185
|
}
|
|
5087
5186
|
}
|
|
@@ -5089,30 +5188,30 @@ function writeGithubCopilotConfig(config) {
|
|
|
5089
5188
|
}
|
|
5090
5189
|
|
|
5091
5190
|
// src/writers/backup.ts
|
|
5092
|
-
import
|
|
5093
|
-
import
|
|
5191
|
+
import fs17 from "fs";
|
|
5192
|
+
import path16 from "path";
|
|
5094
5193
|
function createBackup(files) {
|
|
5095
5194
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5096
|
-
const backupDir =
|
|
5195
|
+
const backupDir = path16.join(BACKUPS_DIR, timestamp);
|
|
5097
5196
|
for (const file of files) {
|
|
5098
|
-
if (!
|
|
5099
|
-
const dest =
|
|
5100
|
-
const destDir =
|
|
5101
|
-
if (!
|
|
5102
|
-
|
|
5197
|
+
if (!fs17.existsSync(file)) continue;
|
|
5198
|
+
const dest = path16.join(backupDir, file);
|
|
5199
|
+
const destDir = path16.dirname(dest);
|
|
5200
|
+
if (!fs17.existsSync(destDir)) {
|
|
5201
|
+
fs17.mkdirSync(destDir, { recursive: true });
|
|
5103
5202
|
}
|
|
5104
|
-
|
|
5203
|
+
fs17.copyFileSync(file, dest);
|
|
5105
5204
|
}
|
|
5106
5205
|
return backupDir;
|
|
5107
5206
|
}
|
|
5108
5207
|
function restoreBackup(backupDir, file) {
|
|
5109
|
-
const backupFile =
|
|
5110
|
-
if (!
|
|
5111
|
-
const destDir =
|
|
5112
|
-
if (!
|
|
5113
|
-
|
|
5208
|
+
const backupFile = path16.join(backupDir, file);
|
|
5209
|
+
if (!fs17.existsSync(backupFile)) return false;
|
|
5210
|
+
const destDir = path16.dirname(file);
|
|
5211
|
+
if (!fs17.existsSync(destDir)) {
|
|
5212
|
+
fs17.mkdirSync(destDir, { recursive: true });
|
|
5114
5213
|
}
|
|
5115
|
-
|
|
5214
|
+
fs17.copyFileSync(backupFile, file);
|
|
5116
5215
|
return true;
|
|
5117
5216
|
}
|
|
5118
5217
|
|
|
@@ -9290,8 +9389,9 @@ async function initCommand(options) {
|
|
|
9290
9389
|
if (options.agent) {
|
|
9291
9390
|
targetAgent = options.agent;
|
|
9292
9391
|
} else if (options.autoApprove) {
|
|
9293
|
-
|
|
9294
|
-
|
|
9392
|
+
const detected = detectAgents(process.cwd());
|
|
9393
|
+
targetAgent = detected.length > 0 ? detected : ["claude"];
|
|
9394
|
+
log(options.verbose, `Auto-approve: using ${targetAgent.join(", ")}`);
|
|
9295
9395
|
} else {
|
|
9296
9396
|
const detected = detectAgents(process.cwd());
|
|
9297
9397
|
targetAgent = await promptAgent(detected.length > 0 ? detected : void 0);
|
|
@@ -9372,12 +9472,11 @@ async function initCommand(options) {
|
|
|
9372
9472
|
}
|
|
9373
9473
|
if (skipGeneration) {
|
|
9374
9474
|
const {
|
|
9375
|
-
|
|
9376
|
-
appendLearningsBlock: appendLearningsBlock2,
|
|
9377
|
-
appendSyncBlock: appendSyncBlock2,
|
|
9475
|
+
appendManagedBlocks: appendManagedBlocks2,
|
|
9378
9476
|
getCursorPreCommitRule: getCursorPreCommitRule2,
|
|
9379
9477
|
getCursorLearningsRule: getCursorLearningsRule2,
|
|
9380
|
-
getCursorSyncRule: getCursorSyncRule2
|
|
9478
|
+
getCursorSyncRule: getCursorSyncRule2,
|
|
9479
|
+
getCursorSetupRule: getCursorSetupRule2
|
|
9381
9480
|
} = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
|
|
9382
9481
|
const claudeMdPath = "CLAUDE.md";
|
|
9383
9482
|
let claudeContent = "";
|
|
@@ -9389,7 +9488,7 @@ async function initCommand(options) {
|
|
|
9389
9488
|
claudeContent = `# ${path26.basename(process.cwd())}
|
|
9390
9489
|
`;
|
|
9391
9490
|
}
|
|
9392
|
-
const updatedClaude =
|
|
9491
|
+
const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
|
|
9393
9492
|
if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
|
|
9394
9493
|
fs33.writeFileSync(claudeMdPath, updatedClaude);
|
|
9395
9494
|
console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
|
|
@@ -9397,7 +9496,7 @@ async function initCommand(options) {
|
|
|
9397
9496
|
if (targetAgent.includes("cursor")) {
|
|
9398
9497
|
const rulesDir = path26.join(".cursor", "rules");
|
|
9399
9498
|
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9400
|
-
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2()]) {
|
|
9499
|
+
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
|
|
9401
9500
|
fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
|
|
9402
9501
|
}
|
|
9403
9502
|
console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
|
|
@@ -9414,7 +9513,7 @@ async function initCommand(options) {
|
|
|
9414
9513
|
copilotContent = `# ${path26.basename(process.cwd())}
|
|
9415
9514
|
`;
|
|
9416
9515
|
}
|
|
9417
|
-
const updatedCopilot =
|
|
9516
|
+
const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
|
|
9418
9517
|
if (updatedCopilot !== copilotContent) {
|
|
9419
9518
|
fs33.writeFileSync(copilotPath, updatedCopilot);
|
|
9420
9519
|
console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
|
|
@@ -10193,14 +10292,24 @@ var MAX_DIFF_BYTES = 1e5;
|
|
|
10193
10292
|
var DOC_PATTERNS = [
|
|
10194
10293
|
"CLAUDE.md",
|
|
10195
10294
|
"README.md",
|
|
10295
|
+
"AGENTS.md",
|
|
10196
10296
|
".cursorrules",
|
|
10197
10297
|
".cursor/rules/",
|
|
10298
|
+
".cursor/skills/",
|
|
10198
10299
|
".claude/skills/",
|
|
10300
|
+
".agents/skills/",
|
|
10301
|
+
".github/copilot-instructions.md",
|
|
10302
|
+
".github/instructions/",
|
|
10199
10303
|
"CALIBER_LEARNINGS.md"
|
|
10200
10304
|
];
|
|
10201
10305
|
function excludeArgs() {
|
|
10202
10306
|
return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
|
|
10203
10307
|
}
|
|
10308
|
+
function truncateAtLine(text, maxBytes) {
|
|
10309
|
+
if (text.length <= maxBytes) return text;
|
|
10310
|
+
const cut = text.lastIndexOf("\n", maxBytes);
|
|
10311
|
+
return cut > 0 ? text.slice(0, cut) : text.slice(0, maxBytes);
|
|
10312
|
+
}
|
|
10204
10313
|
function safeExec(cmd) {
|
|
10205
10314
|
try {
|
|
10206
10315
|
return execSync15(cmd, {
|
|
@@ -10246,9 +10355,9 @@ function collectDiff(lastSha) {
|
|
|
10246
10355
|
const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
|
|
10247
10356
|
if (totalSize > MAX_DIFF_BYTES) {
|
|
10248
10357
|
const ratio = MAX_DIFF_BYTES / totalSize;
|
|
10249
|
-
committedDiff = committedDiff
|
|
10250
|
-
stagedDiff = stagedDiff
|
|
10251
|
-
unstagedDiff = unstagedDiff
|
|
10358
|
+
committedDiff = truncateAtLine(committedDiff, Math.floor(committedDiff.length * ratio));
|
|
10359
|
+
stagedDiff = truncateAtLine(stagedDiff, Math.floor(stagedDiff.length * ratio));
|
|
10360
|
+
unstagedDiff = truncateAtLine(unstagedDiff, Math.floor(unstagedDiff.length * ratio));
|
|
10252
10361
|
}
|
|
10253
10362
|
const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
|
|
10254
10363
|
const parts = [];
|
|
@@ -10267,13 +10376,17 @@ import path28 from "path";
|
|
|
10267
10376
|
function writeRefreshDocs(docs) {
|
|
10268
10377
|
const written = [];
|
|
10269
10378
|
if (docs.claudeMd) {
|
|
10270
|
-
fs36.writeFileSync("CLAUDE.md",
|
|
10379
|
+
fs36.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd, "claude"));
|
|
10271
10380
|
written.push("CLAUDE.md");
|
|
10272
10381
|
}
|
|
10273
10382
|
if (docs.readmeMd) {
|
|
10274
10383
|
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
10275
10384
|
written.push("README.md");
|
|
10276
10385
|
}
|
|
10386
|
+
if (docs.agentsMd) {
|
|
10387
|
+
fs36.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
|
|
10388
|
+
written.push("AGENTS.md");
|
|
10389
|
+
}
|
|
10277
10390
|
if (docs.cursorrules) {
|
|
10278
10391
|
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
10279
10392
|
written.push(".cursorrules");
|
|
@@ -10296,7 +10409,7 @@ function writeRefreshDocs(docs) {
|
|
|
10296
10409
|
}
|
|
10297
10410
|
if (docs.copilotInstructions) {
|
|
10298
10411
|
fs36.mkdirSync(".github", { recursive: true });
|
|
10299
|
-
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"),
|
|
10412
|
+
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendManagedBlocks(docs.copilotInstructions, "copilot"));
|
|
10300
10413
|
written.push(".github/copilot-instructions.md");
|
|
10301
10414
|
}
|
|
10302
10415
|
if (docs.copilotInstructionFiles) {
|
|
@@ -10315,10 +10428,18 @@ init_config();
|
|
|
10315
10428
|
async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
|
|
10316
10429
|
const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
|
|
10317
10430
|
const fastModel = getFastModel();
|
|
10431
|
+
const docCount = [
|
|
10432
|
+
existingDocs.claudeMd,
|
|
10433
|
+
existingDocs.readmeMd,
|
|
10434
|
+
existingDocs.agentsMd,
|
|
10435
|
+
existingDocs.cursorrules,
|
|
10436
|
+
existingDocs.copilotInstructions
|
|
10437
|
+
].filter(Boolean).length + (existingDocs.cursorRules?.length ?? 0) + (existingDocs.claudeSkills?.length ?? 0);
|
|
10438
|
+
const maxTokens = Math.min(32768, Math.max(8192, docCount * 4096));
|
|
10318
10439
|
const raw = await llmCall({
|
|
10319
10440
|
system: REFRESH_SYSTEM_PROMPT,
|
|
10320
10441
|
prompt,
|
|
10321
|
-
maxTokens
|
|
10442
|
+
maxTokens,
|
|
10322
10443
|
...fastModel ? { model: fastModel } : {}
|
|
10323
10444
|
});
|
|
10324
10445
|
return parseJsonResponse(raw);
|
|
@@ -10377,6 +10498,21 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
10377
10498
|
parts.push(rule.content);
|
|
10378
10499
|
}
|
|
10379
10500
|
}
|
|
10501
|
+
if (existingDocs.agentsMd) {
|
|
10502
|
+
parts.push("\n[AGENTS.md]");
|
|
10503
|
+
parts.push(existingDocs.agentsMd);
|
|
10504
|
+
}
|
|
10505
|
+
if (existingDocs.copilotInstructions) {
|
|
10506
|
+
parts.push("\n[.github/copilot-instructions.md]");
|
|
10507
|
+
parts.push(existingDocs.copilotInstructions);
|
|
10508
|
+
}
|
|
10509
|
+
if (existingDocs.copilotInstructionFiles?.length) {
|
|
10510
|
+
for (const file of existingDocs.copilotInstructionFiles) {
|
|
10511
|
+
parts.push(`
|
|
10512
|
+
[.github/instructions/${file.filename}]`);
|
|
10513
|
+
parts.push(file.content);
|
|
10514
|
+
}
|
|
10515
|
+
}
|
|
10380
10516
|
if (learnedSection) {
|
|
10381
10517
|
parts.push("\n--- Learned Patterns (from session learning) ---");
|
|
10382
10518
|
parts.push("Consider these accumulated learnings when deciding what to update:");
|