@rely-ai/caliber 1.31.0 → 1.32.0-dev.1774817730
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1673 -1113
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -230,21 +230,615 @@ var init_types = __esm({
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
+
// src/writers/pre-commit-block.ts
|
|
234
|
+
var pre_commit_block_exports = {};
|
|
235
|
+
__export(pre_commit_block_exports, {
|
|
236
|
+
appendLearningsBlock: () => appendLearningsBlock,
|
|
237
|
+
appendManagedBlocks: () => appendManagedBlocks,
|
|
238
|
+
appendPreCommitBlock: () => appendPreCommitBlock,
|
|
239
|
+
appendSyncBlock: () => appendSyncBlock,
|
|
240
|
+
getCursorLearningsRule: () => getCursorLearningsRule,
|
|
241
|
+
getCursorPreCommitRule: () => getCursorPreCommitRule,
|
|
242
|
+
getCursorSetupRule: () => getCursorSetupRule,
|
|
243
|
+
getCursorSyncRule: () => getCursorSyncRule,
|
|
244
|
+
hasLearningsBlock: () => hasLearningsBlock,
|
|
245
|
+
hasPreCommitBlock: () => hasPreCommitBlock,
|
|
246
|
+
hasSyncBlock: () => hasSyncBlock,
|
|
247
|
+
stripManagedBlocks: () => stripManagedBlocks
|
|
248
|
+
});
|
|
249
|
+
function getCaliberNotFoundFallback(platform, bin) {
|
|
250
|
+
switch (platform) {
|
|
251
|
+
case "claude":
|
|
252
|
+
return `If \`${bin}\` is not found, tell the user: "This project uses Caliber for agent config sync. Run /setup-caliber to get set up."`;
|
|
253
|
+
case "codex":
|
|
254
|
+
return `If \`${bin}\` is not found, read \`.agents/skills/setup-caliber/SKILL.md\` and follow its instructions to install Caliber.`;
|
|
255
|
+
case "copilot":
|
|
256
|
+
return `If \`${bin}\` is not found, tell the developer to set up Caliber by running \`/setup-caliber\` in Claude Code or Cursor. Alternatively, they can run these commands in their terminal:
|
|
257
|
+
\`\`\`
|
|
258
|
+
npx @rely-ai/caliber hooks --install
|
|
259
|
+
npx @rely-ai/caliber refresh
|
|
260
|
+
\`\`\``;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function getPreCommitBlock(platform = "claude") {
|
|
264
|
+
const bin = resolveCaliber();
|
|
265
|
+
return `${BLOCK_START}
|
|
266
|
+
## Before Committing
|
|
267
|
+
|
|
268
|
+
**IMPORTANT:** Before every git commit, you MUST ensure Caliber syncs agent configs with the latest code changes.
|
|
269
|
+
|
|
270
|
+
First, check if the pre-commit hook is already installed:
|
|
271
|
+
\`\`\`bash
|
|
272
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
- If **hook-active**: the hook handles sync automatically \u2014 just commit normally. Tell the user: "Caliber will sync your agent configs automatically via the pre-commit hook."
|
|
276
|
+
- If **no-hook**: run Caliber manually before committing:
|
|
277
|
+
1. Tell the user: "Caliber: Syncing agent configs with your latest changes..."
|
|
278
|
+
2. Run: \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\`
|
|
279
|
+
3. After it completes, briefly tell the user what Caliber updated. Then proceed with the commit.
|
|
280
|
+
|
|
281
|
+
${getCaliberNotFoundFallback(platform, bin)}
|
|
282
|
+
${BLOCK_END}`;
|
|
283
|
+
}
|
|
284
|
+
function getCursorRuleContent() {
|
|
285
|
+
const bin = resolveCaliber();
|
|
286
|
+
return `---
|
|
287
|
+
description: Run Caliber sync before git commits to keep agent configs up to date
|
|
288
|
+
alwaysApply: true
|
|
289
|
+
---
|
|
290
|
+
Before every git commit, check if the Caliber pre-commit hook is installed:
|
|
291
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "hook-active" || echo "no-hook"\`
|
|
292
|
+
|
|
293
|
+
If hook-active: just commit normally \u2014 Caliber syncs automatically via the hook. Tell the user: "Caliber will sync your agent configs automatically."
|
|
294
|
+
If no-hook: run \`${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null\` before committing, and tell the user what was updated.
|
|
295
|
+
|
|
296
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
function hasPreCommitBlock(content) {
|
|
300
|
+
return content.includes(BLOCK_START);
|
|
301
|
+
}
|
|
302
|
+
function appendPreCommitBlock(content, platform = "claude") {
|
|
303
|
+
if (hasPreCommitBlock(content)) return content;
|
|
304
|
+
const trimmed = content.trimEnd();
|
|
305
|
+
return trimmed + "\n\n" + getPreCommitBlock(platform) + "\n";
|
|
306
|
+
}
|
|
307
|
+
function getCursorPreCommitRule() {
|
|
308
|
+
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
309
|
+
}
|
|
310
|
+
function hasLearningsBlock(content) {
|
|
311
|
+
return content.includes(LEARNINGS_BLOCK_START);
|
|
312
|
+
}
|
|
313
|
+
function appendLearningsBlock(content) {
|
|
314
|
+
if (hasLearningsBlock(content)) return content;
|
|
315
|
+
const trimmed = content.trimEnd();
|
|
316
|
+
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
317
|
+
}
|
|
318
|
+
function getCursorLearningsRule() {
|
|
319
|
+
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
320
|
+
}
|
|
321
|
+
function getSyncSetupInstruction(platform) {
|
|
322
|
+
switch (platform) {
|
|
323
|
+
case "claude":
|
|
324
|
+
return `If the pre-commit hook is not set up, run \`/setup-caliber\` to configure everything automatically.`;
|
|
325
|
+
case "codex":
|
|
326
|
+
return `If the pre-commit hook is not set up, read \`.agents/skills/setup-caliber/SKILL.md\` and follow the setup instructions.`;
|
|
327
|
+
case "copilot":
|
|
328
|
+
return `If the pre-commit hook is not set up, the developer should run \`/setup-caliber\` in Claude Code or Cursor for automated setup. Alternatively, run in terminal:
|
|
329
|
+
\`\`\`bash
|
|
330
|
+
npx @rely-ai/caliber hooks --install
|
|
331
|
+
npx @rely-ai/caliber refresh
|
|
332
|
+
git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
333
|
+
\`\`\``;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function getSyncBlock(platform = "claude") {
|
|
337
|
+
const bin = resolveCaliber();
|
|
338
|
+
return `${SYNC_BLOCK_START}
|
|
339
|
+
## Context Sync
|
|
340
|
+
|
|
341
|
+
This project uses [Caliber](https://github.com/caliber-ai-org/ai-setup) to keep AI agent configs in sync across Claude Code, Cursor, Copilot, and Codex.
|
|
342
|
+
Configs update automatically before each commit via \`${bin} refresh\`.
|
|
343
|
+
${getSyncSetupInstruction(platform)}
|
|
344
|
+
${SYNC_BLOCK_END}`;
|
|
345
|
+
}
|
|
346
|
+
function hasSyncBlock(content) {
|
|
347
|
+
return content.includes(SYNC_BLOCK_START);
|
|
348
|
+
}
|
|
349
|
+
function appendSyncBlock(content, platform = "claude") {
|
|
350
|
+
if (hasSyncBlock(content)) return content;
|
|
351
|
+
const trimmed = content.trimEnd();
|
|
352
|
+
return trimmed + "\n\n" + getSyncBlock(platform) + "\n";
|
|
353
|
+
}
|
|
354
|
+
function appendManagedBlocks(content, platform = "claude") {
|
|
355
|
+
return appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(content, platform)), platform);
|
|
356
|
+
}
|
|
357
|
+
function getCursorSyncContent() {
|
|
358
|
+
const bin = resolveCaliber();
|
|
359
|
+
return `---
|
|
360
|
+
description: This project uses Caliber for automatic AI agent context sync
|
|
361
|
+
alwaysApply: true
|
|
362
|
+
---
|
|
363
|
+
This project uses Caliber to keep AI agent configs (CLAUDE.md, Cursor rules, Copilot instructions)
|
|
364
|
+
in sync with the codebase automatically. Before each commit, run \`${bin} refresh\` to sync configs.
|
|
365
|
+
If \`${bin}\` is not found, read the setup-caliber skill from .cursor/skills/setup-caliber/SKILL.md and follow its instructions to install Caliber.
|
|
366
|
+
`;
|
|
367
|
+
}
|
|
368
|
+
function getCursorSyncRule() {
|
|
369
|
+
return { filename: CURSOR_SYNC_FILENAME, content: getCursorSyncContent() };
|
|
370
|
+
}
|
|
371
|
+
function getCursorSetupContent() {
|
|
372
|
+
return `---
|
|
373
|
+
description: Check if Caliber agent config sync is set up and offer installation on new sessions
|
|
374
|
+
alwaysApply: true
|
|
375
|
+
---
|
|
376
|
+
This project uses Caliber to keep AI agent configs in sync automatically.
|
|
377
|
+
At the start of each session, silently check if sync is active:
|
|
378
|
+
|
|
379
|
+
Run: \`grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "SYNC_ACTIVE" || echo "NO_SYNC"\`
|
|
380
|
+
|
|
381
|
+
If SYNC_ACTIVE: No action needed \u2014 Caliber syncs automatically on every commit.
|
|
382
|
+
If NO_SYNC: Tell the user: "This project uses Caliber for automatic agent config sync, but it's not set up on your machine yet. Want me to set it up? It takes about 30 seconds."
|
|
383
|
+
|
|
384
|
+
If the user agrees, read \`.cursor/skills/setup-caliber/SKILL.md\` and follow its instructions step by step.
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
function getCursorSetupRule() {
|
|
388
|
+
return { filename: CURSOR_SETUP_FILENAME, content: getCursorSetupContent() };
|
|
389
|
+
}
|
|
390
|
+
function stripManagedBlocks(content) {
|
|
391
|
+
let result = content;
|
|
392
|
+
for (const [start, end] of MANAGED_BLOCK_PAIRS) {
|
|
393
|
+
const regex = new RegExp(`\\n?${start.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g");
|
|
394
|
+
result = result.replace(regex, "\n");
|
|
395
|
+
}
|
|
396
|
+
return result.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
397
|
+
}
|
|
398
|
+
var BLOCK_START, BLOCK_END, MANAGED_DOC_PATHS, CURSOR_RULE_FILENAME, LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END, LEARNINGS_BLOCK, CURSOR_LEARNINGS_FILENAME, CURSOR_LEARNINGS_CONTENT, SYNC_BLOCK_START, SYNC_BLOCK_END, CURSOR_SYNC_FILENAME, CURSOR_SETUP_FILENAME, MANAGED_BLOCK_PAIRS;
|
|
399
|
+
var init_pre_commit_block = __esm({
|
|
400
|
+
"src/writers/pre-commit-block.ts"() {
|
|
401
|
+
"use strict";
|
|
402
|
+
init_resolve_caliber();
|
|
403
|
+
BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
404
|
+
BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
405
|
+
MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .cursorrules .github/copilot-instructions.md .github/instructions/ AGENTS.md CALIBER_LEARNINGS.md";
|
|
406
|
+
CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
407
|
+
LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
408
|
+
LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
409
|
+
LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
410
|
+
## Session Learnings
|
|
411
|
+
|
|
412
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
413
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
414
|
+
${LEARNINGS_BLOCK_END}`;
|
|
415
|
+
CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
416
|
+
CURSOR_LEARNINGS_CONTENT = `---
|
|
417
|
+
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
418
|
+
alwaysApply: true
|
|
419
|
+
---
|
|
420
|
+
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
421
|
+
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
422
|
+
`;
|
|
423
|
+
SYNC_BLOCK_START = "<!-- caliber:managed:sync -->";
|
|
424
|
+
SYNC_BLOCK_END = "<!-- /caliber:managed:sync -->";
|
|
425
|
+
CURSOR_SYNC_FILENAME = "caliber-sync.mdc";
|
|
426
|
+
CURSOR_SETUP_FILENAME = "caliber-setup.mdc";
|
|
427
|
+
MANAGED_BLOCK_PAIRS = [
|
|
428
|
+
[BLOCK_START, BLOCK_END],
|
|
429
|
+
[LEARNINGS_BLOCK_START, LEARNINGS_BLOCK_END],
|
|
430
|
+
[SYNC_BLOCK_START, SYNC_BLOCK_END]
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// src/lib/builtin-skills.ts
|
|
436
|
+
var builtin_skills_exports = {};
|
|
437
|
+
__export(builtin_skills_exports, {
|
|
438
|
+
BUILTIN_SKILLS: () => BUILTIN_SKILLS,
|
|
439
|
+
BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
|
|
440
|
+
FIND_SKILLS_SKILL: () => FIND_SKILLS_SKILL,
|
|
441
|
+
PLATFORM_CONFIGS: () => PLATFORM_CONFIGS,
|
|
442
|
+
SAVE_LEARNING_SKILL: () => SAVE_LEARNING_SKILL,
|
|
443
|
+
SETUP_CALIBER_SKILL: () => SETUP_CALIBER_SKILL,
|
|
444
|
+
buildSkillContent: () => buildSkillContent,
|
|
445
|
+
ensureBuiltinSkills: () => ensureBuiltinSkills
|
|
446
|
+
});
|
|
447
|
+
import fs17 from "fs";
|
|
448
|
+
import path17 from "path";
|
|
449
|
+
function buildSkillContent(skill) {
|
|
450
|
+
const frontmatter = `---
|
|
451
|
+
name: ${skill.name}
|
|
452
|
+
description: ${skill.description}
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
`;
|
|
456
|
+
return frontmatter + skill.content;
|
|
457
|
+
}
|
|
458
|
+
function getFindSkillsContent() {
|
|
459
|
+
const bin = resolveCaliber();
|
|
460
|
+
return `# Find Skills
|
|
461
|
+
|
|
462
|
+
Search the public skill registry for community-contributed skills
|
|
463
|
+
relevant to the user's current task and install them into this project.
|
|
464
|
+
|
|
465
|
+
## Instructions
|
|
466
|
+
|
|
467
|
+
1. Identify the key technologies, frameworks, or task types from the
|
|
468
|
+
user's request that might have community skills available
|
|
469
|
+
2. Ask the user: "Would you like me to search for community skills
|
|
470
|
+
for [identified technologies]?"
|
|
471
|
+
3. If the user agrees, run:
|
|
472
|
+
\`\`\`bash
|
|
473
|
+
${bin} skills --query "<relevant terms>"
|
|
474
|
+
\`\`\`
|
|
475
|
+
This outputs the top 5 matching skills with scores and descriptions.
|
|
476
|
+
4. Present the results to the user and ask which ones to install
|
|
477
|
+
5. Install the selected skills:
|
|
478
|
+
\`\`\`bash
|
|
479
|
+
${bin} skills --install <slug1>,<slug2>
|
|
480
|
+
\`\`\`
|
|
481
|
+
6. Read the installed SKILL.md files to load them into your current
|
|
482
|
+
context so you can use them immediately in this session
|
|
483
|
+
7. Summarize what was installed and continue with the user's task
|
|
484
|
+
|
|
485
|
+
## Examples
|
|
486
|
+
|
|
487
|
+
User: "let's build a web app using React"
|
|
488
|
+
-> "I notice you want to work with React. Would you like me to search
|
|
489
|
+
for community skills that could help with React development?"
|
|
490
|
+
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
491
|
+
-> Show the user the results, ask which to install
|
|
492
|
+
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
493
|
+
-> Read the installed files and continue
|
|
494
|
+
|
|
495
|
+
User: "help me set up Docker for this project"
|
|
496
|
+
-> "Would you like me to search for Docker-related skills?"
|
|
497
|
+
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
498
|
+
|
|
499
|
+
User: "I need to write tests for this Python ML pipeline"
|
|
500
|
+
-> "Would you like me to find skills for Python ML testing?"
|
|
501
|
+
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
502
|
+
|
|
503
|
+
## When NOT to trigger
|
|
504
|
+
|
|
505
|
+
- The user is working within an already well-configured area
|
|
506
|
+
- You already suggested skills for this technology in this session
|
|
507
|
+
- The user is in the middle of urgent debugging or time-sensitive work
|
|
508
|
+
- The technology is too generic (e.g. just "code" or "programming")
|
|
509
|
+
`;
|
|
510
|
+
}
|
|
511
|
+
function getSaveLearningContent() {
|
|
512
|
+
const bin = resolveCaliber();
|
|
513
|
+
return `# Save Learning
|
|
514
|
+
|
|
515
|
+
Save a user's instruction or preference as a persistent learning that
|
|
516
|
+
will be applied in all future sessions on this project.
|
|
517
|
+
|
|
518
|
+
## Instructions
|
|
519
|
+
|
|
520
|
+
1. Detect when the user gives an instruction to remember, such as:
|
|
521
|
+
- "remember this", "save this", "always do X", "never do Y"
|
|
522
|
+
- "from now on", "going forward", "in this project we..."
|
|
523
|
+
- Any stated convention, preference, or rule
|
|
524
|
+
2. Refine the instruction into a clean, actionable learning bullet with
|
|
525
|
+
an appropriate type prefix:
|
|
526
|
+
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
527
|
+
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
528
|
+
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
529
|
+
- \`**[preference]**\` \u2014 personal/team preferences
|
|
530
|
+
- \`**[context]**\` \u2014 project-specific context
|
|
531
|
+
3. Show the refined learning to the user and ask for confirmation
|
|
532
|
+
4. If confirmed, run:
|
|
533
|
+
\`\`\`bash
|
|
534
|
+
${bin} learn add "<refined learning>"
|
|
535
|
+
\`\`\`
|
|
536
|
+
For personal preferences (not project-level), add \`--personal\`:
|
|
537
|
+
\`\`\`bash
|
|
538
|
+
${bin} learn add --personal "<refined learning>"
|
|
539
|
+
\`\`\`
|
|
540
|
+
5. Stage the learnings file for the next commit:
|
|
541
|
+
\`\`\`bash
|
|
542
|
+
git add CALIBER_LEARNINGS.md
|
|
543
|
+
\`\`\`
|
|
544
|
+
|
|
545
|
+
## Examples
|
|
546
|
+
|
|
547
|
+
User: "when developing features, push to next branch not master, remember it"
|
|
548
|
+
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
549
|
+
-> "I'll save this as a project learning:
|
|
550
|
+
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
551
|
+
Save for future sessions?"
|
|
552
|
+
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
553
|
+
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
554
|
+
|
|
555
|
+
User: "always use bun instead of npm"
|
|
556
|
+
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
557
|
+
-> Confirm and save
|
|
558
|
+
|
|
559
|
+
User: "never use any in TypeScript, use unknown instead"
|
|
560
|
+
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
561
|
+
-> Confirm and save
|
|
562
|
+
|
|
563
|
+
## When NOT to trigger
|
|
564
|
+
|
|
565
|
+
- The user is giving a one-time instruction for the current task only
|
|
566
|
+
- The instruction is too vague to be actionable
|
|
567
|
+
- The user explicitly says "just for now" or "only this time"
|
|
568
|
+
`;
|
|
569
|
+
}
|
|
570
|
+
function getSetupCaliberContent() {
|
|
571
|
+
return `# Setup Caliber
|
|
572
|
+
|
|
573
|
+
Dynamic onboarding for Caliber \u2014 automatic AI agent context sync.
|
|
574
|
+
Run all diagnostic steps below on every invocation to determine what's already
|
|
575
|
+
set up and what still needs to be done.
|
|
576
|
+
|
|
577
|
+
## Instructions
|
|
578
|
+
|
|
579
|
+
Run these checks in order. For each step, check the current state first,
|
|
580
|
+
then only act if something is missing.
|
|
581
|
+
|
|
582
|
+
### Step 1: Check if Caliber is installed
|
|
583
|
+
|
|
584
|
+
\`\`\`bash
|
|
585
|
+
command -v caliber >/dev/null 2>&1 && caliber --version || echo "NOT_INSTALLED"
|
|
586
|
+
\`\`\`
|
|
587
|
+
|
|
588
|
+
- If a version prints \u2192 Caliber is installed globally. Set \`CALIBER="caliber"\` and move to Step 2.
|
|
589
|
+
- If NOT_INSTALLED \u2192 Install it globally (faster for daily use since the pre-commit hook runs on every commit):
|
|
590
|
+
\`\`\`bash
|
|
591
|
+
npm install -g @rely-ai/caliber
|
|
592
|
+
\`\`\`
|
|
593
|
+
Set \`CALIBER="caliber"\`.
|
|
594
|
+
|
|
595
|
+
If npm fails (permissions, no sudo, etc.), fall back to npx:
|
|
596
|
+
\`\`\`bash
|
|
597
|
+
npx @rely-ai/caliber --version 2>/dev/null || echo "NO_NODE"
|
|
598
|
+
\`\`\`
|
|
599
|
+
- If npx works \u2192 Set \`CALIBER="npx @rely-ai/caliber"\`. This works but adds ~500ms per invocation.
|
|
600
|
+
- If NO_NODE \u2192 Tell the user: "Caliber requires Node.js >= 20. Install Node first, then run /setup-caliber again." Stop here.
|
|
601
|
+
|
|
602
|
+
### Step 2: Check if pre-commit hook is installed
|
|
603
|
+
|
|
604
|
+
\`\`\`bash
|
|
605
|
+
grep -q "caliber" .git/hooks/pre-commit 2>/dev/null && echo "HOOK_ACTIVE" || echo "NO_HOOK"
|
|
606
|
+
\`\`\`
|
|
607
|
+
|
|
608
|
+
- If HOOK_ACTIVE \u2192 Tell the user: "Pre-commit hook is active \u2014 configs sync on every commit." Move to Step 3.
|
|
609
|
+
- If NO_HOOK \u2192 Tell the user: "I'll install the pre-commit hook so your agent configs sync automatically on every commit."
|
|
610
|
+
\`\`\`bash
|
|
611
|
+
$CALIBER hooks --install
|
|
612
|
+
\`\`\`
|
|
613
|
+
|
|
614
|
+
### Step 3: Detect agents and check if configs exist
|
|
615
|
+
|
|
616
|
+
First, detect which coding agents are configured in this project:
|
|
617
|
+
\`\`\`bash
|
|
618
|
+
AGENTS=""
|
|
619
|
+
[ -d .claude ] && AGENTS="claude"
|
|
620
|
+
[ -d .cursor ] && AGENTS="\${AGENTS:+$AGENTS,}cursor"
|
|
621
|
+
[ -d .agents ] || [ -f AGENTS.md ] && AGENTS="\${AGENTS:+$AGENTS,}codex"
|
|
622
|
+
[ -f .github/copilot-instructions.md ] && AGENTS="\${AGENTS:+$AGENTS,}github-copilot"
|
|
623
|
+
echo "DETECTED_AGENTS=\${AGENTS:-none}"
|
|
624
|
+
\`\`\`
|
|
625
|
+
|
|
626
|
+
If no agents are detected, ask the user which coding agents they use (Claude Code, Cursor, Codex, GitHub Copilot).
|
|
627
|
+
Build the agent list from their answer as a comma-separated string (e.g. "claude,cursor").
|
|
628
|
+
|
|
629
|
+
Then check if agent configs exist:
|
|
630
|
+
\`\`\`bash
|
|
631
|
+
echo "CLAUDE_MD=$([ -f CLAUDE.md ] && echo exists || echo missing)"
|
|
632
|
+
echo "CURSOR_RULES=$([ -d .cursor/rules ] && ls .cursor/rules/*.mdc 2>/dev/null | wc -l | tr -d ' ' || echo 0)"
|
|
633
|
+
echo "AGENTS_MD=$([ -f AGENTS.md ] && echo exists || echo missing)"
|
|
634
|
+
echo "COPILOT=$([ -f .github/copilot-instructions.md ] && echo exists || echo missing)"
|
|
635
|
+
\`\`\`
|
|
636
|
+
|
|
637
|
+
- If configs exist for the detected agents \u2192 Tell the user which configs are present. Move to Step 4.
|
|
638
|
+
- If configs are missing \u2192 Tell the user: "No agent configs found. I'll generate them now."
|
|
639
|
+
Use the detected or user-selected agent list:
|
|
640
|
+
\`\`\`bash
|
|
641
|
+
$CALIBER init --auto-approve --agent <comma-separated-agents>
|
|
642
|
+
\`\`\`
|
|
643
|
+
For example: \`$CALIBER init --auto-approve --agent claude,cursor\`
|
|
644
|
+
This generates CLAUDE.md, Cursor rules, AGENTS.md, skills, and sync infrastructure for the specified agents.
|
|
645
|
+
|
|
646
|
+
### Step 4: Check if configs are fresh
|
|
647
|
+
|
|
648
|
+
\`\`\`bash
|
|
649
|
+
$CALIBER score --json --quiet 2>/dev/null | head -1
|
|
650
|
+
\`\`\`
|
|
651
|
+
|
|
652
|
+
- If score is 80+ \u2192 Tell the user: "Your configs are in good shape (score: X/100)."
|
|
653
|
+
- If score is below 80 \u2192 Tell the user: "Your configs could be improved (score: X/100). Want me to run a refresh?"
|
|
654
|
+
If yes:
|
|
655
|
+
\`\`\`bash
|
|
656
|
+
$CALIBER refresh
|
|
657
|
+
\`\`\`
|
|
658
|
+
|
|
659
|
+
### Step 5: Ask about team setup
|
|
660
|
+
|
|
661
|
+
Ask the user: "Are you setting up for yourself only, or for your team too?"
|
|
662
|
+
|
|
663
|
+
- If **solo** \u2192 Continue with solo setup:
|
|
664
|
+
|
|
665
|
+
Check if session learning is enabled:
|
|
666
|
+
\`\`\`bash
|
|
667
|
+
$CALIBER learn status 2>/dev/null | head -3
|
|
668
|
+
\`\`\`
|
|
669
|
+
- If learning is already enabled \u2192 note it in the summary.
|
|
670
|
+
- If not enabled \u2192 ask the user: "Caliber can learn from your coding sessions \u2014 when you correct a mistake or fix a pattern, it remembers for next time. Enable session learning?"
|
|
671
|
+
If yes:
|
|
672
|
+
\`\`\`bash
|
|
673
|
+
$CALIBER learn install
|
|
674
|
+
\`\`\`
|
|
675
|
+
|
|
676
|
+
Then tell the user:
|
|
677
|
+
"You're all set! Here's what happens next:
|
|
678
|
+
- Every time you commit, Caliber syncs your agent configs automatically
|
|
679
|
+
- Your CLAUDE.md, Cursor rules, and AGENTS.md stay current with your code
|
|
680
|
+
- Run \`$CALIBER skills\` anytime to discover community skills for your stack"
|
|
681
|
+
|
|
682
|
+
Then show the summary (see below) and stop.
|
|
683
|
+
|
|
684
|
+
- If **team** \u2192 Check if the GitHub Action already exists:
|
|
685
|
+
\`\`\`bash
|
|
686
|
+
[ -f .github/workflows/caliber-sync.yml ] && echo "ACTION_EXISTS" || echo "NO_ACTION"
|
|
687
|
+
\`\`\`
|
|
688
|
+
- If ACTION_EXISTS \u2192 Tell the user: "GitHub Action is already configured."
|
|
689
|
+
- If NO_ACTION \u2192 Tell the user: "I'll create a GitHub Action that syncs configs nightly and on every PR."
|
|
690
|
+
Write this file to \`.github/workflows/caliber-sync.yml\`:
|
|
691
|
+
\`\`\`yaml
|
|
692
|
+
name: Caliber Sync
|
|
693
|
+
on:
|
|
694
|
+
schedule:
|
|
695
|
+
- cron: '0 3 * * 1-5'
|
|
696
|
+
pull_request:
|
|
697
|
+
types: [opened, synchronize]
|
|
698
|
+
workflow_dispatch:
|
|
699
|
+
jobs:
|
|
700
|
+
sync:
|
|
701
|
+
runs-on: ubuntu-latest
|
|
702
|
+
steps:
|
|
703
|
+
- uses: actions/checkout@v4
|
|
704
|
+
- uses: caliber-ai-org/ai-setup@v1
|
|
705
|
+
with:
|
|
706
|
+
mode: sync
|
|
707
|
+
auto-refresh: true
|
|
708
|
+
comment: true
|
|
709
|
+
github-token: \${{ secrets.GITHUB_TOKEN }}
|
|
710
|
+
env:
|
|
711
|
+
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
712
|
+
\`\`\`
|
|
713
|
+
Now determine which LLM provider the team uses. Check the local Caliber config:
|
|
714
|
+
\`\`\`bash
|
|
715
|
+
$CALIBER config --show 2>/dev/null || echo "NO_CONFIG"
|
|
716
|
+
\`\`\`
|
|
717
|
+
|
|
718
|
+
Based on the provider, the GitHub Action needs the corresponding secret:
|
|
719
|
+
- **anthropic** \u2192 \`ANTHROPIC_API_KEY\`
|
|
720
|
+
- **openai** \u2192 \`OPENAI_API_KEY\`
|
|
721
|
+
- **vertex** \u2192 \`VERTEX_PROJECT_ID\` and \`GOOGLE_APPLICATION_CREDENTIALS\` (service account JSON)
|
|
722
|
+
|
|
723
|
+
Update the workflow env block to match the provider. For example, if using OpenAI:
|
|
724
|
+
\`\`\`yaml
|
|
725
|
+
env:
|
|
726
|
+
OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
|
|
727
|
+
\`\`\`
|
|
728
|
+
|
|
729
|
+
Then check if the \`gh\` CLI is available to set the secret:
|
|
730
|
+
\`\`\`bash
|
|
731
|
+
command -v gh >/dev/null 2>&1 && echo "GH_AVAILABLE" || echo "NO_GH"
|
|
732
|
+
\`\`\`
|
|
733
|
+
|
|
734
|
+
- If GH_AVAILABLE \u2192 Ask the user for their API key and set it:
|
|
735
|
+
\`\`\`bash
|
|
736
|
+
gh secret set ANTHROPIC_API_KEY
|
|
737
|
+
\`\`\`
|
|
738
|
+
(This prompts for the value securely via stdin)
|
|
739
|
+
- If NO_GH \u2192 Tell the user exactly what to do:
|
|
740
|
+
"Go to your repo on GitHub \u2192 Settings \u2192 Secrets and variables \u2192 Actions \u2192 New repository secret.
|
|
741
|
+
Name: ANTHROPIC_API_KEY (or OPENAI_API_KEY depending on provider)
|
|
742
|
+
Value: your API key"
|
|
743
|
+
|
|
744
|
+
Finally, offer to commit and push the workflow file:
|
|
745
|
+
\`\`\`bash
|
|
746
|
+
git add .github/workflows/caliber-sync.yml
|
|
747
|
+
git commit -m "feat: add Caliber sync GitHub Action"
|
|
748
|
+
git push
|
|
749
|
+
\`\`\`
|
|
750
|
+
|
|
751
|
+
### Summary
|
|
752
|
+
|
|
753
|
+
After completing all steps, show the user what's configured:
|
|
754
|
+
|
|
755
|
+
\`\`\`
|
|
756
|
+
Caliber Setup Complete:
|
|
757
|
+
\u2713 Caliber installed (vX.X.X)
|
|
758
|
+
\u2713 Pre-commit hook \u2014 configs sync on every commit
|
|
759
|
+
\u2713 Agent configs \u2014 CLAUDE.md, Cursor rules, AGENTS.md
|
|
760
|
+
\u2713 Config score: X/100
|
|
761
|
+
\u2713 GitHub Action \u2014 nightly sync + PR checks (team only)
|
|
762
|
+
|
|
763
|
+
From now on, every commit keeps all your agent configs in sync automatically.
|
|
764
|
+
\`\`\`
|
|
765
|
+
|
|
766
|
+
## When to trigger
|
|
767
|
+
|
|
768
|
+
- When a developer first opens this project
|
|
769
|
+
- When the user asks about keeping agent configs up to date
|
|
770
|
+
- When the user asks "how do I set up Caliber" or "what is Caliber"
|
|
771
|
+
- When the user sees a message saying "Run /setup-caliber to get set up"
|
|
772
|
+
|
|
773
|
+
## When NOT to trigger
|
|
774
|
+
|
|
775
|
+
- The user is in the middle of time-sensitive work
|
|
776
|
+
`;
|
|
777
|
+
}
|
|
778
|
+
function ensureBuiltinSkills() {
|
|
779
|
+
const written = [];
|
|
780
|
+
for (const { platformDir, skillsDir } of PLATFORM_CONFIGS) {
|
|
781
|
+
if (!fs17.existsSync(platformDir)) continue;
|
|
782
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
783
|
+
const skillPath = path17.join(skillsDir, skill.name, "SKILL.md");
|
|
784
|
+
fs17.mkdirSync(path17.dirname(skillPath), { recursive: true });
|
|
785
|
+
fs17.writeFileSync(skillPath, buildSkillContent(skill));
|
|
786
|
+
written.push(skillPath);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return written;
|
|
790
|
+
}
|
|
791
|
+
var FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL, BUILTIN_SKILLS, PLATFORM_CONFIGS, BUILTIN_SKILL_NAMES;
|
|
792
|
+
var init_builtin_skills = __esm({
|
|
793
|
+
"src/lib/builtin-skills.ts"() {
|
|
794
|
+
"use strict";
|
|
795
|
+
init_resolve_caliber();
|
|
796
|
+
FIND_SKILLS_SKILL = {
|
|
797
|
+
name: "find-skills",
|
|
798
|
+
description: "Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.",
|
|
799
|
+
get content() {
|
|
800
|
+
return getFindSkillsContent();
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
SAVE_LEARNING_SKILL = {
|
|
804
|
+
name: "save-learning",
|
|
805
|
+
description: "Saves user instructions as persistent learnings for future sessions. Use when the user says 'remember this', 'always do X', 'from now on', 'never do Y', or gives any instruction they want persisted across sessions. Proactively suggest when the user states a preference, convention, or rule they clearly want followed in the future.",
|
|
806
|
+
get content() {
|
|
807
|
+
return getSaveLearningContent();
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
SETUP_CALIBER_SKILL = {
|
|
811
|
+
name: "setup-caliber",
|
|
812
|
+
description: "Sets up Caliber for automatic AI agent context sync. Installs pre-commit hooks so CLAUDE.md, Cursor rules, and Copilot instructions update automatically on every commit. Use when Caliber hooks are not yet installed or when the user asks about keeping agent configs in sync.",
|
|
813
|
+
get content() {
|
|
814
|
+
return getSetupCaliberContent();
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
BUILTIN_SKILLS = [FIND_SKILLS_SKILL, SAVE_LEARNING_SKILL, SETUP_CALIBER_SKILL];
|
|
818
|
+
PLATFORM_CONFIGS = [
|
|
819
|
+
{ platformDir: ".claude", skillsDir: path17.join(".claude", "skills") },
|
|
820
|
+
{ platformDir: ".cursor", skillsDir: path17.join(".cursor", "skills") },
|
|
821
|
+
{ platformDir: ".agents", skillsDir: path17.join(".agents", "skills") }
|
|
822
|
+
];
|
|
823
|
+
BUILTIN_SKILL_NAMES = new Set(BUILTIN_SKILLS.map((s) => s.name));
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
233
827
|
// src/utils/editor.ts
|
|
234
|
-
import { execSync as
|
|
235
|
-
import
|
|
236
|
-
import
|
|
828
|
+
import { execSync as execSync14, spawn as spawn3 } from "child_process";
|
|
829
|
+
import fs27 from "fs";
|
|
830
|
+
import path24 from "path";
|
|
237
831
|
import os6 from "os";
|
|
238
832
|
function getEmptyFilePath(proposedPath) {
|
|
239
|
-
|
|
240
|
-
const tempPath =
|
|
241
|
-
|
|
833
|
+
fs27.mkdirSync(DIFF_TEMP_DIR, { recursive: true });
|
|
834
|
+
const tempPath = path24.join(DIFF_TEMP_DIR, path24.basename(proposedPath));
|
|
835
|
+
fs27.writeFileSync(tempPath, "");
|
|
242
836
|
return tempPath;
|
|
243
837
|
}
|
|
244
838
|
function commandExists(cmd) {
|
|
245
839
|
try {
|
|
246
840
|
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
247
|
-
|
|
841
|
+
execSync14(check, { stdio: "ignore" });
|
|
248
842
|
return true;
|
|
249
843
|
} catch {
|
|
250
844
|
return false;
|
|
@@ -278,7 +872,7 @@ var init_editor = __esm({
|
|
|
278
872
|
"src/utils/editor.ts"() {
|
|
279
873
|
"use strict";
|
|
280
874
|
IS_WINDOWS3 = process.platform === "win32";
|
|
281
|
-
DIFF_TEMP_DIR =
|
|
875
|
+
DIFF_TEMP_DIR = path24.join(os6.tmpdir(), "caliber-diff");
|
|
282
876
|
}
|
|
283
877
|
});
|
|
284
878
|
|
|
@@ -290,7 +884,7 @@ __export(review_exports, {
|
|
|
290
884
|
promptWantsReview: () => promptWantsReview
|
|
291
885
|
});
|
|
292
886
|
import chalk10 from "chalk";
|
|
293
|
-
import
|
|
887
|
+
import fs28 from "fs";
|
|
294
888
|
import select4 from "@inquirer/select";
|
|
295
889
|
import { createTwoFilesPatch } from "diff";
|
|
296
890
|
async function promptWantsReview() {
|
|
@@ -327,8 +921,8 @@ async function openReview(method, stagedFiles) {
|
|
|
327
921
|
return;
|
|
328
922
|
}
|
|
329
923
|
const fileInfos = stagedFiles.map((file) => {
|
|
330
|
-
const proposed =
|
|
331
|
-
const current = file.currentPath ?
|
|
924
|
+
const proposed = fs28.readFileSync(file.proposedPath, "utf-8");
|
|
925
|
+
const current = file.currentPath ? fs28.readFileSync(file.currentPath, "utf-8") : "";
|
|
332
926
|
const patch = createTwoFilesPatch(
|
|
333
927
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
334
928
|
file.relativePath,
|
|
@@ -502,14 +1096,14 @@ __export(lock_exports, {
|
|
|
502
1096
|
acquireLock: () => acquireLock,
|
|
503
1097
|
isCaliberRunning: () => isCaliberRunning,
|
|
504
1098
|
releaseLock: () => releaseLock
|
|
505
|
-
});
|
|
506
|
-
import
|
|
507
|
-
import
|
|
1099
|
+
});
|
|
1100
|
+
import fs38 from "fs";
|
|
1101
|
+
import path31 from "path";
|
|
508
1102
|
import os8 from "os";
|
|
509
1103
|
function isCaliberRunning() {
|
|
510
1104
|
try {
|
|
511
|
-
if (!
|
|
512
|
-
const raw =
|
|
1105
|
+
if (!fs38.existsSync(LOCK_FILE)) return false;
|
|
1106
|
+
const raw = fs38.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
513
1107
|
const { pid, ts } = JSON.parse(raw);
|
|
514
1108
|
if (Date.now() - ts > STALE_MS) return false;
|
|
515
1109
|
try {
|
|
@@ -524,13 +1118,13 @@ function isCaliberRunning() {
|
|
|
524
1118
|
}
|
|
525
1119
|
function acquireLock() {
|
|
526
1120
|
try {
|
|
527
|
-
|
|
1121
|
+
fs38.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
528
1122
|
} catch {
|
|
529
1123
|
}
|
|
530
1124
|
}
|
|
531
1125
|
function releaseLock() {
|
|
532
1126
|
try {
|
|
533
|
-
if (
|
|
1127
|
+
if (fs38.existsSync(LOCK_FILE)) fs38.unlinkSync(LOCK_FILE);
|
|
534
1128
|
} catch {
|
|
535
1129
|
}
|
|
536
1130
|
}
|
|
@@ -538,21 +1132,21 @@ var LOCK_FILE, STALE_MS;
|
|
|
538
1132
|
var init_lock = __esm({
|
|
539
1133
|
"src/lib/lock.ts"() {
|
|
540
1134
|
"use strict";
|
|
541
|
-
LOCK_FILE =
|
|
1135
|
+
LOCK_FILE = path31.join(os8.tmpdir(), ".caliber.lock");
|
|
542
1136
|
STALE_MS = 10 * 60 * 1e3;
|
|
543
1137
|
}
|
|
544
1138
|
});
|
|
545
1139
|
|
|
546
1140
|
// src/cli.ts
|
|
547
1141
|
import { Command } from "commander";
|
|
548
|
-
import
|
|
549
|
-
import
|
|
1142
|
+
import fs49 from "fs";
|
|
1143
|
+
import path40 from "path";
|
|
550
1144
|
import { fileURLToPath } from "url";
|
|
551
1145
|
|
|
552
1146
|
// src/commands/init.ts
|
|
553
|
-
import
|
|
1147
|
+
import path27 from "path";
|
|
554
1148
|
import chalk14 from "chalk";
|
|
555
|
-
import
|
|
1149
|
+
import fs33 from "fs";
|
|
556
1150
|
|
|
557
1151
|
// src/fingerprint/index.ts
|
|
558
1152
|
import fs8 from "fs";
|
|
@@ -3227,9 +3821,151 @@ function getCursorConfigDir() {
|
|
|
3227
3821
|
return path9.join(home, ".config", "Cursor");
|
|
3228
3822
|
}
|
|
3229
3823
|
|
|
3230
|
-
// src/
|
|
3824
|
+
// src/lib/hooks.ts
|
|
3825
|
+
init_resolve_caliber();
|
|
3231
3826
|
import fs10 from "fs";
|
|
3232
3827
|
import path10 from "path";
|
|
3828
|
+
import { execSync as execSync8 } from "child_process";
|
|
3829
|
+
var SETTINGS_PATH = path10.join(".claude", "settings.json");
|
|
3830
|
+
var REFRESH_TAIL = "refresh --quiet";
|
|
3831
|
+
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
3832
|
+
function getHookCommand() {
|
|
3833
|
+
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
3834
|
+
}
|
|
3835
|
+
function readSettings() {
|
|
3836
|
+
if (!fs10.existsSync(SETTINGS_PATH)) return {};
|
|
3837
|
+
try {
|
|
3838
|
+
return JSON.parse(fs10.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
3839
|
+
} catch {
|
|
3840
|
+
return {};
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
function writeSettings(settings) {
|
|
3844
|
+
const dir = path10.dirname(SETTINGS_PATH);
|
|
3845
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3846
|
+
fs10.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
3847
|
+
}
|
|
3848
|
+
function findHookIndex(sessionEnd) {
|
|
3849
|
+
return sessionEnd.findIndex(
|
|
3850
|
+
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
3851
|
+
);
|
|
3852
|
+
}
|
|
3853
|
+
function isHookInstalled() {
|
|
3854
|
+
const settings = readSettings();
|
|
3855
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3856
|
+
if (!Array.isArray(sessionEnd)) return false;
|
|
3857
|
+
return findHookIndex(sessionEnd) !== -1;
|
|
3858
|
+
}
|
|
3859
|
+
function installHook() {
|
|
3860
|
+
const settings = readSettings();
|
|
3861
|
+
if (!settings.hooks) settings.hooks = {};
|
|
3862
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
3863
|
+
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
3864
|
+
return { installed: false, alreadyInstalled: true };
|
|
3865
|
+
}
|
|
3866
|
+
settings.hooks.SessionEnd.push({
|
|
3867
|
+
matcher: "",
|
|
3868
|
+
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
3869
|
+
});
|
|
3870
|
+
writeSettings(settings);
|
|
3871
|
+
return { installed: true, alreadyInstalled: false };
|
|
3872
|
+
}
|
|
3873
|
+
function removeHook() {
|
|
3874
|
+
const settings = readSettings();
|
|
3875
|
+
const sessionEnd = settings.hooks?.SessionEnd;
|
|
3876
|
+
if (!Array.isArray(sessionEnd)) {
|
|
3877
|
+
return { removed: false, notFound: true };
|
|
3878
|
+
}
|
|
3879
|
+
const idx = findHookIndex(sessionEnd);
|
|
3880
|
+
if (idx === -1) {
|
|
3881
|
+
return { removed: false, notFound: true };
|
|
3882
|
+
}
|
|
3883
|
+
sessionEnd.splice(idx, 1);
|
|
3884
|
+
if (sessionEnd.length === 0) {
|
|
3885
|
+
delete settings.hooks.SessionEnd;
|
|
3886
|
+
}
|
|
3887
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
3888
|
+
delete settings.hooks;
|
|
3889
|
+
}
|
|
3890
|
+
writeSettings(settings);
|
|
3891
|
+
return { removed: true, notFound: false };
|
|
3892
|
+
}
|
|
3893
|
+
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
3894
|
+
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
3895
|
+
function getPrecommitBlock() {
|
|
3896
|
+
const bin = resolveCaliber();
|
|
3897
|
+
const npx = isNpxResolution();
|
|
3898
|
+
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
3899
|
+
const invoke = npx ? bin : `"${bin}"`;
|
|
3900
|
+
return `${PRECOMMIT_START}
|
|
3901
|
+
if ${guard}; then
|
|
3902
|
+
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
3903
|
+
${invoke} refresh 2>/dev/null || true
|
|
3904
|
+
${invoke} learn finalize 2>/dev/null || true
|
|
3905
|
+
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
3906
|
+
fi
|
|
3907
|
+
${PRECOMMIT_END}`;
|
|
3908
|
+
}
|
|
3909
|
+
function getGitHooksDir() {
|
|
3910
|
+
try {
|
|
3911
|
+
const gitDir = execSync8("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3912
|
+
return path10.join(gitDir, "hooks");
|
|
3913
|
+
} catch {
|
|
3914
|
+
return null;
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
function getPreCommitPath() {
|
|
3918
|
+
const hooksDir = getGitHooksDir();
|
|
3919
|
+
return hooksDir ? path10.join(hooksDir, "pre-commit") : null;
|
|
3920
|
+
}
|
|
3921
|
+
function isPreCommitHookInstalled() {
|
|
3922
|
+
const hookPath = getPreCommitPath();
|
|
3923
|
+
if (!hookPath || !fs10.existsSync(hookPath)) return false;
|
|
3924
|
+
const content = fs10.readFileSync(hookPath, "utf-8");
|
|
3925
|
+
return content.includes(PRECOMMIT_START);
|
|
3926
|
+
}
|
|
3927
|
+
function installPreCommitHook() {
|
|
3928
|
+
if (isPreCommitHookInstalled()) {
|
|
3929
|
+
return { installed: false, alreadyInstalled: true };
|
|
3930
|
+
}
|
|
3931
|
+
const hookPath = getPreCommitPath();
|
|
3932
|
+
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
3933
|
+
const hooksDir = path10.dirname(hookPath);
|
|
3934
|
+
if (!fs10.existsSync(hooksDir)) fs10.mkdirSync(hooksDir, { recursive: true });
|
|
3935
|
+
let content = "";
|
|
3936
|
+
if (fs10.existsSync(hookPath)) {
|
|
3937
|
+
content = fs10.readFileSync(hookPath, "utf-8");
|
|
3938
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
3939
|
+
content += "\n" + getPrecommitBlock() + "\n";
|
|
3940
|
+
} else {
|
|
3941
|
+
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
3942
|
+
}
|
|
3943
|
+
fs10.writeFileSync(hookPath, content);
|
|
3944
|
+
fs10.chmodSync(hookPath, 493);
|
|
3945
|
+
return { installed: true, alreadyInstalled: false };
|
|
3946
|
+
}
|
|
3947
|
+
function removePreCommitHook() {
|
|
3948
|
+
const hookPath = getPreCommitPath();
|
|
3949
|
+
if (!hookPath || !fs10.existsSync(hookPath)) {
|
|
3950
|
+
return { removed: false, notFound: true };
|
|
3951
|
+
}
|
|
3952
|
+
let content = fs10.readFileSync(hookPath, "utf-8");
|
|
3953
|
+
if (!content.includes(PRECOMMIT_START)) {
|
|
3954
|
+
return { removed: false, notFound: true };
|
|
3955
|
+
}
|
|
3956
|
+
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
3957
|
+
content = content.replace(regex, "\n");
|
|
3958
|
+
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
3959
|
+
fs10.unlinkSync(hookPath);
|
|
3960
|
+
} else {
|
|
3961
|
+
fs10.writeFileSync(hookPath, content);
|
|
3962
|
+
}
|
|
3963
|
+
return { removed: true, notFound: false };
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
// src/fingerprint/sources.ts
|
|
3967
|
+
import fs11 from "fs";
|
|
3968
|
+
import path11 from "path";
|
|
3233
3969
|
|
|
3234
3970
|
// src/scoring/utils.ts
|
|
3235
3971
|
import { existsSync as existsSync2, readFileSync, readdirSync } from "fs";
|
|
@@ -3558,7 +4294,7 @@ var SOURCE_CONTENT_LIMIT = 2e3;
|
|
|
3558
4294
|
var README_CONTENT_LIMIT = 1e3;
|
|
3559
4295
|
var ORIGIN_PRIORITY = { cli: 0, config: 1, workspace: 2 };
|
|
3560
4296
|
function loadSourcesConfig(dir) {
|
|
3561
|
-
const configPath =
|
|
4297
|
+
const configPath = path11.join(dir, ".caliber", "sources.json");
|
|
3562
4298
|
const content = readFileOrNull(configPath);
|
|
3563
4299
|
if (!content) return [];
|
|
3564
4300
|
try {
|
|
@@ -3576,29 +4312,29 @@ function loadSourcesConfig(dir) {
|
|
|
3576
4312
|
}
|
|
3577
4313
|
}
|
|
3578
4314
|
function writeSourcesConfig(dir, sources2) {
|
|
3579
|
-
const configDir =
|
|
3580
|
-
if (!
|
|
3581
|
-
|
|
4315
|
+
const configDir = path11.join(dir, ".caliber");
|
|
4316
|
+
if (!fs11.existsSync(configDir)) {
|
|
4317
|
+
fs11.mkdirSync(configDir, { recursive: true });
|
|
3582
4318
|
}
|
|
3583
|
-
const configPath =
|
|
3584
|
-
|
|
4319
|
+
const configPath = path11.join(configDir, "sources.json");
|
|
4320
|
+
fs11.writeFileSync(configPath, JSON.stringify({ sources: sources2 }, null, 2) + "\n", "utf-8");
|
|
3585
4321
|
}
|
|
3586
4322
|
function detectSourceType(absPath) {
|
|
3587
4323
|
try {
|
|
3588
|
-
return
|
|
4324
|
+
return fs11.statSync(absPath).isDirectory() ? "repo" : "file";
|
|
3589
4325
|
} catch {
|
|
3590
4326
|
return "file";
|
|
3591
4327
|
}
|
|
3592
4328
|
}
|
|
3593
4329
|
function isInsideDir(childPath, parentDir) {
|
|
3594
|
-
const relative2 =
|
|
3595
|
-
return !relative2.startsWith("..") && !
|
|
4330
|
+
const relative2 = path11.relative(parentDir, childPath);
|
|
4331
|
+
return !relative2.startsWith("..") && !path11.isAbsolute(relative2);
|
|
3596
4332
|
}
|
|
3597
4333
|
function resolveAllSources(dir, cliSources, workspaces) {
|
|
3598
4334
|
const seen = /* @__PURE__ */ new Map();
|
|
3599
|
-
const projectRoot =
|
|
4335
|
+
const projectRoot = path11.resolve(dir);
|
|
3600
4336
|
for (const src of cliSources) {
|
|
3601
|
-
const absPath =
|
|
4337
|
+
const absPath = path11.resolve(dir, src);
|
|
3602
4338
|
if (seen.has(absPath)) continue;
|
|
3603
4339
|
const type = detectSourceType(absPath);
|
|
3604
4340
|
seen.set(absPath, {
|
|
@@ -3611,12 +4347,12 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3611
4347
|
for (const cfg of configSources) {
|
|
3612
4348
|
if (cfg.type === "url") continue;
|
|
3613
4349
|
if (!cfg.path) continue;
|
|
3614
|
-
const absPath =
|
|
4350
|
+
const absPath = path11.resolve(dir, cfg.path);
|
|
3615
4351
|
if (seen.has(absPath)) continue;
|
|
3616
4352
|
seen.set(absPath, { absPath, config: cfg, origin: "config" });
|
|
3617
4353
|
}
|
|
3618
4354
|
for (const ws of workspaces) {
|
|
3619
|
-
const absPath =
|
|
4355
|
+
const absPath = path11.resolve(dir, ws);
|
|
3620
4356
|
if (seen.has(absPath)) continue;
|
|
3621
4357
|
if (!isInsideDir(absPath, projectRoot)) continue;
|
|
3622
4358
|
seen.set(absPath, {
|
|
@@ -3629,7 +4365,7 @@ function resolveAllSources(dir, cliSources, workspaces) {
|
|
|
3629
4365
|
for (const [absPath, resolved] of seen) {
|
|
3630
4366
|
let stat;
|
|
3631
4367
|
try {
|
|
3632
|
-
stat =
|
|
4368
|
+
stat = fs11.statSync(absPath);
|
|
3633
4369
|
} catch {
|
|
3634
4370
|
console.warn(`Source ${resolved.config.path || absPath} not found, skipping`);
|
|
3635
4371
|
continue;
|
|
@@ -3658,13 +4394,13 @@ function collectSourceSummary(resolved, projectDir) {
|
|
|
3658
4394
|
if (config.type === "file") {
|
|
3659
4395
|
return collectFileSummary(resolved, projectDir);
|
|
3660
4396
|
}
|
|
3661
|
-
const summaryPath =
|
|
4397
|
+
const summaryPath = path11.join(absPath, ".caliber", "summary.json");
|
|
3662
4398
|
const summaryContent = readFileOrNull(summaryPath);
|
|
3663
4399
|
if (summaryContent) {
|
|
3664
4400
|
try {
|
|
3665
4401
|
const published = JSON.parse(summaryContent);
|
|
3666
4402
|
return {
|
|
3667
|
-
name: published.name ||
|
|
4403
|
+
name: published.name || path11.basename(absPath),
|
|
3668
4404
|
type: "repo",
|
|
3669
4405
|
role: config.role || published.role || "related-repo",
|
|
3670
4406
|
description: config.description || published.description || "",
|
|
@@ -3684,18 +4420,18 @@ function collectRepoSummary(resolved, _projectDir) {
|
|
|
3684
4420
|
let topLevelDirs;
|
|
3685
4421
|
let keyFiles;
|
|
3686
4422
|
try {
|
|
3687
|
-
const entries =
|
|
4423
|
+
const entries = fs11.readdirSync(absPath, { withFileTypes: true });
|
|
3688
4424
|
topLevelDirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).slice(0, 20);
|
|
3689
4425
|
keyFiles = entries.filter((e) => e.isFile() && !e.name.startsWith(".")).map((e) => e.name).slice(0, 15);
|
|
3690
4426
|
} catch {
|
|
3691
4427
|
}
|
|
3692
|
-
const claudeMdContent = readFileOrNull(
|
|
4428
|
+
const claudeMdContent = readFileOrNull(path11.join(absPath, "CLAUDE.md"));
|
|
3693
4429
|
const existingClaudeMd = claudeMdContent ? claudeMdContent.slice(0, SOURCE_CONTENT_LIMIT) : void 0;
|
|
3694
|
-
const readmeContent = readFileOrNull(
|
|
4430
|
+
const readmeContent = readFileOrNull(path11.join(absPath, "README.md"));
|
|
3695
4431
|
const readmeExcerpt = readmeContent ? readmeContent.slice(0, README_CONTENT_LIMIT) : void 0;
|
|
3696
4432
|
const gitRemoteUrl = getGitRemoteUrl(absPath);
|
|
3697
4433
|
return {
|
|
3698
|
-
name: packageName ||
|
|
4434
|
+
name: packageName || path11.basename(absPath),
|
|
3699
4435
|
type: "repo",
|
|
3700
4436
|
role: config.role || "related-repo",
|
|
3701
4437
|
description: config.description || "",
|
|
@@ -3712,7 +4448,7 @@ function collectFileSummary(resolved, _projectDir) {
|
|
|
3712
4448
|
const { config, origin, absPath } = resolved;
|
|
3713
4449
|
const content = readFileOrNull(absPath);
|
|
3714
4450
|
return {
|
|
3715
|
-
name:
|
|
4451
|
+
name: path11.basename(absPath),
|
|
3716
4452
|
type: "file",
|
|
3717
4453
|
role: config.role || "reference-doc",
|
|
3718
4454
|
description: config.description || content?.slice(0, 100).split("\n")[0] || "",
|
|
@@ -3756,15 +4492,15 @@ init_config();
|
|
|
3756
4492
|
// src/utils/dependencies.ts
|
|
3757
4493
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3758
4494
|
import { join as join2 } from "path";
|
|
3759
|
-
function readFileOrNull2(
|
|
4495
|
+
function readFileOrNull2(path42) {
|
|
3760
4496
|
try {
|
|
3761
|
-
return readFileSync2(
|
|
4497
|
+
return readFileSync2(path42, "utf-8");
|
|
3762
4498
|
} catch {
|
|
3763
4499
|
return null;
|
|
3764
4500
|
}
|
|
3765
4501
|
}
|
|
3766
|
-
function readJsonOrNull(
|
|
3767
|
-
const content = readFileOrNull2(
|
|
4502
|
+
function readJsonOrNull(path42) {
|
|
4503
|
+
const content = readFileOrNull2(path42);
|
|
3768
4504
|
if (!content) return null;
|
|
3769
4505
|
try {
|
|
3770
4506
|
return JSON.parse(content);
|
|
@@ -4305,191 +5041,71 @@ ${existing.personalLearnings}`);
|
|
|
4305
5041
|
if (allDeps.length > 0) {
|
|
4306
5042
|
parts.push(`
|
|
4307
5043
|
Project dependencies (${allDeps.length}):`);
|
|
4308
|
-
parts.push(allDeps.join(", "));
|
|
4309
|
-
}
|
|
4310
|
-
if (prompt) parts.push(`
|
|
4311
|
-
User instructions: ${prompt}`);
|
|
4312
|
-
if (fingerprint.codeAnalysis) {
|
|
4313
|
-
const ca = fingerprint.codeAnalysis;
|
|
4314
|
-
const basePrompt = parts.join("\n");
|
|
4315
|
-
const maxPromptTokens = getMaxPromptTokens();
|
|
4316
|
-
const baseTokens = estimateTokens(basePrompt);
|
|
4317
|
-
const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
|
|
4318
|
-
const codeLines = [];
|
|
4319
|
-
let codeChars = 0;
|
|
4320
|
-
const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
|
|
4321
|
-
codeLines.push(introLine);
|
|
4322
|
-
let runningCodeLen = introLine.length;
|
|
4323
|
-
const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
4324
|
-
let includedFiles = 0;
|
|
4325
|
-
for (const f of sortedFiles) {
|
|
4326
|
-
const entry = `[${f.path}]
|
|
4327
|
-
${f.content}
|
|
4328
|
-
`;
|
|
4329
|
-
const projectedLen = runningCodeLen + 1 + entry.length;
|
|
4330
|
-
if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
|
|
4331
|
-
codeLines.push(entry);
|
|
4332
|
-
codeChars += f.content.length;
|
|
4333
|
-
runningCodeLen = projectedLen;
|
|
4334
|
-
includedFiles++;
|
|
4335
|
-
}
|
|
4336
|
-
const includedTokens = Math.ceil(codeChars / 4);
|
|
4337
|
-
let header;
|
|
4338
|
-
if (includedFiles < ca.files.length) {
|
|
4339
|
-
const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4340
|
-
header = `
|
|
4341
|
-
--- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4342
|
-
} else if (ca.truncated) {
|
|
4343
|
-
const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
4344
|
-
header = `
|
|
4345
|
-
--- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
4346
|
-
} else {
|
|
4347
|
-
header = `
|
|
4348
|
-
--- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
|
|
4349
|
-
}
|
|
4350
|
-
parts.push(header);
|
|
4351
|
-
parts.push(codeLines.join("\n"));
|
|
4352
|
-
}
|
|
4353
|
-
if (fingerprint.sources?.length) {
|
|
4354
|
-
parts.push(formatSourcesForPrompt(fingerprint.sources));
|
|
4355
|
-
}
|
|
4356
|
-
return parts.join("\n");
|
|
4357
|
-
}
|
|
4358
|
-
|
|
4359
|
-
// src/writers/index.ts
|
|
4360
|
-
import fs18 from "fs";
|
|
4361
|
-
|
|
4362
|
-
// src/writers/claude/index.ts
|
|
4363
|
-
import fs11 from "fs";
|
|
4364
|
-
import path11 from "path";
|
|
4365
|
-
|
|
4366
|
-
// src/writers/pre-commit-block.ts
|
|
4367
|
-
init_resolve_caliber();
|
|
4368
|
-
var BLOCK_START = "<!-- caliber:managed:pre-commit -->";
|
|
4369
|
-
var BLOCK_END = "<!-- /caliber:managed:pre-commit -->";
|
|
4370
|
-
var MANAGED_DOC_PATHS = "CLAUDE.md .claude/ .cursor/ .github/copilot-instructions.md AGENTS.md CALIBER_LEARNINGS.md";
|
|
4371
|
-
function getPreCommitBlock() {
|
|
4372
|
-
const bin = resolveCaliber();
|
|
4373
|
-
return `${BLOCK_START}
|
|
4374
|
-
## Before Committing
|
|
4375
|
-
|
|
4376
|
-
Run \`${bin} refresh\` before creating git commits to keep docs in sync with code changes.
|
|
4377
|
-
After it completes, stage any modified doc files before committing:
|
|
4378
|
-
|
|
4379
|
-
\`\`\`bash
|
|
4380
|
-
${bin} refresh && git add ${MANAGED_DOC_PATHS} 2>/dev/null
|
|
4381
|
-
\`\`\`
|
|
4382
|
-
${BLOCK_END}`;
|
|
4383
|
-
}
|
|
4384
|
-
var CURSOR_RULE_FILENAME = "caliber-pre-commit.mdc";
|
|
4385
|
-
function getCursorRuleContent() {
|
|
4386
|
-
const bin = resolveCaliber();
|
|
4387
|
-
return `---
|
|
4388
|
-
description: Run caliber refresh before git commits to keep docs in sync
|
|
4389
|
-
alwaysApply: true
|
|
4390
|
-
---
|
|
4391
|
-
Before creating git commits, run \`${bin} refresh\` to update docs based on code changes,
|
|
4392
|
-
then stage modified doc files (${MANAGED_DOC_PATHS}).
|
|
4393
|
-
`;
|
|
4394
|
-
}
|
|
4395
|
-
function hasPreCommitBlock(content) {
|
|
4396
|
-
return content.includes(BLOCK_START);
|
|
4397
|
-
}
|
|
4398
|
-
function appendPreCommitBlock(content) {
|
|
4399
|
-
if (hasPreCommitBlock(content)) return content;
|
|
4400
|
-
const trimmed = content.trimEnd();
|
|
4401
|
-
return trimmed + "\n\n" + getPreCommitBlock() + "\n";
|
|
4402
|
-
}
|
|
4403
|
-
function getCursorPreCommitRule() {
|
|
4404
|
-
return { filename: CURSOR_RULE_FILENAME, content: getCursorRuleContent() };
|
|
4405
|
-
}
|
|
4406
|
-
var LEARNINGS_BLOCK_START = "<!-- caliber:managed:learnings -->";
|
|
4407
|
-
var LEARNINGS_BLOCK_END = "<!-- /caliber:managed:learnings -->";
|
|
4408
|
-
var LEARNINGS_BLOCK = `${LEARNINGS_BLOCK_START}
|
|
4409
|
-
## Session Learnings
|
|
4410
|
-
|
|
4411
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
4412
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
4413
|
-
${LEARNINGS_BLOCK_END}`;
|
|
4414
|
-
var CURSOR_LEARNINGS_FILENAME = "caliber-learnings.mdc";
|
|
4415
|
-
var CURSOR_LEARNINGS_CONTENT = `---
|
|
4416
|
-
description: Reference session-learned patterns from CALIBER_LEARNINGS.md
|
|
4417
|
-
alwaysApply: true
|
|
4418
|
-
---
|
|
4419
|
-
Read \`CALIBER_LEARNINGS.md\` for patterns and anti-patterns learned from previous sessions.
|
|
4420
|
-
These are auto-extracted from real tool usage \u2014 treat them as project-specific rules.
|
|
4421
|
-
`;
|
|
4422
|
-
function hasLearningsBlock(content) {
|
|
4423
|
-
return content.includes(LEARNINGS_BLOCK_START);
|
|
4424
|
-
}
|
|
4425
|
-
function appendLearningsBlock(content) {
|
|
4426
|
-
if (hasLearningsBlock(content)) return content;
|
|
4427
|
-
const trimmed = content.trimEnd();
|
|
4428
|
-
return trimmed + "\n\n" + LEARNINGS_BLOCK + "\n";
|
|
4429
|
-
}
|
|
4430
|
-
function getCursorLearningsRule() {
|
|
4431
|
-
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
4432
|
-
}
|
|
4433
|
-
|
|
4434
|
-
// src/writers/claude/index.ts
|
|
4435
|
-
function writeClaudeConfig(config) {
|
|
4436
|
-
const written = [];
|
|
4437
|
-
fs11.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
|
|
4438
|
-
written.push("CLAUDE.md");
|
|
4439
|
-
if (config.skills?.length) {
|
|
4440
|
-
for (const skill of config.skills) {
|
|
4441
|
-
const skillDir = path11.join(".claude", "skills", skill.name);
|
|
4442
|
-
if (!fs11.existsSync(skillDir)) fs11.mkdirSync(skillDir, { recursive: true });
|
|
4443
|
-
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
4444
|
-
const frontmatter = [
|
|
4445
|
-
"---",
|
|
4446
|
-
`name: ${skill.name}`,
|
|
4447
|
-
`description: ${skill.description}`,
|
|
4448
|
-
"---",
|
|
4449
|
-
""
|
|
4450
|
-
].join("\n");
|
|
4451
|
-
fs11.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4452
|
-
written.push(skillPath);
|
|
4453
|
-
}
|
|
5044
|
+
parts.push(allDeps.join(", "));
|
|
4454
5045
|
}
|
|
4455
|
-
if (
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
5046
|
+
if (prompt) parts.push(`
|
|
5047
|
+
User instructions: ${prompt}`);
|
|
5048
|
+
if (fingerprint.codeAnalysis) {
|
|
5049
|
+
const ca = fingerprint.codeAnalysis;
|
|
5050
|
+
const basePrompt = parts.join("\n");
|
|
5051
|
+
const maxPromptTokens = getMaxPromptTokens();
|
|
5052
|
+
const baseTokens = estimateTokens(basePrompt);
|
|
5053
|
+
const tokenBudgetForCode = Math.max(0, maxPromptTokens - baseTokens);
|
|
5054
|
+
const codeLines = [];
|
|
5055
|
+
let codeChars = 0;
|
|
5056
|
+
const introLine = "Study these files to extract patterns for skills. Use the exact code patterns you see here.\n";
|
|
5057
|
+
codeLines.push(introLine);
|
|
5058
|
+
let runningCodeLen = introLine.length;
|
|
5059
|
+
const sortedFiles = [...ca.files].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
5060
|
+
let includedFiles = 0;
|
|
5061
|
+
for (const f of sortedFiles) {
|
|
5062
|
+
const entry = `[${f.path}]
|
|
5063
|
+
${f.content}
|
|
5064
|
+
`;
|
|
5065
|
+
const projectedLen = runningCodeLen + 1 + entry.length;
|
|
5066
|
+
if (Math.ceil(projectedLen / 4) > tokenBudgetForCode && includedFiles > 0) break;
|
|
5067
|
+
codeLines.push(entry);
|
|
5068
|
+
codeChars += f.content.length;
|
|
5069
|
+
runningCodeLen = projectedLen;
|
|
5070
|
+
includedFiles++;
|
|
4463
5071
|
}
|
|
4464
|
-
const
|
|
4465
|
-
|
|
4466
|
-
|
|
5072
|
+
const includedTokens = Math.ceil(codeChars / 4);
|
|
5073
|
+
let header;
|
|
5074
|
+
if (includedFiles < ca.files.length) {
|
|
5075
|
+
const pct = ca.totalProjectTokens > 0 ? Math.round(includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
5076
|
+
header = `
|
|
5077
|
+
--- Project Files (trimmed to ~${includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
5078
|
+
} else if (ca.truncated) {
|
|
5079
|
+
const pct = ca.totalProjectTokens > 0 ? Math.round(ca.includedTokens / ca.totalProjectTokens * 100) : 100;
|
|
5080
|
+
header = `
|
|
5081
|
+
--- Project Files (trimmed to ~${ca.includedTokens.toLocaleString()}/${ca.totalProjectTokens.toLocaleString()} tokens, ${pct}% of total) ---`;
|
|
5082
|
+
} else {
|
|
5083
|
+
header = `
|
|
5084
|
+
--- Project Files (${ca.files.length} files, ~${ca.includedTokens.toLocaleString()} tokens) ---`;
|
|
5085
|
+
}
|
|
5086
|
+
parts.push(header);
|
|
5087
|
+
parts.push(codeLines.join("\n"));
|
|
4467
5088
|
}
|
|
4468
|
-
|
|
5089
|
+
if (fingerprint.sources?.length) {
|
|
5090
|
+
parts.push(formatSourcesForPrompt(fingerprint.sources));
|
|
5091
|
+
}
|
|
5092
|
+
return parts.join("\n");
|
|
4469
5093
|
}
|
|
4470
5094
|
|
|
4471
|
-
// src/writers/
|
|
5095
|
+
// src/writers/index.ts
|
|
5096
|
+
import fs19 from "fs";
|
|
5097
|
+
|
|
5098
|
+
// src/writers/claude/index.ts
|
|
5099
|
+
init_pre_commit_block();
|
|
4472
5100
|
import fs12 from "fs";
|
|
4473
5101
|
import path12 from "path";
|
|
4474
|
-
function
|
|
5102
|
+
function writeClaudeConfig(config) {
|
|
4475
5103
|
const written = [];
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
written.push(".cursorrules");
|
|
4479
|
-
}
|
|
4480
|
-
const preCommitRule = getCursorPreCommitRule();
|
|
4481
|
-
const learningsRule = getCursorLearningsRule();
|
|
4482
|
-
const allRules = [...config.rules || [], preCommitRule, learningsRule];
|
|
4483
|
-
const rulesDir = path12.join(".cursor", "rules");
|
|
4484
|
-
if (!fs12.existsSync(rulesDir)) fs12.mkdirSync(rulesDir, { recursive: true });
|
|
4485
|
-
for (const rule of allRules) {
|
|
4486
|
-
const rulePath = path12.join(rulesDir, rule.filename);
|
|
4487
|
-
fs12.writeFileSync(rulePath, rule.content);
|
|
4488
|
-
written.push(rulePath);
|
|
4489
|
-
}
|
|
5104
|
+
fs12.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
|
|
5105
|
+
written.push("CLAUDE.md");
|
|
4490
5106
|
if (config.skills?.length) {
|
|
4491
5107
|
for (const skill of config.skills) {
|
|
4492
|
-
const skillDir = path12.join(".
|
|
5108
|
+
const skillDir = path12.join(".claude", "skills", skill.name);
|
|
4493
5109
|
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
4494
5110
|
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
4495
5111
|
const frontmatter = [
|
|
@@ -4504,286 +5120,183 @@ function writeCursorConfig(config) {
|
|
|
4504
5120
|
}
|
|
4505
5121
|
}
|
|
4506
5122
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
4507
|
-
const cursorDir = ".cursor";
|
|
4508
|
-
if (!fs12.existsSync(cursorDir)) fs12.mkdirSync(cursorDir, { recursive: true });
|
|
4509
|
-
const mcpPath = path12.join(cursorDir, "mcp.json");
|
|
4510
5123
|
let existingServers = {};
|
|
4511
5124
|
try {
|
|
4512
|
-
if (fs12.existsSync(
|
|
4513
|
-
const existing = JSON.parse(fs12.readFileSync(
|
|
5125
|
+
if (fs12.existsSync(".mcp.json")) {
|
|
5126
|
+
const existing = JSON.parse(fs12.readFileSync(".mcp.json", "utf-8"));
|
|
4514
5127
|
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
4515
5128
|
}
|
|
4516
5129
|
} catch {
|
|
4517
5130
|
}
|
|
4518
5131
|
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
4519
|
-
fs12.writeFileSync(
|
|
4520
|
-
written.push(
|
|
4521
|
-
}
|
|
4522
|
-
return written;
|
|
4523
|
-
}
|
|
4524
|
-
|
|
4525
|
-
// src/writers/codex/index.ts
|
|
4526
|
-
import fs13 from "fs";
|
|
4527
|
-
import path13 from "path";
|
|
4528
|
-
function writeCodexConfig(config) {
|
|
4529
|
-
const written = [];
|
|
4530
|
-
fs13.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4531
|
-
written.push("AGENTS.md");
|
|
4532
|
-
if (config.skills?.length) {
|
|
4533
|
-
for (const skill of config.skills) {
|
|
4534
|
-
const skillDir = path13.join(".agents", "skills", skill.name);
|
|
4535
|
-
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
4536
|
-
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
4537
|
-
const frontmatter = [
|
|
4538
|
-
"---",
|
|
4539
|
-
`name: ${skill.name}`,
|
|
4540
|
-
`description: ${skill.description}`,
|
|
4541
|
-
"---",
|
|
4542
|
-
""
|
|
4543
|
-
].join("\n");
|
|
4544
|
-
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4545
|
-
written.push(skillPath);
|
|
4546
|
-
}
|
|
4547
|
-
}
|
|
4548
|
-
return written;
|
|
4549
|
-
}
|
|
4550
|
-
|
|
4551
|
-
// src/writers/github-copilot/index.ts
|
|
4552
|
-
import fs14 from "fs";
|
|
4553
|
-
import path14 from "path";
|
|
4554
|
-
function writeGithubCopilotConfig(config) {
|
|
4555
|
-
const written = [];
|
|
4556
|
-
if (config.instructions) {
|
|
4557
|
-
fs14.mkdirSync(".github", { recursive: true });
|
|
4558
|
-
fs14.writeFileSync(path14.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
|
|
4559
|
-
written.push(".github/copilot-instructions.md");
|
|
4560
|
-
}
|
|
4561
|
-
if (config.instructionFiles?.length) {
|
|
4562
|
-
const instructionsDir = path14.join(".github", "instructions");
|
|
4563
|
-
fs14.mkdirSync(instructionsDir, { recursive: true });
|
|
4564
|
-
for (const file of config.instructionFiles) {
|
|
4565
|
-
fs14.writeFileSync(path14.join(instructionsDir, file.filename), file.content);
|
|
4566
|
-
written.push(`.github/instructions/${file.filename}`);
|
|
4567
|
-
}
|
|
5132
|
+
fs12.writeFileSync(".mcp.json", JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
5133
|
+
written.push(".mcp.json");
|
|
4568
5134
|
}
|
|
4569
5135
|
return written;
|
|
4570
5136
|
}
|
|
4571
5137
|
|
|
4572
|
-
// src/writers/
|
|
4573
|
-
|
|
4574
|
-
import
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
const
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
const destDir = path15.dirname(dest);
|
|
4582
|
-
if (!fs15.existsSync(destDir)) {
|
|
4583
|
-
fs15.mkdirSync(destDir, { recursive: true });
|
|
4584
|
-
}
|
|
4585
|
-
fs15.copyFileSync(file, dest);
|
|
4586
|
-
}
|
|
4587
|
-
return backupDir;
|
|
4588
|
-
}
|
|
4589
|
-
function restoreBackup(backupDir, file) {
|
|
4590
|
-
const backupFile = path15.join(backupDir, file);
|
|
4591
|
-
if (!fs15.existsSync(backupFile)) return false;
|
|
4592
|
-
const destDir = path15.dirname(file);
|
|
4593
|
-
if (!fs15.existsSync(destDir)) {
|
|
4594
|
-
fs15.mkdirSync(destDir, { recursive: true });
|
|
4595
|
-
}
|
|
4596
|
-
fs15.copyFileSync(backupFile, file);
|
|
4597
|
-
return true;
|
|
4598
|
-
}
|
|
4599
|
-
|
|
4600
|
-
// src/lib/builtin-skills.ts
|
|
4601
|
-
init_resolve_caliber();
|
|
4602
|
-
import fs16 from "fs";
|
|
4603
|
-
import path16 from "path";
|
|
4604
|
-
function buildSkillContent(skill) {
|
|
4605
|
-
const frontmatter = `---
|
|
4606
|
-
name: ${skill.name}
|
|
4607
|
-
description: ${skill.description}
|
|
4608
|
-
---
|
|
4609
|
-
|
|
4610
|
-
`;
|
|
4611
|
-
return frontmatter + skill.content;
|
|
4612
|
-
}
|
|
4613
|
-
function getFindSkillsContent() {
|
|
4614
|
-
const bin = resolveCaliber();
|
|
4615
|
-
return `# Find Skills
|
|
4616
|
-
|
|
4617
|
-
Search the public skill registry for community-contributed skills
|
|
4618
|
-
relevant to the user's current task and install them into this project.
|
|
4619
|
-
|
|
4620
|
-
## Instructions
|
|
4621
|
-
|
|
4622
|
-
1. Identify the key technologies, frameworks, or task types from the
|
|
4623
|
-
user's request that might have community skills available
|
|
4624
|
-
2. Ask the user: "Would you like me to search for community skills
|
|
4625
|
-
for [identified technologies]?"
|
|
4626
|
-
3. If the user agrees, run:
|
|
4627
|
-
\`\`\`bash
|
|
4628
|
-
${bin} skills --query "<relevant terms>"
|
|
4629
|
-
\`\`\`
|
|
4630
|
-
This outputs the top 5 matching skills with scores and descriptions.
|
|
4631
|
-
4. Present the results to the user and ask which ones to install
|
|
4632
|
-
5. Install the selected skills:
|
|
4633
|
-
\`\`\`bash
|
|
4634
|
-
${bin} skills --install <slug1>,<slug2>
|
|
4635
|
-
\`\`\`
|
|
4636
|
-
6. Read the installed SKILL.md files to load them into your current
|
|
4637
|
-
context so you can use them immediately in this session
|
|
4638
|
-
7. Summarize what was installed and continue with the user's task
|
|
4639
|
-
|
|
4640
|
-
## Examples
|
|
4641
|
-
|
|
4642
|
-
User: "let's build a web app using React"
|
|
4643
|
-
-> "I notice you want to work with React. Would you like me to search
|
|
4644
|
-
for community skills that could help with React development?"
|
|
4645
|
-
-> If yes: run \`${bin} skills --query "react frontend"\`
|
|
4646
|
-
-> Show the user the results, ask which to install
|
|
4647
|
-
-> Run \`${bin} skills --install <selected-slugs>\`
|
|
4648
|
-
-> Read the installed files and continue
|
|
4649
|
-
|
|
4650
|
-
User: "help me set up Docker for this project"
|
|
4651
|
-
-> "Would you like me to search for Docker-related skills?"
|
|
4652
|
-
-> If yes: run \`${bin} skills --query "docker deployment"\`
|
|
4653
|
-
|
|
4654
|
-
User: "I need to write tests for this Python ML pipeline"
|
|
4655
|
-
-> "Would you like me to find skills for Python ML testing?"
|
|
4656
|
-
-> If yes: run \`${bin} skills --query "python machine-learning testing"\`
|
|
4657
|
-
|
|
4658
|
-
## When NOT to trigger
|
|
4659
|
-
|
|
4660
|
-
- The user is working within an already well-configured area
|
|
4661
|
-
- You already suggested skills for this technology in this session
|
|
4662
|
-
- The user is in the middle of urgent debugging or time-sensitive work
|
|
4663
|
-
- The technology is too generic (e.g. just "code" or "programming")
|
|
4664
|
-
`;
|
|
4665
|
-
}
|
|
4666
|
-
function getSaveLearningContent() {
|
|
4667
|
-
const bin = resolveCaliber();
|
|
4668
|
-
return `# Save Learning
|
|
4669
|
-
|
|
4670
|
-
Save a user's instruction or preference as a persistent learning that
|
|
4671
|
-
will be applied in all future sessions on this project.
|
|
4672
|
-
|
|
4673
|
-
## Instructions
|
|
4674
|
-
|
|
4675
|
-
1. Detect when the user gives an instruction to remember, such as:
|
|
4676
|
-
- "remember this", "save this", "always do X", "never do Y"
|
|
4677
|
-
- "from now on", "going forward", "in this project we..."
|
|
4678
|
-
- Any stated convention, preference, or rule
|
|
4679
|
-
2. Refine the instruction into a clean, actionable learning bullet with
|
|
4680
|
-
an appropriate type prefix:
|
|
4681
|
-
- \`**[convention]**\` \u2014 coding style, workflow, git conventions
|
|
4682
|
-
- \`**[pattern]**\` \u2014 reusable code patterns
|
|
4683
|
-
- \`**[anti-pattern]**\` \u2014 things to avoid
|
|
4684
|
-
- \`**[preference]**\` \u2014 personal/team preferences
|
|
4685
|
-
- \`**[context]**\` \u2014 project-specific context
|
|
4686
|
-
3. Show the refined learning to the user and ask for confirmation
|
|
4687
|
-
4. If confirmed, run:
|
|
4688
|
-
\`\`\`bash
|
|
4689
|
-
${bin} learn add "<refined learning>"
|
|
4690
|
-
\`\`\`
|
|
4691
|
-
For personal preferences (not project-level), add \`--personal\`:
|
|
4692
|
-
\`\`\`bash
|
|
4693
|
-
${bin} learn add --personal "<refined learning>"
|
|
4694
|
-
\`\`\`
|
|
4695
|
-
5. Stage the learnings file for the next commit:
|
|
4696
|
-
\`\`\`bash
|
|
4697
|
-
git add CALIBER_LEARNINGS.md
|
|
4698
|
-
\`\`\`
|
|
4699
|
-
|
|
4700
|
-
## Examples
|
|
4701
|
-
|
|
4702
|
-
User: "when developing features, push to next branch not master, remember it"
|
|
4703
|
-
-> Refine: \`**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`\`
|
|
4704
|
-
-> "I'll save this as a project learning:
|
|
4705
|
-
**[convention]** Push feature commits to the \\\`next\\\` branch, not \\\`master\\\`
|
|
4706
|
-
Save for future sessions?"
|
|
4707
|
-
-> If yes: run \`${bin} learn add "**[convention]** Push feature commits to the next branch, not master"\`
|
|
4708
|
-
-> Run \`git add CALIBER_LEARNINGS.md\`
|
|
4709
|
-
|
|
4710
|
-
User: "always use bun instead of npm"
|
|
4711
|
-
-> Refine: \`**[preference]** Use \\\`bun\\\` instead of \\\`npm\\\` for package management\`
|
|
4712
|
-
-> Confirm and save
|
|
4713
|
-
|
|
4714
|
-
User: "never use any in TypeScript, use unknown instead"
|
|
4715
|
-
-> Refine: \`**[convention]** Use \\\`unknown\\\` instead of \\\`any\\\` in TypeScript\`
|
|
4716
|
-
-> Confirm and save
|
|
4717
|
-
|
|
4718
|
-
## When NOT to trigger
|
|
4719
|
-
|
|
4720
|
-
- The user is giving a one-time instruction for the current task only
|
|
4721
|
-
- The instruction is too vague to be actionable
|
|
4722
|
-
- The user explicitly says "just for now" or "only this time"
|
|
4723
|
-
`;
|
|
4724
|
-
}
|
|
4725
|
-
var FIND_SKILLS_SKILL = {
|
|
4726
|
-
name: "find-skills",
|
|
4727
|
-
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.",
|
|
4728
|
-
get content() {
|
|
4729
|
-
return getFindSkillsContent();
|
|
5138
|
+
// src/writers/cursor/index.ts
|
|
5139
|
+
init_pre_commit_block();
|
|
5140
|
+
import fs13 from "fs";
|
|
5141
|
+
import path13 from "path";
|
|
5142
|
+
function writeCursorConfig(config) {
|
|
5143
|
+
const written = [];
|
|
5144
|
+
if (config.cursorrules) {
|
|
5145
|
+
fs13.writeFileSync(".cursorrules", config.cursorrules);
|
|
5146
|
+
written.push(".cursorrules");
|
|
4730
5147
|
}
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
5148
|
+
const preCommitRule = getCursorPreCommitRule();
|
|
5149
|
+
const learningsRule = getCursorLearningsRule();
|
|
5150
|
+
const allRules = [...config.rules || [], preCommitRule, learningsRule];
|
|
5151
|
+
const rulesDir = path13.join(".cursor", "rules");
|
|
5152
|
+
if (!fs13.existsSync(rulesDir)) fs13.mkdirSync(rulesDir, { recursive: true });
|
|
5153
|
+
for (const rule of allRules) {
|
|
5154
|
+
const rulePath = path13.join(rulesDir, rule.filename);
|
|
5155
|
+
fs13.writeFileSync(rulePath, rule.content);
|
|
5156
|
+
written.push(rulePath);
|
|
4737
5157
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
5158
|
+
if (config.skills?.length) {
|
|
5159
|
+
for (const skill of config.skills) {
|
|
5160
|
+
const skillDir = path13.join(".cursor", "skills", skill.name);
|
|
5161
|
+
if (!fs13.existsSync(skillDir)) fs13.mkdirSync(skillDir, { recursive: true });
|
|
5162
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
5163
|
+
const frontmatter = [
|
|
5164
|
+
"---",
|
|
5165
|
+
`name: ${skill.name}`,
|
|
5166
|
+
`description: ${skill.description}`,
|
|
5167
|
+
"---",
|
|
5168
|
+
""
|
|
5169
|
+
].join("\n");
|
|
5170
|
+
fs13.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5171
|
+
written.push(skillPath);
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
5175
|
+
const cursorDir = ".cursor";
|
|
5176
|
+
if (!fs13.existsSync(cursorDir)) fs13.mkdirSync(cursorDir, { recursive: true });
|
|
5177
|
+
const mcpPath = path13.join(cursorDir, "mcp.json");
|
|
5178
|
+
let existingServers = {};
|
|
5179
|
+
try {
|
|
5180
|
+
if (fs13.existsSync(mcpPath)) {
|
|
5181
|
+
const existing = JSON.parse(fs13.readFileSync(mcpPath, "utf-8"));
|
|
5182
|
+
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
5183
|
+
}
|
|
5184
|
+
} catch {
|
|
5185
|
+
}
|
|
5186
|
+
const mergedServers = { ...existingServers, ...config.mcpServers };
|
|
5187
|
+
fs13.writeFileSync(mcpPath, JSON.stringify({ mcpServers: mergedServers }, null, 2));
|
|
5188
|
+
written.push(mcpPath);
|
|
5189
|
+
}
|
|
5190
|
+
return written;
|
|
5191
|
+
}
|
|
5192
|
+
|
|
5193
|
+
// src/writers/codex/index.ts
|
|
5194
|
+
init_pre_commit_block();
|
|
5195
|
+
import fs14 from "fs";
|
|
5196
|
+
import path14 from "path";
|
|
5197
|
+
function writeCodexConfig(config) {
|
|
4746
5198
|
const written = [];
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
5199
|
+
fs14.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
5200
|
+
written.push("AGENTS.md");
|
|
5201
|
+
if (config.skills?.length) {
|
|
5202
|
+
for (const skill of config.skills) {
|
|
5203
|
+
const skillDir = path14.join(".agents", "skills", skill.name);
|
|
5204
|
+
if (!fs14.existsSync(skillDir)) fs14.mkdirSync(skillDir, { recursive: true });
|
|
5205
|
+
const skillPath = path14.join(skillDir, "SKILL.md");
|
|
5206
|
+
const frontmatter = [
|
|
5207
|
+
"---",
|
|
5208
|
+
`name: ${skill.name}`,
|
|
5209
|
+
`description: ${skill.description}`,
|
|
5210
|
+
"---",
|
|
5211
|
+
""
|
|
5212
|
+
].join("\n");
|
|
5213
|
+
fs14.writeFileSync(skillPath, frontmatter + skill.content);
|
|
4754
5214
|
written.push(skillPath);
|
|
4755
5215
|
}
|
|
4756
5216
|
}
|
|
4757
5217
|
return written;
|
|
4758
5218
|
}
|
|
4759
5219
|
|
|
5220
|
+
// src/writers/github-copilot/index.ts
|
|
5221
|
+
init_pre_commit_block();
|
|
5222
|
+
import fs15 from "fs";
|
|
5223
|
+
import path15 from "path";
|
|
5224
|
+
function writeGithubCopilotConfig(config) {
|
|
5225
|
+
const written = [];
|
|
5226
|
+
if (config.instructions) {
|
|
5227
|
+
fs15.mkdirSync(".github", { recursive: true });
|
|
5228
|
+
fs15.writeFileSync(path15.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(config.instructions)));
|
|
5229
|
+
written.push(".github/copilot-instructions.md");
|
|
5230
|
+
}
|
|
5231
|
+
if (config.instructionFiles?.length) {
|
|
5232
|
+
const instructionsDir = path15.join(".github", "instructions");
|
|
5233
|
+
fs15.mkdirSync(instructionsDir, { recursive: true });
|
|
5234
|
+
for (const file of config.instructionFiles) {
|
|
5235
|
+
fs15.writeFileSync(path15.join(instructionsDir, file.filename), file.content);
|
|
5236
|
+
written.push(`.github/instructions/${file.filename}`);
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
return written;
|
|
5240
|
+
}
|
|
5241
|
+
|
|
5242
|
+
// src/writers/backup.ts
|
|
5243
|
+
import fs16 from "fs";
|
|
5244
|
+
import path16 from "path";
|
|
5245
|
+
function createBackup(files) {
|
|
5246
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5247
|
+
const backupDir = path16.join(BACKUPS_DIR, timestamp);
|
|
5248
|
+
for (const file of files) {
|
|
5249
|
+
if (!fs16.existsSync(file)) continue;
|
|
5250
|
+
const dest = path16.join(backupDir, file);
|
|
5251
|
+
const destDir = path16.dirname(dest);
|
|
5252
|
+
if (!fs16.existsSync(destDir)) {
|
|
5253
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5254
|
+
}
|
|
5255
|
+
fs16.copyFileSync(file, dest);
|
|
5256
|
+
}
|
|
5257
|
+
return backupDir;
|
|
5258
|
+
}
|
|
5259
|
+
function restoreBackup(backupDir, file) {
|
|
5260
|
+
const backupFile = path16.join(backupDir, file);
|
|
5261
|
+
if (!fs16.existsSync(backupFile)) return false;
|
|
5262
|
+
const destDir = path16.dirname(file);
|
|
5263
|
+
if (!fs16.existsSync(destDir)) {
|
|
5264
|
+
fs16.mkdirSync(destDir, { recursive: true });
|
|
5265
|
+
}
|
|
5266
|
+
fs16.copyFileSync(backupFile, file);
|
|
5267
|
+
return true;
|
|
5268
|
+
}
|
|
5269
|
+
|
|
5270
|
+
// src/writers/index.ts
|
|
5271
|
+
init_builtin_skills();
|
|
5272
|
+
|
|
4760
5273
|
// src/writers/manifest.ts
|
|
4761
|
-
import
|
|
5274
|
+
import fs18 from "fs";
|
|
4762
5275
|
import crypto3 from "crypto";
|
|
4763
5276
|
function readManifest() {
|
|
4764
5277
|
try {
|
|
4765
|
-
if (!
|
|
4766
|
-
return JSON.parse(
|
|
5278
|
+
if (!fs18.existsSync(MANIFEST_FILE)) return null;
|
|
5279
|
+
return JSON.parse(fs18.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
4767
5280
|
} catch {
|
|
4768
5281
|
return null;
|
|
4769
5282
|
}
|
|
4770
5283
|
}
|
|
4771
5284
|
function writeManifest(manifest) {
|
|
4772
|
-
if (!
|
|
4773
|
-
|
|
5285
|
+
if (!fs18.existsSync(CALIBER_DIR)) {
|
|
5286
|
+
fs18.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
4774
5287
|
}
|
|
4775
|
-
|
|
5288
|
+
fs18.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
4776
5289
|
}
|
|
4777
5290
|
function fileChecksum(filePath) {
|
|
4778
|
-
const content =
|
|
5291
|
+
const content = fs18.readFileSync(filePath);
|
|
4779
5292
|
return crypto3.createHash("sha256").update(content).digest("hex");
|
|
4780
5293
|
}
|
|
4781
5294
|
|
|
4782
5295
|
// src/writers/index.ts
|
|
4783
5296
|
function writeSetup(setup) {
|
|
4784
5297
|
const filesToWrite = getFilesToWrite(setup);
|
|
4785
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
4786
|
-
const existingFiles = [...filesToWrite.filter((f) =>
|
|
5298
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs19.existsSync(f));
|
|
5299
|
+
const existingFiles = [...filesToWrite.filter((f) => fs19.existsSync(f)), ...filesToDelete];
|
|
4787
5300
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
4788
5301
|
const written = [];
|
|
4789
5302
|
if (setup.targetAgent.includes("claude") && setup.claude) {
|
|
@@ -4800,7 +5313,7 @@ function writeSetup(setup) {
|
|
|
4800
5313
|
}
|
|
4801
5314
|
const deleted = [];
|
|
4802
5315
|
for (const filePath of filesToDelete) {
|
|
4803
|
-
|
|
5316
|
+
fs19.unlinkSync(filePath);
|
|
4804
5317
|
deleted.push(filePath);
|
|
4805
5318
|
}
|
|
4806
5319
|
written.push(...ensureBuiltinSkills());
|
|
@@ -4831,8 +5344,8 @@ function undoSetup() {
|
|
|
4831
5344
|
const removed = [];
|
|
4832
5345
|
for (const entry of manifest.entries) {
|
|
4833
5346
|
if (entry.action === "created") {
|
|
4834
|
-
if (
|
|
4835
|
-
|
|
5347
|
+
if (fs19.existsSync(entry.path)) {
|
|
5348
|
+
fs19.unlinkSync(entry.path);
|
|
4836
5349
|
removed.push(entry.path);
|
|
4837
5350
|
}
|
|
4838
5351
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -4841,8 +5354,8 @@ function undoSetup() {
|
|
|
4841
5354
|
}
|
|
4842
5355
|
}
|
|
4843
5356
|
}
|
|
4844
|
-
if (
|
|
4845
|
-
|
|
5357
|
+
if (fs19.existsSync(MANIFEST_FILE)) {
|
|
5358
|
+
fs19.unlinkSync(MANIFEST_FILE);
|
|
4846
5359
|
}
|
|
4847
5360
|
return { restored, removed };
|
|
4848
5361
|
}
|
|
@@ -4884,22 +5397,22 @@ function getFilesToWrite(setup) {
|
|
|
4884
5397
|
}
|
|
4885
5398
|
function ensureGitignore() {
|
|
4886
5399
|
const gitignorePath = ".gitignore";
|
|
4887
|
-
if (
|
|
4888
|
-
const content =
|
|
5400
|
+
if (fs19.existsSync(gitignorePath)) {
|
|
5401
|
+
const content = fs19.readFileSync(gitignorePath, "utf-8");
|
|
4889
5402
|
if (!content.includes(".caliber/")) {
|
|
4890
|
-
|
|
5403
|
+
fs19.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
4891
5404
|
}
|
|
4892
5405
|
} else {
|
|
4893
|
-
|
|
5406
|
+
fs19.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
4894
5407
|
}
|
|
4895
5408
|
}
|
|
4896
5409
|
|
|
4897
5410
|
// src/writers/staging.ts
|
|
4898
|
-
import
|
|
4899
|
-
import
|
|
4900
|
-
var STAGED_DIR =
|
|
4901
|
-
var PROPOSED_DIR =
|
|
4902
|
-
var CURRENT_DIR =
|
|
5411
|
+
import fs20 from "fs";
|
|
5412
|
+
import path18 from "path";
|
|
5413
|
+
var STAGED_DIR = path18.join(CALIBER_DIR, "staged");
|
|
5414
|
+
var PROPOSED_DIR = path18.join(STAGED_DIR, "proposed");
|
|
5415
|
+
var CURRENT_DIR = path18.join(STAGED_DIR, "current");
|
|
4903
5416
|
function normalizeContent(content) {
|
|
4904
5417
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
4905
5418
|
}
|
|
@@ -4910,20 +5423,20 @@ function stageFiles(files, projectDir) {
|
|
|
4910
5423
|
const stagedFiles = [];
|
|
4911
5424
|
for (const file of files) {
|
|
4912
5425
|
assertPathWithinDir(file.path, projectDir);
|
|
4913
|
-
const originalPath =
|
|
4914
|
-
if (
|
|
4915
|
-
const existing =
|
|
5426
|
+
const originalPath = path18.join(projectDir, file.path);
|
|
5427
|
+
if (fs20.existsSync(originalPath)) {
|
|
5428
|
+
const existing = fs20.readFileSync(originalPath, "utf-8");
|
|
4916
5429
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
4917
5430
|
continue;
|
|
4918
5431
|
}
|
|
4919
5432
|
}
|
|
4920
|
-
const proposedPath =
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
if (
|
|
4924
|
-
const currentPath =
|
|
4925
|
-
|
|
4926
|
-
|
|
5433
|
+
const proposedPath = path18.join(PROPOSED_DIR, file.path);
|
|
5434
|
+
fs20.mkdirSync(path18.dirname(proposedPath), { recursive: true });
|
|
5435
|
+
fs20.writeFileSync(proposedPath, file.content);
|
|
5436
|
+
if (fs20.existsSync(originalPath)) {
|
|
5437
|
+
const currentPath = path18.join(CURRENT_DIR, file.path);
|
|
5438
|
+
fs20.mkdirSync(path18.dirname(currentPath), { recursive: true });
|
|
5439
|
+
fs20.copyFileSync(originalPath, currentPath);
|
|
4927
5440
|
modifiedFiles++;
|
|
4928
5441
|
stagedFiles.push({
|
|
4929
5442
|
relativePath: file.path,
|
|
@@ -4940,13 +5453,14 @@ function stageFiles(files, projectDir) {
|
|
|
4940
5453
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
4941
5454
|
}
|
|
4942
5455
|
function cleanupStaging() {
|
|
4943
|
-
if (
|
|
4944
|
-
|
|
5456
|
+
if (fs20.existsSync(STAGED_DIR)) {
|
|
5457
|
+
fs20.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
4945
5458
|
}
|
|
4946
5459
|
}
|
|
4947
5460
|
|
|
4948
5461
|
// src/commands/setup-files.ts
|
|
4949
|
-
|
|
5462
|
+
init_builtin_skills();
|
|
5463
|
+
import fs21 from "fs";
|
|
4950
5464
|
function collectSetupFiles(setup, targetAgent) {
|
|
4951
5465
|
const files = [];
|
|
4952
5466
|
const claude = setup.claude;
|
|
@@ -5031,7 +5545,7 @@ function collectSetupFiles(setup, targetAgent) {
|
|
|
5031
5545
|
}
|
|
5032
5546
|
}
|
|
5033
5547
|
const codexTargeted = targetAgent ? targetAgent.includes("codex") : false;
|
|
5034
|
-
if (codexTargeted && !
|
|
5548
|
+
if (codexTargeted && !fs21.existsSync("AGENTS.md") && !(codex && codex.agentsMd)) {
|
|
5035
5549
|
const agentRefs = [];
|
|
5036
5550
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
5037
5551
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -5050,9 +5564,9 @@ ${agentRefs.join(" ")}
|
|
|
5050
5564
|
|
|
5051
5565
|
// src/lib/learning-hooks.ts
|
|
5052
5566
|
init_resolve_caliber();
|
|
5053
|
-
import
|
|
5054
|
-
import
|
|
5055
|
-
var
|
|
5567
|
+
import fs22 from "fs";
|
|
5568
|
+
import path19 from "path";
|
|
5569
|
+
var SETTINGS_PATH2 = path19.join(".claude", "settings.json");
|
|
5056
5570
|
var HOOK_TAILS = [
|
|
5057
5571
|
{ event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
|
|
5058
5572
|
{ event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
|
|
@@ -5068,24 +5582,24 @@ function getHookConfigs() {
|
|
|
5068
5582
|
description
|
|
5069
5583
|
}));
|
|
5070
5584
|
}
|
|
5071
|
-
function
|
|
5072
|
-
if (!
|
|
5585
|
+
function readSettings2() {
|
|
5586
|
+
if (!fs22.existsSync(SETTINGS_PATH2)) return {};
|
|
5073
5587
|
try {
|
|
5074
|
-
return JSON.parse(
|
|
5588
|
+
return JSON.parse(fs22.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
5075
5589
|
} catch {
|
|
5076
5590
|
return {};
|
|
5077
5591
|
}
|
|
5078
5592
|
}
|
|
5079
|
-
function
|
|
5080
|
-
const dir =
|
|
5081
|
-
if (!
|
|
5082
|
-
|
|
5593
|
+
function writeSettings2(settings) {
|
|
5594
|
+
const dir = path19.dirname(SETTINGS_PATH2);
|
|
5595
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5596
|
+
fs22.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
5083
5597
|
}
|
|
5084
5598
|
function hasLearningHook(matchers, tail) {
|
|
5085
5599
|
return matchers.some((entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, tail)));
|
|
5086
5600
|
}
|
|
5087
5601
|
function areLearningHooksInstalled() {
|
|
5088
|
-
const settings =
|
|
5602
|
+
const settings = readSettings2();
|
|
5089
5603
|
if (!settings.hooks) return false;
|
|
5090
5604
|
return HOOK_TAILS.every((cfg) => {
|
|
5091
5605
|
const matchers = settings.hooks[cfg.event];
|
|
@@ -5096,7 +5610,7 @@ function installLearningHooks() {
|
|
|
5096
5610
|
if (areLearningHooksInstalled()) {
|
|
5097
5611
|
return { installed: false, alreadyInstalled: true };
|
|
5098
5612
|
}
|
|
5099
|
-
const settings =
|
|
5613
|
+
const settings = readSettings2();
|
|
5100
5614
|
if (!settings.hooks) settings.hooks = {};
|
|
5101
5615
|
const configs = getHookConfigs();
|
|
5102
5616
|
for (const cfg of configs) {
|
|
@@ -5110,10 +5624,10 @@ function installLearningHooks() {
|
|
|
5110
5624
|
});
|
|
5111
5625
|
}
|
|
5112
5626
|
}
|
|
5113
|
-
|
|
5627
|
+
writeSettings2(settings);
|
|
5114
5628
|
return { installed: true, alreadyInstalled: false };
|
|
5115
5629
|
}
|
|
5116
|
-
var CURSOR_HOOKS_PATH =
|
|
5630
|
+
var CURSOR_HOOKS_PATH = path19.join(".cursor", "hooks.json");
|
|
5117
5631
|
var CURSOR_HOOK_EVENTS = [
|
|
5118
5632
|
{ event: "postToolUse", tail: "learn observe" },
|
|
5119
5633
|
{ event: "postToolUseFailure", tail: "learn observe --failure" },
|
|
@@ -5121,17 +5635,17 @@ var CURSOR_HOOK_EVENTS = [
|
|
|
5121
5635
|
{ event: "sessionEnd", tail: "learn finalize --auto" }
|
|
5122
5636
|
];
|
|
5123
5637
|
function readCursorHooks() {
|
|
5124
|
-
if (!
|
|
5638
|
+
if (!fs22.existsSync(CURSOR_HOOKS_PATH)) return { version: 1, hooks: {} };
|
|
5125
5639
|
try {
|
|
5126
|
-
return JSON.parse(
|
|
5640
|
+
return JSON.parse(fs22.readFileSync(CURSOR_HOOKS_PATH, "utf-8"));
|
|
5127
5641
|
} catch {
|
|
5128
5642
|
return { version: 1, hooks: {} };
|
|
5129
5643
|
}
|
|
5130
5644
|
}
|
|
5131
5645
|
function writeCursorHooks(config) {
|
|
5132
|
-
const dir =
|
|
5133
|
-
if (!
|
|
5134
|
-
|
|
5646
|
+
const dir = path19.dirname(CURSOR_HOOKS_PATH);
|
|
5647
|
+
if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
|
|
5648
|
+
fs22.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
5135
5649
|
}
|
|
5136
5650
|
function hasCursorHook(entries, tail) {
|
|
5137
5651
|
return entries.some((e) => isCaliberCommand(e.command, tail));
|
|
@@ -5178,7 +5692,7 @@ function removeCursorLearningHooks() {
|
|
|
5178
5692
|
return { removed: true, notFound: false };
|
|
5179
5693
|
}
|
|
5180
5694
|
function removeLearningHooks() {
|
|
5181
|
-
const settings =
|
|
5695
|
+
const settings = readSettings2();
|
|
5182
5696
|
if (!settings.hooks) return { removed: false, notFound: true };
|
|
5183
5697
|
let removedAny = false;
|
|
5184
5698
|
for (const cfg of HOOK_TAILS) {
|
|
@@ -5195,7 +5709,7 @@ function removeLearningHooks() {
|
|
|
5195
5709
|
delete settings.hooks;
|
|
5196
5710
|
}
|
|
5197
5711
|
if (!removedAny) return { removed: false, notFound: true };
|
|
5198
|
-
|
|
5712
|
+
writeSettings2(settings);
|
|
5199
5713
|
return { removed: true, notFound: false };
|
|
5200
5714
|
}
|
|
5201
5715
|
|
|
@@ -5203,10 +5717,10 @@ function removeLearningHooks() {
|
|
|
5203
5717
|
init_resolve_caliber();
|
|
5204
5718
|
|
|
5205
5719
|
// src/lib/state.ts
|
|
5206
|
-
import
|
|
5207
|
-
import
|
|
5208
|
-
import { execSync as
|
|
5209
|
-
var STATE_FILE =
|
|
5720
|
+
import fs23 from "fs";
|
|
5721
|
+
import path20 from "path";
|
|
5722
|
+
import { execSync as execSync9 } from "child_process";
|
|
5723
|
+
var STATE_FILE = path20.join(CALIBER_DIR, ".caliber-state.json");
|
|
5210
5724
|
function normalizeTargetAgent(value) {
|
|
5211
5725
|
if (Array.isArray(value)) return value;
|
|
5212
5726
|
if (typeof value === "string") {
|
|
@@ -5217,8 +5731,8 @@ function normalizeTargetAgent(value) {
|
|
|
5217
5731
|
}
|
|
5218
5732
|
function readState() {
|
|
5219
5733
|
try {
|
|
5220
|
-
if (!
|
|
5221
|
-
const raw = JSON.parse(
|
|
5734
|
+
if (!fs23.existsSync(STATE_FILE)) return null;
|
|
5735
|
+
const raw = JSON.parse(fs23.readFileSync(STATE_FILE, "utf-8"));
|
|
5222
5736
|
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
5223
5737
|
return raw;
|
|
5224
5738
|
} catch {
|
|
@@ -5226,14 +5740,14 @@ function readState() {
|
|
|
5226
5740
|
}
|
|
5227
5741
|
}
|
|
5228
5742
|
function writeState(state) {
|
|
5229
|
-
if (!
|
|
5230
|
-
|
|
5743
|
+
if (!fs23.existsSync(CALIBER_DIR)) {
|
|
5744
|
+
fs23.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
5231
5745
|
}
|
|
5232
|
-
|
|
5746
|
+
fs23.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
5233
5747
|
}
|
|
5234
5748
|
function getCurrentHeadSha() {
|
|
5235
5749
|
try {
|
|
5236
|
-
return
|
|
5750
|
+
return execSync9("git rev-parse HEAD", {
|
|
5237
5751
|
encoding: "utf-8",
|
|
5238
5752
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5239
5753
|
}).trim();
|
|
@@ -5343,6 +5857,9 @@ async function runInteractiveProviderSetup(options) {
|
|
|
5343
5857
|
return config;
|
|
5344
5858
|
}
|
|
5345
5859
|
|
|
5860
|
+
// src/commands/init.ts
|
|
5861
|
+
import confirm2 from "@inquirer/confirm";
|
|
5862
|
+
|
|
5346
5863
|
// src/scoring/index.ts
|
|
5347
5864
|
import { existsSync as existsSync7 } from "fs";
|
|
5348
5865
|
import { join as join9 } from "path";
|
|
@@ -5833,7 +6350,7 @@ function checkGrounding(dir) {
|
|
|
5833
6350
|
|
|
5834
6351
|
// src/scoring/checks/accuracy.ts
|
|
5835
6352
|
import { existsSync as existsSync4, statSync } from "fs";
|
|
5836
|
-
import { execSync as
|
|
6353
|
+
import { execSync as execSync10 } from "child_process";
|
|
5837
6354
|
import { join as join5 } from "path";
|
|
5838
6355
|
init_resolve_caliber();
|
|
5839
6356
|
function validateReferences(dir) {
|
|
@@ -5843,13 +6360,13 @@ function validateReferences(dir) {
|
|
|
5843
6360
|
}
|
|
5844
6361
|
function detectGitDrift(dir) {
|
|
5845
6362
|
try {
|
|
5846
|
-
|
|
6363
|
+
execSync10("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
|
|
5847
6364
|
} catch {
|
|
5848
6365
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
|
|
5849
6366
|
}
|
|
5850
6367
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
|
|
5851
6368
|
try {
|
|
5852
|
-
const headTimestamp =
|
|
6369
|
+
const headTimestamp = execSync10(
|
|
5853
6370
|
"git log -1 --format=%ct HEAD",
|
|
5854
6371
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5855
6372
|
).trim();
|
|
@@ -5870,7 +6387,7 @@ function detectGitDrift(dir) {
|
|
|
5870
6387
|
let latestConfigCommitHash = null;
|
|
5871
6388
|
for (const file of configFiles) {
|
|
5872
6389
|
try {
|
|
5873
|
-
const hash =
|
|
6390
|
+
const hash = execSync10(
|
|
5874
6391
|
`git log -1 --format=%H -- "${file}"`,
|
|
5875
6392
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5876
6393
|
).trim();
|
|
@@ -5879,7 +6396,7 @@ function detectGitDrift(dir) {
|
|
|
5879
6396
|
latestConfigCommitHash = hash;
|
|
5880
6397
|
} else {
|
|
5881
6398
|
try {
|
|
5882
|
-
|
|
6399
|
+
execSync10(
|
|
5883
6400
|
`git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
|
|
5884
6401
|
{ cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
|
|
5885
6402
|
);
|
|
@@ -5894,12 +6411,12 @@ function detectGitDrift(dir) {
|
|
|
5894
6411
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
|
|
5895
6412
|
}
|
|
5896
6413
|
try {
|
|
5897
|
-
const countStr =
|
|
6414
|
+
const countStr = execSync10(
|
|
5898
6415
|
`git rev-list --count ${latestConfigCommitHash}..HEAD`,
|
|
5899
6416
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5900
6417
|
).trim();
|
|
5901
6418
|
const commitsSince = parseInt(countStr, 10) || 0;
|
|
5902
|
-
const lastDate =
|
|
6419
|
+
const lastDate = execSync10(
|
|
5903
6420
|
`git log -1 --format=%ci ${latestConfigCommitHash}`,
|
|
5904
6421
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5905
6422
|
).trim();
|
|
@@ -5972,12 +6489,12 @@ function checkAccuracy(dir) {
|
|
|
5972
6489
|
// src/scoring/checks/freshness.ts
|
|
5973
6490
|
init_resolve_caliber();
|
|
5974
6491
|
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
5975
|
-
import { execSync as
|
|
6492
|
+
import { execSync as execSync11 } from "child_process";
|
|
5976
6493
|
import { join as join6 } from "path";
|
|
5977
6494
|
function getCommitsSinceConfigUpdate(dir) {
|
|
5978
6495
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
|
|
5979
6496
|
try {
|
|
5980
|
-
const headTimestamp =
|
|
6497
|
+
const headTimestamp = execSync11(
|
|
5981
6498
|
"git log -1 --format=%ct HEAD",
|
|
5982
6499
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5983
6500
|
).trim();
|
|
@@ -5997,12 +6514,12 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
5997
6514
|
}
|
|
5998
6515
|
for (const file of configFiles) {
|
|
5999
6516
|
try {
|
|
6000
|
-
const hash =
|
|
6517
|
+
const hash = execSync11(
|
|
6001
6518
|
`git log -1 --format=%H -- "${file}"`,
|
|
6002
6519
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6003
6520
|
).trim();
|
|
6004
6521
|
if (hash) {
|
|
6005
|
-
const countStr =
|
|
6522
|
+
const countStr = execSync11(
|
|
6006
6523
|
`git rev-list --count ${hash}..HEAD`,
|
|
6007
6524
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6008
6525
|
).trim();
|
|
@@ -6120,12 +6637,13 @@ function checkFreshness(dir) {
|
|
|
6120
6637
|
|
|
6121
6638
|
// src/scoring/checks/bonus.ts
|
|
6122
6639
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
6123
|
-
import { execSync as
|
|
6640
|
+
import { execSync as execSync12 } from "child_process";
|
|
6124
6641
|
import { join as join7 } from "path";
|
|
6125
6642
|
init_resolve_caliber();
|
|
6643
|
+
init_pre_commit_block();
|
|
6126
6644
|
function hasPreCommitHook(dir) {
|
|
6127
6645
|
try {
|
|
6128
|
-
const gitDir =
|
|
6646
|
+
const gitDir = execSync12("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6129
6647
|
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
6130
6648
|
const content = readFileOrNull(hookPath);
|
|
6131
6649
|
return content ? content.includes("caliber") : false;
|
|
@@ -6282,22 +6800,22 @@ function checkSources(dir) {
|
|
|
6282
6800
|
}
|
|
6283
6801
|
|
|
6284
6802
|
// src/scoring/dismissed.ts
|
|
6285
|
-
import
|
|
6286
|
-
import
|
|
6287
|
-
var DISMISSED_FILE =
|
|
6803
|
+
import fs24 from "fs";
|
|
6804
|
+
import path21 from "path";
|
|
6805
|
+
var DISMISSED_FILE = path21.join(CALIBER_DIR, "dismissed-checks.json");
|
|
6288
6806
|
function readDismissedChecks() {
|
|
6289
6807
|
try {
|
|
6290
|
-
if (!
|
|
6291
|
-
return JSON.parse(
|
|
6808
|
+
if (!fs24.existsSync(DISMISSED_FILE)) return [];
|
|
6809
|
+
return JSON.parse(fs24.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
6292
6810
|
} catch {
|
|
6293
6811
|
return [];
|
|
6294
6812
|
}
|
|
6295
6813
|
}
|
|
6296
6814
|
function writeDismissedChecks(checks) {
|
|
6297
|
-
if (!
|
|
6298
|
-
|
|
6815
|
+
if (!fs24.existsSync(CALIBER_DIR)) {
|
|
6816
|
+
fs24.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
6299
6817
|
}
|
|
6300
|
-
|
|
6818
|
+
fs24.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
6301
6819
|
}
|
|
6302
6820
|
function getDismissedIds() {
|
|
6303
6821
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -6327,8 +6845,8 @@ function detectTargetAgent(dir) {
|
|
|
6327
6845
|
const agents = [];
|
|
6328
6846
|
if (existsSync7(join9(dir, "CLAUDE.md")) || existsSync7(join9(dir, ".claude", "skills"))) agents.push("claude");
|
|
6329
6847
|
if (existsSync7(join9(dir, ".cursorrules")) || existsSync7(join9(dir, ".cursor", "rules"))) agents.push("cursor");
|
|
6330
|
-
if (existsSync7(join9(dir, ".codex")) || existsSync7(join9(dir, ".agents", "skills"))) agents.push("codex");
|
|
6331
|
-
if (existsSync7(join9(dir, ".github", "copilot-instructions.md"))) agents.push("github-copilot");
|
|
6848
|
+
if (existsSync7(join9(dir, ".codex")) || existsSync7(join9(dir, ".agents", "skills")) || existsSync7(join9(dir, "AGENTS.md"))) agents.push("codex");
|
|
6849
|
+
if (existsSync7(join9(dir, ".github", "copilot-instructions.md")) || existsSync7(join9(dir, ".github", "instructions"))) agents.push("github-copilot");
|
|
6332
6850
|
return agents.length > 0 ? agents : ["claude"];
|
|
6333
6851
|
}
|
|
6334
6852
|
function computeLocalScore(dir, targetAgent) {
|
|
@@ -6552,27 +7070,27 @@ import { PostHog } from "posthog-node";
|
|
|
6552
7070
|
import chalk5 from "chalk";
|
|
6553
7071
|
|
|
6554
7072
|
// src/telemetry/config.ts
|
|
6555
|
-
import
|
|
6556
|
-
import
|
|
7073
|
+
import fs25 from "fs";
|
|
7074
|
+
import path22 from "path";
|
|
6557
7075
|
import os5 from "os";
|
|
6558
7076
|
import crypto4 from "crypto";
|
|
6559
|
-
import { execSync as
|
|
6560
|
-
var CONFIG_DIR2 =
|
|
6561
|
-
var CONFIG_FILE2 =
|
|
7077
|
+
import { execSync as execSync13 } from "child_process";
|
|
7078
|
+
var CONFIG_DIR2 = path22.join(os5.homedir(), ".caliber");
|
|
7079
|
+
var CONFIG_FILE2 = path22.join(CONFIG_DIR2, "config.json");
|
|
6562
7080
|
var runtimeDisabled = false;
|
|
6563
7081
|
function readConfig() {
|
|
6564
7082
|
try {
|
|
6565
|
-
if (!
|
|
6566
|
-
return JSON.parse(
|
|
7083
|
+
if (!fs25.existsSync(CONFIG_FILE2)) return {};
|
|
7084
|
+
return JSON.parse(fs25.readFileSync(CONFIG_FILE2, "utf-8"));
|
|
6567
7085
|
} catch {
|
|
6568
7086
|
return {};
|
|
6569
7087
|
}
|
|
6570
7088
|
}
|
|
6571
7089
|
function writeConfig(config) {
|
|
6572
|
-
if (!
|
|
6573
|
-
|
|
7090
|
+
if (!fs25.existsSync(CONFIG_DIR2)) {
|
|
7091
|
+
fs25.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
6574
7092
|
}
|
|
6575
|
-
|
|
7093
|
+
fs25.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
6576
7094
|
}
|
|
6577
7095
|
function getMachineId() {
|
|
6578
7096
|
const config = readConfig();
|
|
@@ -6582,11 +7100,57 @@ function getMachineId() {
|
|
|
6582
7100
|
return machineId;
|
|
6583
7101
|
}
|
|
6584
7102
|
var EMAIL_HASH_KEY = "caliber-telemetry-v1";
|
|
6585
|
-
|
|
7103
|
+
var PERSONAL_DOMAINS = /* @__PURE__ */ new Set([
|
|
7104
|
+
"gmail.com",
|
|
7105
|
+
"googlemail.com",
|
|
7106
|
+
"outlook.com",
|
|
7107
|
+
"hotmail.com",
|
|
7108
|
+
"live.com",
|
|
7109
|
+
"yahoo.com",
|
|
7110
|
+
"yahoo.co.uk",
|
|
7111
|
+
"icloud.com",
|
|
7112
|
+
"me.com",
|
|
7113
|
+
"mac.com",
|
|
7114
|
+
"aol.com",
|
|
7115
|
+
"protonmail.com",
|
|
7116
|
+
"proton.me",
|
|
7117
|
+
"pm.me",
|
|
7118
|
+
"mail.com",
|
|
7119
|
+
"zoho.com",
|
|
7120
|
+
"yandex.com",
|
|
7121
|
+
"gmx.com",
|
|
7122
|
+
"gmx.net",
|
|
7123
|
+
"fastmail.com",
|
|
7124
|
+
"tutanota.com",
|
|
7125
|
+
"tuta.io"
|
|
7126
|
+
]);
|
|
7127
|
+
function getGitEmail() {
|
|
6586
7128
|
try {
|
|
6587
|
-
const email =
|
|
6588
|
-
|
|
6589
|
-
|
|
7129
|
+
const email = execSync13("git config user.email", { encoding: "utf-8" }).trim();
|
|
7130
|
+
return email || void 0;
|
|
7131
|
+
} catch {
|
|
7132
|
+
return void 0;
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
function getGitEmailInfo() {
|
|
7136
|
+
const email = getGitEmail();
|
|
7137
|
+
if (!email) return {};
|
|
7138
|
+
const hash = crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(email).digest("hex");
|
|
7139
|
+
let domain;
|
|
7140
|
+
if (email.includes("@")) {
|
|
7141
|
+
const d = email.split("@")[1].toLowerCase();
|
|
7142
|
+
if (!PERSONAL_DOMAINS.has(d)) domain = d;
|
|
7143
|
+
}
|
|
7144
|
+
return { hash, domain };
|
|
7145
|
+
}
|
|
7146
|
+
function getRepoHash() {
|
|
7147
|
+
try {
|
|
7148
|
+
const remote = execSync13("git remote get-url origin || git rev-parse --show-toplevel", {
|
|
7149
|
+
encoding: "utf-8",
|
|
7150
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7151
|
+
}).trim();
|
|
7152
|
+
if (!remote) return void 0;
|
|
7153
|
+
return crypto4.createHmac("sha256", EMAIL_HASH_KEY).update(remote).digest("hex").slice(0, 16);
|
|
6590
7154
|
} catch {
|
|
6591
7155
|
return void 0;
|
|
6592
7156
|
}
|
|
@@ -6611,6 +7175,7 @@ function markNoticeShown() {
|
|
|
6611
7175
|
var POSTHOG_KEY = "phc_XXrV0pSX4s2QVxVoOaeuyXDvtlRwPAjovt1ttMGVMPp";
|
|
6612
7176
|
var client = null;
|
|
6613
7177
|
var distinctId = null;
|
|
7178
|
+
var superProperties = {};
|
|
6614
7179
|
function initTelemetry() {
|
|
6615
7180
|
if (isTelemetryDisabled()) return;
|
|
6616
7181
|
const machineId = getMachineId();
|
|
@@ -6626,11 +7191,17 @@ function initTelemetry() {
|
|
|
6626
7191
|
);
|
|
6627
7192
|
markNoticeShown();
|
|
6628
7193
|
}
|
|
6629
|
-
const gitEmailHash =
|
|
7194
|
+
const { hash: gitEmailHash, domain: emailDomain } = getGitEmailInfo();
|
|
7195
|
+
const repoHash = getRepoHash();
|
|
7196
|
+
superProperties = {
|
|
7197
|
+
...repoHash ? { repo_hash: repoHash } : {},
|
|
7198
|
+
...emailDomain ? { email_domain: emailDomain } : {}
|
|
7199
|
+
};
|
|
6630
7200
|
client.identify({
|
|
6631
7201
|
distinctId: machineId,
|
|
6632
7202
|
properties: {
|
|
6633
|
-
...gitEmailHash ? { git_email_hash: gitEmailHash } : {}
|
|
7203
|
+
...gitEmailHash ? { git_email_hash: gitEmailHash } : {},
|
|
7204
|
+
...emailDomain ? { email_domain: emailDomain } : {}
|
|
6634
7205
|
}
|
|
6635
7206
|
});
|
|
6636
7207
|
}
|
|
@@ -6639,7 +7210,7 @@ function trackEvent(name, properties) {
|
|
|
6639
7210
|
client.capture({
|
|
6640
7211
|
distinctId,
|
|
6641
7212
|
event: name,
|
|
6642
|
-
properties: properties
|
|
7213
|
+
properties: { ...superProperties, ...properties }
|
|
6643
7214
|
});
|
|
6644
7215
|
}
|
|
6645
7216
|
async function flushTelemetry() {
|
|
@@ -6688,11 +7259,14 @@ function trackInitSkillsSearch(searched, installedCount) {
|
|
|
6688
7259
|
function trackInitScoreRegression(oldScore, newScore) {
|
|
6689
7260
|
trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
|
|
6690
7261
|
}
|
|
7262
|
+
function trackInitCompleted(path42, score) {
|
|
7263
|
+
trackEvent("init_completed", { path: path42, score });
|
|
7264
|
+
}
|
|
6691
7265
|
function trackRegenerateCompleted(action, durationMs) {
|
|
6692
7266
|
trackEvent("regenerate_completed", { action, duration_ms: durationMs });
|
|
6693
7267
|
}
|
|
6694
|
-
function trackRefreshCompleted(changesCount, durationMs) {
|
|
6695
|
-
trackEvent("refresh_completed", { changes_count: changesCount, duration_ms: durationMs });
|
|
7268
|
+
function trackRefreshCompleted(changesCount, durationMs, trigger) {
|
|
7269
|
+
trackEvent("refresh_completed", { changes_count: changesCount, duration_ms: durationMs, trigger: trigger ?? "manual" });
|
|
6696
7270
|
}
|
|
6697
7271
|
function trackScoreComputed(score, agent) {
|
|
6698
7272
|
trackEvent("score_computed", { score, agent });
|
|
@@ -6706,6 +7280,9 @@ function trackSkillsInstalled(count) {
|
|
|
6706
7280
|
function trackUndoExecuted() {
|
|
6707
7281
|
trackEvent("undo_executed");
|
|
6708
7282
|
}
|
|
7283
|
+
function trackUninstallExecuted() {
|
|
7284
|
+
trackEvent("uninstall_executed");
|
|
7285
|
+
}
|
|
6709
7286
|
function trackInitLearnEnabled(enabled) {
|
|
6710
7287
|
trackEvent("init_learn_enabled", { enabled });
|
|
6711
7288
|
}
|
|
@@ -7677,11 +8254,11 @@ function countIssuePoints(issues) {
|
|
|
7677
8254
|
}
|
|
7678
8255
|
async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
7679
8256
|
const existsCache = /* @__PURE__ */ new Map();
|
|
7680
|
-
const cachedExists = (
|
|
7681
|
-
const cached = existsCache.get(
|
|
8257
|
+
const cachedExists = (path42) => {
|
|
8258
|
+
const cached = existsCache.get(path42);
|
|
7682
8259
|
if (cached !== void 0) return cached;
|
|
7683
|
-
const result = existsSync9(
|
|
7684
|
-
existsCache.set(
|
|
8260
|
+
const result = existsSync9(path42);
|
|
8261
|
+
existsCache.set(path42, result);
|
|
7685
8262
|
return result;
|
|
7686
8263
|
};
|
|
7687
8264
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -7820,8 +8397,8 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7820
8397
|
}
|
|
7821
8398
|
|
|
7822
8399
|
// src/lib/debug-report.ts
|
|
7823
|
-
import
|
|
7824
|
-
import
|
|
8400
|
+
import fs26 from "fs";
|
|
8401
|
+
import path23 from "path";
|
|
7825
8402
|
var DebugReport = class {
|
|
7826
8403
|
sections = [];
|
|
7827
8404
|
startTime;
|
|
@@ -7890,11 +8467,11 @@ var DebugReport = class {
|
|
|
7890
8467
|
lines.push(`| **Total** | **${formatMs(totalMs)}** |`);
|
|
7891
8468
|
lines.push("");
|
|
7892
8469
|
}
|
|
7893
|
-
const dir =
|
|
7894
|
-
if (!
|
|
7895
|
-
|
|
8470
|
+
const dir = path23.dirname(outputPath);
|
|
8471
|
+
if (!fs26.existsSync(dir)) {
|
|
8472
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
7896
8473
|
}
|
|
7897
|
-
|
|
8474
|
+
fs26.writeFileSync(outputPath, lines.join("\n"));
|
|
7898
8475
|
}
|
|
7899
8476
|
};
|
|
7900
8477
|
function formatMs(ms) {
|
|
@@ -8295,7 +8872,7 @@ import chalk11 from "chalk";
|
|
|
8295
8872
|
import ora3 from "ora";
|
|
8296
8873
|
import select5 from "@inquirer/select";
|
|
8297
8874
|
import checkbox from "@inquirer/checkbox";
|
|
8298
|
-
import
|
|
8875
|
+
import fs29 from "fs";
|
|
8299
8876
|
|
|
8300
8877
|
// src/ai/refine.ts
|
|
8301
8878
|
async function refineSetup(currentSetup, message, conversationHistory, callbacks) {
|
|
@@ -8436,10 +9013,10 @@ init_config();
|
|
|
8436
9013
|
init_review();
|
|
8437
9014
|
function detectAgents(dir) {
|
|
8438
9015
|
const agents = [];
|
|
8439
|
-
if (
|
|
8440
|
-
if (
|
|
8441
|
-
if (
|
|
8442
|
-
if (
|
|
9016
|
+
if (fs29.existsSync(`${dir}/.claude`)) agents.push("claude");
|
|
9017
|
+
if (fs29.existsSync(`${dir}/.cursor`)) agents.push("cursor");
|
|
9018
|
+
if (fs29.existsSync(`${dir}/.agents`) || fs29.existsSync(`${dir}/AGENTS.md`)) agents.push("codex");
|
|
9019
|
+
if (fs29.existsSync(`${dir}/.github/copilot-instructions.md`)) agents.push("github-copilot");
|
|
8443
9020
|
return agents;
|
|
8444
9021
|
}
|
|
8445
9022
|
async function promptAgent(detected) {
|
|
@@ -8461,25 +9038,6 @@ async function promptAgent(detected) {
|
|
|
8461
9038
|
});
|
|
8462
9039
|
return selected;
|
|
8463
9040
|
}
|
|
8464
|
-
async function promptLearnInstall(targetAgent) {
|
|
8465
|
-
const hasClaude = targetAgent.includes("claude");
|
|
8466
|
-
const hasCursor = targetAgent.includes("cursor");
|
|
8467
|
-
const agentName = hasClaude && hasCursor ? "Claude and Cursor" : hasClaude ? "Claude" : "Cursor";
|
|
8468
|
-
console.log(chalk11.bold(`
|
|
8469
|
-
Session Learning
|
|
8470
|
-
`));
|
|
8471
|
-
console.log(chalk11.dim(` Caliber can learn from your ${agentName} sessions \u2014 when a tool fails`));
|
|
8472
|
-
console.log(chalk11.dim(` or you correct a mistake, it captures the lesson so it won't`));
|
|
8473
|
-
console.log(chalk11.dim(` happen again. Runs once at session end using the fast model.
|
|
8474
|
-
`));
|
|
8475
|
-
return select5({
|
|
8476
|
-
message: "Enable session learning?",
|
|
8477
|
-
choices: [
|
|
8478
|
-
{ name: "Enable session learning (recommended)", value: true },
|
|
8479
|
-
{ name: "Skip for now", value: false }
|
|
8480
|
-
]
|
|
8481
|
-
});
|
|
8482
|
-
}
|
|
8483
9041
|
async function promptReviewAction(hasSkillResults, hasChanges, staged) {
|
|
8484
9042
|
if (!hasChanges && !hasSkillResults) return "accept";
|
|
8485
9043
|
const choices = [];
|
|
@@ -8561,18 +9119,18 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8561
9119
|
|
|
8562
9120
|
// src/commands/init-display.ts
|
|
8563
9121
|
import chalk12 from "chalk";
|
|
8564
|
-
import
|
|
9122
|
+
import fs30 from "fs";
|
|
8565
9123
|
function formatWhatChanged(setup) {
|
|
8566
9124
|
const lines = [];
|
|
8567
9125
|
const claude = setup.claude;
|
|
8568
9126
|
const codex = setup.codex;
|
|
8569
9127
|
const cursor = setup.cursor;
|
|
8570
9128
|
if (claude?.claudeMd) {
|
|
8571
|
-
const action =
|
|
9129
|
+
const action = fs30.existsSync("CLAUDE.md") ? "Updated" : "Created";
|
|
8572
9130
|
lines.push(`${action} CLAUDE.md`);
|
|
8573
9131
|
}
|
|
8574
9132
|
if (codex?.agentsMd) {
|
|
8575
|
-
const action =
|
|
9133
|
+
const action = fs30.existsSync("AGENTS.md") ? "Updated" : "Created";
|
|
8576
9134
|
lines.push(`${action} AGENTS.md`);
|
|
8577
9135
|
}
|
|
8578
9136
|
const allSkills = [];
|
|
@@ -8615,7 +9173,7 @@ function printSetupSummary(setup) {
|
|
|
8615
9173
|
};
|
|
8616
9174
|
if (claude) {
|
|
8617
9175
|
if (claude.claudeMd) {
|
|
8618
|
-
const icon =
|
|
9176
|
+
const icon = fs30.existsSync("CLAUDE.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8619
9177
|
const desc = getDescription("CLAUDE.md");
|
|
8620
9178
|
console.log(` ${icon} ${chalk12.bold("CLAUDE.md")}`);
|
|
8621
9179
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8625,7 +9183,7 @@ function printSetupSummary(setup) {
|
|
|
8625
9183
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
8626
9184
|
for (const skill of skills) {
|
|
8627
9185
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
8628
|
-
const icon =
|
|
9186
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8629
9187
|
const desc = getDescription(skillPath);
|
|
8630
9188
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8631
9189
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8636,7 +9194,7 @@ function printSetupSummary(setup) {
|
|
|
8636
9194
|
const codex = setup.codex;
|
|
8637
9195
|
if (codex) {
|
|
8638
9196
|
if (codex.agentsMd) {
|
|
8639
|
-
const icon =
|
|
9197
|
+
const icon = fs30.existsSync("AGENTS.md") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8640
9198
|
const desc = getDescription("AGENTS.md");
|
|
8641
9199
|
console.log(` ${icon} ${chalk12.bold("AGENTS.md")}`);
|
|
8642
9200
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8646,7 +9204,7 @@ function printSetupSummary(setup) {
|
|
|
8646
9204
|
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
8647
9205
|
for (const skill of codexSkills) {
|
|
8648
9206
|
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
8649
|
-
const icon =
|
|
9207
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8650
9208
|
const desc = getDescription(skillPath);
|
|
8651
9209
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8652
9210
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8656,7 +9214,7 @@ function printSetupSummary(setup) {
|
|
|
8656
9214
|
}
|
|
8657
9215
|
if (cursor) {
|
|
8658
9216
|
if (cursor.cursorrules) {
|
|
8659
|
-
const icon =
|
|
9217
|
+
const icon = fs30.existsSync(".cursorrules") ? chalk12.yellow("~") : chalk12.green("+");
|
|
8660
9218
|
const desc = getDescription(".cursorrules");
|
|
8661
9219
|
console.log(` ${icon} ${chalk12.bold(".cursorrules")}`);
|
|
8662
9220
|
if (desc) console.log(chalk12.dim(` ${desc}`));
|
|
@@ -8666,7 +9224,7 @@ function printSetupSummary(setup) {
|
|
|
8666
9224
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
8667
9225
|
for (const skill of cursorSkills) {
|
|
8668
9226
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
8669
|
-
const icon =
|
|
9227
|
+
const icon = fs30.existsSync(skillPath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8670
9228
|
const desc = getDescription(skillPath);
|
|
8671
9229
|
console.log(` ${icon} ${chalk12.bold(skillPath)}`);
|
|
8672
9230
|
console.log(chalk12.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -8677,7 +9235,7 @@ function printSetupSummary(setup) {
|
|
|
8677
9235
|
if (Array.isArray(rulesArr) && rulesArr.length > 0) {
|
|
8678
9236
|
for (const rule of rulesArr) {
|
|
8679
9237
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
8680
|
-
const icon =
|
|
9238
|
+
const icon = fs30.existsSync(rulePath) ? chalk12.yellow("~") : chalk12.green("+");
|
|
8681
9239
|
const desc = getDescription(rulePath);
|
|
8682
9240
|
console.log(` ${icon} ${chalk12.bold(rulePath)}`);
|
|
8683
9241
|
if (desc) {
|
|
@@ -8732,12 +9290,12 @@ function displayTokenUsage() {
|
|
|
8732
9290
|
// src/commands/init-helpers.ts
|
|
8733
9291
|
init_config();
|
|
8734
9292
|
import chalk13 from "chalk";
|
|
8735
|
-
import
|
|
8736
|
-
import
|
|
9293
|
+
import fs31 from "fs";
|
|
9294
|
+
import path25 from "path";
|
|
8737
9295
|
function isFirstRun(dir) {
|
|
8738
|
-
const caliberDir =
|
|
9296
|
+
const caliberDir = path25.join(dir, ".caliber");
|
|
8739
9297
|
try {
|
|
8740
|
-
const stat =
|
|
9298
|
+
const stat = fs31.statSync(caliberDir);
|
|
8741
9299
|
return !stat.isDirectory();
|
|
8742
9300
|
} catch {
|
|
8743
9301
|
return true;
|
|
@@ -8790,8 +9348,8 @@ function ensurePermissions(fingerprint) {
|
|
|
8790
9348
|
const settingsPath = ".claude/settings.json";
|
|
8791
9349
|
let settings = {};
|
|
8792
9350
|
try {
|
|
8793
|
-
if (
|
|
8794
|
-
settings = JSON.parse(
|
|
9351
|
+
if (fs31.existsSync(settingsPath)) {
|
|
9352
|
+
settings = JSON.parse(fs31.readFileSync(settingsPath, "utf-8"));
|
|
8795
9353
|
}
|
|
8796
9354
|
} catch {
|
|
8797
9355
|
}
|
|
@@ -8800,12 +9358,12 @@ function ensurePermissions(fingerprint) {
|
|
|
8800
9358
|
if (Array.isArray(allow) && allow.length > 0) return;
|
|
8801
9359
|
permissions.allow = derivePermissions(fingerprint);
|
|
8802
9360
|
settings.permissions = permissions;
|
|
8803
|
-
if (!
|
|
8804
|
-
|
|
9361
|
+
if (!fs31.existsSync(".claude")) fs31.mkdirSync(".claude", { recursive: true });
|
|
9362
|
+
fs31.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
8805
9363
|
}
|
|
8806
9364
|
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
8807
9365
|
try {
|
|
8808
|
-
const logPath =
|
|
9366
|
+
const logPath = path25.join(process.cwd(), ".caliber", "error-log.md");
|
|
8809
9367
|
const lines = [
|
|
8810
9368
|
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
8811
9369
|
"",
|
|
@@ -8818,8 +9376,8 @@ function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
|
8818
9376
|
lines.push("## Error", "```", error, "```", "");
|
|
8819
9377
|
}
|
|
8820
9378
|
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
8821
|
-
|
|
8822
|
-
|
|
9379
|
+
fs31.mkdirSync(path25.join(process.cwd(), ".caliber"), { recursive: true });
|
|
9380
|
+
fs31.writeFileSync(logPath, lines.join("\n"));
|
|
8823
9381
|
console.log(chalk13.dim(`
|
|
8824
9382
|
Error log written to .caliber/error-log.md`));
|
|
8825
9383
|
} catch {
|
|
@@ -8870,13 +9428,13 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
8870
9428
|
}
|
|
8871
9429
|
|
|
8872
9430
|
// src/scoring/history.ts
|
|
8873
|
-
import
|
|
8874
|
-
import
|
|
9431
|
+
import fs32 from "fs";
|
|
9432
|
+
import path26 from "path";
|
|
8875
9433
|
var HISTORY_FILE = "score-history.jsonl";
|
|
8876
9434
|
var MAX_ENTRIES = 500;
|
|
8877
9435
|
var TRIM_THRESHOLD = MAX_ENTRIES + 50;
|
|
8878
9436
|
function historyFilePath() {
|
|
8879
|
-
return
|
|
9437
|
+
return path26.join(CALIBER_DIR, HISTORY_FILE);
|
|
8880
9438
|
}
|
|
8881
9439
|
function recordScore(result, trigger) {
|
|
8882
9440
|
const entry = {
|
|
@@ -8887,14 +9445,14 @@ function recordScore(result, trigger) {
|
|
|
8887
9445
|
trigger
|
|
8888
9446
|
};
|
|
8889
9447
|
try {
|
|
8890
|
-
|
|
9448
|
+
fs32.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
8891
9449
|
const filePath = historyFilePath();
|
|
8892
|
-
|
|
8893
|
-
const stat =
|
|
9450
|
+
fs32.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
9451
|
+
const stat = fs32.statSync(filePath);
|
|
8894
9452
|
if (stat.size > TRIM_THRESHOLD * 120) {
|
|
8895
|
-
const lines =
|
|
9453
|
+
const lines = fs32.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
8896
9454
|
if (lines.length > MAX_ENTRIES) {
|
|
8897
|
-
|
|
9455
|
+
fs32.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
|
|
8898
9456
|
}
|
|
8899
9457
|
}
|
|
8900
9458
|
} catch {
|
|
@@ -8903,7 +9461,7 @@ function recordScore(result, trigger) {
|
|
|
8903
9461
|
function readScoreHistory() {
|
|
8904
9462
|
const filePath = historyFilePath();
|
|
8905
9463
|
try {
|
|
8906
|
-
const content =
|
|
9464
|
+
const content = fs32.readFileSync(filePath, "utf-8");
|
|
8907
9465
|
const entries = [];
|
|
8908
9466
|
for (const line of content.split("\n")) {
|
|
8909
9467
|
if (!line) continue;
|
|
@@ -8941,49 +9499,73 @@ async function initCommand(options) {
|
|
|
8941
9499
|
const bin = resolveCaliber();
|
|
8942
9500
|
const firstRun = isFirstRun(process.cwd());
|
|
8943
9501
|
if (firstRun) {
|
|
8944
|
-
console.log(
|
|
8945
|
-
brand.bold(`
|
|
9502
|
+
console.log(brand.bold(`
|
|
8946
9503
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
8947
9504
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
8948
9505
|
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
8949
9506
|
\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
|
|
8950
9507
|
\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
|
|
8951
9508
|
\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
|
|
8952
|
-
`)
|
|
8953
|
-
);
|
|
8954
|
-
console.log(chalk14.dim("
|
|
8955
|
-
console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
9509
|
+
`));
|
|
9510
|
+
console.log(chalk14.dim(" Keep your AI agent configs in sync \u2014 automatically."));
|
|
9511
|
+
console.log(chalk14.dim(" Works across Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
8956
9512
|
console.log(title.bold(" How it works:\n"));
|
|
8957
|
-
console.log(chalk14.dim(" 1. Connect
|
|
8958
|
-
console.log(chalk14.dim(" 2.
|
|
8959
|
-
console.log(chalk14.dim(" 3. Review
|
|
8960
|
-
console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
|
|
9513
|
+
console.log(chalk14.dim(" 1. Connect Auto-detect your LLM provider and agents"));
|
|
9514
|
+
console.log(chalk14.dim(" 2. Build Install sync, scan your project, generate configs"));
|
|
9515
|
+
console.log(chalk14.dim(" 3. Done Review score and start syncing\n"));
|
|
8961
9516
|
} else {
|
|
8962
|
-
console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014
|
|
9517
|
+
console.log(brand.bold("\n CALIBER") + chalk14.dim(" \u2014 setting up continuous sync\n"));
|
|
8963
9518
|
}
|
|
8964
9519
|
const platforms = detectPlatforms();
|
|
8965
9520
|
if (!platforms.claude && !platforms.cursor && !platforms.codex) {
|
|
8966
9521
|
console.log(chalk14.yellow(" \u26A0 No supported AI platforms detected (Claude, Cursor, Codex)."));
|
|
8967
|
-
console.log(
|
|
8968
|
-
chalk14.yellow(
|
|
8969
|
-
" Caliber will still generate config files, but they won't be auto-installed.\n"
|
|
8970
|
-
)
|
|
8971
|
-
);
|
|
9522
|
+
console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
|
|
8972
9523
|
}
|
|
8973
9524
|
const report = options.debugReport ? new DebugReport() : null;
|
|
8974
|
-
console.log(title.bold(" Step 1/
|
|
9525
|
+
console.log(title.bold(" Step 1/3 \u2014 Connect\n"));
|
|
8975
9526
|
let config = loadConfig();
|
|
9527
|
+
if (!config && !options.autoApprove) {
|
|
9528
|
+
if (isClaudeCliAvailable()) {
|
|
9529
|
+
console.log(chalk14.dim(" Detected: Claude Code CLI (uses your Pro/Max/Team subscription)\n"));
|
|
9530
|
+
const useIt = await confirm2({ message: "Use Claude Code as your LLM provider?" });
|
|
9531
|
+
if (useIt) {
|
|
9532
|
+
const autoConfig = { provider: "claude-cli", model: "default" };
|
|
9533
|
+
writeConfigFile(autoConfig);
|
|
9534
|
+
config = autoConfig;
|
|
9535
|
+
}
|
|
9536
|
+
} else if (isCursorAgentAvailable() && isCursorLoggedIn()) {
|
|
9537
|
+
console.log(chalk14.dim(" Detected: Cursor (uses your existing subscription)\n"));
|
|
9538
|
+
const useIt = await confirm2({ message: "Use Cursor as your LLM provider?" });
|
|
9539
|
+
if (useIt) {
|
|
9540
|
+
const autoConfig = { provider: "cursor", model: "sonnet-4.6" };
|
|
9541
|
+
writeConfigFile(autoConfig);
|
|
9542
|
+
config = autoConfig;
|
|
9543
|
+
}
|
|
9544
|
+
}
|
|
9545
|
+
}
|
|
8976
9546
|
if (!config) {
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
9547
|
+
if (options.autoApprove) {
|
|
9548
|
+
if (isClaudeCliAvailable()) {
|
|
9549
|
+
const autoConfig = { provider: "claude-cli", model: "default" };
|
|
9550
|
+
writeConfigFile(autoConfig);
|
|
9551
|
+
config = autoConfig;
|
|
9552
|
+
} else if (isCursorAgentAvailable() && isCursorLoggedIn()) {
|
|
9553
|
+
const autoConfig = { provider: "cursor", model: "sonnet-4.6" };
|
|
9554
|
+
writeConfigFile(autoConfig);
|
|
9555
|
+
config = autoConfig;
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
8982
9558
|
if (!config) {
|
|
8983
|
-
console.log(chalk14.
|
|
8984
|
-
|
|
9559
|
+
console.log(chalk14.dim(" No LLM provider detected.\n"));
|
|
9560
|
+
await runInteractiveProviderSetup({
|
|
9561
|
+
selectMessage: "How do you want to use Caliber? (choose LLM provider)"
|
|
9562
|
+
});
|
|
9563
|
+
config = loadConfig();
|
|
9564
|
+
if (!config) {
|
|
9565
|
+
console.log(chalk14.red(" Configuration cancelled or failed.\n"));
|
|
9566
|
+
throw new Error("__exit__");
|
|
9567
|
+
}
|
|
8985
9568
|
}
|
|
8986
|
-
console.log(chalk14.green(" \u2713 Provider saved\n"));
|
|
8987
9569
|
}
|
|
8988
9570
|
trackInitProviderSelected(config.provider, config.model, firstRun);
|
|
8989
9571
|
const displayModel = getDisplayModel(config);
|
|
@@ -8992,51 +9574,67 @@ async function initCommand(options) {
|
|
|
8992
9574
|
console.log(chalk14.dim(modelLine + "\n"));
|
|
8993
9575
|
if (report) {
|
|
8994
9576
|
report.markStep("Provider connection");
|
|
8995
|
-
report.addSection(
|
|
8996
|
-
"LLM Provider",
|
|
8997
|
-
`- **Provider**: ${config.provider}
|
|
9577
|
+
report.addSection("LLM Provider", `- **Provider**: ${config.provider}
|
|
8998
9578
|
- **Model**: ${displayModel}
|
|
8999
|
-
- **Fast model**: ${fastModel || "none"}`
|
|
9000
|
-
);
|
|
9579
|
+
- **Fast model**: ${fastModel || "none"}`);
|
|
9001
9580
|
}
|
|
9002
9581
|
await validateModel({ fast: true });
|
|
9003
9582
|
let targetAgent;
|
|
9004
9583
|
const agentAutoDetected = !options.agent;
|
|
9005
9584
|
if (options.agent) {
|
|
9006
9585
|
targetAgent = options.agent;
|
|
9007
|
-
} else if (options.autoApprove) {
|
|
9008
|
-
targetAgent = ["claude"];
|
|
9009
|
-
log(options.verbose, "Auto-approve: defaulting to claude agent");
|
|
9010
9586
|
} else {
|
|
9011
9587
|
const detected = detectAgents(process.cwd());
|
|
9012
|
-
|
|
9588
|
+
if (detected.length > 0 && (options.autoApprove || firstRun)) {
|
|
9589
|
+
targetAgent = detected;
|
|
9590
|
+
console.log(chalk14.dim(` Coding agents in this repo: ${detected.join(", ")}
|
|
9591
|
+
`));
|
|
9592
|
+
} else if (detected.length > 0) {
|
|
9593
|
+
console.log(chalk14.dim(` Coding agents in this repo: ${detected.join(", ")}
|
|
9594
|
+
`));
|
|
9595
|
+
const useDetected = await confirm2({ message: "Generate configs for these agents?" });
|
|
9596
|
+
targetAgent = useDetected ? detected : await promptAgent();
|
|
9597
|
+
} else {
|
|
9598
|
+
targetAgent = options.autoApprove ? ["claude"] : await promptAgent();
|
|
9599
|
+
}
|
|
9013
9600
|
}
|
|
9014
9601
|
console.log(chalk14.dim(` Target: ${targetAgent.join(", ")}
|
|
9015
9602
|
`));
|
|
9016
9603
|
trackInitAgentSelected(targetAgent, agentAutoDetected);
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
}
|
|
9604
|
+
console.log(title.bold(" Step 2/3 \u2014 Build\n"));
|
|
9605
|
+
const hookResult = installPreCommitHook();
|
|
9606
|
+
if (hookResult.installed) {
|
|
9607
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook installed`);
|
|
9608
|
+
} else if (hookResult.alreadyInstalled) {
|
|
9609
|
+
console.log(` ${chalk14.green("\u2713")} Pre-commit hook \u2014 active`);
|
|
9610
|
+
}
|
|
9611
|
+
const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
9612
|
+
for (const agent of targetAgent) {
|
|
9613
|
+
if (agent === "claude" && !fs33.existsSync(".claude")) fs33.mkdirSync(".claude", { recursive: true });
|
|
9614
|
+
if (agent === "cursor" && !fs33.existsSync(".cursor")) fs33.mkdirSync(".cursor", { recursive: true });
|
|
9615
|
+
if (agent === "codex" && !fs33.existsSync(".agents")) fs33.mkdirSync(".agents", { recursive: true });
|
|
9616
|
+
}
|
|
9617
|
+
const skillsWritten = ensureBuiltinSkills2();
|
|
9618
|
+
if (skillsWritten.length > 0) {
|
|
9619
|
+
console.log(` ${chalk14.green("\u2713")} Agent skills installed`);
|
|
9620
|
+
}
|
|
9621
|
+
const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
|
|
9622
|
+
if (hasLearnableAgent) {
|
|
9623
|
+
if (targetAgent.includes("claude")) installLearningHooks();
|
|
9624
|
+
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9625
|
+
console.log(` ${chalk14.green("\u2713")} Session learning enabled`);
|
|
9626
|
+
trackInitLearnEnabled(true);
|
|
9027
9627
|
}
|
|
9628
|
+
console.log("");
|
|
9629
|
+
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
9630
|
+
log(options.verbose, `Baseline score: ${baselineScore.score}/100`);
|
|
9028
9631
|
if (report) {
|
|
9029
9632
|
report.markStep("Baseline scoring");
|
|
9030
|
-
report.addSection(
|
|
9031
|
-
"Scoring: Baseline",
|
|
9032
|
-
`**Score**: ${baselineScore.score}/100
|
|
9633
|
+
report.addSection("Scoring: Baseline", `**Score**: ${baselineScore.score}/100
|
|
9033
9634
|
|
|
9034
9635
|
| Check | Passed | Points | Max |
|
|
9035
9636
|
|-------|--------|--------|-----|
|
|
9036
|
-
` + baselineScore.checks.map(
|
|
9037
|
-
(c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
|
|
9038
|
-
).join("\n")
|
|
9039
|
-
);
|
|
9637
|
+
` + baselineScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
9040
9638
|
report.addSection("Generation: Target Agents", targetAgent.join(", "));
|
|
9041
9639
|
}
|
|
9042
9640
|
const hasExistingConfig = !!(baselineScore.checks.some((c) => c.id === "claude_md_exists" && c.passed) || baselineScore.checks.some((c) => c.id === "cursorrules_exists" && c.passed));
|
|
@@ -9050,33 +9648,77 @@ async function initCommand(options) {
|
|
|
9050
9648
|
]);
|
|
9051
9649
|
const passingCount = baselineScore.checks.filter((c) => c.passed).length;
|
|
9052
9650
|
const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
|
|
9053
|
-
if (hasExistingConfig && baselineScore.score === 100) {
|
|
9054
|
-
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
9055
|
-
console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
|
|
9056
|
-
console.log(
|
|
9057
|
-
chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to regenerate anyway.\n")
|
|
9058
|
-
);
|
|
9059
|
-
if (!options.force) return;
|
|
9060
|
-
}
|
|
9061
|
-
const allFailingChecks = baselineScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
|
|
9062
|
-
const llmFixableChecks = allFailingChecks.filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
9063
9651
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, false);
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9652
|
+
let skipGeneration = false;
|
|
9653
|
+
if (hasExistingConfig && baselineScore.score === 100 && !options.force) {
|
|
9654
|
+
skipGeneration = true;
|
|
9655
|
+
} else if (hasExistingConfig && !options.force && !options.autoApprove) {
|
|
9656
|
+
console.log(chalk14.dim(` Config score: ${baselineScore.score}/100 \u2014 Caliber can improve this.
|
|
9657
|
+
`));
|
|
9658
|
+
const improveAnswer = await confirm2({ message: "Improve your existing configs?" });
|
|
9659
|
+
skipGeneration = !improveAnswer;
|
|
9660
|
+
}
|
|
9661
|
+
if (skipGeneration) {
|
|
9662
|
+
const {
|
|
9663
|
+
appendManagedBlocks: appendManagedBlocks2,
|
|
9664
|
+
getCursorPreCommitRule: getCursorPreCommitRule2,
|
|
9665
|
+
getCursorLearningsRule: getCursorLearningsRule2,
|
|
9666
|
+
getCursorSyncRule: getCursorSyncRule2,
|
|
9667
|
+
getCursorSetupRule: getCursorSetupRule2
|
|
9668
|
+
} = await Promise.resolve().then(() => (init_pre_commit_block(), pre_commit_block_exports));
|
|
9669
|
+
const claudeMdPath = "CLAUDE.md";
|
|
9670
|
+
let claudeContent = "";
|
|
9671
|
+
try {
|
|
9672
|
+
claudeContent = fs33.readFileSync(claudeMdPath, "utf-8");
|
|
9673
|
+
} catch {
|
|
9674
|
+
}
|
|
9675
|
+
if (!claudeContent) {
|
|
9676
|
+
claudeContent = `# ${path27.basename(process.cwd())}
|
|
9677
|
+
`;
|
|
9678
|
+
}
|
|
9679
|
+
const updatedClaude = appendManagedBlocks2(claudeContent, "claude");
|
|
9680
|
+
if (updatedClaude !== claudeContent || !fs33.existsSync(claudeMdPath)) {
|
|
9681
|
+
fs33.writeFileSync(claudeMdPath, updatedClaude);
|
|
9682
|
+
console.log(` ${chalk14.green("\u2713")} CLAUDE.md \u2014 added Caliber sync instructions`);
|
|
9683
|
+
}
|
|
9684
|
+
if (targetAgent.includes("cursor")) {
|
|
9685
|
+
const rulesDir = path27.join(".cursor", "rules");
|
|
9686
|
+
if (!fs33.existsSync(rulesDir)) fs33.mkdirSync(rulesDir, { recursive: true });
|
|
9687
|
+
for (const rule of [getCursorPreCommitRule2(), getCursorLearningsRule2(), getCursorSyncRule2(), getCursorSetupRule2()]) {
|
|
9688
|
+
fs33.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
|
|
9071
9689
|
}
|
|
9690
|
+
console.log(` ${chalk14.green("\u2713")} Cursor rules \u2014 added Caliber sync rules`);
|
|
9072
9691
|
}
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9692
|
+
if (targetAgent.includes("github-copilot")) {
|
|
9693
|
+
const copilotPath = path27.join(".github", "copilot-instructions.md");
|
|
9694
|
+
let copilotContent = "";
|
|
9695
|
+
try {
|
|
9696
|
+
copilotContent = fs33.readFileSync(copilotPath, "utf-8");
|
|
9697
|
+
} catch {
|
|
9698
|
+
}
|
|
9699
|
+
if (!copilotContent) {
|
|
9700
|
+
fs33.mkdirSync(".github", { recursive: true });
|
|
9701
|
+
copilotContent = `# ${path27.basename(process.cwd())}
|
|
9702
|
+
`;
|
|
9703
|
+
}
|
|
9704
|
+
const updatedCopilot = appendManagedBlocks2(copilotContent, "copilot");
|
|
9705
|
+
if (updatedCopilot !== copilotContent) {
|
|
9706
|
+
fs33.writeFileSync(copilotPath, updatedCopilot);
|
|
9707
|
+
console.log(` ${chalk14.green("\u2713")} Copilot instructions \u2014 added Caliber sync instructions`);
|
|
9708
|
+
}
|
|
9709
|
+
}
|
|
9710
|
+
const sha2 = getCurrentHeadSha();
|
|
9711
|
+
writeState({
|
|
9712
|
+
lastRefreshSha: sha2 ?? "",
|
|
9713
|
+
lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9714
|
+
targetAgent
|
|
9715
|
+
});
|
|
9716
|
+
trackInitCompleted("sync-only", baselineScore.score);
|
|
9717
|
+
console.log(chalk14.bold.green("\n Caliber sync is set up!\n"));
|
|
9718
|
+
console.log(chalk14.dim(" Your agent configs will sync automatically on every commit."));
|
|
9719
|
+
console.log(chalk14.dim(" Run ") + title(`${bin} init --force`) + chalk14.dim(" anytime to generate or improve configs.\n"));
|
|
9077
9720
|
return;
|
|
9078
9721
|
}
|
|
9079
|
-
console.log(title.bold(" Step 2/4 \u2014 Engine\n"));
|
|
9080
9722
|
const genModelInfo = fastModel ? ` Using ${displayModel} for docs, ${fastModel} for skills` : ` Using ${displayModel}`;
|
|
9081
9723
|
console.log(chalk14.dim(genModelInfo + "\n"));
|
|
9082
9724
|
if (report) report.markStep("Generation");
|
|
@@ -9092,14 +9734,8 @@ async function initCommand(options) {
|
|
|
9092
9734
|
const TASK_STACK = display.add("Detecting project stack", { pipelineLabel: "Scan" });
|
|
9093
9735
|
const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
|
|
9094
9736
|
const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
|
|
9095
|
-
const TASK_SKILLS_SEARCH = display.add("Searching community skills", {
|
|
9096
|
-
|
|
9097
|
-
pipelineLabel: "Search",
|
|
9098
|
-
pipelineRow: 1
|
|
9099
|
-
});
|
|
9100
|
-
const TASK_SCORE_REFINE = display.add("Validating & refining config", {
|
|
9101
|
-
pipelineLabel: "Validate"
|
|
9102
|
-
});
|
|
9737
|
+
const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
|
|
9738
|
+
const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
|
|
9103
9739
|
display.start();
|
|
9104
9740
|
display.enableWaitingContent();
|
|
9105
9741
|
try {
|
|
@@ -9109,39 +9745,21 @@ async function initCommand(options) {
|
|
|
9109
9745
|
const stackSummary = stackParts.join(", ") || "no languages";
|
|
9110
9746
|
const largeRepoNote = fingerprint.fileTree.length > 5e3 ? ` (${fingerprint.fileTree.length.toLocaleString()} files, smart sampling active)` : "";
|
|
9111
9747
|
display.update(TASK_STACK, "done", stackSummary + largeRepoNote);
|
|
9112
|
-
trackInitProjectDiscovered(
|
|
9113
|
-
|
|
9114
|
-
fingerprint.frameworks.length,
|
|
9115
|
-
fingerprint.fileTree.length
|
|
9116
|
-
);
|
|
9117
|
-
log(
|
|
9118
|
-
options.verbose,
|
|
9119
|
-
`Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`
|
|
9120
|
-
);
|
|
9748
|
+
trackInitProjectDiscovered(fingerprint.languages.length, fingerprint.frameworks.length, fingerprint.fileTree.length);
|
|
9749
|
+
log(options.verbose, `Fingerprint: ${fingerprint.languages.length} languages, ${fingerprint.frameworks.length} frameworks, ${fingerprint.fileTree.length} files`);
|
|
9121
9750
|
const cliSources = options.source || [];
|
|
9122
9751
|
const workspaces = getDetectedWorkspaces(process.cwd());
|
|
9123
9752
|
const sources2 = resolveAllSources(process.cwd(), cliSources, workspaces);
|
|
9124
9753
|
if (sources2.length > 0) {
|
|
9125
9754
|
fingerprint.sources = sources2;
|
|
9126
|
-
log(
|
|
9127
|
-
options.verbose,
|
|
9128
|
-
`Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`
|
|
9129
|
-
);
|
|
9755
|
+
log(options.verbose, `Sources: ${sources2.length} resolved (${sources2.map((s) => s.name).join(", ")})`);
|
|
9130
9756
|
}
|
|
9131
9757
|
if (report) {
|
|
9132
|
-
report.addJson("Fingerprint: Git", {
|
|
9133
|
-
remote: fingerprint.gitRemoteUrl,
|
|
9134
|
-
packageName: fingerprint.packageName
|
|
9135
|
-
});
|
|
9758
|
+
report.addJson("Fingerprint: Git", { remote: fingerprint.gitRemoteUrl, packageName: fingerprint.packageName });
|
|
9136
9759
|
report.addCodeBlock("Fingerprint: File Tree", fingerprint.fileTree.join("\n"));
|
|
9137
|
-
report.addJson("Fingerprint: Detected Stack", {
|
|
9138
|
-
languages: fingerprint.languages,
|
|
9139
|
-
frameworks: fingerprint.frameworks,
|
|
9140
|
-
tools: fingerprint.tools
|
|
9141
|
-
});
|
|
9760
|
+
report.addJson("Fingerprint: Detected Stack", { languages: fingerprint.languages, frameworks: fingerprint.frameworks, tools: fingerprint.tools });
|
|
9142
9761
|
report.addJson("Fingerprint: Existing Configs", fingerprint.existingConfigs);
|
|
9143
|
-
if (fingerprint.codeAnalysis)
|
|
9144
|
-
report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
|
|
9762
|
+
if (fingerprint.codeAnalysis) report.addJson("Fingerprint: Code Analysis", fingerprint.codeAnalysis);
|
|
9145
9763
|
}
|
|
9146
9764
|
const isEmpty = fingerprint.fileTree.length < 3;
|
|
9147
9765
|
if (isEmpty) {
|
|
@@ -9172,26 +9790,13 @@ async function initCommand(options) {
|
|
|
9172
9790
|
let passingChecks;
|
|
9173
9791
|
let currentScore;
|
|
9174
9792
|
if (hasExistingConfig && localBaseline.score >= 95 && !options.force) {
|
|
9175
|
-
const currentLlmFixable = localBaseline.checks.filter(
|
|
9176
|
-
|
|
9177
|
-
);
|
|
9178
|
-
failingChecks = currentLlmFixable.map((c) => ({
|
|
9179
|
-
name: c.name,
|
|
9180
|
-
suggestion: c.suggestion,
|
|
9181
|
-
fix: c.fix
|
|
9182
|
-
}));
|
|
9793
|
+
const currentLlmFixable = localBaseline.checks.filter((c) => !c.passed && c.maxPoints > 0 && !NON_LLM_CHECKS.has(c.id));
|
|
9794
|
+
failingChecks = currentLlmFixable.map((c) => ({ name: c.name, suggestion: c.suggestion, fix: c.fix }));
|
|
9183
9795
|
passingChecks = localBaseline.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
9184
9796
|
currentScore = localBaseline.score;
|
|
9185
9797
|
}
|
|
9186
9798
|
if (report) {
|
|
9187
|
-
const fullPrompt = buildGeneratePrompt(
|
|
9188
|
-
fingerprint,
|
|
9189
|
-
targetAgent,
|
|
9190
|
-
fingerprint.description,
|
|
9191
|
-
failingChecks,
|
|
9192
|
-
currentScore,
|
|
9193
|
-
passingChecks
|
|
9194
|
-
);
|
|
9799
|
+
const fullPrompt = buildGeneratePrompt(fingerprint, targetAgent, fingerprint.description, failingChecks, currentScore, passingChecks);
|
|
9195
9800
|
report.addCodeBlock("Generation: Full LLM Prompt", fullPrompt);
|
|
9196
9801
|
}
|
|
9197
9802
|
const result = await generateSetup(
|
|
@@ -9311,11 +9916,8 @@ async function initCommand(options) {
|
|
|
9311
9916
|
if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
|
|
9312
9917
|
report.addJson("Generation: Parsed Config", generatedSetup);
|
|
9313
9918
|
}
|
|
9314
|
-
log(
|
|
9315
|
-
|
|
9316
|
-
`Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`
|
|
9317
|
-
);
|
|
9318
|
-
console.log(title.bold(" Step 3/4 \u2014 Review\n"));
|
|
9919
|
+
log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
|
|
9920
|
+
console.log(title.bold(" Step 3/3 \u2014 Done\n"));
|
|
9319
9921
|
const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9320
9922
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
9321
9923
|
const totalChanges = staged.newFiles + staged.modifiedFiles;
|
|
@@ -9324,20 +9926,12 @@ async function initCommand(options) {
|
|
|
9324
9926
|
for (const line of changes) {
|
|
9325
9927
|
console.log(` ${chalk14.dim("\u2022")} ${line}`);
|
|
9326
9928
|
}
|
|
9327
|
-
console.log("");
|
|
9328
|
-
}
|
|
9329
|
-
console.log(
|
|
9330
|
-
chalk14.dim(
|
|
9331
|
-
` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`
|
|
9332
|
-
)
|
|
9333
|
-
);
|
|
9929
|
+
console.log("");
|
|
9930
|
+
}
|
|
9931
|
+
console.log(chalk14.dim(` ${chalk14.green(`${staged.newFiles} new`)} / ${chalk14.yellow(`${staged.modifiedFiles} modified`)} file${totalChanges !== 1 ? "s" : ""}`));
|
|
9334
9932
|
if (skillSearchResult.results.length > 0) {
|
|
9335
|
-
console.log(
|
|
9336
|
-
|
|
9337
|
-
` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
|
|
9338
|
-
`
|
|
9339
|
-
)
|
|
9340
|
-
);
|
|
9933
|
+
console.log(chalk14.dim(` ${chalk14.cyan(`${skillSearchResult.results.length}`)} community skills available to install
|
|
9934
|
+
`));
|
|
9341
9935
|
} else {
|
|
9342
9936
|
console.log("");
|
|
9343
9937
|
}
|
|
@@ -9373,12 +9967,8 @@ async function initCommand(options) {
|
|
|
9373
9967
|
}
|
|
9374
9968
|
const updatedFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9375
9969
|
const restaged = stageFiles(updatedFiles, process.cwd());
|
|
9376
|
-
console.log(
|
|
9377
|
-
|
|
9378
|
-
` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
9379
|
-
`
|
|
9380
|
-
)
|
|
9381
|
-
);
|
|
9970
|
+
console.log(chalk14.dim(` ${chalk14.green(`${restaged.newFiles} new`)} / ${chalk14.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
|
|
9971
|
+
`));
|
|
9382
9972
|
printSetupSummary(generatedSetup);
|
|
9383
9973
|
const { openReview: openRev } = await Promise.resolve().then(() => (init_review(), review_exports));
|
|
9384
9974
|
await openRev("terminal", restaged.stagedFiles);
|
|
@@ -9390,7 +9980,6 @@ async function initCommand(options) {
|
|
|
9390
9980
|
console.log(chalk14.dim("Declined. No files were modified."));
|
|
9391
9981
|
return;
|
|
9392
9982
|
}
|
|
9393
|
-
console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
|
|
9394
9983
|
if (options.dryRun) {
|
|
9395
9984
|
console.log(chalk14.yellow("\n[Dry run] Would write the following files:"));
|
|
9396
9985
|
console.log(JSON.stringify(generatedSetup, null, 2));
|
|
@@ -9399,14 +9988,13 @@ async function initCommand(options) {
|
|
|
9399
9988
|
const { default: ora9 } = await import("ora");
|
|
9400
9989
|
const writeSpinner = ora9("Writing config files...").start();
|
|
9401
9990
|
try {
|
|
9402
|
-
if (targetAgent.includes("codex") && !
|
|
9991
|
+
if (targetAgent.includes("codex") && !fs33.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
9403
9992
|
const claude = generatedSetup.claude;
|
|
9404
9993
|
const cursor = generatedSetup.cursor;
|
|
9405
9994
|
const agentRefs = [];
|
|
9406
9995
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
9407
9996
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
9408
|
-
if (agentRefs.length === 0)
|
|
9409
|
-
agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
9997
|
+
if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
|
|
9410
9998
|
const stubContent = `# AGENTS.md
|
|
9411
9999
|
|
|
9412
10000
|
This project uses AI coding agents configured by [Caliber](https://github.com/caliber-ai-org/ai-setup).
|
|
@@ -9453,49 +10041,32 @@ ${agentRefs.join(" ")}
|
|
|
9453
10041
|
if (afterScore.score < baselineScore.score) {
|
|
9454
10042
|
trackInitScoreRegression(baselineScore.score, afterScore.score);
|
|
9455
10043
|
console.log("");
|
|
9456
|
-
console.log(
|
|
9457
|
-
chalk14.yellow(
|
|
9458
|
-
` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`
|
|
9459
|
-
)
|
|
9460
|
-
);
|
|
10044
|
+
console.log(chalk14.yellow(` Score would drop from ${baselineScore.score} to ${afterScore.score} \u2014 reverting changes.`));
|
|
9461
10045
|
try {
|
|
9462
10046
|
const { restored, removed } = undoSetup();
|
|
9463
10047
|
if (restored.length > 0 || removed.length > 0) {
|
|
9464
|
-
console.log(
|
|
9465
|
-
chalk14.dim(
|
|
9466
|
-
` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`
|
|
9467
|
-
)
|
|
9468
|
-
);
|
|
10048
|
+
console.log(chalk14.dim(` Reverted ${restored.length + removed.length} file${restored.length + removed.length === 1 ? "" : "s"} from backup.`));
|
|
9469
10049
|
}
|
|
9470
10050
|
} catch {
|
|
9471
10051
|
}
|
|
9472
|
-
console.log(
|
|
9473
|
-
chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n")
|
|
9474
|
-
);
|
|
10052
|
+
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} init --force`) + chalk14.dim(" to override.\n"));
|
|
9475
10053
|
return;
|
|
9476
10054
|
}
|
|
9477
10055
|
if (report) {
|
|
9478
10056
|
report.markStep("Post-write scoring");
|
|
9479
|
-
report.addSection(
|
|
9480
|
-
"Scoring: Post-Write",
|
|
9481
|
-
`**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
|
|
10057
|
+
report.addSection("Scoring: Post-Write", `**Score**: ${afterScore.score}/100 (delta: ${afterScore.score - baselineScore.score >= 0 ? "+" : ""}${afterScore.score - baselineScore.score})
|
|
9482
10058
|
|
|
9483
10059
|
| Check | Passed | Points | Max |
|
|
9484
10060
|
|-------|--------|--------|-----|
|
|
9485
|
-
` + afterScore.checks.map(
|
|
9486
|
-
(c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`
|
|
9487
|
-
).join("\n")
|
|
9488
|
-
);
|
|
10061
|
+
` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
9489
10062
|
}
|
|
9490
10063
|
recordScore(afterScore, "init");
|
|
10064
|
+
trackInitCompleted("full-generation", afterScore.score);
|
|
9491
10065
|
displayScoreDelta(baselineScore, afterScore);
|
|
9492
10066
|
if (options.verbose) {
|
|
9493
10067
|
log(options.verbose, `Final score: ${afterScore.score}/100`);
|
|
9494
10068
|
for (const c of afterScore.checks.filter((ch) => !ch.passed)) {
|
|
9495
|
-
log(
|
|
9496
|
-
options.verbose,
|
|
9497
|
-
` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`
|
|
9498
|
-
);
|
|
10069
|
+
log(options.verbose, ` Still failing: ${c.name} (${c.earnedPoints}/${c.maxPoints})${c.suggestion ? ` \u2014 ${c.suggestion}` : ""}`);
|
|
9499
10070
|
}
|
|
9500
10071
|
}
|
|
9501
10072
|
let communitySkillsInstalled = 0;
|
|
@@ -9508,99 +10079,36 @@ ${agentRefs.join(" ")}
|
|
|
9508
10079
|
communitySkillsInstalled = selected.length;
|
|
9509
10080
|
}
|
|
9510
10081
|
}
|
|
9511
|
-
console.log("");
|
|
9512
|
-
console.log(
|
|
9513
|
-
` ${chalk14.green("\u2713")} Docs auto-refresh ${chalk14.dim(`agents run ${resolveCaliber()} refresh before commits`)}`
|
|
9514
|
-
);
|
|
9515
10082
|
trackInitHookSelected("config-instructions");
|
|
9516
|
-
const hasLearnableAgent = targetAgent.includes("claude") || targetAgent.includes("cursor");
|
|
9517
|
-
let enableLearn = false;
|
|
9518
|
-
if (hasLearnableAgent) {
|
|
9519
|
-
if (!options.autoApprove) {
|
|
9520
|
-
enableLearn = await promptLearnInstall(targetAgent);
|
|
9521
|
-
trackInitLearnEnabled(enableLearn);
|
|
9522
|
-
if (enableLearn) {
|
|
9523
|
-
if (targetAgent.includes("claude")) {
|
|
9524
|
-
const r = installLearningHooks();
|
|
9525
|
-
if (r.installed)
|
|
9526
|
-
console.log(` ${chalk14.green("\u2713")} Learning hooks installed for Claude Code`);
|
|
9527
|
-
else if (r.alreadyInstalled)
|
|
9528
|
-
console.log(chalk14.dim(" Claude Code learning hooks already installed"));
|
|
9529
|
-
}
|
|
9530
|
-
if (targetAgent.includes("cursor")) {
|
|
9531
|
-
const r = installCursorLearningHooks();
|
|
9532
|
-
if (r.installed) console.log(` ${chalk14.green("\u2713")} Learning hooks installed for Cursor`);
|
|
9533
|
-
else if (r.alreadyInstalled)
|
|
9534
|
-
console.log(chalk14.dim(" Cursor learning hooks already installed"));
|
|
9535
|
-
}
|
|
9536
|
-
console.log(
|
|
9537
|
-
chalk14.dim(" Run ") + chalk14.hex("#83D1EB")(`${bin} learn status`) + chalk14.dim(" to see insights")
|
|
9538
|
-
);
|
|
9539
|
-
} else {
|
|
9540
|
-
console.log(
|
|
9541
|
-
chalk14.dim(" Skipped. Run ") + chalk14.hex("#83D1EB")(`${bin} learn install`) + chalk14.dim(" later to enable.")
|
|
9542
|
-
);
|
|
9543
|
-
}
|
|
9544
|
-
} else {
|
|
9545
|
-
enableLearn = true;
|
|
9546
|
-
if (targetAgent.includes("claude")) installLearningHooks();
|
|
9547
|
-
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9548
|
-
}
|
|
9549
|
-
}
|
|
9550
|
-
console.log(chalk14.bold.green("\n Configuration complete!"));
|
|
9551
|
-
console.log(
|
|
9552
|
-
chalk14.dim(" Your AI agents now understand your project's architecture, build commands,")
|
|
9553
|
-
);
|
|
9554
|
-
console.log(
|
|
9555
|
-
chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n")
|
|
9556
|
-
);
|
|
9557
10083
|
const done = chalk14.green("\u2713");
|
|
9558
|
-
|
|
9559
|
-
console.log(chalk14.bold(" What
|
|
9560
|
-
console.log(
|
|
9561
|
-
|
|
9562
|
-
);
|
|
9563
|
-
console.log(
|
|
9564
|
-
` ${done} Docs auto-refresh ${chalk14.dim(`agents run ${bin} refresh before commits`)}`
|
|
9565
|
-
);
|
|
10084
|
+
console.log(chalk14.bold.green("\n Caliber is set up!\n"));
|
|
10085
|
+
console.log(chalk14.bold(" What's configured:\n"));
|
|
10086
|
+
console.log(` ${done} Continuous sync ${chalk14.dim("pre-commit hook keeps all agent configs in sync")}`);
|
|
10087
|
+
console.log(` ${done} Config generated ${chalk14.dim(`score: ${afterScore.score}/100`)}`);
|
|
10088
|
+
console.log(` ${done} Agent skills ${chalk14.dim("/setup-caliber for new team members")}`);
|
|
9566
10089
|
if (hasLearnableAgent) {
|
|
9567
|
-
|
|
9568
|
-
console.log(
|
|
9569
|
-
` ${done} Session learning ${chalk14.dim("agent learns from your feedback")}`
|
|
9570
|
-
);
|
|
9571
|
-
} else {
|
|
9572
|
-
console.log(
|
|
9573
|
-
` ${skip} Session learning ${title(`${bin} learn install`)} to enable later`
|
|
9574
|
-
);
|
|
9575
|
-
}
|
|
10090
|
+
console.log(` ${done} Session learning ${chalk14.dim("learns from your corrections")}`);
|
|
9576
10091
|
}
|
|
9577
10092
|
if (communitySkillsInstalled > 0) {
|
|
9578
|
-
console.log(
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
}
|
|
9584
|
-
console.log(
|
|
9585
|
-
console.log(
|
|
9586
|
-
|
|
9587
|
-
);
|
|
9588
|
-
console.log(
|
|
9589
|
-
` ${title(`${bin} score`)} See the full scoring breakdown with improvement tips`
|
|
9590
|
-
);
|
|
9591
|
-
console.log(` ${title(`${bin} undo`)} Revert all changes from this run`);
|
|
10093
|
+
console.log(` ${done} Community skills ${chalk14.dim(`${communitySkillsInstalled} installed for your stack`)}`);
|
|
10094
|
+
}
|
|
10095
|
+
console.log(chalk14.bold("\n What happens next:\n"));
|
|
10096
|
+
console.log(chalk14.dim(" Every commit syncs your agent configs automatically."));
|
|
10097
|
+
console.log(chalk14.dim(" New team members run /setup-caliber to get set up instantly.\n"));
|
|
10098
|
+
console.log(` ${title(`${bin} score`)} Full scoring breakdown`);
|
|
10099
|
+
console.log(` ${title(`${bin} skills`)} Find community skills`);
|
|
10100
|
+
console.log(` ${title(`${bin} undo`)} Revert changes`);
|
|
10101
|
+
console.log(` ${title(`${bin} uninstall`)} Remove Caliber completely`);
|
|
9592
10102
|
console.log("");
|
|
9593
10103
|
if (options.showTokens) {
|
|
9594
10104
|
displayTokenUsage();
|
|
9595
10105
|
}
|
|
9596
10106
|
if (report) {
|
|
9597
10107
|
report.markStep("Finished");
|
|
9598
|
-
const reportPath =
|
|
10108
|
+
const reportPath = path27.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9599
10109
|
report.write(reportPath);
|
|
9600
|
-
console.log(
|
|
9601
|
-
|
|
9602
|
-
`)
|
|
9603
|
-
);
|
|
10110
|
+
console.log(chalk14.dim(` Debug report written to ${path27.relative(process.cwd(), reportPath)}
|
|
10111
|
+
`));
|
|
9604
10112
|
}
|
|
9605
10113
|
}
|
|
9606
10114
|
|
|
@@ -9638,7 +10146,7 @@ function undoCommand() {
|
|
|
9638
10146
|
|
|
9639
10147
|
// src/commands/status.ts
|
|
9640
10148
|
import chalk16 from "chalk";
|
|
9641
|
-
import
|
|
10149
|
+
import fs34 from "fs";
|
|
9642
10150
|
init_config();
|
|
9643
10151
|
init_resolve_caliber();
|
|
9644
10152
|
async function statusCommand(options) {
|
|
@@ -9667,7 +10175,7 @@ async function statusCommand(options) {
|
|
|
9667
10175
|
}
|
|
9668
10176
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9669
10177
|
for (const entry of manifest.entries) {
|
|
9670
|
-
const exists =
|
|
10178
|
+
const exists = fs34.existsSync(entry.path);
|
|
9671
10179
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9672
10180
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9673
10181
|
}
|
|
@@ -9824,9 +10332,9 @@ async function regenerateCommand(options) {
|
|
|
9824
10332
|
}
|
|
9825
10333
|
|
|
9826
10334
|
// src/commands/score.ts
|
|
9827
|
-
import
|
|
10335
|
+
import fs35 from "fs";
|
|
9828
10336
|
import os7 from "os";
|
|
9829
|
-
import
|
|
10337
|
+
import path28 from "path";
|
|
9830
10338
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9831
10339
|
import chalk18 from "chalk";
|
|
9832
10340
|
init_resolve_caliber();
|
|
@@ -9834,7 +10342,7 @@ var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS
|
|
|
9834
10342
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9835
10343
|
function scoreBaseRef(ref, target) {
|
|
9836
10344
|
if (!/^[\w.\-/~^@{}]+$/.test(ref)) return null;
|
|
9837
|
-
const tmpDir =
|
|
10345
|
+
const tmpDir = fs35.mkdtempSync(path28.join(os7.tmpdir(), "caliber-compare-"));
|
|
9838
10346
|
try {
|
|
9839
10347
|
for (const file of CONFIG_FILES) {
|
|
9840
10348
|
try {
|
|
@@ -9842,7 +10350,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9842
10350
|
encoding: "utf-8",
|
|
9843
10351
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9844
10352
|
});
|
|
9845
|
-
|
|
10353
|
+
fs35.writeFileSync(path28.join(tmpDir, file), content);
|
|
9846
10354
|
} catch {
|
|
9847
10355
|
}
|
|
9848
10356
|
}
|
|
@@ -9853,13 +10361,13 @@ function scoreBaseRef(ref, target) {
|
|
|
9853
10361
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9854
10362
|
}).trim().split("\n").filter(Boolean);
|
|
9855
10363
|
for (const file of files) {
|
|
9856
|
-
const filePath =
|
|
9857
|
-
|
|
10364
|
+
const filePath = path28.join(tmpDir, file);
|
|
10365
|
+
fs35.mkdirSync(path28.dirname(filePath), { recursive: true });
|
|
9858
10366
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], {
|
|
9859
10367
|
encoding: "utf-8",
|
|
9860
10368
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9861
10369
|
});
|
|
9862
|
-
|
|
10370
|
+
fs35.writeFileSync(filePath, content);
|
|
9863
10371
|
}
|
|
9864
10372
|
} catch {
|
|
9865
10373
|
}
|
|
@@ -9869,7 +10377,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9869
10377
|
} catch {
|
|
9870
10378
|
return null;
|
|
9871
10379
|
} finally {
|
|
9872
|
-
|
|
10380
|
+
fs35.rmSync(tmpDir, { recursive: true, force: true });
|
|
9873
10381
|
}
|
|
9874
10382
|
}
|
|
9875
10383
|
async function scoreCommand(options) {
|
|
@@ -9953,13 +10461,13 @@ async function scoreCommand(options) {
|
|
|
9953
10461
|
}
|
|
9954
10462
|
|
|
9955
10463
|
// src/commands/refresh.ts
|
|
9956
|
-
import
|
|
9957
|
-
import
|
|
10464
|
+
import fs39 from "fs";
|
|
10465
|
+
import path32 from "path";
|
|
9958
10466
|
import chalk19 from "chalk";
|
|
9959
10467
|
import ora6 from "ora";
|
|
9960
10468
|
|
|
9961
10469
|
// src/lib/git-diff.ts
|
|
9962
|
-
import { execSync as
|
|
10470
|
+
import { execSync as execSync15 } from "child_process";
|
|
9963
10471
|
var MAX_DIFF_BYTES = 1e5;
|
|
9964
10472
|
var DOC_PATTERNS = [
|
|
9965
10473
|
"CLAUDE.md",
|
|
@@ -9974,7 +10482,7 @@ function excludeArgs() {
|
|
|
9974
10482
|
}
|
|
9975
10483
|
function safeExec(cmd) {
|
|
9976
10484
|
try {
|
|
9977
|
-
return
|
|
10485
|
+
return execSync15(cmd, {
|
|
9978
10486
|
encoding: "utf-8",
|
|
9979
10487
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9980
10488
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -10032,48 +10540,49 @@ function collectDiff(lastSha) {
|
|
|
10032
10540
|
}
|
|
10033
10541
|
|
|
10034
10542
|
// src/writers/refresh.ts
|
|
10035
|
-
|
|
10036
|
-
import
|
|
10543
|
+
init_pre_commit_block();
|
|
10544
|
+
import fs36 from "fs";
|
|
10545
|
+
import path29 from "path";
|
|
10037
10546
|
function writeRefreshDocs(docs) {
|
|
10038
10547
|
const written = [];
|
|
10039
10548
|
if (docs.claudeMd) {
|
|
10040
|
-
|
|
10549
|
+
fs36.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
|
|
10041
10550
|
written.push("CLAUDE.md");
|
|
10042
10551
|
}
|
|
10043
10552
|
if (docs.readmeMd) {
|
|
10044
|
-
|
|
10553
|
+
fs36.writeFileSync("README.md", docs.readmeMd);
|
|
10045
10554
|
written.push("README.md");
|
|
10046
10555
|
}
|
|
10047
10556
|
if (docs.cursorrules) {
|
|
10048
|
-
|
|
10557
|
+
fs36.writeFileSync(".cursorrules", docs.cursorrules);
|
|
10049
10558
|
written.push(".cursorrules");
|
|
10050
10559
|
}
|
|
10051
10560
|
if (docs.cursorRules) {
|
|
10052
|
-
const rulesDir =
|
|
10053
|
-
if (!
|
|
10561
|
+
const rulesDir = path29.join(".cursor", "rules");
|
|
10562
|
+
if (!fs36.existsSync(rulesDir)) fs36.mkdirSync(rulesDir, { recursive: true });
|
|
10054
10563
|
for (const rule of docs.cursorRules) {
|
|
10055
|
-
|
|
10564
|
+
fs36.writeFileSync(path29.join(rulesDir, rule.filename), rule.content);
|
|
10056
10565
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
10057
10566
|
}
|
|
10058
10567
|
}
|
|
10059
10568
|
if (docs.claudeSkills) {
|
|
10060
|
-
const skillsDir =
|
|
10061
|
-
if (!
|
|
10569
|
+
const skillsDir = path29.join(".claude", "skills");
|
|
10570
|
+
if (!fs36.existsSync(skillsDir)) fs36.mkdirSync(skillsDir, { recursive: true });
|
|
10062
10571
|
for (const skill of docs.claudeSkills) {
|
|
10063
|
-
|
|
10572
|
+
fs36.writeFileSync(path29.join(skillsDir, skill.filename), skill.content);
|
|
10064
10573
|
written.push(`.claude/skills/${skill.filename}`);
|
|
10065
10574
|
}
|
|
10066
10575
|
}
|
|
10067
10576
|
if (docs.copilotInstructions) {
|
|
10068
|
-
|
|
10069
|
-
|
|
10577
|
+
fs36.mkdirSync(".github", { recursive: true });
|
|
10578
|
+
fs36.writeFileSync(path29.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
|
|
10070
10579
|
written.push(".github/copilot-instructions.md");
|
|
10071
10580
|
}
|
|
10072
10581
|
if (docs.copilotInstructionFiles) {
|
|
10073
|
-
const instructionsDir =
|
|
10074
|
-
|
|
10582
|
+
const instructionsDir = path29.join(".github", "instructions");
|
|
10583
|
+
fs36.mkdirSync(instructionsDir, { recursive: true });
|
|
10075
10584
|
for (const file of docs.copilotInstructionFiles) {
|
|
10076
|
-
|
|
10585
|
+
fs36.writeFileSync(path29.join(instructionsDir, file.filename), file.content);
|
|
10077
10586
|
written.push(`.github/instructions/${file.filename}`);
|
|
10078
10587
|
}
|
|
10079
10588
|
}
|
|
@@ -10159,8 +10668,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
10159
10668
|
}
|
|
10160
10669
|
|
|
10161
10670
|
// src/learner/writer.ts
|
|
10162
|
-
import
|
|
10163
|
-
import
|
|
10671
|
+
import fs37 from "fs";
|
|
10672
|
+
import path30 from "path";
|
|
10164
10673
|
|
|
10165
10674
|
// src/learner/utils.ts
|
|
10166
10675
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -10277,20 +10786,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
10277
10786
|
}
|
|
10278
10787
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
10279
10788
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
10280
|
-
|
|
10281
|
-
if (mode)
|
|
10789
|
+
fs37.writeFileSync(filePath, header + merged + "\n");
|
|
10790
|
+
if (mode) fs37.chmodSync(filePath, mode);
|
|
10282
10791
|
return { newCount, newItems };
|
|
10283
10792
|
}
|
|
10284
10793
|
function writeLearnedSection(content) {
|
|
10285
10794
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
10286
10795
|
}
|
|
10287
10796
|
function writeLearnedSkill(skill) {
|
|
10288
|
-
const skillDir =
|
|
10289
|
-
if (!
|
|
10290
|
-
const skillPath =
|
|
10291
|
-
if (!skill.isNew &&
|
|
10292
|
-
const existing =
|
|
10293
|
-
|
|
10797
|
+
const skillDir = path30.join(".claude", "skills", skill.name);
|
|
10798
|
+
if (!fs37.existsSync(skillDir)) fs37.mkdirSync(skillDir, { recursive: true });
|
|
10799
|
+
const skillPath = path30.join(skillDir, "SKILL.md");
|
|
10800
|
+
if (!skill.isNew && fs37.existsSync(skillPath)) {
|
|
10801
|
+
const existing = fs37.readFileSync(skillPath, "utf-8");
|
|
10802
|
+
fs37.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
10294
10803
|
} else {
|
|
10295
10804
|
const frontmatter = [
|
|
10296
10805
|
"---",
|
|
@@ -10299,12 +10808,12 @@ function writeLearnedSkill(skill) {
|
|
|
10299
10808
|
"---",
|
|
10300
10809
|
""
|
|
10301
10810
|
].join("\n");
|
|
10302
|
-
|
|
10811
|
+
fs37.writeFileSync(skillPath, frontmatter + skill.content);
|
|
10303
10812
|
}
|
|
10304
10813
|
return skillPath;
|
|
10305
10814
|
}
|
|
10306
10815
|
function writePersonalLearnedSection(content) {
|
|
10307
|
-
if (!
|
|
10816
|
+
if (!fs37.existsSync(AUTH_DIR)) fs37.mkdirSync(AUTH_DIR, { recursive: true });
|
|
10308
10817
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
10309
10818
|
}
|
|
10310
10819
|
function addLearning(bullet, scope = "project") {
|
|
@@ -10317,55 +10826,56 @@ function addLearning(bullet, scope = "project") {
|
|
|
10317
10826
|
return { file: LEARNINGS_FILE, added: result.newCount > 0 };
|
|
10318
10827
|
}
|
|
10319
10828
|
function readPersonalLearnings() {
|
|
10320
|
-
if (!
|
|
10321
|
-
const content =
|
|
10829
|
+
if (!fs37.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
10830
|
+
const content = fs37.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
10322
10831
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10323
10832
|
return bullets || null;
|
|
10324
10833
|
}
|
|
10325
10834
|
function readLearnedSection() {
|
|
10326
|
-
if (
|
|
10327
|
-
const content2 =
|
|
10835
|
+
if (fs37.existsSync(LEARNINGS_FILE)) {
|
|
10836
|
+
const content2 = fs37.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
10328
10837
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
10329
10838
|
return bullets || null;
|
|
10330
10839
|
}
|
|
10331
10840
|
const claudeMdPath = "CLAUDE.md";
|
|
10332
|
-
if (!
|
|
10333
|
-
const content =
|
|
10841
|
+
if (!fs37.existsSync(claudeMdPath)) return null;
|
|
10842
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10334
10843
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10335
10844
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10336
10845
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
10337
10846
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
10338
10847
|
}
|
|
10339
10848
|
function migrateInlineLearnings() {
|
|
10340
|
-
if (
|
|
10849
|
+
if (fs37.existsSync(LEARNINGS_FILE)) return false;
|
|
10341
10850
|
const claudeMdPath = "CLAUDE.md";
|
|
10342
|
-
if (!
|
|
10343
|
-
const content =
|
|
10851
|
+
if (!fs37.existsSync(claudeMdPath)) return false;
|
|
10852
|
+
const content = fs37.readFileSync(claudeMdPath, "utf-8");
|
|
10344
10853
|
const startIdx = content.indexOf(LEARNED_START);
|
|
10345
10854
|
const endIdx = content.indexOf(LEARNED_END);
|
|
10346
10855
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
10347
10856
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
10348
10857
|
if (!section) return false;
|
|
10349
|
-
|
|
10858
|
+
fs37.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
10350
10859
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
10351
|
-
|
|
10860
|
+
fs37.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
10352
10861
|
return true;
|
|
10353
10862
|
}
|
|
10354
10863
|
|
|
10355
10864
|
// src/commands/refresh.ts
|
|
10356
10865
|
init_config();
|
|
10357
10866
|
init_resolve_caliber();
|
|
10867
|
+
init_builtin_skills();
|
|
10358
10868
|
function log2(quiet, ...args) {
|
|
10359
10869
|
if (!quiet) console.log(...args);
|
|
10360
10870
|
}
|
|
10361
10871
|
function discoverGitRepos(parentDir) {
|
|
10362
10872
|
const repos = [];
|
|
10363
10873
|
try {
|
|
10364
|
-
const entries =
|
|
10874
|
+
const entries = fs39.readdirSync(parentDir, { withFileTypes: true });
|
|
10365
10875
|
for (const entry of entries) {
|
|
10366
10876
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
10367
|
-
const childPath =
|
|
10368
|
-
if (
|
|
10877
|
+
const childPath = path32.join(parentDir, entry.name);
|
|
10878
|
+
if (fs39.existsSync(path32.join(childPath, ".git"))) {
|
|
10369
10879
|
repos.push(childPath);
|
|
10370
10880
|
}
|
|
10371
10881
|
}
|
|
@@ -10451,26 +10961,27 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
10451
10961
|
const filesToWrite = response.docsUpdated || [];
|
|
10452
10962
|
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
10453
10963
|
for (const filePath of filesToWrite) {
|
|
10454
|
-
const fullPath =
|
|
10964
|
+
const fullPath = path32.resolve(repoDir, filePath);
|
|
10455
10965
|
try {
|
|
10456
|
-
preRefreshContents.set(filePath,
|
|
10966
|
+
preRefreshContents.set(filePath, fs39.readFileSync(fullPath, "utf-8"));
|
|
10457
10967
|
} catch {
|
|
10458
10968
|
preRefreshContents.set(filePath, null);
|
|
10459
10969
|
}
|
|
10460
10970
|
}
|
|
10461
10971
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
10462
|
-
|
|
10972
|
+
const trigger = quiet ? "hook" : "manual";
|
|
10973
|
+
trackRefreshCompleted(written.length, Date.now(), trigger);
|
|
10463
10974
|
const postScore = computeLocalScore(repoDir, targetAgent);
|
|
10464
10975
|
if (postScore.score < preScore.score) {
|
|
10465
10976
|
for (const [filePath, content] of preRefreshContents) {
|
|
10466
|
-
const fullPath =
|
|
10977
|
+
const fullPath = path32.resolve(repoDir, filePath);
|
|
10467
10978
|
if (content === null) {
|
|
10468
10979
|
try {
|
|
10469
|
-
|
|
10980
|
+
fs39.unlinkSync(fullPath);
|
|
10470
10981
|
} catch {
|
|
10471
10982
|
}
|
|
10472
10983
|
} else {
|
|
10473
|
-
|
|
10984
|
+
fs39.writeFileSync(fullPath, content);
|
|
10474
10985
|
}
|
|
10475
10986
|
}
|
|
10476
10987
|
spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
|
|
@@ -10525,7 +11036,7 @@ async function refreshCommand(options) {
|
|
|
10525
11036
|
`));
|
|
10526
11037
|
const originalDir = process.cwd();
|
|
10527
11038
|
for (const repo of repos) {
|
|
10528
|
-
const repoName =
|
|
11039
|
+
const repoName = path32.basename(repo);
|
|
10529
11040
|
try {
|
|
10530
11041
|
process.chdir(repo);
|
|
10531
11042
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -10547,150 +11058,6 @@ async function refreshCommand(options) {
|
|
|
10547
11058
|
// src/commands/hooks.ts
|
|
10548
11059
|
import chalk20 from "chalk";
|
|
10549
11060
|
import fs40 from "fs";
|
|
10550
|
-
|
|
10551
|
-
// src/lib/hooks.ts
|
|
10552
|
-
init_resolve_caliber();
|
|
10553
|
-
import fs39 from "fs";
|
|
10554
|
-
import path32 from "path";
|
|
10555
|
-
import { execSync as execSync15 } from "child_process";
|
|
10556
|
-
var SETTINGS_PATH2 = path32.join(".claude", "settings.json");
|
|
10557
|
-
var REFRESH_TAIL = "refresh --quiet";
|
|
10558
|
-
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
10559
|
-
function getHookCommand() {
|
|
10560
|
-
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
10561
|
-
}
|
|
10562
|
-
function readSettings2() {
|
|
10563
|
-
if (!fs39.existsSync(SETTINGS_PATH2)) return {};
|
|
10564
|
-
try {
|
|
10565
|
-
return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
10566
|
-
} catch {
|
|
10567
|
-
return {};
|
|
10568
|
-
}
|
|
10569
|
-
}
|
|
10570
|
-
function writeSettings2(settings) {
|
|
10571
|
-
const dir = path32.dirname(SETTINGS_PATH2);
|
|
10572
|
-
if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
|
|
10573
|
-
fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
10574
|
-
}
|
|
10575
|
-
function findHookIndex(sessionEnd) {
|
|
10576
|
-
return sessionEnd.findIndex(
|
|
10577
|
-
(entry) => entry.hooks?.some((h) => isCaliberCommand(h.command, REFRESH_TAIL))
|
|
10578
|
-
);
|
|
10579
|
-
}
|
|
10580
|
-
function isHookInstalled() {
|
|
10581
|
-
const settings = readSettings2();
|
|
10582
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10583
|
-
if (!Array.isArray(sessionEnd)) return false;
|
|
10584
|
-
return findHookIndex(sessionEnd) !== -1;
|
|
10585
|
-
}
|
|
10586
|
-
function installHook() {
|
|
10587
|
-
const settings = readSettings2();
|
|
10588
|
-
if (!settings.hooks) settings.hooks = {};
|
|
10589
|
-
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
10590
|
-
if (findHookIndex(settings.hooks.SessionEnd) !== -1) {
|
|
10591
|
-
return { installed: false, alreadyInstalled: true };
|
|
10592
|
-
}
|
|
10593
|
-
settings.hooks.SessionEnd.push({
|
|
10594
|
-
matcher: "",
|
|
10595
|
-
hooks: [{ type: "command", command: getHookCommand(), description: HOOK_DESCRIPTION }]
|
|
10596
|
-
});
|
|
10597
|
-
writeSettings2(settings);
|
|
10598
|
-
return { installed: true, alreadyInstalled: false };
|
|
10599
|
-
}
|
|
10600
|
-
function removeHook() {
|
|
10601
|
-
const settings = readSettings2();
|
|
10602
|
-
const sessionEnd = settings.hooks?.SessionEnd;
|
|
10603
|
-
if (!Array.isArray(sessionEnd)) {
|
|
10604
|
-
return { removed: false, notFound: true };
|
|
10605
|
-
}
|
|
10606
|
-
const idx = findHookIndex(sessionEnd);
|
|
10607
|
-
if (idx === -1) {
|
|
10608
|
-
return { removed: false, notFound: true };
|
|
10609
|
-
}
|
|
10610
|
-
sessionEnd.splice(idx, 1);
|
|
10611
|
-
if (sessionEnd.length === 0) {
|
|
10612
|
-
delete settings.hooks.SessionEnd;
|
|
10613
|
-
}
|
|
10614
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
10615
|
-
delete settings.hooks;
|
|
10616
|
-
}
|
|
10617
|
-
writeSettings2(settings);
|
|
10618
|
-
return { removed: true, notFound: false };
|
|
10619
|
-
}
|
|
10620
|
-
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
10621
|
-
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
10622
|
-
function getPrecommitBlock() {
|
|
10623
|
-
const bin = resolveCaliber();
|
|
10624
|
-
const npx = isNpxResolution();
|
|
10625
|
-
const guard = npx ? "command -v npx >/dev/null 2>&1" : `[ -x "${bin}" ] || command -v "${bin}" >/dev/null 2>&1`;
|
|
10626
|
-
const invoke = npx ? bin : `"${bin}"`;
|
|
10627
|
-
return `${PRECOMMIT_START}
|
|
10628
|
-
if ${guard}; then
|
|
10629
|
-
echo "\\033[2mcaliber: refreshing docs...\\033[0m"
|
|
10630
|
-
${invoke} refresh 2>/dev/null || true
|
|
10631
|
-
${invoke} learn finalize 2>/dev/null || true
|
|
10632
|
-
git diff --name-only -- CLAUDE.md .claude/ .cursor/ AGENTS.md CALIBER_LEARNINGS.md 2>/dev/null | xargs git add 2>/dev/null || true
|
|
10633
|
-
fi
|
|
10634
|
-
${PRECOMMIT_END}`;
|
|
10635
|
-
}
|
|
10636
|
-
function getGitHooksDir() {
|
|
10637
|
-
try {
|
|
10638
|
-
const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10639
|
-
return path32.join(gitDir, "hooks");
|
|
10640
|
-
} catch {
|
|
10641
|
-
return null;
|
|
10642
|
-
}
|
|
10643
|
-
}
|
|
10644
|
-
function getPreCommitPath() {
|
|
10645
|
-
const hooksDir = getGitHooksDir();
|
|
10646
|
-
return hooksDir ? path32.join(hooksDir, "pre-commit") : null;
|
|
10647
|
-
}
|
|
10648
|
-
function isPreCommitHookInstalled() {
|
|
10649
|
-
const hookPath = getPreCommitPath();
|
|
10650
|
-
if (!hookPath || !fs39.existsSync(hookPath)) return false;
|
|
10651
|
-
const content = fs39.readFileSync(hookPath, "utf-8");
|
|
10652
|
-
return content.includes(PRECOMMIT_START);
|
|
10653
|
-
}
|
|
10654
|
-
function installPreCommitHook() {
|
|
10655
|
-
if (isPreCommitHookInstalled()) {
|
|
10656
|
-
return { installed: false, alreadyInstalled: true };
|
|
10657
|
-
}
|
|
10658
|
-
const hookPath = getPreCommitPath();
|
|
10659
|
-
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
10660
|
-
const hooksDir = path32.dirname(hookPath);
|
|
10661
|
-
if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
|
|
10662
|
-
let content = "";
|
|
10663
|
-
if (fs39.existsSync(hookPath)) {
|
|
10664
|
-
content = fs39.readFileSync(hookPath, "utf-8");
|
|
10665
|
-
if (!content.endsWith("\n")) content += "\n";
|
|
10666
|
-
content += "\n" + getPrecommitBlock() + "\n";
|
|
10667
|
-
} else {
|
|
10668
|
-
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
10669
|
-
}
|
|
10670
|
-
fs39.writeFileSync(hookPath, content);
|
|
10671
|
-
fs39.chmodSync(hookPath, 493);
|
|
10672
|
-
return { installed: true, alreadyInstalled: false };
|
|
10673
|
-
}
|
|
10674
|
-
function removePreCommitHook() {
|
|
10675
|
-
const hookPath = getPreCommitPath();
|
|
10676
|
-
if (!hookPath || !fs39.existsSync(hookPath)) {
|
|
10677
|
-
return { removed: false, notFound: true };
|
|
10678
|
-
}
|
|
10679
|
-
let content = fs39.readFileSync(hookPath, "utf-8");
|
|
10680
|
-
if (!content.includes(PRECOMMIT_START)) {
|
|
10681
|
-
return { removed: false, notFound: true };
|
|
10682
|
-
}
|
|
10683
|
-
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
10684
|
-
content = content.replace(regex, "\n");
|
|
10685
|
-
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
10686
|
-
fs39.unlinkSync(hookPath);
|
|
10687
|
-
} else {
|
|
10688
|
-
fs39.writeFileSync(hookPath, content);
|
|
10689
|
-
}
|
|
10690
|
-
return { removed: true, notFound: false };
|
|
10691
|
-
}
|
|
10692
|
-
|
|
10693
|
-
// src/commands/hooks.ts
|
|
10694
11061
|
var HOOKS = [
|
|
10695
11062
|
{
|
|
10696
11063
|
id: "session-end",
|
|
@@ -12299,10 +12666,199 @@ async function publishCommand() {
|
|
|
12299
12666
|
}
|
|
12300
12667
|
}
|
|
12301
12668
|
|
|
12669
|
+
// src/commands/bootstrap.ts
|
|
12670
|
+
init_builtin_skills();
|
|
12671
|
+
import fs47 from "fs";
|
|
12672
|
+
import chalk27 from "chalk";
|
|
12673
|
+
var PLATFORM_SKILL_DIRS = {
|
|
12674
|
+
claude: ".claude/skills",
|
|
12675
|
+
cursor: ".cursor/skills",
|
|
12676
|
+
codex: ".agents/skills"
|
|
12677
|
+
};
|
|
12678
|
+
async function bootstrapCommand() {
|
|
12679
|
+
const platforms = detectPlatforms();
|
|
12680
|
+
const detected = [];
|
|
12681
|
+
if (platforms.claude) detected.push("claude");
|
|
12682
|
+
if (platforms.cursor) detected.push("cursor");
|
|
12683
|
+
if (platforms.codex) detected.push("codex");
|
|
12684
|
+
if (detected.length === 0) detected.push("claude");
|
|
12685
|
+
const written = [];
|
|
12686
|
+
for (const platform of detected) {
|
|
12687
|
+
const skillsDir = PLATFORM_SKILL_DIRS[platform];
|
|
12688
|
+
if (!skillsDir) continue;
|
|
12689
|
+
for (const skill of BUILTIN_SKILLS) {
|
|
12690
|
+
const skillDir = `${skillsDir}/${skill.name}`;
|
|
12691
|
+
const skillPath = `${skillDir}/SKILL.md`;
|
|
12692
|
+
fs47.mkdirSync(skillDir, { recursive: true });
|
|
12693
|
+
fs47.writeFileSync(skillPath, buildSkillContent(skill));
|
|
12694
|
+
written.push(skillPath);
|
|
12695
|
+
}
|
|
12696
|
+
}
|
|
12697
|
+
if (written.length === 0) {
|
|
12698
|
+
console.log(chalk27.yellow("No skills were written."));
|
|
12699
|
+
return;
|
|
12700
|
+
}
|
|
12701
|
+
console.log(chalk27.bold.green("\n Caliber skills installed!\n"));
|
|
12702
|
+
for (const file of written) {
|
|
12703
|
+
console.log(` ${chalk27.green("\u2713")} ${file}`);
|
|
12704
|
+
}
|
|
12705
|
+
console.log(chalk27.dim("\n Your agent can now run /setup-caliber to complete the setup."));
|
|
12706
|
+
console.log(chalk27.dim(' Just tell your agent: "Run /setup-caliber"\n'));
|
|
12707
|
+
}
|
|
12708
|
+
|
|
12709
|
+
// src/commands/uninstall.ts
|
|
12710
|
+
import fs48 from "fs";
|
|
12711
|
+
import path39 from "path";
|
|
12712
|
+
import chalk28 from "chalk";
|
|
12713
|
+
import confirm3 from "@inquirer/confirm";
|
|
12714
|
+
init_pre_commit_block();
|
|
12715
|
+
init_builtin_skills();
|
|
12716
|
+
init_config();
|
|
12717
|
+
var MANAGED_DOC_FILES = [
|
|
12718
|
+
"CLAUDE.md",
|
|
12719
|
+
path39.join(".github", "copilot-instructions.md"),
|
|
12720
|
+
"AGENTS.md"
|
|
12721
|
+
];
|
|
12722
|
+
var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
|
|
12723
|
+
var CURSOR_RULES_DIR = path39.join(".cursor", "rules");
|
|
12724
|
+
function removeCaliberCursorRules() {
|
|
12725
|
+
const removed = [];
|
|
12726
|
+
if (!fs48.existsSync(CURSOR_RULES_DIR)) return removed;
|
|
12727
|
+
for (const file of fs48.readdirSync(CURSOR_RULES_DIR)) {
|
|
12728
|
+
if (file.startsWith("caliber-") && file.endsWith(".mdc")) {
|
|
12729
|
+
const fullPath = path39.join(CURSOR_RULES_DIR, file);
|
|
12730
|
+
fs48.unlinkSync(fullPath);
|
|
12731
|
+
removed.push(fullPath);
|
|
12732
|
+
}
|
|
12733
|
+
}
|
|
12734
|
+
return removed;
|
|
12735
|
+
}
|
|
12736
|
+
function removeBuiltinSkills() {
|
|
12737
|
+
const removed = [];
|
|
12738
|
+
for (const skillsDir of SKILL_DIRS) {
|
|
12739
|
+
if (!fs48.existsSync(skillsDir)) continue;
|
|
12740
|
+
for (const name of BUILTIN_SKILL_NAMES) {
|
|
12741
|
+
const skillDir = path39.join(skillsDir, name);
|
|
12742
|
+
if (fs48.existsSync(skillDir)) {
|
|
12743
|
+
fs48.rmSync(skillDir, { recursive: true });
|
|
12744
|
+
removed.push(skillDir);
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
}
|
|
12748
|
+
return removed;
|
|
12749
|
+
}
|
|
12750
|
+
function stripManagedBlocksFromFiles() {
|
|
12751
|
+
const modified = [];
|
|
12752
|
+
for (const filePath of MANAGED_DOC_FILES) {
|
|
12753
|
+
if (!fs48.existsSync(filePath)) continue;
|
|
12754
|
+
const original = fs48.readFileSync(filePath, "utf-8");
|
|
12755
|
+
const stripped = stripManagedBlocks(original);
|
|
12756
|
+
if (stripped !== original) {
|
|
12757
|
+
const trimmed = stripped.trim();
|
|
12758
|
+
if (!trimmed || /^#\s*\S*$/.test(trimmed)) {
|
|
12759
|
+
fs48.unlinkSync(filePath);
|
|
12760
|
+
} else {
|
|
12761
|
+
fs48.writeFileSync(filePath, stripped);
|
|
12762
|
+
}
|
|
12763
|
+
modified.push(filePath);
|
|
12764
|
+
}
|
|
12765
|
+
}
|
|
12766
|
+
return modified;
|
|
12767
|
+
}
|
|
12768
|
+
function removeDirectory(dir) {
|
|
12769
|
+
if (!fs48.existsSync(dir)) return false;
|
|
12770
|
+
fs48.rmSync(dir, { recursive: true });
|
|
12771
|
+
return true;
|
|
12772
|
+
}
|
|
12773
|
+
async function uninstallCommand(options) {
|
|
12774
|
+
console.log(chalk28.bold("\n Caliber Uninstall\n"));
|
|
12775
|
+
console.log(chalk28.dim(" This will remove all Caliber resources from this project:\n"));
|
|
12776
|
+
console.log(chalk28.dim(" \u2022 Pre-commit hook"));
|
|
12777
|
+
console.log(chalk28.dim(" \u2022 Session learning hooks"));
|
|
12778
|
+
console.log(chalk28.dim(" \u2022 Managed blocks in CLAUDE.md, AGENTS.md, copilot-instructions.md"));
|
|
12779
|
+
console.log(chalk28.dim(" \u2022 Cursor rules (caliber-*.mdc)"));
|
|
12780
|
+
console.log(chalk28.dim(" \u2022 Built-in skills (setup-caliber, find-skills, save-learning)"));
|
|
12781
|
+
console.log(chalk28.dim(" \u2022 CALIBER_LEARNINGS.md"));
|
|
12782
|
+
console.log(chalk28.dim(" \u2022 .caliber/ directory (backups, cache, state)\n"));
|
|
12783
|
+
if (!options.force) {
|
|
12784
|
+
const proceed = await confirm3({ message: "Continue with uninstall?" });
|
|
12785
|
+
if (!proceed) {
|
|
12786
|
+
console.log(chalk28.dim("\n Cancelled.\n"));
|
|
12787
|
+
return;
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
console.log("");
|
|
12791
|
+
const actions = [];
|
|
12792
|
+
const hookResult = removePreCommitHook();
|
|
12793
|
+
if (hookResult.removed) {
|
|
12794
|
+
console.log(` ${chalk28.red("\u2717")} Pre-commit hook removed`);
|
|
12795
|
+
actions.push("pre-commit hook");
|
|
12796
|
+
}
|
|
12797
|
+
const learnResult = removeLearningHooks();
|
|
12798
|
+
if (learnResult.removed) {
|
|
12799
|
+
console.log(` ${chalk28.red("\u2717")} Claude Code learning hooks removed`);
|
|
12800
|
+
actions.push("claude learning hooks");
|
|
12801
|
+
}
|
|
12802
|
+
const cursorLearnResult = removeCursorLearningHooks();
|
|
12803
|
+
if (cursorLearnResult.removed) {
|
|
12804
|
+
console.log(` ${chalk28.red("\u2717")} Cursor learning hooks removed`);
|
|
12805
|
+
actions.push("cursor learning hooks");
|
|
12806
|
+
}
|
|
12807
|
+
const strippedFiles = stripManagedBlocksFromFiles();
|
|
12808
|
+
for (const file of strippedFiles) {
|
|
12809
|
+
console.log(` ${chalk28.yellow("~")} ${file} \u2014 managed blocks removed`);
|
|
12810
|
+
actions.push(file);
|
|
12811
|
+
}
|
|
12812
|
+
const removedRules = removeCaliberCursorRules();
|
|
12813
|
+
for (const rule of removedRules) {
|
|
12814
|
+
console.log(` ${chalk28.red("\u2717")} ${rule}`);
|
|
12815
|
+
}
|
|
12816
|
+
if (removedRules.length > 0) actions.push("cursor rules");
|
|
12817
|
+
const removedSkills = removeBuiltinSkills();
|
|
12818
|
+
for (const skill of removedSkills) {
|
|
12819
|
+
console.log(` ${chalk28.red("\u2717")} ${skill}/`);
|
|
12820
|
+
}
|
|
12821
|
+
if (removedSkills.length > 0) actions.push("builtin skills");
|
|
12822
|
+
if (fs48.existsSync("CALIBER_LEARNINGS.md")) {
|
|
12823
|
+
fs48.unlinkSync("CALIBER_LEARNINGS.md");
|
|
12824
|
+
console.log(` ${chalk28.red("\u2717")} CALIBER_LEARNINGS.md`);
|
|
12825
|
+
actions.push("learnings file");
|
|
12826
|
+
}
|
|
12827
|
+
if (removeDirectory(CALIBER_DIR)) {
|
|
12828
|
+
console.log(` ${chalk28.red("\u2717")} .caliber/ directory`);
|
|
12829
|
+
actions.push(".caliber directory");
|
|
12830
|
+
}
|
|
12831
|
+
if (actions.length === 0) {
|
|
12832
|
+
console.log(chalk28.dim(" Nothing to remove \u2014 Caliber is not installed in this project.\n"));
|
|
12833
|
+
return;
|
|
12834
|
+
}
|
|
12835
|
+
trackUninstallExecuted();
|
|
12836
|
+
const configPath = getConfigFilePath();
|
|
12837
|
+
if (fs48.existsSync(configPath)) {
|
|
12838
|
+
console.log("");
|
|
12839
|
+
const removeConfig = options.force || await confirm3({
|
|
12840
|
+
message: `Remove global config (~/.caliber/config.json)? This affects all projects.`
|
|
12841
|
+
});
|
|
12842
|
+
if (removeConfig) {
|
|
12843
|
+
fs48.unlinkSync(configPath);
|
|
12844
|
+
console.log(` ${chalk28.red("\u2717")} ${configPath}`);
|
|
12845
|
+
const configDir = path39.dirname(configPath);
|
|
12846
|
+
try {
|
|
12847
|
+
const remaining = fs48.readdirSync(configDir);
|
|
12848
|
+
if (remaining.length === 0) fs48.rmdirSync(configDir);
|
|
12849
|
+
} catch {
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
}
|
|
12853
|
+
console.log(chalk28.bold.green(`
|
|
12854
|
+
Caliber has been removed from this project.`));
|
|
12855
|
+
console.log(chalk28.dim(" Your code is untouched \u2014 only Caliber config files were removed.\n"));
|
|
12856
|
+
}
|
|
12857
|
+
|
|
12302
12858
|
// src/cli.ts
|
|
12303
|
-
var __dirname =
|
|
12859
|
+
var __dirname = path40.dirname(fileURLToPath(import.meta.url));
|
|
12304
12860
|
var pkg = JSON.parse(
|
|
12305
|
-
|
|
12861
|
+
fs49.readFileSync(path40.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
12306
12862
|
);
|
|
12307
12863
|
var program = new Command();
|
|
12308
12864
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
@@ -12310,9 +12866,11 @@ program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI co
|
|
|
12310
12866
|
function tracked(commandName, handler) {
|
|
12311
12867
|
const wrapper = async (...args) => {
|
|
12312
12868
|
const start = Date.now();
|
|
12869
|
+
const isCI = !!(process.env.CI || process.env.GITHUB_ACTIONS);
|
|
12313
12870
|
trackEvent("command_started", {
|
|
12314
12871
|
command: commandName,
|
|
12315
|
-
cli_version: pkg.version
|
|
12872
|
+
cli_version: pkg.version,
|
|
12873
|
+
is_ci: isCI
|
|
12316
12874
|
});
|
|
12317
12875
|
try {
|
|
12318
12876
|
await handler(...args);
|
|
@@ -12364,7 +12922,9 @@ function parseAgentOption(value) {
|
|
|
12364
12922
|
return agents;
|
|
12365
12923
|
}
|
|
12366
12924
|
program.command("init").description("Initialize your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, github-copilot", parseAgentOption).option("--source <paths...>", "Related source paths to include as context").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing config without prompting").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").option("--auto-approve", "Run without interactive prompts (auto-accept all)").option("--verbose", "Show detailed logs of each step").action(tracked("init", initCommand));
|
|
12925
|
+
program.command("bootstrap").description("Install agent skills (/setup-caliber, /find-skills, /save-learning) without running init").action(tracked("bootstrap", bootstrapCommand));
|
|
12367
12926
|
program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
|
|
12927
|
+
program.command("uninstall").description("Remove all Caliber resources from this project").option("--force", "Skip confirmation prompt").action(tracked("uninstall", (options) => uninstallCommand(options)));
|
|
12368
12928
|
program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
|
|
12369
12929
|
program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate config").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
|
|
12370
12930
|
program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
|
|
@@ -12389,15 +12949,15 @@ learn.command("delete <index>").description("Delete a learning by its index numb
|
|
|
12389
12949
|
learn.command("add <content>").description("Add a learning directly (used by agent skills)").option("--personal", "Save as a personal learning instead of project-level").action(tracked("learn:add", learnAddCommand));
|
|
12390
12950
|
|
|
12391
12951
|
// src/utils/version-check.ts
|
|
12392
|
-
import
|
|
12393
|
-
import
|
|
12952
|
+
import fs50 from "fs";
|
|
12953
|
+
import path41 from "path";
|
|
12394
12954
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
12395
12955
|
import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
|
|
12396
|
-
import
|
|
12956
|
+
import chalk29 from "chalk";
|
|
12397
12957
|
import ora8 from "ora";
|
|
12398
|
-
import
|
|
12399
|
-
var __dirname_vc =
|
|
12400
|
-
var pkg2 = JSON.parse(
|
|
12958
|
+
import confirm4 from "@inquirer/confirm";
|
|
12959
|
+
var __dirname_vc = path41.dirname(fileURLToPath2(import.meta.url));
|
|
12960
|
+
var pkg2 = JSON.parse(fs50.readFileSync(path41.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
|
|
12401
12961
|
function getChannel(version) {
|
|
12402
12962
|
const match = version.match(/-(dev|next)\./);
|
|
12403
12963
|
return match ? match[1] : "latest";
|
|
@@ -12424,8 +12984,8 @@ function getInstalledVersion() {
|
|
|
12424
12984
|
encoding: "utf-8",
|
|
12425
12985
|
stdio: ["pipe", "pipe", "pipe"]
|
|
12426
12986
|
}).trim();
|
|
12427
|
-
const pkgPath =
|
|
12428
|
-
return JSON.parse(
|
|
12987
|
+
const pkgPath = path41.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
12988
|
+
return JSON.parse(fs50.readFileSync(pkgPath, "utf-8")).version;
|
|
12429
12989
|
} catch {
|
|
12430
12990
|
return null;
|
|
12431
12991
|
}
|
|
@@ -12450,18 +13010,18 @@ async function checkForUpdates() {
|
|
|
12450
13010
|
if (!isInteractive) {
|
|
12451
13011
|
const installTag = channel === "latest" ? "" : `@${channel}`;
|
|
12452
13012
|
console.log(
|
|
12453
|
-
|
|
13013
|
+
chalk29.yellow(
|
|
12454
13014
|
`
|
|
12455
13015
|
Update available: ${current} -> ${latest}
|
|
12456
|
-
Run ${
|
|
13016
|
+
Run ${chalk29.bold(`npm install -g @rely-ai/caliber${installTag}`)} to upgrade.
|
|
12457
13017
|
`
|
|
12458
13018
|
)
|
|
12459
13019
|
);
|
|
12460
13020
|
return;
|
|
12461
13021
|
}
|
|
12462
|
-
console.log(
|
|
13022
|
+
console.log(chalk29.yellow(`
|
|
12463
13023
|
Update available: ${current} -> ${latest}`));
|
|
12464
|
-
const shouldUpdate = await
|
|
13024
|
+
const shouldUpdate = await confirm4({
|
|
12465
13025
|
message: "Would you like to update now? (Y/n)",
|
|
12466
13026
|
default: true
|
|
12467
13027
|
});
|
|
@@ -12482,14 +13042,14 @@ Update available: ${current} -> ${latest}`));
|
|
|
12482
13042
|
if (installed !== latest) {
|
|
12483
13043
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
12484
13044
|
console.log(
|
|
12485
|
-
|
|
13045
|
+
chalk29.yellow(`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually.
|
|
12486
13046
|
`)
|
|
12487
13047
|
);
|
|
12488
13048
|
return;
|
|
12489
13049
|
}
|
|
12490
|
-
spinner.succeed(
|
|
13050
|
+
spinner.succeed(chalk29.green(`Updated to ${latest}`));
|
|
12491
13051
|
const args = process.argv.slice(2);
|
|
12492
|
-
console.log(
|
|
13052
|
+
console.log(chalk29.dim(`
|
|
12493
13053
|
Restarting: caliber ${args.join(" ")}
|
|
12494
13054
|
`));
|
|
12495
13055
|
execFileSync3("caliber", args, {
|
|
@@ -12502,11 +13062,11 @@ Restarting: caliber ${args.join(" ")}
|
|
|
12502
13062
|
if (err instanceof Error) {
|
|
12503
13063
|
const stderr = err.stderr;
|
|
12504
13064
|
const errMsg = stderr ? String(stderr).trim().split("\n").pop() : err.message.split("\n")[0];
|
|
12505
|
-
if (errMsg && !errMsg.includes("SIGTERM")) console.log(
|
|
13065
|
+
if (errMsg && !errMsg.includes("SIGTERM")) console.log(chalk29.dim(` ${errMsg}`));
|
|
12506
13066
|
}
|
|
12507
13067
|
console.log(
|
|
12508
|
-
|
|
12509
|
-
`Run ${
|
|
13068
|
+
chalk29.yellow(
|
|
13069
|
+
`Run ${chalk29.bold(`npm install -g @rely-ai/caliber@${tag}`)} manually to upgrade.
|
|
12510
13070
|
`
|
|
12511
13071
|
)
|
|
12512
13072
|
);
|