@rely-ai/caliber 1.37.1 → 1.39.0
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 +718 -327
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1099,14 +1099,14 @@ __export(lock_exports, {
|
|
|
1099
1099
|
isCaliberRunning: () => isCaliberRunning,
|
|
1100
1100
|
releaseLock: () => releaseLock
|
|
1101
1101
|
});
|
|
1102
|
-
import
|
|
1103
|
-
import
|
|
1102
|
+
import fs40 from "fs";
|
|
1103
|
+
import path33 from "path";
|
|
1104
1104
|
import os8 from "os";
|
|
1105
1105
|
import crypto5 from "crypto";
|
|
1106
1106
|
function buildLockPath() {
|
|
1107
1107
|
const cwd = process.cwd();
|
|
1108
1108
|
const hash = crypto5.createHash("md5").update(cwd).digest("hex").slice(0, 8);
|
|
1109
|
-
return
|
|
1109
|
+
return path33.join(os8.tmpdir(), `.caliber-${hash}.lock`);
|
|
1110
1110
|
}
|
|
1111
1111
|
function getLockFile() {
|
|
1112
1112
|
if (!_lockPath) _lockPath = buildLockPath();
|
|
@@ -1115,8 +1115,8 @@ function getLockFile() {
|
|
|
1115
1115
|
function isCaliberRunning() {
|
|
1116
1116
|
try {
|
|
1117
1117
|
const lockFile = buildLockPath();
|
|
1118
|
-
if (!
|
|
1119
|
-
const raw =
|
|
1118
|
+
if (!fs40.existsSync(lockFile)) return false;
|
|
1119
|
+
const raw = fs40.readFileSync(lockFile, "utf-8").trim();
|
|
1120
1120
|
const { pid, ts } = JSON.parse(raw);
|
|
1121
1121
|
if (Date.now() - ts > STALE_MS) return false;
|
|
1122
1122
|
try {
|
|
@@ -1131,14 +1131,14 @@ function isCaliberRunning() {
|
|
|
1131
1131
|
}
|
|
1132
1132
|
function acquireLock() {
|
|
1133
1133
|
try {
|
|
1134
|
-
|
|
1134
|
+
fs40.writeFileSync(getLockFile(), JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
1135
1135
|
} catch {
|
|
1136
1136
|
}
|
|
1137
1137
|
}
|
|
1138
1138
|
function releaseLock() {
|
|
1139
1139
|
try {
|
|
1140
1140
|
const lockFile = getLockFile();
|
|
1141
|
-
if (
|
|
1141
|
+
if (fs40.existsSync(lockFile)) fs40.unlinkSync(lockFile);
|
|
1142
1142
|
} catch {
|
|
1143
1143
|
}
|
|
1144
1144
|
}
|
|
@@ -1153,8 +1153,8 @@ var init_lock = __esm({
|
|
|
1153
1153
|
|
|
1154
1154
|
// src/cli.ts
|
|
1155
1155
|
import { Command } from "commander";
|
|
1156
|
-
import
|
|
1157
|
-
import
|
|
1156
|
+
import fs51 from "fs";
|
|
1157
|
+
import path42 from "path";
|
|
1158
1158
|
import { fileURLToPath } from "url";
|
|
1159
1159
|
|
|
1160
1160
|
// src/commands/init.ts
|
|
@@ -1309,6 +1309,19 @@ var REFRESH_LAST_ERROR_FILE = path2.join(CALIBER_DIR, "last-refresh-error.json")
|
|
|
1309
1309
|
var MIN_SESSIONS_FOR_COMPARISON = 3;
|
|
1310
1310
|
|
|
1311
1311
|
// src/fingerprint/existing-config.ts
|
|
1312
|
+
var CALIBER_MANAGED_PREFIX = "caliber-";
|
|
1313
|
+
var INCLUDABLE_DOC_PATTERNS = [
|
|
1314
|
+
"ARCHITECTURE.md",
|
|
1315
|
+
"CONTRIBUTING.md",
|
|
1316
|
+
"DEVELOPMENT.md",
|
|
1317
|
+
"SETUP.md",
|
|
1318
|
+
"docs/ARCHITECTURE.md",
|
|
1319
|
+
"docs/CONTRIBUTING.md",
|
|
1320
|
+
"docs/DEVELOPMENT.md",
|
|
1321
|
+
"docs/API.md",
|
|
1322
|
+
"docs/GUIDE.md",
|
|
1323
|
+
"docs/SETUP.md"
|
|
1324
|
+
];
|
|
1312
1325
|
function readSkillsFromDir(skillsDir) {
|
|
1313
1326
|
if (!fs2.existsSync(skillsDir)) return void 0;
|
|
1314
1327
|
try {
|
|
@@ -1371,6 +1384,18 @@ function readExistingConfigs(dir) {
|
|
|
1371
1384
|
} catch {
|
|
1372
1385
|
}
|
|
1373
1386
|
}
|
|
1387
|
+
const claudeRulesDir = path3.join(dir, ".claude", "rules");
|
|
1388
|
+
if (fs2.existsSync(claudeRulesDir)) {
|
|
1389
|
+
try {
|
|
1390
|
+
const files = fs2.readdirSync(claudeRulesDir).filter((f) => f.endsWith(".md"));
|
|
1391
|
+
const rules = files.map((f) => ({
|
|
1392
|
+
filename: f,
|
|
1393
|
+
content: fs2.readFileSync(path3.join(claudeRulesDir, f), "utf-8")
|
|
1394
|
+
}));
|
|
1395
|
+
if (rules.length > 0) configs.claudeRules = rules;
|
|
1396
|
+
} catch {
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1374
1399
|
const cursorrulesPath = path3.join(dir, ".cursorrules");
|
|
1375
1400
|
if (fs2.existsSync(cursorrulesPath)) {
|
|
1376
1401
|
configs.cursorrules = fs2.readFileSync(cursorrulesPath, "utf-8");
|
|
@@ -1434,6 +1459,8 @@ function readExistingConfigs(dir) {
|
|
|
1434
1459
|
} catch {
|
|
1435
1460
|
}
|
|
1436
1461
|
}
|
|
1462
|
+
const found = INCLUDABLE_DOC_PATTERNS.filter((p) => fs2.existsSync(path3.join(dir, p)));
|
|
1463
|
+
if (found.length > 0) configs.includableDocs = found;
|
|
1437
1464
|
return configs;
|
|
1438
1465
|
}
|
|
1439
1466
|
|
|
@@ -2708,15 +2735,16 @@ var CLAUDE_CLI_BIN = "claude";
|
|
|
2708
2735
|
var DEFAULT_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
2709
2736
|
var IS_WINDOWS2 = process.platform === "win32";
|
|
2710
2737
|
function spawnClaude(args) {
|
|
2738
|
+
const env = { ...process.env, CLAUDE_CODE_SIMPLE: "1" };
|
|
2711
2739
|
return IS_WINDOWS2 ? spawn2([CLAUDE_CLI_BIN, ...args].join(" "), {
|
|
2712
2740
|
cwd: process.cwd(),
|
|
2713
2741
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2714
|
-
env
|
|
2742
|
+
env,
|
|
2715
2743
|
shell: true
|
|
2716
2744
|
}) : spawn2(CLAUDE_CLI_BIN, args, {
|
|
2717
2745
|
cwd: process.cwd(),
|
|
2718
2746
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2719
|
-
env
|
|
2747
|
+
env
|
|
2720
2748
|
});
|
|
2721
2749
|
}
|
|
2722
2750
|
var ClaudeCliProvider = class {
|
|
@@ -3113,6 +3141,7 @@ var CONFIG_FILE_TYPES = `You understand these config files:
|
|
|
3113
3141
|
- .agents/skills/{name}/SKILL.md: Same OpenSkills format for Codex skills (Codex scans .agents/skills/ for skills).
|
|
3114
3142
|
- .opencode/skills/{name}/SKILL.md: Same OpenSkills format for OpenCode skills (OpenCode scans .opencode/skills/ for skills).
|
|
3115
3143
|
- .cursor/skills/{name}/SKILL.md: Same OpenSkills format for Cursor skills.
|
|
3144
|
+
- .claude/rules/*.md: Path-scoped rules for Claude Code with YAML frontmatter. Each rule file contains a \`paths:\` field with glob patterns \u2014 Claude Code only loads the rule when the user works on matching files. Use for domain-specific conventions (e.g., API patterns, test conventions, database rules). Always-apply rules omit the \`paths:\` field.
|
|
3116
3145
|
- .cursorrules: Coding rules for Cursor (deprecated legacy format \u2014 do NOT generate this).
|
|
3117
3146
|
- .cursor/rules/*.mdc: Modern Cursor rules with frontmatter (description, globs, alwaysApply).
|
|
3118
3147
|
- .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.
|
|
@@ -3152,6 +3181,7 @@ Skill field requirements:
|
|
|
3152
3181
|
- "name" MUST NOT be any of these reserved names (they are managed by Caliber automatically): "setup-caliber", "find-skills", "save-learning". Do NOT generate skills with these names.
|
|
3153
3182
|
- "description": MUST include WHAT it does + WHEN to use it with specific trigger phrases. Example: "Manages database migrations. Use when user says 'run migration', 'create migration', 'db schema change', or modifies files in db/migrations/."
|
|
3154
3183
|
- "content": markdown body only \u2014 do NOT include YAML frontmatter, it is generated from name+description.
|
|
3184
|
+
- "paths" (optional): array of glob patterns. When provided, the skill is only loaded when the user works on matching files. Use for skills tied to specific file types or directories. Example: ["src/api/**", "src/routes/**"] for an API skill, or ["Dockerfile*", "docker-compose*"] for a Docker skill. Omit for general-purpose skills.
|
|
3155
3185
|
|
|
3156
3186
|
Skill content structure \u2014 follow this template:
|
|
3157
3187
|
1. A heading with the skill name
|
|
@@ -3160,6 +3190,31 @@ Skill content structure \u2014 follow this template:
|
|
|
3160
3190
|
4. "## Troubleshooting" (optional) \u2014 common errors and how to fix them
|
|
3161
3191
|
|
|
3162
3192
|
Keep skill content under 200 lines. Focus on actionable instructions, not documentation prose.`;
|
|
3193
|
+
var CLAUDE_RULES_FORMAT = `CLAUDE RULES FORMAT \u2014 .claude/rules/*.md files:
|
|
3194
|
+
Claude Code loads .claude/rules/*.md files as path-scoped instructions. Generate 2-4 rules that extract domain-specific conventions from CLAUDE.md into focused, contextually-loaded files.
|
|
3195
|
+
|
|
3196
|
+
Each rule file uses YAML frontmatter with an optional \`paths:\` field:
|
|
3197
|
+
\`\`\`markdown
|
|
3198
|
+
---
|
|
3199
|
+
paths:
|
|
3200
|
+
- src/api/**
|
|
3201
|
+
- src/routes/**
|
|
3202
|
+
---
|
|
3203
|
+
|
|
3204
|
+
# API Conventions
|
|
3205
|
+
|
|
3206
|
+
- All endpoints return \`{ data, error }\` envelope
|
|
3207
|
+
- Use \`validateRequest(schema)\` middleware before handlers
|
|
3208
|
+
\`\`\`
|
|
3209
|
+
|
|
3210
|
+
Rules without \`paths:\` apply globally (use sparingly \u2014 prefer path-scoped rules).
|
|
3211
|
+
|
|
3212
|
+
Guidelines:
|
|
3213
|
+
- Extract patterns from the codebase that apply to specific file types or directories
|
|
3214
|
+
- Keep each rule under 50 lines \u2014 these are loaded into context, conciseness matters
|
|
3215
|
+
- Good candidates: testing patterns, API conventions, database query patterns, component structure
|
|
3216
|
+
- Do NOT duplicate content from CLAUDE.md \u2014 rules supplement it with path-specific detail
|
|
3217
|
+
- filename must be kebab-case ending in .md (e.g. \`testing-patterns.md\`, \`api-conventions.md\`)`;
|
|
3163
3218
|
var SCORING_CRITERIA = `SCORING CRITERIA \u2014 your output is scored deterministically against the actual filesystem. Optimize for 100/100:
|
|
3164
3219
|
|
|
3165
3220
|
Existence (25 pts):
|
|
@@ -3202,7 +3257,13 @@ For command sections, use code blocks with one command per line.
|
|
|
3202
3257
|
|
|
3203
3258
|
- Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
|
|
3204
3259
|
- Cursor rules: max 5 .mdc files.
|
|
3205
|
-
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything
|
|
3260
|
+
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.
|
|
3261
|
+
|
|
3262
|
+
@include directives (Claude Code only):
|
|
3263
|
+
- If the project has existing documentation files (ARCHITECTURE.md, CONTRIBUTING.md, API docs, etc.), reference them in CLAUDE.md using \`@./path\` instead of summarizing their content. Claude Code will inline them automatically.
|
|
3264
|
+
- Example: Instead of writing an architecture summary, add \`@./ARCHITECTURE.md\` in the relevant section.
|
|
3265
|
+
- This keeps CLAUDE.md compact while giving the agent access to detailed docs.
|
|
3266
|
+
- Only use @include for files that actually exist in the provided file tree.`;
|
|
3206
3267
|
var GENERATION_SYSTEM_PROMPT = `${ROLE_AND_CONTEXT}
|
|
3207
3268
|
|
|
3208
3269
|
${CONFIG_FILE_TYPES}
|
|
@@ -3224,18 +3285,19 @@ AgentSetup schema:
|
|
|
3224
3285
|
],
|
|
3225
3286
|
"claude": {
|
|
3226
3287
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
3227
|
-
"
|
|
3288
|
+
"rules": [{ "filename": "string.md (kebab-case, e.g. api-conventions.md)", "content": "string (markdown with YAML frontmatter containing paths: glob)" }],
|
|
3289
|
+
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)", "paths": ["optional array of glob patterns \u2014 omit for general-purpose skills"] }]
|
|
3228
3290
|
},
|
|
3229
3291
|
"codex": {
|
|
3230
3292
|
"agentsMd": "string (markdown content for AGENTS.md \u2014 the primary Codex instructions file, same quality/structure as CLAUDE.md)",
|
|
3231
|
-
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }]
|
|
3293
|
+
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)", "paths": ["optional array of glob patterns \u2014 omit for general-purpose skills"] }]
|
|
3232
3294
|
},
|
|
3233
3295
|
"opencode": {
|
|
3234
3296
|
"agentsMd": "string (markdown content for AGENTS.md \u2014 reuse codex.agentsMd if codex is also targeted, otherwise generate fresh)",
|
|
3235
|
-
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }]
|
|
3297
|
+
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)", "paths": ["optional array of glob patterns \u2014 omit for general-purpose skills"] }]
|
|
3236
3298
|
},
|
|
3237
3299
|
"cursor": {
|
|
3238
|
-
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)" }],
|
|
3300
|
+
"skills": [{ "name": "string (kebab-case, matches directory name)", "description": "string (what this skill does and when to use it)", "content": "string (markdown body \u2014 NO frontmatter, it will be generated from name+description)", "paths": ["optional array of glob patterns \u2014 omit for general-purpose skills"] }],
|
|
3239
3301
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
3240
3302
|
},
|
|
3241
3303
|
"copilot": {
|
|
@@ -3248,12 +3310,15 @@ NOTE: If both "codex" and "opencode" are targeted, set opencode.agentsMd to the
|
|
|
3248
3310
|
|
|
3249
3311
|
${SKILL_FORMAT_RULES}
|
|
3250
3312
|
|
|
3313
|
+
${CLAUDE_RULES_FORMAT}
|
|
3314
|
+
|
|
3251
3315
|
${FILE_DESCRIPTIONS_RULES}
|
|
3252
3316
|
|
|
3253
3317
|
${SCORING_CRITERIA}
|
|
3254
3318
|
|
|
3255
3319
|
${OUTPUT_SIZE_CONSTRAINTS}
|
|
3256
|
-
- Skills: generate 3-6 skills per target platform based on project complexity. Each skill should cover a distinct tool, workflow, or domain \u2014 don't pad with generic skills
|
|
3320
|
+
- Skills: generate 3-6 skills per target platform based on project complexity. Each skill should cover a distinct tool, workflow, or domain \u2014 don't pad with generic skills.
|
|
3321
|
+
- Claude rules: generate 2-4 .claude/rules/*.md files when claude is targeted. Each rule should extract domain-specific patterns from the codebase.`;
|
|
3257
3322
|
var CORE_GENERATION_PROMPT = `${ROLE_AND_CONTEXT}
|
|
3258
3323
|
|
|
3259
3324
|
${CONFIG_FILE_TYPES}
|
|
@@ -3275,6 +3340,7 @@ CoreSetup schema:
|
|
|
3275
3340
|
],
|
|
3276
3341
|
"claude": {
|
|
3277
3342
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
3343
|
+
"rules": [{ "filename": "string.md (kebab-case, e.g. api-conventions.md)", "content": "string (markdown with YAML frontmatter containing paths: glob)" }],
|
|
3278
3344
|
"skillTopics": [{ "name": "string (kebab-case)", "description": "string (what this skill does and WHEN to use it \u2014 include trigger phrases)" }]
|
|
3279
3345
|
},
|
|
3280
3346
|
"codex": {
|
|
@@ -3308,12 +3374,15 @@ Skill topic description MUST follow this formula: [What it does] + [When to use
|
|
|
3308
3374
|
Include specific trigger phrases users would actually say. Also include negative triggers to prevent over-triggering.
|
|
3309
3375
|
Example: "Creates a new API endpoint following the project's route pattern. Handles request validation, error responses, and DB queries. Use when user says 'add endpoint', 'new route', 'create API', or adds files to src/routes/. Do NOT use for modifying existing routes."
|
|
3310
3376
|
|
|
3377
|
+
${CLAUDE_RULES_FORMAT}
|
|
3378
|
+
|
|
3311
3379
|
${FILE_DESCRIPTIONS_RULES}
|
|
3312
3380
|
|
|
3313
3381
|
${SCORING_CRITERIA}
|
|
3314
3382
|
|
|
3315
3383
|
${OUTPUT_SIZE_CONSTRAINTS}
|
|
3316
|
-
- Skill topics: 3-6 per platform based on project complexity (name + description only, no content)
|
|
3384
|
+
- Skill topics: 3-6 per platform based on project complexity (name + description only, no content).
|
|
3385
|
+
- Claude rules: generate 2-4 .claude/rules/*.md files when claude is targeted.`;
|
|
3317
3386
|
var SKILL_GENERATION_PROMPT = `You generate a single skill file for a coding agent (Claude Code, Cursor, Codex, or OpenCode).
|
|
3318
3387
|
|
|
3319
3388
|
Given project context and a skill topic, produce a focused SKILL.md body.
|
|
@@ -3343,7 +3412,7 @@ Rules:
|
|
|
3343
3412
|
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.
|
|
3344
3413
|
|
|
3345
3414
|
Return ONLY a JSON object:
|
|
3346
|
-
{"name": "string (kebab-case)", "description": "string (what + when + capabilities + negative triggers)", "content": "string (markdown body)"}`;
|
|
3415
|
+
{"name": "string (kebab-case)", "description": "string (what + when + capabilities + negative triggers)", "content": "string (markdown body)", "paths": ["optional glob patterns \u2014 include when the skill applies to specific file types or directories, omit for general-purpose skills"]}`;
|
|
3347
3416
|
var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code, Cursor, Codex, OpenCode, and GitHub Copilot).
|
|
3348
3417
|
|
|
3349
3418
|
You will receive the current AgentSetup JSON and a user request describing what to change.
|
|
@@ -3400,7 +3469,7 @@ var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project doc
|
|
|
3400
3469
|
|
|
3401
3470
|
You will receive:
|
|
3402
3471
|
1. Git diffs showing what code changed
|
|
3403
|
-
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor rules, copilot instructions)
|
|
3472
|
+
2. Current contents of documentation files (CLAUDE.md, .claude/rules/*.md, README.md, skills, cursor rules, copilot instructions)
|
|
3404
3473
|
3. Project context (languages, frameworks, file tree)
|
|
3405
3474
|
|
|
3406
3475
|
CONSERVATIVE UPDATE means:
|
|
@@ -3419,6 +3488,12 @@ Quality constraints (the output is scored deterministically):
|
|
|
3419
3488
|
- ONLY reference file paths that exist in the provided file tree \u2014 do NOT invent paths
|
|
3420
3489
|
- Preserve the existing structure (headings, bullet style, formatting)
|
|
3421
3490
|
|
|
3491
|
+
Claude rules (.claude/rules/*.md):
|
|
3492
|
+
- If the diff affects code in a domain covered by an existing rule, update that rule
|
|
3493
|
+
- If the diff introduces a new domain pattern (e.g., new API conventions, new test patterns), create a new rule file
|
|
3494
|
+
- Rules with paths: frontmatter should only contain conventions relevant to those paths
|
|
3495
|
+
- Keep rules under 50 lines \u2014 they load into context alongside other instructions
|
|
3496
|
+
|
|
3422
3497
|
Cross-agent sync:
|
|
3423
3498
|
- When a code change affects documentation, update ALL provided platform configs together.
|
|
3424
3499
|
- A renamed command, moved file, or changed convention must be reflected in every config (CLAUDE.md, AGENTS.md, copilot instructions, skills across all platforms).
|
|
@@ -3435,6 +3510,7 @@ Return a JSON object with this exact shape:
|
|
|
3435
3510
|
"updatedDocs": {
|
|
3436
3511
|
"agentsMd": "<updated content or null>",
|
|
3437
3512
|
"claudeMd": "<updated content or null>",
|
|
3513
|
+
"claudeRules": [{"filename": "name.md", "content": "full content with frontmatter"}] or null,
|
|
3438
3514
|
"readmeMd": "<updated content or null>",
|
|
3439
3515
|
"cursorrules": "<updated content or null>",
|
|
3440
3516
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
@@ -3695,7 +3771,7 @@ function getDetectedWorkspaces(dir) {
|
|
|
3695
3771
|
|
|
3696
3772
|
// src/fingerprint/index.ts
|
|
3697
3773
|
async function collectFingerprint(dir) {
|
|
3698
|
-
const gitRemoteUrl = getGitRemoteUrl();
|
|
3774
|
+
const gitRemoteUrl = getGitRemoteUrl(dir);
|
|
3699
3775
|
const fileTree = getFileTree(dir);
|
|
3700
3776
|
const existingConfigs = readExistingConfigs(dir);
|
|
3701
3777
|
const packageName = readPackageName(dir);
|
|
@@ -4028,6 +4104,55 @@ function removeHook() {
|
|
|
4028
4104
|
writeSettings(settings);
|
|
4029
4105
|
return { removed: true, notFound: false };
|
|
4030
4106
|
}
|
|
4107
|
+
function createScriptHook(config) {
|
|
4108
|
+
const { eventName, scriptPath, scriptContent, description } = config;
|
|
4109
|
+
const hasHook = (matchers) => matchers.some((entry) => entry.hooks?.some((h) => h.description === description));
|
|
4110
|
+
function isInstalled() {
|
|
4111
|
+
const settings = readSettings();
|
|
4112
|
+
const matchers = settings.hooks?.[eventName];
|
|
4113
|
+
return Array.isArray(matchers) && hasHook(matchers);
|
|
4114
|
+
}
|
|
4115
|
+
function install() {
|
|
4116
|
+
const settings = readSettings();
|
|
4117
|
+
if (!settings.hooks) settings.hooks = {};
|
|
4118
|
+
const matchers = settings.hooks[eventName];
|
|
4119
|
+
if (Array.isArray(matchers) && hasHook(matchers)) {
|
|
4120
|
+
return { installed: false, alreadyInstalled: true };
|
|
4121
|
+
}
|
|
4122
|
+
const scriptDir = path10.dirname(scriptPath);
|
|
4123
|
+
if (!fs10.existsSync(scriptDir)) fs10.mkdirSync(scriptDir, { recursive: true });
|
|
4124
|
+
fs10.writeFileSync(scriptPath, scriptContent);
|
|
4125
|
+
fs10.chmodSync(scriptPath, 493);
|
|
4126
|
+
if (!Array.isArray(settings.hooks[eventName])) {
|
|
4127
|
+
settings.hooks[eventName] = [];
|
|
4128
|
+
}
|
|
4129
|
+
settings.hooks[eventName].push({
|
|
4130
|
+
matcher: "",
|
|
4131
|
+
hooks: [{ type: "command", command: scriptPath, description }]
|
|
4132
|
+
});
|
|
4133
|
+
writeSettings(settings);
|
|
4134
|
+
return { installed: true, alreadyInstalled: false };
|
|
4135
|
+
}
|
|
4136
|
+
function remove() {
|
|
4137
|
+
const settings = readSettings();
|
|
4138
|
+
const matchers = settings.hooks?.[eventName];
|
|
4139
|
+
if (!Array.isArray(matchers)) return { removed: false, notFound: true };
|
|
4140
|
+
const idx = matchers.findIndex(
|
|
4141
|
+
(entry) => entry.hooks?.some((h) => h.description === description)
|
|
4142
|
+
);
|
|
4143
|
+
if (idx === -1) return { removed: false, notFound: true };
|
|
4144
|
+
matchers.splice(idx, 1);
|
|
4145
|
+
if (matchers.length === 0) delete settings.hooks[eventName];
|
|
4146
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
4147
|
+
writeSettings(settings);
|
|
4148
|
+
try {
|
|
4149
|
+
fs10.unlinkSync(scriptPath);
|
|
4150
|
+
} catch {
|
|
4151
|
+
}
|
|
4152
|
+
return { removed: true, notFound: false };
|
|
4153
|
+
}
|
|
4154
|
+
return { isInstalled, install, remove };
|
|
4155
|
+
}
|
|
4031
4156
|
var STOP_HOOK_SCRIPT_CONTENT = `#!/bin/sh
|
|
4032
4157
|
if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
|
|
4033
4158
|
exit 0
|
|
@@ -4040,58 +4165,44 @@ fi
|
|
|
4040
4165
|
touch "$FLAG"
|
|
4041
4166
|
printf '{"decision":"block","reason":"Caliber agent config sync is not set up on this machine. Tell the user: This project uses Caliber to keep agent configs in sync \u2014 it is not set up yet. Want me to run /setup-caliber? (~30 seconds)"}'
|
|
4042
4167
|
`;
|
|
4043
|
-
var
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
const idx = stop.findIndex(
|
|
4082
|
-
(entry) => entry.hooks?.some((h) => h.description === STOP_HOOK_DESCRIPTION)
|
|
4083
|
-
);
|
|
4084
|
-
if (idx === -1) return { removed: false, notFound: true };
|
|
4085
|
-
stop.splice(idx, 1);
|
|
4086
|
-
if (stop.length === 0) delete settings.hooks.Stop;
|
|
4087
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
4088
|
-
writeSettings(settings);
|
|
4089
|
-
try {
|
|
4090
|
-
fs10.unlinkSync(STOP_HOOK_SCRIPT_PATH);
|
|
4091
|
-
} catch {
|
|
4092
|
-
}
|
|
4093
|
-
return { removed: true, notFound: false };
|
|
4094
|
-
}
|
|
4168
|
+
var stopHook = createScriptHook({
|
|
4169
|
+
eventName: "Stop",
|
|
4170
|
+
scriptPath: path10.join(".claude", "hooks", "caliber-check-sync.sh"),
|
|
4171
|
+
scriptContent: STOP_HOOK_SCRIPT_CONTENT,
|
|
4172
|
+
description: "Caliber: offer setup if not configured"
|
|
4173
|
+
});
|
|
4174
|
+
var installStopHook = stopHook.install;
|
|
4175
|
+
var removeStopHook = stopHook.remove;
|
|
4176
|
+
var FRESHNESS_SCRIPT = `#!/bin/sh
|
|
4177
|
+
STATE_FILE=".caliber/.caliber-state.json"
|
|
4178
|
+
[ ! -f "$STATE_FILE" ] && exit 0
|
|
4179
|
+
LAST_SHA=$(grep -o '"lastRefreshSha":"[^"]*"' "$STATE_FILE" 2>/dev/null | cut -d'"' -f4)
|
|
4180
|
+
[ -z "$LAST_SHA" ] && exit 0
|
|
4181
|
+
CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null)
|
|
4182
|
+
[ "$LAST_SHA" = "$CURRENT_SHA" ] && exit 0
|
|
4183
|
+
COMMITS_BEHIND=$(git rev-list --count "$LAST_SHA".."$CURRENT_SHA" 2>/dev/null || echo 0)
|
|
4184
|
+
if [ "$COMMITS_BEHIND" -gt 15 ]; then
|
|
4185
|
+
printf '{"systemMessage":"Caliber: agent configs are %s commits behind. Run caliber refresh to sync."}' "$COMMITS_BEHIND"
|
|
4186
|
+
fi
|
|
4187
|
+
`;
|
|
4188
|
+
var sessionStartHook = createScriptHook({
|
|
4189
|
+
eventName: "SessionStart",
|
|
4190
|
+
scriptPath: path10.join(".claude", "hooks", "caliber-session-freshness.sh"),
|
|
4191
|
+
scriptContent: FRESHNESS_SCRIPT,
|
|
4192
|
+
description: "Caliber: check config freshness on session start"
|
|
4193
|
+
});
|
|
4194
|
+
var isSessionStartHookInstalled = sessionStartHook.isInstalled;
|
|
4195
|
+
var installSessionStartHook = sessionStartHook.install;
|
|
4196
|
+
var removeSessionStartHook = sessionStartHook.remove;
|
|
4197
|
+
var notificationHook = createScriptHook({
|
|
4198
|
+
eventName: "Notification",
|
|
4199
|
+
scriptPath: path10.join(".claude", "hooks", "caliber-freshness-notify.sh"),
|
|
4200
|
+
scriptContent: FRESHNESS_SCRIPT,
|
|
4201
|
+
description: "Caliber: warn when agent configs are stale"
|
|
4202
|
+
});
|
|
4203
|
+
var isNotificationHookInstalled = notificationHook.isInstalled;
|
|
4204
|
+
var installNotificationHook = notificationHook.install;
|
|
4205
|
+
var removeNotificationHook = notificationHook.remove;
|
|
4095
4206
|
var PRECOMMIT_START = "# caliber:pre-commit:start";
|
|
4096
4207
|
var PRECOMMIT_END = "# caliber:pre-commit:end";
|
|
4097
4208
|
function getPrecommitBlock() {
|
|
@@ -4698,15 +4809,15 @@ init_config();
|
|
|
4698
4809
|
// src/utils/dependencies.ts
|
|
4699
4810
|
import { readFileSync as readFileSync2 } from "fs";
|
|
4700
4811
|
import { join as join2 } from "path";
|
|
4701
|
-
function readFileOrNull2(
|
|
4812
|
+
function readFileOrNull2(path44) {
|
|
4702
4813
|
try {
|
|
4703
|
-
return readFileSync2(
|
|
4814
|
+
return readFileSync2(path44, "utf-8");
|
|
4704
4815
|
} catch {
|
|
4705
4816
|
return null;
|
|
4706
4817
|
}
|
|
4707
4818
|
}
|
|
4708
|
-
function readJsonOrNull(
|
|
4709
|
-
const content = readFileOrNull2(
|
|
4819
|
+
function readJsonOrNull(path44) {
|
|
4820
|
+
const content = readFileOrNull2(path44);
|
|
4710
4821
|
if (!content) return null;
|
|
4711
4822
|
try {
|
|
4712
4823
|
return JSON.parse(content);
|
|
@@ -4972,7 +5083,8 @@ Generate the skill content following the instructions in the system prompt.`;
|
|
|
4972
5083
|
return {
|
|
4973
5084
|
name: result.name || topic.name,
|
|
4974
5085
|
description: result.description || topic.description,
|
|
4975
|
-
content
|
|
5086
|
+
content,
|
|
5087
|
+
...result.paths?.length ? { paths: result.paths } : {}
|
|
4976
5088
|
};
|
|
4977
5089
|
}
|
|
4978
5090
|
async function streamGeneration(config) {
|
|
@@ -5188,7 +5300,7 @@ function sampleFileTree(fileTree, codeAnalysisPaths, limit) {
|
|
|
5188
5300
|
function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks) {
|
|
5189
5301
|
const parts = [];
|
|
5190
5302
|
const existing = fingerprint.existingConfigs;
|
|
5191
|
-
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.agentsMd || existing.cursorrules || existing.cursorRules?.length);
|
|
5303
|
+
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.claudeRules?.length || existing.readmeMd || existing.agentsMd || existing.cursorrules || existing.cursorRules?.length);
|
|
5192
5304
|
const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
|
|
5193
5305
|
if (isTargetedFix) {
|
|
5194
5306
|
parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
|
|
@@ -5280,6 +5392,16 @@ ${truncate(skill.content, LIMITS.SKILL_CHARS)}`
|
|
|
5280
5392
|
(${existing.claudeSkills.length - LIMITS.SKILLS_MAX} more skills omitted)`);
|
|
5281
5393
|
}
|
|
5282
5394
|
}
|
|
5395
|
+
if (existing.claudeRules?.length) {
|
|
5396
|
+
parts.push("\n--- Existing Claude Rules ---");
|
|
5397
|
+
for (const rule of existing.claudeRules.slice(0, LIMITS.RULES_MAX)) {
|
|
5398
|
+
parts.push(
|
|
5399
|
+
`
|
|
5400
|
+
[.claude/rules/${rule.filename}]
|
|
5401
|
+
${truncate(rule.content, LIMITS.SKILL_CHARS)}`
|
|
5402
|
+
);
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5283
5405
|
if (existing.cursorrules)
|
|
5284
5406
|
parts.push(
|
|
5285
5407
|
`
|
|
@@ -5327,6 +5449,13 @@ ${existing.personalLearnings}`
|
|
|
5327
5449
|
Project dependencies (${allDeps.length}):`);
|
|
5328
5450
|
parts.push(allDeps.join(", "));
|
|
5329
5451
|
}
|
|
5452
|
+
if (existing.includableDocs?.length) {
|
|
5453
|
+
parts.push("\n--- Existing Documentation Files (use @include) ---");
|
|
5454
|
+
parts.push("These files exist and can be referenced in CLAUDE.md using @./path:");
|
|
5455
|
+
for (const doc of existing.includableDocs) {
|
|
5456
|
+
parts.push(`- ${doc}`);
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5330
5459
|
if (prompt) parts.push(`
|
|
5331
5460
|
User instructions: ${prompt}`);
|
|
5332
5461
|
if (fingerprint.codeAnalysis) {
|
|
@@ -5390,18 +5519,29 @@ function writeClaudeConfig(config) {
|
|
|
5390
5519
|
appendSyncBlock(appendLearningsBlock(appendPreCommitBlock(config.claudeMd)))
|
|
5391
5520
|
);
|
|
5392
5521
|
written.push("CLAUDE.md");
|
|
5522
|
+
if (config.rules?.length) {
|
|
5523
|
+
const rulesDir = path12.join(".claude", "rules");
|
|
5524
|
+
if (!fs12.existsSync(rulesDir)) fs12.mkdirSync(rulesDir, { recursive: true });
|
|
5525
|
+
for (const rule of config.rules) {
|
|
5526
|
+
const rulePath = path12.join(rulesDir, rule.filename);
|
|
5527
|
+
fs12.writeFileSync(rulePath, rule.content);
|
|
5528
|
+
written.push(rulePath);
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5393
5531
|
if (config.skills?.length) {
|
|
5394
5532
|
for (const skill of config.skills) {
|
|
5395
5533
|
const skillDir = path12.join(".claude", "skills", skill.name);
|
|
5396
5534
|
if (!fs12.existsSync(skillDir)) fs12.mkdirSync(skillDir, { recursive: true });
|
|
5397
5535
|
const skillPath = path12.join(skillDir, "SKILL.md");
|
|
5398
|
-
const
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5536
|
+
const frontmatterLines = ["---", `name: ${skill.name}`, `description: ${skill.description}`];
|
|
5537
|
+
if (skill.paths?.length) {
|
|
5538
|
+
frontmatterLines.push("paths:");
|
|
5539
|
+
for (const p of skill.paths) {
|
|
5540
|
+
frontmatterLines.push(` - ${p}`);
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
frontmatterLines.push("---", "");
|
|
5544
|
+
const frontmatter = frontmatterLines.join("\n");
|
|
5405
5545
|
fs12.writeFileSync(skillPath, frontmatter + skill.content);
|
|
5406
5546
|
written.push(skillPath);
|
|
5407
5547
|
}
|
|
@@ -5688,6 +5828,9 @@ function getFilesToWrite(setup) {
|
|
|
5688
5828
|
if (setup.targetAgent.includes("claude") && setup.claude) {
|
|
5689
5829
|
files.push("CLAUDE.md");
|
|
5690
5830
|
if (setup.claude.mcpServers) files.push(".mcp.json");
|
|
5831
|
+
if (setup.claude.rules) {
|
|
5832
|
+
for (const r of setup.claude.rules) files.push(`.claude/rules/${r.filename}`);
|
|
5833
|
+
}
|
|
5691
5834
|
if (setup.claude.skills) {
|
|
5692
5835
|
for (const s of setup.claude.skills) {
|
|
5693
5836
|
files.push(`.claude/skills/${s.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}/SKILL.md`);
|
|
@@ -6230,6 +6373,7 @@ var POINTS_SKILLS_BONUS_PER_EXTRA = 1;
|
|
|
6230
6373
|
var POINTS_SKILLS_BONUS_CAP = 2;
|
|
6231
6374
|
var POINTS_CURSOR_MDC_RULES = 3;
|
|
6232
6375
|
var POINTS_MCP_SERVERS = 3;
|
|
6376
|
+
var POINTS_CLAUDE_RULES = 3;
|
|
6233
6377
|
var POINTS_CROSS_PLATFORM_PARITY = 2;
|
|
6234
6378
|
var POINTS_EXECUTABLE_CONTENT = 8;
|
|
6235
6379
|
var POINTS_CONCISE_CONFIG = 6;
|
|
@@ -6299,7 +6443,11 @@ var SECRET_PLACEHOLDER_PATTERNS = [
|
|
|
6299
6443
|
/<[^>]+>/
|
|
6300
6444
|
];
|
|
6301
6445
|
var CURSOR_ONLY_CHECKS = /* @__PURE__ */ new Set(["cursor_rules_exist", "cursor_mdc_rules"]);
|
|
6302
|
-
var CLAUDE_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
6446
|
+
var CLAUDE_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
6447
|
+
"claude_md_exists",
|
|
6448
|
+
"claude_md_freshness",
|
|
6449
|
+
"claude_rules_exist"
|
|
6450
|
+
]);
|
|
6303
6451
|
var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set(["cross_platform_parity", "no_duplicate_content"]);
|
|
6304
6452
|
var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set(["codex_agents_md_exists"]);
|
|
6305
6453
|
var COPILOT_ONLY_CHECKS = /* @__PURE__ */ new Set(["copilot_instructions_exists"]);
|
|
@@ -6368,6 +6516,32 @@ function checkExistence(dir) {
|
|
|
6368
6516
|
instruction: "Create CLAUDE.md with project context, commands, architecture, and conventions."
|
|
6369
6517
|
}
|
|
6370
6518
|
});
|
|
6519
|
+
const claudeRulesDir = join3(dir, ".claude", "rules");
|
|
6520
|
+
let claudeRuleFiles = [];
|
|
6521
|
+
if (existsSync3(claudeRulesDir)) {
|
|
6522
|
+
try {
|
|
6523
|
+
claudeRuleFiles = readdirSync2(claudeRulesDir).filter(
|
|
6524
|
+
(f) => f.endsWith(".md") && !f.startsWith(CALIBER_MANAGED_PREFIX)
|
|
6525
|
+
);
|
|
6526
|
+
} catch {
|
|
6527
|
+
}
|
|
6528
|
+
}
|
|
6529
|
+
const hasClaudeRules = claudeRuleFiles.length > 0;
|
|
6530
|
+
checks.push({
|
|
6531
|
+
id: "claude_rules_exist",
|
|
6532
|
+
name: "Claude rules exist (.claude/rules/)",
|
|
6533
|
+
category: "existence",
|
|
6534
|
+
maxPoints: POINTS_CLAUDE_RULES,
|
|
6535
|
+
earnedPoints: hasClaudeRules ? POINTS_CLAUDE_RULES : 0,
|
|
6536
|
+
passed: hasClaudeRules,
|
|
6537
|
+
detail: hasClaudeRules ? `${claudeRuleFiles.length} rule${claudeRuleFiles.length === 1 ? "" : "s"} found` : "No .claude/rules/*.md files",
|
|
6538
|
+
suggestion: hasClaudeRules ? void 0 : "Add .claude/rules/*.md with path-scoped conventions for better context efficiency",
|
|
6539
|
+
fix: hasClaudeRules ? void 0 : {
|
|
6540
|
+
action: "create_file",
|
|
6541
|
+
data: { file: ".claude/rules/" },
|
|
6542
|
+
instruction: "Create .claude/rules/ with path-scoped markdown rules (e.g., testing-patterns.md, api-conventions.md)."
|
|
6543
|
+
}
|
|
6544
|
+
});
|
|
6371
6545
|
const hasCursorrules = existsSync3(join3(dir, ".cursorrules"));
|
|
6372
6546
|
const cursorRulesDir = existsSync3(join3(dir, ".cursor", "rules"));
|
|
6373
6547
|
const cursorRulesExist = hasCursorrules || cursorRulesDir;
|
|
@@ -7626,8 +7800,8 @@ function trackInitSkillsSearch(searched, installedCount) {
|
|
|
7626
7800
|
function trackInitScoreRegression(oldScore, newScore) {
|
|
7627
7801
|
trackEvent("init_score_regression", { old_score: oldScore, new_score: newScore });
|
|
7628
7802
|
}
|
|
7629
|
-
function trackInitCompleted(
|
|
7630
|
-
trackEvent("init_completed", { path:
|
|
7803
|
+
function trackInitCompleted(path44, score) {
|
|
7804
|
+
trackEvent("init_completed", { path: path44, score });
|
|
7631
7805
|
}
|
|
7632
7806
|
function trackRegenerateCompleted(action, durationMs) {
|
|
7633
7807
|
trackEvent("regenerate_completed", { action, duration_ms: durationMs });
|
|
@@ -8640,11 +8814,11 @@ async function scoreAndRefine(setup, dir, sessionHistory, callbacks, options) {
|
|
|
8640
8814
|
const maxIterations = options?.thorough ? 3 : MAX_REFINE_ITERATIONS;
|
|
8641
8815
|
const minPoints = options?.thorough ? 1 : MIN_POINTS_TO_REFINE;
|
|
8642
8816
|
const existsCache = /* @__PURE__ */ new Map();
|
|
8643
|
-
const cachedExists = (
|
|
8644
|
-
const cached = existsCache.get(
|
|
8817
|
+
const cachedExists = (path44) => {
|
|
8818
|
+
const cached = existsCache.get(path44);
|
|
8645
8819
|
if (cached !== void 0) return cached;
|
|
8646
|
-
const result = existsSync9(
|
|
8647
|
-
existsCache.set(
|
|
8820
|
+
const result = existsSync9(path44);
|
|
8821
|
+
existsCache.set(path44, result);
|
|
8648
8822
|
return result;
|
|
8649
8823
|
};
|
|
8650
8824
|
const projectStructure = collectProjectStructure(dir);
|
|
@@ -10056,6 +10230,8 @@ async function initCommand(options) {
|
|
|
10056
10230
|
}
|
|
10057
10231
|
installStopHook();
|
|
10058
10232
|
console.log(` ${chalk14.green("\u2713")} Onboarding hook \u2014 nudges new team members to set up`);
|
|
10233
|
+
installSessionStartHook();
|
|
10234
|
+
console.log(` ${chalk14.green("\u2713")} Freshness hook \u2014 warns when configs are stale`);
|
|
10059
10235
|
const { ensureBuiltinSkills: ensureBuiltinSkills2 } = await Promise.resolve().then(() => (init_builtin_skills(), builtin_skills_exports));
|
|
10060
10236
|
for (const agent of targetAgent) {
|
|
10061
10237
|
if (agent === "claude" && !fs34.existsSync(".claude"))
|
|
@@ -11051,8 +11227,8 @@ async function scoreCommand(options) {
|
|
|
11051
11227
|
}
|
|
11052
11228
|
|
|
11053
11229
|
// src/commands/refresh.ts
|
|
11054
|
-
import
|
|
11055
|
-
import
|
|
11230
|
+
import fs41 from "fs";
|
|
11231
|
+
import path34 from "path";
|
|
11056
11232
|
import chalk19 from "chalk";
|
|
11057
11233
|
import ora6 from "ora";
|
|
11058
11234
|
|
|
@@ -11139,52 +11315,91 @@ function collectDiff(lastSha) {
|
|
|
11139
11315
|
const summary = parts.join(", ") || "no changes";
|
|
11140
11316
|
return { hasChanges, committedDiff, stagedDiff, unstagedDiff, changedFiles, summary };
|
|
11141
11317
|
}
|
|
11318
|
+
function scopeDiffToDir(diff, dir, allConfigDirs) {
|
|
11319
|
+
if (dir === ".") {
|
|
11320
|
+
const otherDirs = allConfigDirs.filter((d) => d !== ".");
|
|
11321
|
+
if (otherDirs.length === 0) return diff;
|
|
11322
|
+
const changedFiles2 = diff.changedFiles.filter(
|
|
11323
|
+
(f) => !otherDirs.some((d) => f.startsWith(`${d}/`))
|
|
11324
|
+
);
|
|
11325
|
+
const hasChanges2 = changedFiles2.length > 0;
|
|
11326
|
+
return {
|
|
11327
|
+
...diff,
|
|
11328
|
+
changedFiles: changedFiles2,
|
|
11329
|
+
hasChanges: hasChanges2,
|
|
11330
|
+
summary: hasChanges2 ? `${changedFiles2.length} files changed` : "no changes"
|
|
11331
|
+
};
|
|
11332
|
+
}
|
|
11333
|
+
const prefix = `${dir}/`;
|
|
11334
|
+
const changedFiles = diff.changedFiles.filter((f) => f.startsWith(prefix)).map((f) => f.slice(prefix.length));
|
|
11335
|
+
const hasChanges = changedFiles.length > 0;
|
|
11336
|
+
return {
|
|
11337
|
+
...diff,
|
|
11338
|
+
changedFiles,
|
|
11339
|
+
hasChanges,
|
|
11340
|
+
summary: hasChanges ? `${changedFiles.length} files changed` : "no changes"
|
|
11341
|
+
};
|
|
11342
|
+
}
|
|
11142
11343
|
|
|
11143
11344
|
// src/writers/refresh.ts
|
|
11144
11345
|
init_pre_commit_block();
|
|
11145
11346
|
import fs37 from "fs";
|
|
11146
11347
|
import path30 from "path";
|
|
11147
|
-
function
|
|
11348
|
+
function writeFileGroup(groupDir, files) {
|
|
11349
|
+
fs37.mkdirSync(groupDir, { recursive: true });
|
|
11350
|
+
return files.map((file) => {
|
|
11351
|
+
const filePath = path30.join(groupDir, file.filename);
|
|
11352
|
+
fs37.writeFileSync(filePath, file.content);
|
|
11353
|
+
return filePath.replace(/\\/g, "/");
|
|
11354
|
+
});
|
|
11355
|
+
}
|
|
11356
|
+
function writeRefreshDocs(docs, dir = ".") {
|
|
11148
11357
|
const written = [];
|
|
11358
|
+
const p = (relPath) => (dir === "." ? relPath : path30.join(dir, relPath)).replace(/\\/g, "/");
|
|
11359
|
+
const ensureParent = (filePath) => {
|
|
11360
|
+
const parent = path30.dirname(filePath);
|
|
11361
|
+
if (parent !== "." && !fs37.existsSync(parent)) fs37.mkdirSync(parent, { recursive: true });
|
|
11362
|
+
};
|
|
11149
11363
|
if (docs.agentsMd) {
|
|
11150
|
-
|
|
11151
|
-
|
|
11364
|
+
const filePath = p("AGENTS.md");
|
|
11365
|
+
ensureParent(filePath);
|
|
11366
|
+
fs37.writeFileSync(filePath, appendManagedBlocks(docs.agentsMd, "codex"));
|
|
11367
|
+
written.push(filePath);
|
|
11152
11368
|
}
|
|
11153
11369
|
if (docs.claudeMd) {
|
|
11154
|
-
|
|
11155
|
-
|
|
11370
|
+
const filePath = p("CLAUDE.md");
|
|
11371
|
+
ensureParent(filePath);
|
|
11372
|
+
fs37.writeFileSync(filePath, appendManagedBlocks(docs.claudeMd));
|
|
11373
|
+
written.push(filePath);
|
|
11374
|
+
}
|
|
11375
|
+
if (docs.claudeRules) {
|
|
11376
|
+
written.push(...writeFileGroup(p(path30.join(".claude", "rules")), docs.claudeRules));
|
|
11156
11377
|
}
|
|
11157
11378
|
if (docs.readmeMd) {
|
|
11158
|
-
|
|
11159
|
-
|
|
11379
|
+
const filePath = p("README.md");
|
|
11380
|
+
ensureParent(filePath);
|
|
11381
|
+
fs37.writeFileSync(filePath, docs.readmeMd);
|
|
11382
|
+
written.push(filePath);
|
|
11160
11383
|
}
|
|
11161
11384
|
if (docs.cursorrules) {
|
|
11162
|
-
|
|
11163
|
-
|
|
11385
|
+
const filePath = p(".cursorrules");
|
|
11386
|
+
ensureParent(filePath);
|
|
11387
|
+
fs37.writeFileSync(filePath, docs.cursorrules);
|
|
11388
|
+
written.push(filePath);
|
|
11164
11389
|
}
|
|
11165
11390
|
if (docs.cursorRules) {
|
|
11166
|
-
|
|
11167
|
-
if (!fs37.existsSync(rulesDir)) fs37.mkdirSync(rulesDir, { recursive: true });
|
|
11168
|
-
for (const rule of docs.cursorRules) {
|
|
11169
|
-
fs37.writeFileSync(path30.join(rulesDir, rule.filename), rule.content);
|
|
11170
|
-
written.push(`.cursor/rules/${rule.filename}`);
|
|
11171
|
-
}
|
|
11391
|
+
written.push(...writeFileGroup(p(path30.join(".cursor", "rules")), docs.cursorRules));
|
|
11172
11392
|
}
|
|
11173
11393
|
if (docs.copilotInstructions) {
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
);
|
|
11179
|
-
written.push(".github/copilot-instructions.md");
|
|
11394
|
+
const filePath = p(path30.join(".github", "copilot-instructions.md"));
|
|
11395
|
+
ensureParent(filePath);
|
|
11396
|
+
fs37.writeFileSync(filePath, appendManagedBlocks(docs.copilotInstructions, "copilot"));
|
|
11397
|
+
written.push(filePath);
|
|
11180
11398
|
}
|
|
11181
11399
|
if (docs.copilotInstructionFiles) {
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
fs37.writeFileSync(path30.join(instructionsDir, file.filename), file.content);
|
|
11186
|
-
written.push(`.github/instructions/${file.filename}`);
|
|
11187
|
-
}
|
|
11400
|
+
written.push(
|
|
11401
|
+
...writeFileGroup(p(path30.join(".github", "instructions")), docs.copilotInstructionFiles)
|
|
11402
|
+
);
|
|
11188
11403
|
}
|
|
11189
11404
|
return written;
|
|
11190
11405
|
}
|
|
@@ -11192,8 +11407,15 @@ function writeRefreshDocs(docs) {
|
|
|
11192
11407
|
// src/ai/refresh.ts
|
|
11193
11408
|
init_config();
|
|
11194
11409
|
init_pre_commit_block();
|
|
11195
|
-
async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2) {
|
|
11196
|
-
const prompt = buildRefreshPrompt(
|
|
11410
|
+
async function refreshDocs(diff, existingDocs, projectContext, learnedSection, sources2, scope) {
|
|
11411
|
+
const prompt = buildRefreshPrompt(
|
|
11412
|
+
diff,
|
|
11413
|
+
existingDocs,
|
|
11414
|
+
projectContext,
|
|
11415
|
+
learnedSection,
|
|
11416
|
+
sources2,
|
|
11417
|
+
scope
|
|
11418
|
+
);
|
|
11197
11419
|
const fastModel = getFastModel();
|
|
11198
11420
|
const raw = await llmCall({
|
|
11199
11421
|
system: REFRESH_SYSTEM_PROMPT,
|
|
@@ -11203,8 +11425,14 @@ async function refreshDocs(diff, existingDocs, projectContext, learnedSection, s
|
|
|
11203
11425
|
});
|
|
11204
11426
|
return parseJsonResponse(raw);
|
|
11205
11427
|
}
|
|
11206
|
-
function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2) {
|
|
11428
|
+
function buildRefreshPrompt(diff, existingDocs, projectContext, learnedSection, sources2, scope) {
|
|
11207
11429
|
const parts = [];
|
|
11430
|
+
if (scope) {
|
|
11431
|
+
parts.push(`You are updating docs for the \`${scope}\` subdirectory of a monorepo.`);
|
|
11432
|
+
parts.push("Only include changes relevant to files under this directory.");
|
|
11433
|
+
parts.push("The changed files list has been filtered to this directory already.");
|
|
11434
|
+
parts.push("Ignore diff content for files outside this directory.\n");
|
|
11435
|
+
}
|
|
11208
11436
|
parts.push("Update documentation based on the following code changes.\n");
|
|
11209
11437
|
if (projectContext.packageName) parts.push(`Project: ${projectContext.packageName}`);
|
|
11210
11438
|
if (projectContext.languages?.length)
|
|
@@ -11258,9 +11486,17 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
11258
11486
|
parts.push(skill.content);
|
|
11259
11487
|
}
|
|
11260
11488
|
}
|
|
11489
|
+
if (existingDocs.claudeRules?.length) {
|
|
11490
|
+
for (const rule of existingDocs.claudeRules) {
|
|
11491
|
+
if (rule.filename.startsWith(CALIBER_MANAGED_PREFIX)) continue;
|
|
11492
|
+
parts.push(`
|
|
11493
|
+
[.claude/rules/${rule.filename}]`);
|
|
11494
|
+
parts.push(rule.content);
|
|
11495
|
+
}
|
|
11496
|
+
}
|
|
11261
11497
|
if (existingDocs.cursorRules?.length) {
|
|
11262
11498
|
for (const rule of existingDocs.cursorRules) {
|
|
11263
|
-
if (rule.filename.startsWith(
|
|
11499
|
+
if (rule.filename.startsWith(CALIBER_MANAGED_PREFIX)) continue;
|
|
11264
11500
|
parts.push(`
|
|
11265
11501
|
[.cursor/rules/${rule.filename}]`);
|
|
11266
11502
|
parts.push(rule.content);
|
|
@@ -11277,6 +11513,16 @@ Changed files: ${diff.changedFiles.join(", ")}`);
|
|
|
11277
11513
|
parts.push(file.content);
|
|
11278
11514
|
}
|
|
11279
11515
|
}
|
|
11516
|
+
if (existingDocs.includableDocs?.length) {
|
|
11517
|
+
parts.push(`
|
|
11518
|
+
--- Existing Documentation Files (use @include) ---`);
|
|
11519
|
+
parts.push(
|
|
11520
|
+
"These files exist in the project and can be referenced in CLAUDE.md using @./path:"
|
|
11521
|
+
);
|
|
11522
|
+
for (const doc of existingDocs.includableDocs) {
|
|
11523
|
+
parts.push(`- ${doc}`);
|
|
11524
|
+
}
|
|
11525
|
+
}
|
|
11280
11526
|
if (learnedSection) {
|
|
11281
11527
|
parts.push("\n--- Learned Patterns (from session learning) ---");
|
|
11282
11528
|
parts.push("Consider these accumulated learnings when deciding what to update:");
|
|
@@ -11486,10 +11732,77 @@ function migrateInlineLearnings() {
|
|
|
11486
11732
|
init_config();
|
|
11487
11733
|
init_resolve_caliber();
|
|
11488
11734
|
init_builtin_skills();
|
|
11735
|
+
|
|
11736
|
+
// src/lib/config-discovery.ts
|
|
11737
|
+
import fs39 from "fs";
|
|
11738
|
+
import path32 from "path";
|
|
11739
|
+
var CONFIG_FILE_MARKERS = [
|
|
11740
|
+
"CLAUDE.md",
|
|
11741
|
+
"AGENTS.md",
|
|
11742
|
+
".cursorrules",
|
|
11743
|
+
".github/copilot-instructions.md"
|
|
11744
|
+
];
|
|
11745
|
+
var CONFIG_DIR_MARKERS = [".cursor/rules", ".github/instructions", ".opencode/skills"];
|
|
11746
|
+
var IGNORE_DIRS3 = /* @__PURE__ */ new Set([
|
|
11747
|
+
"node_modules",
|
|
11748
|
+
".git",
|
|
11749
|
+
".next",
|
|
11750
|
+
"dist",
|
|
11751
|
+
"build",
|
|
11752
|
+
".cache",
|
|
11753
|
+
".turbo",
|
|
11754
|
+
"coverage",
|
|
11755
|
+
".caliber",
|
|
11756
|
+
"__pycache__",
|
|
11757
|
+
".venv",
|
|
11758
|
+
"vendor",
|
|
11759
|
+
"target"
|
|
11760
|
+
]);
|
|
11761
|
+
var MAX_DEPTH = 4;
|
|
11762
|
+
function hasConfigFiles(dir) {
|
|
11763
|
+
for (const marker of CONFIG_FILE_MARKERS) {
|
|
11764
|
+
if (fs39.existsSync(path32.join(dir, marker))) return true;
|
|
11765
|
+
}
|
|
11766
|
+
for (const marker of CONFIG_DIR_MARKERS) {
|
|
11767
|
+
const markerPath = path32.join(dir, marker);
|
|
11768
|
+
if (fs39.existsSync(markerPath) && fs39.statSync(markerPath).isDirectory()) return true;
|
|
11769
|
+
}
|
|
11770
|
+
return false;
|
|
11771
|
+
}
|
|
11772
|
+
function discoverConfigDirs(rootDir) {
|
|
11773
|
+
const dirs = [];
|
|
11774
|
+
if (hasConfigFiles(rootDir)) {
|
|
11775
|
+
dirs.push(".");
|
|
11776
|
+
}
|
|
11777
|
+
walkForConfigs(rootDir, rootDir, 0, dirs);
|
|
11778
|
+
dirs.sort();
|
|
11779
|
+
return dirs;
|
|
11780
|
+
}
|
|
11781
|
+
function walkForConfigs(baseDir, currentDir, depth, result) {
|
|
11782
|
+
if (depth >= MAX_DEPTH) return;
|
|
11783
|
+
let entries;
|
|
11784
|
+
try {
|
|
11785
|
+
entries = fs39.readdirSync(currentDir, { withFileTypes: true });
|
|
11786
|
+
} catch {
|
|
11787
|
+
return;
|
|
11788
|
+
}
|
|
11789
|
+
for (const entry of entries) {
|
|
11790
|
+
if (!entry.isDirectory()) continue;
|
|
11791
|
+
if (entry.name.startsWith(".") || IGNORE_DIRS3.has(entry.name)) continue;
|
|
11792
|
+
const fullPath = path32.join(currentDir, entry.name);
|
|
11793
|
+
const relPath = path32.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
11794
|
+
if (hasConfigFiles(fullPath)) {
|
|
11795
|
+
result.push(relPath);
|
|
11796
|
+
}
|
|
11797
|
+
walkForConfigs(baseDir, fullPath, depth + 1, result);
|
|
11798
|
+
}
|
|
11799
|
+
}
|
|
11800
|
+
|
|
11801
|
+
// src/commands/refresh.ts
|
|
11489
11802
|
function writeRefreshError(error) {
|
|
11490
11803
|
try {
|
|
11491
|
-
if (!
|
|
11492
|
-
|
|
11804
|
+
if (!fs41.existsSync(CALIBER_DIR)) fs41.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
11805
|
+
fs41.writeFileSync(
|
|
11493
11806
|
REFRESH_LAST_ERROR_FILE,
|
|
11494
11807
|
JSON.stringify(
|
|
11495
11808
|
{
|
|
@@ -11508,15 +11821,15 @@ function writeRefreshError(error) {
|
|
|
11508
11821
|
}
|
|
11509
11822
|
function readRefreshError() {
|
|
11510
11823
|
try {
|
|
11511
|
-
if (!
|
|
11512
|
-
return JSON.parse(
|
|
11824
|
+
if (!fs41.existsSync(REFRESH_LAST_ERROR_FILE)) return null;
|
|
11825
|
+
return JSON.parse(fs41.readFileSync(REFRESH_LAST_ERROR_FILE, "utf-8"));
|
|
11513
11826
|
} catch {
|
|
11514
11827
|
return null;
|
|
11515
11828
|
}
|
|
11516
11829
|
}
|
|
11517
11830
|
function clearRefreshError() {
|
|
11518
11831
|
try {
|
|
11519
|
-
if (
|
|
11832
|
+
if (fs41.existsSync(REFRESH_LAST_ERROR_FILE)) fs41.unlinkSync(REFRESH_LAST_ERROR_FILE);
|
|
11520
11833
|
} catch {
|
|
11521
11834
|
}
|
|
11522
11835
|
}
|
|
@@ -11536,11 +11849,11 @@ function log2(quiet, ...args) {
|
|
|
11536
11849
|
function discoverGitRepos(parentDir) {
|
|
11537
11850
|
const repos = [];
|
|
11538
11851
|
try {
|
|
11539
|
-
const entries =
|
|
11852
|
+
const entries = fs41.readdirSync(parentDir, { withFileTypes: true });
|
|
11540
11853
|
for (const entry of entries) {
|
|
11541
11854
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
11542
|
-
const childPath =
|
|
11543
|
-
if (
|
|
11855
|
+
const childPath = path34.join(parentDir, entry.name);
|
|
11856
|
+
if (fs41.existsSync(path34.join(childPath, ".git"))) {
|
|
11544
11857
|
repos.push(childPath);
|
|
11545
11858
|
}
|
|
11546
11859
|
}
|
|
@@ -11548,51 +11861,37 @@ function discoverGitRepos(parentDir) {
|
|
|
11548
11861
|
}
|
|
11549
11862
|
return repos.sort();
|
|
11550
11863
|
}
|
|
11551
|
-
function collectFilesToWrite(updatedDocs) {
|
|
11864
|
+
function collectFilesToWrite(updatedDocs, dir = ".") {
|
|
11552
11865
|
const files = [];
|
|
11553
|
-
|
|
11554
|
-
if (updatedDocs.
|
|
11555
|
-
if (updatedDocs.
|
|
11556
|
-
if (updatedDocs.
|
|
11866
|
+
const p = (relPath) => (dir === "." ? relPath : path34.join(dir, relPath)).replace(/\\/g, "/");
|
|
11867
|
+
if (updatedDocs.agentsMd) files.push(p("AGENTS.md"));
|
|
11868
|
+
if (updatedDocs.claudeMd) files.push(p("CLAUDE.md"));
|
|
11869
|
+
if (Array.isArray(updatedDocs.claudeRules)) {
|
|
11870
|
+
for (const r of updatedDocs.claudeRules)
|
|
11871
|
+
files.push(p(`.claude/rules/${r.filename}`));
|
|
11872
|
+
}
|
|
11873
|
+
if (updatedDocs.readmeMd) files.push(p("README.md"));
|
|
11874
|
+
if (updatedDocs.cursorrules) files.push(p(".cursorrules"));
|
|
11557
11875
|
if (Array.isArray(updatedDocs.cursorRules)) {
|
|
11558
11876
|
for (const r of updatedDocs.cursorRules)
|
|
11559
|
-
files.push(`.cursor/rules/${r.filename}`);
|
|
11877
|
+
files.push(p(`.cursor/rules/${r.filename}`));
|
|
11560
11878
|
}
|
|
11561
|
-
if (updatedDocs.copilotInstructions) files.push(".github/copilot-instructions.md");
|
|
11879
|
+
if (updatedDocs.copilotInstructions) files.push(p(".github/copilot-instructions.md"));
|
|
11562
11880
|
if (Array.isArray(updatedDocs.copilotInstructionFiles)) {
|
|
11563
11881
|
for (const f of updatedDocs.copilotInstructionFiles)
|
|
11564
|
-
files.push(`.github/instructions/${f.filename}`);
|
|
11882
|
+
files.push(p(`.github/instructions/${f.filename}`));
|
|
11565
11883
|
}
|
|
11566
11884
|
return files;
|
|
11567
11885
|
}
|
|
11568
11886
|
var REFRESH_COOLDOWN_MS = 3e4;
|
|
11569
|
-
async function
|
|
11887
|
+
async function refreshDir(repoDir, dir, diff, options) {
|
|
11570
11888
|
const quiet = !!options.quiet;
|
|
11571
11889
|
const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
|
|
11572
|
-
const
|
|
11573
|
-
const
|
|
11574
|
-
const currentSha = getCurrentHeadSha();
|
|
11575
|
-
if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
|
|
11576
|
-
const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
|
|
11577
|
-
if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
|
|
11578
|
-
log2(
|
|
11579
|
-
quiet,
|
|
11580
|
-
chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
|
|
11581
|
-
);
|
|
11582
|
-
return;
|
|
11583
|
-
}
|
|
11584
|
-
}
|
|
11585
|
-
const diff = collectDiff(lastSha);
|
|
11586
|
-
if (!diff.hasChanges) {
|
|
11587
|
-
if (currentSha) {
|
|
11588
|
-
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11589
|
-
}
|
|
11590
|
-
log2(quiet, chalk19.dim(`${prefix}No changes since last refresh.`));
|
|
11591
|
-
return;
|
|
11592
|
-
}
|
|
11890
|
+
const absDir = dir === "." ? repoDir : path34.resolve(repoDir, dir);
|
|
11891
|
+
const scope = dir === "." ? void 0 : dir;
|
|
11593
11892
|
const spinner = quiet ? null : ora6(`${prefix}Analyzing changes...`).start();
|
|
11594
11893
|
const learnedSection = readLearnedSection();
|
|
11595
|
-
const fingerprint = await collectFingerprint(
|
|
11894
|
+
const fingerprint = await collectFingerprint(absDir);
|
|
11596
11895
|
const existingDocs = fingerprint.existingConfigs;
|
|
11597
11896
|
const projectContext = {
|
|
11598
11897
|
languages: fingerprint.languages,
|
|
@@ -11600,8 +11899,8 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11600
11899
|
packageName: fingerprint.packageName,
|
|
11601
11900
|
fileTree: fingerprint.fileTree
|
|
11602
11901
|
};
|
|
11603
|
-
const workspaces = getDetectedWorkspaces(
|
|
11604
|
-
const sources2 = resolveAllSources(
|
|
11902
|
+
const workspaces = getDetectedWorkspaces(absDir);
|
|
11903
|
+
const sources2 = resolveAllSources(absDir, [], workspaces);
|
|
11605
11904
|
const diffPayload = {
|
|
11606
11905
|
committed: diff.committedDiff,
|
|
11607
11906
|
staged: diff.stagedDiff,
|
|
@@ -11617,7 +11916,8 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11617
11916
|
existingDocs,
|
|
11618
11917
|
projectContext,
|
|
11619
11918
|
learnedSection,
|
|
11620
|
-
sourcesPayload
|
|
11919
|
+
sourcesPayload,
|
|
11920
|
+
scope
|
|
11621
11921
|
);
|
|
11622
11922
|
} catch (firstErr) {
|
|
11623
11923
|
const isTransient = firstErr instanceof Error && TRANSIENT_ERRORS.some((e) => firstErr.message.toLowerCase().includes(e.toLowerCase()));
|
|
@@ -11628,7 +11928,8 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11628
11928
|
existingDocs,
|
|
11629
11929
|
projectContext,
|
|
11630
11930
|
learnedSection,
|
|
11631
|
-
sourcesPayload
|
|
11931
|
+
sourcesPayload,
|
|
11932
|
+
scope
|
|
11632
11933
|
);
|
|
11633
11934
|
} catch {
|
|
11634
11935
|
spinner?.fail(`${prefix}Refresh failed after retry`);
|
|
@@ -11637,10 +11938,7 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11637
11938
|
}
|
|
11638
11939
|
if (!response.docsUpdated || response.docsUpdated.length === 0) {
|
|
11639
11940
|
spinner?.succeed(`${prefix}No doc updates needed`);
|
|
11640
|
-
|
|
11641
|
-
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11642
|
-
}
|
|
11643
|
-
return;
|
|
11941
|
+
return { written: [] };
|
|
11644
11942
|
}
|
|
11645
11943
|
if (options.dryRun) {
|
|
11646
11944
|
spinner?.info(`${prefix}Dry run \u2014 would update:`);
|
|
@@ -11651,49 +11949,53 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11651
11949
|
console.log(chalk19.dim(`
|
|
11652
11950
|
${response.changesSummary}`));
|
|
11653
11951
|
}
|
|
11654
|
-
return;
|
|
11952
|
+
return { written: [] };
|
|
11655
11953
|
}
|
|
11656
|
-
const
|
|
11657
|
-
const preScore = computeLocalScore(repoDir, targetAgent);
|
|
11658
|
-
const allFilesToWrite = collectFilesToWrite(response.updatedDocs);
|
|
11954
|
+
const allFilesToWrite = collectFilesToWrite(response.updatedDocs, dir);
|
|
11659
11955
|
const preRefreshContents = /* @__PURE__ */ new Map();
|
|
11660
11956
|
for (const filePath of allFilesToWrite) {
|
|
11661
|
-
const fullPath =
|
|
11957
|
+
const fullPath = path34.resolve(repoDir, filePath);
|
|
11662
11958
|
try {
|
|
11663
|
-
preRefreshContents.set(filePath,
|
|
11959
|
+
preRefreshContents.set(filePath, fs41.readFileSync(fullPath, "utf-8"));
|
|
11664
11960
|
} catch {
|
|
11665
11961
|
preRefreshContents.set(filePath, null);
|
|
11666
11962
|
}
|
|
11667
11963
|
}
|
|
11668
|
-
const
|
|
11964
|
+
const state = readState();
|
|
11965
|
+
const targetAgent = state?.targetAgent ?? detectTargetAgent(repoDir);
|
|
11966
|
+
const runQualityGate = dir === ".";
|
|
11967
|
+
const preScore = runQualityGate ? computeLocalScore(absDir, targetAgent) : null;
|
|
11968
|
+
const written = writeRefreshDocs(response.updatedDocs, dir);
|
|
11669
11969
|
const trigger = quiet ? "hook" : "manual";
|
|
11670
11970
|
trackRefreshCompleted(written.length, Date.now(), trigger);
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
const
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11971
|
+
if (runQualityGate && preScore) {
|
|
11972
|
+
const postScore = computeLocalScore(absDir, targetAgent);
|
|
11973
|
+
if (postScore.score < preScore.score) {
|
|
11974
|
+
for (const [filePath, content] of preRefreshContents) {
|
|
11975
|
+
const fullPath = path34.resolve(repoDir, filePath);
|
|
11976
|
+
if (content === null) {
|
|
11977
|
+
try {
|
|
11978
|
+
fs41.unlinkSync(fullPath);
|
|
11979
|
+
} catch {
|
|
11980
|
+
}
|
|
11981
|
+
} else {
|
|
11982
|
+
fs41.writeFileSync(fullPath, content);
|
|
11679
11983
|
}
|
|
11680
|
-
} else {
|
|
11681
|
-
fs40.writeFileSync(fullPath, content);
|
|
11682
11984
|
}
|
|
11985
|
+
spinner?.warn(
|
|
11986
|
+
`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
|
|
11987
|
+
);
|
|
11988
|
+
log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
|
|
11989
|
+
return { written: [] };
|
|
11683
11990
|
}
|
|
11684
|
-
|
|
11685
|
-
`${prefix}Refresh reverted \u2014 score would drop from ${preScore.score} to ${postScore.score}`
|
|
11686
|
-
);
|
|
11687
|
-
log2(quiet, chalk19.dim(` Config quality gate prevented a regression. No files were changed.`));
|
|
11688
|
-
if (currentSha) {
|
|
11689
|
-
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11690
|
-
}
|
|
11691
|
-
return;
|
|
11991
|
+
recordScore(postScore, "refresh");
|
|
11692
11992
|
}
|
|
11693
|
-
recordScore(postScore, "refresh");
|
|
11694
11993
|
spinner?.succeed(`${prefix}Updated ${written.length} doc${written.length === 1 ? "" : "s"}`);
|
|
11695
11994
|
const fileChangesMap = new Map(
|
|
11696
|
-
(response.fileChanges || []).map((fc) => [
|
|
11995
|
+
(response.fileChanges || []).map((fc) => [
|
|
11996
|
+
fc.file,
|
|
11997
|
+
fc.description
|
|
11998
|
+
])
|
|
11697
11999
|
);
|
|
11698
12000
|
for (const file of written) {
|
|
11699
12001
|
const desc = fileChangesMap.get(file);
|
|
@@ -11709,6 +12011,59 @@ async function refreshSingleRepo(repoDir, options) {
|
|
|
11709
12011
|
log2(quiet, chalk19.dim(`
|
|
11710
12012
|
${response.changesSummary}`));
|
|
11711
12013
|
}
|
|
12014
|
+
return { written };
|
|
12015
|
+
}
|
|
12016
|
+
async function refreshSingleRepo(repoDir, options) {
|
|
12017
|
+
const quiet = !!options.quiet;
|
|
12018
|
+
const prefix = options.label ? `${chalk19.bold(options.label)} ` : "";
|
|
12019
|
+
const state = readState();
|
|
12020
|
+
const lastSha = state?.lastRefreshSha ?? null;
|
|
12021
|
+
const currentSha = getCurrentHeadSha();
|
|
12022
|
+
if (state?.lastRefreshTimestamp && lastSha && currentSha === lastSha) {
|
|
12023
|
+
const elapsed = Date.now() - new Date(state.lastRefreshTimestamp).getTime();
|
|
12024
|
+
if (elapsed < REFRESH_COOLDOWN_MS && elapsed > 0) {
|
|
12025
|
+
log2(
|
|
12026
|
+
quiet,
|
|
12027
|
+
chalk19.dim(`${prefix}Skipped \u2014 last refresh was ${Math.round(elapsed / 1e3)}s ago.`)
|
|
12028
|
+
);
|
|
12029
|
+
return;
|
|
12030
|
+
}
|
|
12031
|
+
}
|
|
12032
|
+
const diff = collectDiff(lastSha);
|
|
12033
|
+
if (!diff.hasChanges) {
|
|
12034
|
+
if (currentSha) {
|
|
12035
|
+
writeState({ lastRefreshSha: currentSha, lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
12036
|
+
}
|
|
12037
|
+
log2(quiet, chalk19.dim(`${prefix}No changes since last refresh.`));
|
|
12038
|
+
return;
|
|
12039
|
+
}
|
|
12040
|
+
const configDirs = discoverConfigDirs(repoDir);
|
|
12041
|
+
if (configDirs.length <= 1) {
|
|
12042
|
+
await refreshDir(repoDir, ".", diff, options);
|
|
12043
|
+
} else {
|
|
12044
|
+
log2(quiet, chalk19.dim(`${prefix}Found configs in ${configDirs.length} directories
|
|
12045
|
+
`));
|
|
12046
|
+
let hadFailure = false;
|
|
12047
|
+
for (const dir of configDirs) {
|
|
12048
|
+
const scopedDiff = scopeDiffToDir(diff, dir, configDirs);
|
|
12049
|
+
if (!scopedDiff.hasChanges) continue;
|
|
12050
|
+
const dirLabel = dir === "." ? "root" : dir;
|
|
12051
|
+
try {
|
|
12052
|
+
await refreshDir(repoDir, dir, scopedDiff, { ...options, label: dirLabel });
|
|
12053
|
+
} catch (err) {
|
|
12054
|
+
hadFailure = true;
|
|
12055
|
+
log2(
|
|
12056
|
+
quiet,
|
|
12057
|
+
chalk19.yellow(
|
|
12058
|
+
` ${dirLabel}: refresh failed \u2014 ${err instanceof Error ? err.message : "unknown error"}`
|
|
12059
|
+
)
|
|
12060
|
+
);
|
|
12061
|
+
}
|
|
12062
|
+
}
|
|
12063
|
+
if (hadFailure) {
|
|
12064
|
+
return;
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
11712
12067
|
const builtinWritten = ensureBuiltinSkills();
|
|
11713
12068
|
for (const file of builtinWritten) {
|
|
11714
12069
|
log2(quiet, ` ${chalk19.green("\u2713")} ${file} ${chalk19.dim("(built-in)")}`);
|
|
@@ -11765,7 +12120,7 @@ async function refreshCommand(options) {
|
|
|
11765
12120
|
`));
|
|
11766
12121
|
const originalDir = process.cwd();
|
|
11767
12122
|
for (const repo of repos) {
|
|
11768
|
-
const repoName =
|
|
12123
|
+
const repoName = path34.basename(repo);
|
|
11769
12124
|
try {
|
|
11770
12125
|
process.chdir(repo);
|
|
11771
12126
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -11793,7 +12148,7 @@ async function refreshCommand(options) {
|
|
|
11793
12148
|
|
|
11794
12149
|
// src/commands/hooks.ts
|
|
11795
12150
|
import chalk20 from "chalk";
|
|
11796
|
-
import
|
|
12151
|
+
import fs42 from "fs";
|
|
11797
12152
|
var HOOKS = [
|
|
11798
12153
|
{
|
|
11799
12154
|
id: "session-end",
|
|
@@ -11810,6 +12165,22 @@ var HOOKS = [
|
|
|
11810
12165
|
isInstalled: isPreCommitHookInstalled,
|
|
11811
12166
|
install: installPreCommitHook,
|
|
11812
12167
|
remove: removePreCommitHook
|
|
12168
|
+
},
|
|
12169
|
+
{
|
|
12170
|
+
id: "session-start",
|
|
12171
|
+
label: "Claude Code SessionStart",
|
|
12172
|
+
description: "Check config freshness when a session starts",
|
|
12173
|
+
isInstalled: isSessionStartHookInstalled,
|
|
12174
|
+
install: installSessionStartHook,
|
|
12175
|
+
remove: removeSessionStartHook
|
|
12176
|
+
},
|
|
12177
|
+
{
|
|
12178
|
+
id: "notification",
|
|
12179
|
+
label: "Claude Code Notification",
|
|
12180
|
+
description: "Warn when agent configs are stale (>15 commits behind)",
|
|
12181
|
+
isInstalled: isNotificationHookInstalled,
|
|
12182
|
+
install: installNotificationHook,
|
|
12183
|
+
remove: removeNotificationHook
|
|
11813
12184
|
}
|
|
11814
12185
|
];
|
|
11815
12186
|
function printStatus() {
|
|
@@ -11825,8 +12196,12 @@ function printStatus() {
|
|
|
11825
12196
|
}
|
|
11826
12197
|
async function hooksCommand(options) {
|
|
11827
12198
|
if (!options.install && !options.remove) {
|
|
11828
|
-
console.log(
|
|
11829
|
-
|
|
12199
|
+
console.log(
|
|
12200
|
+
chalk20.dim("\n Note: caliber now adds refresh instructions directly to config files.")
|
|
12201
|
+
);
|
|
12202
|
+
console.log(
|
|
12203
|
+
chalk20.dim(" These hooks are available for non-agent workflows (manual commits).\n")
|
|
12204
|
+
);
|
|
11830
12205
|
}
|
|
11831
12206
|
if (options.install) {
|
|
11832
12207
|
for (const hook of HOOKS) {
|
|
@@ -11837,11 +12212,11 @@ async function hooksCommand(options) {
|
|
|
11837
12212
|
console.log(chalk20.green(" \u2713") + ` ${hook.label} enabled`);
|
|
11838
12213
|
}
|
|
11839
12214
|
}
|
|
11840
|
-
if (
|
|
12215
|
+
if (fs42.existsSync(".claude")) {
|
|
11841
12216
|
const r = installLearningHooks();
|
|
11842
12217
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Claude Code learning hooks enabled");
|
|
11843
12218
|
}
|
|
11844
|
-
if (
|
|
12219
|
+
if (fs42.existsSync(".cursor")) {
|
|
11845
12220
|
const r = installCursorLearningHooks();
|
|
11846
12221
|
if (r.installed) console.log(chalk20.green(" \u2713") + " Cursor learning hooks enabled");
|
|
11847
12222
|
}
|
|
@@ -12009,8 +12384,8 @@ async function configCommand() {
|
|
|
12009
12384
|
}
|
|
12010
12385
|
|
|
12011
12386
|
// src/commands/learn.ts
|
|
12012
|
-
import
|
|
12013
|
-
import
|
|
12387
|
+
import fs46 from "fs";
|
|
12388
|
+
import path38 from "path";
|
|
12014
12389
|
import chalk23 from "chalk";
|
|
12015
12390
|
|
|
12016
12391
|
// src/learner/stdin.ts
|
|
@@ -12041,8 +12416,8 @@ function readStdin() {
|
|
|
12041
12416
|
}
|
|
12042
12417
|
|
|
12043
12418
|
// src/learner/storage.ts
|
|
12044
|
-
import
|
|
12045
|
-
import
|
|
12419
|
+
import fs43 from "fs";
|
|
12420
|
+
import path35 from "path";
|
|
12046
12421
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
12047
12422
|
var DEFAULT_STATE = {
|
|
12048
12423
|
sessionId: null,
|
|
@@ -12051,15 +12426,15 @@ var DEFAULT_STATE = {
|
|
|
12051
12426
|
lastAnalysisEventCount: 0
|
|
12052
12427
|
};
|
|
12053
12428
|
function ensureLearningDir() {
|
|
12054
|
-
if (!
|
|
12055
|
-
|
|
12429
|
+
if (!fs43.existsSync(getLearningDir())) {
|
|
12430
|
+
fs43.mkdirSync(getLearningDir(), { recursive: true });
|
|
12056
12431
|
}
|
|
12057
12432
|
}
|
|
12058
12433
|
function sessionFilePath() {
|
|
12059
|
-
return
|
|
12434
|
+
return path35.join(getLearningDir(), LEARNING_SESSION_FILE);
|
|
12060
12435
|
}
|
|
12061
12436
|
function stateFilePath() {
|
|
12062
|
-
return
|
|
12437
|
+
return path35.join(getLearningDir(), LEARNING_STATE_FILE);
|
|
12063
12438
|
}
|
|
12064
12439
|
function truncateResponse(response) {
|
|
12065
12440
|
const str = JSON.stringify(response);
|
|
@@ -12069,10 +12444,10 @@ function truncateResponse(response) {
|
|
|
12069
12444
|
function trimSessionFileIfNeeded(filePath) {
|
|
12070
12445
|
const state = readState2();
|
|
12071
12446
|
if (state.eventCount + 1 > LEARNING_MAX_EVENTS) {
|
|
12072
|
-
const lines =
|
|
12447
|
+
const lines = fs43.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
12073
12448
|
if (lines.length > LEARNING_MAX_EVENTS) {
|
|
12074
12449
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
12075
|
-
|
|
12450
|
+
fs43.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
12076
12451
|
}
|
|
12077
12452
|
}
|
|
12078
12453
|
}
|
|
@@ -12080,19 +12455,19 @@ function appendEvent(event) {
|
|
|
12080
12455
|
ensureLearningDir();
|
|
12081
12456
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
12082
12457
|
const filePath = sessionFilePath();
|
|
12083
|
-
|
|
12458
|
+
fs43.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
12084
12459
|
trimSessionFileIfNeeded(filePath);
|
|
12085
12460
|
}
|
|
12086
12461
|
function appendPromptEvent(event) {
|
|
12087
12462
|
ensureLearningDir();
|
|
12088
12463
|
const filePath = sessionFilePath();
|
|
12089
|
-
|
|
12464
|
+
fs43.appendFileSync(filePath, JSON.stringify(event) + "\n");
|
|
12090
12465
|
trimSessionFileIfNeeded(filePath);
|
|
12091
12466
|
}
|
|
12092
12467
|
function readAllEvents() {
|
|
12093
12468
|
const filePath = sessionFilePath();
|
|
12094
|
-
if (!
|
|
12095
|
-
const lines =
|
|
12469
|
+
if (!fs43.existsSync(filePath)) return [];
|
|
12470
|
+
const lines = fs43.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
12096
12471
|
const events = [];
|
|
12097
12472
|
for (const line of lines) {
|
|
12098
12473
|
try {
|
|
@@ -12104,26 +12479,26 @@ function readAllEvents() {
|
|
|
12104
12479
|
}
|
|
12105
12480
|
function getEventCount() {
|
|
12106
12481
|
const filePath = sessionFilePath();
|
|
12107
|
-
if (!
|
|
12108
|
-
const content =
|
|
12482
|
+
if (!fs43.existsSync(filePath)) return 0;
|
|
12483
|
+
const content = fs43.readFileSync(filePath, "utf-8");
|
|
12109
12484
|
return content.split("\n").filter(Boolean).length;
|
|
12110
12485
|
}
|
|
12111
12486
|
function clearSession() {
|
|
12112
12487
|
const filePath = sessionFilePath();
|
|
12113
|
-
if (
|
|
12488
|
+
if (fs43.existsSync(filePath)) fs43.unlinkSync(filePath);
|
|
12114
12489
|
}
|
|
12115
12490
|
function readState2() {
|
|
12116
12491
|
const filePath = stateFilePath();
|
|
12117
|
-
if (!
|
|
12492
|
+
if (!fs43.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
12118
12493
|
try {
|
|
12119
|
-
return JSON.parse(
|
|
12494
|
+
return JSON.parse(fs43.readFileSync(filePath, "utf-8"));
|
|
12120
12495
|
} catch {
|
|
12121
12496
|
return { ...DEFAULT_STATE };
|
|
12122
12497
|
}
|
|
12123
12498
|
}
|
|
12124
12499
|
function writeState2(state) {
|
|
12125
12500
|
ensureLearningDir();
|
|
12126
|
-
|
|
12501
|
+
fs43.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
12127
12502
|
}
|
|
12128
12503
|
function resetState() {
|
|
12129
12504
|
writeState2({ ...DEFAULT_STATE });
|
|
@@ -12131,16 +12506,16 @@ function resetState() {
|
|
|
12131
12506
|
var LOCK_FILE = "finalize.lock";
|
|
12132
12507
|
var LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
12133
12508
|
function lockFilePath() {
|
|
12134
|
-
return
|
|
12509
|
+
return path35.join(getLearningDir(), LOCK_FILE);
|
|
12135
12510
|
}
|
|
12136
12511
|
function acquireFinalizeLock() {
|
|
12137
12512
|
ensureLearningDir();
|
|
12138
12513
|
const lockPath = lockFilePath();
|
|
12139
|
-
if (
|
|
12514
|
+
if (fs43.existsSync(lockPath)) {
|
|
12140
12515
|
try {
|
|
12141
|
-
const stat =
|
|
12516
|
+
const stat = fs43.statSync(lockPath);
|
|
12142
12517
|
if (Date.now() - stat.mtimeMs < LOCK_STALE_MS) {
|
|
12143
|
-
const pid = parseInt(
|
|
12518
|
+
const pid = parseInt(fs43.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
12144
12519
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
12145
12520
|
return false;
|
|
12146
12521
|
}
|
|
@@ -12148,12 +12523,12 @@ function acquireFinalizeLock() {
|
|
|
12148
12523
|
} catch {
|
|
12149
12524
|
}
|
|
12150
12525
|
try {
|
|
12151
|
-
|
|
12526
|
+
fs43.unlinkSync(lockPath);
|
|
12152
12527
|
} catch {
|
|
12153
12528
|
}
|
|
12154
12529
|
}
|
|
12155
12530
|
try {
|
|
12156
|
-
|
|
12531
|
+
fs43.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
12157
12532
|
return true;
|
|
12158
12533
|
} catch {
|
|
12159
12534
|
return false;
|
|
@@ -12170,30 +12545,30 @@ function isProcessAlive(pid) {
|
|
|
12170
12545
|
function releaseFinalizeLock() {
|
|
12171
12546
|
const lockPath = lockFilePath();
|
|
12172
12547
|
try {
|
|
12173
|
-
if (
|
|
12548
|
+
if (fs43.existsSync(lockPath)) fs43.unlinkSync(lockPath);
|
|
12174
12549
|
} catch {
|
|
12175
12550
|
}
|
|
12176
12551
|
}
|
|
12177
12552
|
|
|
12178
12553
|
// src/lib/notifications.ts
|
|
12179
|
-
import
|
|
12180
|
-
import
|
|
12554
|
+
import fs44 from "fs";
|
|
12555
|
+
import path36 from "path";
|
|
12181
12556
|
import chalk22 from "chalk";
|
|
12182
12557
|
function notificationFilePath() {
|
|
12183
|
-
return
|
|
12558
|
+
return path36.join(getLearningDir(), "last-finalize-summary.json");
|
|
12184
12559
|
}
|
|
12185
12560
|
function writeFinalizeSummary(summary) {
|
|
12186
12561
|
try {
|
|
12187
12562
|
ensureLearningDir();
|
|
12188
|
-
|
|
12563
|
+
fs44.writeFileSync(notificationFilePath(), JSON.stringify(summary, null, 2));
|
|
12189
12564
|
} catch {
|
|
12190
12565
|
}
|
|
12191
12566
|
}
|
|
12192
12567
|
function checkPendingNotifications() {
|
|
12193
12568
|
try {
|
|
12194
|
-
if (!
|
|
12195
|
-
const raw =
|
|
12196
|
-
|
|
12569
|
+
if (!fs44.existsSync(notificationFilePath())) return;
|
|
12570
|
+
const raw = fs44.readFileSync(notificationFilePath(), "utf-8");
|
|
12571
|
+
fs44.unlinkSync(notificationFilePath());
|
|
12197
12572
|
const summary = JSON.parse(raw);
|
|
12198
12573
|
if (!summary.newItemCount || summary.newItemCount === 0) return;
|
|
12199
12574
|
const wasteLabel = summary.wasteTokens > 0 ? ` (~${summary.wasteTokens.toLocaleString()} wasted tokens captured)` : "";
|
|
@@ -12209,7 +12584,7 @@ function checkPendingNotifications() {
|
|
|
12209
12584
|
console.log("");
|
|
12210
12585
|
} catch {
|
|
12211
12586
|
try {
|
|
12212
|
-
|
|
12587
|
+
fs44.unlinkSync(notificationFilePath());
|
|
12213
12588
|
} catch {
|
|
12214
12589
|
}
|
|
12215
12590
|
}
|
|
@@ -12361,8 +12736,8 @@ function calculateSessionWaste(events) {
|
|
|
12361
12736
|
init_config();
|
|
12362
12737
|
|
|
12363
12738
|
// src/learner/roi.ts
|
|
12364
|
-
import
|
|
12365
|
-
import
|
|
12739
|
+
import fs45 from "fs";
|
|
12740
|
+
import path37 from "path";
|
|
12366
12741
|
var DEFAULT_TOTALS = {
|
|
12367
12742
|
totalWasteTokens: 0,
|
|
12368
12743
|
totalWasteSeconds: 0,
|
|
@@ -12376,19 +12751,19 @@ var DEFAULT_TOTALS = {
|
|
|
12376
12751
|
lastSessionTimestamp: ""
|
|
12377
12752
|
};
|
|
12378
12753
|
function roiFilePath() {
|
|
12379
|
-
return
|
|
12754
|
+
return path37.join(getLearningDir(), LEARNING_ROI_FILE);
|
|
12380
12755
|
}
|
|
12381
12756
|
function readROIStats() {
|
|
12382
12757
|
const filePath = roiFilePath();
|
|
12383
|
-
if (!
|
|
12758
|
+
if (!fs45.existsSync(filePath)) {
|
|
12384
12759
|
return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
|
|
12385
12760
|
}
|
|
12386
12761
|
try {
|
|
12387
|
-
return JSON.parse(
|
|
12762
|
+
return JSON.parse(fs45.readFileSync(filePath, "utf-8"));
|
|
12388
12763
|
} catch {
|
|
12389
12764
|
try {
|
|
12390
12765
|
const corruptPath = filePath + ".corrupt";
|
|
12391
|
-
|
|
12766
|
+
fs45.renameSync(filePath, corruptPath);
|
|
12392
12767
|
console.error(`caliber: roi-stats.json was corrupt \u2014 renamed to ${corruptPath}`);
|
|
12393
12768
|
} catch {
|
|
12394
12769
|
}
|
|
@@ -12397,7 +12772,7 @@ function readROIStats() {
|
|
|
12397
12772
|
}
|
|
12398
12773
|
function writeROIStats(stats) {
|
|
12399
12774
|
ensureLearningDir();
|
|
12400
|
-
|
|
12775
|
+
fs45.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
|
|
12401
12776
|
}
|
|
12402
12777
|
function recalculateTotals(stats) {
|
|
12403
12778
|
const totals = stats.totals;
|
|
@@ -12606,9 +12981,9 @@ var AUTO_SETTLE_MS = 200;
|
|
|
12606
12981
|
var INCREMENTAL_INTERVAL = 50;
|
|
12607
12982
|
function writeFinalizeError(message) {
|
|
12608
12983
|
try {
|
|
12609
|
-
const errorPath =
|
|
12610
|
-
if (!
|
|
12611
|
-
|
|
12984
|
+
const errorPath = path38.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
12985
|
+
if (!fs46.existsSync(getLearningDir())) fs46.mkdirSync(getLearningDir(), { recursive: true });
|
|
12986
|
+
fs46.writeFileSync(errorPath, JSON.stringify({
|
|
12612
12987
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12613
12988
|
error: message,
|
|
12614
12989
|
pid: process.pid
|
|
@@ -12618,9 +12993,9 @@ function writeFinalizeError(message) {
|
|
|
12618
12993
|
}
|
|
12619
12994
|
function readFinalizeError() {
|
|
12620
12995
|
try {
|
|
12621
|
-
const errorPath =
|
|
12622
|
-
if (!
|
|
12623
|
-
return JSON.parse(
|
|
12996
|
+
const errorPath = path38.join(getLearningDir(), LEARNING_LAST_ERROR_FILE);
|
|
12997
|
+
if (!fs46.existsSync(errorPath)) return null;
|
|
12998
|
+
return JSON.parse(fs46.readFileSync(errorPath, "utf-8"));
|
|
12624
12999
|
} catch {
|
|
12625
13000
|
return null;
|
|
12626
13001
|
}
|
|
@@ -12667,14 +13042,14 @@ async function learnObserveCommand(options) {
|
|
|
12667
13042
|
const { resolveCaliber: resolveCaliber2 } = await Promise.resolve().then(() => (init_resolve_caliber(), resolve_caliber_exports));
|
|
12668
13043
|
const bin = resolveCaliber2();
|
|
12669
13044
|
const { spawn: spawn4 } = await import("child_process");
|
|
12670
|
-
const logPath =
|
|
12671
|
-
if (!
|
|
12672
|
-
const logFd =
|
|
13045
|
+
const logPath = path38.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
13046
|
+
if (!fs46.existsSync(getLearningDir())) fs46.mkdirSync(getLearningDir(), { recursive: true });
|
|
13047
|
+
const logFd = fs46.openSync(logPath, "a");
|
|
12673
13048
|
spawn4(bin, ["learn", "finalize", "--auto", "--incremental"], {
|
|
12674
13049
|
detached: true,
|
|
12675
13050
|
stdio: ["ignore", logFd, logFd]
|
|
12676
13051
|
}).unref();
|
|
12677
|
-
|
|
13052
|
+
fs46.closeSync(logFd);
|
|
12678
13053
|
} catch {
|
|
12679
13054
|
}
|
|
12680
13055
|
}
|
|
@@ -12881,7 +13256,7 @@ async function learnFinalizeCommand(options) {
|
|
|
12881
13256
|
}
|
|
12882
13257
|
async function learnInstallCommand() {
|
|
12883
13258
|
let anyInstalled = false;
|
|
12884
|
-
if (
|
|
13259
|
+
if (fs46.existsSync(".claude")) {
|
|
12885
13260
|
const r = installLearningHooks();
|
|
12886
13261
|
if (r.installed) {
|
|
12887
13262
|
console.log(chalk23.green("\u2713") + " Claude Code learning hooks installed");
|
|
@@ -12890,7 +13265,7 @@ async function learnInstallCommand() {
|
|
|
12890
13265
|
console.log(chalk23.dim(" Claude Code hooks already installed"));
|
|
12891
13266
|
}
|
|
12892
13267
|
}
|
|
12893
|
-
if (
|
|
13268
|
+
if (fs46.existsSync(".cursor")) {
|
|
12894
13269
|
const r = installCursorLearningHooks();
|
|
12895
13270
|
if (r.installed) {
|
|
12896
13271
|
console.log(chalk23.green("\u2713") + " Cursor learning hooks installed");
|
|
@@ -12899,7 +13274,7 @@ async function learnInstallCommand() {
|
|
|
12899
13274
|
console.log(chalk23.dim(" Cursor hooks already installed"));
|
|
12900
13275
|
}
|
|
12901
13276
|
}
|
|
12902
|
-
if (!
|
|
13277
|
+
if (!fs46.existsSync(".claude") && !fs46.existsSync(".cursor")) {
|
|
12903
13278
|
console.log(chalk23.yellow("No .claude/ or .cursor/ directory found."));
|
|
12904
13279
|
console.log(chalk23.dim(` Run \`${resolveCaliber()} init\` first, or create the directory manually.`));
|
|
12905
13280
|
return;
|
|
@@ -12957,8 +13332,8 @@ async function learnStatusCommand() {
|
|
|
12957
13332
|
if (lastError) {
|
|
12958
13333
|
console.log(`Last error: ${chalk23.red(lastError.error)}`);
|
|
12959
13334
|
console.log(chalk23.dim(` at ${lastError.timestamp}`));
|
|
12960
|
-
const logPath =
|
|
12961
|
-
if (
|
|
13335
|
+
const logPath = path38.join(getLearningDir(), LEARNING_FINALIZE_LOG);
|
|
13336
|
+
if (fs46.existsSync(logPath)) {
|
|
12962
13337
|
console.log(chalk23.dim(` Full log: ${logPath}`));
|
|
12963
13338
|
}
|
|
12964
13339
|
}
|
|
@@ -13038,11 +13413,11 @@ async function learnDeleteCommand(indexStr) {
|
|
|
13038
13413
|
}
|
|
13039
13414
|
const item = items[targetIdx];
|
|
13040
13415
|
const filePath = item.source === "personal" ? PERSONAL_LEARNINGS_FILE : "CALIBER_LEARNINGS.md";
|
|
13041
|
-
if (!
|
|
13416
|
+
if (!fs46.existsSync(filePath)) {
|
|
13042
13417
|
console.log(chalk23.red("Learnings file not found."));
|
|
13043
13418
|
return;
|
|
13044
13419
|
}
|
|
13045
|
-
const content =
|
|
13420
|
+
const content = fs46.readFileSync(filePath, "utf-8");
|
|
13046
13421
|
const lines = content.split("\n");
|
|
13047
13422
|
const bulletsOfSource = items.filter((i) => i.source === item.source);
|
|
13048
13423
|
const posInFile = bulletsOfSource.indexOf(item);
|
|
@@ -13063,9 +13438,9 @@ async function learnDeleteCommand(indexStr) {
|
|
|
13063
13438
|
}
|
|
13064
13439
|
const bulletToRemove = lines[lineToRemove];
|
|
13065
13440
|
const newLines = lines.filter((_, i) => i !== lineToRemove);
|
|
13066
|
-
|
|
13441
|
+
fs46.writeFileSync(filePath, newLines.join("\n"));
|
|
13067
13442
|
if (item.source === "personal") {
|
|
13068
|
-
|
|
13443
|
+
fs46.chmodSync(filePath, 384);
|
|
13069
13444
|
}
|
|
13070
13445
|
const roiStats = readROIStats();
|
|
13071
13446
|
const cleanText = bulletToRemove.replace(/^- /, "").replace(/^\*\*\[[^\]]+\]\*\*\s*/, "").trim();
|
|
@@ -13238,8 +13613,8 @@ async function insightsCommand(options) {
|
|
|
13238
13613
|
}
|
|
13239
13614
|
|
|
13240
13615
|
// src/commands/sources.ts
|
|
13241
|
-
import
|
|
13242
|
-
import
|
|
13616
|
+
import fs47 from "fs";
|
|
13617
|
+
import path39 from "path";
|
|
13243
13618
|
import chalk25 from "chalk";
|
|
13244
13619
|
init_resolve_caliber();
|
|
13245
13620
|
async function sourcesListCommand() {
|
|
@@ -13256,9 +13631,9 @@ async function sourcesListCommand() {
|
|
|
13256
13631
|
if (configSources.length > 0) {
|
|
13257
13632
|
for (const source of configSources) {
|
|
13258
13633
|
const sourcePath = source.path || source.url || "";
|
|
13259
|
-
const exists = source.path ?
|
|
13634
|
+
const exists = source.path ? fs47.existsSync(path39.resolve(dir, source.path)) : false;
|
|
13260
13635
|
const status = exists ? chalk25.green("reachable") : chalk25.red("not found");
|
|
13261
|
-
const hasSummary = source.path &&
|
|
13636
|
+
const hasSummary = source.path && fs47.existsSync(path39.join(path39.resolve(dir, source.path), ".caliber", "summary.json"));
|
|
13262
13637
|
console.log(` ${chalk25.bold(source.role || source.type)} ${chalk25.dim(sourcePath)}`);
|
|
13263
13638
|
console.log(` Type: ${source.type} Status: ${status}${hasSummary ? " " + chalk25.cyan("has summary.json") : ""}`);
|
|
13264
13639
|
if (source.description) console.log(` ${chalk25.dim(source.description)}`);
|
|
@@ -13268,7 +13643,7 @@ async function sourcesListCommand() {
|
|
|
13268
13643
|
if (workspaces.length > 0) {
|
|
13269
13644
|
console.log(chalk25.dim(" Auto-detected workspaces:"));
|
|
13270
13645
|
for (const ws of workspaces) {
|
|
13271
|
-
const exists =
|
|
13646
|
+
const exists = fs47.existsSync(path39.resolve(dir, ws));
|
|
13272
13647
|
console.log(` ${exists ? chalk25.green("\u25CF") : chalk25.red("\u25CF")} ${ws}`);
|
|
13273
13648
|
}
|
|
13274
13649
|
console.log("");
|
|
@@ -13276,8 +13651,8 @@ async function sourcesListCommand() {
|
|
|
13276
13651
|
}
|
|
13277
13652
|
async function sourcesAddCommand(sourcePath) {
|
|
13278
13653
|
const dir = process.cwd();
|
|
13279
|
-
const absPath =
|
|
13280
|
-
if (!
|
|
13654
|
+
const absPath = path39.resolve(dir, sourcePath);
|
|
13655
|
+
if (!fs47.existsSync(absPath)) {
|
|
13281
13656
|
console.log(chalk25.red(`
|
|
13282
13657
|
Path not found: ${sourcePath}
|
|
13283
13658
|
`));
|
|
@@ -13292,7 +13667,7 @@ async function sourcesAddCommand(sourcePath) {
|
|
|
13292
13667
|
}
|
|
13293
13668
|
const existing = loadSourcesConfig(dir);
|
|
13294
13669
|
const alreadyConfigured = existing.some(
|
|
13295
|
-
(s) => s.path &&
|
|
13670
|
+
(s) => s.path && path39.resolve(dir, s.path) === absPath
|
|
13296
13671
|
);
|
|
13297
13672
|
if (alreadyConfigured) {
|
|
13298
13673
|
console.log(chalk25.yellow(`
|
|
@@ -13340,8 +13715,8 @@ async function sourcesRemoveCommand(name) {
|
|
|
13340
13715
|
}
|
|
13341
13716
|
|
|
13342
13717
|
// src/commands/publish.ts
|
|
13343
|
-
import
|
|
13344
|
-
import
|
|
13718
|
+
import fs48 from "fs";
|
|
13719
|
+
import path40 from "path";
|
|
13345
13720
|
import chalk26 from "chalk";
|
|
13346
13721
|
import ora7 from "ora";
|
|
13347
13722
|
init_config();
|
|
@@ -13356,10 +13731,10 @@ async function publishCommand() {
|
|
|
13356
13731
|
const spinner = ora7("Generating project summary...").start();
|
|
13357
13732
|
try {
|
|
13358
13733
|
const fingerprint = await collectFingerprint(dir);
|
|
13359
|
-
const claudeMd = readFileOrNull(
|
|
13734
|
+
const claudeMd = readFileOrNull(path40.join(dir, "CLAUDE.md"));
|
|
13360
13735
|
const topLevelDirs = fingerprint.fileTree.filter((f) => f.endsWith("/") && !f.includes("/")).map((f) => f.replace(/\/$/, ""));
|
|
13361
13736
|
const summary = {
|
|
13362
|
-
name: fingerprint.packageName ||
|
|
13737
|
+
name: fingerprint.packageName || path40.basename(dir),
|
|
13363
13738
|
version: "1.0.0",
|
|
13364
13739
|
description: fingerprint.description || "",
|
|
13365
13740
|
languages: fingerprint.languages,
|
|
@@ -13371,7 +13746,7 @@ async function publishCommand() {
|
|
|
13371
13746
|
summary.conventions = claudeMd.slice(0, 2e3);
|
|
13372
13747
|
}
|
|
13373
13748
|
try {
|
|
13374
|
-
const pkgContent = readFileOrNull(
|
|
13749
|
+
const pkgContent = readFileOrNull(path40.join(dir, "package.json"));
|
|
13375
13750
|
if (pkgContent) {
|
|
13376
13751
|
const pkg3 = JSON.parse(pkgContent);
|
|
13377
13752
|
if (pkg3.scripts) {
|
|
@@ -13384,14 +13759,14 @@ async function publishCommand() {
|
|
|
13384
13759
|
}
|
|
13385
13760
|
} catch {
|
|
13386
13761
|
}
|
|
13387
|
-
const outputDir =
|
|
13388
|
-
if (!
|
|
13389
|
-
|
|
13762
|
+
const outputDir = path40.join(dir, ".caliber");
|
|
13763
|
+
if (!fs48.existsSync(outputDir)) {
|
|
13764
|
+
fs48.mkdirSync(outputDir, { recursive: true });
|
|
13390
13765
|
}
|
|
13391
|
-
const outputPath =
|
|
13392
|
-
|
|
13766
|
+
const outputPath = path40.join(outputDir, "summary.json");
|
|
13767
|
+
fs48.writeFileSync(outputPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
|
|
13393
13768
|
spinner.succeed("Project summary published");
|
|
13394
|
-
console.log(` ${chalk26.green("\u2713")} ${
|
|
13769
|
+
console.log(` ${chalk26.green("\u2713")} ${path40.relative(dir, outputPath)}`);
|
|
13395
13770
|
console.log(chalk26.dim("\n Other projects can now reference this repo as a source."));
|
|
13396
13771
|
console.log(chalk26.dim(" When they run `caliber init`, they'll read this summary automatically.\n"));
|
|
13397
13772
|
} catch (err) {
|
|
@@ -13404,7 +13779,7 @@ async function publishCommand() {
|
|
|
13404
13779
|
|
|
13405
13780
|
// src/commands/bootstrap.ts
|
|
13406
13781
|
init_builtin_skills();
|
|
13407
|
-
import
|
|
13782
|
+
import fs49 from "fs";
|
|
13408
13783
|
import chalk27 from "chalk";
|
|
13409
13784
|
var PLATFORM_SKILL_DIRS = {
|
|
13410
13785
|
claude: ".claude/skills",
|
|
@@ -13425,8 +13800,8 @@ async function bootstrapCommand() {
|
|
|
13425
13800
|
for (const skill of BUILTIN_SKILLS) {
|
|
13426
13801
|
const skillDir = `${skillsDir}/${skill.name}`;
|
|
13427
13802
|
const skillPath = `${skillDir}/SKILL.md`;
|
|
13428
|
-
|
|
13429
|
-
|
|
13803
|
+
fs49.mkdirSync(skillDir, { recursive: true });
|
|
13804
|
+
fs49.writeFileSync(skillPath, buildSkillContent(skill));
|
|
13430
13805
|
written.push(skillPath);
|
|
13431
13806
|
}
|
|
13432
13807
|
}
|
|
@@ -13443,8 +13818,8 @@ async function bootstrapCommand() {
|
|
|
13443
13818
|
}
|
|
13444
13819
|
|
|
13445
13820
|
// src/commands/uninstall.ts
|
|
13446
|
-
import
|
|
13447
|
-
import
|
|
13821
|
+
import fs50 from "fs";
|
|
13822
|
+
import path41 from "path";
|
|
13448
13823
|
import chalk28 from "chalk";
|
|
13449
13824
|
import confirm3 from "@inquirer/confirm";
|
|
13450
13825
|
init_pre_commit_block();
|
|
@@ -13452,18 +13827,19 @@ init_builtin_skills();
|
|
|
13452
13827
|
init_config();
|
|
13453
13828
|
var MANAGED_DOC_FILES = [
|
|
13454
13829
|
"CLAUDE.md",
|
|
13455
|
-
|
|
13830
|
+
path41.join(".github", "copilot-instructions.md"),
|
|
13456
13831
|
"AGENTS.md"
|
|
13457
13832
|
];
|
|
13458
13833
|
var SKILL_DIRS = PLATFORM_CONFIGS.map((c) => c.skillsDir);
|
|
13459
|
-
var CURSOR_RULES_DIR =
|
|
13460
|
-
|
|
13834
|
+
var CURSOR_RULES_DIR = path41.join(".cursor", "rules");
|
|
13835
|
+
var CLAUDE_RULES_DIR = path41.join(".claude", "rules");
|
|
13836
|
+
function removeCaliberManagedFiles(dir, extension) {
|
|
13461
13837
|
const removed = [];
|
|
13462
|
-
if (!
|
|
13463
|
-
for (const file of
|
|
13464
|
-
if (file.startsWith(
|
|
13465
|
-
const fullPath =
|
|
13466
|
-
|
|
13838
|
+
if (!fs50.existsSync(dir)) return removed;
|
|
13839
|
+
for (const file of fs50.readdirSync(dir)) {
|
|
13840
|
+
if (file.startsWith(CALIBER_MANAGED_PREFIX) && file.endsWith(extension)) {
|
|
13841
|
+
const fullPath = path41.join(dir, file);
|
|
13842
|
+
fs50.unlinkSync(fullPath);
|
|
13467
13843
|
removed.push(fullPath);
|
|
13468
13844
|
}
|
|
13469
13845
|
}
|
|
@@ -13472,11 +13848,11 @@ function removeCaliberCursorRules() {
|
|
|
13472
13848
|
function removeBuiltinSkills() {
|
|
13473
13849
|
const removed = [];
|
|
13474
13850
|
for (const skillsDir of SKILL_DIRS) {
|
|
13475
|
-
if (!
|
|
13851
|
+
if (!fs50.existsSync(skillsDir)) continue;
|
|
13476
13852
|
for (const name of BUILTIN_SKILL_NAMES) {
|
|
13477
|
-
const skillDir =
|
|
13478
|
-
if (
|
|
13479
|
-
|
|
13853
|
+
const skillDir = path41.join(skillsDir, name);
|
|
13854
|
+
if (fs50.existsSync(skillDir)) {
|
|
13855
|
+
fs50.rmSync(skillDir, { recursive: true });
|
|
13480
13856
|
removed.push(skillDir);
|
|
13481
13857
|
}
|
|
13482
13858
|
}
|
|
@@ -13486,15 +13862,15 @@ function removeBuiltinSkills() {
|
|
|
13486
13862
|
function stripManagedBlocksFromFiles() {
|
|
13487
13863
|
const modified = [];
|
|
13488
13864
|
for (const filePath of MANAGED_DOC_FILES) {
|
|
13489
|
-
if (!
|
|
13490
|
-
const original =
|
|
13865
|
+
if (!fs50.existsSync(filePath)) continue;
|
|
13866
|
+
const original = fs50.readFileSync(filePath, "utf-8");
|
|
13491
13867
|
const stripped = stripManagedBlocks(original);
|
|
13492
13868
|
if (stripped !== original) {
|
|
13493
13869
|
const trimmed = stripped.trim();
|
|
13494
13870
|
if (!trimmed || /^#\s*\S*$/.test(trimmed)) {
|
|
13495
|
-
|
|
13871
|
+
fs50.unlinkSync(filePath);
|
|
13496
13872
|
} else {
|
|
13497
|
-
|
|
13873
|
+
fs50.writeFileSync(filePath, stripped);
|
|
13498
13874
|
}
|
|
13499
13875
|
modified.push(filePath);
|
|
13500
13876
|
}
|
|
@@ -13502,8 +13878,8 @@ function stripManagedBlocksFromFiles() {
|
|
|
13502
13878
|
return modified;
|
|
13503
13879
|
}
|
|
13504
13880
|
function removeDirectory(dir) {
|
|
13505
|
-
if (!
|
|
13506
|
-
|
|
13881
|
+
if (!fs50.existsSync(dir)) return false;
|
|
13882
|
+
fs50.rmSync(dir, { recursive: true });
|
|
13507
13883
|
return true;
|
|
13508
13884
|
}
|
|
13509
13885
|
async function uninstallCommand(options) {
|
|
@@ -13535,6 +13911,16 @@ async function uninstallCommand(options) {
|
|
|
13535
13911
|
console.log(` ${chalk28.red("\u2717")} Onboarding hook removed`);
|
|
13536
13912
|
actions.push("onboarding hook");
|
|
13537
13913
|
}
|
|
13914
|
+
const notificationHookResult = removeNotificationHook();
|
|
13915
|
+
if (notificationHookResult.removed) {
|
|
13916
|
+
console.log(` ${chalk28.red("\u2717")} Notification hook removed`);
|
|
13917
|
+
actions.push("notification hook");
|
|
13918
|
+
}
|
|
13919
|
+
const sessionStartResult = removeSessionStartHook();
|
|
13920
|
+
if (sessionStartResult.removed) {
|
|
13921
|
+
console.log(` ${chalk28.red("\u2717")} SessionStart hook removed`);
|
|
13922
|
+
actions.push("session-start hook");
|
|
13923
|
+
}
|
|
13538
13924
|
const learnResult = removeLearningHooks();
|
|
13539
13925
|
if (learnResult.removed) {
|
|
13540
13926
|
console.log(` ${chalk28.red("\u2717")} Claude Code learning hooks removed`);
|
|
@@ -13550,18 +13936,23 @@ async function uninstallCommand(options) {
|
|
|
13550
13936
|
console.log(` ${chalk28.yellow("~")} ${file} \u2014 managed blocks removed`);
|
|
13551
13937
|
actions.push(file);
|
|
13552
13938
|
}
|
|
13553
|
-
const
|
|
13554
|
-
for (const rule of
|
|
13939
|
+
const removedCursorRules = removeCaliberManagedFiles(CURSOR_RULES_DIR, ".mdc");
|
|
13940
|
+
for (const rule of removedCursorRules) {
|
|
13555
13941
|
console.log(` ${chalk28.red("\u2717")} ${rule}`);
|
|
13556
13942
|
}
|
|
13557
|
-
if (
|
|
13943
|
+
if (removedCursorRules.length > 0) actions.push("cursor rules");
|
|
13944
|
+
const removedClaudeRules = removeCaliberManagedFiles(CLAUDE_RULES_DIR, ".md");
|
|
13945
|
+
for (const rule of removedClaudeRules) {
|
|
13946
|
+
console.log(` ${chalk28.red("\u2717")} ${rule}`);
|
|
13947
|
+
}
|
|
13948
|
+
if (removedClaudeRules.length > 0) actions.push("claude rules");
|
|
13558
13949
|
const removedSkills = removeBuiltinSkills();
|
|
13559
13950
|
for (const skill of removedSkills) {
|
|
13560
13951
|
console.log(` ${chalk28.red("\u2717")} ${skill}/`);
|
|
13561
13952
|
}
|
|
13562
13953
|
if (removedSkills.length > 0) actions.push("builtin skills");
|
|
13563
|
-
if (
|
|
13564
|
-
|
|
13954
|
+
if (fs50.existsSync("CALIBER_LEARNINGS.md")) {
|
|
13955
|
+
fs50.unlinkSync("CALIBER_LEARNINGS.md");
|
|
13565
13956
|
console.log(` ${chalk28.red("\u2717")} CALIBER_LEARNINGS.md`);
|
|
13566
13957
|
actions.push("learnings file");
|
|
13567
13958
|
}
|
|
@@ -13575,18 +13966,18 @@ async function uninstallCommand(options) {
|
|
|
13575
13966
|
}
|
|
13576
13967
|
trackUninstallExecuted();
|
|
13577
13968
|
const configPath = getConfigFilePath();
|
|
13578
|
-
if (
|
|
13969
|
+
if (fs50.existsSync(configPath)) {
|
|
13579
13970
|
console.log("");
|
|
13580
13971
|
const removeConfig = options.force || await confirm3({
|
|
13581
13972
|
message: `Remove global config (~/.caliber/config.json)? This affects all projects.`
|
|
13582
13973
|
});
|
|
13583
13974
|
if (removeConfig) {
|
|
13584
|
-
|
|
13975
|
+
fs50.unlinkSync(configPath);
|
|
13585
13976
|
console.log(` ${chalk28.red("\u2717")} ${configPath}`);
|
|
13586
|
-
const configDir =
|
|
13977
|
+
const configDir = path41.dirname(configPath);
|
|
13587
13978
|
try {
|
|
13588
|
-
const remaining =
|
|
13589
|
-
if (remaining.length === 0)
|
|
13979
|
+
const remaining = fs50.readdirSync(configDir);
|
|
13980
|
+
if (remaining.length === 0) fs50.rmdirSync(configDir);
|
|
13590
13981
|
} catch {
|
|
13591
13982
|
}
|
|
13592
13983
|
}
|
|
@@ -13597,8 +13988,8 @@ async function uninstallCommand(options) {
|
|
|
13597
13988
|
}
|
|
13598
13989
|
|
|
13599
13990
|
// src/cli.ts
|
|
13600
|
-
var __dirname =
|
|
13601
|
-
var pkg = JSON.parse(
|
|
13991
|
+
var __dirname = path42.dirname(fileURLToPath(import.meta.url));
|
|
13992
|
+
var pkg = JSON.parse(fs51.readFileSync(path42.resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
13602
13993
|
var program = new Command();
|
|
13603
13994
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
13604
13995
|
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");
|
|
@@ -13709,15 +14100,15 @@ learn.command("delete <index>").description("Delete a learning by its index numb
|
|
|
13709
14100
|
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));
|
|
13710
14101
|
|
|
13711
14102
|
// src/utils/version-check.ts
|
|
13712
|
-
import
|
|
13713
|
-
import
|
|
14103
|
+
import fs52 from "fs";
|
|
14104
|
+
import path43 from "path";
|
|
13714
14105
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13715
14106
|
import { execSync as execSync16, execFileSync as execFileSync3 } from "child_process";
|
|
13716
14107
|
import chalk29 from "chalk";
|
|
13717
14108
|
import ora8 from "ora";
|
|
13718
14109
|
import confirm4 from "@inquirer/confirm";
|
|
13719
|
-
var __dirname_vc =
|
|
13720
|
-
var pkg2 = JSON.parse(
|
|
14110
|
+
var __dirname_vc = path43.dirname(fileURLToPath2(import.meta.url));
|
|
14111
|
+
var pkg2 = JSON.parse(fs52.readFileSync(path43.resolve(__dirname_vc, "..", "package.json"), "utf-8"));
|
|
13721
14112
|
function getChannel(version) {
|
|
13722
14113
|
const match = version.match(/-(dev|next)\./);
|
|
13723
14114
|
return match ? match[1] : "latest";
|
|
@@ -13744,8 +14135,8 @@ function getInstalledVersion() {
|
|
|
13744
14135
|
encoding: "utf-8",
|
|
13745
14136
|
stdio: ["pipe", "pipe", "pipe"]
|
|
13746
14137
|
}).trim();
|
|
13747
|
-
const pkgPath =
|
|
13748
|
-
return JSON.parse(
|
|
14138
|
+
const pkgPath = path43.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
14139
|
+
return JSON.parse(fs52.readFileSync(pkgPath, "utf-8")).version;
|
|
13749
14140
|
} catch {
|
|
13750
14141
|
return null;
|
|
13751
14142
|
}
|