@rely-ai/caliber 1.7.9 → 1.9.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 +370 -165
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -627,6 +627,33 @@ init_config();
|
|
|
627
627
|
|
|
628
628
|
// src/llm/anthropic.ts
|
|
629
629
|
import Anthropic from "@anthropic-ai/sdk";
|
|
630
|
+
|
|
631
|
+
// src/llm/usage.ts
|
|
632
|
+
var usageByModel = /* @__PURE__ */ new Map();
|
|
633
|
+
function trackUsage(model, usage) {
|
|
634
|
+
const existing = usageByModel.get(model);
|
|
635
|
+
if (existing) {
|
|
636
|
+
existing.inputTokens += usage.inputTokens;
|
|
637
|
+
existing.outputTokens += usage.outputTokens;
|
|
638
|
+
existing.cacheReadTokens += usage.cacheReadTokens ?? 0;
|
|
639
|
+
existing.cacheWriteTokens += usage.cacheWriteTokens ?? 0;
|
|
640
|
+
existing.calls += 1;
|
|
641
|
+
} else {
|
|
642
|
+
usageByModel.set(model, {
|
|
643
|
+
model,
|
|
644
|
+
inputTokens: usage.inputTokens,
|
|
645
|
+
outputTokens: usage.outputTokens,
|
|
646
|
+
cacheReadTokens: usage.cacheReadTokens ?? 0,
|
|
647
|
+
cacheWriteTokens: usage.cacheWriteTokens ?? 0,
|
|
648
|
+
calls: 1
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function getUsageSummary() {
|
|
653
|
+
return Array.from(usageByModel.values());
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/llm/anthropic.ts
|
|
630
657
|
var AnthropicProvider = class {
|
|
631
658
|
client;
|
|
632
659
|
defaultModel;
|
|
@@ -641,6 +668,16 @@ var AnthropicProvider = class {
|
|
|
641
668
|
system: [{ type: "text", text: options.system, cache_control: { type: "ephemeral" } }],
|
|
642
669
|
messages: [{ role: "user", content: options.prompt }]
|
|
643
670
|
});
|
|
671
|
+
const model = options.model || this.defaultModel;
|
|
672
|
+
if (response.usage) {
|
|
673
|
+
const u = response.usage;
|
|
674
|
+
trackUsage(model, {
|
|
675
|
+
inputTokens: u.input_tokens ?? 0,
|
|
676
|
+
outputTokens: u.output_tokens ?? 0,
|
|
677
|
+
cacheReadTokens: u.cache_read_input_tokens,
|
|
678
|
+
cacheWriteTokens: u.cache_creation_input_tokens
|
|
679
|
+
});
|
|
680
|
+
}
|
|
644
681
|
const block = response.content[0];
|
|
645
682
|
return block.type === "text" ? block.text : "";
|
|
646
683
|
}
|
|
@@ -667,11 +704,24 @@ var AnthropicProvider = class {
|
|
|
667
704
|
messages
|
|
668
705
|
});
|
|
669
706
|
let stopReason;
|
|
707
|
+
let usage;
|
|
708
|
+
const model = options.model || this.defaultModel;
|
|
670
709
|
stream.on("message", (message) => {
|
|
671
|
-
|
|
710
|
+
const msg = message;
|
|
711
|
+
stopReason = msg.stop_reason;
|
|
712
|
+
const u = msg.usage;
|
|
713
|
+
if (u) {
|
|
714
|
+
usage = {
|
|
715
|
+
inputTokens: u.input_tokens ?? 0,
|
|
716
|
+
outputTokens: u.output_tokens ?? 0,
|
|
717
|
+
cacheReadTokens: u.cache_read_input_tokens,
|
|
718
|
+
cacheWriteTokens: u.cache_creation_input_tokens
|
|
719
|
+
};
|
|
720
|
+
trackUsage(model, usage);
|
|
721
|
+
}
|
|
672
722
|
});
|
|
673
723
|
stream.on("text", (text) => callbacks.onText(text));
|
|
674
|
-
stream.on("end", () => callbacks.onEnd({ stopReason }));
|
|
724
|
+
stream.on("end", () => callbacks.onEnd({ stopReason, usage }));
|
|
675
725
|
stream.on("error", (error) => callbacks.onError(error));
|
|
676
726
|
}
|
|
677
727
|
};
|
|
@@ -722,6 +772,16 @@ var VertexProvider = class {
|
|
|
722
772
|
system: [{ type: "text", text: options.system, cache_control: { type: "ephemeral" } }],
|
|
723
773
|
messages: [{ role: "user", content: options.prompt }]
|
|
724
774
|
});
|
|
775
|
+
const model = options.model || this.defaultModel;
|
|
776
|
+
if (response.usage) {
|
|
777
|
+
const u = response.usage;
|
|
778
|
+
trackUsage(model, {
|
|
779
|
+
inputTokens: u.input_tokens ?? 0,
|
|
780
|
+
outputTokens: u.output_tokens ?? 0,
|
|
781
|
+
cacheReadTokens: u.cache_read_input_tokens,
|
|
782
|
+
cacheWriteTokens: u.cache_creation_input_tokens
|
|
783
|
+
});
|
|
784
|
+
}
|
|
725
785
|
const block = response.content[0];
|
|
726
786
|
return block.type === "text" ? block.text : "";
|
|
727
787
|
}
|
|
@@ -740,11 +800,24 @@ var VertexProvider = class {
|
|
|
740
800
|
messages
|
|
741
801
|
});
|
|
742
802
|
let stopReason;
|
|
803
|
+
let usage;
|
|
804
|
+
const model = options.model || this.defaultModel;
|
|
743
805
|
stream.on("message", (message) => {
|
|
744
|
-
|
|
806
|
+
const msg = message;
|
|
807
|
+
stopReason = msg.stop_reason;
|
|
808
|
+
const u = msg.usage;
|
|
809
|
+
if (u) {
|
|
810
|
+
usage = {
|
|
811
|
+
inputTokens: u.input_tokens ?? 0,
|
|
812
|
+
outputTokens: u.output_tokens ?? 0,
|
|
813
|
+
cacheReadTokens: u.cache_read_input_tokens,
|
|
814
|
+
cacheWriteTokens: u.cache_creation_input_tokens
|
|
815
|
+
};
|
|
816
|
+
trackUsage(model, usage);
|
|
817
|
+
}
|
|
745
818
|
});
|
|
746
819
|
stream.on("text", (text) => callbacks.onText(text));
|
|
747
|
-
stream.on("end", () => callbacks.onEnd({ stopReason }));
|
|
820
|
+
stream.on("end", () => callbacks.onEnd({ stopReason, usage }));
|
|
748
821
|
stream.on("error", (error) => callbacks.onError(error));
|
|
749
822
|
}
|
|
750
823
|
};
|
|
@@ -770,6 +843,13 @@ var OpenAICompatProvider = class {
|
|
|
770
843
|
{ role: "user", content: options.prompt }
|
|
771
844
|
]
|
|
772
845
|
});
|
|
846
|
+
const model = options.model || this.defaultModel;
|
|
847
|
+
if (response.usage) {
|
|
848
|
+
trackUsage(model, {
|
|
849
|
+
inputTokens: response.usage.prompt_tokens ?? 0,
|
|
850
|
+
outputTokens: response.usage.completion_tokens ?? 0
|
|
851
|
+
});
|
|
852
|
+
}
|
|
773
853
|
return response.choices[0]?.message?.content || "";
|
|
774
854
|
}
|
|
775
855
|
async listModels() {
|
|
@@ -797,13 +877,23 @@ var OpenAICompatProvider = class {
|
|
|
797
877
|
});
|
|
798
878
|
try {
|
|
799
879
|
let stopReason;
|
|
880
|
+
let usage;
|
|
881
|
+
const model = options.model || this.defaultModel;
|
|
800
882
|
for await (const chunk of stream) {
|
|
801
883
|
const delta = chunk.choices[0]?.delta?.content;
|
|
802
884
|
if (delta != null) callbacks.onText(delta);
|
|
803
885
|
const finishReason = chunk.choices[0]?.finish_reason;
|
|
804
886
|
if (finishReason) stopReason = finishReason === "length" ? "max_tokens" : finishReason;
|
|
887
|
+
const chunkUsage = chunk.usage;
|
|
888
|
+
if (chunkUsage) {
|
|
889
|
+
usage = {
|
|
890
|
+
inputTokens: chunkUsage.prompt_tokens ?? 0,
|
|
891
|
+
outputTokens: chunkUsage.completion_tokens ?? 0
|
|
892
|
+
};
|
|
893
|
+
trackUsage(model, usage);
|
|
894
|
+
}
|
|
805
895
|
}
|
|
806
|
-
callbacks.onEnd({ stopReason });
|
|
896
|
+
callbacks.onEnd({ stopReason, usage });
|
|
807
897
|
} catch (error) {
|
|
808
898
|
callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
809
899
|
}
|
|
@@ -1476,7 +1566,7 @@ Quality (25 pts):
|
|
|
1476
1566
|
- No contradictions (2 pts) \u2014 consistent tool/style recommendations
|
|
1477
1567
|
|
|
1478
1568
|
Coverage (20 pts):
|
|
1479
|
-
- Dependency coverage (10 pts) \u2014 CRITICAL:
|
|
1569
|
+
- Dependency coverage (10 pts) \u2014 CRITICAL: the exact dependency list is provided in your input under "DEPENDENCY COVERAGE". Mention AT LEAST 85% of them by name in CLAUDE.md or skills. You get full points at 85%+, proportional below that. Weave them naturally into architecture, key deps, and conventions sections.
|
|
1480
1570
|
- Service/MCP coverage (6 pts) \u2014 reference detected services (DB, cloud, etc.)
|
|
1481
1571
|
- MCP completeness (4 pts) \u2014 full points if no external services detected
|
|
1482
1572
|
|
|
@@ -1737,6 +1827,98 @@ async function enrichWithLLM(fingerprint, dir) {
|
|
|
1737
1827
|
}
|
|
1738
1828
|
}
|
|
1739
1829
|
|
|
1830
|
+
// src/utils/dependencies.ts
|
|
1831
|
+
import { readFileSync } from "fs";
|
|
1832
|
+
import { join } from "path";
|
|
1833
|
+
function readFileOrNull(path26) {
|
|
1834
|
+
try {
|
|
1835
|
+
return readFileSync(path26, "utf-8");
|
|
1836
|
+
} catch {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
function readJsonOrNull(path26) {
|
|
1841
|
+
const content = readFileOrNull(path26);
|
|
1842
|
+
if (!content) return null;
|
|
1843
|
+
try {
|
|
1844
|
+
return JSON.parse(content);
|
|
1845
|
+
} catch {
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function extractNpmDeps(dir) {
|
|
1850
|
+
const pkg3 = readJsonOrNull(join(dir, "package.json"));
|
|
1851
|
+
if (!pkg3) return [];
|
|
1852
|
+
const deps = {
|
|
1853
|
+
...pkg3.dependencies,
|
|
1854
|
+
...pkg3.devDependencies
|
|
1855
|
+
};
|
|
1856
|
+
const trivial = /* @__PURE__ */ new Set([
|
|
1857
|
+
"typescript",
|
|
1858
|
+
"@types/node",
|
|
1859
|
+
"tslib",
|
|
1860
|
+
"ts-node",
|
|
1861
|
+
"tsx",
|
|
1862
|
+
"prettier",
|
|
1863
|
+
"eslint",
|
|
1864
|
+
"@eslint/js",
|
|
1865
|
+
"rimraf",
|
|
1866
|
+
"cross-env",
|
|
1867
|
+
"dotenv",
|
|
1868
|
+
"nodemon",
|
|
1869
|
+
"husky",
|
|
1870
|
+
"lint-staged",
|
|
1871
|
+
"commitlint",
|
|
1872
|
+
"@commitlint/cli",
|
|
1873
|
+
"@commitlint/config-conventional"
|
|
1874
|
+
]);
|
|
1875
|
+
const trivialPatterns = [
|
|
1876
|
+
/^@rely-ai\//,
|
|
1877
|
+
/^@caliber-ai\//,
|
|
1878
|
+
/^eslint-/,
|
|
1879
|
+
/^@eslint\//,
|
|
1880
|
+
/^prettier-/,
|
|
1881
|
+
/^@typescript-eslint\//
|
|
1882
|
+
];
|
|
1883
|
+
return Object.keys(deps).filter((d) => !trivial.has(d) && !d.startsWith("@types/") && !trivialPatterns.some((p) => p.test(d))).slice(0, 30);
|
|
1884
|
+
}
|
|
1885
|
+
function extractPythonDeps(dir) {
|
|
1886
|
+
const reqTxt = readFileOrNull(join(dir, "requirements.txt"));
|
|
1887
|
+
if (reqTxt) {
|
|
1888
|
+
return reqTxt.split("\n").map((l) => l.trim().split(/[=<>!~\[]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 30);
|
|
1889
|
+
}
|
|
1890
|
+
const pyproject = readFileOrNull(join(dir, "pyproject.toml"));
|
|
1891
|
+
if (pyproject) {
|
|
1892
|
+
const depMatch = pyproject.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
1893
|
+
if (depMatch) {
|
|
1894
|
+
return depMatch[1].split("\n").map((l) => l.trim().replace(/["',]/g, "").split(/[=<>!~\[]/)[0].trim()).filter((l) => l.length > 0).slice(0, 30);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return [];
|
|
1898
|
+
}
|
|
1899
|
+
function extractGoDeps(dir) {
|
|
1900
|
+
const goMod = readFileOrNull(join(dir, "go.mod"));
|
|
1901
|
+
if (!goMod) return [];
|
|
1902
|
+
const requireBlock = goMod.match(/require\s*\(([\s\S]*?)\)/);
|
|
1903
|
+
if (!requireBlock) return [];
|
|
1904
|
+
return requireBlock[1].split("\n").map((l) => l.trim().split(/\s/)[0]).filter((l) => l && !l.startsWith("//")).map((l) => l.split("/").pop() || l).slice(0, 30);
|
|
1905
|
+
}
|
|
1906
|
+
function extractRustDeps(dir) {
|
|
1907
|
+
const cargo = readFileOrNull(join(dir, "Cargo.toml"));
|
|
1908
|
+
if (!cargo) return [];
|
|
1909
|
+
const depSection = cargo.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/);
|
|
1910
|
+
if (!depSection) return [];
|
|
1911
|
+
return depSection[1].split("\n").map((l) => l.trim().split(/\s*=/)[0].trim()).filter((l) => l.length > 0 && !l.startsWith("#")).slice(0, 30);
|
|
1912
|
+
}
|
|
1913
|
+
function extractAllDeps(dir) {
|
|
1914
|
+
return [
|
|
1915
|
+
...extractNpmDeps(dir),
|
|
1916
|
+
...extractPythonDeps(dir),
|
|
1917
|
+
...extractGoDeps(dir),
|
|
1918
|
+
...extractRustDeps(dir)
|
|
1919
|
+
];
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1740
1922
|
// src/ai/generate.ts
|
|
1741
1923
|
var GENERATION_MAX_TOKENS = 64e3;
|
|
1742
1924
|
var MODEL_MAX_OUTPUT_TOKENS = 128e3;
|
|
@@ -1818,9 +2000,9 @@ async function generateSetup(fingerprint, targetAgent, prompt, callbacks, failin
|
|
|
1818
2000
|
}
|
|
1819
2001
|
if (setup) {
|
|
1820
2002
|
if (callbacks) callbacks.onComplete(setup, explanation);
|
|
1821
|
-
resolve2({ setup, explanation });
|
|
2003
|
+
resolve2({ setup, explanation, stopReason: stopReason ?? void 0 });
|
|
1822
2004
|
} else {
|
|
1823
|
-
resolve2({ setup: null, explanation, raw: preJsonBuffer });
|
|
2005
|
+
resolve2({ setup: null, explanation, raw: preJsonBuffer, stopReason: stopReason ?? void 0 });
|
|
1824
2006
|
}
|
|
1825
2007
|
},
|
|
1826
2008
|
onError: (error) => {
|
|
@@ -1830,12 +2012,12 @@ async function generateSetup(fingerprint, targetAgent, prompt, callbacks, failin
|
|
|
1830
2012
|
return;
|
|
1831
2013
|
}
|
|
1832
2014
|
if (callbacks) callbacks.onError(error.message);
|
|
1833
|
-
resolve2({ setup: null, raw: error.message });
|
|
2015
|
+
resolve2({ setup: null, raw: error.message, stopReason: "error" });
|
|
1834
2016
|
}
|
|
1835
2017
|
}
|
|
1836
2018
|
).catch((error) => {
|
|
1837
2019
|
if (callbacks) callbacks.onError(error.message);
|
|
1838
|
-
resolve2({ setup: null, raw: error.message });
|
|
2020
|
+
resolve2({ setup: null, raw: error.message, stopReason: "error" });
|
|
1839
2021
|
});
|
|
1840
2022
|
});
|
|
1841
2023
|
};
|
|
@@ -1983,6 +2165,12 @@ ${truncate(cfg.content, LIMITS.CONFIG_FILE_CHARS)}`);
|
|
|
1983
2165
|
parts.push("\n(Code analysis was truncated due to size limits \u2014 not all files are shown.)");
|
|
1984
2166
|
}
|
|
1985
2167
|
}
|
|
2168
|
+
const allDeps = extractAllDeps(process.cwd());
|
|
2169
|
+
if (allDeps.length > 0) {
|
|
2170
|
+
parts.push(`
|
|
2171
|
+
DEPENDENCY COVERAGE \u2014 mention at least 85% of these ${allDeps.length} packages by name in CLAUDE.md or skills for full coverage points:`);
|
|
2172
|
+
parts.push(allDeps.join(", "));
|
|
2173
|
+
}
|
|
1986
2174
|
if (prompt) parts.push(`
|
|
1987
2175
|
User instructions: ${prompt}`);
|
|
1988
2176
|
return parts.join("\n");
|
|
@@ -3140,11 +3328,11 @@ async function runInteractiveProviderSetup(options) {
|
|
|
3140
3328
|
|
|
3141
3329
|
// src/scoring/index.ts
|
|
3142
3330
|
import { existsSync as existsSync8 } from "fs";
|
|
3143
|
-
import { join as
|
|
3331
|
+
import { join as join8 } from "path";
|
|
3144
3332
|
|
|
3145
3333
|
// src/scoring/checks/existence.ts
|
|
3146
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as
|
|
3147
|
-
import { join as
|
|
3334
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3335
|
+
import { join as join3 } from "path";
|
|
3148
3336
|
|
|
3149
3337
|
// src/scoring/constants.ts
|
|
3150
3338
|
var POINTS_CLAUDE_MD_EXISTS = 6;
|
|
@@ -3248,100 +3436,27 @@ function computeGrade(score) {
|
|
|
3248
3436
|
}
|
|
3249
3437
|
|
|
3250
3438
|
// src/scoring/checks/coverage.ts
|
|
3251
|
-
import { readFileSync, readdirSync } from "fs";
|
|
3252
|
-
import { join } from "path";
|
|
3253
|
-
function
|
|
3254
|
-
try {
|
|
3255
|
-
return readFileSync(path26, "utf-8");
|
|
3256
|
-
} catch {
|
|
3257
|
-
return null;
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
function readJsonOrNull(path26) {
|
|
3261
|
-
const content = readFileOrNull(path26);
|
|
3262
|
-
if (!content) return null;
|
|
3439
|
+
import { readFileSync as readFileSync2, readdirSync } from "fs";
|
|
3440
|
+
import { join as join2 } from "path";
|
|
3441
|
+
function readFileOrNull2(path26) {
|
|
3263
3442
|
try {
|
|
3264
|
-
return
|
|
3443
|
+
return readFileSync2(path26, "utf-8");
|
|
3265
3444
|
} catch {
|
|
3266
3445
|
return null;
|
|
3267
3446
|
}
|
|
3268
3447
|
}
|
|
3269
|
-
function extractNpmDeps(dir) {
|
|
3270
|
-
const pkg3 = readJsonOrNull(join(dir, "package.json"));
|
|
3271
|
-
if (!pkg3) return [];
|
|
3272
|
-
const deps = {
|
|
3273
|
-
...pkg3.dependencies,
|
|
3274
|
-
...pkg3.devDependencies
|
|
3275
|
-
};
|
|
3276
|
-
const trivial = /* @__PURE__ */ new Set([
|
|
3277
|
-
"typescript",
|
|
3278
|
-
"@types/node",
|
|
3279
|
-
"tslib",
|
|
3280
|
-
"ts-node",
|
|
3281
|
-
"tsx",
|
|
3282
|
-
"prettier",
|
|
3283
|
-
"eslint",
|
|
3284
|
-
"@eslint/js",
|
|
3285
|
-
"rimraf",
|
|
3286
|
-
"cross-env",
|
|
3287
|
-
"dotenv",
|
|
3288
|
-
"nodemon",
|
|
3289
|
-
"husky",
|
|
3290
|
-
"lint-staged",
|
|
3291
|
-
"commitlint",
|
|
3292
|
-
"@commitlint/cli",
|
|
3293
|
-
"@commitlint/config-conventional"
|
|
3294
|
-
]);
|
|
3295
|
-
const trivialPatterns = [
|
|
3296
|
-
/^@rely-ai\//,
|
|
3297
|
-
/^@caliber-ai\//,
|
|
3298
|
-
/^eslint-/,
|
|
3299
|
-
/^@eslint\//,
|
|
3300
|
-
/^prettier-/,
|
|
3301
|
-
/^@typescript-eslint\//
|
|
3302
|
-
];
|
|
3303
|
-
return Object.keys(deps).filter((d) => !trivial.has(d) && !d.startsWith("@types/") && !trivialPatterns.some((p) => p.test(d))).slice(0, 30);
|
|
3304
|
-
}
|
|
3305
|
-
function extractPythonDeps(dir) {
|
|
3306
|
-
const reqTxt = readFileOrNull(join(dir, "requirements.txt"));
|
|
3307
|
-
if (reqTxt) {
|
|
3308
|
-
return reqTxt.split("\n").map((l) => l.trim().split(/[=<>!~\[]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 30);
|
|
3309
|
-
}
|
|
3310
|
-
const pyproject = readFileOrNull(join(dir, "pyproject.toml"));
|
|
3311
|
-
if (pyproject) {
|
|
3312
|
-
const depMatch = pyproject.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
3313
|
-
if (depMatch) {
|
|
3314
|
-
return depMatch[1].split("\n").map((l) => l.trim().replace(/["',]/g, "").split(/[=<>!~\[]/)[0].trim()).filter((l) => l.length > 0).slice(0, 30);
|
|
3315
|
-
}
|
|
3316
|
-
}
|
|
3317
|
-
return [];
|
|
3318
|
-
}
|
|
3319
|
-
function extractGoDeps(dir) {
|
|
3320
|
-
const goMod = readFileOrNull(join(dir, "go.mod"));
|
|
3321
|
-
if (!goMod) return [];
|
|
3322
|
-
const requireBlock = goMod.match(/require\s*\(([\s\S]*?)\)/);
|
|
3323
|
-
if (!requireBlock) return [];
|
|
3324
|
-
return requireBlock[1].split("\n").map((l) => l.trim().split(/\s/)[0]).filter((l) => l && !l.startsWith("//")).map((l) => l.split("/").pop() || l).slice(0, 30);
|
|
3325
|
-
}
|
|
3326
|
-
function extractRustDeps(dir) {
|
|
3327
|
-
const cargo = readFileOrNull(join(dir, "Cargo.toml"));
|
|
3328
|
-
if (!cargo) return [];
|
|
3329
|
-
const depSection = cargo.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/);
|
|
3330
|
-
if (!depSection) return [];
|
|
3331
|
-
return depSection[1].split("\n").map((l) => l.trim().split(/\s*=/)[0].trim()).filter((l) => l.length > 0 && !l.startsWith("#")).slice(0, 30);
|
|
3332
|
-
}
|
|
3333
3448
|
function collectAllConfigContent(dir) {
|
|
3334
3449
|
const parts = [];
|
|
3335
|
-
const claudeMd =
|
|
3450
|
+
const claudeMd = readFileOrNull2(join2(dir, "CLAUDE.md"));
|
|
3336
3451
|
if (claudeMd) parts.push(claudeMd);
|
|
3337
|
-
const cursorrules =
|
|
3452
|
+
const cursorrules = readFileOrNull2(join2(dir, ".cursorrules"));
|
|
3338
3453
|
if (cursorrules) parts.push(cursorrules);
|
|
3339
|
-
for (const skillsDir of [
|
|
3454
|
+
for (const skillsDir of [join2(dir, ".claude", "skills"), join2(dir, ".cursor", "skills")]) {
|
|
3340
3455
|
try {
|
|
3341
3456
|
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
3342
3457
|
for (const entry of entries) {
|
|
3343
3458
|
if (entry.isDirectory()) {
|
|
3344
|
-
const skill =
|
|
3459
|
+
const skill = readFileOrNull2(join2(skillsDir, entry.name, "SKILL.md"));
|
|
3345
3460
|
if (skill) parts.push(skill);
|
|
3346
3461
|
}
|
|
3347
3462
|
}
|
|
@@ -3349,10 +3464,10 @@ function collectAllConfigContent(dir) {
|
|
|
3349
3464
|
}
|
|
3350
3465
|
}
|
|
3351
3466
|
try {
|
|
3352
|
-
const rulesDir =
|
|
3467
|
+
const rulesDir = join2(dir, ".cursor", "rules");
|
|
3353
3468
|
const mdcFiles = readdirSync(rulesDir).filter((f) => f.endsWith(".mdc"));
|
|
3354
3469
|
for (const f of mdcFiles) {
|
|
3355
|
-
const content =
|
|
3470
|
+
const content = readFileOrNull2(join2(rulesDir, f));
|
|
3356
3471
|
if (content) parts.push(content);
|
|
3357
3472
|
}
|
|
3358
3473
|
} catch {
|
|
@@ -3400,7 +3515,7 @@ function getConfiguredMcpServers(dir) {
|
|
|
3400
3515
|
];
|
|
3401
3516
|
for (const rel of mcpFiles) {
|
|
3402
3517
|
try {
|
|
3403
|
-
const content =
|
|
3518
|
+
const content = readFileSync2(join2(dir, rel), "utf-8");
|
|
3404
3519
|
const parsed = JSON.parse(content);
|
|
3405
3520
|
const mcpServers = parsed.mcpServers;
|
|
3406
3521
|
if (mcpServers) {
|
|
@@ -3516,7 +3631,7 @@ function hasMcpServers(dir) {
|
|
|
3516
3631
|
];
|
|
3517
3632
|
for (const rel of mcpFiles) {
|
|
3518
3633
|
try {
|
|
3519
|
-
const content =
|
|
3634
|
+
const content = readFileSync3(join3(dir, rel), "utf-8");
|
|
3520
3635
|
const parsed = JSON.parse(content);
|
|
3521
3636
|
const servers = parsed.mcpServers;
|
|
3522
3637
|
if (servers && Object.keys(servers).length > 0) {
|
|
@@ -3530,7 +3645,7 @@ function hasMcpServers(dir) {
|
|
|
3530
3645
|
}
|
|
3531
3646
|
function checkExistence(dir) {
|
|
3532
3647
|
const checks = [];
|
|
3533
|
-
const claudeMdExists = existsSync3(
|
|
3648
|
+
const claudeMdExists = existsSync3(join3(dir, "CLAUDE.md"));
|
|
3534
3649
|
checks.push({
|
|
3535
3650
|
id: "claude_md_exists",
|
|
3536
3651
|
name: "CLAUDE.md exists",
|
|
@@ -3541,8 +3656,8 @@ function checkExistence(dir) {
|
|
|
3541
3656
|
detail: claudeMdExists ? "Found at project root" : "Not found",
|
|
3542
3657
|
suggestion: claudeMdExists ? void 0 : "Create a CLAUDE.md with project context and commands"
|
|
3543
3658
|
});
|
|
3544
|
-
const hasCursorrules = existsSync3(
|
|
3545
|
-
const cursorRulesDir = existsSync3(
|
|
3659
|
+
const hasCursorrules = existsSync3(join3(dir, ".cursorrules"));
|
|
3660
|
+
const cursorRulesDir = existsSync3(join3(dir, ".cursor", "rules"));
|
|
3546
3661
|
const cursorRulesExist = hasCursorrules || cursorRulesDir;
|
|
3547
3662
|
checks.push({
|
|
3548
3663
|
id: "cursor_rules_exist",
|
|
@@ -3554,7 +3669,7 @@ function checkExistence(dir) {
|
|
|
3554
3669
|
detail: hasCursorrules ? ".cursorrules found" : cursorRulesDir ? ".cursor/rules/ found" : "No Cursor rules",
|
|
3555
3670
|
suggestion: cursorRulesExist ? void 0 : "Add .cursor/rules/ for Cursor users on your team"
|
|
3556
3671
|
});
|
|
3557
|
-
const agentsMdExists = existsSync3(
|
|
3672
|
+
const agentsMdExists = existsSync3(join3(dir, "AGENTS.md"));
|
|
3558
3673
|
checks.push({
|
|
3559
3674
|
id: "codex_agents_md_exists",
|
|
3560
3675
|
name: "AGENTS.md exists",
|
|
@@ -3565,8 +3680,8 @@ function checkExistence(dir) {
|
|
|
3565
3680
|
detail: agentsMdExists ? "Found at project root" : "Not found",
|
|
3566
3681
|
suggestion: agentsMdExists ? void 0 : "Create AGENTS.md with project context for Codex"
|
|
3567
3682
|
});
|
|
3568
|
-
const claudeSkills = countFiles(
|
|
3569
|
-
const codexSkills = countFiles(
|
|
3683
|
+
const claudeSkills = countFiles(join3(dir, ".claude", "skills"), /\.(md|SKILL\.md)$/);
|
|
3684
|
+
const codexSkills = countFiles(join3(dir, ".agents", "skills"), /SKILL\.md$/);
|
|
3570
3685
|
const skillCount = claudeSkills.length + codexSkills.length;
|
|
3571
3686
|
const skillBase = skillCount >= 1 ? POINTS_SKILLS_EXIST : 0;
|
|
3572
3687
|
const skillBonus = Math.min((skillCount - 1) * POINTS_SKILLS_BONUS_PER_EXTRA, POINTS_SKILLS_BONUS_CAP);
|
|
@@ -3582,7 +3697,7 @@ function checkExistence(dir) {
|
|
|
3582
3697
|
detail: skillCount === 0 ? "No skills found" : `${skillCount} skill${skillCount === 1 ? "" : "s"} found`,
|
|
3583
3698
|
suggestion: skillCount === 0 ? "Add .claude/skills/ with project-specific workflows" : skillCount < 3 ? "Optimal is 2-3 focused skills (SkillsBench research)" : void 0
|
|
3584
3699
|
});
|
|
3585
|
-
const mdcFiles = countFiles(
|
|
3700
|
+
const mdcFiles = countFiles(join3(dir, ".cursor", "rules"), /\.mdc$/);
|
|
3586
3701
|
const mdcCount = mdcFiles.length;
|
|
3587
3702
|
checks.push({
|
|
3588
3703
|
id: "cursor_mdc_rules",
|
|
@@ -3624,11 +3739,11 @@ function checkExistence(dir) {
|
|
|
3624
3739
|
}
|
|
3625
3740
|
|
|
3626
3741
|
// src/scoring/checks/quality.ts
|
|
3627
|
-
import { readFileSync as
|
|
3628
|
-
import { join as
|
|
3629
|
-
function
|
|
3742
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3743
|
+
import { join as join4 } from "path";
|
|
3744
|
+
function readFileOrNull3(path26) {
|
|
3630
3745
|
try {
|
|
3631
|
-
return
|
|
3746
|
+
return readFileSync4(path26, "utf-8");
|
|
3632
3747
|
} catch {
|
|
3633
3748
|
return null;
|
|
3634
3749
|
}
|
|
@@ -3638,9 +3753,9 @@ function countLines(content) {
|
|
|
3638
3753
|
}
|
|
3639
3754
|
function checkQuality(dir) {
|
|
3640
3755
|
const checks = [];
|
|
3641
|
-
const claudeMd =
|
|
3642
|
-
const cursorrules =
|
|
3643
|
-
const agentsMd =
|
|
3756
|
+
const claudeMd = readFileOrNull3(join4(dir, "CLAUDE.md"));
|
|
3757
|
+
const cursorrules = readFileOrNull3(join4(dir, ".cursorrules"));
|
|
3758
|
+
const agentsMd = readFileOrNull3(join4(dir, "AGENTS.md"));
|
|
3644
3759
|
const allContent = [claudeMd, cursorrules, agentsMd].filter(Boolean);
|
|
3645
3760
|
const combinedContent = allContent.join("\n");
|
|
3646
3761
|
const primaryInstructions = claudeMd ?? agentsMd;
|
|
@@ -3779,17 +3894,17 @@ function checkQuality(dir) {
|
|
|
3779
3894
|
}
|
|
3780
3895
|
|
|
3781
3896
|
// src/scoring/checks/accuracy.ts
|
|
3782
|
-
import { existsSync as existsSync5, readFileSync as
|
|
3783
|
-
import { join as
|
|
3784
|
-
function
|
|
3897
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync } from "fs";
|
|
3898
|
+
import { join as join5 } from "path";
|
|
3899
|
+
function readFileOrNull4(path26) {
|
|
3785
3900
|
try {
|
|
3786
|
-
return
|
|
3901
|
+
return readFileSync5(path26, "utf-8");
|
|
3787
3902
|
} catch {
|
|
3788
3903
|
return null;
|
|
3789
3904
|
}
|
|
3790
3905
|
}
|
|
3791
3906
|
function readJsonOrNull2(path26) {
|
|
3792
|
-
const content =
|
|
3907
|
+
const content = readFileOrNull4(path26);
|
|
3793
3908
|
if (!content) return null;
|
|
3794
3909
|
try {
|
|
3795
3910
|
return JSON.parse(content);
|
|
@@ -3798,12 +3913,12 @@ function readJsonOrNull2(path26) {
|
|
|
3798
3913
|
}
|
|
3799
3914
|
}
|
|
3800
3915
|
function getPackageScripts(dir) {
|
|
3801
|
-
const pkg3 = readJsonOrNull2(
|
|
3916
|
+
const pkg3 = readJsonOrNull2(join5(dir, "package.json"));
|
|
3802
3917
|
if (!pkg3?.scripts) return /* @__PURE__ */ new Set();
|
|
3803
3918
|
return new Set(Object.keys(pkg3.scripts));
|
|
3804
3919
|
}
|
|
3805
3920
|
function validateDocumentedCommands(dir) {
|
|
3806
|
-
const claudeMd =
|
|
3921
|
+
const claudeMd = readFileOrNull4(join5(dir, "CLAUDE.md"));
|
|
3807
3922
|
if (!claudeMd) return { valid: [], invalid: [], total: 0 };
|
|
3808
3923
|
const scripts = getPackageScripts(dir);
|
|
3809
3924
|
const valid = [];
|
|
@@ -3838,8 +3953,8 @@ function validateDocumentedCommands(dir) {
|
|
|
3838
3953
|
valid.push(match[0]);
|
|
3839
3954
|
}
|
|
3840
3955
|
const makePattern = /make\s+(\S+)/g;
|
|
3841
|
-
if (existsSync5(
|
|
3842
|
-
const makefile =
|
|
3956
|
+
if (existsSync5(join5(dir, "Makefile"))) {
|
|
3957
|
+
const makefile = readFileOrNull4(join5(dir, "Makefile"));
|
|
3843
3958
|
const makeTargets = /* @__PURE__ */ new Set();
|
|
3844
3959
|
if (makefile) {
|
|
3845
3960
|
for (const line of makefile.split("\n")) {
|
|
@@ -3861,7 +3976,7 @@ function validateDocumentedCommands(dir) {
|
|
|
3861
3976
|
return { valid, invalid, total: valid.length + invalid.length };
|
|
3862
3977
|
}
|
|
3863
3978
|
function validateDocumentedPaths(dir) {
|
|
3864
|
-
const claudeMd =
|
|
3979
|
+
const claudeMd = readFileOrNull4(join5(dir, "CLAUDE.md"));
|
|
3865
3980
|
if (!claudeMd) return { valid: [], invalid: [], total: 0 };
|
|
3866
3981
|
const valid = [];
|
|
3867
3982
|
const invalid = [];
|
|
@@ -3872,7 +3987,7 @@ function validateDocumentedPaths(dir) {
|
|
|
3872
3987
|
const filePath = match[1];
|
|
3873
3988
|
if (seen.has(filePath)) continue;
|
|
3874
3989
|
seen.add(filePath);
|
|
3875
|
-
if (existsSync5(
|
|
3990
|
+
if (existsSync5(join5(dir, filePath))) {
|
|
3876
3991
|
valid.push(filePath);
|
|
3877
3992
|
} else {
|
|
3878
3993
|
invalid.push(filePath);
|
|
@@ -3884,13 +3999,13 @@ function detectConfigDrift(dir) {
|
|
|
3884
3999
|
const srcDirs = ["src", "lib", "app", "cmd", "internal", "pages", "components"];
|
|
3885
4000
|
let latestSrcMtime = 0;
|
|
3886
4001
|
for (const srcDir of srcDirs) {
|
|
3887
|
-
const fullPath =
|
|
4002
|
+
const fullPath = join5(dir, srcDir);
|
|
3888
4003
|
if (!existsSync5(fullPath)) continue;
|
|
3889
4004
|
try {
|
|
3890
4005
|
const files = readdirSync3(fullPath, { recursive: true }).map(String).filter((f) => /\.(ts|js|tsx|jsx|py|go|rs|java|rb)$/.test(f));
|
|
3891
4006
|
for (const file of files.slice(0, 100)) {
|
|
3892
4007
|
try {
|
|
3893
|
-
const stat = statSync(
|
|
4008
|
+
const stat = statSync(join5(fullPath, file));
|
|
3894
4009
|
if (stat.mtime.getTime() > latestSrcMtime) {
|
|
3895
4010
|
latestSrcMtime = stat.mtime.getTime();
|
|
3896
4011
|
}
|
|
@@ -3904,7 +4019,7 @@ function detectConfigDrift(dir) {
|
|
|
3904
4019
|
let latestConfigMtime = 0;
|
|
3905
4020
|
for (const configFile of configFiles) {
|
|
3906
4021
|
try {
|
|
3907
|
-
const stat = statSync(
|
|
4022
|
+
const stat = statSync(join5(dir, configFile));
|
|
3908
4023
|
if (stat.mtime.getTime() > latestConfigMtime) {
|
|
3909
4024
|
latestConfigMtime = stat.mtime.getTime();
|
|
3910
4025
|
}
|
|
@@ -3970,11 +4085,11 @@ function checkAccuracy(dir) {
|
|
|
3970
4085
|
}
|
|
3971
4086
|
|
|
3972
4087
|
// src/scoring/checks/freshness.ts
|
|
3973
|
-
import { existsSync as existsSync6, readFileSync as
|
|
3974
|
-
import { join as
|
|
3975
|
-
function
|
|
4088
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
4089
|
+
import { join as join6 } from "path";
|
|
4090
|
+
function readFileOrNull5(path26) {
|
|
3976
4091
|
try {
|
|
3977
|
-
return
|
|
4092
|
+
return readFileSync6(path26, "utf-8");
|
|
3978
4093
|
} catch {
|
|
3979
4094
|
return null;
|
|
3980
4095
|
}
|
|
@@ -3991,8 +4106,8 @@ function daysSinceModified(filePath) {
|
|
|
3991
4106
|
}
|
|
3992
4107
|
function checkFreshness(dir) {
|
|
3993
4108
|
const checks = [];
|
|
3994
|
-
const claudeMdPath =
|
|
3995
|
-
const agentsMdPath =
|
|
4109
|
+
const claudeMdPath = join6(dir, "CLAUDE.md");
|
|
4110
|
+
const agentsMdPath = join6(dir, "AGENTS.md");
|
|
3996
4111
|
const primaryPath = existsSync6(claudeMdPath) ? claudeMdPath : agentsMdPath;
|
|
3997
4112
|
const primaryName = existsSync6(claudeMdPath) ? "CLAUDE.md" : "AGENTS.md";
|
|
3998
4113
|
const daysOld = daysSinceModified(primaryPath);
|
|
@@ -4026,7 +4141,7 @@ function checkFreshness(dir) {
|
|
|
4026
4141
|
];
|
|
4027
4142
|
const secretFindings = [];
|
|
4028
4143
|
for (const rel of filesToScan) {
|
|
4029
|
-
const content =
|
|
4144
|
+
const content = readFileOrNull5(join6(dir, rel));
|
|
4030
4145
|
if (!content) continue;
|
|
4031
4146
|
const lines = content.split("\n");
|
|
4032
4147
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4054,10 +4169,10 @@ function checkFreshness(dir) {
|
|
|
4054
4169
|
detail: hasSecrets ? `${secretFindings.length} potential secret${secretFindings.length === 1 ? "" : "s"} found in ${secretFindings[0].file}:${secretFindings[0].line}` : "No secrets detected",
|
|
4055
4170
|
suggestion: hasSecrets ? `Remove secrets from ${secretFindings[0].file}:${secretFindings[0].line} \u2014 use environment variables instead` : void 0
|
|
4056
4171
|
});
|
|
4057
|
-
const settingsPath =
|
|
4172
|
+
const settingsPath = join6(dir, ".claude", "settings.json");
|
|
4058
4173
|
let hasPermissions = false;
|
|
4059
4174
|
let permissionDetail = "";
|
|
4060
|
-
const settingsContent =
|
|
4175
|
+
const settingsContent = readFileOrNull5(settingsPath);
|
|
4061
4176
|
if (settingsContent) {
|
|
4062
4177
|
try {
|
|
4063
4178
|
const settings = JSON.parse(settingsContent);
|
|
@@ -4085,12 +4200,12 @@ function checkFreshness(dir) {
|
|
|
4085
4200
|
}
|
|
4086
4201
|
|
|
4087
4202
|
// src/scoring/checks/bonus.ts
|
|
4088
|
-
import { existsSync as existsSync7, readFileSync as
|
|
4203
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
4089
4204
|
import { execSync as execSync8 } from "child_process";
|
|
4090
|
-
import { join as
|
|
4091
|
-
function
|
|
4205
|
+
import { join as join7 } from "path";
|
|
4206
|
+
function readFileOrNull6(path26) {
|
|
4092
4207
|
try {
|
|
4093
|
-
return
|
|
4208
|
+
return readFileSync7(path26, "utf-8");
|
|
4094
4209
|
} catch {
|
|
4095
4210
|
return null;
|
|
4096
4211
|
}
|
|
@@ -4098,8 +4213,8 @@ function readFileOrNull5(path26) {
|
|
|
4098
4213
|
function hasPreCommitHook(dir) {
|
|
4099
4214
|
try {
|
|
4100
4215
|
const gitDir = execSync8("git rev-parse --git-dir", { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
4101
|
-
const hookPath =
|
|
4102
|
-
const content =
|
|
4216
|
+
const hookPath = join7(gitDir, "hooks", "pre-commit");
|
|
4217
|
+
const content = readFileOrNull6(hookPath);
|
|
4103
4218
|
return content ? content.includes("caliber") : false;
|
|
4104
4219
|
} catch {
|
|
4105
4220
|
return false;
|
|
@@ -4110,7 +4225,7 @@ function checkBonus(dir) {
|
|
|
4110
4225
|
let hasClaudeHooks = false;
|
|
4111
4226
|
let hasPrecommit = false;
|
|
4112
4227
|
const hookSources = [];
|
|
4113
|
-
const settingsContent =
|
|
4228
|
+
const settingsContent = readFileOrNull6(join7(dir, ".claude", "settings.json"));
|
|
4114
4229
|
if (settingsContent) {
|
|
4115
4230
|
try {
|
|
4116
4231
|
const settings = JSON.parse(settingsContent);
|
|
@@ -4138,7 +4253,7 @@ function checkBonus(dir) {
|
|
|
4138
4253
|
detail: hookDetail,
|
|
4139
4254
|
suggestion: hasHooks ? void 0 : "Run `caliber hooks --install` for auto-refresh"
|
|
4140
4255
|
});
|
|
4141
|
-
const agentsMdExists = existsSync7(
|
|
4256
|
+
const agentsMdExists = existsSync7(join7(dir, "AGENTS.md"));
|
|
4142
4257
|
checks.push({
|
|
4143
4258
|
id: "agents_md_exists",
|
|
4144
4259
|
name: "AGENTS.md exists",
|
|
@@ -4149,14 +4264,14 @@ function checkBonus(dir) {
|
|
|
4149
4264
|
detail: agentsMdExists ? "Found at project root" : "Not found",
|
|
4150
4265
|
suggestion: agentsMdExists ? void 0 : "Add AGENTS.md \u2014 the emerging cross-agent standard (60k+ repos)"
|
|
4151
4266
|
});
|
|
4152
|
-
const skillsDir =
|
|
4267
|
+
const skillsDir = join7(dir, ".claude", "skills");
|
|
4153
4268
|
let openSkillsCount = 0;
|
|
4154
4269
|
let totalSkillFiles = 0;
|
|
4155
4270
|
try {
|
|
4156
4271
|
const entries = readdirSync4(skillsDir, { withFileTypes: true });
|
|
4157
4272
|
for (const entry of entries) {
|
|
4158
4273
|
if (entry.isDirectory()) {
|
|
4159
|
-
const skillMd =
|
|
4274
|
+
const skillMd = readFileOrNull6(join7(skillsDir, entry.name, "SKILL.md"));
|
|
4160
4275
|
if (skillMd) {
|
|
4161
4276
|
totalSkillFiles++;
|
|
4162
4277
|
if (skillMd.trimStart().startsWith("---")) {
|
|
@@ -4225,9 +4340,9 @@ function filterChecksForTarget(checks, target) {
|
|
|
4225
4340
|
}
|
|
4226
4341
|
function detectTargetAgent(dir) {
|
|
4227
4342
|
const agents = [];
|
|
4228
|
-
if (existsSync8(
|
|
4229
|
-
if (existsSync8(
|
|
4230
|
-
if (existsSync8(
|
|
4343
|
+
if (existsSync8(join8(dir, "CLAUDE.md")) || existsSync8(join8(dir, ".claude", "skills"))) agents.push("claude");
|
|
4344
|
+
if (existsSync8(join8(dir, ".cursorrules")) || existsSync8(join8(dir, ".cursor", "rules"))) agents.push("cursor");
|
|
4345
|
+
if (existsSync8(join8(dir, ".codex")) || existsSync8(join8(dir, ".agents", "skills"))) agents.push("codex");
|
|
4231
4346
|
return agents.length > 0 ? agents : ["claude"];
|
|
4232
4347
|
}
|
|
4233
4348
|
function computeLocalScore(dir, targetAgent) {
|
|
@@ -4391,8 +4506,8 @@ function displayScoreDelta(before, after) {
|
|
|
4391
4506
|
import chalk7 from "chalk";
|
|
4392
4507
|
import ora from "ora";
|
|
4393
4508
|
import select4 from "@inquirer/select";
|
|
4394
|
-
import { mkdirSync, readFileSync as
|
|
4395
|
-
import { join as
|
|
4509
|
+
import { mkdirSync, readFileSync as readFileSync8, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
|
|
4510
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
4396
4511
|
|
|
4397
4512
|
// src/scanner/index.ts
|
|
4398
4513
|
import fs21 from "fs";
|
|
@@ -4711,19 +4826,19 @@ function detectLocalPlatforms() {
|
|
|
4711
4826
|
}
|
|
4712
4827
|
function getSkillPath(platform, slug) {
|
|
4713
4828
|
if (platform === "cursor") {
|
|
4714
|
-
return
|
|
4829
|
+
return join9(".cursor", "skills", slug, "SKILL.md");
|
|
4715
4830
|
}
|
|
4716
4831
|
if (platform === "codex") {
|
|
4717
|
-
return
|
|
4832
|
+
return join9(".agents", "skills", slug, "SKILL.md");
|
|
4718
4833
|
}
|
|
4719
|
-
return
|
|
4834
|
+
return join9(".claude", "skills", slug, "SKILL.md");
|
|
4720
4835
|
}
|
|
4721
4836
|
function getInstalledSkills() {
|
|
4722
4837
|
const installed = /* @__PURE__ */ new Set();
|
|
4723
4838
|
const dirs = [
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4839
|
+
join9(process.cwd(), ".claude", "skills"),
|
|
4840
|
+
join9(process.cwd(), ".cursor", "skills"),
|
|
4841
|
+
join9(process.cwd(), ".agents", "skills")
|
|
4727
4842
|
];
|
|
4728
4843
|
for (const dir of dirs) {
|
|
4729
4844
|
try {
|
|
@@ -4924,10 +5039,10 @@ Already installed skills: ${Array.from(installed).join(", ")}`);
|
|
|
4924
5039
|
return parts.join("\n");
|
|
4925
5040
|
}
|
|
4926
5041
|
function extractTopDeps() {
|
|
4927
|
-
const pkgPath =
|
|
5042
|
+
const pkgPath = join9(process.cwd(), "package.json");
|
|
4928
5043
|
if (!existsSync9(pkgPath)) return [];
|
|
4929
5044
|
try {
|
|
4930
|
-
const pkg3 = JSON.parse(
|
|
5045
|
+
const pkg3 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
4931
5046
|
const deps = Object.keys(pkg3.dependencies ?? {});
|
|
4932
5047
|
const trivial = /* @__PURE__ */ new Set([
|
|
4933
5048
|
"typescript",
|
|
@@ -5233,7 +5348,7 @@ async function installSkills(recs, platforms, contentMap) {
|
|
|
5233
5348
|
if (!content) continue;
|
|
5234
5349
|
for (const platform of platforms) {
|
|
5235
5350
|
const skillPath = getSkillPath(platform, rec.slug);
|
|
5236
|
-
const fullPath =
|
|
5351
|
+
const fullPath = join9(process.cwd(), skillPath);
|
|
5237
5352
|
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
5238
5353
|
writeFileSync(fullPath, content, "utf-8");
|
|
5239
5354
|
installed.push(`[${platform}] ${skillPath}`);
|
|
@@ -5427,7 +5542,7 @@ async function initCommand(options) {
|
|
|
5427
5542
|
`));
|
|
5428
5543
|
if (report) {
|
|
5429
5544
|
report.markStep("Fingerprint");
|
|
5430
|
-
report.addJson("Fingerprint: Git", { remote: fingerprint.
|
|
5545
|
+
report.addJson("Fingerprint: Git", { remote: fingerprint.gitRemoteUrl, packageName: fingerprint.packageName });
|
|
5431
5546
|
report.addCodeBlock("Fingerprint: File Tree", fingerprint.fileTree.join("\n"));
|
|
5432
5547
|
report.addJson("Fingerprint: Detected Stack", { languages: fingerprint.languages, frameworks: fingerprint.frameworks, tools: fingerprint.tools });
|
|
5433
5548
|
report.addJson("Fingerprint: Existing Configs", fingerprint.existingConfigs);
|
|
@@ -5458,11 +5573,18 @@ async function initCommand(options) {
|
|
|
5458
5573
|
|
|
5459
5574
|
| Check | Passed | Points | Max |
|
|
5460
5575
|
|-------|--------|--------|-----|
|
|
5461
|
-
` + baselineScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.
|
|
5576
|
+
` + baselineScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
5462
5577
|
report.addSection("Generation: Target Agents", targetAgent.join(", "));
|
|
5463
5578
|
}
|
|
5464
5579
|
const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length || fingerprint.existingConfigs.agentsMd);
|
|
5465
|
-
const NON_LLM_CHECKS = /* @__PURE__ */ new Set([
|
|
5580
|
+
const NON_LLM_CHECKS = /* @__PURE__ */ new Set([
|
|
5581
|
+
"hooks_configured",
|
|
5582
|
+
"agents_md_exists",
|
|
5583
|
+
"permissions_configured",
|
|
5584
|
+
"mcp_servers",
|
|
5585
|
+
"service_coverage",
|
|
5586
|
+
"mcp_completeness"
|
|
5587
|
+
]);
|
|
5466
5588
|
if (hasExistingConfig && baselineScore.score === 100) {
|
|
5467
5589
|
trackInitScoreComputed(baselineScore.score, passingCount, failingCount, true);
|
|
5468
5590
|
console.log(chalk8.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
|
|
@@ -5526,6 +5648,7 @@ async function initCommand(options) {
|
|
|
5526
5648
|
genMessages.start();
|
|
5527
5649
|
let generatedSetup = null;
|
|
5528
5650
|
let rawOutput;
|
|
5651
|
+
let genStopReason;
|
|
5529
5652
|
try {
|
|
5530
5653
|
const result = await generateSetup(
|
|
5531
5654
|
fingerprint,
|
|
@@ -5551,15 +5674,18 @@ async function initCommand(options) {
|
|
|
5551
5674
|
generatedSetup = result.setup;
|
|
5552
5675
|
rawOutput = result.raw;
|
|
5553
5676
|
}
|
|
5677
|
+
genStopReason = result.stopReason;
|
|
5554
5678
|
} catch (err) {
|
|
5555
5679
|
genMessages.stop();
|
|
5556
5680
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
5557
5681
|
genSpinner.fail(`Generation failed: ${msg}`);
|
|
5682
|
+
writeErrorLog(config, void 0, msg, "exception");
|
|
5558
5683
|
throw new Error("__exit__");
|
|
5559
5684
|
}
|
|
5560
5685
|
genMessages.stop();
|
|
5561
5686
|
if (!generatedSetup) {
|
|
5562
5687
|
genSpinner.fail("Failed to generate setup.");
|
|
5688
|
+
writeErrorLog(config, rawOutput, void 0, genStopReason);
|
|
5563
5689
|
if (rawOutput) {
|
|
5564
5690
|
console.log(chalk8.dim("\nRaw LLM output (JSON parse failed):"));
|
|
5565
5691
|
console.log(chalk8.dim(rawOutput.slice(0, 500)));
|
|
@@ -5702,7 +5828,44 @@ async function initCommand(options) {
|
|
|
5702
5828
|
if (hookChoice === "skip") {
|
|
5703
5829
|
console.log(chalk8.dim(" Skipped auto-refresh hooks. Run ") + chalk8.hex("#83D1EB")("caliber hooks --install") + chalk8.dim(" later to enable."));
|
|
5704
5830
|
}
|
|
5705
|
-
|
|
5831
|
+
let afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5832
|
+
if (afterScore.score < 100) {
|
|
5833
|
+
const polishFailingChecks = afterScore.checks.filter((c) => !c.passed && c.maxPoints > 0).filter((c) => !NON_LLM_CHECKS.has(c.id));
|
|
5834
|
+
if (polishFailingChecks.length > 0) {
|
|
5835
|
+
console.log("");
|
|
5836
|
+
console.log(chalk8.dim(` Score: ${afterScore.score}/100 \u2014 polishing ${polishFailingChecks.length} remaining check${polishFailingChecks.length === 1 ? "" : "s"}...`));
|
|
5837
|
+
const polishFailing = polishFailingChecks.map((c) => ({
|
|
5838
|
+
name: c.name,
|
|
5839
|
+
suggestion: c.suggestion
|
|
5840
|
+
}));
|
|
5841
|
+
const polishPassing = afterScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
|
|
5842
|
+
try {
|
|
5843
|
+
const polishResult = await generateSetup(
|
|
5844
|
+
fingerprint,
|
|
5845
|
+
targetAgent,
|
|
5846
|
+
void 0,
|
|
5847
|
+
{
|
|
5848
|
+
onStatus: () => {
|
|
5849
|
+
},
|
|
5850
|
+
onComplete: () => {
|
|
5851
|
+
},
|
|
5852
|
+
onError: () => {
|
|
5853
|
+
}
|
|
5854
|
+
},
|
|
5855
|
+
polishFailing,
|
|
5856
|
+
afterScore.score,
|
|
5857
|
+
polishPassing
|
|
5858
|
+
);
|
|
5859
|
+
if (polishResult.setup) {
|
|
5860
|
+
const polishWriteResult = writeSetup(polishResult.setup);
|
|
5861
|
+
if (polishWriteResult.written.length > 0) {
|
|
5862
|
+
afterScore = computeLocalScore(process.cwd(), targetAgent);
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
} catch {
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5706
5869
|
if (afterScore.score < baselineScore.score) {
|
|
5707
5870
|
trackInitScoreRegression(baselineScore.score, afterScore.score);
|
|
5708
5871
|
console.log("");
|
|
@@ -5723,7 +5886,7 @@ async function initCommand(options) {
|
|
|
5723
5886
|
|
|
5724
5887
|
| Check | Passed | Points | Max |
|
|
5725
5888
|
|-------|--------|--------|-----|
|
|
5726
|
-
` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.
|
|
5889
|
+
` + afterScore.checks.map((c) => `| ${c.name} | ${c.passed ? "Yes" : "No"} | ${c.earnedPoints} | ${c.maxPoints} |`).join("\n"));
|
|
5727
5890
|
}
|
|
5728
5891
|
displayScoreDelta(baselineScore, afterScore);
|
|
5729
5892
|
console.log(title.bold("\n Step 6/6 \u2014 Community skills\n"));
|
|
@@ -5756,6 +5919,9 @@ async function initCommand(options) {
|
|
|
5756
5919
|
console.log(` ${title("caliber skills")} Discover community skills for your stack`);
|
|
5757
5920
|
console.log(` ${title("caliber undo")} Revert all changes from this run`);
|
|
5758
5921
|
console.log("");
|
|
5922
|
+
if (options.showTokens) {
|
|
5923
|
+
displayTokenUsage();
|
|
5924
|
+
}
|
|
5759
5925
|
if (report) {
|
|
5760
5926
|
report.markStep("Finished");
|
|
5761
5927
|
const reportPath = path19.join(process.cwd(), ".caliber", "debug-report.md");
|
|
@@ -6032,6 +6198,45 @@ function ensurePermissions() {
|
|
|
6032
6198
|
if (!fs24.existsSync(".claude")) fs24.mkdirSync(".claude", { recursive: true });
|
|
6033
6199
|
fs24.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6034
6200
|
}
|
|
6201
|
+
function displayTokenUsage() {
|
|
6202
|
+
const summary = getUsageSummary();
|
|
6203
|
+
if (summary.length === 0) return;
|
|
6204
|
+
console.log(chalk8.bold(" Token usage:\n"));
|
|
6205
|
+
let totalIn = 0;
|
|
6206
|
+
let totalOut = 0;
|
|
6207
|
+
for (const m of summary) {
|
|
6208
|
+
totalIn += m.inputTokens;
|
|
6209
|
+
totalOut += m.outputTokens;
|
|
6210
|
+
const cacheInfo = m.cacheReadTokens > 0 || m.cacheWriteTokens > 0 ? chalk8.dim(` (cache: ${m.cacheReadTokens.toLocaleString()} read, ${m.cacheWriteTokens.toLocaleString()} write)`) : "";
|
|
6211
|
+
console.log(` ${chalk8.dim(m.model)}: ${m.inputTokens.toLocaleString()} in / ${m.outputTokens.toLocaleString()} out (${m.calls} call${m.calls === 1 ? "" : "s"})${cacheInfo}`);
|
|
6212
|
+
}
|
|
6213
|
+
if (summary.length > 1) {
|
|
6214
|
+
console.log(` ${chalk8.dim("Total")}: ${totalIn.toLocaleString()} in / ${totalOut.toLocaleString()} out`);
|
|
6215
|
+
}
|
|
6216
|
+
console.log("");
|
|
6217
|
+
}
|
|
6218
|
+
function writeErrorLog(config, rawOutput, error, stopReason) {
|
|
6219
|
+
try {
|
|
6220
|
+
const logPath = path19.join(process.cwd(), ".caliber", "error-log.md");
|
|
6221
|
+
const lines = [
|
|
6222
|
+
`# Generation Error \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
6223
|
+
"",
|
|
6224
|
+
`**Provider**: ${config.provider}`,
|
|
6225
|
+
`**Model**: ${config.model}`,
|
|
6226
|
+
`**Stop reason**: ${stopReason || "unknown"}`,
|
|
6227
|
+
""
|
|
6228
|
+
];
|
|
6229
|
+
if (error) {
|
|
6230
|
+
lines.push("## Error", "```", error, "```", "");
|
|
6231
|
+
}
|
|
6232
|
+
lines.push("## Raw LLM Output", "```", rawOutput || "(empty)", "```");
|
|
6233
|
+
fs24.mkdirSync(path19.join(process.cwd(), ".caliber"), { recursive: true });
|
|
6234
|
+
fs24.writeFileSync(logPath, lines.join("\n"));
|
|
6235
|
+
console.log(chalk8.dim(`
|
|
6236
|
+
Error log written to .caliber/error-log.md`));
|
|
6237
|
+
} catch {
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6035
6240
|
|
|
6036
6241
|
// src/commands/undo.ts
|
|
6037
6242
|
import chalk9 from "chalk";
|
|
@@ -7196,7 +7401,7 @@ function parseAgentOption(value) {
|
|
|
7196
7401
|
}
|
|
7197
7402
|
return agents;
|
|
7198
7403
|
}
|
|
7199
|
-
program.command("init").description("Initialize 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").option("--debug-report", void 0, false).action(tracked("init", initCommand));
|
|
7404
|
+
program.command("init").description("Initialize 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").option("--debug-report", void 0, false).option("--show-tokens", "Show token usage summary at the end").action(tracked("init", initCommand));
|
|
7200
7405
|
program.command("undo").description("Revert all config changes made by Caliber").action(tracked("undo", undoCommand));
|
|
7201
7406
|
program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(tracked("status", statusCommand));
|
|
7202
7407
|
program.command("regenerate").alias("regen").alias("re").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(tracked("regenerate", regenerateCommand));
|
package/package.json
CHANGED