@rely-ai/caliber 1.19.7 → 1.20.0-dev.1773685589

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 +114 -28
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2062,7 +2062,7 @@ Return a JSON object with this exact shape:
2062
2062
  Respond with ONLY the JSON object, no markdown fences or extra text.`;
2063
2063
  var LEARN_SYSTEM_PROMPT = `You are an expert developer experience engineer. You analyze raw tool call events from AI coding sessions to extract reusable operational lessons that will help future LLM sessions work more effectively in this project.
2064
2064
 
2065
- You receive a chronological sequence of tool events from a Claude Code session. Each event includes the tool name, its input, its response, and whether it was a success or failure.
2065
+ You receive a chronological sequence of events from a Claude Code session. Most events are tool calls (with tool name, input, response, and success/failure status). Some events are USER_PROMPT events that capture what the user typed \u2014 these are critical for detecting corrections and redirections.
2066
2066
 
2067
2067
  Your job is to find OPERATIONAL patterns \u2014 things that went wrong and how they were fixed, commands that required specific flags or configuration, APIs that needed a particular approach to work. Focus on the WORKFLOW, not the code logic.
2068
2068
 
@@ -2074,6 +2074,7 @@ Look for:
2074
2074
  4. **Project-specific commands**: The correct way to build, test, lint, deploy \u2014 especially if it differs from defaults.
2075
2075
  5. **File/path traps**: Paths that are misleading, files that shouldn't be edited, directories with unexpected structure.
2076
2076
  6. **Configuration quirks**: Settings, flags, or arguments that are required but non-obvious.
2077
+ 7. **User corrections**: The user explicitly told the AI what's wrong, what to use instead, or what to avoid. Look for phrases like "no, use X instead of Y", "don't touch/edit/modify X", "that's wrong, you need to...", "always/never do X in this project", "stop, that file is...". These are the HIGHEST VALUE signals \u2014 they represent direct human feedback about project-specific requirements. If a user correction contradicts a pattern you'd otherwise extract, the correction wins.
2077
2078
 
2078
2079
  DO NOT extract:
2079
2080
  - Descriptions of what the code does or how features work (e.g. "compression removes comments" or "skeleton extraction creates outlines")
@@ -2084,21 +2085,30 @@ DO NOT extract:
2084
2085
  From these observations, produce:
2085
2086
 
2086
2087
  ### claudeMdLearnedSection
2087
- A markdown section with concise, actionable bullet points. Your output will be written to CALIBER_LEARNINGS.md \u2014 a standalone file that all AI coding agents (Claude Code, Cursor, Codex) reference for project-specific operational patterns. Each bullet should be a concrete instruction that prevents a future mistake or encodes a discovered workaround. Format: what to do (or avoid), and why.
2088
+ A markdown section with concise, actionable bullet points. Your output will be written to CALIBER_LEARNINGS.md \u2014 a standalone file that all AI coding agents (Claude Code, Cursor, Codex) reference for project-specific operational patterns.
2089
+
2090
+ Each bullet MUST be prefixed with an observation type in bold brackets. Valid types:
2091
+ - **[correction]** \u2014 user explicitly told the AI what's wrong or what to do differently (HIGHEST PRIORITY \u2014 always include these)
2092
+ - **[gotcha]** \u2014 a trap or edge case that wastes time if you don't know about it
2093
+ - **[fix]** \u2014 a specific failure-to-recovery sequence
2094
+ - **[pattern]** \u2014 a reusable approach that works in this project
2095
+ - **[env]** \u2014 an environment or configuration requirement
2096
+ - **[convention]** \u2014 a project-specific rule or naming convention
2088
2097
 
2089
2098
  Good examples:
2090
- - "Run \`npm install\` before \`npm run build\` \u2014 the build assumes deps are installed and gives a misleading error otherwise"
2091
- - "The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
2092
- - "Use \`pnpm\` not \`npm\` \u2014 the lockfile is pnpm-lock.yaml and npm creates conflicts"
2093
- - "Do NOT run \`jest\` directly \u2014 always use \`npm run test\` which sets the correct NODE_ENV"
2094
- - "API calls to \`/v2/users\` require the \`X-Api-Version\` header \u2014 without it you get a 404 that looks like the endpoint doesn't exist"
2095
- - "When \`tsup\` build fails with a type error, run \`npx tsc --noEmit\` first to get the real error \u2014 tsup swallows the details"
2096
- - "Files in \`src/generated/\` are auto-generated \u2014 editing them directly will be overwritten on next build"
2099
+ - "**[correction]** Files in \`src/generated/\` are auto-generated \u2014 never edit them directly"
2100
+ - "**[correction]** Use \`pnpm\` not \`npm\` \u2014 the lockfile is pnpm-lock.yaml and npm creates conflicts"
2101
+ - "**[gotcha]** When \`tsup\` build fails with a type error, run \`npx tsc --noEmit\` first to get the real error \u2014 tsup swallows the details"
2102
+ - "**[fix]** If \`npm install\` fails with ERESOLVE, use \`--legacy-peer-deps\`"
2103
+ - "**[env]** The test database requires \`DATABASE_URL\` to be set \u2014 use \`source .env.test\` first"
2104
+ - "**[pattern]** Do NOT run \`jest\` directly \u2014 always use \`npm run test\` which sets the correct NODE_ENV"
2105
+ - "**[convention]** API calls to \`/v2/users\` require the \`X-Api-Version\` header \u2014 without it you get a 404 that looks like the endpoint doesn't exist"
2097
2106
 
