@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.
Files changed (2) hide show
  1. package/dist/bin.js +370 -165
  2. 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
- stopReason = message.stop_reason;
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
- stopReason = message.stop_reason;
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: mention the project's actual dependencies by name in CLAUDE.md or skills. Reference the key packages from package.json/requirements.txt/go.mod. The scoring checks whether each non-trivial dependency name appears somewhere in your output. Aim for >80% coverage.
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 join7 } from "path";
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 readFileSync2 } from "fs";
3147
- import { join as join2 } from "path";
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 readFileOrNull(path26) {
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 JSON.parse(content);
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 = readFileOrNull(join(dir, "CLAUDE.md"));
3450
+ const claudeMd = readFileOrNull2(join2(dir, "CLAUDE.md"));
3336
3451
  if (claudeMd) parts.push(claudeMd);
3337
- const cursorrules = readFileOrNull(join(dir, ".cursorrules"));
3452
+ const cursorrules = readFileOrNull2(join2(dir, ".cursorrules"));
3338
3453
  if (cursorrules) parts.push(cursorrules);
3339
- for (const skillsDir of [join(dir, ".claude", "skills"), join(dir, ".cursor", "skills")]) {
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 = readFileOrNull(join(skillsDir, entry.name, "SKILL.md"));
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 = join(dir, ".cursor", "rules");
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 = readFileOrNull(join(rulesDir, f));
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 = readFileSync(join(dir, rel), "utf-8");
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 = readFileSync2(join2(dir, rel), "utf-8");
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(join2(dir, "CLAUDE.md"));
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(join2(dir, ".cursorrules"));
3545
- const cursorRulesDir = existsSync3(join2(dir, ".cursor", "rules"));
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(join2(dir, "AGENTS.md"));
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(join2(dir, ".claude", "skills"), /\.(md|SKILL\.md)$/);
3569
- const codexSkills = countFiles(join2(dir, ".agents", "skills"), /SKILL\.md$/);
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(join2(dir, ".cursor", "rules"), /\.mdc$/);
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 readFileSync3 } from "fs";
3628
- import { join as join3 } from "path";
3629
- function readFileOrNull2(path26) {
3742
+ import { readFileSync as readFileSync4 } from "fs";
3743
+ import { join as join4 } from "path";
3744
+ function readFileOrNull3(path26) {
3630
3745
  try {
3631
- return readFileSync3(path26, "utf-8");
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 = readFileOrNull2(join3(dir, "CLAUDE.md"));
3642
- const cursorrules = readFileOrNull2(join3(dir, ".cursorrules"));
3643
- const agentsMd = readFileOrNull2(join3(dir, "AGENTS.md"));
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 readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
3783
- import { join as join4 } from "path";
3784
- function readFileOrNull3(path26) {
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 readFileSync4(path26, "utf-8");
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 = readFileOrNull3(path26);
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(join4(dir, "package.json"));
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 = readFileOrNull3(join4(dir, "CLAUDE.md"));
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(join4(dir, "Makefile"))) {
3842
- const makefile = readFileOrNull3(join4(dir, "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 = readFileOrNull3(join4(dir, "CLAUDE.md"));
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(join4(dir, filePath))) {
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 = join4(dir, srcDir);
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(join4(fullPath, file));
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(join4(dir, configFile));
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 readFileSync5, statSync as statSync2 } from "fs";
3974
- import { join as join5 } from "path";
3975
- function readFileOrNull4(path26) {
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 readFileSync5(path26, "utf-8");
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 = join5(dir, "CLAUDE.md");
3995
- const agentsMdPath = join5(dir, "AGENTS.md");
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 = readFileOrNull4(join5(dir, rel));
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 = join5(dir, ".claude", "settings.json");
4172
+ const settingsPath = join6(dir, ".claude", "settings.json");
4058
4173
  let hasPermissions = false;
4059
4174
  let permissionDetail = "";
4060
- const settingsContent = readFileOrNull4(settingsPath);
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 readFileSync6, readdirSync as readdirSync4 } from "fs";
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 join6 } from "path";
4091
- function readFileOrNull5(path26) {
4205
+ import { join as join7 } from "path";
4206
+ function readFileOrNull6(path26) {
4092
4207
  try {
4093
- return readFileSync6(path26, "utf-8");
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 = join6(gitDir, "hooks", "pre-commit");
4102
- const content = readFileOrNull5(hookPath);
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 = readFileOrNull5(join6(dir, ".claude", "settings.json"));
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(join6(dir, "AGENTS.md"));
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 = join6(dir, ".claude", "skills");
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 = readFileOrNull5(join6(skillsDir, entry.name, "SKILL.md"));
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(join7(dir, "CLAUDE.md")) || existsSync8(join7(dir, ".claude", "skills"))) agents.push("claude");
4229
- if (existsSync8(join7(dir, ".cursorrules")) || existsSync8(join7(dir, ".cursor", "rules"))) agents.push("cursor");
4230
- if (existsSync8(join7(dir, ".codex")) || existsSync8(join7(dir, ".agents", "skills"))) agents.push("codex");
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 readFileSync7, readdirSync as readdirSync5, existsSync as existsSync9, writeFileSync } from "fs";
4395
- import { join as join8, dirname as dirname2 } from "path";
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 join8(".cursor", "skills", slug, "SKILL.md");
4829
+ return join9(".cursor", "skills", slug, "SKILL.md");
4715
4830
  }
4716
4831
  if (platform === "codex") {
4717
- return join8(".agents", "skills", slug, "SKILL.md");
4832
+ return join9(".agents", "skills", slug, "SKILL.md");
4718
4833
  }
4719
- return join8(".claude", "skills", slug, "SKILL.md");
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
- join8(process.cwd(), ".claude", "skills"),
4725
- join8(process.cwd(), ".cursor", "skills"),
4726
- join8(process.cwd(), ".agents", "skills")
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 = join8(process.cwd(), "package.json");
5042
+ const pkgPath = join9(process.cwd(), "package.json");
4928
5043
  if (!existsSync9(pkgPath)) return [];
4929
5044
  try {
4930
- const pkg3 = JSON.parse(readFileSync7(pkgPath, "utf-8"));
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 = join8(process.cwd(), skillPath);
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.remote, packageName: fingerprint.packageName });
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.points} | ${c.maxPoints} |`).join("\n"));
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(["hooks_configured", "agents_md_exists", "permissions_configured", "mcp_servers"]);
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
- const afterScore = computeLocalScore(process.cwd(), targetAgent);
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.points} | ${c.maxPoints} |`).join("\n"));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.7.9",
3
+ "version": "1.9.0",
4
4
  "description": "Analyze your codebase and generate optimized AI agent configs (CLAUDE.md, .cursorrules, skills) — no API key needed",
5
5
  "type": "module",
6
6
  "bin": {