@rely-ai/caliber 1.29.7 → 1.30.0-dev.1774285227
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 +442 -288
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -495,13 +495,13 @@ __export(lock_exports, {
|
|
|
495
495
|
isCaliberRunning: () => isCaliberRunning,
|
|
496
496
|
releaseLock: () => releaseLock
|
|
497
497
|
});
|
|
498
|
-
import
|
|
499
|
-
import
|
|
498
|
+
import fs37 from "fs";
|
|
499
|
+
import path29 from "path";
|
|
500
500
|
import os8 from "os";
|
|
501
501
|
function isCaliberRunning() {
|
|
502
502
|
try {
|
|
503
|
-
if (!
|
|
504
|
-
const raw =
|
|
503
|
+
if (!fs37.existsSync(LOCK_FILE)) return false;
|
|
504
|
+
const raw = fs37.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
505
505
|
const { pid, ts } = JSON.parse(raw);
|
|
506
506
|
if (Date.now() - ts > STALE_MS) return false;
|
|
507
507
|
try {
|
|
@@ -516,13 +516,13 @@ function isCaliberRunning() {
|
|
|
516
516
|
}
|
|
517
517
|
function acquireLock() {
|
|
518
518
|
try {
|
|
519
|
-
|
|
519
|
+
fs37.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
520
520
|
} catch {
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
function releaseLock() {
|
|
524
524
|
try {
|
|
525
|
-
if (
|
|
525
|
+
if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
|
|
526
526
|
} catch {
|
|
527
527
|
}
|
|
528
528
|
}
|
|
@@ -530,21 +530,21 @@ var LOCK_FILE, STALE_MS;
|
|
|
530
530
|
var init_lock = __esm({
|
|
531
531
|
"src/lib/lock.ts"() {
|
|
532
532
|
"use strict";
|
|
533
|
-
LOCK_FILE =
|
|
533
|
+
LOCK_FILE = path29.join(os8.tmpdir(), ".caliber.lock");
|
|
534
534
|
STALE_MS = 10 * 60 * 1e3;
|
|
535
535
|
}
|
|
536
536
|
});
|
|
537
537
|
|
|
538
538
|
// src/cli.ts
|
|
539
539
|
import { Command } from "commander";
|
|
540
|
-
import
|
|
541
|
-
import
|
|
540
|
+
import fs47 from "fs";
|
|
541
|
+
import path38 from "path";
|
|
542
542
|
import { fileURLToPath } from "url";
|
|
543
543
|
|
|
544
544
|
// src/commands/init.ts
|
|
545
|
-
import
|
|
545
|
+
import path25 from "path";
|
|
546
546
|
import chalk14 from "chalk";
|
|
547
|
-
import
|
|
547
|
+
import fs32 from "fs";
|
|
548
548
|
|
|
549
549
|
// src/fingerprint/index.ts
|
|
550
550
|
import fs7 from "fs";
|
|
@@ -2307,7 +2307,7 @@ init_config();
|
|
|
2307
2307
|
var ROLE_AND_CONTEXT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, Codex, and GitHub Copilot).
|
|
2308
2308
|
|
|
2309
2309
|
Your job depends on context:
|
|
2310
|
-
- If no existing configs exist \u2192 generate an initial
|
|
2310
|
+
- If no existing configs exist \u2192 generate an initial configuration from scratch.
|
|
2311
2311
|
- If existing configs are provided \u2192 audit them and suggest targeted improvements. Preserve accurate content \u2014 don't rewrite what's already correct.`;
|
|
2312
2312
|
var CONFIG_FILE_TYPES = `You understand these config files:
|
|
2313
2313
|
- CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
|
|
@@ -2320,6 +2320,12 @@ var CONFIG_FILE_TYPES = `You understand these config files:
|
|
|
2320
2320
|
- .github/copilot-instructions.md: Always-on repository-wide instructions for GitHub Copilot \u2014 same purpose as CLAUDE.md but for Copilot. Plain markdown, no frontmatter.
|
|
2321
2321
|
- .github/instructions/*.instructions.md: Path-specific instruction files for GitHub Copilot with YAML frontmatter containing an \`applyTo\` glob pattern (e.g. \`applyTo: "**/*.ts,**/*.tsx"\`). Only loaded when Copilot is working on matching files.`;
|
|
2322
2322
|
var EXCLUSIONS = `Do NOT generate .claude/settings.json, .claude/settings.local.json, or mcpServers \u2014 those are managed separately.`;
|
|
2323
|
+
var AUDIT_CHECKLIST = `Audit checklist (when existing configs are provided):
|
|
2324
|
+
1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
|
|
2325
|
+
2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
|
|
2326
|
+
3. Duplicate or overlapping skills \u2014 can any be merged or removed?
|
|
2327
|
+
4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
|
|
2328
|
+
5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?`;
|
|
2323
2329
|
var OUTPUT_FORMAT = `Your output MUST follow this exact format (no markdown fences):
|
|
2324
2330
|
|
|
2325
2331
|
1. Exactly 6 short status lines (one per line, prefixed with "STATUS: "). Each should be a creative, specific description of what you're analyzing for THIS project \u2014 reference the project's actual languages, frameworks, or tools.
|
|
@@ -2383,7 +2389,10 @@ Accuracy (15 pts) \u2014 CRITICAL:
|
|
|
2383
2389
|
|
|
2384
2390
|
Safety: Never include API keys, tokens, or credentials in config files.
|
|
2385
2391
|
|
|
2386
|
-
|
|
2392
|
+
PRIORITY WHEN CONSTRAINTS CONFLICT: Grounding and reference density matter more than raw token count. A 2500-token config that references 50%+ of the project's directories scores higher than a 1500-token config that only mentions 3 paths. Pack references densely using the inline path style shown in OUTPUT SIZE CONSTRAINTS.
|
|
2393
|
+
|
|
2394
|
+
Note: Permissions, hooks, freshness tracking, and OpenSkills frontmatter are scored automatically by caliber \u2014 do not optimize for them.
|
|
2395
|
+
README.md is provided for context only \u2014 do NOT include a readmeMd field in your output.`;
|
|
2387
2396
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
2388
2397
|
- CLAUDE.md / AGENTS.md: MUST be under 150 lines for maximum score. Aim for 100-140 lines. Be concise \u2014 commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
|
|
2389
2398
|
|
|
@@ -2399,12 +2408,7 @@ var GENERATION_SYSTEM_PROMPT = `${ROLE_AND_CONTEXT}
|
|
|
2399
2408
|
|
|
2400
2409
|
${CONFIG_FILE_TYPES}
|
|
2401
2410
|
|
|
2402
|
-
|
|
2403
|
-
1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
|
|
2404
|
-
2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
|
|
2405
|
-
3. Duplicate or overlapping skills \u2014 can any be merged or removed?
|
|
2406
|
-
4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
|
|
2407
|
-
5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?
|
|
2411
|
+
${AUDIT_CHECKLIST}
|
|
2408
2412
|
|
|
2409
2413
|
${EXCLUSIONS}
|
|
2410
2414
|
|
|
@@ -2449,6 +2453,8 @@ var CORE_GENERATION_PROMPT = `${ROLE_AND_CONTEXT}
|
|
|
2449
2453
|
|
|
2450
2454
|
${CONFIG_FILE_TYPES}
|
|
2451
2455
|
|
|
2456
|
+
${AUDIT_CHECKLIST}
|
|
2457
|
+
|
|
2452
2458
|
${EXCLUSIONS}
|
|
2453
2459
|
|
|
2454
2460
|
${OUTPUT_FORMAT}
|
|
@@ -2468,10 +2474,10 @@ CoreSetup schema:
|
|
|
2468
2474
|
},
|
|
2469
2475
|
"codex": {
|
|
2470
2476
|
"agentsMd": "string (markdown content for AGENTS.md)",
|
|
2471
|
-
"skillTopics": [{ "name": "string (kebab-case)", "description": "string" }]
|
|
2477
|
+
"skillTopics": [{ "name": "string (kebab-case)", "description": "string (what this skill does and WHEN to use it \u2014 include trigger phrases)" }]
|
|
2472
2478
|
},
|
|
2473
2479
|
"cursor": {
|
|
2474
|
-
"skillTopics": [{ "name": "string (kebab-case)", "description": "string" }],
|
|
2480
|
+
"skillTopics": [{ "name": "string (kebab-case)", "description": "string (what this skill does and WHEN to use it \u2014 include trigger phrases)" }],
|
|
2475
2481
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
2476
2482
|
},
|
|
2477
2483
|
"copilot": {
|
|
@@ -2523,6 +2529,7 @@ Rules:
|
|
|
2523
2529
|
- Reference actual commands, paths, and packages from the project context provided.
|
|
2524
2530
|
- Do NOT include YAML frontmatter \u2014 it will be generated separately.
|
|
2525
2531
|
- Be specific to THIS project \u2014 avoid generic advice. The skill should produce code that looks identical to what's already in the codebase.
|
|
2532
|
+
- If the project context does not contain enough code examples for this skill topic, generate the skill based on the detected frameworks and conventions rather than inventing patterns. Prefer fewer, grounded instructions over many speculative ones.
|
|
2526
2533
|
|
|
2527
2534
|
Description field formula: [What it does] + [When to use it with trigger phrases] + [Key capabilities]. Include negative triggers ("Do NOT use for X") to prevent over-triggering.
|
|
2528
2535
|
|
|
@@ -2569,32 +2576,40 @@ Rules:
|
|
|
2569
2576
|
- Update the "fileDescriptions" to reflect any changes you make.
|
|
2570
2577
|
|
|
2571
2578
|
Quality constraints \u2014 your changes are scored, so do not break these:
|
|
2572
|
-
- CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget.
|
|
2579
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If adding content, remove less important lines to stay within budget. Do not refuse the user's request \u2014 make the change and trim elsewhere.
|
|
2573
2580
|
- Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
|
|
2574
2581
|
- Do NOT add directory tree listings in code blocks.
|
|
2582
|
+
- Do NOT remove existing code blocks \u2014 they contribute to the executable content score.
|
|
2575
2583
|
- Use backticks for every file path, command, and identifier.
|
|
2576
2584
|
- Keep skill content under 150 lines, focused on actionable instructions.
|
|
2577
2585
|
- Only reference file paths that actually exist in the project.`;
|
|
2578
|
-
var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project documentation. Your job is to
|
|
2586
|
+
var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project documentation. Your job is to apply minimal, surgical updates to existing documentation files based on code changes (git diffs).
|
|
2579
2587
|
|
|
2580
2588
|
You will receive:
|
|
2581
2589
|
1. Git diffs showing what code changed
|
|
2582
|
-
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules)
|
|
2583
|
-
3. Project context (languages, frameworks)
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
-
|
|
2587
|
-
-
|
|
2588
|
-
-
|
|
2589
|
-
-
|
|
2590
|
+
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules, copilot instructions)
|
|
2591
|
+
3. Project context (languages, frameworks, file tree)
|
|
2592
|
+
|
|
2593
|
+
CONSERVATIVE UPDATE means:
|
|
2594
|
+
- Touch ONLY the specific lines/sections affected by the diff
|
|
2595
|
+
- If a command was renamed in the diff, update that command in the docs \u2014 don't rewrite the surrounding section
|
|
2596
|
+
- If a file was added/removed/renamed, update the architecture section \u2014 don't restructure it
|
|
2597
|
+
- If nothing in the diff affects a doc file, return null for it
|
|
2598
|
+
- NEVER add new sections, new prose, or new explanations that weren't in the original
|
|
2599
|
+
- NEVER remove code blocks, backtick references, or architecture paths unless the diff deleted them
|
|
2600
|
+
- NEVER replace specific paths/commands with generic prose
|
|
2601
|
+
|
|
2602
|
+
Quality constraints (the output is scored deterministically):
|
|
2603
|
+
- CLAUDE.md / AGENTS.md: MUST stay under 150 lines. If the diff adds content, trim the least important lines elsewhere.
|
|
2604
|
+
- Keep 3+ code blocks with executable commands \u2014 do not remove code blocks
|
|
2605
|
+
- Every file path, command, and identifier must be in backticks
|
|
2606
|
+
- ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
|
|
2607
|
+
- Preserve the existing structure (headings, bullet style, formatting)
|
|
2608
|
+
|
|
2609
|
+
Managed content:
|
|
2590
2610
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
2591
|
-
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2611
|
+
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2592
2612
|
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
2593
|
-
- If a doc doesn't need updating, return null for it
|
|
2594
|
-
- For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them. Keep under 150 lines.
|
|
2595
|
-
- For README.md: update setup instructions, API docs, or feature descriptions if affected
|
|
2596
|
-
- Only reference file paths that exist in the project
|
|
2597
|
-
- Use backticks for all file paths, commands, and identifiers
|
|
2598
2613
|
|
|
2599
2614
|
Return a JSON object with this exact shape:
|
|
2600
2615
|
{
|
|
@@ -2618,15 +2633,8 @@ You receive a chronological sequence of events from a Claude Code session. Most
|
|
|
2618
2633
|
|
|
2619
2634
|
Your job is to find OPERATIONAL patterns \u2014 things that went wrong and how they were fixed, commands that required specific flags or configuration, APIs that needed a particular approach to work. Focus on the WORKFLOW, not the code logic.
|
|
2620
2635
|
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
1. **Failure \u2192 Recovery sequences**: A tool call failed, then a different approach succeeded. Document what works and what doesn't. Example: an API call failed with one config but succeeded with different headers or parameters.
|
|
2624
|
-
2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
|
|
2625
|
-
3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
|
|
2626
|
-
4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
|
|
2627
|
-
5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
|
|
2628
|
-
6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
|
|
2629
|
-
7. **User corrections**: The user explicitly told the AI what's wrong, what to use instead, or what to avoid. Look for phrases like "no, use X instead of Y", "don't touch/edit/modify X", "that's wrong, you need to...", "always/never do X in this project", "stop, that file is...". These are the HIGHEST VALUE signals \u2014 they represent direct human feedback about project-specific requirements. If a user correction contradicts a pattern you'd otherwise extract, the correction wins.
|
|
2636
|
+
CRITICAL FILTER \u2014 apply this to every potential learning before including it:
|
|
2637
|
+
The litmus test: "Would a different developer, working on a DIFFERENT task in this same repo next week, benefit from knowing this?" If the answer is no \u2014 if it only matters for the exact problem being debugged today \u2014 do NOT include it.
|
|
2630
2638
|
|
|
2631
2639
|
DO NOT extract:
|
|
2632
2640
|
- Descriptions of what the code does or how features work (e.g. "compression removes comments" or "skeleton extraction creates outlines")
|
|
@@ -2637,7 +2645,15 @@ DO NOT extract:
|
|
|
2637
2645
|
- **Session-specific file paths, worktree locations, or branch names** \u2014 these are ephemeral and won't apply to future sessions
|
|
2638
2646
|
- **Implementation details of a feature being built** \u2014 the learning should be about HOW to work in this project, not WHAT was built
|
|
2639
2647
|
|
|
2640
|
-
|
|
2648
|
+
Look for:
|
|
2649
|
+
|
|
2650
|
+
1. **Failure \u2192 Recovery sequences**: A tool call failed, then a different approach succeeded. Document what works and what doesn't. Example: an API call failed with one config but succeeded with different headers or parameters.
|
|
2651
|
+
2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
|
|
2652
|
+
3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
|
|
2653
|
+
4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
|
|
2654
|
+
5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
|
|
2655
|
+
6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
|
|
2656
|
+
7. **User corrections**: The user explicitly told the AI what's wrong, what to use instead, or what to avoid. Look for phrases like "no, use X instead of Y", "don't touch/edit/modify X", "that's wrong, you need to...", "always/never do X in this project", "stop, that file is...". These are the HIGHEST VALUE signals \u2014 they represent direct human feedback about project-specific requirements. If a user correction contradicts a pattern you'd otherwise extract, the correction wins.
|
|
2641
2657
|
|
|
2642
2658
|
From these observations, produce:
|
|
2643
2659
|
|
|
@@ -2716,6 +2732,10 @@ Be thorough \u2014 reason from:
|
|
|
2716
2732
|
- File extensions and their frequency distribution
|
|
2717
2733
|
- Directory structure and naming conventions
|
|
2718
2734
|
- Configuration files (e.g. next.config.js implies Next.js, .tf files imply Terraform + cloud providers)
|
|
2735
|
+
- Package manager lockfiles (pnpm-lock.yaml \u2192 pnpm, yarn.lock \u2192 yarn, bun.lockb \u2192 bun, package-lock.json \u2192 npm)
|
|
2736
|
+
- Database/ORM files (schema.prisma \u2192 Prisma, drizzle.config.ts \u2192 Drizzle, knexfile \u2192 Knex, .sql files)
|
|
2737
|
+
- Test framework configs (vitest.config.ts \u2192 Vitest, jest.config.js \u2192 Jest, .mocharc \u2192 Mocha, cypress.config \u2192 Cypress)
|
|
2738
|
+
- Monorepo tools (nx.json \u2192 Nx, turbo.json \u2192 Turborepo, lerna.json \u2192 Lerna)
|
|
2719
2739
|
- Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
|
|
2720
2740
|
- CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
|
|
2721
2741
|
|
|
@@ -3616,15 +3636,15 @@ init_config();
|
|
|
3616
3636
|
// src/utils/dependencies.ts
|
|
3617
3637
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3618
3638
|
import { join as join2 } from "path";
|
|
3619
|
-
function readFileOrNull2(
|
|
3639
|
+
function readFileOrNull2(path40) {
|
|
3620
3640
|
try {
|
|
3621
|
-
return readFileSync2(
|
|
3641
|
+
return readFileSync2(path40, "utf-8");
|
|
3622
3642
|
} catch {
|
|
3623
3643
|
return null;
|
|
3624
3644
|
}
|
|
3625
3645
|
}
|
|
3626
|
-
function readJsonOrNull(
|
|
3627
|
-
const content = readFileOrNull2(
|
|
3646
|
+
function readJsonOrNull(path40) {
|
|
3647
|
+
const content = readFileOrNull2(path40);
|
|
3628
3648
|
if (!content) return null;
|
|
3629
3649
|
try {
|
|
3630
3650
|
return JSON.parse(content);
|
|
@@ -6181,7 +6201,7 @@ var AGENT_DISPLAY_NAMES = {
|
|
|
6181
6201
|
codex: "Codex"
|
|
6182
6202
|
};
|
|
6183
6203
|
var CATEGORY_LABELS = {
|
|
6184
|
-
existence: { icon: "\u{1F4C1}", label: "FILES &
|
|
6204
|
+
existence: { icon: "\u{1F4C1}", label: "FILES & CONFIG" },
|
|
6185
6205
|
quality: { icon: "\u26A1", label: "QUALITY" },
|
|
6186
6206
|
grounding: { icon: "\u{1F3AF}", label: "GROUNDING" },
|
|
6187
6207
|
accuracy: { icon: "\u{1F50D}", label: "ACCURACY" },
|
|
@@ -7440,11 +7460,11 @@ function countIssuePoints(issues) {
|
|
|
7440
7460
|
}
|
|
7441
7461
|
async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
7442
7462
|
const existsCache = /* @__PURE__ */ new Map();
|
|
7443
|
-
const cachedExists = (
|
|
7444
|
-
const cached = existsCache.get(
|
|
7463
|
+
const cachedExists = (path40) => {
|
|
7464
|
+
const cached = existsCache.get(path40);
|
|
7445
7465
|
if (cached !== void 0) return cached;
|
|
7446
|
-
const result = existsSync9(
|
|
7447
|
-
existsCache.set(
|
|
7466
|
+
const result = existsSync9(path40);
|
|
7467
|
+
existsCache.set(path40, result);
|
|
7448
7468
|
return result;
|
|
7449
7469
|
};
|
|
7450
7470
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -7459,7 +7479,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
7459
7479
|
bestLostPoints = lostPoints;
|
|
7460
7480
|
}
|
|
7461
7481
|
if (lostPoints === 0) {
|
|
7462
|
-
if (callbacks?.onStatus) callbacks.onStatus("
|
|
7482
|
+
if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
|
|
7463
7483
|
return bestSetup;
|
|
7464
7484
|
}
|
|
7465
7485
|
const pointIssues = issues.filter((i) => i.pointsLost > 0);
|
|
@@ -7469,7 +7489,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
7469
7489
|
}
|
|
7470
7490
|
const refined = await applyTargetedFixes(currentSetup, issues);
|
|
7471
7491
|
if (!refined) {
|
|
7472
|
-
if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current
|
|
7492
|
+
if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current config");
|
|
7473
7493
|
return bestSetup;
|
|
7474
7494
|
}
|
|
7475
7495
|
sessionHistory.push({
|
|
@@ -7562,7 +7582,7 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
|
|
|
7562
7582
|
}
|
|
7563
7583
|
}
|
|
7564
7584
|
async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
7565
|
-
const spinner = ora2("Validating
|
|
7585
|
+
const spinner = ora2("Validating config against scoring criteria...").start();
|
|
7566
7586
|
try {
|
|
7567
7587
|
const refined = await scoreAndRefine(setup, dir, sessionHistory, {
|
|
7568
7588
|
onStatus: (msg) => {
|
|
@@ -7570,9 +7590,9 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7570
7590
|
}
|
|
7571
7591
|
});
|
|
7572
7592
|
if (refined !== setup) {
|
|
7573
|
-
spinner.succeed("
|
|
7593
|
+
spinner.succeed("Config refined based on scoring feedback");
|
|
7574
7594
|
} else {
|
|
7575
|
-
spinner.succeed("
|
|
7595
|
+
spinner.succeed("Config passes scoring validation");
|
|
7576
7596
|
}
|
|
7577
7597
|
return refined;
|
|
7578
7598
|
} catch (err) {
|
|
@@ -8127,7 +8147,7 @@ var REFINE_MESSAGES = [
|
|
|
8127
8147
|
"Rebalancing permissions and tool settings...",
|
|
8128
8148
|
"Refining skills and workflows...",
|
|
8129
8149
|
"Updating rules to match your preferences...",
|
|
8130
|
-
"Finalizing the revised
|
|
8150
|
+
"Finalizing the revised config..."
|
|
8131
8151
|
];
|
|
8132
8152
|
var SpinnerMessages = class {
|
|
8133
8153
|
spinner;
|
|
@@ -8297,10 +8317,10 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8297
8317
|
if (!isValid) {
|
|
8298
8318
|
console.log(chalk11.dim(" This doesn't look like a config change request."));
|
|
8299
8319
|
console.log(chalk11.dim(" Describe what to add, remove, or modify in your configs."));
|
|
8300
|
-
console.log(chalk11.dim(' Type "done" to accept the current
|
|
8320
|
+
console.log(chalk11.dim(' Type "done" to accept the current config.\n'));
|
|
8301
8321
|
continue;
|
|
8302
8322
|
}
|
|
8303
|
-
const refineSpinner = ora3("Refining
|
|
8323
|
+
const refineSpinner = ora3("Refining config...").start();
|
|
8304
8324
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
8305
8325
|
refineMessages.start();
|
|
8306
8326
|
const refined = await refineSetup(setup, message, sessionHistory);
|
|
@@ -8312,12 +8332,12 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8312
8332
|
role: "assistant",
|
|
8313
8333
|
content: summarizeSetup2("Applied changes", refined)
|
|
8314
8334
|
});
|
|
8315
|
-
refineSpinner.succeed("
|
|
8335
|
+
refineSpinner.succeed("Config updated");
|
|
8316
8336
|
printSummary(refined);
|
|
8317
8337
|
console.log(chalk11.dim('Type "done" to accept, or describe more changes.'));
|
|
8318
8338
|
} else {
|
|
8319
8339
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
8320
|
-
console.log(chalk11.dim('Try rephrasing your request, or type "done" to keep the current
|
|
8340
|
+
console.log(chalk11.dim('Try rephrasing your request, or type "done" to keep the current config.'));
|
|
8321
8341
|
}
|
|
8322
8342
|
}
|
|
8323
8343
|
}
|
|
@@ -8366,7 +8386,7 @@ function printSetupSummary(setup) {
|
|
|
8366
8386
|
const fileDescriptions = setup.fileDescriptions;
|
|
8367
8387
|
const deletions = setup.deletions;
|
|
8368
8388
|
console.log("");
|
|
8369
|
-
console.log(chalk12.bold(" Your tailored
|
|
8389
|
+
console.log(chalk12.bold(" Your tailored config:\n"));
|
|
8370
8390
|
const getDescription = (filePath) => {
|
|
8371
8391
|
return fileDescriptions?.[filePath];
|
|
8372
8392
|
};
|
|
@@ -8595,6 +8615,7 @@ Only dismiss checks that truly don't apply. Examples:
|
|
|
8595
8615
|
- "Build/test/lint commands" for a GitOps/Helm/Terraform/config repo with no build system
|
|
8596
8616
|
- "Build/test/lint commands" for a repo with only YAML, HCL, or config files and no package.json/Makefile
|
|
8597
8617
|
- "Dependency coverage" for a repo with no package manager
|
|
8618
|
+
- "Skills configured" for a documentation-only or data-science notebook repo with no repeating code patterns
|
|
8598
8619
|
|
|
8599
8620
|
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
8600
8621
|
|
|
@@ -8607,7 +8628,7 @@ Top files: ${topFiles}
|
|
|
8607
8628
|
|
|
8608
8629
|
Failing checks:
|
|
8609
8630
|
${JSON.stringify(checkList, null, 2)}`,
|
|
8610
|
-
maxTokens:
|
|
8631
|
+
maxTokens: 500,
|
|
8611
8632
|
...fastModel ? { model: fastModel } : {}
|
|
8612
8633
|
});
|
|
8613
8634
|
if (!Array.isArray(result.dismissed)) return [];
|
|
@@ -8617,6 +8638,68 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
8617
8638
|
}
|
|
8618
8639
|
}
|
|
8619
8640
|
|
|
8641
|
+
// src/scoring/history.ts
|
|
8642
|
+
import fs31 from "fs";
|
|
8643
|
+
import path24 from "path";
|
|
8644
|
+
var HISTORY_FILE = "score-history.jsonl";
|
|
8645
|
+
var MAX_ENTRIES = 500;
|
|
8646
|
+
var TRIM_THRESHOLD = MAX_ENTRIES + 50;
|
|
8647
|
+
function historyFilePath() {
|
|
8648
|
+
return path24.join(CALIBER_DIR, HISTORY_FILE);
|
|
8649
|
+
}
|
|
8650
|
+
function recordScore(result, trigger) {
|
|
8651
|
+
const entry = {
|
|
8652
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8653
|
+
score: result.score,
|
|
8654
|
+
grade: result.grade,
|
|
8655
|
+
targetAgent: [...result.targetAgent],
|
|
8656
|
+
trigger
|
|
8657
|
+
};
|
|
8658
|
+
try {
|
|
8659
|
+
fs31.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
8660
|
+
const filePath = historyFilePath();
|
|
8661
|
+
fs31.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
8662
|
+
const stat = fs31.statSync(filePath);
|
|
8663
|
+
if (stat.size > TRIM_THRESHOLD * 120) {
|
|
8664
|
+
const lines = fs31.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
8665
|
+
if (lines.length > MAX_ENTRIES) {
|
|
8666
|
+
fs31.writeFileSync(filePath, lines.slice(-MAX_ENTRIES).join("\n") + "\n");
|
|
8667
|
+
}
|
|
8668
|
+
}
|
|
8669
|
+
} catch {
|
|
8670
|
+
}
|
|
8671
|
+
}
|
|
8672
|
+
function readScoreHistory() {
|
|
8673
|
+
const filePath = historyFilePath();
|
|
8674
|
+
try {
|
|
8675
|
+
const content = fs31.readFileSync(filePath, "utf-8");
|
|
8676
|
+
const entries = [];
|
|
8677
|
+
for (const line of content.split("\n")) {
|
|
8678
|
+
if (!line) continue;
|
|
8679
|
+
try {
|
|
8680
|
+
entries.push(JSON.parse(line));
|
|
8681
|
+
} catch {
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
return entries;
|
|
8685
|
+
} catch {
|
|
8686
|
+
return [];
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
function getScoreTrend(entries) {
|
|
8690
|
+
if (entries.length < 2) return null;
|
|
8691
|
+
const first = entries[0];
|
|
8692
|
+
const last = entries[entries.length - 1];
|
|
8693
|
+
const delta = last.score - first.score;
|
|
8694
|
+
return {
|
|
8695
|
+
direction: delta > 0 ? "up" : delta < 0 ? "down" : "stable",
|
|
8696
|
+
delta,
|
|
8697
|
+
entries: entries.length,
|
|
8698
|
+
firstScore: first.score,
|
|
8699
|
+
lastScore: last.score
|
|
8700
|
+
};
|
|
8701
|
+
}
|
|
8702
|
+
|
|
8620
8703
|
// src/commands/init.ts
|
|
8621
8704
|
function log(verbose, ...args) {
|
|
8622
8705
|
if (verbose) console.log(chalk14.dim(` [verbose] ${args.map(String).join(" ")}`));
|
|
@@ -8637,7 +8720,7 @@ async function initCommand(options) {
|
|
|
8637
8720
|
console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
|
|
8638
8721
|
console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
8639
8722
|
console.log(title.bold(" How it works:\n"));
|
|
8640
|
-
console.log(chalk14.dim(" 1.
|
|
8723
|
+
console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
|
|
8641
8724
|
console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
|
|
8642
8725
|
console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
|
|
8643
8726
|
console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
|
|
@@ -8650,7 +8733,7 @@ async function initCommand(options) {
|
|
|
8650
8733
|
console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
|
|
8651
8734
|
}
|
|
8652
8735
|
const report = options.debugReport ? new DebugReport() : null;
|
|
8653
|
-
console.log(title.bold(" Step 1/4 \u2014
|
|
8736
|
+
console.log(title.bold(" Step 1/4 \u2014 Connect\n"));
|
|
8654
8737
|
let config = loadConfig();
|
|
8655
8738
|
if (!config) {
|
|
8656
8739
|
console.log(chalk14.dim(" No LLM provider configured yet.\n"));
|
|
@@ -8659,7 +8742,7 @@ async function initCommand(options) {
|
|
|
8659
8742
|
});
|
|
8660
8743
|
config = loadConfig();
|
|
8661
8744
|
if (!config) {
|
|
8662
|
-
console.log(chalk14.red("
|
|
8745
|
+
console.log(chalk14.red(" Configuration cancelled or failed.\n"));
|
|
8663
8746
|
throw new Error("__exit__");
|
|
8664
8747
|
}
|
|
8665
8748
|
console.log(chalk14.green(" \u2713 Provider saved\n"));
|
|
@@ -8670,7 +8753,7 @@ async function initCommand(options) {
|
|
|
8670
8753
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
8671
8754
|
console.log(chalk14.dim(modelLine + "\n"));
|
|
8672
8755
|
if (report) {
|
|
8673
|
-
report.markStep("Provider
|
|
8756
|
+
report.markStep("Provider connection");
|
|
8674
8757
|
report.addSection("LLM Provider", `- **Provider**: ${config.provider}
|
|
8675
8758
|
- **Model**: ${displayModel}
|
|
8676
8759
|
- **Fast model**: ${fastModel || "none"}`);
|
|
@@ -8691,7 +8774,7 @@ async function initCommand(options) {
|
|
|
8691
8774
|
`));
|
|
8692
8775
|
trackInitAgentSelected(targetAgent, agentAutoDetected);
|
|
8693
8776
|
let baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
8694
|
-
console.log(chalk14.dim("\n Current
|
|
8777
|
+
console.log(chalk14.dim("\n Current config score:"));
|
|
8695
8778
|
displayScoreSummary(baselineScore);
|
|
8696
8779
|
if (options.verbose) {
|
|
8697
8780
|
for (const c of baselineScore.checks) {
|
|
@@ -8720,7 +8803,7 @@ async function initCommand(options) {
|
|
|
8720
8803
|
const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
|
|
8721
8804
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
8722
8805
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
8723
|
-
console.log(chalk14.bold.green(" Your
|
|
8806
|
+
console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
|
|
8724
8807
|
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber init --force") + chalk14.dim(" to regenerate anyway.\n"));
|
|
8725
8808
|
if (!options.force) return;
|
|
8726
8809
|
}
|
|
@@ -8757,7 +8840,7 @@ async function initCommand(options) {
|
|
|
8757
8840
|
const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
|
|
8758
8841
|
const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
|
|
8759
8842
|
const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
|
|
8760
|
-
const TASK_SCORE_REFINE = display.add("Validating & refining
|
|
8843
|
+
const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
|
|
8761
8844
|
display.start();
|
|
8762
8845
|
display.enableWaitingContent();
|
|
8763
8846
|
try {
|
|
@@ -8905,7 +8988,7 @@ async function initCommand(options) {
|
|
|
8905
8988
|
display.update(TASK_SCORE_REFINE, "done", "Skipped");
|
|
8906
8989
|
}
|
|
8907
8990
|
} else {
|
|
8908
|
-
display.update(TASK_SCORE_REFINE, "failed", "No
|
|
8991
|
+
display.update(TASK_SCORE_REFINE, "failed", "No config to validate");
|
|
8909
8992
|
}
|
|
8910
8993
|
} catch (err) {
|
|
8911
8994
|
display.stop();
|
|
@@ -8926,7 +9009,7 @@ async function initCommand(options) {
|
|
|
8926
9009
|
Done in ${timeStr}
|
|
8927
9010
|
`));
|
|
8928
9011
|
if (!generatedSetup) {
|
|
8929
|
-
console.log(chalk14.red(" Failed to generate
|
|
9012
|
+
console.log(chalk14.red(" Failed to generate config."));
|
|
8930
9013
|
writeErrorLog(config, rawOutput, void 0, genStopReason);
|
|
8931
9014
|
if (rawOutput) {
|
|
8932
9015
|
console.log(chalk14.dim("\nRaw LLM output (JSON parse failed):"));
|
|
@@ -8936,7 +9019,7 @@ async function initCommand(options) {
|
|
|
8936
9019
|
}
|
|
8937
9020
|
if (report) {
|
|
8938
9021
|
if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
|
|
8939
|
-
report.addJson("Generation: Parsed
|
|
9022
|
+
report.addJson("Generation: Parsed Config", generatedSetup);
|
|
8940
9023
|
}
|
|
8941
9024
|
log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
|
|
8942
9025
|
console.log(title.bold(" Step 3/4 \u2014 Review\n"));
|
|
@@ -8999,7 +9082,7 @@ async function initCommand(options) {
|
|
|
8999
9082
|
}
|
|
9000
9083
|
cleanupStaging();
|
|
9001
9084
|
if (action === "decline") {
|
|
9002
|
-
console.log(chalk14.dim("
|
|
9085
|
+
console.log(chalk14.dim("Declined. No files were modified."));
|
|
9003
9086
|
return;
|
|
9004
9087
|
}
|
|
9005
9088
|
console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
|
|
@@ -9011,7 +9094,7 @@ async function initCommand(options) {
|
|
|
9011
9094
|
const { default: ora9 } = await import("ora");
|
|
9012
9095
|
const writeSpinner = ora9("Writing config files...").start();
|
|
9013
9096
|
try {
|
|
9014
|
-
if (targetAgent.includes("codex") && !
|
|
9097
|
+
if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
9015
9098
|
const claude = generatedSetup.claude;
|
|
9016
9099
|
const cursor = generatedSetup.cursor;
|
|
9017
9100
|
const agentRefs = [];
|
|
@@ -9083,6 +9166,7 @@ ${agentRefs.join(" ")}
|
|
|
9083
9166
|
|-------|--------|--------|-----|
|
|
9084
9167
|
` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
9085
9168
|
}
|
|
9169
|
+
recordScore(afterScore, "init");
|
|
9086
9170
|
displayScoreDelta(baselineScore, afterScore);
|
|
9087
9171
|
if (options.verbose) {
|
|
9088
9172
|
log(options.verbose, `Final score: ${afterScore.score}/100`);
|
|
@@ -9130,12 +9214,12 @@ ${agentRefs.join(" ")}
|
|
|
9130
9214
|
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9131
9215
|
}
|
|
9132
9216
|
}
|
|
9133
|
-
console.log(chalk14.bold.green("\n
|
|
9217
|
+
console.log(chalk14.bold.green("\n Configuration complete!"));
|
|
9134
9218
|
console.log(chalk14.dim(" Your AI agents now understand your project's architecture, build commands,"));
|
|
9135
9219
|
console.log(chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n"));
|
|
9136
9220
|
const done = chalk14.green("\u2713");
|
|
9137
9221
|
const skip = chalk14.dim("\u2013");
|
|
9138
|
-
console.log(chalk14.bold(" What was
|
|
9222
|
+
console.log(chalk14.bold(" What was configured:\n"));
|
|
9139
9223
|
console.log(` ${done} Config generated ${title("caliber score")} ${chalk14.dim("for full breakdown")}`);
|
|
9140
9224
|
console.log(` ${done} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
|
|
9141
9225
|
if (hasLearnableAgent) {
|
|
@@ -9160,9 +9244,9 @@ ${agentRefs.join(" ")}
|
|
|
9160
9244
|
}
|
|
9161
9245
|
if (report) {
|
|
9162
9246
|
report.markStep("Finished");
|
|
9163
|
-
const reportPath =
|
|
9247
|
+
const reportPath = path25.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9164
9248
|
report.write(reportPath);
|
|
9165
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
9249
|
+
console.log(chalk14.dim(` Debug report written to ${path25.relative(process.cwd(), reportPath)}
|
|
9166
9250
|
`));
|
|
9167
9251
|
}
|
|
9168
9252
|
}
|
|
@@ -9171,7 +9255,7 @@ ${agentRefs.join(" ")}
|
|
|
9171
9255
|
import chalk15 from "chalk";
|
|
9172
9256
|
import ora4 from "ora";
|
|
9173
9257
|
function undoCommand() {
|
|
9174
|
-
const spinner = ora4("Reverting
|
|
9258
|
+
const spinner = ora4("Reverting config changes...").start();
|
|
9175
9259
|
try {
|
|
9176
9260
|
const { restored, removed } = undoSetup();
|
|
9177
9261
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -9179,7 +9263,7 @@ function undoCommand() {
|
|
|
9179
9263
|
return;
|
|
9180
9264
|
}
|
|
9181
9265
|
trackUndoExecuted();
|
|
9182
|
-
spinner.succeed("
|
|
9266
|
+
spinner.succeed("Config reverted successfully.\n");
|
|
9183
9267
|
if (restored.length > 0) {
|
|
9184
9268
|
console.log(chalk15.cyan(" Restored from backup:"));
|
|
9185
9269
|
for (const file of restored) {
|
|
@@ -9201,7 +9285,7 @@ function undoCommand() {
|
|
|
9201
9285
|
|
|
9202
9286
|
// src/commands/status.ts
|
|
9203
9287
|
import chalk16 from "chalk";
|
|
9204
|
-
import
|
|
9288
|
+
import fs33 from "fs";
|
|
9205
9289
|
init_config();
|
|
9206
9290
|
async function statusCommand(options) {
|
|
9207
9291
|
const config = loadConfig();
|
|
@@ -9222,13 +9306,13 @@ async function statusCommand(options) {
|
|
|
9222
9306
|
console.log(` LLM: ${chalk16.yellow("Not configured")} \u2014 run ${chalk16.hex("#83D1EB")("caliber config")}`);
|
|
9223
9307
|
}
|
|
9224
9308
|
if (!manifest) {
|
|
9225
|
-
console.log(`
|
|
9309
|
+
console.log(` Config: ${chalk16.dim("No config applied")}`);
|
|
9226
9310
|
console.log(chalk16.dim("\n Run ") + chalk16.hex("#83D1EB")("caliber init") + chalk16.dim(" to get started.\n"));
|
|
9227
9311
|
return;
|
|
9228
9312
|
}
|
|
9229
9313
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9230
9314
|
for (const entry of manifest.entries) {
|
|
9231
|
-
const exists =
|
|
9315
|
+
const exists = fs33.existsSync(entry.path);
|
|
9232
9316
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9233
9317
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9234
9318
|
}
|
|
@@ -9249,7 +9333,7 @@ async function regenerateCommand(options) {
|
|
|
9249
9333
|
}
|
|
9250
9334
|
const manifest = readManifest();
|
|
9251
9335
|
if (!manifest) {
|
|
9252
|
-
console.log(chalk17.yellow("No existing
|
|
9336
|
+
console.log(chalk17.yellow("No existing config found. Run ") + chalk17.hex("#83D1EB")("caliber init") + chalk17.yellow(" first."));
|
|
9253
9337
|
throw new Error("__exit__");
|
|
9254
9338
|
}
|
|
9255
9339
|
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
@@ -9260,10 +9344,10 @@ async function regenerateCommand(options) {
|
|
|
9260
9344
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
9261
9345
|
displayScoreSummary(baselineScore);
|
|
9262
9346
|
if (baselineScore.score === 100) {
|
|
9263
|
-
console.log(chalk17.green(" Your
|
|
9347
|
+
console.log(chalk17.green(" Your config is already at 100/100 \u2014 nothing to regenerate.\n"));
|
|
9264
9348
|
return;
|
|
9265
9349
|
}
|
|
9266
|
-
const genSpinner = ora5("Regenerating
|
|
9350
|
+
const genSpinner = ora5("Regenerating config...").start();
|
|
9267
9351
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
9268
9352
|
genMessages.start();
|
|
9269
9353
|
let generatedSetup = null;
|
|
@@ -9294,10 +9378,10 @@ async function regenerateCommand(options) {
|
|
|
9294
9378
|
}
|
|
9295
9379
|
genMessages.stop();
|
|
9296
9380
|
if (!generatedSetup) {
|
|
9297
|
-
genSpinner.fail("Failed to regenerate
|
|
9381
|
+
genSpinner.fail("Failed to regenerate config.");
|
|
9298
9382
|
throw new Error("__exit__");
|
|
9299
9383
|
}
|
|
9300
|
-
genSpinner.succeed("
|
|
9384
|
+
genSpinner.succeed("Config regenerated");
|
|
9301
9385
|
generatedSetup = await runScoreRefineWithSpinner(generatedSetup, process.cwd(), []);
|
|
9302
9386
|
const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9303
9387
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
@@ -9324,7 +9408,7 @@ async function regenerateCommand(options) {
|
|
|
9324
9408
|
await openReview(reviewMethod, staged.stagedFiles);
|
|
9325
9409
|
}
|
|
9326
9410
|
const action = await select6({
|
|
9327
|
-
message: "Apply regenerated
|
|
9411
|
+
message: "Apply regenerated config?",
|
|
9328
9412
|
choices: [
|
|
9329
9413
|
{ name: "Accept and apply", value: "accept" },
|
|
9330
9414
|
{ name: "Decline", value: "decline" }
|
|
@@ -9383,21 +9467,21 @@ async function regenerateCommand(options) {
|
|
|
9383
9467
|
}
|
|
9384
9468
|
|
|
9385
9469
|
// src/commands/score.ts
|
|
9386
|
-
import
|
|
9470
|
+
import fs34 from "fs";
|
|
9387
9471
|
import os7 from "os";
|
|
9388
|
-
import
|
|
9472
|
+
import path26 from "path";
|
|
9389
9473
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9390
9474
|
import chalk18 from "chalk";
|
|
9391
9475
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
|
|
9392
9476
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9393
9477
|
function scoreBaseRef(ref, target) {
|
|
9394
9478
|
if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
|
|
9395
|
-
const tmpDir =
|
|
9479
|
+
const tmpDir = fs34.mkdtempSync(path26.join(os7.tmpdir(), "caliber-compare-"));
|
|
9396
9480
|
try {
|
|
9397
9481
|
for (const file of CONFIG_FILES) {
|
|
9398
9482
|
try {
|
|
9399
9483
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9400
|
-
|
|
9484
|
+
fs34.writeFileSync(path26.join(tmpDir, file), content);
|
|
9401
9485
|
} catch {
|
|
9402
9486
|
}
|
|
9403
9487
|
}
|
|
@@ -9405,10 +9489,10 @@ function scoreBaseRef(ref, target) {
|
|
|
9405
9489
|
try {
|
|
9406
9490
|
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
|
|
9407
9491
|
for (const file of files) {
|
|
9408
|
-
const filePath =
|
|
9409
|
-
|
|
9492
|
+
const filePath = path26.join(tmpDir, file);
|
|
9493
|
+
fs34.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
9410
9494
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9411
|
-
|
|
9495
|
+
fs34.writeFileSync(filePath, content);
|
|
9412
9496
|
}
|
|
9413
9497
|
} catch {
|
|
9414
9498
|
}
|
|
@@ -9418,7 +9502,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9418
9502
|
} catch {
|
|
9419
9503
|
return null;
|
|
9420
9504
|
} finally {
|
|
9421
|
-
|
|
9505
|
+
fs34.rmSync(tmpDir, { recursive: true, force: true });
|
|
9422
9506
|
}
|
|
9423
9507
|
}
|
|
9424
9508
|
async function scoreCommand(options) {
|
|
@@ -9426,6 +9510,7 @@ async function scoreCommand(options) {
|
|
|
9426
9510
|
const target = options.agent ?? readState()?.targetAgent;
|
|
9427
9511
|
const result = computeLocalScore(dir, target);
|
|
9428
9512
|
trackScoreComputed(result.score, target);
|
|
9513
|
+
recordScore(result, "score");
|
|
9429
9514
|
if (options.compare) {
|
|
9430
9515
|
const baseResult = scoreBaseRef(options.compare, target);
|
|
9431
9516
|
if (!baseResult) {
|
|
@@ -9468,9 +9553,9 @@ async function scoreCommand(options) {
|
|
|
9468
9553
|
const separator = chalk18.gray(" " + "\u2500".repeat(53));
|
|
9469
9554
|
console.log(separator);
|
|
9470
9555
|
if (result.score < 40) {
|
|
9471
|
-
console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to generate a complete, optimized
|
|
9556
|
+
console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to generate a complete, optimized config."));
|
|
9472
9557
|
} else if (result.score < 70) {
|
|
9473
|
-
console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to improve your
|
|
9558
|
+
console.log(chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber init") + chalk18.gray(" to improve your config."));
|
|
9474
9559
|
} else {
|
|
9475
9560
|
console.log(chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber regenerate") + chalk18.gray(" to rebuild from scratch."));
|
|
9476
9561
|
}
|
|
@@ -9478,8 +9563,8 @@ async function scoreCommand(options) {
|
|
|
9478
9563
|
}
|
|
9479
9564
|
|
|
9480
9565
|
// src/commands/refresh.ts
|
|
9481
|
-
import
|
|
9482
|
-
import
|
|
9566
|
+
import fs38 from "fs";
|
|
9567
|
+
import path30 from "path";
|
|
9483
9568
|
import chalk19 from "chalk";
|
|
9484
9569
|
import ora6 from "ora";
|
|
9485
9570
|
|
|
@@ -9557,48 +9642,48 @@ function collectDiff(lastSha) {
|
|
|
9557
9642
|
}
|
|
9558
9643
|
|
|
9559
9644
|
// src/writers/refresh.ts
|
|
9560
|
-
import
|
|
9561
|
-
import
|
|
9645
|
+
import fs35 from "fs";
|
|
9646
|
+
import path27 from "path";
|
|
9562
9647
|
function writeRefreshDocs(docs) {
|
|
9563
9648
|
const written = [];
|
|
9564
9649
|
if (docs.claudeMd) {
|
|
9565
|
-
|
|
9650
|
+
fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
|
|
9566
9651
|
written.push("CLAUDE.md");
|
|
9567
9652
|
}
|
|
9568
9653
|
if (docs.readmeMd) {
|
|
9569
|
-
|
|
9654
|
+
fs35.writeFileSync("README.md", docs.readmeMd);
|
|
9570
9655
|
written.push("README.md");
|
|
9571
9656
|
}
|
|
9572
9657
|
if (docs.cursorrules) {
|
|
9573
|
-
|
|
9658
|
+
fs35.writeFileSync(".cursorrules", docs.cursorrules);
|
|
9574
9659
|
written.push(".cursorrules");
|
|
9575
9660
|
}
|
|
9576
9661
|
if (docs.cursorRules) {
|
|
9577
|
-
const rulesDir =
|
|
9578
|
-
if (!
|
|
9662
|
+
const rulesDir = path27.join(".cursor", "rules");
|
|
9663
|
+
if (!fs35.existsSync(rulesDir)) fs35.mkdirSync(rulesDir, { recursive: true });
|
|
9579
9664
|
for (const rule of docs.cursorRules) {
|
|
9580
|
-
|
|
9665
|
+
fs35.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
|
|
9581
9666
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
9582
9667
|
}
|
|
9583
9668
|
}
|
|
9584
9669
|
if (docs.claudeSkills) {
|
|
9585
|
-
const skillsDir =
|
|
9586
|
-
if (!
|
|
9670
|
+
const skillsDir = path27.join(".claude", "skills");
|
|
9671
|
+
if (!fs35.existsSync(skillsDir)) fs35.mkdirSync(skillsDir, { recursive: true });
|
|
9587
9672
|
for (const skill of docs.claudeSkills) {
|
|
9588
|
-
|
|
9673
|
+
fs35.writeFileSync(path27.join(skillsDir, skill.filename), skill.content);
|
|
9589
9674
|
written.push(`.claude/skills/${skill.filename}`);
|
|
9590
9675
|
}
|
|
9591
9676
|
}
|
|
9592
9677
|
if (docs.copilotInstructions) {
|
|
9593
|
-
|
|
9594
|
-
|
|
9678
|
+
fs35.mkdirSync(".github", { recursive: true });
|
|
9679
|
+
fs35.writeFileSync(path27.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
|
|
9595
9680
|
written.push(".github/copilot-instructions.md");
|
|
9596
9681
|
}
|
|
9597
9682
|
if (docs.copilotInstructionFiles) {
|
|
9598
|
-
const instructionsDir =
|
|
9599
|
-
|
|
9683
|
+
const instructionsDir = path27.join(".github", "instructions");
|
|
9684
|
+
fs35.mkdirSync(instructionsDir, { recursive: true });
|
|
9600
9685
|
for (const file of docs.copilotInstructionFiles) {
|
|
9601
|
-
|
|
9686
|
+
fs35.writeFileSync(path27.join(instructionsDir, file.filename), file.content);
|
|
9602
9687
|
written.push(`.github/instructions/${file.filename}`);
|
|
9603
9688
|
}
|
|
9604
9689
|
}
|
|
@@ -9624,6 +9709,12 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
|
|
|
9624
9709
|
if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
|
|
9625
9710
|
if (projectContext.languages?.length) parts.push(`Languages: ${projectContext.languages.join(", ")}`);
|
|
9626
9711
|
if (projectContext.frameworks?.length) parts.push(`Frameworks: ${projectContext.frameworks.join(", ")}`);
|
|
9712
|
+
if (projectContext.fileTree?.length) {
|
|
9713
|
+
const tree = projectContext.fileTree.slice(0, 200);
|
|
9714
|
+
parts.push(`
|
|
9715
|
+
File tree (${tree.length}/${projectContext.fileTree.length} \u2014 only reference paths from this list):
|
|
9716
|
+
${tree.join("\n")}`);
|
|
9717
|
+
}
|
|
9627
9718
|
parts.push(`
|
|
9628
9719
|
Changed files: ${diff.changedFiles.join(", ")}`);
|
|
9629
9720
|
parts.push(`Summary: ${diff.summary}`);
|
|
@@ -9678,8 +9769,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
9678
9769
|
}
|
|
9679
9770
|
|
|
9680
9771
|
// src/learner/writer.ts
|
|
9681
|
-
import
|
|
9682
|
-
import
|
|
9772
|
+
import fs36 from "fs";
|
|
9773
|
+
import path28 from "path";
|
|
9683
9774
|
|
|
9684
9775
|
// src/learner/utils.ts
|
|
9685
9776
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -9796,20 +9887,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9796
9887
|
}
|
|
9797
9888
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
9798
9889
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
9799
|
-
|
|
9800
|
-
if (mode)
|
|
9890
|
+
fs36.writeFileSync(filePath, header + merged + "\n");
|
|
9891
|
+
if (mode) fs36.chmodSync(filePath, mode);
|
|
9801
9892
|
return { newCount, newItems };
|
|
9802
9893
|
}
|
|
9803
9894
|
function writeLearnedSection(content) {
|
|
9804
9895
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
9805
9896
|
}
|
|
9806
9897
|
function writeLearnedSkill(skill) {
|
|
9807
|
-
const skillDir =
|
|
9808
|
-
if (!
|
|
9809
|
-
const skillPath =
|
|
9810
|
-
if (!skill.isNew &&
|
|
9811
|
-
const existing =
|
|
9812
|
-
|
|
9898
|
+
const skillDir = path28.join(".claude", "skills", skill.name);
|
|
9899
|
+
if (!fs36.existsSync(skillDir)) fs36.mkdirSync(skillDir, { recursive: true });
|
|
9900
|
+
const skillPath = path28.join(skillDir, "SKILL.md");
|
|
9901
|
+
if (!skill.isNew && fs36.existsSync(skillPath)) {
|
|
9902
|
+
const existing = fs36.readFileSync(skillPath, "utf-8");
|
|
9903
|
+
fs36.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
9813
9904
|
} else {
|
|
9814
9905
|
const frontmatter = [
|
|
9815
9906
|
"---",
|
|
@@ -9818,12 +9909,12 @@ function writeLearnedSkill(skill) {
|
|
|
9818
9909
|
"---",
|
|
9819
9910
|
""
|
|
9820
9911
|
].join("\n");
|
|
9821
|
-
|
|
9912
|
+
fs36.writeFileSync(skillPath, frontmatter + skill.content);
|
|
9822
9913
|
}
|
|
9823
9914
|
return skillPath;
|
|
9824
9915
|
}
|
|
9825
9916
|
function writePersonalLearnedSection(content) {
|
|
9826
|
-
if (!
|
|
9917
|
+
if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
|
|
9827
9918
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
9828
9919
|
}
|
|
9829
9920
|
function addLearning(bullet, scope = "project") {
|
|
@@ -9836,38 +9927,38 @@ function addLearning(bullet, scope = "project") {
|
|
|
9836
9927
|
return { file: LEARNINGS_FILE, added: result.newCount > 0 };
|
|
9837
9928
|
}
|
|
9838
9929
|
function readPersonalLearnings() {
|
|
9839
|
-
if (!
|
|
9840
|
-
const content =
|
|
9930
|
+
if (!fs36.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
9931
|
+
const content = fs36.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
9841
9932
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9842
9933
|
return bullets || null;
|
|
9843
9934
|
}
|
|
9844
9935
|
function readLearnedSection() {
|
|
9845
|
-
if (
|
|
9846
|
-
const content2 =
|
|
9936
|
+
if (fs36.existsSync(LEARNINGS_FILE)) {
|
|
9937
|
+
const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
9847
9938
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9848
9939
|
return bullets || null;
|
|
9849
9940
|
}
|
|
9850
9941
|
const claudeMdPath = "CLAUDE.md";
|
|
9851
|
-
if (!
|
|
9852
|
-
const content =
|
|
9942
|
+
if (!fs36.existsSync(claudeMdPath)) return null;
|
|
9943
|
+
const content = fs36.readFileSync(claudeMdPath, "utf-8");
|
|
9853
9944
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9854
9945
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9855
9946
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
9856
9947
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
9857
9948
|
}
|
|
9858
9949
|
function migrateInlineLearnings() {
|
|
9859
|
-
if (
|
|
9950
|
+
if (fs36.existsSync(LEARNINGS_FILE)) return false;
|
|
9860
9951
|
const claudeMdPath = "CLAUDE.md";
|
|
9861
|
-
if (!
|
|
9862
|
-
const content =
|
|
9952
|
+
if (!fs36.existsSync(claudeMdPath)) return false;
|
|
9953
|
+
const content = fs36.readFileSync(claudeMdPath, "utf-8");
|
|
9863
9954
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9864
9955
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9865
9956
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
9866
9957
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
9867
9958
|
if (!section) return false;
|
|
9868
|
-
|
|
9959
|
+
fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
9869
9960
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
9870
|
-
|
|
9961
|
+
fs36.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
9871
9962
|
return true;
|
|
9872
9963
|
}
|
|
9873
9964
|
|
|
@@ -9879,11 +9970,11 @@ function log2(quiet, ...args) {
|
|
|
9879
9970
|
function discoverGitRepos(parentDir) {
|
|
9880
9971
|
const repos = [];
|
|
9881
9972
|
try {
|
|
9882
|
-
const entries =
|
|
9973
|
+
const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
|
|
9883
9974
|
for (const entry of entries) {
|
|
9884
9975
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
9885
|
-
const childPath =
|
|
9886
|
-
if (
|
|
9976
|
+
const childPath = path30.join(parentDir, entry.name);
|
|
9977
|
+
if (fs38.existsSync(path30.join(childPath, ".git"))) {
|
|
9887
9978
|
repos.push(childPath);
|
|
9888
9979
|
}
|
|
9889
9980
|
}
|
|
@@ -9891,11 +9982,19 @@ function discoverGitRepos(parentDir) {
|
|
|
9891
9982
|
}
|
|
9892
9983
|
return repos.sort();
|
|
9893
9984
|
}
|
|
9985
|
+
var REFRESH_COOLDOWN_MS = 3e4;
|
|
9894
9986
|
async function refreshSingleRepo(repoDir, options) {
|
|
9895
9987
|
const quiet = !!options.quiet;
|
|
9896
9988
|
const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
|
|
9897
9989
|
const state = readState();
|
|
9898
9990
|
const lastSha = state?.lastRefreshSha ?? null;
|
|
9991
|
+
if (state?.lastRefreshTimestamp) {
|
|
9992
|
+
const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
|
|
9993
|
+
if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
|
|
9994
|
+
log2(quiet, chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`));
|
|
9995
|
+
return;
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9899
9998
|
const diff = collectDiff(lastSha);
|
|
9900
9999
|
const currentSha = getCurrentHeadSha();
|
|
9901
10000
|
if (!diff.hasChanges) {
|
|
@@ -9912,23 +10011,32 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
9912
10011
|
const projectContext = {
|
|
9913
10012
|
languages: fingerprint.languages,
|
|
9914
10013
|
frameworks: fingerprint.frameworks,
|
|
9915
|
-
packageName: fingerprint.packageName
|
|
10014
|
+
packageName: fingerprint.packageName,
|
|
10015
|
+
fileTree: fingerprint.fileTree
|
|
9916
10016
|
};
|
|
9917
10017
|
const workspaces = getDetectedWorkspaces(repoDir);
|
|
9918
10018
|
const sources2 = resolveAllSources(repoDir, [], workspaces);
|
|
9919
|
-
const
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
learnedSection,
|
|
9930
|
-
|
|
9931
|
-
|
|
10019
|
+
const diffPayload = {
|
|
10020
|
+
committed: diff.committedDiff,
|
|
10021
|
+
staged: diff.stagedDiff,
|
|
10022
|
+
unstaged: diff.unstagedDiff,
|
|
10023
|
+
changedFiles: diff.changedFiles,
|
|
10024
|
+
summary: diff.summary
|
|
10025
|
+
};
|
|
10026
|
+
const sourcesPayload = sources2.length > 0 ? sources2 : void 0;
|
|
10027
|
+
let response;
|
|
10028
|
+
try {
|
|
10029
|
+
response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
|
|
10030
|
+
} catch (firstErr) {
|
|
10031
|
+
const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
|
|
10032
|
+
if (!isTransient) throw firstErr;
|
|
10033
|
+
try {
|
|
10034
|
+
response = await refreshDocs(diffPayload, existingDocs, projectContext, learnedSection, sourcesPayload);
|
|
10035
|
+
} catch {
|
|
10036
|
+
spinner?.fail(`${prefix}Refresh failed after retry`);
|
|
10037
|
+
throw firstErr;
|
|
10038
|
+
}
|
|
10039
|
+
}
|
|
9932
10040
|
if (!response.docsUpdated || response.docsUpdated.length === 0) {
|
|
9933
10041
|
spinner?.succeed(`${prefix}No doc updates needed`);
|
|
9934
10042
|
if (currentSha) {
|
|
@@ -9947,8 +10055,41 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
9947
10055
|
}
|
|
9948
10056
|
return;
|
|
9949
10057
|
}
|
|
10058
|
+
const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
|
|
10059
|
+
const preScore = computeLocalScore(repoDir, targetAgent);
|
|
10060
|
+
const filesToWrite = response.docsUpdated || [];
|
|
10061
|
+
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
10062
|
+
for (const filePath of filesToWrite) {
|
|
10063
|
+
const fullPath = path30.resolve(repoDir, filePath);
|
|
10064
|
+
try {
|
|
10065
|
+
preRefreshContents.set(filePath, fs38.readFileSync(fullPath, "utf-8"));
|
|
10066
|
+
} catch {
|
|
10067
|
+
preRefreshContents.set(filePath, null);
|
|
10068
|
+
}
|
|
10069
|
+
}
|
|
9950
10070
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
9951
10071
|
trackRefreshCompleted(written.length, Date.now());
|
|
10072
|
+
const postScore = computeLocalScore(repoDir, targetAgent);
|
|
10073
|
+
if (postScore.score < preScore.score) {
|
|
10074
|
+
for (const [filePath, content] of preRefreshContents) {
|
|
10075
|
+
const fullPath = path30.resolve(repoDir, filePath);
|
|
10076
|
+
if (content === null) {
|
|
10077
|
+
try {
|
|
10078
|
+
fs38.unlinkSync(fullPath);
|
|
10079
|
+
} catch {
|
|
10080
|
+
}
|
|
10081
|
+
} else {
|
|
10082
|
+
fs38.writeFileSync(fullPath, content);
|
|
10083
|
+
}
|
|
10084
|
+
}
|
|
10085
|
+
spinner?.warn(`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`);
|
|
10086
|
+
log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
|
|
10087
|
+
if (currentSha) {
|
|
10088
|
+
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10089
|
+
}
|
|
10090
|
+
return;
|
|
10091
|
+
}
|
|
10092
|
+
recordScore(postScore, "refresh");
|
|
9952
10093
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
9953
10094
|
for (const file of written) {
|
|
9954
10095
|
log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
|
|
@@ -9993,7 +10134,7 @@ async function refreshCommand(options) {
|
|
|
9993
10134
|
`));
|
|
9994
10135
|
const originalDir = process.cwd();
|
|
9995
10136
|
for (const repo of repos) {
|
|
9996
|
-
const repoName =
|
|
10137
|
+
const repoName = path30.basename(repo);
|
|
9997
10138
|
try {
|
|
9998
10139
|
process.chdir(repo);
|
|
9999
10140
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -10014,31 +10155,31 @@ async function refreshCommand(options) {
|
|
|
10014
10155
|
|
|
10015
10156
|
// src/commands/hooks.ts
|
|
10016
10157
|
import chalk20 from "chalk";
|
|
10017
|
-
import
|
|
10158
|
+
import fs40 from "fs";
|
|
10018
10159
|
|
|
10019
10160
|
// src/lib/hooks.ts
|
|
10020
10161
|
init_resolve_caliber();
|
|
10021
|
-
import
|
|
10022
|
-
import
|
|
10162
|
+
import fs39 from "fs";
|
|
10163
|
+
import path31 from "path";
|
|
10023
10164
|
import { execSync as execSync15 } from "child_process";
|
|
10024
|
-
var SETTINGS_PATH2 =
|
|
10165
|
+
var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
|
|
10025
10166
|
var REFRESH_TAIL = "refresh --quiet";
|
|
10026
10167
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
10027
10168
|
function getHookCommand() {
|
|
10028
10169
|
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
10029
10170
|
}
|
|
10030
10171
|
function readSettings2() {
|
|
10031
|
-
if (!
|
|
10172
|
+
if (!fs39.existsSync(SETTINGS_PATH2)) return {};
|
|
10032
10173
|
try {
|
|
10033
|
-
return JSON.parse(
|
|
10174
|
+
return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
10034
10175
|
} catch {
|
|
10035
10176
|
return {};
|
|
10036
10177
|
}
|
|
10037
10178
|
}
|
|
10038
10179
|
function writeSettings2(settings) {
|
|
10039
|
-
const dir =
|
|
10040
|
-
if (!
|
|
10041
|
-
|
|
10180
|
+
const dir = path31.dirname(SETTINGS_PATH2);
|
|
10181
|
+
if (!fs39.existsSync(dir)) fs39.mkdirSync(dir, { recursive: true });
|
|
10182
|
+
fs39.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
10042
10183
|
}
|
|
10043
10184
|
function findHookIndex(sessionEnd) {
|
|
10044
10185
|
return sessionEnd.findIndex(
|
|
@@ -10101,19 +10242,19 @@ ${PRECOMMIT_END}`;
|
|
|
10101
10242
|
function getGitHooksDir() {
|
|
10102
10243
|
try {
|
|
10103
10244
|
const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10104
|
-
return
|
|
10245
|
+
return path31.join(gitDir, "hooks");
|
|
10105
10246
|
} catch {
|
|
10106
10247
|
return null;
|
|
10107
10248
|
}
|
|
10108
10249
|
}
|
|
10109
10250
|
function getPreCommitPath() {
|
|
10110
10251
|
const hooksDir = getGitHooksDir();
|
|
10111
|
-
return hooksDir ?
|
|
10252
|
+
return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
|
|
10112
10253
|
}
|
|
10113
10254
|
function isPreCommitHookInstalled() {
|
|
10114
10255
|
const hookPath = getPreCommitPath();
|
|
10115
|
-
if (!hookPath || !
|
|
10116
|
-
const content =
|
|
10256
|
+
if (!hookPath || !fs39.existsSync(hookPath)) return false;
|
|
10257
|
+
const content = fs39.readFileSync(hookPath, "utf-8");
|
|
10117
10258
|
return content.includes(PRECOMMIT_START);
|
|
10118
10259
|
}
|
|
10119
10260
|
function installPreCommitHook() {
|
|
@@ -10122,35 +10263,35 @@ function installPreCommitHook() {
|
|
|
10122
10263
|
}
|
|
10123
10264
|
const hookPath = getPreCommitPath();
|
|
10124
10265
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
10125
|
-
const hooksDir =
|
|
10126
|
-
if (!
|
|
10266
|
+
const hooksDir = path31.dirname(hookPath);
|
|
10267
|
+
if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
|
|
10127
10268
|
let content = "";
|
|
10128
|
-
if (
|
|
10129
|
-
content =
|
|
10269
|
+
if (fs39.existsSync(hookPath)) {
|
|
10270
|
+
content = fs39.readFileSync(hookPath, "utf-8");
|
|
10130
10271
|
if (!content.endsWith("\n")) content += "\n";
|
|
10131
10272
|
content += "\n" + getPrecommitBlock() + "\n";
|
|
10132
10273
|
} else {
|
|
10133
10274
|
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
10134
10275
|
}
|
|
10135
|
-
|
|
10136
|
-
|
|
10276
|
+
fs39.writeFileSync(hookPath, content);
|
|
10277
|
+
fs39.chmodSync(hookPath, 493);
|
|
10137
10278
|
return { installed: true, alreadyInstalled: false };
|
|
10138
10279
|
}
|
|
10139
10280
|
function removePreCommitHook() {
|
|
10140
10281
|
const hookPath = getPreCommitPath();
|
|
10141
|
-
if (!hookPath || !
|
|
10282
|
+
if (!hookPath || !fs39.existsSync(hookPath)) {
|
|
10142
10283
|
return { removed: false, notFound: true };
|
|
10143
10284
|
}
|
|
10144
|
-
let content =
|
|
10285
|
+
let content = fs39.readFileSync(hookPath, "utf-8");
|
|
10145
10286
|
if (!content.includes(PRECOMMIT_START)) {
|
|
10146
10287
|
return { removed: false, notFound: true };
|
|
10147
10288
|
}
|
|
10148
10289
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
10149
10290
|
content = content.replace(regex, "\n");
|
|
10150
10291
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
10151
|
-
|
|
10292
|
+
fs39.unlinkSync(hookPath);
|
|
10152
10293
|
} else {
|
|
10153
|
-
|
|
10294
|
+
fs39.writeFileSync(hookPath, content);
|
|
10154
10295
|
}
|
|
10155
10296
|
return { removed: true, notFound: false };
|
|
10156
10297
|
}
|
|
@@ -10199,11 +10340,11 @@ async function hooksCommand(options) {
|
|
|
10199
10340
|
console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
|
|
10200
10341
|
}
|
|
10201
10342
|
}
|
|
10202
|
-
if (
|
|
10343
|
+
if (fs40.existsSync(".claude")) {
|
|
10203
10344
|
const r = installLearningHooks();
|
|
10204
10345
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
|
|
10205
10346
|
}
|
|
10206
|
-
if (
|
|
10347
|
+
if (fs40.existsSync(".cursor")) {
|
|
10207
10348
|
const r = installCursorLearningHooks();
|
|
10208
10349
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
|
|
10209
10350
|
}
|
|
@@ -10371,8 +10512,8 @@ async function configCommand() {
|
|
|
10371
10512
|
}
|
|
10372
10513
|
|
|
10373
10514
|
// src/commands/learn.ts
|
|
10374
|
-
import
|
|
10375
|
-
import
|
|
10515
|
+
import fs44 from "fs";
|
|
10516
|
+
import path35 from "path";
|
|
10376
10517
|
import chalk23 from "chalk";
|
|
10377
10518
|
|
|
10378
10519
|
// src/learner/stdin.ts
|
|
@@ -10403,8 +10544,8 @@ function readStdin() {
|
|
|
10403
10544
|
}
|
|
10404
10545
|
|
|
10405
10546
|
// src/learner/storage.ts
|
|
10406
|
-
import
|
|
10407
|
-
import
|
|
10547
|
+
import fs41 from "fs";
|
|
10548
|
+
import path32 from "path";
|
|
10408
10549
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
10409
10550
|
var DEFAULT_STATE = {
|
|
10410
10551
|
sessionId: null,
|
|
@@ -10413,15 +10554,15 @@ var DEFAULT_STATE = {
|
|
|
10413
10554
|
lastAnalysisEventCount: 0
|
|
10414
10555
|
};
|
|
10415
10556
|
function ensureLearningDir() {
|
|
10416
|
-
if (!
|
|
10417
|
-
|
|
10557
|
+
if (!fs41.existsSync(getLearningDir())) {
|
|
10558
|
+
fs41.mkdirSync(getLearningDir(), { recursive: true });
|
|
10418
10559
|
}
|
|
10419
10560
|
}
|
|
10420
10561
|
function sessionFilePath() {
|
|
10421
|
-
return
|
|
10562
|
+
return path32.join(getLearningDir(), LEARNING_SESSION_FILE);
|
|
10422
10563
|
}
|
|
10423
10564
|
function stateFilePath() {
|
|
10424
|
-
return
|
|
10565
|
+
return path32.join(getLearningDir(), LEARNING_STATE_FILE);
|
|
10425
10566
|
}
|
|
10426
10567
|
function truncateResponse(response) {
|
|
10427
10568
|
const str = JSON.stringify(response);
|
|
@@ -10431,10 +10572,10 @@ function truncateResponse(response) {
|
|
|
10431
10572
|
function trimSessionFileIfNeeded(filePath) {
|
|
10432
10573
|
const state = readState2();
|
|
10433
10574
|
if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
|
|
10434
|
-
const lines =
|
|
10575
|
+
const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
10435
10576
|
if (lines.length > LEARNING_MAX_EVENTS) {
|
|
10436
10577
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
10437
|
-
|
|
10578
|
+
fs41.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
10438
10579
|
}
|
|
10439
10580
|
}
|
|
10440
10581
|
}
|
|
@@ -10442,19 +10583,19 @@ function appendEvent(event) {
|
|
|
10442
10583
|
ensureLearningDir();
|
|
10443
10584
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
10444
10585
|
const filePath = sessionFilePath();
|
|
10445
|
-
|
|
10586
|
+
fs41.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
10446
10587
|
trimSessionFileIfNeeded(filePath);
|
|
10447
10588
|
}
|
|
10448
10589
|
function appendPromptEvent(event) {
|
|
10449
10590
|
ensureLearningDir();
|
|
10450
10591
|
const filePath = sessionFilePath();
|
|
10451
|
-
|
|
10592
|
+
fs41.appendFileSync(filePath, JSON.stringify(event) + "\n");
|
|
10452
10593
|
trimSessionFileIfNeeded(filePath);
|
|
10453
10594
|
}
|
|
10454
10595
|
function readAllEvents() {
|
|
10455
10596
|
const filePath = sessionFilePath();
|
|
10456
|
-
if (!
|
|
10457
|
-
const lines =
|
|
10597
|
+
if (!fs41.existsSync(filePath)) return [];
|
|
10598
|
+
const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
10458
10599
|
const events = [];
|
|
10459
10600
|
for (const line of lines) {
|
|
10460
10601
|
try {
|
|
@@ -10466,26 +10607,26 @@ function readAllEvents() {
|
|
|
10466
10607
|
}
|
|
10467
10608
|
function getEventCount() {
|
|
10468
10609
|
const filePath = sessionFilePath();
|
|
10469
|
-
if (!
|
|
10470
|
-
const content =
|
|
10610
|
+
if (!fs41.existsSync(filePath)) return 0;
|
|
10611
|
+
const content = fs41.readFileSync(filePath, "utf-8");
|
|
10471
10612
|
return content.split("\n").filter(Boolean).length;
|
|
10472
10613
|
}
|
|
10473
10614
|
function clearSession() {
|
|
10474
10615
|
const filePath = sessionFilePath();
|
|
10475
|
-
if (
|
|
10616
|
+
if (fs41.existsSync(filePath)) fs41.unlinkSync(filePath);
|
|
10476
10617
|
}
|
|
10477
10618
|
function readState2() {
|
|
10478
10619
|
const filePath = stateFilePath();
|
|
10479
|
-
if (!
|
|
10620
|
+
if (!fs41.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
10480
10621
|
try {
|
|
10481
|
-
return JSON.parse(
|
|
10622
|
+
return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
|
|
10482
10623
|
} catch {
|
|
10483
10624
|
return { ...DEFAULT_STATE };
|
|
10484
10625
|
}
|
|
10485
10626
|
}
|
|
10486
10627
|
function writeState2(state) {
|
|
10487
10628
|
ensureLearningDir();
|
|
10488
|
-
|
|
10629
|
+
fs41.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
10489
10630
|
}
|
|
10490
10631
|
function resetState() {
|
|
10491
10632
|
writeState2({ ...DEFAULT_STATE });
|
|
@@ -10493,16 +10634,16 @@ function resetState() {
|
|
|
10493
10634
|
var LOCK_FILE2 = "finalize.lock";
|
|
10494
10635
|
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
10495
10636
|
function lockFilePath() {
|
|
10496
|
-
return
|
|
10637
|
+
return path32.join(getLearningDir(), LOCK_FILE2);
|
|
10497
10638
|
}
|
|
10498
10639
|
function acquireFinalizeLock() {
|
|
10499
10640
|
ensureLearningDir();
|
|
10500
10641
|
const lockPath = lockFilePath();
|
|
10501
|
-
if (
|
|
10642
|
+
if (fs41.existsSync(lockPath)) {
|
|
10502
10643
|
try {
|
|
10503
|
-
const stat =
|
|
10644
|
+
const stat = fs41.statSync(lockPath);
|
|
10504
10645
|
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
|
|
10505
|
-
const pid = parseInt(
|
|
10646
|
+
const pid = parseInt(fs41.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
10506
10647
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
10507
10648
|
return false;
|
|
10508
10649
|
}
|
|
@@ -10510,12 +10651,12 @@ function acquireFinalizeLock() {
|
|
|
10510
10651
|
} catch {
|
|
10511
10652
|
}
|
|
10512
10653
|
try {
|
|
10513
|
-
|
|
10654
|
+
fs41.unlinkSync(lockPath);
|
|
10514
10655
|
} catch {
|
|
10515
10656
|
}
|
|
10516
10657
|
}
|
|
10517
10658
|
try {
|
|
10518
|
-
|
|
10659
|
+
fs41.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
10519
10660
|
return true;
|
|
10520
10661
|
} catch {
|
|
10521
10662
|
return false;
|
|
@@ -10532,7 +10673,7 @@ function isProcessAlive(pid) {
|
|
|
10532
10673
|
function releaseFinalizeLock() {
|
|
10533
10674
|
const lockPath = lockFilePath();
|
|
10534
10675
|
try {
|
|
10535
|
-
if (
|
|
10676
|
+
if (fs41.existsSync(lockPath)) fs41.unlinkSync(lockPath);
|
|
10536
10677
|
} catch {
|
|
10537
10678
|
}
|
|
10538
10679
|
}
|
|
@@ -10577,24 +10718,24 @@ function sanitizeSecrets(text) {
|
|
|
10577
10718
|
}
|
|
10578
10719
|
|
|
10579
10720
|
// src/lib/notifications.ts
|
|
10580
|
-
import
|
|
10581
|
-
import
|
|
10721
|
+
import fs42 from "fs";
|
|
10722
|
+
import path33 from "path";
|
|
10582
10723
|
import chalk22 from "chalk";
|
|
10583
10724
|
function notificationFilePath() {
|
|
10584
|
-
return
|
|
10725
|
+
return path33.join(getLearningDir(), "last-finalize-summary.json");
|
|
10585
10726
|
}
|
|
10586
10727
|
function writeFinalizeSummary(summary) {
|
|
10587
10728
|
try {
|
|
10588
10729
|
ensureLearningDir();
|
|
10589
|
-
|
|
10730
|
+
fs42.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
|
|
10590
10731
|
} catch {
|
|
10591
10732
|
}
|
|
10592
10733
|
}
|
|
10593
10734
|
function checkPendingNotifications() {
|
|
10594
10735
|
try {
|
|
10595
|
-
if (!
|
|
10596
|
-
const raw =
|
|
10597
|
-
|
|
10736
|
+
if (!fs42.existsSync(notificationFilePath())) return;
|
|
10737
|
+
const raw = fs42.readFileSync(notificationFilePath(), "utf-8");
|
|
10738
|
+
fs42.unlinkSync(notificationFilePath());
|
|
10598
10739
|
const summary = JSON.parse(raw);
|
|
10599
10740
|
if (!summary.newItemCount || summary.newItemCount === 0) return;
|
|
10600
10741
|
const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
|
|
@@ -10610,7 +10751,7 @@ function checkPendingNotifications() {
|
|
|
10610
10751
|
console.log("");
|
|
10611
10752
|
} catch {
|
|
10612
10753
|
try {
|
|
10613
|
-
|
|
10754
|
+
fs42.unlinkSync(notificationFilePath());
|
|
10614
10755
|
} catch {
|
|
10615
10756
|
}
|
|
10616
10757
|
}
|
|
@@ -10762,8 +10903,8 @@ function calculateSessionWaste(events) {
|
|
|
10762
10903
|
init_config();
|
|
10763
10904
|
|
|
10764
10905
|
// src/learner/roi.ts
|
|
10765
|
-
import
|
|
10766
|
-
import
|
|
10906
|
+
import fs43 from "fs";
|
|
10907
|
+
import path34 from "path";
|
|
10767
10908
|
var DEFAULT_TOTALS = {
|
|
10768
10909
|
totalWasteTokens: 0,
|
|
10769
10910
|
totalWasteSeconds: 0,
|
|
@@ -10777,19 +10918,19 @@ var DEFAULT_TOTALS = {
|
|
|
10777
10918
|
lastSessionTimestamp: ""
|
|
10778
10919
|
};
|
|
10779
10920
|
function roiFilePath() {
|
|
10780
|
-
return
|
|
10921
|
+
return path34.join(getLearningDir(), LEARNING_ROI_FILE);
|
|
10781
10922
|
}
|
|
10782
10923
|
function readROIStats() {
|
|
10783
10924
|
const filePath = roiFilePath();
|
|
10784
|
-
if (!
|
|
10925
|
+
if (!fs43.existsSync(filePath)) {
|
|
10785
10926
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
10786
10927
|
}
|
|
10787
10928
|
try {
|
|
10788
|
-
return JSON.parse(
|
|
10929
|
+
return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
|
|
10789
10930
|
} catch {
|
|
10790
10931
|
try {
|
|
10791
10932
|
const corruptPath = filePath + ".corrupt";
|
|
10792
|
-
|
|
10933
|
+
fs43.renameSync(filePath, corruptPath);
|
|
10793
10934
|
console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
|
|
10794
10935
|
} catch {
|
|
10795
10936
|
}
|
|
@@ -10798,7 +10939,7 @@ function readROIStats() {
|
|
|
10798
10939
|
}
|
|
10799
10940
|
function writeROIStats(stats) {
|
|
10800
10941
|
ensureLearningDir();
|
|
10801
|
-
|
|
10942
|
+
fs43.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
|
|
10802
10943
|
}
|
|
10803
10944
|
function recalculateTotals(stats) {
|
|
10804
10945
|
const totals = stats.totals;
|
|
@@ -11004,9 +11145,9 @@ var AUTO_SETTLE_MS = 200;
|
|
|
11004
11145
|
var INCREMENTAL_INTERVAL = 50;
|
|
11005
11146
|
function writeFinalizeError(message) {
|
|
11006
11147
|
try {
|
|
11007
|
-
const errorPath =
|
|
11008
|
-
if (!
|
|
11009
|
-
|
|
11148
|
+
const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
11149
|
+
if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
|
|
11150
|
+
fs44.writeFileSync(errorPath, JSON.stringify({
|
|
11010
11151
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11011
11152
|
error: message,
|
|
11012
11153
|
pid: process.pid
|
|
@@ -11016,9 +11157,9 @@ function writeFinalizeError(message) {
|
|
|
11016
11157
|
}
|
|
11017
11158
|
function readFinalizeError() {
|
|
11018
11159
|
try {
|
|
11019
|
-
const errorPath =
|
|
11020
|
-
if (!
|
|
11021
|
-
return JSON.parse(
|
|
11160
|
+
const errorPath = path35.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
11161
|
+
if (!fs44.existsSync(errorPath)) return null;
|
|
11162
|
+
return JSON.parse(fs44.readFileSync(errorPath, "utf-8"));
|
|
11022
11163
|
} catch {
|
|
11023
11164
|
return null;
|
|
11024
11165
|
}
|
|
@@ -11065,14 +11206,14 @@ async function learnObserveCommand(options) {
|
|
|
11065
11206
|
const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
|
|
11066
11207
|
const bin = resolveCaliber2();
|
|
11067
11208
|
const { spawn: spawn4 } = await import("child_process");
|
|
11068
|
-
const logPath =
|
|
11069
|
-
if (!
|
|
11070
|
-
const logFd =
|
|
11209
|
+
const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
11210
|
+
if (!fs44.existsSync(getLearningDir())) fs44.mkdirSync(getLearningDir(), { recursive: true });
|
|
11211
|
+
const logFd = fs44.openSync(logPath, "a");
|
|
11071
11212
|
spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
|
|
11072
11213
|
detached: true,
|
|
11073
11214
|
stdio: ["ignore", logFd, logFd]
|
|
11074
11215
|
}).unref();
|
|
11075
|
-
|
|
11216
|
+
fs44.closeSync(logFd);
|
|
11076
11217
|
} catch {
|
|
11077
11218
|
}
|
|
11078
11219
|
}
|
|
@@ -11279,7 +11420,7 @@ async function learnFinalizeCommand(options) {
|
|
|
11279
11420
|
}
|
|
11280
11421
|
async function learnInstallCommand() {
|
|
11281
11422
|
let anyInstalled = false;
|
|
11282
|
-
if (
|
|
11423
|
+
if (fs44.existsSync(".claude")) {
|
|
11283
11424
|
const r = installLearningHooks();
|
|
11284
11425
|
if (r.installed) {
|
|
11285
11426
|
console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
|
|
@@ -11288,7 +11429,7 @@ async function learnInstallCommand() {
|
|
|
11288
11429
|
console.log(chalk23.dim(" Claude Code hooks already installed"));
|
|
11289
11430
|
}
|
|
11290
11431
|
}
|
|
11291
|
-
if (
|
|
11432
|
+
if (fs44.existsSync(".cursor")) {
|
|
11292
11433
|
const r = installCursorLearningHooks();
|
|
11293
11434
|
if (r.installed) {
|
|
11294
11435
|
console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
|
|
@@ -11297,7 +11438,7 @@ async function learnInstallCommand() {
|
|
|
11297
11438
|
console.log(chalk23.dim(" Cursor hooks already installed"));
|
|
11298
11439
|
}
|
|
11299
11440
|
}
|
|
11300
|
-
if (!
|
|
11441
|
+
if (!fs44.existsSync(".claude") && !fs44.existsSync(".cursor")) {
|
|
11301
11442
|
console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
|
|
11302
11443
|
console.log(chalk23.dim(" Run `caliber init` first, or create the directory manually."));
|
|
11303
11444
|
return;
|
|
@@ -11355,8 +11496,8 @@ async function learnStatusCommand() {
|
|
|
11355
11496
|
if (lastError) {
|
|
11356
11497
|
console.log(`Last error: ${chalk23.red(lastError.error)}`);
|
|
11357
11498
|
console.log(chalk23.dim(` at ${lastError.timestamp}`));
|
|
11358
|
-
const logPath =
|
|
11359
|
-
if (
|
|
11499
|
+
const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
11500
|
+
if (fs44.existsSync(logPath)) {
|
|
11360
11501
|
console.log(chalk23.dim(` Full log: ${logPath}`));
|
|
11361
11502
|
}
|
|
11362
11503
|
}
|
|
@@ -11436,11 +11577,11 @@ async function learnDeleteCommand(indexStr) {
|
|
|
11436
11577
|
}
|
|
11437
11578
|
const item = items[targetIdx];
|
|
11438
11579
|
const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
|
|
11439
|
-
if (!
|
|
11580
|
+
if (!fs44.existsSync(filePath)) {
|
|
11440
11581
|
console.log(chalk23.red("Learnings file not found."));
|
|
11441
11582
|
return;
|
|
11442
11583
|
}
|
|
11443
|
-
const content =
|
|
11584
|
+
const content = fs44.readFileSync(filePath, "utf-8");
|
|
11444
11585
|
const lines = content.split("\n");
|
|
11445
11586
|
const bulletsOfSource = items.filter((i) => i.source === item.source);
|
|
11446
11587
|
const posInFile = bulletsOfSource.indexOf(item);
|
|
@@ -11461,9 +11602,9 @@ async function learnDeleteCommand(indexStr) {
|
|
|
11461
11602
|
}
|
|
11462
11603
|
const bulletToRemove = lines[lineToRemove];
|
|
11463
11604
|
const newLines = lines.filter((_, i) => i !== lineToRemove);
|
|
11464
|
-
|
|
11605
|
+
fs44.writeFileSync(filePath, newLines.join("\n"));
|
|
11465
11606
|
if (item.source === "personal") {
|
|
11466
|
-
|
|
11607
|
+
fs44.chmodSync(filePath, 384);
|
|
11467
11608
|
}
|
|
11468
11609
|
const roiStats = readROIStats();
|
|
11469
11610
|
const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
|
|
@@ -11532,11 +11673,14 @@ function displayColdStart(score) {
|
|
|
11532
11673
|
console.log(chalk24.bold("\n Agent Insights\n"));
|
|
11533
11674
|
const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
|
|
11534
11675
|
if (!hooksInstalled) {
|
|
11535
|
-
console.log(chalk24.yellow("
|
|
11536
|
-
console.log(chalk24.dim("
|
|
11676
|
+
console.log(chalk24.yellow(" Learning hooks not installed."));
|
|
11677
|
+
console.log(chalk24.dim(" Session learning captures patterns from your AI coding sessions \u2014 what"));
|
|
11678
|
+
console.log(chalk24.dim(" fails, what works, corrections you make \u2014 so your agents improve over time.\n"));
|
|
11679
|
+
console.log(chalk24.dim(" Run ") + chalk24.cyan("caliber learn install") + chalk24.dim(" to enable."));
|
|
11537
11680
|
} else {
|
|
11538
|
-
console.log(chalk24.dim("
|
|
11539
|
-
console.log(chalk24.dim("
|
|
11681
|
+
console.log(chalk24.dim(" Learning hooks are active. Use your AI agent and insights"));
|
|
11682
|
+
console.log(chalk24.dim(" will appear automatically after each session.\n"));
|
|
11683
|
+
console.log(chalk24.dim(` Progress: 0/${MIN_SESSIONS_FULL} sessions \u2014 full insights unlock at ${MIN_SESSIONS_FULL}`));
|
|
11540
11684
|
}
|
|
11541
11685
|
console.log(chalk24.dim(`
|
|
11542
11686
|
Config score: ${score.score}/100 (${score.grade})`));
|
|
@@ -11544,7 +11688,9 @@ function displayColdStart(score) {
|
|
|
11544
11688
|
}
|
|
11545
11689
|
function displayEarlyData(data, score) {
|
|
11546
11690
|
console.log(chalk24.bold("\n Agent Insights") + chalk24.yellow(" (early data)\n"));
|
|
11547
|
-
|
|
11691
|
+
const remaining = MIN_SESSIONS_FULL - data.totalSessions;
|
|
11692
|
+
console.log(chalk24.dim(` ${data.totalSessions}/${MIN_SESSIONS_FULL} sessions tracked \u2014 ${remaining} more for full insights.
|
|
11693
|
+
`));
|
|
11548
11694
|
console.log(` Sessions tracked: ${chalk24.cyan(String(data.totalSessions))}`);
|
|
11549
11695
|
console.log(` Learnings accumulated: ${chalk24.cyan(String(data.learningCount))}`);
|
|
11550
11696
|
if (data.totalWasteTokens > 0) {
|
|
@@ -11596,6 +11742,14 @@ function displayFullInsights(data, score) {
|
|
|
11596
11742
|
}
|
|
11597
11743
|
console.log(chalk24.bold("\n Config Quality"));
|
|
11598
11744
|
console.log(` Score: ${chalk24.cyan(`${score.score}/100`)} (${score.grade})`);
|
|
11745
|
+
const history = readScoreHistory();
|
|
11746
|
+
const trend = getScoreTrend(history);
|
|
11747
|
+
if (trend) {
|
|
11748
|
+
const trendColor = trend.direction === "up" ? chalk24.green : trend.direction === "down" ? chalk24.red : chalk24.gray;
|
|
11749
|
+
const arrow = trend.direction === "up" ? "\u2191" : trend.direction === "down" ? "\u2193" : "\u2192";
|
|
11750
|
+
const sign = trend.delta > 0 ? "+" : "";
|
|
11751
|
+
console.log(` Trend: ${trendColor(`${arrow} ${sign}${trend.delta} pts`)} ${chalk24.dim(`over ${trend.entries} checks`)}`);
|
|
11752
|
+
}
|
|
11599
11753
|
console.log("");
|
|
11600
11754
|
}
|
|
11601
11755
|
async function insightsCommand(options) {
|
|
@@ -11622,8 +11776,8 @@ async function insightsCommand(options) {
|
|
|
11622
11776
|
}
|
|
11623
11777
|
|
|
11624
11778
|
// src/commands/sources.ts
|
|
11625
|
-
import
|
|
11626
|
-
import
|
|
11779
|
+
import fs45 from "fs";
|
|
11780
|
+
import path36 from "path";
|
|
11627
11781
|
import chalk25 from "chalk";
|
|
11628
11782
|
async function sourcesListCommand() {
|
|
11629
11783
|
const dir = process.cwd();
|
|
@@ -11639,9 +11793,9 @@ async function sourcesListCommand() {
|
|
|
11639
11793
|
if (configSources.length > 0) {
|
|
11640
11794
|
for (const source of configSources) {
|
|
11641
11795
|
const sourcePath = source.path || source.url || "";
|
|
11642
|
-
const exists = source.path ?
|
|
11796
|
+
const exists = source.path ? fs45.existsSync(path36.resolve(dir, source.path)) : false;
|
|
11643
11797
|
const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
|
|
11644
|
-
const hasSummary = source.path &&
|
|
11798
|
+
const hasSummary = source.path && fs45.existsSync(path36.join(path36.resolve(dir, source.path), ".caliber", "summary.json"));
|
|
11645
11799
|
console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
|
|
11646
11800
|
console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
|
|
11647
11801
|
if (source.description) console.log(` ${chalk25.dim(source.description)}`);
|
|
@@ -11651,7 +11805,7 @@ async function sourcesListCommand() {
|
|
|
11651
11805
|
if (workspaces.length > 0) {
|
|
11652
11806
|
console.log(chalk25.dim(" Auto-detected workspaces:"));
|
|
11653
11807
|
for (const ws of workspaces) {
|
|
11654
|
-
const exists =
|
|
11808
|
+
const exists = fs45.existsSync(path36.resolve(dir, ws));
|
|
11655
11809
|
console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
|
|
11656
11810
|
}
|
|
11657
11811
|
console.log("");
|
|
@@ -11659,8 +11813,8 @@ async function sourcesListCommand() {
|
|
|
11659
11813
|
}
|
|
11660
11814
|
async function sourcesAddCommand(sourcePath) {
|
|
11661
11815
|
const dir = process.cwd();
|
|
11662
|
-
const absPath =
|
|
11663
|
-
if (!
|
|
11816
|
+
const absPath = path36.resolve(dir, sourcePath);
|
|
11817
|
+
if (!fs45.existsSync(absPath)) {
|
|
11664
11818
|
console.log(chalk25.red(`
|
|
11665
11819
|
Path not found: ${sourcePath}
|
|
11666
11820
|
`));
|
|
@@ -11675,7 +11829,7 @@ async function sourcesAddCommand(sourcePath) {
|
|
|
11675
11829
|
}
|
|
11676
11830
|
const existing = loadSourcesConfig(dir);
|
|
11677
11831
|
const alreadyConfigured = existing.some(
|
|
11678
|
-
(s) => s.path &&
|
|
11832
|
+
(s) => s.path && path36.resolve(dir, s.path) === absPath
|
|
11679
11833
|
);
|
|
11680
11834
|
if (alreadyConfigured) {
|
|
11681
11835
|
console.log(chalk25.yellow(`
|
|
@@ -11723,8 +11877,8 @@ async function sourcesRemoveCommand(name) {
|
|
|
11723
11877
|
}
|
|
11724
11878
|
|
|
11725
11879
|
// src/commands/publish.ts
|
|
11726
|
-
import
|
|
11727
|
-
import
|
|
11880
|
+
import fs46 from "fs";
|
|
11881
|
+
import path37 from "path";
|
|
11728
11882
|
import chalk26 from "chalk";
|
|
11729
11883
|
import ora7 from "ora";
|
|
11730
11884
|
init_config();
|
|
@@ -11738,10 +11892,10 @@ async function publishCommand() {
|
|
|
11738
11892
|
const spinner = ora7("Generating project summary...").start();
|
|
11739
11893
|
try {
|
|
11740
11894
|
const fingerprint = await collectFingerprint(dir);
|
|
11741
|
-
const claudeMd = readFileOrNull(
|
|
11895
|
+
const claudeMd = readFileOrNull(path37.join(dir, "CLAUDE.md"));
|
|
11742
11896
|
const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
|
|
11743
11897
|
const summary = {
|
|
11744
|
-
name: fingerprint.packageName ||
|
|
11898
|
+
name: fingerprint.packageName || path37.basename(dir),
|
|
11745
11899
|
version: "1.0.0",
|
|
11746
11900
|
description: fingerprint.description || "",
|
|
11747
11901
|
languages: fingerprint.languages,
|
|
@@ -11753,7 +11907,7 @@ async function publishCommand() {
|
|
|
11753
11907
|
summary.conventions = claudeMd.slice(0, 2e3);
|
|
11754
11908
|
}
|
|
11755
11909
|
try {
|
|
11756
|
-
const pkgContent = readFileOrNull(
|
|
11910
|
+
const pkgContent = readFileOrNull(path37.join(dir, "package.json"));
|
|
11757
11911
|
if (pkgContent) {
|
|
11758
11912
|
const pkg3 = JSON.parse(pkgContent);
|
|
11759
11913
|
if (pkg3.scripts) {
|
|
@@ -11766,14 +11920,14 @@ async function publishCommand() {
|
|
|
11766
11920
|
}
|
|
11767
11921
|
} catch {
|
|
11768
11922
|
}
|
|
11769
|
-
const outputDir =
|
|
11770
|
-
if (!
|
|
11771
|
-
|
|
11923
|
+
const outputDir = path37.join(dir, ".caliber");
|
|
11924
|
+
if (!fs46.existsSync(outputDir)) {
|
|
11925
|
+
fs46.mkdirSync(outputDir, { recursive: true });
|
|
11772
11926
|
}
|
|
11773
|
-
const outputPath =
|
|
11774
|
-
|
|
11927
|
+
const outputPath = path37.join(outputDir, "summary.json");
|
|
11928
|
+
fs46.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
11775
11929
|
spinner.succeed("Project summary published");
|
|
11776
|
-
console.log(` ${chalk26.green("\u2713")} ${
|
|
11930
|
+
console.log(` ${chalk26.green("\u2713")} ${path37.relative(dir, outputPath)}`);
|
|
11777
11931
|
console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
|
|
11778
11932
|
console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
|
|
11779
11933
|
} catch (err) {
|
|
@@ -11785,13 +11939,13 @@ async function publishCommand() {
|
|
|
11785
11939
|
}
|
|
11786
11940
|
|
|
11787
11941
|
// src/cli.ts
|
|
11788
|
-
var __dirname =
|
|
11942
|
+
var __dirname = path38.dirname(fileURLToPath(import.meta.url));
|
|
11789
11943
|
var pkg = JSON.parse(
|
|
11790
|
-
|
|
11944
|
+
fs47.readFileSync(path38.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
11791
11945
|
);
|
|
11792
11946
|
var program = new Command();
|
|
11793
11947
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
11794
|
-
program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("
|
|
11948
|
+
program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("AI context infrastructure for coding agents").version(displayVersion).option("--no-traces", "Disable anonymous telemetry for this run");
|
|
11795
11949
|
function tracked(commandName, handler) {
|
|
11796
11950
|
const wrapper = async (...args) => {
|
|
11797
11951
|
const start = Date.now();
|
|
@@ -11848,13 +12002,13 @@ function parseAgentOption(value) {
|
|
|
11848
12002
|
}
|
|
11849
12003
|
return agents;
|
|
11850
12004
|
}
|
|
11851
|
-
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
|
|
12005
|
+
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));
|
|
11852
12006
|
program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
|
|
11853
|
-
program.command("status").description("Show current Caliber
|
|
11854
|
-
program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate
|
|
12007
|
+
program.command("status").description("Show current Caliber config status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
|
|
12008
|
+
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));
|
|
11855
12009
|
program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
|
|
11856
12010
|
program.command("skills").description("Discover and install community skills for your project").option("--query <terms>", 'Search for skills by topic (e.g. "react frontend")').option("--install <slugs>", "Install specific skills by slug (comma-separated)").action(tracked("skills", recommendCommand));
|
|
11857
|
-
program.command("score").description("Score your
|
|
12011
|
+
program.command("score").description("Score your AI context configuration (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex, github-copilot", parseAgentOption).option("--compare <ref>", "Compare score against a git ref (branch, tag, or SHA)").action(tracked("score", scoreCommand));
|
|
11858
12012
|
program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(tracked("refresh", refreshCommand));
|
|
11859
12013
|
program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(tracked("hooks", hooksCommand));
|
|
11860
12014
|
program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
|
|
@@ -11863,7 +12017,7 @@ sources.command("list").description("Show configured and auto-detected sources")
|
|
|
11863
12017
|
sources.command("add").description("Add an external source").argument("<path>", "Path to repo directory or file").action(tracked("sources:add", sourcesAddCommand));
|
|
11864
12018
|
sources.command("remove").description("Remove a configured source").argument("<name>", "Source path or role to remove").action(tracked("sources:remove", sourcesRemoveCommand));
|
|
11865
12019
|
program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
|
|
11866
|
-
var learn = program.command("learn"
|
|
12020
|
+
var learn = program.command("learn").description("Manage session learning \u2014 extract patterns from your AI coding sessions");
|
|
11867
12021
|
learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").option("--prompt", "Record a user prompt event").action(tracked("learn:observe", learnObserveCommand));
|
|
11868
12022
|
learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").option("--auto", "Silent mode for hooks (lower threshold, no interactive output)").option("--incremental", "Extract learnings mid-session without clearing events").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
|
|
11869
12023
|
learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
|
|
@@ -11874,16 +12028,16 @@ learn.command("delete <index>").description("Delete a learning by its index numb
|
|
|
11874
12028
|
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));
|
|
11875
12029
|
|
|
11876
12030
|
// src/utils/version-check.ts
|
|
11877
|
-
import
|
|
11878
|
-
import
|
|
12031
|
+
import fs48 from "fs";
|
|
12032
|
+
import path39 from "path";
|
|
11879
12033
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11880
12034
|
import { execSync as execSync16 } from "child_process";
|
|
11881
12035
|
import chalk27 from "chalk";
|
|
11882
12036
|
import ora8 from "ora";
|
|
11883
12037
|
import confirm2 from "@inquirer/confirm";
|
|
11884
|
-
var __dirname_vc =
|
|
12038
|
+
var __dirname_vc = path39.dirname(fileURLToPath2(import.meta.url));
|
|
11885
12039
|
var pkg2 = JSON.parse(
|
|
11886
|
-
|
|
12040
|
+
fs48.readFileSync(path39.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
11887
12041
|
);
|
|
11888
12042
|
function getChannel(version) {
|
|
11889
12043
|
const match = version.match(/-(dev|next)\./);
|
|
@@ -11908,8 +12062,8 @@ function isNewer(registry, current) {
|
|
|
11908
12062
|
function getInstalledVersion() {
|
|
11909
12063
|
try {
|
|
11910
12064
|
const globalRoot = execSync16("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
11911
|
-
const pkgPath =
|
|
11912
|
-
return JSON.parse(
|
|
12065
|
+
const pkgPath = path39.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
12066
|
+
return JSON.parse(fs48.readFileSync(pkgPath, "utf-8")).version;
|
|
11913
12067
|
} catch {
|
|
11914
12068
|
return null;
|
|
11915
12069
|
}
|