@unerr-ai/unerr 0.2.10 → 0.2.11
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.
- package/README.md +102 -116
- package/dist/cli.js +116 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>
|
|
6
|
+
<strong>To make a coding agent work well on real code, you end up bolting a bunch of separate things onto it —<br/>
|
|
7
|
+
one to find the right code, one to stop it forgetting, one to trim the clutter, your rules, a few checks to catch<br/>
|
|
8
|
+
mistakes. You set them all up, you keep them running, and it still ignores half of them — because each one is<br/>
|
|
9
|
+
only advice it can skip, and they all pull at its attention at once.</strong>
|
|
7
10
|
</p>
|
|
8
11
|
|
|
9
12
|
<p align="center">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
that rule when the code moves, so it never goes quietly stale. The 24 callers and the standard it's about to break are<br/>
|
|
15
|
-
on screen <em>before</em> the function changes. Every time. Whether or not the agent thought to ask.
|
|
13
|
+
unerr puts all of that into one piece, built into the way the agent already works — so it's not one more thing the<br/>
|
|
14
|
+
agent can choose to ignore. As the agent goes, it finds the right code, keeps your rules in front of it, trims the<br/>
|
|
15
|
+
clutter, and catches a break before it lands — all together, with nothing for you to set up. The agent wastes less<br/>
|
|
16
|
+
time, money, and attention redoing work, and you waste less of yours setting tools up and cleaning up after it.
|
|
16
17
|
</p>
|
|
17
18
|
|
|
18
19
|
<p align="center">
|
|
@@ -25,13 +26,18 @@
|
|
|
25
26
|
<img src="https://img.shields.io/badge/runtime-Node.js_≥20-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node.js" />
|
|
26
27
|
<img src="https://img.shields.io/badge/protocol-MCP-7C3AED?style=flat-square" alt="MCP" />
|
|
27
28
|
<img src="https://img.shields.io/badge/local--first-no_cloud-22D3EE?style=flat-square" alt="Local-first" />
|
|
28
|
-
<img src="https://img.shields.io/badge/license-
|
|
29
|
+
<img src="https://img.shields.io/badge/license-Apache--2.0-A1A1AA?style=flat-square" alt="License" />
|
|
29
30
|
</p>
|
|
30
31
|
|
|
31
32
|
<p align="center">
|
|
32
33
|
<code>npm install -g @unerr-ai/unerr</code>
|
|
33
34
|
<br /><br />
|
|
34
|
-
<sub>
|
|
35
|
+
<sub>Install, restart your IDE, and the next prompt already knows your repo. No config, no account, nothing leaves your machine.</sub>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<a href="https://youtu.be/pL1izMwYZpI"><img src="https://unerr.dev/open-cli/video/unerr-cascade.gif" alt="unerr firing inside a live Claude Code session — 12 dependent call sites surfaced before a signature edit" width="760" /></a>
|
|
40
|
+
<br/><sub><strong>Live, inside the agent</strong> · the agent tries to change <code>extractFilePath</code>; before the edit lands, unerr surfaces the <strong>12 places that depend on it across 4 files</strong> — so it fixes every one in the same turn instead of breaking them silently. ▶ <a href="https://youtu.be/pL1izMwYZpI">Watch the full demo</a>.</sub>
|
|
35
41
|
</p>
|
|
36
42
|
|
|
37
43
|
---
|
|
@@ -39,103 +45,86 @@
|
|
|
39
45
|
<details>
|
|
40
46
|
<summary><strong>Contents</strong></summary>
|
|
41
47
|
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
44
|
-
- [What changes when you
|
|
48
|
+
- [Why I built this](#why-i-built-this)
|
|
49
|
+
- [What's actually going wrong](#whats-actually-going-wrong)
|
|
50
|
+
- [What changes when you use it](#what-changes-when-you-use-it)
|
|
45
51
|
- [See it in action](#see-it-in-action)
|
|
46
52
|
- [Quick Start](#quick-start)
|
|
47
53
|
- [Who it's for](#who-its-for)
|
|
48
|
-
- [Why
|
|
49
|
-
- [
|
|
50
|
-
- [
|
|
54
|
+
- [Why it's one thing and not five plugins](#why-its-one-thing-and-not-five-plugins)
|
|
55
|
+
- [What it does under the hood](#what-it-does-under-the-hood)
|
|
56
|
+
- [About the fewer tokens](#about-the-fewer-tokens)
|
|
51
57
|
- [License](#license)
|
|
52
58
|
|
|
53
59
|
</details>
|
|
54
60
|
|
|
55
61
|
---
|
|
56
62
|
|
|
57
|
-
##
|
|
63
|
+
## Why I built this
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
I built unerr because I got tired of cleaning up after my own coding agent.
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
I was running coding agents on real work — not toy projects — and to stop them from messing things up I kept bolting on extra stuff. A memory file here, some rules there, something to keep the agent from forgetting what it was doing, a few guardrails. And two things drove me crazy.
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|---|---|---|
|
|
65
|
-
| **Tells the agent things.** Memory stores, code-graph servers, context packers, rule files. | A tool the agent calls *when it remembers to.* | Optional context is optional. Agents skip the retrieval tool **~58% of the time even when explicitly told to use it** ([CodeCompass, 2026](https://arxiv.org/abs/2602.20048)). Advice it can ignore, it ignores. |
|
|
66
|
-
| **Checks the agent afterward.** Reviewers, linters, CI gates. | A pass over the diff *after the code is written.* | The break already happened. Now it's a comment on a pull request and a second round of work — not a change that never broke anything. |
|
|
69
|
+
One: setting all that up is its own job, and every one of those things is really just a *suggestion* to the agent. A rule it can acknowledge and then ignore once it gets busy. A memory it has to remember to check. A reviewer that only speaks up after the break is already written. They don't work together — they compete for the agent's attention, and half of them get dropped exactly when you need them.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Two: while all that's going on, the agent is burning time and money redoing work and breaking things it shouldn't have touched — and I'm sitting there babysitting it, because the one time I look away is the time it quietly breaks something that matters.
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
unerr is the thing I wanted to exist: one piece that does all of that itself, right while the agent is working — so you don't have to assemble a toolchain, or write a flawless prompt every time, or sit through the back-and-forth just to trust what it ships. The agent wastes less of its own time and a lot less of your money, and you spend far less effort watching over it.
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|---|---|
|
|
74
|
-
| The agent changes a function without reading its 24 callers — 7 sites break silently. | **Cascade guard** puts the call graph in front of the edit *before it runs* — every caller on screen, no asking required. |
|
|
75
|
-
| You wrote the rule in `.cursorrules`. The agent acknowledged it, then ignored it once context filled up. | **Anchored rules** surface the standard the instant the agent touches that scope — and re-anchor when the code moves instead of going stale. |
|
|
76
|
-
| A rule or spec stays confident long after the code moved out from under it. Nothing recomputes it. | Every fact is pinned to a live entity in the graph. When the code moves, the fact **fails loud** instead of staying silently wrong. |
|
|
75
|
+
It's free, open source, and runs entirely on your machine.
|
|
77
76
|
|
|
78
77
|
---
|
|
79
78
|
|
|
80
|
-
##
|
|
79
|
+
## What's actually going wrong
|
|
80
|
+
|
|
81
|
+
On any codebase big enough to matter, the agent can't hold the whole thing in its head. So it works from the slice it can see and never looks at the rest. It changes a function and breaks the other places that call it — places it never read. It writes a fourth copy of a pattern your team already settled on, even with the rule sitting right there in your `.cursorrules`. Neither of those shows up as an error. They show up later, as your afternoon.
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
The usual fixes both leak:
|
|
83
84
|
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
86
|
-
- **The rule you wrote gets acknowledged, then dropped.** A few turns later the context fills up and your `.cursorrules` line may as well not exist.
|
|
87
|
-
- **Approval fatigue.** You approve so many reasonable edits that the dangerous one slides through — the hundredth confirmation looks exactly like the first.
|
|
85
|
+
- **Things that *tell* the agent stuff** — memory stores, rule files, context tools — only help when the agent remembers to use them. Optional advice is optional, and a busy agent skips it.
|
|
86
|
+
- **Things that *check* the agent afterward** — reviewers, linters, CI — only speak up after the code is already written. By then it's a pull-request comment and a second round of work, not a break that never happened.
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
And every one of these is a separate thing you have to install, configure, and keep current. The more you add, the more they pull against each other for the agent's limited attention, and the more of your time goes into maintaining the setup instead of shipping.
|
|
89
|
+
|
|
90
|
+
unerr closes that gap by doing the work at the moment it matters — when the agent reads and when it edits — instead of waiting to be asked or waiting to complain after the fact. The agent doesn't have to remember anything. The thing that would have stopped the break is already in front of it, before the change lands.
|
|
90
91
|
|
|
91
92
|
---
|
|
92
93
|
|
|
93
|
-
## What changes when you
|
|
94
|
+
## What changes when you use it
|
|
94
95
|
|
|
95
|
-
|
|
|
96
|
+
| What you feel | What's happening |
|
|
96
97
|
|---|---|
|
|
97
|
-
| **You stop babysitting.** The agent runs for an hour and you're not bracing for a silent break. |
|
|
98
|
-
| **Your rules finally
|
|
99
|
-
| **It stops
|
|
100
|
-
| **
|
|
98
|
+
| **You stop babysitting.** The agent runs for an hour and you're not bracing for a silent break. | Before it changes a function, unerr shows it every other place that depends on that function — on its own, without the agent asking. |
|
|
99
|
+
| **Your rules finally stick.** The standard you set gets applied at the edit, not acknowledged and forgotten three turns later. | unerr ties each rule to the part of the code it's about and brings it up the moment the agent touches that part — and keeps it pinned there even after the code moves. |
|
|
100
|
+
| **It stops going in circles.** No more watching it try the same broken fix three times. | unerr notices when the agent is re-trying something that already failed and stops it before it burns another turn. |
|
|
101
|
+
| **It stays sharp deep into a long session.** | unerr hands the agent the small, relevant slice of a file or a command's output instead of dumping thousands of lines into the window, so the model isn't drowning in noise by turn 50. |
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
Here's what it actually looks like in your chat. Before the edit runs, unerr drops a line like this into the agent's context, on its own:
|
|
103
104
|
|
|
104
|
-
> ⚡ unerr ·
|
|
105
|
+
> ⚡ unerr · editing `src/payments/gateway.ts` changes a function that **24 other places depend on, across 6 files**. Update every one of them in this same change before finishing.
|
|
105
106
|
|
|
106
|
-
The
|
|
107
|
+
The result is an agent that behaves a lot more like a careful senior engineer: it checks what a change affects before making it, honors the standards you set, and doesn't keep retrying something that already failed.
|
|
107
108
|
|
|
108
109
|
---
|
|
109
110
|
|
|
110
111
|
## See it in action
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
The demo above is one moment, caught live. Day to day, there are two places you watch it working — in the chat, and in a browser.
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
<a href="https://youtu.be/pL1izMwYZpI"><img src="https://unerr.dev/open-cli/video/unerr-cascade.gif" alt="unerr cascade guard firing inside a live Claude Code session — 12 callers surfaced before a signature edit" width="760" /></a>
|
|
116
|
-
<br/><sub><strong>Cascade guard, live</strong> · unerr catches the 12 callers of <code>extractFilePath</code> before the edit ripples. ▶ <a href="https://youtu.be/pL1izMwYZpI">Watch the full demo on YouTube</a>.</sub>
|
|
117
|
-
</p>
|
|
118
|
-
|
|
119
|
-
Two places unerr shows up so you know it's working — inside the chat, and in a browser.
|
|
115
|
+
**In the chat.** Every coding turn opens with one line naming what unerr brought in ("brought in a convention you wrote yesterday for `src/payments/gateway.ts`…") and closes with one line totalling what it caught and saved you. The catches are named, countable events — not a vague percentage.
|
|
120
116
|
|
|
121
|
-
**
|
|
122
|
-
|
|
123
|
-
**In a browser.** A live dashboard at `http://localhost:9847` reads from the same store the agent reads from over MCP — the graph it navigates, the facts it remembers, the breaks it caught, and the score showing which of those facts actually shaped the next answer.
|
|
117
|
+
**In a browser.** A live dashboard at `http://localhost:9847` reads from the same place the agent reads from — what it remembers, what it caught, and which of those things actually shaped the next answer.
|
|
124
118
|
|
|
125
119
|
<p align="center">
|
|
126
|
-
<img src="https://unerr.dev/open-cli/screenshots/end-of-turn-receipt.png" alt="unerr end-of-turn receipt —
|
|
127
|
-
<img src="https://unerr.dev/open-cli/screenshots/end-of-turn-receipt-2.png" alt="unerr end-of-turn receipt — named, countable catches
|
|
128
|
-
<br/><sub><strong>End-of-turn receipt</strong> · every
|
|
120
|
+
<img src="https://unerr.dev/open-cli/screenshots/end-of-turn-receipt.png" alt="unerr end-of-turn receipt — what it caught and saved this turn" width="380" />
|
|
121
|
+
<img src="https://unerr.dev/open-cli/screenshots/end-of-turn-receipt-2.png" alt="unerr end-of-turn receipt — named, countable catches at the close of a turn" width="380" />
|
|
122
|
+
<br/><sub><strong>End-of-turn receipt</strong> · every turn closes with one line totalling what unerr caught and saved you — named, countable, not a ratio.</sub>
|
|
129
123
|
</p>
|
|
130
124
|
|
|
131
125
|
<p align="center">
|
|
132
126
|
<img src="https://unerr.dev/open-cli/screenshots/dashboard.png" alt="unerr dashboard — live overview" width="300" />
|
|
133
|
-
<br/><sub><strong>Dashboard</strong> · live overview — active sessions, recent
|
|
134
|
-
</p>
|
|
135
|
-
|
|
136
|
-
<p align="center">
|
|
137
|
-
<img src="https://unerr.dev/open-cli/screenshots/token-trace-main.png" alt="unerr token trace" width="300" />
|
|
138
|
-
<br/><sub><strong>Token Trace</strong> · context kept out of the window, broken down by mechanism — graph hits, skipped re-reads, compressed shell output, deduped fetches.</sub>
|
|
127
|
+
<br/><sub><strong>Dashboard</strong> · live overview — active sessions, recent activity, breaks caught.</sub>
|
|
139
128
|
</p>
|
|
140
129
|
|
|
141
130
|
<p align="center"><sub>More views in the <a href="https://www.unerr.dev/">full dashboard tour</a>.</sub></p>
|
|
@@ -152,16 +141,16 @@ Three steps. Step 1 is once per machine; steps 2–3 are per repo.
|
|
|
152
141
|
npm install -g @unerr-ai/unerr
|
|
153
142
|
```
|
|
154
143
|
|
|
155
|
-
Puts the `unerr` binary on your PATH. If your shell can't find it (
|
|
144
|
+
Puts the `unerr` binary on your PATH. If your shell can't find it afterward (this happens with nvm, fnm, volta, and pnpm), run `unerr doctor` once — it patches your shell config and won't need to run again.
|
|
156
145
|
|
|
157
|
-
### 2.
|
|
146
|
+
### 2. Set it up for your agent (per repo)
|
|
158
147
|
|
|
159
148
|
```bash
|
|
160
149
|
cd ~/your-project
|
|
161
150
|
unerr install cursor
|
|
162
151
|
```
|
|
163
152
|
|
|
164
|
-
|
|
153
|
+
That writes the MCP config, skills, hooks, and instructions for that agent in the current repo. Swap `cursor` for any of the supported agents:
|
|
165
154
|
|
|
166
155
|
```bash
|
|
167
156
|
unerr install claude-code
|
|
@@ -172,73 +161,60 @@ unerr install gemini-cli
|
|
|
172
161
|
unerr install github-copilot-cli
|
|
173
162
|
```
|
|
174
163
|
|
|
175
|
-
|
|
164
|
+
You can install more than one agent in the same repo — each writes its own config. Re-running updates the setup if anything changed and skips it if nothing did. Remove it with `unerr uninstall`.
|
|
176
165
|
|
|
177
166
|
### 3. Restart your IDE
|
|
178
167
|
|
|
179
|
-
Close and reopen your IDE
|
|
168
|
+
Close and reopen your IDE, or start a new chat session. Your agent picks up unerr through MCP and everything is available from the next prompt.
|
|
180
169
|
|
|
181
|
-
> **Dashboard:** <http://localhost:9847> — open any time to watch unerr
|
|
170
|
+
> **Dashboard:** <http://localhost:9847> — open it any time to watch unerr work.
|
|
182
171
|
|
|
183
|
-
>
|
|
172
|
+
> Using a different MCP client, or setting it up by hand? `unerr install --show-instructions <agent>` prints copy-pasteable steps.
|
|
184
173
|
|
|
185
174
|
---
|
|
186
175
|
|
|
187
176
|
## Who it's for
|
|
188
177
|
|
|
189
|
-
- **Engineers
|
|
190
|
-
- **Teams with conventions worth
|
|
191
|
-
- **Solo builders shipping into a codebase that's already grown.**
|
|
178
|
+
- **Engineers working in large, existing codebases.** The things a senior engineer keeps in their head — what depends on what, which patterns are load-bearing, what broke here before — handed to the agent before every edit, so it stops breaking code it never read.
|
|
179
|
+
- **Teams with conventions worth keeping.** The standard you agreed on once, applied every time the agent touches that part of the code — no rules file to hand-maintain, re-paste, or fight merge conflicts over, and no hoping the agent remembers to look.
|
|
180
|
+
- **Solo builders and vibe coders shipping into a codebase that's already grown.** One continuous thread across your tools — move from Claude Code in the terminal to Cursor in the IDE and what unerr knows about your repo comes with you, instead of relearning it every session.
|
|
192
181
|
|
|
193
182
|
---
|
|
194
183
|
|
|
195
|
-
## Why
|
|
196
|
-
|
|
197
|
-
A guardrail the agent *can't skip* can't be a tool the agent chooses to call. That's the whole reason unerr is one local runtime sitting *behind* the MCP every agent already speaks — not a fifth server in the agent's tool list.
|
|
184
|
+
## Why it's one thing and not five plugins
|
|
198
185
|
|
|
199
|
-
|
|
186
|
+
This is the part that took me a while to get right, so it's worth saying plainly.
|
|
200
187
|
|
|
201
|
-
|
|
188
|
+
Every coding agent on your machine speaks the same protocol, MCP. MCP carries requests the agent *chooses* to make — it doesn't hand the agent context on its own, and it doesn't fire anything by itself. So a memory plugin, a code-search plugin, and a context trimmer all just sit there waiting to be called. And an agent that's busy or low on room skips the thing it has to remember to call. That's the whole leak.
|
|
202
189
|
|
|
203
|
-
|
|
204
|
-
- **Drift** needs memory that's anchored to a live graph — so the fact knows the moment its code moved.
|
|
205
|
-
- **Convention drift** needs the auto-detected pattern store *and* the new-code stream in the same memory space.
|
|
206
|
-
- **Loop breaker** needs the full timeline of what the agent already tried.
|
|
190
|
+
unerr doesn't sit and wait. It steps in at the moments that matter — when the agent reads a file, when it's about to make a change — and puts the one relevant thing in front of it automatically. You can't forget to call something that isn't waiting to be called.
|
|
207
191
|
|
|
208
|
-
|
|
192
|
+
The catch is that this only works if the pieces live together, because the useful ones each need information no single plugin has on its own:
|
|
209
193
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
194
|
+
- Catching a breaking change needs to know both *what the agent is about to edit* and *what depends on it* — at the same instant.
|
|
195
|
+
- Knowing a saved rule has gone stale needs that rule tied to the actual code, so it notices the moment the code moves.
|
|
196
|
+
- Spotting a convention slipping needs both the patterns your codebase already uses and the new code being written, side by side.
|
|
197
|
+
- Stopping a retry-loop needs the full history of what the agent already tried this session.
|
|
213
198
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
| Mechanism (the how) | What's inside | What it powers (the what) |
|
|
217
|
-
|---|---|---|
|
|
218
|
-
| **Live code graph** | CozoDB · tree-sitter ASTs · SCIP-verified call graphs · 18+ languages · <5ms queries | The agent opens 50 targeted lines and a caller list — not 3,000 lines and a guess. Read *before every file read*, so cascade guard knows what an edit breaks. |
|
|
219
|
-
| **Anchored memory** | Typed facts · conventions auto-detected at ≥70% adherence · decay-adjusted confidence | Every fact is pinned to a file or entity in the graph. When the code moves, the fact gets a **drift signal** — never silent staleness. |
|
|
220
|
-
| **Context delivery** | Shell output compression (645+ command classifiers) · web fetches (5–10× via Defuddle + BM25) · entity-targeted file reads | The relevant slice arrives automatically at the read — the agent never has to remember which tool to invoke for which content. |
|
|
221
|
-
| **Behaviour modules** | cascade guard · convention drift · loop breaker · session continuity · auto-doc · change narrative · architecture guard | Each guardrail fires on a join of the three above, *at the moment of the edit* — not as a tool the agent chose, not as a review after the fact. |
|
|
199
|
+
You can't buy those as five separate tools and bolt them together — they only exist when everything lives in one place. That's why unerr is one local thing, not a fifth plugin in your agent's list. And one thing instead of five means the agent isn't spending its attention deciding which plugin to call — a real cost once that list gets long. Researchers have measured a routine set of these add-ons eating [more than 20% of an agent's context window before it does any actual work](https://eclipsesource.com/blogs/2026/01/22/mcp-context-overload/).
|
|
222
200
|
|
|
223
|
-
|
|
201
|
+
(This isn't an MCP gateway that bundles your existing servers behind one address — those still hand the agent every tool up front. unerr replaces what those add-ons *do*, so there's nothing left to bundle.)
|
|
224
202
|
|
|
225
203
|
---
|
|
226
204
|
|
|
227
|
-
##
|
|
205
|
+
## What it does under the hood
|
|
228
206
|
|
|
229
|
-
|
|
207
|
+
One local process per repo. You don't have to think about any of this to use it — but if you want to know what's actually running, here it is.
|
|
230
208
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
The
|
|
237
|
-
|
|
238
|
-
---
|
|
209
|
+
| The piece | What's in it | What it gives the agent |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| **A live map of your code** | CozoDB · tree-sitter · SCIP-verified call data · 18+ languages · sub-5ms lookups | Before any file read, the agent gets the 50 lines that matter and the list of what depends on them — not 3,000 lines and a guess. |
|
|
212
|
+
| **Memory tied to the code** | typed facts · conventions auto-detected once a pattern holds ≥70% of the time · confidence that decays over time | Every saved fact is pinned to a real file or function. When that code moves, the fact flags itself instead of quietly going wrong. |
|
|
213
|
+
| **The right slice, delivered automatically** | shell-output trimming (645+ command types) · web pages fetched at 5–10× less bulk · function-targeted file reads | The relevant piece shows up at the moment the agent reads — it never has to remember which tool to reach for. |
|
|
214
|
+
| **The behaviors that catch problems** | breaking-change guard · convention-slip guard · retry-loop breaker · session continuity · auto-doc · change narrative · architecture guard | Each one fires on a combination of the three above, *at the moment of the edit* — not as a tool the agent picked, not as a review after the fact. |
|
|
239
215
|
|
|
240
216
|
<details>
|
|
241
|
-
<summary><strong>
|
|
217
|
+
<summary><strong>Architecture, CLI commands, MCP tools, manual config, dev setup</strong></summary>
|
|
242
218
|
|
|
243
219
|
### Architecture
|
|
244
220
|
|
|
@@ -265,19 +241,17 @@ AI Agent (Claude Code / Cursor / Windsurf / any MCP client)
|
|
|
265
241
|
|
|
266
242
|
One local DB per repo. Zero network calls. No API keys. No cloud. Your code never leaves the machine.
|
|
267
243
|
|
|
268
|
-
|
|
244
|
+
**Design principles** — zero network calls; stdout is sacred (MCP JSON-RPC only, everything else to stderr); sub-5ms query responses; first useful output in under 5s (shallow index first, deep enrichment in the background); graceful degradation (the agent still works if unerr is down — you just lose the extra layer).
|
|
269
245
|
|
|
270
|
-
**
|
|
271
|
-
|
|
272
|
-
**Tech stack** TypeScript (ESM) · CozoDB (Rust/NAPI) · web-tree-sitter (WASM) · MCP SDK · Ink (React CLI) · React + Vite (dashboard) · tsup · Vitest
|
|
246
|
+
**Tech stack** — TypeScript (ESM) · CozoDB (Rust/NAPI) · web-tree-sitter (WASM) · MCP SDK · Ink (React CLI) · React + Vite (dashboard) · tsup · Vitest
|
|
273
247
|
|
|
274
248
|
### CLI commands
|
|
275
249
|
|
|
276
250
|
```bash
|
|
277
251
|
unerr install <agent> # MCP config + skills + hooks + instructions for one agent
|
|
278
|
-
unerr uninstall # Remove unerr
|
|
252
|
+
unerr uninstall # Remove unerr from this repo
|
|
279
253
|
unerr doctor # Check PATH + environment, auto-fix if unerr isn't on all shells
|
|
280
|
-
unerr status #
|
|
254
|
+
unerr status # Process health, entity count, graph age
|
|
281
255
|
unerr stats # Session statistics (tokens, tool calls, compression)
|
|
282
256
|
unerr --mcp # Stdio bridge — what your IDE invokes via .mcp.json
|
|
283
257
|
|
|
@@ -286,7 +260,7 @@ unerr pm logs # Tail ~/.unerr/logs/unerrd.log
|
|
|
286
260
|
unerr pm dashboard # Open http://localhost:9847
|
|
287
261
|
```
|
|
288
262
|
|
|
289
|
-
`unerrd` is a lightweight Node process that supervises every registered repo. Your IDE invocation auto-spawns it; it exits cleanly after 30 minutes of no
|
|
263
|
+
`unerrd` is a lightweight Node process that supervises every registered repo. Your IDE invocation auto-spawns it; it exits cleanly after 30 minutes of no activity. `unerr pm --help` lists the rest.
|
|
290
264
|
|
|
291
265
|
### MCP tools (22)
|
|
292
266
|
|
|
@@ -300,7 +274,7 @@ Grouped by what the agent gets, not by file:
|
|
|
300
274
|
- **Web fetch (1)** — `fetch_url` (DOM-extracted markdown, BM25 re-ranking, content-hash cache). Replaces built-in WebFetch.
|
|
301
275
|
- **Code review (1)** — `review_changes` (graph-evidenced review of a diff — flags breaking callers, contract drift, duplicate logic).
|
|
302
276
|
|
|
303
|
-
Every response carries inline `ur|<tag>` signals for high-priority guidance — drift,
|
|
277
|
+
Every response carries inline `ur|<tag>` signals for high-priority guidance — drift, breaking-change warnings, loop-breaker halts — so the agent acts on what it just learned without burning a turn.
|
|
304
278
|
|
|
305
279
|
### Manual MCP config (any MCP-compatible client)
|
|
306
280
|
|
|
@@ -321,12 +295,24 @@ unerr removes **86–90% of the tokens** an agent would otherwise spend navigati
|
|
|
321
295
|
|
|
322
296
|
### Contributing
|
|
323
297
|
|
|
324
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, day-to-day commands, code conventions, and pre-PR checklist.
|
|
298
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, day-to-day commands, code conventions, and the pre-PR checklist.
|
|
325
299
|
|
|
326
300
|
</details>
|
|
327
301
|
|
|
328
302
|
---
|
|
329
303
|
|
|
304
|
+
## About the fewer tokens
|
|
305
|
+
|
|
306
|
+
I didn't build unerr to save tokens — I built it to stop bad changes. But a tool that only ever hands the agent the one relevant thing — the rule for the function in front of it, 50 lines instead of 3,000 — ends up spending far fewer tokens almost by accident. So you get that too:
|
|
307
|
+
|
|
308
|
+
- **86–90%** of an agent's code-navigation tokens removed in head-to-head benchmarks against grep-and-read — real tokenizer, fidelity-gated, reproducible on any repo. [See the benchmarks →](./benchmarks/README.md)
|
|
309
|
+
- Roughly **84%** of an agent's tokens are tool output, mostly file reads ([JetBrains, NeurIPS 2025](https://blog.jetbrains.com/research/2025/12/efficient-context-management/)). unerr steps in at the read, so the window doesn't fill up with noise.
|
|
310
|
+
- **0** AI calls per query in the core — the lookups, facts, and warnings are all computed directly. No API keys, no per-turn inference cost, no telemetry.
|
|
311
|
+
|
|
312
|
+
But the token number was never the point. The point is that the agent lands on the right code, sees the thing that would have stopped the break, and you stop paying — in money *and* in afternoons — for work it would otherwise have had to undo.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
330
316
|
## License
|
|
331
317
|
|
|
332
318
|
[Apache License 2.0](./LICENSE) — free to use, modify, and distribute, including commercially. Includes an explicit patent grant.
|
package/dist/cli.js
CHANGED
|
@@ -9331,8 +9331,21 @@ __export(metrics_store_exports, {
|
|
|
9331
9331
|
openMetricsStore: () => openMetricsStore
|
|
9332
9332
|
});
|
|
9333
9333
|
import { mkdirSync as mkdirSync11 } from "fs";
|
|
9334
|
+
import { createRequire as createRequire2 } from "module";
|
|
9334
9335
|
import { join as join21 } from "path";
|
|
9335
|
-
|
|
9336
|
+
function loadDatabaseCtor() {
|
|
9337
|
+
if (cachedDriver !== void 0) return cachedDriver;
|
|
9338
|
+
try {
|
|
9339
|
+
cachedDriver = requireFromHere("better-sqlite3");
|
|
9340
|
+
} catch (err2) {
|
|
9341
|
+
cachedDriver = null;
|
|
9342
|
+
process.stderr.write(
|
|
9343
|
+
`[unerr] WARN: better-sqlite3 native driver unavailable (${err2 instanceof Error ? err2.message : String(err2)}); metrics/telemetry disabled (dashboard counters stay empty). Install the prebuilt binary or a build toolchain to enable them \u2014 core graph tools are unaffected.
|
|
9344
|
+
`
|
|
9345
|
+
);
|
|
9346
|
+
}
|
|
9347
|
+
return cachedDriver;
|
|
9348
|
+
}
|
|
9336
9349
|
function reconcileAdditiveColumns(db) {
|
|
9337
9350
|
for (const { table: table2, column, decl } of ADDITIVE_COLUMNS) {
|
|
9338
9351
|
const cols = db.prepare(`PRAGMA table_info(${table2})`).all();
|
|
@@ -9366,10 +9379,11 @@ function closeAllMetricsStores() {
|
|
|
9366
9379
|
instances.delete(dir);
|
|
9367
9380
|
}
|
|
9368
9381
|
}
|
|
9369
|
-
var SCHEMA, SCHEMA_VERSION, ADDITIVE_COLUMNS, POST_RECONCILE_INDEXES, MetricsStore, instances;
|
|
9382
|
+
var requireFromHere, cachedDriver, SCHEMA, SCHEMA_VERSION, ADDITIVE_COLUMNS, POST_RECONCILE_INDEXES, MetricsStore, instances;
|
|
9370
9383
|
var init_metrics_store = __esm({
|
|
9371
9384
|
"src/tracking/metrics-store.ts"() {
|
|
9372
9385
|
"use strict";
|
|
9386
|
+
requireFromHere = createRequire2(import.meta.url);
|
|
9373
9387
|
SCHEMA = `
|
|
9374
9388
|
CREATE TABLE IF NOT EXISTS compression_events (
|
|
9375
9389
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -9551,9 +9565,17 @@ CREATE INDEX IF NOT EXISTS idx_token_flow_agent ON token_flow_events(agent);
|
|
|
9551
9565
|
CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
9552
9566
|
`;
|
|
9553
9567
|
MetricsStore = class {
|
|
9568
|
+
// Null when the better-sqlite3 native driver is unavailable — the store then
|
|
9569
|
+
// degrades to a no-op (writes drop, reads return empty). See loadDatabaseCtor.
|
|
9554
9570
|
db;
|
|
9555
9571
|
stmt;
|
|
9556
9572
|
constructor(dbPath) {
|
|
9573
|
+
const Database = loadDatabaseCtor();
|
|
9574
|
+
if (!Database) {
|
|
9575
|
+
this.db = null;
|
|
9576
|
+
this.stmt = null;
|
|
9577
|
+
return;
|
|
9578
|
+
}
|
|
9557
9579
|
this.db = new Database(dbPath);
|
|
9558
9580
|
this.db.pragma("journal_mode = WAL");
|
|
9559
9581
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -9732,89 +9754,111 @@ CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
|
9732
9754
|
};
|
|
9733
9755
|
}
|
|
9734
9756
|
upsertFetchCacheRow(row) {
|
|
9757
|
+
if (!this.stmt) return;
|
|
9735
9758
|
this.stmt.upsertFetchCache.run(row);
|
|
9736
9759
|
}
|
|
9737
9760
|
getFetchCacheRow(url) {
|
|
9761
|
+
if (!this.stmt) return null;
|
|
9738
9762
|
return this.stmt.getFetchCache.get({ url }) ?? null;
|
|
9739
9763
|
}
|
|
9740
9764
|
bumpFetchCacheHitFor(url) {
|
|
9765
|
+
if (!this.stmt) return;
|
|
9741
9766
|
this.stmt.bumpFetchCacheHit.run({ url });
|
|
9742
9767
|
}
|
|
9743
9768
|
// ── Agent Transcripts ───────────────────────────────────────────────
|
|
9744
9769
|
upsertAgentTranscript(row) {
|
|
9770
|
+
if (!this.stmt) return;
|
|
9745
9771
|
this.stmt.upsertAgentTranscript.run(row);
|
|
9746
9772
|
}
|
|
9747
9773
|
getAgentTranscriptsForSession(session_id) {
|
|
9774
|
+
if (!this.stmt) return [];
|
|
9748
9775
|
return this.stmt.agentTranscriptsBySession.all({ session_id });
|
|
9749
9776
|
}
|
|
9750
9777
|
getAgentTranscriptsForTurn(session_id, turn) {
|
|
9778
|
+
if (!this.stmt) return [];
|
|
9751
9779
|
return this.stmt.agentTranscriptsBySessionTurn.all({
|
|
9752
9780
|
session_id,
|
|
9753
9781
|
turn
|
|
9754
9782
|
});
|
|
9755
9783
|
}
|
|
9756
9784
|
hasAgentTranscripts(session_id) {
|
|
9785
|
+
if (!this.stmt) return false;
|
|
9757
9786
|
return !!this.stmt.agentTranscriptSessionExists.get({ session_id });
|
|
9758
9787
|
}
|
|
9759
9788
|
// ── Writes ──────────────────────────────────────────────────────────
|
|
9760
9789
|
insertCompression(row) {
|
|
9790
|
+
if (!this.stmt) return 0;
|
|
9761
9791
|
return Number(this.stmt.insertCompression.run(row).lastInsertRowid);
|
|
9762
9792
|
}
|
|
9763
9793
|
insertFileRead(row) {
|
|
9794
|
+
if (!this.stmt) return 0;
|
|
9764
9795
|
return Number(this.stmt.insertFileRead.run(row).lastInsertRowid);
|
|
9765
9796
|
}
|
|
9766
9797
|
insertTokenFlow(row) {
|
|
9798
|
+
if (!this.stmt) return 0;
|
|
9767
9799
|
const withAgent = { ...row, agent: row.agent ?? "unknown" };
|
|
9768
9800
|
return Number(this.stmt.insertTokenFlow.run(withAgent).lastInsertRowid);
|
|
9769
9801
|
}
|
|
9770
9802
|
insertBehaviorEvent(row) {
|
|
9803
|
+
if (!this.stmt) return 0;
|
|
9771
9804
|
const withAgent = { ...row, agent: row.agent ?? "unknown" };
|
|
9772
9805
|
return Number(this.stmt.insertBehaviorEvent.run(withAgent).lastInsertRowid);
|
|
9773
9806
|
}
|
|
9774
9807
|
upsertSessionHistory(row) {
|
|
9808
|
+
if (!this.stmt) return;
|
|
9775
9809
|
this.stmt.upsertSessionHistory.run(row);
|
|
9776
9810
|
}
|
|
9777
9811
|
upsertSessionSummary(row) {
|
|
9812
|
+
if (!this.stmt) return;
|
|
9778
9813
|
this.stmt.upsertSessionSummary.run(row);
|
|
9779
9814
|
}
|
|
9780
9815
|
// ── Reads ───────────────────────────────────────────────────────────
|
|
9781
9816
|
recentCompression(limit) {
|
|
9817
|
+
if (!this.stmt) return [];
|
|
9782
9818
|
return this.stmt.recentCompression.all({ limit });
|
|
9783
9819
|
}
|
|
9784
9820
|
recentFileReads(limit) {
|
|
9821
|
+
if (!this.stmt) return [];
|
|
9785
9822
|
return this.stmt.recentFileReads.all({ limit });
|
|
9786
9823
|
}
|
|
9787
9824
|
/** Poll API used by the log-tailer. */
|
|
9788
9825
|
compressionSince(lastId, limit = 500) {
|
|
9826
|
+
if (!this.stmt) return [];
|
|
9789
9827
|
return this.stmt.compressionSince.all({
|
|
9790
9828
|
lastId,
|
|
9791
9829
|
limit
|
|
9792
9830
|
});
|
|
9793
9831
|
}
|
|
9794
9832
|
fileReadsSince(lastId, limit = 500) {
|
|
9833
|
+
if (!this.stmt) return [];
|
|
9795
9834
|
return this.stmt.fileReadsSince.all({
|
|
9796
9835
|
lastId,
|
|
9797
9836
|
limit
|
|
9798
9837
|
});
|
|
9799
9838
|
}
|
|
9800
9839
|
tokenFlowSince(lastId, limit = 500) {
|
|
9840
|
+
if (!this.stmt) return [];
|
|
9801
9841
|
return this.stmt.tokenFlowSince.all({
|
|
9802
9842
|
lastId,
|
|
9803
9843
|
limit
|
|
9804
9844
|
});
|
|
9805
9845
|
}
|
|
9806
9846
|
allTokenFlow() {
|
|
9847
|
+
if (!this.stmt) return [];
|
|
9807
9848
|
return this.stmt.tokenFlowAll.all({});
|
|
9808
9849
|
}
|
|
9809
9850
|
tokenFlowBySession(sessionId) {
|
|
9851
|
+
if (!this.stmt) return [];
|
|
9810
9852
|
return this.stmt.tokenFlowBySession.all({
|
|
9811
9853
|
sessionId
|
|
9812
9854
|
});
|
|
9813
9855
|
}
|
|
9814
9856
|
allBehaviorEvents() {
|
|
9857
|
+
if (!this.stmt) return [];
|
|
9815
9858
|
return this.stmt.behaviorEventsAll.all({});
|
|
9816
9859
|
}
|
|
9817
9860
|
behaviorEventsBySession(sessionId) {
|
|
9861
|
+
if (!this.stmt) return [];
|
|
9818
9862
|
return this.stmt.behaviorEventsBySession.all({
|
|
9819
9863
|
sessionId
|
|
9820
9864
|
});
|
|
@@ -9838,6 +9882,7 @@ CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
|
9838
9882
|
* `hash` is `null` for legacy rows written before the digest field existed.
|
|
9839
9883
|
*/
|
|
9840
9884
|
latestUserPromptBoundary(sessionId) {
|
|
9885
|
+
if (!this.db) return null;
|
|
9841
9886
|
const row = this.db.prepare(
|
|
9842
9887
|
"SELECT ts, detail FROM behavior_events WHERE session_id = ? AND type = 'user_prompt_received' ORDER BY ts DESC LIMIT 1"
|
|
9843
9888
|
).get(sessionId);
|
|
@@ -9851,18 +9896,22 @@ CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
|
9851
9896
|
return { ts: row.ts, hash: hash2 };
|
|
9852
9897
|
}
|
|
9853
9898
|
behaviorEventsSince(lastId, limit = 500) {
|
|
9899
|
+
if (!this.stmt) return [];
|
|
9854
9900
|
return this.stmt.behaviorEventsSince.all({
|
|
9855
9901
|
lastId,
|
|
9856
9902
|
limit
|
|
9857
9903
|
});
|
|
9858
9904
|
}
|
|
9859
9905
|
allSessionHistory() {
|
|
9906
|
+
if (!this.stmt) return [];
|
|
9860
9907
|
return this.stmt.allSessionHistory.all({});
|
|
9861
9908
|
}
|
|
9862
9909
|
sessionSummary(sessionId) {
|
|
9910
|
+
if (!this.stmt) return null;
|
|
9863
9911
|
return this.stmt.sessionSummaryById.get({ sessionId }) ?? null;
|
|
9864
9912
|
}
|
|
9865
9913
|
allSessionSummaries() {
|
|
9914
|
+
if (!this.stmt) return [];
|
|
9866
9915
|
return this.stmt.allSessionSummaries.all({});
|
|
9867
9916
|
}
|
|
9868
9917
|
/**
|
|
@@ -9871,6 +9920,8 @@ CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
|
9871
9920
|
* `COALESCE(MAX(id), 0)` keeps the call O(1) on an indexed PK.
|
|
9872
9921
|
*/
|
|
9873
9922
|
lastIds() {
|
|
9923
|
+
if (!this.db)
|
|
9924
|
+
return { compression: 0, fileRead: 0, tokenFlow: 0, behaviorEvent: 0 };
|
|
9874
9925
|
const c = this.db.prepare("SELECT COALESCE(MAX(id), 0) AS id FROM compression_events").get();
|
|
9875
9926
|
const f = this.db.prepare("SELECT COALESCE(MAX(id), 0) AS id FROM file_read_events").get();
|
|
9876
9927
|
const t = this.db.prepare("SELECT COALESCE(MAX(id), 0) AS id FROM token_flow_events").get();
|
|
@@ -9884,10 +9935,12 @@ CREATE INDEX IF NOT EXISTS idx_behavior_events_agent ON behavior_events(agent);
|
|
|
9884
9935
|
}
|
|
9885
9936
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
9886
9937
|
close() {
|
|
9938
|
+
if (!this.db) return;
|
|
9887
9939
|
this.db.close();
|
|
9888
9940
|
}
|
|
9889
9941
|
/** Test-only — wipe every metric table. */
|
|
9890
9942
|
reset() {
|
|
9943
|
+
if (!this.db) return;
|
|
9891
9944
|
this.db.exec(`
|
|
9892
9945
|
DELETE FROM compression_events;
|
|
9893
9946
|
DELETE FROM file_read_events;
|
|
@@ -11050,8 +11103,8 @@ async function openReadOnly(path7) {
|
|
|
11050
11103
|
if (!existsSync27(path7)) return null;
|
|
11051
11104
|
try {
|
|
11052
11105
|
const mod = await import("better-sqlite3");
|
|
11053
|
-
const
|
|
11054
|
-
const db = new
|
|
11106
|
+
const Database = mod.default ?? mod;
|
|
11107
|
+
const db = new Database(path7, { readonly: true, fileMustExist: true });
|
|
11055
11108
|
try {
|
|
11056
11109
|
db.pragma("busy_timeout = 250");
|
|
11057
11110
|
} catch {
|
|
@@ -12472,8 +12525,8 @@ async function createSqliteDb(dbPath) {
|
|
|
12472
12525
|
}
|
|
12473
12526
|
async function enableWalMode(dbPath) {
|
|
12474
12527
|
try {
|
|
12475
|
-
const { default:
|
|
12476
|
-
const sqlite = new
|
|
12528
|
+
const { default: Database } = await import("better-sqlite3");
|
|
12529
|
+
const sqlite = new Database(dbPath);
|
|
12477
12530
|
try {
|
|
12478
12531
|
sqlite.pragma("journal_mode = WAL");
|
|
12479
12532
|
} finally {
|
|
@@ -12488,8 +12541,8 @@ async function enableWalMode(dbPath) {
|
|
|
12488
12541
|
}
|
|
12489
12542
|
async function checkpointWal(dbPath) {
|
|
12490
12543
|
try {
|
|
12491
|
-
const { default:
|
|
12492
|
-
const sqlite = new
|
|
12544
|
+
const { default: Database } = await import("better-sqlite3");
|
|
12545
|
+
const sqlite = new Database(dbPath);
|
|
12493
12546
|
try {
|
|
12494
12547
|
sqlite.pragma("busy_timeout = 2000");
|
|
12495
12548
|
const MAX_ATTEMPTS = 6;
|
|
@@ -56490,10 +56543,10 @@ ${signalFooter.trimEnd()}` : "";
|
|
|
56490
56543
|
startup.addStep(
|
|
56491
56544
|
"MCP ready",
|
|
56492
56545
|
"done",
|
|
56493
|
-
`PARSE mode (${parseStats?.entityCount ?? 0} entities)`
|
|
56546
|
+
`PARSE mode \u2014 graph engine unavailable (${parseStats?.entityCount ?? 0} entities)`
|
|
56494
56547
|
);
|
|
56495
56548
|
log21.info(
|
|
56496
|
-
`MCP server running on stdio \u2014 PARSE mode (${parseStats?.entityCount ?? 0} entities from ${parseStats?.fileCount ?? 0} files)
|
|
56549
|
+
`MCP server running on stdio \u2014 PARSE mode (reduced accuracy): ${proxyModeReason} Serving ${parseStats?.entityCount ?? 0} entities from ${parseStats?.fileCount ?? 0} files via regex extraction (no call graph, drift, or rules). Run \`unerr doctor\` for how to restore the full graph engine.`
|
|
56497
56550
|
);
|
|
56498
56551
|
} else {
|
|
56499
56552
|
const localToolCount = 14;
|
|
@@ -59650,31 +59703,73 @@ Otherwise unerrd scans the next ~100 ports for a free one at startup.`
|
|
|
59650
59703
|
});
|
|
59651
59704
|
});
|
|
59652
59705
|
}
|
|
59706
|
+
function isModuleMissing(err2) {
|
|
59707
|
+
const code = err2?.code;
|
|
59708
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND")
|
|
59709
|
+
return true;
|
|
59710
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
59711
|
+
return /cannot find (module|package)|module not found/i.test(msg);
|
|
59712
|
+
}
|
|
59713
|
+
var NATIVE_FIX_HINT = "If the prebuilt binary download was blocked, set a proxy and reinstall:\n npm config set proxy http://<host>:<port> ; npm config set https-proxy http://<host>:<port>\n npm i -g @unerr-ai/unerr\nOtherwise install a build toolchain so the source fallback can compile:\n Windows: `npm i -g windows-build-tools` or Visual Studio Build Tools + Python 3 (https://github.com/nodejs/node-gyp#on-windows)";
|
|
59653
59714
|
async function checkNativeModule() {
|
|
59654
59715
|
try {
|
|
59655
59716
|
const cozo = await import("cozo-node");
|
|
59656
59717
|
if (cozo?.CozoDb) {
|
|
59657
59718
|
return {
|
|
59658
|
-
name: "
|
|
59719
|
+
name: "Graph engine (cozo-node)",
|
|
59659
59720
|
status: "ok",
|
|
59660
59721
|
message: "cozo-node loaded"
|
|
59661
59722
|
};
|
|
59662
59723
|
}
|
|
59663
59724
|
return {
|
|
59664
|
-
name: "
|
|
59725
|
+
name: "Graph engine (cozo-node)",
|
|
59665
59726
|
status: "warn",
|
|
59666
|
-
message: "cozo-node loaded but CozoDb export missing"
|
|
59727
|
+
message: "cozo-node loaded but CozoDb export missing \u2014 unerr will run in PARSE mode (regex graph, reduced accuracy)"
|
|
59667
59728
|
};
|
|
59668
59729
|
} catch (err2) {
|
|
59669
59730
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
59731
|
+
if (isModuleMissing(err2)) {
|
|
59732
|
+
return {
|
|
59733
|
+
name: "Graph engine (cozo-node)",
|
|
59734
|
+
status: "warn",
|
|
59735
|
+
message: "cozo-node not installed \u2014 unerr runs in PARSE mode (regex graph, reduced accuracy)",
|
|
59736
|
+
detail: "cozo-node is a required native module, but its prebuilt binary could not be downloaded or built at install time.\n" + NATIVE_FIX_HINT
|
|
59737
|
+
};
|
|
59738
|
+
}
|
|
59670
59739
|
return {
|
|
59671
|
-
name: "
|
|
59672
|
-
status: "
|
|
59673
|
-
message: "cozo-node failed to load",
|
|
59740
|
+
name: "Graph engine (cozo-node)",
|
|
59741
|
+
status: "warn",
|
|
59742
|
+
message: "cozo-node failed to load \u2014 unerr runs in PARSE mode (regex graph, reduced accuracy)",
|
|
59674
59743
|
detail: `${msg}
|
|
59675
59744
|
The native binary is likely built for a different Node ABI or platform.
|
|
59676
|
-
Reinstall under the node you intend to use: \`npm i -g @unerr-ai/unerr
|
|
59677
|
-
|
|
59745
|
+
Reinstall under the node you intend to use: \`npm i -g @unerr-ai/unerr\`.`
|
|
59746
|
+
};
|
|
59747
|
+
}
|
|
59748
|
+
}
|
|
59749
|
+
async function checkMetricsDriver() {
|
|
59750
|
+
try {
|
|
59751
|
+
const mod = await import("better-sqlite3");
|
|
59752
|
+
if (mod?.default) {
|
|
59753
|
+
return {
|
|
59754
|
+
name: "Telemetry driver (better-sqlite3)",
|
|
59755
|
+
status: "ok",
|
|
59756
|
+
message: "better-sqlite3 loaded"
|
|
59757
|
+
};
|
|
59758
|
+
}
|
|
59759
|
+
return {
|
|
59760
|
+
name: "Telemetry driver (better-sqlite3)",
|
|
59761
|
+
status: "warn",
|
|
59762
|
+
message: "better-sqlite3 loaded but Database export missing \u2014 dashboard metrics disabled"
|
|
59763
|
+
};
|
|
59764
|
+
} catch (err2) {
|
|
59765
|
+
const missing = isModuleMissing(err2);
|
|
59766
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
59767
|
+
return {
|
|
59768
|
+
name: "Telemetry driver (better-sqlite3)",
|
|
59769
|
+
status: "warn",
|
|
59770
|
+
message: missing ? "better-sqlite3 not installed \u2014 dashboard metrics disabled (core graph tools unaffected)" : "better-sqlite3 failed to load \u2014 dashboard metrics disabled (core graph tools unaffected)",
|
|
59771
|
+
detail: (missing ? "" : `${msg}
|
|
59772
|
+
`) + NATIVE_FIX_HINT
|
|
59678
59773
|
};
|
|
59679
59774
|
}
|
|
59680
59775
|
}
|
|
@@ -59702,6 +59797,9 @@ async function runEnvironmentChecks(opts) {
|
|
|
59702
59797
|
const native = await checkNativeModule();
|
|
59703
59798
|
printCheckResult(native);
|
|
59704
59799
|
results.push(native);
|
|
59800
|
+
const metricsDriver = await checkMetricsDriver();
|
|
59801
|
+
printCheckResult(metricsDriver);
|
|
59802
|
+
results.push(metricsDriver);
|
|
59705
59803
|
const blocking = results.some(
|
|
59706
59804
|
(r) => r.status === "fail" && r.blocking === true
|
|
59707
59805
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unerr-ai/unerr",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"mcpName": "io.github.unerr-ai/unerr",
|
|
5
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": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "pnpm run build:ui && pnpm run build:cli",
|
|
19
|
-
"build:cli": "tsup src/entrypoints/cli.ts --format esm --target node20 --dts --no-splitting",
|
|
19
|
+
"build:cli": "tsup src/entrypoints/cli.ts --format esm --target node20 --dts --no-splitting --external cozo-node --external better-sqlite3",
|
|
20
20
|
"dev": "tsx watch src/entrypoints/cli.ts",
|
|
21
21
|
"test": "vitest",
|
|
22
22
|
"test:run": "vitest run < /dev/null",
|