@rely-ai/caliber 1.1.4 → 1.3.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 +458 -286
- package/package.json +2 -1
package/dist/bin.js
CHANGED
|
@@ -32,17 +32,17 @@ __export(constants_exports, {
|
|
|
32
32
|
LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
|
|
33
33
|
MANIFEST_FILE: () => MANIFEST_FILE
|
|
34
34
|
});
|
|
35
|
-
import
|
|
35
|
+
import path9 from "path";
|
|
36
36
|
import os2 from "os";
|
|
37
37
|
var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
|
|
38
38
|
var init_constants = __esm({
|
|
39
39
|
"src/constants.ts"() {
|
|
40
40
|
"use strict";
|
|
41
|
-
AUTH_DIR =
|
|
41
|
+
AUTH_DIR = path9.join(os2.homedir(), ".caliber");
|
|
42
42
|
CALIBER_DIR = ".caliber";
|
|
43
|
-
MANIFEST_FILE =
|
|
44
|
-
BACKUPS_DIR =
|
|
45
|
-
LEARNING_DIR =
|
|
43
|
+
MANIFEST_FILE = path9.join(CALIBER_DIR, "manifest.json");
|
|
44
|
+
BACKUPS_DIR = path9.join(CALIBER_DIR, "backups");
|
|
45
|
+
LEARNING_DIR = path9.join(CALIBER_DIR, "learning");
|
|
46
46
|
LEARNING_SESSION_FILE = "current-session.jsonl";
|
|
47
47
|
LEARNING_STATE_FILE = "state.json";
|
|
48
48
|
LEARNING_MAX_EVENTS = 500;
|
|
@@ -51,8 +51,8 @@ var init_constants = __esm({
|
|
|
51
51
|
|
|
52
52
|
// src/cli.ts
|
|
53
53
|
import { Command } from "commander";
|
|
54
|
-
import
|
|
55
|
-
import
|
|
54
|
+
import fs26 from "fs";
|
|
55
|
+
import path22 from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
|
|
58
58
|
// src/commands/onboard.ts
|
|
@@ -60,7 +60,8 @@ import chalk5 from "chalk";
|
|
|
60
60
|
import ora2 from "ora";
|
|
61
61
|
import readline4 from "readline";
|
|
62
62
|
import select2 from "@inquirer/select";
|
|
63
|
-
import
|
|
63
|
+
import checkbox from "@inquirer/checkbox";
|
|
64
|
+
import fs19 from "fs";
|
|
64
65
|
|
|
65
66
|
// src/fingerprint/index.ts
|
|
66
67
|
import fs6 from "fs";
|
|
@@ -145,6 +146,10 @@ function readExistingConfigs(dir) {
|
|
|
145
146
|
if (fs2.existsSync(readmeMdPath)) {
|
|
146
147
|
configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
|
|
147
148
|
}
|
|
149
|
+
const agentsMdPath = path2.join(dir, "AGENTS.md");
|
|
150
|
+
if (fs2.existsSync(agentsMdPath)) {
|
|
151
|
+
configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
|
|
152
|
+
}
|
|
148
153
|
const claudeMdPath = path2.join(dir, "CLAUDE.md");
|
|
149
154
|
if (fs2.existsSync(claudeMdPath)) {
|
|
150
155
|
configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
@@ -1172,7 +1177,7 @@ async function llmJsonCall(options) {
|
|
|
1172
1177
|
}
|
|
1173
1178
|
|
|
1174
1179
|
// src/ai/prompts.ts
|
|
1175
|
-
var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code and
|
|
1180
|
+
var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, and Codex).
|
|
1176
1181
|
|
|
1177
1182
|
Your job depends on context:
|
|
1178
1183
|
- If no existing configs exist \u2192 generate an initial setup from scratch.
|
|
@@ -1180,7 +1185,9 @@ Your job depends on context:
|
|
|
1180
1185
|
|
|
1181
1186
|
You understand these config files:
|
|
1182
1187
|
- CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
|
|
1188
|
+
- AGENTS.md: Primary instructions file for OpenAI Codex \u2014 same purpose as CLAUDE.md but for the Codex agent. Also serves as a cross-agent coordination file.
|
|
1183
1189
|
- .claude/skills/{name}/SKILL.md: Skill files following the OpenSkills standard (agentskills.io). Each skill is a directory named after the skill, containing a SKILL.md with YAML frontmatter.
|
|
1190
|
+
- .agents/skills/{name}/SKILL.md: Same OpenSkills format for Codex skills (Codex scans .agents/skills/ for skills).
|
|
1184
1191
|
- .cursorrules: Coding rules for Cursor (deprecated legacy format \u2014 do NOT generate this).
|
|
1185
1192
|
- .cursor/rules/*.mdc: Modern Cursor rules with frontmatter (description, globs, alwaysApply).
|
|
1186
1193
|
- .cursor/skills/{name}/SKILL.md: Same OpenSkills format as Claude skills.
|
|
@@ -1212,7 +1219,7 @@ Omit empty categories. Keep each reason punchy and specific. End with a blank li
|
|
|
1212
1219
|
|
|
1213
1220
|
AgentSetup schema:
|
|
1214
1221
|
{
|
|
1215
|
-
"targetAgent": "claude"
|
|
1222
|
+
"targetAgent": ["claude", "cursor", "codex"] (array of selected agents),
|
|
1216
1223
|
"fileDescriptions": {
|
|
1217
1224
|
"<file-path>": "reason for this change (max 80 chars)"
|
|
1218
1225
|
},
|
|
@@ -1223,6 +1230,10 @@ AgentSetup schema:
|
|
|
1223
1230
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
1224
1231
|
"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)" }]
|
|
1225
1232
|
},
|
|
1233
|
+
"codex": {
|
|
1234
|
+
"agentsMd": "string (markdown content for AGENTS.md \u2014 the primary Codex instructions file, same quality/structure as CLAUDE.md)",
|
|
1235
|
+
"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)" }]
|
|
1236
|
+
},
|
|
1226
1237
|
"cursor": {
|
|
1227
1238
|
"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)" }],
|
|
1228
1239
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
@@ -1237,14 +1248,15 @@ All skills follow the OpenSkills standard (agentskills.io):
|
|
|
1237
1248
|
- The "content" field is the markdown body only \u2014 do NOT include YAML frontmatter in the content, it will be generated from the name and description fields.
|
|
1238
1249
|
- Keep skill content under 500 lines. Move detailed references to separate files if needed.
|
|
1239
1250
|
|
|
1240
|
-
The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", ".claude/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
|
|
1251
|
+
The "fileDescriptions" object MUST include a one-liner for every file that will be created or modified. Use actual file paths as keys (e.g. "CLAUDE.md", "AGENTS.md", ".claude/skills/my-skill/SKILL.md", ".agents/skills/my-skill/SKILL.md", ".cursor/skills/my-skill/SKILL.md", ".cursor/rules/my-rule.mdc"). Each description should explain why the change is needed, be concise and lowercase.
|
|
1241
1252
|
|
|
1242
1253
|
The "deletions" array should list files that should be removed (e.g. duplicate skills, stale configs). Include a reason for each. Omit the array or leave empty if nothing should be deleted.
|
|
1243
1254
|
|
|
1244
1255
|
SCORING CRITERIA \u2014 your output is scored deterministically. Optimize for 100/100:
|
|
1245
1256
|
|
|
1246
1257
|
Existence (25 pts):
|
|
1247
|
-
- CLAUDE.md exists (6 pts) \u2014 always generate
|
|
1258
|
+
- CLAUDE.md exists (6 pts) \u2014 always generate for claude/both targets
|
|
1259
|
+
- AGENTS.md exists (6 pts) \u2014 always generate for codex target (serves as primary instructions file)
|
|
1248
1260
|
- Skills configured (8 pts) \u2014 generate exactly 3 focused skills for full points (6 base + 1 per extra, cap 2). Two skills = 7 pts, three = 8 pts.
|
|
1249
1261
|
- MCP servers mentioned (3 pts) \u2014 reference detected MCP integrations
|
|
1250
1262
|
- For "both" target: .cursorrules/.cursor/rules/ exist (3+3 pts), cross-platform parity (2 pts)
|
|
@@ -1278,12 +1290,12 @@ Bonus (5 pts):
|
|
|
1278
1290
|
- Hooks configured (2 pts), AGENTS.md (1 pt), OpenSkills format (2 pts) \u2014 handled by caliber
|
|
1279
1291
|
|
|
1280
1292
|
OUTPUT SIZE CONSTRAINTS \u2014 these are critical:
|
|
1281
|
-
- CLAUDE.md: MUST be under 100 lines for maximum score. Aim for 70-90 lines. Be extremely concise \u2014 only commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
|
|
1293
|
+
- CLAUDE.md / AGENTS.md: MUST be under 100 lines for maximum score. Aim for 70-90 lines. Be extremely concise \u2014 only commands, architecture overview, and key conventions. Use bullet points and tables, not prose.
|
|
1282
1294
|
- Skills: generate exactly 3 skills per target platform. Only go above 3 for large multi-framework projects.
|
|
1283
1295
|
- Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
|
|
1284
1296
|
- Cursor rules: max 5 .mdc files.
|
|
1285
1297
|
- If the project is large, prioritize depth on the 3-4 most critical tools over breadth across everything.`;
|
|
1286
|
-
var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code and
|
|
1298
|
+
var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code, Cursor, and Codex).
|
|
1287
1299
|
|
|
1288
1300
|
You will receive the current AgentSetup JSON and a user request describing what to change.
|
|
1289
1301
|
|
|
@@ -1291,7 +1303,7 @@ Apply the requested changes to the setup and return the complete updated AgentSe
|
|
|
1291
1303
|
|
|
1292
1304
|
AgentSetup schema:
|
|
1293
1305
|
{
|
|
1294
|
-
"targetAgent": "claude"
|
|
1306
|
+
"targetAgent": ["claude", "cursor", "codex"] (array of selected agents),
|
|
1295
1307
|
"fileDescriptions": {
|
|
1296
1308
|
"<file-path>": "reason for this change (max 80 chars)"
|
|
1297
1309
|
},
|
|
@@ -1302,6 +1314,10 @@ AgentSetup schema:
|
|
|
1302
1314
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
1303
1315
|
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
|
|
1304
1316
|
},
|
|
1317
|
+
"codex": {
|
|
1318
|
+
"agentsMd": "string (markdown content for AGENTS.md)",
|
|
1319
|
+
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
|
|
1320
|
+
},
|
|
1305
1321
|
"cursor": {
|
|
1306
1322
|
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }],
|
|
1307
1323
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
@@ -1318,7 +1334,7 @@ var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project doc
|
|
|
1318
1334
|
|
|
1319
1335
|
You will receive:
|
|
1320
1336
|
1. Git diffs showing what code changed
|
|
1321
|
-
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor skills, cursor rules)
|
|
1337
|
+
2. Current contents of documentation files (CLAUDE.md, AGENTS.md, README.md, skills, cursor skills, cursor rules)
|
|
1322
1338
|
3. Project context (languages, frameworks)
|
|
1323
1339
|
|
|
1324
1340
|
Rules:
|
|
@@ -1329,6 +1345,7 @@ Rules:
|
|
|
1329
1345
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
1330
1346
|
- If a doc doesn't need updating, return null for it
|
|
1331
1347
|
- For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them
|
|
1348
|
+
- For AGENTS.md: same as CLAUDE.md \u2014 this is the primary instructions file for Codex users
|
|
1332
1349
|
- For README.md: update setup instructions, API docs, or feature descriptions if affected
|
|
1333
1350
|
- For cursor skills: update skill content if the diffs affect their domains
|
|
1334
1351
|
|
|
@@ -1336,13 +1353,14 @@ Return a JSON object with this exact shape:
|
|
|
1336
1353
|
{
|
|
1337
1354
|
"updatedDocs": {
|
|
1338
1355
|
"claudeMd": "<updated content or null>",
|
|
1356
|
+
"agentsMd": "<updated content or null>",
|
|
1339
1357
|
"readmeMd": "<updated content or null>",
|
|
1340
1358
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
1341
1359
|
"cursorSkills": [{"slug": "string", "name": "string", "content": "..."}] or null,
|
|
1342
1360
|
"claudeSkills": [{"filename": "name.md", "content": "..."}] or null
|
|
1343
1361
|
},
|
|
1344
1362
|
"changesSummary": "<1-2 sentence summary of what was updated and why>",
|
|
1345
|
-
"docsUpdated": ["CLAUDE.md", "README.md"]
|
|
1363
|
+
"docsUpdated": ["CLAUDE.md", "AGENTS.md", "README.md"]
|
|
1346
1364
|
}
|
|
1347
1365
|
|
|
1348
1366
|
Respond with ONLY the JSON object, no markdown fences or extra text.`;
|
|
@@ -1361,7 +1379,7 @@ Your job is to reason deeply about these events and identify:
|
|
|
1361
1379
|
From these observations, produce:
|
|
1362
1380
|
|
|
1363
1381
|
### claudeMdLearnedSection
|
|
1364
|
-
A markdown section with concise, actionable bullet points that should be added to the project's CLAUDE.md
|
|
1382
|
+
A markdown section with concise, actionable bullet points that should be added to the project's primary instructions file (CLAUDE.md for Claude Code, AGENTS.md for Codex). Each bullet should be a concrete instruction that prevents a past mistake or encodes a discovered convention. Examples:
|
|
1365
1383
|
- "Always run \`npm install\` before \`npm run build\` in this project"
|
|
1366
1384
|
- "The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
|
|
1367
1385
|
- "TypeScript strict mode is enabled \u2014 never use \`any\`, use \`unknown\` with type guards"
|
|
@@ -1644,7 +1662,7 @@ function truncate(text, maxChars) {
|
|
|
1644
1662
|
function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks) {
|
|
1645
1663
|
const parts = [];
|
|
1646
1664
|
const existing = fingerprint.existingConfigs;
|
|
1647
|
-
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.cursorrules || existing.cursorRules?.length);
|
|
1665
|
+
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.agentsMd || existing.cursorrules || existing.cursorRules?.length);
|
|
1648
1666
|
const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
|
|
1649
1667
|
if (isTargetedFix) {
|
|
1650
1668
|
parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
|
|
@@ -1682,6 +1700,9 @@ ${tree.join("\n")}`);
|
|
|
1682
1700
|
if (existing.claudeMd) parts.push(`
|
|
1683
1701
|
Existing CLAUDE.md:
|
|
1684
1702
|
${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
1703
|
+
if (existing.agentsMd) parts.push(`
|
|
1704
|
+
Existing AGENTS.md:
|
|
1705
|
+
${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
1685
1706
|
if (existing.readmeMd) parts.push(`
|
|
1686
1707
|
Existing README.md:
|
|
1687
1708
|
${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
@@ -1817,7 +1838,7 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
|
|
|
1817
1838
|
}
|
|
1818
1839
|
|
|
1819
1840
|
// src/writers/index.ts
|
|
1820
|
-
import
|
|
1841
|
+
import fs12 from "fs";
|
|
1821
1842
|
|
|
1822
1843
|
// src/writers/claude/index.ts
|
|
1823
1844
|
import fs7 from "fs";
|
|
@@ -1911,77 +1932,106 @@ function writeCursorConfig(config) {
|
|
|
1911
1932
|
return written;
|
|
1912
1933
|
}
|
|
1913
1934
|
|
|
1935
|
+
// src/writers/codex/index.ts
|
|
1936
|
+
import fs9 from "fs";
|
|
1937
|
+
import path8 from "path";
|
|
1938
|
+
function writeCodexConfig(config) {
|
|
1939
|
+
const written = [];
|
|
1940
|
+
fs9.writeFileSync("AGENTS.md", config.agentsMd);
|
|
1941
|
+
written.push("AGENTS.md");
|
|
1942
|
+
if (config.skills?.length) {
|
|
1943
|
+
for (const skill of config.skills) {
|
|
1944
|
+
const skillDir = path8.join(".agents", "skills", skill.name);
|
|
1945
|
+
if (!fs9.existsSync(skillDir)) fs9.mkdirSync(skillDir, { recursive: true });
|
|
1946
|
+
const skillPath = path8.join(skillDir, "SKILL.md");
|
|
1947
|
+
const frontmatter = [
|
|
1948
|
+
"---",
|
|
1949
|
+
`name: ${skill.name}`,
|
|
1950
|
+
`description: ${skill.description}`,
|
|
1951
|
+
"---",
|
|
1952
|
+
""
|
|
1953
|
+
].join("\n");
|
|
1954
|
+
fs9.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1955
|
+
written.push(skillPath);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
return written;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1914
1961
|
// src/writers/backup.ts
|
|
1915
1962
|
init_constants();
|
|
1916
|
-
import
|
|
1917
|
-
import
|
|
1963
|
+
import fs10 from "fs";
|
|
1964
|
+
import path10 from "path";
|
|
1918
1965
|
function createBackup(files) {
|
|
1919
1966
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1920
|
-
const backupDir =
|
|
1967
|
+
const backupDir = path10.join(BACKUPS_DIR, timestamp);
|
|
1921
1968
|
for (const file of files) {
|
|
1922
|
-
if (!
|
|
1923
|
-
const dest =
|
|
1924
|
-
const destDir =
|
|
1925
|
-
if (!
|
|
1926
|
-
|
|
1969
|
+
if (!fs10.existsSync(file)) continue;
|
|
1970
|
+
const dest = path10.join(backupDir, file);
|
|
1971
|
+
const destDir = path10.dirname(dest);
|
|
1972
|
+
if (!fs10.existsSync(destDir)) {
|
|
1973
|
+
fs10.mkdirSync(destDir, { recursive: true });
|
|
1927
1974
|
}
|
|
1928
|
-
|
|
1975
|
+
fs10.copyFileSync(file, dest);
|
|
1929
1976
|
}
|
|
1930
1977
|
return backupDir;
|
|
1931
1978
|
}
|
|
1932
1979
|
function restoreBackup(backupDir, file) {
|
|
1933
|
-
const backupFile =
|
|
1934
|
-
if (!
|
|
1935
|
-
const destDir =
|
|
1936
|
-
if (!
|
|
1937
|
-
|
|
1980
|
+
const backupFile = path10.join(backupDir, file);
|
|
1981
|
+
if (!fs10.existsSync(backupFile)) return false;
|
|
1982
|
+
const destDir = path10.dirname(file);
|
|
1983
|
+
if (!fs10.existsSync(destDir)) {
|
|
1984
|
+
fs10.mkdirSync(destDir, { recursive: true });
|
|
1938
1985
|
}
|
|
1939
|
-
|
|
1986
|
+
fs10.copyFileSync(backupFile, file);
|
|
1940
1987
|
return true;
|
|
1941
1988
|
}
|
|
1942
1989
|
|
|
1943
1990
|
// src/writers/manifest.ts
|
|
1944
1991
|
init_constants();
|
|
1945
|
-
import
|
|
1992
|
+
import fs11 from "fs";
|
|
1946
1993
|
import crypto from "crypto";
|
|
1947
1994
|
function readManifest() {
|
|
1948
1995
|
try {
|
|
1949
|
-
if (!
|
|
1950
|
-
return JSON.parse(
|
|
1996
|
+
if (!fs11.existsSync(MANIFEST_FILE)) return null;
|
|
1997
|
+
return JSON.parse(fs11.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
1951
1998
|
} catch {
|
|
1952
1999
|
return null;
|
|
1953
2000
|
}
|
|
1954
2001
|
}
|
|
1955
2002
|
function writeManifest(manifest) {
|
|
1956
|
-
if (!
|
|
1957
|
-
|
|
2003
|
+
if (!fs11.existsSync(CALIBER_DIR)) {
|
|
2004
|
+
fs11.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1958
2005
|
}
|
|
1959
|
-
|
|
2006
|
+
fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
1960
2007
|
}
|
|
1961
2008
|
function fileChecksum(filePath) {
|
|
1962
|
-
const content =
|
|
2009
|
+
const content = fs11.readFileSync(filePath);
|
|
1963
2010
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
1964
2011
|
}
|
|
1965
2012
|
|
|
1966
2013
|
// src/writers/index.ts
|
|
1967
2014
|
function writeSetup(setup) {
|
|
1968
2015
|
const filesToWrite = getFilesToWrite(setup);
|
|
1969
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
2016
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs12.existsSync(f));
|
|
1970
2017
|
const existingFiles = [
|
|
1971
|
-
...filesToWrite.filter((f) =>
|
|
2018
|
+
...filesToWrite.filter((f) => fs12.existsSync(f)),
|
|
1972
2019
|
...filesToDelete
|
|
1973
2020
|
];
|
|
1974
2021
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
1975
2022
|
const written = [];
|
|
1976
|
-
if (
|
|
2023
|
+
if (setup.targetAgent.includes("claude") && setup.claude) {
|
|
1977
2024
|
written.push(...writeClaudeConfig(setup.claude));
|
|
1978
2025
|
}
|
|
1979
|
-
if (
|
|
2026
|
+
if (setup.targetAgent.includes("cursor") && setup.cursor) {
|
|
1980
2027
|
written.push(...writeCursorConfig(setup.cursor));
|
|
1981
2028
|
}
|
|
2029
|
+
if (setup.targetAgent.includes("codex") && setup.codex) {
|
|
2030
|
+
written.push(...writeCodexConfig(setup.codex));
|
|
2031
|
+
}
|
|
1982
2032
|
const deleted = [];
|
|
1983
2033
|
for (const filePath of filesToDelete) {
|
|
1984
|
-
|
|
2034
|
+
fs12.unlinkSync(filePath);
|
|
1985
2035
|
deleted.push(filePath);
|
|
1986
2036
|
}
|
|
1987
2037
|
ensureGitignore();
|
|
@@ -2011,8 +2061,8 @@ function undoSetup() {
|
|
|
2011
2061
|
const removed = [];
|
|
2012
2062
|
for (const entry of manifest.entries) {
|
|
2013
2063
|
if (entry.action === "created") {
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2064
|
+
if (fs12.existsSync(entry.path)) {
|
|
2065
|
+
fs12.unlinkSync(entry.path);
|
|
2016
2066
|
removed.push(entry.path);
|
|
2017
2067
|
}
|
|
2018
2068
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -2022,14 +2072,14 @@ function undoSetup() {
|
|
|
2022
2072
|
}
|
|
2023
2073
|
}
|
|
2024
2074
|
const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2075
|
+
if (fs12.existsSync(MANIFEST_FILE2)) {
|
|
2076
|
+
fs12.unlinkSync(MANIFEST_FILE2);
|
|
2027
2077
|
}
|
|
2028
2078
|
return { restored, removed };
|
|
2029
2079
|
}
|
|
2030
2080
|
function getFilesToWrite(setup) {
|
|
2031
2081
|
const files = [];
|
|
2032
|
-
if (
|
|
2082
|
+
if (setup.targetAgent.includes("claude") && setup.claude) {
|
|
2033
2083
|
files.push("CLAUDE.md");
|
|
2034
2084
|
if (setup.claude.mcpServers) files.push(".mcp.json");
|
|
2035
2085
|
if (setup.claude.skills) {
|
|
@@ -2038,7 +2088,7 @@ function getFilesToWrite(setup) {
|
|
|
2038
2088
|
}
|
|
2039
2089
|
}
|
|
2040
2090
|
}
|
|
2041
|
-
if (
|
|
2091
|
+
if (setup.targetAgent.includes("cursor") && setup.cursor) {
|
|
2042
2092
|
if (setup.cursor.cursorrules) files.push(".cursorrules");
|
|
2043
2093
|
if (setup.cursor.rules) {
|
|
2044
2094
|
for (const r of setup.cursor.rules) files.push(`.cursor/rules/${r.filename}`);
|
|
@@ -2048,27 +2098,33 @@ function getFilesToWrite(setup) {
|
|
|
2048
2098
|
}
|
|
2049
2099
|
if (setup.cursor.mcpServers) files.push(".cursor/mcp.json");
|
|
2050
2100
|
}
|
|
2101
|
+
if (setup.targetAgent.includes("codex") && setup.codex) {
|
|
2102
|
+
files.push("AGENTS.md");
|
|
2103
|
+
if (setup.codex.skills) {
|
|
2104
|
+
for (const s of setup.codex.skills) files.push(`.agents/skills/${s.name}/SKILL.md`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2051
2107
|
return files;
|
|
2052
2108
|
}
|
|
2053
2109
|
function ensureGitignore() {
|
|
2054
2110
|
const gitignorePath = ".gitignore";
|
|
2055
|
-
if (
|
|
2056
|
-
const content =
|
|
2111
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
2112
|
+
const content = fs12.readFileSync(gitignorePath, "utf-8");
|
|
2057
2113
|
if (!content.includes(".caliber/")) {
|
|
2058
|
-
|
|
2114
|
+
fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
2059
2115
|
}
|
|
2060
2116
|
} else {
|
|
2061
|
-
|
|
2117
|
+
fs12.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
2062
2118
|
}
|
|
2063
2119
|
}
|
|
2064
2120
|
|
|
2065
2121
|
// src/writers/staging.ts
|
|
2066
2122
|
init_constants();
|
|
2067
|
-
import
|
|
2068
|
-
import
|
|
2069
|
-
var STAGED_DIR =
|
|
2070
|
-
var PROPOSED_DIR =
|
|
2071
|
-
var CURRENT_DIR =
|
|
2123
|
+
import fs13 from "fs";
|
|
2124
|
+
import path11 from "path";
|
|
2125
|
+
var STAGED_DIR = path11.join(CALIBER_DIR, "staged");
|
|
2126
|
+
var PROPOSED_DIR = path11.join(STAGED_DIR, "proposed");
|
|
2127
|
+
var CURRENT_DIR = path11.join(STAGED_DIR, "current");
|
|
2072
2128
|
function normalizeContent(content) {
|
|
2073
2129
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2074
2130
|
}
|
|
@@ -2078,20 +2134,20 @@ function stageFiles(files, projectDir) {
|
|
|
2078
2134
|
let modifiedFiles = 0;
|
|
2079
2135
|
const stagedFiles = [];
|
|
2080
2136
|
for (const file of files) {
|
|
2081
|
-
const originalPath =
|
|
2082
|
-
if (
|
|
2083
|
-
const existing =
|
|
2137
|
+
const originalPath = path11.join(projectDir, file.path);
|
|
2138
|
+
if (fs13.existsSync(originalPath)) {
|
|
2139
|
+
const existing = fs13.readFileSync(originalPath, "utf-8");
|
|
2084
2140
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2085
2141
|
continue;
|
|
2086
2142
|
}
|
|
2087
2143
|
}
|
|
2088
|
-
const proposedPath =
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
if (
|
|
2092
|
-
const currentPath =
|
|
2093
|
-
|
|
2094
|
-
|
|
2144
|
+
const proposedPath = path11.join(PROPOSED_DIR, file.path);
|
|
2145
|
+
fs13.mkdirSync(path11.dirname(proposedPath), { recursive: true });
|
|
2146
|
+
fs13.writeFileSync(proposedPath, file.content);
|
|
2147
|
+
if (fs13.existsSync(originalPath)) {
|
|
2148
|
+
const currentPath = path11.join(CURRENT_DIR, file.path);
|
|
2149
|
+
fs13.mkdirSync(path11.dirname(currentPath), { recursive: true });
|
|
2150
|
+
fs13.copyFileSync(originalPath, currentPath);
|
|
2095
2151
|
modifiedFiles++;
|
|
2096
2152
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
2097
2153
|
} else {
|
|
@@ -2102,8 +2158,8 @@ function stageFiles(files, projectDir) {
|
|
|
2102
2158
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
2103
2159
|
}
|
|
2104
2160
|
function cleanupStaging() {
|
|
2105
|
-
if (
|
|
2106
|
-
|
|
2161
|
+
if (fs13.existsSync(STAGED_DIR)) {
|
|
2162
|
+
fs13.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
2107
2163
|
}
|
|
2108
2164
|
}
|
|
2109
2165
|
|
|
@@ -2149,24 +2205,24 @@ function openDiffsInEditor(editor, files) {
|
|
|
2149
2205
|
import { createTwoFilesPatch } from "diff";
|
|
2150
2206
|
|
|
2151
2207
|
// src/lib/hooks.ts
|
|
2152
|
-
import
|
|
2153
|
-
import
|
|
2208
|
+
import fs14 from "fs";
|
|
2209
|
+
import path12 from "path";
|
|
2154
2210
|
import { execSync as execSync5 } from "child_process";
|
|
2155
|
-
var SETTINGS_PATH =
|
|
2211
|
+
var SETTINGS_PATH = path12.join(".claude", "settings.json");
|
|
2156
2212
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
2157
2213
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
2158
2214
|
function readSettings() {
|
|
2159
|
-
if (!
|
|
2215
|
+
if (!fs14.existsSync(SETTINGS_PATH)) return {};
|
|
2160
2216
|
try {
|
|
2161
|
-
return JSON.parse(
|
|
2217
|
+
return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2162
2218
|
} catch {
|
|
2163
2219
|
return {};
|
|
2164
2220
|
}
|
|
2165
2221
|
}
|
|
2166
2222
|
function writeSettings(settings) {
|
|
2167
|
-
const dir =
|
|
2168
|
-
if (!
|
|
2169
|
-
|
|
2223
|
+
const dir = path12.dirname(SETTINGS_PATH);
|
|
2224
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
2225
|
+
fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
2170
2226
|
}
|
|
2171
2227
|
function findHookIndex(sessionEnd) {
|
|
2172
2228
|
return sessionEnd.findIndex(
|
|
@@ -2224,19 +2280,19 @@ ${PRECOMMIT_END}`;
|
|
|
2224
2280
|
function getGitHooksDir() {
|
|
2225
2281
|
try {
|
|
2226
2282
|
const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2227
|
-
return
|
|
2283
|
+
return path12.join(gitDir, "hooks");
|
|
2228
2284
|
} catch {
|
|
2229
2285
|
return null;
|
|
2230
2286
|
}
|
|
2231
2287
|
}
|
|
2232
2288
|
function getPreCommitPath() {
|
|
2233
2289
|
const hooksDir = getGitHooksDir();
|
|
2234
|
-
return hooksDir ?
|
|
2290
|
+
return hooksDir ? path12.join(hooksDir, "pre-commit") : null;
|
|
2235
2291
|
}
|
|
2236
2292
|
function isPreCommitHookInstalled() {
|
|
2237
2293
|
const hookPath = getPreCommitPath();
|
|
2238
|
-
if (!hookPath || !
|
|
2239
|
-
const content =
|
|
2294
|
+
if (!hookPath || !fs14.existsSync(hookPath)) return false;
|
|
2295
|
+
const content = fs14.readFileSync(hookPath, "utf-8");
|
|
2240
2296
|
return content.includes(PRECOMMIT_START);
|
|
2241
2297
|
}
|
|
2242
2298
|
function installPreCommitHook() {
|
|
@@ -2245,43 +2301,43 @@ function installPreCommitHook() {
|
|
|
2245
2301
|
}
|
|
2246
2302
|
const hookPath = getPreCommitPath();
|
|
2247
2303
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
2248
|
-
const hooksDir =
|
|
2249
|
-
if (!
|
|
2304
|
+
const hooksDir = path12.dirname(hookPath);
|
|
2305
|
+
if (!fs14.existsSync(hooksDir)) fs14.mkdirSync(hooksDir, { recursive: true });
|
|
2250
2306
|
let content = "";
|
|
2251
|
-
if (
|
|
2252
|
-
content =
|
|
2307
|
+
if (fs14.existsSync(hookPath)) {
|
|
2308
|
+
content = fs14.readFileSync(hookPath, "utf-8");
|
|
2253
2309
|
if (!content.endsWith("\n")) content += "\n";
|
|
2254
2310
|
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
2255
2311
|
} else {
|
|
2256
2312
|
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
2257
2313
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2314
|
+
fs14.writeFileSync(hookPath, content);
|
|
2315
|
+
fs14.chmodSync(hookPath, 493);
|
|
2260
2316
|
return { installed: true, alreadyInstalled: false };
|
|
2261
2317
|
}
|
|
2262
2318
|
function removePreCommitHook() {
|
|
2263
2319
|
const hookPath = getPreCommitPath();
|
|
2264
|
-
if (!hookPath || !
|
|
2320
|
+
if (!hookPath || !fs14.existsSync(hookPath)) {
|
|
2265
2321
|
return { removed: false, notFound: true };
|
|
2266
2322
|
}
|
|
2267
|
-
let content =
|
|
2323
|
+
let content = fs14.readFileSync(hookPath, "utf-8");
|
|
2268
2324
|
if (!content.includes(PRECOMMIT_START)) {
|
|
2269
2325
|
return { removed: false, notFound: true };
|
|
2270
2326
|
}
|
|
2271
2327
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
2272
2328
|
content = content.replace(regex, "\n");
|
|
2273
2329
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
2274
|
-
|
|
2330
|
+
fs14.unlinkSync(hookPath);
|
|
2275
2331
|
} else {
|
|
2276
|
-
|
|
2332
|
+
fs14.writeFileSync(hookPath, content);
|
|
2277
2333
|
}
|
|
2278
2334
|
return { removed: true, notFound: false };
|
|
2279
2335
|
}
|
|
2280
2336
|
|
|
2281
2337
|
// src/lib/learning-hooks.ts
|
|
2282
|
-
import
|
|
2283
|
-
import
|
|
2284
|
-
var SETTINGS_PATH2 =
|
|
2338
|
+
import fs15 from "fs";
|
|
2339
|
+
import path13 from "path";
|
|
2340
|
+
var SETTINGS_PATH2 = path13.join(".claude", "settings.json");
|
|
2285
2341
|
var HOOK_CONFIGS = [
|
|
2286
2342
|
{
|
|
2287
2343
|
event: "PostToolUse",
|
|
@@ -2300,17 +2356,17 @@ var HOOK_CONFIGS = [
|
|
|
2300
2356
|
}
|
|
2301
2357
|
];
|
|
2302
2358
|
function readSettings2() {
|
|
2303
|
-
if (!
|
|
2359
|
+
if (!fs15.existsSync(SETTINGS_PATH2)) return {};
|
|
2304
2360
|
try {
|
|
2305
|
-
return JSON.parse(
|
|
2361
|
+
return JSON.parse(fs15.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
2306
2362
|
} catch {
|
|
2307
2363
|
return {};
|
|
2308
2364
|
}
|
|
2309
2365
|
}
|
|
2310
2366
|
function writeSettings2(settings) {
|
|
2311
|
-
const dir =
|
|
2312
|
-
if (!
|
|
2313
|
-
|
|
2367
|
+
const dir = path13.dirname(SETTINGS_PATH2);
|
|
2368
|
+
if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
|
|
2369
|
+
fs15.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
2314
2370
|
}
|
|
2315
2371
|
function hasLearningHook(matchers, command) {
|
|
2316
2372
|
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
@@ -2367,23 +2423,33 @@ function removeLearningHooks() {
|
|
|
2367
2423
|
|
|
2368
2424
|
// src/lib/state.ts
|
|
2369
2425
|
init_constants();
|
|
2370
|
-
import
|
|
2371
|
-
import
|
|
2426
|
+
import fs16 from "fs";
|
|
2427
|
+
import path14 from "path";
|
|
2372
2428
|
import { execSync as execSync6 } from "child_process";
|
|
2373
|
-
var STATE_FILE =
|
|
2429
|
+
var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
|
|
2430
|
+
function normalizeTargetAgent(value) {
|
|
2431
|
+
if (Array.isArray(value)) return value;
|
|
2432
|
+
if (typeof value === "string") {
|
|
2433
|
+
if (value === "both") return ["claude", "cursor"];
|
|
2434
|
+
if (["claude", "cursor", "codex"].includes(value)) return [value];
|
|
2435
|
+
}
|
|
2436
|
+
return void 0;
|
|
2437
|
+
}
|
|
2374
2438
|
function readState() {
|
|
2375
2439
|
try {
|
|
2376
|
-
if (!
|
|
2377
|
-
|
|
2440
|
+
if (!fs16.existsSync(STATE_FILE)) return null;
|
|
2441
|
+
const raw = JSON.parse(fs16.readFileSync(STATE_FILE, "utf-8"));
|
|
2442
|
+
if (raw.targetAgent) raw.targetAgent = normalizeTargetAgent(raw.targetAgent);
|
|
2443
|
+
return raw;
|
|
2378
2444
|
} catch {
|
|
2379
2445
|
return null;
|
|
2380
2446
|
}
|
|
2381
2447
|
}
|
|
2382
2448
|
function writeState(state) {
|
|
2383
|
-
if (!
|
|
2384
|
-
|
|
2449
|
+
if (!fs16.existsSync(CALIBER_DIR)) {
|
|
2450
|
+
fs16.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2385
2451
|
}
|
|
2386
|
-
|
|
2452
|
+
fs16.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
2387
2453
|
}
|
|
2388
2454
|
function getCurrentHeadSha() {
|
|
2389
2455
|
try {
|
|
@@ -2649,6 +2715,9 @@ var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
|
2649
2715
|
"cross_platform_parity",
|
|
2650
2716
|
"no_duplicate_content"
|
|
2651
2717
|
]);
|
|
2718
|
+
var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
2719
|
+
"codex_agents_md_exists"
|
|
2720
|
+
]);
|
|
2652
2721
|
var GRADE_THRESHOLDS = [
|
|
2653
2722
|
{ minScore: 85, grade: "A" },
|
|
2654
2723
|
{ minScore: 70, grade: "B" },
|
|
@@ -2666,15 +2735,15 @@ function computeGrade(score) {
|
|
|
2666
2735
|
// src/scoring/checks/coverage.ts
|
|
2667
2736
|
import { readFileSync, readdirSync } from "fs";
|
|
2668
2737
|
import { join } from "path";
|
|
2669
|
-
function readFileOrNull(
|
|
2738
|
+
function readFileOrNull(path24) {
|
|
2670
2739
|
try {
|
|
2671
|
-
return readFileSync(
|
|
2740
|
+
return readFileSync(path24, "utf-8");
|
|
2672
2741
|
} catch {
|
|
2673
2742
|
return null;
|
|
2674
2743
|
}
|
|
2675
2744
|
}
|
|
2676
|
-
function readJsonOrNull(
|
|
2677
|
-
const content = readFileOrNull(
|
|
2745
|
+
function readJsonOrNull(path24) {
|
|
2746
|
+
const content = readFileOrNull(path24);
|
|
2678
2747
|
if (!content) return null;
|
|
2679
2748
|
try {
|
|
2680
2749
|
return JSON.parse(content);
|
|
@@ -2970,8 +3039,20 @@ function checkExistence(dir) {
|
|
|
2970
3039
|
detail: hasCursorrules ? ".cursorrules found" : cursorRulesDir ? ".cursor/rules/ found" : "No Cursor rules",
|
|
2971
3040
|
suggestion: cursorRulesExist ? void 0 : "Add .cursor/rules/ for Cursor users on your team"
|
|
2972
3041
|
});
|
|
3042
|
+
const agentsMdExists = existsSync3(join2(dir, "AGENTS.md"));
|
|
3043
|
+
checks.push({
|
|
3044
|
+
id: "codex_agents_md_exists",
|
|
3045
|
+
name: "AGENTS.md exists",
|
|
3046
|
+
category: "existence",
|
|
3047
|
+
maxPoints: POINTS_CLAUDE_MD_EXISTS,
|
|
3048
|
+
earnedPoints: agentsMdExists ? POINTS_CLAUDE_MD_EXISTS : 0,
|
|
3049
|
+
passed: agentsMdExists,
|
|
3050
|
+
detail: agentsMdExists ? "Found at project root" : "Not found",
|
|
3051
|
+
suggestion: agentsMdExists ? void 0 : "Create AGENTS.md with project context for Codex"
|
|
3052
|
+
});
|
|
2973
3053
|
const claudeSkills = countFiles(join2(dir, ".claude", "skills"), /\.(md|SKILL\.md)$/);
|
|
2974
|
-
const
|
|
3054
|
+
const codexSkills = countFiles(join2(dir, ".agents", "skills"), /SKILL\.md$/);
|
|
3055
|
+
const skillCount = claudeSkills.length + codexSkills.length;
|
|
2975
3056
|
const skillBase = skillCount >= 1 ? POINTS_SKILLS_EXIST : 0;
|
|
2976
3057
|
const skillBonus = Math.min((skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA, POINTS_SKILLS_BONUS_CAP);
|
|
2977
3058
|
const skillPoints = skillCount >= 1 ? skillBase + Math.max(0, skillBonus) : 0;
|
|
@@ -3030,9 +3111,9 @@ function checkExistence(dir) {
|
|
|
3030
3111
|
// src/scoring/checks/quality.ts
|
|
3031
3112
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3032
3113
|
import { join as join3 } from "path";
|
|
3033
|
-
function readFileOrNull2(
|
|
3114
|
+
function readFileOrNull2(path24) {
|
|
3034
3115
|
try {
|
|
3035
|
-
return readFileSync3(
|
|
3116
|
+
return readFileSync3(path24, "utf-8");
|
|
3036
3117
|
} catch {
|
|
3037
3118
|
return null;
|
|
3038
3119
|
}
|
|
@@ -3044,11 +3125,13 @@ function checkQuality(dir) {
|
|
|
3044
3125
|
const checks = [];
|
|
3045
3126
|
const claudeMd = readFileOrNull2(join3(dir, "CLAUDE.md"));
|
|
3046
3127
|
const cursorrules = readFileOrNull2(join3(dir, ".cursorrules"));
|
|
3047
|
-
const
|
|
3128
|
+
const agentsMd = readFileOrNull2(join3(dir, "AGENTS.md"));
|
|
3129
|
+
const allContent = [claudeMd, cursorrules, agentsMd].filter(Boolean);
|
|
3048
3130
|
const combinedContent = allContent.join("\n");
|
|
3049
|
-
const
|
|
3050
|
-
const
|
|
3051
|
-
|
|
3131
|
+
const primaryInstructions = claudeMd ?? agentsMd;
|
|
3132
|
+
const hasCommands = primaryInstructions ? COMMAND_PATTERNS.some((p) => p.test(primaryInstructions)) : false;
|
|
3133
|
+
const matchedCommands = primaryInstructions ? COMMAND_PATTERNS.filter((p) => p.test(primaryInstructions)).map((p) => {
|
|
3134
|
+
const m = primaryInstructions.match(p);
|
|
3052
3135
|
return m ? m[0] : "";
|
|
3053
3136
|
}).filter(Boolean) : [];
|
|
3054
3137
|
checks.push({
|
|
@@ -3058,11 +3141,11 @@ function checkQuality(dir) {
|
|
|
3058
3141
|
maxPoints: POINTS_HAS_COMMANDS,
|
|
3059
3142
|
earnedPoints: hasCommands ? POINTS_HAS_COMMANDS : 0,
|
|
3060
3143
|
passed: hasCommands,
|
|
3061
|
-
detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` :
|
|
3062
|
-
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to
|
|
3144
|
+
detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` : primaryInstructions ? "No build/test/lint commands detected" : "No instructions file to check",
|
|
3145
|
+
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to your instructions file"
|
|
3063
3146
|
});
|
|
3064
|
-
const primaryFile = claudeMd ?? cursorrules;
|
|
3065
|
-
const primaryName = claudeMd ? "CLAUDE.md" : cursorrules ? ".cursorrules" : null;
|
|
3147
|
+
const primaryFile = claudeMd ?? agentsMd ?? cursorrules;
|
|
3148
|
+
const primaryName = claudeMd ? "CLAUDE.md" : agentsMd ? "AGENTS.md" : cursorrules ? ".cursorrules" : null;
|
|
3066
3149
|
let bloatPoints = 0;
|
|
3067
3150
|
let lineCount = 0;
|
|
3068
3151
|
if (primaryFile) {
|
|
@@ -3183,15 +3266,15 @@ function checkQuality(dir) {
|
|
|
3183
3266
|
// src/scoring/checks/accuracy.ts
|
|
3184
3267
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3185
3268
|
import { join as join4 } from "path";
|
|
3186
|
-
function readFileOrNull3(
|
|
3269
|
+
function readFileOrNull3(path24) {
|
|
3187
3270
|
try {
|
|
3188
|
-
return readFileSync4(
|
|
3271
|
+
return readFileSync4(path24, "utf-8");
|
|
3189
3272
|
} catch {
|
|
3190
3273
|
return null;
|
|
3191
3274
|
}
|
|
3192
3275
|
}
|
|
3193
|
-
function readJsonOrNull2(
|
|
3194
|
-
const content = readFileOrNull3(
|
|
3276
|
+
function readJsonOrNull2(path24) {
|
|
3277
|
+
const content = readFileOrNull3(path24);
|
|
3195
3278
|
if (!content) return null;
|
|
3196
3279
|
try {
|
|
3197
3280
|
return JSON.parse(content);
|
|
@@ -3372,11 +3455,11 @@ function checkAccuracy(dir) {
|
|
|
3372
3455
|
}
|
|
3373
3456
|
|
|
3374
3457
|
// src/scoring/checks/freshness.ts
|
|
3375
|
-
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3458
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3376
3459
|
import { join as join5 } from "path";
|
|
3377
|
-
function readFileOrNull4(
|
|
3460
|
+
function readFileOrNull4(path24) {
|
|
3378
3461
|
try {
|
|
3379
|
-
return readFileSync5(
|
|
3462
|
+
return readFileSync5(path24, "utf-8");
|
|
3380
3463
|
} catch {
|
|
3381
3464
|
return null;
|
|
3382
3465
|
}
|
|
@@ -3394,11 +3477,14 @@ function daysSinceModified(filePath) {
|
|
|
3394
3477
|
function checkFreshness(dir) {
|
|
3395
3478
|
const checks = [];
|
|
3396
3479
|
const claudeMdPath = join5(dir, "CLAUDE.md");
|
|
3397
|
-
const
|
|
3480
|
+
const agentsMdPath = join5(dir, "AGENTS.md");
|
|
3481
|
+
const primaryPath = existsSync6(claudeMdPath) ? claudeMdPath : agentsMdPath;
|
|
3482
|
+
const primaryName = existsSync6(claudeMdPath) ? "CLAUDE.md" : "AGENTS.md";
|
|
3483
|
+
const daysOld = daysSinceModified(primaryPath);
|
|
3398
3484
|
let freshnessPoints = 0;
|
|
3399
3485
|
let freshnessDetail = "";
|
|
3400
3486
|
if (daysOld === null) {
|
|
3401
|
-
freshnessDetail = "No
|
|
3487
|
+
freshnessDetail = "No instructions file to check";
|
|
3402
3488
|
} else {
|
|
3403
3489
|
const threshold = FRESHNESS_THRESHOLDS.find((t) => daysOld <= t.maxDaysOld);
|
|
3404
3490
|
freshnessPoints = threshold ? threshold.points : 0;
|
|
@@ -3406,16 +3492,17 @@ function checkFreshness(dir) {
|
|
|
3406
3492
|
}
|
|
3407
3493
|
checks.push({
|
|
3408
3494
|
id: "claude_md_freshness",
|
|
3409
|
-
name:
|
|
3495
|
+
name: `${primaryName} freshness`,
|
|
3410
3496
|
category: "freshness",
|
|
3411
3497
|
maxPoints: POINTS_FRESHNESS,
|
|
3412
3498
|
earnedPoints: freshnessPoints,
|
|
3413
3499
|
passed: freshnessPoints >= 4,
|
|
3414
3500
|
detail: freshnessDetail,
|
|
3415
|
-
suggestion: daysOld !== null && freshnessPoints < 4 ?
|
|
3501
|
+
suggestion: daysOld !== null && freshnessPoints < 4 ? `${primaryName} is ${daysOld} days old \u2014 run \`caliber refresh\` to update it` : void 0
|
|
3416
3502
|
});
|
|
3417
3503
|
const filesToScan = [
|
|
3418
3504
|
"CLAUDE.md",
|
|
3505
|
+
"AGENTS.md",
|
|
3419
3506
|
".cursorrules",
|
|
3420
3507
|
".claude/settings.json",
|
|
3421
3508
|
".claude/settings.local.json",
|
|
@@ -3486,9 +3573,9 @@ function checkFreshness(dir) {
|
|
|
3486
3573
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3487
3574
|
import { execSync as execSync7 } from "child_process";
|
|
3488
3575
|
import { join as join6 } from "path";
|
|
3489
|
-
function readFileOrNull5(
|
|
3576
|
+
function readFileOrNull5(path24) {
|
|
3490
3577
|
try {
|
|
3491
|
-
return readFileSync6(
|
|
3578
|
+
return readFileSync6(path24, "utf-8");
|
|
3492
3579
|
} catch {
|
|
3493
3580
|
return null;
|
|
3494
3581
|
}
|
|
@@ -3583,22 +3670,22 @@ function checkBonus(dir) {
|
|
|
3583
3670
|
|
|
3584
3671
|
// src/scoring/dismissed.ts
|
|
3585
3672
|
init_constants();
|
|
3586
|
-
import
|
|
3587
|
-
import
|
|
3588
|
-
var DISMISSED_FILE =
|
|
3673
|
+
import fs17 from "fs";
|
|
3674
|
+
import path15 from "path";
|
|
3675
|
+
var DISMISSED_FILE = path15.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3589
3676
|
function readDismissedChecks() {
|
|
3590
3677
|
try {
|
|
3591
|
-
if (!
|
|
3592
|
-
return JSON.parse(
|
|
3678
|
+
if (!fs17.existsSync(DISMISSED_FILE)) return [];
|
|
3679
|
+
return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3593
3680
|
} catch {
|
|
3594
3681
|
return [];
|
|
3595
3682
|
}
|
|
3596
3683
|
}
|
|
3597
3684
|
function writeDismissedChecks(checks) {
|
|
3598
|
-
if (!
|
|
3599
|
-
|
|
3685
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
3686
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3600
3687
|
}
|
|
3601
|
-
|
|
3688
|
+
fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3602
3689
|
}
|
|
3603
3690
|
function getDismissedIds() {
|
|
3604
3691
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -3614,21 +3701,19 @@ function sumCategory(checks, category) {
|
|
|
3614
3701
|
}
|
|
3615
3702
|
function filterChecksForTarget(checks, target) {
|
|
3616
3703
|
return checks.filter((c) => {
|
|
3617
|
-
if (
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
if (target
|
|
3621
|
-
return !CLAUDE_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3622
|
-
}
|
|
3704
|
+
if (CLAUDE_ONLY_CHECKS.has(c.id)) return target.includes("claude");
|
|
3705
|
+
if (CURSOR_ONLY_CHECKS.has(c.id)) return target.includes("cursor");
|
|
3706
|
+
if (CODEX_ONLY_CHECKS.has(c.id)) return target.includes("codex");
|
|
3707
|
+
if (BOTH_ONLY_CHECKS.has(c.id)) return target.includes("claude") && target.includes("cursor");
|
|
3623
3708
|
return true;
|
|
3624
3709
|
});
|
|
3625
3710
|
}
|
|
3626
3711
|
function detectTargetAgent(dir) {
|
|
3627
|
-
const
|
|
3628
|
-
|
|
3629
|
-
if (
|
|
3630
|
-
if (
|
|
3631
|
-
return "claude";
|
|
3712
|
+
const agents = [];
|
|
3713
|
+
if (existsSync8(join7(dir, "CLAUDE.md")) || existsSync8(join7(dir, ".claude", "skills"))) agents.push("claude");
|
|
3714
|
+
if (existsSync8(join7(dir, ".cursorrules")) || existsSync8(join7(dir, ".cursor", "rules"))) agents.push("cursor");
|
|
3715
|
+
if (existsSync8(join7(dir, ".codex")) || existsSync8(join7(dir, ".agents", "skills"))) agents.push("codex");
|
|
3716
|
+
return agents.length > 0 ? agents : ["claude"];
|
|
3632
3717
|
}
|
|
3633
3718
|
function computeLocalScore(dir, targetAgent) {
|
|
3634
3719
|
const target = targetAgent ?? detectTargetAgent(dir);
|
|
@@ -3665,6 +3750,11 @@ function computeLocalScore(dir, targetAgent) {
|
|
|
3665
3750
|
|
|
3666
3751
|
// src/scoring/display.ts
|
|
3667
3752
|
import chalk3 from "chalk";
|
|
3753
|
+
var AGENT_DISPLAY_NAMES = {
|
|
3754
|
+
claude: "Claude Code",
|
|
3755
|
+
cursor: "Cursor",
|
|
3756
|
+
codex: "Codex"
|
|
3757
|
+
};
|
|
3668
3758
|
var CATEGORY_LABELS = {
|
|
3669
3759
|
existence: "FILES & SETUP",
|
|
3670
3760
|
quality: "QUALITY",
|
|
@@ -3707,7 +3797,7 @@ function formatCheck(check) {
|
|
|
3707
3797
|
}
|
|
3708
3798
|
function displayScore(result) {
|
|
3709
3799
|
const gc = gradeColor(result.grade);
|
|
3710
|
-
const agentLabel = result.targetAgent
|
|
3800
|
+
const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
|
|
3711
3801
|
console.log("");
|
|
3712
3802
|
console.log(chalk3.gray(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
3713
3803
|
console.log(chalk3.gray(" \u2502") + " " + chalk3.gray("\u2502"));
|
|
@@ -3733,7 +3823,7 @@ function displayScore(result) {
|
|
|
3733
3823
|
}
|
|
3734
3824
|
function displayScoreSummary(result) {
|
|
3735
3825
|
const gc = gradeColor(result.grade);
|
|
3736
|
-
const agentLabel = result.targetAgent
|
|
3826
|
+
const agentLabel = result.targetAgent.map((a) => AGENT_DISPLAY_NAMES[a] || a).join(" + ");
|
|
3737
3827
|
console.log("");
|
|
3738
3828
|
console.log(
|
|
3739
3829
|
chalk3.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk3.gray(` (Grade ${result.grade})`) + chalk3.gray(` \xB7 ${agentLabel}`) + chalk3.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
|
|
@@ -3799,8 +3889,8 @@ function displayScoreDelta(before, after) {
|
|
|
3799
3889
|
import chalk4 from "chalk";
|
|
3800
3890
|
import ora from "ora";
|
|
3801
3891
|
import readline3 from "readline";
|
|
3802
|
-
import
|
|
3803
|
-
import
|
|
3892
|
+
import fs18 from "fs";
|
|
3893
|
+
import path16 from "path";
|
|
3804
3894
|
|
|
3805
3895
|
// src/mcp/search.ts
|
|
3806
3896
|
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
@@ -4147,36 +4237,36 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4147
4237
|
if (installedNames.length === 0) {
|
|
4148
4238
|
return { installed: 0, names: [] };
|
|
4149
4239
|
}
|
|
4150
|
-
if (targetAgent
|
|
4151
|
-
writeMcpJson(
|
|
4240
|
+
if (targetAgent.includes("claude") || targetAgent.includes("codex")) {
|
|
4241
|
+
writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
|
|
4152
4242
|
}
|
|
4153
|
-
if (targetAgent
|
|
4154
|
-
const cursorDir =
|
|
4155
|
-
if (!
|
|
4156
|
-
writeMcpJson(
|
|
4243
|
+
if (targetAgent.includes("cursor")) {
|
|
4244
|
+
const cursorDir = path16.join(dir, ".cursor");
|
|
4245
|
+
if (!fs18.existsSync(cursorDir)) fs18.mkdirSync(cursorDir, { recursive: true });
|
|
4246
|
+
writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
|
|
4157
4247
|
}
|
|
4158
4248
|
return { installed: installedNames.length, names: installedNames };
|
|
4159
4249
|
}
|
|
4160
4250
|
function writeMcpJson(filePath, mcpServers) {
|
|
4161
4251
|
let existing = {};
|
|
4162
4252
|
try {
|
|
4163
|
-
if (
|
|
4164
|
-
const parsed = JSON.parse(
|
|
4253
|
+
if (fs18.existsSync(filePath)) {
|
|
4254
|
+
const parsed = JSON.parse(fs18.readFileSync(filePath, "utf-8"));
|
|
4165
4255
|
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4166
4256
|
}
|
|
4167
4257
|
} catch {
|
|
4168
4258
|
}
|
|
4169
4259
|
const merged = { ...existing, ...mcpServers };
|
|
4170
|
-
|
|
4260
|
+
fs18.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4171
4261
|
}
|
|
4172
4262
|
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4173
4263
|
const names = [];
|
|
4174
|
-
if (targetAgent
|
|
4264
|
+
if (targetAgent.includes("claude")) {
|
|
4175
4265
|
if (fingerprint.existingConfigs.claudeMcpServers) {
|
|
4176
4266
|
names.push(...Object.keys(fingerprint.existingConfigs.claudeMcpServers).map((k) => k.toLowerCase()));
|
|
4177
4267
|
}
|
|
4178
4268
|
}
|
|
4179
|
-
if (targetAgent
|
|
4269
|
+
if (targetAgent.includes("cursor")) {
|
|
4180
4270
|
if (fingerprint.existingConfigs.cursorMcpServers) {
|
|
4181
4271
|
names.push(...Object.keys(fingerprint.existingConfigs.cursorMcpServers).map((k) => k.toLowerCase()));
|
|
4182
4272
|
}
|
|
@@ -4355,7 +4445,7 @@ async function initCommand(options) {
|
|
|
4355
4445
|
}
|
|
4356
4446
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4357
4447
|
displayScoreSummary(baselineScore);
|
|
4358
|
-
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
4448
|
+
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
4359
4449
|
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
4360
4450
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
4361
4451
|
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
@@ -4656,7 +4746,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4656
4746
|
}
|
|
4657
4747
|
function summarizeSetup(action, setup) {
|
|
4658
4748
|
const descriptions = setup.fileDescriptions;
|
|
4659
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4749
|
+
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4660
4750
|
return `${action}. Files:
|
|
4661
4751
|
${files}`;
|
|
4662
4752
|
}
|
|
@@ -4717,22 +4807,28 @@ function promptInput3(question) {
|
|
|
4717
4807
|
});
|
|
4718
4808
|
}
|
|
4719
4809
|
async function promptAgent() {
|
|
4720
|
-
|
|
4721
|
-
message: "Which coding
|
|
4810
|
+
const selected = await checkbox({
|
|
4811
|
+
message: "Which coding agents do you use? (toggle with space)",
|
|
4722
4812
|
choices: [
|
|
4723
4813
|
{ name: "Claude Code", value: "claude" },
|
|
4724
4814
|
{ name: "Cursor", value: "cursor" },
|
|
4725
|
-
{ name: "
|
|
4726
|
-
]
|
|
4815
|
+
{ name: "Codex (OpenAI)", value: "codex" }
|
|
4816
|
+
],
|
|
4817
|
+
validate: (items) => {
|
|
4818
|
+
if (items.length === 0) return "At least one agent must be selected";
|
|
4819
|
+
return true;
|
|
4820
|
+
}
|
|
4727
4821
|
});
|
|
4822
|
+
return selected;
|
|
4728
4823
|
}
|
|
4729
4824
|
async function promptHookType(targetAgent) {
|
|
4730
4825
|
const choices = [];
|
|
4731
|
-
|
|
4826
|
+
const hasClaude = targetAgent.includes("claude");
|
|
4827
|
+
if (hasClaude) {
|
|
4732
4828
|
choices.push({ name: "Claude Code hook (auto-refresh on session end)", value: "claude" });
|
|
4733
4829
|
}
|
|
4734
4830
|
choices.push({ name: "Git pre-commit hook (refresh before each commit)", value: "precommit" });
|
|
4735
|
-
if (
|
|
4831
|
+
if (hasClaude) {
|
|
4736
4832
|
choices.push({ name: "Both (Claude Code + pre-commit)", value: "both" });
|
|
4737
4833
|
}
|
|
4738
4834
|
choices.push({ name: "Skip for now", value: "skip" });
|
|
@@ -4776,8 +4872,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4776
4872
|
return;
|
|
4777
4873
|
}
|
|
4778
4874
|
const fileInfos = stagedFiles.map((file) => {
|
|
4779
|
-
const proposed =
|
|
4780
|
-
const current = file.currentPath ?
|
|
4875
|
+
const proposed = fs19.readFileSync(file.proposedPath, "utf-8");
|
|
4876
|
+
const current = file.currentPath ? fs19.readFileSync(file.currentPath, "utf-8") : "";
|
|
4781
4877
|
const patch = createTwoFilesPatch(
|
|
4782
4878
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
4783
4879
|
file.relativePath,
|
|
@@ -4960,7 +5056,7 @@ function printSetupSummary(setup) {
|
|
|
4960
5056
|
};
|
|
4961
5057
|
if (claude) {
|
|
4962
5058
|
if (claude.claudeMd) {
|
|
4963
|
-
const icon =
|
|
5059
|
+
const icon = fs19.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4964
5060
|
const desc = getDescription("CLAUDE.md");
|
|
4965
5061
|
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
4966
5062
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -4970,7 +5066,28 @@ function printSetupSummary(setup) {
|
|
|
4970
5066
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4971
5067
|
for (const skill of skills) {
|
|
4972
5068
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4973
|
-
const icon =
|
|
5069
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5070
|
+
const desc = getDescription(skillPath);
|
|
5071
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5072
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
5073
|
+
console.log("");
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
}
|
|
5077
|
+
const codex = setup.codex;
|
|
5078
|
+
if (codex) {
|
|
5079
|
+
if (codex.agentsMd) {
|
|
5080
|
+
const icon = fs19.existsSync("AGENTS.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
5081
|
+
const desc = getDescription("AGENTS.md");
|
|
5082
|
+
console.log(` ${icon} ${chalk5.bold("AGENTS.md")}`);
|
|
5083
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
5084
|
+
console.log("");
|
|
5085
|
+
}
|
|
5086
|
+
const codexSkills = codex.skills;
|
|
5087
|
+
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
5088
|
+
for (const skill of codexSkills) {
|
|
5089
|
+
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
5090
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4974
5091
|
const desc = getDescription(skillPath);
|
|
4975
5092
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4976
5093
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -4980,7 +5097,7 @@ function printSetupSummary(setup) {
|
|
|
4980
5097
|
}
|
|
4981
5098
|
if (cursor) {
|
|
4982
5099
|
if (cursor.cursorrules) {
|
|
4983
|
-
const icon =
|
|
5100
|
+
const icon = fs19.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4984
5101
|
const desc = getDescription(".cursorrules");
|
|
4985
5102
|
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
4986
5103
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -4990,7 +5107,7 @@ function printSetupSummary(setup) {
|
|
|
4990
5107
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4991
5108
|
for (const skill of cursorSkills) {
|
|
4992
5109
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4993
|
-
const icon =
|
|
5110
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4994
5111
|
const desc = getDescription(skillPath);
|
|
4995
5112
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4996
5113
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -5001,7 +5118,7 @@ function printSetupSummary(setup) {
|
|
|
5001
5118
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
5002
5119
|
for (const rule of rules) {
|
|
5003
5120
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5004
|
-
const icon =
|
|
5121
|
+
const icon = fs19.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5005
5122
|
const desc = getDescription(rulePath);
|
|
5006
5123
|
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
5007
5124
|
if (desc) {
|
|
@@ -5014,7 +5131,7 @@ function printSetupSummary(setup) {
|
|
|
5014
5131
|
}
|
|
5015
5132
|
}
|
|
5016
5133
|
}
|
|
5017
|
-
if (!
|
|
5134
|
+
if (!codex && !fs19.existsSync("AGENTS.md")) {
|
|
5018
5135
|
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
5019
5136
|
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
5020
5137
|
console.log("");
|
|
@@ -5042,8 +5159,8 @@ function ensurePermissions() {
|
|
|
5042
5159
|
const settingsPath = ".claude/settings.json";
|
|
5043
5160
|
let settings = {};
|
|
5044
5161
|
try {
|
|
5045
|
-
if (
|
|
5046
|
-
settings = JSON.parse(
|
|
5162
|
+
if (fs19.existsSync(settingsPath)) {
|
|
5163
|
+
settings = JSON.parse(fs19.readFileSync(settingsPath, "utf-8"));
|
|
5047
5164
|
}
|
|
5048
5165
|
} catch {
|
|
5049
5166
|
}
|
|
@@ -5057,13 +5174,14 @@ function ensurePermissions() {
|
|
|
5057
5174
|
"Bash(git *)"
|
|
5058
5175
|
];
|
|
5059
5176
|
settings.permissions = permissions;
|
|
5060
|
-
if (!
|
|
5061
|
-
|
|
5177
|
+
if (!fs19.existsSync(".claude")) fs19.mkdirSync(".claude", { recursive: true });
|
|
5178
|
+
fs19.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5062
5179
|
}
|
|
5063
5180
|
function collectSetupFiles(setup) {
|
|
5064
5181
|
const files = [];
|
|
5065
5182
|
const claude = setup.claude;
|
|
5066
5183
|
const cursor = setup.cursor;
|
|
5184
|
+
const codex = setup.codex;
|
|
5067
5185
|
if (claude) {
|
|
5068
5186
|
if (claude.claudeMd) files.push({ path: "CLAUDE.md", content: claude.claudeMd });
|
|
5069
5187
|
const skills = claude.skills;
|
|
@@ -5073,6 +5191,15 @@ function collectSetupFiles(setup) {
|
|
|
5073
5191
|
}
|
|
5074
5192
|
}
|
|
5075
5193
|
}
|
|
5194
|
+
if (codex) {
|
|
5195
|
+
if (codex.agentsMd) files.push({ path: "AGENTS.md", content: codex.agentsMd });
|
|
5196
|
+
const codexSkills = codex.skills;
|
|
5197
|
+
if (Array.isArray(codexSkills)) {
|
|
5198
|
+
for (const skill of codexSkills) {
|
|
5199
|
+
files.push({ path: `.agents/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5076
5203
|
if (cursor) {
|
|
5077
5204
|
if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
|
|
5078
5205
|
const cursorSkills = cursor.skills;
|
|
@@ -5088,7 +5215,8 @@ function collectSetupFiles(setup) {
|
|
|
5088
5215
|
}
|
|
5089
5216
|
}
|
|
5090
5217
|
}
|
|
5091
|
-
|
|
5218
|
+
const hasCodexAgentsMd = codex && codex.agentsMd;
|
|
5219
|
+
if (!fs19.existsSync("AGENTS.md") && !hasCodexAgentsMd) {
|
|
5092
5220
|
const agentRefs = [];
|
|
5093
5221
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
5094
5222
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -5139,7 +5267,7 @@ function undoCommand() {
|
|
|
5139
5267
|
|
|
5140
5268
|
// src/commands/status.ts
|
|
5141
5269
|
import chalk7 from "chalk";
|
|
5142
|
-
import
|
|
5270
|
+
import fs20 from "fs";
|
|
5143
5271
|
async function statusCommand(options) {
|
|
5144
5272
|
const config = loadConfig();
|
|
5145
5273
|
const manifest = readManifest();
|
|
@@ -5165,7 +5293,7 @@ async function statusCommand(options) {
|
|
|
5165
5293
|
}
|
|
5166
5294
|
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
5167
5295
|
for (const entry of manifest.entries) {
|
|
5168
|
-
const exists =
|
|
5296
|
+
const exists = fs20.existsSync(entry.path);
|
|
5169
5297
|
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
5170
5298
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
5171
5299
|
}
|
|
@@ -5195,7 +5323,7 @@ async function regenerateCommand(options) {
|
|
|
5195
5323
|
genMessages.start();
|
|
5196
5324
|
let generatedSetup = null;
|
|
5197
5325
|
try {
|
|
5198
|
-
const targetAgent = readState()?.targetAgent ?? "
|
|
5326
|
+
const targetAgent = readState()?.targetAgent ?? ["claude", "cursor"];
|
|
5199
5327
|
const result2 = await generateSetup(
|
|
5200
5328
|
fingerprint,
|
|
5201
5329
|
targetAgent,
|
|
@@ -5252,13 +5380,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
|
|
|
5252
5380
|
import { join as join8, dirname as dirname2 } from "path";
|
|
5253
5381
|
|
|
5254
5382
|
// src/scanner/index.ts
|
|
5255
|
-
import
|
|
5256
|
-
import
|
|
5383
|
+
import fs21 from "fs";
|
|
5384
|
+
import path17 from "path";
|
|
5257
5385
|
import crypto2 from "crypto";
|
|
5258
5386
|
function scanLocalState(dir) {
|
|
5259
5387
|
const items = [];
|
|
5260
|
-
const claudeMdPath =
|
|
5261
|
-
if (
|
|
5388
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
5389
|
+
if (fs21.existsSync(claudeMdPath)) {
|
|
5262
5390
|
items.push({
|
|
5263
5391
|
type: "rule",
|
|
5264
5392
|
platform: "claude",
|
|
@@ -5267,10 +5395,10 @@ function scanLocalState(dir) {
|
|
|
5267
5395
|
path: claudeMdPath
|
|
5268
5396
|
});
|
|
5269
5397
|
}
|
|
5270
|
-
const skillsDir =
|
|
5271
|
-
if (
|
|
5272
|
-
for (const file of
|
|
5273
|
-
const filePath =
|
|
5398
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
5399
|
+
if (fs21.existsSync(skillsDir)) {
|
|
5400
|
+
for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5401
|
+
const filePath = path17.join(skillsDir, file);
|
|
5274
5402
|
items.push({
|
|
5275
5403
|
type: "skill",
|
|
5276
5404
|
platform: "claude",
|
|
@@ -5280,10 +5408,10 @@ function scanLocalState(dir) {
|
|
|
5280
5408
|
});
|
|
5281
5409
|
}
|
|
5282
5410
|
}
|
|
5283
|
-
const mcpJsonPath =
|
|
5284
|
-
if (
|
|
5411
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
5412
|
+
if (fs21.existsSync(mcpJsonPath)) {
|
|
5285
5413
|
try {
|
|
5286
|
-
const mcpJson = JSON.parse(
|
|
5414
|
+
const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
|
|
5287
5415
|
if (mcpJson.mcpServers) {
|
|
5288
5416
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5289
5417
|
items.push({
|
|
@@ -5298,8 +5426,36 @@ function scanLocalState(dir) {
|
|
|
5298
5426
|
} catch {
|
|
5299
5427
|
}
|
|
5300
5428
|
}
|
|
5301
|
-
const
|
|
5302
|
-
if (
|
|
5429
|
+
const agentsMdPath = path17.join(dir, "AGENTS.md");
|
|
5430
|
+
if (fs21.existsSync(agentsMdPath)) {
|
|
5431
|
+
items.push({
|
|
5432
|
+
type: "rule",
|
|
5433
|
+
platform: "codex",
|
|
5434
|
+
name: "AGENTS.md",
|
|
5435
|
+
contentHash: hashFile(agentsMdPath),
|
|
5436
|
+
path: agentsMdPath
|
|
5437
|
+
});
|
|
5438
|
+
}
|
|
5439
|
+
const codexSkillsDir = path17.join(dir, ".agents", "skills");
|
|
5440
|
+
if (fs21.existsSync(codexSkillsDir)) {
|
|
5441
|
+
try {
|
|
5442
|
+
for (const name of fs21.readdirSync(codexSkillsDir)) {
|
|
5443
|
+
const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
|
|
5444
|
+
if (fs21.existsSync(skillFile)) {
|
|
5445
|
+
items.push({
|
|
5446
|
+
type: "skill",
|
|
5447
|
+
platform: "codex",
|
|
5448
|
+
name: `${name}/SKILL.md`,
|
|
5449
|
+
contentHash: hashFile(skillFile),
|
|
5450
|
+
path: skillFile
|
|
5451
|
+
});
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
} catch {
|
|
5455
|
+
}
|
|
5456
|
+
}
|
|
5457
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
5458
|
+
if (fs21.existsSync(cursorrulesPath)) {
|
|
5303
5459
|
items.push({
|
|
5304
5460
|
type: "rule",
|
|
5305
5461
|
platform: "cursor",
|
|
@@ -5308,10 +5464,10 @@ function scanLocalState(dir) {
|
|
|
5308
5464
|
path: cursorrulesPath
|
|
5309
5465
|
});
|
|
5310
5466
|
}
|
|
5311
|
-
const cursorRulesDir =
|
|
5312
|
-
if (
|
|
5313
|
-
for (const file of
|
|
5314
|
-
const filePath =
|
|
5467
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
5468
|
+
if (fs21.existsSync(cursorRulesDir)) {
|
|
5469
|
+
for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5470
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
5315
5471
|
items.push({
|
|
5316
5472
|
type: "rule",
|
|
5317
5473
|
platform: "cursor",
|
|
@@ -5321,12 +5477,12 @@ function scanLocalState(dir) {
|
|
|
5321
5477
|
});
|
|
5322
5478
|
}
|
|
5323
5479
|
}
|
|
5324
|
-
const cursorSkillsDir =
|
|
5325
|
-
if (
|
|
5480
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
5481
|
+
if (fs21.existsSync(cursorSkillsDir)) {
|
|
5326
5482
|
try {
|
|
5327
|
-
for (const name of
|
|
5328
|
-
const skillFile =
|
|
5329
|
-
if (
|
|
5483
|
+
for (const name of fs21.readdirSync(cursorSkillsDir)) {
|
|
5484
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
5485
|
+
if (fs21.existsSync(skillFile)) {
|
|
5330
5486
|
items.push({
|
|
5331
5487
|
type: "skill",
|
|
5332
5488
|
platform: "cursor",
|
|
@@ -5339,10 +5495,10 @@ function scanLocalState(dir) {
|
|
|
5339
5495
|
} catch {
|
|
5340
5496
|
}
|
|
5341
5497
|
}
|
|
5342
|
-
const cursorMcpPath =
|
|
5343
|
-
if (
|
|
5498
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
5499
|
+
if (fs21.existsSync(cursorMcpPath)) {
|
|
5344
5500
|
try {
|
|
5345
|
-
const mcpJson = JSON.parse(
|
|
5501
|
+
const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
|
|
5346
5502
|
if (mcpJson.mcpServers) {
|
|
5347
5503
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5348
5504
|
items.push({
|
|
@@ -5360,7 +5516,7 @@ function scanLocalState(dir) {
|
|
|
5360
5516
|
return items;
|
|
5361
5517
|
}
|
|
5362
5518
|
function hashFile(filePath) {
|
|
5363
|
-
const text =
|
|
5519
|
+
const text = fs21.readFileSync(filePath, "utf-8");
|
|
5364
5520
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
5365
5521
|
}
|
|
5366
5522
|
function hashJson(obj) {
|
|
@@ -5380,13 +5536,17 @@ function getSkillPath(platform, slug) {
|
|
|
5380
5536
|
if (platform === "cursor") {
|
|
5381
5537
|
return join8(".cursor", "skills", slug, "SKILL.md");
|
|
5382
5538
|
}
|
|
5539
|
+
if (platform === "codex") {
|
|
5540
|
+
return join8(".agents", "skills", slug, "SKILL.md");
|
|
5541
|
+
}
|
|
5383
5542
|
return join8(".claude", "skills", slug, "SKILL.md");
|
|
5384
5543
|
}
|
|
5385
5544
|
function getInstalledSkills() {
|
|
5386
5545
|
const installed = /* @__PURE__ */ new Set();
|
|
5387
5546
|
const dirs = [
|
|
5388
5547
|
join8(process.cwd(), ".claude", "skills"),
|
|
5389
|
-
join8(process.cwd(), ".cursor", "skills")
|
|
5548
|
+
join8(process.cwd(), ".cursor", "skills"),
|
|
5549
|
+
join8(process.cwd(), ".agents", "skills")
|
|
5390
5550
|
];
|
|
5391
5551
|
for (const dir of dirs) {
|
|
5392
5552
|
try {
|
|
@@ -5834,7 +5994,8 @@ async function fetchSkillContent(rec) {
|
|
|
5834
5994
|
const candidates = [
|
|
5835
5995
|
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
5836
5996
|
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
5837
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md
|
|
5997
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
5998
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
5838
5999
|
];
|
|
5839
6000
|
for (const url of candidates) {
|
|
5840
6001
|
try {
|
|
@@ -5936,8 +6097,8 @@ async function scoreCommand(options) {
|
|
|
5936
6097
|
}
|
|
5937
6098
|
|
|
5938
6099
|
// src/commands/refresh.ts
|
|
5939
|
-
import
|
|
5940
|
-
import
|
|
6100
|
+
import fs23 from "fs";
|
|
6101
|
+
import path19 from "path";
|
|
5941
6102
|
import chalk11 from "chalk";
|
|
5942
6103
|
import ora6 from "ora";
|
|
5943
6104
|
|
|
@@ -6014,37 +6175,37 @@ function collectDiff(lastSha) {
|
|
|
6014
6175
|
}
|
|
6015
6176
|
|
|
6016
6177
|
// src/writers/refresh.ts
|
|
6017
|
-
import
|
|
6018
|
-
import
|
|
6178
|
+
import fs22 from "fs";
|
|
6179
|
+
import path18 from "path";
|
|
6019
6180
|
function writeRefreshDocs(docs) {
|
|
6020
6181
|
const written = [];
|
|
6021
6182
|
if (docs.claudeMd) {
|
|
6022
|
-
|
|
6183
|
+
fs22.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6023
6184
|
written.push("CLAUDE.md");
|
|
6024
6185
|
}
|
|
6025
6186
|
if (docs.readmeMd) {
|
|
6026
|
-
|
|
6187
|
+
fs22.writeFileSync("README.md", docs.readmeMd);
|
|
6027
6188
|
written.push("README.md");
|
|
6028
6189
|
}
|
|
6029
6190
|
if (docs.cursorrules) {
|
|
6030
|
-
|
|
6191
|
+
fs22.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6031
6192
|
written.push(".cursorrules");
|
|
6032
6193
|
}
|
|
6033
6194
|
if (docs.cursorRules) {
|
|
6034
|
-
const rulesDir =
|
|
6035
|
-
if (!
|
|
6195
|
+
const rulesDir = path18.join(".cursor", "rules");
|
|
6196
|
+
if (!fs22.existsSync(rulesDir)) fs22.mkdirSync(rulesDir, { recursive: true });
|
|
6036
6197
|
for (const rule of docs.cursorRules) {
|
|
6037
|
-
const filePath =
|
|
6038
|
-
|
|
6198
|
+
const filePath = path18.join(rulesDir, rule.filename);
|
|
6199
|
+
fs22.writeFileSync(filePath, rule.content);
|
|
6039
6200
|
written.push(filePath);
|
|
6040
6201
|
}
|
|
6041
6202
|
}
|
|
6042
6203
|
if (docs.claudeSkills) {
|
|
6043
|
-
const skillsDir =
|
|
6044
|
-
if (!
|
|
6204
|
+
const skillsDir = path18.join(".claude", "skills");
|
|
6205
|
+
if (!fs22.existsSync(skillsDir)) fs22.mkdirSync(skillsDir, { recursive: true });
|
|
6045
6206
|
for (const skill of docs.claudeSkills) {
|
|
6046
|
-
const filePath =
|
|
6047
|
-
|
|
6207
|
+
const filePath = path18.join(skillsDir, skill.filename);
|
|
6208
|
+
fs22.writeFileSync(filePath, skill.content);
|
|
6048
6209
|
written.push(filePath);
|
|
6049
6210
|
}
|
|
6050
6211
|
}
|
|
@@ -6119,11 +6280,11 @@ function log(quiet, ...args) {
|
|
|
6119
6280
|
function discoverGitRepos(parentDir) {
|
|
6120
6281
|
const repos = [];
|
|
6121
6282
|
try {
|
|
6122
|
-
const entries =
|
|
6283
|
+
const entries = fs23.readdirSync(parentDir, { withFileTypes: true });
|
|
6123
6284
|
for (const entry of entries) {
|
|
6124
6285
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
6125
|
-
const childPath =
|
|
6126
|
-
if (
|
|
6286
|
+
const childPath = path19.join(parentDir, entry.name);
|
|
6287
|
+
if (fs23.existsSync(path19.join(childPath, ".git"))) {
|
|
6127
6288
|
repos.push(childPath);
|
|
6128
6289
|
}
|
|
6129
6290
|
}
|
|
@@ -6218,7 +6379,7 @@ async function refreshCommand(options) {
|
|
|
6218
6379
|
`));
|
|
6219
6380
|
const originalDir = process.cwd();
|
|
6220
6381
|
for (const repo of repos) {
|
|
6221
|
-
const repoName =
|
|
6382
|
+
const repoName = path19.basename(repo);
|
|
6222
6383
|
try {
|
|
6223
6384
|
process.chdir(repo);
|
|
6224
6385
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -6465,8 +6626,8 @@ function readStdin() {
|
|
|
6465
6626
|
|
|
6466
6627
|
// src/learner/storage.ts
|
|
6467
6628
|
init_constants();
|
|
6468
|
-
import
|
|
6469
|
-
import
|
|
6629
|
+
import fs24 from "fs";
|
|
6630
|
+
import path20 from "path";
|
|
6470
6631
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
6471
6632
|
var DEFAULT_STATE = {
|
|
6472
6633
|
sessionId: null,
|
|
@@ -6474,15 +6635,15 @@ var DEFAULT_STATE = {
|
|
|
6474
6635
|
lastAnalysisTimestamp: null
|
|
6475
6636
|
};
|
|
6476
6637
|
function ensureLearningDir() {
|
|
6477
|
-
if (!
|
|
6478
|
-
|
|
6638
|
+
if (!fs24.existsSync(LEARNING_DIR)) {
|
|
6639
|
+
fs24.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6479
6640
|
}
|
|
6480
6641
|
}
|
|
6481
6642
|
function sessionFilePath() {
|
|
6482
|
-
return
|
|
6643
|
+
return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6483
6644
|
}
|
|
6484
6645
|
function stateFilePath() {
|
|
6485
|
-
return
|
|
6646
|
+
return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6486
6647
|
}
|
|
6487
6648
|
function truncateResponse(response) {
|
|
6488
6649
|
const str = JSON.stringify(response);
|
|
@@ -6493,50 +6654,50 @@ function appendEvent(event) {
|
|
|
6493
6654
|
ensureLearningDir();
|
|
6494
6655
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6495
6656
|
const filePath = sessionFilePath();
|
|
6496
|
-
|
|
6657
|
+
fs24.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6497
6658
|
const count = getEventCount();
|
|
6498
6659
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6499
|
-
const lines =
|
|
6660
|
+
const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6500
6661
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6501
|
-
|
|
6662
|
+
fs24.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6502
6663
|
}
|
|
6503
6664
|
}
|
|
6504
6665
|
function readAllEvents() {
|
|
6505
6666
|
const filePath = sessionFilePath();
|
|
6506
|
-
if (!
|
|
6507
|
-
const lines =
|
|
6667
|
+
if (!fs24.existsSync(filePath)) return [];
|
|
6668
|
+
const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6508
6669
|
return lines.map((line) => JSON.parse(line));
|
|
6509
6670
|
}
|
|
6510
6671
|
function getEventCount() {
|
|
6511
6672
|
const filePath = sessionFilePath();
|
|
6512
|
-
if (!
|
|
6513
|
-
const content =
|
|
6673
|
+
if (!fs24.existsSync(filePath)) return 0;
|
|
6674
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6514
6675
|
return content.split("\n").filter(Boolean).length;
|
|
6515
6676
|
}
|
|
6516
6677
|
function clearSession() {
|
|
6517
6678
|
const filePath = sessionFilePath();
|
|
6518
|
-
if (
|
|
6679
|
+
if (fs24.existsSync(filePath)) fs24.unlinkSync(filePath);
|
|
6519
6680
|
}
|
|
6520
6681
|
function readState2() {
|
|
6521
6682
|
const filePath = stateFilePath();
|
|
6522
|
-
if (!
|
|
6683
|
+
if (!fs24.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6523
6684
|
try {
|
|
6524
|
-
return JSON.parse(
|
|
6685
|
+
return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
|
|
6525
6686
|
} catch {
|
|
6526
6687
|
return { ...DEFAULT_STATE };
|
|
6527
6688
|
}
|
|
6528
6689
|
}
|
|
6529
6690
|
function writeState2(state) {
|
|
6530
6691
|
ensureLearningDir();
|
|
6531
|
-
|
|
6692
|
+
fs24.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6532
6693
|
}
|
|
6533
6694
|
function resetState() {
|
|
6534
6695
|
writeState2({ ...DEFAULT_STATE });
|
|
6535
6696
|
}
|
|
6536
6697
|
|
|
6537
6698
|
// src/learner/writer.ts
|
|
6538
|
-
import
|
|
6539
|
-
import
|
|
6699
|
+
import fs25 from "fs";
|
|
6700
|
+
import path21 from "path";
|
|
6540
6701
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6541
6702
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6542
6703
|
function writeLearnedContent(update) {
|
|
@@ -6556,8 +6717,8 @@ function writeLearnedContent(update) {
|
|
|
6556
6717
|
function writeLearnedSection(content) {
|
|
6557
6718
|
const claudeMdPath = "CLAUDE.md";
|
|
6558
6719
|
let existing = "";
|
|
6559
|
-
if (
|
|
6560
|
-
existing =
|
|
6720
|
+
if (fs25.existsSync(claudeMdPath)) {
|
|
6721
|
+
existing = fs25.readFileSync(claudeMdPath, "utf-8");
|
|
6561
6722
|
}
|
|
6562
6723
|
const section = `${LEARNED_START}
|
|
6563
6724
|
${content}
|
|
@@ -6571,15 +6732,15 @@ ${LEARNED_END}`;
|
|
|
6571
6732
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6572
6733
|
updated = existing + separator + "\n" + section + "\n";
|
|
6573
6734
|
}
|
|
6574
|
-
|
|
6735
|
+
fs25.writeFileSync(claudeMdPath, updated);
|
|
6575
6736
|
}
|
|
6576
6737
|
function writeLearnedSkill(skill) {
|
|
6577
|
-
const skillDir =
|
|
6578
|
-
if (!
|
|
6579
|
-
const skillPath =
|
|
6580
|
-
if (!skill.isNew &&
|
|
6581
|
-
const existing =
|
|
6582
|
-
|
|
6738
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
6739
|
+
if (!fs25.existsSync(skillDir)) fs25.mkdirSync(skillDir, { recursive: true });
|
|
6740
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
6741
|
+
if (!skill.isNew && fs25.existsSync(skillPath)) {
|
|
6742
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
6743
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6583
6744
|
} else {
|
|
6584
6745
|
const frontmatter = [
|
|
6585
6746
|
"---",
|
|
@@ -6588,14 +6749,14 @@ function writeLearnedSkill(skill) {
|
|
|
6588
6749
|
"---",
|
|
6589
6750
|
""
|
|
6590
6751
|
].join("\n");
|
|
6591
|
-
|
|
6752
|
+
fs25.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6592
6753
|
}
|
|
6593
6754
|
return skillPath;
|
|
6594
6755
|
}
|
|
6595
6756
|
function readLearnedSection() {
|
|
6596
6757
|
const claudeMdPath = "CLAUDE.md";
|
|
6597
|
-
if (!
|
|
6598
|
-
const content =
|
|
6758
|
+
if (!fs25.existsSync(claudeMdPath)) return null;
|
|
6759
|
+
const content = fs25.readFileSync(claudeMdPath, "utf-8");
|
|
6599
6760
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6600
6761
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6601
6762
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6779,20 +6940,31 @@ Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
|
6779
6940
|
}
|
|
6780
6941
|
|
|
6781
6942
|
// src/cli.ts
|
|
6782
|
-
var __dirname =
|
|
6943
|
+
var __dirname = path22.dirname(fileURLToPath(import.meta.url));
|
|
6783
6944
|
var pkg = JSON.parse(
|
|
6784
|
-
|
|
6945
|
+
fs26.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6785
6946
|
);
|
|
6786
6947
|
var program = new Command();
|
|
6787
6948
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
6788
6949
|
program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
|
|
6789
|
-
|
|
6950
|
+
function parseAgentOption(value) {
|
|
6951
|
+
if (value === "both") return ["claude", "cursor"];
|
|
6952
|
+
if (value === "all") return ["claude", "cursor", "codex"];
|
|
6953
|
+
const valid = ["claude", "cursor", "codex"];
|
|
6954
|
+
const agents = [...new Set(value.split(",").map((s) => s.trim().toLowerCase()).filter((a) => valid.includes(a)))];
|
|
6955
|
+
if (agents.length === 0) {
|
|
6956
|
+
console.error(`Invalid agent "${value}". Choose from: claude, cursor, codex (comma-separated for multiple)`);
|
|
6957
|
+
process.exit(1);
|
|
6958
|
+
}
|
|
6959
|
+
return agents;
|
|
6960
|
+
}
|
|
6961
|
+
program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
|
|
6790
6962
|
program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
|
|
6791
6963
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
|
|
6792
6964
|
program.command("regenerate").alias("regen").alias("re").alias("update").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
|
|
6793
6965
|
program.command("config").description("Configure LLM provider, API key, and model").action(configCommand);
|
|
6794
6966
|
program.command("recommend").description("Discover and install skill recommendations").option("--generate", "Force fresh recommendation search").action(recommendCommand);
|
|
6795
|
-
program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target
|
|
6967
|
+
program.command("score").description("Score your current agent config setup (deterministic, no network)").option("--json", "Output as JSON").option("--quiet", "One-line output for scripts/hooks").option("--agent <type>", "Target agents (comma-separated): claude, cursor, codex", parseAgentOption).action(scoreCommand);
|
|
6796
6968
|
program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(refreshCommand);
|
|
6797
6969
|
program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(hooksCommand);
|
|
6798
6970
|
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
@@ -6803,22 +6975,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
6803
6975
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
6804
6976
|
|
|
6805
6977
|
// src/utils/version-check.ts
|
|
6806
|
-
import
|
|
6807
|
-
import
|
|
6978
|
+
import fs27 from "fs";
|
|
6979
|
+
import path23 from "path";
|
|
6808
6980
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6809
6981
|
import { execSync as execSync9 } from "child_process";
|
|
6810
6982
|
import chalk15 from "chalk";
|
|
6811
6983
|
import ora7 from "ora";
|
|
6812
6984
|
import confirm2 from "@inquirer/confirm";
|
|
6813
|
-
var __dirname_vc =
|
|
6985
|
+
var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
|
|
6814
6986
|
var pkg2 = JSON.parse(
|
|
6815
|
-
|
|
6987
|
+
fs27.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6816
6988
|
);
|
|
6817
6989
|
function getInstalledVersion() {
|
|
6818
6990
|
try {
|
|
6819
6991
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6820
|
-
const pkgPath =
|
|
6821
|
-
return JSON.parse(
|
|
6992
|
+
const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6993
|
+
return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
|
|
6822
6994
|
} catch {
|
|
6823
6995
|
return null;
|
|
6824
6996
|
}
|