@rely-ai/caliber 1.31.0-dev.1774710550 → 1.31.0-dev.1774711826
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +854 -744
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -230,144 +230,560 @@ var init_types = __esm({
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
-
// src/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
// src/writers/pre-commit-block.ts
|
|
234
|
+
var pre_commit_block_exports = {};
|
|
235
|
+
__export(pre_commit_block_exports, {
|
|
236
|
+
appendLearningsBlock: () => appendLearningsBlock,
|
|
237
|
+
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
238
|
+
appendSyncBlock: () => appendSyncBlock,
|
|
239
|
+
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
240
|
+
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
241
|
+
getCursorSyncRule: () => getCursorSyncRule,
|
|
242
|
+
hasLearningsBlock: () => hasLearningsBlock,
|
|
243
|
+
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
244
|
+
hasSyncBlock: () => hasSyncBlock
|
|
245
|
+
});
|
|
246
|
+
function getPreCommitBlock() {
|
|
247
|
+
const bin = resolveCaliber();
|
|
248
|
+
return `${BLOCK_START}
|
|
249
|
+
## Before Committing
|
|
250
|
+
|
|
251
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
252
|
+
|
|
253
|
+
First, check if the pre-commit hook is already installed:
|
|
254
|
+
\`\`\`bash
|
|
255
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
- If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
|
|
259
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
260
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
261
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
262
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
263
|
+
|
|
264
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
|
|
265
|
+
${BLOCK_END}`;
|
|
243
266
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
267
|
+
function getCursorRuleContent() {
|
|
268
|
+
const bin = resolveCaliber();
|
|
269
|
+
return `---
|
|
270
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
271
|
+
alwaysApply: true
|
|
272
|
+
---
|
|
273
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
274
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
275
|
+
|
|
276
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
277
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
278
|
+
|
|
279
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
280
|
+
`;
|
|
252
281
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
if (commandExists("cursor")) methods.push("cursor");
|
|
256
|
-
if (commandExists("code")) methods.push("vscode");
|
|
257
|
-
methods.push("terminal");
|
|
258
|
-
return methods;
|
|
282
|
+
function hasPreCommitBlock(content) {
|
|
283
|
+
return content.includes(BLOCK_START);
|
|
259
284
|
}
|
|
260
|
-
function
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const leftPath = file.originalPath ?? getEmptyFilePath(file.proposedPath);
|
|
265
|
-
if (IS_WINDOWS3) {
|
|
266
|
-
const quote = (s) => `"${s}"`;
|
|
267
|
-
spawn3([cmd, "--diff", quote(leftPath), quote(file.proposedPath)].join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
|
|
268
|
-
} else {
|
|
269
|
-
spawn3(cmd, ["--diff", leftPath, file.proposedPath], { stdio: "ignore", detached: true }).unref();
|
|
270
|
-
}
|
|
271
|
-
} catch {
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
285
|
+
function appendPreCommitBlock(content) {
|
|
286
|
+
if (hasPreCommitBlock(content)) return content;
|
|
287
|
+
const trimmed = content.trimEnd();
|
|
288
|
+
return trimmed + "\n\n" + getPreCommitBlock() + "\n";
|
|
275
289
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
function getCursorPreCommitRule() {
|
|
291
|
+
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
292
|
+
}
|
|
293
|
+
function hasLearningsBlock(content) {
|
|
294
|
+
return content.includes(LEARNINGS_BLOCK_START);
|
|
295
|
+
}
|
|
296
|
+
function appendLearningsBlock(content) {
|
|
297
|
+
if (hasLearningsBlock(content)) return content;
|
|
298
|
+
const trimmed = content.trimEnd();
|
|
299
|
+
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
300
|
+
}
|
|
301
|
+
function getCursorLearningsRule() {
|
|
302
|
+
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
303
|
+
}
|
|
304
|
+
function getSyncBlock() {
|
|
305
|
+
const bin = resolveCaliber();
|
|
306
|
+
return `${SYNC_BLOCK_START}
|
|
307
|
+
## Context Sync
|
|
308
|
+
|
|
309
|
+
This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
310
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
311
|
+
If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
|
|
312
|
+
${SYNC_BLOCK_END}`;
|
|
313
|
+
}
|
|
314
|
+
function hasSyncBlock(content) {
|
|
315
|
+
return content.includes(SYNC_BLOCK_START);
|
|
316
|
+
}
|
|
317
|
+
function appendSyncBlock(content) {
|
|
318
|
+
if (hasSyncBlock(content)) return content;
|
|
319
|
+
const trimmed = content.trimEnd();
|
|
320
|
+
return trimmed + "\n\n" + getSyncBlock() + "\n";
|
|
321
|
+
}
|
|
322
|
+
function getCursorSyncContent() {
|
|
323
|
+
const bin = resolveCaliber();
|
|
324
|
+
return `---
|
|
325
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
326
|
+
alwaysApply: true
|
|
327
|
+
---
|
|
328
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
329
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
330
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
function getCursorSyncRule() {
|
|
334
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
335
|
+
}
|
|
336
|
+
var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME;
|
|
337
|
+
var init_pre_commit_block = __esm({
|
|
338
|
+
"src/writers/pre-commit-block.ts"() {
|
|
279
339
|
"use strict";
|
|
280
|
-
|
|
281
|
-
|
|
340
|
+
init_resolve_caliber();
|
|
341
|
+
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
342
|
+
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
343
|
+
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
344
|
+
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
345
|
+
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
346
|
+
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
347
|
+
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
348
|
+
## Session Learnings
|
|
349
|
+
|
|
350
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
351
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
352
|
+
${LEARNINGS_BLOCK_END}`;
|
|
353
|
+
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
354
|
+
CURSOR_LEARNINGS_CONTENT = `---
|
|
355
|
+
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
356
|
+
alwaysApply: true
|
|
357
|
+
---
|
|
358
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
359
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
360
|
+
`;
|
|
361
|
+
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
362
|
+
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
363
|
+
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
282
364
|
}
|
|
283
365
|
});
|
|
284
366
|
|
|
285
|
-
// src/
|
|
286
|
-
var
|
|
287
|
-
__export(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
367
|
+
// src/lib/builtin-skills.ts
|
|
368
|
+
var builtin_skills_exports = {};
|
|
369
|
+
__export(builtin_skills_exports, {
|
|
370
|
+
BUILTIN_SKILLS: () => BUILTIN_SKILLS,
|
|
371
|
+
FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
|
|
372
|
+
SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
|
|
373
|
+
SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
|
|
374
|
+
buildSkillContent: () => buildSkillContent,
|
|
375
|
+
ensureBuiltinSkills: () => ensureBuiltinSkills
|
|
291
376
|
});
|
|
292
|
-
import
|
|
293
|
-
import
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
]
|
|
303
|
-
});
|
|
377
|
+
import fs17 from "fs";
|
|
378
|
+
import path16 from "path";
|
|
379
|
+
function buildSkillContent(skill) {
|
|
380
|
+
const frontmatter = `---
|
|
381
|
+
name: ${skill.name}
|
|
382
|
+
description: ${skill.description}
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
`;
|
|
386
|
+
return frontmatter + skill.content;
|
|
304
387
|
}
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
388
|
+
function getFindSkillsContent() {
|
|
389
|
+
const bin = resolveCaliber();
|
|
390
|
+
return `# Find Skills
|
|
391
|
+
|
|
392
|
+
Search the public skill registry for community-contributed skills
|
|
393
|
+
relevant to the user's current task and install them into this project.
|
|
394
|
+
|
|
395
|
+
## Instructions
|
|
396
|
+
|
|
397
|
+
1. Identify the key technologies, frameworks, or task types from the
|
|
398
|
+
user's request that might have community skills available
|
|
399
|
+
2. Ask the user: "Would you like me to search for community skills
|
|
400
|
+
for [identified technologies]?"
|
|
401
|
+
3. If the user agrees, run:
|
|
402
|
+
\`\`\`bash
|
|
403
|
+
${bin} skills --query "<relevant terms>"
|
|
404
|
+
\`\`\`
|
|
405
|
+
This outputs the top 5 matching skills with scores and descriptions.
|
|
406
|
+
4. Present the results to the user and ask which ones to install
|
|
407
|
+
5. Install the selected skills:
|
|
408
|
+
\`\`\`bash
|
|
409
|
+
${bin} skills --install <slug1>,<slug2>
|
|
410
|
+
\`\`\`
|
|
411
|
+
6. Read the installed SKILL.md files to load them into your current
|
|
412
|
+
context so you can use them immediately in this session
|
|
413
|
+
7. Summarize what was installed and continue with the user's task
|
|
414
|
+
|
|
415
|
+
## Examples
|
|
416
|
+
|
|
417
|
+
User: "let's build a web app using React"
|
|
418
|
+
-> "I notice you want to work with React. Would you like me to search
|
|
419
|
+
for community skills that could help with React development?"
|
|
420
|
+
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
421
|
+
-> Show the user the results, ask which to install
|
|
422
|
+
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
423
|
+
-> Read the installed files and continue
|
|
424
|
+
|
|
425
|
+
User: "help me set up Docker for this project"
|
|
426
|
+
-> "Would you like me to search for Docker-related skills?"
|
|
427
|
+
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
428
|
+
|
|
429
|
+
User: "I need to write tests for this Python ML pipeline"
|
|
430
|
+
-> "Would you like me to find skills for Python ML testing?"
|
|
431
|
+
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
432
|
+
|
|
433
|
+
## When NOT to trigger
|
|
434
|
+
|
|
435
|
+
- The user is working within an already well-configured area
|
|
436
|
+
- You already suggested skills for this technology in this session
|
|
437
|
+
- The user is in the middle of urgent debugging or time-sensitive work
|
|
438
|
+
- The technology is too generic (e.g. just "code" or "programming")
|
|
439
|
+
`;
|
|
319
440
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
441
|
+
function getSaveLearningContent() {
|
|
442
|
+
const bin = resolveCaliber();
|
|
443
|
+
return `# Save Learning
|
|
444
|
+
|
|
445
|
+
Save a user's instruction or preference as a persistent learning that
|
|
446
|
+
will be applied in all future sessions on this project.
|
|
447
|
+
|
|
448
|
+
## Instructions
|
|
449
|
+
|
|
450
|
+
1. Detect when the user gives an instruction to remember, such as:
|
|
451
|
+
- "remember this", "save this", "always do X", "never do Y"
|
|
452
|
+
- "from now on", "going forward", "in this project we..."
|
|
453
|
+
- Any stated convention, preference, or rule
|
|
454
|
+
2. Refine the instruction into a clean, actionable learning bullet with
|
|
455
|
+
an appropriate type prefix:
|
|
456
|
+
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
457
|
+
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
458
|
+
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
459
|
+
- \`**[preference]**\` \u2014 personal/team preferences
|
|
460
|
+
- \`**[context]**\` \u2014 project-specific context
|
|
461
|
+
3. Show the refined learning to the user and ask for confirmation
|
|
462
|
+
4. If confirmed, run:
|
|
463
|
+
\`\`\`bash
|
|
464
|
+
${bin} learn add "<refined learning>"
|
|
465
|
+
\`\`\`
|
|
466
|
+
For personal preferences (not project-level), add \`--personal\`:
|
|
467
|
+
\`\`\`bash
|
|
468
|
+
${bin} learn add --personal "<refined learning>"
|
|
469
|
+
\`\`\`
|
|
470
|
+
5. Stage the learnings file for the next commit:
|
|
471
|
+
\`\`\`bash
|
|
472
|
+
git add CALIBER_LEARNINGS.md
|
|
473
|
+
\`\`\`
|
|
474
|
+
|
|
475
|
+
## Examples
|
|
476
|
+
|
|
477
|
+
User: "when developing features, push to next branch not master, remember it"
|
|
478
|
+
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
479
|
+
-> "I'll save this as a project learning:
|
|
480
|
+
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
481
|
+
Save for future sessions?"
|
|
482
|
+
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
483
|
+
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
484
|
+
|
|
485
|
+
User: "always use bun instead of npm"
|
|
486
|
+
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
487
|
+
-> Confirm and save
|
|
488
|
+
|
|
489
|
+
User: "never use any in TypeScript, use unknown instead"
|
|
490
|
+
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
491
|
+
-> Confirm and save
|
|
492
|
+
|
|
493
|
+
## When NOT to trigger
|
|
494
|
+
|
|
495
|
+
- The user is giving a one-time instruction for the current task only
|
|
496
|
+
- The instruction is too vague to be actionable
|
|
497
|
+
- The user explicitly says "just for now" or "only this time"
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
function getSetupCaliberContent() {
|
|
501
|
+
const bin = resolveCaliber();
|
|
502
|
+
return `# Setup Caliber
|
|
503
|
+
|
|
504
|
+
Set up Caliber for automatic AI agent context sync on this project.
|
|
505
|
+
Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
|
|
506
|
+
in sync with your codebase \u2014 automatically on every commit.
|
|
507
|
+
|
|
508
|
+
## Instructions
|
|
509
|
+
|
|
510
|
+
1. Check if Caliber is already set up locally:
|
|
511
|
+
\`\`\`bash
|
|
512
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
513
|
+
\`\`\`
|
|
514
|
+
|
|
515
|
+
2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
|
|
516
|
+
Your agent configs update automatically on every commit." Then stop.
|
|
517
|
+
|
|
518
|
+
3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
|
|
519
|
+
or for your team?"
|
|
520
|
+
|
|
521
|
+
### Solo Setup
|
|
522
|
+
|
|
523
|
+
a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
|
|
524
|
+
on every commit."
|
|
525
|
+
b. Install the pre-commit hook:
|
|
526
|
+
\`\`\`bash
|
|
527
|
+
${bin} hooks --install
|
|
528
|
+
\`\`\`
|
|
529
|
+
c. Run a refresh to make sure configs are current:
|
|
530
|
+
\`\`\`bash
|
|
531
|
+
${bin} refresh
|
|
532
|
+
\`\`\`
|
|
533
|
+
d. Tell the user what happened and that configs will auto-sync on every commit.
|
|
534
|
+
|
|
535
|
+
### Team Setup
|
|
536
|
+
|
|
537
|
+
a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
|
|
538
|
+
plus a GitHub Action for automatic sync across the team."
|
|
539
|
+
b. Install the local pre-commit hook:
|
|
540
|
+
\`\`\`bash
|
|
541
|
+
${bin} hooks --install
|
|
542
|
+
\`\`\`
|
|
543
|
+
c. Run a refresh:
|
|
544
|
+
\`\`\`bash
|
|
545
|
+
${bin} refresh
|
|
546
|
+
\`\`\`
|
|
547
|
+
d. Create the GitHub Action workflow for nightly sync. Write this file to
|
|
548
|
+
\`.github/workflows/caliber-sync.yml\`:
|
|
549
|
+
\`\`\`yaml
|
|
550
|
+
name: Caliber Sync
|
|
551
|
+
on:
|
|
552
|
+
schedule:
|
|
553
|
+
- cron: '0 3 * * 1-5'
|
|
554
|
+
pull_request:
|
|
555
|
+
types: [opened, synchronize]
|
|
556
|
+
workflow_dispatch:
|
|
557
|
+
jobs:
|
|
558
|
+
sync:
|
|
559
|
+
runs-on: ubuntu-latest
|
|
560
|
+
steps:
|
|
561
|
+
- uses: actions/checkout@v4
|
|
562
|
+
- uses: caliber-ai-org/ai-setup@v1
|
|
563
|
+
with:
|
|
564
|
+
mode: sync
|
|
565
|
+
auto-refresh: true
|
|
566
|
+
comment: true
|
|
567
|
+
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
568
|
+
env:
|
|
569
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
570
|
+
\`\`\`
|
|
571
|
+
e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
|
|
572
|
+
to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
|
|
573
|
+
workflow file. From now on, every PR gets a Caliber sync check and configs
|
|
574
|
+
refresh nightly."
|
|
575
|
+
f. Offer to commit the workflow file and the updated configs.
|
|
576
|
+
|
|
577
|
+
### If \`${bin}\` is not found
|
|
578
|
+
|
|
579
|
+
Install it first:
|
|
580
|
+
\`\`\`bash
|
|
581
|
+
npm install -g @rely-ai/caliber
|
|
582
|
+
\`\`\`
|
|
583
|
+
Or use npx (no install needed):
|
|
584
|
+
\`\`\`bash
|
|
585
|
+
npx @rely-ai/caliber hooks --install
|
|
586
|
+
\`\`\`
|
|
587
|
+
|
|
588
|
+
## When to trigger
|
|
589
|
+
|
|
590
|
+
- When a developer first opens this project and Caliber hooks are not installed
|
|
591
|
+
- When the user asks about keeping agent configs up to date
|
|
592
|
+
- When the user asks "how do I set up Caliber" or "what is Caliber"
|
|
593
|
+
|
|
594
|
+
## When NOT to trigger
|
|
595
|
+
|
|
596
|
+
- Hooks are already installed and working
|
|
597
|
+
- The user is in the middle of time-sensitive work
|
|
598
|
+
`;
|
|
599
|
+
}
|
|
600
|
+
function ensureBuiltinSkills() {
|
|
601
|
+
const written = [];
|
|
602
|
+
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
603
|
+
if (!fs17.existsSync(platformDir)) continue;
|
|
604
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
605
|
+
const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
|
|
606
|
+
if (fs17.existsSync(skillPath)) continue;
|
|
607
|
+
fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
|
|
608
|
+
fs17.writeFileSync(skillPath, buildSkillContent(skill));
|
|
609
|
+
written.push(skillPath);
|
|
342
610
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
611
|
+
}
|
|
612
|
+
return written;
|
|
613
|
+
}
|
|
614
|
+
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS;
|
|
615
|
+
var init_builtin_skills = __esm({
|
|
616
|
+
"src/lib/builtin-skills.ts"() {
|
|
617
|
+
"use strict";
|
|
618
|
+
init_resolve_caliber();
|
|
619
|
+
FIND_SKILLS_SKILL = {
|
|
620
|
+
name: "find-skills",
|
|
621
|
+
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.",
|
|
622
|
+
get content() {
|
|
623
|
+
return getFindSkillsContent();
|
|
624
|
+
}
|
|
350
625
|
};
|
|
351
|
-
|
|
352
|
-
|
|
626
|
+
SAVE_LEARNING_SKILL = {
|
|
627
|
+
name: "save-learning",
|
|
628
|
+
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.",
|
|
629
|
+
get content() {
|
|
630
|
+
return getSaveLearningContent();
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
SETUP_CALIBER_SKILL = {
|
|
634
|
+
name: "setup-caliber",
|
|
635
|
+
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.",
|
|
636
|
+
get content() {
|
|
637
|
+
return getSetupCaliberContent();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
641
|
+
PLATFORM_CONFIGS = [
|
|
642
|
+
{ platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
|
|
643
|
+
{ platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
|
|
644
|
+
{ platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
|
|
645
|
+
];
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/utils/editor.ts
|
|
650
|
+
import { execSync as execSync14, spawn as spawn3 } from "child_process";
|
|
651
|
+
import fs27 from "fs";
|
|
652
|
+
import path23 from "path";
|
|
653
|
+
import os6 from "os";
|
|
654
|
+
function getEmptyFilePath(proposedPath) {
|
|
655
|
+
fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
656
|
+
const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
|
|
657
|
+
fs27.writeFileSync(tempPath, "");
|
|
658
|
+
return tempPath;
|
|
353
659
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
660
|
+
function commandExists(cmd) {
|
|
661
|
+
try {
|
|
662
|
+
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
663
|
+
execSync14(check, { stdio: "ignore" });
|
|
664
|
+
return true;
|
|
665
|
+
} catch {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function detectAvailableEditors() {
|
|
670
|
+
const methods = [];
|
|
671
|
+
if (commandExists("cursor")) methods.push("cursor");
|
|
672
|
+
if (commandExists("code")) methods.push("vscode");
|
|
673
|
+
methods.push("terminal");
|
|
674
|
+
return methods;
|
|
675
|
+
}
|
|
676
|
+
function openDiffsInEditor(editor, files) {
|
|
677
|
+
const cmd = editor === "cursor" ? "cursor" : "code";
|
|
678
|
+
for (const file of files) {
|
|
679
|
+
try {
|
|
680
|
+
const leftPath = file.originalPath ?? getEmptyFilePath(file.proposedPath);
|
|
681
|
+
if (IS_WINDOWS3) {
|
|
682
|
+
const quote = (s) => `"${s}"`;
|
|
683
|
+
spawn3([cmd, "--diff", quote(leftPath), quote(file.proposedPath)].join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
|
|
684
|
+
} else {
|
|
685
|
+
spawn3(cmd, ["--diff", leftPath, file.proposedPath], { stdio: "ignore", detached: true }).unref();
|
|
686
|
+
}
|
|
687
|
+
} catch {
|
|
688
|
+
continue;
|
|
360
689
|
}
|
|
361
|
-
console.log("");
|
|
362
|
-
return;
|
|
363
690
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
691
|
+
}
|
|
692
|
+
var IS_WINDOWS3, DIFF_TEMP_DIR;
|
|
693
|
+
var init_editor = __esm({
|
|
694
|
+
"src/utils/editor.ts"() {
|
|
695
|
+
"use strict";
|
|
696
|
+
IS_WINDOWS3 = process.platform === "win32";
|
|
697
|
+
DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// src/utils/review.ts
|
|
702
|
+
var review_exports = {};
|
|
703
|
+
__export(review_exports, {
|
|
704
|
+
openReview: () => openReview,
|
|
705
|
+
promptReviewMethod: () => promptReviewMethod,
|
|
706
|
+
promptWantsReview: () => promptWantsReview
|
|
707
|
+
});
|
|
708
|
+
import chalk10 from "chalk";
|
|
709
|
+
import fs28 from "fs";
|
|
710
|
+
import select4 from "@inquirer/select";
|
|
711
|
+
import { createTwoFilesPatch } from "diff";
|
|
712
|
+
async function promptWantsReview() {
|
|
713
|
+
return select4({
|
|
714
|
+
message: "Would you like to review the diffs before deciding?",
|
|
715
|
+
choices: [
|
|
716
|
+
{ name: "Yes, show me the diffs", value: true },
|
|
717
|
+
{ name: "No, continue", value: false }
|
|
718
|
+
]
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
async function promptReviewMethod() {
|
|
722
|
+
const available = detectAvailableEditors();
|
|
723
|
+
if (available.length === 1) return "terminal";
|
|
724
|
+
const choices = available.map((method) => {
|
|
725
|
+
switch (method) {
|
|
726
|
+
case "cursor":
|
|
727
|
+
return { name: "Cursor (diff view)", value: "cursor" };
|
|
728
|
+
case "vscode":
|
|
729
|
+
return { name: "VS Code (diff view)", value: "vscode" };
|
|
730
|
+
case "terminal":
|
|
731
|
+
return { name: "Terminal", value: "terminal" };
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
return select4({ message: "How would you like to review the changes?", choices });
|
|
735
|
+
}
|
|
736
|
+
async function openReview(method, stagedFiles) {
|
|
737
|
+
if (method === "cursor" || method === "vscode") {
|
|
738
|
+
openDiffsInEditor(method, stagedFiles.map((f) => ({
|
|
739
|
+
originalPath: f.originalPath,
|
|
740
|
+
proposedPath: f.proposedPath
|
|
741
|
+
})));
|
|
742
|
+
console.log(chalk10.dim(" Diffs opened in your editor.\n"));
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const fileInfos = stagedFiles.map((file) => {
|
|
746
|
+
const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
|
|
747
|
+
const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
|
|
748
|
+
const patch = createTwoFilesPatch(
|
|
749
|
+
file.isNew ? "/dev/null" : file.relativePath,
|
|
750
|
+
file.relativePath,
|
|
751
|
+
current,
|
|
752
|
+
proposed
|
|
753
|
+
);
|
|
754
|
+
let added = 0, removed = 0;
|
|
755
|
+
for (const line of patch.split("\n")) {
|
|
756
|
+
if (line.startsWith("+") && !line.startsWith("+++")) added++;
|
|
757
|
+
if (line.startsWith("-") && !line.startsWith("---")) removed++;
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
relativePath: file.relativePath,
|
|
761
|
+
isNew: file.isNew,
|
|
762
|
+
added,
|
|
763
|
+
removed,
|
|
764
|
+
lines: proposed.split("\n").length,
|
|
765
|
+
patch
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
await interactiveDiffExplorer(fileInfos);
|
|
769
|
+
}
|
|
770
|
+
async function interactiveDiffExplorer(files) {
|
|
771
|
+
if (!process.stdin.isTTY) {
|
|
772
|
+
for (const f of files) {
|
|
773
|
+
const icon = f.isNew ? chalk10.green("+") : chalk10.yellow("~");
|
|
774
|
+
const stats = f.isNew ? chalk10.dim(`${f.lines} lines`) : `${chalk10.green(`+${f.added}`)} ${chalk10.red(`-${f.removed}`)}`;
|
|
775
|
+
console.log(` ${icon} ${f.relativePath} ${stats}`);
|
|
776
|
+
}
|
|
777
|
+
console.log("");
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const { stdin, stdout } = process;
|
|
781
|
+
let cursor = 0;
|
|
782
|
+
let viewing = null;
|
|
783
|
+
let scrollOffset = 0;
|
|
784
|
+
let lineCount = 0;
|
|
785
|
+
function getTermHeight() {
|
|
786
|
+
return (stdout.rows || 24) - 4;
|
|
371
787
|
}
|
|
372
788
|
function renderFileList() {
|
|
373
789
|
const lines = [];
|
|
@@ -4385,610 +4801,232 @@ Project dependencies (${allDeps.length}):`);
|
|
|
4385
4801
|
}
|
|
4386
4802
|
if (prompt) parts.push(`
|
|
4387
4803
|
User instructions: ${prompt}`);
|
|
4388
|
-
if (fingerprint.codeAnalysis) {
|
|
4389
|
-
const ca = fingerprint.codeAnalysis;
|
|
4390
|
-
const basePrompt = parts.join("\n");
|
|
4391
|
-
const maxPromptTokens = getMaxPromptTokens();
|
|
4392
|
-
const baseTokens = estimateTokens(basePrompt);
|
|
4393
|
-
const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
|
|
4394
|
-
const codeLines = [];
|
|
4395
|
-
let codeChars = 0;
|
|
4396
|
-
const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
|
|
4397
|
-
codeLines.push(introLine);
|
|
4398
|
-
let runningCodeLen = introLine.length;
|
|
4399
|
-
const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
4400
|
-
let includedFiles = 0;
|
|
4401
|
-
for (const f of sortedFiles) {
|
|
4402
|
-
const entry = `[${f.path}]
|
|
4403
|
-
${f.content}
|
|
4404
|
-
`;
|
|
4405
|
-
const projectedLen = runningCodeLen + 1 + entry.length;
|
|
4406
|
-
if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
|
|
4407
|
-
codeLines.push(entry);
|
|
4408
|
-
codeChars += f.content.length;
|
|
4409
|
-
runningCodeLen = projectedLen;
|
|
4410
|
-
includedFiles++;
|
|
4411
|
-
}
|
|
4412
|
-
const includedTokens = Math.ceil(codeChars / 4);
|
|
4413
|
-
let header;
|
|
4414
|
-
if (includedFiles < ca.files.length) {
|
|
4415
|
-
const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4416
|
-
header = `
|
|
4417
|
-
--- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4418
|
-
} else if (ca.truncated) {
|
|
4419
|
-
const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4420
|
-
header = `
|
|
4421
|
-
--- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4422
|
-
} else {
|
|
4423
|
-
header = `
|
|
4424
|
-
--- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
|
|
4425
|
-
}
|
|
4426
|
-
parts.push(header);
|
|
4427
|
-
parts.push(codeLines.join("\n"));
|
|
4428
|
-
}
|
|
4429
|
-
if (fingerprint.sources?.length) {
|
|
4430
|
-
parts.push(formatSourcesForPrompt(fingerprint.sources));
|
|
4431
|
-
}
|
|
4432
|
-
return parts.join("\n");
|
|
4433
|
-
}
|
|
4434
|
-
|
|
4435
|
-
// src/writers/index.ts
|
|
4436
|
-
import fs19 from "fs";
|
|
4437
|
-
|
|
4438
|
-
// src/writers/claude/index.ts
|
|
4439
|
-
import fs12 from "fs";
|
|
4440
|
-
import path11 from "path";
|
|
4441
|
-
|
|
4442
|
-
// src/writers/pre-commit-block.ts
|
|
4443
|
-
init_resolve_caliber();
|
|
4444
|
-
var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
4445
|
-
var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
4446
|
-
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
4447
|
-
function getPreCommitBlock() {
|
|
4448
|
-
const bin = resolveCaliber();
|
|
4449
|
-
return `${BLOCK_START}
|
|
4450
|
-
## Before Committing
|
|
4451
|
-
|
|
4452
|
-
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
4453
|
-
|
|
4454
|
-
First, check if the pre-commit hook is already installed:
|
|
4455
|
-
\`\`\`bash
|
|
4456
|
-
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
4457
|
-
\`\`\`
|
|
4458
|
-
|
|
4459
|
-
- 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."
|
|
4460
|
-
- If **no-hook**: run Caliber manually before committing:
|
|
4461
|
-
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
4462
|
-
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
4463
|
-
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
4464
|
-
|
|
4465
|
-
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install it with: npm install -g @rely-ai/caliber"
|
|
4466
|
-
${BLOCK_END}`;
|
|
4467
|
-
}
|
|
4468
|
-
var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
4469
|
-
function getCursorRuleContent() {
|
|
4470
|
-
const bin = resolveCaliber();
|
|
4471
|
-
return `---
|
|
4472
|
-
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
4473
|
-
alwaysApply: true
|
|
4474
|
-
---
|
|
4475
|
-
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
4476
|
-
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
4477
|
-
|
|
4478
|
-
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
4479
|
-
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
4480
|
-
|
|
4481
|
-
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4482
|
-
`;
|
|
4483
|
-
}
|
|
4484
|
-
function hasPreCommitBlock(content) {
|
|
4485
|
-
return content.includes(BLOCK_START);
|
|
4486
|
-
}
|
|
4487
|
-
function appendPreCommitBlock(content) {
|
|
4488
|
-
if (hasPreCommitBlock(content)) return content;
|
|
4489
|
-
const trimmed = content.trimEnd();
|
|
4490
|
-
return trimmed + "\n\n" + getPreCommitBlock() + "\n";
|
|
4491
|
-
}
|
|
4492
|
-
function getCursorPreCommitRule() {
|
|
4493
|
-
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
4494
|
-
}
|
|
4495
|
-
var LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
4496
|
-
var LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
4497
|
-
var LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
4498
|
-
## Session Learnings
|
|
4499
|
-
|
|
4500
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
4501
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
4502
|
-
${LEARNINGS_BLOCK_END}`;
|
|
4503
|
-
var CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
4504
|
-
var CURSOR_LEARNINGS_CONTENT = `---
|
|
4505
|
-
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
4506
|
-
alwaysApply: true
|
|
4507
|
-
---
|
|
4508
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
4509
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
4510
|
-
`;
|
|
4511
|
-
function hasLearningsBlock(content) {
|
|
4512
|
-
return content.includes(LEARNINGS_BLOCK_START);
|
|
4513
|
-
}
|
|
4514
|
-
function appendLearningsBlock(content) {
|
|
4515
|
-
if (hasLearningsBlock(content)) return content;
|
|
4516
|
-
const trimmed = content.trimEnd();
|
|
4517
|
-
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
4518
|
-
}
|
|
4519
|
-
function getCursorLearningsRule() {
|
|
4520
|
-
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
4521
|
-
}
|
|
4522
|
-
var SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
4523
|
-
var SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
4524
|
-
function getSyncBlock() {
|
|
4525
|
-
const bin = resolveCaliber();
|
|
4526
|
-
return `${SYNC_BLOCK_START}
|
|
4527
|
-
## Context Sync
|
|
4528
|
-
|
|
4529
|
-
This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
4530
|
-
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
4531
|
-
If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
|
|
4532
|
-
${SYNC_BLOCK_END}`;
|
|
4533
|
-
}
|
|
4534
|
-
function hasSyncBlock(content) {
|
|
4535
|
-
return content.includes(SYNC_BLOCK_START);
|
|
4536
|
-
}
|
|
4537
|
-
function appendSyncBlock(content) {
|
|
4538
|
-
if (hasSyncBlock(content)) return content;
|
|
4539
|
-
const trimmed = content.trimEnd();
|
|
4540
|
-
return trimmed + "\n\n" + getSyncBlock() + "\n";
|
|
4541
|
-
}
|
|
4542
|
-
var CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
4543
|
-
function getCursorSyncContent() {
|
|
4544
|
-
const bin = resolveCaliber();
|
|
4545
|
-
return `---
|
|
4546
|
-
description: This project uses Caliber for automatic AI agent context sync
|
|
4547
|
-
alwaysApply: true
|
|
4548
|
-
---
|
|
4549
|
-
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
4550
|
-
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
4551
|
-
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4552
|
-
`;
|
|
4553
|
-
}
|
|
4554
|
-
function getCursorSyncRule() {
|
|
4555
|
-
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
|
-
// src/writers/claude/index.ts
|
|
4559
|
-
function writeClaudeConfig(config) {
|
|
4560
|
-
const written = [];
|
|
4561
|
-
fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
|
|
4562
|
-
written.push("CLAUDE.md");
|
|
4563
|
-
if (config.skills?.length) {
|
|
4564
|
-
for (const skill of config.skills) {
|
|
4565
|
-
const skillDir = path11.join(".claude", "skills", skill.name);
|
|
4566
|
-
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
4567
|
-
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
4568
|
-
const frontmatter = [
|
|
4569
|
-
"---",
|
|
4570
|
-
`name: ${skill.name}`,
|
|
4571
|
-
`description: ${skill.description}`,
|
|
4572
|
-
"---",
|
|
4573
|
-
""
|
|
4574
|
-
].join("\n");
|
|
4575
|
-
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4576
|
-
written.push(skillPath);
|
|
4577
|
-
}
|
|
4578
|
-
}
|
|
4579
|
-
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4580
|
-
let existingServers = {};
|
|
4581
|
-
try {
|
|
4582
|
-
if (fs12.existsSync(".mcp.json")) {
|
|
4583
|
-
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
4584
|
-
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4585
|
-
}
|
|
4586
|
-
} catch {
|
|
4587
|
-
}
|
|
4588
|
-
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4589
|
-
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4590
|
-
written.push(".mcp.json");
|
|
4591
|
-
}
|
|
4592
|
-
return written;
|
|
4593
|
-
}
|
|
4594
|
-
|
|
4595
|
-
// src/writers/cursor/index.ts
|
|
4596
|
-
import fs13 from "fs";
|
|
4597
|
-
import path12 from "path";
|
|
4598
|
-
function writeCursorConfig(config) {
|
|
4599
|
-
const written = [];
|
|
4600
|
-
if (config.cursorrules) {
|
|
4601
|
-
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
4602
|
-
written.push(".cursorrules");
|
|
4603
|
-
}
|
|
4604
|
-
const preCommitRule = getCursorPreCommitRule();
|
|
4605
|
-
const learningsRule = getCursorLearningsRule();
|
|
4606
|
-
const syncRule = getCursorSyncRule();
|
|
4607
|
-
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
|
|
4608
|
-
const rulesDir = path12.join(".cursor", "rules");
|
|
4609
|
-
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
4610
|
-
for (const rule of allRules) {
|
|
4611
|
-
const rulePath = path12.join(rulesDir, rule.filename);
|
|
4612
|
-
fs13.writeFileSync(rulePath, rule.content);
|
|
4613
|
-
written.push(rulePath);
|
|
4614
|
-
}
|
|
4615
|
-
if (config.skills?.length) {
|
|
4616
|
-
for (const skill of config.skills) {
|
|
4617
|
-
const skillDir = path12.join(".cursor", "skills", skill.name);
|
|
4618
|
-
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
4619
|
-
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4620
|
-
const frontmatter = [
|
|
4621
|
-
"---",
|
|
4622
|
-
`name: ${skill.name}`,
|
|
4623
|
-
`description: ${skill.description}`,
|
|
4624
|
-
"---",
|
|
4625
|
-
""
|
|
4626
|
-
].join("\n");
|
|
4627
|
-
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4628
|
-
written.push(skillPath);
|
|
4629
|
-
}
|
|
4630
|
-
}
|
|
4631
|
-
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4632
|
-
const cursorDir = ".cursor";
|
|
4633
|
-
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
4634
|
-
const mcpPath = path12.join(cursorDir, "mcp.json");
|
|
4635
|
-
let existingServers = {};
|
|
4636
|
-
try {
|
|
4637
|
-
if (fs13.existsSync(mcpPath)) {
|
|
4638
|
-
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
4639
|
-
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4640
|
-
}
|
|
4641
|
-
} catch {
|
|
4642
|
-
}
|
|
4643
|
-
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4644
|
-
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4645
|
-
written.push(mcpPath);
|
|
4646
|
-
}
|
|
4647
|
-
return written;
|
|
4648
|
-
}
|
|
4649
|
-
|
|
4650
|
-
// src/writers/codex/index.ts
|
|
4651
|
-
import fs14 from "fs";
|
|
4652
|
-
import path13 from "path";
|
|
4653
|
-
function writeCodexConfig(config) {
|
|
4654
|
-
const written = [];
|
|
4655
|
-
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4656
|
-
written.push("AGENTS.md");
|
|
4657
|
-
if (config.skills?.length) {
|
|
4658
|
-
for (const skill of config.skills) {
|
|
4659
|
-
const skillDir = path13.join(".agents", "skills", skill.name);
|
|
4660
|
-
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
4661
|
-
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
4662
|
-
const frontmatter = [
|
|
4663
|
-
"---",
|
|
4664
|
-
`name: ${skill.name}`,
|
|
4665
|
-
`description: ${skill.description}`,
|
|
4666
|
-
"---",
|
|
4667
|
-
""
|
|
4668
|
-
].join("\n");
|
|
4669
|
-
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4670
|
-
written.push(skillPath);
|
|
4671
|
-
}
|
|
4672
|
-
}
|
|
4673
|
-
return written;
|
|
4674
|
-
}
|
|
4675
|
-
|
|
4676
|
-
// src/writers/github-copilot/index.ts
|
|
4677
|
-
import fs15 from "fs";
|
|
4678
|
-
import path14 from "path";
|
|
4679
|
-
function writeGithubCopilotConfig(config) {
|
|
4680
|
-
const written = [];
|
|
4681
|
-
if (config.instructions) {
|
|
4682
|
-
fs15.mkdirSync(".github", { recursive: true });
|
|
4683
|
-
fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
|
|
4684
|
-
written.push(".github/copilot-instructions.md");
|
|
4685
|
-
}
|
|
4686
|
-
if (config.instructionFiles?.length) {
|
|
4687
|
-
const instructionsDir = path14.join(".github", "instructions");
|
|
4688
|
-
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
4689
|
-
for (const file of config.instructionFiles) {
|
|
4690
|
-
fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
|
|
4691
|
-
written.push(`.github/instructions/${file.filename}`);
|
|
4692
|
-
}
|
|
4693
|
-
}
|
|
4694
|
-
return written;
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
|
-
// src/writers/backup.ts
|
|
4698
|
-
import fs16 from "fs";
|
|
4699
|
-
import path15 from "path";
|
|
4700
|
-
function createBackup(files) {
|
|
4701
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4702
|
-
const backupDir = path15.join(BACKUPS_DIR, timestamp);
|
|
4703
|
-
for (const file of files) {
|
|
4704
|
-
if (!fs16.existsSync(file)) continue;
|
|
4705
|
-
const dest = path15.join(backupDir, file);
|
|
4706
|
-
const destDir = path15.dirname(dest);
|
|
4707
|
-
if (!fs16.existsSync(destDir)) {
|
|
4708
|
-
fs16.mkdirSync(destDir, { recursive: true });
|
|
4709
|
-
}
|
|
4710
|
-
fs16.copyFileSync(file, dest);
|
|
4711
|
-
}
|
|
4712
|
-
return backupDir;
|
|
4713
|
-
}
|
|
4714
|
-
function restoreBackup(backupDir, file) {
|
|
4715
|
-
const backupFile = path15.join(backupDir, file);
|
|
4716
|
-
if (!fs16.existsSync(backupFile)) return false;
|
|
4717
|
-
const destDir = path15.dirname(file);
|
|
4718
|
-
if (!fs16.existsSync(destDir)) {
|
|
4719
|
-
fs16.mkdirSync(destDir, { recursive: true });
|
|
4720
|
-
}
|
|
4721
|
-
fs16.copyFileSync(backupFile, file);
|
|
4722
|
-
return true;
|
|
4723
|
-
}
|
|
4724
|
-
|
|
4725
|
-
// src/lib/builtin-skills.ts
|
|
4726
|
-
init_resolve_caliber();
|
|
4727
|
-
import fs17 from "fs";
|
|
4728
|
-
import path16 from "path";
|
|
4729
|
-
function buildSkillContent(skill) {
|
|
4730
|
-
const frontmatter = `---
|
|
4731
|
-
name: ${skill.name}
|
|
4732
|
-
description: ${skill.description}
|
|
4733
|
-
---
|
|
4734
|
-
|
|
4735
|
-
`;
|
|
4736
|
-
return frontmatter + skill.content;
|
|
4737
|
-
}
|
|
4738
|
-
function getFindSkillsContent() {
|
|
4739
|
-
const bin = resolveCaliber();
|
|
4740
|
-
return `# Find Skills
|
|
4741
|
-
|
|
4742
|
-
Search the public skill registry for community-contributed skills
|
|
4743
|
-
relevant to the user's current task and install them into this project.
|
|
4744
|
-
|
|
4745
|
-
## Instructions
|
|
4746
|
-
|
|
4747
|
-
1. Identify the key technologies, frameworks, or task types from the
|
|
4748
|
-
user's request that might have community skills available
|
|
4749
|
-
2. Ask the user: "Would you like me to search for community skills
|
|
4750
|
-
for [identified technologies]?"
|
|
4751
|
-
3. If the user agrees, run:
|
|
4752
|
-
\`\`\`bash
|
|
4753
|
-
${bin} skills --query "<relevant terms>"
|
|
4754
|
-
\`\`\`
|
|
4755
|
-
This outputs the top 5 matching skills with scores and descriptions.
|
|
4756
|
-
4. Present the results to the user and ask which ones to install
|
|
4757
|
-
5. Install the selected skills:
|
|
4758
|
-
\`\`\`bash
|
|
4759
|
-
${bin} skills --install <slug1>,<slug2>
|
|
4760
|
-
\`\`\`
|
|
4761
|
-
6. Read the installed SKILL.md files to load them into your current
|
|
4762
|
-
context so you can use them immediately in this session
|
|
4763
|
-
7. Summarize what was installed and continue with the user's task
|
|
4764
|
-
|
|
4765
|
-
## Examples
|
|
4766
|
-
|
|
4767
|
-
User: "let's build a web app using React"
|
|
4768
|
-
-> "I notice you want to work with React. Would you like me to search
|
|
4769
|
-
for community skills that could help with React development?"
|
|
4770
|
-
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
4771
|
-
-> Show the user the results, ask which to install
|
|
4772
|
-
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
4773
|
-
-> Read the installed files and continue
|
|
4774
|
-
|
|
4775
|
-
User: "help me set up Docker for this project"
|
|
4776
|
-
-> "Would you like me to search for Docker-related skills?"
|
|
4777
|
-
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
4778
|
-
|
|
4779
|
-
User: "I need to write tests for this Python ML pipeline"
|
|
4780
|
-
-> "Would you like me to find skills for Python ML testing?"
|
|
4781
|
-
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
4782
|
-
|
|
4783
|
-
## When NOT to trigger
|
|
4784
|
-
|
|
4785
|
-
- The user is working within an already well-configured area
|
|
4786
|
-
- You already suggested skills for this technology in this session
|
|
4787
|
-
- The user is in the middle of urgent debugging or time-sensitive work
|
|
4788
|
-
- The technology is too generic (e.g. just "code" or "programming")
|
|
4789
|
-
`;
|
|
4790
|
-
}
|
|
4791
|
-
function getSaveLearningContent() {
|
|
4792
|
-
const bin = resolveCaliber();
|
|
4793
|
-
return `# Save Learning
|
|
4794
|
-
|
|
4795
|
-
Save a user's instruction or preference as a persistent learning that
|
|
4796
|
-
will be applied in all future sessions on this project.
|
|
4797
|
-
|
|
4798
|
-
## Instructions
|
|
4799
|
-
|
|
4800
|
-
1. Detect when the user gives an instruction to remember, such as:
|
|
4801
|
-
- "remember this", "save this", "always do X", "never do Y"
|
|
4802
|
-
- "from now on", "going forward", "in this project we..."
|
|
4803
|
-
- Any stated convention, preference, or rule
|
|
4804
|
-
2. Refine the instruction into a clean, actionable learning bullet with
|
|
4805
|
-
an appropriate type prefix:
|
|
4806
|
-
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
4807
|
-
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
4808
|
-
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
4809
|
-
- \`**[preference]**\` \u2014 personal/team preferences
|
|
4810
|
-
- \`**[context]**\` \u2014 project-specific context
|
|
4811
|
-
3. Show the refined learning to the user and ask for confirmation
|
|
4812
|
-
4. If confirmed, run:
|
|
4813
|
-
\`\`\`bash
|
|
4814
|
-
${bin} learn add "<refined learning>"
|
|
4815
|
-
\`\`\`
|
|
4816
|
-
For personal preferences (not project-level), add \`--personal\`:
|
|
4817
|
-
\`\`\`bash
|
|
4818
|
-
${bin} learn add --personal "<refined learning>"
|
|
4819
|
-
\`\`\`
|
|
4820
|
-
5. Stage the learnings file for the next commit:
|
|
4821
|
-
\`\`\`bash
|
|
4822
|
-
git add CALIBER_LEARNINGS.md
|
|
4823
|
-
\`\`\`
|
|
4824
|
-
|
|
4825
|
-
## Examples
|
|
4826
|
-
|
|
4827
|
-
User: "when developing features, push to next branch not master, remember it"
|
|
4828
|
-
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
4829
|
-
-> "I'll save this as a project learning:
|
|
4830
|
-
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
4831
|
-
Save for future sessions?"
|
|
4832
|
-
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
4833
|
-
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
4834
|
-
|
|
4835
|
-
User: "always use bun instead of npm"
|
|
4836
|
-
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
4837
|
-
-> Confirm and save
|
|
4838
|
-
|
|
4839
|
-
User: "never use any in TypeScript, use unknown instead"
|
|
4840
|
-
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
4841
|
-
-> Confirm and save
|
|
4842
|
-
|
|
4843
|
-
## When NOT to trigger
|
|
4844
|
-
|
|
4845
|
-
- The user is giving a one-time instruction for the current task only
|
|
4846
|
-
- The instruction is too vague to be actionable
|
|
4847
|
-
- The user explicitly says "just for now" or "only this time"
|
|
4848
|
-
`;
|
|
4849
|
-
}
|
|
4850
|
-
var FIND_SKILLS_SKILL = {
|
|
4851
|
-
name: "find-skills",
|
|
4852
|
-
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.",
|
|
4853
|
-
get content() {
|
|
4854
|
-
return getFindSkillsContent();
|
|
4855
|
-
}
|
|
4856
|
-
};
|
|
4857
|
-
var SAVE_LEARNING_SKILL = {
|
|
4858
|
-
name: "save-learning",
|
|
4859
|
-
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.",
|
|
4860
|
-
get content() {
|
|
4861
|
-
return getSaveLearningContent();
|
|
4862
|
-
}
|
|
4863
|
-
};
|
|
4864
|
-
function getSetupCaliberContent() {
|
|
4865
|
-
const bin = resolveCaliber();
|
|
4866
|
-
return `# Setup Caliber
|
|
4867
|
-
|
|
4868
|
-
Set up Caliber for automatic AI agent context sync on this project.
|
|
4869
|
-
Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
|
|
4870
|
-
in sync with your codebase \u2014 automatically on every commit.
|
|
4871
|
-
|
|
4872
|
-
## Instructions
|
|
4873
|
-
|
|
4874
|
-
1. Check if Caliber is already set up locally:
|
|
4875
|
-
\`\`\`bash
|
|
4876
|
-
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
4877
|
-
\`\`\`
|
|
4878
|
-
|
|
4879
|
-
2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
|
|
4880
|
-
Your agent configs update automatically on every commit." Then stop.
|
|
4881
|
-
|
|
4882
|
-
3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
|
|
4883
|
-
or for your team?"
|
|
4884
|
-
|
|
4885
|
-
### Solo Setup
|
|
4886
|
-
|
|
4887
|
-
a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
|
|
4888
|
-
on every commit."
|
|
4889
|
-
b. Install the pre-commit hook:
|
|
4890
|
-
\`\`\`bash
|
|
4891
|
-
${bin} hooks --install
|
|
4892
|
-
\`\`\`
|
|
4893
|
-
c. Run a refresh to make sure configs are current:
|
|
4894
|
-
\`\`\`bash
|
|
4895
|
-
${bin} refresh
|
|
4896
|
-
\`\`\`
|
|
4897
|
-
d. Tell the user what happened and that configs will auto-sync on every commit.
|
|
4898
|
-
|
|
4899
|
-
### Team Setup
|
|
4900
|
-
|
|
4901
|
-
a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
|
|
4902
|
-
plus a GitHub Action for automatic sync across the team."
|
|
4903
|
-
b. Install the local pre-commit hook:
|
|
4904
|
-
\`\`\`bash
|
|
4905
|
-
${bin} hooks --install
|
|
4906
|
-
\`\`\`
|
|
4907
|
-
c. Run a refresh:
|
|
4908
|
-
\`\`\`bash
|
|
4909
|
-
${bin} refresh
|
|
4910
|
-
\`\`\`
|
|
4911
|
-
d. Create the GitHub Action workflow for nightly sync. Write this file to
|
|
4912
|
-
\`.github/workflows/caliber-sync.yml\`:
|
|
4913
|
-
\`\`\`yaml
|
|
4914
|
-
name: Caliber Sync
|
|
4915
|
-
on:
|
|
4916
|
-
schedule:
|
|
4917
|
-
- cron: '0 3 * * 1-5'
|
|
4918
|
-
pull_request:
|
|
4919
|
-
types: [opened, synchronize]
|
|
4920
|
-
workflow_dispatch:
|
|
4921
|
-
jobs:
|
|
4922
|
-
sync:
|
|
4923
|
-
runs-on: ubuntu-latest
|
|
4924
|
-
steps:
|
|
4925
|
-
- uses: actions/checkout@v4
|
|
4926
|
-
- uses: caliber-ai-org/ai-setup@v1
|
|
4927
|
-
with:
|
|
4928
|
-
mode: sync
|
|
4929
|
-
auto-refresh: true
|
|
4930
|
-
comment: true
|
|
4931
|
-
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
4932
|
-
env:
|
|
4933
|
-
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
4934
|
-
\`\`\`
|
|
4935
|
-
e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
|
|
4936
|
-
to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
|
|
4937
|
-
workflow file. From now on, every PR gets a Caliber sync check and configs
|
|
4938
|
-
refresh nightly."
|
|
4939
|
-
f. Offer to commit the workflow file and the updated configs.
|
|
4940
|
-
|
|
4941
|
-
### If \`${bin}\` is not found
|
|
4942
|
-
|
|
4943
|
-
Install it first:
|
|
4944
|
-
\`\`\`bash
|
|
4945
|
-
npm install -g @rely-ai/caliber
|
|
4946
|
-
\`\`\`
|
|
4947
|
-
Or use npx (no install needed):
|
|
4948
|
-
\`\`\`bash
|
|
4949
|
-
npx @rely-ai/caliber hooks --install
|
|
4950
|
-
\`\`\`
|
|
4951
|
-
|
|
4952
|
-
## When to trigger
|
|
4953
|
-
|
|
4954
|
-
- When a developer first opens this project and Caliber hooks are not installed
|
|
4955
|
-
- When the user asks about keeping agent configs up to date
|
|
4956
|
-
- When the user asks "how do I set up Caliber" or "what is Caliber"
|
|
4804
|
+
if (fingerprint.codeAnalysis) {
|
|
4805
|
+
const ca = fingerprint.codeAnalysis;
|
|
4806
|
+
const basePrompt = parts.join("\n");
|
|
4807
|
+
const maxPromptTokens = getMaxPromptTokens();
|
|
4808
|
+
const baseTokens = estimateTokens(basePrompt);
|
|
4809
|
+
const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
|
|
4810
|
+
const codeLines = [];
|
|
4811
|
+
let codeChars = 0;
|
|
4812
|
+
const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
|
|
4813
|
+
codeLines.push(introLine);
|
|
4814
|
+
let runningCodeLen = introLine.length;
|
|
4815
|
+
const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
4816
|
+
let includedFiles = 0;
|
|
4817
|
+
for (const f of sortedFiles) {
|
|
4818
|
+
const entry = `[${f.path}]
|
|
4819
|
+
${f.content}
|
|
4820
|
+
`;
|
|
4821
|
+
const projectedLen = runningCodeLen + 1 + entry.length;
|
|
4822
|
+
if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
|
|
4823
|
+
codeLines.push(entry);
|
|
4824
|
+
codeChars += f.content.length;
|
|
4825
|
+
runningCodeLen = projectedLen;
|
|
4826
|
+
includedFiles++;
|
|
4827
|
+
}
|
|
4828
|
+
const includedTokens = Math.ceil(codeChars / 4);
|
|
4829
|
+
let header;
|
|
4830
|
+
if (includedFiles < ca.files.length) {
|
|
4831
|
+
const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4832
|
+
header = `
|
|
4833
|
+
--- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4834
|
+
} else if (ca.truncated) {
|
|
4835
|
+
const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4836
|
+
header = `
|
|
4837
|
+
--- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4838
|
+
} else {
|
|
4839
|
+
header = `
|
|
4840
|
+
--- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
|
|
4841
|
+
}
|
|
4842
|
+
parts.push(header);
|
|
4843
|
+
parts.push(codeLines.join("\n"));
|
|
4844
|
+
}
|
|
4845
|
+
if (fingerprint.sources?.length) {
|
|
4846
|
+
parts.push(formatSourcesForPrompt(fingerprint.sources));
|
|
4847
|
+
}
|
|
4848
|
+
return parts.join("\n");
|
|
4849
|
+
}
|
|
4957
4850
|
|
|
4958
|
-
|
|
4851
|
+
// src/writers/index.ts
|
|
4852
|
+
import fs19 from "fs";
|
|
4959
4853
|
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4854
|
+
// src/writers/claude/index.ts
|
|
4855
|
+
init_pre_commit_block();
|
|
4856
|
+
import fs12 from "fs";
|
|
4857
|
+
import path11 from "path";
|
|
4858
|
+
function writeClaudeConfig(config) {
|
|
4859
|
+
const written = [];
|
|
4860
|
+
fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
|
|
4861
|
+
written.push("CLAUDE.md");
|
|
4862
|
+
if (config.skills?.length) {
|
|
4863
|
+
for (const skill of config.skills) {
|
|
4864
|
+
const skillDir = path11.join(".claude", "skills", skill.name);
|
|
4865
|
+
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
4866
|
+
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
4867
|
+
const frontmatter = [
|
|
4868
|
+
"---",
|
|
4869
|
+
`name: ${skill.name}`,
|
|
4870
|
+
`description: ${skill.description}`,
|
|
4871
|
+
"---",
|
|
4872
|
+
""
|
|
4873
|
+
].join("\n");
|
|
4874
|
+
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4875
|
+
written.push(skillPath);
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4879
|
+
let existingServers = {};
|
|
4880
|
+
try {
|
|
4881
|
+
if (fs12.existsSync(".mcp.json")) {
|
|
4882
|
+
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
4883
|
+
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4884
|
+
}
|
|
4885
|
+
} catch {
|
|
4886
|
+
}
|
|
4887
|
+
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4888
|
+
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4889
|
+
written.push(".mcp.json");
|
|
4890
|
+
}
|
|
4891
|
+
return written;
|
|
4963
4892
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4893
|
+
|
|
4894
|
+
// src/writers/cursor/index.ts
|
|
4895
|
+
init_pre_commit_block();
|
|
4896
|
+
import fs13 from "fs";
|
|
4897
|
+
import path12 from "path";
|
|
4898
|
+
function writeCursorConfig(config) {
|
|
4899
|
+
const written = [];
|
|
4900
|
+
if (config.cursorrules) {
|
|
4901
|
+
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
4902
|
+
written.push(".cursorrules");
|
|
4969
4903
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4904
|
+
const preCommitRule = getCursorPreCommitRule();
|
|
4905
|
+
const learningsRule = getCursorLearningsRule();
|
|
4906
|
+
const syncRule = getCursorSyncRule();
|
|
4907
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
|
|
4908
|
+
const rulesDir = path12.join(".cursor", "rules");
|
|
4909
|
+
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
4910
|
+
for (const rule of allRules) {
|
|
4911
|
+
const rulePath = path12.join(rulesDir, rule.filename);
|
|
4912
|
+
fs13.writeFileSync(rulePath, rule.content);
|
|
4913
|
+
written.push(rulePath);
|
|
4914
|
+
}
|
|
4915
|
+
if (config.skills?.length) {
|
|
4916
|
+
for (const skill of config.skills) {
|
|
4917
|
+
const skillDir = path12.join(".cursor", "skills", skill.name);
|
|
4918
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
4919
|
+
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4920
|
+
const frontmatter = [
|
|
4921
|
+
"---",
|
|
4922
|
+
`name: ${skill.name}`,
|
|
4923
|
+
`description: ${skill.description}`,
|
|
4924
|
+
"---",
|
|
4925
|
+
""
|
|
4926
|
+
].join("\n");
|
|
4927
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4928
|
+
written.push(skillPath);
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4932
|
+
const cursorDir = ".cursor";
|
|
4933
|
+
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
4934
|
+
const mcpPath = path12.join(cursorDir, "mcp.json");
|
|
4935
|
+
let existingServers = {};
|
|
4936
|
+
try {
|
|
4937
|
+
if (fs13.existsSync(mcpPath)) {
|
|
4938
|
+
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
4939
|
+
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4940
|
+
}
|
|
4941
|
+
} catch {
|
|
4942
|
+
}
|
|
4943
|
+
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4944
|
+
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4945
|
+
written.push(mcpPath);
|
|
4946
|
+
}
|
|
4947
|
+
return written;
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
// src/writers/codex/index.ts
|
|
4951
|
+
init_pre_commit_block();
|
|
4952
|
+
import fs14 from "fs";
|
|
4953
|
+
import path13 from "path";
|
|
4954
|
+
function writeCodexConfig(config) {
|
|
4978
4955
|
const written = [];
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4956
|
+
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4957
|
+
written.push("AGENTS.md");
|
|
4958
|
+
if (config.skills?.length) {
|
|
4959
|
+
for (const skill of config.skills) {
|
|
4960
|
+
const skillDir = path13.join(".agents", "skills", skill.name);
|
|
4961
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
4962
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
4963
|
+
const frontmatter = [
|
|
4964
|
+
"---",
|
|
4965
|
+
`name: ${skill.name}`,
|
|
4966
|
+
`description: ${skill.description}`,
|
|
4967
|
+
"---",
|
|
4968
|
+
""
|
|
4969
|
+
].join("\n");
|
|
4970
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4986
4971
|
written.push(skillPath);
|
|
4987
4972
|
}
|
|
4988
4973
|
}
|
|
4989
4974
|
return written;
|
|
4990
4975
|
}
|
|
4991
4976
|
|
|
4977
|
+
// src/writers/github-copilot/index.ts
|
|
4978
|
+
init_pre_commit_block();
|
|
4979
|
+
import fs15 from "fs";
|
|
4980
|
+
import path14 from "path";
|
|
4981
|
+
function writeGithubCopilotConfig(config) {
|
|
4982
|
+
const written = [];
|
|
4983
|
+
if (config.instructions) {
|
|
4984
|
+
fs15.mkdirSync(".github", { recursive: true });
|
|
4985
|
+
fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
|
|
4986
|
+
written.push(".github/copilot-instructions.md");
|
|
4987
|
+
}
|
|
4988
|
+
if (config.instructionFiles?.length) {
|
|
4989
|
+
const instructionsDir = path14.join(".github", "instructions");
|
|
4990
|
+
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
4991
|
+
for (const file of config.instructionFiles) {
|
|
4992
|
+
fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
|
|
4993
|
+
written.push(`.github/instructions/${file.filename}`);
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
return written;
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4999
|
+
// src/writers/backup.ts
|
|
5000
|
+
import fs16 from "fs";
|
|
5001
|
+
import path15 from "path";
|
|
5002
|
+
function createBackup(files) {
|
|
5003
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5004
|
+
const backupDir = path15.join(BACKUPS_DIR, timestamp);
|
|
5005
|
+
for (const file of files) {
|
|
5006
|
+
if (!fs16.existsSync(file)) continue;
|
|
5007
|
+
const dest = path15.join(backupDir, file);
|
|
5008
|
+
const destDir = path15.dirname(dest);
|
|
5009
|
+
if (!fs16.existsSync(destDir)) {
|
|
5010
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5011
|
+
}
|
|
5012
|
+
fs16.copyFileSync(file, dest);
|
|
5013
|
+
}
|
|
5014
|
+
return backupDir;
|
|
5015
|
+
}
|
|
5016
|
+
function restoreBackup(backupDir, file) {
|
|
5017
|
+
const backupFile = path15.join(backupDir, file);
|
|
5018
|
+
if (!fs16.existsSync(backupFile)) return false;
|
|
5019
|
+
const destDir = path15.dirname(file);
|
|
5020
|
+
if (!fs16.existsSync(destDir)) {
|
|
5021
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5022
|
+
}
|
|
5023
|
+
fs16.copyFileSync(backupFile, file);
|
|
5024
|
+
return true;
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
// src/writers/index.ts
|
|
5028
|
+
init_builtin_skills();
|
|
5029
|
+
|
|
4992
5030
|
// src/writers/manifest.ts
|
|
4993
5031
|
import fs18 from "fs";
|
|
4994
5032
|
import crypto3 from "crypto";
|
|
@@ -5173,6 +5211,7 @@ function cleanupStaging() {
|
|
|
5173
5211
|
}
|
|
5174
5212
|
|
|
5175
5213
|
// src/commands/setup-files.ts
|
|
5214
|
+
init_builtin_skills();
|
|
5176
5215
|
import fs21 from "fs";
|
|
5177
5216
|
function collectSetupFiles(setup, targetAgent) {
|
|
5178
5217
|
const files = [];
|
|
@@ -6336,6 +6375,7 @@ import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
|
6336
6375
|
import { execSync as execSync12 } from "child_process";
|
|
6337
6376
|
import { join as join7 } from "path";
|
|
6338
6377
|
init_resolve_caliber();
|
|
6378
|
+
init_pre_commit_block();
|
|
6339
6379
|
function hasPreCommitHook(dir) {
|
|
6340
6380
|
try {
|
|
6341
6381
|
const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
@@ -9115,9 +9155,9 @@ async function initCommand(options) {
|
|
|
9115
9155
|
console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
9116
9156
|
console.log(title.bold(" How it works:\n"));
|
|
9117
9157
|
console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
|
|
9118
|
-
console.log(chalk14.dim(" 2.
|
|
9119
|
-
console.log(chalk14.dim(" 3.
|
|
9120
|
-
console.log(chalk14.dim(" 4. Finalize
|
|
9158
|
+
console.log(chalk14.dim(" 2. Setup Detect stack, install sync hooks & skills"));
|
|
9159
|
+
console.log(chalk14.dim(" 3. Generate Audit existing config or generate from scratch"));
|
|
9160
|
+
console.log(chalk14.dim(" 4. Finalize Review changes and score your setup\n"));
|
|
9121
9161
|
} else {
|
|
9122
9162
|
console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
|
|
9123
9163
|
}
|
|
@@ -9167,8 +9207,26 @@ async function initCommand(options) {
|
|
|
9167
9207
|
console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
|
|
9168
9208
|
`));
|
|
9169
9209
|
trackInitAgentSelected(targetAgent, agentAutoDetected);
|
|
9210
|
+
console.log(title.bold(" Step 2/4 \u2014 Setup\n"));
|
|
9211
|
+
const hookResult = installPreCommitHook();
|
|
9212
|
+
if (hookResult.installed) {
|
|
9213
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 configs sync on every commit`);
|
|
9214
|
+
} else if (hookResult.alreadyInstalled) {
|
|
9215
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 already installed`);
|
|
9216
|
+
}
|
|
9217
|
+
const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
9218
|
+
for (const agent of targetAgent) {
|
|
9219
|
+
if (agent === "claude" && !fs33.existsSync(".claude")) fs33.mkdirSync(".claude", { recursive: true });
|
|
9220
|
+
if (agent === "cursor" && !fs33.existsSync(".cursor")) fs33.mkdirSync(".cursor", { recursive: true });
|
|
9221
|
+
if (agent === "codex" && !fs33.existsSync(".agents")) fs33.mkdirSync(".agents", { recursive: true });
|
|
9222
|
+
}
|
|
9223
|
+
const skillsWritten = ensureBuiltinSkills2();
|
|
9224
|
+
if (skillsWritten.length > 0) {
|
|
9225
|
+
console.log(` ${chalk14.green("\u2713")} Agent skills \u2014 ${skillsWritten.length} skills installed (setup-caliber, find-skills, save-learning)`);
|
|
9226
|
+
}
|
|
9227
|
+
console.log("");
|
|
9170
9228
|
let baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
9171
|
-
console.log(chalk14.dim("
|
|
9229
|
+
console.log(chalk14.dim(" Current config score:"));
|
|
9172
9230
|
displayScoreSummary(baselineScore);
|
|
9173
9231
|
if (options.verbose) {
|
|
9174
9232
|
for (const c of baselineScore.checks) {
|
|
@@ -9195,29 +9253,86 @@ async function initCommand(options) {
|
|
|
9195
9253
|
]);
|
|
9196
9254
|
const passingCount = baselineScore.checks.filter((c) => c.passed).length;
|
|
9197
9255
|
const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
|
|
9256
|
+
let skipGeneration = false;
|
|
9198
9257
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
9199
9258
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
9200
|
-
console.log(chalk14.bold.green(" Your config is already optimal
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9259
|
+
console.log(chalk14.bold.green("\n Your config is already optimal.\n"));
|
|
9260
|
+
skipGeneration = !options.force;
|
|
9261
|
+
} else if (hasExistingConfig && !options.force && !options.autoApprove) {
|
|
9262
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9263
|
+
const auditAnswer = await promptInput(" Want Caliber to audit and improve your existing config? (Y/n) ");
|
|
9264
|
+
skipGeneration = auditAnswer.toLowerCase() === "n";
|
|
9265
|
+
} else if (!hasExistingConfig && !options.force && !options.autoApprove) {
|
|
9266
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9267
|
+
const generateAnswer = await promptInput(" Want Caliber to generate agent configs for your project? (Y/n) ");
|
|
9268
|
+
skipGeneration = generateAnswer.toLowerCase() === "n";
|
|
9269
|
+
} else {
|
|
9270
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9271
|
+
}
|
|
9272
|
+
if (skipGeneration) {
|
|
9273
|
+
const {
|
|
9274
|
+
appendPreCommitBlock: appendPreCommitBlock2,
|
|
9275
|
+
appendLearningsBlock: appendLearningsBlock2,
|
|
9276
|
+
appendSyncBlock: appendSyncBlock2,
|
|
9277
|
+
getCursorPreCommitRule: getCursorPreCommitRule2,
|
|
9278
|
+
getCursorLearningsRule: getCursorLearningsRule2,
|
|
9279
|
+
getCursorSyncRule: getCursorSyncRule2
|
|
9280
|
+
} = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
|
|
9281
|
+
const claudeMdPath = "CLAUDE.md";
|
|
9282
|
+
let claudeContent = "";
|
|
9283
|
+
try {
|
|
9284
|
+
claudeContent = fs33.readFileSync(claudeMdPath, "utf-8");
|
|
9285
|
+
} catch {
|
|
9286
|
+
}
|
|
9287
|
+
if (!claudeContent) {
|
|
9288
|
+
claudeContent = `# ${path26.basename(process.cwd())}
|
|
9289
|
+
`;
|
|
9290
|
+
}
|
|
9291
|
+
const updatedClaude = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(claudeContent)));
|
|
9292
|
+
if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
|
|
9293
|
+
fs33.writeFileSync(claudeMdPath, updatedClaude);
|
|
9294
|
+
console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
|
|
9295
|
+
}
|
|
9296
|
+
if (targetAgent.includes("cursor")) {
|
|
9297
|
+
const rulesDir = path26.join(".cursor", "rules");
|
|
9298
|
+
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9299
|
+
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2()]) {
|
|
9300
|
+
fs33.writeFileSync(path26.join(rulesDir, rule.filename), rule.content);
|
|
9214
9301
|
}
|
|
9302
|
+
console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
|
|
9215
9303
|
}
|
|
9216
|
-
|
|
9217
|
-
|
|
9304
|
+
if (targetAgent.includes("github-copilot")) {
|
|
9305
|
+
const copilotPath = path26.join(".github", "copilot-instructions.md");
|
|
9306
|
+
let copilotContent = "";
|
|
9307
|
+
try {
|
|
9308
|
+
copilotContent = fs33.readFileSync(copilotPath, "utf-8");
|
|
9309
|
+
} catch {
|
|
9310
|
+
}
|
|
9311
|
+
if (!copilotContent) {
|
|
9312
|
+
fs33.mkdirSync(".github", { recursive: true });
|
|
9313
|
+
copilotContent = `# ${path26.basename(process.cwd())}
|
|
9314
|
+
`;
|
|
9315
|
+
}
|
|
9316
|
+
const updatedCopilot = appendSyncBlock2(appendLearningsBlock2(appendPreCommitBlock2(copilotContent)));
|
|
9317
|
+
if (updatedCopilot !== copilotContent) {
|
|
9318
|
+
fs33.writeFileSync(copilotPath, updatedCopilot);
|
|
9319
|
+
console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
const sha2 = getCurrentHeadSha();
|
|
9323
|
+
writeState({
|
|
9324
|
+
lastRefreshSha: sha2 ?? "",
|
|
9325
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9326
|
+
targetAgent
|
|
9327
|
+
});
|
|
9328
|
+
console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
|
|
9329
|
+
console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
|
|
9330
|
+
console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
|
|
9218
9331
|
return;
|
|
9219
9332
|
}
|
|
9220
|
-
|
|
9333
|
+
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
9334
|
+
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
9335
|
+
console.log(title.bold("\n Step 3/4 \u2014 Generate\n"));
|
|
9221
9336
|
const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
|
|
9222
9337
|
console.log(chalk14.dim(genModelInfo + "\n"));
|
|
9223
9338
|
if (report) report.markStep("Generation");
|
|
@@ -9416,7 +9531,7 @@ async function initCommand(options) {
|
|
|
9416
9531
|
report.addJson("Generation: Parsed Config", generatedSetup);
|
|
9417
9532
|
}
|
|
9418
9533
|
log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
|
|
9419
|
-
console.log(title.bold(" Step
|
|
9534
|
+
console.log(title.bold(" Step 4/4 \u2014 Finalize\n"));
|
|
9420
9535
|
const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9421
9536
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
9422
9537
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
@@ -9479,7 +9594,6 @@ async function initCommand(options) {
|
|
|
9479
9594
|
console.log(chalk14.dim("Declined. No files were modified."));
|
|
9480
9595
|
return;
|
|
9481
9596
|
}
|
|
9482
|
-
console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
|
|
9483
9597
|
if (options.dryRun) {
|
|
9484
9598
|
console.log(chalk14.yellow("\n[Dry run] Would write the following files:"));
|
|
9485
9599
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
@@ -9531,12 +9645,6 @@ ${agentRefs.join(" ")}
|
|
|
9531
9645
|
throw new Error("__exit__");
|
|
9532
9646
|
}
|
|
9533
9647
|
if (fingerprint) ensurePermissions(fingerprint);
|
|
9534
|
-
const hookResult = installPreCommitHook();
|
|
9535
|
-
if (hookResult.installed) {
|
|
9536
|
-
console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed \u2014 configs sync on every commit`);
|
|
9537
|
-
} else if (hookResult.alreadyInstalled) {
|
|
9538
|
-
console.log(chalk14.dim(" Pre-commit hook already installed"));
|
|
9539
|
-
}
|
|
9540
9648
|
const sha = getCurrentHeadSha();
|
|
9541
9649
|
writeState({
|
|
9542
9650
|
lastRefreshSha: sha ?? "",
|
|
@@ -10048,6 +10156,7 @@ function collectDiff(lastSha) {
|
|
|
10048
10156
|
}
|
|
10049
10157
|
|
|
10050
10158
|
// src/writers/refresh.ts
|
|
10159
|
+
init_pre_commit_block();
|
|
10051
10160
|
import fs36 from "fs";
|
|
10052
10161
|
import path28 from "path";
|
|
10053
10162
|
function writeRefreshDocs(docs) {
|
|
@@ -10371,6 +10480,7 @@ function migrateInlineLearnings() {
|
|
|
10371
10480
|
// src/commands/refresh.ts
|
|
10372
10481
|
init_config();
|
|
10373
10482
|
init_resolve_caliber();
|
|
10483
|
+
init_builtin_skills();
|
|
10374
10484
|
function detectSyncedAgents(writtenFiles) {
|
|
10375
10485
|
const agents = [];
|
|
10376
10486
|
const joined = writtenFiles.join(" ");
|