eminiar 0.11.0 β†’ 0.13.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.11.0",
4
+ "version": "0.13.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"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eminiar",
3
3
  "displayName": "Eminiar",
4
- "version": "0.11.0",
4
+ "version": "0.13.0",
5
5
  "description": "Local-first context, memory and knowledge graph for coding agents: sandboxed execution, FTS5 + vector search, session continuity, code graph.",
6
6
  "author": {
7
7
  "name": "Ryan Dick"
package/README.md CHANGED
@@ -40,7 +40,7 @@ Claude Code is very capable, but three things slow it down on a real project:
40
40
 
41
41
  ## The solution
42
42
 
43
- eminiar is a Claude Code plugin that also installs into Cursor. It adds an MCP server with 23
43
+ eminiar is a Claude Code plugin that also installs into Cursor. It adds an MCP server with 24
44
44
  `em_*` tools and a set of hooks.
45
45
  Everything runs locally in one SQLite database per project.
46
46
 
@@ -49,9 +49,10 @@ Everything runs locally in one SQLite database per project.
49
49
  | **Large output** | The full output goes into context | Output is indexed. Only the sections that match your `intent` come back |
50
50
  | **Memory** | Lost at `/compact` or a new session | Markdown memories in your repo, given back to Claude when they are relevant |
51
51
  | **Code structure** | `grep` and full-file reads | A knowledge graph of files, symbols, calls, routes and hotspots |
52
- | **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 |
53
53
  | **Library docs** | Web search on each question | A local, searchable index of the docs your project uses |
54
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 |
55
56
 
56
57
  ## What you get
57
58
 
@@ -63,6 +64,12 @@ indexed. Claude gets back only the sections that match its `intent`. Snippets ob
63
64
  Code `Bash` deny rules, and `em_execute_file` refuses a `path` that one of your `Read` deny rules
64
65
  matches ([which rule forms](docs/how-it-works.md#sandbox)).
65
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
+
66
73
  ### πŸ”Ž Hybrid search β€” keyword and meaning together
67
74
 
68
75
  SQLite FTS5 (porter + trigram, BM25, typo correction) is fused with local ONNX embeddings
@@ -77,6 +84,10 @@ and commit them. Decisions have an ADR-style status (`proposed`, `accepted`, `su
77
84
  attach matching memories to your prompts, to file reads and edits, to failed commands and to
78
85
  subagent briefs. **Claude does not have to remember to look.**
79
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
+
80
91
  ### πŸ•ΈοΈ Code knowledge graph β€” a map before an edit
81
92
 
82
93
  tree-sitter parses TypeScript, JavaScript, Python, Go and PHP into files, symbols, imports, calls,
@@ -85,12 +96,20 @@ Python decorator routes, Go routers, Laravel, Symfony and Slim). eminiar
85
96
  adds fan-in, complexity, churn, a hotspot score, module clusters, import cycles and git co-change
86
97
  coupling. Ask *"what breaks if I change this?"* with `em_graph_impact`.
87
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
+
88
103
  ### πŸ“œ Git history, read for Claude
89
104
 
90
105
  `em_history` classifies every commit (feat, fix, refactor, perf …). It traces each fix back to
91
106
  the commit that wrote the broken lines. Files get a risk score with reasons. Before Claude edits
92
107
  a bug-prone file, it sees the record.
93
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
+
94
113
  ### πŸ“š Documentation knowledge base
95
114
 
96
115
  `em_docs` finds your local docs (READMEs, ADRs, OpenAPI, Postman) and the libraries, services
@@ -106,18 +125,38 @@ session with its compactions, which tools fill the context, insights with recomm
106
125
  what eminiar's own tools saved in dollars. Everything stays local; cost is an estimate at list
107
126
  price ([details](docs/usage.md)).
108
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
+
137
+ ### βš–οΈ Adjudicator (opt-in)
138
+
139
+ Scores how complex each prompt, subagent dispatch and swarm task is, and suggests the `em_*` tool
140
+ to start with, the Claude model size and the agent role, so small work goes to small models. A
141
+ local heuristic always runs; you can blend in Jev, an OpenAI-compatible endpoint (Groq, Cerebras,
142
+ Ollama, LM Studio, llama.cpp) or the local embedding model. Remote providers need your consent
143
+ and a key, and only redacted text leaves the machine. Off by default, and it costs nothing while
144
+ off: `eminiar adjudicator enable` ([details](docs/adjudicator.md)).
145
+
109
146
  ### πŸ“Š Local dashboard
110
147
 
111
148
  `eminiar ui` opens a local web app with token savings, usage and cost, a source explorer with
112
- impact analysis, a graph "galaxy", an architecture map, a history explorer, a memory editor and
113
- a docs manager.
149
+ impact analysis, a graph "galaxy", an architecture map, a history explorer with a history
150
+ graph, a memory editor with a memory graph, and a docs manager.
114
151
 
115
152
  | | |
116
153
  |:---:|:---:|
117
- | [<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) |
118
- | **Galaxy:** the knowledge graph | **History:** classified commits and fix tracing |
119
- | [<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) |
120
- | **Memory:** decisions linked to code | **Hotspots:** where change is risky |
154
+ | [<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) |
155
+ | **Galaxy:** the knowledge graph | **Usage:** cost, cache and burn rate |
156
+ | [<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) |
157
+ | **History graph:** branches to files and memories | **Memory graph:** what each memory is about |
158
+ | [<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) |
159
+ | **History:** classified commits and fix tracing | **Hotspots:** where change is risky |
121
160
 
122
161
  ## What Claude sees
123
162
 
@@ -134,6 +173,17 @@ eminiar graph: src/cli.ts is imported by 6 files; em_graph_impact {node:"file:sr
134
173
  them before you change an exported signature.
135
174
  ```
136
175
 
176
+ When a command would flood the context, routing names the tool to use instead:
177
+
178
+ ```text
179
+ eminiar: `git diff .claude-plugin/plugin.json package.json README.md` usually prints a lot;
180
+ em_execute {language:"shell", code, intent} keeps the output out of context and returns only
181
+ matching sections. Retry with em_execute.
182
+
183
+ eminiar history: this commit includes files that deserve a second look:
184
+ - package.json is bug-prone (3/25 commits were fixes; 2 earlier changes later needed fixing)
185
+ ```
186
+
137
187
  Each note appears once per context window. After a `/compact`, notes come back when they are
138
188
  relevant again.
139
189
 
@@ -168,7 +218,7 @@ flowchart LR
168
218
  CC["Claude Code"]
169
219
  subgraph eminiar ["eminiar (local)"]
170
220
  H["Hooks<br/>recall Β· routing Β· capture"]
171
- S["MCP server<br/>23 em_* tools"]
221
+ S["MCP server<br/>24 em_* tools"]
172
222
  SB["Sandbox<br/>shell Β· JS Β· TS Β· Python …"]
173
223
  DB[("SQLite per project<br/>FTS5 Β· vectors Β· graph")]
174
224
  M["Memory files<br/>.eminiar/memories/*.md"]
@@ -202,6 +252,8 @@ Read more in [How it works](docs/how-it-works.md).
202
252
  | [Knowledge graph and history](docs/knowledge-graph.md) | Graph, symbols, impact analysis, git history |
203
253
  | [Documentation knowledge base](docs/documentation-kb.md) | Discover, index and look up docs |
204
254
  | [Usage insights](docs/usage.md) | Tokens, cache, cost, billing blocks, insights and savings per session |
255
+ | [Agent swarms](docs/swarms.md) | Opt-in worker swarms: the swarm file, topologies, the tool and CLI |
256
+ | [Adjudicator](docs/adjudicator.md) | Opt-in tool, model and agent-role suggestions: providers, privacy, savings, accuracy |
205
257
  | [Dashboard](docs/dashboard.md) | The `eminiar ui` web app |
206
258
  | [Configuration](docs/configuration.md) | Every config key with its default |
207
259
  | [CLI](docs/cli.md) | The `eminiar` command |
@@ -214,7 +266,9 @@ Your code, tool output and memories stay on your machine. eminiar uses the netwo
214
266
 
215
267
  - download the embedding model once (you can skip this with `install --no-model`);
216
268
  - fetch web pages and documentation that you or Claude ask it to index;
217
- - look up package metadata in the npm, PyPI and Go registries when it adds documentation.
269
+ - look up package metadata in the npm, PyPI and Go registries when it adds documentation;
270
+ - only if you turn on the adjudicator with a remote provider and agree to it: send redacted
271
+ prompt and task text to that provider ([what is sent](docs/adjudicator.md#providers-and-privacy)).
218
272
 
219
273
  The dashboard listens on `127.0.0.1` only.
220
274
 
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" "$@"