@rely-ai/caliber 1.30.0-dev.1774189509 → 1.30.0-dev.1774286800
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 +523 -380
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -181,7 +181,7 @@ __export(resolve_caliber_exports, {
|
|
|
181
181
|
resolveCaliber: () => resolveCaliber
|
|
182
182
|
});
|
|
183
183
|
import fs20 from "fs";
|
|
184
|
-
import { execSync as
|
|
184
|
+
import { execSync as execSync7 } from "child_process";
|
|
185
185
|
function resolveCaliber() {
|
|
186
186
|
if (_resolved) return _resolved;
|
|
187
187
|
const isNpx = process.argv[1]?.includes("_npx") || process.env.npm_execpath?.includes("npx");
|
|
@@ -191,14 +191,12 @@ function resolveCaliber() {
|
|
|
191
191
|
}
|
|
192
192
|
try {
|
|
193
193
|
const whichCmd = process.platform === "win32" ? "where caliber" : "which caliber";
|
|
194
|
-
|
|
194
|
+
execSync7(whichCmd, {
|
|
195
195
|
encoding: "utf-8",
|
|
196
196
|
stdio: ["pipe", "pipe", "pipe"]
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return _resolved;
|
|
201
|
-
}
|
|
197
|
+
});
|
|
198
|
+
_resolved = "caliber";
|
|
199
|
+
return _resolved;
|
|
202
200
|
} catch {
|
|
203
201
|
}
|
|
204
202
|
const binPath = process.argv[1];
|
|
@@ -225,7 +223,7 @@ var init_resolve_caliber = __esm({
|
|
|
225
223
|
});
|
|
226
224
|
|
|
227
225
|
// src/utils/editor.ts
|
|
228
|
-
import { execSync as
|
|
226
|
+
import { execSync as execSync13, spawn as spawn3 } from "child_process";
|
|
229
227
|
import fs26 from "fs";
|
|
230
228
|
import path22 from "path";
|
|
231
229
|
import os6 from "os";
|
|
@@ -238,7 +236,7 @@ function getEmptyFilePath(proposedPath) {
|
|
|
238
236
|
function commandExists(cmd) {
|
|
239
237
|
try {
|
|
240
238
|
const check = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
241
|
-
|
|
239
|
+
execSync13(check, { stdio: "ignore" });
|
|
242
240
|
return true;
|
|
243
241
|
} catch {
|
|
244
242
|
return false;
|
|
@@ -497,13 +495,13 @@ __export(lock_exports, {
|
|
|
497
495
|
isCaliberRunning: () => isCaliberRunning,
|
|
498
496
|
releaseLock: () => releaseLock
|
|
499
497
|
});
|
|
500
|
-
import
|
|
501
|
-
import
|
|
498
|
+
import fs37 from "fs";
|
|
499
|
+
import path29 from "path";
|
|
502
500
|
import os8 from "os";
|
|
503
501
|
function isCaliberRunning() {
|
|
504
502
|
try {
|
|
505
|
-
if (!
|
|
506
|
-
const raw =
|
|
503
|
+
if (!fs37.existsSync(LOCK_FILE)) return false;
|
|
504
|
+
const raw = fs37.readFileSync(LOCK_FILE, "utf-8").trim();
|
|
507
505
|
const { pid, ts } = JSON.parse(raw);
|
|
508
506
|
if (Date.now() - ts > STALE_MS) return false;
|
|
509
507
|
try {
|
|
@@ -518,13 +516,13 @@ function isCaliberRunning() {
|
|
|
518
516
|
}
|
|
519
517
|
function acquireLock() {
|
|
520
518
|
try {
|
|
521
|
-
|
|
519
|
+
fs37.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
522
520
|
} catch {
|
|
523
521
|
}
|
|
524
522
|
}
|
|
525
523
|
function releaseLock() {
|
|
526
524
|
try {
|
|
527
|
-
if (
|
|
525
|
+
if (fs37.existsSync(LOCK_FILE)) fs37.unlinkSync(LOCK_FILE);
|
|
528
526
|
} catch {
|
|
529
527
|
}
|
|
530
528
|
}
|
|
@@ -532,21 +530,21 @@ var LOCK_FILE, STALE_MS;
|
|
|
532
530
|
var init_lock = __esm({
|
|
533
531
|
"src/lib/lock.ts"() {
|
|
534
532
|
"use strict";
|
|
535
|
-
LOCK_FILE =
|
|
533
|
+
LOCK_FILE = path29.join(os8.tmpdir(), ".caliber.lock");
|
|
536
534
|
STALE_MS = 10 * 60 * 1e3;
|
|
537
535
|
}
|
|
538
536
|
});
|
|
539
537
|
|
|
540
538
|
// src/cli.ts
|
|
541
539
|
import { Command } from "commander";
|
|
542
|
-
import
|
|
543
|
-
import
|
|
540
|
+
import fs47 from "fs";
|
|
541
|
+
import path38 from "path";
|
|
544
542
|
import { fileURLToPath } from "url";
|
|
545
543
|
|
|
546
544
|
// src/commands/init.ts
|
|
547
|
-
import
|
|
545
|
+
import path25 from "path";
|
|
548
546
|
import chalk14 from "chalk";
|
|
549
|
-
import
|
|
547
|
+
import fs32 from "fs";
|
|
550
548
|
|
|
551
549
|
// src/fingerprint/index.ts
|
|
552
550
|
import fs7 from "fs";
|
|
@@ -664,11 +662,26 @@ import path3 from "path";
|
|
|
664
662
|
// src/constants.ts
|
|
665
663
|
import path2 from "path";
|
|
666
664
|
import os from "os";
|
|
665
|
+
import { execSync as execSync2 } from "child_process";
|
|
667
666
|
var AUTH_DIR = path2.join(os.homedir(), ".caliber");
|
|
668
667
|
var CALIBER_DIR = ".caliber";
|
|
669
668
|
var MANIFEST_FILE = path2.join(CALIBER_DIR, "manifest.json");
|
|
670
669
|
var BACKUPS_DIR = path2.join(CALIBER_DIR, "backups");
|
|
671
|
-
var
|
|
670
|
+
var _learningDirCache = null;
|
|
671
|
+
function getLearningDir() {
|
|
672
|
+
if (_learningDirCache) return _learningDirCache;
|
|
673
|
+
try {
|
|
674
|
+
const gitCommonDir = execSync2("git rev-parse --git-common-dir", {
|
|
675
|
+
encoding: "utf-8",
|
|
676
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
677
|
+
}).trim();
|
|
678
|
+
const mainRoot = path2.dirname(path2.resolve(gitCommonDir));
|
|
679
|
+
_learningDirCache = path2.join(mainRoot, CALIBER_DIR, "learning");
|
|
680
|
+
} catch {
|
|
681
|
+
_learningDirCache = path2.join(CALIBER_DIR, "learning");
|
|
682
|
+
}
|
|
683
|
+
return _learningDirCache;
|
|
684
|
+
}
|
|
672
685
|
var LEARNING_SESSION_FILE = "current-session.jsonl";
|
|
673
686
|
var LEARNING_STATE_FILE = "state.json";
|
|
674
687
|
var LEARNING_MAX_EVENTS = 500;
|
|
@@ -781,7 +794,7 @@ function readExistingConfigs(dir) {
|
|
|
781
794
|
// src/fingerprint/code-analysis.ts
|
|
782
795
|
import fs3 from "fs";
|
|
783
796
|
import path4 from "path";
|
|
784
|
-
import { execSync as
|
|
797
|
+
import { execSync as execSync3 } from "child_process";
|
|
785
798
|
var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
|
|
786
799
|
"node_modules",
|
|
787
800
|
".git",
|
|
@@ -1128,7 +1141,7 @@ function buildImportCounts(files) {
|
|
|
1128
1141
|
function getGitFrequency(dir) {
|
|
1129
1142
|
const freq = /* @__PURE__ */ new Map();
|
|
1130
1143
|
try {
|
|
1131
|
-
const output =
|
|
1144
|
+
const output = execSync3(
|
|
1132
1145
|
'git log --since="6 months ago" --format="" --name-only --diff-filter=ACMR 2>/dev/null | head -10000',
|
|
1133
1146
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5e3 }
|
|
1134
1147
|
);
|
|
@@ -1577,7 +1590,7 @@ var OpenAICompatProvider = class {
|
|
|
1577
1590
|
};
|
|
1578
1591
|
|
|
1579
1592
|
// src/llm/cursor-acp.ts
|
|
1580
|
-
import { spawn, execSync as
|
|
1593
|
+
import { spawn, execSync as execSync4 } from "child_process";
|
|
1581
1594
|
import os3 from "os";
|
|
1582
1595
|
|
|
1583
1596
|
// src/llm/seat-based-errors.ts
|
|
@@ -1853,7 +1866,7 @@ var CursorAcpProvider = class {
|
|
|
1853
1866
|
function isCursorAgentAvailable() {
|
|
1854
1867
|
try {
|
|
1855
1868
|
const cmd = IS_WINDOWS ? `where ${AGENT_BIN}` : `which ${AGENT_BIN}`;
|
|
1856
|
-
|
|
1869
|
+
execSync4(cmd, { stdio: "ignore" });
|
|
1857
1870
|
return true;
|
|
1858
1871
|
} catch {
|
|
1859
1872
|
return false;
|
|
@@ -1861,7 +1874,7 @@ function isCursorAgentAvailable() {
|
|
|
1861
1874
|
}
|
|
1862
1875
|
function isCursorLoggedIn() {
|
|
1863
1876
|
try {
|
|
1864
|
-
const result =
|
|
1877
|
+
const result = execSync4(`${AGENT_BIN} status`, { stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 });
|
|
1865
1878
|
return !result.toString().includes("not logged in");
|
|
1866
1879
|
} catch {
|
|
1867
1880
|
return false;
|
|
@@ -1869,7 +1882,7 @@ function isCursorLoggedIn() {
|
|
|
1869
1882
|
}
|
|
1870
1883
|
|
|
1871
1884
|
// src/llm/claude-cli.ts
|
|
1872
|
-
import { spawn as spawn2, execSync as
|
|
1885
|
+
import { spawn as spawn2, execSync as execSync5 } from "child_process";
|
|
1873
1886
|
var CLAUDE_CLI_BIN = "claude";
|
|
1874
1887
|
var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
1875
1888
|
var IS_WINDOWS2 = process.platform === "win32";
|
|
@@ -2004,7 +2017,7 @@ var ClaudeCliProvider = class {
|
|
|
2004
2017
|
function isClaudeCliAvailable() {
|
|
2005
2018
|
try {
|
|
2006
2019
|
const cmd = process.platform === "win32" ? `where ${CLAUDE_CLI_BIN}` : `which ${CLAUDE_CLI_BIN}`;
|
|
2007
|
-
|
|
2020
|
+
execSync5(cmd, { stdio: "ignore" });
|
|
2008
2021
|
return true;
|
|
2009
2022
|
} catch {
|
|
2010
2023
|
return false;
|
|
@@ -2294,7 +2307,7 @@ init_config();
|
|
|
2294
2307
|
var ROLE_AND_CONTEXT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, Codex, and GitHub Copilot).
|
|
2295
2308
|
|
|
2296
2309
|
Your job depends on context:
|
|
2297
|
-
- If no existing configs exist \u2192 generate an initial
|
|
2310
|
+
- If no existing configs exist \u2192 generate an initial configuration from scratch.
|
|
2298
2311
|
- If existing configs are provided \u2192 audit them and suggest targeted improvements. Preserve accurate content \u2014 don't rewrite what's already correct.`;
|
|
2299
2312
|
var CONFIG_FILE_TYPES = `You understand these config files:
|
|
2300
2313
|
- CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
|
|
@@ -2307,6 +2320,12 @@ var CONFIG_FILE_TYPES = `You understand these config files:
|
|
|
2307
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.
|
|
2308
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.`;
|
|
2309
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?`;
|
|
2310
2329
|
var OUTPUT_FORMAT = `Your output MUST follow this exact format (no markdown fences):
|
|
2311
2330
|
|
|
2312
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.
|
|
@@ -2370,7 +2389,10 @@ Accuracy (15 pts) \u2014 CRITICAL:
|
|
|
2370
2389
|
|
|
2371
2390
|
Safety: Never include API keys, tokens, or credentials in config files.
|
|
2372
2391
|
|
|
2373
|
-
|
|
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.`;
|
|
2374
2396
|
var OUTPUT_SIZE_CONSTRAINTS = `OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
2375
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.
|
|
2376
2398
|
|
|
@@ -2381,26 +2403,12 @@ For command sections, use code blocks with one command per line.
|
|
|
2381
2403
|
|
|
2382
2404
|
- Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
|
|
2383
2405
|
- Cursor rules: max 5 .mdc files.
|
|
2384
|
-
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything
|
|
2385
|
-
|
|
2386
|
-
CONVENTIONS SECTION PRIORITY \u2014 this is the highest-value section for agent performance:
|
|
2387
|
-
The Conventions section determines whether agents choose correct, project-consistent solutions vs technically clever but fragile ones. Prioritize:
|
|
2388
|
-
1. Error handling patterns \u2014 does the project use try/except, Result types, or assertions? Show the actual pattern with a code example.
|
|
2389
|
-
2. Backward compatibility approach \u2014 does the project prefer duck typing (try/except TypeError) over introspection (inspect.signature)? Defensive patterns over clever ones?
|
|
2390
|
-
3. Testing infrastructure \u2014 test runner, key fixtures, how to add a new test. Agents that understand the test infra write tests that pass.
|
|
2391
|
-
4. "Prefer X over Y" guidelines \u2014 for common trade-offs in this codebase (e.g. "prefer composition over inheritance", "prefer simple try/except over type checking").
|
|
2392
|
-
5. Anti-patterns \u2014 2-3 specific things NOT to do in this codebase. These prevent agents from choosing overengineered solutions.
|
|
2393
|
-
Keep this section tight \u2014 bullet points, not prose. Each line should be a decision rule an agent can act on.`;
|
|
2406
|
+
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.`;
|
|
2394
2407
|
var GENERATION_SYSTEM_PROMPT = `${ROLE_AND_CONTEXT}
|
|
2395
2408
|
|
|
2396
2409
|
${CONFIG_FILE_TYPES}
|
|
2397
2410
|
|
|
2398
|
-
|
|
2399
|
-
1. CLAUDE.md / README accuracy \u2014 do documented commands, paths, and architecture match the actual codebase?
|
|
2400
|
-
2. Missing skills \u2014 are there detected tools/frameworks that should have dedicated skills?
|
|
2401
|
-
3. Duplicate or overlapping skills \u2014 can any be merged or removed?
|
|
2402
|
-
4. Undocumented conventions \u2014 are there code patterns (commit style, async patterns, error handling) not captured in docs?
|
|
2403
|
-
5. Stale references \u2014 do docs mention removed files, renamed commands, or outdated patterns?
|
|
2411
|
+
${AUDIT_CHECKLIST}
|
|
2404
2412
|
|
|
2405
2413
|
${EXCLUSIONS}
|
|
2406
2414
|
|
|
@@ -2445,6 +2453,8 @@ var CORE_GENERATION_PROMPT = `${ROLE_AND_CONTEXT}
|
|
|
2445
2453
|
|
|
2446
2454
|
${CONFIG_FILE_TYPES}
|
|
2447
2455
|
|
|
2456
|
+
${AUDIT_CHECKLIST}
|
|
2457
|
+
|
|
2448
2458
|
${EXCLUSIONS}
|
|
2449
2459
|
|
|
2450
2460
|
${OUTPUT_FORMAT}
|
|
@@ -2464,10 +2474,10 @@ CoreSetup schema:
|
|
|
2464
2474
|
},
|
|
2465
2475
|
"codex": {
|
|
2466
2476
|
"agentsMd": "string (markdown content for AGENTS.md)",
|
|
2467
|
-
"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)" }]
|
|
2468
2478
|
},
|
|
2469
2479
|
"cursor": {
|
|
2470
|
-
"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)" }],
|
|
2471
2481
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
2472
2482
|
},
|
|
2473
2483
|
"copilot": {
|
|
@@ -2509,8 +2519,7 @@ Structure:
|
|
|
2509
2519
|
- Have a validation gate: "Verify X before proceeding to the next step"
|
|
2510
2520
|
- Specify dependencies: "This step uses the output from Step N"
|
|
2511
2521
|
4. "## Examples" \u2014 at least one example showing: User says \u2192 Actions taken \u2192 Result. The example should mirror how existing code in the project is structured.
|
|
2512
|
-
5. "##
|
|
2513
|
-
6. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
|
|
2522
|
+
5. "## Common Issues" (required) \u2014 specific error messages and their fixes. Not "check your config" but "If you see 'Connection refused on port 5432': 1. Verify postgres is running: docker ps | grep postgres 2. Check .env has correct DATABASE_URL"
|
|
2514
2523
|
|
|
2515
2524
|
Rules:
|
|
2516
2525
|
- Max 150 lines. Focus on actionable instructions, not documentation prose.
|
|
@@ -2520,6 +2529,7 @@ Rules:
|
|
|
2520
2529
|
- Reference actual commands, paths, and packages from the project context provided.
|
|
2521
2530
|
- Do NOT include YAML frontmatter \u2014 it will be generated separately.
|
|
2522
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.
|
|
2523
2533
|
|
|
2524
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.
|
|
2525
2535
|
|
|
@@ -2566,32 +2576,40 @@ Rules:
|
|
|
2566
2576
|
- Update the "fileDescriptions" to reflect any changes you make.
|
|
2567
2577
|
|
|
2568
2578
|
Quality constraints \u2014 your changes are scored, so do not break these:
|
|
2569
|
-
- 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.
|
|
2570
2580
|
- Avoid vague instructions ("follow best practices", "write clean code", "ensure quality").
|
|
2571
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.
|
|
2572
2583
|
- Use backticks for every file path, command, and identifier.
|
|
2573
2584
|
- Keep skill content under 150 lines, focused on actionable instructions.
|
|
2574
2585
|
- Only reference file paths that actually exist in the project.`;
|
|
2575
|
-
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).
|
|
2576
2587
|
|
|
2577
2588
|
You will receive:
|
|
2578
2589
|
1. Git diffs showing what code changed
|
|
2579
|
-
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules)
|
|
2580
|
-
3. Project context (languages, frameworks)
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
-
|
|
2584
|
-
-
|
|
2585
|
-
-
|
|
2586
|
-
-
|
|
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:
|
|
2587
2610
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
2588
|
-
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2611
|
+
- Do NOT modify CALIBER_LEARNINGS.md \u2014 it is managed separately
|
|
2589
2612
|
- Preserve any references to CALIBER_LEARNINGS.md in CLAUDE.md
|
|
2590
|
-
- If a doc doesn't need updating, return null for it
|
|
2591
|
-
- For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them. Keep under 150 lines.
|
|
2592
|
-
- For README.md: update setup instructions, API docs, or feature descriptions if affected
|
|
2593
|
-
- Only reference file paths that exist in the project
|
|
2594
|
-
- Use backticks for all file paths, commands, and identifiers
|
|
2595
2613
|
|
|
2596
2614
|
Return a JSON object with this exact shape:
|
|
2597
2615
|
{
|
|
@@ -2615,15 +2633,8 @@ You receive a chronological sequence of events from a Claude Code session. Most
|
|
|
2615
2633
|
|
|
2616
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.
|
|
2617
2635
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
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.
|
|
2621
|
-
2. **Environment gotchas**: Commands that need specific env vars, flags, or preconditions to work in this project.
|
|
2622
|
-
3. **Retry patterns**: When something had to be called multiple times with different arguments before succeeding.
|
|
2623
|
-
4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
|
|
2624
|
-
5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
|
|
2625
|
-
6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
|
|
2626
|
-
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.
|
|
2627
2638
|
|
|
2628
2639
|
DO NOT extract:
|
|
2629
2640
|
- Descriptions of what the code does or how features work (e.g. "compression removes comments" or "skeleton extraction creates outlines")
|
|
@@ -2634,7 +2645,15 @@ DO NOT extract:
|
|
|
2634
2645
|
- **Session-specific file paths, worktree locations, or branch names** \u2014 these are ephemeral and won't apply to future sessions
|
|
2635
2646
|
- **Implementation details of a feature being built** \u2014 the learning should be about HOW to work in this project, not WHAT was built
|
|
2636
2647
|
|
|
2637
|
-
|
|
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.
|
|
2638
2657
|
|
|
2639
2658
|
From these observations, produce:
|
|
2640
2659
|
|
|
@@ -2713,6 +2732,10 @@ Be thorough \u2014 reason from:
|
|
|
2713
2732
|
- File extensions and their frequency distribution
|
|
2714
2733
|
- Directory structure and naming conventions
|
|
2715
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)
|
|
2716
2739
|
- Infrastructure-as-code files (Terraform, CloudFormation, Pulumi, Dockerfiles, k8s manifests)
|
|
2717
2740
|
- CI/CD configs (.github/workflows, .gitlab-ci.yml, Jenkinsfile)
|
|
2718
2741
|
|
|
@@ -2754,7 +2777,7 @@ init_config();
|
|
|
2754
2777
|
import fs6 from "fs";
|
|
2755
2778
|
import path6 from "path";
|
|
2756
2779
|
import crypto from "crypto";
|
|
2757
|
-
import { execSync as
|
|
2780
|
+
import { execSync as execSync6 } from "child_process";
|
|
2758
2781
|
var CACHE_VERSION = 1;
|
|
2759
2782
|
var CACHE_DIR = ".caliber/cache";
|
|
2760
2783
|
var CACHE_FILE = "fingerprint.json";
|
|
@@ -2763,7 +2786,7 @@ function getCachePath(dir) {
|
|
|
2763
2786
|
}
|
|
2764
2787
|
function getGitHead(dir) {
|
|
2765
2788
|
try {
|
|
2766
|
-
return
|
|
2789
|
+
return execSync6("git rev-parse HEAD", {
|
|
2767
2790
|
cwd: dir,
|
|
2768
2791
|
encoding: "utf-8",
|
|
2769
2792
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2775,7 +2798,7 @@ function getGitHead(dir) {
|
|
|
2775
2798
|
}
|
|
2776
2799
|
function getDirtySignature(dir) {
|
|
2777
2800
|
try {
|
|
2778
|
-
const output =
|
|
2801
|
+
const output = execSync6("git diff --name-only HEAD", {
|
|
2779
2802
|
cwd: dir,
|
|
2780
2803
|
encoding: "utf-8",
|
|
2781
2804
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3613,15 +3636,15 @@ init_config();
|
|
|
3613
3636
|
// src/utils/dependencies.ts
|
|
3614
3637
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3615
3638
|
import { join as join2 } from "path";
|
|
3616
|
-
function readFileOrNull2(
|
|
3639
|
+
function readFileOrNull2(path40) {
|
|
3617
3640
|
try {
|
|
3618
|
-
return readFileSync2(
|
|
3641
|
+
return readFileSync2(path40, "utf-8");
|
|
3619
3642
|
} catch {
|
|
3620
3643
|
return null;
|
|
3621
3644
|
}
|
|
3622
3645
|
}
|
|
3623
|
-
function readJsonOrNull(
|
|
3624
|
-
const content = readFileOrNull2(
|
|
3646
|
+
function readJsonOrNull(path40) {
|
|
3647
|
+
const content = readFileOrNull2(path40);
|
|
3625
3648
|
if (!content) return null;
|
|
3626
3649
|
try {
|
|
3627
3650
|
return JSON.parse(content);
|
|
@@ -4274,38 +4297,11 @@ function appendLearningsBlock(content) {
|
|
|
4274
4297
|
function getCursorLearningsRule() {
|
|
4275
4298
|
return { filename: CURSOR_LEARNINGS_FILENAME, content: CURSOR_LEARNINGS_CONTENT };
|
|
4276
4299
|
}
|
|
4277
|
-
var SKILLS_HEADING = "## Available Skills";
|
|
4278
|
-
function truncateDescription(description) {
|
|
4279
|
-
const match = description.match(/^[^.]*\.\s/);
|
|
4280
|
-
if (match) return match[0].trim();
|
|
4281
|
-
if (description.length > 80) return description.slice(0, 77) + "...";
|
|
4282
|
-
return description;
|
|
4283
|
-
}
|
|
4284
|
-
function appendSkillListing(content, skills, pathPrefix) {
|
|
4285
|
-
if (skills.length === 0) return content;
|
|
4286
|
-
if (content.includes(SKILLS_HEADING)) return content;
|
|
4287
|
-
const lines = [
|
|
4288
|
-
"",
|
|
4289
|
-
SKILLS_HEADING,
|
|
4290
|
-
"",
|
|
4291
|
-
"The following skill files are available in this repo. Read them when working on related areas:",
|
|
4292
|
-
""
|
|
4293
|
-
];
|
|
4294
|
-
for (const skill of skills) {
|
|
4295
|
-
const shortDesc = truncateDescription(skill.description);
|
|
4296
|
-
lines.push(`- \`${pathPrefix}${skill.name}/SKILL.md\` \u2014 ${shortDesc}`);
|
|
4297
|
-
}
|
|
4298
|
-
return content.trimEnd() + "\n" + lines.join("\n") + "\n";
|
|
4299
|
-
}
|
|
4300
4300
|
|
|
4301
4301
|
// src/writers/claude/index.ts
|
|
4302
4302
|
function writeClaudeConfig(config) {
|
|
4303
4303
|
const written = [];
|
|
4304
|
-
|
|
4305
|
-
if (config.skills?.length) {
|
|
4306
|
-
claudeMd = appendSkillListing(claudeMd, config.skills, ".claude/skills/");
|
|
4307
|
-
}
|
|
4308
|
-
fs10.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(claudeMd)));
|
|
4304
|
+
fs10.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(config.claudeMd)));
|
|
4309
4305
|
written.push("CLAUDE.md");
|
|
4310
4306
|
if (config.skills?.length) {
|
|
4311
4307
|
for (const skill of config.skills) {
|
|
@@ -4398,11 +4394,7 @@ import fs12 from "fs";
|
|
|
4398
4394
|
import path12 from "path";
|
|
4399
4395
|
function writeCodexConfig(config) {
|
|
4400
4396
|
const written = [];
|
|
4401
|
-
|
|
4402
|
-
if (config.skills?.length) {
|
|
4403
|
-
agentsMd = appendSkillListing(agentsMd, config.skills, ".agents/skills/");
|
|
4404
|
-
}
|
|
4405
|
-
fs12.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(agentsMd)));
|
|
4397
|
+
fs12.writeFileSync("AGENTS.md", appendLearningsBlock(appendPreCommitBlock(config.agentsMd)));
|
|
4406
4398
|
written.push("AGENTS.md");
|
|
4407
4399
|
if (config.skills?.length) {
|
|
4408
4400
|
for (const skill of config.skills) {
|
|
@@ -5032,7 +5024,7 @@ function removeLearningHooks() {
|
|
|
5032
5024
|
// src/lib/state.ts
|
|
5033
5025
|
import fs22 from "fs";
|
|
5034
5026
|
import path18 from "path";
|
|
5035
|
-
import { execSync as
|
|
5027
|
+
import { execSync as execSync8 } from "child_process";
|
|
5036
5028
|
var STATE_FILE = path18.join(CALIBER_DIR, ".caliber-state.json");
|
|
5037
5029
|
function normalizeTargetAgent(value) {
|
|
5038
5030
|
if (Array.isArray(value)) return value;
|
|
@@ -5060,7 +5052,7 @@ function writeState(state) {
|
|
|
5060
5052
|
}
|
|
5061
5053
|
function getCurrentHeadSha() {
|
|
5062
5054
|
try {
|
|
5063
|
-
return
|
|
5055
|
+
return execSync8("git rev-parse HEAD", {
|
|
5064
5056
|
encoding: "utf-8",
|
|
5065
5057
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5066
5058
|
}).trim();
|
|
@@ -5673,7 +5665,7 @@ function checkGrounding(dir) {
|
|
|
5673
5665
|
|
|
5674
5666
|
// src/scoring/checks/accuracy.ts
|
|
5675
5667
|
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
5676
|
-
import { execSync as
|
|
5668
|
+
import { execSync as execSync9 } from "child_process";
|
|
5677
5669
|
import { join as join5 } from "path";
|
|
5678
5670
|
function validateReferences(dir) {
|
|
5679
5671
|
const configContent = collectPrimaryConfigContent(dir);
|
|
@@ -5682,13 +5674,13 @@ function validateReferences(dir) {
|
|
|
5682
5674
|
}
|
|
5683
5675
|
function detectGitDrift(dir) {
|
|
5684
5676
|
try {
|
|
5685
|
-
|
|
5677
|
+
execSync9("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe", "pipe", "pipe"] });
|
|
5686
5678
|
} catch {
|
|
5687
5679
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: false };
|
|
5688
5680
|
}
|
|
5689
5681
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".cursor/rules"];
|
|
5690
5682
|
try {
|
|
5691
|
-
const headTimestamp =
|
|
5683
|
+
const headTimestamp = execSync9(
|
|
5692
5684
|
"git log -1 --format=%ct HEAD",
|
|
5693
5685
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5694
5686
|
).trim();
|
|
@@ -5709,7 +5701,7 @@ function detectGitDrift(dir) {
|
|
|
5709
5701
|
let latestConfigCommitHash = null;
|
|
5710
5702
|
for (const file of configFiles) {
|
|
5711
5703
|
try {
|
|
5712
|
-
const hash =
|
|
5704
|
+
const hash = execSync9(
|
|
5713
5705
|
`git log -1 --format=%H -- "${file}"`,
|
|
5714
5706
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5715
5707
|
).trim();
|
|
@@ -5718,7 +5710,7 @@ function detectGitDrift(dir) {
|
|
|
5718
5710
|
latestConfigCommitHash = hash;
|
|
5719
5711
|
} else {
|
|
5720
5712
|
try {
|
|
5721
|
-
|
|
5713
|
+
execSync9(
|
|
5722
5714
|
`git merge-base --is-ancestor ${latestConfigCommitHash} ${hash}`,
|
|
5723
5715
|
{ cwd: dir, stdio: ["pipe", "pipe", "pipe"] }
|
|
5724
5716
|
);
|
|
@@ -5733,12 +5725,12 @@ function detectGitDrift(dir) {
|
|
|
5733
5725
|
return { commitsSinceConfigUpdate: 0, lastConfigCommit: null, isGitRepo: true };
|
|
5734
5726
|
}
|
|
5735
5727
|
try {
|
|
5736
|
-
const countStr =
|
|
5728
|
+
const countStr = execSync9(
|
|
5737
5729
|
`git rev-list --count ${latestConfigCommitHash}..HEAD`,
|
|
5738
5730
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5739
5731
|
).trim();
|
|
5740
5732
|
const commitsSince = parseInt(countStr, 10) || 0;
|
|
5741
|
-
const lastDate =
|
|
5733
|
+
const lastDate = execSync9(
|
|
5742
5734
|
`git log -1 --format=%ci ${latestConfigCommitHash}`,
|
|
5743
5735
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5744
5736
|
).trim();
|
|
@@ -5810,12 +5802,12 @@ function checkAccuracy(dir) {
|
|
|
5810
5802
|
|
|
5811
5803
|
// src/scoring/checks/freshness.ts
|
|
5812
5804
|
import { existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
5813
|
-
import { execSync as
|
|
5805
|
+
import { execSync as execSync10 } from "child_process";
|
|
5814
5806
|
import { join as join6 } from "path";
|
|
5815
5807
|
function getCommitsSinceConfigUpdate(dir) {
|
|
5816
5808
|
const configFiles = ["CLAUDE.md", "AGENTS.md", ".cursorrules"];
|
|
5817
5809
|
try {
|
|
5818
|
-
const headTimestamp =
|
|
5810
|
+
const headTimestamp = execSync10(
|
|
5819
5811
|
"git log -1 --format=%ct HEAD",
|
|
5820
5812
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5821
5813
|
).trim();
|
|
@@ -5835,12 +5827,12 @@ function getCommitsSinceConfigUpdate(dir) {
|
|
|
5835
5827
|
}
|
|
5836
5828
|
for (const file of configFiles) {
|
|
5837
5829
|
try {
|
|
5838
|
-
const hash =
|
|
5830
|
+
const hash = execSync10(
|
|
5839
5831
|
`git log -1 --format=%H -- "${file}"`,
|
|
5840
5832
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5841
5833
|
).trim();
|
|
5842
5834
|
if (hash) {
|
|
5843
|
-
const countStr =
|
|
5835
|
+
const countStr = execSync10(
|
|
5844
5836
|
`git rev-list --count ${hash}..HEAD`,
|
|
5845
5837
|
{ cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5846
5838
|
).trim();
|
|
@@ -5958,11 +5950,11 @@ function checkFreshness(dir) {
|
|
|
5958
5950
|
|
|
5959
5951
|
// src/scoring/checks/bonus.ts
|
|
5960
5952
|
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "fs";
|
|
5961
|
-
import { execSync as
|
|
5953
|
+
import { execSync as execSync11 } from "child_process";
|
|
5962
5954
|
import { join as join7 } from "path";
|
|
5963
5955
|
function hasPreCommitHook(dir) {
|
|
5964
5956
|
try {
|
|
5965
|
-
const gitDir =
|
|
5957
|
+
const gitDir = execSync11("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
5966
5958
|
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
5967
5959
|
const content = readFileOrNull(hookPath);
|
|
5968
5960
|
return content ? content.includes("caliber") : false;
|
|
@@ -6209,7 +6201,7 @@ var AGENT_DISPLAY_NAMES = {
|
|
|
6209
6201
|
codex: "Codex"
|
|
6210
6202
|
};
|
|
6211
6203
|
var CATEGORY_LABELS = {
|
|
6212
|
-
existence: { icon: "\u{1F4C1}", label: "FILES &
|
|
6204
|
+
existence: { icon: "\u{1F4C1}", label: "FILES & CONFIG" },
|
|
6213
6205
|
quality: { icon: "\u26A1", label: "QUALITY" },
|
|
6214
6206
|
grounding: { icon: "\u{1F3AF}", label: "GROUNDING" },
|
|
6215
6207
|
accuracy: { icon: "\u{1F50D}", label: "ACCURACY" },
|
|
@@ -6391,7 +6383,7 @@ import fs24 from "fs";
|
|
|
6391
6383
|
import path20 from "path";
|
|
6392
6384
|
import os5 from "os";
|
|
6393
6385
|
import crypto4 from "crypto";
|
|
6394
|
-
import { execSync as
|
|
6386
|
+
import { execSync as execSync12 } from "child_process";
|
|
6395
6387
|
var CONFIG_DIR2 = path20.join(os5.homedir(), ".caliber");
|
|
6396
6388
|
var CONFIG_FILE2 = path20.join(CONFIG_DIR2, "config.json");
|
|
6397
6389
|
var runtimeDisabled = false;
|
|
@@ -6418,7 +6410,7 @@ function getMachineId() {
|
|
|
6418
6410
|
}
|
|
6419
6411
|
function getGitEmailHash() {
|
|
6420
6412
|
try {
|
|
6421
|
-
const email =
|
|
6413
|
+
const email = execSync12("git config user.email", { encoding: "utf-8" }).trim();
|
|
6422
6414
|
if (!email) return void 0;
|
|
6423
6415
|
return crypto4.createHash("sha256").update(email).digest("hex");
|
|
6424
6416
|
} catch {
|
|
@@ -7468,11 +7460,11 @@ function countIssuePoints(issues) {
|
|
|
7468
7460
|
}
|
|
7469
7461
|
async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
7470
7462
|
const existsCache = /* @__PURE__ */ new Map();
|
|
7471
|
-
const cachedExists = (
|
|
7472
|
-
const cached = existsCache.get(
|
|
7463
|
+
const cachedExists = (path40) => {
|
|
7464
|
+
const cached = existsCache.get(path40);
|
|
7473
7465
|
if (cached !== void 0) return cached;
|
|
7474
|
-
const result = existsSync9(
|
|
7475
|
-
existsCache.set(
|
|
7466
|
+
const result = existsSync9(path40);
|
|
7467
|
+
existsCache.set(path40, result);
|
|
7476
7468
|
return result;
|
|
7477
7469
|
};
|
|
7478
7470
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -7487,7 +7479,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
7487
7479
|
bestLostPoints = lostPoints;
|
|
7488
7480
|
}
|
|
7489
7481
|
if (lostPoints === 0) {
|
|
7490
|
-
if (callbacks?.onStatus) callbacks.onStatus("
|
|
7482
|
+
if (callbacks?.onStatus) callbacks.onStatus("Config passes all scoring checks");
|
|
7491
7483
|
return bestSetup;
|
|
7492
7484
|
}
|
|
7493
7485
|
const pointIssues = issues.filter((i) => i.pointsLost > 0);
|
|
@@ -7497,7 +7489,7 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks) {
|
|
|
7497
7489
|
}
|
|
7498
7490
|
const refined = await applyTargetedFixes(currentSetup, issues);
|
|
7499
7491
|
if (!refined) {
|
|
7500
|
-
if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current
|
|
7492
|
+
if (callbacks?.onStatus) callbacks.onStatus("Refinement failed, keeping current config");
|
|
7501
7493
|
return bestSetup;
|
|
7502
7494
|
}
|
|
7503
7495
|
sessionHistory.push({
|
|
@@ -7590,7 +7582,7 @@ Return ONLY the fixed content as a JSON object with keys ${targets.map((t) => `"
|
|
|
7590
7582
|
}
|
|
7591
7583
|
}
|
|
7592
7584
|
async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
7593
|
-
const spinner = ora2("Validating
|
|
7585
|
+
const spinner = ora2("Validating config against scoring criteria...").start();
|
|
7594
7586
|
try {
|
|
7595
7587
|
const refined = await scoreAndRefine(setup, dir, sessionHistory, {
|
|
7596
7588
|
onStatus: (msg) => {
|
|
@@ -7598,9 +7590,9 @@ async function runScoreRefineWithSpinner(setup, dir, sessionHistory) {
|
|
|
7598
7590
|
}
|
|
7599
7591
|
});
|
|
7600
7592
|
if (refined !== setup) {
|
|
7601
|
-
spinner.succeed("
|
|
7593
|
+
spinner.succeed("Config refined based on scoring feedback");
|
|
7602
7594
|
} else {
|
|
7603
|
-
spinner.succeed("
|
|
7595
|
+
spinner.succeed("Config passes scoring validation");
|
|
7604
7596
|
}
|
|
7605
7597
|
return refined;
|
|
7606
7598
|
} catch (err) {
|
|
@@ -8155,7 +8147,7 @@ var REFINE_MESSAGES = [
|
|
|
8155
8147
|
"Rebalancing permissions and tool settings...",
|
|
8156
8148
|
"Refining skills and workflows...",
|
|
8157
8149
|
"Updating rules to match your preferences...",
|
|
8158
|
-
"Finalizing the revised
|
|
8150
|
+
"Finalizing the revised config..."
|
|
8159
8151
|
];
|
|
8160
8152
|
var SpinnerMessages = class {
|
|
8161
8153
|
spinner;
|
|
@@ -8325,10 +8317,10 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8325
8317
|
if (!isValid) {
|
|
8326
8318
|
console.log(chalk11.dim(" This doesn't look like a config change request."));
|
|
8327
8319
|
console.log(chalk11.dim(" Describe what to add, remove, or modify in your configs."));
|
|
8328
|
-
console.log(chalk11.dim(' Type "done" to accept the current
|
|
8320
|
+
console.log(chalk11.dim(' Type "done" to accept the current config.\n'));
|
|
8329
8321
|
continue;
|
|
8330
8322
|
}
|
|
8331
|
-
const refineSpinner = ora3("Refining
|
|
8323
|
+
const refineSpinner = ora3("Refining config...").start();
|
|
8332
8324
|
const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
|
|
8333
8325
|
refineMessages.start();
|
|
8334
8326
|
const refined = await refineSetup(setup, message, sessionHistory);
|
|
@@ -8340,12 +8332,12 @@ async function refineLoop(currentSetup, sessionHistory, summarizeSetup2, printSu
|
|
|
8340
8332
|
role: "assistant",
|
|
8341
8333
|
content: summarizeSetup2("Applied changes", refined)
|
|
8342
8334
|
});
|
|
8343
|
-
refineSpinner.succeed("
|
|
8335
|
+
refineSpinner.succeed("Config updated");
|
|
8344
8336
|
printSummary(refined);
|
|
8345
8337
|
console.log(chalk11.dim('Type "done" to accept, or describe more changes.'));
|
|
8346
8338
|
} else {
|
|
8347
8339
|
refineSpinner.fail("Refinement failed \u2014 could not parse AI response.");
|
|
8348
|
-
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.'));
|
|
8349
8341
|
}
|
|
8350
8342
|
}
|
|
8351
8343
|
}
|
|
@@ -8394,7 +8386,7 @@ function printSetupSummary(setup) {
|
|
|
8394
8386
|
const fileDescriptions = setup.fileDescriptions;
|
|
8395
8387
|
const deletions = setup.deletions;
|
|
8396
8388
|
console.log("");
|
|
8397
|
-
console.log(chalk12.bold(" Your tailored
|
|
8389
|
+
console.log(chalk12.bold(" Your tailored config:\n"));
|
|
8398
8390
|
const getDescription = (filePath) => {
|
|
8399
8391
|
return fileDescriptions?.[filePath];
|
|
8400
8392
|
};
|
|
@@ -8623,6 +8615,7 @@ Only dismiss checks that truly don't apply. Examples:
|
|
|
8623
8615
|
- "Build/test/lint commands" for a GitOps/Helm/Terraform/config repo with no build system
|
|
8624
8616
|
- "Build/test/lint commands" for a repo with only YAML, HCL, or config files and no package.json/Makefile
|
|
8625
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
|
|
8626
8619
|
|
|
8627
8620
|
Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
|
|
8628
8621
|
|
|
@@ -8635,7 +8628,7 @@ Top files: ${topFiles}
|
|
|
8635
8628
|
|
|
8636
8629
|
Failing checks:
|
|
8637
8630
|
${JSON.stringify(checkList, null, 2)}`,
|
|
8638
|
-
maxTokens:
|
|
8631
|
+
maxTokens: 500,
|
|
8639
8632
|
...fastModel ? { model: fastModel } : {}
|
|
8640
8633
|
});
|
|
8641
8634
|
if (!Array.isArray(result.dismissed)) return [];
|
|
@@ -8645,6 +8638,68 @@ ${JSON.stringify(checkList, null, 2)}`,
|
|
|
8645
8638
|
}
|
|
8646
8639
|
}
|
|
8647
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
|
+
|
|
8648
8703
|
// src/commands/init.ts
|
|
8649
8704
|
function log(verbose, ...args) {
|
|
8650
8705
|
if (verbose) console.log(chalk14.dim(` [verbose] ${args.map(String).join(" ")}`));
|
|
@@ -8665,7 +8720,7 @@ async function initCommand(options) {
|
|
|
8665
8720
|
console.log(chalk14.dim(" Scan your project and generate tailored config files for"));
|
|
8666
8721
|
console.log(chalk14.dim(" Claude Code, Cursor, Codex, and GitHub Copilot.\n"));
|
|
8667
8722
|
console.log(title.bold(" How it works:\n"));
|
|
8668
|
-
console.log(chalk14.dim(" 1.
|
|
8723
|
+
console.log(chalk14.dim(" 1. Connect Link your LLM provider and select your agents"));
|
|
8669
8724
|
console.log(chalk14.dim(" 2. Engine Detect stack, generate configs & skills in parallel"));
|
|
8670
8725
|
console.log(chalk14.dim(" 3. Review See all changes \u2014 accept, refine, or decline"));
|
|
8671
8726
|
console.log(chalk14.dim(" 4. Finalize Score check and auto-sync hooks\n"));
|
|
@@ -8678,7 +8733,7 @@ async function initCommand(options) {
|
|
|
8678
8733
|
console.log(chalk14.yellow(" Caliber will still generate config files, but they won't be auto-installed.\n"));
|
|
8679
8734
|
}
|
|
8680
8735
|
const report = options.debugReport ? new DebugReport() : null;
|
|
8681
|
-
console.log(title.bold(" Step 1/4 \u2014
|
|
8736
|
+
console.log(title.bold(" Step 1/4 \u2014 Connect\n"));
|
|
8682
8737
|
let config = loadConfig();
|
|
8683
8738
|
if (!config) {
|
|
8684
8739
|
console.log(chalk14.dim(" No LLM provider configured yet.\n"));
|
|
@@ -8687,7 +8742,7 @@ async function initCommand(options) {
|
|
|
8687
8742
|
});
|
|
8688
8743
|
config = loadConfig();
|
|
8689
8744
|
if (!config) {
|
|
8690
|
-
console.log(chalk14.red("
|
|
8745
|
+
console.log(chalk14.red(" Configuration cancelled or failed.\n"));
|
|
8691
8746
|
throw new Error("__exit__");
|
|
8692
8747
|
}
|
|
8693
8748
|
console.log(chalk14.green(" \u2713 Provider saved\n"));
|
|
@@ -8698,7 +8753,7 @@ async function initCommand(options) {
|
|
|
8698
8753
|
const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
|
|
8699
8754
|
console.log(chalk14.dim(modelLine + "\n"));
|
|
8700
8755
|
if (report) {
|
|
8701
|
-
report.markStep("Provider
|
|
8756
|
+
report.markStep("Provider connection");
|
|
8702
8757
|
report.addSection("LLM Provider", `- **Provider**: ${config.provider}
|
|
8703
8758
|
- **Model**: ${displayModel}
|
|
8704
8759
|
- **Fast model**: ${fastModel || "none"}`);
|
|
@@ -8719,7 +8774,7 @@ async function initCommand(options) {
|
|
|
8719
8774
|
`));
|
|
8720
8775
|
trackInitAgentSelected(targetAgent, agentAutoDetected);
|
|
8721
8776
|
let baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
8722
|
-
console.log(chalk14.dim("\n Current
|
|
8777
|
+
console.log(chalk14.dim("\n Current config score:"));
|
|
8723
8778
|
displayScoreSummary(baselineScore);
|
|
8724
8779
|
if (options.verbose) {
|
|
8725
8780
|
for (const c of baselineScore.checks) {
|
|
@@ -8748,7 +8803,7 @@ async function initCommand(options) {
|
|
|
8748
8803
|
const failingCount = baselineScore.checks.filter((c) => !c.passed).length;
|
|
8749
8804
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
8750
8805
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
8751
|
-
console.log(chalk14.bold.green(" Your
|
|
8806
|
+
console.log(chalk14.bold.green(" Your config is already optimal \u2014 nothing to change.\n"));
|
|
8752
8807
|
console.log(chalk14.dim(" Run ") + chalk14.hex("#83D1EB")("caliber init --force") + chalk14.dim(" to regenerate anyway.\n"));
|
|
8753
8808
|
if (!options.force) return;
|
|
8754
8809
|
}
|
|
@@ -8785,7 +8840,7 @@ async function initCommand(options) {
|
|
|
8785
8840
|
const TASK_CONFIG = display.add("Generating configs", { depth: 1, pipelineLabel: "Generate" });
|
|
8786
8841
|
const TASK_SKILLS_GEN = display.add("Generating skills", { depth: 2, pipelineLabel: "Skills" });
|
|
8787
8842
|
const TASK_SKILLS_SEARCH = display.add("Searching community skills", { depth: 1, pipelineLabel: "Search", pipelineRow: 1 });
|
|
8788
|
-
const TASK_SCORE_REFINE = display.add("Validating & refining
|
|
8843
|
+
const TASK_SCORE_REFINE = display.add("Validating & refining config", { pipelineLabel: "Validate" });
|
|
8789
8844
|
display.start();
|
|
8790
8845
|
display.enableWaitingContent();
|
|
8791
8846
|
try {
|
|
@@ -8933,7 +8988,7 @@ async function initCommand(options) {
|
|
|
8933
8988
|
display.update(TASK_SCORE_REFINE, "done", "Skipped");
|
|
8934
8989
|
}
|
|
8935
8990
|
} else {
|
|
8936
|
-
display.update(TASK_SCORE_REFINE, "failed", "No
|
|
8991
|
+
display.update(TASK_SCORE_REFINE, "failed", "No config to validate");
|
|
8937
8992
|
}
|
|
8938
8993
|
} catch (err) {
|
|
8939
8994
|
display.stop();
|
|
@@ -8954,7 +9009,7 @@ async function initCommand(options) {
|
|
|
8954
9009
|
Done in ${timeStr}
|
|
8955
9010
|
`));
|
|
8956
9011
|
if (!generatedSetup) {
|
|
8957
|
-
console.log(chalk14.red(" Failed to generate
|
|
9012
|
+
console.log(chalk14.red(" Failed to generate config."));
|
|
8958
9013
|
writeErrorLog(config, rawOutput, void 0, genStopReason);
|
|
8959
9014
|
if (rawOutput) {
|
|
8960
9015
|
console.log(chalk14.dim("\nRaw LLM output (JSON parse failed):"));
|
|
@@ -8964,7 +9019,7 @@ async function initCommand(options) {
|
|
|
8964
9019
|
}
|
|
8965
9020
|
if (report) {
|
|
8966
9021
|
if (rawOutput) report.addCodeBlock("Generation: Raw LLM Response", rawOutput);
|
|
8967
|
-
report.addJson("Generation: Parsed
|
|
9022
|
+
report.addJson("Generation: Parsed Config", generatedSetup);
|
|
8968
9023
|
}
|
|
8969
9024
|
log(options.verbose, `Generation completed: ${elapsedMs}ms, stopReason: ${genStopReason || "end_turn"}`);
|
|
8970
9025
|
console.log(title.bold(" Step 3/4 \u2014 Review\n"));
|
|
@@ -9027,7 +9082,7 @@ async function initCommand(options) {
|
|
|
9027
9082
|
}
|
|
9028
9083
|
cleanupStaging();
|
|
9029
9084
|
if (action === "decline") {
|
|
9030
|
-
console.log(chalk14.dim("
|
|
9085
|
+
console.log(chalk14.dim("Declined. No files were modified."));
|
|
9031
9086
|
return;
|
|
9032
9087
|
}
|
|
9033
9088
|
console.log(title.bold("\n Step 4/4 \u2014 Finalize\n"));
|
|
@@ -9039,7 +9094,7 @@ async function initCommand(options) {
|
|
|
9039
9094
|
const { default: ora9 } = await import("ora");
|
|
9040
9095
|
const writeSpinner = ora9("Writing config files...").start();
|
|
9041
9096
|
try {
|
|
9042
|
-
if (targetAgent.includes("codex") && !
|
|
9097
|
+
if (targetAgent.includes("codex") && !fs32.existsSync("AGENTS.md") && !generatedSetup.codex) {
|
|
9043
9098
|
const claude = generatedSetup.claude;
|
|
9044
9099
|
const cursor = generatedSetup.cursor;
|
|
9045
9100
|
const agentRefs = [];
|
|
@@ -9111,6 +9166,7 @@ ${agentRefs.join(" ")}
|
|
|
9111
9166
|
|-------|--------|--------|-----|
|
|
9112
9167
|
` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
9113
9168
|
}
|
|
9169
|
+
recordScore(afterScore, "init");
|
|
9114
9170
|
displayScoreDelta(baselineScore, afterScore);
|
|
9115
9171
|
if (options.verbose) {
|
|
9116
9172
|
log(options.verbose, `Final score: ${afterScore.score}/100`);
|
|
@@ -9158,12 +9214,12 @@ ${agentRefs.join(" ")}
|
|
|
9158
9214
|
if (targetAgent.includes("cursor")) installCursorLearningHooks();
|
|
9159
9215
|
}
|
|
9160
9216
|
}
|
|
9161
|
-
console.log(chalk14.bold.green("\n
|
|
9217
|
+
console.log(chalk14.bold.green("\n Configuration complete!"));
|
|
9162
9218
|
console.log(chalk14.dim(" Your AI agents now understand your project's architecture, build commands,"));
|
|
9163
9219
|
console.log(chalk14.dim(" testing patterns, and conventions. All changes are backed up automatically.\n"));
|
|
9164
9220
|
const done = chalk14.green("\u2713");
|
|
9165
9221
|
const skip = chalk14.dim("\u2013");
|
|
9166
|
-
console.log(chalk14.bold(" What was
|
|
9222
|
+
console.log(chalk14.bold(" What was configured:\n"));
|
|
9167
9223
|
console.log(` ${done} Config generated ${title("caliber score")} ${chalk14.dim("for full breakdown")}`);
|
|
9168
9224
|
console.log(` ${done} Docs auto-refresh ${chalk14.dim("agents run caliber refresh before commits")}`);
|
|
9169
9225
|
if (hasLearnableAgent) {
|
|
@@ -9188,9 +9244,9 @@ ${agentRefs.join(" ")}
|
|
|
9188
9244
|
}
|
|
9189
9245
|
if (report) {
|
|
9190
9246
|
report.markStep("Finished");
|
|
9191
|
-
const reportPath =
|
|
9247
|
+
const reportPath = path25.join(process.cwd(), ".caliber", "debug-report.md");
|
|
9192
9248
|
report.write(reportPath);
|
|
9193
|
-
console.log(chalk14.dim(` Debug report written to ${
|
|
9249
|
+
console.log(chalk14.dim(` Debug report written to ${path25.relative(process.cwd(), reportPath)}
|
|
9194
9250
|
`));
|
|
9195
9251
|
}
|
|
9196
9252
|
}
|
|
@@ -9199,7 +9255,7 @@ ${agentRefs.join(" ")}
|
|
|
9199
9255
|
import chalk15 from "chalk";
|
|
9200
9256
|
import ora4 from "ora";
|
|
9201
9257
|
function undoCommand() {
|
|
9202
|
-
const spinner = ora4("Reverting
|
|
9258
|
+
const spinner = ora4("Reverting config changes...").start();
|
|
9203
9259
|
try {
|
|
9204
9260
|
const { restored, removed } = undoSetup();
|
|
9205
9261
|
if (restored.length === 0 && removed.length === 0) {
|
|
@@ -9207,7 +9263,7 @@ function undoCommand() {
|
|
|
9207
9263
|
return;
|
|
9208
9264
|
}
|
|
9209
9265
|
trackUndoExecuted();
|
|
9210
|
-
spinner.succeed("
|
|
9266
|
+
spinner.succeed("Config reverted successfully.\n");
|
|
9211
9267
|
if (restored.length > 0) {
|
|
9212
9268
|
console.log(chalk15.cyan(" Restored from backup:"));
|
|
9213
9269
|
for (const file of restored) {
|
|
@@ -9229,7 +9285,7 @@ function undoCommand() {
|
|
|
9229
9285
|
|
|
9230
9286
|
// src/commands/status.ts
|
|
9231
9287
|
import chalk16 from "chalk";
|
|
9232
|
-
import
|
|
9288
|
+
import fs33 from "fs";
|
|
9233
9289
|
init_config();
|
|
9234
9290
|
async function statusCommand(options) {
|
|
9235
9291
|
const config = loadConfig();
|
|
@@ -9250,13 +9306,13 @@ async function statusCommand(options) {
|
|
|
9250
9306
|
console.log(` LLM: ${chalk16.yellow("Not configured")} \u2014 run ${chalk16.hex("#83D1EB")("caliber config")}`);
|
|
9251
9307
|
}
|
|
9252
9308
|
if (!manifest) {
|
|
9253
|
-
console.log(`
|
|
9309
|
+
console.log(` Config: ${chalk16.dim("No config applied")}`);
|
|
9254
9310
|
console.log(chalk16.dim("\n Run ") + chalk16.hex("#83D1EB")("caliber init") + chalk16.dim(" to get started.\n"));
|
|
9255
9311
|
return;
|
|
9256
9312
|
}
|
|
9257
9313
|
console.log(` Files managed: ${chalk16.cyan(manifest.entries.length.toString())}`);
|
|
9258
9314
|
for (const entry of manifest.entries) {
|
|
9259
|
-
const exists =
|
|
9315
|
+
const exists = fs33.existsSync(entry.path);
|
|
9260
9316
|
const icon = exists ? chalk16.green("\u2713") : chalk16.red("\u2717");
|
|
9261
9317
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
9262
9318
|
}
|
|
@@ -9277,7 +9333,7 @@ async function regenerateCommand(options) {
|
|
|
9277
9333
|
}
|
|
9278
9334
|
const manifest = readManifest();
|
|
9279
9335
|
if (!manifest) {
|
|
9280
|
-
console.log(chalk17.yellow("No existing
|
|
9336
|
+
console.log(chalk17.yellow("No existing config found. Run ") + chalk17.hex("#83D1EB")("caliber init") + chalk17.yellow(" first."));
|
|
9281
9337
|
throw new Error("__exit__");
|
|
9282
9338
|
}
|
|
9283
9339
|
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
@@ -9288,10 +9344,10 @@ async function regenerateCommand(options) {
|
|
|
9288
9344
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
9289
9345
|
displayScoreSummary(baselineScore);
|
|
9290
9346
|
if (baselineScore.score === 100) {
|
|
9291
|
-
console.log(chalk17.green(" Your
|
|
9347
|
+
console.log(chalk17.green(" Your config is already at 100/100 \u2014 nothing to regenerate.\n"));
|
|
9292
9348
|
return;
|
|
9293
9349
|
}
|
|
9294
|
-
const genSpinner = ora5("Regenerating
|
|
9350
|
+
const genSpinner = ora5("Regenerating config...").start();
|
|
9295
9351
|
const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES, { showElapsedTime: true });
|
|
9296
9352
|
genMessages.start();
|
|
9297
9353
|
let generatedSetup = null;
|
|
@@ -9322,10 +9378,10 @@ async function regenerateCommand(options) {
|
|
|
9322
9378
|
}
|
|
9323
9379
|
genMessages.stop();
|
|
9324
9380
|
if (!generatedSetup) {
|
|
9325
|
-
genSpinner.fail("Failed to regenerate
|
|
9381
|
+
genSpinner.fail("Failed to regenerate config.");
|
|
9326
9382
|
throw new Error("__exit__");
|
|
9327
9383
|
}
|
|
9328
|
-
genSpinner.succeed("
|
|
9384
|
+
genSpinner.succeed("Config regenerated");
|
|
9329
9385
|
generatedSetup = await runScoreRefineWithSpinner(generatedSetup, process.cwd(), []);
|
|
9330
9386
|
const setupFiles = collectSetupFiles(generatedSetup, targetAgent);
|
|
9331
9387
|
const staged = stageFiles(setupFiles, process.cwd());
|
|
@@ -9352,7 +9408,7 @@ async function regenerateCommand(options) {
|
|
|
9352
9408
|
await openReview(reviewMethod, staged.stagedFiles);
|
|
9353
9409
|
}
|
|
9354
9410
|
const action = await select6({
|
|
9355
|
-
message: "Apply regenerated
|
|
9411
|
+
message: "Apply regenerated config?",
|
|
9356
9412
|
choices: [
|
|
9357
9413
|
{ name: "Accept and apply", value: "accept" },
|
|
9358
9414
|
{ name: "Decline", value: "decline" }
|
|
@@ -9411,21 +9467,21 @@ async function regenerateCommand(options) {
|
|
|
9411
9467
|
}
|
|
9412
9468
|
|
|
9413
9469
|
// src/commands/score.ts
|
|
9414
|
-
import
|
|
9470
|
+
import fs34 from "fs";
|
|
9415
9471
|
import os7 from "os";
|
|
9416
|
-
import
|
|
9472
|
+
import path26 from "path";
|
|
9417
9473
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9418
9474
|
import chalk18 from "chalk";
|
|
9419
9475
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", "CALIBER_LEARNINGS.md"];
|
|
9420
9476
|
var CONFIG_DIRS = [".claude", ".cursor"];
|
|
9421
9477
|
function scoreBaseRef(ref, target) {
|
|
9422
9478
|
if (!/^[\w.\-\/~^@{}]+$/.test(ref)) return null;
|
|
9423
|
-
const tmpDir =
|
|
9479
|
+
const tmpDir = fs34.mkdtempSync(path26.join(os7.tmpdir(), "caliber-compare-"));
|
|
9424
9480
|
try {
|
|
9425
9481
|
for (const file of CONFIG_FILES) {
|
|
9426
9482
|
try {
|
|
9427
9483
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9428
|
-
|
|
9484
|
+
fs34.writeFileSync(path26.join(tmpDir, file), content);
|
|
9429
9485
|
} catch {
|
|
9430
9486
|
}
|
|
9431
9487
|
}
|
|
@@ -9433,10 +9489,10 @@ function scoreBaseRef(ref, target) {
|
|
|
9433
9489
|
try {
|
|
9434
9490
|
const files = execFileSync2("git", ["ls-tree", "-r", "--name-only", ref, `${dir}/`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter(Boolean);
|
|
9435
9491
|
for (const file of files) {
|
|
9436
|
-
const filePath =
|
|
9437
|
-
|
|
9492
|
+
const filePath = path26.join(tmpDir, file);
|
|
9493
|
+
fs34.mkdirSync(path26.dirname(filePath), { recursive: true });
|
|
9438
9494
|
const content = execFileSync2("git", ["show", `${ref}:${file}`], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
9439
|
-
|
|
9495
|
+
fs34.writeFileSync(filePath, content);
|
|
9440
9496
|
}
|
|
9441
9497
|
} catch {
|
|
9442
9498
|
}
|
|
@@ -9446,7 +9502,7 @@ function scoreBaseRef(ref, target) {
|
|
|
9446
9502
|
} catch {
|
|
9447
9503
|
return null;
|
|
9448
9504
|
} finally {
|
|
9449
|
-
|
|
9505
|
+
fs34.rmSync(tmpDir, { recursive: true, force: true });
|
|
9450
9506
|
}
|
|
9451
9507
|
}
|
|
9452
9508
|
async function scoreCommand(options) {
|
|
@@ -9454,6 +9510,7 @@ async function scoreCommand(options) {
|
|
|
9454
9510
|
const target = options.agent ?? readState()?.targetAgent;
|
|
9455
9511
|
const result = computeLocalScore(dir, target);
|
|
9456
9512
|
trackScoreComputed(result.score, target);
|
|
9513
|
+
recordScore(result, "score");
|
|
9457
9514
|
if (options.compare) {
|
|
9458
9515
|
const baseResult = scoreBaseRef(options.compare, target);
|
|
9459
9516
|
if (!baseResult) {
|
|
@@ -9496,9 +9553,9 @@ async function scoreCommand(options) {
|
|
|
9496
9553
|
const separator = chalk18.gray(" " + "\u2500".repeat(53));
|
|
9497
9554
|
console.log(separator);
|
|
9498
9555
|
if (result.score < 40) {
|
|
9499
|
-
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."));
|
|
9500
9557
|
} else if (result.score < 70) {
|
|
9501
|
-
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."));
|
|
9502
9559
|
} else {
|
|
9503
9560
|
console.log(chalk18.green(" Looking good!") + chalk18.gray(" Run ") + chalk18.hex("#83D1EB")("caliber regenerate") + chalk18.gray(" to rebuild from scratch."));
|
|
9504
9561
|
}
|
|
@@ -9506,13 +9563,13 @@ async function scoreCommand(options) {
|
|
|
9506
9563
|
}
|
|
9507
9564
|
|
|
9508
9565
|
// src/commands/refresh.ts
|
|
9509
|
-
import
|
|
9510
|
-
import
|
|
9566
|
+
import fs38 from "fs";
|
|
9567
|
+
import path30 from "path";
|
|
9511
9568
|
import chalk19 from "chalk";
|
|
9512
9569
|
import ora6 from "ora";
|
|
9513
9570
|
|
|
9514
9571
|
// src/lib/git-diff.ts
|
|
9515
|
-
import { execSync as
|
|
9572
|
+
import { execSync as execSync14 } from "child_process";
|
|
9516
9573
|
var MAX_DIFF_BYTES = 1e5;
|
|
9517
9574
|
var DOC_PATTERNS = [
|
|
9518
9575
|
"CLAUDE.md",
|
|
@@ -9527,7 +9584,7 @@ function excludeArgs() {
|
|
|
9527
9584
|
}
|
|
9528
9585
|
function safeExec(cmd) {
|
|
9529
9586
|
try {
|
|
9530
|
-
return
|
|
9587
|
+
return execSync14(cmd, {
|
|
9531
9588
|
encoding: "utf-8",
|
|
9532
9589
|
stdio: ["pipe", "pipe", "pipe"],
|
|
9533
9590
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -9585,48 +9642,48 @@ function collectDiff(lastSha) {
|
|
|
9585
9642
|
}
|
|
9586
9643
|
|
|
9587
9644
|
// src/writers/refresh.ts
|
|
9588
|
-
import
|
|
9589
|
-
import
|
|
9645
|
+
import fs35 from "fs";
|
|
9646
|
+
import path27 from "path";
|
|
9590
9647
|
function writeRefreshDocs(docs) {
|
|
9591
9648
|
const written = [];
|
|
9592
9649
|
if (docs.claudeMd) {
|
|
9593
|
-
|
|
9650
|
+
fs35.writeFileSync("CLAUDE.md", appendLearningsBlock(appendPreCommitBlock(docs.claudeMd)));
|
|
9594
9651
|
written.push("CLAUDE.md");
|
|
9595
9652
|
}
|
|
9596
9653
|
if (docs.readmeMd) {
|
|
9597
|
-
|
|
9654
|
+
fs35.writeFileSync("README.md", docs.readmeMd);
|
|
9598
9655
|
written.push("README.md");
|
|
9599
9656
|
}
|
|
9600
9657
|
if (docs.cursorrules) {
|
|
9601
|
-
|
|
9658
|
+
fs35.writeFileSync(".cursorrules", docs.cursorrules);
|
|
9602
9659
|
written.push(".cursorrules");
|
|
9603
9660
|
}
|
|
9604
9661
|
if (docs.cursorRules) {
|
|
9605
|
-
const rulesDir =
|
|
9606
|
-
if (!
|
|
9662
|
+
const rulesDir = path27.join(".cursor", "rules");
|
|
9663
|
+
if (!fs35.existsSync(rulesDir)) fs35.mkdirSync(rulesDir, { recursive: true });
|
|
9607
9664
|
for (const rule of docs.cursorRules) {
|
|
9608
|
-
|
|
9665
|
+
fs35.writeFileSync(path27.join(rulesDir, rule.filename), rule.content);
|
|
9609
9666
|
written.push(`.cursor/rules/${rule.filename}`);
|
|
9610
9667
|
}
|
|
9611
9668
|
}
|
|
9612
9669
|
if (docs.claudeSkills) {
|
|
9613
|
-
const skillsDir =
|
|
9614
|
-
if (!
|
|
9670
|
+
const skillsDir = path27.join(".claude", "skills");
|
|
9671
|
+
if (!fs35.existsSync(skillsDir)) fs35.mkdirSync(skillsDir, { recursive: true });
|
|
9615
9672
|
for (const skill of docs.claudeSkills) {
|
|
9616
|
-
|
|
9673
|
+
fs35.writeFileSync(path27.join(skillsDir, skill.filename), skill.content);
|
|
9617
9674
|
written.push(`.claude/skills/${skill.filename}`);
|
|
9618
9675
|
}
|
|
9619
9676
|
}
|
|
9620
9677
|
if (docs.copilotInstructions) {
|
|
9621
|
-
|
|
9622
|
-
|
|
9678
|
+
fs35.mkdirSync(".github", { recursive: true });
|
|
9679
|
+
fs35.writeFileSync(path27.join(".github", "copilot-instructions.md"), appendLearningsBlock(appendPreCommitBlock(docs.copilotInstructions)));
|
|
9623
9680
|
written.push(".github/copilot-instructions.md");
|
|
9624
9681
|
}
|
|
9625
9682
|
if (docs.copilotInstructionFiles) {
|
|
9626
|
-
const instructionsDir =
|
|
9627
|
-
|
|
9683
|
+
const instructionsDir = path27.join(".github", "instructions");
|
|
9684
|
+
fs35.mkdirSync(instructionsDir, { recursive: true });
|
|
9628
9685
|
for (const file of docs.copilotInstructionFiles) {
|
|
9629
|
-
|
|
9686
|
+
fs35.writeFileSync(path27.join(instructionsDir, file.filename), file.content);
|
|
9630
9687
|
written.push(`.github/instructions/${file.filename}`);
|
|
9631
9688
|
}
|
|
9632
9689
|
}
|
|
@@ -9652,6 +9709,12 @@ function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection,
|
|
|
9652
9709
|
if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
|
|
9653
9710
|
if (projectContext.languages?.length) parts.push(`Languages: ${projectContext.languages.join(", ")}`);
|
|
9654
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
|
+
}
|
|
9655
9718
|
parts.push(`
|
|
9656
9719
|
Changed files: ${diff.changedFiles.join(", ")}`);
|
|
9657
9720
|
parts.push(`Summary: ${diff.summary}`);
|
|
@@ -9706,8 +9769,8 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
9706
9769
|
}
|
|
9707
9770
|
|
|
9708
9771
|
// src/learner/writer.ts
|
|
9709
|
-
import
|
|
9710
|
-
import
|
|
9772
|
+
import fs36 from "fs";
|
|
9773
|
+
import path28 from "path";
|
|
9711
9774
|
|
|
9712
9775
|
// src/learner/utils.ts
|
|
9713
9776
|
var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
|
|
@@ -9824,20 +9887,20 @@ function deduplicateLearnedItems(existing, incoming) {
|
|
|
9824
9887
|
}
|
|
9825
9888
|
function writeLearnedSectionTo(filePath, header, existing, incoming, mode) {
|
|
9826
9889
|
const { merged, newCount, newItems } = deduplicateLearnedItems(existing, incoming);
|
|
9827
|
-
|
|
9828
|
-
if (mode)
|
|
9890
|
+
fs36.writeFileSync(filePath, header + merged + "\n");
|
|
9891
|
+
if (mode) fs36.chmodSync(filePath, mode);
|
|
9829
9892
|
return { newCount, newItems };
|
|
9830
9893
|
}
|
|
9831
9894
|
function writeLearnedSection(content) {
|
|
9832
9895
|
return writeLearnedSectionTo(LEARNINGS_FILE, LEARNINGS_HEADER, readLearnedSection(), content);
|
|
9833
9896
|
}
|
|
9834
9897
|
function writeLearnedSkill(skill) {
|
|
9835
|
-
const skillDir =
|
|
9836
|
-
if (!
|
|
9837
|
-
const skillPath =
|
|
9838
|
-
if (!skill.isNew &&
|
|
9839
|
-
const existing =
|
|
9840
|
-
|
|
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);
|
|
9841
9904
|
} else {
|
|
9842
9905
|
const frontmatter = [
|
|
9843
9906
|
"---",
|
|
@@ -9846,12 +9909,12 @@ function writeLearnedSkill(skill) {
|
|
|
9846
9909
|
"---",
|
|
9847
9910
|
""
|
|
9848
9911
|
].join("\n");
|
|
9849
|
-
|
|
9912
|
+
fs36.writeFileSync(skillPath, frontmatter + skill.content);
|
|
9850
9913
|
}
|
|
9851
9914
|
return skillPath;
|
|
9852
9915
|
}
|
|
9853
9916
|
function writePersonalLearnedSection(content) {
|
|
9854
|
-
if (!
|
|
9917
|
+
if (!fs36.existsSync(AUTH_DIR)) fs36.mkdirSync(AUTH_DIR, { recursive: true });
|
|
9855
9918
|
return writeLearnedSectionTo(PERSONAL_LEARNINGS_FILE, PERSONAL_LEARNINGS_HEADER, readPersonalLearnings(), content, 384);
|
|
9856
9919
|
}
|
|
9857
9920
|
function addLearning(bullet, scope = "project") {
|
|
@@ -9864,38 +9927,38 @@ function addLearning(bullet, scope = "project") {
|
|
|
9864
9927
|
return { file: LEARNINGS_FILE, added: result.newCount > 0 };
|
|
9865
9928
|
}
|
|
9866
9929
|
function readPersonalLearnings() {
|
|
9867
|
-
if (!
|
|
9868
|
-
const content =
|
|
9930
|
+
if (!fs36.existsSync(PERSONAL_LEARNINGS_FILE)) return null;
|
|
9931
|
+
const content = fs36.readFileSync(PERSONAL_LEARNINGS_FILE, "utf-8");
|
|
9869
9932
|
const bullets = content.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9870
9933
|
return bullets || null;
|
|
9871
9934
|
}
|
|
9872
9935
|
function readLearnedSection() {
|
|
9873
|
-
if (
|
|
9874
|
-
const content2 =
|
|
9936
|
+
if (fs36.existsSync(LEARNINGS_FILE)) {
|
|
9937
|
+
const content2 = fs36.readFileSync(LEARNINGS_FILE, "utf-8");
|
|
9875
9938
|
const bullets = content2.split("\n").filter((l) => l.startsWith("- ")).join("\n");
|
|
9876
9939
|
return bullets || null;
|
|
9877
9940
|
}
|
|
9878
9941
|
const claudeMdPath = "CLAUDE.md";
|
|
9879
|
-
if (!
|
|
9880
|
-
const content =
|
|
9942
|
+
if (!fs36.existsSync(claudeMdPath)) return null;
|
|
9943
|
+
const content = fs36.readFileSync(claudeMdPath, "utf-8");
|
|
9881
9944
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9882
9945
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9883
9946
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
9884
9947
|
return content.slice(startIdx + LEARNED_START.length, endIdx).trim() || null;
|
|
9885
9948
|
}
|
|
9886
9949
|
function migrateInlineLearnings() {
|
|
9887
|
-
if (
|
|
9950
|
+
if (fs36.existsSync(LEARNINGS_FILE)) return false;
|
|
9888
9951
|
const claudeMdPath = "CLAUDE.md";
|
|
9889
|
-
if (!
|
|
9890
|
-
const content =
|
|
9952
|
+
if (!fs36.existsSync(claudeMdPath)) return false;
|
|
9953
|
+
const content = fs36.readFileSync(claudeMdPath, "utf-8");
|
|
9891
9954
|
const startIdx = content.indexOf(LEARNED_START);
|
|
9892
9955
|
const endIdx = content.indexOf(LEARNED_END);
|
|
9893
9956
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
9894
9957
|
const section = content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
9895
9958
|
if (!section) return false;
|
|
9896
|
-
|
|
9959
|
+
fs36.writeFileSync(LEARNINGS_FILE, LEARNINGS_HEADER + section + "\n");
|
|
9897
9960
|
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + LEARNED_END.length);
|
|
9898
|
-
|
|
9961
|
+
fs36.writeFileSync(claudeMdPath, cleaned.replace(/\n{3,}/g, "\n\n").trim() + "\n");
|
|
9899
9962
|
return true;
|
|
9900
9963
|
}
|
|
9901
9964
|
|
|
@@ -9907,11 +9970,11 @@ function log2(quiet, ...args) {
|
|
|
9907
9970
|
function discoverGitRepos(parentDir) {
|
|
9908
9971
|
const repos = [];
|
|
9909
9972
|
try {
|
|
9910
|
-
const entries =
|
|
9973
|
+
const entries = fs38.readdirSync(parentDir, { withFileTypes: true });
|
|
9911
9974
|
for (const entry of entries) {
|
|
9912
9975
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
9913
|
-
const childPath =
|
|
9914
|
-
if (
|
|
9976
|
+
const childPath = path30.join(parentDir, entry.name);
|
|
9977
|
+
if (fs38.existsSync(path30.join(childPath, ".git"))) {
|
|
9915
9978
|
repos.push(childPath);
|
|
9916
9979
|
}
|
|
9917
9980
|
}
|
|
@@ -9919,11 +9982,19 @@ function discoverGitRepos(parentDir) {
|
|
|
9919
9982
|
}
|
|
9920
9983
|
return repos.sort();
|
|
9921
9984
|
}
|
|
9985
|
+
var REFRESH_COOLDOWN_MS = 3e4;
|
|
9922
9986
|
async function refreshSingleRepo(repoDir, options) {
|
|
9923
9987
|
const quiet = !!options.quiet;
|
|
9924
9988
|
const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
|
|
9925
9989
|
const state = readState();
|
|
9926
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
|
+
}
|
|
9927
9998
|
const diff = collectDiff(lastSha);
|
|
9928
9999
|
const currentSha = getCurrentHeadSha();
|
|
9929
10000
|
if (!diff.hasChanges) {
|
|
@@ -9940,23 +10011,32 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
9940
10011
|
const projectContext = {
|
|
9941
10012
|
languages: fingerprint.languages,
|
|
9942
10013
|
frameworks: fingerprint.frameworks,
|
|
9943
|
-
packageName: fingerprint.packageName
|
|
10014
|
+
packageName: fingerprint.packageName,
|
|
10015
|
+
fileTree: fingerprint.fileTree
|
|
9944
10016
|
};
|
|
9945
10017
|
const workspaces = getDetectedWorkspaces(repoDir);
|
|
9946
10018
|
const sources2 = resolveAllSources(repoDir, [], workspaces);
|
|
9947
|
-
const
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
learnedSection,
|
|
9958
|
-
|
|
9959
|
-
|
|
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
|
+
}
|
|
9960
10040
|
if (!response.docsUpdated || response.docsUpdated.length === 0) {
|
|
9961
10041
|
spinner?.succeed(`${prefix}No doc updates needed`);
|
|
9962
10042
|
if (currentSha) {
|
|
@@ -9975,8 +10055,41 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
9975
10055
|
}
|
|
9976
10056
|
return;
|
|
9977
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
|
+
}
|
|
9978
10070
|
const written = writeRefreshDocs(response.updatedDocs);
|
|
9979
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");
|
|
9980
10093
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
9981
10094
|
for (const file of written) {
|
|
9982
10095
|
log2(quiet, ` ${chalk19.green("\u2713")} ${file}`);
|
|
@@ -10021,7 +10134,7 @@ async function refreshCommand(options) {
|
|
|
10021
10134
|
`));
|
|
10022
10135
|
const originalDir = process.cwd();
|
|
10023
10136
|
for (const repo of repos) {
|
|
10024
|
-
const repoName =
|
|
10137
|
+
const repoName = path30.basename(repo);
|
|
10025
10138
|
try {
|
|
10026
10139
|
process.chdir(repo);
|
|
10027
10140
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -10042,31 +10155,31 @@ async function refreshCommand(options) {
|
|
|
10042
10155
|
|
|
10043
10156
|
// src/commands/hooks.ts
|
|
10044
10157
|
import chalk20 from "chalk";
|
|
10045
|
-
import
|
|
10158
|
+
import fs40 from "fs";
|
|
10046
10159
|
|
|
10047
10160
|
// src/lib/hooks.ts
|
|
10048
10161
|
init_resolve_caliber();
|
|
10049
|
-
import
|
|
10050
|
-
import
|
|
10051
|
-
import { execSync as
|
|
10052
|
-
var SETTINGS_PATH2 =
|
|
10162
|
+
import fs39 from "fs";
|
|
10163
|
+
import path31 from "path";
|
|
10164
|
+
import { execSync as execSync15 } from "child_process";
|
|
10165
|
+
var SETTINGS_PATH2 = path31.join(".claude", "settings.json");
|
|
10053
10166
|
var REFRESH_TAIL = "refresh --quiet";
|
|
10054
10167
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
10055
10168
|
function getHookCommand() {
|
|
10056
10169
|
return `${resolveCaliber()} ${REFRESH_TAIL}`;
|
|
10057
10170
|
}
|
|
10058
10171
|
function readSettings2() {
|
|
10059
|
-
if (!
|
|
10172
|
+
if (!fs39.existsSync(SETTINGS_PATH2)) return {};
|
|
10060
10173
|
try {
|
|
10061
|
-
return JSON.parse(
|
|
10174
|
+
return JSON.parse(fs39.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
10062
10175
|
} catch {
|
|
10063
10176
|
return {};
|
|
10064
10177
|
}
|
|
10065
10178
|
}
|
|
10066
10179
|
function writeSettings2(settings) {
|
|
10067
|
-
const dir =
|
|
10068
|
-
if (!
|
|
10069
|
-
|
|
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));
|
|
10070
10183
|
}
|
|
10071
10184
|
function findHookIndex(sessionEnd) {
|
|
10072
10185
|
return sessionEnd.findIndex(
|
|
@@ -10128,20 +10241,20 @@ ${PRECOMMIT_END}`;
|
|
|
10128
10241
|
}
|
|
10129
10242
|
function getGitHooksDir() {
|
|
10130
10243
|
try {
|
|
10131
|
-
const gitDir =
|
|
10132
|
-
return
|
|
10244
|
+
const gitDir = execSync15("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10245
|
+
return path31.join(gitDir, "hooks");
|
|
10133
10246
|
} catch {
|
|
10134
10247
|
return null;
|
|
10135
10248
|
}
|
|
10136
10249
|
}
|
|
10137
10250
|
function getPreCommitPath() {
|
|
10138
10251
|
const hooksDir = getGitHooksDir();
|
|
10139
|
-
return hooksDir ?
|
|
10252
|
+
return hooksDir ? path31.join(hooksDir, "pre-commit") : null;
|
|
10140
10253
|
}
|
|
10141
10254
|
function isPreCommitHookInstalled() {
|
|
10142
10255
|
const hookPath = getPreCommitPath();
|
|
10143
|
-
if (!hookPath || !
|
|
10144
|
-
const content =
|
|
10256
|
+
if (!hookPath || !fs39.existsSync(hookPath)) return false;
|
|
10257
|
+
const content = fs39.readFileSync(hookPath, "utf-8");
|
|
10145
10258
|
return content.includes(PRECOMMIT_START);
|
|
10146
10259
|
}
|
|
10147
10260
|
function installPreCommitHook() {
|
|
@@ -10150,35 +10263,35 @@ function installPreCommitHook() {
|
|
|
10150
10263
|
}
|
|
10151
10264
|
const hookPath = getPreCommitPath();
|
|
10152
10265
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
10153
|
-
const hooksDir =
|
|
10154
|
-
if (!
|
|
10266
|
+
const hooksDir = path31.dirname(hookPath);
|
|
10267
|
+
if (!fs39.existsSync(hooksDir)) fs39.mkdirSync(hooksDir, { recursive: true });
|
|
10155
10268
|
let content = "";
|
|
10156
|
-
if (
|
|
10157
|
-
content =
|
|
10269
|
+
if (fs39.existsSync(hookPath)) {
|
|
10270
|
+
content = fs39.readFileSync(hookPath, "utf-8");
|
|
10158
10271
|
if (!content.endsWith("\n")) content += "\n";
|
|
10159
10272
|
content += "\n" + getPrecommitBlock() + "\n";
|
|
10160
10273
|
} else {
|
|
10161
10274
|
content = "#!/bin/sh\n\n" + getPrecommitBlock() + "\n";
|
|
10162
10275
|
}
|
|
10163
|
-
|
|
10164
|
-
|
|
10276
|
+
fs39.writeFileSync(hookPath, content);
|
|
10277
|
+
fs39.chmodSync(hookPath, 493);
|
|
10165
10278
|
return { installed: true, alreadyInstalled: false };
|
|
10166
10279
|
}
|
|
10167
10280
|
function removePreCommitHook() {
|
|
10168
10281
|
const hookPath = getPreCommitPath();
|
|
10169
|
-
if (!hookPath || !
|
|
10282
|
+
if (!hookPath || !fs39.existsSync(hookPath)) {
|
|
10170
10283
|
return { removed: false, notFound: true };
|
|
10171
10284
|
}
|
|
10172
|
-
let content =
|
|
10285
|
+
let content = fs39.readFileSync(hookPath, "utf-8");
|
|
10173
10286
|
if (!content.includes(PRECOMMIT_START)) {
|
|
10174
10287
|
return { removed: false, notFound: true };
|
|
10175
10288
|
}
|
|
10176
10289
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
10177
10290
|
content = content.replace(regex, "\n");
|
|
10178
10291
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
10179
|
-
|
|
10292
|
+
fs39.unlinkSync(hookPath);
|
|
10180
10293
|
} else {
|
|
10181
|
-
|
|
10294
|
+
fs39.writeFileSync(hookPath, content);
|
|
10182
10295
|
}
|
|
10183
10296
|
return { removed: true, notFound: false };
|
|
10184
10297
|
}
|
|
@@ -10227,11 +10340,11 @@ async function hooksCommand(options) {
|
|
|
10227
10340
|
console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
|
|
10228
10341
|
}
|
|
10229
10342
|
}
|
|
10230
|
-
if (
|
|
10343
|
+
if (fs40.existsSync(".claude")) {
|
|
10231
10344
|
const r = installLearningHooks();
|
|
10232
10345
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
|
|
10233
10346
|
}
|
|
10234
|
-
if (
|
|
10347
|
+
if (fs40.existsSync(".cursor")) {
|
|
10235
10348
|
const r = installCursorLearningHooks();
|
|
10236
10349
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
|
|
10237
10350
|
}
|
|
@@ -10399,8 +10512,8 @@ async function configCommand() {
|
|
|
10399
10512
|
}
|
|
10400
10513
|
|
|
10401
10514
|
// src/commands/learn.ts
|
|
10402
|
-
import
|
|
10403
|
-
import
|
|
10515
|
+
import fs44 from "fs";
|
|
10516
|
+
import path35 from "path";
|
|
10404
10517
|
import chalk23 from "chalk";
|
|
10405
10518
|
|
|
10406
10519
|
// src/learner/stdin.ts
|
|
@@ -10431,8 +10544,8 @@ function readStdin() {
|
|
|
10431
10544
|
}
|
|
10432
10545
|
|
|
10433
10546
|
// src/learner/storage.ts
|
|
10434
|
-
import
|
|
10435
|
-
import
|
|
10547
|
+
import fs41 from "fs";
|
|
10548
|
+
import path32 from "path";
|
|
10436
10549
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
10437
10550
|
var DEFAULT_STATE = {
|
|
10438
10551
|
sessionId: null,
|
|
@@ -10441,15 +10554,15 @@ var DEFAULT_STATE = {
|
|
|
10441
10554
|
lastAnalysisEventCount: 0
|
|
10442
10555
|
};
|
|
10443
10556
|
function ensureLearningDir() {
|
|
10444
|
-
if (!
|
|
10445
|
-
|
|
10557
|
+
if (!fs41.existsSync(getLearningDir())) {
|
|
10558
|
+
fs41.mkdirSync(getLearningDir(), { recursive: true });
|
|
10446
10559
|
}
|
|
10447
10560
|
}
|
|
10448
10561
|
function sessionFilePath() {
|
|
10449
|
-
return
|
|
10562
|
+
return path32.join(getLearningDir(), LEARNING_SESSION_FILE);
|
|
10450
10563
|
}
|
|
10451
10564
|
function stateFilePath() {
|
|
10452
|
-
return
|
|
10565
|
+
return path32.join(getLearningDir(), LEARNING_STATE_FILE);
|
|
10453
10566
|
}
|
|
10454
10567
|
function truncateResponse(response) {
|
|
10455
10568
|
const str = JSON.stringify(response);
|
|
@@ -10459,10 +10572,10 @@ function truncateResponse(response) {
|
|
|
10459
10572
|
function trimSessionFileIfNeeded(filePath) {
|
|
10460
10573
|
const state = readState2();
|
|
10461
10574
|
if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
|
|
10462
|
-
const lines =
|
|
10575
|
+
const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
10463
10576
|
if (lines.length > LEARNING_MAX_EVENTS) {
|
|
10464
10577
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
10465
|
-
|
|
10578
|
+
fs41.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
10466
10579
|
}
|
|
10467
10580
|
}
|
|
10468
10581
|
}
|
|
@@ -10470,19 +10583,19 @@ function appendEvent(event) {
|
|
|
10470
10583
|
ensureLearningDir();
|
|
10471
10584
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
10472
10585
|
const filePath = sessionFilePath();
|
|
10473
|
-
|
|
10586
|
+
fs41.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
10474
10587
|
trimSessionFileIfNeeded(filePath);
|
|
10475
10588
|
}
|
|
10476
10589
|
function appendPromptEvent(event) {
|
|
10477
10590
|
ensureLearningDir();
|
|
10478
10591
|
const filePath = sessionFilePath();
|
|
10479
|
-
|
|
10592
|
+
fs41.appendFileSync(filePath, JSON.stringify(event) + "\n");
|
|
10480
10593
|
trimSessionFileIfNeeded(filePath);
|
|
10481
10594
|
}
|
|
10482
10595
|
function readAllEvents() {
|
|
10483
10596
|
const filePath = sessionFilePath();
|
|
10484
|
-
if (!
|
|
10485
|
-
const lines =
|
|
10597
|
+
if (!fs41.existsSync(filePath)) return [];
|
|
10598
|
+
const lines = fs41.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
10486
10599
|
const events = [];
|
|
10487
10600
|
for (const line of lines) {
|
|
10488
10601
|
try {
|
|
@@ -10494,26 +10607,26 @@ function readAllEvents() {
|
|
|
10494
10607
|
}
|
|
10495
10608
|
function getEventCount() {
|
|
10496
10609
|
const filePath = sessionFilePath();
|
|
10497
|
-
if (!
|
|
10498
|
-
const content =
|
|
10610
|
+
if (!fs41.existsSync(filePath)) return 0;
|
|
10611
|
+
const content = fs41.readFileSync(filePath, "utf-8");
|
|
10499
10612
|
return content.split("\n").filter(Boolean).length;
|
|
10500
10613
|
}
|
|
10501
10614
|
function clearSession() {
|
|
10502
10615
|
const filePath = sessionFilePath();
|
|
10503
|
-
if (
|
|
10616
|
+
if (fs41.existsSync(filePath)) fs41.unlinkSync(filePath);
|
|
10504
10617
|
}
|
|
10505
10618
|
function readState2() {
|
|
10506
10619
|
const filePath = stateFilePath();
|
|
10507
|
-
if (!
|
|
10620
|
+
if (!fs41.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
10508
10621
|
try {
|
|
10509
|
-
return JSON.parse(
|
|
10622
|
+
return JSON.parse(fs41.readFileSync(filePath, "utf-8"));
|
|
10510
10623
|
} catch {
|
|
10511
10624
|
return { ...DEFAULT_STATE };
|
|
10512
10625
|
}
|
|
10513
10626
|
}
|
|
10514
10627
|
function writeState2(state) {
|
|
10515
10628
|
ensureLearningDir();
|
|
10516
|
-
|
|
10629
|
+
fs41.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
10517
10630
|
}
|
|
10518
10631
|
function resetState() {
|
|
10519
10632
|
writeState2({ ...DEFAULT_STATE });
|
|
@@ -10521,16 +10634,16 @@ function resetState() {
|
|
|
10521
10634
|
var LOCK_FILE2 = "finalize.lock";
|
|
10522
10635
|
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
10523
10636
|
function lockFilePath() {
|
|
10524
|
-
return
|
|
10637
|
+
return path32.join(getLearningDir(), LOCK_FILE2);
|
|
10525
10638
|
}
|
|
10526
10639
|
function acquireFinalizeLock() {
|
|
10527
10640
|
ensureLearningDir();
|
|
10528
10641
|
const lockPath = lockFilePath();
|
|
10529
|
-
if (
|
|
10642
|
+
if (fs41.existsSync(lockPath)) {
|
|
10530
10643
|
try {
|
|
10531
|
-
const stat =
|
|
10644
|
+
const stat = fs41.statSync(lockPath);
|
|
10532
10645
|
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
|
|
10533
|
-
const pid = parseInt(
|
|
10646
|
+
const pid = parseInt(fs41.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
10534
10647
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
10535
10648
|
return false;
|
|
10536
10649
|
}
|
|
@@ -10538,12 +10651,12 @@ function acquireFinalizeLock() {
|
|
|
10538
10651
|
} catch {
|
|
10539
10652
|
}
|
|
10540
10653
|
try {
|
|
10541
|
-
|
|
10654
|
+
fs41.unlinkSync(lockPath);
|
|
10542
10655
|
} catch {
|
|
10543
10656
|
}
|
|
10544
10657
|
}
|
|
10545
10658
|
try {
|
|
10546
|
-
|
|
10659
|
+
fs41.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
10547
10660
|
return true;
|
|
10548
10661
|
} catch {
|
|
10549
10662
|
return false;
|
|
@@ -10560,7 +10673,7 @@ function isProcessAlive(pid) {
|
|
|
10560
10673
|
function releaseFinalizeLock() {
|
|
10561
10674
|
const lockPath = lockFilePath();
|
|
10562
10675
|
try {
|
|
10563
|
-
if (
|
|
10676
|
+
if (fs41.existsSync(lockPath)) fs41.unlinkSync(lockPath);
|
|
10564
10677
|
} catch {
|
|
10565
10678
|
}
|
|
10566
10679
|
}
|
|
@@ -10605,22 +10718,24 @@ function sanitizeSecrets(text) {
|
|
|
10605
10718
|
}
|
|
10606
10719
|
|
|
10607
10720
|
// src/lib/notifications.ts
|
|
10608
|
-
import
|
|
10609
|
-
import
|
|
10721
|
+
import fs42 from "fs";
|
|
10722
|
+
import path33 from "path";
|
|
10610
10723
|
import chalk22 from "chalk";
|
|
10611
|
-
|
|
10724
|
+
function notificationFilePath() {
|
|
10725
|
+
return path33.join(getLearningDir(), "last-finalize-summary.json");
|
|
10726
|
+
}
|
|
10612
10727
|
function writeFinalizeSummary(summary) {
|
|
10613
10728
|
try {
|
|
10614
10729
|
ensureLearningDir();
|
|
10615
|
-
|
|
10730
|
+
fs42.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
|
|
10616
10731
|
} catch {
|
|
10617
10732
|
}
|
|
10618
10733
|
}
|
|
10619
10734
|
function checkPendingNotifications() {
|
|
10620
10735
|
try {
|
|
10621
|
-
if (!
|
|
10622
|
-
const raw =
|
|
10623
|
-
|
|
10736
|
+
if (!fs42.existsSync(notificationFilePath())) return;
|
|
10737
|
+
const raw = fs42.readFileSync(notificationFilePath(), "utf-8");
|
|
10738
|
+
fs42.unlinkSync(notificationFilePath());
|
|
10624
10739
|
const summary = JSON.parse(raw);
|
|
10625
10740
|
if (!summary.newItemCount || summary.newItemCount === 0) return;
|
|
10626
10741
|
const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
|
|
@@ -10636,7 +10751,7 @@ function checkPendingNotifications() {
|
|
|
10636
10751
|
console.log("");
|
|
10637
10752
|
} catch {
|
|
10638
10753
|
try {
|
|
10639
|
-
|
|
10754
|
+
fs42.unlinkSync(notificationFilePath());
|
|
10640
10755
|
} catch {
|
|
10641
10756
|
}
|
|
10642
10757
|
}
|
|
@@ -10675,10 +10790,25 @@ function trimEventsToFit(events, maxTokens) {
|
|
|
10675
10790
|
if (estimateTokens(formatted) <= maxTokens) return kept;
|
|
10676
10791
|
return kept.slice(-50);
|
|
10677
10792
|
}
|
|
10793
|
+
function normalizeAnalysisResult(parsed) {
|
|
10794
|
+
let section = parsed.claudeMdLearnedSection ?? null;
|
|
10795
|
+
if (Array.isArray(section)) {
|
|
10796
|
+
section = section.join("\n");
|
|
10797
|
+
}
|
|
10798
|
+
if (section !== null && typeof section !== "string") {
|
|
10799
|
+
section = String(section);
|
|
10800
|
+
}
|
|
10801
|
+
return {
|
|
10802
|
+
...parsed,
|
|
10803
|
+
claudeMdLearnedSection: section,
|
|
10804
|
+
skills: parsed.skills ?? null,
|
|
10805
|
+
explanations: parsed.explanations ?? []
|
|
10806
|
+
};
|
|
10807
|
+
}
|
|
10678
10808
|
function parseAnalysisResponse(raw) {
|
|
10679
10809
|
const cleaned = stripMarkdownFences(raw);
|
|
10680
10810
|
try {
|
|
10681
|
-
return JSON.parse(cleaned);
|
|
10811
|
+
return normalizeAnalysisResult(JSON.parse(cleaned));
|
|
10682
10812
|
} catch {
|
|
10683
10813
|
}
|
|
10684
10814
|
const json = extractJson(cleaned);
|
|
@@ -10686,7 +10816,7 @@ function parseAnalysisResponse(raw) {
|
|
|
10686
10816
|
return { claudeMdLearnedSection: null, skills: null, explanations: ["LLM response could not be parsed."] };
|
|
10687
10817
|
}
|
|
10688
10818
|
try {
|
|
10689
|
-
return JSON.parse(json);
|
|
10819
|
+
return normalizeAnalysisResult(JSON.parse(json));
|
|
10690
10820
|
} catch {
|
|
10691
10821
|
return { claudeMdLearnedSection: null, skills: null, explanations: ["LLM response contained invalid JSON."] };
|
|
10692
10822
|
}
|
|
@@ -10773,8 +10903,8 @@ function calculateSessionWaste(events) {
|
|
|
10773
10903
|
init_config();
|
|
10774
10904
|
|
|
10775
10905
|
// src/learner/roi.ts
|
|
10776
|
-
import
|
|
10777
|
-
import
|
|
10906
|
+
import fs43 from "fs";
|
|
10907
|
+
import path34 from "path";
|
|
10778
10908
|
var DEFAULT_TOTALS = {
|
|
10779
10909
|
totalWasteTokens: 0,
|
|
10780
10910
|
totalWasteSeconds: 0,
|
|
@@ -10788,19 +10918,19 @@ var DEFAULT_TOTALS = {
|
|
|
10788
10918
|
lastSessionTimestamp: ""
|
|
10789
10919
|
};
|
|
10790
10920
|
function roiFilePath() {
|
|
10791
|
-
return
|
|
10921
|
+
return path34.join(getLearningDir(), LEARNING_ROI_FILE);
|
|
10792
10922
|
}
|
|
10793
10923
|
function readROIStats() {
|
|
10794
10924
|
const filePath = roiFilePath();
|
|
10795
|
-
if (!
|
|
10925
|
+
if (!fs43.existsSync(filePath)) {
|
|
10796
10926
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
10797
10927
|
}
|
|
10798
10928
|
try {
|
|
10799
|
-
return JSON.parse(
|
|
10929
|
+
return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
|
|
10800
10930
|
} catch {
|
|
10801
10931
|
try {
|
|
10802
10932
|
const corruptPath = filePath + ".corrupt";
|
|
10803
|
-
|
|
10933
|
+
fs43.renameSync(filePath, corruptPath);
|
|
10804
10934
|
console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
|
|
10805
10935
|
} catch {
|
|
10806
10936
|
}
|
|
@@ -10809,7 +10939,7 @@ function readROIStats() {
|
|
|
10809
10939
|
}
|
|
10810
10940
|
function writeROIStats(stats) {
|
|
10811
10941
|
ensureLearningDir();
|
|
10812
|
-
|
|
10942
|
+
fs43.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
|
|
10813
10943
|
}
|
|
10814
10944
|
function recalculateTotals(stats) {
|
|
10815
10945
|
const totals = stats.totals;
|
|
@@ -11015,9 +11145,9 @@ var AUTO_SETTLE_MS = 200;
|
|
|
11015
11145
|
var INCREMENTAL_INTERVAL = 50;
|
|
11016
11146
|
function writeFinalizeError(message) {
|
|
11017
11147
|
try {
|
|
11018
|
-
const errorPath =
|
|
11019
|
-
if (!
|
|
11020
|
-
|
|
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({
|
|
11021
11151
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11022
11152
|
error: message,
|
|
11023
11153
|
pid: process.pid
|
|
@@ -11027,9 +11157,9 @@ function writeFinalizeError(message) {
|
|
|
11027
11157
|
}
|
|
11028
11158
|
function readFinalizeError() {
|
|
11029
11159
|
try {
|
|
11030
|
-
const errorPath =
|
|
11031
|
-
if (!
|
|
11032
|
-
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"));
|
|
11033
11163
|
} catch {
|
|
11034
11164
|
return null;
|
|
11035
11165
|
}
|
|
@@ -11076,14 +11206,14 @@ async function learnObserveCommand(options) {
|
|
|
11076
11206
|
const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
|
|
11077
11207
|
const bin = resolveCaliber2();
|
|
11078
11208
|
const { spawn: spawn4 } = await import("child_process");
|
|
11079
|
-
const logPath =
|
|
11080
|
-
if (!
|
|
11081
|
-
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");
|
|
11082
11212
|
spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
|
|
11083
11213
|
detached: true,
|
|
11084
11214
|
stdio: ["ignore", logFd, logFd]
|
|
11085
11215
|
}).unref();
|
|
11086
|
-
|
|
11216
|
+
fs44.closeSync(logFd);
|
|
11087
11217
|
} catch {
|
|
11088
11218
|
}
|
|
11089
11219
|
}
|
|
@@ -11290,7 +11420,7 @@ async function learnFinalizeCommand(options) {
|
|
|
11290
11420
|
}
|
|
11291
11421
|
async function learnInstallCommand() {
|
|
11292
11422
|
let anyInstalled = false;
|
|
11293
|
-
if (
|
|
11423
|
+
if (fs44.existsSync(".claude")) {
|
|
11294
11424
|
const r = installLearningHooks();
|
|
11295
11425
|
if (r.installed) {
|
|
11296
11426
|
console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
|
|
@@ -11299,7 +11429,7 @@ async function learnInstallCommand() {
|
|
|
11299
11429
|
console.log(chalk23.dim(" Claude Code hooks already installed"));
|
|
11300
11430
|
}
|
|
11301
11431
|
}
|
|
11302
|
-
if (
|
|
11432
|
+
if (fs44.existsSync(".cursor")) {
|
|
11303
11433
|
const r = installCursorLearningHooks();
|
|
11304
11434
|
if (r.installed) {
|
|
11305
11435
|
console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
|
|
@@ -11308,7 +11438,7 @@ async function learnInstallCommand() {
|
|
|
11308
11438
|
console.log(chalk23.dim(" Cursor hooks already installed"));
|
|
11309
11439
|
}
|
|
11310
11440
|
}
|
|
11311
|
-
if (!
|
|
11441
|
+
if (!fs44.existsSync(".claude") && !fs44.existsSync(".cursor")) {
|
|
11312
11442
|
console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
|
|
11313
11443
|
console.log(chalk23.dim(" Run `caliber init` first, or create the directory manually."));
|
|
11314
11444
|
return;
|
|
@@ -11366,8 +11496,8 @@ async function learnStatusCommand() {
|
|
|
11366
11496
|
if (lastError) {
|
|
11367
11497
|
console.log(`Last error: ${chalk23.red(lastError.error)}`);
|
|
11368
11498
|
console.log(chalk23.dim(` at ${lastError.timestamp}`));
|
|
11369
|
-
const logPath =
|
|
11370
|
-
if (
|
|
11499
|
+
const logPath = path35.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
11500
|
+
if (fs44.existsSync(logPath)) {
|
|
11371
11501
|
console.log(chalk23.dim(` Full log: ${logPath}`));
|
|
11372
11502
|
}
|
|
11373
11503
|
}
|
|
@@ -11447,11 +11577,11 @@ async function learnDeleteCommand(indexStr) {
|
|
|
11447
11577
|
}
|
|
11448
11578
|
const item = items[targetIdx];
|
|
11449
11579
|
const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
|
|
11450
|
-
if (!
|
|
11580
|
+
if (!fs44.existsSync(filePath)) {
|
|
11451
11581
|
console.log(chalk23.red("Learnings file not found."));
|
|
11452
11582
|
return;
|
|
11453
11583
|
}
|
|
11454
|
-
const content =
|
|
11584
|
+
const content = fs44.readFileSync(filePath, "utf-8");
|
|
11455
11585
|
const lines = content.split("\n");
|
|
11456
11586
|
const bulletsOfSource = items.filter((i) => i.source === item.source);
|
|
11457
11587
|
const posInFile = bulletsOfSource.indexOf(item);
|
|
@@ -11472,9 +11602,9 @@ async function learnDeleteCommand(indexStr) {
|
|
|
11472
11602
|
}
|
|
11473
11603
|
const bulletToRemove = lines[lineToRemove];
|
|
11474
11604
|
const newLines = lines.filter((_, i) => i !== lineToRemove);
|
|
11475
|
-
|
|
11605
|
+
fs44.writeFileSync(filePath, newLines.join("\n"));
|
|
11476
11606
|
if (item.source === "personal") {
|
|
11477
|
-
|
|
11607
|
+
fs44.chmodSync(filePath, 384);
|
|
11478
11608
|
}
|
|
11479
11609
|
const roiStats = readROIStats();
|
|
11480
11610
|
const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
|
|
@@ -11543,11 +11673,14 @@ function displayColdStart(score) {
|
|
|
11543
11673
|
console.log(chalk24.bold("\n Agent Insights\n"));
|
|
11544
11674
|
const hooksInstalled = areLearningHooksInstalled() || areCursorLearningHooksInstalled();
|
|
11545
11675
|
if (!hooksInstalled) {
|
|
11546
|
-
console.log(chalk24.yellow("
|
|
11547
|
-
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."));
|
|
11548
11680
|
} else {
|
|
11549
|
-
console.log(chalk24.dim("
|
|
11550
|
-
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}`));
|
|
11551
11684
|
}
|
|
11552
11685
|
console.log(chalk24.dim(`
|
|
11553
11686
|
Config score: ${score.score}/100 (${score.grade})`));
|
|
@@ -11555,7 +11688,9 @@ function displayColdStart(score) {
|
|
|
11555
11688
|
}
|
|
11556
11689
|
function displayEarlyData(data, score) {
|
|
11557
11690
|
console.log(chalk24.bold("\n Agent Insights") + chalk24.yellow(" (early data)\n"));
|
|
11558
|
-
|
|
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
|
+
`));
|
|
11559
11694
|
console.log(` Sessions tracked: ${chalk24.cyan(String(data.totalSessions))}`);
|
|
11560
11695
|
console.log(` Learnings accumulated: ${chalk24.cyan(String(data.learningCount))}`);
|
|
11561
11696
|
if (data.totalWasteTokens > 0) {
|
|
@@ -11607,6 +11742,14 @@ function displayFullInsights(data, score) {
|
|
|
11607
11742
|
}
|
|
11608
11743
|
console.log(chalk24.bold("\n Config Quality"));
|
|
11609
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
|
+
}
|
|
11610
11753
|
console.log("");
|
|
11611
11754
|
}
|
|
11612
11755
|
async function insightsCommand(options) {
|
|
@@ -11633,8 +11776,8 @@ async function insightsCommand(options) {
|
|
|
11633
11776
|
}
|
|
11634
11777
|
|
|
11635
11778
|
// src/commands/sources.ts
|
|
11636
|
-
import
|
|
11637
|
-
import
|
|
11779
|
+
import fs45 from "fs";
|
|
11780
|
+
import path36 from "path";
|
|
11638
11781
|
import chalk25 from "chalk";
|
|
11639
11782
|
async function sourcesListCommand() {
|
|
11640
11783
|
const dir = process.cwd();
|
|
@@ -11650,9 +11793,9 @@ async function sourcesListCommand() {
|
|
|
11650
11793
|
if (configSources.length > 0) {
|
|
11651
11794
|
for (const source of configSources) {
|
|
11652
11795
|
const sourcePath = source.path || source.url || "";
|
|
11653
|
-
const exists = source.path ?
|
|
11796
|
+
const exists = source.path ? fs45.existsSync(path36.resolve(dir, source.path)) : false;
|
|
11654
11797
|
const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
|
|
11655
|
-
const hasSummary = source.path &&
|
|
11798
|
+
const hasSummary = source.path && fs45.existsSync(path36.join(path36.resolve(dir, source.path), ".caliber", "summary.json"));
|
|
11656
11799
|
console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
|
|
11657
11800
|
console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
|
|
11658
11801
|
if (source.description) console.log(` ${chalk25.dim(source.description)}`);
|
|
@@ -11662,7 +11805,7 @@ async function sourcesListCommand() {
|
|
|
11662
11805
|
if (workspaces.length > 0) {
|
|
11663
11806
|
console.log(chalk25.dim(" Auto-detected workspaces:"));
|
|
11664
11807
|
for (const ws of workspaces) {
|
|
11665
|
-
const exists =
|
|
11808
|
+
const exists = fs45.existsSync(path36.resolve(dir, ws));
|
|
11666
11809
|
console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
|
|
11667
11810
|
}
|
|
11668
11811
|
console.log("");
|
|
@@ -11670,8 +11813,8 @@ async function sourcesListCommand() {
|
|
|
11670
11813
|
}
|
|
11671
11814
|
async function sourcesAddCommand(sourcePath) {
|
|
11672
11815
|
const dir = process.cwd();
|
|
11673
|
-
const absPath =
|
|
11674
|
-
if (!
|
|
11816
|
+
const absPath = path36.resolve(dir, sourcePath);
|
|
11817
|
+
if (!fs45.existsSync(absPath)) {
|
|
11675
11818
|
console.log(chalk25.red(`
|
|
11676
11819
|
Path not found: ${sourcePath}
|
|
11677
11820
|
`));
|
|
@@ -11686,7 +11829,7 @@ async function sourcesAddCommand(sourcePath) {
|
|
|
11686
11829
|
}
|
|
11687
11830
|
const existing = loadSourcesConfig(dir);
|
|
11688
11831
|
const alreadyConfigured = existing.some(
|
|
11689
|
-
(s) => s.path &&
|
|
11832
|
+
(s) => s.path && path36.resolve(dir, s.path) === absPath
|
|
11690
11833
|
);
|
|
11691
11834
|
if (alreadyConfigured) {
|
|
11692
11835
|
console.log(chalk25.yellow(`
|
|
@@ -11734,8 +11877,8 @@ async function sourcesRemoveCommand(name) {
|
|
|
11734
11877
|
}
|
|
11735
11878
|
|
|
11736
11879
|
// src/commands/publish.ts
|
|
11737
|
-
import
|
|
11738
|
-
import
|
|
11880
|
+
import fs46 from "fs";
|
|
11881
|
+
import path37 from "path";
|
|
11739
11882
|
import chalk26 from "chalk";
|
|
11740
11883
|
import ora7 from "ora";
|
|
11741
11884
|
init_config();
|
|
@@ -11749,10 +11892,10 @@ async function publishCommand() {
|
|
|
11749
11892
|
const spinner = ora7("Generating project summary...").start();
|
|
11750
11893
|
try {
|
|
11751
11894
|
const fingerprint = await collectFingerprint(dir);
|
|
11752
|
-
const claudeMd = readFileOrNull(
|
|
11895
|
+
const claudeMd = readFileOrNull(path37.join(dir, "CLAUDE.md"));
|
|
11753
11896
|
const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
|
|
11754
11897
|
const summary = {
|
|
11755
|
-
name: fingerprint.packageName ||
|
|
11898
|
+
name: fingerprint.packageName || path37.basename(dir),
|
|
11756
11899
|
version: "1.0.0",
|
|
11757
11900
|
description: fingerprint.description || "",
|
|
11758
11901
|
languages: fingerprint.languages,
|
|
@@ -11764,7 +11907,7 @@ async function publishCommand() {
|
|
|
11764
11907
|
summary.conventions = claudeMd.slice(0, 2e3);
|
|
11765
11908
|
}
|
|
11766
11909
|
try {
|
|
11767
|
-
const pkgContent = readFileOrNull(
|
|
11910
|
+
const pkgContent = readFileOrNull(path37.join(dir, "package.json"));
|
|
11768
11911
|
if (pkgContent) {
|
|
11769
11912
|
const pkg3 = JSON.parse(pkgContent);
|
|
11770
11913
|
if (pkg3.scripts) {
|
|
@@ -11777,14 +11920,14 @@ async function publishCommand() {
|
|
|
11777
11920
|
}
|
|
11778
11921
|
} catch {
|
|
11779
11922
|
}
|
|
11780
|
-
const outputDir =
|
|
11781
|
-
if (!
|
|
11782
|
-
|
|
11923
|
+
const outputDir = path37.join(dir, ".caliber");
|
|
11924
|
+
if (!fs46.existsSync(outputDir)) {
|
|
11925
|
+
fs46.mkdirSync(outputDir, { recursive: true });
|
|
11783
11926
|
}
|
|
11784
|
-
const outputPath =
|
|
11785
|
-
|
|
11927
|
+
const outputPath = path37.join(outputDir, "summary.json");
|
|
11928
|
+
fs46.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
11786
11929
|
spinner.succeed("Project summary published");
|
|
11787
|
-
console.log(` ${chalk26.green("\u2713")} ${
|
|
11930
|
+
console.log(` ${chalk26.green("\u2713")} ${path37.relative(dir, outputPath)}`);
|
|
11788
11931
|
console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
|
|
11789
11932
|
console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
|
|
11790
11933
|
} catch (err) {
|
|
@@ -11796,13 +11939,13 @@ async function publishCommand() {
|
|
|
11796
11939
|
}
|
|
11797
11940
|
|
|
11798
11941
|
// src/cli.ts
|
|
11799
|
-
var __dirname =
|
|
11942
|
+
var __dirname = path38.dirname(fileURLToPath(import.meta.url));
|
|
11800
11943
|
var pkg = JSON.parse(
|
|
11801
|
-
|
|
11944
|
+
fs47.readFileSync(path38.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
11802
11945
|
);
|
|
11803
11946
|
var program = new Command();
|
|
11804
11947
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
11805
|
-
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");
|
|
11806
11949
|
function tracked(commandName, handler) {
|
|
11807
11950
|
const wrapper = async (...args) => {
|
|
11808
11951
|
const start = Date.now();
|
|
@@ -11859,13 +12002,13 @@ function parseAgentOption(value) {
|
|
|
11859
12002
|
}
|
|
11860
12003
|
return agents;
|
|
11861
12004
|
}
|
|
11862
|
-
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));
|
|
11863
12006
|
program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
|
|
11864
|
-
program.command("status").description("Show current Caliber
|
|
11865
|
-
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));
|
|
11866
12009
|
program.command("config").description("Configure LLM provider, API key, and model").action(tracked("config", configCommand));
|
|
11867
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));
|
|
11868
|
-
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));
|
|
11869
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));
|
|
11870
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));
|
|
11871
12014
|
program.command("insights").description("Show agent performance insights and learning impact").option("--json", "Output as JSON").action(tracked("insights", insightsCommand));
|
|
@@ -11874,7 +12017,7 @@ sources.command("list").description("Show configured and auto-detected sources")
|
|
|
11874
12017
|
sources.command("add").description("Add an external source").argument("<path>", "Path to repo directory or file").action(tracked("sources:add", sourcesAddCommand));
|
|
11875
12018
|
sources.command("remove").description("Remove a configured source").argument("<name>", "Source path or role to remove").action(tracked("sources:remove", sourcesRemoveCommand));
|
|
11876
12019
|
program.command("publish").description("Generate a machine-readable summary for other repos to consume").action(tracked("publish", publishCommand));
|
|
11877
|
-
var learn = program.command("learn"
|
|
12020
|
+
var learn = program.command("learn").description("Manage session learning \u2014 extract patterns from your AI coding sessions");
|
|
11878
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));
|
|
11879
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)));
|
|
11880
12023
|
learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
|
|
@@ -11885,16 +12028,16 @@ learn.command("delete <index>").description("Delete a learning by its index numb
|
|
|
11885
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));
|
|
11886
12029
|
|
|
11887
12030
|
// src/utils/version-check.ts
|
|
11888
|
-
import
|
|
11889
|
-
import
|
|
12031
|
+
import fs48 from "fs";
|
|
12032
|
+
import path39 from "path";
|
|
11890
12033
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11891
|
-
import { execSync as
|
|
12034
|
+
import { execSync as execSync16 } from "child_process";
|
|
11892
12035
|
import chalk27 from "chalk";
|
|
11893
12036
|
import ora8 from "ora";
|
|
11894
12037
|
import confirm2 from "@inquirer/confirm";
|
|
11895
|
-
var __dirname_vc =
|
|
12038
|
+
var __dirname_vc = path39.dirname(fileURLToPath2(import.meta.url));
|
|
11896
12039
|
var pkg2 = JSON.parse(
|
|
11897
|
-
|
|
12040
|
+
fs48.readFileSync(path39.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
11898
12041
|
);
|
|
11899
12042
|
function getChannel(version) {
|
|
11900
12043
|
const match = version.match(/-(dev|next)\./);
|
|
@@ -11918,9 +12061,9 @@ function isNewer(registry, current) {
|
|
|
11918
12061
|
}
|
|
11919
12062
|
function getInstalledVersion() {
|
|
11920
12063
|
try {
|
|
11921
|
-
const globalRoot =
|
|
11922
|
-
const pkgPath =
|
|
11923
|
-
return JSON.parse(
|
|
12064
|
+
const globalRoot = execSync16("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
12065
|
+
const pkgPath = path39.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
12066
|
+
return JSON.parse(fs48.readFileSync(pkgPath, "utf-8")).version;
|
|
11924
12067
|
} catch {
|
|
11925
12068
|
return null;
|
|
11926
12069
|
}
|
|
@@ -11966,7 +12109,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
11966
12109
|
const tag = channel === "latest" ? latest : channel;
|
|
11967
12110
|
const spinner = ora8("Updating caliber...").start();
|
|
11968
12111
|
try {
|
|
11969
|
-
|
|
12112
|
+
execSync16(`npm install -g @rely-ai/caliber@${tag}`, {
|
|
11970
12113
|
stdio: "pipe",
|
|
11971
12114
|
timeout: 12e4,
|
|
11972
12115
|
env: { ...process.env, npm_config_fund: "false", npm_config_audit: "false" }
|
|
@@ -11983,7 +12126,7 @@ Update available: ${current} -> ${latest}`)
|
|
|
11983
12126
|
console.log(chalk27.dim(`
|
|
11984
12127
|
Restarting: caliber ${args.join(" ")}
|
|
11985
12128
|
`));
|
|
11986
|
-
|
|
12129
|
+
execSync16(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
11987
12130
|
stdio: "inherit",
|
|
11988
12131
|
env: { ...process.env, CALIBER_SKIP_UPDATE_CHECK: "1" }
|
|
11989
12132
|
});
|