@rely-ai/caliber 1.31.0-dev.1774714955 → 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 +779 -587
- 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, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."
|
|
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, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."
|
|
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
|
+
\`\`\`
|
|
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").
|
|
545
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
|
|
|
@@ -573,7 +291,27 @@ caliber score --json --quiet 2>/dev/null | head -1
|
|
|
573
291
|
|
|
574
292
|
Ask the user: "Are you setting up for yourself only, or for your team too?"
|
|
575
293
|
|
|
576
|
-
- If **solo** \u2192
|
|
294
|
+
- If **solo** \u2192 Continue with solo setup:
|
|
295
|
+
|
|
296
|
+
Check if session learning is enabled:
|
|
297
|
+
\`\`\`bash
|
|
298
|
+
caliber learn status 2>/dev/null | head -3
|
|
299
|
+
\`\`\`
|
|
300
|
+
- If learning is already enabled \u2192 note it in the summary.
|
|
301
|
+
- If not enabled \u2192 ask the user: "Caliber can learn from your coding sessions \u2014 when you correct a mistake or fix a pattern, it remembers for next time. Enable session learning?"
|
|
302
|
+
If yes:
|
|
303
|
+
\`\`\`bash
|
|
304
|
+
caliber learn install
|
|
305
|
+
\`\`\`
|
|
306
|
+
|
|
307
|
+
Then tell the user:
|
|
308
|
+
"You're all set! Here's what happens next:
|
|
309
|
+
- Every time you commit, Caliber syncs your agent configs automatically
|
|
310
|
+
- Your CLAUDE.md, Cursor rules, and AGENTS.md stay current with your code
|
|
311
|
+
- Run \`caliber skills\` anytime to discover community skills for your stack"
|
|
312
|
+
|
|
313
|
+
Then show the summary (see below) and stop.
|
|
314
|
+
|
|
577
315
|
- If **team** \u2192 Check if the GitHub Action already exists:
|
|
578
316
|
\`\`\`bash
|
|
579
317
|
[ -f .github/workflows/caliber-sync.yml ] && echo "ACTION_EXISTS" || echo "NO_ACTION"
|
|
@@ -603,7 +341,43 @@ Ask the user: "Are you setting up for yourself only, or for your team too?"
|
|
|
603
341
|
env:
|
|
604
342
|
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
605
343
|
\`\`\`
|
|
606
|
-
|
|
344
|
+
Now determine which LLM provider the team uses. Check the local Caliber config:
|
|
345
|
+
\`\`\`bash
|
|
346
|
+
caliber config --show 2>/dev/null || echo "NO_CONFIG"
|
|
347
|
+
\`\`\`
|
|
348
|
+
|
|
349
|
+
Based on the provider, the GitHub Action needs the corresponding secret:
|
|
350
|
+
- **anthropic** \u2192 \`ANTHROPIC_API_KEY\`
|
|
351
|
+
- **openai** \u2192 \`OPENAI_API_KEY\`
|
|
352
|
+
- **vertex** \u2192 \`VERTEX_PROJECT_ID\` and \`GOOGLE_APPLICATION_CREDENTIALS\` (service account JSON)
|
|
353
|
+
|
|
354
|
+
Update the workflow env block to match the provider. For example, if using OpenAI:
|
|
355
|
+
\`\`\`yaml
|
|
356
|
+
env:
|
|
357
|
+
OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
|
|
358
|
+
\`\`\`
|
|
359
|
+
|
|
360
|
+
Then check if the \`gh\` CLI is available to set the secret:
|
|
361
|
+
\`\`\`bash
|
|
362
|
+
command -v gh >/dev/null 2>&1 && echo "GH_AVAILABLE" || echo "NO_GH"
|
|
363
|
+
\`\`\`
|
|
364
|
+
|
|
365
|
+
- If GH_AVAILABLE \u2192 Ask the user for their API key and set it:
|
|
366
|
+
\`\`\`bash
|
|
367
|
+
gh secret set ANTHROPIC_API_KEY
|
|
368
|
+
\`\`\`
|
|
369
|
+
(This prompts for the value securely via stdin)
|
|
370
|
+
- If NO_GH \u2192 Tell the user exactly what to do:
|
|
371
|
+
"Go to your repo on GitHub \u2192 Settings \u2192 Secrets and variables \u2192 Actions \u2192 New repository secret.
|
|
372
|
+
Name: ANTHROPIC_API_KEY (or OPENAI_API_KEY depending on provider)
|
|
373
|
+
Value: your API key"
|
|
374
|
+
|
|
375
|
+
Finally, offer to commit and push the workflow file:
|
|
376
|
+
\`\`\`bash
|
|
377
|
+
git add .github/workflows/caliber-sync.yml
|
|
378
|
+
git commit -m "feat: add Caliber sync GitHub Action"
|
|
379
|
+
git push
|
|
380
|
+
\`\`\`
|
|
607
381
|
|
|
608
382
|
### Summary
|
|
609
383
|
|
|
@@ -632,52 +406,402 @@ From now on, every commit keeps all your agent configs in sync automatically.
|
|
|
632
406
|
- The user is in the middle of time-sensitive work
|
|
633
407
|
`;
|
|
634
408
|
}
|
|
635
|
-
function ensureBuiltinSkills() {
|
|
636
|
-
const written = [];
|
|
637
|
-
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
638
|
-
if (!
|
|
639
|
-
for (const skill of BUILTIN_SKILLS) {
|
|
640
|
-
const skillPath =
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
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;
|
|
644
719
|
}
|
|
720
|
+
return parsed;
|
|
721
|
+
} catch {
|
|
722
|
+
return null;
|
|
645
723
|
}
|
|
646
|
-
return written;
|
|
647
724
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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"() {
|
|
651
758
|
"use strict";
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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"
|
|
659
767
|
};
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
|
666
778
|
};
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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"
|
|
673
788
|
};
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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"]);
|
|
681
805
|
}
|
|
682
806
|
});
|
|
683
807
|
|
|
@@ -1006,8 +1130,8 @@ import chalk14 from "chalk";
|
|
|
1006
1130
|
import fs33 from "fs";
|
|
1007
1131
|
|
|
1008
1132
|
// src/fingerprint/index.ts
|
|
1009
|
-
import
|
|
1010
|
-
import
|
|
1133
|
+
import fs9 from "fs";
|
|
1134
|
+
import path8 from "path";
|
|
1011
1135
|
|
|
1012
1136
|
// src/fingerprint/git.ts
|
|
1013
1137
|
import { execSync } from "child_process";
|
|
@@ -1115,8 +1239,8 @@ function scan(base, rel, depth, maxDepth, result) {
|
|
|
1115
1239
|
}
|
|
1116
1240
|
|
|
1117
1241
|
// src/fingerprint/existing-config.ts
|
|
1118
|
-
import
|
|
1119
|
-
import
|
|
1242
|
+
import fs4 from "fs";
|
|
1243
|
+
import path4 from "path";
|
|
1120
1244
|
|
|
1121
1245
|
// src/constants.ts
|
|
1122
1246
|
import path2 from "path";
|
|
@@ -1151,97 +1275,121 @@ var LEARNING_LAST_ERROR_FILE = "last-error.json";
|
|
|
1151
1275
|
var MIN_SESSIONS_FOR_COMPARISON = 3;
|
|
1152
1276
|
|
|
1153
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
|
+
]);
|
|
1154
1286
|
function readExistingConfigs(dir) {
|
|
1155
1287
|
const configs = {};
|
|
1156
|
-
const readmeMdPath =
|
|
1157
|
-
if (
|
|
1158
|
-
configs.readmeMd =
|
|
1288
|
+
const readmeMdPath = path4.join(dir, "README.md");
|
|
1289
|
+
if (fs4.existsSync(readmeMdPath)) {
|
|
1290
|
+
configs.readmeMd = fs4.readFileSync(readmeMdPath, "utf-8");
|
|
1159
1291
|
}
|
|
1160
|
-
const agentsMdPath =
|
|
1161
|
-
if (
|
|
1162
|
-
configs.agentsMd =
|
|
1292
|
+
const agentsMdPath = path4.join(dir, "AGENTS.md");
|
|
1293
|
+
if (fs4.existsSync(agentsMdPath)) {
|
|
1294
|
+
configs.agentsMd = fs4.readFileSync(agentsMdPath, "utf-8");
|
|
1163
1295
|
}
|
|
1164
|
-
const claudeMdPath =
|
|
1165
|
-
if (
|
|
1166
|
-
configs.claudeMd =
|
|
1296
|
+
const claudeMdPath = path4.join(dir, "CLAUDE.md");
|
|
1297
|
+
if (fs4.existsSync(claudeMdPath)) {
|
|
1298
|
+
configs.claudeMd = fs4.readFileSync(claudeMdPath, "utf-8");
|
|
1167
1299
|
}
|
|
1168
|
-
const claudeSettingsPath =
|
|
1169
|
-
if (
|
|
1300
|
+
const claudeSettingsPath = path4.join(dir, ".claude", "settings.json");
|
|
1301
|
+
if (fs4.existsSync(claudeSettingsPath)) {
|
|
1170
1302
|
try {
|
|
1171
|
-
configs.claudeSettings = JSON.parse(
|
|
1303
|
+
configs.claudeSettings = JSON.parse(fs4.readFileSync(claudeSettingsPath, "utf-8"));
|
|
1172
1304
|
} catch {
|
|
1173
1305
|
}
|
|
1174
1306
|
}
|
|
1175
|
-
const skillsDir =
|
|
1176
|
-
if (
|
|
1307
|
+
const skillsDir = path4.join(dir, ".claude", "skills");
|
|
1308
|
+
if (fs4.existsSync(skillsDir)) {
|
|
1177
1309
|
try {
|
|
1178
|
-
const entries =
|
|
1310
|
+
const entries = fs4.readdirSync(skillsDir);
|
|
1179
1311
|
const skills = [];
|
|
1180
1312
|
for (const entry of entries) {
|
|
1181
|
-
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
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") });
|
|
1185
1318
|
} else if (entry.endsWith(".md")) {
|
|
1186
|
-
skills.push({ filename: entry, content:
|
|
1319
|
+
skills.push({ filename: entry, content: fs4.readFileSync(entryPath, "utf-8") });
|
|
1187
1320
|
}
|
|
1188
1321
|
}
|
|
1189
1322
|
if (skills.length > 0) configs.claudeSkills = skills;
|
|
1190
1323
|
} catch {
|
|
1191
1324
|
}
|
|
1192
1325
|
}
|
|
1193
|
-
const cursorrulesPath =
|
|
1194
|
-
if (
|
|
1195
|
-
configs.cursorrules =
|
|
1326
|
+
const cursorrulesPath = path4.join(dir, ".cursorrules");
|
|
1327
|
+
if (fs4.existsSync(cursorrulesPath)) {
|
|
1328
|
+
configs.cursorrules = fs4.readFileSync(cursorrulesPath, "utf-8");
|
|
1196
1329
|
}
|
|
1197
|
-
const cursorRulesDir =
|
|
1198
|
-
if (
|
|
1330
|
+
const cursorRulesDir = path4.join(dir, ".cursor", "rules");
|
|
1331
|
+
if (fs4.existsSync(cursorRulesDir)) {
|
|
1199
1332
|
try {
|
|
1200
|
-
const files =
|
|
1333
|
+
const files = fs4.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc") && !MANAGED_CURSOR_RULES.has(f));
|
|
1201
1334
|
configs.cursorRules = files.map((f) => ({
|
|
1202
1335
|
filename: f,
|
|
1203
|
-
content:
|
|
1336
|
+
content: fs4.readFileSync(path4.join(cursorRulesDir, f), "utf-8")
|
|
1204
1337
|
}));
|
|
1205
1338
|
} catch {
|
|
1206
1339
|
}
|
|
1207
1340
|
}
|
|
1208
|
-
const cursorSkillsDir =
|
|
1209
|
-
if (
|
|
1341
|
+
const cursorSkillsDir = path4.join(dir, ".cursor", "skills");
|
|
1342
|
+
if (fs4.existsSync(cursorSkillsDir)) {
|
|
1210
1343
|
try {
|
|
1211
|
-
const slugs =
|
|
1212
|
-
return
|
|
1344
|
+
const slugs = fs4.readdirSync(cursorSkillsDir).filter((f) => {
|
|
1345
|
+
return fs4.statSync(path4.join(cursorSkillsDir, f)).isDirectory() && !BUILTIN_SKILL_NAMES.has(f);
|
|
1213
1346
|
});
|
|
1214
|
-
configs.cursorSkills = slugs.filter((slug) =>
|
|
1347
|
+
configs.cursorSkills = slugs.filter((slug) => fs4.existsSync(path4.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
1215
1348
|
name,
|
|
1216
1349
|
filename: "SKILL.md",
|
|
1217
|
-
content:
|
|
1350
|
+
content: fs4.readFileSync(path4.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
1218
1351
|
}));
|
|
1219
1352
|
} catch {
|
|
1220
1353
|
}
|
|
1221
1354
|
}
|
|
1222
|
-
const mcpJsonPath =
|
|
1223
|
-
if (
|
|
1355
|
+
const mcpJsonPath = path4.join(dir, ".mcp.json");
|
|
1356
|
+
if (fs4.existsSync(mcpJsonPath)) {
|
|
1224
1357
|
try {
|
|
1225
|
-
const mcpJson = JSON.parse(
|
|
1358
|
+
const mcpJson = JSON.parse(fs4.readFileSync(mcpJsonPath, "utf-8"));
|
|
1226
1359
|
if (mcpJson.mcpServers) {
|
|
1227
1360
|
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
1228
1361
|
}
|
|
1229
1362
|
} catch {
|
|
1230
1363
|
}
|
|
1231
1364
|
}
|
|
1232
|
-
const cursorMcpPath =
|
|
1233
|
-
if (
|
|
1365
|
+
const cursorMcpPath = path4.join(dir, ".cursor", "mcp.json");
|
|
1366
|
+
if (fs4.existsSync(cursorMcpPath)) {
|
|
1234
1367
|
try {
|
|
1235
|
-
const cursorMcpJson = JSON.parse(
|
|
1368
|
+
const cursorMcpJson = JSON.parse(fs4.readFileSync(cursorMcpPath, "utf-8"));
|
|
1236
1369
|
if (cursorMcpJson.mcpServers) {
|
|
1237
1370
|
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
1238
1371
|
}
|
|
1239
1372
|
} catch {
|
|
1240
1373
|
}
|
|
1241
1374
|
}
|
|
1242
|
-
|
|
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)) {
|
|
1243
1391
|
try {
|
|
1244
|
-
const content =
|
|
1392
|
+
const content = fs4.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
1245
1393
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
1246
1394
|
if (bullets) configs.personalLearnings = bullets;
|
|
1247
1395
|
} catch {
|
|
@@ -1251,9 +1399,9 @@ function readExistingConfigs(dir) {
|
|
|
1251
1399
|
}
|
|
1252
1400
|
|
|
1253
1401
|
// src/fingerprint/code-analysis.ts
|
|
1254
|
-
import
|
|
1255
|
-
import
|
|
1256
|
-
import { execSync as
|
|
1402
|
+
import fs5 from "fs";
|
|
1403
|
+
import path5 from "path";
|
|
1404
|
+
import { execSync as execSync4 } from "child_process";
|
|
1257
1405
|
|
|
1258
1406
|
// src/lib/sanitize.ts
|
|
1259
1407
|
var KNOWN_PREFIX_PATTERNS = [
|
|
@@ -1597,18 +1745,18 @@ function extractSkeletonIndentBased(lines, ext) {
|
|
|
1597
1745
|
}
|
|
1598
1746
|
function extractImports(content, filePath) {
|
|
1599
1747
|
const imports = [];
|
|
1600
|
-
const dir =
|
|
1748
|
+
const dir = path5.dirname(filePath);
|
|
1601
1749
|
for (const line of content.split("\n")) {
|
|
1602
1750
|
const trimmed = line.trim();
|
|
1603
1751
|
const jsMatch = trimmed.match(/(?:from|require\()\s*['"]([^'"]+)['"]/);
|
|
1604
1752
|
if (jsMatch && jsMatch[1].startsWith(".")) {
|
|
1605
|
-
imports.push(
|
|
1753
|
+
imports.push(path5.normalize(path5.join(dir, jsMatch[1])));
|
|
1606
1754
|
continue;
|
|
1607
1755
|
}
|
|
1608
1756
|
const pyMatch = trimmed.match(/^from\s+(\.[.\w]*)\s+import/);
|
|
1609
1757
|
if (pyMatch) {
|
|
1610
1758
|
const modulePath = pyMatch[1].replace(/\./g, "/");
|
|
1611
|
-
imports.push(
|
|
1759
|
+
imports.push(path5.normalize(path5.join(dir, modulePath)));
|
|
1612
1760
|
continue;
|
|
1613
1761
|
}
|
|
1614
1762
|
const goMatch = trimmed.match(/^\s*"([^"]+)"/);
|
|
@@ -1621,7 +1769,7 @@ function extractImports(content, filePath) {
|
|
|
1621
1769
|
function buildImportCounts(files) {
|
|
1622
1770
|
const counts = /* @__PURE__ */ new Map();
|
|
1623
1771
|
for (const [filePath, content] of files) {
|
|
1624
|
-
const ext =
|
|
1772
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
1625
1773
|
if (!SOURCE_EXTENSIONS.has(ext)) continue;
|
|
1626
1774
|
const imports = extractImports(content, filePath);
|
|
1627
1775
|
for (const imp of imports) {
|
|
@@ -1640,7 +1788,7 @@ function buildImportCounts(files) {
|
|
|
1640
1788
|
function getGitFrequency(dir) {
|
|
1641
1789
|
const freq = /* @__PURE__ */ new Map();
|
|
1642
1790
|
try {
|
|
1643
|
-
const output =
|
|
1791
|
+
const output = execSync4(
|
|
1644
1792
|
'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
|
|
1645
1793
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
1646
1794
|
);
|
|
@@ -1655,7 +1803,7 @@ function getGitFrequency(dir) {
|
|
|
1655
1803
|
function groupByDirectory(files) {
|
|
1656
1804
|
const groups = /* @__PURE__ */ new Map();
|
|
1657
1805
|
for (const f of files) {
|
|
1658
|
-
const dir =
|
|
1806
|
+
const dir = path5.dirname(f.path);
|
|
1659
1807
|
const group = groups.get(dir) || [];
|
|
1660
1808
|
group.push(f);
|
|
1661
1809
|
groups.set(dir, group);
|
|
@@ -1676,14 +1824,14 @@ function analyzeCode(dir) {
|
|
|
1676
1824
|
let totalChars = 0;
|
|
1677
1825
|
for (const relPath of allPaths) {
|
|
1678
1826
|
try {
|
|
1679
|
-
totalChars +=
|
|
1827
|
+
totalChars += fs5.statSync(path5.join(dir, relPath)).size;
|
|
1680
1828
|
} catch {
|
|
1681
1829
|
}
|
|
1682
1830
|
}
|
|
1683
1831
|
const fileContents = /* @__PURE__ */ new Map();
|
|
1684
1832
|
for (const relPath of allPaths) {
|
|
1685
1833
|
try {
|
|
1686
|
-
const content =
|
|
1834
|
+
const content = fs5.readFileSync(path5.join(dir, relPath), "utf-8");
|
|
1687
1835
|
if (content.split("\n").length <= 500) fileContents.set(relPath, content);
|
|
1688
1836
|
} catch {
|
|
1689
1837
|
}
|
|
@@ -1693,7 +1841,7 @@ function analyzeCode(dir) {
|
|
|
1693
1841
|
const scored = [];
|
|
1694
1842
|
let compressedChars = 0;
|
|
1695
1843
|
for (const [relPath, rawContent] of fileContents) {
|
|
1696
|
-
const ext =
|
|
1844
|
+
const ext = path5.extname(relPath).toLowerCase();
|
|
1697
1845
|
const compressed = compressContent(rawContent, ext);
|
|
1698
1846
|
const skeleton = extractSkeleton(compressed, ext);
|
|
1699
1847
|
compressedChars += compressed.length;
|
|
@@ -1722,7 +1870,7 @@ function analyzeCode(dir) {
|
|
|
1722
1870
|
}
|
|
1723
1871
|
if (similar.length > 0) {
|
|
1724
1872
|
dupGroups++;
|
|
1725
|
-
const names = similar.map((f) =>
|
|
1873
|
+
const names = similar.map((f) => path5.basename(f.path));
|
|
1726
1874
|
const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
|
|
1727
1875
|
const summarySize = summary.length + 30;
|
|
1728
1876
|
if (includedChars + summarySize <= CHAR_BUDGET) {
|
|
@@ -1759,10 +1907,10 @@ function analyzeCode(dir) {
|
|
|
1759
1907
|
}
|
|
1760
1908
|
function walkDir(base, rel, depth, maxDepth, files) {
|
|
1761
1909
|
if (depth > maxDepth) return;
|
|
1762
|
-
const fullPath =
|
|
1910
|
+
const fullPath = path5.join(base, rel);
|
|
1763
1911
|
let entries;
|
|
1764
1912
|
try {
|
|
1765
|
-
entries =
|
|
1913
|
+
entries = fs5.readdirSync(fullPath, { withFileTypes: true });
|
|
1766
1914
|
} catch {
|
|
1767
1915
|
return;
|
|
1768
1916
|
}
|
|
@@ -1775,7 +1923,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
|
|
|
1775
1923
|
} else if (entry.isFile()) {
|
|
1776
1924
|
if (SKIP_FILES.has(entry.name)) continue;
|
|
1777
1925
|
if (SKIP_PATTERNS.some((p) => p.test(entry.name))) continue;
|
|
1778
|
-
const ext =
|
|
1926
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
1779
1927
|
if (TEXT_EXTENSIONS.has(ext) || depth === 0 && !ext && !entry.name.startsWith(".")) {
|
|
1780
1928
|
files.push(relPath);
|
|
1781
1929
|
}
|
|
@@ -1783,7 +1931,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
|
|
|
1783
1931
|
}
|
|
1784
1932
|
}
|
|
1785
1933
|
function filePriority(filePath) {
|
|
1786
|
-
const base =
|
|
1934
|
+
const base = path5.basename(filePath);
|
|
1787
1935
|
const entryPoints = /* @__PURE__ */ new Set([
|
|
1788
1936
|
"index.ts",
|
|
1789
1937
|
"index.js",
|
|
@@ -1915,7 +2063,7 @@ var AnthropicProvider = class {
|
|
|
1915
2063
|
};
|
|
1916
2064
|
|
|
1917
2065
|
// src/llm/vertex.ts
|
|
1918
|
-
import
|
|
2066
|
+
import fs7 from "fs";
|
|
1919
2067
|
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
|
|
1920
2068
|
import { GoogleAuth } from "google-auth-library";
|
|
1921
2069
|
var VertexProvider = class {
|
|
@@ -1938,7 +2086,7 @@ var VertexProvider = class {
|
|
|
1938
2086
|
}
|
|
1939
2087
|
} else {
|
|
1940
2088
|
try {
|
|
1941
|
-
creds = JSON.parse(
|
|
2089
|
+
creds = JSON.parse(fs7.readFileSync(raw, "utf-8"));
|
|
1942
2090
|
} catch {
|
|
1943
2091
|
throw new Error(`Cannot read credentials file: ${raw}`);
|
|
1944
2092
|
}
|
|
@@ -3107,6 +3255,11 @@ CONSERVATIVE UPDATE means:
|
|
|
3107
3255
|
- NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
|
|
3108
3256
|
- NEVER replace specific paths/commands with generic prose
|
|
3109
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
|
+
|
|
3110
3263
|
Quality constraints (the output is scored deterministically):
|
|
3111
3264
|
- CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
3112
3265
|
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
@@ -3115,16 +3268,17 @@ Quality constraints (the output is scored deterministically):
|
|
|
3115
3268
|
- Preserve the existing structure (headings, bullet style, formatting)
|
|
3116
3269
|
|
|
3117
3270
|
Managed content:
|
|
3118
|
-
-
|
|
3119
|
-
-
|
|
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
|
|
3120
3273
|
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
3121
|
-
- Preserve any references to CALIBER_LEARNINGS.md
|
|
3274
|
+
- Preserve any references to CALIBER_LEARNINGS.md
|
|
3122
3275
|
|
|
3123
3276
|
Return a JSON object with this exact shape:
|
|
3124
3277
|
{
|
|
3125
3278
|
"updatedDocs": {
|
|
3126
3279
|
"claudeMd": "<updated content or null>",
|
|
3127
3280
|
"readmeMd": "<updated content or null>",
|
|
3281
|
+
"agentsMd": "<updated content or null>",
|
|
3128
3282
|
"cursorrules": "<updated content or null>",
|
|
3129
3283
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
3130
3284
|
"claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
|
|
@@ -3286,15 +3440,15 @@ async function detectProjectStack(fileTree, suffixCounts) {
|
|
|
3286
3440
|
init_config();
|
|
3287
3441
|
|
|
3288
3442
|
// src/fingerprint/cache.ts
|
|
3289
|
-
import
|
|
3290
|
-
import
|
|
3443
|
+
import fs8 from "fs";
|
|
3444
|
+
import path7 from "path";
|
|
3291
3445
|
import crypto from "crypto";
|
|
3292
3446
|
import { execSync as execSync7 } from "child_process";
|
|
3293
3447
|
var CACHE_VERSION = 1;
|
|
3294
3448
|
var CACHE_DIR = ".caliber/cache";
|
|
3295
3449
|
var CACHE_FILE = "fingerprint.json";
|
|
3296
3450
|
function getCachePath(dir) {
|
|
3297
|
-
return
|
|
3451
|
+
return path7.join(dir, CACHE_DIR, CACHE_FILE);
|
|
3298
3452
|
}
|
|
3299
3453
|
function getGitHead(dir) {
|
|
3300
3454
|
try {
|
|
@@ -3331,8 +3485,8 @@ function computeTreeSignature(fileTree, dir) {
|
|
|
3331
3485
|
function loadFingerprintCache(dir, fileTree) {
|
|
3332
3486
|
const cachePath = getCachePath(dir);
|
|
3333
3487
|
try {
|
|
3334
|
-
if (!
|
|
3335
|
-
const raw =
|
|
3488
|
+
if (!fs8.existsSync(cachePath)) return null;
|
|
3489
|
+
const raw = fs8.readFileSync(cachePath, "utf-8");
|
|
3336
3490
|
const cache = JSON.parse(raw);
|
|
3337
3491
|
if (cache.version !== CACHE_VERSION) return null;
|
|
3338
3492
|
const currentHead = getGitHead(dir);
|
|
@@ -3353,9 +3507,9 @@ function loadFingerprintCache(dir, fileTree) {
|
|
|
3353
3507
|
function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks, tools, workspaces) {
|
|
3354
3508
|
const cachePath = getCachePath(dir);
|
|
3355
3509
|
try {
|
|
3356
|
-
const cacheDir =
|
|
3357
|
-
if (!
|
|
3358
|
-
|
|
3510
|
+
const cacheDir = path7.dirname(cachePath);
|
|
3511
|
+
if (!fs8.existsSync(cacheDir)) {
|
|
3512
|
+
fs8.mkdirSync(cacheDir, { recursive: true });
|
|
3359
3513
|
}
|
|
3360
3514
|
const cache = {
|
|
3361
3515
|
version: CACHE_VERSION,
|
|
@@ -3367,15 +3521,15 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
|
|
|
3367
3521
|
tools,
|
|
3368
3522
|
workspaces
|
|
3369
3523
|
};
|
|
3370
|
-
|
|
3524
|
+
fs8.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
|
|
3371
3525
|
} catch {
|
|
3372
3526
|
}
|
|
3373
3527
|
}
|
|
3374
3528
|
function getDetectedWorkspaces(dir) {
|
|
3375
3529
|
const cachePath = getCachePath(dir);
|
|
3376
3530
|
try {
|
|
3377
|
-
if (!
|
|
3378
|
-
const raw =
|
|
3531
|
+
if (!fs8.existsSync(cachePath)) return [];
|
|
3532
|
+
const raw = fs8.readFileSync(cachePath, "utf-8");
|
|
3379
3533
|
const cache = JSON.parse(raw);
|
|
3380
3534
|
return cache.workspaces ?? [];
|
|
3381
3535
|
} catch {
|
|
@@ -3427,9 +3581,9 @@ async function collectFingerprint(dir) {
|
|
|
3427
3581
|
}
|
|
3428
3582
|
function readPackageName(dir) {
|
|
3429
3583
|
try {
|
|
3430
|
-
const pkgPath =
|
|
3431
|
-
if (!
|
|
3432
|
-
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"));
|
|
3433
3587
|
return pkg3.name;
|
|
3434
3588
|
} catch {
|
|
3435
3589
|
return void 0;
|
|
@@ -3443,7 +3597,7 @@ async function enrichWithLLM(fingerprint) {
|
|
|
3443
3597
|
const suffixCounts = {};
|
|
3444
3598
|
for (const entry of fingerprint.fileTree) {
|
|
3445
3599
|
if (entry.endsWith("/")) continue;
|
|
3446
|
-
const ext =
|
|
3600
|
+
const ext = path8.extname(entry).toLowerCase();
|
|
3447
3601
|
if (ext) {
|
|
3448
3602
|
suffixCounts[ext] = (suffixCounts[ext] || 0) + 1;
|
|
3449
3603
|
}
|
|
@@ -3459,22 +3613,22 @@ async function enrichWithLLM(fingerprint) {
|
|
|
3459
3613
|
}
|
|
3460
3614
|
|
|
3461
3615
|
// src/scanner/index.ts
|
|
3462
|
-
import
|
|
3463
|
-
import
|
|
3616
|
+
import fs10 from "fs";
|
|
3617
|
+
import path9 from "path";
|
|
3464
3618
|
import crypto2 from "crypto";
|
|
3465
3619
|
import os4 from "os";
|
|
3466
3620
|
function detectPlatforms() {
|
|
3467
3621
|
const home = os4.homedir();
|
|
3468
3622
|
return {
|
|
3469
|
-
claude:
|
|
3470
|
-
cursor:
|
|
3471
|
-
codex:
|
|
3623
|
+
claude: fs10.existsSync(path9.join(home, ".claude")),
|
|
3624
|
+
cursor: fs10.existsSync(getCursorConfigDir()),
|
|
3625
|
+
codex: fs10.existsSync(path9.join(home, ".codex"))
|
|
3472
3626
|
};
|
|
3473
3627
|
}
|
|
3474
3628
|
function scanLocalState(dir) {
|
|
3475
3629
|
const items = [];
|
|
3476
|
-
const claudeMdPath =
|
|
3477
|
-
if (
|
|
3630
|
+
const claudeMdPath = path9.join(dir, "CLAUDE.md");
|
|
3631
|
+
if (fs10.existsSync(claudeMdPath)) {
|
|
3478
3632
|
items.push({
|
|
3479
3633
|
type: "rule",
|
|
3480
3634
|
platform: "claude",
|
|
@@ -3483,10 +3637,10 @@ function scanLocalState(dir) {
|
|
|
3483
3637
|
path: claudeMdPath
|
|
3484
3638
|
});
|
|
3485
3639
|
}
|
|
3486
|
-
const skillsDir =
|
|
3487
|
-
if (
|
|
3488
|
-
for (const file of
|
|
3489
|
-
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);
|
|
3490
3644
|
items.push({
|
|
3491
3645
|
type: "skill",
|
|
3492
3646
|
platform: "claude",
|
|
@@ -3496,10 +3650,10 @@ function scanLocalState(dir) {
|
|
|
3496
3650
|
});
|
|
3497
3651
|
}
|
|
3498
3652
|
}
|
|
3499
|
-
const mcpJsonPath =
|
|
3500
|
-
if (
|
|
3653
|
+
const mcpJsonPath = path9.join(dir, ".mcp.json");
|
|
3654
|
+
if (fs10.existsSync(mcpJsonPath)) {
|
|
3501
3655
|
try {
|
|
3502
|
-
const mcpJson = JSON.parse(
|
|
3656
|
+
const mcpJson = JSON.parse(fs10.readFileSync(mcpJsonPath, "utf-8"));
|
|
3503
3657
|
if (mcpJson.mcpServers) {
|
|
3504
3658
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3505
3659
|
items.push({
|
|
@@ -3515,8 +3669,8 @@ function scanLocalState(dir) {
|
|
|
3515
3669
|
warnScanSkip(".mcp.json", error);
|
|
3516
3670
|
}
|
|
3517
3671
|
}
|
|
3518
|
-
const agentsMdPath =
|
|
3519
|
-
if (
|
|
3672
|
+
const agentsMdPath = path9.join(dir, "AGENTS.md");
|
|
3673
|
+
if (fs10.existsSync(agentsMdPath)) {
|
|
3520
3674
|
items.push({
|
|
3521
3675
|
type: "rule",
|
|
3522
3676
|
platform: "codex",
|
|
@@ -3525,12 +3679,12 @@ function scanLocalState(dir) {
|
|
|
3525
3679
|
path: agentsMdPath
|
|
3526
3680
|
});
|
|
3527
3681
|
}
|
|
3528
|
-
const codexSkillsDir =
|
|
3529
|
-
if (
|
|
3682
|
+
const codexSkillsDir = path9.join(dir, ".agents", "skills");
|
|
3683
|
+
if (fs10.existsSync(codexSkillsDir)) {
|
|
3530
3684
|
try {
|
|
3531
|
-
for (const name of
|
|
3532
|
-
const skillFile =
|
|
3533
|
-
if (
|
|
3685
|
+
for (const name of fs10.readdirSync(codexSkillsDir)) {
|
|
3686
|
+
const skillFile = path9.join(codexSkillsDir, name, "SKILL.md");
|
|
3687
|
+
if (fs10.existsSync(skillFile)) {
|
|
3534
3688
|
items.push({
|
|
3535
3689
|
type: "skill",
|
|
3536
3690
|
platform: "codex",
|
|
@@ -3544,8 +3698,8 @@ function scanLocalState(dir) {
|
|
|
3544
3698
|
warnScanSkip(".agents/skills", error);
|
|
3545
3699
|
}
|
|
3546
3700
|
}
|
|
3547
|
-
const cursorrulesPath =
|
|
3548
|
-
if (
|
|
3701
|
+
const cursorrulesPath = path9.join(dir, ".cursorrules");
|
|
3702
|
+
if (fs10.existsSync(cursorrulesPath)) {
|
|
3549
3703
|
items.push({
|
|
3550
3704
|
type: "rule",
|
|
3551
3705
|
platform: "cursor",
|
|
@@ -3554,10 +3708,10 @@ function scanLocalState(dir) {
|
|
|
3554
3708
|
path: cursorrulesPath
|
|
3555
3709
|
});
|
|
3556
3710
|
}
|
|
3557
|
-
const cursorRulesDir =
|
|
3558
|
-
if (
|
|
3559
|
-
for (const file of
|
|
3560
|
-
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);
|
|
3561
3715
|
items.push({
|
|
3562
3716
|
type: "rule",
|
|
3563
3717
|
platform: "cursor",
|
|
@@ -3567,12 +3721,12 @@ function scanLocalState(dir) {
|
|
|
3567
3721
|
});
|
|
3568
3722
|
}
|
|
3569
3723
|
}
|
|
3570
|
-
const cursorSkillsDir =
|
|
3571
|
-
if (
|
|
3724
|
+
const cursorSkillsDir = path9.join(dir, ".cursor", "skills");
|
|
3725
|
+
if (fs10.existsSync(cursorSkillsDir)) {
|
|
3572
3726
|
try {
|
|
3573
|
-
for (const name of
|
|
3574
|
-
const skillFile =
|
|
3575
|
-
if (
|
|
3727
|
+
for (const name of fs10.readdirSync(cursorSkillsDir)) {
|
|
3728
|
+
const skillFile = path9.join(cursorSkillsDir, name, "SKILL.md");
|
|
3729
|
+
if (fs10.existsSync(skillFile)) {
|
|
3576
3730
|
items.push({
|
|
3577
3731
|
type: "skill",
|
|
3578
3732
|
platform: "cursor",
|
|
@@ -3586,10 +3740,10 @@ function scanLocalState(dir) {
|
|
|
3586
3740
|
warnScanSkip(".cursor/skills", error);
|
|
3587
3741
|
}
|
|
3588
3742
|
}
|
|
3589
|
-
const cursorMcpPath =
|
|
3590
|
-
if (
|
|
3743
|
+
const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
|
|
3744
|
+
if (fs10.existsSync(cursorMcpPath)) {
|
|
3591
3745
|
try {
|
|
3592
|
-
const mcpJson = JSON.parse(
|
|
3746
|
+
const mcpJson = JSON.parse(fs10.readFileSync(cursorMcpPath, "utf-8"));
|
|
3593
3747
|
if (mcpJson.mcpServers) {
|
|
3594
3748
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3595
3749
|
items.push({
|
|
@@ -3608,7 +3762,7 @@ function scanLocalState(dir) {
|
|
|
3608
3762
|
return items;
|
|
3609
3763
|
}
|
|
3610
3764
|
function hashFile(filePath) {
|
|
3611
|
-
const text =
|
|
3765
|
+
const text = fs10.readFileSync(filePath, "utf-8");
|
|
3612
3766
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
3613
3767
|
}
|
|
3614
3768
|
function hashJson(obj) {
|
|
@@ -3621,37 +3775,37 @@ function warnScanSkip(target, error) {
|
|
|
3621
3775
|
function getCursorConfigDir() {
|
|
3622
3776
|
const home = os4.homedir();
|
|
3623
3777
|
if (process.platform === "darwin") {
|
|
3624
|
-
return
|
|
3778
|
+
return path9.join(home, "Library", "Application Support", "Cursor");
|
|
3625
3779
|
}
|
|
3626
3780
|
if (process.platform === "win32") {
|
|
3627
|
-
return
|
|
3781
|
+
return path9.join(home, "AppData", "Roaming", "Cursor");
|
|
3628
3782
|
}
|
|
3629
|
-
return
|
|
3783
|
+
return path9.join(home, ".config", "Cursor");
|
|
3630
3784
|
}
|
|
3631
3785
|
|
|
3632
3786
|
// src/lib/hooks.ts
|
|
3633
3787
|
init_resolve_caliber();
|
|
3634
|
-
import
|
|
3635
|
-
import
|
|
3788
|
+
import fs11 from "fs";
|
|
3789
|
+
import path10 from "path";
|
|
3636
3790
|
import { execSync as execSync8 } from "child_process";
|
|
3637
|
-
var SETTINGS_PATH =
|
|
3791
|
+
var SETTINGS_PATH = path10.join(".claude", "settings.json");
|
|
3638
3792
|
var REFRESH_TAIL = "refresh --quiet";
|
|
3639
3793
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
3640
3794
|
function getHookCommand() {
|
|
3641
3795
|
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3642
3796
|
}
|
|
3643
3797
|
function readSettings() {
|
|
3644
|
-
if (!
|
|
3798
|
+
if (!fs11.existsSync(SETTINGS_PATH)) return {};
|
|
3645
3799
|
try {
|
|
3646
|
-
return JSON.parse(
|
|
3800
|
+
return JSON.parse(fs11.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3647
3801
|
} catch {
|
|
3648
3802
|
return {};
|
|
3649
3803
|
}
|
|
3650
3804
|
}
|
|
3651
3805
|
function writeSettings(settings) {
|
|
3652
|
-
const dir =
|
|
3653
|
-
if (!
|
|
3654
|
-
|
|
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));
|
|
3655
3809
|
}
|
|
3656
3810
|
function findHookIndex(sessionEnd) {
|
|
3657
3811
|
return sessionEnd.findIndex(
|
|
@@ -3717,19 +3871,19 @@ ${PRECOMMIT_END}`;
|
|
|
3717
3871
|
function getGitHooksDir() {
|
|
3718
3872
|
try {
|
|
3719
3873
|
const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3720
|
-
return
|
|
3874
|
+
return path10.join(gitDir, "hooks");
|
|
3721
3875
|
} catch {
|
|
3722
3876
|
return null;
|
|
3723
3877
|
}
|
|
3724
3878
|
}
|
|
3725
3879
|
function getPreCommitPath() {
|
|
3726
3880
|
const hooksDir = getGitHooksDir();
|
|
3727
|
-
return hooksDir ?
|
|
3881
|
+
return hooksDir ? path10.join(hooksDir, "pre-commit") : null;
|
|
3728
3882
|
}
|
|
3729
3883
|
function isPreCommitHookInstalled() {
|
|
3730
3884
|
const hookPath = getPreCommitPath();
|
|
3731
|
-
if (!hookPath || !
|
|
3732
|
-
const content =
|
|
3885
|
+
if (!hookPath || !fs11.existsSync(hookPath)) return false;
|
|
3886
|
+
const content = fs11.readFileSync(hookPath, "utf-8");
|
|
3733
3887
|
return content.includes(PRECOMMIT_START);
|
|
3734
3888
|
}
|
|
3735
3889
|
function installPreCommitHook() {
|
|
@@ -3738,42 +3892,42 @@ function installPreCommitHook() {
|
|
|
3738
3892
|
}
|
|
3739
3893
|
const hookPath = getPreCommitPath();
|
|
3740
3894
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3741
|
-
const hooksDir =
|
|
3742
|
-
if (!
|
|
3895
|
+
const hooksDir = path10.dirname(hookPath);
|
|
3896
|
+
if (!fs11.existsSync(hooksDir)) fs11.mkdirSync(hooksDir, { recursive: true });
|
|
3743
3897
|
let content = "";
|
|
3744
|
-
if (
|
|
3745
|
-
content =
|
|
3898
|
+
if (fs11.existsSync(hookPath)) {
|
|
3899
|
+
content = fs11.readFileSync(hookPath, "utf-8");
|
|
3746
3900
|
if (!content.endsWith("\n")) content += "\n";
|
|
3747
3901
|
content += "\n" + getPrecommitBlock() + "\n";
|
|
3748
3902
|
} else {
|
|
3749
3903
|
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3750
3904
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3905
|
+
fs11.writeFileSync(hookPath, content);
|
|
3906
|
+
fs11.chmodSync(hookPath, 493);
|
|
3753
3907
|
return { installed: true, alreadyInstalled: false };
|
|
3754
3908
|
}
|
|
3755
3909
|
function removePreCommitHook() {
|
|
3756
3910
|
const hookPath = getPreCommitPath();
|
|
3757
|
-
if (!hookPath || !
|
|
3911
|
+
if (!hookPath || !fs11.existsSync(hookPath)) {
|
|
3758
3912
|
return { removed: false, notFound: true };
|
|
3759
3913
|
}
|
|
3760
|
-
let content =
|
|
3914
|
+
let content = fs11.readFileSync(hookPath, "utf-8");
|
|
3761
3915
|
if (!content.includes(PRECOMMIT_START)) {
|
|
3762
3916
|
return { removed: false, notFound: true };
|
|
3763
3917
|
}
|
|
3764
3918
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3765
3919
|
content = content.replace(regex, "\n");
|
|
3766
3920
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3767
|
-
|
|
3921
|
+
fs11.unlinkSync(hookPath);
|
|
3768
3922
|
} else {
|
|
3769
|
-
|
|
3923
|
+
fs11.writeFileSync(hookPath, content);
|
|
3770
3924
|
}
|
|
3771
3925
|
return { removed: true, notFound: false };
|
|
3772
3926
|
}
|
|
3773
3927
|
|
|
3774
3928
|
// src/fingerprint/sources.ts
|
|
3775
|
-
import
|
|
3776
|
-
import
|
|
3929
|
+
import fs12 from "fs";
|
|
3930
|
+
import path11 from "path";
|
|
3777
3931
|
|
|
3778
3932
|
// src/scoring/utils.ts
|
|
3779
3933
|
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
@@ -4092,7 +4246,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
|
|
|
4092
4246
|
var README_CONTENT_LIMIT = 1e3;
|
|
4093
4247
|
var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
|
|
4094
4248
|
function loadSourcesConfig(dir) {
|
|
4095
|
-
const configPath =
|
|
4249
|
+
const configPath = path11.join(dir, ".caliber", "sources.json");
|
|
4096
4250
|
const content = readFileOrNull(configPath);
|
|
4097
4251
|
if (!content) return [];
|
|
4098
4252
|
try {
|
|
@@ -4110,29 +4264,29 @@ function loadSourcesConfig(dir) {
|
|
|
4110
4264
|
}
|
|
4111
4265
|
}
|
|
4112
4266
|
function writeSourcesConfig(dir, sources2) {
|
|
4113
|
-
const configDir =
|
|
4114
|
-
if (!
|
|
4115
|
-
|
|
4267
|
+
const configDir = path11.join(dir, ".caliber");
|
|
4268
|
+
if (!fs12.existsSync(configDir)) {
|
|
4269
|
+
fs12.mkdirSync(configDir, { recursive: true });
|
|
4116
4270
|
}
|
|
4117
|
-
const configPath =
|
|
4118
|
-
|
|
4271
|
+
const configPath = path11.join(configDir, "sources.json");
|
|
4272
|
+
fs12.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
4119
4273
|
}
|
|
4120
4274
|
function detectSourceType(absPath) {
|
|
4121
4275
|
try {
|
|
4122
|
-
return
|
|
4276
|
+
return fs12.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
4123
4277
|
} catch {
|
|
4124
4278
|
return "file";
|
|
4125
4279
|
}
|
|
4126
4280
|
}
|
|
4127
4281
|
function isInsideDir(childPath, parentDir) {
|
|
4128
|
-
const relative2 =
|
|
4129
|
-
return !relative2.startsWith("..") && !
|
|
4282
|
+
const relative2 = path11.relative(parentDir, childPath);
|
|
4283
|
+
return !relative2.startsWith("..") && !path11.isAbsolute(relative2);
|
|
4130
4284
|
}
|
|
4131
4285
|
function resolveAllSources(dir, cliSources, workspaces) {
|
|
4132
4286
|
const seen = /* @__PURE__ */ new Map();
|
|
4133
|
-
const projectRoot =
|
|
4287
|
+
const projectRoot = path11.resolve(dir);
|
|
4134
4288
|
for (const src of cliSources) {
|
|
4135
|
-
const absPath =
|
|
4289
|
+
const absPath = path11.resolve(dir, src);
|
|
4136
4290
|
if (seen.has(absPath)) continue;
|
|
4137
4291
|
const type = detectSourceType(absPath);
|
|
4138
4292
|
seen.set(absPath, {
|
|
@@ -4145,12 +4299,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
4145
4299
|
for (const cfg of configSources) {
|
|
4146
4300
|
if (cfg.type === "url") continue;
|
|
4147
4301
|
if (!cfg.path) continue;
|
|
4148
|
-
const absPath =
|
|
4302
|
+
const absPath = path11.resolve(dir, cfg.path);
|
|
4149
4303
|
if (seen.has(absPath)) continue;
|
|
4150
4304
|
seen.set(absPath, { absPath, config: cfg, origin: "config" });
|
|
4151
4305
|
}
|
|
4152
4306
|
for (const ws of workspaces) {
|
|
4153
|
-
const absPath =
|
|
4307
|
+
const absPath = path11.resolve(dir, ws);
|
|
4154
4308
|
if (seen.has(absPath)) continue;
|
|
4155
4309
|
if (!isInsideDir(absPath, projectRoot)) continue;
|
|
4156
4310
|
seen.set(absPath, {
|
|
@@ -4163,7 +4317,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
4163
4317
|
for (const [absPath, resolved] of seen) {
|
|
4164
4318
|
let stat;
|
|
4165
4319
|
try {
|
|
4166
|
-
stat =
|
|
4320
|
+
stat = fs12.statSync(absPath);
|
|
4167
4321
|
} catch {
|
|
4168
4322
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
4169
4323
|
continue;
|
|
@@ -4192,13 +4346,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
4192
4346
|
if (config.type === "file") {
|
|
4193
4347
|
return collectFileSummary(resolved, projectDir);
|
|
4194
4348
|
}
|
|
4195
|
-
const summaryPath =
|
|
4349
|
+
const summaryPath = path11.join(absPath, ".caliber", "summary.json");
|
|
4196
4350
|
const summaryContent = readFileOrNull(summaryPath);
|
|
4197
4351
|
if (summaryContent) {
|
|
4198
4352
|
try {
|
|
4199
4353
|
const published = JSON.parse(summaryContent);
|
|
4200
4354
|
return {
|
|
4201
|
-
name: published.name ||
|
|
4355
|
+
name: published.name || path11.basename(absPath),
|
|
4202
4356
|
type: "repo",
|
|
4203
4357
|
role: config.role || published.role || "related-repo",
|
|
4204
4358
|
description: config.description || published.description || "",
|
|
@@ -4218,18 +4372,18 @@ function collectRepoSummary(resolved, projectDir) {
|
|
|
4218
4372
|
let topLevelDirs;
|
|
4219
4373
|
let keyFiles;
|
|
4220
4374
|
try {
|
|
4221
|
-
const entries =
|
|
4375
|
+
const entries = fs12.readdirSync(absPath, { withFileTypes: true });
|
|
4222
4376
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
4223
4377
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
4224
4378
|
} catch {
|
|
4225
4379
|
}
|
|
4226
|
-
const claudeMdContent = readFileOrNull(
|
|
4380
|
+
const claudeMdContent = readFileOrNull(path11.join(absPath, "CLAUDE.md"));
|
|
4227
4381
|
const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
|
|
4228
|
-
const readmeContent = readFileOrNull(
|
|
4382
|
+
const readmeContent = readFileOrNull(path11.join(absPath, "README.md"));
|
|
4229
4383
|
const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
|
|
4230
4384
|
const gitRemoteUrl = getGitRemoteUrl(absPath);
|
|
4231
4385
|
return {
|
|
4232
|
-
name: packageName ||
|
|
4386
|
+
name: packageName || path11.basename(absPath),
|
|
4233
4387
|
type: "repo",
|
|
4234
4388
|
role: config.role || "related-repo",
|
|
4235
4389
|
description: config.description || "",
|
|
@@ -4246,7 +4400,7 @@ function collectFileSummary(resolved, projectDir) {
|
|
|
4246
4400
|
const { config, origin, absPath } = resolved;
|
|
4247
4401
|
const content = readFileOrNull(absPath);
|
|
4248
4402
|
return {
|
|
4249
|
-
name:
|
|
4403
|
+
name: path11.basename(absPath),
|
|
4250
4404
|
type: "file",
|
|
4251
4405
|
role: config.role || "reference-doc",
|
|
4252
4406
|
description: config.description || content?.slice(0, 100).split("\n")[0] || "",
|
|
@@ -4889,17 +5043,17 @@ import fs19 from "fs";
|
|
|
4889
5043
|
|
|
4890
5044
|
// src/writers/claude/index.ts
|
|
4891
5045
|
init_pre_commit_block();
|
|
4892
|
-
import
|
|
4893
|
-
import
|
|
5046
|
+
import fs13 from "fs";
|
|
5047
|
+
import path12 from "path";
|
|
4894
5048
|
function writeClaudeConfig(config) {
|
|
4895
5049
|
const written = [];
|
|
4896
|
-
|
|
5050
|
+
fs13.writeFileSync("CLAUDE.md", appendManagedBlocks(config.claudeMd, "claude"));
|
|
4897
5051
|
written.push("CLAUDE.md");
|
|
4898
5052
|
if (config.skills?.length) {
|
|
4899
5053
|
for (const skill of config.skills) {
|
|
4900
|
-
const skillDir =
|
|
4901
|
-
if (!
|
|
4902
|
-
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");
|
|
4903
5057
|
const frontmatter = [
|
|
4904
5058
|
"---",
|
|
4905
5059
|
`name: ${skill.name}`,
|
|
@@ -4907,21 +5061,21 @@ function writeClaudeConfig(config) {
|
|
|
4907
5061
|
"---",
|
|
4908
5062
|
""
|
|
4909
5063
|
].join("\n");
|
|
4910
|
-
|
|
5064
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4911
5065
|
written.push(skillPath);
|
|
4912
5066
|
}
|
|
4913
5067
|
}
|
|
4914
5068
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4915
5069
|
let existingServers = {};
|
|
4916
5070
|
try {
|
|
4917
|
-
if (
|
|
4918
|
-
const existing = JSON.parse(
|
|
5071
|
+
if (fs13.existsSync(".mcp.json")) {
|
|
5072
|
+
const existing = JSON.parse(fs13.readFileSync(".mcp.json", "utf-8"));
|
|
4919
5073
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4920
5074
|
}
|
|
4921
5075
|
} catch {
|
|
4922
5076
|
}
|
|
4923
5077
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4924
|
-
|
|
5078
|
+
fs13.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4925
5079
|
written.push(".mcp.json");
|
|
4926
5080
|
}
|
|
4927
5081
|
return written;
|
|
@@ -4929,30 +5083,31 @@ function writeClaudeConfig(config) {
|
|
|
4929
5083
|
|
|
4930
5084
|
// src/writers/cursor/index.ts
|
|
4931
5085
|
init_pre_commit_block();
|
|
4932
|
-
import
|
|
4933
|
-
import
|
|
5086
|
+
import fs14 from "fs";
|
|
5087
|
+
import path13 from "path";
|
|
4934
5088
|
function writeCursorConfig(config) {
|
|
4935
5089
|
const written = [];
|
|
4936
5090
|
if (config.cursorrules) {
|
|
4937
|
-
|
|
5091
|
+
fs14.writeFileSync(".cursorrules", config.cursorrules);
|
|
4938
5092
|
written.push(".cursorrules");
|
|
4939
5093
|
}
|
|
4940
5094
|
const preCommitRule = getCursorPreCommitRule();
|
|
4941
5095
|
const learningsRule = getCursorLearningsRule();
|
|
4942
5096
|
const syncRule = getCursorSyncRule();
|
|
4943
|
-
const
|
|
4944
|
-
const
|
|
4945
|
-
|
|
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 });
|
|
4946
5101
|
for (const rule of allRules) {
|
|
4947
|
-
const rulePath =
|
|
4948
|
-
|
|
5102
|
+
const rulePath = path13.join(rulesDir, rule.filename);
|
|
5103
|
+
fs14.writeFileSync(rulePath, rule.content);
|
|
4949
5104
|
written.push(rulePath);
|
|
4950
5105
|
}
|
|
4951
5106
|
if (config.skills?.length) {
|
|
4952
5107
|
for (const skill of config.skills) {
|
|
4953
|
-
const skillDir =
|
|
4954
|
-
if (!
|
|
4955
|
-
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");
|
|
4956
5111
|
const frontmatter = [
|
|
4957
5112
|
"---",
|
|
4958
5113
|
`name: ${skill.name}`,
|
|
@@ -4960,24 +5115,24 @@ function writeCursorConfig(config) {
|
|
|
4960
5115
|
"---",
|
|
4961
5116
|
""
|
|
4962
5117
|
].join("\n");
|
|
4963
|
-
|
|
5118
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4964
5119
|
written.push(skillPath);
|
|
4965
5120
|
}
|
|
4966
5121
|
}
|
|
4967
5122
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4968
5123
|
const cursorDir = ".cursor";
|
|
4969
|
-
if (!
|
|
4970
|
-
const mcpPath =
|
|
5124
|
+
if (!fs14.existsSync(cursorDir)) fs14.mkdirSync(cursorDir, { recursive: true });
|
|
5125
|
+
const mcpPath = path13.join(cursorDir, "mcp.json");
|
|
4971
5126
|
let existingServers = {};
|
|
4972
5127
|
try {
|
|
4973
|
-
if (
|
|
4974
|
-
const existing = JSON.parse(
|
|
5128
|
+
if (fs14.existsSync(mcpPath)) {
|
|
5129
|
+
const existing = JSON.parse(fs14.readFileSync(mcpPath, "utf-8"));
|
|
4975
5130
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4976
5131
|
}
|
|
4977
5132
|
} catch {
|
|
4978
5133
|
}
|
|
4979
5134
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4980
|
-
|
|
5135
|
+
fs14.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4981
5136
|
written.push(mcpPath);
|
|
4982
5137
|
}
|
|
4983
5138
|
return written;
|
|
@@ -4985,17 +5140,17 @@ function writeCursorConfig(config) {
|
|
|
4985
5140
|
|
|
4986
5141
|
// src/writers/codex/index.ts
|
|
4987
5142
|
init_pre_commit_block();
|
|
4988
|
-
import
|
|
4989
|
-
import
|
|
5143
|
+
import fs15 from "fs";
|
|
5144
|
+
import path14 from "path";
|
|
4990
5145
|
function writeCodexConfig(config) {
|
|
4991
5146
|
const written = [];
|
|
4992
|
-
|
|
5147
|
+
fs15.writeFileSync("AGENTS.md", appendManagedBlocks(config.agentsMd, "codex"));
|
|
4993
5148
|
written.push("AGENTS.md");
|
|
4994
5149
|
if (config.skills?.length) {
|
|
4995
5150
|
for (const skill of config.skills) {
|
|
4996
|
-
const skillDir =
|
|
4997
|
-
if (!
|
|
4998
|
-
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");
|
|
4999
5154
|
const frontmatter = [
|
|
5000
5155
|
"---",
|
|
5001
5156
|
`name: ${skill.name}`,
|
|
@@ -5003,7 +5158,7 @@ function writeCodexConfig(config) {
|
|
|
5003
5158
|
"---",
|
|
5004
5159
|
""
|
|
5005
5160
|
].join("\n");
|
|
5006
|
-
|
|
5161
|
+
fs15.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5007
5162
|
written.push(skillPath);
|
|
5008
5163
|
}
|
|
5009
5164
|
}
|
|
@@ -5012,20 +5167,20 @@ function writeCodexConfig(config) {
|
|
|
5012
5167
|
|
|
5013
5168
|
// src/writers/github-copilot/index.ts
|
|
5014
5169
|
init_pre_commit_block();
|
|
5015
|
-
import
|
|
5016
|
-
import
|
|
5170
|
+
import fs16 from "fs";
|
|
5171
|
+
import path15 from "path";
|
|
5017
5172
|
function writeGithubCopilotConfig(config) {
|
|
5018
5173
|
const written = [];
|
|
5019
5174
|
if (config.instructions) {
|
|
5020
|
-
|
|
5021
|
-
|
|
5175
|
+
fs16.mkdirSync(".github", { recursive: true });
|
|
5176
|
+
fs16.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendManagedBlocks(config.instructions, "copilot"));
|
|
5022
5177
|
written.push(".github/copilot-instructions.md");
|
|
5023
5178
|
}
|
|
5024
5179
|
if (config.instructionFiles?.length) {
|
|
5025
|
-
const instructionsDir =
|
|
5026
|
-
|
|
5180
|
+
const instructionsDir = path15.join(".github", "instructions");
|
|
5181
|
+
fs16.mkdirSync(instructionsDir, { recursive: true });
|
|
5027
5182
|
for (const file of config.instructionFiles) {
|
|
5028
|
-
|
|
5183
|
+
fs16.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
|
|
5029
5184
|
written.push(`.github/instructions/${file.filename}`);
|
|
5030
5185
|
}
|
|
5031
5186
|
}
|
|
@@ -5033,30 +5188,30 @@ function writeGithubCopilotConfig(config) {
|
|
|
5033
5188
|
}
|
|
5034
5189
|
|
|
5035
5190
|
// src/writers/backup.ts
|
|
5036
|
-
import
|
|
5037
|
-
import
|
|
5191
|
+
import fs17 from "fs";
|
|
5192
|
+
import path16 from "path";
|
|
5038
5193
|
function createBackup(files) {
|
|
5039
5194
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5040
|
-
const backupDir =
|
|
5195
|
+
const backupDir = path16.join(BACKUPS_DIR, timestamp);
|
|
5041
5196
|
for (const file of files) {
|
|
5042
|
-
if (!
|
|
5043
|
-
const dest =
|
|
5044
|
-
const destDir =
|
|
5045
|
-
if (!
|
|
5046
|
-
|
|
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 });
|
|
5047
5202
|
}
|
|
5048
|
-
|
|
5203
|
+
fs17.copyFileSync(file, dest);
|
|
5049
5204
|
}
|
|
5050
5205
|
return backupDir;
|
|
5051
5206
|
}
|
|
5052
5207
|
function restoreBackup(backupDir, file) {
|
|
5053
|
-
const backupFile =
|
|
5054
|
-
if (!
|
|
5055
|
-
const destDir =
|
|
5056
|
-
if (!
|
|
5057
|
-
|
|
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 });
|
|
5058
5213
|
}
|
|
5059
|
-
|
|
5214
|
+
fs17.copyFileSync(backupFile, file);
|
|
5060
5215
|
return true;
|
|
5061
5216
|
}
|
|
5062
5217
|
|
|
@@ -9234,8 +9389,9 @@ async function initCommand(options) {
|
|
|
9234
9389
|
if (options.agent) {
|
|
9235
9390
|
targetAgent = options.agent;
|
|
9236
9391
|
} else if (options.autoApprove) {
|
|
9237
|
-
|
|
9238
|
-
|
|
9392
|
+
const detected = detectAgents(process.cwd());
|
|
9393
|
+
targetAgent = detected.length > 0 ? detected : ["claude"];
|
|
9394
|
+
log(options.verbose, `Auto-approve: using ${targetAgent.join(", ")}`);
|
|
9239
9395
|
} else {
|
|
9240
9396
|
const detected = detectAgents(process.cwd());
|
|
9241
9397
|
targetAgent = await promptAgent(detected.length > 0 ? detected : void 0);
|
|
@@ -9316,12 +9472,11 @@ async function initCommand(options) {
|
|
|
9316
9472
|
}
|
|
9317
9473
|
if (skipGeneration) {
|
|
9318
9474
|
const {
|
|
9319
|
-
|
|
9320
|
-
appendLearningsBlock: appendLearningsBlock2,
|
|
9321
|
-
appendSyncBlock: appendSyncBlock2,
|
|
9475
|
+
appendManagedBlocks: appendManagedBlocks2,
|
|
9322
9476
|
getCursorPreCommitRule: getCursorPreCommitRule2,
|
|
9323
9477
|
getCursorLearningsRule: getCursorLearningsRule2,
|
|
9324
|
-
getCursorSyncRule: getCursorSyncRule2
|
|
9478
|
+
getCursorSyncRule: getCursorSyncRule2,
|
|
9479
|
+
getCursorSetupRule: getCursorSetupRule2
|
|
9325
9480
|
} = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
|
|
9326
9481
|
const claudeMdPath = "CLAUDE.md";
|
|
9327
9482
|
let claudeContent = "";
|
|
@@ -9333,7 +9488,7 @@ async function initCommand(options) {
|
|
|
9333
9488
|
claudeContent = `# ${path26.basename(process.cwd())}
|
|
9334
9489
|
`;
|
|
9335
9490
|
}
|
|
9336
|
-
const updatedClaude =
|
|
9491
|
+
const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
|
|
9337
9492
|
if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
|
|
9338
9493
|
fs33.writeFileSync(claudeMdPath, updatedClaude);
|
|
9339
9494
|
console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
|
|
@@ -9341,7 +9496,7 @@ async function initCommand(options) {
|
|
|
9341
9496
|
if (targetAgent.includes("cursor")) {
|
|
9342
9497
|
const rulesDir = path26.join(".cursor", "rules");
|
|
9343
9498
|
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9344
|
-
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2()]) {
|
|
9499
|
+
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
|
|
9345
9500
|
fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
|
|
9346
9501
|
}
|
|
9347
9502
|
console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
|
|
@@ -9358,7 +9513,7 @@ async function initCommand(options) {
|
|
|
9358
9513
|
copilotContent = `# ${path26.basename(process.cwd())}
|
|
9359
9514
|
`;
|
|
9360
9515
|
}
|
|
9361
|
-
const updatedCopilot =
|
|
9516
|
+
const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
|
|
9362
9517
|
if (updatedCopilot !== copilotContent) {
|
|
9363
9518
|
fs33.writeFileSync(copilotPath, updatedCopilot);
|
|
9364
9519
|
console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
|
|
@@ -10137,14 +10292,24 @@ var MAX_DIFF_BYTES = 1e5;
|
|
|
10137
10292
|
var DOC_PATTERNS = [
|
|
10138
10293
|
"CLAUDE.md",
|
|
10139
10294
|
"README.md",
|
|
10295
|
+
"AGENTS.md",
|
|
10140
10296
|
".cursorrules",
|
|
10141
10297
|
".cursor/rules/",
|
|
10298
|
+
".cursor/skills/",
|
|
10142
10299
|
".claude/skills/",
|
|
10300
|
+
".agents/skills/",
|
|
10301
|
+
".github/copilot-instructions.md",
|
|
10302
|
+
".github/instructions/",
|
|
10143
10303
|
"CALIBER_LEARNINGS.md"
|
|
10144
10304
|
];
|
|
10145
10305
|
function excludeArgs() {
|
|
10146
10306
|
return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
|
|
10147
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
|
+
}
|
|
10148
10313
|
function safeExec(cmd) {
|
|
10149
10314
|
try {
|
|
10150
10315
|
return execSync15(cmd, {
|
|
@@ -10190,9 +10355,9 @@ function collectDiff(lastSha) {
|
|
|
10190
10355
|
const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
|
|
10191
10356
|
if (totalSize > MAX_DIFF_BYTES) {
|
|
10192
10357
|
const ratio = MAX_DIFF_BYTES / totalSize;
|
|
10193
|
-
committedDiff = committedDiff
|
|
10194
|
-
stagedDiff = stagedDiff
|
|
10195
|
-
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));
|
|
10196
10361
|
}
|
|
10197
10362
|
const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
|
|
10198
10363
|
const parts = [];
|
|
@@ -10211,13 +10376,17 @@ import path28 from "path";
|
|
|
10211
10376
|
function writeRefreshDocs(docs) {
|
|
10212
10377
|
const written = [];
|
|
10213
10378
|
if (docs.claudeMd) {
|
|
10214
|
-
fs36.writeFileSync("CLAUDE.md",
|
|
10379
|
+
fs36.writeFileSync("CLAUDE.md", appendManagedBlocks(docs.claudeMd, "claude"));
|
|
10215
10380
|
written.push("CLAUDE.md");
|
|
10216
10381
|
}
|
|
10217
10382
|
if (docs.readmeMd) {
|
|
10218
10383
|
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
10219
10384
|
written.push("README.md");
|
|
10220
10385
|
}
|
|
10386
|
+
if (docs.agentsMd) {
|
|
10387
|
+
fs36.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
|
|
10388
|
+
written.push("AGENTS.md");
|
|
10389
|
+
}
|
|
10221
10390
|
if (docs.cursorrules) {
|
|
10222
10391
|
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
10223
10392
|
written.push(".cursorrules");
|
|
@@ -10240,7 +10409,7 @@ function writeRefreshDocs(docs) {
|
|
|
10240
10409
|
}
|
|
10241
10410
|
if (docs.copilotInstructions) {
|
|
10242
10411
|
fs36.mkdirSync(".github", { recursive: true });
|
|
10243
|
-
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"),
|
|
10412
|
+
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendManagedBlocks(docs.copilotInstructions, "copilot"));
|
|
10244
10413
|
written.push(".github/copilot-instructions.md");
|
|
10245
10414
|
}
|
|
10246
10415
|
if (docs.copilotInstructionFiles) {
|
|
@@ -10259,10 +10428,18 @@ init_config();
|
|
|
10259
10428
|
async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
|
|
10260
10429
|
const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
|
|
10261
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));
|
|
10262
10439
|
const raw = await llmCall({
|
|
10263
10440
|
system: REFRESH_SYSTEM_PROMPT,
|
|
10264
10441
|
prompt,
|
|
10265
|
-
maxTokens
|
|
10442
|
+
maxTokens,
|
|
10266
10443
|
...fastModel ? { model: fastModel } : {}
|
|
10267
10444
|
});
|
|
10268
10445
|
return parseJsonResponse(raw);
|
|
@@ -10321,6 +10498,21 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
10321
10498
|
parts.push(rule.content);
|
|
10322
10499
|
}
|
|
10323
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
|
+
}
|
|
10324
10516
|
if (learnedSection) {
|
|
10325
10517
|
parts.push("\n--- Learned Patterns (from session learning) ---");
|
|
10326
10518
|
parts.push("Consider these accumulated learnings when deciding what to update:");
|