2098
2107
  Bad examples (do NOT produce these):
2099
2108
  - "The codebase uses TypeScript with strict mode" (describes code, not actionable)
2100
2109
  - "Components follow a pattern of X" (describes architecture, not operational)
2101
2110
  - "The project has a scoring module" (summarizes code structure)
2111
+ - Any bullet without a **[type]** prefix
2102
2112
 
2103
2113
  Rules for the learned section:
2104
2114
  - Be additive: keep all existing learned items, add new ones, remove duplicates
@@ -3686,6 +3696,7 @@ var SETTINGS_PATH2 = path13.join(".claude", "settings.json");
3686
3696
  var HOOK_TAILS = [
3687
3697
  { event: "PostToolUse", tail: "learn observe", description: "Caliber: recording tool usage for session learning" },
3688
3698
  { event: "PostToolUseFailure", tail: "learn observe --failure", description: "Caliber: recording tool failure for session learning" },
3699
+ { event: "UserPromptSubmit", tail: "learn observe --prompt", description: "Caliber: recording user prompt for correction detection" },
3689
3700
  { event: "SessionEnd", tail: "learn finalize", description: "Caliber: finalizing session learnings" }
3690
3701
  ];
3691
3702
  function getHookConfigs() {
@@ -3746,6 +3757,7 @@ var CURSOR_HOOKS_PATH = path13.join(".cursor", "hooks.json");
3746
3757
  var CURSOR_HOOK_EVENTS = [
3747
3758
  { event: "postToolUse", tail: "learn observe" },
3748
3759
  { event: "postToolUseFailure", tail: "learn observe --failure" },
3760
+ { event: "userPromptSubmit", tail: "learn observe --prompt" },
3749
3761
  { event: "sessionEnd", tail: "learn finalize" }
3750
3762
  ];
3751
3763
  function readCursorHooks() {
@@ -7649,8 +7661,12 @@ function parseBullets(content) {
7649
7661
  if (current) bullets.push(current);
7650
7662
  return bullets;
7651
7663
  }
7664
+ var TYPE_PREFIX_RE = /^\*\*\[[^\]]+\]\*\*\s*/;
7652
7665
  function normalizeBullet(bullet) {
7653
- return bullet.replace(/^- /, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
7666
+ return bullet.replace(/^- /, "").replace(TYPE_PREFIX_RE, "").replace(/`[^`]*`/g, "").replace(/\s+/g, " ").toLowerCase().trim();
7667
+ }
7668
+ function hasTypePrefix(bullet) {
7669
+ return TYPE_PREFIX_RE.test(bullet.replace(/^- /, ""));
7654
7670
  }
7655
7671
  function deduplicateLearnedItems(existing, incoming) {
7656
7672
  const existingBullets = existing ? parseBullets(existing) : [];
@@ -7660,14 +7676,18 @@ function deduplicateLearnedItems(existing, incoming) {
7660
7676
  for (const bullet of incomingBullets) {
7661
7677
  const norm = normalizeBullet(bullet);
7662
7678
  if (!norm) continue;
7663
- const isDup = merged.some((e) => {
7679
+ const dupIdx = merged.findIndex((e) => {
7664
7680
  const eNorm = normalizeBullet(e);
7665
7681
  const shorter = Math.min(norm.length, eNorm.length);
7666
7682
  const longer = Math.max(norm.length, eNorm.length);
7667
7683
  if (!(eNorm.includes(norm) || norm.includes(eNorm))) return false;
7668
7684
  return shorter / longer > 0.7;
7669
7685
  });
7670
- if (!isDup) {
7686
+ if (dupIdx !== -1) {
7687
+ if (hasTypePrefix(bullet) && !hasTypePrefix(merged[dupIdx])) {
7688
+ merged[dupIdx] = bullet;
7689
+ }
7690
+ } else {
7671
7691
  merged.push(bullet);
7672
7692
  newItems.push(bullet);
7673
7693
  }
@@ -8137,6 +8157,17 @@ function appendEvent(event) {
8137
8157
  fs30.writeFileSync(filePath, kept.join("\n") + "\n");
8138
8158
  }
8139
8159
  }
8160
+ function appendPromptEvent(event) {
8161
+ ensureLearningDir();
8162
+ const filePath = sessionFilePath();
8163
+ fs30.appendFileSync(filePath, JSON.stringify(event) + "\n");
8164
+ const count = getEventCount();
8165
+ if (count > LEARNING_MAX_EVENTS) {
8166
+ const lines = fs30.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
8167
+ const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
8168
+ fs30.writeFileSync(filePath, kept.join("\n") + "\n");
8169
+ }
8170
+ }
8140
8171
  function readAllEvents() {
8141
8172
  const filePath = sessionFilePath();
8142
8173
  if (!fs30.existsSync(filePath)) return [];
@@ -8197,14 +8228,6 @@ function acquireFinalizeLock() {
8197
8228
  fs30.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
8198
8229
  return true;
8199
8230
  } catch {
8200
- try {
8201
- const stat = fs30.statSync(lockPath);
8202
- if (Date.now() - stat.mtimeMs >= LOCK_STALE_MS) {
8203
- fs30.writeFileSync(lockPath, String(process.pid));
8204
- return true;
8205
- }
8206
- } catch {
8207
- }
8208
8231
  return false;
8209
8232
  }
8210
8233
  }
@@ -8216,17 +8239,64 @@ function releaseFinalizeLock() {
8216
8239
  }
8217
8240
  }
8218
8241
 
8242
+ // src/lib/sanitize.ts
8243
+ var KNOWN_PREFIX_PATTERNS = [
8244
+ // Anthropic (before generic sk- pattern)
8245
+ [/sk-ant-[A-Za-z0-9_-]{20,}/g, "[REDACTED]"],
8246
+ // AWS access key IDs
8247
+ [/AKIA[0-9A-Z]{16}/g, "[REDACTED]"],
8248
+ // AWS secret keys in assignments
8249
+ [/(?:aws)?_?secret_?(?:access)?_?key\s*[:=]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/gi, "[REDACTED]"],
8250
+ // GitHub tokens (PAT, OAuth, server, app install, fine-grained)
8251
+ [/gh[pousr]_[A-Za-z0-9_]{36,}/g, "[REDACTED]"],
8252
+ [/github_pat_[A-Za-z0-9_]{22,}/g, "[REDACTED]"],
8253
+ // Stripe keys
8254
+ [/[sr]k_(live|test)_[A-Za-z0-9]{20,}/g, "[REDACTED]"],
8255
+ // Slack tokens
8256
+ [/xox[bpsar]-[A-Za-z0-9-]{10,}/g, "[REDACTED]"],
8257
+ // JWTs (3-segment base64url)
8258
+ [/eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g, "[REDACTED]"],
8259
+ // OpenAI keys (after sk-ant- to avoid false match)
8260
+ [/sk-[A-Za-z0-9-]{20,}/g, "[REDACTED]"],
8261
+ // Google API keys
8262
+ [/AIza[A-Za-z0-9_-]{35}/g, "[REDACTED]"],
8263
+ // Bearer tokens
8264
+ [/[Bb]earer\s+[A-Za-z0-9_\-.]{20,}/g, "[REDACTED]"],
8265
+ // PEM private keys
8266
+ [/-----BEGIN[A-Z ]+KEY-----[\s\S]+?-----END[A-Z ]+KEY-----/g, "[REDACTED]"]
8267
+ ];
8268
+ var SENSITIVE_ASSIGNMENT = /(?:api[_-]?key|secret[_-]?key|password|token|credential|auth[_-]?token|private[_-]?key)\s*[:=]\s*['"]?([^\s'"]{8,500})['"]?/gi;
8269
+ function sanitizeSecrets(text) {
8270
+ let result = text;
8271
+ for (const [pattern, replacement] of KNOWN_PREFIX_PATTERNS) {
8272
+ result = result.replace(pattern, replacement);
8273
+ }
8274
+ result = result.replace(
8275
+ SENSITIVE_ASSIGNMENT,
8276
+ (match, value) => match.replace(value, "[REDACTED]")
8277
+ );
8278
+ return result;
8279
+ }
8280
+
8219
8281
  // src/ai/learn.ts
8220
8282
  init_config();
8221
8283
  var MAX_PROMPT_TOKENS = 1e5;
8222
8284
  function formatEventsForPrompt(events) {
8223
8285
  return events.map((e, i) => {
8224
- const status = e.hook_event_name === "PostToolUseFailure" ? "FAILURE" : "SUCCESS";
8225
- const inputStr = JSON.stringify(e.tool_input, null, 2);
8226
- const responseStr = typeof e.tool_response === "object" && "_truncated" in e.tool_response ? String(e.tool_response._truncated) : JSON.stringify(e.tool_response, null, 2);
8286
+ if (e.hook_event_name === "UserPromptSubmit") {
8287
+ const pe = e;
8288
+ return `--- Event ${i + 1} [USER_PROMPT] ---
8289
+ Time: ${pe.timestamp}
8290
+ User said:
8291
+ ${pe.prompt_content}`;
8292
+ }
8293
+ const te = e;
8294
+ const status = te.hook_event_name === "PostToolUseFailure" ? "FAILURE" : "SUCCESS";
8295
+ const inputStr = JSON.stringify(te.tool_input, null, 2);
8296
+ const responseStr = typeof te.tool_response === "object" && "_truncated" in te.tool_response ? String(te.tool_response._truncated) : JSON.stringify(te.tool_response, null, 2);
8227
8297
  return `--- Event ${i + 1} [${status}] ---
8228
- Tool: ${e.tool_name}
8229
- Time: ${e.timestamp}
8298
+ Tool: ${te.tool_name}
8299
+ Time: ${te.timestamp}
8230
8300
  Input:
8231
8301
  ${inputStr}
8232
8302
  Response:
@@ -8298,9 +8368,25 @@ async function learnObserveCommand(options) {
8298
8368
  const raw = await readStdin();
8299
8369
  if (!raw.trim()) return;
8300
8370
  const hookData = JSON.parse(raw);
8371
+ const sessionId = hookData.session_id || hookData.conversation_id || "unknown";
8372
+ if (options.prompt) {
8373
+ const event2 = {
8374
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8375
+ session_id: sessionId,
8376
+ hook_event_name: "UserPromptSubmit",
8377
+ prompt_content: sanitizeSecrets(String(hookData.prompt_content || hookData.content || hookData.prompt || "")),
8378
+ cwd: hookData.cwd || process.cwd()
8379
+ };
8380
+ appendPromptEvent(event2);
8381
+ const state2 = readState2();
8382
+ state2.eventCount++;
8383
+ if (!state2.sessionId) state2.sessionId = sessionId;
8384
+ writeState2(state2);
8385
+ return;
8386
+ }
8301
8387
  const event = {
8302
8388
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8303
- session_id: hookData.session_id || hookData.conversation_id || "unknown",
8389
+ session_id: sessionId,
8304
8390
  hook_event_name: options.failure ? "PostToolUseFailure" : "PostToolUse",
8305
8391
  tool_name: hookData.tool_name || "unknown",
8306
8392
  tool_input: hookData.tool_input || {},
@@ -8311,7 +8397,7 @@ async function learnObserveCommand(options) {
8311
8397
  appendEvent(event);
8312
8398
  const state = readState2();
8313
8399
  state.eventCount++;
8314
- if (!state.sessionId) state.sessionId = event.session_id;
8400
+ if (!state.sessionId) state.sessionId = sessionId;
8315
8401
  writeState2(state);
8316
8402
  } catch {
8317
8403
  }
@@ -8530,7 +8616,7 @@ program.command("score").description("Score your current agent config setup (det
8530
8616
  program.command("refresh").description("Update docs based on recent code changes").option("--quiet", "Suppress output (for use in hooks)").option("--dry-run", "Preview changes without writing files").action(tracked("refresh", refreshCommand));
8531
8617
  program.command("hooks").description("Manage auto-refresh hooks (toggle interactively)").option("--install", "Enable all hooks non-interactively").option("--remove", "Disable all hooks non-interactively").action(tracked("hooks", hooksCommand));
8532
8618
  var learn = program.command("learn", { hidden: true }).description("[dev] Session learning \u2014 observe tool usage and extract reusable instructions");
8533
- learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").action(tracked("learn:observe", learnObserveCommand));
8619
+ learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").option("--prompt", "Record a user prompt event").action(tracked("learn:observe", learnObserveCommand));
8534
8620
  learn.command("finalize").description("Analyze session events and update CALIBER_LEARNINGS.md (called on SessionEnd)").option("--force", "Skip the running-process check (for manual invocation)").action(tracked("learn:finalize", (opts) => learnFinalizeCommand(opts)));
8535
8621
  learn.command("install").description("Install learning hooks into .claude/settings.json").action(tracked("learn:install", learnInstallCommand));
8536
8622
  learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(tracked("learn:remove", learnRemoveCommand));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.19.7",
3
+ "version": "1.20.0-dev.1773685589",
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": {