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