@rely-ai/caliber 1.20.0-dev.1773687255 → 1.20.0-dev.1773690306

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 +241 -21
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -149,13 +149,14 @@ __export(constants_exports, {
149
149
  CALIBER_DIR: () => CALIBER_DIR,
150
150
  LEARNING_DIR: () => LEARNING_DIR,
151
151
  LEARNING_MAX_EVENTS: () => LEARNING_MAX_EVENTS,
152
+ LEARNING_ROI_FILE: () => LEARNING_ROI_FILE,
152
153
  LEARNING_SESSION_FILE: () => LEARNING_SESSION_FILE,
153
154
  LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
154
155
  MANIFEST_FILE: () => MANIFEST_FILE
155
156
  });
156
157
  import path9 from "path";
157
158
  import os2 from "os";
158
- var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
159
+ var AUTH_DIR, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS, LEARNING_ROI_FILE;
159
160
  var init_constants = __esm({
160
161
  "src/constants.ts"() {
161
162
  "use strict";
@@ -167,6 +168,7 @@ var init_constants = __esm({
167
168
  LEARNING_SESSION_FILE = "current-session.jsonl";
168
169
  LEARNING_STATE_FILE = "state.json";
169
170
  LEARNING_MAX_EVENTS = 500;
171
+ LEARNING_ROI_FILE = "roi-stats.json";
170
172
  }
171
173
  });
172
174
 
@@ -219,8 +221,8 @@ var init_lock = __esm({
219
221
 
220
222
  // src/cli.ts
221
223
  import { Command } from "commander";
222
- import fs33 from "fs";
223
- import path26 from "path";
224
+ import fs34 from "fs";
225
+ import path27 from "path";
224
226
  import { fileURLToPath } from "url";
225
227
 
226
228
  // src/commands/init.ts
@@ -2240,15 +2242,15 @@ init_config();
2240
2242
  // src/utils/dependencies.ts
2241
2243
  import { readFileSync } from "fs";
2242
2244
  import { join } from "path";
2243
- function readFileOrNull(path28) {
2245
+ function readFileOrNull(path29) {
2244
2246
  try {
2245
- return readFileSync(path28, "utf-8");
2247
+ return readFileSync(path29, "utf-8");
2246
2248
  } catch {
2247
2249
  return null;
2248
2250
  }
2249
2251
  }
2250
- function readJsonOrNull(path28) {
2251
- const content = readFileOrNull(path28);
2252
+ function readJsonOrNull(path29) {
2253
+ const content = readFileOrNull(path29);
2252
2254
  if (!content) return null;
2253
2255
  try {
2254
2256
  return JSON.parse(content);
@@ -5757,6 +5759,36 @@ function trackSkillsInstalled(count) {
5757
5759
  function trackUndoExecuted() {
5758
5760
  trackEvent("undo_executed");
5759
5761
  }
5762
+ function trackLearnSessionAnalyzed(props) {
5763
+ trackEvent("learn_session_analyzed", {
5764
+ event_count: props.eventCount,
5765
+ failure_count: props.failureCount,
5766
+ correction_count: props.correctionCount,
5767
+ had_learnings_available: props.hadLearningsAvailable,
5768
+ learnings_available_count: props.learningsAvailableCount,
5769
+ new_learnings_produced: props.newLearningsProduced,
5770
+ waste_tokens: props.wasteTokens
5771
+ });
5772
+ }
5773
+ function trackLearnROISnapshot(props) {
5774
+ trackEvent("learn_roi_snapshot", {
5775
+ total_waste_tokens: props.totalWasteTokens,
5776
+ total_sessions: props.totalSessions,
5777
+ sessions_with_learnings: props.sessionsWithLearnings,
5778
+ sessions_without_learnings: props.sessionsWithoutLearnings,
5779
+ failure_rate_with_learnings: props.failureRateWithLearnings,
5780
+ failure_rate_without_learnings: props.failureRateWithoutLearnings,
5781
+ estimated_savings_tokens: props.estimatedSavingsTokens,
5782
+ learning_count: props.learningCount
5783
+ });
5784
+ }
5785
+ function trackLearnNewLearning(props) {
5786
+ trackEvent("learn_new_learning", {
5787
+ observation_type: props.observationType,
5788
+ waste_tokens: props.wasteTokens,
5789
+ source_event_count: props.sourceEventCount
5790
+ });
5791
+ }
5760
5792
 
5761
5793
  // src/commands/recommend.ts
5762
5794
  function detectLocalPlatforms() {
@@ -7084,7 +7116,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
7084
7116
  }
7085
7117
  function summarizeSetup(action, setup) {
7086
7118
  const descriptions = setup.fileDescriptions;
7087
- const files = descriptions ? Object.entries(descriptions).map(([path28, desc]) => ` ${path28}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
7119
+ const files = descriptions ? Object.entries(descriptions).map(([path29, desc]) => ` ${path29}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
7088
7120
  return `${action}. Files:
7089
7121
  ${files}`;
7090
7122
  }
@@ -8285,7 +8317,7 @@ async function configCommand() {
8285
8317
  }
8286
8318
 
8287
8319
  // src/commands/learn.ts
8288
- import fs32 from "fs";
8320
+ import fs33 from "fs";
8289
8321
  import chalk18 from "chalk";
8290
8322
 
8291
8323
  // src/learner/stdin.ts
@@ -8555,9 +8587,124 @@ ${eventsText}`;
8555
8587
  });
8556
8588
  return parseAnalysisResponse(raw);
8557
8589
  }
8590
+ function calculateSessionWaste(events) {
8591
+ let totalWasteTokens = 0;
8592
+ let failureCount = 0;
8593
+ let promptCount = 0;
8594
+ for (const event of events) {
8595
+ if (event.hook_event_name === "PostToolUseFailure") {
8596
+ const te = event;
8597
+ const inputStr = JSON.stringify(te.tool_input);
8598
+ const responseStr = typeof te.tool_response === "object" && "_truncated" in te.tool_response ? String(te.tool_response._truncated) : JSON.stringify(te.tool_response);
8599
+ totalWasteTokens += estimateTokens(inputStr + responseStr);
8600
+ failureCount++;
8601
+ } else if (event.hook_event_name === "UserPromptSubmit") {
8602
+ promptCount++;
8603
+ }
8604
+ }
8605
+ return { totalWasteTokens, failureCount, promptCount };
8606
+ }
8558
8607
 
8559
8608
  // src/commands/learn.ts
8560
8609
  init_config();
8610
+
8611
+ // src/learner/roi.ts
8612
+ init_constants();
8613
+ import fs32 from "fs";
8614
+ import path26 from "path";
8615
+ var DEFAULT_TOTALS = {
8616
+ totalWasteTokens: 0,
8617
+ totalSessionsWithLearnings: 0,
8618
+ totalSessionsWithoutLearnings: 0,
8619
+ totalFailuresWithLearnings: 0,
8620
+ totalFailuresWithoutLearnings: 0,
8621
+ estimatedSavingsTokens: 0,
8622
+ firstSessionTimestamp: "",
8623
+ lastSessionTimestamp: ""
8624
+ };
8625
+ function roiFilePath() {
8626
+ return path26.join(LEARNING_DIR, LEARNING_ROI_FILE);
8627
+ }
8628
+ function readROIStats() {
8629
+ const filePath = roiFilePath();
8630
+ if (!fs32.existsSync(filePath)) {
8631
+ return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
8632
+ }
8633
+ try {
8634
+ return JSON.parse(fs32.readFileSync(filePath, "utf-8"));
8635
+ } catch {
8636
+ return { learnings: [], sessions: [], totals: { ...DEFAULT_TOTALS } };
8637
+ }
8638
+ }
8639
+ function writeROIStats(stats) {
8640
+ ensureLearningDir();
8641
+ fs32.writeFileSync(roiFilePath(), JSON.stringify(stats, null, 2));
8642
+ }
8643
+ function recalculateTotals(stats) {
8644
+ const totals = stats.totals;
8645
+ totals.totalWasteTokens = stats.learnings.reduce((sum, l) => sum + l.wasteTokens, 0);
8646
+ totals.totalSessionsWithLearnings = 0;
8647
+ totals.totalSessionsWithoutLearnings = 0;
8648
+ totals.totalFailuresWithLearnings = 0;
8649
+ totals.totalFailuresWithoutLearnings = 0;
8650
+ for (const s of stats.sessions) {
8651
+ if (s.hadLearningsAvailable) {
8652
+ totals.totalSessionsWithLearnings++;
8653
+ totals.totalFailuresWithLearnings += s.failureCount;
8654
+ } else {
8655
+ totals.totalSessionsWithoutLearnings++;
8656
+ totals.totalFailuresWithoutLearnings += s.failureCount;
8657
+ }
8658
+ }
8659
+ totals.estimatedSavingsTokens = totals.totalWasteTokens * totals.totalSessionsWithLearnings;
8660
+ if (stats.sessions.length > 0) {
8661
+ totals.firstSessionTimestamp = stats.sessions[0].timestamp;
8662
+ totals.lastSessionTimestamp = stats.sessions[stats.sessions.length - 1].timestamp;
8663
+ }
8664
+ }
8665
+ var MAX_SESSIONS = 500;
8666
+ var MAX_LEARNINGS = 1e3;
8667
+ function recordSession(summary, learnings) {
8668
+ const stats = readROIStats();
8669
+ stats.sessions.push(summary);
8670
+ if (learnings?.length) {
8671
+ stats.learnings.push(...learnings);
8672
+ }
8673
+ if (stats.sessions.length > MAX_SESSIONS) {
8674
+ stats.sessions = stats.sessions.slice(-MAX_SESSIONS);
8675
+ }
8676
+ if (stats.learnings.length > MAX_LEARNINGS) {
8677
+ stats.learnings = stats.learnings.slice(-MAX_LEARNINGS);
8678
+ }
8679
+ recalculateTotals(stats);
8680
+ writeROIStats(stats);
8681
+ return stats;
8682
+ }
8683
+ function formatROISummary(stats) {
8684
+ const t = stats.totals;
8685
+ const totalSessions = t.totalSessionsWithLearnings + t.totalSessionsWithoutLearnings;
8686
+ if (totalSessions === 0) return "";
8687
+ const lines = ["ROI Summary"];
8688
+ lines.push(` Sessions tracked: ${totalSessions}`);
8689
+ lines.push(` Sessions with learnings: ${t.totalSessionsWithLearnings}`);
8690
+ if (t.totalSessionsWithoutLearnings > 0) {
8691
+ const rateWithout = t.totalSessionsWithoutLearnings > 0 ? (t.totalFailuresWithoutLearnings / t.totalSessionsWithoutLearnings).toFixed(1) : "0.0";
8692
+ lines.push(` Failure rate (no learnings): ${rateWithout}/session`);
8693
+ }
8694
+ if (t.totalSessionsWithLearnings > 0) {
8695
+ const rateWith = (t.totalFailuresWithLearnings / t.totalSessionsWithLearnings).toFixed(1);
8696
+ lines.push(` Failure rate (with learnings): ${rateWith}/session`);
8697
+ }
8698
+ if (t.totalWasteTokens > 0) {
8699
+ lines.push(` Total waste captured: ${t.totalWasteTokens.toLocaleString()} tokens`);
8700
+ }
8701
+ if (t.estimatedSavingsTokens > 0) {
8702
+ lines.push(` Estimated savings: ~${t.estimatedSavingsTokens.toLocaleString()} tokens`);
8703
+ }
8704
+ return lines.join("\n");
8705
+ }
8706
+
8707
+ // src/commands/learn.ts
8561
8708
  var MIN_EVENTS_FOR_ANALYSIS = 25;
8562
8709
  async function learnObserveCommand(options) {
8563
8710
  try {
@@ -8636,18 +8783,82 @@ async function learnFinalizeCommand(options) {
8636
8783
  existingSkills
8637
8784
  );
8638
8785
  analyzed = true;
8786
+ const waste = calculateSessionWaste(events);
8787
+ const existingLearnedItems = existingLearnedSection ? existingLearnedSection.split("\n").filter((l) => l.startsWith("- ")).length : 0;
8788
+ const hadLearnings = existingLearnedItems > 0;
8789
+ let newLearningsProduced = 0;
8790
+ let roiLearningEntries = [];
8639
8791
  if (response.claudeMdLearnedSection || response.skills?.length) {
8640
8792
  const result = writeLearnedContent({
8641
8793
  claudeMdLearnedSection: response.claudeMdLearnedSection,
8642
8794
  skills: response.skills
8643
8795
  });
8796
+ newLearningsProduced = result.newItemCount;
8644
8797
  if (result.newItemCount > 0) {
8645
- console.log(chalk18.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}`));
8798
+ const wasteLabel = waste.totalWasteTokens > 0 ? ` (~${waste.totalWasteTokens.toLocaleString()} wasted tokens captured)` : "";
8799
+ console.log(chalk18.dim(`caliber: learned ${result.newItemCount} new pattern${result.newItemCount === 1 ? "" : "s"}${wasteLabel}`));
8646
8800
  for (const item of result.newItems) {
8647
8801
  console.log(chalk18.dim(` + ${item.replace(/^- /, "").slice(0, 80)}`));
8648
8802
  }
8803
+ const wastePerLearning = Math.round(waste.totalWasteTokens / result.newItemCount);
8804
+ const TYPE_RE = /^\*\*\[([^\]]+)\]\*\*/;
8805
+ const learningEntries = result.newItems.map((item) => {
8806
+ const clean = item.replace(/^- /, "");
8807
+ const typeMatch = clean.match(TYPE_RE);
8808
+ return {
8809
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8810
+ observationType: typeMatch ? typeMatch[1] : "unknown",
8811
+ summary: clean.replace(TYPE_RE, "").trim().slice(0, 80),
8812
+ wasteTokens: wastePerLearning,
8813
+ sourceEventCount: events.length
8814
+ };
8815
+ });
8816
+ for (const entry of learningEntries) {
8817
+ trackLearnNewLearning({
8818
+ observationType: entry.observationType,
8819
+ wasteTokens: entry.wasteTokens,
8820
+ sourceEventCount: entry.sourceEventCount
8821
+ });
8822
+ }
8823
+ roiLearningEntries = learningEntries;
8649
8824
  }
8650
8825
  }
8826
+ const sessionSummary = {
8827
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8828
+ sessionId: readState2().sessionId || "unknown",
8829
+ eventCount: events.length,
8830
+ failureCount: waste.failureCount,
8831
+ promptCount: waste.promptCount,
8832
+ hadLearningsAvailable: hadLearnings,
8833
+ learningsCount: existingLearnedItems,
8834
+ newLearningsProduced
8835
+ };
8836
+ const roiStats = recordSession(sessionSummary, roiLearningEntries);
8837
+ trackLearnSessionAnalyzed({
8838
+ eventCount: events.length,
8839
+ failureCount: waste.failureCount,
8840
+ correctionCount: waste.promptCount,
8841
+ hadLearningsAvailable: hadLearnings,
8842
+ learningsAvailableCount: existingLearnedItems,
8843
+ newLearningsProduced,
8844
+ wasteTokens: waste.totalWasteTokens
8845
+ });
8846
+ const t = roiStats.totals;
8847
+ const totalSessions = t.totalSessionsWithLearnings + t.totalSessionsWithoutLearnings;
8848
+ trackLearnROISnapshot({
8849
+ totalWasteTokens: t.totalWasteTokens,
8850
+ totalSessions,
8851
+ sessionsWithLearnings: t.totalSessionsWithLearnings,
8852
+ sessionsWithoutLearnings: t.totalSessionsWithoutLearnings,
8853
+ failureRateWithLearnings: t.totalSessionsWithLearnings > 0 ? t.totalFailuresWithLearnings / t.totalSessionsWithLearnings : 0,
8854
+ failureRateWithoutLearnings: t.totalSessionsWithoutLearnings > 0 ? t.totalFailuresWithoutLearnings / t.totalSessionsWithoutLearnings : 0,
8855
+ estimatedSavingsTokens: t.estimatedSavingsTokens,
8856
+ learningCount: roiStats.learnings.length
8857
+ });
8858
+ if (t.estimatedSavingsTokens > 0) {
8859
+ const totalLearnings = existingLearnedItems + newLearningsProduced;
8860
+ console.log(chalk18.dim(`caliber: ${totalLearnings} learnings active \u2014 est. ~${t.estimatedSavingsTokens.toLocaleString()} tokens saved across ${t.totalSessionsWithLearnings} sessions`));
8861
+ }
8651
8862
  } catch (err) {
8652
8863
  if (options?.force) {
8653
8864
  console.error(chalk18.red("caliber: finalize failed \u2014"), err instanceof Error ? err.message : err);
@@ -8662,7 +8873,7 @@ async function learnFinalizeCommand(options) {
8662
8873
  }
8663
8874
  async function learnInstallCommand() {
8664
8875
  let anyInstalled = false;
8665
- if (fs32.existsSync(".claude")) {
8876
+ if (fs33.existsSync(".claude")) {
8666
8877
  const r = installLearningHooks();
8667
8878
  if (r.installed) {
8668
8879
  console.log(chalk18.green("\u2713") + " Claude Code learning hooks installed");
@@ -8671,7 +8882,7 @@ async function learnInstallCommand() {
8671
8882
  console.log(chalk18.dim(" Claude Code hooks already installed"));
8672
8883
  }
8673
8884
  }
8674
- if (fs32.existsSync(".cursor")) {
8885
+ if (fs33.existsSync(".cursor")) {
8675
8886
  const r = installCursorLearningHooks();
8676
8887
  if (r.installed) {
8677
8888
  console.log(chalk18.green("\u2713") + " Cursor learning hooks installed");
@@ -8680,7 +8891,7 @@ async function learnInstallCommand() {
8680
8891
  console.log(chalk18.dim(" Cursor hooks already installed"));
8681
8892
  }
8682
8893
  }
8683
- if (!fs32.existsSync(".claude") && !fs32.existsSync(".cursor")) {
8894
+ if (!fs33.existsSync(".claude") && !fs33.existsSync(".cursor")) {
8684
8895
  console.log(chalk18.yellow("No .claude/ or .cursor/ directory found."));
8685
8896
  console.log(chalk18.dim(" Run `caliber init` first, or create the directory manually."));
8686
8897
  return;
@@ -8740,12 +8951,21 @@ async function learnStatusCommand() {
8740
8951
  console.log(`
8741
8952
  Learned items in CALIBER_LEARNINGS.md: ${chalk18.cyan(String(lineCount))}`);
8742
8953
  }
8954
+ const roiStats = readROIStats();
8955
+ const roiSummary = formatROISummary(roiStats);
8956
+ if (roiSummary) {
8957
+ console.log();
8958
+ console.log(chalk18.bold(roiSummary.split("\n")[0]));
8959
+ for (const line of roiSummary.split("\n").slice(1)) {
8960
+ console.log(line);
8961
+ }
8962
+ }
8743
8963
  }
8744
8964
 
8745
8965
  // src/cli.ts
8746
- var __dirname = path26.dirname(fileURLToPath(import.meta.url));
8966
+ var __dirname = path27.dirname(fileURLToPath(import.meta.url));
8747
8967
  var pkg = JSON.parse(
8748
- fs33.readFileSync(path26.resolve(__dirname, "..", "package.json"), "utf-8")
8968
+ fs34.readFileSync(path27.resolve(__dirname, "..", "package.json"), "utf-8")
8749
8969
  );
8750
8970
  var program = new Command();
8751
8971
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
@@ -8819,16 +9039,16 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
8819
9039
  learn.command("status").description("Show learning system status").action(tracked("learn:status", learnStatusCommand));
8820
9040
 
8821
9041
  // src/utils/version-check.ts
8822
- import fs34 from "fs";
8823
- import path27 from "path";
9042
+ import fs35 from "fs";
9043
+ import path28 from "path";
8824
9044
  import { fileURLToPath as fileURLToPath2 } from "url";
8825
9045
  import { execSync as execSync14 } from "child_process";
8826
9046
  import chalk19 from "chalk";
8827
9047
  import ora6 from "ora";
8828
9048
  import confirm2 from "@inquirer/confirm";
8829
- var __dirname_vc = path27.dirname(fileURLToPath2(import.meta.url));
9049
+ var __dirname_vc = path28.dirname(fileURLToPath2(import.meta.url));
8830
9050
  var pkg2 = JSON.parse(
8831
- fs34.readFileSync(path27.resolve(__dirname_vc, "..", "package.json"), "utf-8")
9051
+ fs35.readFileSync(path28.resolve(__dirname_vc, "..", "package.json"), "utf-8")
8832
9052
  );
8833
9053
  function getChannel(version) {
8834
9054
  const match = version.match(/-(dev|next)\./);
@@ -8853,8 +9073,8 @@ function isNewer(registry, current) {
8853
9073
  function getInstalledVersion() {
8854
9074
  try {
8855
9075
  const globalRoot = execSync14("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8856
- const pkgPath = path27.join(globalRoot, "@rely-ai", "caliber", "package.json");
8857
- return JSON.parse(fs34.readFileSync(pkgPath, "utf-8")).version;
9076
+ const pkgPath = path28.join(globalRoot, "@rely-ai", "caliber", "package.json");
9077
+ return JSON.parse(fs35.readFileSync(pkgPath, "utf-8")).version;
8858
9078
  } catch {
8859
9079
  return null;
8860
9080
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.20.0-dev.1773687255",
3
+ "version": "1.20.0-dev.1773690306",
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": {