@unerr-ai/unerr 0.2.6 → 0.2.8

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 (3) hide show
  1. package/README.md +52 -25
  2. package/dist/cli.js +40 -41
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
- <p align="center">
2
- <a href="https://www.unerr.dev/"><img src="https://unerr.dev/icon-wordmark.svg" alt="unerr — operational intelligence for your codebase" width="320" /></a>
3
- </p>
1
+ <h1 align="center">
2
+ <a href="https://www.unerr.dev/"><img src="https://unerr.dev/icon-wordmark.svg" alt="unerr" width="320" /></a>
3
+ </h1>
4
4
 
5
5
  <p align="center">
6
- <strong>Stop babysitting your AI.</strong>
6
+ <strong>Your AI agent has read your codebase. It still can't safely change it.</strong>
7
7
  </p>
8
8
 
9
9
  <p align="center">
10
- <strong>unerr is operational intelligence for your codebase</strong> one local runtime, <em>behind</em> every MCP your agent speaks,<br/>
11
- that catches the refactor about to break 7 call sites, remembers what the team already decided, and keeps context lean.<br/>
12
- Joins no single-purpose memory or graph tool can make because everything lives in one process.
10
+ On a large, existing codebase your agent can't hold the whole thing in context so it breaks callers it never read<br/>
11
+ and rebuilds patterns your team already standardized, even with the rule written down. <strong>unerr is a guardrail<br/>
12
+ your agent reaches over MCP</strong> that, the moment it edits, hands it the live call graph <em>plus the rule you pinned to that<br/>
13
+ exact function</em> — a rule that re-anchors itself when the code moves instead of going stale. So it sees the 24 callers,<br/>
14
+ and the standard it's about to violate, <em>before</em> it touches the function.
13
15
  </p>
14
16
 
15
17
  <p align="center">
@@ -28,38 +30,63 @@
28
30
  <p align="center">
29
31
  <code>npm install -g @unerr-ai/unerr</code>
30
32
  <br /><br />
31
- <sub>Zero configuration. Install, restart your IDE, and the next prompt is smarter.</sub>
33
+ <sub>Zero configuration. Install, restart your IDE, and the next prompt already knows your repo.</sub>
32
34
  </p>
33
35
 
34
36
  <p align="center">
35
- <sub>Measured, not estimated: removes <strong>86–90%</strong> of the tokens an agent spends navigating code —<br/>
36
- same corpus, same tokenizer, with a fidelity gate that discards any "saving" that lost the answer. <a href="./benchmarks/README.md">See the benchmarks →</a></sub>
37
+ <sub><strong>It nets your context down, not up.</strong> Five separate MCP servers burn ~55K tokens of schemas just to announce themselves;<br/>
38
+ unerr is one runtime whose tools load on demand, and each edit injects a single scoped line the rule for the entity in front of it.<br/>
39
+ Measured: the agent lands on the right code with <strong>86–90% fewer tokens</strong>, same corpus, same tokenizer, with a fidelity gate<br/>
40
+ that discards any "saving" that lost the answer. <a href="./benchmarks/README.md">See the benchmarks →</a></sub>
37
41
  </p>
38
42
 
39
43
  ---
40
44
 
