@rely-ai/caliber 0.8.0 → 0.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 +212 -39
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -3763,19 +3763,16 @@ function displayScoreSummary(result) {
3763
3763
  console.log(
3764
3764
  chalk3.gray(" ") + gc(`${result.score}/${result.maxScore}`) + chalk3.gray(` (Grade ${result.grade})`) + chalk3.gray(` \xB7 ${agentLabel}`) + chalk3.gray(` \xB7 ${progressBar(result.score, result.maxScore, 20)}`)
3765
3765
  );
3766
- const failing = result.checks.filter((c) => !c.passed && c.suggestion);
3766
+ const failing = result.checks.filter((c) => !c.passed);
3767
3767
  if (failing.length > 0) {
3768
- const shown = failing.slice(0, 4);
3768
+ const shown = failing.slice(0, 5);
3769
3769
  for (const check of shown) {
3770
- console.log(chalk3.gray(` \u2717 ${check.name}`) + chalk3.dim(` \u2014 ${check.suggestion.slice(0, 60)}`));
3770
+ console.log(chalk3.gray(` \u2717 ${check.name}`));
3771
3771
  }
3772
- if (failing.length > shown.length) {
3773
- console.log(chalk3.dim(` \u2026 and ${failing.length - shown.length} more \u2014 run ${chalk3.reset("caliber score")} for details`));
3774
- }
3775
- }
3776
- if (failing.length > 0) {
3772
+ const remaining = failing.length - shown.length;
3773
+ const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
3777
3774
  console.log(chalk3.dim(`
3778
- Run ${chalk3.reset("caliber score")} for the full breakdown.`));
3775
+ Run ${chalk3.reset("caliber score")} for details.${moreText}`));
3779
3776
  }
3780
3777
  console.log("");
3781
3778
  }
@@ -3968,6 +3965,11 @@ async function initCommand(options) {
3968
3965
  const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
3969
3966
  genSpinner.succeed(`Setup generated ${chalk4.dim(`in ${timeStr}`)}`);
3970
3967
  printSetupSummary(generatedSetup);
3968
+ const sessionHistory = [];
3969
+ sessionHistory.push({
3970
+ role: "assistant",
3971
+ content: summarizeSetup("Initial generation", generatedSetup)
3972
+ });
3971
3973
  console.log(title.bold(" Step 4/4 \u2014 Review\n"));
3972
3974
  const setupFiles = collectSetupFiles(generatedSetup);
3973
3975
  const staged = stageFiles(setupFiles, process.cwd());
@@ -3976,11 +3978,11 @@ async function initCommand(options) {
3976
3978
  const wantsReview = await promptWantsReview();
3977
3979
  if (wantsReview) {
3978
3980
  const reviewMethod = await promptReviewMethod();
3979
- openReview(reviewMethod, staged.stagedFiles);
3981
+ await openReview(reviewMethod, staged.stagedFiles);
3980
3982
  }
3981
3983
  let action = await promptReviewAction();
3982
3984
  while (action === "refine") {
3983
- generatedSetup = await refineLoop(generatedSetup, targetAgent);
3985
+ generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
3984
3986
  if (!generatedSetup) {
3985
3987
  cleanupStaging();
3986
3988
  console.log(chalk4.dim("Refinement cancelled. No files were modified."));
@@ -3991,11 +3993,7 @@ async function initCommand(options) {
3991
3993
  console.log(chalk4.dim(` ${chalk4.green(`${restaged.newFiles} new`)} / ${chalk4.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
3992
3994
  `));
3993
3995
  printSetupSummary(generatedSetup);
3994
- const wantsReviewAgain = await promptWantsReview();
3995
- if (wantsReviewAgain) {
3996
- const reviewMethod = await promptReviewMethod();
3997
- openReview(reviewMethod, restaged.stagedFiles);
3998
- }
3996
+ await openReview("terminal", restaged.stagedFiles);
3999
3997
  action = await promptReviewAction();
4000
3998
  }
4001
3999
  cleanupStaging();
@@ -4095,8 +4093,7 @@ async function initCommand(options) {
4095
4093
  console.log(` ${title("caliber undo")} Revert all changes from this run`);
4096
4094
  console.log("");
4097
4095
  }
4098
- async function refineLoop(currentSetup, _targetAgent) {
4099
- const history = [];
4096
+ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
4100
4097
  while (true) {
4101
4098
  const message = await promptInput2("\nWhat would you like to change?");
4102
4099
  if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
@@ -4105,19 +4102,29 @@ async function refineLoop(currentSetup, _targetAgent) {
4105
4102
  if (message.toLowerCase() === "cancel") {
4106
4103
  return null;
4107
4104
  }
4105
+ const isValid = await classifyRefineIntent(message);
4106
+ if (!isValid) {
4107
+ console.log(chalk4.dim(" This doesn't look like a config change request."));
4108
+ console.log(chalk4.dim(" Describe what to add, remove, or modify in your configs."));
4109
+ console.log(chalk4.dim(' Type "done" to accept the current setup.\n'));
4110
+ continue;
4111
+ }
4108
4112
  const refineSpinner = ora("Refining setup...").start();
4109
4113
  const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
4110
4114
  refineMessages.start();
4111
4115
  const refined = await refineSetup(
4112
4116
  currentSetup,
4113
4117
  message,
4114
- history
4118
+ sessionHistory
4115
4119
  );
4116
4120
  refineMessages.stop();
4117
4121
  if (refined) {
4118
4122
  currentSetup = refined;
4119
- history.push({ role: "user", content: message });
4120
- history.push({ role: "assistant", content: JSON.stringify(refined) });
4123
+ sessionHistory.push({ role: "user", content: message });
4124
+ sessionHistory.push({
4125
+ role: "assistant",
4126
+ content: summarizeSetup("Applied changes", refined)
4127
+ });
4121
4128
  refineSpinner.succeed("Setup updated");
4122
4129
  printSetupSummary(refined);
4123
4130
  console.log(chalk4.dim('Type "done" to accept, or describe more changes.'));
@@ -4127,6 +4134,29 @@ async function refineLoop(currentSetup, _targetAgent) {
4127
4134
  }
4128
4135
  }
4129
4136
  }
4137
+ function summarizeSetup(action, setup) {
4138
+ const descriptions = setup.fileDescriptions;
4139
+ const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4140
+ return `${action}. Files:
4141
+ ${files}`;
4142
+ }
4143
+ async function classifyRefineIntent(message) {
4144
+ const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4145
+ try {
4146
+ const result = await llmJsonCall({
4147
+ system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
4148
+ Valid: requests to add, remove, change, or restructure config content. Examples: "add testing commands", "remove the terraform section", "make CLAUDE.md shorter".
4149
+ Invalid: questions, requests to show/display something, general chat, or anything that isn't a concrete config change.
4150
+ Return {"valid": true} or {"valid": false}. Nothing else.`,
4151
+ prompt: message,
4152
+ maxTokens: 20,
4153
+ ...fastModel ? { model: fastModel } : {}
4154
+ });
4155
+ return result.valid === true;
4156
+ } catch {
4157
+ return true;
4158
+ }
4159
+ }
4130
4160
  function promptInput2(question) {
4131
4161
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
4132
4162
  return new Promise((resolve2) => {
@@ -4186,34 +4216,177 @@ async function promptReviewMethod() {
4186
4216
  });
4187
4217
  return select2({ message: "How would you like to review the changes?", choices });
4188
4218
  }
4189
- function openReview(method, stagedFiles) {
4219
+ async function openReview(method, stagedFiles) {
4190
4220
  if (method === "cursor" || method === "vscode") {
4191
4221
  openDiffsInEditor(method, stagedFiles.map((f) => ({
4192
4222
  originalPath: f.originalPath,
4193
4223
  proposedPath: f.proposedPath
4194
4224
  })));
4195
4225
  console.log(chalk4.dim(" Diffs opened in your editor.\n"));
4196
- } else {
4197
- for (const file of stagedFiles) {
4198
- if (file.currentPath) {
4199
- const currentLines = fs17.readFileSync(file.currentPath, "utf-8").split("\n");
4200
- const proposedLines = fs17.readFileSync(file.proposedPath, "utf-8").split("\n");
4201
- const patch = createTwoFilesPatch(file.relativePath, file.relativePath, currentLines.join("\n"), proposedLines.join("\n"));
4202
- let added = 0, removed = 0;
4203
- for (const line of patch.split("\n")) {
4204
- if (line.startsWith("+") && !line.startsWith("+++")) added++;
4205
- if (line.startsWith("-") && !line.startsWith("---")) removed++;
4206
- }
4207
- console.log(` ${chalk4.yellow("~")} ${file.relativePath} ${chalk4.green(`+${added}`)} ${chalk4.red(`-${removed}`)}`);
4226
+ return;
4227
+ }
4228
+ const fileInfos = stagedFiles.map((file) => {
4229
+ const proposed = fs17.readFileSync(file.proposedPath, "utf-8");
4230
+ const current = file.currentPath ? fs17.readFileSync(file.currentPath, "utf-8") : "";
4231
+ const patch = createTwoFilesPatch(
4232
+ file.isNew ? "/dev/null" : file.relativePath,
4233
+ file.relativePath,
4234
+ current,
4235
+ proposed
4236
+ );
4237
+ let added = 0, removed = 0;
4238
+ for (const line of patch.split("\n")) {
4239
+ if (line.startsWith("+") && !line.startsWith("+++")) added++;
4240
+ if (line.startsWith("-") && !line.startsWith("---")) removed++;
4241
+ }
4242
+ return {
4243
+ relativePath: file.relativePath,
4244
+ isNew: file.isNew,
4245
+ added,
4246
+ removed,
4247
+ lines: proposed.split("\n").length,
4248
+ patch
4249
+ };
4250
+ });
4251
+ await interactiveDiffExplorer(fileInfos);
4252
+ }
4253
+ async function interactiveDiffExplorer(files) {
4254
+ if (!process.stdin.isTTY) {
4255
+ for (const f of files) {
4256
+ const icon = f.isNew ? chalk4.green("+") : chalk4.yellow("~");
4257
+ const stats = f.isNew ? chalk4.dim(`${f.lines} lines`) : `${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4258
+ console.log(` ${icon} ${f.relativePath} ${stats}`);
4259
+ }
4260
+ console.log("");
4261
+ return;
4262
+ }
4263
+ const { stdin, stdout } = process;
4264
+ let cursor = 0;
4265
+ let viewing = null;
4266
+ let scrollOffset = 0;
4267
+ let lineCount = 0;
4268
+ function getTermHeight() {
4269
+ return (stdout.rows || 24) - 4;
4270
+ }
4271
+ function renderFileList() {
4272
+ const lines = [];
4273
+ lines.push(chalk4.bold(" Review changes"));
4274
+ lines.push("");
4275
+ for (let i = 0; i < files.length; i++) {
4276
+ const f = files[i];
4277
+ const ptr = i === cursor ? chalk4.cyan(">") : " ";
4278
+ const icon = f.isNew ? chalk4.green("+") : chalk4.yellow("~");
4279
+ const stats = f.isNew ? chalk4.dim(`${f.lines} lines`) : `${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4280
+ lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
4281
+ }
4282
+ lines.push("");
4283
+ lines.push(chalk4.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
4284
+ return lines.join("\n");
4285
+ }
4286
+ function renderDiff(index) {
4287
+ const f = files[index];
4288
+ const lines = [];
4289
+ const header = f.isNew ? ` ${chalk4.green("+")} ${f.relativePath} ${chalk4.dim("(new file)")}` : ` ${chalk4.yellow("~")} ${f.relativePath} ${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4290
+ lines.push(header);
4291
+ lines.push(chalk4.dim(" " + "\u2500".repeat(60)));
4292
+ const patchLines = f.patch.split("\n");
4293
+ const bodyLines = patchLines.slice(4);
4294
+ const maxVisible = getTermHeight() - 4;
4295
+ const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
4296
+ for (const line of visibleLines) {
4297
+ if (line.startsWith("+")) {
4298
+ lines.push(chalk4.green(" " + line));
4299
+ } else if (line.startsWith("-")) {
4300
+ lines.push(chalk4.red(" " + line));
4301
+ } else if (line.startsWith("@@")) {
4302
+ lines.push(chalk4.cyan(" " + line));
4208
4303
  } else {
4209
- const lines = fs17.readFileSync(file.proposedPath, "utf-8").split("\n").length;
4210
- console.log(` ${chalk4.green("+")} ${file.relativePath} ${chalk4.dim(`${lines} lines`)}`);
4304
+ lines.push(chalk4.dim(" " + line));
4211
4305
  }
4212
4306
  }
4213
- console.log("");
4214
- console.log(chalk4.dim(` Files staged at .caliber/staged/ for manual inspection.
4215
- `));
4307
+ const totalBody = bodyLines.length;
4308
+ if (totalBody > maxVisible) {
4309
+ const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
4310
+ lines.push(chalk4.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
4311
+ }
4312
+ lines.push("");
4313
+ lines.push(chalk4.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
4314
+ return lines.join("\n");
4216
4315
  }
4316
+ function draw(initial) {
4317
+ if (!initial && lineCount > 0) {
4318
+ stdout.write(`\x1B[${lineCount}A`);
4319
+ }
4320
+ stdout.write("\x1B[0J");
4321
+ const output = viewing !== null ? renderDiff(viewing) : renderFileList();
4322
+ stdout.write(output + "\n");
4323
+ lineCount = output.split("\n").length;
4324
+ }
4325
+ return new Promise((resolve2) => {
4326
+ console.log("");
4327
+ draw(true);
4328
+ stdin.setRawMode(true);
4329
+ stdin.resume();
4330
+ stdin.setEncoding("utf8");
4331
+ function cleanup() {
4332
+ stdin.removeListener("data", onData);
4333
+ stdin.setRawMode(false);
4334
+ stdin.pause();
4335
+ }
4336
+ function onData(key) {
4337
+ if (viewing !== null) {
4338
+ const f = files[viewing];
4339
+ const totalBody = f.patch.split("\n").length - 4;
4340
+ const maxVisible = getTermHeight() - 4;
4341
+ switch (key) {
4342
+ case "\x1B[A":
4343
+ scrollOffset = Math.max(0, scrollOffset - 1);
4344
+ draw(false);
4345
+ break;
4346
+ case "\x1B[B":
4347
+ scrollOffset = Math.min(Math.max(0, totalBody - maxVisible), scrollOffset + 1);
4348
+ draw(false);
4349
+ break;
4350
+ case " ":
4351
+ case "\x1B":
4352
+ viewing = null;
4353
+ scrollOffset = 0;
4354
+ draw(false);
4355
+ break;
4356
+ case "q":
4357
+ case "":
4358
+ cleanup();
4359
+ console.log("");
4360
+ resolve2();
4361
+ break;
4362
+ }
4363
+ } else {
4364
+ switch (key) {
4365
+ case "\x1B[A":
4366
+ cursor = (cursor - 1 + files.length) % files.length;
4367
+ draw(false);
4368
+ break;
4369
+ case "\x1B[B":
4370
+ cursor = (cursor + 1) % files.length;
4371
+ draw(false);
4372
+ break;
4373
+ case "\r":
4374
+ case "\n":
4375
+ viewing = cursor;
4376
+ scrollOffset = 0;
4377
+ draw(false);
4378
+ break;
4379
+ case "q":
4380
+ case "":
4381
+ cleanup();
4382
+ console.log("");
4383
+ resolve2();
4384
+ break;
4385
+ }
4386
+ }
4387
+ }
4388
+ stdin.on("data", onData);
4389
+ });
4217
4390
  }
4218
4391
  async function promptReviewAction() {
4219
4392
  return select2({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "0.8.0",
3
+ "version": "0.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": {