@rely-ai/caliber 1.30.7 → 1.31.0-dev.1774711564
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 +1 -1
- package/dist/bin.js +950 -728
- package/package.json +4 -4
package/dist/bin.js
CHANGED
|
@@ -230,21 +230,303 @@ var init_types = __esm({
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
+
// src/lib/builtin-skills.ts
|
|
234
|
+
var builtin_skills_exports = {};
|
|
235
|
+
__export(builtin_skills_exports, {
|
|
236
|
+
BUILTIN_SKILLS: () => BUILTIN_SKILLS,
|
|
237
|
+
FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
|
|
238
|
+
SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
|
|
239
|
+
SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
|
|
240
|
+
buildSkillContent: () => buildSkillContent,
|
|
241
|
+
ensureBuiltinSkills: () => ensureBuiltinSkills
|
|
242
|
+
});
|
|
243
|
+
import fs17 from "fs";
|
|
244
|
+
import path16 from "path";
|
|
245
|
+
function buildSkillContent(skill) {
|
|
246
|
+
const frontmatter = `---
|
|
247
|
+
name: ${skill.name}
|
|
248
|
+
description: ${skill.description}
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
`;
|
|
252
|
+
return frontmatter + skill.content;
|
|
253
|
+
}
|
|
254
|
+
function getFindSkillsContent() {
|
|
255
|
+
const bin = resolveCaliber();
|
|
256
|
+
return `# Find Skills
|
|
257
|
+
|
|
258
|
+
Search the public skill registry for community-contributed skills
|
|
259
|
+
relevant to the user's current task and install them into this project.
|
|
260
|
+
|
|
261
|
+
## Instructions
|
|
262
|
+
|
|
263
|
+
1. Identify the key technologies, frameworks, or task types from the
|
|
264
|
+
user's request that might have community skills available
|
|
265
|
+
2. Ask the user: "Would you like me to search for community skills
|
|
266
|
+
for [identified technologies]?"
|
|
267
|
+
3. If the user agrees, run:
|
|
268
|
+
\`\`\`bash
|
|
269
|
+
${bin} skills --query "<relevant terms>"
|
|
270
|
+
\`\`\`
|
|
271
|
+
This outputs the top 5 matching skills with scores and descriptions.
|
|
272
|
+
4. Present the results to the user and ask which ones to install
|
|
273
|
+
5. Install the selected skills:
|
|
274
|
+
\`\`\`bash
|
|
275
|
+
${bin} skills --install <slug1>,<slug2>
|
|
276
|
+
\`\`\`
|
|
277
|
+
6. Read the installed SKILL.md files to load them into your current
|
|
278
|
+
context so you can use them immediately in this session
|
|
279
|
+
7. Summarize what was installed and continue with the user's task
|
|
280
|
+
|
|
281
|
+
## Examples
|
|
282
|
+
|
|
283
|
+
User: "let's build a web app using React"
|
|
284
|
+
-> "I notice you want to work with React. Would you like me to search
|
|
285
|
+
for community skills that could help with React development?"
|
|
286
|
+
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
287
|
+
-> Show the user the results, ask which to install
|
|
288
|
+
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
289
|
+
-> Read the installed files and continue
|
|
290
|
+
|
|
291
|
+
User: "help me set up Docker for this project"
|
|
292
|
+
-> "Would you like me to search for Docker-related skills?"
|
|
293
|
+
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
294
|
+
|
|
295
|
+
User: "I need to write tests for this Python ML pipeline"
|
|
296
|
+
-> "Would you like me to find skills for Python ML testing?"
|
|
297
|
+
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
298
|
+
|
|
299
|
+
## When NOT to trigger
|
|
300
|
+
|
|
301
|
+
- The user is working within an already well-configured area
|
|
302
|
+
- You already suggested skills for this technology in this session
|
|
303
|
+
- The user is in the middle of urgent debugging or time-sensitive work
|
|
304
|
+
- The technology is too generic (e.g. just "code" or "programming")
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
function getSaveLearningContent() {
|
|
308
|
+
const bin = resolveCaliber();
|
|
309
|
+
return `# Save Learning
|
|
310
|
+
|
|
311
|
+
Save a user's instruction or preference as a persistent learning that
|
|
312
|
+
will be applied in all future sessions on this project.
|
|
313
|
+
|
|
314
|
+
## Instructions
|
|
315
|
+
|
|
316
|
+
1. Detect when the user gives an instruction to remember, such as:
|
|
317
|
+
- "remember this", "save this", "always do X", "never do Y"
|
|
318
|
+
- "from now on", "going forward", "in this project we..."
|
|
319
|
+
- Any stated convention, preference, or rule
|
|
320
|
+
2. Refine the instruction into a clean, actionable learning bullet with
|
|
321
|
+
an appropriate type prefix:
|
|
322
|
+
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
323
|
+
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
324
|
+
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
325
|
+
- \`**[preference]**\` \u2014 personal/team preferences
|
|
326
|
+
- \`**[context]**\` \u2014 project-specific context
|
|
327
|
+
3. Show the refined learning to the user and ask for confirmation
|
|
328
|
+
4. If confirmed, run:
|
|
329
|
+
\`\`\`bash
|
|
330
|
+
${bin} learn add "<refined learning>"
|
|
331
|
+
\`\`\`
|
|
332
|
+
For personal preferences (not project-level), add \`--personal\`:
|
|
333
|
+
\`\`\`bash
|
|
334
|
+
${bin} learn add --personal "<refined learning>"
|
|
335
|
+
\`\`\`
|
|
336
|
+
5. Stage the learnings file for the next commit:
|
|
337
|
+
\`\`\`bash
|
|
338
|
+
git add CALIBER_LEARNINGS.md
|
|
339
|
+
\`\`\`
|
|
340
|
+
|
|
341
|
+
## Examples
|
|
342
|
+
|
|
343
|
+
User: "when developing features, push to next branch not master, remember it"
|
|
344
|
+
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
345
|
+
-> "I'll save this as a project learning:
|
|
346
|
+
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
347
|
+
Save for future sessions?"
|
|
348
|
+
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
349
|
+
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
350
|
+
|
|
351
|
+
User: "always use bun instead of npm"
|
|
352
|
+
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
353
|
+
-> Confirm and save
|
|
354
|
+
|
|
355
|
+
User: "never use any in TypeScript, use unknown instead"
|
|
356
|
+
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
357
|
+
-> Confirm and save
|
|
358
|
+
|
|
359
|
+
## When NOT to trigger
|
|
360
|
+
|
|
361
|
+
- The user is giving a one-time instruction for the current task only
|
|
362
|
+
- The instruction is too vague to be actionable
|
|
363
|
+
- The user explicitly says "just for now" or "only this time"
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
function getSetupCaliberContent() {
|
|
367
|
+
const bin = resolveCaliber();
|
|
368
|
+
return `# Setup Caliber
|
|
369
|
+
|
|
370
|
+
Set up Caliber for automatic AI agent context sync on this project.
|
|
371
|
+
Caliber keeps your CLAUDE.md, Cursor rules, Copilot instructions, and Codex configs
|
|
372
|
+
in sync with your codebase \u2014 automatically on every commit.
|
|
373
|
+
|
|
374
|
+
## Instructions
|
|
375
|
+
|
|
376
|
+
1. Check if Caliber is already set up locally:
|
|
377
|
+
\`\`\`bash
|
|
378
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
2. If **hook-active**, tell the user: "Caliber is already set up on this repo.
|
|
382
|
+
Your agent configs update automatically on every commit." Then stop.
|
|
383
|
+
|
|
384
|
+
3. If **no-hook**, ask the user: "Are you setting up Caliber for yourself (solo)
|
|
385
|
+
or for your team?"
|
|
386
|
+
|
|
387
|
+
### Solo Setup
|
|
388
|
+
|
|
389
|
+
a. Tell the user: "I'll set up Caliber to sync your agent configs automatically
|
|
390
|
+
on every commit."
|
|
391
|
+
b. Install the pre-commit hook:
|
|
392
|
+
\`\`\`bash
|
|
393
|
+
${bin} hooks --install
|
|
394
|
+
\`\`\`
|
|
395
|
+
c. Run a refresh to make sure configs are current:
|
|
396
|
+
\`\`\`bash
|
|
397
|
+
${bin} refresh
|
|
398
|
+
\`\`\`
|
|
399
|
+
d. Tell the user what happened and that configs will auto-sync on every commit.
|
|
400
|
+
|
|
401
|
+
### Team Setup
|
|
402
|
+
|
|
403
|
+
a. Tell the user: "I'll set up Caliber for your team \u2014 local hooks for you,
|
|
404
|
+
plus a GitHub Action for automatic sync across the team."
|
|
405
|
+
b. Install the local pre-commit hook:
|
|
406
|
+
\`\`\`bash
|
|
407
|
+
${bin} hooks --install
|
|
408
|
+
\`\`\`
|
|
409
|
+
c. Run a refresh:
|
|
410
|
+
\`\`\`bash
|
|
411
|
+
${bin} refresh
|
|
412
|
+
\`\`\`
|
|
413
|
+
d. Create the GitHub Action workflow for nightly sync. Write this file to
|
|
414
|
+
\`.github/workflows/caliber-sync.yml\`:
|
|
415
|
+
\`\`\`yaml
|
|
416
|
+
name: Caliber Sync
|
|
417
|
+
on:
|
|
418
|
+
schedule:
|
|
419
|
+
- cron: '0 3 * * 1-5'
|
|
420
|
+
pull_request:
|
|
421
|
+
types: [opened, synchronize]
|
|
422
|
+
workflow_dispatch:
|
|
423
|
+
jobs:
|
|
424
|
+
sync:
|
|
425
|
+
runs-on: ubuntu-latest
|
|
426
|
+
steps:
|
|
427
|
+
- uses: actions/checkout@v4
|
|
428
|
+
- uses: caliber-ai-org/ai-setup@v1
|
|
429
|
+
with:
|
|
430
|
+
mode: sync
|
|
431
|
+
auto-refresh: true
|
|
432
|
+
comment: true
|
|
433
|
+
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
434
|
+
env:
|
|
435
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
436
|
+
\`\`\`
|
|
437
|
+
e. Tell the user: "Created the GitHub Action workflow. Add your ANTHROPIC_API_KEY
|
|
438
|
+
to your repo secrets (Settings \u2192 Secrets \u2192 Actions), then commit and push this
|
|
439
|
+
workflow file. From now on, every PR gets a Caliber sync check and configs
|
|
440
|
+
refresh nightly."
|
|
441
|
+
f. Offer to commit the workflow file and the updated configs.
|
|
442
|
+
|
|
443
|
+
### If \`${bin}\` is not found
|
|
444
|
+
|
|
445
|
+
Install it first:
|
|
446
|
+
\`\`\`bash
|
|
447
|
+
npm install -g @rely-ai/caliber
|
|
448
|
+
\`\`\`
|
|
449
|
+
Or use npx (no install needed):
|
|
450
|
+
\`\`\`bash
|
|
451
|
+
npx @rely-ai/caliber hooks --install
|
|
452
|
+
\`\`\`
|
|
453
|
+
|
|
454
|
+
## When to trigger
|
|
455
|
+
|
|
456
|
+
- When a developer first opens this project and Caliber hooks are not installed
|
|
457
|
+
- When the user asks about keeping agent configs up to date
|
|
458
|
+
- When the user asks "how do I set up Caliber" or "what is Caliber"
|
|
459
|
+
|
|
460
|
+
## When NOT to trigger
|
|
461
|
+
|
|
462
|
+
- Hooks are already installed and working
|
|
463
|
+
- The user is in the middle of time-sensitive work
|
|
464
|
+
`;
|
|
465
|
+
}
|
|
466
|
+
function ensureBuiltinSkills() {
|
|
467
|
+
const written = [];
|
|
468
|
+
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
469
|
+
if (!fs17.existsSync(platformDir)) continue;
|
|
470
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
471
|
+
const skillPath = path16.join(skillsDir, skill.name, "SKILL.md");
|
|
472
|
+
if (fs17.existsSync(skillPath)) continue;
|
|
473
|
+
fs17.mkdirSync(path16.dirname(skillPath), { recursive: true });
|
|
474
|
+
fs17.writeFileSync(skillPath, buildSkillContent(skill));
|
|
475
|
+
written.push(skillPath);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return written;
|
|
479
|
+
}
|
|
480
|
+
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS;
|
|
481
|
+
var init_builtin_skills = __esm({
|
|
482
|
+
"src/lib/builtin-skills.ts"() {
|
|
483
|
+
"use strict";
|
|
484
|
+
init_resolve_caliber();
|
|
485
|
+
FIND_SKILLS_SKILL = {
|
|
486
|
+
name: "find-skills",
|
|
487
|
+
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.",
|
|
488
|
+
get content() {
|
|
489
|
+
return getFindSkillsContent();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
SAVE_LEARNING_SKILL = {
|
|
493
|
+
name: "save-learning",
|
|
494
|
+
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.",
|
|
495
|
+
get content() {
|
|
496
|
+
return getSaveLearningContent();
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
SETUP_CALIBER_SKILL = {
|
|
500
|
+
name: "setup-caliber",
|
|
501
|
+
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.",
|
|
502
|
+
get content() {
|
|
503
|
+
return getSetupCaliberContent();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
507
|
+
PLATFORM_CONFIGS = [
|
|
508
|
+
{ platformDir: ".claude", skillsDir: path16.join(".claude", "skills") },
|
|
509
|
+
{ platformDir: ".cursor", skillsDir: path16.join(".cursor", "skills") },
|
|
510
|
+
{ platformDir: ".agents", skillsDir: path16.join(".agents", "skills") }
|
|
511
|
+
];
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
233
515
|
// src/utils/editor.ts
|
|
234
|
-
import { execSync as
|
|
235
|
-
import
|
|
236
|
-
import
|
|
516
|
+
import { execSync as execSync14, spawn as spawn3 } from "child_process";
|
|
517
|
+
import fs27 from "fs";
|
|
518
|
+
import path23 from "path";
|
|
237
519
|
import os6 from "os";
|
|
238
520
|
function getEmptyFilePath(proposedPath) {
|
|
239
|
-
|
|
240
|
-
const tempPath =
|
|
241
|
-
|
|
521
|
+
fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
522
|
+
const tempPath = path23.join(DIFF_TEMP_DIR, path23.basename(proposedPath));
|
|
523
|
+
fs27.writeFileSync(tempPath, "");
|
|
242
524
|
return tempPath;
|
|
243
525
|
}
|
|
244
526
|
function commandExists(cmd) {
|
|
245
527
|
try {
|
|
246
528
|
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
247
|
-
|
|
529
|
+
execSync14(check, { stdio: "ignore" });
|
|
248
530
|
return true;
|
|
249
531
|
} catch {
|
|
250
532
|
return false;
|
|
@@ -278,7 +560,7 @@ var init_editor = __esm({
|
|
|
278
560
|
"src/utils/editor.ts"() {
|
|
279
561
|
"use strict";
|
|
280
562
|
IS_WINDOWS3 = process.platform === "win32";
|
|
281
|
-
DIFF_TEMP_DIR =
|
|
563
|
+
DIFF_TEMP_DIR = path23.join(os6.tmpdir(), "caliber-diff");
|
|
282
564
|
}
|
|
283
565
|
});
|
|
284
566
|
|
|
@@ -290,7 +572,7 @@ __export(review_exports, {
|
|
|
290
572
|
promptWantsReview: () => promptWantsReview
|
|
291
573
|
});
|
|
292
574
|
import chalk10 from "chalk";
|
|
293
|
-
import
|
|
575
|
+
import fs28 from "fs";
|
|
294
576
|
import select4 from "@inquirer/select";
|
|
295
577
|
import { createTwoFilesPatch } from "diff";
|
|
296
578
|
async function promptWantsReview() {
|
|
@@ -327,8 +609,8 @@ async function openReview(method, stagedFiles) {
|
|
|
327
609
|
return;
|
|
328
610
|
}
|
|
329
611
|
const fileInfos = stagedFiles.map((file) => {
|
|
330
|
-
const proposed =
|
|
331
|
-
const current = file.currentPath ?
|
|
612
|
+
const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
|
|
613
|
+
const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
|
|
332
614
|
const patch = createTwoFilesPatch(
|
|
333
615
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
334
616
|
file.relativePath,
|
|
@@ -503,13 +785,13 @@ __export(lock_exports, {
|
|
|
503
785
|
isCaliberRunning: () => isCaliberRunning,
|
|
504
786
|
releaseLock: () => releaseLock
|
|
505
787
|
});
|
|
506
|
-
import
|
|
507
|
-
import
|
|
788
|
+
import fs38 from "fs";
|
|
789
|
+
import path30 from "path";
|
|
508
790
|
import os8 from "os";
|
|
509
791
|
function isCaliberRunning() {
|
|
510
792
|
try {
|
|
511
|
-
if (!
|
|
512
|
-
const raw =
|
|
793
|
+
if (!fs38.existsSync(LOCK_FILE)) return false;
|
|
794
|
+
const raw = fs38.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
513
795
|
const { pid, ts } = JSON.parse(raw);
|
|
514
796
|
if (Date.now() - ts > STALE_MS) return false;
|
|
515
797
|
try {
|
|
@@ -524,13 +806,13 @@ function isCaliberRunning() {
|
|
|
524
806
|
}
|
|
525
807
|
function acquireLock() {
|
|
526
808
|
try {
|
|
527
|
-
|
|
809
|
+
fs38.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
528
810
|
} catch {
|
|
529
811
|
}
|
|
530
812
|
}
|
|
531
813
|
function releaseLock() {
|
|
532
814
|
try {
|
|
533
|
-
if (
|
|
815
|
+
if (fs38.existsSync(LOCK_FILE)) fs38.unlinkSync(LOCK_FILE);
|
|
534
816
|
} catch {
|
|
535
817
|
}
|
|
536
818
|
}
|
|
@@ -538,7 +820,7 @@ var LOCK_FILE, STALE_MS;
|
|
|
538
820
|
var init_lock = __esm({
|
|
539
821
|
"src/lib/lock.ts"() {
|
|
540
822
|
"use strict";
|
|
541
|
-
LOCK_FILE =
|
|
823
|
+
LOCK_FILE = path30.join(os8.tmpdir(), ".caliber.lock");
|
|
542
824
|
STALE_MS = 10 * 60 * 1e3;
|
|
543
825
|
}
|
|
544
826
|
});
|
|
@@ -550,9 +832,9 @@ import path38 from "path";
|
|
|
550
832
|
import { fileURLToPath } from "url";
|
|
551
833
|
|
|
552
834
|
// src/commands/init.ts
|
|
553
|
-
import
|
|
835
|
+
import path26 from "path";
|
|
554
836
|
import chalk14 from "chalk";
|
|
555
|
-
import
|
|
837
|
+
import fs33 from "fs";
|
|
556
838
|
|
|
557
839
|
// src/fingerprint/index.ts
|
|
558
840
|
import fs8 from "fs";
|
|
@@ -2450,7 +2732,7 @@ PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more
|
|
|
2450
2732
|
Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
|
|
2451
2733
|
README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
|
|
2452
2734
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
2453
|
-
- CLAUDE.md / AGENTS.md: MUST be under
|
|
2735
|
+
- CLAUDE.md / AGENTS.md: MUST be under 400 lines for maximum score. Aim for 200-350 lines. Be thorough \u2014 commands, architecture overview, key conventions, data flow, and important patterns. Use bullet points and tables, not prose.
|
|
2454
2736
|
|
|
2455
2737
|
Pack project references densely in architecture sections \u2014 use inline paths, not prose paragraphs:
|
|
2456
2738
|
GOOD: **Entry**: \`src/bin.ts\` \u2192 \`src/cli.ts\` \xB7 **LLM** (\`src/llm/\`): \`anthropic.ts\` \xB7 \`vertex.ts\` \xB7 \`openai-compat.ts\`
|
|
@@ -2578,7 +2860,7 @@ Structure:
|
|
|
2578
2860
|
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"
|
|
2579
2861
|
|
|
2580
2862
|
Rules:
|
|
2581
|
-
- Max
|
|
2863
|
+
- Max 400 lines. Focus on actionable instructions, not documentation prose.
|
|
2582
2864
|
- 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.
|
|
2583
2865
|
- Be specific and actionable. GOOD: "Run \`pnpm test -- --filter=api\` to verify". BAD: "Validate the data before proceeding."
|
|
2584
2866
|
- 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\`."
|
|
@@ -2632,7 +2914,7 @@ Rules:
|
|
|
2632
2914
|
- Update the "fileDescriptions" to reflect any changes you make.
|
|
2633
2915
|
|
|
2634
2916
|
Quality constraints \u2014 your changes are scored, so do not break these:
|
|
2635
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
2917
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 400 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.
|
|
2636
2918
|
- Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
|
|
2637
2919
|
- Do NOT add directory tree listings in code blocks.
|
|
2638
2920
|
- Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
|
|
@@ -2656,7 +2938,7 @@ CONSERVATIVE UPDATE means:
|
|
|
2656
2938
|
- NEVER replace specific paths/commands with generic prose
|
|
2657
2939
|
|
|
2658
2940
|
Quality constraints (the output is scored deterministically):
|
|
2659
|
-
- CLAUDE.md / AGENTS.md: MUST stay under
|
|
2941
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 400 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
2660
2942
|
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
2661
2943
|
- Every file path, command, and identifier must be in backticks
|
|
2662
2944
|
- ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
|
|
@@ -2664,6 +2946,7 @@ Quality constraints (the output is scored deterministically):
|
|
|
2664
2946
|
|
|
2665
2947
|
Managed content:
|
|
2666
2948
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
2949
|
+
- Keep context sync blocks (<!-- caliber:managed:sync --> ... <!-- /caliber:managed:sync -->) intact
|
|
2667
2950
|
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2668
2951
|
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
2669
2952
|
|
|
@@ -2679,9 +2962,12 @@ Return a JSON object with this exact shape:
|
|
|
2679
2962
|
"copilotInstructionFiles": [{"filename": "name.instructions.md", "content": "..."}] or null
|
|
2680
2963
|
},
|
|
2681
2964
|
"changesSummary": "<1-2 sentence summary of what was updated and why>",
|
|
2965
|
+
"fileChanges": [{"file": "CLAUDE.md", "description": "added new API routes, updated build commands"}],
|
|
2682
2966
|
"docsUpdated": ["CLAUDE.md", "README.md"]
|
|
2683
2967
|
}
|
|
2684
2968
|
|
|
2969
|
+
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").
|
|
2970
|
+
|
|
2685
2971
|
Respond with ONLY the JSON object, no markdown fences or extra text.`;
|
|
2686
2972
|
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.
|
|
2687
2973
|
|
|
@@ -3173,63 +3459,205 @@ function getCursorConfigDir() {
|
|
|
3173
3459
|
return path8.join(home, ".config", "Cursor");
|
|
3174
3460
|
}
|
|
3175
3461
|
|
|
3176
|
-
// src/
|
|
3462
|
+
// src/lib/hooks.ts
|
|
3463
|
+
init_resolve_caliber();
|
|
3177
3464
|
import fs10 from "fs";
|
|
3178
3465
|
import path9 from "path";
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3466
|
+
import { execSync as execSync8 } from "child_process";
|
|
3467
|
+
var SETTINGS_PATH = path9.join(".claude", "settings.json");
|
|
3468
|
+
var REFRESH_TAIL = "refresh --quiet";
|
|
3469
|
+
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
3470
|
+
function getHookCommand() {
|
|
3471
|
+
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3472
|
+
}
|
|
3473
|
+
function readSettings() {
|
|
3474
|
+
if (!fs10.existsSync(SETTINGS_PATH)) return {};
|
|
3185
3475
|
try {
|
|
3186
|
-
return readFileSync(
|
|
3476
|
+
return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3187
3477
|
} catch {
|
|
3188
|
-
return
|
|
3478
|
+
return {};
|
|
3189
3479
|
}
|
|
3190
3480
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
]
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3481
|
+
function writeSettings(settings) {
|
|
3482
|
+
const dir = path9.dirname(SETTINGS_PATH);
|
|
3483
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3484
|
+
fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
3485
|
+
}
|
|
3486
|
+
function findHookIndex(sessionEnd) {
|
|
3487
|
+
return sessionEnd.findIndex(
|
|
3488
|
+
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3491
|
+
function isHookInstalled() {
|
|
3492
|
+
const settings = readSettings();
|
|
3493
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3494
|
+
if (!Array.isArray(sessionEnd)) return false;
|
|
3495
|
+
return findHookIndex(sessionEnd) !== -1;
|
|
3496
|
+
}
|
|
3497
|
+
function installHook() {
|
|
3498
|
+
const settings = readSettings();
|
|
3499
|
+
if (!settings.hooks) settings.hooks = {};
|
|
3500
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
3501
|
+
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
3502
|
+
return { installed: false, alreadyInstalled: true };
|
|
3503
|
+
}
|
|
3504
|
+
settings.hooks.SessionEnd.push({
|
|
3505
|
+
matcher: "",
|
|
3506
|
+
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
3507
|
+
});
|
|
3508
|
+
writeSettings(settings);
|
|
3509
|
+
return { installed: true, alreadyInstalled: false };
|
|
3510
|
+
}
|
|
3511
|
+
function removeHook() {
|
|
3512
|
+
const settings = readSettings();
|
|
3513
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3514
|
+
if (!Array.isArray(sessionEnd)) {
|
|
3515
|
+
return { removed: false, notFound: true };
|
|
3516
|
+
}
|
|
3517
|
+
const idx = findHookIndex(sessionEnd);
|
|
3518
|
+
if (idx === -1) {
|
|
3519
|
+
return { removed: false, notFound: true };
|
|
3520
|
+
}
|
|
3521
|
+
sessionEnd.splice(idx, 1);
|
|
3522
|
+
if (sessionEnd.length === 0) {
|
|
3523
|
+
delete settings.hooks.SessionEnd;
|
|
3524
|
+
}
|
|
3525
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
3526
|
+
delete settings.hooks;
|
|
3527
|
+
}
|
|
3528
|
+
writeSettings(settings);
|
|
3529
|
+
return { removed: true, notFound: false };
|
|
3530
|
+
}
|
|
3531
|
+
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
3532
|
+
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
3533
|
+
function getPrecommitBlock() {
|
|
3534
|
+
const bin = resolveCaliber();
|
|
3535
|
+
const npx = isNpxResolution();
|
|
3536
|
+
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
3537
|
+
const invoke = npx ? bin : `"${bin}"`;
|
|
3538
|
+
return `${PRECOMMIT_START}
|
|
3539
|
+
if ${guard}; then
|
|
3540
|
+
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
3541
|
+
${invoke} refresh 2>/dev/null || true
|
|
3542
|
+
${invoke} learn finalize 2>/dev/null || true
|
|
3543
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
3544
|
+
fi
|
|
3545
|
+
${PRECOMMIT_END}`;
|
|
3546
|
+
}
|
|
3547
|
+
function getGitHooksDir() {
|
|
3548
|
+
try {
|
|
3549
|
+
const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3550
|
+
return path9.join(gitDir, "hooks");
|
|
3551
|
+
} catch {
|
|
3552
|
+
return null;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
function getPreCommitPath() {
|
|
3556
|
+
const hooksDir = getGitHooksDir();
|
|
3557
|
+
return hooksDir ? path9.join(hooksDir, "pre-commit") : null;
|
|
3558
|
+
}
|
|
3559
|
+
function isPreCommitHookInstalled() {
|
|
3560
|
+
const hookPath = getPreCommitPath();
|
|
3561
|
+
if (!hookPath || !fs10.existsSync(hookPath)) return false;
|
|
3562
|
+
const content = fs10.readFileSync(hookPath, "utf-8");
|
|
3563
|
+
return content.includes(PRECOMMIT_START);
|
|
3564
|
+
}
|
|
3565
|
+
function installPreCommitHook() {
|
|
3566
|
+
if (isPreCommitHookInstalled()) {
|
|
3567
|
+
return { installed: false, alreadyInstalled: true };
|
|
3568
|
+
}
|
|
3569
|
+
const hookPath = getPreCommitPath();
|
|
3570
|
+
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3571
|
+
const hooksDir = path9.dirname(hookPath);
|
|
3572
|
+
if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
|
|
3573
|
+
let content = "";
|
|
3574
|
+
if (fs10.existsSync(hookPath)) {
|
|
3575
|
+
content = fs10.readFileSync(hookPath, "utf-8");
|
|
3576
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
3577
|
+
content += "\n" + getPrecommitBlock() + "\n";
|
|
3578
|
+
} else {
|
|
3579
|
+
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3580
|
+
}
|
|
3581
|
+
fs10.writeFileSync(hookPath, content);
|
|
3582
|
+
fs10.chmodSync(hookPath, 493);
|
|
3583
|
+
return { installed: true, alreadyInstalled: false };
|
|
3584
|
+
}
|
|
3585
|
+
function removePreCommitHook() {
|
|
3586
|
+
const hookPath = getPreCommitPath();
|
|
3587
|
+
if (!hookPath || !fs10.existsSync(hookPath)) {
|
|
3588
|
+
return { removed: false, notFound: true };
|
|
3589
|
+
}
|
|
3590
|
+
let content = fs10.readFileSync(hookPath, "utf-8");
|
|
3591
|
+
if (!content.includes(PRECOMMIT_START)) {
|
|
3592
|
+
return { removed: false, notFound: true };
|
|
3593
|
+
}
|
|
3594
|
+
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3595
|
+
content = content.replace(regex, "\n");
|
|
3596
|
+
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3597
|
+
fs10.unlinkSync(hookPath);
|
|
3598
|
+
} else {
|
|
3599
|
+
fs10.writeFileSync(hookPath, content);
|
|
3600
|
+
}
|
|
3601
|
+
return { removed: true, notFound: false };
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
// src/fingerprint/sources.ts
|
|
3605
|
+
import fs11 from "fs";
|
|
3606
|
+
import path10 from "path";
|
|
3607
|
+
|
|
3608
|
+
// src/scoring/utils.ts
|
|
3609
|
+
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
3610
|
+
import { execFileSync } from "child_process";
|
|
3611
|
+
import { join, relative } from "path";
|
|
3612
|
+
function readFileOrNull(filePath) {
|
|
3613
|
+
try {
|
|
3614
|
+
return readFileSync(filePath, "utf-8");
|
|
3615
|
+
} catch {
|
|
3616
|
+
return null;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
3620
|
+
"node_modules",
|
|
3621
|
+
".git",
|
|
3622
|
+
"dist",
|
|
3623
|
+
"build",
|
|
3624
|
+
"out",
|
|
3625
|
+
".next",
|
|
3626
|
+
".nuxt",
|
|
3627
|
+
"__pycache__",
|
|
3628
|
+
".venv",
|
|
3629
|
+
"venv",
|
|
3630
|
+
"env",
|
|
3631
|
+
".env",
|
|
3632
|
+
"target",
|
|
3633
|
+
"vendor",
|
|
3634
|
+
".cache",
|
|
3635
|
+
".parcel-cache",
|
|
3636
|
+
"coverage",
|
|
3637
|
+
".nyc_output",
|
|
3638
|
+
".turbo",
|
|
3639
|
+
".caliber",
|
|
3640
|
+
".claude",
|
|
3641
|
+
".cursor",
|
|
3642
|
+
".agents",
|
|
3643
|
+
".codex"
|
|
3644
|
+
]);
|
|
3645
|
+
var IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
3646
|
+
".DS_Store",
|
|
3647
|
+
"Thumbs.db",
|
|
3648
|
+
".gitignore",
|
|
3649
|
+
".editorconfig",
|
|
3650
|
+
".prettierrc",
|
|
3651
|
+
".prettierignore",
|
|
3652
|
+
".eslintignore",
|
|
3653
|
+
"package-lock.json",
|
|
3654
|
+
"yarn.lock",
|
|
3655
|
+
"pnpm-lock.yaml",
|
|
3656
|
+
"bun.lockb"
|
|
3657
|
+
]);
|
|
3658
|
+
function isGitRepo2(dir) {
|
|
3659
|
+
try {
|
|
3660
|
+
execFileSync("git", ["rev-parse", "--git-dir"], {
|
|
3233
3661
|
cwd: dir,
|
|
3234
3662
|
encoding: "utf-8",
|
|
3235
3663
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -3494,7 +3922,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
|
|
|
3494
3922
|
var README_CONTENT_LIMIT = 1e3;
|
|
3495
3923
|
var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
|
|
3496
3924
|
function loadSourcesConfig(dir) {
|
|
3497
|
-
const configPath =
|
|
3925
|
+
const configPath = path10.join(dir, ".caliber", "sources.json");
|
|
3498
3926
|
const content = readFileOrNull(configPath);
|
|
3499
3927
|
if (!content) return [];
|
|
3500
3928
|
try {
|
|
@@ -3512,29 +3940,29 @@ function loadSourcesConfig(dir) {
|
|
|
3512
3940
|
}
|
|
3513
3941
|
}
|
|
3514
3942
|
function writeSourcesConfig(dir, sources2) {
|
|
3515
|
-
const configDir =
|
|
3516
|
-
if (!
|
|
3517
|
-
|
|
3943
|
+
const configDir = path10.join(dir, ".caliber");
|
|
3944
|
+
if (!fs11.existsSync(configDir)) {
|
|
3945
|
+
fs11.mkdirSync(configDir, { recursive: true });
|
|
3518
3946
|
}
|
|
3519
|
-
const configPath =
|
|
3520
|
-
|
|
3947
|
+
const configPath = path10.join(configDir, "sources.json");
|
|
3948
|
+
fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
3521
3949
|
}
|
|
3522
3950
|
function detectSourceType(absPath) {
|
|
3523
3951
|
try {
|
|
3524
|
-
return
|
|
3952
|
+
return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
3525
3953
|
} catch {
|
|
3526
3954
|
return "file";
|
|
3527
3955
|
}
|
|
3528
3956
|
}
|
|
3529
3957
|
function isInsideDir(childPath, parentDir) {
|
|
3530
|
-
const relative2 =
|
|
3531
|
-
return !relative2.startsWith("..") && !
|
|
3958
|
+
const relative2 = path10.relative(parentDir, childPath);
|
|
3959
|
+
return !relative2.startsWith("..") && !path10.isAbsolute(relative2);
|
|
3532
3960
|
}
|
|
3533
3961
|
function resolveAllSources(dir, cliSources, workspaces) {
|
|
3534
3962
|
const seen = /* @__PURE__ */ new Map();
|
|
3535
|
-
const projectRoot =
|
|
3963
|
+
const projectRoot = path10.resolve(dir);
|
|
3536
3964
|
for (const src of cliSources) {
|
|
3537
|
-
const absPath =
|
|
3965
|
+
const absPath = path10.resolve(dir, src);
|
|
3538
3966
|
if (seen.has(absPath)) continue;
|
|
3539
3967
|
const type = detectSourceType(absPath);
|
|
3540
3968
|
seen.set(absPath, {
|
|
@@ -3547,12 +3975,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3547
3975
|
for (const cfg of configSources) {
|
|
3548
3976
|
if (cfg.type === "url") continue;
|
|
3549
3977
|
if (!cfg.path) continue;
|
|
3550
|
-
const absPath =
|
|
3978
|
+
const absPath = path10.resolve(dir, cfg.path);
|
|
3551
3979
|
if (seen.has(absPath)) continue;
|
|
3552
3980
|
seen.set(absPath, { absPath, config: cfg, origin: "config" });
|
|
3553
3981
|
}
|
|
3554
3982
|
for (const ws of workspaces) {
|
|
3555
|
-
const absPath =
|
|
3983
|
+
const absPath = path10.resolve(dir, ws);
|
|
3556
3984
|
if (seen.has(absPath)) continue;
|
|
3557
3985
|
if (!isInsideDir(absPath, projectRoot)) continue;
|
|
3558
3986
|
seen.set(absPath, {
|
|
@@ -3565,7 +3993,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3565
3993
|
for (const [absPath, resolved] of seen) {
|
|
3566
3994
|
let stat;
|
|
3567
3995
|
try {
|
|
3568
|
-
stat =
|
|
3996
|
+
stat = fs11.statSync(absPath);
|
|
3569
3997
|
} catch {
|
|
3570
3998
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
3571
3999
|
continue;
|
|
@@ -3594,13 +4022,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
3594
4022
|
if (config.type === "file") {
|
|
3595
4023
|
return collectFileSummary(resolved, projectDir);
|
|
3596
4024
|
}
|
|
3597
|
-
const summaryPath =
|
|
4025
|
+
const summaryPath = path10.join(absPath, ".caliber", "summary.json");
|
|
3598
4026
|
const summaryContent = readFileOrNull(summaryPath);
|
|
3599
4027
|
if (summaryContent) {
|
|
3600
4028
|
try {
|
|
3601
4029
|
const published = JSON.parse(summaryContent);
|
|
3602
4030
|
return {
|
|
3603
|
-
name: published.name ||
|
|
4031
|
+
name: published.name || path10.basename(absPath),
|
|
3604
4032
|
type: "repo",
|
|
3605
4033
|
role: config.role || published.role || "related-repo",
|
|
3606
4034
|
description: config.description || published.description || "",
|
|
@@ -3620,18 +4048,18 @@ function collectRepoSummary(resolved, projectDir) {
|
|
|
3620
4048
|
let topLevelDirs;
|
|
3621
4049
|
let keyFiles;
|
|
3622
4050
|
try {
|
|
3623
|
-
const entries =
|
|
4051
|
+
const entries = fs11.readdirSync(absPath, { withFileTypes: true });
|
|
3624
4052
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
3625
4053
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
3626
4054
|
} catch {
|
|
3627
4055
|
}
|
|
3628
|
-
const claudeMdContent = readFileOrNull(
|
|
4056
|
+
const claudeMdContent = readFileOrNull(path10.join(absPath, "CLAUDE.md"));
|
|
3629
4057
|
const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
|
|
3630
|
-
const readmeContent = readFileOrNull(
|
|
4058
|
+
const readmeContent = readFileOrNull(path10.join(absPath, "README.md"));
|
|
3631
4059
|
const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
|
|
3632
4060
|
const gitRemoteUrl = getGitRemoteUrl(absPath);
|
|
3633
4061
|
return {
|
|
3634
|
-
name: packageName ||
|
|
4062
|
+
name: packageName || path10.basename(absPath),
|
|
3635
4063
|
type: "repo",
|
|
3636
4064
|
role: config.role || "related-repo",
|
|
3637
4065
|
description: config.description || "",
|
|
@@ -3648,7 +4076,7 @@ function collectFileSummary(resolved, projectDir) {
|
|
|
3648
4076
|
const { config, origin, absPath } = resolved;
|
|
3649
4077
|
const content = readFileOrNull(absPath);
|
|
3650
4078
|
return {
|
|
3651
|
-
name:
|
|
4079
|
+
name: path10.basename(absPath),
|
|
3652
4080
|
type: "file",
|
|
3653
4081
|
role: config.role || "reference-doc",
|
|
3654
4082
|
description: config.description || content?.slice(0, 100).split("\n")[0] || "",
|
|
@@ -4287,39 +4715,52 @@ ${f.content}
|
|
|
4287
4715
|
}
|
|
4288
4716
|
|
|
4289
4717
|
// src/writers/index.ts
|
|
4290
|
-
import
|
|
4718
|
+
import fs19 from "fs";
|
|
4291
4719
|
|
|
4292
4720
|
// src/writers/claude/index.ts
|
|
4293
|
-
import
|
|
4294
|
-
import
|
|
4721
|
+
import fs12 from "fs";
|
|
4722
|
+
import path11 from "path";
|
|
4295
4723
|
|
|
4296
4724
|
// src/writers/pre-commit-block.ts
|
|
4297
4725
|
init_resolve_caliber();
|
|
4298
4726
|
var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
4299
4727
|
var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
4300
|
-
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
|
|
4728
|
+
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
4301
4729
|
function getPreCommitBlock() {
|
|
4302
4730
|
const bin = resolveCaliber();
|
|
4303
4731
|
return `${BLOCK_START}
|
|
4304
4732
|
## Before Committing
|
|
4305
4733
|
|
|
4306
|
-
|
|
4307
|
-
After it completes, stage any modified doc files before committing:
|
|
4734
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
4308
4735
|
|
|
4736
|
+
First, check if the pre-commit hook is already installed:
|
|
4309
4737
|
\`\`\`bash
|
|
4310
|
-
|
|
4738
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
4311
4739
|
\`\`\`
|
|
4740
|
+
|
|
4741
|
+
- 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."
|
|
4742
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
4743
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
4744
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
4745
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
4746
|
+
|
|
4747
|
+
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"
|
|
4312
4748
|
${BLOCK_END}`;
|
|
4313
4749
|
}
|
|
4314
4750
|
var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
4315
4751
|
function getCursorRuleContent() {
|
|
4316
4752
|
const bin = resolveCaliber();
|
|
4317
4753
|
return `---
|
|
4318
|
-
description: Run
|
|
4754
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
4319
4755
|
alwaysApply: true
|
|
4320
4756
|
---
|
|
4321
|
-
Before
|
|
4322
|
-
|
|
4757
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
4758
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
4759
|
+
|
|
4760
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
4761
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
4762
|
+
|
|
4763
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4323
4764
|
`;
|
|
4324
4765
|
}
|
|
4325
4766
|
function hasPreCommitBlock(content) {
|
|
@@ -4360,17 +4801,52 @@ function appendLearningsBlock(content) {
|
|
|
4360
4801
|
function getCursorLearningsRule() {
|
|
4361
4802
|
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
4362
4803
|
}
|
|
4804
|
+
var SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
4805
|
+
var SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
4806
|
+
function getSyncBlock() {
|
|
4807
|
+
const bin = resolveCaliber();
|
|
4808
|
+
return `${SYNC_BLOCK_START}
|
|
4809
|
+
## Context Sync
|
|
4810
|
+
|
|
4811
|
+
This project uses [Caliber](https://github.com/rely-ai/caliber) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
4812
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
4813
|
+
If the pre-commit hook is not set up, run \`${bin} hooks --install\` to enable automatic sync.
|
|
4814
|
+
${SYNC_BLOCK_END}`;
|
|
4815
|
+
}
|
|
4816
|
+
function hasSyncBlock(content) {
|
|
4817
|
+
return content.includes(SYNC_BLOCK_START);
|
|
4818
|
+
}
|
|
4819
|
+
function appendSyncBlock(content) {
|
|
4820
|
+
if (hasSyncBlock(content)) return content;
|
|
4821
|
+
const trimmed = content.trimEnd();
|
|
4822
|
+
return trimmed + "\n\n" + getSyncBlock() + "\n";
|
|
4823
|
+
}
|
|
4824
|
+
var CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
4825
|
+
function getCursorSyncContent() {
|
|
4826
|
+
const bin = resolveCaliber();
|
|
4827
|
+
return `---
|
|
4828
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
4829
|
+
alwaysApply: true
|
|
4830
|
+
---
|
|
4831
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
4832
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
4833
|
+
If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Install with: npm install -g @rely-ai/caliber"
|
|
4834
|
+
`;
|
|
4835
|
+
}
|
|
4836
|
+
function getCursorSyncRule() {
|
|
4837
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
4838
|
+
}
|
|
4363
4839
|
|
|
4364
4840
|
// src/writers/claude/index.ts
|
|
4365
4841
|
function writeClaudeConfig(config) {
|
|
4366
4842
|
const written = [];
|
|
4367
|
-
|
|
4843
|
+
fs12.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd))));
|
|
4368
4844
|
written.push("CLAUDE.md");
|
|
4369
4845
|
if (config.skills?.length) {
|
|
4370
4846
|
for (const skill of config.skills) {
|
|
4371
|
-
const skillDir =
|
|
4372
|
-
if (!
|
|
4373
|
-
const skillPath =
|
|
4847
|
+
const skillDir = path11.join(".claude", "skills", skill.name);
|
|
4848
|
+
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
4849
|
+
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
4374
4850
|
const frontmatter = [
|
|
4375
4851
|
"---",
|
|
4376
4852
|
`name: ${skill.name}`,
|
|
@@ -4378,50 +4854,51 @@ function writeClaudeConfig(config) {
|
|
|
4378
4854
|
"---",
|
|
4379
4855
|
""
|
|
4380
4856
|
].join("\n");
|
|
4381
|
-
|
|
4857
|
+
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4382
4858
|
written.push(skillPath);
|
|
4383
4859
|
}
|
|
4384
4860
|
}
|
|
4385
4861
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4386
4862
|
let existingServers = {};
|
|
4387
4863
|
try {
|
|
4388
|
-
if (
|
|
4389
|
-
const existing = JSON.parse(
|
|
4864
|
+
if (fs12.existsSync(".mcp.json")) {
|
|
4865
|
+
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
4390
4866
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4391
4867
|
}
|
|
4392
4868
|
} catch {
|
|
4393
4869
|
}
|
|
4394
4870
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4395
|
-
|
|
4871
|
+
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4396
4872
|
written.push(".mcp.json");
|
|
4397
4873
|
}
|
|
4398
4874
|
return written;
|
|
4399
4875
|
}
|
|
4400
4876
|
|
|
4401
4877
|
// src/writers/cursor/index.ts
|
|
4402
|
-
import
|
|
4403
|
-
import
|
|
4878
|
+
import fs13 from "fs";
|
|
4879
|
+
import path12 from "path";
|
|
4404
4880
|
function writeCursorConfig(config) {
|
|
4405
4881
|
const written = [];
|
|
4406
4882
|
if (config.cursorrules) {
|
|
4407
|
-
|
|
4883
|
+
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
4408
4884
|
written.push(".cursorrules");
|
|
4409
4885
|
}
|
|
4410
4886
|
const preCommitRule = getCursorPreCommitRule();
|
|
4411
4887
|
const learningsRule = getCursorLearningsRule();
|
|
4412
|
-
const
|
|
4413
|
-
const
|
|
4414
|
-
|
|
4888
|
+
const syncRule = getCursorSyncRule();
|
|
4889
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule, syncRule];
|
|
4890
|
+
const rulesDir = path12.join(".cursor", "rules");
|
|
4891
|
+
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
4415
4892
|
for (const rule of allRules) {
|
|
4416
|
-
const rulePath =
|
|
4417
|
-
|
|
4893
|
+
const rulePath = path12.join(rulesDir, rule.filename);
|
|
4894
|
+
fs13.writeFileSync(rulePath, rule.content);
|
|
4418
4895
|
written.push(rulePath);
|
|
4419
4896
|
}
|
|
4420
4897
|
if (config.skills?.length) {
|
|
4421
4898
|
for (const skill of config.skills) {
|
|
4422
|
-
const skillDir =
|
|
4423
|
-
if (!
|
|
4424
|
-
const skillPath =
|
|
4899
|
+
const skillDir = path12.join(".cursor", "skills", skill.name);
|
|
4900
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
4901
|
+
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4425
4902
|
const frontmatter = [
|
|
4426
4903
|
"---",
|
|
4427
4904
|
`name: ${skill.name}`,
|
|
@@ -4429,41 +4906,41 @@ function writeCursorConfig(config) {
|
|
|
4429
4906
|
"---",
|
|
4430
4907
|
""
|
|
4431
4908
|
].join("\n");
|
|
4432
|
-
|
|
4909
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4433
4910
|
written.push(skillPath);
|
|
4434
4911
|
}
|
|
4435
4912
|
}
|
|
4436
4913
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4437
4914
|
const cursorDir = ".cursor";
|
|
4438
|
-
if (!
|
|
4439
|
-
const mcpPath =
|
|
4915
|
+
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
4916
|
+
const mcpPath = path12.join(cursorDir, "mcp.json");
|
|
4440
4917
|
let existingServers = {};
|
|
4441
4918
|
try {
|
|
4442
|
-
if (
|
|
4443
|
-
const existing = JSON.parse(
|
|
4919
|
+
if (fs13.existsSync(mcpPath)) {
|
|
4920
|
+
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
4444
4921
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4445
4922
|
}
|
|
4446
4923
|
} catch {
|
|
4447
4924
|
}
|
|
4448
4925
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4449
|
-
|
|
4926
|
+
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
4450
4927
|
written.push(mcpPath);
|
|
4451
4928
|
}
|
|
4452
4929
|
return written;
|
|
4453
4930
|
}
|
|
4454
4931
|
|
|
4455
4932
|
// src/writers/codex/index.ts
|
|
4456
|
-
import
|
|
4457
|
-
import
|
|
4933
|
+
import fs14 from "fs";
|
|
4934
|
+
import path13 from "path";
|
|
4458
4935
|
function writeCodexConfig(config) {
|
|
4459
4936
|
const written = [];
|
|
4460
|
-
|
|
4937
|
+
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4461
4938
|
written.push("AGENTS.md");
|
|
4462
4939
|
if (config.skills?.length) {
|
|
4463
4940
|
for (const skill of config.skills) {
|
|
4464
|
-
const skillDir =
|
|
4465
|
-
if (!
|
|
4466
|
-
const skillPath =
|
|
4941
|
+
const skillDir = path13.join(".agents", "skills", skill.name);
|
|
4942
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
4943
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
4467
4944
|
const frontmatter = [
|
|
4468
4945
|
"---",
|
|
4469
4946
|
`name: ${skill.name}`,
|
|
@@ -4471,7 +4948,7 @@ function writeCodexConfig(config) {
|
|
|
4471
4948
|
"---",
|
|
4472
4949
|
""
|
|
4473
4950
|
].join("\n");
|
|
4474
|
-
|
|
4951
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4475
4952
|
written.push(skillPath);
|
|
4476
4953
|
}
|
|
4477
4954
|
}
|
|
@@ -4479,242 +4956,85 @@ function writeCodexConfig(config) {
|
|
|
4479
4956
|
}
|
|
4480
4957
|
|
|
4481
4958
|
// src/writers/github-copilot/index.ts
|
|
4482
|
-
import fs14 from "fs";
|
|
4483
|
-
import path13 from "path";
|
|
4484
|
-
function writeGithubCopilotConfig(config) {
|
|
4485
|
-
const written = [];
|
|
4486
|
-
if (config.instructions) {
|
|
4487
|
-
fs14.mkdirSync(".github", { recursive: true });
|
|
4488
|
-
fs14.writeFileSync(path13.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
|
|
4489
|
-
written.push(".github/copilot-instructions.md");
|
|
4490
|
-
}
|
|
4491
|
-
if (config.instructionFiles?.length) {
|
|
4492
|
-
const instructionsDir = path13.join(".github", "instructions");
|
|
4493
|
-
fs14.mkdirSync(instructionsDir, { recursive: true });
|
|
4494
|
-
for (const file of config.instructionFiles) {
|
|
4495
|
-
fs14.writeFileSync(path13.join(instructionsDir, file.filename), file.content);
|
|
4496
|
-
written.push(`.github/instructions/${file.filename}`);
|
|
4497
|
-
}
|
|
4498
|
-
}
|
|
4499
|
-
return written;
|
|
4500
|
-
}
|
|
4501
|
-
|
|
4502
|
-
// src/writers/backup.ts
|
|
4503
4959
|
import fs15 from "fs";
|
|
4504
|
-
import path14 from "path";
|
|
4505
|
-
function
|
|
4506
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4507
|
-
const backupDir = path14.join(BACKUPS_DIR, timestamp);
|
|
4508
|
-
for (const file of files) {
|
|
4509
|
-
if (!fs15.existsSync(file)) continue;
|
|
4510
|
-
const dest = path14.join(backupDir, file);
|
|
4511
|
-
const destDir = path14.dirname(dest);
|
|
4512
|
-
if (!fs15.existsSync(destDir)) {
|
|
4513
|
-
fs15.mkdirSync(destDir, { recursive: true });
|
|
4514
|
-
}
|
|
4515
|
-
fs15.copyFileSync(file, dest);
|
|
4516
|
-
}
|
|
4517
|
-
return backupDir;
|
|
4518
|
-
}
|
|
4519
|
-
function restoreBackup(backupDir, file) {
|
|
4520
|
-
const backupFile = path14.join(backupDir, file);
|
|
4521
|
-
if (!fs15.existsSync(backupFile)) return false;
|
|
4522
|
-
const destDir = path14.dirname(file);
|
|
4523
|
-
if (!fs15.existsSync(destDir)) {
|
|
4524
|
-
fs15.mkdirSync(destDir, { recursive: true });
|
|
4525
|
-
}
|
|
4526
|
-
fs15.copyFileSync(backupFile, file);
|
|
4527
|
-
return true;
|
|
4528
|
-
}
|
|
4529
|
-
|
|
4530
|
-
// src/lib/builtin-skills.ts
|
|
4531
|
-
init_resolve_caliber();
|
|
4532
|
-
import fs16 from "fs";
|
|
4533
|
-
import path15 from "path";
|
|
4534
|
-
function buildSkillContent(skill) {
|
|
4535
|
-
const frontmatter = `---
|
|
4536
|
-
name: ${skill.name}
|
|
4537
|
-
description: ${skill.description}
|
|
4538
|
-
---
|
|
4539
|
-
|
|
4540
|
-
`;
|
|
4541
|
-
return frontmatter + skill.content;
|
|
4542
|
-
}
|
|
4543
|
-
function getFindSkillsContent() {
|
|
4544
|
-
const bin = resolveCaliber();
|
|
4545
|
-
return `# Find Skills
|
|
4546
|
-
|
|
4547
|
-
Search the public skill registry for community-contributed skills
|
|
4548
|
-
relevant to the user's current task and install them into this project.
|
|
4549
|
-
|
|
4550
|
-
## Instructions
|
|
4551
|
-
|
|
4552
|
-
1. Identify the key technologies, frameworks, or task types from the
|
|
4553
|
-
user's request that might have community skills available
|
|
4554
|
-
2. Ask the user: "Would you like me to search for community skills
|
|
4555
|
-
for [identified technologies]?"
|
|
4556
|
-
3. If the user agrees, run:
|
|
4557
|
-
\`\`\`bash
|
|
4558
|
-
${bin} skills --query "<relevant terms>"
|
|
4559
|
-
\`\`\`
|
|
4560
|
-
This outputs the top 5 matching skills with scores and descriptions.
|
|
4561
|
-
4. Present the results to the user and ask which ones to install
|
|
4562
|
-
5. Install the selected skills:
|
|
4563
|
-
\`\`\`bash
|
|
4564
|
-
${bin} skills --install <slug1>,<slug2>
|
|
4565
|
-
\`\`\`
|
|
4566
|
-
6. Read the installed SKILL.md files to load them into your current
|
|
4567
|
-
context so you can use them immediately in this session
|
|
4568
|
-
7. Summarize what was installed and continue with the user's task
|
|
4569
|
-
|
|
4570
|
-
## Examples
|
|
4571
|
-
|
|
4572
|
-
User: "let's build a web app using React"
|
|
4573
|
-
-> "I notice you want to work with React. Would you like me to search
|
|
4574
|
-
for community skills that could help with React development?"
|
|
4575
|
-
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
4576
|
-
-> Show the user the results, ask which to install
|
|
4577
|
-
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
4578
|
-
-> Read the installed files and continue
|
|
4579
|
-
|
|
4580
|
-
User: "help me set up Docker for this project"
|
|
4581
|
-
-> "Would you like me to search for Docker-related skills?"
|
|
4582
|
-
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
4583
|
-
|
|
4584
|
-
User: "I need to write tests for this Python ML pipeline"
|
|
4585
|
-
-> "Would you like me to find skills for Python ML testing?"
|
|
4586
|
-
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
4587
|
-
|
|
4588
|
-
## When NOT to trigger
|
|
4589
|
-
|
|
4590
|
-
- The user is working within an already well-configured area
|
|
4591
|
-
- You already suggested skills for this technology in this session
|
|
4592
|
-
- The user is in the middle of urgent debugging or time-sensitive work
|
|
4593
|
-
- The technology is too generic (e.g. just "code" or "programming")
|
|
4594
|
-
`;
|
|
4595
|
-
}
|
|
4596
|
-
function getSaveLearningContent() {
|
|
4597
|
-
const bin = resolveCaliber();
|
|
4598
|
-
return `# Save Learning
|
|
4599
|
-
|
|
4600
|
-
Save a user's instruction or preference as a persistent learning that
|
|
4601
|
-
will be applied in all future sessions on this project.
|
|
4602
|
-
|
|
4603
|
-
## Instructions
|
|
4604
|
-
|
|
4605
|
-
1. Detect when the user gives an instruction to remember, such as:
|
|
4606
|
-
- "remember this", "save this", "always do X", "never do Y"
|
|
4607
|
-
- "from now on", "going forward", "in this project we..."
|
|
4608
|
-
- Any stated convention, preference, or rule
|
|
4609
|
-
2. Refine the instruction into a clean, actionable learning bullet with
|
|
4610
|
-
an appropriate type prefix:
|
|
4611
|
-
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
4612
|
-
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
4613
|
-
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
4614
|
-
- \`**[preference]**\` \u2014 personal/team preferences
|
|
4615
|
-
- \`**[context]**\` \u2014 project-specific context
|
|
4616
|
-
3. Show the refined learning to the user and ask for confirmation
|
|
4617
|
-
4. If confirmed, run:
|
|
4618
|
-
\`\`\`bash
|
|
4619
|
-
${bin} learn add "<refined learning>"
|
|
4620
|
-
\`\`\`
|
|
4621
|
-
For personal preferences (not project-level), add \`--personal\`:
|
|
4622
|
-
\`\`\`bash
|
|
4623
|
-
${bin} learn add --personal "<refined learning>"
|
|
4624
|
-
\`\`\`
|
|
4625
|
-
5. Stage the learnings file for the next commit:
|
|
4626
|
-
\`\`\`bash
|
|
4627
|
-
git add CALIBER_LEARNINGS.md
|
|
4628
|
-
\`\`\`
|
|
4629
|
-
|
|
4630
|
-
## Examples
|
|
4631
|
-
|
|
4632
|
-
User: "when developing features, push to next branch not master, remember it"
|
|
4633
|
-
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
4634
|
-
-> "I'll save this as a project learning:
|
|
4635
|
-
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
4636
|
-
Save for future sessions?"
|
|
4637
|
-
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
4638
|
-
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
4639
|
-
|
|
4640
|
-
User: "always use bun instead of npm"
|
|
4641
|
-
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
4642
|
-
-> Confirm and save
|
|
4643
|
-
|
|
4644
|
-
User: "never use any in TypeScript, use unknown instead"
|
|
4645
|
-
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
4646
|
-
-> Confirm and save
|
|
4647
|
-
|
|
4648
|
-
## When NOT to trigger
|
|
4649
|
-
|
|
4650
|
-
- The user is giving a one-time instruction for the current task only
|
|
4651
|
-
- The instruction is too vague to be actionable
|
|
4652
|
-
- The user explicitly says "just for now" or "only this time"
|
|
4653
|
-
`;
|
|
4654
|
-
}
|
|
4655
|
-
var FIND_SKILLS_SKILL = {
|
|
4656
|
-
name: "find-skills",
|
|
4657
|
-
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.",
|
|
4658
|
-
get content() {
|
|
4659
|
-
return getFindSkillsContent();
|
|
4660
|
-
}
|
|
4661
|
-
};
|
|
4662
|
-
var SAVE_LEARNING_SKILL = {
|
|
4663
|
-
name: "save-learning",
|
|
4664
|
-
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.",
|
|
4665
|
-
get content() {
|
|
4666
|
-
return getSaveLearningContent();
|
|
4667
|
-
}
|
|
4668
|
-
};
|
|
4669
|
-
var BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL];
|
|
4670
|
-
var PLATFORM_CONFIGS = [
|
|
4671
|
-
{ platformDir: ".claude", skillsDir: path15.join(".claude", "skills") },
|
|
4672
|
-
{ platformDir: ".cursor", skillsDir: path15.join(".cursor", "skills") },
|
|
4673
|
-
{ platformDir: ".agents", skillsDir: path15.join(".agents", "skills") }
|
|
4674
|
-
];
|
|
4675
|
-
function ensureBuiltinSkills() {
|
|
4960
|
+
import path14 from "path";
|
|
4961
|
+
function writeGithubCopilotConfig(config) {
|
|
4676
4962
|
const written = [];
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4963
|
+
if (config.instructions) {
|
|
4964
|
+
fs15.mkdirSync(".github", { recursive: true });
|
|
4965
|
+
fs15.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.instructions))));
|
|
4966
|
+
written.push(".github/copilot-instructions.md");
|
|
4967
|
+
}
|
|
4968
|
+
if (config.instructionFiles?.length) {
|
|
4969
|
+
const instructionsDir = path14.join(".github", "instructions");
|
|
4970
|
+
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
4971
|
+
for (const file of config.instructionFiles) {
|
|
4972
|
+
fs15.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
|
|
4973
|
+
written.push(`.github/instructions/${file.filename}`);
|
|
4685
4974
|
}
|
|
4686
4975
|
}
|
|
4687
4976
|
return written;
|
|
4688
4977
|
}
|
|
4689
4978
|
|
|
4979
|
+
// src/writers/backup.ts
|
|
4980
|
+
import fs16 from "fs";
|
|
4981
|
+
import path15 from "path";
|
|
4982
|
+
function createBackup(files) {
|
|
4983
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4984
|
+
const backupDir = path15.join(BACKUPS_DIR, timestamp);
|
|
4985
|
+
for (const file of files) {
|
|
4986
|
+
if (!fs16.existsSync(file)) continue;
|
|
4987
|
+
const dest = path15.join(backupDir, file);
|
|
4988
|
+
const destDir = path15.dirname(dest);
|
|
4989
|
+
if (!fs16.existsSync(destDir)) {
|
|
4990
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
4991
|
+
}
|
|
4992
|
+
fs16.copyFileSync(file, dest);
|
|
4993
|
+
}
|
|
4994
|
+
return backupDir;
|
|
4995
|
+
}
|
|
4996
|
+
function restoreBackup(backupDir, file) {
|
|
4997
|
+
const backupFile = path15.join(backupDir, file);
|
|
4998
|
+
if (!fs16.existsSync(backupFile)) return false;
|
|
4999
|
+
const destDir = path15.dirname(file);
|
|
5000
|
+
if (!fs16.existsSync(destDir)) {
|
|
5001
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5002
|
+
}
|
|
5003
|
+
fs16.copyFileSync(backupFile, file);
|
|
5004
|
+
return true;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
// src/writers/index.ts
|
|
5008
|
+
init_builtin_skills();
|
|
5009
|
+
|
|
4690
5010
|
// src/writers/manifest.ts
|
|
4691
|
-
import
|
|
5011
|
+
import fs18 from "fs";
|
|
4692
5012
|
import crypto3 from "crypto";
|
|
4693
5013
|
function readManifest() {
|
|
4694
5014
|
try {
|
|
4695
|
-
if (!
|
|
4696
|
-
return JSON.parse(
|
|
5015
|
+
if (!fs18.existsSync(MANIFEST_FILE)) return null;
|
|
5016
|
+
return JSON.parse(fs18.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
4697
5017
|
} catch {
|
|
4698
5018
|
return null;
|
|
4699
5019
|
}
|
|
4700
5020
|
}
|
|
4701
5021
|
function writeManifest(manifest) {
|
|
4702
|
-
if (!
|
|
4703
|
-
|
|
5022
|
+
if (!fs18.existsSync(CALIBER_DIR)) {
|
|
5023
|
+
fs18.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
4704
5024
|
}
|
|
4705
|
-
|
|
5025
|
+
fs18.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
4706
5026
|
}
|
|
4707
5027
|
function fileChecksum(filePath) {
|
|
4708
|
-
const content =
|
|
5028
|
+
const content = fs18.readFileSync(filePath);
|
|
4709
5029
|
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
4710
5030
|
}
|
|
4711
5031
|
|
|
4712
5032
|
// src/writers/index.ts
|
|
4713
5033
|
function writeSetup(setup) {
|
|
4714
5034
|
const filesToWrite = getFilesToWrite(setup);
|
|
4715
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
5035
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
|
|
4716
5036
|
const existingFiles = [
|
|
4717
|
-
...filesToWrite.filter((f) =>
|
|
5037
|
+
...filesToWrite.filter((f) => fs19.existsSync(f)),
|
|
4718
5038
|
...filesToDelete
|
|
4719
5039
|
];
|
|
4720
5040
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
@@ -4733,7 +5053,7 @@ function writeSetup(setup) {
|
|
|
4733
5053
|
}
|
|
4734
5054
|
const deleted = [];
|
|
4735
5055
|
for (const filePath of filesToDelete) {
|
|
4736
|
-
|
|
5056
|
+
fs19.unlinkSync(filePath);
|
|
4737
5057
|
deleted.push(filePath);
|
|
4738
5058
|
}
|
|
4739
5059
|
written.push(...ensureBuiltinSkills());
|
|
@@ -4764,8 +5084,8 @@ function undoSetup() {
|
|
|
4764
5084
|
const removed = [];
|
|
4765
5085
|
for (const entry of manifest.entries) {
|
|
4766
5086
|
if (entry.action === "created") {
|
|
4767
|
-
if (
|
|
4768
|
-
|
|
5087
|
+
if (fs19.existsSync(entry.path)) {
|
|
5088
|
+
fs19.unlinkSync(entry.path);
|
|
4769
5089
|
removed.push(entry.path);
|
|
4770
5090
|
}
|
|
4771
5091
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -4774,8 +5094,8 @@ function undoSetup() {
|
|
|
4774
5094
|
}
|
|
4775
5095
|
}
|
|
4776
5096
|
}
|
|
4777
|
-
if (
|
|
4778
|
-
|
|
5097
|
+
if (fs19.existsSync(MANIFEST_FILE)) {
|
|
5098
|
+
fs19.unlinkSync(MANIFEST_FILE);
|
|
4779
5099
|
}
|
|
4780
5100
|
return { restored, removed };
|
|
4781
5101
|
}
|
|
@@ -4816,22 +5136,22 @@ function getFilesToWrite(setup) {
|
|
|
4816
5136
|
}
|
|
4817
5137
|
function ensureGitignore() {
|
|
4818
5138
|
const gitignorePath = ".gitignore";
|
|
4819
|
-
if (
|
|
4820
|
-
const content =
|
|
5139
|
+
if (fs19.existsSync(gitignorePath)) {
|
|
5140
|
+
const content = fs19.readFileSync(gitignorePath, "utf-8");
|
|
4821
5141
|
if (!content.includes(".caliber/")) {
|
|
4822
|
-
|
|
5142
|
+
fs19.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
4823
5143
|
}
|
|
4824
5144
|
} else {
|
|
4825
|
-
|
|
5145
|
+
fs19.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
4826
5146
|
}
|
|
4827
5147
|
}
|
|
4828
5148
|
|
|
4829
5149
|
// src/writers/staging.ts
|
|
4830
|
-
import
|
|
4831
|
-
import
|
|
4832
|
-
var STAGED_DIR =
|
|
4833
|
-
var PROPOSED_DIR =
|
|
4834
|
-
var CURRENT_DIR =
|
|
5150
|
+
import fs20 from "fs";
|
|
5151
|
+
import path17 from "path";
|
|
5152
|
+
var STAGED_DIR = path17.join(CALIBER_DIR, "staged");
|
|
5153
|
+
var PROPOSED_DIR = path17.join(STAGED_DIR, "proposed");
|
|
5154
|
+
var CURRENT_DIR = path17.join(STAGED_DIR, "current");
|
|
4835
5155
|
function normalizeContent(content) {
|
|
4836
5156
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4837
5157
|
}
|
|
@@ -4841,20 +5161,20 @@ function stageFiles(files, projectDir) {
|
|
|
4841
5161
|
let modifiedFiles = 0;
|
|
4842
5162
|
const stagedFiles = [];
|
|
4843
5163
|
for (const file of files) {
|
|
4844
|
-
const originalPath =
|
|
4845
|
-
if (
|
|
4846
|
-
const existing =
|
|
5164
|
+
const originalPath = path17.join(projectDir, file.path);
|
|
5165
|
+
if (fs20.existsSync(originalPath)) {
|
|
5166
|
+
const existing = fs20.readFileSync(originalPath, "utf-8");
|
|
4847
5167
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
4848
5168
|
continue;
|
|
4849
5169
|
}
|
|
4850
5170
|
}
|
|
4851
|
-
const proposedPath =
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
if (
|
|
4855
|
-
const currentPath =
|
|
4856
|
-
|
|
4857
|
-
|
|
5171
|
+
const proposedPath = path17.join(PROPOSED_DIR, file.path);
|
|
5172
|
+
fs20.mkdirSync(path17.dirname(proposedPath), { recursive: true });
|
|
5173
|
+
fs20.writeFileSync(proposedPath, file.content);
|
|
5174
|
+
if (fs20.existsSync(originalPath)) {
|
|
5175
|
+
const currentPath = path17.join(CURRENT_DIR, file.path);
|
|
5176
|
+
fs20.mkdirSync(path17.dirname(currentPath), { recursive: true });
|
|
5177
|
+
fs20.copyFileSync(originalPath, currentPath);
|
|
4858
5178
|
modifiedFiles++;
|
|
4859
5179
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
4860
5180
|
} else {
|
|
@@ -4865,13 +5185,14 @@ function stageFiles(files, projectDir) {
|
|
|
4865
5185
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
4866
5186
|
}
|
|
4867
5187
|
function cleanupStaging() {
|
|
4868
|
-
if (
|
|
4869
|
-
|
|
5188
|
+
if (fs20.existsSync(STAGED_DIR)) {
|
|
5189
|
+
fs20.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
4870
5190
|
}
|
|
4871
5191
|
}
|
|
4872
5192
|
|
|
4873
5193
|
// src/commands/setup-files.ts
|
|
4874
|
-
|
|
5194
|
+
init_builtin_skills();
|
|
5195
|
+
import fs21 from "fs";
|
|
4875
5196
|
function collectSetupFiles(setup, targetAgent) {
|
|
4876
5197
|
const files = [];
|
|
4877
5198
|
const claude = setup.claude;
|
|
@@ -4930,14 +5251,14 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
4930
5251
|
}
|
|
4931
5252
|
}
|
|
4932
5253
|
const codexTargeted = targetAgent ? targetAgent.includes("codex") : false;
|
|
4933
|
-
if (codexTargeted && !
|
|
5254
|
+
if (codexTargeted && !fs21.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
|
|
4934
5255
|
const agentRefs = [];
|
|
4935
5256
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
4936
5257
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
4937
5258
|
if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
4938
5259
|
const stubContent = `# AGENTS.md
|
|
4939
5260
|
|
|
4940
|
-
This project uses AI coding agents configured by [Caliber](https://github.com/
|
|
5261
|
+
This project uses AI coding agents configured by [Caliber](https://github.com/rely-ai-org/caliber).
|
|
4941
5262
|
|
|
4942
5263
|
${agentRefs.join(" ")}
|
|
4943
5264
|
`;
|
|
@@ -4948,9 +5269,9 @@ ${agentRefs.join(" ")}
|
|
|
4948
5269
|
|
|
4949
5270
|
// src/lib/learning-hooks.ts
|
|
4950
5271
|
init_resolve_caliber();
|
|
4951
|
-
import
|
|
4952
|
-
import
|
|
4953
|
-
var
|
|
5272
|
+
import fs22 from "fs";
|
|
5273
|
+
import path18 from "path";
|
|
5274
|
+
var SETTINGS_PATH2 = path18.join(".claude", "settings.json");
|
|
4954
5275
|
var HOOK_TAILS = [
|
|
4955
5276
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
4956
5277
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
@@ -4966,24 +5287,24 @@ function getHookConfigs() {
|
|
|
4966
5287
|
description
|
|
4967
5288
|
}));
|
|
4968
5289
|
}
|
|
4969
|
-
function
|
|
4970
|
-
if (!
|
|
5290
|
+
function readSettings2() {
|
|
5291
|
+
if (!fs22.existsSync(SETTINGS_PATH2)) return {};
|
|
4971
5292
|
try {
|
|
4972
|
-
return JSON.parse(
|
|
5293
|
+
return JSON.parse(fs22.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
4973
5294
|
} catch {
|
|
4974
5295
|
return {};
|
|
4975
5296
|
}
|
|
4976
5297
|
}
|
|
4977
|
-
function
|
|
4978
|
-
const dir =
|
|
4979
|
-
if (!
|
|
4980
|
-
|
|
5298
|
+
function writeSettings2(settings) {
|
|
5299
|
+
const dir = path18.dirname(SETTINGS_PATH2);
|
|
5300
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5301
|
+
fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
4981
5302
|
}
|
|
4982
5303
|
function hasLearningHook(matchers, tail) {
|
|
4983
5304
|
return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
|
|
4984
5305
|
}
|
|
4985
5306
|
function areLearningHooksInstalled() {
|
|
4986
|
-
const settings =
|
|
5307
|
+
const settings = readSettings2();
|
|
4987
5308
|
if (!settings.hooks) return false;
|
|
4988
5309
|
return HOOK_TAILS.every((cfg) => {
|
|
4989
5310
|
const matchers = settings.hooks[cfg.event];
|
|
@@ -4994,7 +5315,7 @@ function installLearningHooks() {
|
|
|
4994
5315
|
if (areLearningHooksInstalled()) {
|
|
4995
5316
|
return { installed: false, alreadyInstalled: true };
|
|
4996
5317
|
}
|
|
4997
|
-
const settings =
|
|
5318
|
+
const settings = readSettings2();
|
|
4998
5319
|
if (!settings.hooks) settings.hooks = {};
|
|
4999
5320
|
const configs = getHookConfigs();
|
|
5000
5321
|
for (const cfg of configs) {
|
|
@@ -5008,10 +5329,10 @@ function installLearningHooks() {
|
|
|
5008
5329
|
});
|
|
5009
5330
|
}
|
|
5010
5331
|
}
|
|
5011
|
-
|
|
5332
|
+
writeSettings2(settings);
|
|
5012
5333
|
return { installed: true, alreadyInstalled: false };
|
|
5013
5334
|
}
|
|
5014
|
-
var CURSOR_HOOKS_PATH =
|
|
5335
|
+
var CURSOR_HOOKS_PATH = path18.join(".cursor", "hooks.json");
|
|
5015
5336
|
var CURSOR_HOOK_EVENTS = [
|
|
5016
5337
|
{ event: "postToolUse", tail: "learn observe" },
|
|
5017
5338
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
@@ -5019,17 +5340,17 @@ var CURSOR_HOOK_EVENTS = [
|
|
|
5019
5340
|
{ event: "sessionEnd", tail: "learn finalize --auto" }
|
|
5020
5341
|
];
|
|
5021
5342
|
function readCursorHooks() {
|
|
5022
|
-
if (!
|
|
5343
|
+
if (!fs22.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
5023
5344
|
try {
|
|
5024
|
-
return JSON.parse(
|
|
5345
|
+
return JSON.parse(fs22.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
|
|
5025
5346
|
} catch {
|
|
5026
5347
|
return { version: 1, hooks: {} };
|
|
5027
5348
|
}
|
|
5028
5349
|
}
|
|
5029
5350
|
function writeCursorHooks(config) {
|
|
5030
|
-
const dir =
|
|
5031
|
-
if (!
|
|
5032
|
-
|
|
5351
|
+
const dir = path18.dirname(CURSOR_HOOKS_PATH);
|
|
5352
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5353
|
+
fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
5033
5354
|
}
|
|
5034
5355
|
function hasCursorHook(entries, tail) {
|
|
5035
5356
|
return entries.some((e) => isCaliberCommand(e.command, tail));
|
|
@@ -5076,7 +5397,7 @@ function removeCursorLearningHooks() {
|
|
|
5076
5397
|
return { removed: true, notFound: false };
|
|
5077
5398
|
}
|
|
5078
5399
|
function removeLearningHooks() {
|
|
5079
|
-
const settings =
|
|
5400
|
+
const settings = readSettings2();
|
|
5080
5401
|
if (!settings.hooks) return { removed: false, notFound: true };
|
|
5081
5402
|
let removedAny = false;
|
|
5082
5403
|
for (const cfg of HOOK_TAILS) {
|
|
@@ -5093,7 +5414,7 @@ function removeLearningHooks() {
|
|
|
5093
5414
|
delete settings.hooks;
|
|
5094
5415
|
}
|
|
5095
5416
|
if (!removedAny) return { removed: false, notFound: true };
|
|
5096
|
-
|
|
5417
|
+
writeSettings2(settings);
|
|
5097
5418
|
return { removed: true, notFound: false };
|
|
5098
5419
|
}
|
|
5099
5420
|
|
|
@@ -5101,10 +5422,10 @@ function removeLearningHooks() {
|
|
|
5101
5422
|
init_resolve_caliber();
|
|
5102
5423
|
|
|
5103
5424
|
// src/lib/state.ts
|
|
5104
|
-
import
|
|
5105
|
-
import
|
|
5106
|
-
import { execSync as
|
|
5107
|
-
var STATE_FILE =
|
|
5425
|
+
import fs23 from "fs";
|
|
5426
|
+
import path19 from "path";
|
|
5427
|
+
import { execSync as execSync9 } from "child_process";
|
|
5428
|
+
var STATE_FILE = path19.join(CALIBER_DIR, ".caliber-state.json");
|
|
5108
5429
|
function normalizeTargetAgent(value) {
|
|
5109
5430
|
if (Array.isArray(value)) return value;
|
|
5110
5431
|
if (typeof value === "string") {
|
|
@@ -5115,8 +5436,8 @@ function normalizeTargetAgent(value) {
|
|
|
5115
5436
|
}
|
|
5116
5437
|
function readState() {
|
|
5117
5438
|
try {
|
|
5118
|
-
if (!
|
|
5119
|
-
const raw = JSON.parse(
|
|
5439
|
+
if (!fs23.existsSync(STATE_FILE)) return null;
|
|
5440
|
+
const raw = JSON.parse(fs23.readFileSync(STATE_FILE, "utf-8"));
|
|
5120
5441
|
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
5121
5442
|
return raw;
|
|
5122
5443
|
} catch {
|
|
@@ -5124,14 +5445,14 @@ function readState() {
|
|
|
5124
5445
|
}
|
|
5125
5446
|
}
|
|
5126
5447
|
function writeState(state) {
|
|
5127
|
-
if (!
|
|
5128
|
-
|
|
5448
|
+
if (!fs23.existsSync(CALIBER_DIR)) {
|
|
5449
|
+
fs23.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
5129
5450
|
}
|
|
5130
|
-
|
|
5451
|
+
fs23.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
5131
5452
|
}
|
|
5132
5453
|
function getCurrentHeadSha() {
|
|
5133
5454
|
try {
|
|
5134
|
-
return
|
|
5455
|
+
return execSync9("git rev-parse HEAD", {
|
|
5135
5456
|
encoding: "utf-8",
|
|
5136
5457
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5137
5458
|
}).trim();
|
|
@@ -5278,11 +5599,11 @@ var POINTS_LEARNED_CONTENT = 2;
|
|
|
5278
5599
|
var POINTS_SOURCES_CONFIGURED = 3;
|
|
5279
5600
|
var POINTS_SOURCES_REFERENCED = 3;
|
|
5280
5601
|
var TOKEN_BUDGET_THRESHOLDS = [
|
|
5281
|
-
{ maxTokens:
|
|
5282
|
-
{ maxTokens:
|
|
5283
|
-
{ maxTokens:
|
|
5284
|
-
{ maxTokens:
|
|
5285
|
-
{ maxTokens:
|
|
5602
|
+
{ maxTokens: 5e3, points: 6 },
|
|
5603
|
+
{ maxTokens: 8e3, points: 5 },
|
|
5604
|
+
{ maxTokens: 12e3, points: 4 },
|
|
5605
|
+
{ maxTokens: 16e3, points: 2 },
|
|
5606
|
+
{ maxTokens: 24e3, points: 1 }
|
|
5286
5607
|
];
|
|
5287
5608
|
var CODE_BLOCK_THRESHOLDS = [
|
|
5288
5609
|
{ minBlocks: 3, points: 8 },
|
|
@@ -5744,7 +6065,7 @@ function checkGrounding(dir) {
|
|
|
5744
6065
|
|
|
5745
6066
|
// src/scoring/checks/accuracy.ts
|
|
5746
6067
|
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
5747
|
-
import { execSync as
|
|
6068
|
+
import { execSync as execSync10 } from "child_process";
|
|
5748
6069
|
import { join as join5 } from "path";
|
|
5749
6070
|
init_resolve_caliber();
|
|
5750
6071
|
function validateReferences(dir) {
|
|
@@ -5754,13 +6075,13 @@ function validateReferences(dir) {
|
|
|
5754
6075
|
}
|
|
5755
6076
|
function detectGitDrift(dir) {
|
|
5756
6077
|
try {
|
|
5757
|
-
|
|
6078
|
+
execSync10("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
|
|
5758
6079
|
} catch {
|
|
5759
6080
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
|
|
5760
6081
|
}
|
|
5761
6082
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
|
|
5762
6083
|
try {
|
|
5763
|
-
const headTimestamp =
|
|
6084
|
+
const headTimestamp = execSync10(
|
|
5764
6085
|
"git log -1 --format=%ct HEAD",
|
|
5765
6086
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5766
6087
|
).trim();
|
|
@@ -5781,7 +6102,7 @@ function detectGitDrift(dir) {
|
|
|
5781
6102
|
let latestConfigCommitHash = null;
|
|
5782
6103
|
for (const file of configFiles) {
|
|
5783
6104
|
try {
|
|
5784
|
-
const hash =
|
|
6105
|
+
const hash = execSync10(
|
|
5785
6106
|
`git log -1 --format=%H -- "${file}"`,
|
|
5786
6107
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5787
6108
|
).trim();
|
|
@@ -5790,7 +6111,7 @@ function detectGitDrift(dir) {
|
|
|
5790
6111
|
latestConfigCommitHash = hash;
|
|
5791
6112
|
} else {
|
|
5792
6113
|
try {
|
|
5793
|
-
|
|
6114
|
+
execSync10(
|
|
5794
6115
|
`git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
|
|
5795
6116
|
{ cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
|
|
5796
6117
|
);
|
|
@@ -5805,12 +6126,12 @@ function detectGitDrift(dir) {
|
|
|
5805
6126
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
|
|
5806
6127
|
}
|
|
5807
6128
|
try {
|
|
5808
|
-
const countStr =
|
|
6129
|
+
const countStr = execSync10(
|
|
5809
6130
|
`git rev-list --count ${latestConfigCommitHash}..HEAD`,
|
|
5810
6131
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5811
6132
|
).trim();
|
|
5812
6133
|
const commitsSince = parseInt(countStr, 10) || 0;
|
|
5813
|
-
const lastDate =
|
|
6134
|
+
const lastDate = execSync10(
|
|
5814
6135
|
`git log -1 --format=%ci ${latestConfigCommitHash}`,
|
|
5815
6136
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5816
6137
|
).trim();
|
|
@@ -5883,12 +6204,12 @@ function checkAccuracy(dir) {
|
|
|
5883
6204
|
// src/scoring/checks/freshness.ts
|
|
5884
6205
|
init_resolve_caliber();
|
|
5885
6206
|
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
5886
|
-
import { execSync as
|
|
6207
|
+
import { execSync as execSync11 } from "child_process";
|
|
5887
6208
|
import { join as join6 } from "path";
|
|
5888
6209
|
function getCommitsSinceConfigUpdate(dir) {
|
|
5889
6210
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
|
|
5890
6211
|
try {
|
|
5891
|
-
const headTimestamp =
|
|
6212
|
+
const headTimestamp = execSync11(
|
|
5892
6213
|
"git log -1 --format=%ct HEAD",
|
|
5893
6214
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5894
6215
|
).trim();
|
|
@@ -5908,12 +6229,12 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
5908
6229
|
}
|
|
5909
6230
|
for (const file of configFiles) {
|
|
5910
6231
|
try {
|
|
5911
|
-
const hash =
|
|
6232
|
+
const hash = execSync11(
|
|
5912
6233
|
`git log -1 --format=%H -- "${file}"`,
|
|
5913
6234
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5914
6235
|
).trim();
|
|
5915
6236
|
if (hash) {
|
|
5916
|
-
const countStr =
|
|
6237
|
+
const countStr = execSync11(
|
|
5917
6238
|
`git rev-list --count ${hash}..HEAD`,
|
|
5918
6239
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5919
6240
|
).trim();
|
|
@@ -6031,12 +6352,12 @@ function checkFreshness(dir) {
|
|
|
6031
6352
|
|
|
6032
6353
|
// src/scoring/checks/bonus.ts
|
|
6033
6354
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
6034
|
-
import { execSync as
|
|
6355
|
+
import { execSync as execSync12 } from "child_process";
|
|
6035
6356
|
import { join as join7 } from "path";
|
|
6036
6357
|
init_resolve_caliber();
|
|
6037
6358
|
function hasPreCommitHook(dir) {
|
|
6038
6359
|
try {
|
|
6039
|
-
const gitDir =
|
|
6360
|
+
const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6040
6361
|
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
6041
6362
|
const content = readFileOrNull(hookPath);
|
|
6042
6363
|
return content ? content.includes("caliber") : false;
|
|
@@ -6193,22 +6514,22 @@ function checkSources(dir) {
|
|
|
6193
6514
|
}
|
|
6194
6515
|
|
|
6195
6516
|
// src/scoring/dismissed.ts
|
|
6196
|
-
import
|
|
6197
|
-
import
|
|
6198
|
-
var DISMISSED_FILE =
|
|
6517
|
+
import fs24 from "fs";
|
|
6518
|
+
import path20 from "path";
|
|
6519
|
+
var DISMISSED_FILE = path20.join(CALIBER_DIR, "dismissed-checks.json");
|
|
6199
6520
|
function readDismissedChecks() {
|
|
6200
6521
|
try {
|
|
6201
|
-
if (!
|
|
6202
|
-
return JSON.parse(
|
|
6522
|
+
if (!fs24.existsSync(DISMISSED_FILE)) return [];
|
|
6523
|
+
return JSON.parse(fs24.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
6203
6524
|
} catch {
|
|
6204
6525
|
return [];
|
|
6205
6526
|
}
|
|
6206
6527
|
}
|
|
6207
6528
|
function writeDismissedChecks(checks) {
|
|
6208
|
-
if (!
|
|
6209
|
-
|
|
6529
|
+
if (!fs24.existsSync(CALIBER_DIR)) {
|
|
6530
|
+
fs24.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
6210
6531
|
}
|
|
6211
|
-
|
|
6532
|
+
fs24.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
6212
6533
|
}
|
|
6213
6534
|
function getDismissedIds() {
|
|
6214
6535
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -6463,27 +6784,27 @@ import { PostHog } from "posthog-node";
|
|
|
6463
6784
|
import chalk5 from "chalk";
|
|
6464
6785
|
|
|
6465
6786
|
// src/telemetry/config.ts
|
|
6466
|
-
import
|
|
6467
|
-
import
|
|
6787
|
+
import fs25 from "fs";
|
|
6788
|
+
import path21 from "path";
|
|
6468
6789
|
import os5 from "os";
|
|
6469
6790
|
import crypto4 from "crypto";
|
|
6470
|
-
import { execSync as
|
|
6471
|
-
var CONFIG_DIR2 =
|
|
6472
|
-
var CONFIG_FILE2 =
|
|
6791
|
+
import { execSync as execSync13 } from "child_process";
|
|
6792
|
+
var CONFIG_DIR2 = path21.join(os5.homedir(), ".caliber");
|
|
6793
|
+
var CONFIG_FILE2 = path21.join(CONFIG_DIR2, "config.json");
|
|
6473
6794
|
var runtimeDisabled = false;
|
|
6474
6795
|
function readConfig() {
|
|
6475
6796
|
try {
|
|
6476
|
-
if (!
|
|
6477
|
-
return JSON.parse(
|
|
6797
|
+
if (!fs25.existsSync(CONFIG_FILE2)) return {};
|
|
6798
|
+
return JSON.parse(fs25.readFileSync(CONFIG_FILE2, "utf-8"));
|
|
6478
6799
|
} catch {
|
|
6479
6800
|
return {};
|
|
6480
6801
|
}
|
|
6481
6802
|
}
|
|
6482
6803
|
function writeConfig(config) {
|
|
6483
|
-
if (!
|
|
6484
|
-
|
|
6804
|
+
if (!fs25.existsSync(CONFIG_DIR2)) {
|
|
6805
|
+
fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
6485
6806
|
}
|
|
6486
|
-
|
|
6807
|
+
fs25.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
6487
6808
|
}
|
|
6488
6809
|
function getMachineId() {
|
|
6489
6810
|
const config = readConfig();
|
|
@@ -6495,7 +6816,7 @@ function getMachineId() {
|
|
|
6495
6816
|
var EMAIL_HASH_KEY = "caliber-telemetry-v1";
|
|
6496
6817
|
function getGitEmailHash() {
|
|
6497
6818
|
try {
|
|
6498
|
-
const email =
|
|
6819
|
+
const email = execSync13("git config user.email", { encoding: "utf-8" }).trim();
|
|
6499
6820
|
if (!email) return void 0;
|
|
6500
6821
|
return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
|
|
6501
6822
|
} catch {
|
|
@@ -7694,8 +8015,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7694
8015
|
}
|
|
7695
8016
|
|
|
7696
8017
|
// src/lib/debug-report.ts
|
|
7697
|
-
import
|
|
7698
|
-
import
|
|
8018
|
+
import fs26 from "fs";
|
|
8019
|
+
import path22 from "path";
|
|
7699
8020
|
var DebugReport = class {
|
|
7700
8021
|
sections = [];
|
|
7701
8022
|
startTime;
|
|
@@ -7764,11 +8085,11 @@ var DebugReport = class {
|
|
|
7764
8085
|
lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
|
|
7765
8086
|
lines.push("");
|
|
7766
8087
|
}
|
|
7767
|
-
const dir =
|
|
7768
|
-
if (!
|
|
7769
|
-
|
|
8088
|
+
const dir = path22.dirname(outputPath);
|
|
8089
|
+
if (!fs26.existsSync(dir)) {
|
|
8090
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
7770
8091
|
}
|
|
7771
|
-
|
|
8092
|
+
fs26.writeFileSync(outputPath, lines.join("\n"));
|
|
7772
8093
|
}
|
|
7773
8094
|
};
|
|
7774
8095
|
function formatMs(ms) {
|
|
@@ -8169,7 +8490,7 @@ import chalk11 from "chalk";
|
|
|
8169
8490
|
import ora3 from "ora";
|
|
8170
8491
|
import select5 from "@inquirer/select";
|
|
8171
8492
|
import checkbox from "@inquirer/checkbox";
|
|
8172
|
-
import
|
|
8493
|
+
import fs29 from "fs";
|
|
8173
8494
|
|
|
8174
8495
|
// src/ai/refine.ts
|
|
8175
8496
|
async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
|
|
@@ -8310,10 +8631,10 @@ init_config();
|
|
|
8310
8631
|
init_review();
|
|
8311
8632
|
function detectAgents(dir) {
|
|
8312
8633
|
const agents = [];
|
|
8313
|
-
if (
|
|
8314
|
-
if (
|
|
8315
|
-
if (
|
|
8316
|
-
if (
|
|
8634
|
+
if (fs29.existsSync(`${dir}/.claude`)) agents.push("claude");
|
|
8635
|
+
if (fs29.existsSync(`${dir}/.cursor`)) agents.push("cursor");
|
|
8636
|
+
if (fs29.existsSync(`${dir}/.agents`) || fs29.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
|
|
8637
|
+
if (fs29.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
|
|
8317
8638
|
return agents;
|
|
8318
8639
|
}
|
|
8319
8640
|
async function promptAgent(detected) {
|
|
@@ -8435,18 +8756,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8435
8756
|
|
|
8436
8757
|
// src/commands/init-display.ts
|
|
8437
8758
|
import chalk12 from "chalk";
|
|
8438
|
-
import
|
|
8759
|
+
import fs30 from "fs";
|
|
8439
8760
|
function formatWhatChanged(setup) {
|
|
8440
8761
|
const lines = [];
|
|
8441
8762
|
const claude = setup.claude;
|
|
8442
8763
|
const codex = setup.codex;
|
|
8443
8764
|
const cursor = setup.cursor;
|
|
8444
8765
|
if (claude?.claudeMd) {
|
|
8445
|
-
const action =
|
|
8766
|
+
const action = fs30.existsSync("CLAUDE.md") ? "Updated" : "Created";
|
|
8446
8767
|
lines.push(`${action} CLAUDE.md`);
|
|
8447
8768
|
}
|
|
8448
8769
|
if (codex?.agentsMd) {
|
|
8449
|
-
const action =
|
|
8770
|
+
const action = fs30.existsSync("AGENTS.md") ? "Updated" : "Created";
|
|
8450
8771
|
lines.push(`${action} AGENTS.md`);
|
|
8451
8772
|
}
|
|
8452
8773
|
const allSkills = [];
|
|
@@ -8483,7 +8804,7 @@ function printSetupSummary(setup) {
|
|
|
8483
8804
|
};
|
|
8484
8805
|
if (claude) {
|
|
8485
8806
|
if (claude.claudeMd) {
|
|
8486
|
-
const icon =
|
|
8807
|
+
const icon = fs30.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8487
8808
|
const desc = getDescription("CLAUDE.md");
|
|
8488
8809
|
console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
|
|
8489
8810
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8493,7 +8814,7 @@ function printSetupSummary(setup) {
|
|
|
8493
8814
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
8494
8815
|
for (const skill of skills) {
|
|
8495
8816
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
8496
|
-
const icon =
|
|
8817
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8497
8818
|
const desc = getDescription(skillPath);
|
|
8498
8819
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8499
8820
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8504,7 +8825,7 @@ function printSetupSummary(setup) {
|
|
|
8504
8825
|
const codex = setup.codex;
|
|
8505
8826
|
if (codex) {
|
|
8506
8827
|
if (codex.agentsMd) {
|
|
8507
|
-
const icon =
|
|
8828
|
+
const icon = fs30.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8508
8829
|
const desc = getDescription("AGENTS.md");
|
|
8509
8830
|
console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
|
|
8510
8831
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8514,7 +8835,7 @@ function printSetupSummary(setup) {
|
|
|
8514
8835
|
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
8515
8836
|
for (const skill of codexSkills) {
|
|
8516
8837
|
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
8517
|
-
const icon =
|
|
8838
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8518
8839
|
const desc = getDescription(skillPath);
|
|
8519
8840
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8520
8841
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8524,7 +8845,7 @@ function printSetupSummary(setup) {
|
|
|
8524
8845
|
}
|
|
8525
8846
|
if (cursor) {
|
|
8526
8847
|
if (cursor.cursorrules) {
|
|
8527
|
-
const icon =
|
|
8848
|
+
const icon = fs30.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8528
8849
|
const desc = getDescription(".cursorrules");
|
|
8529
8850
|
console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
|
|
8530
8851
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8534,7 +8855,7 @@ function printSetupSummary(setup) {
|
|
|
8534
8855
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
8535
8856
|
for (const skill of cursorSkills) {
|
|
8536
8857
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
8537
|
-
const icon =
|
|
8858
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8538
8859
|
const desc = getDescription(skillPath);
|
|
8539
8860
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8540
8861
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8545,7 +8866,7 @@ function printSetupSummary(setup) {
|
|
|
8545
8866
|
if (Array.isArray(rulesArr) && rulesArr.length > 0) {
|
|
8546
8867
|
for (const rule of rulesArr) {
|
|
8547
8868
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
8548
|
-
const icon =
|
|
8869
|
+
const icon = fs30.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8549
8870
|
const desc = getDescription(rulePath);
|
|
8550
8871
|
console.log(` ${icon} ${chalk12.bold(rulePath)}`);
|
|
8551
8872
|
if (desc) {
|
|
@@ -8592,12 +8913,12 @@ function displayTokenUsage() {
|
|
|
8592
8913
|
// src/commands/init-helpers.ts
|
|
8593
8914
|
init_config();
|
|
8594
8915
|
import chalk13 from "chalk";
|
|
8595
|
-
import
|
|
8596
|
-
import
|
|
8916
|
+
import fs31 from "fs";
|
|
8917
|
+
import path24 from "path";
|
|
8597
8918
|
function isFirstRun(dir) {
|
|
8598
|
-
const caliberDir =
|
|
8919
|
+
const caliberDir = path24.join(dir, ".caliber");
|
|
8599
8920
|
try {
|
|
8600
|
-
const stat =
|
|
8921
|
+
const stat = fs31.statSync(caliberDir);
|
|
8601
8922
|
return !stat.isDirectory();
|
|
8602
8923
|
} catch {
|
|
8603
8924
|
return true;
|
|
@@ -8650,8 +8971,8 @@ function ensurePermissions(fingerprint) {
|
|
|
8650
8971
|
const settingsPath = ".claude/settings.json";
|
|
8651
8972
|
let settings = {};
|
|
8652
8973
|
try {
|
|
8653
|
-
if (
|
|
8654
|
-
settings = JSON.parse(
|
|
8974
|
+
if (fs31.existsSync(settingsPath)) {
|
|
8975
|
+
settings = JSON.parse(fs31.readFileSync(settingsPath, "utf-8"));
|
|
8655
8976
|
}
|
|
8656
8977
|
} catch {
|
|
8657
8978
|
}
|
|
@@ -8660,12 +8981,12 @@ function ensurePermissions(fingerprint) {
|
|
|
8660
8981
|
if (Array.isArray(allow) && allow.length > 0) return;
|
|
8661
8982
|
permissions.allow = derivePermissions(fingerprint);
|
|
8662
8983
|
settings.permissions = permissions;
|
|
8663
|
-
if (!
|
|
8664
|
-
|
|
8984
|
+
if (!fs31.existsSync(".claude")) fs31.mkdirSync(".claude", { recursive: true });
|
|
8985
|
+
fs31.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8665
8986
|
}
|
|
8666
8987
|
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
8667
8988
|
try {
|
|
8668
|
-
const logPath =
|
|
8989
|
+
const logPath = path24.join(process.cwd(), ".caliber", "error-log.md");
|
|
8669
8990
|
const lines = [
|
|
8670
8991
|
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
8671
8992
|
"",
|
|
@@ -8678,8 +8999,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
|
8678
8999
|
lines.push("## Error", "```", error, "```", "");
|
|
8679
9000
|
}
|
|
8680
9001
|
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
8681
|
-
|
|
8682
|
-
|
|
9002
|
+
fs31.mkdirSync(path24.join(process.cwd(), ".caliber"), { recursive: true });
|
|
9003
|
+
fs31.writeFileSync(logPath, lines.join("\n"));
|
|
8683
9004
|
console.log(chalk13.dim(`
|
|
8684
9005
|
Error log written to .caliber/error-log.md`));
|
|
8685
9006
|
} catch {
|
|
@@ -8730,13 +9051,13 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
8730
9051
|
}
|
|
8731
9052
|
|
|
8732
9053
|
// src/scoring/history.ts
|
|
8733
|
-
import
|
|
8734
|
-
import
|
|
9054
|
+
import fs32 from "fs";
|
|
9055
|
+
import path25 from "path";
|
|
8735
9056
|
var HISTORY_FILE = "score-history.jsonl";
|
|
8736
9057
|
var MAX_ENTRIES = 500;
|
|
8737
9058
|
var TRIM_THRESHOLD = MAX_ENTRIES + 50;
|
|
8738
9059
|
function historyFilePath() {
|
|
8739
|
-
return
|
|
9060
|
+
return path25.join(CALIBER_DIR, HISTORY_FILE);
|
|
8740
9061
|
}
|
|
8741
9062
|
function recordScore(result, trigger) {
|
|
8742
9063
|
const entry = {
|
|
@@ -8747,14 +9068,14 @@ function recordScore(result, trigger) {
|
|
|
8747
9068
|
trigger
|
|
8748
9069
|
};
|
|
8749
9070
|
try {
|
|
8750
|
-
|
|
9071
|
+
fs32.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
8751
9072
|
const filePath = historyFilePath();
|
|
8752
|
-
|
|
8753
|
-
const stat =
|
|
9073
|
+
fs32.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
9074
|
+
const stat = fs32.statSync(filePath);
|
|
8754
9075
|
if (stat.size > TRIM_THRESHOLD * 120) {
|
|
8755
|
-
const lines =
|
|
9076
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
8756
9077
|
if (lines.length > MAX_ENTRIES) {
|
|
8757
|
-
|
|
9078
|
+
fs32.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
|
|
8758
9079
|
}
|
|
8759
9080
|
}
|
|
8760
9081
|
} catch {
|
|
@@ -8763,7 +9084,7 @@ function recordScore(result, trigger) {
|
|
|
8763
9084
|
function readScoreHistory() {
|
|
8764
9085
|
const filePath = historyFilePath();
|
|
8765
9086
|
try {
|
|
8766
|
-
const content =
|
|
9087
|
+
const content = fs32.readFileSync(filePath, "utf-8");
|
|
8767
9088
|
const entries = [];
|
|
8768
9089
|
for (const line of content.split("\n")) {
|
|
8769
9090
|
if (!line) continue;
|
|
@@ -8809,13 +9130,13 @@ async function initCommand(options) {
|
|
|
8809
9130
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
8810
9131
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
8811
9132
|
`));
|
|
8812
|
-
console.log(chalk14.dim("
|
|
8813
|
-
console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
9133
|
+
console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
|
|
9134
|
+
console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
8814
9135
|
console.log(title.bold(" How it works:\n"));
|
|
8815
9136
|
console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
|
|
8816
|
-
console.log(chalk14.dim(" 2.
|
|
8817
|
-
console.log(chalk14.dim(" 3.
|
|
8818
|
-
console.log(chalk14.dim(" 4. Finalize
|
|
9137
|
+
console.log(chalk14.dim(" 2. Setup Detect stack, install sync hooks & skills"));
|
|
9138
|
+
console.log(chalk14.dim(" 3. Generate Audit existing config or generate from scratch"));
|
|
9139
|
+
console.log(chalk14.dim(" 4. Finalize Review changes and score your setup\n"));
|
|
8819
9140
|
} else {
|
|
8820
9141
|
console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 regenerating config\n"));
|
|
8821
9142
|
}
|
|
@@ -8865,8 +9186,26 @@ async function initCommand(options) {
|
|
|
8865
9186
|
console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
|
|
8866
9187
|
`));
|
|
8867
9188
|
trackInitAgentSelected(targetAgent, agentAutoDetected);
|
|
9189
|
+
console.log(title.bold(" Step 2/4 \u2014 Setup\n"));
|
|
9190
|
+
const hookResult = installPreCommitHook();
|
|
9191
|
+
if (hookResult.installed) {
|
|
9192
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 configs sync on every commit`);
|
|
9193
|
+
} else if (hookResult.alreadyInstalled) {
|
|
9194
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 already installed`);
|
|
9195
|
+
}
|
|
9196
|
+
const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
9197
|
+
for (const agent of targetAgent) {
|
|
9198
|
+
if (agent === "claude" && !fs33.existsSync(".claude")) fs33.mkdirSync(".claude", { recursive: true });
|
|
9199
|
+
if (agent === "cursor" && !fs33.existsSync(".cursor")) fs33.mkdirSync(".cursor", { recursive: true });
|
|
9200
|
+
if (agent === "codex" && !fs33.existsSync(".agents")) fs33.mkdirSync(".agents", { recursive: true });
|
|
9201
|
+
}
|
|
9202
|
+
const skillsWritten = ensureBuiltinSkills2();
|
|
9203
|
+
if (skillsWritten.length > 0) {
|
|
9204
|
+
console.log(` ${chalk14.green("\u2713")} Agent skills \u2014 ${skillsWritten.length} skills installed (setup-caliber, find-skills, save-learning)`);
|
|
9205
|
+
}
|
|
9206
|
+
console.log("");
|
|
8868
9207
|
let baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
8869
|
-
console.log(chalk14.dim("
|
|
9208
|
+
console.log(chalk14.dim(" Current config score:"));
|
|
8870
9209
|
displayScoreSummary(baselineScore);
|
|
8871
9210
|
if (options.verbose) {
|
|
8872
9211
|
for (const c of baselineScore.checks) {
|
|
@@ -8893,29 +9232,37 @@ async function initCommand(options) {
|
|
|
8893
9232
|
]);
|
|
8894
9233
|
const passingCount = baselineScore.checks.filter((c) => c.passed).length;
|
|
8895
9234
|
const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
|
|
9235
|
+
let skipGeneration = false;
|
|
8896
9236
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
8897
9237
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
8898
|
-
console.log(chalk14.bold.green(" Your config is already optimal
|
|
8899
|
-
|
|
8900
|
-
|
|
9238
|
+
console.log(chalk14.bold.green("\n Your config is already optimal.\n"));
|
|
9239
|
+
skipGeneration = !options.force;
|
|
9240
|
+
} else if (hasExistingConfig && !options.force && !options.autoApprove) {
|
|
9241
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9242
|
+
const auditAnswer = await promptInput(" Want Caliber to audit and improve your existing config? (Y/n) ");
|
|
9243
|
+
skipGeneration = auditAnswer.toLowerCase() === "n";
|
|
9244
|
+
} else if (!hasExistingConfig && !options.force && !options.autoApprove) {
|
|
9245
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9246
|
+
const generateAnswer = await promptInput(" Want Caliber to generate agent configs for your project? (Y/n) ");
|
|
9247
|
+
skipGeneration = generateAnswer.toLowerCase() === "n";
|
|
9248
|
+
} else {
|
|
9249
|
+
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9250
|
+
}
|
|
9251
|
+
if (skipGeneration) {
|
|
9252
|
+
const sha2 = getCurrentHeadSha();
|
|
9253
|
+
writeState({
|
|
9254
|
+
lastRefreshSha: sha2 ?? "",
|
|
9255
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9256
|
+
targetAgent
|
|
9257
|
+
});
|
|
9258
|
+
console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
|
|
9259
|
+
console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
|
|
9260
|
+
console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
|
|
9261
|
+
return;
|
|
8901
9262
|
}
|
|
8902
9263
|
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
8903
9264
|
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
8904
|
-
|
|
8905
|
-
if (hasExistingConfig && llmFixableChecks.length === 0 && allFailingChecks.length > 0 && !options.force) {
|
|
8906
|
-
console.log(chalk14.bold.green("\n Your config is fully optimized for LLM generation.\n"));
|
|
8907
|
-
console.log(chalk14.dim(" Remaining items need CLI actions:\n"));
|
|
8908
|
-
for (const check of allFailingChecks) {
|
|
8909
|
-
console.log(chalk14.dim(` \u2022 ${check.name}`));
|
|
8910
|
-
if (check.suggestion) {
|
|
8911
|
-
console.log(` ${chalk14.hex("#83D1EB")(check.suggestion)}`);
|
|
8912
|
-
}
|
|
8913
|
-
}
|
|
8914
|
-
console.log("");
|
|
8915
|
-
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n"));
|
|
8916
|
-
return;
|
|
8917
|
-
}
|
|
8918
|
-
console.log(title.bold(" Step 2/4 \u2014 Engine\n"));
|
|
9265
|
+
console.log(title.bold("\n Step 3/4 \u2014 Generate\n"));
|
|
8919
9266
|
const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
|
|
8920
9267
|
console.log(chalk14.dim(genModelInfo + "\n"));
|
|
8921
9268
|
if (report) report.markStep("Generation");
|
|
@@ -9114,7 +9461,7 @@ async function initCommand(options) {
|
|
|
9114
9461
|
report.addJson("Generation: Parsed Config", generatedSetup);
|
|
9115
9462
|
}
|
|
9116
9463
|
log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
|
|
9117
|
-
console.log(title.bold(" Step
|
|
9464
|
+
console.log(title.bold(" Step 4/4 \u2014 Finalize\n"));
|
|
9118
9465
|
const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9119
9466
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
9120
9467
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
@@ -9177,7 +9524,6 @@ async function initCommand(options) {
|
|
|
9177
9524
|
console.log(chalk14.dim("Declined. No files were modified."));
|
|
9178
9525
|
return;
|
|
9179
9526
|
}
|
|
9180
|
-
console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
|
|
9181
9527
|
if (options.dryRun) {
|
|
9182
9528
|
console.log(chalk14.yellow("\n[Dry run] Would write the following files:"));
|
|
9183
9529
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
@@ -9186,7 +9532,7 @@ async function initCommand(options) {
|
|
|
9186
9532
|
const { default: ora9 } = await import("ora");
|
|
9187
9533
|
const writeSpinner = ora9("Writing config files...").start();
|
|
9188
9534
|
try {
|
|
9189
|
-
if (targetAgent.includes("codex") && !
|
|
9535
|
+
if (targetAgent.includes("codex") && !fs33.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
9190
9536
|
const claude = generatedSetup.claude;
|
|
9191
9537
|
const cursor = generatedSetup.cursor;
|
|
9192
9538
|
const agentRefs = [];
|
|
@@ -9195,7 +9541,7 @@ async function initCommand(options) {
|
|
|
9195
9541
|
if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
9196
9542
|
const stubContent = `# AGENTS.md
|
|
9197
9543
|
|
|
9198
|
-
This project uses AI coding agents configured by [Caliber](https://github.com/
|
|
9544
|
+
This project uses AI coding agents configured by [Caliber](https://github.com/rely-ai-org/caliber).
|
|
9199
9545
|
|
|
9200
9546
|
${agentRefs.join(" ")}
|
|
9201
9547
|
`;
|
|
@@ -9306,9 +9652,9 @@ ${agentRefs.join(" ")}
|
|
|
9306
9652
|
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9307
9653
|
}
|
|
9308
9654
|
}
|
|
9309
|
-
console.log(chalk14.bold.green("\n
|
|
9310
|
-
console.log(chalk14.dim(" Your AI
|
|
9311
|
-
console.log(chalk14.dim("
|
|
9655
|
+
console.log(chalk14.bold.green("\n Caliber is set up!"));
|
|
9656
|
+
console.log(chalk14.dim(" Your AI agent configs will now stay in sync with your codebase automatically."));
|
|
9657
|
+
console.log(chalk14.dim(" Every commit refreshes configs for all your agents. All changes are backed up.\n"));
|
|
9312
9658
|
const done = chalk14.green("\u2713");
|
|
9313
9659
|
const skip = chalk14.dim("\u2013");
|
|
9314
9660
|
console.log(chalk14.bold(" What was configured:\n"));
|
|
@@ -9336,9 +9682,9 @@ ${agentRefs.join(" ")}
|
|
|
9336
9682
|
}
|
|
9337
9683
|
if (report) {
|
|
9338
9684
|
report.markStep("Finished");
|
|
9339
|
-
const reportPath =
|
|
9685
|
+
const reportPath = path26.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9340
9686
|
report.write(reportPath);
|
|
9341
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
9687
|
+
console.log(chalk14.dim(` Debug report written to ${path26.relative(process.cwd(), reportPath)}
|
|
9342
9688
|
`));
|
|
9343
9689
|
}
|
|
9344
9690
|
}
|
|
@@ -9377,7 +9723,7 @@ function undoCommand() {
|
|
|
9377
9723
|
|
|
9378
9724
|
// src/commands/status.ts
|
|
9379
9725
|
import chalk16 from "chalk";
|
|
9380
|
-
import
|
|
9726
|
+
import fs34 from "fs";
|
|
9381
9727
|
init_config();
|
|
9382
9728
|
init_resolve_caliber();
|
|
9383
9729
|
async function statusCommand(options) {
|
|
@@ -9406,7 +9752,7 @@ async function statusCommand(options) {
|
|
|
9406
9752
|
}
|
|
9407
9753
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9408
9754
|
for (const entry of manifest.entries) {
|
|
9409
|
-
const exists =
|
|
9755
|
+
const exists = fs34.existsSync(entry.path);
|
|
9410
9756
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9411
9757
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9412
9758
|
}
|
|
@@ -9563,9 +9909,9 @@ async function regenerateCommand(options) {
|
|
|
9563
9909
|
}
|
|
9564
9910
|
|
|
9565
9911
|
// src/commands/score.ts
|
|
9566
|
-
import
|
|
9912
|
+
import fs35 from "fs";
|
|
9567
9913
|
import os7 from "os";
|
|
9568
|
-
import
|
|
9914
|
+
import path27 from "path";
|
|
9569
9915
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9570
9916
|
import chalk18 from "chalk";
|
|
9571
9917
|
init_resolve_caliber();
|
|
@@ -9573,12 +9919,12 @@ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS
|
|
|
9573
9919
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9574
9920
|
function scoreBaseRef(ref, target) {
|
|
9575
9921
|
if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
|
|
9576
|
-
const tmpDir =
|
|
9922
|
+
const tmpDir = fs35.mkdtempSync(path27.join(os7.tmpdir(), "caliber-compare-"));
|
|
9577
9923
|
try {
|
|
9578
9924
|
for (const file of CONFIG_FILES) {
|
|
9579
9925
|
try {
|
|
9580
9926
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9581
|
-
|
|
9927
|
+
fs35.writeFileSync(path27.join(tmpDir, file), content);
|
|
9582
9928
|
} catch {
|
|
9583
9929
|
}
|
|
9584
9930
|
}
|
|
@@ -9586,10 +9932,10 @@ function scoreBaseRef(ref, target) {
|
|
|
9586
9932
|
try {
|
|
9587
9933
|
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
|
|
9588
9934
|
for (const file of files) {
|
|
9589
|
-
const filePath =
|
|
9590
|
-
|
|
9935
|
+
const filePath = path27.join(tmpDir, file);
|
|
9936
|
+
fs35.mkdirSync(path27.dirname(filePath), { recursive: true });
|
|
9591
9937
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9592
|
-
|
|
9938
|
+
fs35.writeFileSync(filePath, content);
|
|
9593
9939
|
}
|
|
9594
9940
|
} catch {
|
|
9595
9941
|
}
|
|
@@ -9599,7 +9945,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9599
9945
|
} catch {
|
|
9600
9946
|
return null;
|
|
9601
9947
|
} finally {
|
|
9602
|
-
|
|
9948
|
+
fs35.rmSync(tmpDir, { recursive: true, force: true });
|
|
9603
9949
|
}
|
|
9604
9950
|
}
|
|
9605
9951
|
async function scoreCommand(options) {
|
|
@@ -9661,13 +10007,13 @@ async function scoreCommand(options) {
|
|
|
9661
10007
|
}
|
|
9662
10008
|
|
|
9663
10009
|
// src/commands/refresh.ts
|
|
9664
|
-
import
|
|
9665
|
-
import
|
|
10010
|
+
import fs39 from "fs";
|
|
10011
|
+
import path31 from "path";
|
|
9666
10012
|
import chalk19 from "chalk";
|
|
9667
10013
|
import ora6 from "ora";
|
|
9668
10014
|
|
|
9669
10015
|
// src/lib/git-diff.ts
|
|
9670
|
-
import { execSync as
|
|
10016
|
+
import { execSync as execSync15 } from "child_process";
|
|
9671
10017
|
var MAX_DIFF_BYTES = 1e5;
|
|
9672
10018
|
var DOC_PATTERNS = [
|
|
9673
10019
|
"CLAUDE.md",
|
|
@@ -9682,7 +10028,7 @@ function excludeArgs() {
|
|
|
9682
10028
|
}
|
|
9683
10029
|
function safeExec(cmd) {
|
|
9684
10030
|
try {
|
|
9685
|
-
return
|
|
10031
|
+
return execSync15(cmd, {
|
|
9686
10032
|
encoding: "utf-8",
|
|
9687
10033
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9688
10034
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -9740,48 +10086,48 @@ function collectDiff(lastSha) {
|
|
|
9740
10086
|
}
|
|
9741
10087
|
|
|
9742
10088
|
// src/writers/refresh.ts
|
|
9743
|
-
import
|
|
9744
|
-
import
|
|
10089
|
+
import fs36 from "fs";
|
|
10090
|
+
import path28 from "path";
|
|
9745
10091
|
function writeRefreshDocs(docs) {
|
|
9746
10092
|
const written = [];
|
|
9747
10093
|
if (docs.claudeMd) {
|
|
9748
|
-
|
|
10094
|
+
fs36.writeFileSync("CLAUDE.md", appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.claudeMd))));
|
|
9749
10095
|
written.push("CLAUDE.md");
|
|
9750
10096
|
}
|
|
9751
10097
|
if (docs.readmeMd) {
|
|
9752
|
-
|
|
10098
|
+
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
9753
10099
|
written.push("README.md");
|
|
9754
10100
|
}
|
|
9755
10101
|
if (docs.cursorrules) {
|
|
9756
|
-
|
|
10102
|
+
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
9757
10103
|
written.push(".cursorrules");
|
|
9758
10104
|
}
|
|
9759
10105
|
if (docs.cursorRules) {
|
|
9760
|
-
const rulesDir =
|
|
9761
|
-
if (!
|
|
10106
|
+
const rulesDir = path28.join(".cursor", "rules");
|
|
10107
|
+
if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
|
|
9762
10108
|
for (const rule of docs.cursorRules) {
|
|
9763
|
-
|
|
10109
|
+
fs36.writeFileSync(path28.join(rulesDir, rule.filename), rule.content);
|
|
9764
10110
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
9765
10111
|
}
|
|
9766
10112
|
}
|
|
9767
10113
|
if (docs.claudeSkills) {
|
|
9768
|
-
const skillsDir =
|
|
9769
|
-
if (!
|
|
10114
|
+
const skillsDir = path28.join(".claude", "skills");
|
|
10115
|
+
if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
|
|
9770
10116
|
for (const skill of docs.claudeSkills) {
|
|
9771
|
-
|
|
10117
|
+
fs36.writeFileSync(path28.join(skillsDir, skill.filename), skill.content);
|
|
9772
10118
|
written.push(`.claude/skills/${skill.filename}`);
|
|
9773
10119
|
}
|
|
9774
10120
|
}
|
|
9775
10121
|
if (docs.copilotInstructions) {
|
|
9776
|
-
|
|
9777
|
-
|
|
10122
|
+
fs36.mkdirSync(".github", { recursive: true });
|
|
10123
|
+
fs36.writeFileSync(path28.join(".github", "copilot-instructions.md"), appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions))));
|
|
9778
10124
|
written.push(".github/copilot-instructions.md");
|
|
9779
10125
|
}
|
|
9780
10126
|
if (docs.copilotInstructionFiles) {
|
|
9781
|
-
const instructionsDir =
|
|
9782
|
-
|
|
10127
|
+
const instructionsDir = path28.join(".github", "instructions");
|
|
10128
|
+
fs36.mkdirSync(instructionsDir, { recursive: true });
|
|
9783
10129
|
for (const file of docs.copilotInstructionFiles) {
|
|
9784
|
-
|
|
10130
|
+
fs36.writeFileSync(path28.join(instructionsDir, file.filename), file.content);
|
|
9785
10131
|
written.push(`.github/instructions/${file.filename}`);
|
|
9786
10132
|
}
|
|
9787
10133
|
}
|
|
@@ -9867,8 +10213,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
9867
10213
|
}
|
|
9868
10214
|
|
|
9869
10215
|
// src/learner/writer.ts
|
|
9870
|
-
import
|
|
9871
|
-
import
|
|
10216
|
+
import fs37 from "fs";
|
|
10217
|
+
import path29 from "path";
|
|
9872
10218
|
|
|
9873
10219
|
// src/learner/utils.ts
|
|
9874
10220
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -9901,13 +10247,13 @@ var LEARNINGS_FILE = "CALIBER_LEARNINGS.md";
|
|
|
9901
10247
|
var LEARNINGS_HEADER = `# Caliber Learnings
|
|
9902
10248
|
|
|
9903
10249
|
Accumulated patterns and anti-patterns from development sessions.
|
|
9904
|
-
Auto-managed by [caliber](https://github.com/
|
|
10250
|
+
Auto-managed by [caliber](https://github.com/rely-ai-org/caliber) \u2014 do not edit manually.
|
|
9905
10251
|
|
|
9906
10252
|
`;
|
|
9907
10253
|
var PERSONAL_LEARNINGS_HEADER = `# Personal Learnings
|
|
9908
10254
|
|
|
9909
10255
|
Developer-specific patterns and preferences.
|
|
9910
|
-
Auto-managed by [caliber](https://github.com/
|
|
10256
|
+
Auto-managed by [caliber](https://github.com/rely-ai-org/caliber) \u2014 do not edit manually.
|
|
9911
10257
|
|
|
9912
10258
|
`;
|
|
9913
10259
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
@@ -9985,20 +10331,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9985
10331
|
}
|
|
9986
10332
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
9987
10333
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
9988
|
-
|
|
9989
|
-
if (mode)
|
|
10334
|
+
fs37.writeFileSync(filePath, header + merged + "\n");
|
|
10335
|
+
if (mode) fs37.chmodSync(filePath, mode);
|
|
9990
10336
|
return { newCount, newItems };
|
|
9991
10337
|
}
|
|
9992
10338
|
function writeLearnedSection(content) {
|
|
9993
10339
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
9994
10340
|
}
|
|
9995
10341
|
function writeLearnedSkill(skill) {
|
|
9996
|
-
const skillDir =
|
|
9997
|
-
if (!
|
|
9998
|
-
const skillPath =
|
|
9999
|
-
if (!skill.isNew &&
|
|
10000
|
-
const existing =
|
|
10001
|
-
|
|
10342
|
+
const skillDir = path29.join(".claude", "skills", skill.name);
|
|
10343
|
+
if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
|
|
10344
|
+
const skillPath = path29.join(skillDir, "SKILL.md");
|
|
10345
|
+
if (!skill.isNew && fs37.existsSync(skillPath)) {
|
|
10346
|
+
const existing = fs37.readFileSync(skillPath, "utf-8");
|
|
10347
|
+
fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
10002
10348
|
} else {
|
|
10003
10349
|
const frontmatter = [
|
|
10004
10350
|
"---",
|
|
@@ -10007,12 +10353,12 @@ function writeLearnedSkill(skill) {
|
|
|
10007
10353
|
"---",
|
|
10008
10354
|
""
|
|
10009
10355
|
].join("\n");
|
|
10010
|
-
|
|
10356
|
+
fs37.writeFileSync(skillPath, frontmatter + skill.content);
|
|
10011
10357
|
}
|
|
10012
10358
|
return skillPath;
|
|
10013
10359
|
}
|
|
10014
10360
|
function writePersonalLearnedSection(content) {
|
|
10015
|
-
if (!
|
|
10361
|
+
if (!fs37.existsSync(AUTH_DIR)) fs37.mkdirSync(AUTH_DIR, { recursive: true });
|
|
10016
10362
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
10017
10363
|
}
|
|
10018
10364
|
function addLearning(bullet, scope = "project") {
|
|
@@ -10025,55 +10371,65 @@ function addLearning(bullet, scope = "project") {
|
|
|
10025
10371
|
return { file: LEARNINGS_FILE, added: result.newCount > 0 };
|
|
10026
10372
|
}
|
|
10027
10373
|
function readPersonalLearnings() {
|
|
10028
|
-
if (!
|
|
10029
|
-
const content =
|
|
10374
|
+
if (!fs37.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
10375
|
+
const content = fs37.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
10030
10376
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10031
10377
|
return bullets || null;
|
|
10032
10378
|
}
|
|
10033
10379
|
function readLearnedSection() {
|
|
10034
|
-
if (
|
|
10035
|
-
const content2 =
|
|
10380
|
+
if (fs37.existsSync(LEARNINGS_FILE)) {
|
|
10381
|
+
const content2 = fs37.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
10036
10382
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10037
10383
|
return bullets || null;
|
|
10038
10384
|
}
|
|
10039
10385
|
const claudeMdPath = "CLAUDE.md";
|
|
10040
|
-
if (!
|
|
10041
|
-
const content =
|
|
10386
|
+
if (!fs37.existsSync(claudeMdPath)) return null;
|
|
10387
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10042
10388
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10043
10389
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10044
10390
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
10045
10391
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
10046
10392
|
}
|
|
10047
10393
|
function migrateInlineLearnings() {
|
|
10048
|
-
if (
|
|
10394
|
+
if (fs37.existsSync(LEARNINGS_FILE)) return false;
|
|
10049
10395
|
const claudeMdPath = "CLAUDE.md";
|
|
10050
|
-
if (!
|
|
10051
|
-
const content =
|
|
10396
|
+
if (!fs37.existsSync(claudeMdPath)) return false;
|
|
10397
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10052
10398
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10053
10399
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10054
10400
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
10055
10401
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
10056
10402
|
if (!section) return false;
|
|
10057
|
-
|
|
10403
|
+
fs37.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
10058
10404
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
10059
|
-
|
|
10405
|
+
fs37.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
10060
10406
|
return true;
|
|
10061
10407
|
}
|
|
10062
10408
|
|
|
10063
10409
|
// src/commands/refresh.ts
|
|
10064
10410
|
init_config();
|
|
10065
10411
|
init_resolve_caliber();
|
|
10412
|
+
init_builtin_skills();
|
|
10413
|
+
function detectSyncedAgents(writtenFiles) {
|
|
10414
|
+
const agents = [];
|
|
10415
|
+
const joined = writtenFiles.join(" ");
|
|
10416
|
+
if (joined.includes("CLAUDE.md") || joined.includes(".claude/")) agents.push("Claude Code");
|
|
10417
|
+
if (joined.includes(".cursor/") || joined.includes(".cursorrules")) agents.push("Cursor");
|
|
10418
|
+
if (joined.includes("copilot-instructions") || joined.includes(".github/instructions/")) agents.push("Copilot");
|
|
10419
|
+
if (joined.includes("AGENTS.md") || joined.includes(".agents/")) agents.push("Codex");
|
|
10420
|
+
return agents;
|
|
10421
|
+
}
|
|
10066
10422
|
function log2(quiet, ...args) {
|
|
10067
10423
|
if (!quiet) console.log(...args);
|
|
10068
10424
|
}
|
|
10069
10425
|
function discoverGitRepos(parentDir) {
|
|
10070
10426
|
const repos = [];
|
|
10071
10427
|
try {
|
|
10072
|
-
const entries =
|
|
10428
|
+
const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
|
|
10073
10429
|
for (const entry of entries) {
|
|
10074
10430
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
10075
|
-
const childPath =
|
|
10076
|
-
if (
|
|
10431
|
+
const childPath = path31.join(parentDir, entry.name);
|
|
10432
|
+
if (fs39.existsSync(path31.join(childPath, ".git"))) {
|
|
10077
10433
|
repos.push(childPath);
|
|
10078
10434
|
}
|
|
10079
10435
|
}
|
|
@@ -10159,9 +10515,9 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10159
10515
|
const filesToWrite = response.docsUpdated || [];
|
|
10160
10516
|
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
10161
10517
|
for (const filePath of filesToWrite) {
|
|
10162
|
-
const fullPath =
|
|
10518
|
+
const fullPath = path31.resolve(repoDir, filePath);
|
|
10163
10519
|
try {
|
|
10164
|
-
preRefreshContents.set(filePath,
|
|
10520
|
+
preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
|
|
10165
10521
|
} catch {
|
|
10166
10522
|
preRefreshContents.set(filePath, null);
|
|
10167
10523
|
}
|
|
@@ -10171,14 +10527,14 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10171
10527
|
const postScore = computeLocalScore(repoDir, targetAgent);
|
|
10172
10528
|
if (postScore.score < preScore.score) {
|
|
10173
10529
|
for (const [filePath, content] of preRefreshContents) {
|
|
10174
|
-
const fullPath =
|
|
10530
|
+
const fullPath = path31.resolve(repoDir, filePath);
|
|
10175
10531
|
if (content === null) {
|
|
10176
10532
|
try {
|
|
10177
|
-
|
|
10533
|
+
fs39.unlinkSync(fullPath);
|
|
10178
10534
|
} catch {
|
|
10179
10535
|
}
|
|
10180
10536
|
} else {
|
|
10181
|
-
|
|
10537
|
+
fs39.writeFileSync(fullPath, content);
|
|
10182
10538
|
}
|
|
10183
10539
|
}
|
|
10184
10540
|
spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
|
|
@@ -10190,8 +10546,18 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10190
10546
|
}
|
|
10191
10547
|
recordScore(postScore, "refresh");
|
|
10192
10548
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
10549
|
+
const fileChangesMap = new Map(
|
|
10550
|
+
(response.fileChanges || []).map((fc) => [fc.file, fc.description])
|
|
10551
|
+
);
|
|
10193
10552
|
for (const file of written) {
|
|
10194
|
-
|
|
10553
|
+
const desc = fileChangesMap.get(file);
|
|
10554
|
+
const suffix = desc ? chalk19.dim(` \u2014 ${desc}`) : "";
|
|
10555
|
+
log2(quiet, ` ${chalk19.green("\u2713")} ${file}${suffix}`);
|
|
10556
|
+
}
|
|
10557
|
+
const agents = detectSyncedAgents(written);
|
|
10558
|
+
if (agents.length > 1) {
|
|
10559
|
+
log2(quiet, chalk19.cyan(`
|
|
10560
|
+
${agents.length} agent formats in sync (${agents.join(", ")})`));
|
|
10195
10561
|
}
|
|
10196
10562
|
if (response.changesSummary) {
|
|
10197
10563
|
log2(quiet, chalk19.dim(`
|
|
@@ -10233,7 +10599,7 @@ async function refreshCommand(options) {
|
|
|
10233
10599
|
`));
|
|
10234
10600
|
const originalDir = process.cwd();
|
|
10235
10601
|
for (const repo of repos) {
|
|
10236
|
-
const repoName =
|
|
10602
|
+
const repoName = path31.basename(repo);
|
|
10237
10603
|
try {
|
|
10238
10604
|
process.chdir(repo);
|
|
10239
10605
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -10255,150 +10621,6 @@ async function refreshCommand(options) {
|
|
|
10255
10621
|
// src/commands/hooks.ts
|
|
10256
10622
|
import chalk20 from "chalk";
|
|
10257
10623
|
import fs40 from "fs";
|
|
10258
|
-
|
|
10259
|
-
// src/lib/hooks.ts
|
|
10260
|
-
init_resolve_caliber();
|
|
10261
|
-
import fs39 from "fs";
|
|
10262
|
-
import path31 from "path";
|
|
10263
|
-
import { execSync as execSync15 } from "child_process";
|
|
10264
|
-
var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
|
|
10265
|
-
var REFRESH_TAIL = "refresh --quiet";
|
|
10266
|
-
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
10267
|
-
function getHookCommand() {
|
|
10268
|
-
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
10269
|
-
}
|
|
10270
|
-
function readSettings2() {
|
|
10271
|
-
if (!fs39.existsSync(SETTINGS_PATH2)) return {};
|
|
10272
|
-
try {
|
|
10273
|
-
return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
10274
|
-
} catch {
|
|
10275
|
-
return {};
|
|
10276
|
-
}
|
|
10277
|
-
}
|
|
10278
|
-
function writeSettings2(settings) {
|
|
10279
|
-
const dir = path31.dirname(SETTINGS_PATH2);
|
|
10280
|
-
if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
|
|
10281
|
-
fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
10282
|
-
}
|
|
10283
|
-
function findHookIndex(sessionEnd) {
|
|
10284
|
-
return sessionEnd.findIndex(
|
|
10285
|
-
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
10286
|
-
);
|
|
10287
|
-
}
|
|
10288
|
-
function isHookInstalled() {
|
|
10289
|
-
const settings = readSettings2();
|
|
10290
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10291
|
-
if (!Array.isArray(sessionEnd)) return false;
|
|
10292
|
-
return findHookIndex(sessionEnd) !== -1;
|
|
10293
|
-
}
|
|
10294
|
-
function installHook() {
|
|
10295
|
-
const settings = readSettings2();
|
|
10296
|
-
if (!settings.hooks) settings.hooks = {};
|
|
10297
|
-
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
10298
|
-
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
10299
|
-
return { installed: false, alreadyInstalled: true };
|
|
10300
|
-
}
|
|
10301
|
-
settings.hooks.SessionEnd.push({
|
|
10302
|
-
matcher: "",
|
|
10303
|
-
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
10304
|
-
});
|
|
10305
|
-
writeSettings2(settings);
|
|
10306
|
-
return { installed: true, alreadyInstalled: false };
|
|
10307
|
-
}
|
|
10308
|
-
function removeHook() {
|
|
10309
|
-
const settings = readSettings2();
|
|
10310
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10311
|
-
if (!Array.isArray(sessionEnd)) {
|
|
10312
|
-
return { removed: false, notFound: true };
|
|
10313
|
-
}
|
|
10314
|
-
const idx = findHookIndex(sessionEnd);
|
|
10315
|
-
if (idx === -1) {
|
|
10316
|
-
return { removed: false, notFound: true };
|
|
10317
|
-
}
|
|
10318
|
-
sessionEnd.splice(idx, 1);
|
|
10319
|
-
if (sessionEnd.length === 0) {
|
|
10320
|
-
delete settings.hooks.SessionEnd;
|
|
10321
|
-
}
|
|
10322
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
10323
|
-
delete settings.hooks;
|
|
10324
|
-
}
|
|
10325
|
-
writeSettings2(settings);
|
|
10326
|
-
return { removed: true, notFound: false };
|
|
10327
|
-
}
|
|
10328
|
-
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
10329
|
-
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
10330
|
-
function getPrecommitBlock() {
|
|
10331
|
-
const bin = resolveCaliber();
|
|
10332
|
-
const npx = isNpxResolution();
|
|
10333
|
-
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
10334
|
-
const invoke = npx ? bin : `"${bin}"`;
|
|
10335
|
-
return `${PRECOMMIT_START}
|
|
10336
|
-
if ${guard}; then
|
|
10337
|
-
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
10338
|
-
${invoke} refresh 2>/dev/null || true
|
|
10339
|
-
${invoke} learn finalize 2>/dev/null || true
|
|
10340
|
-
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
10341
|
-
fi
|
|
10342
|
-
${PRECOMMIT_END}`;
|
|
10343
|
-
}
|
|
10344
|
-
function getGitHooksDir() {
|
|
10345
|
-
try {
|
|
10346
|
-
const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10347
|
-
return path31.join(gitDir, "hooks");
|
|
10348
|
-
} catch {
|
|
10349
|
-
return null;
|
|
10350
|
-
}
|
|
10351
|
-
}
|
|
10352
|
-
function getPreCommitPath() {
|
|
10353
|
-
const hooksDir = getGitHooksDir();
|
|
10354
|
-
return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
|
|
10355
|
-
}
|
|
10356
|
-
function isPreCommitHookInstalled() {
|
|
10357
|
-
const hookPath = getPreCommitPath();
|
|
10358
|
-
if (!hookPath || !fs39.existsSync(hookPath)) return false;
|
|
10359
|
-
const content = fs39.readFileSync(hookPath, "utf-8");
|
|
10360
|
-
return content.includes(PRECOMMIT_START);
|
|
10361
|
-
}
|
|
10362
|
-
function installPreCommitHook() {
|
|
10363
|
-
if (isPreCommitHookInstalled()) {
|
|
10364
|
-
return { installed: false, alreadyInstalled: true };
|
|
10365
|
-
}
|
|
10366
|
-
const hookPath = getPreCommitPath();
|
|
10367
|
-
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
10368
|
-
const hooksDir = path31.dirname(hookPath);
|
|
10369
|
-
if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
|
|
10370
|
-
let content = "";
|
|
10371
|
-
if (fs39.existsSync(hookPath)) {
|
|
10372
|
-
content = fs39.readFileSync(hookPath, "utf-8");
|
|
10373
|
-
if (!content.endsWith("\n")) content += "\n";
|
|
10374
|
-
content += "\n" + getPrecommitBlock() + "\n";
|
|
10375
|
-
} else {
|
|
10376
|
-
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
10377
|
-
}
|
|
10378
|
-
fs39.writeFileSync(hookPath, content);
|
|
10379
|
-
fs39.chmodSync(hookPath, 493);
|
|
10380
|
-
return { installed: true, alreadyInstalled: false };
|
|
10381
|
-
}
|
|
10382
|
-
function removePreCommitHook() {
|
|
10383
|
-
const hookPath = getPreCommitPath();
|
|
10384
|
-
if (!hookPath || !fs39.existsSync(hookPath)) {
|
|
10385
|
-
return { removed: false, notFound: true };
|
|
10386
|
-
}
|
|
10387
|
-
let content = fs39.readFileSync(hookPath, "utf-8");
|
|
10388
|
-
if (!content.includes(PRECOMMIT_START)) {
|
|
10389
|
-
return { removed: false, notFound: true };
|
|
10390
|
-
}
|
|
10391
|
-
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
10392
|
-
content = content.replace(regex, "\n");
|
|
10393
|
-
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
10394
|
-
fs39.unlinkSync(hookPath);
|
|
10395
|
-
} else {
|
|
10396
|
-
fs39.writeFileSync(hookPath, content);
|
|
10397
|
-
}
|
|
10398
|
-
return { removed: true, notFound: false };
|
|
10399
|
-
}
|
|
10400
|
-
|
|
10401
|
-
// src/commands/hooks.ts
|
|
10402
10624
|
var HOOKS = [
|
|
10403
10625
|
{
|
|
10404
10626
|
id: "session-end",
|