41
- ## The old way is over
45
+ <details>
46
+ <summary><strong>Contents</strong></summary>
47
+
48
+ - [Agents read your code. They can't safely change it.](#agents-read-your-code-they-cant-safely-change-it)
49
+ - [The pains this fixes](#the-pains-this-fixes)
50
+ - [What changes when you install it](#what-changes-when-you-install-it)
51
+ - [See it in action](#see-it-in-action)
52
+ - [Quick Start](#quick-start)
53
+ - [Who it's for](#who-its-for)
54
+ - [Why one runtime, not five separate tools](#why-one-runtime-not-five-separate-tools)
55
+ - [How the runtime works](#how-the-runtime-works)
56
+ - [License](#license)
57
+
58
+ </details>
59
+
60
+ ---
61
+
62
+ ## Agents read your code. They can't safely change it.
63
+
64
+ On a small or greenfield project the agent holds the whole repo in its head and grepping the live code is enough — you don't need us. The wall is the *large, existing, multi-contributor* codebase, and it's the same wall every time: the agent can't fit the whole thing in context, so it acts on the slice it can see and never reads the rest.
65
+
66
+ So it ships damage that looks locally correct. It changes a signature and breaks the 24 callers it never read. It writes a fourth copy of a registry pattern your team standardized months ago — even with the rule spelled out in `.cursorrules`. Neither shows up as an error. They show up as a senior engineer's afternoon.
67
+
68
+ The knowledge that would have stopped it — who calls this function, which pattern is load-bearing — exists. It's just nowhere the agent can reach *at the moment it edits*: in a call graph it doesn't build, and conventions it was never told.
42
69
 
43
- Coding agents now write the code. The bottleneck moved from *writing* code to an agent **landing on the right code** without burning turns, re-reading files it's already seen, or breaking things it couldn't see.
70
+ **unerr is the guardrail that closes that gap.** One local runtime your agent reaches over MCP, that hands it the call graph and the rules you anchored to each entity the moment it edits — and re-anchors those rules when the code moves, so they never go silently stale. A change that would break 7 call sites is caught before it lands; a standard you set once is enforced every time the agent touches that scope.
44
71
 
45
72
  | The old way | With unerr |
46
73
  |---|---|
47
- | The agent refactors a function blind to its 24 callers — 7 sites break silently. | **Cascade guard** reads the call graph *before* the edit; every caller is on screen first. |
48
- | Conventions and decisions live in `MEMORY.md` / `.cursorrules` you hand-maintain and re-paste each session. | **Anchored memory + drift detection** facts pin to the code and get a drift signal when it moves, instead of going silently stale. |
49
- | Five single-purpose MCP servers — memory, graph, compressor — that can't reach across each other. | **One runtime** — so cascade guard, convention drift, and loop breaker fire on joins no point tool can make. |
74
+ | The agent changes a function without reading its 24 callers — 7 sites break silently. | **Cascade guard** reads the call graph *before* the edit; every caller is on screen first. |
75
+ | The agent ships a fourth copy of a pattern your team standardized — the rule in `.cursorrules` was never honored. | **Anchored rules** surface the standard the moment the agent touches that scope, and re-anchor when the code moves instead of going silently stale. |
76
+ | Five single-purpose MCP servers — memory, graph, compressor — that can't reach across each other. | **One runtime** — cascade guard, convention drift, and a loop breaker (*stops the agent re-trying a fix that already failed twice*) fire on joins no point tool can make. |
50
77
 
51
78
  ---
52
79
 
53
80
  ## The pains this fixes
54
81
 
55
- You've felt all four of these in the last 48 hours:
82
+ If you're maintaining a large, existing codebase, you've hit these and they get *worse* as the repo grows, not better:
56
83
 
57
- - Claude is brilliant for 20 minutes, then hallucinates a duplicate component and forgets the styling rules you set five turns ago.
58
- - More time spent writing `MEMORY.md`, updating `.cursorrules`, and pasting session summaries than writing code.
59
- - The agent reads a 2,000-line file to find a 5-line function, then still doesn't know that function has 24 callers in six other files.
60
- - You don't trust the agent to refactor anything important. It treats your codebase like a flat string of text locally correct, globally wrong.
84
+ - The agent is sharp for 20 minutes, then writes a second implementation of something you already have, because it never saw the first.
85
+ - You don't trust it to change anything load-bearing. It treats your codebase as a flat string of text — locally correct, globally wrong.
86
+ - It reads a 2,000-line file to find a 5-line function, then still doesn't know that function has 24 callers in six other files.
87
+ - The rule you wrote in `.cursorrules` gets acknowledged, then ignored a few turns later once the context fills up.
61
88
 
62
- These aren't four problems. They're one: today's agents are incredibly smart but structurally blind and severely amnesiac. They grep when a senior engineer would check the call graph. They forget on Tuesday what they learned on Monday.
89
+ These aren't four problems. They're one: **your agent acts on a codebase it can't hold in context, with no idea what each change will break or which rule it's about to violate.**
63
90
 
64
91
  ---
65
92
 
@@ -68,7 +95,7 @@ These aren't four problems. They're one: today's agents are incredibly smart but
68
95
  | You feel | What unerr does |
69
96
  |---|---|
70
97
  | **Trust returns.** The agent runs for an hour without you watching. | Every edit is preceded by a graph lookup. All 24 callers are visible *before* it touches the function. Refactors stop rippling silently. |
71
- | **The babysitter tax disappears.** You delete `MEMORY.md` and `.cursorrules`. | A local fact store remembers what you decided, what failed, and the conventions the team accretedwith decay-adjusted confidence. Open the laptop on Tuesday and the agent already knows what you decided on Monday. |
98
+ | **Your rules finally get honored.** The standard you set is enforced at the edit, not acknowledged and forgotten once context fills up. | unerr anchors each rule and decision to the file or entity it governs and surfaces it the instant the agent touches that scope then re-anchors it when the code moves, so it never goes silently stale. Keep your `.cursorrules` and specs; unerr makes sure they're applied. |
72
99
  | **The agent stays sharp at turn 50.** | `file_read({entity})` returns 200 lines instead of 3,000. Shell output is compressed 93% on average. The context window stays uncluttered, so the model isn't fighting "lost in the middle." |
73
100
  | **Tool sprawl dies.** | One graph, one set of tools, project-aware routing. Five MCP servers no longer compete for the agent's attention. |
74
101
 
@@ -159,9 +186,9 @@ Close and reopen your IDE (or start a new chat session). Your agent picks up une
159
186
 
160
187
  ## Who it's for
161
188
 
162
- - **Vibe coders.** The thing that stops your app from breaking on turn 30 when the AI gets confused.
163
- - **Solo builders.** The continuous thread. Switch from Claude Code in the terminal to Cursor in the IDE your project memory comes with you.
164
- - **Senior / staff engineers.** The dependency graph, prior incidents, and team conventions a human engineer would already carry in their head fed to AI on every edit.
189
+ - **Engineers on large, existing codebases.** The dependency graph, the load-bearing patterns, and the prior incidents a senior engineer carries in their head — handed to the agent before every edit, so it stops breaking callers it never read.
190
+ - **Teams with conventions worth enforcing.** The standard you agreed on once, applied every time the agent touches that scope no `.cursorrules` file to hand-maintain, re-paste, or merge-conflict over.
191
+ - **Solo builders shipping into a codebase that's already grown.** The continuous thread across tools switch from Claude Code in the terminal to Cursor in the IDE and the graph, rules, and history come with you, instead of relearning the repo every session.
165
192
 
166
193
  ---
167
194
 
package/dist/cli.js CHANGED
@@ -5639,13 +5639,13 @@ var init_temporal_facts = __esm({
5639
5639
  session_analysis: 0.6,
5640
5640
  user_fed: 0.95
5641
5641
  };
5642
- MAX_CONTENT_LENGTH = 500;
5642
+ MAX_CONTENT_LENGTH = 1400;
5643
5643
  TYPE_CONTENT_LIMITS = {
5644
- convention: 600,
5645
- semantic: 500,
5646
- episodic: 800,
5647
- procedural: 400,
5648
- negative: 400
5644
+ convention: 1400,
5645
+ semantic: 1400,
5646
+ episodic: 1600,
5647
+ procedural: 1400,
5648
+ negative: 1400
5649
5649
  };
5650
5650
  TemporalFactStore = class _TemporalFactStore {
5651
5651
  constructor(db, config) {
@@ -12996,12 +12996,12 @@ function formatSessionResumeBlock(payload) {
12996
12996
  );
12997
12997
  if (payload.last_intents && payload.last_intents.length > 0) {
12998
12998
  const intent = payload.last_intents[0];
12999
- if (intent) parts.push(`\u25B8 last intent: ${intent.text}`);
12999
+ if (intent) parts.push(`\u25B8 last intent: ${truncateForStrip(intent.text)}`);
13000
13000
  }
13001
13001
  if (payload.open_blockers && payload.open_blockers.length > 0) {
13002
13002
  for (const b of payload.open_blockers.slice(0, 3)) {
13003
13003
  const anchor = b.file_path ? ` [${b.file_path}]` : "";
13004
- parts.push(`\u25B8 unresolved blocker: ${b.text}${anchor}`);
13004
+ parts.push(`\u25B8 unresolved blocker: ${truncateForStrip(b.text)}${anchor}`);
13005
13005
  }
13006
13006
  }
13007
13007
  if (payload.broken_callers && payload.broken_callers.length > 0) {
@@ -13031,6 +13031,10 @@ function formatSessionResumeBlock(payload) {
13031
13031
  const result = parts.join("\n");
13032
13032
  return result.length > 500 ? `${result.slice(0, 497)}...` : result;
13033
13033
  }
13034
+ function truncateForStrip(text2, max = 100) {
13035
+ const t = text2.trim();
13036
+ return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
13037
+ }
13034
13038
  function formatElapsed(ms) {
13035
13039
  const minutes = Math.floor(ms / 6e4);
13036
13040
  if (minutes < 60) return `${minutes}m`;
@@ -26756,27 +26760,27 @@ var init_tool_descriptions = __esm({
26756
26760
  // ── Tier 3 — intent unlock ─────────────────────────────────────────────
26757
26761
  mark_intent: {
26758
26762
  tier: 3,
26759
- active: "REQUIRED first on coding tasks (implement/fix/refactor/build). \u226480 chars summary. Skip only for read-only questions. Powers resume strip.",
26760
- locked: "[locked, unlock: first non-trivial action] REQUIRED first on coding tasks. \u226480 chars summary.",
26761
- unlocked: "REQUIRED first on coding tasks. \u226480 chars summary. One per task; powers turn titles + cross-session resume."
26763
+ active: "REQUIRED first on coding tasks (implement/fix/refactor/build). 1 terse sentence. Skip only for read-only questions. Powers resume strip.",
26764
+ locked: "[locked, unlock: first non-trivial action] REQUIRED first on coding tasks. 1 terse sentence.",
26765
+ unlocked: "REQUIRED first on coding tasks. 1 terse sentence. One per task; powers turn titles + cross-session resume."
26762
26766
  },
26763
26767
  mark_decision: {
26764
26768
  tier: 3,
26765
- active: "Record a deliberate choice between alternatives (\u2264140 chars). Optional list of considered alternatives.",
26769
+ active: "Record a deliberate choice between alternatives (1-2 sentences, \u22641400 chars). Optional list of considered alternatives.",
26766
26770
  locked: "[locked, unlock: after mark_intent] Record a deliberate choice between alternatives.",
26767
- unlocked: "Record a deliberate choice (\u2264140 chars). Optional alternatives list (\u22645, each \u226480 chars). Surfaces in timeline."
26771
+ unlocked: "Record a deliberate choice (1-2 sentences, \u22641400 chars). Optional alternatives list (\u22645, each \u226480 chars). Surfaces in timeline."
26768
26772
  },
26769
26773
  mark_blocker: {
26770
26774
  tier: 3,
26771
- active: "Record an unresolved obstacle (\u2264140 chars). Returned marker_id is required by mark_resolution when fixed.",
26775
+ active: "Record an unresolved obstacle (1-2 sentences, \u22641400 chars). Returned marker_id is required by mark_resolution when fixed.",
26772
26776
  locked: "[locked, unlock: after mark_intent] Record an unresolved obstacle.",
26773
- unlocked: "Record an obstacle (\u2264140 chars). Returned marker_id must be passed to mark_resolution when fixed. Surfaces in resume."
26777
+ unlocked: "Record an obstacle (1-2 sentences, \u22641400 chars). Returned marker_id must be passed to mark_resolution when fixed. Surfaces in resume."
26774
26778
  },
26775
26779
  mark_resolution: {
26776
26780
  tier: 3,
26777
26781
  active: "Resolve a prior blocker. blocker_ref is the marker_id from mark_blocker; text describes the fix.",
26778
26782
  locked: "[locked, unlock: after mark_blocker] Resolve a prior blocker.",
26779
- unlocked: "Resolve a prior blocker. blocker_ref is the marker_id from mark_blocker. Text (\u2264140 chars) describes the fix."
26783
+ unlocked: "Resolve a prior blocker. blocker_ref is the marker_id from mark_blocker. Text (1-3 sentences, \u22641400 chars) describes the fix."
26780
26784
  },
26781
26785
  recall_facts: {
26782
26786
  tier: 3,
@@ -27238,7 +27242,7 @@ var init_tool_definitions = __esm({
27238
27242
  properties: {
27239
27243
  text: {
27240
27244
  type: "string",
27241
- description: "One short sentence describing the task (\u226480 chars)."
27245
+ description: "Task in 1 terse sentence. Hard cap 1400 chars; be concise."
27242
27246
  }
27243
27247
  },
27244
27248
  required: ["text"]
@@ -27255,7 +27259,7 @@ var init_tool_definitions = __esm({
27255
27259
  properties: {
27256
27260
  text: {
27257
27261
  type: "string",
27258
- description: "One short sentence describing the decision (\u2264140 chars)."
27262
+ description: "Decision in 1-2 sentences. Hard cap 1400 chars; be concise."
27259
27263
  },
27260
27264
  alternatives: {
27261
27265
  type: "array",
@@ -27277,7 +27281,7 @@ var init_tool_definitions = __esm({
27277
27281
  properties: {
27278
27282
  text: {
27279
27283
  type: "string",
27280
- description: "One short sentence describing the blocker (\u2264140 chars)."
27284
+ description: "Blocker in 1-2 sentences. Hard cap 1400 chars; be concise."
27281
27285
  },
27282
27286
  file_path: {
27283
27287
  type: "string",
@@ -27302,7 +27306,7 @@ var init_tool_definitions = __esm({
27302
27306
  },
27303
27307
  text: {
27304
27308
  type: "string",
27305
- description: "One short sentence describing the resolution (\u2264140 chars)."
27309
+ description: "What fixed the blocker, in 1-3 sentences. Hard cap 1400 chars."
27306
27310
  }
27307
27311
  },
27308
27312
  required: ["blocker_ref", "text"]
@@ -27425,7 +27429,7 @@ var init_tool_definitions = __esm({
27425
27429
  // ── Legacy free-form (TemporalFactStore) payload ──
27426
27430
  content: {
27427
27431
  type: "string",
27428
- description: "Normalised statement of the user's fact (max 280 chars). Keep terse."
27432
+ description: "Normalised statement of the user's fact, 1-3 sentences. Hard cap 1400 chars; keep terse."
27429
27433
  },
27430
27434
  source_quote: {
27431
27435
  type: "string",
@@ -28023,9 +28027,9 @@ async function executeRecordFact(args, factStore, sessionId) {
28023
28027
  if (!content || content.trim().length === 0) {
28024
28028
  throw new Error("content is required and cannot be empty");
28025
28029
  }
28026
- if (content.length > 280) {
28030
+ if (content.length > 1400) {
28027
28031
  throw new Error(
28028
- `content exceeds 280 character limit (got ${content.length}). Shorten the fact.`
28032
+ `content is ${content.length} chars, exceeds 1400-char cap. Shorten to \u22641400 (1-3 sentences).`
28029
28033
  );
28030
28034
  }
28031
28035
  if (!["procedural", "semantic", "negative", "convention"].includes(fact_type)) {
@@ -28048,11 +28052,8 @@ async function executeRecordFact(args, factStore, sessionId) {
28048
28052
  base_confidence: 0.95
28049
28053
  };
28050
28054
  const { fact_id, deduplicated } = await factStore.createFact(input);
28051
- const verb = deduplicated ? "Reinforced" : "Recorded";
28052
- const message = `${verb}: "${content.trim().slice(0, 60)}${content.trim().length > 60 ? "..." : ""}" [${fact_type}] \u2192 ${scope}`;
28053
28055
  return {
28054
28056
  fact_id,
28055
- message,
28056
28057
  deduplicated
28057
28058
  };
28058
28059
  }
@@ -28193,9 +28194,9 @@ async function executeUnerrRemember(args, factStore, sessionId, turn, behaviorEv
28193
28194
  if (!content || content.trim().length === 0) {
28194
28195
  throw new Error("content is required and cannot be empty");
28195
28196
  }
28196
- if (content.length > 280) {
28197
+ if (content.length > 1400) {
28197
28198
  throw new Error(
28198
- `content exceeds 280 character limit (got ${content.length}). Shorten the fact.`
28199
+ `content is ${content.length} chars, exceeds 1400-char cap. Shorten to \u22641400 (1-3 sentences).`
28199
28200
  );
28200
28201
  }
28201
28202
  if (!source_quote || source_quote.trim().length === 0) {
@@ -28248,11 +28249,6 @@ async function executeUnerrRemember(args, factStore, sessionId, turn, behaviorEv
28248
28249
  });
28249
28250
  const ambiguity_flag = confidence < REMEMBER_AMBIGUITY_THRESHOLD;
28250
28251
  const trimmedContent = content.trim();
28251
- const previewContent = `${trimmedContent.slice(0, 60)}${trimmedContent.length > 60 ? "..." : ""}`;
28252
- const scopeText = scope.trim() === "project" ? "the whole project" : `\`${scope.trim()}\``;
28253
- const verb = deduplicated ? "reinforced an existing note" : "added a new note";
28254
- const tail = ambiguity_flag ? ` \u2014 I'm only ${Math.round(confidence * 100)}% sure I got that right, please confirm or correct` : "";
28255
- const echo_summary = `unerr ${verb} for ${scopeText}: "${previewContent}"${tail}`;
28256
28252
  if (ambiguity_flag && pendingConfirmations) {
28257
28253
  pendingConfirmations.register({
28258
28254
  fact_id,
@@ -28289,8 +28285,7 @@ async function executeUnerrRemember(args, factStore, sessionId, turn, behaviorEv
28289
28285
  fact_id,
28290
28286
  deduplicated,
28291
28287
  ambiguity_flag,
28292
- confidence,
28293
- echo_summary
28288
+ confidence
28294
28289
  };
28295
28290
  }
28296
28291
  var REMEMBER_CONFIDENCE_FLOOR, REMEMBER_AMBIGUITY_THRESHOLD;
@@ -42158,6 +42153,7 @@ var init_loop_breaker = __esm({
42158
42153
  // src/tools/intelligence/timeline-markers.ts
42159
42154
  var timeline_markers_exports = {};
42160
42155
  __export(timeline_markers_exports, {
42156
+ MARKER_TEXT_CAP: () => MARKER_TEXT_CAP,
42161
42157
  MARKER_TOOLS: () => MARKER_TOOLS,
42162
42158
  handleMarkerCall: () => handleMarkerCall,
42163
42159
  isMarkerTool: () => isMarkerTool
@@ -42172,7 +42168,9 @@ async function handleMarkerCall(toolName, args, deps) {
42172
42168
  return errorResult2(`${toolName}: text is required`);
42173
42169
  }
42174
42170
  if (text2.length > cap2) {
42175
- return errorResult2(`${toolName}: text exceeds ${cap2}-char limit`);
42171
+ return errorResult2(
42172
+ `${toolName}: text is ${text2.length} chars, exceeds ${cap2}-char cap \u2014 shorten to \u2264${cap2}.`
42173
+ );
42176
42174
  }
42177
42175
  const argsToPersist = { text: text2 };
42178
42176
  let blockerRef = "";
@@ -42251,7 +42249,7 @@ function errorResult2(msg) {
42251
42249
  ]
42252
42250
  };
42253
42251
  }
42254
- var MARKER_TOOLS, MARKER_TOOL_SET, TEXT_CAP;
42252
+ var MARKER_TOOLS, MARKER_TOOL_SET, MARKER_TEXT_CAP, TEXT_CAP;
42255
42253
  var init_timeline_markers = __esm({
42256
42254
  "src/tools/intelligence/timeline-markers.ts"() {
42257
42255
  "use strict";
@@ -42263,11 +42261,12 @@ var init_timeline_markers = __esm({
42263
42261
  "mark_resolution"
42264
42262
  ];
42265
42263
  MARKER_TOOL_SET = new Set(MARKER_TOOLS);
42264
+ MARKER_TEXT_CAP = 1400;
42266
42265
  TEXT_CAP = {
42267
- mark_intent: 80,
42268
- mark_decision: 140,
42269
- mark_blocker: 140,
42270
- mark_resolution: 140
42266
+ mark_intent: MARKER_TEXT_CAP,
42267
+ mark_decision: MARKER_TEXT_CAP,
42268
+ mark_blocker: MARKER_TEXT_CAP,
42269
+ mark_resolution: MARKER_TEXT_CAP
42271
42270
  };
42272
42271
  }
42273
42272
  });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@unerr-ai/unerr",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "mcpName": "io.github.unerr-ai/unerr",
5
- "description": "Local-first code intelligence CLI for unerr",
5
+ "description": "Your AI agent has read your codebase but still can't safely change it. unerr is a local guardrail that hands the agent the call graph and your rules at the moment it edits.",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/unerr-ai/unerr-cli.git"