eminiar 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eminiar",
3
3
  "displayName": "Eminiar",
4
- "version": "0.10.1",
4
+ "version": "0.12.0",
5
5
  "description": "Local-first context, memory and knowledge graph for Claude Code: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
6
6
  "author": {
7
7
  "name": "Ryan Dick"
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "eminiar": {
4
+ "command": "${CURSOR_PLUGIN_ROOT}/bin/em-server",
5
+ "args": [],
6
+ "env": {
7
+ "EMINIAR_PLUGIN_ROOT": "${CURSOR_PLUGIN_ROOT}"
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "eminiar",
3
+ "displayName": "Eminiar",
4
+ "version": "0.12.0",
5
+ "description": "Local-first context, memory and knowledge graph for coding agents: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
6
+ "author": {
7
+ "name": "Ryan Dick"
8
+ },
9
+ "homepage": "https://github.com/rydn/eminiar#readme",
10
+ "repository": "https://github.com/rydn/eminiar",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "context",
14
+ "memory",
15
+ "search",
16
+ "sandbox",
17
+ "knowledge-graph"
18
+ ],
19
+ "hooks": "./hooks/cursor.json",
20
+ "mcpServers": "./.cursor-plugin/mcp.json",
21
+ "skills": "./skills",
22
+ "rules": "./rules"
23
+ }
package/README.md CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  # eminiar
4
4
 
5
- **A memory, a code map and a clean context window for Claude Code — all on your machine.**
5
+ **A memory, a code map and a clean context window for Claude Code and Cursor — all on your machine.**
6
6
 
7
7
  *Pronounced ee-MIN-ee-ar*
8
8
 
9
9
  [![License: MIT](https://img.shields.io/badge/license-MIT-3b82f6.svg)](#license)
10
10
  [![Node](https://img.shields.io/badge/node-%E2%89%A522.12-64748b.svg)](https://nodejs.org)
11
11
  [![Claude Code plugin](https://img.shields.io/badge/Claude%20Code-plugin-3b82f6.svg)](https://docs.claude.com/en/docs/claude-code)
12
+ [![Cursor](https://img.shields.io/badge/Cursor-hooks%20%2B%20MCP-3b82f6.svg)](docs/getting-started.md#cursor)
12
13
  [![Local-first](https://img.shields.io/badge/local--first-no%20cloud-64748b.svg)](docs/how-it-works.md#privacy-and-data)
13
14
 
14
15
  [Quick start](#quick-start) · [Features](#what-you-get) · [How it works](docs/how-it-works.md) · [Tools](docs/tools.md) · [Docs](docs/README.md)
@@ -39,7 +40,8 @@ Claude Code is very capable, but three things slow it down on a real project:
39
40
 
40
41
  ## The solution
41
42
 
42
- eminiar is a Claude Code plugin. It adds an MCP server with 23 `em_*` tools and a set of hooks.
43
+ eminiar is a Claude Code plugin that also installs into Cursor. It adds an MCP server with 24
44
+ `em_*` tools and a set of hooks.
43
45
  Everything runs locally in one SQLite database per project.
44
46
 
45
47
  | | Without eminiar | With eminiar |
@@ -47,9 +49,10 @@ Everything runs locally in one SQLite database per project.
47
49
  | **Large output** | The full output goes into context | Output is indexed. Only the sections that match your `intent` come back |
48
50
  | **Memory** | Lost at `/compact` or a new session | Markdown memories in your repo, given back to Claude when they are relevant |
49
51
  | **Code structure** | `grep` and full-file reads | A knowledge graph of files, symbols, calls, routes and hotspots |
50
- | **Git history** | Raw `git log` / `git blame` | Classified commits, with each fix traced back to the commit that caused it |
52
+ | **Git history** | Raw `git log` / `git blame` | Classified commits, fix tracing, and a graph from branches to the files, memories and docs they touched |
51
53
  | **Library docs** | Web search on each question | A local, searchable index of the docs your project uses |
52
54
  | **Getting knowledge** | Claude must remember to ask | Hooks push the relevant knowledge to Claude **automatically** |
55
+ | **Cost** | A guess, one session at a time | Tokens, cache hits and estimated cost for every model call, with insights |
53
56
 
54
57
  ## What you get
55
58
 
@@ -61,6 +64,12 @@ indexed. Claude gets back only the sections that match its `intent`. Snippets ob
61
64
  Code `Bash` deny rules, and `em_execute_file` refuses a `path` that one of your `Read` deny rules
62
65
  matches ([which rule forms](docs/how-it-works.md#sandbox)).
63
66
 
67
+ Hooks route noisy commands for Claude. A Bash line is judged one command at a time: output that
68
+ is already bounded (`| head`, `| wc`, `grep -l`, `cat` of a small file) and commands that change
69
+ files stay native. `git log`, `git blame` and `git show` go to `em_history`, and a `Grep` for a
70
+ symbol the graph knows goes to `em_symbols`. `em_stats` shows each routing decision and the rule
71
+ behind it, so you can see what was sent where.
72
+
64
73
  ### 🔎 Hybrid search — keyword and meaning together
65
74
 
66
75
  SQLite FTS5 (porter + trigram, BM25, typo correction) is fused with local ONNX embeddings
@@ -75,6 +84,10 @@ and commit them. Decisions have an ADR-style status (`proposed`, `accepted`, `su
75
84
  attach matching memories to your prompts, to file reads and edits, to failed commands and to
76
85
  subagent briefs. **Claude does not have to remember to look.**
77
86
 
87
+ Recall matches a memory when the prompt names it, one of its tags, a file it mentions or a
88
+ symbol from that file. A memory can come back later in a long session. To see why a memory did
89
+ or did not appear, run `eminiar recall --explain "<prompt>"`.
90
+
78
91
  ### 🕸️ Code knowledge graph — a map before an edit
79
92
 
80
93
  tree-sitter parses TypeScript, JavaScript, Python, Go and PHP into files, symbols, imports, calls,
@@ -83,12 +96,20 @@ Python decorator routes, Go routers, Laravel, Symfony and Slim). eminiar
83
96
  adds fan-in, complexity, churn, a hotspot score, module clusters, import cycles and git co-change
84
97
  coupling. Ask *"what breaks if I change this?"* with `em_graph_impact`.
85
98
 
99
+ Memories and documentation share the same graph. Each memory links to the files and symbols it
100
+ mentions, and each doc source links to the code that uses it. One graph answers *"what do we
101
+ know about this file?"*
102
+
86
103
  ### 📜 Git history, read for Claude
87
104
 
88
105
  `em_history` classifies every commit (feat, fix, refactor, perf …). It traces each fix back to
89
106
  the commit that wrote the broken lines. Files get a risk score with reasons. Before Claude edits
90
107
  a bug-prone file, it sees the record.
91
108
 
109
+ The history graph connects branches to their commits, commits to the files they changed, and
110
+ those files to the memories and docs about them. Merged and deleted branches keep their commits.
111
+ A fix points at the commit it repairs.
112
+
92
113
  ### 📚 Documentation knowledge base
93
114
 
94
115
  `em_docs` finds your local docs (READMEs, ADRs, OpenAPI, Postman) and the libraries, services
@@ -104,18 +125,29 @@ session with its compactions, which tools fill the context, insights with recomm
104
125
  what eminiar's own tools saved in dollars. Everything stays local; cost is an estimate at list
105
126
  price ([details](docs/usage.md)).
106
127
 
128
+ ### 🐝 Agent swarms (opt-in)
129
+
130
+ Declare worker roles in `.eminiar/swarm.yaml`, with their prompts, models, tools, skills and
131
+ hooks. eminiar generates a Claude Code agent file per role. `/eminiar:em-swarm <swarm> <goal>`
132
+ makes the main session the coordinator: it plans small tasks, fans workers out in parallel and
133
+ reviews what comes back. Every worker starts with the memories, history risk and hot files for
134
+ its task. Capacity limits, a shared blackboard and a live Swarms tab in the dashboard come with
135
+ it. Nothing changes until you run `eminiar swarm init` ([details](docs/swarms.md)).
136
+
107
137
  ### 📊 Local dashboard
108
138
 
109
139
  `eminiar ui` opens a local web app with token savings, usage and cost, a source explorer with
110
- impact analysis, a graph "galaxy", an architecture map, a history explorer, a memory editor and
111
- a docs manager.
140
+ impact analysis, a graph "galaxy", an architecture map, a history explorer with a history
141
+ graph, a memory editor with a memory graph, and a docs manager.
112
142
 
113
143
  | | |
114
144
  |:---:|:---:|
115
- | [<img alt="Galaxy: the knowledge graph around a file" src="docs/images/dashboard-galaxy-dark.webp" width="430">](docs/dashboard.md#galaxy) | [<img alt="History: commits classified by kind, with the branch graph" src="docs/images/dashboard-history-dark.webp" width="430">](docs/dashboard.md#history) |
116
- | **Galaxy:** the knowledge graph | **History:** classified commits and fix tracing |
117
- | [<img alt="Memory: a decision with its linked files" src="docs/images/dashboard-memory-dark.webp" width="430">](docs/dashboard.md#memory) | [<img alt="Hotspots: fan-in, churn and complexity per file" src="docs/images/dashboard-hotspots-dark.webp" width="430">](docs/dashboard.md#architecture) |
118
- | **Memory:** decisions linked to code | **Hotspots:** where change is risky |
145
+ | [<img alt="Galaxy: the knowledge graph around a file" src="docs/images/dashboard-galaxy-dark.webp" width="430">](docs/dashboard.md#galaxy) | [<img alt="Usage: cost per day by model, cache hit rate, burn rate and session scores" src="docs/images/dashboard-usage-dark.webp" width="430">](docs/dashboard.md#usage) |
146
+ | **Galaxy:** the knowledge graph | **Usage:** cost, cache and burn rate |
147
+ | [<img alt="History graph: branches, their commits, and the files, memories and docs they touched" src="docs/images/dashboard-history-graph-dark.webp" width="430">](docs/dashboard.md#history) | [<img alt="Memory graph: memories linked to the files, tags and topics they are about" src="docs/images/dashboard-memory-graph-dark.webp" width="430">](docs/dashboard.md#memory) |
148
+ | **History graph:** branches to files and memories | **Memory graph:** what each memory is about |
149
+ | [<img alt="History: commits classified by kind, with the branch graph" src="docs/images/dashboard-history-dark.webp" width="430">](docs/dashboard.md#history) | [<img alt="Hotspots: fan-in, churn and complexity per file" src="docs/images/dashboard-hotspots-dark.webp" width="430">](docs/dashboard.md#architecture) |
150
+ | **History:** classified commits and fix tracing | **Hotspots:** where change is risky |
119
151
 
120
152
  ## What Claude sees
121
153
 
@@ -132,26 +164,39 @@ eminiar graph: src/cli.ts is imported by 6 files; em_graph_impact {node:"file:sr
132
164
  them before you change an exported signature.
133
165
  ```
134
166
 
167
+ When a command would flood the context, routing names the tool to use instead:
168
+
169
+ ```text
170
+ eminiar: `git diff .claude-plugin/plugin.json package.json README.md` usually prints a lot;
171
+ em_execute {language:"shell", code, intent} keeps the output out of context and returns only
172
+ matching sections. Retry with em_execute.
173
+
174
+ eminiar history: this commit includes files that deserve a second look:
175
+ - package.json is bug-prone (3/25 commits were fixes; 2 earlier changes later needed fixing)
176
+ ```
177
+
135
178
  Each note appears once per context window. After a `/compact`, notes come back when they are
136
179
  relevant again.
137
180
 
138
181
  ## Quick start
139
182
 
140
- **Requirements:** Node.js 22.12 or later, and Claude Code.
183
+ **Requirements:** Node.js 22.12 or later, and Claude Code or Cursor.
141
184
 
142
185
  ```sh
143
- npx eminiar install # registers the plugin, installs a stable copy, downloads the model
144
- npx eminiar doctor # checks every part
186
+ npx eminiar install # registers the plugin, installs a stable copy, downloads the model
187
+ npx eminiar install --cursor # the same, plus hooks, MCP server and skills for Cursor
188
+ npx eminiar doctor # checks every part
145
189
  ```
146
190
 
147
- Restart Claude Code. Then, in your project:
191
+ Restart Claude Code (or Cursor). Then, in your project:
148
192
 
149
193
  ```text
150
194
  /em-onboard # writes onboarding memories and builds the docs knowledge base
151
195
  ```
152
196
 
153
- That is all. Use Claude Code as usual. To see how much context eminiar saves, run `/em-stats` or
154
- open the dashboard with `eminiar ui`.
197
+ That is all. Use Claude Code or Cursor as usual. To see how much context eminiar saves, run
198
+ `/em-stats` or open the dashboard with `eminiar ui`. One flag covers Cursor because the hook
199
+ runner speaks both dialects; see [Cursor](docs/getting-started.md#cursor) for what differs.
155
200
 
156
201
  **For a team:** run `npx eminiar install --team` once and commit `eminiar.config.json` and
157
202
  `.eminiar/`. Teammates then run `npx eminiar install`. See
@@ -164,7 +209,7 @@ flowchart LR
164
209
  CC["Claude Code"]
165
210
  subgraph eminiar ["eminiar (local)"]
166
211
  H["Hooks<br/>recall · routing · capture"]
167
- S["MCP server<br/>23 em_* tools"]
212
+ S["MCP server<br/>24 em_* tools"]
168
213
  SB["Sandbox<br/>shell · JS · TS · Python …"]
169
214
  DB[("SQLite per project<br/>FTS5 · vectors · graph")]
170
215
  M["Memory files<br/>.eminiar/memories/*.md"]
@@ -198,6 +243,7 @@ Read more in [How it works](docs/how-it-works.md).
198
243
  | [Knowledge graph and history](docs/knowledge-graph.md) | Graph, symbols, impact analysis, git history |
199
244
  | [Documentation knowledge base](docs/documentation-kb.md) | Discover, index and look up docs |
200
245
  | [Usage insights](docs/usage.md) | Tokens, cache, cost, billing blocks, insights and savings per session |
246
+ | [Agent swarms](docs/swarms.md) | Opt-in worker swarms: the swarm file, topologies, the tool and CLI |
201
247
  | [Dashboard](docs/dashboard.md) | The `eminiar ui` web app |
202
248
  | [Configuration](docs/configuration.md) | Every config key with its default |
203
249
  | [CLI](docs/cli.md) | The `eminiar` command |
package/bin/em-hook CHANGED
@@ -2,14 +2,87 @@
2
2
  # Hook entry shim. Node and package-root resolution live in _resolve.sh.
3
3
  # A hook must never block Claude: if nothing can run, print an empty JSON object.
4
4
  . "$(dirname "$0")/_resolve.sh"
5
+ # Swarm enforcement fails closed: when nothing can run, an Agent/Task call in a project whose
6
+ # .eminiar/swarm.yaml says `enforce: true` is denied. Reads stdin only here; every other call
7
+ # (and every other project) gets the empty answer below.
8
+ swarm_gate() {
9
+ case " $* " in *" cursor "*) return 0 ;; esac
10
+ payload=$(cat 2>/dev/null) || return 0
11
+ case "$payload" in *'"PreToolUse"'*) ;; *) return 0 ;; esac
12
+ printf '%s' "$payload" | grep -Eq '"tool_name"[[:space:]]*:[[:space:]]*"(Agent|Task)"' || return 0
13
+ # Payloads with "agent_id" (a call made inside a subagent) are denied too: the Node path
14
+ # allows nested calls only for allow-listed agents, and this shim cannot read allowed_agents,
15
+ # so a nested call gets the same deny text as a main-session one.
16
+ # Root order matches the hooks: CLAUDE_PROJECT_DIR (set by Claude Code per hook command)
17
+ # before EMINIAR_PROJECT_ROOT (may be inherited from an outer session).
18
+ root="${CLAUDE_PROJECT_DIR:-${EMINIAR_PROJECT_ROOT:-$PWD}}"
19
+ # Walk up to the first directory with a project marker (src/project.ts ROOT_MARKERS), else
20
+ # stay on the start dir, so this checks the root resolveProjectRoot would.
21
+ d=$root
22
+ while :; do
23
+ for m in .git package.json pnpm-workspace.yaml go.mod Cargo.toml pyproject.toml; do
24
+ [ -e "$d/$m" ] && break 2
25
+ done
26
+ p=${d%/*}
27
+ [ -n "$p" ] || p=/
28
+ [ "$p" != "$d" ] || { d=$root; break; }
29
+ d=$p
30
+ done
31
+ root=$d
32
+ main=
33
+ # A `claude --worktree` session: .git is a file ("gitdir: <main>/.git/worktrees/<n>"), and the
34
+ # uncommitted swarm file lives in the main checkout. Follow gitdir -> commondir to it. A .git
35
+ # file without a gitdir line, or a gitdir without commondir (a submodule), is not followed.
36
+ if [ -f "$root/.git" ]; then
37
+ cr=$(printf '\r')
38
+ gd=$(grep -m1 '^gitdir:' "$root/.git" 2>/dev/null)
39
+ gd=${gd#gitdir:}
40
+ gd=${gd# }
41
+ gd=${gd%"$cr"}
42
+ if [ -n "$gd" ]; then
43
+ case "$gd" in /*) ;; *) gd="$root/$gd" ;; esac
44
+ cdir=
45
+ [ -f "$gd/commondir" ] && read -r cdir < "$gd/commondir"
46
+ cdir=${cdir%"$cr"}
47
+ if [ -n "$cdir" ]; then
48
+ case "$cdir" in /*) ;; *) cdir="$gd/$cdir" ;; esac
49
+ main=$(cd "$cdir/.." 2>/dev/null && pwd)
50
+ [ "$main" = "$root" ] && main=
51
+ fi
52
+ fi
53
+ fi
54
+ # The same rule as enforceIntent (src/swarm/config.ts): top-level `enforce: true|True|TRUE`
55
+ # (plain or quoted key, BOM, CRLF, space before the colon, trailing comment), and not
56
+ # `enabled: false|False|FALSE`. Flow-style or indented documents are not recognised here; a
57
+ # parsing Node reader decides those.
58
+ bom=$(printf '\357\273\277')
59
+ tail='[[:space:]]*:[[:space:]]+'
60
+ end='([[:space:]]+#.*)?[[:space:]]*$'
61
+ for r in "$root" "$main"; do
62
+ [ -n "$r" ] || continue
63
+ for f in "$r/.eminiar/swarm.yaml" "$r/.eminiar/swarm.yml"; do
64
+ [ -f "$f" ] || continue
65
+ grep -Eq "^($bom)?(enforce|\"enforce\"|'enforce')${tail}(true|True|TRUE)${end}" "$f" 2>/dev/null || return 0
66
+ grep -Eq "^($bom)?(enabled|\"enabled\"|'enabled')${tail}(false|False|FALSE)${end}" "$f" 2>/dev/null && return 0
67
+ echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"eminiar swarm enforcement: eminiar'"'"'s hook cannot run (no Node.js or no installed copy; npx eminiar install fixes it), so this subagent is denied. Do the work in this session, or ask the user to fix the install or set enforce: false in .eminiar/swarm.yaml."}}'
68
+ return 1
69
+ done
70
+ done
71
+ return 0
72
+ }
73
+ # --gate: decide-only mode for a project-level onFailure:"block" entry. Nothing to run means
74
+ # failure (exit 1), so that entry blocks; the swarm_gate fallback below is for the plugin hook.
75
+ case " $* " in *" --gate "*) EM_GATE=1 ;; *) EM_GATE= ;; esac
5
76
  if [ ! -x "$NODE" ]; then
6
77
  echo "eminiar hook: no node binary found (run: npx eminiar install)" >&2
7
- echo '{}'
78
+ [ -n "$EM_GATE" ] && exit 1
79
+ swarm_gate "$@" && echo '{}'
8
80
  exit 0
9
81
  fi
10
82
  if [ -z "$RUN" ]; then
11
83
  echo "eminiar hook: $DIR has no node_modules and no stable copy is pinned (run: npx eminiar install)" >&2
12
- echo '{}'
84
+ [ -n "$EM_GATE" ] && exit 1
85
+ swarm_gate "$@" && echo '{}'
13
86
  exit 0
14
87
  fi
15
88
  exec "$NODE" "$RUN/dist/hook.js" "$@"