@rely-ai/caliber 1.1.4 → 1.2.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 +414 -264
- package/package.json +1 -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,7 @@ 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 fs19 from "fs";
|
|
64
64
|
|
|
65
65
|
// src/fingerprint/index.ts
|
|
66
66
|
import fs6 from "fs";
|
|
@@ -145,6 +145,10 @@ function readExistingConfigs(dir) {
|
|
|
145
145
|
if (fs2.existsSync(readmeMdPath)) {
|
|
146
146
|
configs.readmeMd = fs2.readFileSync(readmeMdPath, "utf-8");
|
|
147
147
|
}
|
|
148
|
+
const agentsMdPath = path2.join(dir, "AGENTS.md");
|
|
149
|
+
if (fs2.existsSync(agentsMdPath)) {
|
|
150
|
+
configs.agentsMd = fs2.readFileSync(agentsMdPath, "utf-8");
|
|
151
|
+
}
|
|
148
152
|
const claudeMdPath = path2.join(dir, "CLAUDE.md");
|
|
149
153
|
if (fs2.existsSync(claudeMdPath)) {
|
|
150
154
|
configs.claudeMd = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
@@ -1172,7 +1176,7 @@ async function llmJsonCall(options) {
|
|
|
1172
1176
|
}
|
|
1173
1177
|
|
|
1174
1178
|
// src/ai/prompts.ts
|
|
1175
|
-
var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code and
|
|
1179
|
+
var GENERATION_SYSTEM_PROMPT = `You are an expert auditor for coding agent configurations (Claude Code, Cursor, and Codex).
|
|
1176
1180
|
|
|
1177
1181
|
Your job depends on context:
|
|
1178
1182
|
- If no existing configs exist \u2192 generate an initial setup from scratch.
|
|
@@ -1180,7 +1184,9 @@ Your job depends on context:
|
|
|
1180
1184
|
|
|
1181
1185
|
You understand these config files:
|
|
1182
1186
|
- CLAUDE.md: Project context for Claude Code \u2014 build/test commands, architecture, conventions.
|
|
1187
|
+
- 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
1188
|
- .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.
|
|
1189
|
+
- .agents/skills/{name}/SKILL.md: Same OpenSkills format for Codex skills (Codex scans .agents/skills/ for skills).
|
|
1184
1190
|
- .cursorrules: Coding rules for Cursor (deprecated legacy format \u2014 do NOT generate this).
|
|
1185
1191
|
- .cursor/rules/*.mdc: Modern Cursor rules with frontmatter (description, globs, alwaysApply).
|
|
1186
1192
|
- .cursor/skills/{name}/SKILL.md: Same OpenSkills format as Claude skills.
|
|
@@ -1212,7 +1218,7 @@ Omit empty categories. Keep each reason punchy and specific. End with a blank li
|
|
|
1212
1218
|
|
|
1213
1219
|
AgentSetup schema:
|
|
1214
1220
|
{
|
|
1215
|
-
"targetAgent": "claude" | "cursor" | "both",
|
|
1221
|
+
"targetAgent": "claude" | "cursor" | "codex" | "both",
|
|
1216
1222
|
"fileDescriptions": {
|
|
1217
1223
|
"<file-path>": "reason for this change (max 80 chars)"
|
|
1218
1224
|
},
|
|
@@ -1223,6 +1229,10 @@ AgentSetup schema:
|
|
|
1223
1229
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
1224
1230
|
"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
1231
|
},
|
|
1232
|
+
"codex": {
|
|
1233
|
+
"agentsMd": "string (markdown content for AGENTS.md \u2014 the primary Codex instructions file, same quality/structure as CLAUDE.md)",
|
|
1234
|
+
"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)" }]
|
|
1235
|
+
},
|
|
1226
1236
|
"cursor": {
|
|
1227
1237
|
"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
1238
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
@@ -1237,14 +1247,15 @@ All skills follow the OpenSkills standard (agentskills.io):
|
|
|
1237
1247
|
- 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
1248
|
- Keep skill content under 500 lines. Move detailed references to separate files if needed.
|
|
1239
1249
|
|
|
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.
|
|
1250
|
+
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
1251
|
|
|
1242
1252
|
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
1253
|
|
|
1244
1254
|
SCORING CRITERIA \u2014 your output is scored deterministically. Optimize for 100/100:
|
|
1245
1255
|
|
|
1246
1256
|
Existence (25 pts):
|
|
1247
|
-
- CLAUDE.md exists (6 pts) \u2014 always generate
|
|
1257
|
+
- CLAUDE.md exists (6 pts) \u2014 always generate for claude/both targets
|
|
1258
|
+
- AGENTS.md exists (6 pts) \u2014 always generate for codex target (serves as primary instructions file)
|
|
1248
1259
|
- 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
1260
|
- MCP servers mentioned (3 pts) \u2014 reference detected MCP integrations
|
|
1250
1261
|
- For "both" target: .cursorrules/.cursor/rules/ exist (3+3 pts), cross-platform parity (2 pts)
|
|
@@ -1278,12 +1289,12 @@ Bonus (5 pts):
|
|
|
1278
1289
|
- Hooks configured (2 pts), AGENTS.md (1 pt), OpenSkills format (2 pts) \u2014 handled by caliber
|
|
1279
1290
|
|
|
1280
1291
|
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.
|
|
1292
|
+
- 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
1293
|
- Skills: generate exactly 3 skills per target platform. Only go above 3 for large multi-framework projects.
|
|
1283
1294
|
- Each skill content: max 150 lines. Focus on patterns and examples, not exhaustive docs.
|
|
1284
1295
|
- Cursor rules: max 5 .mdc files.
|
|
1285
1296
|
- 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
|
|
1297
|
+
var REFINE_SYSTEM_PROMPT = `You are an expert at modifying coding agent configurations (Claude Code, Cursor, and Codex).
|
|
1287
1298
|
|
|
1288
1299
|
You will receive the current AgentSetup JSON and a user request describing what to change.
|
|
1289
1300
|
|
|
@@ -1291,7 +1302,7 @@ Apply the requested changes to the setup and return the complete updated AgentSe
|
|
|
1291
1302
|
|
|
1292
1303
|
AgentSetup schema:
|
|
1293
1304
|
{
|
|
1294
|
-
"targetAgent": "claude" | "cursor" | "both",
|
|
1305
|
+
"targetAgent": "claude" | "cursor" | "codex" | "both",
|
|
1295
1306
|
"fileDescriptions": {
|
|
1296
1307
|
"<file-path>": "reason for this change (max 80 chars)"
|
|
1297
1308
|
},
|
|
@@ -1302,6 +1313,10 @@ AgentSetup schema:
|
|
|
1302
1313
|
"claudeMd": "string (markdown content for CLAUDE.md)",
|
|
1303
1314
|
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
|
|
1304
1315
|
},
|
|
1316
|
+
"codex": {
|
|
1317
|
+
"agentsMd": "string (markdown content for AGENTS.md)",
|
|
1318
|
+
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }]
|
|
1319
|
+
},
|
|
1305
1320
|
"cursor": {
|
|
1306
1321
|
"skills": [{ "name": "string (kebab-case)", "description": "string", "content": "string (markdown body, no frontmatter)" }],
|
|
1307
1322
|
"rules": [{ "filename": "string.mdc", "content": "string (with frontmatter)" }]
|
|
@@ -1318,7 +1333,7 @@ var REFRESH_SYSTEM_PROMPT = `You are an expert at maintaining coding project doc
|
|
|
1318
1333
|
|
|
1319
1334
|
You will receive:
|
|
1320
1335
|
1. Git diffs showing what code changed
|
|
1321
|
-
2. Current contents of documentation files (CLAUDE.md, README.md, skills, cursor skills, cursor rules)
|
|
1336
|
+
2. Current contents of documentation files (CLAUDE.md, AGENTS.md, README.md, skills, cursor skills, cursor rules)
|
|
1322
1337
|
3. Project context (languages, frameworks)
|
|
1323
1338
|
|
|
1324
1339
|
Rules:
|
|
@@ -1329,6 +1344,7 @@ Rules:
|
|
|
1329
1344
|
- Keep managed blocks (<!-- caliber:managed --> ... <!-- /caliber:managed -->) intact
|
|
1330
1345
|
- If a doc doesn't need updating, return null for it
|
|
1331
1346
|
- For CLAUDE.md: update commands, architecture notes, conventions, key files if the diffs affect them
|
|
1347
|
+
- For AGENTS.md: same as CLAUDE.md \u2014 this is the primary instructions file for Codex users
|
|
1332
1348
|
- For README.md: update setup instructions, API docs, or feature descriptions if affected
|
|
1333
1349
|
- For cursor skills: update skill content if the diffs affect their domains
|
|
1334
1350
|
|
|
@@ -1336,13 +1352,14 @@ Return a JSON object with this exact shape:
|
|
|
1336
1352
|
{
|
|
1337
1353
|
"updatedDocs": {
|
|
1338
1354
|
"claudeMd": "<updated content or null>",
|
|
1355
|
+
"agentsMd": "<updated content or null>",
|
|
1339
1356
|
"readmeMd": "<updated content or null>",
|
|
1340
1357
|
"cursorRules": [{"filename": "name.mdc", "content": "..."}] or null,
|
|
1341
1358
|
"cursorSkills": [{"slug": "string", "name": "string", "content": "..."}] or null,
|
|
1342
1359
|
"claudeSkills": [{"filename": "name.md", "content": "..."}] or null
|
|
1343
1360
|
},
|
|
1344
1361
|
"changesSummary": "<1-2 sentence summary of what was updated and why>",
|
|
1345
|
-
"docsUpdated": ["CLAUDE.md", "README.md"]
|
|
1362
|
+
"docsUpdated": ["CLAUDE.md", "AGENTS.md", "README.md"]
|
|
1346
1363
|
}
|
|
1347
1364
|
|
|
1348
1365
|
Respond with ONLY the JSON object, no markdown fences or extra text.`;
|
|
@@ -1361,7 +1378,7 @@ Your job is to reason deeply about these events and identify:
|
|
|
1361
1378
|
From these observations, produce:
|
|
1362
1379
|
|
|
1363
1380
|
### claudeMdLearnedSection
|
|
1364
|
-
A markdown section with concise, actionable bullet points that should be added to the project's CLAUDE.md
|
|
1381
|
+
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
1382
|
- "Always run \`npm install\` before \`npm run build\` in this project"
|
|
1366
1383
|
- "The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
|
|
1367
1384
|
- "TypeScript strict mode is enabled \u2014 never use \`any\`, use \`unknown\` with type guards"
|
|
@@ -1644,7 +1661,7 @@ function truncate(text, maxChars) {
|
|
|
1644
1661
|
function buildGeneratePrompt(fingerprint, targetAgent, prompt, failingChecks, currentScore, passingChecks) {
|
|
1645
1662
|
const parts = [];
|
|
1646
1663
|
const existing = fingerprint.existingConfigs;
|
|
1647
|
-
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.cursorrules || existing.cursorRules?.length);
|
|
1664
|
+
const hasExistingConfigs = !!(existing.claudeMd || existing.claudeSettings || existing.claudeSkills?.length || existing.readmeMd || existing.agentsMd || existing.cursorrules || existing.cursorRules?.length);
|
|
1648
1665
|
const isTargetedFix = failingChecks && failingChecks.length > 0 && currentScore !== void 0 && currentScore >= 95;
|
|
1649
1666
|
if (isTargetedFix) {
|
|
1650
1667
|
parts.push(`TARGETED FIX MODE \u2014 current score: ${currentScore}/100, target: ${targetAgent}`);
|
|
@@ -1682,6 +1699,9 @@ ${tree.join("\n")}`);
|
|
|
1682
1699
|
if (existing.claudeMd) parts.push(`
|
|
1683
1700
|
Existing CLAUDE.md:
|
|
1684
1701
|
${truncate(existing.claudeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
1702
|
+
if (existing.agentsMd) parts.push(`
|
|
1703
|
+
Existing AGENTS.md:
|
|
1704
|
+
${truncate(existing.agentsMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
1685
1705
|
if (existing.readmeMd) parts.push(`
|
|
1686
1706
|
Existing README.md:
|
|
1687
1707
|
${truncate(existing.readmeMd, LIMITS.EXISTING_CONFIG_CHARS)}`);
|
|
@@ -1817,7 +1837,7 @@ Return the complete updated AgentSetup JSON incorporating the user's changes. Re
|
|
|
1817
1837
|
}
|
|
1818
1838
|
|
|
1819
1839
|
// src/writers/index.ts
|
|
1820
|
-
import
|
|
1840
|
+
import fs12 from "fs";
|
|
1821
1841
|
|
|
1822
1842
|
// src/writers/claude/index.ts
|
|
1823
1843
|
import fs7 from "fs";
|
|
@@ -1911,64 +1931,90 @@ function writeCursorConfig(config) {
|
|
|
1911
1931
|
return written;
|
|
1912
1932
|
}
|
|
1913
1933
|
|
|
1934
|
+
// src/writers/codex/index.ts
|
|
1935
|
+
import fs9 from "fs";
|
|
1936
|
+
import path8 from "path";
|
|
1937
|
+
function writeCodexConfig(config) {
|
|
1938
|
+
const written = [];
|
|
1939
|
+
fs9.writeFileSync("AGENTS.md", config.agentsMd);
|
|
1940
|
+
written.push("AGENTS.md");
|
|
1941
|
+
if (config.skills?.length) {
|
|
1942
|
+
for (const skill of config.skills) {
|
|
1943
|
+
const skillDir = path8.join(".agents", "skills", skill.name);
|
|
1944
|
+
if (!fs9.existsSync(skillDir)) fs9.mkdirSync(skillDir, { recursive: true });
|
|
1945
|
+
const skillPath = path8.join(skillDir, "SKILL.md");
|
|
1946
|
+
const frontmatter = [
|
|
1947
|
+
"---",
|
|
1948
|
+
`name: ${skill.name}`,
|
|
1949
|
+
`description: ${skill.description}`,
|
|
1950
|
+
"---",
|
|
1951
|
+
""
|
|
1952
|
+
].join("\n");
|
|
1953
|
+
fs9.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1954
|
+
written.push(skillPath);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return written;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1914
1960
|
// src/writers/backup.ts
|
|
1915
1961
|
init_constants();
|
|
1916
|
-
import
|
|
1917
|
-
import
|
|
1962
|
+
import fs10 from "fs";
|
|
1963
|
+
import path10 from "path";
|
|
1918
1964
|
function createBackup(files) {
|
|
1919
1965
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1920
|
-
const backupDir =
|
|
1966
|
+
const backupDir = path10.join(BACKUPS_DIR, timestamp);
|
|
1921
1967
|
for (const file of files) {
|
|
1922
|
-
if (!
|
|
1923
|
-
const dest =
|
|
1924
|
-
const destDir =
|
|
1925
|
-
if (!
|
|
1926
|
-
|
|
1968
|
+
if (!fs10.existsSync(file)) continue;
|
|
1969
|
+
const dest = path10.join(backupDir, file);
|
|
1970
|
+
const destDir = path10.dirname(dest);
|
|
1971
|
+
if (!fs10.existsSync(destDir)) {
|
|
1972
|
+
fs10.mkdirSync(destDir, { recursive: true });
|
|
1927
1973
|
}
|
|
1928
|
-
|
|
1974
|
+
fs10.copyFileSync(file, dest);
|
|
1929
1975
|
}
|
|
1930
1976
|
return backupDir;
|
|
1931
1977
|
}
|
|
1932
1978
|
function restoreBackup(backupDir, file) {
|
|
1933
|
-
const backupFile =
|
|
1934
|
-
if (!
|
|
1935
|
-
const destDir =
|
|
1936
|
-
if (!
|
|
1937
|
-
|
|
1979
|
+
const backupFile = path10.join(backupDir, file);
|
|
1980
|
+
if (!fs10.existsSync(backupFile)) return false;
|
|
1981
|
+
const destDir = path10.dirname(file);
|
|
1982
|
+
if (!fs10.existsSync(destDir)) {
|
|
1983
|
+
fs10.mkdirSync(destDir, { recursive: true });
|
|
1938
1984
|
}
|
|
1939
|
-
|
|
1985
|
+
fs10.copyFileSync(backupFile, file);
|
|
1940
1986
|
return true;
|
|
1941
1987
|
}
|
|
1942
1988
|
|
|
1943
1989
|
// src/writers/manifest.ts
|
|
1944
1990
|
init_constants();
|
|
1945
|
-
import
|
|
1991
|
+
import fs11 from "fs";
|
|
1946
1992
|
import crypto from "crypto";
|
|
1947
1993
|
function readManifest() {
|
|
1948
1994
|
try {
|
|
1949
|
-
if (!
|
|
1950
|
-
return JSON.parse(
|
|
1995
|
+
if (!fs11.existsSync(MANIFEST_FILE)) return null;
|
|
1996
|
+
return JSON.parse(fs11.readFileSync(MANIFEST_FILE, "utf-8"));
|
|
1951
1997
|
} catch {
|
|
1952
1998
|
return null;
|
|
1953
1999
|
}
|
|
1954
2000
|
}
|
|
1955
2001
|
function writeManifest(manifest) {
|
|
1956
|
-
if (!
|
|
1957
|
-
|
|
2002
|
+
if (!fs11.existsSync(CALIBER_DIR)) {
|
|
2003
|
+
fs11.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1958
2004
|
}
|
|
1959
|
-
|
|
2005
|
+
fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
1960
2006
|
}
|
|
1961
2007
|
function fileChecksum(filePath) {
|
|
1962
|
-
const content =
|
|
2008
|
+
const content = fs11.readFileSync(filePath);
|
|
1963
2009
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
1964
2010
|
}
|
|
1965
2011
|
|
|
1966
2012
|
// src/writers/index.ts
|
|
1967
2013
|
function writeSetup(setup) {
|
|
1968
2014
|
const filesToWrite = getFilesToWrite(setup);
|
|
1969
|
-
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) =>
|
|
2015
|
+
const filesToDelete = (setup.deletions || []).map((d) => d.filePath).filter((f) => fs12.existsSync(f));
|
|
1970
2016
|
const existingFiles = [
|
|
1971
|
-
...filesToWrite.filter((f) =>
|
|
2017
|
+
...filesToWrite.filter((f) => fs12.existsSync(f)),
|
|
1972
2018
|
...filesToDelete
|
|
1973
2019
|
];
|
|
1974
2020
|
const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
|
|
@@ -1979,9 +2025,12 @@ function writeSetup(setup) {
|
|
|
1979
2025
|
if ((setup.targetAgent === "cursor" || setup.targetAgent === "both") && setup.cursor) {
|
|
1980
2026
|
written.push(...writeCursorConfig(setup.cursor));
|
|
1981
2027
|
}
|
|
2028
|
+
if (setup.targetAgent === "codex" && setup.codex) {
|
|
2029
|
+
written.push(...writeCodexConfig(setup.codex));
|
|
2030
|
+
}
|
|
1982
2031
|
const deleted = [];
|
|
1983
2032
|
for (const filePath of filesToDelete) {
|
|
1984
|
-
|
|
2033
|
+
fs12.unlinkSync(filePath);
|
|
1985
2034
|
deleted.push(filePath);
|
|
1986
2035
|
}
|
|
1987
2036
|
ensureGitignore();
|
|
@@ -2011,8 +2060,8 @@ function undoSetup() {
|
|
|
2011
2060
|
const removed = [];
|
|
2012
2061
|
for (const entry of manifest.entries) {
|
|
2013
2062
|
if (entry.action === "created") {
|
|
2014
|
-
if (
|
|
2015
|
-
|
|
2063
|
+
if (fs12.existsSync(entry.path)) {
|
|
2064
|
+
fs12.unlinkSync(entry.path);
|
|
2016
2065
|
removed.push(entry.path);
|
|
2017
2066
|
}
|
|
2018
2067
|
} else if ((entry.action === "modified" || entry.action === "deleted") && manifest.backupDir) {
|
|
@@ -2022,8 +2071,8 @@ function undoSetup() {
|
|
|
2022
2071
|
}
|
|
2023
2072
|
}
|
|
2024
2073
|
const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
|
|
2025
|
-
if (
|
|
2026
|
-
|
|
2074
|
+
if (fs12.existsSync(MANIFEST_FILE2)) {
|
|
2075
|
+
fs12.unlinkSync(MANIFEST_FILE2);
|
|
2027
2076
|
}
|
|
2028
2077
|
return { restored, removed };
|
|
2029
2078
|
}
|
|
@@ -2048,27 +2097,33 @@ function getFilesToWrite(setup) {
|
|
|
2048
2097
|
}
|
|
2049
2098
|
if (setup.cursor.mcpServers) files.push(".cursor/mcp.json");
|
|
2050
2099
|
}
|
|
2100
|
+
if (setup.targetAgent === "codex" && setup.codex) {
|
|
2101
|
+
files.push("AGENTS.md");
|
|
2102
|
+
if (setup.codex.skills) {
|
|
2103
|
+
for (const s of setup.codex.skills) files.push(`.agents/skills/${s.name}/SKILL.md`);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2051
2106
|
return files;
|
|
2052
2107
|
}
|
|
2053
2108
|
function ensureGitignore() {
|
|
2054
2109
|
const gitignorePath = ".gitignore";
|
|
2055
|
-
if (
|
|
2056
|
-
const content =
|
|
2110
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
2111
|
+
const content = fs12.readFileSync(gitignorePath, "utf-8");
|
|
2057
2112
|
if (!content.includes(".caliber/")) {
|
|
2058
|
-
|
|
2113
|
+
fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
2059
2114
|
}
|
|
2060
2115
|
} else {
|
|
2061
|
-
|
|
2116
|
+
fs12.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
|
|
2062
2117
|
}
|
|
2063
2118
|
}
|
|
2064
2119
|
|
|
2065
2120
|
// src/writers/staging.ts
|
|
2066
2121
|
init_constants();
|
|
2067
|
-
import
|
|
2068
|
-
import
|
|
2069
|
-
var STAGED_DIR =
|
|
2070
|
-
var PROPOSED_DIR =
|
|
2071
|
-
var CURRENT_DIR =
|
|
2122
|
+
import fs13 from "fs";
|
|
2123
|
+
import path11 from "path";
|
|
2124
|
+
var STAGED_DIR = path11.join(CALIBER_DIR, "staged");
|
|
2125
|
+
var PROPOSED_DIR = path11.join(STAGED_DIR, "proposed");
|
|
2126
|
+
var CURRENT_DIR = path11.join(STAGED_DIR, "current");
|
|
2072
2127
|
function normalizeContent(content) {
|
|
2073
2128
|
return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2074
2129
|
}
|
|
@@ -2078,20 +2133,20 @@ function stageFiles(files, projectDir) {
|
|
|
2078
2133
|
let modifiedFiles = 0;
|
|
2079
2134
|
const stagedFiles = [];
|
|
2080
2135
|
for (const file of files) {
|
|
2081
|
-
const originalPath =
|
|
2082
|
-
if (
|
|
2083
|
-
const existing =
|
|
2136
|
+
const originalPath = path11.join(projectDir, file.path);
|
|
2137
|
+
if (fs13.existsSync(originalPath)) {
|
|
2138
|
+
const existing = fs13.readFileSync(originalPath, "utf-8");
|
|
2084
2139
|
if (normalizeContent(existing) === normalizeContent(file.content)) {
|
|
2085
2140
|
continue;
|
|
2086
2141
|
}
|
|
2087
2142
|
}
|
|
2088
|
-
const proposedPath =
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
if (
|
|
2092
|
-
const currentPath =
|
|
2093
|
-
|
|
2094
|
-
|
|
2143
|
+
const proposedPath = path11.join(PROPOSED_DIR, file.path);
|
|
2144
|
+
fs13.mkdirSync(path11.dirname(proposedPath), { recursive: true });
|
|
2145
|
+
fs13.writeFileSync(proposedPath, file.content);
|
|
2146
|
+
if (fs13.existsSync(originalPath)) {
|
|
2147
|
+
const currentPath = path11.join(CURRENT_DIR, file.path);
|
|
2148
|
+
fs13.mkdirSync(path11.dirname(currentPath), { recursive: true });
|
|
2149
|
+
fs13.copyFileSync(originalPath, currentPath);
|
|
2095
2150
|
modifiedFiles++;
|
|
2096
2151
|
stagedFiles.push({ relativePath: file.path, proposedPath, currentPath, originalPath, isNew: false });
|
|
2097
2152
|
} else {
|
|
@@ -2102,8 +2157,8 @@ function stageFiles(files, projectDir) {
|
|
|
2102
2157
|
return { newFiles, modifiedFiles, stagedFiles };
|
|
2103
2158
|
}
|
|
2104
2159
|
function cleanupStaging() {
|
|
2105
|
-
if (
|
|
2106
|
-
|
|
2160
|
+
if (fs13.existsSync(STAGED_DIR)) {
|
|
2161
|
+
fs13.rmSync(STAGED_DIR, { recursive: true, force: true });
|
|
2107
2162
|
}
|
|
2108
2163
|
}
|
|
2109
2164
|
|
|
@@ -2149,24 +2204,24 @@ function openDiffsInEditor(editor, files) {
|
|
|
2149
2204
|
import { createTwoFilesPatch } from "diff";
|
|
2150
2205
|
|
|
2151
2206
|
// src/lib/hooks.ts
|
|
2152
|
-
import
|
|
2153
|
-
import
|
|
2207
|
+
import fs14 from "fs";
|
|
2208
|
+
import path12 from "path";
|
|
2154
2209
|
import { execSync as execSync5 } from "child_process";
|
|
2155
|
-
var SETTINGS_PATH =
|
|
2210
|
+
var SETTINGS_PATH = path12.join(".claude", "settings.json");
|
|
2156
2211
|
var HOOK_COMMAND = "caliber refresh --quiet";
|
|
2157
2212
|
var HOOK_DESCRIPTION = "Caliber: auto-refreshing docs based on code changes";
|
|
2158
2213
|
function readSettings() {
|
|
2159
|
-
if (!
|
|
2214
|
+
if (!fs14.existsSync(SETTINGS_PATH)) return {};
|
|
2160
2215
|
try {
|
|
2161
|
-
return JSON.parse(
|
|
2216
|
+
return JSON.parse(fs14.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
2162
2217
|
} catch {
|
|
2163
2218
|
return {};
|
|
2164
2219
|
}
|
|
2165
2220
|
}
|
|
2166
2221
|
function writeSettings(settings) {
|
|
2167
|
-
const dir =
|
|
2168
|
-
if (!
|
|
2169
|
-
|
|
2222
|
+
const dir = path12.dirname(SETTINGS_PATH);
|
|
2223
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
2224
|
+
fs14.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
2170
2225
|
}
|
|
2171
2226
|
function findHookIndex(sessionEnd) {
|
|
2172
2227
|
return sessionEnd.findIndex(
|
|
@@ -2224,19 +2279,19 @@ ${PRECOMMIT_END}`;
|
|
|
2224
2279
|
function getGitHooksDir() {
|
|
2225
2280
|
try {
|
|
2226
2281
|
const gitDir = execSync5("git rev-parse --git-dir", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2227
|
-
return
|
|
2282
|
+
return path12.join(gitDir, "hooks");
|
|
2228
2283
|
} catch {
|
|
2229
2284
|
return null;
|
|
2230
2285
|
}
|
|
2231
2286
|
}
|
|
2232
2287
|
function getPreCommitPath() {
|
|
2233
2288
|
const hooksDir = getGitHooksDir();
|
|
2234
|
-
return hooksDir ?
|
|
2289
|
+
return hooksDir ? path12.join(hooksDir, "pre-commit") : null;
|
|
2235
2290
|
}
|
|
2236
2291
|
function isPreCommitHookInstalled() {
|
|
2237
2292
|
const hookPath = getPreCommitPath();
|
|
2238
|
-
if (!hookPath || !
|
|
2239
|
-
const content =
|
|
2293
|
+
if (!hookPath || !fs14.existsSync(hookPath)) return false;
|
|
2294
|
+
const content = fs14.readFileSync(hookPath, "utf-8");
|
|
2240
2295
|
return content.includes(PRECOMMIT_START);
|
|
2241
2296
|
}
|
|
2242
2297
|
function installPreCommitHook() {
|
|
@@ -2245,43 +2300,43 @@ function installPreCommitHook() {
|
|
|
2245
2300
|
}
|
|
2246
2301
|
const hookPath = getPreCommitPath();
|
|
2247
2302
|
if (!hookPath) return { installed: false, alreadyInstalled: false };
|
|
2248
|
-
const hooksDir =
|
|
2249
|
-
if (!
|
|
2303
|
+
const hooksDir = path12.dirname(hookPath);
|
|
2304
|
+
if (!fs14.existsSync(hooksDir)) fs14.mkdirSync(hooksDir, { recursive: true });
|
|
2250
2305
|
let content = "";
|
|
2251
|
-
if (
|
|
2252
|
-
content =
|
|
2306
|
+
if (fs14.existsSync(hookPath)) {
|
|
2307
|
+
content = fs14.readFileSync(hookPath, "utf-8");
|
|
2253
2308
|
if (!content.endsWith("\n")) content += "\n";
|
|
2254
2309
|
content += "\n" + PRECOMMIT_BLOCK + "\n";
|
|
2255
2310
|
} else {
|
|
2256
2311
|
content = "#!/bin/sh\n\n" + PRECOMMIT_BLOCK + "\n";
|
|
2257
2312
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2313
|
+
fs14.writeFileSync(hookPath, content);
|
|
2314
|
+
fs14.chmodSync(hookPath, 493);
|
|
2260
2315
|
return { installed: true, alreadyInstalled: false };
|
|
2261
2316
|
}
|
|
2262
2317
|
function removePreCommitHook() {
|
|
2263
2318
|
const hookPath = getPreCommitPath();
|
|
2264
|
-
if (!hookPath || !
|
|
2319
|
+
if (!hookPath || !fs14.existsSync(hookPath)) {
|
|
2265
2320
|
return { removed: false, notFound: true };
|
|
2266
2321
|
}
|
|
2267
|
-
let content =
|
|
2322
|
+
let content = fs14.readFileSync(hookPath, "utf-8");
|
|
2268
2323
|
if (!content.includes(PRECOMMIT_START)) {
|
|
2269
2324
|
return { removed: false, notFound: true };
|
|
2270
2325
|
}
|
|
2271
2326
|
const regex = new RegExp(`\\n?${PRECOMMIT_START}[\\s\\S]*?${PRECOMMIT_END}\\n?`);
|
|
2272
2327
|
content = content.replace(regex, "\n");
|
|
2273
2328
|
if (content.trim() === "#!/bin/sh" || content.trim() === "") {
|
|
2274
|
-
|
|
2329
|
+
fs14.unlinkSync(hookPath);
|
|
2275
2330
|
} else {
|
|
2276
|
-
|
|
2331
|
+
fs14.writeFileSync(hookPath, content);
|
|
2277
2332
|
}
|
|
2278
2333
|
return { removed: true, notFound: false };
|
|
2279
2334
|
}
|
|
2280
2335
|
|
|
2281
2336
|
// src/lib/learning-hooks.ts
|
|
2282
|
-
import
|
|
2283
|
-
import
|
|
2284
|
-
var SETTINGS_PATH2 =
|
|
2337
|
+
import fs15 from "fs";
|
|
2338
|
+
import path13 from "path";
|
|
2339
|
+
var SETTINGS_PATH2 = path13.join(".claude", "settings.json");
|
|
2285
2340
|
var HOOK_CONFIGS = [
|
|
2286
2341
|
{
|
|
2287
2342
|
event: "PostToolUse",
|
|
@@ -2300,17 +2355,17 @@ var HOOK_CONFIGS = [
|
|
|
2300
2355
|
}
|
|
2301
2356
|
];
|
|
2302
2357
|
function readSettings2() {
|
|
2303
|
-
if (!
|
|
2358
|
+
if (!fs15.existsSync(SETTINGS_PATH2)) return {};
|
|
2304
2359
|
try {
|
|
2305
|
-
return JSON.parse(
|
|
2360
|
+
return JSON.parse(fs15.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
2306
2361
|
} catch {
|
|
2307
2362
|
return {};
|
|
2308
2363
|
}
|
|
2309
2364
|
}
|
|
2310
2365
|
function writeSettings2(settings) {
|
|
2311
|
-
const dir =
|
|
2312
|
-
if (!
|
|
2313
|
-
|
|
2366
|
+
const dir = path13.dirname(SETTINGS_PATH2);
|
|
2367
|
+
if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
|
|
2368
|
+
fs15.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
2314
2369
|
}
|
|
2315
2370
|
function hasLearningHook(matchers, command) {
|
|
2316
2371
|
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
@@ -2367,23 +2422,23 @@ function removeLearningHooks() {
|
|
|
2367
2422
|
|
|
2368
2423
|
// src/lib/state.ts
|
|
2369
2424
|
init_constants();
|
|
2370
|
-
import
|
|
2371
|
-
import
|
|
2425
|
+
import fs16 from "fs";
|
|
2426
|
+
import path14 from "path";
|
|
2372
2427
|
import { execSync as execSync6 } from "child_process";
|
|
2373
|
-
var STATE_FILE =
|
|
2428
|
+
var STATE_FILE = path14.join(CALIBER_DIR, ".caliber-state.json");
|
|
2374
2429
|
function readState() {
|
|
2375
2430
|
try {
|
|
2376
|
-
if (!
|
|
2377
|
-
return JSON.parse(
|
|
2431
|
+
if (!fs16.existsSync(STATE_FILE)) return null;
|
|
2432
|
+
return JSON.parse(fs16.readFileSync(STATE_FILE, "utf-8"));
|
|
2378
2433
|
} catch {
|
|
2379
2434
|
return null;
|
|
2380
2435
|
}
|
|
2381
2436
|
}
|
|
2382
2437
|
function writeState(state) {
|
|
2383
|
-
if (!
|
|
2384
|
-
|
|
2438
|
+
if (!fs16.existsSync(CALIBER_DIR)) {
|
|
2439
|
+
fs16.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
2385
2440
|
}
|
|
2386
|
-
|
|
2441
|
+
fs16.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
2387
2442
|
}
|
|
2388
2443
|
function getCurrentHeadSha() {
|
|
2389
2444
|
try {
|
|
@@ -2649,6 +2704,9 @@ var BOTH_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
|
2649
2704
|
"cross_platform_parity",
|
|
2650
2705
|
"no_duplicate_content"
|
|
2651
2706
|
]);
|
|
2707
|
+
var CODEX_ONLY_CHECKS = /* @__PURE__ */ new Set([
|
|
2708
|
+
"codex_agents_md_exists"
|
|
2709
|
+
]);
|
|
2652
2710
|
var GRADE_THRESHOLDS = [
|
|
2653
2711
|
{ minScore: 85, grade: "A" },
|
|
2654
2712
|
{ minScore: 70, grade: "B" },
|
|
@@ -2666,15 +2724,15 @@ function computeGrade(score) {
|
|
|
2666
2724
|
// src/scoring/checks/coverage.ts
|
|
2667
2725
|
import { readFileSync, readdirSync } from "fs";
|
|
2668
2726
|
import { join } from "path";
|
|
2669
|
-
function readFileOrNull(
|
|
2727
|
+
function readFileOrNull(path24) {
|
|
2670
2728
|
try {
|
|
2671
|
-
return readFileSync(
|
|
2729
|
+
return readFileSync(path24, "utf-8");
|
|
2672
2730
|
} catch {
|
|
2673
2731
|
return null;
|
|
2674
2732
|
}
|
|
2675
2733
|
}
|
|
2676
|
-
function readJsonOrNull(
|
|
2677
|
-
const content = readFileOrNull(
|
|
2734
|
+
function readJsonOrNull(path24) {
|
|
2735
|
+
const content = readFileOrNull(path24);
|
|
2678
2736
|
if (!content) return null;
|
|
2679
2737
|
try {
|
|
2680
2738
|
return JSON.parse(content);
|
|
@@ -2970,8 +3028,20 @@ function checkExistence(dir) {
|
|
|
2970
3028
|
detail: hasCursorrules ? ".cursorrules found" : cursorRulesDir ? ".cursor/rules/ found" : "No Cursor rules",
|
|
2971
3029
|
suggestion: cursorRulesExist ? void 0 : "Add .cursor/rules/ for Cursor users on your team"
|
|
2972
3030
|
});
|
|
3031
|
+
const agentsMdExists = existsSync3(join2(dir, "AGENTS.md"));
|
|
3032
|
+
checks.push({
|
|
3033
|
+
id: "codex_agents_md_exists",
|
|
3034
|
+
name: "AGENTS.md exists",
|
|
3035
|
+
category: "existence",
|
|
3036
|
+
maxPoints: POINTS_CLAUDE_MD_EXISTS,
|
|
3037
|
+
earnedPoints: agentsMdExists ? POINTS_CLAUDE_MD_EXISTS : 0,
|
|
3038
|
+
passed: agentsMdExists,
|
|
3039
|
+
detail: agentsMdExists ? "Found at project root" : "Not found",
|
|
3040
|
+
suggestion: agentsMdExists ? void 0 : "Create AGENTS.md with project context for Codex"
|
|
3041
|
+
});
|
|
2973
3042
|
const claudeSkills = countFiles(join2(dir, ".claude", "skills"), /\.(md|SKILL\.md)$/);
|
|
2974
|
-
const
|
|
3043
|
+
const codexSkills = countFiles(join2(dir, ".agents", "skills"), /SKILL\.md$/);
|
|
3044
|
+
const skillCount = claudeSkills.length + codexSkills.length;
|
|
2975
3045
|
const skillBase = skillCount >= 1 ? POINTS_SKILLS_EXIST : 0;
|
|
2976
3046
|
const skillBonus = Math.min((skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA, POINTS_SKILLS_BONUS_CAP);
|
|
2977
3047
|
const skillPoints = skillCount >= 1 ? skillBase + Math.max(0, skillBonus) : 0;
|
|
@@ -3030,9 +3100,9 @@ function checkExistence(dir) {
|
|
|
3030
3100
|
// src/scoring/checks/quality.ts
|
|
3031
3101
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3032
3102
|
import { join as join3 } from "path";
|
|
3033
|
-
function readFileOrNull2(
|
|
3103
|
+
function readFileOrNull2(path24) {
|
|
3034
3104
|
try {
|
|
3035
|
-
return readFileSync3(
|
|
3105
|
+
return readFileSync3(path24, "utf-8");
|
|
3036
3106
|
} catch {
|
|
3037
3107
|
return null;
|
|
3038
3108
|
}
|
|
@@ -3044,11 +3114,13 @@ function checkQuality(dir) {
|
|
|
3044
3114
|
const checks = [];
|
|
3045
3115
|
const claudeMd = readFileOrNull2(join3(dir, "CLAUDE.md"));
|
|
3046
3116
|
const cursorrules = readFileOrNull2(join3(dir, ".cursorrules"));
|
|
3047
|
-
const
|
|
3117
|
+
const agentsMd = readFileOrNull2(join3(dir, "AGENTS.md"));
|
|
3118
|
+
const allContent = [claudeMd, cursorrules, agentsMd].filter(Boolean);
|
|
3048
3119
|
const combinedContent = allContent.join("\n");
|
|
3049
|
-
const
|
|
3050
|
-
const
|
|
3051
|
-
|
|
3120
|
+
const primaryInstructions = claudeMd ?? agentsMd;
|
|
3121
|
+
const hasCommands = primaryInstructions ? COMMAND_PATTERNS.some((p) => p.test(primaryInstructions)) : false;
|
|
3122
|
+
const matchedCommands = primaryInstructions ? COMMAND_PATTERNS.filter((p) => p.test(primaryInstructions)).map((p) => {
|
|
3123
|
+
const m = primaryInstructions.match(p);
|
|
3052
3124
|
return m ? m[0] : "";
|
|
3053
3125
|
}).filter(Boolean) : [];
|
|
3054
3126
|
checks.push({
|
|
@@ -3058,11 +3130,11 @@ function checkQuality(dir) {
|
|
|
3058
3130
|
maxPoints: POINTS_HAS_COMMANDS,
|
|
3059
3131
|
earnedPoints: hasCommands ? POINTS_HAS_COMMANDS : 0,
|
|
3060
3132
|
passed: hasCommands,
|
|
3061
|
-
detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` :
|
|
3062
|
-
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to
|
|
3133
|
+
detail: hasCommands ? `Found: ${matchedCommands.slice(0, 3).join(", ")}` : primaryInstructions ? "No build/test/lint commands detected" : "No instructions file to check",
|
|
3134
|
+
suggestion: hasCommands ? void 0 : "Add build, test, and lint commands to your instructions file"
|
|
3063
3135
|
});
|
|
3064
|
-
const primaryFile = claudeMd ?? cursorrules;
|
|
3065
|
-
const primaryName = claudeMd ? "CLAUDE.md" : cursorrules ? ".cursorrules" : null;
|
|
3136
|
+
const primaryFile = claudeMd ?? agentsMd ?? cursorrules;
|
|
3137
|
+
const primaryName = claudeMd ? "CLAUDE.md" : agentsMd ? "AGENTS.md" : cursorrules ? ".cursorrules" : null;
|
|
3066
3138
|
let bloatPoints = 0;
|
|
3067
3139
|
let lineCount = 0;
|
|
3068
3140
|
if (primaryFile) {
|
|
@@ -3183,15 +3255,15 @@ function checkQuality(dir) {
|
|
|
3183
3255
|
// src/scoring/checks/accuracy.ts
|
|
3184
3256
|
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
|
|
3185
3257
|
import { join as join4 } from "path";
|
|
3186
|
-
function readFileOrNull3(
|
|
3258
|
+
function readFileOrNull3(path24) {
|
|
3187
3259
|
try {
|
|
3188
|
-
return readFileSync4(
|
|
3260
|
+
return readFileSync4(path24, "utf-8");
|
|
3189
3261
|
} catch {
|
|
3190
3262
|
return null;
|
|
3191
3263
|
}
|
|
3192
3264
|
}
|
|
3193
|
-
function readJsonOrNull2(
|
|
3194
|
-
const content = readFileOrNull3(
|
|
3265
|
+
function readJsonOrNull2(path24) {
|
|
3266
|
+
const content = readFileOrNull3(path24);
|
|
3195
3267
|
if (!content) return null;
|
|
3196
3268
|
try {
|
|
3197
3269
|
return JSON.parse(content);
|
|
@@ -3372,11 +3444,11 @@ function checkAccuracy(dir) {
|
|
|
3372
3444
|
}
|
|
3373
3445
|
|
|
3374
3446
|
// src/scoring/checks/freshness.ts
|
|
3375
|
-
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3447
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
3376
3448
|
import { join as join5 } from "path";
|
|
3377
|
-
function readFileOrNull4(
|
|
3449
|
+
function readFileOrNull4(path24) {
|
|
3378
3450
|
try {
|
|
3379
|
-
return readFileSync5(
|
|
3451
|
+
return readFileSync5(path24, "utf-8");
|
|
3380
3452
|
} catch {
|
|
3381
3453
|
return null;
|
|
3382
3454
|
}
|
|
@@ -3394,11 +3466,14 @@ function daysSinceModified(filePath) {
|
|
|
3394
3466
|
function checkFreshness(dir) {
|
|
3395
3467
|
const checks = [];
|
|
3396
3468
|
const claudeMdPath = join5(dir, "CLAUDE.md");
|
|
3397
|
-
const
|
|
3469
|
+
const agentsMdPath = join5(dir, "AGENTS.md");
|
|
3470
|
+
const primaryPath = existsSync6(claudeMdPath) ? claudeMdPath : agentsMdPath;
|
|
3471
|
+
const primaryName = existsSync6(claudeMdPath) ? "CLAUDE.md" : "AGENTS.md";
|
|
3472
|
+
const daysOld = daysSinceModified(primaryPath);
|
|
3398
3473
|
let freshnessPoints = 0;
|
|
3399
3474
|
let freshnessDetail = "";
|
|
3400
3475
|
if (daysOld === null) {
|
|
3401
|
-
freshnessDetail = "No
|
|
3476
|
+
freshnessDetail = "No instructions file to check";
|
|
3402
3477
|
} else {
|
|
3403
3478
|
const threshold = FRESHNESS_THRESHOLDS.find((t) => daysOld <= t.maxDaysOld);
|
|
3404
3479
|
freshnessPoints = threshold ? threshold.points : 0;
|
|
@@ -3406,16 +3481,17 @@ function checkFreshness(dir) {
|
|
|
3406
3481
|
}
|
|
3407
3482
|
checks.push({
|
|
3408
3483
|
id: "claude_md_freshness",
|
|
3409
|
-
name:
|
|
3484
|
+
name: `${primaryName} freshness`,
|
|
3410
3485
|
category: "freshness",
|
|
3411
3486
|
maxPoints: POINTS_FRESHNESS,
|
|
3412
3487
|
earnedPoints: freshnessPoints,
|
|
3413
3488
|
passed: freshnessPoints >= 4,
|
|
3414
3489
|
detail: freshnessDetail,
|
|
3415
|
-
suggestion: daysOld !== null && freshnessPoints < 4 ?
|
|
3490
|
+
suggestion: daysOld !== null && freshnessPoints < 4 ? `${primaryName} is ${daysOld} days old \u2014 run \`caliber refresh\` to update it` : void 0
|
|
3416
3491
|
});
|
|
3417
3492
|
const filesToScan = [
|
|
3418
3493
|
"CLAUDE.md",
|
|
3494
|
+
"AGENTS.md",
|
|
3419
3495
|
".cursorrules",
|
|
3420
3496
|
".claude/settings.json",
|
|
3421
3497
|
".claude/settings.local.json",
|
|
@@ -3486,9 +3562,9 @@ function checkFreshness(dir) {
|
|
|
3486
3562
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
|
|
3487
3563
|
import { execSync as execSync7 } from "child_process";
|
|
3488
3564
|
import { join as join6 } from "path";
|
|
3489
|
-
function readFileOrNull5(
|
|
3565
|
+
function readFileOrNull5(path24) {
|
|
3490
3566
|
try {
|
|
3491
|
-
return readFileSync6(
|
|
3567
|
+
return readFileSync6(path24, "utf-8");
|
|
3492
3568
|
} catch {
|
|
3493
3569
|
return null;
|
|
3494
3570
|
}
|
|
@@ -3583,22 +3659,22 @@ function checkBonus(dir) {
|
|
|
3583
3659
|
|
|
3584
3660
|
// src/scoring/dismissed.ts
|
|
3585
3661
|
init_constants();
|
|
3586
|
-
import
|
|
3587
|
-
import
|
|
3588
|
-
var DISMISSED_FILE =
|
|
3662
|
+
import fs17 from "fs";
|
|
3663
|
+
import path15 from "path";
|
|
3664
|
+
var DISMISSED_FILE = path15.join(CALIBER_DIR, "dismissed-checks.json");
|
|
3589
3665
|
function readDismissedChecks() {
|
|
3590
3666
|
try {
|
|
3591
|
-
if (!
|
|
3592
|
-
return JSON.parse(
|
|
3667
|
+
if (!fs17.existsSync(DISMISSED_FILE)) return [];
|
|
3668
|
+
return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
|
|
3593
3669
|
} catch {
|
|
3594
3670
|
return [];
|
|
3595
3671
|
}
|
|
3596
3672
|
}
|
|
3597
3673
|
function writeDismissedChecks(checks) {
|
|
3598
|
-
if (!
|
|
3599
|
-
|
|
3674
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
3675
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
3600
3676
|
}
|
|
3601
|
-
|
|
3677
|
+
fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
|
|
3602
3678
|
}
|
|
3603
3679
|
function getDismissedIds() {
|
|
3604
3680
|
return new Set(readDismissedChecks().map((c) => c.id));
|
|
@@ -3615,10 +3691,13 @@ function sumCategory(checks, category) {
|
|
|
3615
3691
|
function filterChecksForTarget(checks, target) {
|
|
3616
3692
|
return checks.filter((c) => {
|
|
3617
3693
|
if (target === "claude") {
|
|
3618
|
-
return !CURSOR_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3694
|
+
return !CURSOR_ONLY_CHECKS.has(c.id) && !CODEX_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3619
3695
|
}
|
|
3620
3696
|
if (target === "cursor") {
|
|
3621
|
-
return !CLAUDE_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3697
|
+
return !CLAUDE_ONLY_CHECKS.has(c.id) && !CODEX_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3698
|
+
}
|
|
3699
|
+
if (target === "codex") {
|
|
3700
|
+
return !CLAUDE_ONLY_CHECKS.has(c.id) && !CURSOR_ONLY_CHECKS.has(c.id) && !BOTH_ONLY_CHECKS.has(c.id);
|
|
3622
3701
|
}
|
|
3623
3702
|
return true;
|
|
3624
3703
|
});
|
|
@@ -3626,7 +3705,9 @@ function filterChecksForTarget(checks, target) {
|
|
|
3626
3705
|
function detectTargetAgent(dir) {
|
|
3627
3706
|
const hasClaude = existsSync8(join7(dir, "CLAUDE.md")) || existsSync8(join7(dir, ".claude", "skills"));
|
|
3628
3707
|
const hasCursor = existsSync8(join7(dir, ".cursorrules")) || existsSync8(join7(dir, ".cursor", "rules"));
|
|
3708
|
+
const hasCodex = existsSync8(join7(dir, ".codex")) || existsSync8(join7(dir, ".agents", "skills"));
|
|
3629
3709
|
if (hasClaude && hasCursor) return "both";
|
|
3710
|
+
if (hasCodex && !hasClaude && !hasCursor) return "codex";
|
|
3630
3711
|
if (hasCursor) return "cursor";
|
|
3631
3712
|
return "claude";
|
|
3632
3713
|
}
|
|
@@ -3707,7 +3788,7 @@ function formatCheck(check) {
|
|
|
3707
3788
|
}
|
|
3708
3789
|
function displayScore(result) {
|
|
3709
3790
|
const gc = gradeColor(result.grade);
|
|
3710
|
-
const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : "Cursor";
|
|
3791
|
+
const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : result.targetAgent === "codex" ? "Codex" : "Cursor";
|
|
3711
3792
|
console.log("");
|
|
3712
3793
|
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
3794
|
console.log(chalk3.gray(" \u2502") + " " + chalk3.gray("\u2502"));
|
|
@@ -3733,7 +3814,7 @@ function displayScore(result) {
|
|
|
3733
3814
|
}
|
|
3734
3815
|
function displayScoreSummary(result) {
|
|
3735
3816
|
const gc = gradeColor(result.grade);
|
|
3736
|
-
const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : "Cursor";
|
|
3817
|
+
const agentLabel = result.targetAgent === "both" ? "Claude Code + Cursor" : result.targetAgent === "claude" ? "Claude Code" : result.targetAgent === "codex" ? "Codex" : "Cursor";
|
|
3737
3818
|
console.log("");
|
|
3738
3819
|
console.log(
|
|
3739
3820
|
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 +3880,8 @@ function displayScoreDelta(before, after) {
|
|
|
3799
3880
|
import chalk4 from "chalk";
|
|
3800
3881
|
import ora from "ora";
|
|
3801
3882
|
import readline3 from "readline";
|
|
3802
|
-
import
|
|
3803
|
-
import
|
|
3883
|
+
import fs18 from "fs";
|
|
3884
|
+
import path16 from "path";
|
|
3804
3885
|
|
|
3805
3886
|
// src/mcp/search.ts
|
|
3806
3887
|
var AWESOME_MCP_URL = "https://raw.githubusercontent.com/punkpeye/awesome-mcp-servers/main/README.md";
|
|
@@ -4148,26 +4229,29 @@ async function discoverAndInstallMcps(targetAgent, fingerprint, dir) {
|
|
|
4148
4229
|
return { installed: 0, names: [] };
|
|
4149
4230
|
}
|
|
4150
4231
|
if (targetAgent === "claude" || targetAgent === "both") {
|
|
4151
|
-
writeMcpJson(
|
|
4232
|
+
writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
|
|
4152
4233
|
}
|
|
4153
4234
|
if (targetAgent === "cursor" || targetAgent === "both") {
|
|
4154
|
-
const cursorDir =
|
|
4155
|
-
if (!
|
|
4156
|
-
writeMcpJson(
|
|
4235
|
+
const cursorDir = path16.join(dir, ".cursor");
|
|
4236
|
+
if (!fs18.existsSync(cursorDir)) fs18.mkdirSync(cursorDir, { recursive: true });
|
|
4237
|
+
writeMcpJson(path16.join(cursorDir, "mcp.json"), mcpServers);
|
|
4238
|
+
}
|
|
4239
|
+
if (targetAgent === "codex") {
|
|
4240
|
+
writeMcpJson(path16.join(dir, ".mcp.json"), mcpServers);
|
|
4157
4241
|
}
|
|
4158
4242
|
return { installed: installedNames.length, names: installedNames };
|
|
4159
4243
|
}
|
|
4160
4244
|
function writeMcpJson(filePath, mcpServers) {
|
|
4161
4245
|
let existing = {};
|
|
4162
4246
|
try {
|
|
4163
|
-
if (
|
|
4164
|
-
const parsed = JSON.parse(
|
|
4247
|
+
if (fs18.existsSync(filePath)) {
|
|
4248
|
+
const parsed = JSON.parse(fs18.readFileSync(filePath, "utf-8"));
|
|
4165
4249
|
if (parsed.mcpServers) existing = parsed.mcpServers;
|
|
4166
4250
|
}
|
|
4167
4251
|
} catch {
|
|
4168
4252
|
}
|
|
4169
4253
|
const merged = { ...existing, ...mcpServers };
|
|
4170
|
-
|
|
4254
|
+
fs18.writeFileSync(filePath, JSON.stringify({ mcpServers: merged }, null, 2) + "\n");
|
|
4171
4255
|
}
|
|
4172
4256
|
function getExistingMcpNames(fingerprint, targetAgent) {
|
|
4173
4257
|
const names = [];
|
|
@@ -4355,7 +4439,7 @@ async function initCommand(options) {
|
|
|
4355
4439
|
}
|
|
4356
4440
|
const baselineScore = computeLocalScore(process.cwd(), targetAgent);
|
|
4357
4441
|
displayScoreSummary(baselineScore);
|
|
4358
|
-
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
|
|
4442
|
+
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
4359
4443
|
const NON_LLM_CHECKS = /* @__PURE__ */ new Set(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
|
|
4360
4444
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
4361
4445
|
console.log(chalk5.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
@@ -4559,7 +4643,7 @@ async function initCommand(options) {
|
|
|
4559
4643
|
console.log("");
|
|
4560
4644
|
console.log(title.bold(" Keep your configs fresh\n"));
|
|
4561
4645
|
console.log(chalk5.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
|
|
4562
|
-
const hookChoice = await promptHookType(targetAgent);
|
|
4646
|
+
const hookChoice = targetAgent === "codex" ? await promptHookType("cursor") : await promptHookType(targetAgent);
|
|
4563
4647
|
if (hookChoice === "claude" || hookChoice === "both") {
|
|
4564
4648
|
const hookResult = installHook();
|
|
4565
4649
|
if (hookResult.installed) {
|
|
@@ -4656,7 +4740,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
|
|
|
4656
4740
|
}
|
|
4657
4741
|
function summarizeSetup(action, setup) {
|
|
4658
4742
|
const descriptions = setup.fileDescriptions;
|
|
4659
|
-
const files = descriptions ? Object.entries(descriptions).map(([
|
|
4743
|
+
const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
|
|
4660
4744
|
return `${action}. Files:
|
|
4661
4745
|
${files}`;
|
|
4662
4746
|
}
|
|
@@ -4722,7 +4806,8 @@ async function promptAgent() {
|
|
|
4722
4806
|
choices: [
|
|
4723
4807
|
{ name: "Claude Code", value: "claude" },
|
|
4724
4808
|
{ name: "Cursor", value: "cursor" },
|
|
4725
|
-
{ name: "
|
|
4809
|
+
{ name: "Codex (OpenAI)", value: "codex" },
|
|
4810
|
+
{ name: "Both (Claude + Cursor)", value: "both" }
|
|
4726
4811
|
]
|
|
4727
4812
|
});
|
|
4728
4813
|
}
|
|
@@ -4776,8 +4861,8 @@ async function openReview(method, stagedFiles) {
|
|
|
4776
4861
|
return;
|
|
4777
4862
|
}
|
|
4778
4863
|
const fileInfos = stagedFiles.map((file) => {
|
|
4779
|
-
const proposed =
|
|
4780
|
-
const current = file.currentPath ?
|
|
4864
|
+
const proposed = fs19.readFileSync(file.proposedPath, "utf-8");
|
|
4865
|
+
const current = file.currentPath ? fs19.readFileSync(file.currentPath, "utf-8") : "";
|
|
4781
4866
|
const patch = createTwoFilesPatch(
|
|
4782
4867
|
file.isNew ? "/dev/null" : file.relativePath,
|
|
4783
4868
|
file.relativePath,
|
|
@@ -4960,7 +5045,7 @@ function printSetupSummary(setup) {
|
|
|
4960
5045
|
};
|
|
4961
5046
|
if (claude) {
|
|
4962
5047
|
if (claude.claudeMd) {
|
|
4963
|
-
const icon =
|
|
5048
|
+
const icon = fs19.existsSync("CLAUDE.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4964
5049
|
const desc = getDescription("CLAUDE.md");
|
|
4965
5050
|
console.log(` ${icon} ${chalk5.bold("CLAUDE.md")}`);
|
|
4966
5051
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -4970,7 +5055,28 @@ function printSetupSummary(setup) {
|
|
|
4970
5055
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
4971
5056
|
for (const skill of skills) {
|
|
4972
5057
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
4973
|
-
const icon =
|
|
5058
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5059
|
+
const desc = getDescription(skillPath);
|
|
5060
|
+
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
5061
|
+
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
5062
|
+
console.log("");
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
const codex = setup.codex;
|
|
5067
|
+
if (codex) {
|
|
5068
|
+
if (codex.agentsMd) {
|
|
5069
|
+
const icon = fs19.existsSync("AGENTS.md") ? chalk5.yellow("~") : chalk5.green("+");
|
|
5070
|
+
const desc = getDescription("AGENTS.md");
|
|
5071
|
+
console.log(` ${icon} ${chalk5.bold("AGENTS.md")}`);
|
|
5072
|
+
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
5073
|
+
console.log("");
|
|
5074
|
+
}
|
|
5075
|
+
const codexSkills = codex.skills;
|
|
5076
|
+
if (Array.isArray(codexSkills) && codexSkills.length > 0) {
|
|
5077
|
+
for (const skill of codexSkills) {
|
|
5078
|
+
const skillPath = `.agents/skills/${skill.name}/SKILL.md`;
|
|
5079
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4974
5080
|
const desc = getDescription(skillPath);
|
|
4975
5081
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4976
5082
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -4980,7 +5086,7 @@ function printSetupSummary(setup) {
|
|
|
4980
5086
|
}
|
|
4981
5087
|
if (cursor) {
|
|
4982
5088
|
if (cursor.cursorrules) {
|
|
4983
|
-
const icon =
|
|
5089
|
+
const icon = fs19.existsSync(".cursorrules") ? chalk5.yellow("~") : chalk5.green("+");
|
|
4984
5090
|
const desc = getDescription(".cursorrules");
|
|
4985
5091
|
console.log(` ${icon} ${chalk5.bold(".cursorrules")}`);
|
|
4986
5092
|
if (desc) console.log(chalk5.dim(` ${desc}`));
|
|
@@ -4990,7 +5096,7 @@ function printSetupSummary(setup) {
|
|
|
4990
5096
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
4991
5097
|
for (const skill of cursorSkills) {
|
|
4992
5098
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
4993
|
-
const icon =
|
|
5099
|
+
const icon = fs19.existsSync(skillPath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
4994
5100
|
const desc = getDescription(skillPath);
|
|
4995
5101
|
console.log(` ${icon} ${chalk5.bold(skillPath)}`);
|
|
4996
5102
|
console.log(chalk5.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -5001,7 +5107,7 @@ function printSetupSummary(setup) {
|
|
|
5001
5107
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
5002
5108
|
for (const rule of rules) {
|
|
5003
5109
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
5004
|
-
const icon =
|
|
5110
|
+
const icon = fs19.existsSync(rulePath) ? chalk5.yellow("~") : chalk5.green("+");
|
|
5005
5111
|
const desc = getDescription(rulePath);
|
|
5006
5112
|
console.log(` ${icon} ${chalk5.bold(rulePath)}`);
|
|
5007
5113
|
if (desc) {
|
|
@@ -5014,7 +5120,7 @@ function printSetupSummary(setup) {
|
|
|
5014
5120
|
}
|
|
5015
5121
|
}
|
|
5016
5122
|
}
|
|
5017
|
-
if (!
|
|
5123
|
+
if (!codex && !fs19.existsSync("AGENTS.md")) {
|
|
5018
5124
|
console.log(` ${chalk5.green("+")} ${chalk5.bold("AGENTS.md")}`);
|
|
5019
5125
|
console.log(chalk5.dim(" Cross-agent coordination file"));
|
|
5020
5126
|
console.log("");
|
|
@@ -5042,8 +5148,8 @@ function ensurePermissions() {
|
|
|
5042
5148
|
const settingsPath = ".claude/settings.json";
|
|
5043
5149
|
let settings = {};
|
|
5044
5150
|
try {
|
|
5045
|
-
if (
|
|
5046
|
-
settings = JSON.parse(
|
|
5151
|
+
if (fs19.existsSync(settingsPath)) {
|
|
5152
|
+
settings = JSON.parse(fs19.readFileSync(settingsPath, "utf-8"));
|
|
5047
5153
|
}
|
|
5048
5154
|
} catch {
|
|
5049
5155
|
}
|
|
@@ -5057,13 +5163,14 @@ function ensurePermissions() {
|
|
|
5057
5163
|
"Bash(git *)"
|
|
5058
5164
|
];
|
|
5059
5165
|
settings.permissions = permissions;
|
|
5060
|
-
if (!
|
|
5061
|
-
|
|
5166
|
+
if (!fs19.existsSync(".claude")) fs19.mkdirSync(".claude", { recursive: true });
|
|
5167
|
+
fs19.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
5062
5168
|
}
|
|
5063
5169
|
function collectSetupFiles(setup) {
|
|
5064
5170
|
const files = [];
|
|
5065
5171
|
const claude = setup.claude;
|
|
5066
5172
|
const cursor = setup.cursor;
|
|
5173
|
+
const codex = setup.codex;
|
|
5067
5174
|
if (claude) {
|
|
5068
5175
|
if (claude.claudeMd) files.push({ path: "CLAUDE.md", content: claude.claudeMd });
|
|
5069
5176
|
const skills = claude.skills;
|
|
@@ -5073,6 +5180,15 @@ function collectSetupFiles(setup) {
|
|
|
5073
5180
|
}
|
|
5074
5181
|
}
|
|
5075
5182
|
}
|
|
5183
|
+
if (codex) {
|
|
5184
|
+
if (codex.agentsMd) files.push({ path: "AGENTS.md", content: codex.agentsMd });
|
|
5185
|
+
const codexSkills = codex.skills;
|
|
5186
|
+
if (Array.isArray(codexSkills)) {
|
|
5187
|
+
for (const skill of codexSkills) {
|
|
5188
|
+
files.push({ path: `.agents/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5076
5192
|
if (cursor) {
|
|
5077
5193
|
if (cursor.cursorrules) files.push({ path: ".cursorrules", content: cursor.cursorrules });
|
|
5078
5194
|
const cursorSkills = cursor.skills;
|
|
@@ -5088,7 +5204,8 @@ function collectSetupFiles(setup) {
|
|
|
5088
5204
|
}
|
|
5089
5205
|
}
|
|
5090
5206
|
}
|
|
5091
|
-
|
|
5207
|
+
const hasCodexAgentsMd = codex && codex.agentsMd;
|
|
5208
|
+
if (!fs19.existsSync("AGENTS.md") && !hasCodexAgentsMd) {
|
|
5092
5209
|
const agentRefs = [];
|
|
5093
5210
|
if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
|
|
5094
5211
|
if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
|
|
@@ -5139,7 +5256,7 @@ function undoCommand() {
|
|
|
5139
5256
|
|
|
5140
5257
|
// src/commands/status.ts
|
|
5141
5258
|
import chalk7 from "chalk";
|
|
5142
|
-
import
|
|
5259
|
+
import fs20 from "fs";
|
|
5143
5260
|
async function statusCommand(options) {
|
|
5144
5261
|
const config = loadConfig();
|
|
5145
5262
|
const manifest = readManifest();
|
|
@@ -5165,7 +5282,7 @@ async function statusCommand(options) {
|
|
|
5165
5282
|
}
|
|
5166
5283
|
console.log(` Files managed: ${chalk7.cyan(manifest.entries.length.toString())}`);
|
|
5167
5284
|
for (const entry of manifest.entries) {
|
|
5168
|
-
const exists =
|
|
5285
|
+
const exists = fs20.existsSync(entry.path);
|
|
5169
5286
|
const icon = exists ? chalk7.green("\u2713") : chalk7.red("\u2717");
|
|
5170
5287
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
5171
5288
|
}
|
|
@@ -5252,13 +5369,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
|
|
|
5252
5369
|
import { join as join8, dirname as dirname2 } from "path";
|
|
5253
5370
|
|
|
5254
5371
|
// src/scanner/index.ts
|
|
5255
|
-
import
|
|
5256
|
-
import
|
|
5372
|
+
import fs21 from "fs";
|
|
5373
|
+
import path17 from "path";
|
|
5257
5374
|
import crypto2 from "crypto";
|
|
5258
5375
|
function scanLocalState(dir) {
|
|
5259
5376
|
const items = [];
|
|
5260
|
-
const claudeMdPath =
|
|
5261
|
-
if (
|
|
5377
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
5378
|
+
if (fs21.existsSync(claudeMdPath)) {
|
|
5262
5379
|
items.push({
|
|
5263
5380
|
type: "rule",
|
|
5264
5381
|
platform: "claude",
|
|
@@ -5267,10 +5384,10 @@ function scanLocalState(dir) {
|
|
|
5267
5384
|
path: claudeMdPath
|
|
5268
5385
|
});
|
|
5269
5386
|
}
|
|
5270
|
-
const skillsDir =
|
|
5271
|
-
if (
|
|
5272
|
-
for (const file of
|
|
5273
|
-
const filePath =
|
|
5387
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
5388
|
+
if (fs21.existsSync(skillsDir)) {
|
|
5389
|
+
for (const file of fs21.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
5390
|
+
const filePath = path17.join(skillsDir, file);
|
|
5274
5391
|
items.push({
|
|
5275
5392
|
type: "skill",
|
|
5276
5393
|
platform: "claude",
|
|
@@ -5280,10 +5397,10 @@ function scanLocalState(dir) {
|
|
|
5280
5397
|
});
|
|
5281
5398
|
}
|
|
5282
5399
|
}
|
|
5283
|
-
const mcpJsonPath =
|
|
5284
|
-
if (
|
|
5400
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
5401
|
+
if (fs21.existsSync(mcpJsonPath)) {
|
|
5285
5402
|
try {
|
|
5286
|
-
const mcpJson = JSON.parse(
|
|
5403
|
+
const mcpJson = JSON.parse(fs21.readFileSync(mcpJsonPath, "utf-8"));
|
|
5287
5404
|
if (mcpJson.mcpServers) {
|
|
5288
5405
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5289
5406
|
items.push({
|
|
@@ -5298,8 +5415,36 @@ function scanLocalState(dir) {
|
|
|
5298
5415
|
} catch {
|
|
5299
5416
|
}
|
|
5300
5417
|
}
|
|
5301
|
-
const
|
|
5302
|
-
if (
|
|
5418
|
+
const agentsMdPath = path17.join(dir, "AGENTS.md");
|
|
5419
|
+
if (fs21.existsSync(agentsMdPath)) {
|
|
5420
|
+
items.push({
|
|
5421
|
+
type: "rule",
|
|
5422
|
+
platform: "codex",
|
|
5423
|
+
name: "AGENTS.md",
|
|
5424
|
+
contentHash: hashFile(agentsMdPath),
|
|
5425
|
+
path: agentsMdPath
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
const codexSkillsDir = path17.join(dir, ".agents", "skills");
|
|
5429
|
+
if (fs21.existsSync(codexSkillsDir)) {
|
|
5430
|
+
try {
|
|
5431
|
+
for (const name of fs21.readdirSync(codexSkillsDir)) {
|
|
5432
|
+
const skillFile = path17.join(codexSkillsDir, name, "SKILL.md");
|
|
5433
|
+
if (fs21.existsSync(skillFile)) {
|
|
5434
|
+
items.push({
|
|
5435
|
+
type: "skill",
|
|
5436
|
+
platform: "codex",
|
|
5437
|
+
name: `${name}/SKILL.md`,
|
|
5438
|
+
contentHash: hashFile(skillFile),
|
|
5439
|
+
path: skillFile
|
|
5440
|
+
});
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
} catch {
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
5447
|
+
if (fs21.existsSync(cursorrulesPath)) {
|
|
5303
5448
|
items.push({
|
|
5304
5449
|
type: "rule",
|
|
5305
5450
|
platform: "cursor",
|
|
@@ -5308,10 +5453,10 @@ function scanLocalState(dir) {
|
|
|
5308
5453
|
path: cursorrulesPath
|
|
5309
5454
|
});
|
|
5310
5455
|
}
|
|
5311
|
-
const cursorRulesDir =
|
|
5312
|
-
if (
|
|
5313
|
-
for (const file of
|
|
5314
|
-
const filePath =
|
|
5456
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
5457
|
+
if (fs21.existsSync(cursorRulesDir)) {
|
|
5458
|
+
for (const file of fs21.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
5459
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
5315
5460
|
items.push({
|
|
5316
5461
|
type: "rule",
|
|
5317
5462
|
platform: "cursor",
|
|
@@ -5321,12 +5466,12 @@ function scanLocalState(dir) {
|
|
|
5321
5466
|
});
|
|
5322
5467
|
}
|
|
5323
5468
|
}
|
|
5324
|
-
const cursorSkillsDir =
|
|
5325
|
-
if (
|
|
5469
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
5470
|
+
if (fs21.existsSync(cursorSkillsDir)) {
|
|
5326
5471
|
try {
|
|
5327
|
-
for (const name of
|
|
5328
|
-
const skillFile =
|
|
5329
|
-
if (
|
|
5472
|
+
for (const name of fs21.readdirSync(cursorSkillsDir)) {
|
|
5473
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
5474
|
+
if (fs21.existsSync(skillFile)) {
|
|
5330
5475
|
items.push({
|
|
5331
5476
|
type: "skill",
|
|
5332
5477
|
platform: "cursor",
|
|
@@ -5339,10 +5484,10 @@ function scanLocalState(dir) {
|
|
|
5339
5484
|
} catch {
|
|
5340
5485
|
}
|
|
5341
5486
|
}
|
|
5342
|
-
const cursorMcpPath =
|
|
5343
|
-
if (
|
|
5487
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
5488
|
+
if (fs21.existsSync(cursorMcpPath)) {
|
|
5344
5489
|
try {
|
|
5345
|
-
const mcpJson = JSON.parse(
|
|
5490
|
+
const mcpJson = JSON.parse(fs21.readFileSync(cursorMcpPath, "utf-8"));
|
|
5346
5491
|
if (mcpJson.mcpServers) {
|
|
5347
5492
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
5348
5493
|
items.push({
|
|
@@ -5360,7 +5505,7 @@ function scanLocalState(dir) {
|
|
|
5360
5505
|
return items;
|
|
5361
5506
|
}
|
|
5362
5507
|
function hashFile(filePath) {
|
|
5363
|
-
const text =
|
|
5508
|
+
const text = fs21.readFileSync(filePath, "utf-8");
|
|
5364
5509
|
return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
5365
5510
|
}
|
|
5366
5511
|
function hashJson(obj) {
|
|
@@ -5380,13 +5525,17 @@ function getSkillPath(platform, slug) {
|
|
|
5380
5525
|
if (platform === "cursor") {
|
|
5381
5526
|
return join8(".cursor", "skills", slug, "SKILL.md");
|
|
5382
5527
|
}
|
|
5528
|
+
if (platform === "codex") {
|
|
5529
|
+
return join8(".agents", "skills", slug, "SKILL.md");
|
|
5530
|
+
}
|
|
5383
5531
|
return join8(".claude", "skills", slug, "SKILL.md");
|
|
5384
5532
|
}
|
|
5385
5533
|
function getInstalledSkills() {
|
|
5386
5534
|
const installed = /* @__PURE__ */ new Set();
|
|
5387
5535
|
const dirs = [
|
|
5388
5536
|
join8(process.cwd(), ".claude", "skills"),
|
|
5389
|
-
join8(process.cwd(), ".cursor", "skills")
|
|
5537
|
+
join8(process.cwd(), ".cursor", "skills"),
|
|
5538
|
+
join8(process.cwd(), ".agents", "skills")
|
|
5390
5539
|
];
|
|
5391
5540
|
for (const dir of dirs) {
|
|
5392
5541
|
try {
|
|
@@ -5834,7 +5983,8 @@ async function fetchSkillContent(rec) {
|
|
|
5834
5983
|
const candidates = [
|
|
5835
5984
|
`https://raw.githubusercontent.com/${repoPath}/HEAD/skills/${rec.slug}/SKILL.md`,
|
|
5836
5985
|
`https://raw.githubusercontent.com/${repoPath}/HEAD/${rec.slug}/SKILL.md`,
|
|
5837
|
-
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md
|
|
5986
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.claude/skills/${rec.slug}/SKILL.md`,
|
|
5987
|
+
`https://raw.githubusercontent.com/${repoPath}/HEAD/.agents/skills/${rec.slug}/SKILL.md`
|
|
5838
5988
|
];
|
|
5839
5989
|
for (const url of candidates) {
|
|
5840
5990
|
try {
|
|
@@ -5936,8 +6086,8 @@ async function scoreCommand(options) {
|
|
|
5936
6086
|
}
|
|
5937
6087
|
|
|
5938
6088
|
// src/commands/refresh.ts
|
|
5939
|
-
import
|
|
5940
|
-
import
|
|
6089
|
+
import fs23 from "fs";
|
|
6090
|
+
import path19 from "path";
|
|
5941
6091
|
import chalk11 from "chalk";
|
|
5942
6092
|
import ora6 from "ora";
|
|
5943
6093
|
|
|
@@ -6014,37 +6164,37 @@ function collectDiff(lastSha) {
|
|
|
6014
6164
|
}
|
|
6015
6165
|
|
|
6016
6166
|
// src/writers/refresh.ts
|
|
6017
|
-
import
|
|
6018
|
-
import
|
|
6167
|
+
import fs22 from "fs";
|
|
6168
|
+
import path18 from "path";
|
|
6019
6169
|
function writeRefreshDocs(docs) {
|
|
6020
6170
|
const written = [];
|
|
6021
6171
|
if (docs.claudeMd) {
|
|
6022
|
-
|
|
6172
|
+
fs22.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
6023
6173
|
written.push("CLAUDE.md");
|
|
6024
6174
|
}
|
|
6025
6175
|
if (docs.readmeMd) {
|
|
6026
|
-
|
|
6176
|
+
fs22.writeFileSync("README.md", docs.readmeMd);
|
|
6027
6177
|
written.push("README.md");
|
|
6028
6178
|
}
|
|
6029
6179
|
if (docs.cursorrules) {
|
|
6030
|
-
|
|
6180
|
+
fs22.writeFileSync(".cursorrules", docs.cursorrules);
|
|
6031
6181
|
written.push(".cursorrules");
|
|
6032
6182
|
}
|
|
6033
6183
|
if (docs.cursorRules) {
|
|
6034
|
-
const rulesDir =
|
|
6035
|
-
if (!
|
|
6184
|
+
const rulesDir = path18.join(".cursor", "rules");
|
|
6185
|
+
if (!fs22.existsSync(rulesDir)) fs22.mkdirSync(rulesDir, { recursive: true });
|
|
6036
6186
|
for (const rule of docs.cursorRules) {
|
|
6037
|
-
const filePath =
|
|
6038
|
-
|
|
6187
|
+
const filePath = path18.join(rulesDir, rule.filename);
|
|
6188
|
+
fs22.writeFileSync(filePath, rule.content);
|
|
6039
6189
|
written.push(filePath);
|
|
6040
6190
|
}
|
|
6041
6191
|
}
|
|
6042
6192
|
if (docs.claudeSkills) {
|
|
6043
|
-
const skillsDir =
|
|
6044
|
-
if (!
|
|
6193
|
+
const skillsDir = path18.join(".claude", "skills");
|
|
6194
|
+
if (!fs22.existsSync(skillsDir)) fs22.mkdirSync(skillsDir, { recursive: true });
|
|
6045
6195
|
for (const skill of docs.claudeSkills) {
|
|
6046
|
-
const filePath =
|
|
6047
|
-
|
|
6196
|
+
const filePath = path18.join(skillsDir, skill.filename);
|
|
6197
|
+
fs22.writeFileSync(filePath, skill.content);
|
|
6048
6198
|
written.push(filePath);
|
|
6049
6199
|
}
|
|
6050
6200
|
}
|
|
@@ -6119,11 +6269,11 @@ function log(quiet, ...args) {
|
|
|
6119
6269
|
function discoverGitRepos(parentDir) {
|
|
6120
6270
|
const repos = [];
|
|
6121
6271
|
try {
|
|
6122
|
-
const entries =
|
|
6272
|
+
const entries = fs23.readdirSync(parentDir, { withFileTypes: true });
|
|
6123
6273
|
for (const entry of entries) {
|
|
6124
6274
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
6125
|
-
const childPath =
|
|
6126
|
-
if (
|
|
6275
|
+
const childPath = path19.join(parentDir, entry.name);
|
|
6276
|
+
if (fs23.existsSync(path19.join(childPath, ".git"))) {
|
|
6127
6277
|
repos.push(childPath);
|
|
6128
6278
|
}
|
|
6129
6279
|
}
|
|
@@ -6218,7 +6368,7 @@ async function refreshCommand(options) {
|
|
|
6218
6368
|
`));
|
|
6219
6369
|
const originalDir = process.cwd();
|
|
6220
6370
|
for (const repo of repos) {
|
|
6221
|
-
const repoName =
|
|
6371
|
+
const repoName = path19.basename(repo);
|
|
6222
6372
|
try {
|
|
6223
6373
|
process.chdir(repo);
|
|
6224
6374
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -6465,8 +6615,8 @@ function readStdin() {
|
|
|
6465
6615
|
|
|
6466
6616
|
// src/learner/storage.ts
|
|
6467
6617
|
init_constants();
|
|
6468
|
-
import
|
|
6469
|
-
import
|
|
6618
|
+
import fs24 from "fs";
|
|
6619
|
+
import path20 from "path";
|
|
6470
6620
|
var MAX_RESPONSE_LENGTH = 2e3;
|
|
6471
6621
|
var DEFAULT_STATE = {
|
|
6472
6622
|
sessionId: null,
|
|
@@ -6474,15 +6624,15 @@ var DEFAULT_STATE = {
|
|
|
6474
6624
|
lastAnalysisTimestamp: null
|
|
6475
6625
|
};
|
|
6476
6626
|
function ensureLearningDir() {
|
|
6477
|
-
if (!
|
|
6478
|
-
|
|
6627
|
+
if (!fs24.existsSync(LEARNING_DIR)) {
|
|
6628
|
+
fs24.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
6479
6629
|
}
|
|
6480
6630
|
}
|
|
6481
6631
|
function sessionFilePath() {
|
|
6482
|
-
return
|
|
6632
|
+
return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
6483
6633
|
}
|
|
6484
6634
|
function stateFilePath() {
|
|
6485
|
-
return
|
|
6635
|
+
return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
6486
6636
|
}
|
|
6487
6637
|
function truncateResponse(response) {
|
|
6488
6638
|
const str = JSON.stringify(response);
|
|
@@ -6493,50 +6643,50 @@ function appendEvent(event) {
|
|
|
6493
6643
|
ensureLearningDir();
|
|
6494
6644
|
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
6495
6645
|
const filePath = sessionFilePath();
|
|
6496
|
-
|
|
6646
|
+
fs24.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
6497
6647
|
const count = getEventCount();
|
|
6498
6648
|
if (count > LEARNING_MAX_EVENTS) {
|
|
6499
|
-
const lines =
|
|
6649
|
+
const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6500
6650
|
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
6501
|
-
|
|
6651
|
+
fs24.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
6502
6652
|
}
|
|
6503
6653
|
}
|
|
6504
6654
|
function readAllEvents() {
|
|
6505
6655
|
const filePath = sessionFilePath();
|
|
6506
|
-
if (!
|
|
6507
|
-
const lines =
|
|
6656
|
+
if (!fs24.existsSync(filePath)) return [];
|
|
6657
|
+
const lines = fs24.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
6508
6658
|
return lines.map((line) => JSON.parse(line));
|
|
6509
6659
|
}
|
|
6510
6660
|
function getEventCount() {
|
|
6511
6661
|
const filePath = sessionFilePath();
|
|
6512
|
-
if (!
|
|
6513
|
-
const content =
|
|
6662
|
+
if (!fs24.existsSync(filePath)) return 0;
|
|
6663
|
+
const content = fs24.readFileSync(filePath, "utf-8");
|
|
6514
6664
|
return content.split("\n").filter(Boolean).length;
|
|
6515
6665
|
}
|
|
6516
6666
|
function clearSession() {
|
|
6517
6667
|
const filePath = sessionFilePath();
|
|
6518
|
-
if (
|
|
6668
|
+
if (fs24.existsSync(filePath)) fs24.unlinkSync(filePath);
|
|
6519
6669
|
}
|
|
6520
6670
|
function readState2() {
|
|
6521
6671
|
const filePath = stateFilePath();
|
|
6522
|
-
if (!
|
|
6672
|
+
if (!fs24.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
6523
6673
|
try {
|
|
6524
|
-
return JSON.parse(
|
|
6674
|
+
return JSON.parse(fs24.readFileSync(filePath, "utf-8"));
|
|
6525
6675
|
} catch {
|
|
6526
6676
|
return { ...DEFAULT_STATE };
|
|
6527
6677
|
}
|
|
6528
6678
|
}
|
|
6529
6679
|
function writeState2(state) {
|
|
6530
6680
|
ensureLearningDir();
|
|
6531
|
-
|
|
6681
|
+
fs24.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
6532
6682
|
}
|
|
6533
6683
|
function resetState() {
|
|
6534
6684
|
writeState2({ ...DEFAULT_STATE });
|
|
6535
6685
|
}
|
|
6536
6686
|
|
|
6537
6687
|
// src/learner/writer.ts
|
|
6538
|
-
import
|
|
6539
|
-
import
|
|
6688
|
+
import fs25 from "fs";
|
|
6689
|
+
import path21 from "path";
|
|
6540
6690
|
var LEARNED_START = "<!-- caliber:learned -->";
|
|
6541
6691
|
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
6542
6692
|
function writeLearnedContent(update) {
|
|
@@ -6556,8 +6706,8 @@ function writeLearnedContent(update) {
|
|
|
6556
6706
|
function writeLearnedSection(content) {
|
|
6557
6707
|
const claudeMdPath = "CLAUDE.md";
|
|
6558
6708
|
let existing = "";
|
|
6559
|
-
if (
|
|
6560
|
-
existing =
|
|
6709
|
+
if (fs25.existsSync(claudeMdPath)) {
|
|
6710
|
+
existing = fs25.readFileSync(claudeMdPath, "utf-8");
|
|
6561
6711
|
}
|
|
6562
6712
|
const section = `${LEARNED_START}
|
|
6563
6713
|
${content}
|
|
@@ -6571,15 +6721,15 @@ ${LEARNED_END}`;
|
|
|
6571
6721
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
6572
6722
|
updated = existing + separator + "\n" + section + "\n";
|
|
6573
6723
|
}
|
|
6574
|
-
|
|
6724
|
+
fs25.writeFileSync(claudeMdPath, updated);
|
|
6575
6725
|
}
|
|
6576
6726
|
function writeLearnedSkill(skill) {
|
|
6577
|
-
const skillDir =
|
|
6578
|
-
if (!
|
|
6579
|
-
const skillPath =
|
|
6580
|
-
if (!skill.isNew &&
|
|
6581
|
-
const existing =
|
|
6582
|
-
|
|
6727
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
6728
|
+
if (!fs25.existsSync(skillDir)) fs25.mkdirSync(skillDir, { recursive: true });
|
|
6729
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
6730
|
+
if (!skill.isNew && fs25.existsSync(skillPath)) {
|
|
6731
|
+
const existing = fs25.readFileSync(skillPath, "utf-8");
|
|
6732
|
+
fs25.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
6583
6733
|
} else {
|
|
6584
6734
|
const frontmatter = [
|
|
6585
6735
|
"---",
|
|
@@ -6588,14 +6738,14 @@ function writeLearnedSkill(skill) {
|
|
|
6588
6738
|
"---",
|
|
6589
6739
|
""
|
|
6590
6740
|
].join("\n");
|
|
6591
|
-
|
|
6741
|
+
fs25.writeFileSync(skillPath, frontmatter + skill.content);
|
|
6592
6742
|
}
|
|
6593
6743
|
return skillPath;
|
|
6594
6744
|
}
|
|
6595
6745
|
function readLearnedSection() {
|
|
6596
6746
|
const claudeMdPath = "CLAUDE.md";
|
|
6597
|
-
if (!
|
|
6598
|
-
const content =
|
|
6747
|
+
if (!fs25.existsSync(claudeMdPath)) return null;
|
|
6748
|
+
const content = fs25.readFileSync(claudeMdPath, "utf-8");
|
|
6599
6749
|
const startIdx = content.indexOf(LEARNED_START);
|
|
6600
6750
|
const endIdx = content.indexOf(LEARNED_END);
|
|
6601
6751
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -6779,20 +6929,20 @@ Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
|
6779
6929
|
}
|
|
6780
6930
|
|
|
6781
6931
|
// src/cli.ts
|
|
6782
|
-
var __dirname =
|
|
6932
|
+
var __dirname = path22.dirname(fileURLToPath(import.meta.url));
|
|
6783
6933
|
var pkg = JSON.parse(
|
|
6784
|
-
|
|
6934
|
+
fs26.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
6785
6935
|
);
|
|
6786
6936
|
var program = new Command();
|
|
6787
6937
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
|
|
6788
6938
|
program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
|
|
6789
|
-
program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agent: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
|
|
6939
|
+
program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").option("--agent <type>", "Target agent: claude, cursor, codex, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
|
|
6790
6940
|
program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
|
|
6791
6941
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
|
|
6792
6942
|
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
6943
|
program.command("config").description("Configure LLM provider, API key, and model").action(configCommand);
|
|
6794
6944
|
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 agent: claude, cursor, or both").action(scoreCommand);
|
|
6945
|
+
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 agent: claude, cursor, codex, or both").action(scoreCommand);
|
|
6796
6946
|
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
6947
|
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
6948
|
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
@@ -6803,22 +6953,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
|
|
|
6803
6953
|
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
6804
6954
|
|
|
6805
6955
|
// src/utils/version-check.ts
|
|
6806
|
-
import
|
|
6807
|
-
import
|
|
6956
|
+
import fs27 from "fs";
|
|
6957
|
+
import path23 from "path";
|
|
6808
6958
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6809
6959
|
import { execSync as execSync9 } from "child_process";
|
|
6810
6960
|
import chalk15 from "chalk";
|
|
6811
6961
|
import ora7 from "ora";
|
|
6812
6962
|
import confirm2 from "@inquirer/confirm";
|
|
6813
|
-
var __dirname_vc =
|
|
6963
|
+
var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
|
|
6814
6964
|
var pkg2 = JSON.parse(
|
|
6815
|
-
|
|
6965
|
+
fs27.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
6816
6966
|
);
|
|
6817
6967
|
function getInstalledVersion() {
|
|
6818
6968
|
try {
|
|
6819
6969
|
const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
6820
|
-
const pkgPath =
|
|
6821
|
-
return JSON.parse(
|
|
6970
|
+
const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
|
|
6971
|
+
return JSON.parse(fs27.readFileSync(pkgPath, "utf-8")).version;
|
|
6822
6972
|
} catch {
|
|
6823
6973
|
return null;
|
|
6824
6974
|
}
|