@rely-ai/caliber 1.31.0-dev.1774815271 → 1.32.0-dev.1774815754
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -70
- package/dist/bin.js +1076 -994
- package/package.json +24 -5
package/dist/bin.js
CHANGED
|
@@ -9,6 +9,155 @@ 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 path6 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 = path6.join(os2.homedir(), ".caliber");
|
|
129
|
+
CONFIG_FILE = path6.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
|
+
|
|
12
161
|
// src/lib/resolve-caliber.ts
|
|
13
162
|
var resolve_caliber_exports = {};
|
|
14
163
|
__export(resolve_caliber_exports, {
|
|
@@ -17,8 +166,8 @@ __export(resolve_caliber_exports, {
|
|
|
17
166
|
resetResolvedCaliber: () => resetResolvedCaliber,
|
|
18
167
|
resolveCaliber: () => resolveCaliber
|
|
19
168
|
});
|
|
20
|
-
import
|
|
21
|
-
import { execSync as
|
|
169
|
+
import fs6 from "fs";
|
|
170
|
+
import { execSync as execSync4 } from "child_process";
|
|
22
171
|
function resolveCaliber() {
|
|
23
172
|
if (_resolved) return _resolved;
|
|
24
173
|
const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
|
|
@@ -28,7 +177,7 @@ function resolveCaliber() {
|
|
|
28
177
|
}
|
|
29
178
|
try {
|
|
30
179
|
const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
|
|
31
|
-
|
|
180
|
+
execSync4(whichCmd, {
|
|
32
181
|
encoding: "utf-8",
|
|
33
182
|
stdio: ["pipe", "pipe", "pipe"]
|
|
34
183
|
});
|
|
@@ -37,7 +186,7 @@ function resolveCaliber() {
|
|
|
37
186
|
} catch {
|
|
38
187
|
}
|
|
39
188
|
const binPath = process.argv[1];
|
|
40
|
-
if (binPath && /caliber/.test(binPath) &&
|
|
189
|
+
if (binPath && /caliber/.test(binPath) && fs6.existsSync(binPath)) {
|
|
41
190
|
_resolved = binPath;
|
|
42
191
|
return _resolved;
|
|
43
192
|
}
|
|
@@ -65,65 +214,283 @@ var init_resolve_caliber = __esm({
|
|
|
65
214
|
}
|
|
66
215
|
});
|
|
67
216
|
|
|
68
|
-
// src/
|
|
69
|
-
var
|
|
70
|
-
__export(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
231
|
});
|
|
80
|
-
import fs3 from "fs";
|
|
81
|
-
import path3 from "path";
|
|
82
|
-
function buildSkillContent(skill) {
|
|
83
|
-
const frontmatter = `---
|
|
84
|
-
name: ${skill.name}
|
|
85
|
-
description: ${skill.description}
|
|
86
|
-
---
|
|
87
232
|
|
|
88
|
-
|
|
89
|
-
|
|
233
|
+
// src/writers/pre-commit-block.ts
|
|
234
|
+
var pre_commit_block_exports = {};
|
|
235
|
+
__export(pre_commit_block_exports, {
|
|
236
|
+
appendLearningsBlock: () => appendLearningsBlock,
|
|
237
|
+
appendManagedBlocks: () => appendManagedBlocks,
|
|
238
|
+
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
239
|
+
appendSyncBlock: () => appendSyncBlock,
|
|
240
|
+
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
241
|
+
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
242
|
+
getCursorSetupRule: () => getCursorSetupRule,
|
|
243
|
+
getCursorSyncRule: () => getCursorSyncRule,
|
|
244
|
+
hasLearningsBlock: () => hasLearningsBlock,
|
|
245
|
+
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
246
|
+
hasSyncBlock: () => hasSyncBlock,
|
|
247
|
+
stripManagedBlocks: () => stripManagedBlocks
|
|
248
|
+
});
|
|
249
|
+
function getCaliberNotFoundFallback(platform, bin) {
|
|
250
|
+
switch (platform) {
|
|
251
|
+
case "claude":
|
|
252
|
+
return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
|
|
253
|
+
case "codex":
|
|
254
|
+
return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
|
|
255
|
+
case "copilot":
|
|
256
|
+
return `If \`${bin}\` is not found, tell the developer to set up Caliber by running \`/setup-caliber\` in Claude Code or Cursor. Alternatively, they can run these commands in their terminal:
|
|
257
|
+
\`\`\`
|
|
258
|
+
npx @rely-ai/caliber hooks --install
|
|
259
|
+
npx @rely-ai/caliber refresh
|
|
260
|
+
\`\`\``;
|
|
261
|
+
}
|
|
90
262
|
}
|
|
91
|
-
function
|
|
263
|
+
function getPreCommitBlock(platform = "claude") {
|
|
92
264
|
const bin = resolveCaliber();
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
Search the public skill registry for community-contributed skills
|
|
96
|
-
relevant to the user's current task and install them into this project.
|
|
97
|
-
|
|
98
|
-
## Instructions
|
|
265
|
+
return `${BLOCK_START}
|
|
266
|
+
## Before Committing
|
|
99
267
|
|
|
100
|
-
|
|
101
|
-
user's request that might have community skills available
|
|
102
|
-
2. Ask the user: "Would you like me to search for community skills
|
|
103
|
-
for [identified technologies]?"
|
|
104
|
-
3. If the user agrees, run:
|
|
105
|
-
\`\`\`bash
|
|
106
|
-
${bin} skills --query "<relevant terms>"
|
|
107
|
-
\`\`\`
|
|
108
|
-
This outputs the top 5 matching skills with scores and descriptions.
|
|
109
|
-
4. Present the results to the user and ask which ones to install
|
|
110
|
-
5. Install the selected skills:
|
|
111
|
-
\`\`\`bash
|
|
112
|
-
${bin} skills --install <slug1>,<slug2>
|
|
113
|
-
\`\`\`
|
|
114
|
-
6. Read the installed SKILL.md files to load them into your current
|
|
115
|
-
context so you can use them immediately in this session
|
|
116
|
-
7. Summarize what was installed and continue with the user's task
|
|
268
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
117
269
|
|
|
118
|
-
|
|
270
|
+
First, check if the pre-commit hook is already installed:
|
|
271
|
+
\`\`\`bash
|
|
272
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
273
|
+
\`\`\`
|
|
119
274
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
275
|
+
- 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."
|
|
276
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
277
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
278
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
279
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
280
|
+
|
|
281
|
+
${getCaliberNotFoundFallback(platform, bin)}
|
|
282
|
+
${BLOCK_END}`;
|
|
283
|
+
}
|
|
284
|
+
function getCursorRuleContent() {
|
|
285
|
+
const bin = resolveCaliber();
|
|
286
|
+
return `---
|
|
287
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
288
|
+
alwaysApply: true
|
|
289
|
+
---
|
|
290
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
291
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
292
|
+
|
|
293
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
294
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
295
|
+
|
|
296
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
function hasPreCommitBlock(content) {
|
|
300
|
+
return content.includes(BLOCK_START);
|
|
301
|
+
}
|
|
302
|
+
function appendPreCommitBlock(content, platform = "claude") {
|
|
303
|
+
if (hasPreCommitBlock(content)) return content;
|
|
304
|
+
const trimmed = content.trimEnd();
|
|
305
|
+
return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
|
|
306
|
+
}
|
|
307
|
+
function getCursorPreCommitRule() {
|
|
308
|
+
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
309
|
+
}
|
|
310
|
+
function hasLearningsBlock(content) {
|
|
311
|
+
return content.includes(LEARNINGS_BLOCK_START);
|
|
312
|
+
}
|
|
313
|
+
function appendLearningsBlock(content) {
|
|
314
|
+
if (hasLearningsBlock(content)) return content;
|
|
315
|
+
const trimmed = content.trimEnd();
|
|
316
|
+
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
317
|
+
}
|
|
318
|
+
function getCursorLearningsRule() {
|
|
319
|
+
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
320
|
+
}
|
|
321
|
+
function getSyncSetupInstruction(platform) {
|
|
322
|
+
switch (platform) {
|
|
323
|
+
case "claude":
|
|
324
|
+
return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
|
|
325
|
+
case "codex":
|
|
326
|
+
return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
|
|
327
|
+
case "copilot":
|
|
328
|
+
return `If the pre-commit hook is not set up, the developer should run \`/setup-caliber\` in Claude Code or Cursor for automated setup. Alternatively, run in terminal:
|
|
329
|
+
\`\`\`bash
|
|
330
|
+
npx @rely-ai/caliber hooks --install
|
|
331
|
+
npx @rely-ai/caliber refresh
|
|
332
|
+
git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
333
|
+
\`\`\``;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function getSyncBlock(platform = "claude") {
|
|
337
|
+
const bin = resolveCaliber();
|
|
338
|
+
return `${SYNC_BLOCK_START}
|
|
339
|
+
## Context Sync
|
|
340
|
+
|
|
341
|
+
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.
|
|
342
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
343
|
+
${getSyncSetupInstruction(platform)}
|
|
344
|
+
${SYNC_BLOCK_END}`;
|
|
345
|
+
}
|
|
346
|
+
function hasSyncBlock(content) {
|
|
347
|
+
return content.includes(SYNC_BLOCK_START);
|
|
348
|
+
}
|
|
349
|
+
function appendSyncBlock(content, platform = "claude") {
|
|
350
|
+
if (hasSyncBlock(content)) return content;
|
|
351
|
+
const trimmed = content.trimEnd();
|
|
352
|
+
return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
|
|
353
|
+
}
|
|
354
|
+
function appendManagedBlocks(content, platform = "claude") {
|
|
355
|
+
return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
|
|
356
|
+
}
|
|
357
|
+
function getCursorSyncContent() {
|
|
358
|
+
const bin = resolveCaliber();
|
|
359
|
+
return `---
|
|
360
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
361
|
+
alwaysApply: true
|
|
362
|
+
---
|
|
363
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
364
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
365
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
366
|
+
`;
|
|
367
|
+
}
|
|
368
|
+
function getCursorSyncRule() {
|
|
369
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
370
|
+
}
|
|
371
|
+
function getCursorSetupContent() {
|
|
372
|
+
return `---
|
|
373
|
+
description: Check if Caliber agent config sync is set up and offer installation on new sessions
|
|
374
|
+
alwaysApply: true
|
|
375
|
+
---
|
|
376
|
+
This project uses Caliber to keep AI agent configs in sync automatically.
|
|
377
|
+
At the start of each session, silently check if sync is active:
|
|
378
|
+
|
|
379
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
|
|
380
|
+
|
|
381
|
+
If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
|
|
382
|
+
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."
|
|
383
|
+
|
|
384
|
+
If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
function getCursorSetupRule() {
|
|
388
|
+
return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
|
|
389
|
+
}
|
|
390
|
+
function stripManagedBlocks(content) {
|
|
391
|
+
let result = content;
|
|
392
|
+
for (const [start, end] of MANAGED_BLOCK_PAIRS) {
|
|
393
|
+
const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
|
|
394
|
+
result = result.replace(regex, "\n");
|
|
395
|
+
}
|
|
396
|
+
return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
397
|
+
}
|
|
398
|
+
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, MANAGED_BLOCK_PAIRS;
|
|
399
|
+
var init_pre_commit_block = __esm({
|
|
400
|
+
"src/writers/pre-commit-block.ts"() {
|
|
401
|
+
"use strict";
|
|
402
|
+
init_resolve_caliber();
|
|
403
|
+
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
404
|
+
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
405
|
+
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
406
|
+
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
407
|
+
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
408
|
+
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
409
|
+
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
410
|
+
## Session Learnings
|
|
411
|
+
|
|
412
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
413
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
414
|
+
${LEARNINGS_BLOCK_END}`;
|
|
415
|
+
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
416
|
+
CURSOR_LEARNINGS_CONTENT = `---
|
|
417
|
+
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
418
|
+
alwaysApply: true
|
|
419
|
+
---
|
|
420
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
421
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
422
|
+
`;
|
|
423
|
+
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
424
|
+
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
425
|
+
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
426
|
+
CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
|
|
427
|
+
MANAGED_BLOCK_PAIRS = [
|
|
428
|
+
[BLOCK_START, BLOCK_END],
|
|
429
|
+
[LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END],
|
|
430
|
+
[SYNC_BLOCK_START, SYNC_BLOCK_END]
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// src/lib/builtin-skills.ts
|
|
436
|
+
var builtin_skills_exports = {};
|
|
437
|
+
__export(builtin_skills_exports, {
|
|
438
|
+
BUILTIN_SKILLS: () => BUILTIN_SKILLS,
|
|
439
|
+
BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
|
|
440
|
+
FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
|
|
441
|
+
PLATFORM_CONFIGS: () => PLATFORM_CONFIGS,
|
|
442
|
+
SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
|
|
443
|
+
SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
|
|
444
|
+
buildSkillContent: () => buildSkillContent,
|
|
445
|
+
ensureBuiltinSkills: () => ensureBuiltinSkills
|
|
446
|
+
});
|
|
447
|
+
import fs17 from "fs";
|
|
448
|
+
import path17 from "path";
|
|
449
|
+
function buildSkillContent(skill) {
|
|
450
|
+
const frontmatter = `---
|
|
451
|
+
name: ${skill.name}
|
|
452
|
+
description: ${skill.description}
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
`;
|
|
456
|
+
return frontmatter + skill.content;
|
|
457
|
+
}
|
|
458
|
+
function getFindSkillsContent() {
|
|
459
|
+
const bin = resolveCaliber();
|
|
460
|
+
return `# Find Skills
|
|
461
|
+
|
|
462
|
+
Search the public skill registry for community-contributed skills
|
|
463
|
+
relevant to the user's current task and install them into this project.
|
|
464
|
+
|
|
465
|
+
## Instructions
|
|
466
|
+
|
|
467
|
+
1. Identify the key technologies, frameworks, or task types from the
|
|
468
|
+
user's request that might have community skills available
|
|
469
|
+
2. Ask the user: "Would you like me to search for community skills
|
|
470
|
+
for [identified technologies]?"
|
|
471
|
+
3. If the user agrees, run:
|
|
472
|
+
\`\`\`bash
|
|
473
|
+
${bin} skills --query "<relevant terms>"
|
|
474
|
+
\`\`\`
|
|
475
|
+
This outputs the top 5 matching skills with scores and descriptions.
|
|
476
|
+
4. Present the results to the user and ask which ones to install
|
|
477
|
+
5. Install the selected skills:
|
|
478
|
+
\`\`\`bash
|
|
479
|
+
${bin} skills --install <slug1>,<slug2>
|
|
480
|
+
\`\`\`
|
|
481
|
+
6. Read the installed SKILL.md files to load them into your current
|
|
482
|
+
context so you can use them immediately in this session
|
|
483
|
+
7. Summarize what was installed and continue with the user's task
|
|
484
|
+
|
|
485
|
+
## Examples
|
|
486
|
+
|
|
487
|
+
User: "let's build a web app using React"
|
|
488
|
+
-> "I notice you want to work with React. Would you like me to search
|
|
489
|
+
for community skills that could help with React development?"
|
|
490
|
+
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
491
|
+
-> Show the user the results, ask which to install
|
|
492
|
+
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
493
|
+
-> Read the installed files and continue
|
|
127
494
|
|
|
128
495
|
User: "help me set up Docker for this project"
|
|
129
496
|
-> "Would you like me to search for Docker-related skills?"
|
|
@@ -411,427 +778,60 @@ From now on, every commit keeps all your agent configs in sync automatically.
|
|
|
411
778
|
function ensureBuiltinSkills() {
|
|
412
779
|
const written = [];
|
|
413
780
|
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
414
|
-
if (!
|
|
781
|
+
if (!fs17.existsSync(platformDir)) continue;
|
|
415
782
|
for (const skill of BUILTIN_SKILLS) {
|
|
416
|
-
const skillPath =
|
|
417
|
-
|
|
418
|
-
|
|
783
|
+
const skillPath = path17.join(skillsDir, skill.name, "SKILL.md");
|
|
784
|
+
fs17.mkdirSync(path17.dirname(skillPath), { recursive: true });
|
|
785
|
+
fs17.writeFileSync(skillPath, buildSkillContent(skill));
|
|
419
786
|
written.push(skillPath);
|
|
420
787
|
}
|
|
421
788
|
}
|
|
422
789
|
return written;
|
|
423
|
-
}
|
|
424
|
-
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
|
|
425
|
-
var init_builtin_skills = __esm({
|
|
426
|
-
"src/lib/builtin-skills.ts"() {
|
|
427
|
-
"use strict";
|
|
428
|
-
init_resolve_caliber();
|
|
429
|
-
FIND_SKILLS_SKILL = {
|
|
430
|
-
name: "find-skills",
|
|
431
|
-
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.",
|
|
432
|
-
get content() {
|
|
433
|
-
return getFindSkillsContent();
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
SAVE_LEARNING_SKILL = {
|
|
437
|
-
name: "save-learning",
|
|
438
|
-
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.",
|
|
439
|
-
get content() {
|
|
440
|
-
return getSaveLearningContent();
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
SETUP_CALIBER_SKILL = {
|
|
444
|
-
name: "setup-caliber",
|
|
445
|
-
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.",
|
|
446
|
-
get content() {
|
|
447
|
-
return getSetupCaliberContent();
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
451
|
-
PLATFORM_CONFIGS = [
|
|
452
|
-
{ platformDir: ".claude", skillsDir: path3.join(".claude", "skills") },
|
|
453
|
-
{ platformDir: ".cursor", skillsDir: path3.join(".cursor", "skills") },
|
|
454
|
-
{ platformDir: ".agents", skillsDir: path3.join(".agents", "skills") }
|
|
455
|
-
];
|
|
456
|
-
BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// src/writers/pre-commit-block.ts
|
|
461
|
-
var pre_commit_block_exports = {};
|
|
462
|
-
__export(pre_commit_block_exports, {
|
|
463
|
-
appendLearningsBlock: () => appendLearningsBlock,
|
|
464
|
-
appendManagedBlocks: () => appendManagedBlocks,
|
|
465
|
-
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
466
|
-
appendSyncBlock: () => appendSyncBlock,
|
|
467
|
-
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
468
|
-
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
469
|
-
getCursorSetupRule: () => getCursorSetupRule,
|
|
470
|
-
getCursorSyncRule: () => getCursorSyncRule,
|
|
471
|
-
hasLearningsBlock: () => hasLearningsBlock,
|
|
472
|
-
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
473
|
-
hasSyncBlock: () => hasSyncBlock,
|
|
474
|
-
stripManagedBlocks: () => stripManagedBlocks
|
|
475
|
-
});
|
|
476
|
-
function getCaliberNotFoundFallback(platform, bin) {
|
|
477
|
-
switch (platform) {
|
|
478
|
-
case "claude":
|
|
479
|
-
return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
|
|
480
|
-
case "codex":
|
|
481
|
-
return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
|
|
482
|
-
case "copilot":
|
|
483
|
-
return `If \`${bin}\` is not found, tell the developer to set up Caliber by running \`/setup-caliber\` in Claude Code or Cursor. Alternatively, they can run these commands in their terminal:
|
|
484
|
-
\`\`\`
|
|
485
|
-
npx @rely-ai/caliber hooks --install
|
|
486
|
-
npx @rely-ai/caliber refresh
|
|
487
|
-
\`\`\``;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function getPreCommitBlock(platform = "claude") {
|
|
491
|
-
const bin = resolveCaliber();
|
|
492
|
-
return `${BLOCK_START}
|
|
493
|
-
## Before Committing
|
|
494
|
-
|
|
495
|
-
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
496
|
-
|
|
497
|
-
First, check if the pre-commit hook is already installed:
|
|
498
|
-
\`\`\`bash
|
|
499
|
-
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
500
|
-
\`\`\`
|
|
501
|
-
|
|
502
|
-
- 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."
|
|
503
|
-
- If **no-hook**: run Caliber manually before committing:
|
|
504
|
-
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
505
|
-
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
506
|
-
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
507
|
-
|
|
508
|
-
${getCaliberNotFoundFallback(platform, bin)}
|
|
509
|
-
${BLOCK_END}`;
|
|
510
|
-
}
|
|
511
|
-
function getCursorRuleContent() {
|
|
512
|
-
const bin = resolveCaliber();
|
|
513
|
-
return `---
|
|
514
|
-
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
515
|
-
alwaysApply: true
|
|
516
|
-
---
|
|
517
|
-
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
518
|
-
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
519
|
-
|
|
520
|
-
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
521
|
-
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
522
|
-
|
|
523
|
-
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
524
|
-
`;
|
|
525
|
-
}
|
|
526
|
-
function hasPreCommitBlock(content) {
|
|
527
|
-
return content.includes(BLOCK_START);
|
|
528
|
-
}
|
|
529
|
-
function appendPreCommitBlock(content, platform = "claude") {
|
|
530
|
-
if (hasPreCommitBlock(content)) return content;
|
|
531
|
-
const trimmed = content.trimEnd();
|
|
532
|
-
return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
|
|
533
|
-
}
|
|
534
|
-
function getCursorPreCommitRule() {
|
|
535
|
-
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
536
|
-
}
|
|
537
|
-
function hasLearningsBlock(content) {
|
|
538
|
-
return content.includes(LEARNINGS_BLOCK_START);
|
|
539
|
-
}
|
|
540
|
-
function appendLearningsBlock(content) {
|
|
541
|
-
if (hasLearningsBlock(content)) return content;
|
|
542
|
-
const trimmed = content.trimEnd();
|
|
543
|
-
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
544
|
-
}
|
|
545
|
-
function getCursorLearningsRule() {
|
|
546
|
-
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
547
|
-
}
|
|
548
|
-
function getSyncSetupInstruction(platform, bin) {
|
|
549
|
-
switch (platform) {
|
|
550
|
-
case "claude":
|
|
551
|
-
return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
|
|
552
|
-
case "codex":
|
|
553
|
-
return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
|
|
554
|
-
case "copilot":
|
|
555
|
-
return `If the pre-commit hook is not set up, the developer should run \`/setup-caliber\` in Claude Code or Cursor for automated setup. Alternatively, run in terminal:
|
|
556
|
-
\`\`\`bash
|
|
557
|
-
npx @rely-ai/caliber hooks --install
|
|
558
|
-
npx @rely-ai/caliber refresh
|
|
559
|
-
git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
560
|
-
\`\`\``;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
function getSyncBlock(platform = "claude") {
|
|
564
|
-
const bin = resolveCaliber();
|
|
565
|
-
return `${SYNC_BLOCK_START}
|
|
566
|
-
## Context Sync
|
|
567
|
-
|
|
568
|
-
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.
|
|
569
|
-
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
570
|
-
${getSyncSetupInstruction(platform, bin)}
|
|
571
|
-
${SYNC_BLOCK_END}`;
|
|
572
|
-
}
|
|
573
|
-
function hasSyncBlock(content) {
|
|
574
|
-
return content.includes(SYNC_BLOCK_START);
|
|
575
|
-
}
|
|
576
|
-
function appendSyncBlock(content, platform = "claude") {
|
|
577
|
-
if (hasSyncBlock(content)) return content;
|
|
578
|
-
const trimmed = content.trimEnd();
|
|
579
|
-
return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
|
|
580
|
-
}
|
|
581
|
-
function appendManagedBlocks(content, platform = "claude") {
|
|
582
|
-
return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
|
|
583
|
-
}
|
|
584
|
-
function getCursorSyncContent() {
|
|
585
|
-
const bin = resolveCaliber();
|
|
586
|
-
return `---
|
|
587
|
-
description: This project uses Caliber for automatic AI agent context sync
|
|
588
|
-
alwaysApply: true
|
|
589
|
-
---
|
|
590
|
-
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
591
|
-
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
592
|
-
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
593
|
-
`;
|
|
594
|
-
}
|
|
595
|
-
function getCursorSyncRule() {
|
|
596
|
-
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
597
|
-
}
|
|
598
|
-
function getCursorSetupContent() {
|
|
599
|
-
return `---
|
|
600
|
-
description: Check if Caliber agent config sync is set up and offer installation on new sessions
|
|
601
|
-
alwaysApply: true
|
|
602
|
-
---
|
|
603
|
-
This project uses Caliber to keep AI agent configs in sync automatically.
|
|
604
|
-
At the start of each session, silently check if sync is active:
|
|
605
|
-
|
|
606
|
-
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
|
|
607
|
-
|
|
608
|
-
If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
|
|
609
|
-
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."
|
|
610
|
-
|
|
611
|
-
If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
|
|
612
|
-
`;
|
|
613
|
-
}
|
|
614
|
-
function getCursorSetupRule() {
|
|
615
|
-
return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
|
|
616
|
-
}
|
|
617
|
-
function stripManagedBlocks(content) {
|
|
618
|
-
let result = content;
|
|
619
|
-
for (const [start, end] of MANAGED_BLOCK_PAIRS) {
|
|
620
|
-
const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
|
|
621
|
-
result = result.replace(regex, "\n");
|
|
622
|
-
}
|
|
623
|
-
return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
624
|
-
}
|
|
625
|
-
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, MANAGED_BLOCK_PAIRS;
|
|
626
|
-
var init_pre_commit_block = __esm({
|
|
627
|
-
"src/writers/pre-commit-block.ts"() {
|
|
628
|
-
"use strict";
|
|
629
|
-
init_resolve_caliber();
|
|
630
|
-
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
631
|
-
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
632
|
-
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
633
|
-
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
634
|
-
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
635
|
-
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
636
|
-
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
637
|
-
## Session Learnings
|
|
638
|
-
|
|
639
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
640
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
641
|
-
${LEARNINGS_BLOCK_END}`;
|
|
642
|
-
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
643
|
-
CURSOR_LEARNINGS_CONTENT = `---
|
|
644
|
-
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
645
|
-
alwaysApply: true
|
|
646
|
-
---
|
|
647
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
648
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
649
|
-
`;
|
|
650
|
-
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
651
|
-
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
652
|
-
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
653
|
-
CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
|
|
654
|
-
MANAGED_BLOCK_PAIRS = [
|
|
655
|
-
[BLOCK_START, BLOCK_END],
|
|
656
|
-
[LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END],
|
|
657
|
-
[SYNC_BLOCK_START, SYNC_BLOCK_END]
|
|
658
|
-
];
|
|
659
|
-
}
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
// src/llm/config.ts
|
|
663
|
-
var config_exports = {};
|
|
664
|
-
__export(config_exports, {
|
|
665
|
-
DEFAULT_FAST_MODELS: () => DEFAULT_FAST_MODELS,
|
|
666
|
-
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
667
|
-
MODEL_CONTEXT_WINDOWS: () => MODEL_CONTEXT_WINDOWS,
|
|
668
|
-
getConfigFilePath: () => getConfigFilePath,
|
|
669
|
-
getDisplayModel: () => getDisplayModel,
|
|
670
|
-
getFastModel: () => getFastModel,
|
|
671
|
-
getMaxPromptTokens: () => getMaxPromptTokens,
|
|
672
|
-
loadConfig: () => loadConfig,
|
|
673
|
-
readConfigFile: () => readConfigFile,
|
|
674
|
-
resolveFromEnv: () => resolveFromEnv,
|
|
675
|
-
writeConfigFile: () => writeConfigFile
|
|
676
|
-
});
|
|
677
|
-
import fs6 from "fs";
|
|
678
|
-
import path6 from "path";
|
|
679
|
-
import os2 from "os";
|
|
680
|
-
function getMaxPromptTokens() {
|
|
681
|
-
const config = loadConfig();
|
|
682
|
-
const model = process.env.CALIBER_MODEL || config?.model;
|
|
683
|
-
const contextWindow = model ? MODEL_CONTEXT_WINDOWS[model] ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
|
|
684
|
-
const budget = Math.floor(contextWindow * INPUT_BUDGET_FRACTION);
|
|
685
|
-
return Math.max(MIN_PROMPT_TOKENS, Math.min(budget, MAX_PROMPT_TOKENS_CAP));
|
|
686
|
-
}
|
|
687
|
-
function loadConfig() {
|
|
688
|
-
const envConfig = resolveFromEnv();
|
|
689
|
-
if (envConfig) return envConfig;
|
|
690
|
-
return readConfigFile();
|
|
691
|
-
}
|
|
692
|
-
function resolveFromEnv() {
|
|
693
|
-
if (process.env.ANTHROPIC_API_KEY) {
|
|
694
|
-
return {
|
|
695
|
-
provider: "anthropic",
|
|
696
|
-
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
697
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.anthropic
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
if (process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID) {
|
|
701
|
-
return {
|
|
702
|
-
provider: "vertex",
|
|
703
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.vertex,
|
|
704
|
-
vertexProjectId: process.env.VERTEX_PROJECT_ID || process.env.GCP_PROJECT_ID,
|
|
705
|
-
vertexRegion: process.env.VERTEX_REGION || process.env.GCP_REGION || "us-east5",
|
|
706
|
-
vertexCredentials: process.env.VERTEX_SA_CREDENTIALS || process.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
if (process.env.OPENAI_API_KEY) {
|
|
710
|
-
return {
|
|
711
|
-
provider: "openai",
|
|
712
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
713
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.openai,
|
|
714
|
-
baseUrl: process.env.OPENAI_BASE_URL
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
if (process.env.CALIBER_USE_CURSOR_SEAT === "1" || process.env.CALIBER_USE_CURSOR_SEAT === "true") {
|
|
718
|
-
return {
|
|
719
|
-
provider: "cursor",
|
|
720
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS.cursor
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
|
-
if (process.env.CALIBER_USE_CLAUDE_CLI === "1" || process.env.CALIBER_USE_CLAUDE_CLI === "true") {
|
|
724
|
-
return {
|
|
725
|
-
provider: "claude-cli",
|
|
726
|
-
model: process.env.CALIBER_MODEL || DEFAULT_MODELS["claude-cli"]
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
function readConfigFile() {
|
|
732
|
-
try {
|
|
733
|
-
if (!fs6.existsSync(CONFIG_FILE)) return null;
|
|
734
|
-
const raw = fs6.readFileSync(CONFIG_FILE, "utf-8");
|
|
735
|
-
const parsed = JSON.parse(raw);
|
|
736
|
-
if (!parsed.provider || !["anthropic", "vertex", "openai", "cursor", "claude-cli"].includes(parsed.provider)) {
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
return parsed;
|
|
740
|
-
} catch {
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
function writeConfigFile(config) {
|
|
745
|
-
if (!fs6.existsSync(CONFIG_DIR)) {
|
|
746
|
-
fs6.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
747
|
-
}
|
|
748
|
-
const sanitized = { ...config };
|
|
749
|
-
if (sanitized.apiKey) {
|
|
750
|
-
sanitized.apiKey = sanitized.apiKey.trim();
|
|
751
|
-
}
|
|
752
|
-
fs6.writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2) + "\n", { mode: 384 });
|
|
753
|
-
}
|
|
754
|
-
function getConfigFilePath() {
|
|
755
|
-
return CONFIG_FILE;
|
|
756
|
-
}
|
|
757
|
-
function getDisplayModel(config) {
|
|
758
|
-
if (config.model === "default" && config.provider === "claude-cli") {
|
|
759
|
-
return process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)";
|
|
760
|
-
}
|
|
761
|
-
return config.model;
|
|
762
|
-
}
|
|
763
|
-
function getFastModel() {
|
|
764
|
-
if (process.env.CALIBER_FAST_MODEL) return process.env.CALIBER_FAST_MODEL;
|
|
765
|
-
const config = loadConfig();
|
|
766
|
-
const provider = config?.provider;
|
|
767
|
-
if (process.env.ANTHROPIC_SMALL_FAST_MODEL && (!provider || provider === "anthropic" || provider === "vertex" || provider === "claude-cli")) {
|
|
768
|
-
return process.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
769
|
-
}
|
|
770
|
-
if (config?.fastModel) return config.fastModel;
|
|
771
|
-
if (provider) return DEFAULT_FAST_MODELS[provider];
|
|
772
|
-
return void 0;
|
|
773
|
-
}
|
|
774
|
-
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;
|
|
775
|
-
var init_config = __esm({
|
|
776
|
-
"src/llm/config.ts"() {
|
|
777
|
-
"use strict";
|
|
778
|
-
CONFIG_DIR = path6.join(os2.homedir(), ".caliber");
|
|
779
|
-
CONFIG_FILE = path6.join(CONFIG_DIR, "config.json");
|
|
780
|
-
DEFAULT_MODELS = {
|
|
781
|
-
anthropic: "claude-sonnet-4-6",
|
|
782
|
-
vertex: "claude-sonnet-4-6",
|
|
783
|
-
openai: "gpt-4.1",
|
|
784
|
-
cursor: "sonnet-4.6",
|
|
785
|
-
"claude-cli": "default"
|
|
786
|
-
};
|
|
787
|
-
MODEL_CONTEXT_WINDOWS = {
|
|
788
|
-
"claude-sonnet-4-6": 2e5,
|
|
789
|
-
"claude-opus-4-6": 2e5,
|
|
790
|
-
"claude-haiku-4-5-20251001": 2e5,
|
|
791
|
-
"claude-sonnet-4-5-20250514": 2e5,
|
|
792
|
-
"gpt-4.1": 1e6,
|
|
793
|
-
"gpt-4.1-mini": 1e6,
|
|
794
|
-
"gpt-4o": 128e3,
|
|
795
|
-
"gpt-4o-mini": 128e3,
|
|
796
|
-
"sonnet-4.6": 2e5
|
|
797
|
-
};
|
|
798
|
-
DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
799
|
-
INPUT_BUDGET_FRACTION = 0.6;
|
|
800
|
-
MAX_PROMPT_TOKENS_CAP = 3e5;
|
|
801
|
-
MIN_PROMPT_TOKENS = 3e4;
|
|
802
|
-
DEFAULT_FAST_MODELS = {
|
|
803
|
-
anthropic: "claude-haiku-4-5-20251001",
|
|
804
|
-
vertex: "claude-haiku-4-5-20251001",
|
|
805
|
-
openai: "gpt-4.1-mini",
|
|
806
|
-
cursor: "gpt-5.3-codex-fast"
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
// src/llm/types.ts
|
|
812
|
-
var types_exports = {};
|
|
813
|
-
__export(types_exports, {
|
|
814
|
-
isSeatBased: () => isSeatBased
|
|
815
|
-
});
|
|
816
|
-
function isSeatBased(provider) {
|
|
817
|
-
return SEAT_BASED_PROVIDERS.has(provider);
|
|
818
|
-
}
|
|
819
|
-
var SEAT_BASED_PROVIDERS;
|
|
820
|
-
var init_types = __esm({
|
|
821
|
-
"src/llm/types.ts"() {
|
|
790
|
+
}
|
|
791
|
+
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
|
|
792
|
+
var init_builtin_skills = __esm({
|
|
793
|
+
"src/lib/builtin-skills.ts"() {
|
|
822
794
|
"use strict";
|
|
823
|
-
|
|
795
|
+
init_resolve_caliber();
|
|
796
|
+
FIND_SKILLS_SKILL = {
|
|
797
|
+
name: "find-skills",
|
|
798
|
+
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.",
|
|
799
|
+
get content() {
|
|
800
|
+
return getFindSkillsContent();
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
SAVE_LEARNING_SKILL = {
|
|
804
|
+
name: "save-learning",
|
|
805
|
+
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.",
|
|
806
|
+
get content() {
|
|
807
|
+
return getSaveLearningContent();
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
SETUP_CALIBER_SKILL = {
|
|
811
|
+
name: "setup-caliber",
|
|
812
|
+
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.",
|
|
813
|
+
get content() {
|
|
814
|
+
return getSetupCaliberContent();
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
818
|
+
PLATFORM_CONFIGS = [
|
|
819
|
+
{ platformDir: ".claude", skillsDir: path17.join(".claude", "skills") },
|
|
820
|
+
{ platformDir: ".cursor", skillsDir: path17.join(".cursor", "skills") },
|
|
821
|
+
{ platformDir: ".agents", skillsDir: path17.join(".agents", "skills") }
|
|
822
|
+
];
|
|
823
|
+
BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
|
|
824
824
|
}
|
|
825
825
|
});
|
|
826
826
|
|
|
827
827
|
// src/utils/editor.ts
|
|
828
828
|
import { execSync as execSync14, spawn as spawn3 } from "child_process";
|
|
829
829
|
import fs27 from "fs";
|
|
830
|
-
import
|
|
830
|
+
import path24 from "path";
|
|
831
831
|
import os6 from "os";
|
|
832
832
|
function getEmptyFilePath(proposedPath) {
|
|
833
833
|
fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
834
|
-
const tempPath =
|
|
834
|
+
const tempPath = path24.join(DIFF_TEMP_DIR, path24.basename(proposedPath));
|
|
835
835
|
fs27.writeFileSync(tempPath, "");
|
|
836
836
|
return tempPath;
|
|
837
837
|
}
|
|
@@ -872,7 +872,7 @@ var init_editor = __esm({
|
|
|
872
872
|
"src/utils/editor.ts"() {
|
|
873
873
|
"use strict";
|
|
874
874
|
IS_WINDOWS3 = process.platform === "win32";
|
|
875
|
-
DIFF_TEMP_DIR =
|
|
875
|
+
DIFF_TEMP_DIR = path24.join(os6.tmpdir(), "caliber-diff");
|
|
876
876
|
}
|
|
877
877
|
});
|
|
878
878
|
|
|
@@ -1098,7 +1098,7 @@ __export(lock_exports, {
|
|
|
1098
1098
|
releaseLock: () => releaseLock
|
|
1099
1099
|
});
|
|
1100
1100
|
import fs38 from "fs";
|
|
1101
|
-
import
|
|
1101
|
+
import path31 from "path";
|
|
1102
1102
|
import os8 from "os";
|
|
1103
1103
|
function isCaliberRunning() {
|
|
1104
1104
|
try {
|
|
@@ -1132,7 +1132,7 @@ var LOCK_FILE, STALE_MS;
|
|
|
1132
1132
|
var init_lock = __esm({
|
|
1133
1133
|
"src/lib/lock.ts"() {
|
|
1134
1134
|
"use strict";
|
|
1135
|
-
LOCK_FILE =
|
|
1135
|
+
LOCK_FILE = path31.join(os8.tmpdir(), ".caliber.lock");
|
|
1136
1136
|
STALE_MS = 10 * 60 * 1e3;
|
|
1137
1137
|
}
|
|
1138
1138
|
});
|
|
@@ -1140,16 +1140,16 @@ var init_lock = __esm({
|
|
|
1140
1140
|
// src/cli.ts
|
|
1141
1141
|
import { Command } from "commander";
|
|
1142
1142
|
import fs49 from "fs";
|
|
1143
|
-
import
|
|
1143
|
+
import path40 from "path";
|
|
1144
1144
|
import { fileURLToPath } from "url";
|
|
1145
1145
|
|
|
1146
1146
|
// src/commands/init.ts
|
|
1147
|
-
import
|
|
1147
|
+
import path27 from "path";
|
|
1148
1148
|
import chalk14 from "chalk";
|
|
1149
1149
|
import fs33 from "fs";
|
|
1150
1150
|
|
|
1151
1151
|
// src/fingerprint/index.ts
|
|
1152
|
-
import
|
|
1152
|
+
import fs8 from "fs";
|
|
1153
1153
|
import path8 from "path";
|
|
1154
1154
|
|
|
1155
1155
|
// src/fingerprint/git.ts
|
|
@@ -1258,8 +1258,8 @@ function scan(base, rel, depth, maxDepth, result) {
|
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
1260
|
// src/fingerprint/existing-config.ts
|
|
1261
|
-
import
|
|
1262
|
-
import
|
|
1261
|
+
import fs2 from "fs";
|
|
1262
|
+
import path3 from "path";
|
|
1263
1263
|
|
|
1264
1264
|
// src/constants.ts
|
|
1265
1265
|
import path2 from "path";
|
|
@@ -1294,121 +1294,97 @@ var LEARNING_LAST_ERROR_FILE = "last-error.json";
|
|
|
1294
1294
|
var MIN_SESSIONS_FOR_COMPARISON = 3;
|
|
1295
1295
|
|
|
1296
1296
|
// src/fingerprint/existing-config.ts
|
|
1297
|
-
init_builtin_skills();
|
|
1298
|
-
init_pre_commit_block();
|
|
1299
|
-
var MANAGED_CURSOR_RULES = /* @__PURE__ */ new Set([
|
|
1300
|
-
getCursorPreCommitRule().filename,
|
|
1301
|
-
getCursorLearningsRule().filename,
|
|
1302
|
-
getCursorSyncRule().filename,
|
|
1303
|
-
getCursorSetupRule().filename
|
|
1304
|
-
]);
|
|
1305
1297
|
function readExistingConfigs(dir) {
|
|
1306
1298
|
const configs = {};
|
|
1307
|
-
const readmeMdPath =
|
|
1308
|
-
if (
|
|
1309
|
-
configs.readmeMd =
|
|
1299
|
+
const readmeMdPath = path3.join(dir, "README.md");
|
|
1300
|
+
if (fs2.existsSync(readmeMdPath)) {
|
|
1301
|
+
configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
|
|
1310
1302
|
}
|
|
1311
|
-
const agentsMdPath =
|
|
1312
|
-
if (
|
|
1313
|
-
configs.agentsMd =
|
|
1303
|
+
const agentsMdPath = path3.join(dir, "AGENTS.md");
|
|
1304
|
+
if (fs2.existsSync(agentsMdPath)) {
|
|
1305
|
+
configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
|
|
1314
1306
|
}
|
|
1315
|
-
const claudeMdPath =
|
|
1316
|
-
if (
|
|
1317
|
-
configs.claudeMd =
|
|
1307
|
+
const claudeMdPath = path3.join(dir, "CLAUDE.md");
|
|
1308
|
+
if (fs2.existsSync(claudeMdPath)) {
|
|
1309
|
+
configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
1318
1310
|
}
|
|
1319
|
-
const claudeSettingsPath =
|
|
1320
|
-
if (
|
|
1311
|
+
const claudeSettingsPath = path3.join(dir, ".claude", "settings.json");
|
|
1312
|
+
if (fs2.existsSync(claudeSettingsPath)) {
|
|
1321
1313
|
try {
|
|
1322
|
-
configs.claudeSettings = JSON.parse(
|
|
1314
|
+
configs.claudeSettings = JSON.parse(fs2.readFileSync(claudeSettingsPath, "utf-8"));
|
|
1323
1315
|
} catch {
|
|
1324
1316
|
}
|
|
1325
1317
|
}
|
|
1326
|
-
const skillsDir =
|
|
1327
|
-
if (
|
|
1318
|
+
const skillsDir = path3.join(dir, ".claude", "skills");
|
|
1319
|
+
if (fs2.existsSync(skillsDir)) {
|
|
1328
1320
|
try {
|
|
1329
|
-
const entries =
|
|
1321
|
+
const entries = fs2.readdirSync(skillsDir);
|
|
1330
1322
|
const skills = [];
|
|
1331
1323
|
for (const entry of entries) {
|
|
1332
|
-
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
skills.push({ filename: `${entry}/SKILL.md`, content: fs4.readFileSync(skillMdPath, "utf-8") });
|
|
1324
|
+
const entryPath = path3.join(skillsDir, entry);
|
|
1325
|
+
const skillMdPath = path3.join(entryPath, "SKILL.md");
|
|
1326
|
+
if (fs2.statSync(entryPath).isDirectory() && fs2.existsSync(skillMdPath)) {
|
|
1327
|
+
skills.push({ filename: `${entry}/SKILL.md`, content: fs2.readFileSync(skillMdPath, "utf-8") });
|
|
1337
1328
|
} else if (entry.endsWith(".md")) {
|
|
1338
|
-
skills.push({ filename: entry, content:
|
|
1329
|
+
skills.push({ filename: entry, content: fs2.readFileSync(entryPath, "utf-8") });
|
|
1339
1330
|
}
|
|
1340
1331
|
}
|
|
1341
1332
|
if (skills.length > 0) configs.claudeSkills = skills;
|
|
1342
1333
|
} catch {
|
|
1343
1334
|
}
|
|
1344
1335
|
}
|
|
1345
|
-
const cursorrulesPath =
|
|
1346
|
-
if (
|
|
1347
|
-
configs.cursorrules =
|
|
1336
|
+
const cursorrulesPath = path3.join(dir, ".cursorrules");
|
|
1337
|
+
if (fs2.existsSync(cursorrulesPath)) {
|
|
1338
|
+
configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
|
|
1348
1339
|
}
|
|
1349
|
-
const cursorRulesDir =
|
|
1350
|
-
if (
|
|
1340
|
+
const cursorRulesDir = path3.join(dir, ".cursor", "rules");
|
|
1341
|
+
if (fs2.existsSync(cursorRulesDir)) {
|
|
1351
1342
|
try {
|
|
1352
|
-
const files =
|
|
1343
|
+
const files = fs2.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
|
|
1353
1344
|
configs.cursorRules = files.map((f) => ({
|
|
1354
1345
|
filename: f,
|
|
1355
|
-
content:
|
|
1346
|
+
content: fs2.readFileSync(path3.join(cursorRulesDir, f), "utf-8")
|
|
1356
1347
|
}));
|
|
1357
1348
|
} catch {
|
|
1358
1349
|
}
|
|
1359
1350
|
}
|
|
1360
|
-
const cursorSkillsDir =
|
|
1361
|
-
if (
|
|
1351
|
+
const cursorSkillsDir = path3.join(dir, ".cursor", "skills");
|
|
1352
|
+
if (fs2.existsSync(cursorSkillsDir)) {
|
|
1362
1353
|
try {
|
|
1363
|
-
const slugs =
|
|
1364
|
-
return
|
|
1354
|
+
const slugs = fs2.readdirSync(cursorSkillsDir).filter((f) => {
|
|
1355
|
+
return fs2.statSync(path3.join(cursorSkillsDir, f)).isDirectory();
|
|
1365
1356
|
});
|
|
1366
|
-
configs.cursorSkills = slugs.filter((slug) =>
|
|
1357
|
+
configs.cursorSkills = slugs.filter((slug) => fs2.existsSync(path3.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
1367
1358
|
name,
|
|
1368
1359
|
filename: "SKILL.md",
|
|
1369
|
-
content:
|
|
1360
|
+
content: fs2.readFileSync(path3.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
1370
1361
|
}));
|
|
1371
1362
|
} catch {
|
|
1372
1363
|
}
|
|
1373
1364
|
}
|
|
1374
|
-
const mcpJsonPath =
|
|
1375
|
-
if (
|
|
1365
|
+
const mcpJsonPath = path3.join(dir, ".mcp.json");
|
|
1366
|
+
if (fs2.existsSync(mcpJsonPath)) {
|
|
1376
1367
|
try {
|
|
1377
|
-
const mcpJson = JSON.parse(
|
|
1368
|
+
const mcpJson = JSON.parse(fs2.readFileSync(mcpJsonPath, "utf-8"));
|
|
1378
1369
|
if (mcpJson.mcpServers) {
|
|
1379
1370
|
configs.claudeMcpServers = mcpJson.mcpServers;
|
|
1380
1371
|
}
|
|
1381
1372
|
} catch {
|
|
1382
1373
|
}
|
|
1383
1374
|
}
|
|
1384
|
-
const cursorMcpPath =
|
|
1385
|
-
if (
|
|
1375
|
+
const cursorMcpPath = path3.join(dir, ".cursor", "mcp.json");
|
|
1376
|
+
if (fs2.existsSync(cursorMcpPath)) {
|
|
1386
1377
|
try {
|
|
1387
|
-
const cursorMcpJson = JSON.parse(
|
|
1378
|
+
const cursorMcpJson = JSON.parse(fs2.readFileSync(cursorMcpPath, "utf-8"));
|
|
1388
1379
|
if (cursorMcpJson.mcpServers) {
|
|
1389
1380
|
configs.cursorMcpServers = cursorMcpJson.mcpServers;
|
|
1390
1381
|
}
|
|
1391
1382
|
} catch {
|
|
1392
1383
|
}
|
|
1393
1384
|
}
|
|
1394
|
-
|
|
1395
|
-
if (fs4.existsSync(copilotPath)) {
|
|
1396
|
-
configs.copilotInstructions = fs4.readFileSync(copilotPath, "utf-8");
|
|
1397
|
-
}
|
|
1398
|
-
const instructionsDir = path4.join(dir, ".github", "instructions");
|
|
1399
|
-
if (fs4.existsSync(instructionsDir)) {
|
|
1400
|
-
try {
|
|
1401
|
-
const files = fs4.readdirSync(instructionsDir).filter((f) => f.endsWith(".md"));
|
|
1402
|
-
configs.copilotInstructionFiles = files.map((f) => ({
|
|
1403
|
-
filename: f,
|
|
1404
|
-
content: fs4.readFileSync(path4.join(instructionsDir, f), "utf-8")
|
|
1405
|
-
}));
|
|
1406
|
-
} catch {
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
if (fs4.existsSync(PERSONAL_LEARNINGS_FILE)) {
|
|
1385
|
+
if (fs2.existsSync(PERSONAL_LEARNINGS_FILE)) {
|
|
1410
1386
|
try {
|
|
1411
|
-
const content =
|
|
1387
|
+
const content = fs2.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
1412
1388
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
1413
1389
|
if (bullets) configs.personalLearnings = bullets;
|
|
1414
1390
|
} catch {
|
|
@@ -1418,11 +1394,26 @@ function readExistingConfigs(dir) {
|
|
|
1418
1394
|
}
|
|
1419
1395
|
|
|
1420
1396
|
// src/fingerprint/code-analysis.ts
|
|
1421
|
-
import
|
|
1397
|
+
import fs3 from "fs";
|
|
1422
1398
|
import path5 from "path";
|
|
1423
|
-
import { execSync as
|
|
1399
|
+
import { execSync as execSync3 } from "child_process";
|
|
1424
1400
|
|
|
1425
1401
|
// src/lib/sanitize.ts
|
|
1402
|
+
import path4 from "path";
|
|
1403
|
+
function sanitizePath(component) {
|
|
1404
|
+
const cleaned = component.replace(/\.\./g, "").replace(/[/\\]/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
|
|
1405
|
+
if (!cleaned) {
|
|
1406
|
+
throw new Error(`Invalid path component: ${component}`);
|
|
1407
|
+
}
|
|
1408
|
+
return cleaned;
|
|
1409
|
+
}
|
|
1410
|
+
function assertPathWithinDir(filePath, baseDir) {
|
|
1411
|
+
const resolved = path4.resolve(baseDir, filePath);
|
|
1412
|
+
const resolvedBase = path4.resolve(baseDir);
|
|
1413
|
+
if (!resolved.startsWith(resolvedBase + path4.sep) && resolved !== resolvedBase) {
|
|
1414
|
+
throw new Error(`Path traversal detected: ${filePath} escapes ${baseDir}`);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1426
1417
|
var KNOWN_PREFIX_PATTERNS = [
|
|
1427
1418
|
// Anthropic (before generic sk- pattern)
|
|
1428
1419
|
[/sk-ant-[A-Za-z0-9_-]{20,}/g, "[REDACTED]"],
|
|
@@ -1446,9 +1437,13 @@ var KNOWN_PREFIX_PATTERNS = [
|
|
|
1446
1437
|
// Bearer tokens
|
|
1447
1438
|
[/[Bb]earer\s+[A-Za-z0-9_\-.]{20,}/g, "[REDACTED]"],
|
|
1448
1439
|
// PEM private keys
|
|
1449
|
-
[/-----BEGIN[A-Z ]+KEY-----[\s\S]+?-----END[A-Z ]+KEY-----/g, "[REDACTED]"]
|
|
1440
|
+
[/-----BEGIN[A-Z ]+KEY-----[\s\S]+?-----END[A-Z ]+KEY-----/g, "[REDACTED]"],
|
|
1441
|
+
// Database connection strings with credentials
|
|
1442
|
+
[/(postgresql|mysql|mongodb(\+srv)?|redis|amqp):\/\/[^@\s]+:[^@\s]+@[^\s'"]+/g, "[REDACTED]"],
|
|
1443
|
+
// GitLab tokens
|
|
1444
|
+
[/glpat-[a-zA-Z0-9_-]{20,}/g, "[REDACTED]"]
|
|
1450
1445
|
];
|
|
1451
|
-
var SENSITIVE_ASSIGNMENT = /(?:api[_-]?key|secret[_-]?key|password|token|credential|auth[_-]?token|private[_-]?key)\s*[:=]\s*['"]?([^\s'"]{8,500})['"]?/gi;
|
|
1446
|
+
var SENSITIVE_ASSIGNMENT = /(?:api[_-]?key|secret[_-]?key|password|token|credential|auth[_-]?token|private[_-]?key|database[_-]?url|connection[_-]?string)\s*[:=]\s*['"]?([^\s'"]{8,500})['"]?/gi;
|
|
1452
1447
|
function sanitizeSecrets(text) {
|
|
1453
1448
|
let result = text;
|
|
1454
1449
|
for (const [pattern, replacement] of KNOWN_PREFIX_PATTERNS) {
|
|
@@ -1567,12 +1562,15 @@ var SKIP_PATTERNS = [
|
|
|
1567
1562
|
/\.d\.ts$/,
|
|
1568
1563
|
/\.generated\./,
|
|
1569
1564
|
/\.snap$/,
|
|
1570
|
-
/^\.env($|\.)
|
|
1565
|
+
/^\.env($|\.)/,
|
|
1566
|
+
/\.(pem|key|crt|cer|pfx|p12|jks|keystore)$/,
|
|
1567
|
+
/^id_(rsa|ed25519|ecdsa)$/,
|
|
1568
|
+
/^(known_hosts|authorized_keys)$/
|
|
1571
1569
|
];
|
|
1572
1570
|
var COMMENT_LINE = {
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1571
|
+
c: /^\s*\/\//,
|
|
1572
|
+
h: /^\s*#/,
|
|
1573
|
+
x: /^\s*<!--.*-->\s*$/
|
|
1576
1574
|
};
|
|
1577
1575
|
var EXT_COMMENT = {
|
|
1578
1576
|
".ts": "c",
|
|
@@ -1678,7 +1676,6 @@ function extractSkeleton(content, ext) {
|
|
|
1678
1676
|
const lines = content.split("\n");
|
|
1679
1677
|
const result = [];
|
|
1680
1678
|
let braceDepth = 0;
|
|
1681
|
-
let inSignature = false;
|
|
1682
1679
|
let skipBody = false;
|
|
1683
1680
|
if ([".py", ".pyw", ".rb"].includes(ext)) {
|
|
1684
1681
|
return extractSkeletonIndentBased(lines, ext);
|
|
@@ -1704,7 +1701,9 @@ function extractSkeleton(content, ext) {
|
|
|
1704
1701
|
}
|
|
1705
1702
|
continue;
|
|
1706
1703
|
}
|
|
1707
|
-
const isFnOrClass = /^\s*(export\s+)?(default\s+)?(async\s+)?(function|class|const\s+\w+\s*=\s*(async\s*)?\(|pub\s+fn|fn|func)\s/.test(
|
|
1704
|
+
const isFnOrClass = /^\s*(export\s+)?(default\s+)?(async\s+)?(function|class|const\s+\w+\s*=\s*(async\s*)?\(|pub\s+fn|fn|func)\s/.test(
|
|
1705
|
+
trimmed
|
|
1706
|
+
) || /^\s*(def|func|fn|pub fn|pub async fn)\s/.test(trimmed);
|
|
1708
1707
|
if (isFnOrClass && braceDepth === 0) {
|
|
1709
1708
|
result.push(line);
|
|
1710
1709
|
const opens = (line.match(/{/g) || []).length;
|
|
@@ -1732,7 +1731,7 @@ function extractSkeleton(content, ext) {
|
|
|
1732
1731
|
}
|
|
1733
1732
|
return result.join("\n");
|
|
1734
1733
|
}
|
|
1735
|
-
function extractSkeletonIndentBased(lines,
|
|
1734
|
+
function extractSkeletonIndentBased(lines, _ext) {
|
|
1736
1735
|
const result = [];
|
|
1737
1736
|
let skipIndent = -1;
|
|
1738
1737
|
for (const line of lines) {
|
|
@@ -1792,7 +1791,16 @@ function buildImportCounts(files) {
|
|
|
1792
1791
|
if (!SOURCE_EXTENSIONS.has(ext)) continue;
|
|
1793
1792
|
const imports = extractImports(content, filePath);
|
|
1794
1793
|
for (const imp of imports) {
|
|
1795
|
-
const candidates = [
|
|
1794
|
+
const candidates = [
|
|
1795
|
+
imp,
|
|
1796
|
+
imp + ".ts",
|
|
1797
|
+
imp + ".js",
|
|
1798
|
+
imp + ".tsx",
|
|
1799
|
+
imp + ".jsx",
|
|
1800
|
+
imp + "/index.ts",
|
|
1801
|
+
imp + "/index.js",
|
|
1802
|
+
imp + ".py"
|
|
1803
|
+
];
|
|
1796
1804
|
for (const candidate of candidates) {
|
|
1797
1805
|
const normalized = candidate.replace(/\\/g, "/");
|
|
1798
1806
|
if (files.has(normalized)) {
|
|
@@ -1807,7 +1815,7 @@ function buildImportCounts(files) {
|
|
|
1807
1815
|
function getGitFrequency(dir) {
|
|
1808
1816
|
const freq = /* @__PURE__ */ new Map();
|
|
1809
1817
|
try {
|
|
1810
|
-
const output =
|
|
1818
|
+
const output = execSync3(
|
|
1811
1819
|
'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
|
|
1812
1820
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
1813
1821
|
);
|
|
@@ -1834,7 +1842,9 @@ function structuralFingerprint(content, ext) {
|
|
|
1834
1842
|
const bucket = Math.floor(lines.length / 10) * 10;
|
|
1835
1843
|
const first = (lines[0] || "").trim().slice(0, 50);
|
|
1836
1844
|
const imports = lines.filter((l) => /^\s*(import |from |require\(|use )/.test(l)).length;
|
|
1837
|
-
const fns = lines.filter(
|
|
1845
|
+
const fns = lines.filter(
|
|
1846
|
+
(l) => /^\s*(export\s+)?(async\s+)?(function |def |func |fn |pub fn |class )/.test(l)
|
|
1847
|
+
).length;
|
|
1838
1848
|
return `${ext}:${bucket}:${imports}:${fns}:${first}`;
|
|
1839
1849
|
}
|
|
1840
1850
|
function analyzeCode(dir) {
|
|
@@ -1843,14 +1853,14 @@ function analyzeCode(dir) {
|
|
|
1843
1853
|
let totalChars = 0;
|
|
1844
1854
|
for (const relPath of allPaths) {
|
|
1845
1855
|
try {
|
|
1846
|
-
totalChars +=
|
|
1856
|
+
totalChars += fs3.statSync(path5.join(dir, relPath)).size;
|
|
1847
1857
|
} catch {
|
|
1848
1858
|
}
|
|
1849
1859
|
}
|
|
1850
1860
|
const fileContents = /* @__PURE__ */ new Map();
|
|
1851
1861
|
for (const relPath of allPaths) {
|
|
1852
1862
|
try {
|
|
1853
|
-
const content =
|
|
1863
|
+
const content = fs3.readFileSync(path5.join(dir, relPath), "utf-8");
|
|
1854
1864
|
if (content.split("\n").length <= 500) fileContents.set(relPath, content);
|
|
1855
1865
|
} catch {
|
|
1856
1866
|
}
|
|
@@ -1881,7 +1891,12 @@ function analyzeCode(dir) {
|
|
|
1881
1891
|
const repFP = structuralFingerprint(rep.compressed, rep.ext);
|
|
1882
1892
|
const similar = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) === repFP);
|
|
1883
1893
|
const unique = group.slice(1).filter((f) => structuralFingerprint(f.compressed, f.ext) !== repFP);
|
|
1884
|
-
const repEntry = {
|
|
1894
|
+
const repEntry = {
|
|
1895
|
+
path: rep.path,
|
|
1896
|
+
content: sanitizeSecrets(rep.compressed),
|
|
1897
|
+
size: rep.compressed.length,
|
|
1898
|
+
priority: rep.score
|
|
1899
|
+
};
|
|
1885
1900
|
const repSize = rep.path.length + rep.compressed.length + 10;
|
|
1886
1901
|
if (includedChars + repSize <= CHAR_BUDGET) {
|
|
1887
1902
|
result.push(repEntry);
|
|
@@ -1893,14 +1908,24 @@ function analyzeCode(dir) {
|
|
|
1893
1908
|
const summary = `(${similar.length} similar file${similar.length === 1 ? "" : "s"} in ${dirPath}/: ${names.join(", ")})`;
|
|
1894
1909
|
const summarySize = summary.length + 30;
|
|
1895
1910
|
if (includedChars + summarySize <= CHAR_BUDGET) {
|
|
1896
|
-
result.push({
|
|
1911
|
+
result.push({
|
|
1912
|
+
path: `[similar to ${rep.path}]`,
|
|
1913
|
+
content: summary,
|
|
1914
|
+
size: summary.length,
|
|
1915
|
+
priority: rep.score
|
|
1916
|
+
});
|
|
1897
1917
|
includedChars += summarySize;
|
|
1898
1918
|
}
|
|
1899
1919
|
}
|
|
1900
1920
|
for (const f of unique) {
|
|
1901
1921
|
const skeletonSize = f.path.length + f.skeleton.length + 10;
|
|
1902
1922
|
if (includedChars + skeletonSize <= CHAR_BUDGET) {
|
|
1903
|
-
result.push({
|
|
1923
|
+
result.push({
|
|
1924
|
+
path: f.path,
|
|
1925
|
+
content: sanitizeSecrets(f.skeleton),
|
|
1926
|
+
size: f.skeleton.length,
|
|
1927
|
+
priority: f.score
|
|
1928
|
+
});
|
|
1904
1929
|
includedChars += skeletonSize;
|
|
1905
1930
|
}
|
|
1906
1931
|
}
|
|
@@ -1910,7 +1935,12 @@ function analyzeCode(dir) {
|
|
|
1910
1935
|
if (includedPaths.has(f.path)) continue;
|
|
1911
1936
|
const skeletonSize = f.path.length + f.skeleton.length + 10;
|
|
1912
1937
|
if (includedChars + skeletonSize > CHAR_BUDGET) continue;
|
|
1913
|
-
result.push({
|
|
1938
|
+
result.push({
|
|
1939
|
+
path: f.path,
|
|
1940
|
+
content: sanitizeSecrets(f.skeleton),
|
|
1941
|
+
size: f.skeleton.length,
|
|
1942
|
+
priority: f.score
|
|
1943
|
+
});
|
|
1914
1944
|
includedChars += skeletonSize;
|
|
1915
1945
|
}
|
|
1916
1946
|
return {
|
|
@@ -1929,7 +1959,7 @@ function walkDir(base, rel, depth, maxDepth, files) {
|
|
|
1929
1959
|
const fullPath = path5.join(base, rel);
|
|
1930
1960
|
let entries;
|
|
1931
1961
|
try {
|
|
1932
|
-
entries =
|
|
1962
|
+
entries = fs3.readdirSync(fullPath, { withFileTypes: true });
|
|
1933
1963
|
} catch {
|
|
1934
1964
|
return;
|
|
1935
1965
|
}
|
|
@@ -2082,7 +2112,7 @@ var AnthropicProvider = class {
|
|
|
2082
2112
|
};
|
|
2083
2113
|
|
|
2084
2114
|
// src/llm/vertex.ts
|
|
2085
|
-
import
|
|
2115
|
+
import fs5 from "fs";
|
|
2086
2116
|
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
|
|
2087
2117
|
import { GoogleAuth } from "google-auth-library";
|
|
2088
2118
|
var VertexProvider = class {
|
|
@@ -2105,7 +2135,7 @@ var VertexProvider = class {
|
|
|
2105
2135
|
}
|
|
2106
2136
|
} else {
|
|
2107
2137
|
try {
|
|
2108
|
-
creds = JSON.parse(
|
|
2138
|
+
creds = JSON.parse(fs5.readFileSync(raw, "utf-8"));
|
|
2109
2139
|
} catch {
|
|
2110
2140
|
throw new Error(`Cannot read credentials file: ${raw}`);
|
|
2111
2141
|
}
|
|
@@ -2694,7 +2724,7 @@ function isClaudeCliAvailable() {
|
|
|
2694
2724
|
|
|
2695
2725
|
// src/llm/utils.ts
|
|
2696
2726
|
function extractJson(text) {
|
|
2697
|
-
const startIdx = text.search(/[
|
|
2727
|
+
const startIdx = text.search(/[[{]/);
|
|
2698
2728
|
if (startIdx === -1) return null;
|
|
2699
2729
|
let depth = 0;
|
|
2700
2730
|
let inString = false;
|
|
@@ -3025,7 +3055,6 @@ var SKILL_FORMAT_RULES = `All skills follow the OpenSkills standard (agentskills
|
|
|
3025
3055
|
|
|
3026
3056
|
Skill field requirements:
|
|
3027
3057
|
- "name": kebab-case (lowercase letters, numbers, hyphens only). Becomes the directory name.
|
|
3028
|
-
- "name" MUST NOT be any of these reserved names (they are managed by Caliber automatically): "setup-caliber", "find-skills", "save-learning". Do NOT generate skills with these names.
|
|
3029
3058
|
- "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
|
|
3030
3059
|
- "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
|
|
3031
3060
|
|
|
@@ -3069,7 +3098,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
|
|
|
3069
3098
|
Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
|
|
3070
3099
|
README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
|
|
3071
3100
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
3072
|
-
- CLAUDE.md / AGENTS.md: MUST be under
|
|
3101
|
+
- CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
|
|
3073
3102
|
|
|
3074
3103
|
Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
|
|
3075
3104
|
GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
|
|
@@ -3197,7 +3226,7 @@ Structure:
|
|
|
3197
3226
|
5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
|
|
3198
3227
|
|
|
3199
3228
|
Rules:
|
|
3200
|
-
- Max
|
|
3229
|
+
- Max 150 lines. Focus on actionable instructions, not documentation prose.
|
|
3201
3230
|
- Study existing code in the project context to extract the real patterns being used. A skill for "create API route" should show the exact file structure, imports, error handling, and naming that existing routes use.
|
|
3202
3231
|
- Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
|
|
3203
3232
|
- Never use ambiguous language. Instead of "handle errors properly", write "Wrap the DB call in try/catch. On failure, return { error: string, code: number } matching the ErrorResponse type in \`src/types.ts\`."
|
|
@@ -3251,7 +3280,7 @@ Rules:
|
|
|
3251
3280
|
- Update the "fileDescriptions" to reflect any changes you make.
|
|
3252
3281
|
|
|
3253
3282
|
Quality constraints \u2014 your changes are scored, so do not break these:
|
|
3254
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
3283
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
|
|
3255
3284
|
- Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
|
|
3256
3285
|
- Do NOT add directory tree listings in code blocks.
|
|
3257
3286
|
- Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
|
|
@@ -3274,30 +3303,23 @@ CONSERVATIVE UPDATE means:
|
|
|
3274
3303
|
- NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
|
|
3275
3304
|
- NEVER replace specific paths/commands with generic prose
|
|
3276
3305
|
|
|
3277
|
-
Cross-agent sync:
|
|
3278
|
-
- When a change affects CLAUDE.md, apply the same semantic update to AGENTS.md and copilot instructions if they exist
|
|
3279
|
-
- Each file uses its own format and conventions \u2014 do NOT copy content verbatim between them
|
|
3280
|
-
- The goal is consistency: all agent config files should reflect the same project state after refresh
|
|
3281
|
-
|
|
3282
3306
|
Quality constraints (the output is scored deterministically):
|
|
3283
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
3307
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
3284
3308
|
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
3285
3309
|
- Every file path, command, and identifier must be in backticks
|
|
3286
3310
|
- ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
|
|
3287
3311
|
- Preserve the existing structure (headings, bullet style, formatting)
|
|
3288
3312
|
|
|
3289
3313
|
Managed content:
|
|
3290
|
-
-
|
|
3291
|
-
- This includes: pre-commit, sync, and learnings blocks
|
|
3314
|
+
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
3292
3315
|
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
3293
|
-
- Preserve any references to CALIBER_LEARNINGS.md
|
|
3316
|
+
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
3294
3317
|
|
|
3295
3318
|
Return a JSON object with this exact shape:
|
|
3296
3319
|
{
|
|
3297
3320
|
"updatedDocs": {
|
|
3298
3321
|
"claudeMd": "<updated content or null>",
|
|
3299
3322
|
"readmeMd": "<updated content or null>",
|
|
3300
|
-
"agentsMd": "<updated content or null>",
|
|
3301
3323
|
"cursorrules": "<updated content or null>",
|
|
3302
3324
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
3303
3325
|
"claudeSkills": [{"filename": "name.md", "content": "..."}] or null,
|
|
@@ -3305,12 +3327,9 @@ Return a JSON object with this exact shape:
|
|
|
3305
3327
|
"copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
|
|
3306
3328
|
},
|
|
3307
3329
|
"changesSummary": "<1-2 sentence summary of what was updated and why>",
|
|
3308
|
-
"fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
|
|
3309
3330
|
"docsUpdated": ["CLAUDE.md", "README.md"]
|
|
3310
3331
|
}
|
|
3311
3332
|
|
|
3312
|
-
The "fileChanges" array MUST include one entry per file that was updated (non-null in updatedDocs). Each entry describes what specifically changed in that file \u2014 be concrete (e.g. "added auth middleware section" not "updated docs").
|
|
3313
|
-
|
|
3314
3333
|
Respond with ONLY the JSON object, no markdown fences or extra text.`;
|
|
3315
3334
|
var LEARN_SYSTEM_PROMPT = `You are an expert developer experience engineer. You analyze raw tool call events from AI coding sessions to extract reusable operational lessons that will help future LLM sessions work more effectively in this project.
|
|
3316
3335
|
|
|
@@ -3459,7 +3478,7 @@ async function detectProjectStack(fileTree, suffixCounts) {
|
|
|
3459
3478
|
init_config();
|
|
3460
3479
|
|
|
3461
3480
|
// src/fingerprint/cache.ts
|
|
3462
|
-
import
|
|
3481
|
+
import fs7 from "fs";
|
|
3463
3482
|
import path7 from "path";
|
|
3464
3483
|
import crypto from "crypto";
|
|
3465
3484
|
import { execSync as execSync7 } from "child_process";
|
|
@@ -3504,8 +3523,8 @@ function computeTreeSignature(fileTree, dir) {
|
|
|
3504
3523
|
function loadFingerprintCache(dir, fileTree) {
|
|
3505
3524
|
const cachePath = getCachePath(dir);
|
|
3506
3525
|
try {
|
|
3507
|
-
if (!
|
|
3508
|
-
const raw =
|
|
3526
|
+
if (!fs7.existsSync(cachePath)) return null;
|
|
3527
|
+
const raw = fs7.readFileSync(cachePath, "utf-8");
|
|
3509
3528
|
const cache = JSON.parse(raw);
|
|
3510
3529
|
if (cache.version !== CACHE_VERSION) return null;
|
|
3511
3530
|
const currentHead = getGitHead(dir);
|
|
@@ -3527,8 +3546,8 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
|
|
|
3527
3546
|
const cachePath = getCachePath(dir);
|
|
3528
3547
|
try {
|
|
3529
3548
|
const cacheDir = path7.dirname(cachePath);
|
|
3530
|
-
if (!
|
|
3531
|
-
|
|
3549
|
+
if (!fs7.existsSync(cacheDir)) {
|
|
3550
|
+
fs7.mkdirSync(cacheDir, { recursive: true });
|
|
3532
3551
|
}
|
|
3533
3552
|
const cache = {
|
|
3534
3553
|
version: CACHE_VERSION,
|
|
@@ -3540,15 +3559,15 @@ function saveFingerprintCache(dir, fileTree, codeAnalysis, languages, frameworks
|
|
|
3540
3559
|
tools,
|
|
3541
3560
|
workspaces
|
|
3542
3561
|
};
|
|
3543
|
-
|
|
3562
|
+
fs7.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
|
|
3544
3563
|
} catch {
|
|
3545
3564
|
}
|
|
3546
3565
|
}
|
|
3547
3566
|
function getDetectedWorkspaces(dir) {
|
|
3548
3567
|
const cachePath = getCachePath(dir);
|
|
3549
3568
|
try {
|
|
3550
|
-
if (!
|
|
3551
|
-
const raw =
|
|
3569
|
+
if (!fs7.existsSync(cachePath)) return [];
|
|
3570
|
+
const raw = fs7.readFileSync(cachePath, "utf-8");
|
|
3552
3571
|
const cache = JSON.parse(raw);
|
|
3553
3572
|
return cache.workspaces ?? [];
|
|
3554
3573
|
} catch {
|
|
@@ -3601,8 +3620,8 @@ async function collectFingerprint(dir) {
|
|
|
3601
3620
|
function readPackageName(dir) {
|
|
3602
3621
|
try {
|
|
3603
3622
|
const pkgPath = path8.join(dir, "package.json");
|
|
3604
|
-
if (!
|
|
3605
|
-
const pkg3 = JSON.parse(
|
|
3623
|
+
if (!fs8.existsSync(pkgPath)) return void 0;
|
|
3624
|
+
const pkg3 = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
3606
3625
|
return pkg3.name;
|
|
3607
3626
|
} catch {
|
|
3608
3627
|
return void 0;
|
|
@@ -3632,22 +3651,22 @@ async function enrichWithLLM(fingerprint) {
|
|
|
3632
3651
|
}
|
|
3633
3652
|
|
|
3634
3653
|
// src/scanner/index.ts
|
|
3635
|
-
import
|
|
3654
|
+
import fs9 from "fs";
|
|
3636
3655
|
import path9 from "path";
|
|
3637
3656
|
import crypto2 from "crypto";
|
|
3638
3657
|
import os4 from "os";
|
|
3639
3658
|
function detectPlatforms() {
|
|
3640
3659
|
const home = os4.homedir();
|
|
3641
3660
|
return {
|
|
3642
|
-
claude:
|
|
3643
|
-
cursor:
|
|
3644
|
-
codex:
|
|
3661
|
+
claude: fs9.existsSync(path9.join(home, ".claude")),
|
|
3662
|
+
cursor: fs9.existsSync(getCursorConfigDir()),
|
|
3663
|
+
codex: fs9.existsSync(path9.join(home, ".codex"))
|
|
3645
3664
|
};
|
|
3646
3665
|
}
|
|
3647
3666
|
function scanLocalState(dir) {
|
|
3648
3667
|
const items = [];
|
|
3649
3668
|
const claudeMdPath = path9.join(dir, "CLAUDE.md");
|
|
3650
|
-
if (
|
|
3669
|
+
if (fs9.existsSync(claudeMdPath)) {
|
|
3651
3670
|
items.push({
|
|
3652
3671
|
type: "rule",
|
|
3653
3672
|
platform: "claude",
|
|
@@ -3657,8 +3676,8 @@ function scanLocalState(dir) {
|
|
|
3657
3676
|
});
|
|
3658
3677
|
}
|
|
3659
3678
|
const skillsDir = path9.join(dir, ".claude", "skills");
|
|
3660
|
-
if (
|
|
3661
|
-
for (const file of
|
|
3679
|
+
if (fs9.existsSync(skillsDir)) {
|
|
3680
|
+
for (const file of fs9.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
3662
3681
|
const filePath = path9.join(skillsDir, file);
|
|
3663
3682
|
items.push({
|
|
3664
3683
|
type: "skill",
|
|
@@ -3670,9 +3689,9 @@ function scanLocalState(dir) {
|
|
|
3670
3689
|
}
|
|
3671
3690
|
}
|
|
3672
3691
|
const mcpJsonPath = path9.join(dir, ".mcp.json");
|
|
3673
|
-
if (
|
|
3692
|
+
if (fs9.existsSync(mcpJsonPath)) {
|
|
3674
3693
|
try {
|
|
3675
|
-
const mcpJson = JSON.parse(
|
|
3694
|
+
const mcpJson = JSON.parse(fs9.readFileSync(mcpJsonPath, "utf-8"));
|
|
3676
3695
|
if (mcpJson.mcpServers) {
|
|
3677
3696
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3678
3697
|
items.push({
|
|
@@ -3689,7 +3708,7 @@ function scanLocalState(dir) {
|
|
|
3689
3708
|
}
|
|
3690
3709
|
}
|
|
3691
3710
|
const agentsMdPath = path9.join(dir, "AGENTS.md");
|
|
3692
|
-
if (
|
|
3711
|
+
if (fs9.existsSync(agentsMdPath)) {
|
|
3693
3712
|
items.push({
|
|
3694
3713
|
type: "rule",
|
|
3695
3714
|
platform: "codex",
|
|
@@ -3699,11 +3718,11 @@ function scanLocalState(dir) {
|
|
|
3699
3718
|
});
|
|
3700
3719
|
}
|
|
3701
3720
|
const codexSkillsDir = path9.join(dir, ".agents", "skills");
|
|
3702
|
-
if (
|
|
3721
|
+
if (fs9.existsSync(codexSkillsDir)) {
|
|
3703
3722
|
try {
|
|
3704
|
-
for (const name of
|
|
3723
|
+
for (const name of fs9.readdirSync(codexSkillsDir)) {
|
|
3705
3724
|
const skillFile = path9.join(codexSkillsDir, name, "SKILL.md");
|
|
3706
|
-
if (
|
|
3725
|
+
if (fs9.existsSync(skillFile)) {
|
|
3707
3726
|
items.push({
|
|
3708
3727
|
type: "skill",
|
|
3709
3728
|
platform: "codex",
|
|
@@ -3718,7 +3737,7 @@ function scanLocalState(dir) {
|
|
|
3718
3737
|
}
|
|
3719
3738
|
}
|
|
3720
3739
|
const cursorrulesPath = path9.join(dir, ".cursorrules");
|
|
3721
|
-
if (
|
|
3740
|
+
if (fs9.existsSync(cursorrulesPath)) {
|
|
3722
3741
|
items.push({
|
|
3723
3742
|
type: "rule",
|
|
3724
3743
|
platform: "cursor",
|
|
@@ -3728,8 +3747,8 @@ function scanLocalState(dir) {
|
|
|
3728
3747
|
});
|
|
3729
3748
|
}
|
|
3730
3749
|
const cursorRulesDir = path9.join(dir, ".cursor", "rules");
|
|
3731
|
-
if (
|
|
3732
|
-
for (const file of
|
|
3750
|
+
if (fs9.existsSync(cursorRulesDir)) {
|
|
3751
|
+
for (const file of fs9.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
3733
3752
|
const filePath = path9.join(cursorRulesDir, file);
|
|
3734
3753
|
items.push({
|
|
3735
3754
|
type: "rule",
|
|
@@ -3741,11 +3760,11 @@ function scanLocalState(dir) {
|
|
|
3741
3760
|
}
|
|
3742
3761
|
}
|
|
3743
3762
|
const cursorSkillsDir = path9.join(dir, ".cursor", "skills");
|
|
3744
|
-
if (
|
|
3763
|
+
if (fs9.existsSync(cursorSkillsDir)) {
|
|
3745
3764
|
try {
|
|
3746
|
-
for (const name of
|
|
3765
|
+
for (const name of fs9.readdirSync(cursorSkillsDir)) {
|
|
3747
3766
|
const skillFile = path9.join(cursorSkillsDir, name, "SKILL.md");
|
|
3748
|
-
if (
|
|
3767
|
+
if (fs9.existsSync(skillFile)) {
|
|
3749
3768
|
items.push({
|
|
3750
3769
|
type: "skill",
|
|
3751
3770
|
platform: "cursor",
|
|
@@ -3760,9 +3779,9 @@ function scanLocalState(dir) {
|
|
|
3760
3779
|
}
|
|
3761
3780
|
}
|
|
3762
3781
|
const cursorMcpPath = path9.join(dir, ".cursor", "mcp.json");
|
|
3763
|
-
if (
|
|
3782
|
+
if (fs9.existsSync(cursorMcpPath)) {
|
|
3764
3783
|
try {
|
|
3765
|
-
const mcpJson = JSON.parse(
|
|
3784
|
+
const mcpJson = JSON.parse(fs9.readFileSync(cursorMcpPath, "utf-8"));
|
|
3766
3785
|
if (mcpJson.mcpServers) {
|
|
3767
3786
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
3768
3787
|
items.push({
|
|
@@ -3781,7 +3800,7 @@ function scanLocalState(dir) {
|
|
|
3781
3800
|
return items;
|
|
3782
3801
|
}
|
|
3783
3802
|
function hashFile(filePath) {
|
|
3784
|
-
const text =
|
|
3803
|
+
const text = fs9.readFileSync(filePath, "utf-8");
|
|
3785
3804
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
3786
3805
|
}
|
|
3787
3806
|
function hashJson(obj) {
|
|
@@ -3804,7 +3823,7 @@ function getCursorConfigDir() {
|
|
|
3804
3823
|
|
|
3805
3824
|
// src/lib/hooks.ts
|
|
3806
3825
|
init_resolve_caliber();
|
|
3807
|
-
import
|
|
3826
|
+
import fs10 from "fs";
|
|
3808
3827
|
import path10 from "path";
|
|
3809
3828
|
import { execSync as execSync8 } from "child_process";
|
|
3810
3829
|
var SETTINGS_PATH = path10.join(".claude", "settings.json");
|
|
@@ -3814,17 +3833,17 @@ function getHookCommand() {
|
|
|
3814
3833
|
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3815
3834
|
}
|
|
3816
3835
|
function readSettings() {
|
|
3817
|
-
if (!
|
|
3836
|
+
if (!fs10.existsSync(SETTINGS_PATH)) return {};
|
|
3818
3837
|
try {
|
|
3819
|
-
return JSON.parse(
|
|
3838
|
+
return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3820
3839
|
} catch {
|
|
3821
3840
|
return {};
|
|
3822
3841
|
}
|
|
3823
3842
|
}
|
|
3824
3843
|
function writeSettings(settings) {
|
|
3825
3844
|
const dir = path10.dirname(SETTINGS_PATH);
|
|
3826
|
-
if (!
|
|
3827
|
-
|
|
3845
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3846
|
+
fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
3828
3847
|
}
|
|
3829
3848
|
function findHookIndex(sessionEnd) {
|
|
3830
3849
|
return sessionEnd.findIndex(
|
|
@@ -3883,7 +3902,7 @@ if ${guard}; then
|
|
|
3883
3902
|
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
3884
3903
|
${invoke} refresh 2>/dev/null || true
|
|
3885
3904
|
${invoke} learn finalize 2>/dev/null || true
|
|
3886
|
-
git diff --name-only -- CLAUDE.md .claude/ .cursor/
|
|
3905
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
3887
3906
|
fi
|
|
3888
3907
|
${PRECOMMIT_END}`;
|
|
3889
3908
|
}
|
|
@@ -3901,8 +3920,8 @@ function getPreCommitPath() {
|
|
|
3901
3920
|
}
|
|
3902
3921
|
function isPreCommitHookInstalled() {
|
|
3903
3922
|
const hookPath = getPreCommitPath();
|
|
3904
|
-
if (!hookPath || !
|
|
3905
|
-
const content =
|
|
3923
|
+
if (!hookPath || !fs10.existsSync(hookPath)) return false;
|
|
3924
|
+
const content = fs10.readFileSync(hookPath, "utf-8");
|
|
3906
3925
|
return content.includes(PRECOMMIT_START);
|
|
3907
3926
|
}
|
|
3908
3927
|
function installPreCommitHook() {
|
|
@@ -3912,40 +3931,40 @@ function installPreCommitHook() {
|
|
|
3912
3931
|
const hookPath = getPreCommitPath();
|
|
3913
3932
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3914
3933
|
const hooksDir = path10.dirname(hookPath);
|
|
3915
|
-
if (!
|
|
3934
|
+
if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
|
|
3916
3935
|
let content = "";
|
|
3917
|
-
if (
|
|
3918
|
-
content =
|
|
3936
|
+
if (fs10.existsSync(hookPath)) {
|
|
3937
|
+
content = fs10.readFileSync(hookPath, "utf-8");
|
|
3919
3938
|
if (!content.endsWith("\n")) content += "\n";
|
|
3920
3939
|
content += "\n" + getPrecommitBlock() + "\n";
|
|
3921
3940
|
} else {
|
|
3922
3941
|
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3923
3942
|
}
|
|
3924
|
-
|
|
3925
|
-
|
|
3943
|
+
fs10.writeFileSync(hookPath, content);
|
|
3944
|
+
fs10.chmodSync(hookPath, 493);
|
|
3926
3945
|
return { installed: true, alreadyInstalled: false };
|
|
3927
3946
|
}
|
|
3928
3947
|
function removePreCommitHook() {
|
|
3929
3948
|
const hookPath = getPreCommitPath();
|
|
3930
|
-
if (!hookPath || !
|
|
3949
|
+
if (!hookPath || !fs10.existsSync(hookPath)) {
|
|
3931
3950
|
return { removed: false, notFound: true };
|
|
3932
3951
|
}
|
|
3933
|
-
let content =
|
|
3952
|
+
let content = fs10.readFileSync(hookPath, "utf-8");
|
|
3934
3953
|
if (!content.includes(PRECOMMIT_START)) {
|
|
3935
3954
|
return { removed: false, notFound: true };
|
|
3936
3955
|
}
|
|
3937
3956
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3938
3957
|
content = content.replace(regex, "\n");
|
|
3939
3958
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3940
|
-
|
|
3959
|
+
fs10.unlinkSync(hookPath);
|
|
3941
3960
|
} else {
|
|
3942
|
-
|
|
3961
|
+
fs10.writeFileSync(hookPath, content);
|
|
3943
3962
|
}
|
|
3944
3963
|
return { removed: true, notFound: false };
|
|
3945
3964
|
}
|
|
3946
3965
|
|
|
3947
3966
|
// src/fingerprint/sources.ts
|
|
3948
|
-
import
|
|
3967
|
+
import fs11 from "fs";
|
|
3949
3968
|
import path11 from "path";
|
|
3950
3969
|
|
|
3951
3970
|
// src/scoring/utils.ts
|
|
@@ -4013,12 +4032,14 @@ function isGitRepo2(dir) {
|
|
|
4013
4032
|
function checkGitIgnored(dir, paths) {
|
|
4014
4033
|
if (paths.length === 0) return /* @__PURE__ */ new Set();
|
|
4015
4034
|
try {
|
|
4016
|
-
const result = execFileSync(
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4035
|
+
const result = execFileSync("git", ["check-ignore", ...paths], {
|
|
4036
|
+
cwd: dir,
|
|
4037
|
+
encoding: "utf-8",
|
|
4038
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4039
|
+
});
|
|
4040
|
+
return new Set(
|
|
4041
|
+
result.split("\n").map((l) => l.trim()).filter(Boolean)
|
|
4020
4042
|
);
|
|
4021
|
-
return new Set(result.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
4022
4043
|
} catch (err) {
|
|
4023
4044
|
if (err && typeof err === "object" && "status" in err && err.status === 1) {
|
|
4024
4045
|
return /* @__PURE__ */ new Set();
|
|
@@ -4042,7 +4063,10 @@ function collectProjectStructure(dir, maxDepth = 2) {
|
|
|
4042
4063
|
if (name.startsWith(".") && IGNORED_DIRS.has(name)) continue;
|
|
4043
4064
|
dirEntries.push({ name, rel: relative(dir, join(currentDir, name)) });
|
|
4044
4065
|
}
|
|
4045
|
-
const gitIgnored = useGit ? checkGitIgnored(
|
|
4066
|
+
const gitIgnored = useGit ? checkGitIgnored(
|
|
4067
|
+
dir,
|
|
4068
|
+
dirEntries.map((d) => d.rel)
|
|
4069
|
+
) : null;
|
|
4046
4070
|
for (const entry of entries) {
|
|
4047
4071
|
const name = entry.name;
|
|
4048
4072
|
if (name.startsWith(".") && IGNORED_DIRS.has(name)) continue;
|
|
@@ -4225,7 +4249,9 @@ function countTreeLines(content) {
|
|
|
4225
4249
|
return count;
|
|
4226
4250
|
}
|
|
4227
4251
|
function calculateDuplicatePercent(content1, content2) {
|
|
4228
|
-
const lines1 = new Set(
|
|
4252
|
+
const lines1 = new Set(
|
|
4253
|
+
content1.split("\n").map((l) => l.trim()).filter((l) => l.length > 10)
|
|
4254
|
+
);
|
|
4229
4255
|
const lines2 = content2.split("\n").map((l) => l.trim()).filter((l) => l.length > 10);
|
|
4230
4256
|
const overlapping = lines2.filter((l) => lines1.has(l)).length;
|
|
4231
4257
|
return lines2.length > 0 ? Math.round(overlapping / lines2.length * 100) : 0;
|
|
@@ -4244,7 +4270,9 @@ function isEntryMentioned(entry, contentLower) {
|
|
|
4244
4270
|
if (lastSegment && lastSegment.length > 3) variants.push(lastSegment);
|
|
4245
4271
|
return variants.some((v) => {
|
|
4246
4272
|
const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4247
|
-
return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(
|
|
4273
|
+
return new RegExp(`(?:^|[\\s\`/"'\\.,(])${escaped}(?:[\\s\`/"'.,;:!?)\\\\]|$)`, "i").test(
|
|
4274
|
+
contentLower
|
|
4275
|
+
);
|
|
4248
4276
|
});
|
|
4249
4277
|
}
|
|
4250
4278
|
function classifyLine(line, inCodeBlock) {
|
|
@@ -4255,7 +4283,8 @@ function classifyLine(line, inCodeBlock) {
|
|
|
4255
4283
|
if (/`[^`]+`/.test(trimmed)) return "concrete";
|
|
4256
4284
|
if (/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\.[a-zA-Z]{1,5}/.test(trimmed)) return "concrete";
|
|
4257
4285
|
if (/[a-zA-Z0-9_]{4,}\/[a-zA-Z0-9_.-]/.test(trimmed)) return "concrete";
|
|
4258
|
-
if (/\b[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}\b/.test(trimmed) && !/\b(e\.g|i\.e|vs|etc)\b/i.test(trimmed))
|
|
4286
|
+
if (/\b[a-zA-Z0-9_-]+\.[a-zA-Z]{1,5}\b/.test(trimmed) && !/\b(e\.g|i\.e|vs|etc)\b/i.test(trimmed))
|
|
4287
|
+
return "concrete";
|
|
4259
4288
|
return "abstract";
|
|
4260
4289
|
}
|
|
4261
4290
|
|
|
@@ -4271,12 +4300,12 @@ function loadSourcesConfig(dir) {
|
|
|
4271
4300
|
try {
|
|
4272
4301
|
const parsed = JSON.parse(content);
|
|
4273
4302
|
if (!Array.isArray(parsed.sources)) {
|
|
4274
|
-
console.warn(
|
|
4303
|
+
console.warn(
|
|
4304
|
+
"Warning: .caliber/sources.json is malformed (missing sources array), skipping sources"
|
|
4305
|
+
);
|
|
4275
4306
|
return [];
|
|
4276
4307
|
}
|
|
4277
|
-
return parsed.sources.filter(
|
|
4278
|
-
(s) => s.type && (s.path || s.url)
|
|
4279
|
-
);
|
|
4308
|
+
return parsed.sources.filter((s) => s.type && (s.path || s.url));
|
|
4280
4309
|
} catch {
|
|
4281
4310
|
console.warn("Warning: .caliber/sources.json is malformed, skipping sources");
|
|
4282
4311
|
return [];
|
|
@@ -4284,15 +4313,15 @@ function loadSourcesConfig(dir) {
|
|
|
4284
4313
|
}
|
|
4285
4314
|
function writeSourcesConfig(dir, sources2) {
|
|
4286
4315
|
const configDir = path11.join(dir, ".caliber");
|
|
4287
|
-
if (!
|
|
4288
|
-
|
|
4316
|
+
if (!fs11.existsSync(configDir)) {
|
|
4317
|
+
fs11.mkdirSync(configDir, { recursive: true });
|
|
4289
4318
|
}
|
|
4290
4319
|
const configPath = path11.join(configDir, "sources.json");
|
|
4291
|
-
|
|
4320
|
+
fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
4292
4321
|
}
|
|
4293
4322
|
function detectSourceType(absPath) {
|
|
4294
4323
|
try {
|
|
4295
|
-
return
|
|
4324
|
+
return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
4296
4325
|
} catch {
|
|
4297
4326
|
return "file";
|
|
4298
4327
|
}
|
|
@@ -4336,7 +4365,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
4336
4365
|
for (const [absPath, resolved] of seen) {
|
|
4337
4366
|
let stat;
|
|
4338
4367
|
try {
|
|
4339
|
-
stat =
|
|
4368
|
+
stat = fs11.statSync(absPath);
|
|
4340
4369
|
} catch {
|
|
4341
4370
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
4342
4371
|
continue;
|
|
@@ -4385,13 +4414,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
4385
4414
|
}
|
|
4386
4415
|
return collectRepoSummary(resolved, projectDir);
|
|
4387
4416
|
}
|
|
4388
|
-
function collectRepoSummary(resolved,
|
|
4417
|
+
function collectRepoSummary(resolved, _projectDir) {
|
|
4389
4418
|
const { config, origin, absPath } = resolved;
|
|
4390
4419
|
const packageName = readPackageName(absPath);
|
|
4391
4420
|
let topLevelDirs;
|
|
4392
4421
|
let keyFiles;
|
|
4393
4422
|
try {
|
|
4394
|
-
const entries =
|
|
4423
|
+
const entries = fs11.readdirSync(absPath, { withFileTypes: true });
|
|
4395
4424
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
4396
4425
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
4397
4426
|
} catch {
|
|
@@ -4415,7 +4444,7 @@ function collectRepoSummary(resolved, projectDir) {
|
|
|
4415
4444
|
packageName
|
|
4416
4445
|
};
|
|
4417
4446
|
}
|
|
4418
|
-
function collectFileSummary(resolved,
|
|
4447
|
+
function collectFileSummary(resolved, _projectDir) {
|
|
4419
4448
|
const { config, origin, absPath } = resolved;
|
|
4420
4449
|
const content = readFileOrNull(absPath);
|
|
4421
4450
|
return {
|
|
@@ -4463,15 +4492,15 @@ init_config();
|
|
|
4463
4492
|
// src/utils/dependencies.ts
|
|
4464
4493
|
import { readFileSync as readFileSync2 } from "fs";
|
|
4465
4494
|
import { join as join2 } from "path";
|
|
4466
|
-
function readFileOrNull2(
|
|
4495
|
+
function readFileOrNull2(path42) {
|
|
4467
4496
|
try {
|
|
4468
|
-
return readFileSync2(
|
|
4497
|
+
return readFileSync2(path42, "utf-8");
|
|
4469
4498
|
} catch {
|
|
4470
4499
|
return null;
|
|
4471
4500
|
}
|
|
4472
4501
|
}
|
|
4473
|
-
function readJsonOrNull(
|
|
4474
|
-
const content = readFileOrNull2(
|
|
4502
|
+
function readJsonOrNull(path42) {
|
|
4503
|
+
const content = readFileOrNull2(path42);
|
|
4475
4504
|
if (!content) return null;
|
|
4476
4505
|
try {
|
|
4477
4506
|
return JSON.parse(content);
|
|
@@ -4513,18 +4542,24 @@ function extractNpmDeps(dir) {
|
|
|
4513
4542
|
/^prettier-/,
|
|
4514
4543
|
/^@typescript-eslint\//
|
|
4515
4544
|
];
|
|
4516
|
-
return Object.keys(deps).filter(
|
|
4545
|
+
return Object.keys(deps).filter(
|
|
4546
|
+
(d) => !trivial.has(d) && !d.startsWith("@types/") && !trivialPatterns.some((p) => p.test(d))
|
|
4547
|
+
).slice(0, 30);
|
|
4517
4548
|
}
|
|
4518
4549
|
function extractPythonDeps(dir) {
|
|
4519
4550
|
const reqTxt = readFileOrNull2(join2(dir, "requirements.txt"));
|
|
4520
4551
|
if (reqTxt) {
|
|
4521
|
-
return reqTxt.split("\n").map(
|
|
4552
|
+
return reqTxt.split("\n").map(
|
|
4553
|
+
(l) => l.trim().split(/[=<>!~[]/)[0].trim()
|
|
4554
|
+
).filter((l) => l && !l.startsWith("#")).slice(0, 30);
|
|
4522
4555
|
}
|
|
4523
4556
|
const pyproject = readFileOrNull2(join2(dir, "pyproject.toml"));
|
|
4524
4557
|
if (pyproject) {
|
|
4525
4558
|
const depMatch = pyproject.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
4526
4559
|
if (depMatch) {
|
|
4527
|
-
return depMatch[1].split("\n").map(
|
|
4560
|
+
return depMatch[1].split("\n").map(
|
|
4561
|
+
(l) => l.trim().replace(/["',]/g, "").split(/[=<>!~[]/)[0].trim()
|
|
4562
|
+
).filter((l) => l.length > 0).slice(0, 30);
|
|
4528
4563
|
}
|
|
4529
4564
|
}
|
|
4530
4565
|
return [];
|
|
@@ -5062,16 +5097,16 @@ import fs19 from "fs";
|
|
|
5062
5097
|
|
|
5063
5098
|
// src/writers/claude/index.ts
|
|
5064
5099
|
init_pre_commit_block();
|
|
5065
|
-
import
|
|
5100
|
+
import fs12 from "fs";
|
|
5066
5101
|
import path12 from "path";
|
|
5067
5102
|
function writeClaudeConfig(config) {
|
|
5068
5103
|
const written = [];
|
|
5069
|
-
|
|
5104
|
+
fs12.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
|
|
5070
5105
|
written.push("CLAUDE.md");
|
|
5071
5106
|
if (config.skills?.length) {
|
|
5072
5107
|
for (const skill of config.skills) {
|
|
5073
5108
|
const skillDir = path12.join(".claude", "skills", skill.name);
|
|
5074
|
-
if (!
|
|
5109
|
+
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
5075
5110
|
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
5076
5111
|
const frontmatter = [
|
|
5077
5112
|
"---",
|
|
@@ -5080,21 +5115,21 @@ function writeClaudeConfig(config) {
|
|
|
5080
5115
|
"---",
|
|
5081
5116
|
""
|
|
5082
5117
|
].join("\n");
|
|
5083
|
-
|
|
5118
|
+
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5084
5119
|
written.push(skillPath);
|
|
5085
5120
|
}
|
|
5086
5121
|
}
|
|
5087
5122
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
5088
5123
|
let existingServers = {};
|
|
5089
5124
|
try {
|
|
5090
|
-
if (
|
|
5091
|
-
const existing = JSON.parse(
|
|
5125
|
+
if (fs12.existsSync(".mcp.json")) {
|
|
5126
|
+
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
5092
5127
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
5093
5128
|
}
|
|
5094
5129
|
} catch {
|
|
5095
5130
|
}
|
|
5096
5131
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
5097
|
-
|
|
5132
|
+
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
5098
5133
|
written.push(".mcp.json");
|
|
5099
5134
|
}
|
|
5100
5135
|
return written;
|
|
@@ -5102,30 +5137,28 @@ function writeClaudeConfig(config) {
|
|
|
5102
5137
|
|
|
5103
5138
|
// src/writers/cursor/index.ts
|
|
5104
5139
|
init_pre_commit_block();
|
|
5105
|
-
import
|
|
5140
|
+
import fs13 from "fs";
|
|
5106
5141
|
import path13 from "path";
|
|
5107
5142
|
function writeCursorConfig(config) {
|
|
5108
5143
|
const written = [];
|
|
5109
5144
|
if (config.cursorrules) {
|
|
5110
|
-
|
|
5145
|
+
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
5111
5146
|
written.push(".cursorrules");
|
|
5112
5147
|
}
|
|
5113
5148
|
const preCommitRule = getCursorPreCommitRule();
|
|
5114
5149
|
const learningsRule = getCursorLearningsRule();
|
|
5115
|
-
const
|
|
5116
|
-
const setupRule = getCursorSetupRule();
|
|
5117
|
-
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule, setupRule];
|
|
5150
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule];
|
|
5118
5151
|
const rulesDir = path13.join(".cursor", "rules");
|
|
5119
|
-
if (!
|
|
5152
|
+
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
5120
5153
|
for (const rule of allRules) {
|
|
5121
5154
|
const rulePath = path13.join(rulesDir, rule.filename);
|
|
5122
|
-
|
|
5155
|
+
fs13.writeFileSync(rulePath, rule.content);
|
|
5123
5156
|
written.push(rulePath);
|
|
5124
5157
|
}
|
|
5125
5158
|
if (config.skills?.length) {
|
|
5126
5159
|
for (const skill of config.skills) {
|
|
5127
5160
|
const skillDir = path13.join(".cursor", "skills", skill.name);
|
|
5128
|
-
if (!
|
|
5161
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
5129
5162
|
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
5130
5163
|
const frontmatter = [
|
|
5131
5164
|
"---",
|
|
@@ -5134,24 +5167,24 @@ function writeCursorConfig(config) {
|
|
|
5134
5167
|
"---",
|
|
5135
5168
|
""
|
|
5136
5169
|
].join("\n");
|
|
5137
|
-
|
|
5170
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5138
5171
|
written.push(skillPath);
|
|
5139
5172
|
}
|
|
5140
5173
|
}
|
|
5141
5174
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
5142
5175
|
const cursorDir = ".cursor";
|
|
5143
|
-
if (!
|
|
5176
|
+
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
5144
5177
|
const mcpPath = path13.join(cursorDir, "mcp.json");
|
|
5145
5178
|
let existingServers = {};
|
|
5146
5179
|
try {
|
|
5147
|
-
if (
|
|
5148
|
-
const existing = JSON.parse(
|
|
5180
|
+
if (fs13.existsSync(mcpPath)) {
|
|
5181
|
+
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
5149
5182
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
5150
5183
|
}
|
|
5151
5184
|
} catch {
|
|
5152
5185
|
}
|
|
5153
5186
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
5154
|
-
|
|
5187
|
+
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
5155
5188
|
written.push(mcpPath);
|
|
5156
5189
|
}
|
|
5157
5190
|
return written;
|
|
@@ -5159,16 +5192,16 @@ function writeCursorConfig(config) {
|
|
|
5159
5192
|
|
|
5160
5193
|
// src/writers/codex/index.ts
|
|
5161
5194
|
init_pre_commit_block();
|
|
5162
|
-
import
|
|
5195
|
+
import fs14 from "fs";
|
|
5163
5196
|
import path14 from "path";
|
|
5164
5197
|
function writeCodexConfig(config) {
|
|
5165
5198
|
const written = [];
|
|
5166
|
-
|
|
5199
|
+
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
5167
5200
|
written.push("AGENTS.md");
|
|
5168
5201
|
if (config.skills?.length) {
|
|
5169
5202
|
for (const skill of config.skills) {
|
|
5170
5203
|
const skillDir = path14.join(".agents", "skills", skill.name);
|
|
5171
|
-
if (!
|
|
5204
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
5172
5205
|
const skillPath = path14.join(skillDir, "SKILL.md");
|
|
5173
5206
|
const frontmatter = [
|
|
5174
5207
|
"---",
|
|
@@ -5177,7 +5210,7 @@ function writeCodexConfig(config) {
|
|
|
5177
5210
|
"---",
|
|
5178
5211
|
""
|
|
5179
5212
|
].join("\n");
|
|
5180
|
-
|
|
5213
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5181
5214
|
written.push(skillPath);
|
|
5182
5215
|
}
|
|
5183
5216
|
}
|
|
@@ -5186,20 +5219,20 @@ function writeCodexConfig(config) {
|
|
|
5186
5219
|
|
|
5187
5220
|
// src/writers/github-copilot/index.ts
|
|
5188
5221
|
init_pre_commit_block();
|
|
5189
|
-
import
|
|
5222
|
+
import fs15 from "fs";
|
|
5190
5223
|
import path15 from "path";
|
|
5191
5224
|
function writeGithubCopilotConfig(config) {
|
|
5192
5225
|
const written = [];
|
|
5193
5226
|
if (config.instructions) {
|
|
5194
|
-
|
|
5195
|
-
|
|
5227
|
+
fs15.mkdirSync(".github", { recursive: true });
|
|
5228
|
+
fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
|
|
5196
5229
|
written.push(".github/copilot-instructions.md");
|
|
5197
5230
|
}
|
|
5198
5231
|
if (config.instructionFiles?.length) {
|
|
5199
5232
|
const instructionsDir = path15.join(".github", "instructions");
|
|
5200
|
-
|
|
5233
|
+
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
5201
5234
|
for (const file of config.instructionFiles) {
|
|
5202
|
-
|
|
5235
|
+
fs15.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
|
|
5203
5236
|
written.push(`.github/instructions/${file.filename}`);
|
|
5204
5237
|
}
|
|
5205
5238
|
}
|
|
@@ -5207,30 +5240,30 @@ function writeGithubCopilotConfig(config) {
|
|
|
5207
5240
|
}
|
|
5208
5241
|
|
|
5209
5242
|
// src/writers/backup.ts
|
|
5210
|
-
import
|
|
5243
|
+
import fs16 from "fs";
|
|
5211
5244
|
import path16 from "path";
|
|
5212
5245
|
function createBackup(files) {
|
|
5213
5246
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5214
5247
|
const backupDir = path16.join(BACKUPS_DIR, timestamp);
|
|
5215
5248
|
for (const file of files) {
|
|
5216
|
-
if (!
|
|
5249
|
+
if (!fs16.existsSync(file)) continue;
|
|
5217
5250
|
const dest = path16.join(backupDir, file);
|
|
5218
5251
|
const destDir = path16.dirname(dest);
|
|
5219
|
-
if (!
|
|
5220
|
-
|
|
5252
|
+
if (!fs16.existsSync(destDir)) {
|
|
5253
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5221
5254
|
}
|
|
5222
|
-
|
|
5255
|
+
fs16.copyFileSync(file, dest);
|
|
5223
5256
|
}
|
|
5224
5257
|
return backupDir;
|
|
5225
5258
|
}
|
|
5226
5259
|
function restoreBackup(backupDir, file) {
|
|
5227
5260
|
const backupFile = path16.join(backupDir, file);
|
|
5228
|
-
if (!
|
|
5261
|
+
if (!fs16.existsSync(backupFile)) return false;
|
|
5229
5262
|
const destDir = path16.dirname(file);
|
|
5230
|
-
if (!
|
|
5231
|
-
|
|
5263
|
+
if (!fs16.existsSync(destDir)) {
|
|
5264
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5232
5265
|
}
|
|
5233
|
-
|
|
5266
|
+
fs16.copyFileSync(backupFile, file);
|
|
5234
5267
|
return true;
|
|
5235
5268
|
}
|
|
5236
5269
|
|
|
@@ -5263,10 +5296,7 @@ function fileChecksum(filePath) {
|
|
|
5263
5296
|
function writeSetup(setup) {
|
|
5264
5297
|
const filesToWrite = getFilesToWrite(setup);
|
|
5265
5298
|
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
|
|
5266
|
-
const existingFiles = [
|
|
5267
|
-
...filesToWrite.filter((f) => fs19.existsSync(f)),
|
|
5268
|
-
...filesToDelete
|
|
5269
|
-
];
|
|
5299
|
+
const existingFiles = [...filesToWrite.filter((f) => fs19.existsSync(f)), ...filesToDelete];
|
|
5270
5300
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
5271
5301
|
const written = [];
|
|
5272
5302
|
if (setup.targetAgent.includes("claude") && setup.claude) {
|
|
@@ -5359,7 +5389,8 @@ function getFilesToWrite(setup) {
|
|
|
5359
5389
|
if (setup.targetAgent.includes("github-copilot") && setup.copilot) {
|
|
5360
5390
|
if (setup.copilot.instructions) files.push(".github/copilot-instructions.md");
|
|
5361
5391
|
if (setup.copilot.instructionFiles) {
|
|
5362
|
-
for (const f of setup.copilot.instructionFiles)
|
|
5392
|
+
for (const f of setup.copilot.instructionFiles)
|
|
5393
|
+
files.push(`.github/instructions/${f.filename}`);
|
|
5363
5394
|
}
|
|
5364
5395
|
}
|
|
5365
5396
|
return files;
|
|
@@ -5378,10 +5409,10 @@ function ensureGitignore() {
|
|
|
5378
5409
|
|
|
5379
5410
|
// src/writers/staging.ts
|
|
5380
5411
|
import fs20 from "fs";
|
|
5381
|
-
import
|
|
5382
|
-
var STAGED_DIR =
|
|
5383
|
-
var PROPOSED_DIR =
|
|
5384
|
-
var CURRENT_DIR =
|
|
5412
|
+
import path18 from "path";
|
|
5413
|
+
var STAGED_DIR = path18.join(CALIBER_DIR, "staged");
|
|
5414
|
+
var PROPOSED_DIR = path18.join(STAGED_DIR, "proposed");
|
|
5415
|
+
var CURRENT_DIR = path18.join(STAGED_DIR, "current");
|
|
5385
5416
|
function normalizeContent(content) {
|
|
5386
5417
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
5387
5418
|
}
|
|
@@ -5391,22 +5422,29 @@ function stageFiles(files, projectDir) {
|
|
|
5391
5422
|
let modifiedFiles = 0;
|
|
5392
5423
|
const stagedFiles = [];
|
|
5393
5424
|
for (const file of files) {
|
|
5394
|
-
|
|
5425
|
+
assertPathWithinDir(file.path, projectDir);
|
|
5426
|
+
const originalPath = path18.join(projectDir, file.path);
|
|
5395
5427
|
if (fs20.existsSync(originalPath)) {
|
|
5396
5428
|
const existing = fs20.readFileSync(originalPath, "utf-8");
|
|
5397
5429
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
5398
5430
|
continue;
|
|
5399
5431
|
}
|
|
5400
5432
|
}
|
|
5401
|
-
const proposedPath =
|
|
5402
|
-
fs20.mkdirSync(
|
|
5433
|
+
const proposedPath = path18.join(PROPOSED_DIR, file.path);
|
|
5434
|
+
fs20.mkdirSync(path18.dirname(proposedPath), { recursive: true });
|
|
5403
5435
|
fs20.writeFileSync(proposedPath, file.content);
|
|
5404
5436
|
if (fs20.existsSync(originalPath)) {
|
|
5405
|
-
const currentPath =
|
|
5406
|
-
fs20.mkdirSync(
|
|
5437
|
+
const currentPath = path18.join(CURRENT_DIR, file.path);
|
|
5438
|
+
fs20.mkdirSync(path18.dirname(currentPath), { recursive: true });
|
|
5407
5439
|
fs20.copyFileSync(originalPath, currentPath);
|
|
5408
5440
|
modifiedFiles++;
|
|
5409
|
-
stagedFiles.push({
|
|
5441
|
+
stagedFiles.push({
|
|
5442
|
+
relativePath: file.path,
|
|
5443
|
+
proposedPath,
|
|
5444
|
+
currentPath,
|
|
5445
|
+
originalPath,
|
|
5446
|
+
isNew: false
|
|
5447
|
+
});
|
|
5410
5448
|
} else {
|
|
5411
5449
|
newFiles++;
|
|
5412
5450
|
stagedFiles.push({ relativePath: file.path, proposedPath, isNew: true });
|
|
@@ -5433,11 +5471,17 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
5433
5471
|
const skills = claude.skills;
|
|
5434
5472
|
if (Array.isArray(skills)) {
|
|
5435
5473
|
for (const skill of skills) {
|
|
5436
|
-
files.push({
|
|
5474
|
+
files.push({
|
|
5475
|
+
path: `.claude/skills/${sanitizePath(skill.name)}/SKILL.md`,
|
|
5476
|
+
content: buildSkillContent(skill)
|
|
5477
|
+
});
|
|
5437
5478
|
}
|
|
5438
5479
|
}
|
|
5439
5480
|
for (const builtin of BUILTIN_SKILLS) {
|
|
5440
|
-
files.push({
|
|
5481
|
+
files.push({
|
|
5482
|
+
path: `.claude/skills/${builtin.name}/SKILL.md`,
|
|
5483
|
+
content: buildSkillContent(builtin)
|
|
5484
|
+
});
|
|
5441
5485
|
}
|
|
5442
5486
|
}
|
|
5443
5487
|
if (codex) {
|
|
@@ -5445,38 +5489,58 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
5445
5489
|
const codexSkills = codex.skills;
|
|
5446
5490
|
if (Array.isArray(codexSkills)) {
|
|
5447
5491
|
for (const skill of codexSkills) {
|
|
5448
|
-
files.push({
|
|
5492
|
+
files.push({
|
|
5493
|
+
path: `.agents/skills/${sanitizePath(skill.name)}/SKILL.md`,
|
|
5494
|
+
content: buildSkillContent(skill)
|
|
5495
|
+
});
|
|
5449
5496
|
}
|
|
5450
5497
|
}
|
|
5451
5498
|
for (const builtin of BUILTIN_SKILLS) {
|
|
5452
|
-
files.push({
|
|
5499
|
+
files.push({
|
|
5500
|
+
path: `.agents/skills/${builtin.name}/SKILL.md`,
|
|
5501
|
+
content: buildSkillContent(builtin)
|
|
5502
|
+
});
|
|
5453
5503
|
}
|
|
5454
5504
|
}
|
|
5455
5505
|
if (cursor) {
|
|
5456
|
-
if (cursor.cursorrules)
|
|
5506
|
+
if (cursor.cursorrules)
|
|
5507
|
+
files.push({ path: ".cursorrules", content: cursor.cursorrules });
|
|
5457
5508
|
const cursorSkills = cursor.skills;
|
|
5458
5509
|
if (Array.isArray(cursorSkills)) {
|
|
5459
5510
|
for (const skill of cursorSkills) {
|
|
5460
|
-
files.push({
|
|
5511
|
+
files.push({
|
|
5512
|
+
path: `.cursor/skills/${sanitizePath(skill.name)}/SKILL.md`,
|
|
5513
|
+
content: buildSkillContent(skill)
|
|
5514
|
+
});
|
|
5461
5515
|
}
|
|
5462
5516
|
}
|
|
5463
5517
|
for (const builtin of BUILTIN_SKILLS) {
|
|
5464
|
-
files.push({
|
|
5518
|
+
files.push({
|
|
5519
|
+
path: `.cursor/skills/${builtin.name}/SKILL.md`,
|
|
5520
|
+
content: buildSkillContent(builtin)
|
|
5521
|
+
});
|
|
5465
5522
|
}
|
|
5466
5523
|
const rules = cursor.rules;
|
|
5467
5524
|
if (Array.isArray(rules)) {
|
|
5468
5525
|
for (const rule of rules) {
|
|
5469
|
-
files.push({ path: `.cursor/rules/${rule.filename}`, content: rule.content });
|
|
5526
|
+
files.push({ path: `.cursor/rules/${sanitizePath(rule.filename)}`, content: rule.content });
|
|
5470
5527
|
}
|
|
5471
5528
|
}
|
|
5472
5529
|
}
|
|
5473
5530
|
const copilot = setup.copilot;
|
|
5474
5531
|
if (copilot) {
|
|
5475
|
-
if (copilot.instructions)
|
|
5532
|
+
if (copilot.instructions)
|
|
5533
|
+
files.push({
|
|
5534
|
+
path: ".github/copilot-instructions.md",
|
|
5535
|
+
content: copilot.instructions
|
|
5536
|
+
});
|
|
5476
5537
|
const instructionFiles = copilot.instructionFiles;
|
|
5477
5538
|
if (Array.isArray(instructionFiles)) {
|
|
5478
5539
|
for (const file of instructionFiles) {
|
|
5479
|
-
files.push({
|
|
5540
|
+
files.push({
|
|
5541
|
+
path: `.github/instructions/${sanitizePath(file.filename)}`,
|
|
5542
|
+
content: file.content
|
|
5543
|
+
});
|
|
5480
5544
|
}
|
|
5481
5545
|
}
|
|
5482
5546
|
}
|
|
@@ -5485,7 +5549,8 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
5485
5549
|
const agentRefs = [];
|
|
5486
5550
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
5487
5551
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
5488
|
-
if (agentRefs.length === 0)
|
|
5552
|
+
if (agentRefs.length === 0)
|
|
5553
|
+
agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
5489
5554
|
const stubContent = `# AGENTS.md
|
|
5490
5555
|
|
|
5491
5556
|
This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
|
|
@@ -5500,8 +5565,8 @@ ${agentRefs.join(" ")}
|
|
|
5500
5565
|
// src/lib/learning-hooks.ts
|
|
5501
5566
|
init_resolve_caliber();
|
|
5502
5567
|
import fs22 from "fs";
|
|
5503
|
-
import
|
|
5504
|
-
var SETTINGS_PATH2 =
|
|
5568
|
+
import path19 from "path";
|
|
5569
|
+
var SETTINGS_PATH2 = path19.join(".claude", "settings.json");
|
|
5505
5570
|
var HOOK_TAILS = [
|
|
5506
5571
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
5507
5572
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
@@ -5526,7 +5591,7 @@ function readSettings2() {
|
|
|
5526
5591
|
}
|
|
5527
5592
|
}
|
|
5528
5593
|
function writeSettings2(settings) {
|
|
5529
|
-
const dir =
|
|
5594
|
+
const dir = path19.dirname(SETTINGS_PATH2);
|
|
5530
5595
|
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5531
5596
|
fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
5532
5597
|
}
|
|
@@ -5562,7 +5627,7 @@ function installLearningHooks() {
|
|
|
5562
5627
|
writeSettings2(settings);
|
|
5563
5628
|
return { installed: true, alreadyInstalled: false };
|
|
5564
5629
|
}
|
|
5565
|
-
var CURSOR_HOOKS_PATH =
|
|
5630
|
+
var CURSOR_HOOKS_PATH = path19.join(".cursor", "hooks.json");
|
|
5566
5631
|
var CURSOR_HOOK_EVENTS = [
|
|
5567
5632
|
{ event: "postToolUse", tail: "learn observe" },
|
|
5568
5633
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
@@ -5578,7 +5643,7 @@ function readCursorHooks() {
|
|
|
5578
5643
|
}
|
|
5579
5644
|
}
|
|
5580
5645
|
function writeCursorHooks(config) {
|
|
5581
|
-
const dir =
|
|
5646
|
+
const dir = path19.dirname(CURSOR_HOOKS_PATH);
|
|
5582
5647
|
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5583
5648
|
fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
5584
5649
|
}
|
|
@@ -5653,9 +5718,9 @@ init_resolve_caliber();
|
|
|
5653
5718
|
|
|
5654
5719
|
// src/lib/state.ts
|
|
5655
5720
|
import fs23 from "fs";
|
|
5656
|
-
import
|
|
5721
|
+
import path20 from "path";
|
|
5657
5722
|
import { execSync as execSync9 } from "child_process";
|
|
5658
|
-
var STATE_FILE =
|
|
5723
|
+
var STATE_FILE = path20.join(CALIBER_DIR, ".caliber-state.json");
|
|
5659
5724
|
function normalizeTargetAgent(value) {
|
|
5660
5725
|
if (Array.isArray(value)) return value;
|
|
5661
5726
|
if (typeof value === "string") {
|
|
@@ -5832,11 +5897,11 @@ var POINTS_LEARNED_CONTENT = 2;
|
|
|
5832
5897
|
var POINTS_SOURCES_CONFIGURED = 3;
|
|
5833
5898
|
var POINTS_SOURCES_REFERENCED = 3;
|
|
5834
5899
|
var TOKEN_BUDGET_THRESHOLDS = [
|
|
5835
|
-
{ maxTokens:
|
|
5836
|
-
{ maxTokens:
|
|
5837
|
-
{ maxTokens:
|
|
5838
|
-
{ maxTokens:
|
|
5839
|
-
{ maxTokens:
|
|
5900
|
+
{ maxTokens: 2e3, points: 6 },
|
|
5901
|
+
{ maxTokens: 3500, points: 5 },
|
|
5902
|
+
{ maxTokens: 5e3, points: 4 },
|
|
5903
|
+
{ maxTokens: 8e3, points: 2 },
|
|
5904
|
+
{ maxTokens: 12e3, points: 1 }
|
|
5840
5905
|
];
|
|
5841
5906
|
var CODE_BLOCK_THRESHOLDS = [
|
|
5842
5907
|
{ minBlocks: 3, points: 8 },
|
|
@@ -5866,8 +5931,8 @@ var SECRET_PATTERNS = [
|
|
|
5866
5931
|
/AKIA[A-Z0-9]{16}/,
|
|
5867
5932
|
/ghp_[a-zA-Z0-9]{36}/,
|
|
5868
5933
|
/ghu_[a-zA-Z0-9]{36}/,
|
|
5869
|
-
/glpat-[a-zA-Z0-
|
|
5870
|
-
/xox[bpors]-[a-zA-Z0-9
|
|
5934
|
+
/glpat-[a-zA-Z0-9_-]{20,}/,
|
|
5935
|
+
/xox[bpors]-[a-zA-Z0-9-]{10,}/,
|
|
5871
5936
|
/(?:password|secret|token|api_key)\s*[:=]\s*["'][^"']{8,}["']/i
|
|
5872
5937
|
];
|
|
5873
5938
|
var SECRET_PLACEHOLDER_PATTERNS = [
|
|
@@ -5879,31 +5944,13 @@ var SECRET_PLACEHOLDER_PATTERNS = [
|
|
|
5879
5944
|
/CHANGE[_-]?ME/i,
|
|
5880
5945
|
/<[^>]+>/
|
|
5881
5946
|
];
|
|
5882
|
-
var CURSOR_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
]);
|
|
5886
|
-
var
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
]);
|
|
5890
|
-
var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
5891
|
-
"cross_platform_parity",
|
|
5892
|
-
"no_duplicate_content"
|
|
5893
|
-
]);
|
|
5894
|
-
var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
5895
|
-
"codex_agents_md_exists"
|
|
5896
|
-
]);
|
|
5897
|
-
var COPILOT_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
5898
|
-
"copilot_instructions_exists"
|
|
5899
|
-
]);
|
|
5900
|
-
var NON_CODEX_CHECKS = /* @__PURE__ */ new Set([
|
|
5901
|
-
"agents_md_exists"
|
|
5902
|
-
]);
|
|
5903
|
-
var CLAUDE_OR_CODEX_CHECKS = /* @__PURE__ */ new Set([
|
|
5904
|
-
"skills_exist",
|
|
5905
|
-
"open_skills_format"
|
|
5906
|
-
]);
|
|
5947
|
+
var CURSOR_ONLY_CHECKS = /* @__PURE__ */ new Set(["cursor_rules_exist", "cursor_mdc_rules"]);
|
|
5948
|
+
var CLAUDE_ONLY_CHECKS = /* @__PURE__ */ new Set(["claude_md_exists", "claude_md_freshness"]);
|
|
5949
|
+
var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set(["cross_platform_parity", "no_duplicate_content"]);
|
|
5950
|
+
var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set(["codex_agents_md_exists"]);
|
|
5951
|
+
var COPILOT_ONLY_CHECKS = /* @__PURE__ */ new Set(["copilot_instructions_exists"]);
|
|
5952
|
+
var NON_CODEX_CHECKS = /* @__PURE__ */ new Set(["agents_md_exists"]);
|
|
5953
|
+
var CLAUDE_OR_CODEX_CHECKS = /* @__PURE__ */ new Set(["skills_exist", "open_skills_format"]);
|
|
5907
5954
|
var GRADE_THRESHOLDS = [
|
|
5908
5955
|
{ minScore: 85, grade: "A" },
|
|
5909
5956
|
{ minScore: 70, grade: "B" },
|
|
@@ -6170,7 +6217,12 @@ function checkQuality(dir) {
|
|
|
6170
6217
|
suggestion: concretenessPoints < 3 && totalMeaningful > 0 ? `${abstractCount} lines are generic prose \u2014 replace with specific instructions referencing project files` : void 0,
|
|
6171
6218
|
fix: concretenessPoints < 3 && totalMeaningful > 0 ? {
|
|
6172
6219
|
action: "replace_vague",
|
|
6173
|
-
data: {
|
|
6220
|
+
data: {
|
|
6221
|
+
abstractLines: abstractExamples,
|
|
6222
|
+
abstractCount,
|
|
6223
|
+
concreteCount,
|
|
6224
|
+
ratio: Math.round(concreteRatio * 100)
|
|
6225
|
+
},
|
|
6174
6226
|
instruction: `Replace generic prose with specific references. Examples of vague lines: ${abstractExamples.join("; ")}`
|
|
6175
6227
|
} : void 0
|
|
6176
6228
|
});
|
|
@@ -6297,7 +6349,7 @@ function checkGrounding(dir) {
|
|
|
6297
6349
|
}
|
|
6298
6350
|
|
|
6299
6351
|
// src/scoring/checks/accuracy.ts
|
|
6300
|
-
import { existsSync as existsSync4, statSync
|
|
6352
|
+
import { existsSync as existsSync4, statSync } from "fs";
|
|
6301
6353
|
import { execSync as execSync10 } from "child_process";
|
|
6302
6354
|
import { join as join5 } from "path";
|
|
6303
6355
|
init_resolve_caliber();
|
|
@@ -6323,7 +6375,7 @@ function detectGitDrift(dir) {
|
|
|
6323
6375
|
const filePath = join5(dir, file);
|
|
6324
6376
|
if (!existsSync4(filePath)) continue;
|
|
6325
6377
|
try {
|
|
6326
|
-
const mtime =
|
|
6378
|
+
const mtime = statSync(filePath).mtime.getTime();
|
|
6327
6379
|
if (mtime > headTime) {
|
|
6328
6380
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: "uncommitted (recently modified)", isGitRepo: true };
|
|
6329
6381
|
}
|
|
@@ -6436,7 +6488,7 @@ function checkAccuracy(dir) {
|
|
|
6436
6488
|
|
|
6437
6489
|
// src/scoring/checks/freshness.ts
|
|
6438
6490
|
init_resolve_caliber();
|
|
6439
|
-
import { existsSync as existsSync5, statSync as
|
|
6491
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
6440
6492
|
import { execSync as execSync11 } from "child_process";
|
|
6441
6493
|
import { join as join6 } from "path";
|
|
6442
6494
|
function getCommitsSinceConfigUpdate(dir) {
|
|
@@ -6451,7 +6503,7 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
6451
6503
|
const filePath = join6(dir, file);
|
|
6452
6504
|
if (!existsSync5(filePath)) continue;
|
|
6453
6505
|
try {
|
|
6454
|
-
const mtime =
|
|
6506
|
+
const mtime = statSync2(filePath).mtime.getTime();
|
|
6455
6507
|
if (mtime > headTime) {
|
|
6456
6508
|
return 0;
|
|
6457
6509
|
}
|
|
@@ -6749,8 +6801,8 @@ function checkSources(dir) {
|
|
|
6749
6801
|
|
|
6750
6802
|
// src/scoring/dismissed.ts
|
|
6751
6803
|
import fs24 from "fs";
|
|
6752
|
-
import
|
|
6753
|
-
var DISMISSED_FILE =
|
|
6804
|
+
import path21 from "path";
|
|
6805
|
+
var DISMISSED_FILE = path21.join(CALIBER_DIR, "dismissed-checks.json");
|
|
6754
6806
|
function readDismissedChecks() {
|
|
6755
6807
|
try {
|
|
6756
6808
|
if (!fs24.existsSync(DISMISSED_FILE)) return [];
|
|
@@ -7019,12 +7071,12 @@ import chalk5 from "chalk";
|
|
|
7019
7071
|
|
|
7020
7072
|
// src/telemetry/config.ts
|
|
7021
7073
|
import fs25 from "fs";
|
|
7022
|
-
import
|
|
7074
|
+
import path22 from "path";
|
|
7023
7075
|
import os5 from "os";
|
|
7024
7076
|
import crypto4 from "crypto";
|
|
7025
7077
|
import { execSync as execSync13 } from "child_process";
|
|
7026
|
-
var CONFIG_DIR2 =
|
|
7027
|
-
var CONFIG_FILE2 =
|
|
7078
|
+
var CONFIG_DIR2 = path22.join(os5.homedir(), ".caliber");
|
|
7079
|
+
var CONFIG_FILE2 = path22.join(CONFIG_DIR2, "config.json");
|
|
7028
7080
|
var runtimeDisabled = false;
|
|
7029
7081
|
function readConfig() {
|
|
7030
7082
|
try {
|
|
@@ -7207,8 +7259,8 @@ function trackInitSkillsSearch(searched, installedCount) {
|
|
|
7207
7259
|
function trackInitScoreRegression(oldScore, newScore) {
|
|
7208
7260
|
trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
|
|
7209
7261
|
}
|
|
7210
|
-
function trackInitCompleted(
|
|
7211
|
-
trackEvent("init_completed", { path:
|
|
7262
|
+
function trackInitCompleted(path42, score) {
|
|
7263
|
+
trackEvent("init_completed", { path: path42, score });
|
|
7212
7264
|
}
|
|
7213
7265
|
function trackRegenerateCompleted(action, durationMs) {
|
|
7214
7266
|
trackEvent("regenerate_completed", { action, duration_ms: durationMs });
|
|
@@ -7282,7 +7334,7 @@ function detectLocalPlatforms() {
|
|
|
7282
7334
|
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
7283
7335
|
}
|
|
7284
7336
|
function sanitizeSlug(slug) {
|
|
7285
|
-
return slug.replace(/[^a-zA-Z0-9_
|
|
7337
|
+
return slug.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
7286
7338
|
}
|
|
7287
7339
|
function getSkillPath(platform, slug) {
|
|
7288
7340
|
const safe = sanitizeSlug(slug);
|
|
@@ -7320,9 +7372,12 @@ async function searchSkillsSh(technologies) {
|
|
|
7320
7372
|
const bestBySlug = /* @__PURE__ */ new Map();
|
|
7321
7373
|
for (const tech of technologies) {
|
|
7322
7374
|
try {
|
|
7323
|
-
const resp = await fetch(
|
|
7324
|
-
|
|
7325
|
-
|
|
7375
|
+
const resp = await fetch(
|
|
7376
|
+
`https://skills.sh/api/search?q=${encodeURIComponent(tech)}&limit=10`,
|
|
7377
|
+
{
|
|
7378
|
+
signal: AbortSignal.timeout(1e4)
|
|
7379
|
+
}
|
|
7380
|
+
);
|
|
7326
7381
|
if (!resp.ok) continue;
|
|
7327
7382
|
const data = await resp.json();
|
|
7328
7383
|
if (!data.skills?.length) continue;
|
|
@@ -7381,9 +7436,7 @@ async function searchAwesomeClaudeCode(technologies) {
|
|
|
7381
7436
|
}
|
|
7382
7437
|
}
|
|
7383
7438
|
async function searchAllProviders(technologies, platform) {
|
|
7384
|
-
const searches = [
|
|
7385
|
-
searchSkillsSh(technologies)
|
|
7386
|
-
];
|
|
7439
|
+
const searches = [searchSkillsSh(technologies)];
|
|
7387
7440
|
if (platform === "claude" || !platform) {
|
|
7388
7441
|
searches.push(searchAwesomeClaudeCode(technologies));
|
|
7389
7442
|
}
|
|
@@ -7443,18 +7496,24 @@ ${candidateList}`,
|
|
|
7443
7496
|
function buildProjectContext(fingerprint, platforms) {
|
|
7444
7497
|
const parts = [];
|
|
7445
7498
|
if (fingerprint.packageName) parts.push(`Package: ${fingerprint.packageName}`);
|
|
7446
|
-
if (fingerprint.languages.length > 0)
|
|
7447
|
-
|
|
7499
|
+
if (fingerprint.languages.length > 0)
|
|
7500
|
+
parts.push(`Languages: ${fingerprint.languages.join(", ")}`);
|
|
7501
|
+
if (fingerprint.frameworks.length > 0)
|
|
7502
|
+
parts.push(`Frameworks: ${fingerprint.frameworks.join(", ")}`);
|
|
7448
7503
|
if (fingerprint.description) parts.push(`Description: ${fingerprint.description}`);
|
|
7449
7504
|
if (fingerprint.fileTree.length > 0) {
|
|
7450
|
-
parts.push(
|
|
7505
|
+
parts.push(
|
|
7506
|
+
`
|
|
7451
7507
|
File tree (${fingerprint.fileTree.length} files):
|
|
7452
|
-
${fingerprint.fileTree.slice(0, 50).join("\n")}`
|
|
7508
|
+
${fingerprint.fileTree.slice(0, 50).join("\n")}`
|
|
7509
|
+
);
|
|
7453
7510
|
}
|
|
7454
7511
|
if (fingerprint.existingConfigs.claudeMd) {
|
|
7455
|
-
parts.push(
|
|
7512
|
+
parts.push(
|
|
7513
|
+
`
|
|
7456
7514
|
Existing CLAUDE.md (first 500 chars):
|
|
7457
|
-
${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`
|
|
7515
|
+
${fingerprint.existingConfigs.claudeMd.slice(0, 500)}`
|
|
7516
|
+
);
|
|
7458
7517
|
}
|
|
7459
7518
|
const deps = extractTopDeps();
|
|
7460
7519
|
if (deps.length > 0) {
|
|
@@ -7537,20 +7596,18 @@ function extractTopDeps() {
|
|
|
7537
7596
|
/^@typescript-eslint\//,
|
|
7538
7597
|
/^@commitlint\//
|
|
7539
7598
|
];
|
|
7540
|
-
return deps.filter(
|
|
7541
|
-
(d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d))
|
|
7542
|
-
);
|
|
7599
|
+
return deps.filter((d) => !trivial.has(d) && !trivialPatterns.some((p) => p.test(d)));
|
|
7543
7600
|
} catch {
|
|
7544
7601
|
return [];
|
|
7545
7602
|
}
|
|
7546
7603
|
}
|
|
7547
7604
|
async function searchSkills(fingerprint, targetPlatforms, onStatus) {
|
|
7548
7605
|
const installedSkills = getInstalledSkills(targetPlatforms);
|
|
7549
|
-
const technologies = [
|
|
7550
|
-
...
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
]
|
|
7606
|
+
const technologies = [
|
|
7607
|
+
...new Set(
|
|
7608
|
+
[...fingerprint.languages, ...fingerprint.frameworks, ...extractTopDeps()].filter(Boolean)
|
|
7609
|
+
)
|
|
7610
|
+
];
|
|
7554
7611
|
if (technologies.length === 0) {
|
|
7555
7612
|
return { results: [], contentMap: /* @__PURE__ */ new Map() };
|
|
7556
7613
|
}
|
|
@@ -7582,10 +7639,12 @@ async function searchSkills(fingerprint, targetPlatforms, onStatus) {
|
|
|
7582
7639
|
}
|
|
7583
7640
|
onStatus?.("Fetching skill content...");
|
|
7584
7641
|
const contentMap = /* @__PURE__ */ new Map();
|
|
7585
|
-
await Promise.all(
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7642
|
+
await Promise.all(
|
|
7643
|
+
results.map(async (rec) => {
|
|
7644
|
+
const content = await fetchSkillContent(rec);
|
|
7645
|
+
if (content) contentMap.set(rec.slug, content);
|
|
7646
|
+
})
|
|
7647
|
+
);
|
|
7589
7648
|
const available = results.filter((r) => contentMap.has(r.slug));
|
|
7590
7649
|
return { results: available, contentMap };
|
|
7591
7650
|
}
|
|
@@ -7631,10 +7690,12 @@ async function querySkills(query) {
|
|
|
7631
7690
|
const top = results.slice(0, 5);
|
|
7632
7691
|
const fetchSpinner = ora("Verifying availability...").start();
|
|
7633
7692
|
const contentMap = /* @__PURE__ */ new Map();
|
|
7634
|
-
await Promise.all(
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7693
|
+
await Promise.all(
|
|
7694
|
+
top.map(async (rec) => {
|
|
7695
|
+
const content = await fetchSkillContent(rec);
|
|
7696
|
+
if (content) contentMap.set(rec.slug, content);
|
|
7697
|
+
})
|
|
7698
|
+
);
|
|
7638
7699
|
const available = top.filter((r) => contentMap.has(r.slug));
|
|
7639
7700
|
fetchSpinner.succeed(`${available.length} available`);
|
|
7640
7701
|
if (!available.length) {
|
|
@@ -7649,7 +7710,11 @@ async function querySkills(query) {
|
|
|
7649
7710
|
console.log(` ${r.reason || r.name}`);
|
|
7650
7711
|
}
|
|
7651
7712
|
console.log("");
|
|
7652
|
-
console.log(
|
|
7713
|
+
console.log(
|
|
7714
|
+
chalk6.dim(
|
|
7715
|
+
` Install with: ${resolveCaliber()} skills --install ${available.map((r) => r.slug).join(",")}`
|
|
7716
|
+
)
|
|
7717
|
+
);
|
|
7653
7718
|
console.log("");
|
|
7654
7719
|
}
|
|
7655
7720
|
async function installBySlug(slugStr) {
|
|
@@ -7671,10 +7736,12 @@ async function installBySlug(slugStr) {
|
|
|
7671
7736
|
return;
|
|
7672
7737
|
}
|
|
7673
7738
|
const contentMap = /* @__PURE__ */ new Map();
|
|
7674
|
-
await Promise.all(
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7739
|
+
await Promise.all(
|
|
7740
|
+
matched.map(async (rec) => {
|
|
7741
|
+
const content = await fetchSkillContent(rec);
|
|
7742
|
+
if (content) contentMap.set(rec.slug, content);
|
|
7743
|
+
})
|
|
7744
|
+
);
|
|
7678
7745
|
const installable = matched.filter((r) => contentMap.has(r.slug));
|
|
7679
7746
|
if (!installable.length) {
|
|
7680
7747
|
spinner.fail("Could not fetch skill content.");
|
|
@@ -7711,13 +7778,17 @@ async function searchAndInstallSkills(targetPlatforms) {
|
|
|
7711
7778
|
const fingerprint = await collectFingerprint(process.cwd());
|
|
7712
7779
|
const platforms = targetPlatforms ?? detectLocalPlatforms();
|
|
7713
7780
|
const installedSkills = getInstalledSkills(platforms);
|
|
7714
|
-
const technologies = [
|
|
7715
|
-
...
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
]
|
|
7781
|
+
const technologies = [
|
|
7782
|
+
...new Set(
|
|
7783
|
+
[...fingerprint.languages, ...fingerprint.frameworks, ...extractTopDeps()].filter(Boolean)
|
|
7784
|
+
)
|
|
7785
|
+
];
|
|
7719
7786
|
if (technologies.length === 0) {
|
|
7720
|
-
console.log(
|
|
7787
|
+
console.log(
|
|
7788
|
+
chalk6.yellow(
|
|
7789
|
+
"Could not detect any languages or dependencies. Try running from a project root."
|
|
7790
|
+
)
|
|
7791
|
+
);
|
|
7721
7792
|
throw new Error("__exit__");
|
|
7722
7793
|
}
|
|
7723
7794
|
const primaryPlatform = platforms.includes("claude") ? "claude" : platforms[0];
|
|
@@ -7747,7 +7818,9 @@ async function searchAndInstallSkills(targetPlatforms) {
|
|
|
7747
7818
|
scoreSpinner.succeed("No highly relevant skills found for your specific project.");
|
|
7748
7819
|
return;
|
|
7749
7820
|
}
|
|
7750
|
-
scoreSpinner.succeed(
|
|
7821
|
+
scoreSpinner.succeed(
|
|
7822
|
+
`${results.length} relevant skill${results.length > 1 ? "s" : ""} for your project`
|
|
7823
|
+
);
|
|
7751
7824
|
} catch {
|
|
7752
7825
|
scoreSpinner.warn("Could not score relevance \u2014 showing top results");
|
|
7753
7826
|
results = newCandidates.slice(0, 20);
|
|
@@ -7757,10 +7830,12 @@ async function searchAndInstallSkills(targetPlatforms) {
|
|
|
7757
7830
|
}
|
|
7758
7831
|
const fetchSpinner = ora("Verifying skill availability...").start();
|
|
7759
7832
|
const contentMap = /* @__PURE__ */ new Map();
|
|
7760
|
-
await Promise.all(
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7833
|
+
await Promise.all(
|
|
7834
|
+
results.map(async (rec) => {
|
|
7835
|
+
const content = await fetchSkillContent(rec);
|
|
7836
|
+
if (content) contentMap.set(rec.slug, content);
|
|
7837
|
+
})
|
|
7838
|
+
);
|
|
7764
7839
|
const available = results.filter((r) => contentMap.has(r.slug));
|
|
7765
7840
|
if (!available.length) {
|
|
7766
7841
|
fetchSpinner.fail("No installable skills found \u2014 content could not be fetched.");
|
|
@@ -7808,9 +7883,13 @@ async function interactiveSelect(recs) {
|
|
|
7808
7883
|
if (hasScores) {
|
|
7809
7884
|
const scoreColor = rec.score >= 90 ? chalk6.green : rec.score >= 70 ? chalk6.yellow : chalk6.dim;
|
|
7810
7885
|
const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
|
|
7811
|
-
lines.push(
|
|
7886
|
+
lines.push(
|
|
7887
|
+
` ${ptr} ${check} ${scoreColor(String(rec.score).padStart(3))} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`
|
|
7888
|
+
);
|
|
7812
7889
|
} else {
|
|
7813
|
-
lines.push(
|
|
7890
|
+
lines.push(
|
|
7891
|
+
` ${ptr} ${check} ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`
|
|
7892
|
+
);
|
|
7814
7893
|
}
|
|
7815
7894
|
}
|
|
7816
7895
|
lines.push("");
|
|
@@ -7866,7 +7945,9 @@ async function interactiveSelect(recs) {
|
|
|
7866
7945
|
console.log(chalk6.dim("\n No skills selected.\n"));
|
|
7867
7946
|
resolve3(null);
|
|
7868
7947
|
} else {
|
|
7869
|
-
resolve3(
|
|
7948
|
+
resolve3(
|
|
7949
|
+
Array.from(selected).sort().map((i) => recs[i])
|
|
7950
|
+
);
|
|
7870
7951
|
}
|
|
7871
7952
|
break;
|
|
7872
7953
|
case "q":
|
|
@@ -7935,17 +8016,25 @@ function printSkills(recs) {
|
|
|
7935
8016
|
const prefixWidth = 2;
|
|
7936
8017
|
console.log(chalk6.bold("\n Skills\n"));
|
|
7937
8018
|
if (hasScores) {
|
|
7938
|
-
console.log(
|
|
8019
|
+
console.log(
|
|
8020
|
+
" ".repeat(prefixWidth) + chalk6.dim("Score".padEnd(scoreWidth)) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Why")
|
|
8021
|
+
);
|
|
7939
8022
|
} else {
|
|
7940
|
-
console.log(
|
|
8023
|
+
console.log(
|
|
8024
|
+
" ".repeat(prefixWidth) + chalk6.dim("Name".padEnd(nameWidth)) + chalk6.dim("Technology".padEnd(18)) + chalk6.dim("Source")
|
|
8025
|
+
);
|
|
7941
8026
|
}
|
|
7942
8027
|
console.log(chalk6.dim(" " + "\u2500".repeat(Math.min(cols - 4, 90))));
|
|
7943
8028
|
for (const rec of recs) {
|
|
7944
8029
|
if (hasScores) {
|
|
7945
8030
|
const reasonMax = Math.max(cols - prefixWidth - scoreWidth - nameWidth - 2, 20);
|
|
7946
|
-
console.log(
|
|
8031
|
+
console.log(
|
|
8032
|
+
` ${String(rec.score).padStart(3)} ${rec.name.padEnd(nameWidth)}${chalk6.dim(rec.reason.slice(0, reasonMax))}`
|
|
8033
|
+
);
|
|
7947
8034
|
} else {
|
|
7948
|
-
console.log(
|
|
8035
|
+
console.log(
|
|
8036
|
+
` ${rec.name.padEnd(nameWidth)}${rec.detected_technology.padEnd(16)} ${chalk6.dim(rec.source_url || "")}`
|
|
8037
|
+
);
|
|
7949
8038
|
}
|
|
7950
8039
|
}
|
|
7951
8040
|
console.log("");
|
|
@@ -8165,11 +8254,11 @@ function countIssuePoints(issues) {
|
|
|
8165
8254
|
}
|
|
8166
8255
|
async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
8167
8256
|
const existsCache = /* @__PURE__ */ new Map();
|
|
8168
|
-
const cachedExists = (
|
|
8169
|
-
const cached = existsCache.get(
|
|
8257
|
+
const cachedExists = (path42) => {
|
|
8258
|
+
const cached = existsCache.get(path42);
|
|
8170
8259
|
if (cached !== void 0) return cached;
|
|
8171
|
-
const result = existsSync9(
|
|
8172
|
-
existsCache.set(
|
|
8260
|
+
const result = existsSync9(path42);
|
|
8261
|
+
existsCache.set(path42, result);
|
|
8173
8262
|
return result;
|
|
8174
8263
|
};
|
|
8175
8264
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -8309,7 +8398,7 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
8309
8398
|
|
|
8310
8399
|
// src/lib/debug-report.ts
|
|
8311
8400
|
import fs26 from "fs";
|
|
8312
|
-
import
|
|
8401
|
+
import path23 from "path";
|
|
8313
8402
|
var DebugReport = class {
|
|
8314
8403
|
sections = [];
|
|
8315
8404
|
startTime;
|
|
@@ -8378,7 +8467,7 @@ var DebugReport = class {
|
|
|
8378
8467
|
lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
|
|
8379
8468
|
lines.push("");
|
|
8380
8469
|
}
|
|
8381
|
-
const dir =
|
|
8470
|
+
const dir = path23.dirname(outputPath);
|
|
8382
8471
|
if (!fs26.existsSync(dir)) {
|
|
8383
8472
|
fs26.mkdirSync(dir, { recursive: true });
|
|
8384
8473
|
}
|
|
@@ -9045,7 +9134,11 @@ function formatWhatChanged(setup) {
|
|
|
9045
9134
|
lines.push(`${action} AGENTS.md`);
|
|
9046
9135
|
}
|
|
9047
9136
|
const allSkills = [];
|
|
9048
|
-
for (const [
|
|
9137
|
+
for (const [_platform, obj] of [
|
|
9138
|
+
["claude", claude],
|
|
9139
|
+
["codex", codex],
|
|
9140
|
+
["cursor", cursor]
|
|
9141
|
+
]) {
|
|
9049
9142
|
const skills = obj?.skills;
|
|
9050
9143
|
if (Array.isArray(skills)) {
|
|
9051
9144
|
for (const s of skills) allSkills.push(s.name);
|
|
@@ -9062,7 +9155,9 @@ function formatWhatChanged(setup) {
|
|
|
9062
9155
|
}
|
|
9063
9156
|
const deletions = setup.deletions;
|
|
9064
9157
|
if (Array.isArray(deletions) && deletions.length > 0) {
|
|
9065
|
-
lines.push(
|
|
9158
|
+
lines.push(
|
|
9159
|
+
`Removing ${deletions.length} file${deletions.length === 1 ? "" : "s"}: ${deletions.map((d) => d.filePath).join(", ")}`
|
|
9160
|
+
);
|
|
9066
9161
|
}
|
|
9067
9162
|
return lines;
|
|
9068
9163
|
}
|
|
@@ -9160,7 +9255,9 @@ function printSetupSummary(setup) {
|
|
|
9160
9255
|
console.log("");
|
|
9161
9256
|
}
|
|
9162
9257
|
}
|
|
9163
|
-
console.log(
|
|
9258
|
+
console.log(
|
|
9259
|
+
` ${chalk12.green("+")} ${chalk12.dim("new")} ${chalk12.yellow("~")} ${chalk12.dim("modified")} ${chalk12.red("-")} ${chalk12.dim("removed")}`
|
|
9260
|
+
);
|
|
9164
9261
|
console.log("");
|
|
9165
9262
|
}
|
|
9166
9263
|
function displayTokenUsage() {
|
|
@@ -9175,11 +9272,17 @@ function displayTokenUsage() {
|
|
|
9175
9272
|
for (const m of summary) {
|
|
9176
9273
|
totalIn += m.inputTokens;
|
|
9177
9274
|
totalOut += m.outputTokens;
|
|
9178
|
-
const cacheInfo = m.cacheReadTokens > 0 || m.cacheWriteTokens > 0 ? chalk12.dim(
|
|
9179
|
-
|
|
9275
|
+
const cacheInfo = m.cacheReadTokens > 0 || m.cacheWriteTokens > 0 ? chalk12.dim(
|
|
9276
|
+
` (cache: ${m.cacheReadTokens.toLocaleString()} read, ${m.cacheWriteTokens.toLocaleString()} write)`
|
|
9277
|
+
) : "";
|
|
9278
|
+
console.log(
|
|
9279
|
+
` ${chalk12.dim(m.model)}: ${m.inputTokens.toLocaleString()} in / ${m.outputTokens.toLocaleString()} out (${m.calls} call${m.calls === 1 ? "" : "s"})${cacheInfo}`
|
|
9280
|
+
);
|
|
9180
9281
|
}
|
|
9181
9282
|
if (summary.length > 1) {
|
|
9182
|
-
console.log(
|
|
9283
|
+
console.log(
|
|
9284
|
+
` ${chalk12.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`
|
|
9285
|
+
);
|
|
9183
9286
|
}
|
|
9184
9287
|
console.log("");
|
|
9185
9288
|
}
|
|
@@ -9188,9 +9291,9 @@ function displayTokenUsage() {
|
|
|
9188
9291
|
init_config();
|
|
9189
9292
|
import chalk13 from "chalk";
|
|
9190
9293
|
import fs31 from "fs";
|
|
9191
|
-
import
|
|
9294
|
+
import path25 from "path";
|
|
9192
9295
|
function isFirstRun(dir) {
|
|
9193
|
-
const caliberDir =
|
|
9296
|
+
const caliberDir = path25.join(dir, ".caliber");
|
|
9194
9297
|
try {
|
|
9195
9298
|
const stat = fs31.statSync(caliberDir);
|
|
9196
9299
|
return !stat.isDirectory();
|
|
@@ -9260,7 +9363,7 @@ function ensurePermissions(fingerprint) {
|
|
|
9260
9363
|
}
|
|
9261
9364
|
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
9262
9365
|
try {
|
|
9263
|
-
const logPath =
|
|
9366
|
+
const logPath = path25.join(process.cwd(), ".caliber", "error-log.md");
|
|
9264
9367
|
const lines = [
|
|
9265
9368
|
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
9266
9369
|
"",
|
|
@@ -9273,7 +9376,7 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
|
9273
9376
|
lines.push("## Error", "```", error, "```", "");
|
|
9274
9377
|
}
|
|
9275
9378
|
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
9276
|
-
fs31.mkdirSync(
|
|
9379
|
+
fs31.mkdirSync(path25.join(process.cwd(), ".caliber"), { recursive: true });
|
|
9277
9380
|
fs31.writeFileSync(logPath, lines.join("\n"));
|
|
9278
9381
|
console.log(chalk13.dim(`
|
|
9279
9382
|
Error log written to .caliber/error-log.md`));
|
|
@@ -9326,12 +9429,12 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
9326
9429
|
|
|
9327
9430
|
// src/scoring/history.ts
|
|
9328
9431
|
import fs32 from "fs";
|
|
9329
|
-
import
|
|
9432
|
+
import path26 from "path";
|
|
9330
9433
|
var HISTORY_FILE = "score-history.jsonl";
|
|
9331
9434
|
var MAX_ENTRIES = 500;
|
|
9332
9435
|
var TRIM_THRESHOLD = MAX_ENTRIES + 50;
|
|
9333
9436
|
function historyFilePath() {
|
|
9334
|
-
return
|
|
9437
|
+
return path26.join(CALIBER_DIR, HISTORY_FILE);
|
|
9335
9438
|
}
|
|
9336
9439
|
function recordScore(result, trigger) {
|
|
9337
9440
|
const entry = {
|
|
@@ -9523,7 +9626,7 @@ async function initCommand(options) {
|
|
|
9523
9626
|
trackInitLearnEnabled(true);
|
|
9524
9627
|
}
|
|
9525
9628
|
console.log("");
|
|
9526
|
-
|
|
9629
|
+
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
9527
9630
|
log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
|
|
9528
9631
|
if (report) {
|
|
9529
9632
|
report.markStep("Baseline scoring");
|
|
@@ -9570,7 +9673,7 @@ async function initCommand(options) {
|
|
|
9570
9673
|
} catch {
|
|
9571
9674
|
}
|
|
9572
9675
|
if (!claudeContent) {
|
|
9573
|
-
claudeContent = `# ${
|
|
9676
|
+
claudeContent = `# ${path27.basename(process.cwd())}
|
|
9574
9677
|
`;
|
|
9575
9678
|
}
|
|
9576
9679
|
const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
|
|
@@ -9579,15 +9682,15 @@ async function initCommand(options) {
|
|
|
9579
9682
|
console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
|
|
9580
9683
|
}
|
|
9581
9684
|
if (targetAgent.includes("cursor")) {
|
|
9582
|
-
const rulesDir =
|
|
9685
|
+
const rulesDir = path27.join(".cursor", "rules");
|
|
9583
9686
|
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9584
9687
|
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
|
|
9585
|
-
fs33.writeFileSync(
|
|
9688
|
+
fs33.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
|
|
9586
9689
|
}
|
|
9587
9690
|
console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
|
|
9588
9691
|
}
|
|
9589
9692
|
if (targetAgent.includes("github-copilot")) {
|
|
9590
|
-
const copilotPath =
|
|
9693
|
+
const copilotPath = path27.join(".github", "copilot-instructions.md");
|
|
9591
9694
|
let copilotContent = "";
|
|
9592
9695
|
try {
|
|
9593
9696
|
copilotContent = fs33.readFileSync(copilotPath, "utf-8");
|
|
@@ -9595,7 +9698,7 @@ async function initCommand(options) {
|
|
|
9595
9698
|
}
|
|
9596
9699
|
if (!copilotContent) {
|
|
9597
9700
|
fs33.mkdirSync(".github", { recursive: true });
|
|
9598
|
-
copilotContent = `# ${
|
|
9701
|
+
copilotContent = `# ${path27.basename(process.cwd())}
|
|
9599
9702
|
`;
|
|
9600
9703
|
}
|
|
9601
9704
|
const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
|
|
@@ -9616,8 +9719,6 @@ async function initCommand(options) {
|
|
|
9616
9719
|
console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
|
|
9617
9720
|
return;
|
|
9618
9721
|
}
|
|
9619
|
-
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
9620
|
-
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
9621
9722
|
const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
|
|
9622
9723
|
console.log(chalk14.dim(genModelInfo + "\n"));
|
|
9623
9724
|
if (report) report.markStep("Generation");
|
|
@@ -10004,9 +10105,9 @@ ${agentRefs.join(" ")}
|
|
|
10004
10105
|
}
|
|
10005
10106
|
if (report) {
|
|
10006
10107
|
report.markStep("Finished");
|
|
10007
|
-
const reportPath =
|
|
10108
|
+
const reportPath = path27.join(process.cwd(), ".caliber", "debug-report.md");
|
|
10008
10109
|
report.write(reportPath);
|
|
10009
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
10110
|
+
console.log(chalk14.dim(` Debug report written to ${path27.relative(process.cwd(), reportPath)}
|
|
10010
10111
|
`));
|
|
10011
10112
|
}
|
|
10012
10113
|
}
|
|
@@ -10233,30 +10334,39 @@ async function regenerateCommand(options) {
|
|
|
10233
10334
|
// src/commands/score.ts
|
|
10234
10335
|
import fs35 from "fs";
|
|
10235
10336
|
import os7 from "os";
|
|
10236
|
-
import
|
|
10337
|
+
import path28 from "path";
|
|
10237
10338
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10238
10339
|
import chalk18 from "chalk";
|
|
10239
10340
|
init_resolve_caliber();
|
|
10240
10341
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
|
|
10241
10342
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
10242
10343
|
function scoreBaseRef(ref, target) {
|
|
10243
|
-
if (!/^[\w
|
|
10244
|
-
const tmpDir = fs35.mkdtempSync(
|
|
10344
|
+
if (!/^[\w.\-/~^@{}]+$/.test(ref)) return null;
|
|
10345
|
+
const tmpDir = fs35.mkdtempSync(path28.join(os7.tmpdir(), "caliber-compare-"));
|
|
10245
10346
|
try {
|
|
10246
10347
|
for (const file of CONFIG_FILES) {
|
|
10247
10348
|
try {
|
|
10248
|
-
const content = execFileSync2("git", ["show", `${ref}:${file}`], {
|
|
10249
|
-
|
|
10349
|
+
const content = execFileSync2("git", ["show", `${ref}:${file}`], {
|
|
10350
|
+
encoding: "utf-8",
|
|
10351
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10352
|
+
});
|
|
10353
|
+
fs35.writeFileSync(path28.join(tmpDir, file), content);
|
|
10250
10354
|
} catch {
|
|
10251
10355
|
}
|
|
10252
10356
|
}
|
|
10253
10357
|
for (const dir of CONFIG_DIRS) {
|
|
10254
10358
|
try {
|
|
10255
|
-
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], {
|
|
10359
|
+
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], {
|
|
10360
|
+
encoding: "utf-8",
|
|
10361
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10362
|
+
}).trim().split("\n").filter(Boolean);
|
|
10256
10363
|
for (const file of files) {
|
|
10257
|
-
const filePath =
|
|
10258
|
-
fs35.mkdirSync(
|
|
10259
|
-
const content = execFileSync2("git", ["show", `${ref}:${file}`], {
|
|
10364
|
+
const filePath = path28.join(tmpDir, file);
|
|
10365
|
+
fs35.mkdirSync(path28.dirname(filePath), { recursive: true });
|
|
10366
|
+
const content = execFileSync2("git", ["show", `${ref}:${file}`], {
|
|
10367
|
+
encoding: "utf-8",
|
|
10368
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10369
|
+
});
|
|
10260
10370
|
fs35.writeFileSync(filePath, content);
|
|
10261
10371
|
}
|
|
10262
10372
|
} catch {
|
|
@@ -10279,13 +10389,25 @@ async function scoreCommand(options) {
|
|
|
10279
10389
|
if (options.compare) {
|
|
10280
10390
|
const baseResult = scoreBaseRef(options.compare, target);
|
|
10281
10391
|
if (!baseResult) {
|
|
10282
|
-
console.error(
|
|
10392
|
+
console.error(
|
|
10393
|
+
chalk18.red(`Could not score ref "${options.compare}" \u2014 branch or ref not found.`)
|
|
10394
|
+
);
|
|
10283
10395
|
process.exitCode = 1;
|
|
10284
10396
|
return;
|
|
10285
10397
|
}
|
|
10286
10398
|
const delta = result.score - baseResult.score;
|
|
10287
10399
|
if (options.json) {
|
|
10288
|
-
console.log(
|
|
10400
|
+
console.log(
|
|
10401
|
+
JSON.stringify(
|
|
10402
|
+
{
|
|
10403
|
+
current: result,
|
|
10404
|
+
base: { score: baseResult.score, grade: baseResult.grade, ref: options.compare },
|
|
10405
|
+
delta
|
|
10406
|
+
},
|
|
10407
|
+
null,
|
|
10408
|
+
2
|
|
10409
|
+
)
|
|
10410
|
+
);
|
|
10289
10411
|
return;
|
|
10290
10412
|
}
|
|
10291
10413
|
if (options.quiet) {
|
|
@@ -10297,9 +10419,13 @@ async function scoreCommand(options) {
|
|
|
10297
10419
|
const separator2 = chalk18.gray(" " + "\u2500".repeat(53));
|
|
10298
10420
|
console.log(separator2);
|
|
10299
10421
|
if (delta > 0) {
|
|
10300
|
-
console.log(
|
|
10422
|
+
console.log(
|
|
10423
|
+
chalk18.green(` +${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`)
|
|
10424
|
+
);
|
|
10301
10425
|
} else if (delta < 0) {
|
|
10302
|
-
console.log(
|
|
10426
|
+
console.log(
|
|
10427
|
+
chalk18.red(` ${delta}`) + chalk18.gray(` from ${options.compare} (${baseResult.score}/100)`)
|
|
10428
|
+
);
|
|
10303
10429
|
} else {
|
|
10304
10430
|
console.log(chalk18.gray(` No change from ${options.compare} (${baseResult.score}/100)`));
|
|
10305
10431
|
}
|
|
@@ -10319,18 +10445,24 @@ async function scoreCommand(options) {
|
|
|
10319
10445
|
console.log(separator);
|
|
10320
10446
|
const bin = resolveCaliber();
|
|
10321
10447
|
if (result.score < 40) {
|
|
10322
|
-
console.log(
|
|
10448
|
+
console.log(
|
|
10449
|
+
chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to generate a complete, optimized config.")
|
|
10450
|
+
);
|
|
10323
10451
|
} else if (result.score < 70) {
|
|
10324
|
-
console.log(
|
|
10452
|
+
console.log(
|
|
10453
|
+
chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} init`) + chalk18.gray(" to improve your config.")
|
|
10454
|
+
);
|
|
10325
10455
|
} else {
|
|
10326
|
-
console.log(
|
|
10456
|
+
console.log(
|
|
10457
|
+
chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")(`${bin} regenerate`) + chalk18.gray(" to rebuild from scratch.")
|
|
10458
|
+
);
|
|
10327
10459
|
}
|
|
10328
10460
|
console.log("");
|
|
10329
10461
|
}
|
|
10330
10462
|
|
|
10331
10463
|
// src/commands/refresh.ts
|
|
10332
10464
|
import fs39 from "fs";
|
|
10333
|
-
import
|
|
10465
|
+
import path32 from "path";
|
|
10334
10466
|
import chalk19 from "chalk";
|
|
10335
10467
|
import ora6 from "ora";
|
|
10336
10468
|
|
|
@@ -10340,24 +10472,14 @@ var MAX_DIFF_BYTES = 1e5;
|
|
|
10340
10472
|
var DOC_PATTERNS = [
|
|
10341
10473
|
"CLAUDE.md",
|
|
10342
10474
|
"README.md",
|
|
10343
|
-
"AGENTS.md",
|
|
10344
10475
|
".cursorrules",
|
|
10345
10476
|
".cursor/rules/",
|
|
10346
|
-
".cursor/skills/",
|
|
10347
10477
|
".claude/skills/",
|
|
10348
|
-
".agents/skills/",
|
|
10349
|
-
".github/copilot-instructions.md",
|
|
10350
|
-
".github/instructions/",
|
|
10351
10478
|
"CALIBER_LEARNINGS.md"
|
|
10352
10479
|
];
|
|
10353
10480
|
function excludeArgs() {
|
|
10354
10481
|
return DOC_PATTERNS.flatMap((p) => ["--", `:!${p}`]);
|
|
10355
10482
|
}
|
|
10356
|
-
function truncateAtLine(text, maxBytes) {
|
|
10357
|
-
if (text.length <= maxBytes) return text;
|
|
10358
|
-
const cut = text.lastIndexOf("\n", maxBytes);
|
|
10359
|
-
return cut > 0 ? text.slice(0, cut) : text.slice(0, maxBytes);
|
|
10360
|
-
}
|
|
10361
10483
|
function safeExec(cmd) {
|
|
10362
10484
|
try {
|
|
10363
10485
|
return execSync15(cmd, {
|
|
@@ -10403,9 +10525,9 @@ function collectDiff(lastSha) {
|
|
|
10403
10525
|
const totalSize = committedDiff.length + stagedDiff.length + unstagedDiff.length;
|
|
10404
10526
|
if (totalSize > MAX_DIFF_BYTES) {
|
|
10405
10527
|
const ratio = MAX_DIFF_BYTES / totalSize;
|
|
10406
|
-
committedDiff =
|
|
10407
|
-
stagedDiff =
|
|
10408
|
-
unstagedDiff =
|
|
10528
|
+
committedDiff = committedDiff.slice(0, Math.floor(committedDiff.length * ratio));
|
|
10529
|
+
stagedDiff = stagedDiff.slice(0, Math.floor(stagedDiff.length * ratio));
|
|
10530
|
+
unstagedDiff = unstagedDiff.slice(0, Math.floor(unstagedDiff.length * ratio));
|
|
10409
10531
|
}
|
|
10410
10532
|
const hasChanges = !!(committedDiff || stagedDiff || unstagedDiff || changedFiles.length);
|
|
10411
10533
|
const parts = [];
|
|
@@ -10420,51 +10542,47 @@ function collectDiff(lastSha) {
|
|
|
10420
10542
|
// src/writers/refresh.ts
|
|
10421
10543
|
init_pre_commit_block();
|
|
10422
10544
|
import fs36 from "fs";
|
|
10423
|
-
import
|
|
10545
|
+
import path29 from "path";
|
|
10424
10546
|
function writeRefreshDocs(docs) {
|
|
10425
10547
|
const written = [];
|
|
10426
10548
|
if (docs.claudeMd) {
|
|
10427
|
-
fs36.writeFileSync("CLAUDE.md",
|
|
10549
|
+
fs36.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
|
|
10428
10550
|
written.push("CLAUDE.md");
|
|
10429
10551
|
}
|
|
10430
10552
|
if (docs.readmeMd) {
|
|
10431
10553
|
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
10432
10554
|
written.push("README.md");
|
|
10433
10555
|
}
|
|
10434
|
-
if (docs.agentsMd) {
|
|
10435
|
-
fs36.writeFileSync("AGENTS.md", appendManagedBlocks(docs.agentsMd, "codex"));
|
|
10436
|
-
written.push("AGENTS.md");
|
|
10437
|
-
}
|
|
10438
10556
|
if (docs.cursorrules) {
|
|
10439
10557
|
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
10440
10558
|
written.push(".cursorrules");
|
|
10441
10559
|
}
|
|
10442
10560
|
if (docs.cursorRules) {
|
|
10443
|
-
const rulesDir =
|
|
10561
|
+
const rulesDir = path29.join(".cursor", "rules");
|
|
10444
10562
|
if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
|
|
10445
10563
|
for (const rule of docs.cursorRules) {
|
|
10446
|
-
fs36.writeFileSync(
|
|
10564
|
+
fs36.writeFileSync(path29.join(rulesDir, rule.filename), rule.content);
|
|
10447
10565
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
10448
10566
|
}
|
|
10449
10567
|
}
|
|
10450
10568
|
if (docs.claudeSkills) {
|
|
10451
|
-
const skillsDir =
|
|
10569
|
+
const skillsDir = path29.join(".claude", "skills");
|
|
10452
10570
|
if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
|
|
10453
10571
|
for (const skill of docs.claudeSkills) {
|
|
10454
|
-
fs36.writeFileSync(
|
|
10572
|
+
fs36.writeFileSync(path29.join(skillsDir, skill.filename), skill.content);
|
|
10455
10573
|
written.push(`.claude/skills/${skill.filename}`);
|
|
10456
10574
|
}
|
|
10457
10575
|
}
|
|
10458
10576
|
if (docs.copilotInstructions) {
|
|
10459
10577
|
fs36.mkdirSync(".github", { recursive: true });
|
|
10460
|
-
fs36.writeFileSync(
|
|
10578
|
+
fs36.writeFileSync(path29.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
|
|
10461
10579
|
written.push(".github/copilot-instructions.md");
|
|
10462
10580
|
}
|
|
10463
10581
|
if (docs.copilotInstructionFiles) {
|
|
10464
|
-
const instructionsDir =
|
|
10582
|
+
const instructionsDir = path29.join(".github", "instructions");
|
|
10465
10583
|
fs36.mkdirSync(instructionsDir, { recursive: true });
|
|
10466
10584
|
for (const file of docs.copilotInstructionFiles) {
|
|
10467
|
-
fs36.writeFileSync(
|
|
10585
|
+
fs36.writeFileSync(path29.join(instructionsDir, file.filename), file.content);
|
|
10468
10586
|
written.push(`.github/instructions/${file.filename}`);
|
|
10469
10587
|
}
|
|
10470
10588
|
}
|
|
@@ -10476,18 +10594,10 @@ init_config();
|
|
|
10476
10594
|
async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
|
|
10477
10595
|
const prompt = buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2);
|
|
10478
10596
|
const fastModel = getFastModel();
|
|
10479
|
-
const docCount = [
|
|
10480
|
-
existingDocs.claudeMd,
|
|
10481
|
-
existingDocs.readmeMd,
|
|
10482
|
-
existingDocs.agentsMd,
|
|
10483
|
-
existingDocs.cursorrules,
|
|
10484
|
-
existingDocs.copilotInstructions
|
|
10485
|
-
].filter(Boolean).length + (existingDocs.cursorRules?.length ?? 0) + (existingDocs.claudeSkills?.length ?? 0);
|
|
10486
|
-
const maxTokens = Math.min(32768, Math.max(8192, docCount * 4096));
|
|
10487
10597
|
const raw = await llmCall({
|
|
10488
10598
|
system: REFRESH_SYSTEM_PROMPT,
|
|
10489
10599
|
prompt,
|
|
10490
|
-
maxTokens,
|
|
10600
|
+
maxTokens: 16384,
|
|
10491
10601
|
...fastModel ? { model: fastModel } : {}
|
|
10492
10602
|
});
|
|
10493
10603
|
return parseJsonResponse(raw);
|
|
@@ -10546,21 +10656,6 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
10546
10656
|
parts.push(rule.content);
|
|
10547
10657
|
}
|
|
10548
10658
|
}
|
|
10549
|
-
if (existingDocs.agentsMd) {
|
|
10550
|
-
parts.push("\n[AGENTS.md]");
|
|
10551
|
-
parts.push(existingDocs.agentsMd);
|
|
10552
|
-
}
|
|
10553
|
-
if (existingDocs.copilotInstructions) {
|
|
10554
|
-
parts.push("\n[.github/copilot-instructions.md]");
|
|
10555
|
-
parts.push(existingDocs.copilotInstructions);
|
|
10556
|
-
}
|
|
10557
|
-
if (existingDocs.copilotInstructionFiles?.length) {
|
|
10558
|
-
for (const file of existingDocs.copilotInstructionFiles) {
|
|
10559
|
-
parts.push(`
|
|
10560
|
-
[.github/instructions/${file.filename}]`);
|
|
10561
|
-
parts.push(file.content);
|
|
10562
|
-
}
|
|
10563
|
-
}
|
|
10564
10659
|
if (learnedSection) {
|
|
10565
10660
|
parts.push("\n--- Learned Patterns (from session learning) ---");
|
|
10566
10661
|
parts.push("Consider these accumulated learnings when deciding what to update:");
|
|
@@ -10574,7 +10669,7 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
10574
10669
|
|
|
10575
10670
|
// src/learner/writer.ts
|
|
10576
10671
|
import fs37 from "fs";
|
|
10577
|
-
import
|
|
10672
|
+
import path30 from "path";
|
|
10578
10673
|
|
|
10579
10674
|
// src/learner/utils.ts
|
|
10580
10675
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -10699,9 +10794,9 @@ function writeLearnedSection(content) {
|
|
|
10699
10794
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
10700
10795
|
}
|
|
10701
10796
|
function writeLearnedSkill(skill) {
|
|
10702
|
-
const skillDir =
|
|
10797
|
+
const skillDir = path30.join(".claude", "skills", skill.name);
|
|
10703
10798
|
if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
|
|
10704
|
-
const skillPath =
|
|
10799
|
+
const skillPath = path30.join(skillDir, "SKILL.md");
|
|
10705
10800
|
if (!skill.isNew && fs37.existsSync(skillPath)) {
|
|
10706
10801
|
const existing = fs37.readFileSync(skillPath, "utf-8");
|
|
10707
10802
|
fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
@@ -10770,15 +10865,6 @@ function migrateInlineLearnings() {
|
|
|
10770
10865
|
init_config();
|
|
10771
10866
|
init_resolve_caliber();
|
|
10772
10867
|
init_builtin_skills();
|
|
10773
|
-
function detectSyncedAgents(writtenFiles) {
|
|
10774
|
-
const agents = [];
|
|
10775
|
-
const joined = writtenFiles.join(" ");
|
|
10776
|
-
if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
|
|
10777
|
-
if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
|
|
10778
|
-
if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
|
|
10779
|
-
if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
|
|
10780
|
-
return agents;
|
|
10781
|
-
}
|
|
10782
10868
|
function log2(quiet, ...args) {
|
|
10783
10869
|
if (!quiet) console.log(...args);
|
|
10784
10870
|
}
|
|
@@ -10788,8 +10874,8 @@ function discoverGitRepos(parentDir) {
|
|
|
10788
10874
|
const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
|
|
10789
10875
|
for (const entry of entries) {
|
|
10790
10876
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
10791
|
-
const childPath =
|
|
10792
|
-
if (fs39.existsSync(
|
|
10877
|
+
const childPath = path32.join(parentDir, entry.name);
|
|
10878
|
+
if (fs39.existsSync(path32.join(childPath, ".git"))) {
|
|
10793
10879
|
repos.push(childPath);
|
|
10794
10880
|
}
|
|
10795
10881
|
}
|
|
@@ -10875,7 +10961,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10875
10961
|
const filesToWrite = response.docsUpdated || [];
|
|
10876
10962
|
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
10877
10963
|
for (const filePath of filesToWrite) {
|
|
10878
|
-
const fullPath =
|
|
10964
|
+
const fullPath = path32.resolve(repoDir, filePath);
|
|
10879
10965
|
try {
|
|
10880
10966
|
preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
|
|
10881
10967
|
} catch {
|
|
@@ -10888,7 +10974,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10888
10974
|
const postScore = computeLocalScore(repoDir, targetAgent);
|
|
10889
10975
|
if (postScore.score < preScore.score) {
|
|
10890
10976
|
for (const [filePath, content] of preRefreshContents) {
|
|
10891
|
-
const fullPath =
|
|
10977
|
+
const fullPath = path32.resolve(repoDir, filePath);
|
|
10892
10978
|
if (content === null) {
|
|
10893
10979
|
try {
|
|
10894
10980
|
fs39.unlinkSync(fullPath);
|
|
@@ -10907,18 +10993,8 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10907
10993
|
}
|
|
10908
10994
|
recordScore(postScore, "refresh");
|
|
10909
10995
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
10910
|
-
const fileChangesMap = new Map(
|
|
10911
|
-
(response.fileChanges || []).map((fc) => [fc.file, fc.description])
|
|
10912
|
-
);
|
|
10913
10996
|
for (const file of written) {
|
|
10914
|
-
|
|
10915
|
-
const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
|
|
10916
|
-
log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
|
|
10917
|
-
}
|
|
10918
|
-
const agents = detectSyncedAgents(written);
|
|
10919
|
-
if (agents.length > 1) {
|
|
10920
|
-
log2(quiet, chalk19.cyan(`
|
|
10921
|
-
${agents.length} agent formats in sync (${agents.join(", ")})`));
|
|
10997
|
+
log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
|
|
10922
10998
|
}
|
|
10923
10999
|
if (response.changesSummary) {
|
|
10924
11000
|
log2(quiet, chalk19.dim(`
|
|
@@ -10960,7 +11036,7 @@ async function refreshCommand(options) {
|
|
|
10960
11036
|
`));
|
|
10961
11037
|
const originalDir = process.cwd();
|
|
10962
11038
|
for (const repo of repos) {
|
|
10963
|
-
const repoName =
|
|
11039
|
+
const repoName = path32.basename(repo);
|
|
10964
11040
|
try {
|
|
10965
11041
|
process.chdir(repo);
|
|
10966
11042
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -11198,7 +11274,7 @@ async function configCommand() {
|
|
|
11198
11274
|
|
|
11199
11275
|
// src/commands/learn.ts
|
|
11200
11276
|
import fs44 from "fs";
|
|
11201
|
-
import
|
|
11277
|
+
import path36 from "path";
|
|
11202
11278
|
import chalk23 from "chalk";
|
|
11203
11279
|
|
|
11204
11280
|
// src/learner/stdin.ts
|
|
@@ -11230,7 +11306,7 @@ function readStdin() {
|
|
|
11230
11306
|
|
|
11231
11307
|
// src/learner/storage.ts
|
|
11232
11308
|
import fs41 from "fs";
|
|
11233
|
-
import
|
|
11309
|
+
import path33 from "path";
|
|
11234
11310
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
11235
11311
|
var DEFAULT_STATE = {
|
|
11236
11312
|
sessionId: null,
|
|
@@ -11244,10 +11320,10 @@ function ensureLearningDir() {
|
|
|
11244
11320
|
}
|
|
11245
11321
|
}
|
|
11246
11322
|
function sessionFilePath() {
|
|
11247
|
-
return
|
|
11323
|
+
return path33.join(getLearningDir(), LEARNING_SESSION_FILE);
|
|
11248
11324
|
}
|
|
11249
11325
|
function stateFilePath() {
|
|
11250
|
-
return
|
|
11326
|
+
return path33.join(getLearningDir(), LEARNING_STATE_FILE);
|
|
11251
11327
|
}
|
|
11252
11328
|
function truncateResponse(response) {
|
|
11253
11329
|
const str = JSON.stringify(response);
|
|
@@ -11319,7 +11395,7 @@ function resetState() {
|
|
|
11319
11395
|
var LOCK_FILE2 = "finalize.lock";
|
|
11320
11396
|
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
11321
11397
|
function lockFilePath() {
|
|
11322
|
-
return
|
|
11398
|
+
return path33.join(getLearningDir(), LOCK_FILE2);
|
|
11323
11399
|
}
|
|
11324
11400
|
function acquireFinalizeLock() {
|
|
11325
11401
|
ensureLearningDir();
|
|
@@ -11365,10 +11441,10 @@ function releaseFinalizeLock() {
|
|
|
11365
11441
|
|
|
11366
11442
|
// src/lib/notifications.ts
|
|
11367
11443
|
import fs42 from "fs";
|
|
11368
|
-
import
|
|
11444
|
+
import path34 from "path";
|
|
11369
11445
|
import chalk22 from "chalk";
|
|
11370
11446
|
function notificationFilePath() {
|
|
11371
|
-
return
|
|
11447
|
+
return path34.join(getLearningDir(), "last-finalize-summary.json");
|
|
11372
11448
|
}
|
|
11373
11449
|
function writeFinalizeSummary(summary) {
|
|
11374
11450
|
try {
|
|
@@ -11550,7 +11626,7 @@ init_config();
|
|
|
11550
11626
|
|
|
11551
11627
|
// src/learner/roi.ts
|
|
11552
11628
|
import fs43 from "fs";
|
|
11553
|
-
import
|
|
11629
|
+
import path35 from "path";
|
|
11554
11630
|
var DEFAULT_TOTALS = {
|
|
11555
11631
|
totalWasteTokens: 0,
|
|
11556
11632
|
totalWasteSeconds: 0,
|
|
@@ -11564,7 +11640,7 @@ var DEFAULT_TOTALS = {
|
|
|
11564
11640
|
lastSessionTimestamp: ""
|
|
11565
11641
|
};
|
|
11566
11642
|
function roiFilePath() {
|
|
11567
|
-
return
|
|
11643
|
+
return path35.join(getLearningDir(), LEARNING_ROI_FILE);
|
|
11568
11644
|
}
|
|
11569
11645
|
function readROIStats() {
|
|
11570
11646
|
const filePath = roiFilePath();
|
|
@@ -11754,7 +11830,9 @@ Return a JSON object: {"matchedIndices": [0, 2]} or {"matchedIndices": []} if no
|
|
|
11754
11830
|
const json = extractJson(raw);
|
|
11755
11831
|
if (json) {
|
|
11756
11832
|
const parsed = JSON.parse(json);
|
|
11757
|
-
const indices = (parsed.matchedIndices || []).filter(
|
|
11833
|
+
const indices = (parsed.matchedIndices || []).filter(
|
|
11834
|
+
(i) => typeof i === "number" && i >= 0 && i < learnings.length
|
|
11835
|
+
);
|
|
11758
11836
|
return { matchedIndices: indices, unmatchedFailures: failureEvents.length - indices.length };
|
|
11759
11837
|
}
|
|
11760
11838
|
} catch {
|
|
@@ -11792,7 +11870,7 @@ var AUTO_SETTLE_MS = 200;
|
|
|
11792
11870
|
var INCREMENTAL_INTERVAL = 50;
|
|
11793
11871
|
function writeFinalizeError(message) {
|
|
11794
11872
|
try {
|
|
11795
|
-
const errorPath =
|
|
11873
|
+
const errorPath = path36.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
11796
11874
|
if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
|
|
11797
11875
|
fs44.writeFileSync(errorPath, JSON.stringify({
|
|
11798
11876
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11804,7 +11882,7 @@ function writeFinalizeError(message) {
|
|
|
11804
11882
|
}
|
|
11805
11883
|
function readFinalizeError() {
|
|
11806
11884
|
try {
|
|
11807
|
-
const errorPath =
|
|
11885
|
+
const errorPath = path36.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
11808
11886
|
if (!fs44.existsSync(errorPath)) return null;
|
|
11809
11887
|
return JSON.parse(fs44.readFileSync(errorPath, "utf-8"));
|
|
11810
11888
|
} catch {
|
|
@@ -11853,7 +11931,7 @@ async function learnObserveCommand(options) {
|
|
|
11853
11931
|
const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
|
|
11854
11932
|
const bin = resolveCaliber2();
|
|
11855
11933
|
const { spawn: spawn4 } = await import("child_process");
|
|
11856
|
-
const logPath =
|
|
11934
|
+
const logPath = path36.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
11857
11935
|
if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
|
|
11858
11936
|
const logFd = fs44.openSync(logPath, "a");
|
|
11859
11937
|
spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
|
|
@@ -12143,7 +12221,7 @@ async function learnStatusCommand() {
|
|
|
12143
12221
|
if (lastError) {
|
|
12144
12222
|
console.log(`Last error: ${chalk23.red(lastError.error)}`);
|
|
12145
12223
|
console.log(chalk23.dim(` at ${lastError.timestamp}`));
|
|
12146
|
-
const logPath =
|
|
12224
|
+
const logPath = path36.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
12147
12225
|
if (fs44.existsSync(logPath)) {
|
|
12148
12226
|
console.log(chalk23.dim(` Full log: ${logPath}`));
|
|
12149
12227
|
}
|
|
@@ -12425,7 +12503,7 @@ async function insightsCommand(options) {
|
|
|
12425
12503
|
|
|
12426
12504
|
// src/commands/sources.ts
|
|
12427
12505
|
import fs45 from "fs";
|
|
12428
|
-
import
|
|
12506
|
+
import path37 from "path";
|
|
12429
12507
|
import chalk25 from "chalk";
|
|
12430
12508
|
init_resolve_caliber();
|
|
12431
12509
|
async function sourcesListCommand() {
|
|
@@ -12442,9 +12520,9 @@ async function sourcesListCommand() {
|
|
|
12442
12520
|
if (configSources.length > 0) {
|
|
12443
12521
|
for (const source of configSources) {
|
|
12444
12522
|
const sourcePath = source.path || source.url || "";
|
|
12445
|
-
const exists = source.path ? fs45.existsSync(
|
|
12523
|
+
const exists = source.path ? fs45.existsSync(path37.resolve(dir, source.path)) : false;
|
|
12446
12524
|
const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
|
|
12447
|
-
const hasSummary = source.path && fs45.existsSync(
|
|
12525
|
+
const hasSummary = source.path && fs45.existsSync(path37.join(path37.resolve(dir, source.path), ".caliber", "summary.json"));
|
|
12448
12526
|
console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
|
|
12449
12527
|
console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
|
|
12450
12528
|
if (source.description) console.log(` ${chalk25.dim(source.description)}`);
|
|
@@ -12454,7 +12532,7 @@ async function sourcesListCommand() {
|
|
|
12454
12532
|
if (workspaces.length > 0) {
|
|
12455
12533
|
console.log(chalk25.dim(" Auto-detected workspaces:"));
|
|
12456
12534
|
for (const ws of workspaces) {
|
|
12457
|
-
const exists = fs45.existsSync(
|
|
12535
|
+
const exists = fs45.existsSync(path37.resolve(dir, ws));
|
|
12458
12536
|
console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
|
|
12459
12537
|
}
|
|
12460
12538
|
console.log("");
|
|
@@ -12462,7 +12540,7 @@ async function sourcesListCommand() {
|
|
|
12462
12540
|
}
|
|
12463
12541
|
async function sourcesAddCommand(sourcePath) {
|
|
12464
12542
|
const dir = process.cwd();
|
|
12465
|
-
const absPath =
|
|
12543
|
+
const absPath = path37.resolve(dir, sourcePath);
|
|
12466
12544
|
if (!fs45.existsSync(absPath)) {
|
|
12467
12545
|
console.log(chalk25.red(`
|
|
12468
12546
|
Path not found: ${sourcePath}
|
|
@@ -12478,7 +12556,7 @@ async function sourcesAddCommand(sourcePath) {
|
|
|
12478
12556
|
}
|
|
12479
12557
|
const existing = loadSourcesConfig(dir);
|
|
12480
12558
|
const alreadyConfigured = existing.some(
|
|
12481
|
-
(s) => s.path &&
|
|
12559
|
+
(s) => s.path && path37.resolve(dir, s.path) === absPath
|
|
12482
12560
|
);
|
|
12483
12561
|
if (alreadyConfigured) {
|
|
12484
12562
|
console.log(chalk25.yellow(`
|
|
@@ -12527,7 +12605,7 @@ async function sourcesRemoveCommand(name) {
|
|
|
12527
12605
|
|
|
12528
12606
|
// src/commands/publish.ts
|
|
12529
12607
|
import fs46 from "fs";
|
|
12530
|
-
import
|
|
12608
|
+
import path38 from "path";
|
|
12531
12609
|
import chalk26 from "chalk";
|
|
12532
12610
|
import ora7 from "ora";
|
|
12533
12611
|
init_config();
|
|
@@ -12542,10 +12620,10 @@ async function publishCommand() {
|
|
|
12542
12620
|
const spinner = ora7("Generating project summary...").start();
|
|
12543
12621
|
try {
|
|
12544
12622
|
const fingerprint = await collectFingerprint(dir);
|
|
12545
|
-
const claudeMd = readFileOrNull(
|
|
12623
|
+
const claudeMd = readFileOrNull(path38.join(dir, "CLAUDE.md"));
|
|
12546
12624
|
const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
|
|
12547
12625
|
const summary = {
|
|
12548
|
-
name: fingerprint.packageName ||
|
|
12626
|
+
name: fingerprint.packageName || path38.basename(dir),
|
|
12549
12627
|
version: "1.0.0",
|
|
12550
12628
|
description: fingerprint.description || "",
|
|
12551
12629
|
languages: fingerprint.languages,
|
|
@@ -12557,7 +12635,7 @@ async function publishCommand() {
|
|
|
12557
12635
|
summary.conventions = claudeMd.slice(0, 2e3);
|
|
12558
12636
|
}
|
|
12559
12637
|
try {
|
|
12560
|
-
const pkgContent = readFileOrNull(
|
|
12638
|
+
const pkgContent = readFileOrNull(path38.join(dir, "package.json"));
|
|
12561
12639
|
if (pkgContent) {
|
|
12562
12640
|
const pkg3 = JSON.parse(pkgContent);
|
|
12563
12641
|
if (pkg3.scripts) {
|
|
@@ -12570,14 +12648,14 @@ async function publishCommand() {
|
|
|
12570
12648
|
}
|
|
12571
12649
|
} catch {
|
|
12572
12650
|
}
|
|
12573
|
-
const outputDir =
|
|
12651
|
+
const outputDir = path38.join(dir, ".caliber");
|
|
12574
12652
|
if (!fs46.existsSync(outputDir)) {
|
|
12575
12653
|
fs46.mkdirSync(outputDir, { recursive: true });
|
|
12576
12654
|
}
|
|
12577
|
-
const outputPath =
|
|
12655
|
+
const outputPath = path38.join(outputDir, "summary.json");
|
|
12578
12656
|
fs46.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
12579
12657
|
spinner.succeed("Project summary published");
|
|
12580
|
-
console.log(` ${chalk26.green("\u2713")} ${
|
|
12658
|
+
console.log(` ${chalk26.green("\u2713")} ${path38.relative(dir, outputPath)}`);
|
|
12581
12659
|
console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
|
|
12582
12660
|
console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
|
|
12583
12661
|
} catch (err) {
|
|
@@ -12630,7 +12708,7 @@ async function bootstrapCommand() {
|
|
|
12630
12708
|
|
|
12631
12709
|
// src/commands/uninstall.ts
|
|
12632
12710
|
import fs48 from "fs";
|
|
12633
|
-
import
|
|
12711
|
+
import path39 from "path";
|
|
12634
12712
|
import chalk28 from "chalk";
|
|
12635
12713
|
import confirm3 from "@inquirer/confirm";
|
|
12636
12714
|
init_pre_commit_block();
|
|
@@ -12638,17 +12716,17 @@ init_builtin_skills();
|
|
|
12638
12716
|
init_config();
|
|
12639
12717
|
var MANAGED_DOC_FILES = [
|
|
12640
12718
|
"CLAUDE.md",
|
|
12641
|
-
|
|
12719
|
+
path39.join(".github", "copilot-instructions.md"),
|
|
12642
12720
|
"AGENTS.md"
|
|
12643
12721
|
];
|
|
12644
12722
|
var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
|
|
12645
|
-
var CURSOR_RULES_DIR =
|
|
12723
|
+
var CURSOR_RULES_DIR = path39.join(".cursor", "rules");
|
|
12646
12724
|
function removeCaliberCursorRules() {
|
|
12647
12725
|
const removed = [];
|
|
12648
12726
|
if (!fs48.existsSync(CURSOR_RULES_DIR)) return removed;
|
|
12649
12727
|
for (const file of fs48.readdirSync(CURSOR_RULES_DIR)) {
|
|
12650
12728
|
if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
|
|
12651
|
-
const fullPath =
|
|
12729
|
+
const fullPath = path39.join(CURSOR_RULES_DIR, file);
|
|
12652
12730
|
fs48.unlinkSync(fullPath);
|
|
12653
12731
|
removed.push(fullPath);
|
|
12654
12732
|
}
|
|
@@ -12660,7 +12738,7 @@ function removeBuiltinSkills() {
|
|
|
12660
12738
|
for (const skillsDir of SKILL_DIRS) {
|
|
12661
12739
|
if (!fs48.existsSync(skillsDir)) continue;
|
|
12662
12740
|
for (const name of BUILTIN_SKILL_NAMES) {
|
|
12663
|
-
const skillDir =
|
|
12741
|
+
const skillDir = path39.join(skillsDir, name);
|
|
12664
12742
|
if (fs48.existsSync(skillDir)) {
|
|
12665
12743
|
fs48.rmSync(skillDir, { recursive: true });
|
|
12666
12744
|
removed.push(skillDir);
|
|
@@ -12764,7 +12842,7 @@ async function uninstallCommand(options) {
|
|
|
12764
12842
|
if (removeConfig) {
|
|
12765
12843
|
fs48.unlinkSync(configPath);
|
|
12766
12844
|
console.log(` ${chalk28.red("\u2717")} ${configPath}`);
|
|
12767
|
-
const configDir =
|
|
12845
|
+
const configDir = path39.dirname(configPath);
|
|
12768
12846
|
try {
|
|
12769
12847
|
const remaining = fs48.readdirSync(configDir);
|
|
12770
12848
|
if (remaining.length === 0) fs48.rmdirSync(configDir);
|
|
@@ -12778,9 +12856,9 @@ async function uninstallCommand(options) {
|
|
|
12778
12856
|
}
|
|
12779
12857
|
|
|
12780
12858
|
// src/cli.ts
|
|
12781
|
-
var __dirname =
|
|
12859
|
+
var __dirname = path40.dirname(fileURLToPath(import.meta.url));
|
|
12782
12860
|
var pkg = JSON.parse(
|
|
12783
|
-
fs49.readFileSync(
|
|
12861
|
+
fs49.readFileSync(path40.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
12784
12862
|
);
|
|
12785
12863
|
var program = new Command();
|
|
12786
12864
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -12872,16 +12950,14 @@ learn.command("add <content>").description("Add a learning directly (used by age
|
|
|
12872
12950
|
|
|
12873
12951
|
// src/utils/version-check.ts
|
|
12874
12952
|
import fs50 from "fs";
|
|
12875
|
-
import
|
|
12953
|
+
import path41 from "path";
|
|
12876
12954
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12877
12955
|
import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
|
|
12878
12956
|
import chalk29 from "chalk";
|
|
12879
12957
|
import ora8 from "ora";
|
|
12880
12958
|
import confirm4 from "@inquirer/confirm";
|
|
12881
|
-
var __dirname_vc =
|
|
12882
|
-
var pkg2 = JSON.parse(
|
|
12883
|
-
fs50.readFileSync(path40.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
12884
|
-
);
|
|
12959
|
+
var __dirname_vc = path41.dirname(fileURLToPath2(import.meta.url));
|
|
12960
|
+
var pkg2 = JSON.parse(fs50.readFileSync(path41.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
|
|
12885
12961
|
function getChannel(version) {
|
|
12886
12962
|
const match = version.match(/-(dev|next)\./);
|
|
12887
12963
|
return match ? match[1] : "latest";
|
|
@@ -12904,8 +12980,11 @@ function isNewer(registry, current) {
|
|
|
12904
12980
|
}
|
|
12905
12981
|
function getInstalledVersion() {
|
|
12906
12982
|
try {
|
|
12907
|
-
const globalRoot = execSync16("npm root -g", {
|
|
12908
|
-
|
|
12983
|
+
const globalRoot = execSync16("npm root -g", {
|
|
12984
|
+
encoding: "utf-8",
|
|
12985
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
12986
|
+
}).trim();
|
|
12987
|
+
const pkgPath = path41.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
12909
12988
|
return JSON.parse(fs50.readFileSync(pkgPath, "utf-8")).version;
|
|
12910
12989
|
} catch {
|
|
12911
12990
|
return null;
|
|
@@ -12940,17 +13019,18 @@ Run ${chalk29.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
|
|
|
12940
13019
|
);
|
|
12941
13020
|
return;
|
|
12942
13021
|
}
|
|
12943
|
-
console.log(
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
13022
|
+
console.log(chalk29.yellow(`
|
|
13023
|
+
Update available: ${current} -> ${latest}`));
|
|
13024
|
+
const shouldUpdate = await confirm4({
|
|
13025
|
+
message: "Would you like to update now? (Y/n)",
|
|
13026
|
+
default: true
|
|
13027
|
+
});
|
|
12948
13028
|
if (!shouldUpdate) {
|
|
12949
13029
|
console.log();
|
|
12950
13030
|
return;
|
|
12951
13031
|
}
|
|
12952
13032
|
const tag = channel === "latest" ? latest : channel;
|
|
12953
|
-
if (!/^[\w
|
|
13033
|
+
if (!/^[\w.-]+$/.test(tag)) return;
|
|
12954
13034
|
const spinner = ora8("Updating caliber...").start();
|
|
12955
13035
|
try {
|
|
12956
13036
|
execFileSync3("npm", ["install", "-g", `@rely-ai/caliber@${tag}`], {
|
|
@@ -12961,8 +13041,10 @@ Update available: ${current} -> ${latest}`)
|
|
|
12961
13041
|
const installed = getInstalledVersion();
|
|
12962
13042
|
if (installed !== latest) {
|
|
12963
13043
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
12964
|
-
console.log(
|
|
12965
|
-
`)
|
|
13044
|
+
console.log(
|
|
13045
|
+
chalk29.yellow(`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
|
|
13046
|
+
`)
|
|
13047
|
+
);
|
|
12966
13048
|
return;
|
|
12967
13049
|
}
|
|
12968
13050
|
spinner.succeed(chalk29.green(`Updated to ${latest}`));
|