eminiar 0.9.0 → 0.9.1
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/.claude-plugin/plugin.json +1 -1
- package/dist/cli.js +98 -28
- package/dist/hook.js +259 -25
- package/dist/server.js +94 -31
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
3
|
"displayName": "Eminiar",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.1",
|
|
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"
|
package/dist/cli.js
CHANGED
|
@@ -30645,10 +30645,10 @@ async function search(db, query, opts = {}) {
|
|
|
30645
30645
|
}
|
|
30646
30646
|
}
|
|
30647
30647
|
const fused = rrf(lists);
|
|
30648
|
-
const
|
|
30649
|
-
const chunks = db.getChunks(
|
|
30648
|
+
const top2 = [...fused.entries()].sort((a, b) => b[1] - a[1]).slice(0, Math.max(limit * 4, 20));
|
|
30649
|
+
const chunks = db.getChunks(top2.map(([id]) => id));
|
|
30650
30650
|
const byId = new Map(chunks.map((c) => [c.id, c]));
|
|
30651
|
-
const scored =
|
|
30651
|
+
const scored = top2.map(([id, score]) => ({ chunk: byId.get(id), score, matched: [] })).filter((s) => !!s.chunk);
|
|
30652
30652
|
const allTerms = [...terms, ...parsed.phrases];
|
|
30653
30653
|
const ranked = rerank(scored, allTerms);
|
|
30654
30654
|
const seenHash = /* @__PURE__ */ new Set();
|
|
@@ -30866,10 +30866,10 @@ function cycles(nodes, edges) {
|
|
|
30866
30866
|
stack.push(start);
|
|
30867
30867
|
onStack.add(start);
|
|
30868
30868
|
while (work.length) {
|
|
30869
|
-
const
|
|
30870
|
-
const succ = adj.get(
|
|
30871
|
-
if (
|
|
30872
|
-
const m = succ[
|
|
30869
|
+
const top2 = work[work.length - 1];
|
|
30870
|
+
const succ = adj.get(top2.n);
|
|
30871
|
+
if (top2.i < succ.length) {
|
|
30872
|
+
const m = succ[top2.i++];
|
|
30873
30873
|
if (!idx.has(m)) {
|
|
30874
30874
|
idx.set(m, counter);
|
|
30875
30875
|
low.set(m, counter);
|
|
@@ -30878,20 +30878,20 @@ function cycles(nodes, edges) {
|
|
|
30878
30878
|
onStack.add(m);
|
|
30879
30879
|
work.push({ n: m, i: 0 });
|
|
30880
30880
|
} else if (onStack.has(m)) {
|
|
30881
|
-
low.set(
|
|
30881
|
+
low.set(top2.n, Math.min(low.get(top2.n), idx.get(m)));
|
|
30882
30882
|
}
|
|
30883
30883
|
} else {
|
|
30884
30884
|
work.pop();
|
|
30885
30885
|
const parent = work[work.length - 1];
|
|
30886
|
-
if (parent) low.set(parent.n, Math.min(low.get(parent.n), low.get(
|
|
30887
|
-
if (low.get(
|
|
30886
|
+
if (parent) low.set(parent.n, Math.min(low.get(parent.n), low.get(top2.n)));
|
|
30887
|
+
if (low.get(top2.n) === idx.get(top2.n)) {
|
|
30888
30888
|
const comp = [];
|
|
30889
30889
|
let m;
|
|
30890
30890
|
do {
|
|
30891
30891
|
m = stack.pop();
|
|
30892
30892
|
onStack.delete(m);
|
|
30893
30893
|
comp.push(m);
|
|
30894
|
-
} while (m !==
|
|
30894
|
+
} while (m !== top2.n);
|
|
30895
30895
|
if (comp.length > 1) out.push(comp.sort());
|
|
30896
30896
|
}
|
|
30897
30897
|
}
|
|
@@ -31046,8 +31046,8 @@ function computeMetrics(db) {
|
|
|
31046
31046
|
function topFolder(paths2) {
|
|
31047
31047
|
const counts = /* @__PURE__ */ new Map();
|
|
31048
31048
|
for (const p of paths2) {
|
|
31049
|
-
const
|
|
31050
|
-
counts.set(
|
|
31049
|
+
const top2 = p.includes("/") ? `${p.split("/")[0]}/` : "(root)";
|
|
31050
|
+
counts.set(top2, (counts.get(top2) ?? 0) + 1);
|
|
31051
31051
|
}
|
|
31052
31052
|
return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? "";
|
|
31053
31053
|
}
|
|
@@ -31560,7 +31560,7 @@ function chunkBySymbols(code, rel, symbols, opts = DEFAULT_CHUNKER) {
|
|
|
31560
31560
|
const valid = symbols.filter(
|
|
31561
31561
|
(s) => s.startLine >= 1 && s.endLine >= s.startLine && s.endLine <= total
|
|
31562
31562
|
);
|
|
31563
|
-
const
|
|
31563
|
+
const top2 = valid.filter((s) => depthOf(s.name) === 1).sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
|
|
31564
31564
|
const membersOf = (parent) => valid.filter(
|
|
31565
31565
|
(s) => depthOf(s.name) === depthOf(parent.name) + 1 && s.name.startsWith(`${parent.name}.`) && s.startLine >= parent.startLine && s.endLine <= parent.endLine
|
|
31566
31566
|
).sort((a, b) => a.startLine - b.startLine);
|
|
@@ -31589,7 +31589,7 @@ function chunkBySymbols(code, rel, symbols, opts = DEFAULT_CHUNKER) {
|
|
|
31589
31589
|
}
|
|
31590
31590
|
tiny = [];
|
|
31591
31591
|
};
|
|
31592
|
-
for (const sym of
|
|
31592
|
+
for (const sym of top2) {
|
|
31593
31593
|
if (sym.startLine < cursor) continue;
|
|
31594
31594
|
const a = extendUp(sym.startLine, cursor - 1);
|
|
31595
31595
|
const b = sym.endLine;
|
|
@@ -32691,11 +32691,11 @@ async function parsePhp(root, rel, code) {
|
|
|
32691
32691
|
if (file3) {
|
|
32692
32692
|
if (file3 !== rel) addImport(fileId(file3), label, fqn, line, 0.95);
|
|
32693
32693
|
} else {
|
|
32694
|
-
const
|
|
32695
|
-
importedPkgs.add(
|
|
32696
|
-
if (!imports.has(pkgId(
|
|
32697
|
-
nodes.push({ id: pkgId(
|
|
32698
|
-
addImport(pkgId(
|
|
32694
|
+
const top2 = fqn.split("\\")[0];
|
|
32695
|
+
importedPkgs.add(top2);
|
|
32696
|
+
if (!imports.has(pkgId(top2)))
|
|
32697
|
+
nodes.push({ id: pkgId(top2), kind: "package", name: top2, source: "tree-sitter" });
|
|
32698
|
+
addImport(pkgId(top2), label, fqn, line, 1);
|
|
32699
32699
|
}
|
|
32700
32700
|
if (kind) scope.set(local, { id: pkgId(fqn.split("\\")[0]), confidence: 0.6 });
|
|
32701
32701
|
else classUses.set(local, fqn);
|
|
@@ -32802,8 +32802,8 @@ async function parsePhp(root, rel, code) {
|
|
|
32802
32802
|
return;
|
|
32803
32803
|
}
|
|
32804
32804
|
}
|
|
32805
|
-
function insideDecl(n,
|
|
32806
|
-
for (let cur = n.parent; cur && cur.id !==
|
|
32805
|
+
function insideDecl(n, top2) {
|
|
32806
|
+
for (let cur = n.parent; cur && cur.id !== top2.id; cur = cur.parent) {
|
|
32807
32807
|
if (cur.type === "function_definition" || cur.type === "method_declaration") return true;
|
|
32808
32808
|
if (CLASS_KINDS[cur.type]) return true;
|
|
32809
32809
|
}
|
|
@@ -35950,8 +35950,8 @@ function gitignoreHint(ctx) {
|
|
|
35950
35950
|
const file3 = join22(ctx.root, ".gitignore");
|
|
35951
35951
|
if (!existsSync17(file3)) return void 0;
|
|
35952
35952
|
const dir = memoriesRel(ctx);
|
|
35953
|
-
const
|
|
35954
|
-
const ignored = readFileSync16(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l ===
|
|
35953
|
+
const top2 = dir.split("/")[0];
|
|
35954
|
+
const ignored = readFileSync16(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top2 || l === dir || l === `${top2}/**` || l === `${dir}/**`);
|
|
35955
35955
|
return ignored ? `note: .gitignore ignores ${dir}, so memories stay local to this machine (remove that line to share them with the team)` : void 0;
|
|
35956
35956
|
}
|
|
35957
35957
|
var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, PATH_RE, hinted;
|
|
@@ -36614,11 +36614,11 @@ function folderRows(files, languages2) {
|
|
|
36614
36614
|
score: Number((a.score / a.files).toFixed(3)),
|
|
36615
36615
|
languages: a.langs
|
|
36616
36616
|
}));
|
|
36617
|
-
const
|
|
36617
|
+
const top2 = new Map(rows.filter((r) => r.path.split("/").length <= 2).map((r) => [r.path, r]));
|
|
36618
36618
|
return rows.filter((r) => {
|
|
36619
36619
|
const segs = r.path.split("/");
|
|
36620
36620
|
if (segs.length <= 2) return true;
|
|
36621
|
-
return (
|
|
36621
|
+
return (top2.get(`${segs[0]}/`)?.files ?? 0) >= 12;
|
|
36622
36622
|
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
36623
36623
|
}
|
|
36624
36624
|
function entryPoints(root, db) {
|
|
@@ -42255,6 +42255,63 @@ var init_recall = __esm({
|
|
|
42255
42255
|
}
|
|
42256
42256
|
});
|
|
42257
42257
|
|
|
42258
|
+
// src/session/routing-stats.ts
|
|
42259
|
+
function routingStats(events) {
|
|
42260
|
+
const byTool = {};
|
|
42261
|
+
let decisions = 0;
|
|
42262
|
+
for (const e of events) {
|
|
42263
|
+
if (e.category !== ROUTING_CATEGORY) continue;
|
|
42264
|
+
let p;
|
|
42265
|
+
try {
|
|
42266
|
+
p = JSON.parse(e.payload);
|
|
42267
|
+
} catch {
|
|
42268
|
+
continue;
|
|
42269
|
+
}
|
|
42270
|
+
if (!p.tool) continue;
|
|
42271
|
+
decisions++;
|
|
42272
|
+
let row = byTool[p.tool];
|
|
42273
|
+
if (!row) {
|
|
42274
|
+
row = { calls: 0, denied: 0, annotated: 0, targets: {}, rules: {} };
|
|
42275
|
+
byTool[p.tool] = row;
|
|
42276
|
+
}
|
|
42277
|
+
row.calls++;
|
|
42278
|
+
if (p.decision === "deny") {
|
|
42279
|
+
row.denied++;
|
|
42280
|
+
if (p.target) row.targets[p.target] = (row.targets[p.target] ?? 0) + 1;
|
|
42281
|
+
if (p.rule) row.rules[p.rule] = (row.rules[p.rule] ?? 0) + 1;
|
|
42282
|
+
} else if (p.decision === "annotate") row.annotated++;
|
|
42283
|
+
}
|
|
42284
|
+
return { decisions, byTool };
|
|
42285
|
+
}
|
|
42286
|
+
function formatRoutingStats(title, s) {
|
|
42287
|
+
if (!s.decisions) return `${title}: no routing decisions recorded yet`;
|
|
42288
|
+
const tools = Object.entries(s.byTool).sort((a, b) => b[1].calls - a[1].calls).map(([tool, r]) => {
|
|
42289
|
+
const pct = Math.round(100 * r.denied / r.calls);
|
|
42290
|
+
const parts = [`${tool} ${r.denied}/${r.calls} denied (${pct}%)`];
|
|
42291
|
+
if (r.annotated) parts.push(`${r.annotated} annotated`);
|
|
42292
|
+
return parts.join(", ");
|
|
42293
|
+
});
|
|
42294
|
+
const lines = [`${title}: ${s.decisions} native call(s) \u2014 ${tools.join(" \xB7 ")}`];
|
|
42295
|
+
const rules = {};
|
|
42296
|
+
const targets = {};
|
|
42297
|
+
for (const r of Object.values(s.byTool)) {
|
|
42298
|
+
for (const [k, v] of Object.entries(r.rules)) rules[k] = (rules[k] ?? 0) + v;
|
|
42299
|
+
for (const [k, v] of Object.entries(r.targets)) targets[k] = (targets[k] ?? 0) + v;
|
|
42300
|
+
}
|
|
42301
|
+
if (Object.keys(targets).length)
|
|
42302
|
+
lines.push(` denied \u2192 ${top(targets)}; by rule: ${top(rules, 6)}`);
|
|
42303
|
+
return lines.join("\n");
|
|
42304
|
+
}
|
|
42305
|
+
var ROUTING_CATEGORY, top;
|
|
42306
|
+
var init_routing_stats = __esm({
|
|
42307
|
+
"src/session/routing-stats.ts"() {
|
|
42308
|
+
"use strict";
|
|
42309
|
+
init_esm_shims();
|
|
42310
|
+
ROUTING_CATEGORY = "routing";
|
|
42311
|
+
top = (m, n = 4) => Object.entries(m).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => `${k} \xD7${v}`).join(", ");
|
|
42312
|
+
}
|
|
42313
|
+
});
|
|
42314
|
+
|
|
42258
42315
|
// src/tools/em_stats.ts
|
|
42259
42316
|
var em_stats_exports = {};
|
|
42260
42317
|
__export(em_stats_exports, {
|
|
@@ -42282,13 +42339,14 @@ var init_em_stats = __esm({
|
|
|
42282
42339
|
init_zod();
|
|
42283
42340
|
init_current();
|
|
42284
42341
|
init_recall();
|
|
42342
|
+
init_routing_stats();
|
|
42285
42343
|
init_global();
|
|
42286
42344
|
init_record();
|
|
42287
42345
|
init_storage();
|
|
42288
42346
|
init_types2();
|
|
42289
42347
|
emStats = defineTool({
|
|
42290
42348
|
name: "em_stats",
|
|
42291
|
-
description: "Show how much context eminiar has kept out of the conversation: tokens seen vs returned per tool for this session, this project, and lifetime across all projects, plus index/graph sizes and how often proactive recall (memories, docs, history before prompts, reads and edits) fired versus stayed quiet, with the reasons.",
|
|
42349
|
+
description: "Show how much context eminiar has kept out of the conversation: tokens seen vs returned per tool for this session, this project, and lifetime across all projects, plus index/graph sizes, how often routing intercepted native Bash/Read/Grep calls (and by which rule), and how often proactive recall (memories, docs, history before prompts, reads and edits) fired versus stayed quiet, with the reasons.",
|
|
42292
42350
|
schema: {
|
|
42293
42351
|
scope: external_exports.enum(["session", "project", "lifetime", "all"]).optional().describe("default all")
|
|
42294
42352
|
},
|
|
@@ -42299,6 +42357,12 @@ var init_em_stats = __esm({
|
|
|
42299
42357
|
const parts = [];
|
|
42300
42358
|
if (scope === "session" || scope === "all") {
|
|
42301
42359
|
parts.push(formatStats(`session ${sessionId.slice(0, 8)}`, db.statsBySession(sessionId)));
|
|
42360
|
+
parts.push(
|
|
42361
|
+
formatRoutingStats(
|
|
42362
|
+
"routing (session)",
|
|
42363
|
+
routingStats(db.eventsBySession(sessionId, { categories: [ROUTING_CATEGORY] }))
|
|
42364
|
+
)
|
|
42365
|
+
);
|
|
42302
42366
|
parts.push(
|
|
42303
42367
|
formatRecallStats(
|
|
42304
42368
|
"recall (session)",
|
|
@@ -42308,6 +42372,12 @@ var init_em_stats = __esm({
|
|
|
42308
42372
|
}
|
|
42309
42373
|
if (scope === "project" || scope === "all") {
|
|
42310
42374
|
parts.push(formatStats(`project ${ctx.root}`, mergeByTool(db.statsAll())));
|
|
42375
|
+
parts.push(
|
|
42376
|
+
formatRoutingStats(
|
|
42377
|
+
"routing (project, last 2000 calls)",
|
|
42378
|
+
routingStats(db.eventsRecent(2e3, [ROUTING_CATEGORY]))
|
|
42379
|
+
)
|
|
42380
|
+
);
|
|
42311
42381
|
parts.push(
|
|
42312
42382
|
formatRecallStats(
|
|
42313
42383
|
"recall (project, last 2000 decisions)",
|
|
@@ -45351,7 +45421,7 @@ Use
|
|
|
45351
45421
|
docs add [--pick recommended|local|service|infra|catalog|api|registry|all] [--name N] [--url U ...] [--path P]
|
|
45352
45422
|
[--glob G ...] [--provider npm:x|pypi:x|go:x|env:X|image:x|api:host] [--depth N] [--max N] [--no-sync]
|
|
45353
45423
|
docs list | sync [name ...] [--force] | remove <name> | lookup "<q>" [--name N] | resolve <provider>
|
|
45354
|
-
stats [--lifetime] Context savings
|
|
45424
|
+
stats [--lifetime] Context savings, routing interceptions, how often recall fired
|
|
45355
45425
|
recall ["<q>"] Memories and session history
|
|
45356
45426
|
recall --explain "<prompt>" [--session ID] Why hook recall would (not) fire for a prompt
|
|
45357
45427
|
recall --file <path> What a Read/Edit of the file would attach (memories, history)
|
package/dist/hook.js
CHANGED
|
@@ -5919,7 +5919,9 @@ var CATEGORIES = {
|
|
|
5919
5919
|
tool_call: 4,
|
|
5920
5920
|
session_start: 4,
|
|
5921
5921
|
/** A proactive recall decision (fired or quiet) and what it showed; never in the guide. */
|
|
5922
|
-
recall: 4
|
|
5922
|
+
recall: 4,
|
|
5923
|
+
/** A routing decision (allow / annotate / deny) for one native call; never in the guide. */
|
|
5924
|
+
routing: 4
|
|
5923
5925
|
};
|
|
5924
5926
|
var GUIDE_ORDER = [
|
|
5925
5927
|
{ title: "Last Request", categories: ["user_prompt"] },
|
|
@@ -29119,6 +29121,7 @@ var preCompact = (input2) => {
|
|
|
29119
29121
|
// src/hooks/routing.ts
|
|
29120
29122
|
init_esm_shims();
|
|
29121
29123
|
import { realpathSync as realpathSync4, statSync as statSync4 } from "fs";
|
|
29124
|
+
import { homedir as homedir4 } from "os";
|
|
29122
29125
|
import { extname as extname2, join as join11, relative as relative4, resolve as resolve3 } from "path";
|
|
29123
29126
|
|
|
29124
29127
|
// src/docs/manifest.ts
|
|
@@ -29416,6 +29419,26 @@ function fitBudget(lines, budget) {
|
|
|
29416
29419
|
return out.length > budget ? `${out.slice(0, budget - 1)}\u2026` : out;
|
|
29417
29420
|
}
|
|
29418
29421
|
|
|
29422
|
+
// src/session/routing-stats.ts
|
|
29423
|
+
init_esm_shims();
|
|
29424
|
+
var ROUTING_CATEGORY = "routing";
|
|
29425
|
+
function recordRouting(db, sessionId, e) {
|
|
29426
|
+
db.eventInsert({
|
|
29427
|
+
sessionId,
|
|
29428
|
+
category: ROUTING_CATEGORY,
|
|
29429
|
+
priority: 4,
|
|
29430
|
+
tool: e.tool,
|
|
29431
|
+
payload: {
|
|
29432
|
+
tool: e.tool,
|
|
29433
|
+
decision: e.decision,
|
|
29434
|
+
...e.target ? { target: e.target } : {},
|
|
29435
|
+
...e.rule ? { rule: e.rule } : {},
|
|
29436
|
+
...e.why ? { why: e.why } : {},
|
|
29437
|
+
...e.head ? { head: e.head.slice(0, 120) } : {}
|
|
29438
|
+
}
|
|
29439
|
+
});
|
|
29440
|
+
}
|
|
29441
|
+
|
|
29419
29442
|
// src/session/server-alive.ts
|
|
29420
29443
|
init_esm_shims();
|
|
29421
29444
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -29609,29 +29632,201 @@ var BIG_TEXT_EXT = /* @__PURE__ */ new Set([
|
|
|
29609
29632
|
function withoutHeredocs(cmd) {
|
|
29610
29633
|
return cmd.replace(/<<-?\s*(['"]?)(\w+)\1[^\n]*\n[\s\S]*?\n\s*\2\s*(?=\n|$)/g, "<<$2");
|
|
29611
29634
|
}
|
|
29635
|
+
function splitUnquoted(s, sep2) {
|
|
29636
|
+
const out = [];
|
|
29637
|
+
let cur = "";
|
|
29638
|
+
let quote2;
|
|
29639
|
+
for (let i = 0; i < s.length; i++) {
|
|
29640
|
+
const ch = s[i];
|
|
29641
|
+
if (quote2) {
|
|
29642
|
+
cur += ch;
|
|
29643
|
+
if (ch === quote2 && s[i - 1] !== "\\") quote2 = void 0;
|
|
29644
|
+
continue;
|
|
29645
|
+
}
|
|
29646
|
+
if (ch === "'" || ch === '"') {
|
|
29647
|
+
quote2 = ch;
|
|
29648
|
+
cur += ch;
|
|
29649
|
+
continue;
|
|
29650
|
+
}
|
|
29651
|
+
const n = sep2(s, i);
|
|
29652
|
+
if (n) {
|
|
29653
|
+
out.push(cur);
|
|
29654
|
+
cur = "";
|
|
29655
|
+
i += n - 1;
|
|
29656
|
+
continue;
|
|
29657
|
+
}
|
|
29658
|
+
cur += ch;
|
|
29659
|
+
}
|
|
29660
|
+
out.push(cur);
|
|
29661
|
+
return out.map((p) => p.trim()).filter(Boolean);
|
|
29662
|
+
}
|
|
29663
|
+
function commandSegments(cmd) {
|
|
29664
|
+
return splitUnquoted(cmd, (s, i) => {
|
|
29665
|
+
const ch = s[i];
|
|
29666
|
+
if (ch === ";" || ch === "\n") return 1;
|
|
29667
|
+
if ((ch === "&" || ch === "|") && s[i + 1] === ch) return 2;
|
|
29668
|
+
return 0;
|
|
29669
|
+
});
|
|
29670
|
+
}
|
|
29671
|
+
function pipeStages(segment) {
|
|
29672
|
+
return splitUnquoted(segment, (s, i) => s[i] === "|" && s[i + 1] !== "|" ? 1 : 0);
|
|
29673
|
+
}
|
|
29674
|
+
function headLines(stage) {
|
|
29675
|
+
const m = /(?:^|\s)(?:-n\s*=?\s*|-)(\d+)(?=\s|$)|--lines[= ](\d+)/.exec(stage);
|
|
29676
|
+
return m ? Number(m[1] ?? m[2]) : 10;
|
|
29677
|
+
}
|
|
29678
|
+
var BOUNDED_LINES = 400;
|
|
29679
|
+
function bounds(stage) {
|
|
29680
|
+
if (/^\s*wc\b/.test(stage)) return true;
|
|
29681
|
+
if (!/^\s*(head|tail)\b/.test(stage) || /(^|\s)-c\b|--bytes\b/.test(stage)) return false;
|
|
29682
|
+
return headLines(stage) <= BOUNDED_LINES;
|
|
29683
|
+
}
|
|
29684
|
+
var MUTATING = /^\s*(?:(?:sudo|env|[A-Z_][A-Z0-9_]*=\S*)\s+)*(git\s+(add|commit|checkout|switch|push|pull|fetch|merge|rebase|stash|reset|restore|cherry-pick|revert|tag|init|clone|worktree|mv|rm|am|apply|submodule)|npm\s+(install|i|ci|uninstall|un|link|publish|version|update|up|dedupe)|pnpm\s+(install|i|add|remove|rm|update|up|link)|yarn\s+(add|remove|install|upgrade)|pip3?\s+(install|uninstall)|cargo\s+(add|install)|brew\s+(install|uninstall|upgrade)|gh\s+(pr|issue|release|repo)\s+(create|edit|merge|close|comment|delete|ready)|rm|mv|cp|mkdir|rmdir|touch|chmod|chown|ln|sed\s+(?:-\S+\s+)*-i|tee)\b/;
|
|
29685
|
+
var WRITES_FILE = /(?:^|\s|\d)>{1,2}\s*(?!&)(?!\/dev\/null)\S/;
|
|
29686
|
+
function mutates(segment) {
|
|
29687
|
+
return MUTATING.test(segment) || WRITES_FILE.test(segment);
|
|
29688
|
+
}
|
|
29689
|
+
var READ_LIMIT = 200 * 1024;
|
|
29690
|
+
var BIG_TEXT_LIMIT = 20 * 1024;
|
|
29691
|
+
var GREP_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
29692
|
+
"-e",
|
|
29693
|
+
"-f",
|
|
29694
|
+
"-m",
|
|
29695
|
+
"-A",
|
|
29696
|
+
"-B",
|
|
29697
|
+
"-C",
|
|
29698
|
+
"-d",
|
|
29699
|
+
"-g",
|
|
29700
|
+
"-t",
|
|
29701
|
+
"-T",
|
|
29702
|
+
"--regexp",
|
|
29703
|
+
"--file",
|
|
29704
|
+
"--max-count",
|
|
29705
|
+
"--after-context",
|
|
29706
|
+
"--before-context",
|
|
29707
|
+
"--context",
|
|
29708
|
+
"--include",
|
|
29709
|
+
"--exclude",
|
|
29710
|
+
"--glob",
|
|
29711
|
+
"--type"
|
|
29712
|
+
]);
|
|
29713
|
+
function fileOperands(stage, cwd) {
|
|
29714
|
+
const tokens = stage.trim().split(/\s+/);
|
|
29715
|
+
const cmd = tokens[0];
|
|
29716
|
+
const isGrep = /^(grep|egrep|fgrep|rg)$/.test(cmd);
|
|
29717
|
+
const files = [];
|
|
29718
|
+
let patternGiven = !isGrep;
|
|
29719
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
29720
|
+
const t = tokens[i];
|
|
29721
|
+
if (t === "--") {
|
|
29722
|
+
files.push(...tokens.slice(i + 1));
|
|
29723
|
+
break;
|
|
29724
|
+
}
|
|
29725
|
+
if (t.startsWith("-") && t.length > 1) {
|
|
29726
|
+
if (/^-[ef]$|^--(regexp|file)$/.test(t)) patternGiven = true;
|
|
29727
|
+
if (GREP_VALUE_FLAGS.has(t)) i++;
|
|
29728
|
+
continue;
|
|
29729
|
+
}
|
|
29730
|
+
if (!patternGiven) {
|
|
29731
|
+
patternGiven = true;
|
|
29732
|
+
continue;
|
|
29733
|
+
}
|
|
29734
|
+
files.push(t);
|
|
29735
|
+
}
|
|
29736
|
+
if (!files.length) return void 0;
|
|
29737
|
+
const out = [];
|
|
29738
|
+
for (let f of files) {
|
|
29739
|
+
if (/[*?[\]{}$`\\]/.test(f)) return void 0;
|
|
29740
|
+
f = f.replace(/^['"]|['"]$/g, "");
|
|
29741
|
+
if (f.startsWith("~/")) f = join11(homedir4(), f.slice(2));
|
|
29742
|
+
out.push(resolve3(cwd, f));
|
|
29743
|
+
}
|
|
29744
|
+
return out;
|
|
29745
|
+
}
|
|
29746
|
+
function stageBytes(stage, cwd) {
|
|
29747
|
+
const files = fileOperands(stage, cwd);
|
|
29748
|
+
if (!files) return void 0;
|
|
29749
|
+
let total = 0;
|
|
29750
|
+
for (const f of files) {
|
|
29751
|
+
try {
|
|
29752
|
+
const st = statSync4(f);
|
|
29753
|
+
if (st.isDirectory()) return void 0;
|
|
29754
|
+
if (BIG_TEXT_EXT.has(extname2(f).toLowerCase()) && st.size > BIG_TEXT_LIMIT)
|
|
29755
|
+
return READ_LIMIT + st.size;
|
|
29756
|
+
total += st.size;
|
|
29757
|
+
} catch {
|
|
29758
|
+
}
|
|
29759
|
+
}
|
|
29760
|
+
return total;
|
|
29761
|
+
}
|
|
29762
|
+
function sedRangeLines(stage) {
|
|
29763
|
+
const m = /^\s*sed\s+(?:-\S+\s+)*-n\s+['"]?(\d+),(\d+)p['"]?/.exec(stage);
|
|
29764
|
+
return m ? Number(m[2]) - Number(m[1]) + 1 : void 0;
|
|
29765
|
+
}
|
|
29766
|
+
function firstStageBound(first, cwd) {
|
|
29767
|
+
if (/^\s*(head|tail)\s/.test(first)) return bounds(first) || void 0;
|
|
29768
|
+
const sed = sedRangeLines(first);
|
|
29769
|
+
if (sed !== void 0) return sed <= BOUNDED_LINES || void 0;
|
|
29770
|
+
if (/^\s*du\s+-[a-zA-Z]*s/.test(first)) return true;
|
|
29771
|
+
if (/^\s*git\s+log\b/.test(first) && ONE_COMMIT.test(first)) return true;
|
|
29772
|
+
if (/^\s*(grep|egrep|fgrep|rg)\s/.test(first) && /(^|\s)-[a-zA-Z]*[lLcq][a-zA-Z]*(?=\s|$)|--(files-with-matches|files-without-match|count|quiet)\b/.test(
|
|
29773
|
+
first
|
|
29774
|
+
))
|
|
29775
|
+
return true;
|
|
29776
|
+
if (/^\s*(cat|grep|egrep|fgrep|rg)\s/.test(first)) {
|
|
29777
|
+
const bytes = stageBytes(first, cwd);
|
|
29778
|
+
if (bytes === void 0) return void 0;
|
|
29779
|
+
if (bytes <= READ_LIMIT) return true;
|
|
29780
|
+
return { rule: "big-read", text: `reads about ${Math.round(bytes / 1024)} KB` };
|
|
29781
|
+
}
|
|
29782
|
+
return void 0;
|
|
29783
|
+
}
|
|
29784
|
+
function heavySegment(segment, cwd) {
|
|
29785
|
+
if (TINY_BASH.test(segment)) return void 0;
|
|
29786
|
+
const stages = pipeStages(segment);
|
|
29787
|
+
const first = stages[0];
|
|
29788
|
+
if (!first) return void 0;
|
|
29789
|
+
if (stages.length > 1 && bounds(stages[stages.length - 1])) return void 0;
|
|
29790
|
+
const bound = firstStageBound(first, cwd);
|
|
29791
|
+
if (bound === true) return void 0;
|
|
29792
|
+
if (bound) return bound;
|
|
29793
|
+
if (HEAVY_BASH.test(first)) return { rule: "heavy", text: "usually prints a lot" };
|
|
29794
|
+
if (stages.slice(1).some((s) => /^\s*(grep|rg|sort|uniq|jq|less)\b/.test(s)))
|
|
29795
|
+
return { rule: "pipe", text: "streams an unbounded output through a filter" };
|
|
29796
|
+
return void 0;
|
|
29797
|
+
}
|
|
29798
|
+
function headOf(segment, n = 60) {
|
|
29799
|
+
const one = segment.replace(/\s+/g, " ").trim();
|
|
29800
|
+
return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
|
|
29801
|
+
}
|
|
29802
|
+
function bashSuggestion(raw, cwd) {
|
|
29803
|
+
const cmd = withoutHeredocs(raw);
|
|
29804
|
+
if (!cmd.trim()) return void 0;
|
|
29805
|
+
const segments = commandSegments(cmd);
|
|
29806
|
+
if (segments.some(mutates)) return void 0;
|
|
29807
|
+
const history = historyCall(cmd);
|
|
29808
|
+
if (history) {
|
|
29809
|
+
return {
|
|
29810
|
+
tool: "em_history",
|
|
29811
|
+
rule: "history",
|
|
29812
|
+
reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
|
|
29813
|
+
};
|
|
29814
|
+
}
|
|
29815
|
+
for (const segment of segments) {
|
|
29816
|
+
const heavy = heavySegment(segment, cwd);
|
|
29817
|
+
if (!heavy) continue;
|
|
29818
|
+
return {
|
|
29819
|
+
tool: "em_execute",
|
|
29820
|
+
rule: heavy.rule,
|
|
29821
|
+
reason: `\`${headOf(segment)}\` ${heavy.text}; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
|
|
29822
|
+
};
|
|
29823
|
+
}
|
|
29824
|
+
return void 0;
|
|
29825
|
+
}
|
|
29612
29826
|
function suggest(toolName, input2, root, extra = {}) {
|
|
29613
29827
|
switch (toolName) {
|
|
29614
|
-
case "Bash":
|
|
29615
|
-
|
|
29616
|
-
if (!cmd || TINY_BASH.test(cmd)) return void 0;
|
|
29617
|
-
if (/^\s*cat\b[^|\n]*>/.test(cmd) && !cmd.includes("|")) return void 0;
|
|
29618
|
-
const history = historyCall(cmd);
|
|
29619
|
-
if (history) {
|
|
29620
|
-
return {
|
|
29621
|
-
tool: "em_history",
|
|
29622
|
-
reason: `the git history is already classified (commit kinds, fixes traced to the change that caused them, risk per file); em_history ${history} answers this without the raw log`
|
|
29623
|
-
};
|
|
29624
|
-
}
|
|
29625
|
-
if (/^\s*git\s+log\b/.test(cmd) && ONE_COMMIT.test(cmd) && !cmd.includes("|"))
|
|
29626
|
-
return void 0;
|
|
29627
|
-
if (HEAVY_BASH.test(cmd) || /\|\s*(head|tail|grep|wc|sort|uniq|jq|less)\b/.test(cmd)) {
|
|
29628
|
-
return {
|
|
29629
|
-
tool: "em_execute",
|
|
29630
|
-
reason: `this command is likely to print a lot; em_execute {language:"shell", code, intent} keeps the output out of context and returns only matching sections`
|
|
29631
|
-
};
|
|
29632
|
-
}
|
|
29633
|
-
return void 0;
|
|
29634
|
-
}
|
|
29828
|
+
case "Bash":
|
|
29829
|
+
return bashSuggestion(String(input2.command ?? ""), extra.cwd ?? root);
|
|
29635
29830
|
case "Read": {
|
|
29636
29831
|
const fp = String(input2.file_path ?? "");
|
|
29637
29832
|
if (!fp) return void 0;
|
|
@@ -29643,6 +29838,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29643
29838
|
if (!checkReadPath(loadPermissionRules(root), abs).allowed) return void 0;
|
|
29644
29839
|
return {
|
|
29645
29840
|
tool: "em_execute_file",
|
|
29841
|
+
rule: "big-read",
|
|
29646
29842
|
reason: `${fp} is ${Math.round(st.size / 1024)} KB; em_execute_file {path, code} processes it in the sandbox so only what you print enters context`
|
|
29647
29843
|
};
|
|
29648
29844
|
}
|
|
@@ -29655,6 +29851,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29655
29851
|
if (extra.symbol) {
|
|
29656
29852
|
return {
|
|
29657
29853
|
tool: "em_symbols",
|
|
29854
|
+
rule: "symbol",
|
|
29658
29855
|
annotateOnly: !content,
|
|
29659
29856
|
reason: `the graph indexes "${extra.symbol}": em_symbols {name_path:"${extra.symbol}", mode:"references"} lists its uses, {include_body:true} its definition; em_search {queries:["${extra.symbol}"], source:"code"} finds the code by symbol`
|
|
29660
29857
|
};
|
|
@@ -29662,6 +29859,7 @@ function suggest(toolName, input2, root, extra = {}) {
|
|
|
29662
29859
|
if (content) {
|
|
29663
29860
|
return {
|
|
29664
29861
|
tool: "em_execute",
|
|
29862
|
+
rule: "content-grep",
|
|
29665
29863
|
reason: `content-mode grep can be large; em_execute with rg and an intent returns only matching sections (em_search {queries, mode:"regex"} scans the indexed files with context)`
|
|
29666
29864
|
};
|
|
29667
29865
|
}
|
|
@@ -29830,9 +30028,16 @@ var preToolUse = async (input2) => {
|
|
|
29830
30028
|
const graph = graphContext(ctx, input2);
|
|
29831
30029
|
const hint = joinHints(graph, memo2);
|
|
29832
30030
|
const symbol2 = input2.tool_name === "Grep" && graph ? literalOf(String(input2.tool_input.pattern ?? "")) : void 0;
|
|
29833
|
-
const s = suggest(input2.tool_name, input2.tool_input, ctx.root, { symbol: symbol2 });
|
|
29834
|
-
|
|
29835
|
-
if (s
|
|
30031
|
+
const s = suggest(input2.tool_name, input2.tool_input, ctx.root, { symbol: symbol2, cwd: input2.cwd });
|
|
30032
|
+
const record2 = routingRecorder(ctx, input2, s);
|
|
30033
|
+
if (!s) {
|
|
30034
|
+
record2("allow");
|
|
30035
|
+
return withContext(void 0, hint);
|
|
30036
|
+
}
|
|
30037
|
+
if (s.annotateOnly) {
|
|
30038
|
+
record2("annotate", "annotate-only");
|
|
30039
|
+
return withContext(annotate(s), hint);
|
|
30040
|
+
}
|
|
29836
30041
|
if (mode === "enforce") {
|
|
29837
30042
|
if (s.prefixPrompt) {
|
|
29838
30043
|
const prompt = String(input2.tool_input.prompt ?? "");
|
|
@@ -29853,6 +30058,7 @@ var preToolUse = async (input2) => {
|
|
|
29853
30058
|
"info",
|
|
29854
30059
|
`enforce \u2192 annotate for ${input2.tool_name}: no eminiar MCP server running for ${ctx.root}`
|
|
29855
30060
|
);
|
|
30061
|
+
record2("annotate", "no-server");
|
|
29856
30062
|
return withContext(
|
|
29857
30063
|
annotate(
|
|
29858
30064
|
s,
|
|
@@ -29861,6 +30067,8 @@ var preToolUse = async (input2) => {
|
|
|
29861
30067
|
hint
|
|
29862
30068
|
);
|
|
29863
30069
|
}
|
|
30070
|
+
record2("deny");
|
|
30071
|
+
hookLog("info", `deny ${input2.tool_name} \u2192 ${s.tool} [${s.rule ?? "?"}]: ${callHead(input2)}`);
|
|
29864
30072
|
return {
|
|
29865
30073
|
hookSpecificOutput: {
|
|
29866
30074
|
hookEventName: "PreToolUse",
|
|
@@ -29869,9 +30077,35 @@ var preToolUse = async (input2) => {
|
|
|
29869
30077
|
}
|
|
29870
30078
|
};
|
|
29871
30079
|
}
|
|
30080
|
+
record2("annotate", "mode");
|
|
29872
30081
|
const extra = s.prefixPrompt ? joinHints((await briefFor(ctx, String(input2.tool_input.prompt ?? ""))).trim(), hint) : hint;
|
|
29873
30082
|
return withContext(annotate(s), extra);
|
|
29874
30083
|
};
|
|
30084
|
+
var ROUTED_TOOLS = /* @__PURE__ */ new Set(["Bash", "Read", "Grep", "WebFetch", "WebSearch"]);
|
|
30085
|
+
function callHead(input2) {
|
|
30086
|
+
const t = input2.tool_input ?? {};
|
|
30087
|
+
const v = t.command ?? t.file_path ?? t.pattern ?? t.url ?? t.query ?? "";
|
|
30088
|
+
return headOf(String(v), 120);
|
|
30089
|
+
}
|
|
30090
|
+
function routingRecorder(ctx, input2, s) {
|
|
30091
|
+
const tool = input2.tool_name ?? "";
|
|
30092
|
+
const sessionId = input2.session_id;
|
|
30093
|
+
if (!sessionId || !ROUTED_TOOLS.has(tool)) return () => {
|
|
30094
|
+
};
|
|
30095
|
+
return (decision, why) => {
|
|
30096
|
+
try {
|
|
30097
|
+
recordRouting(getStorage(ctx, { vectors: false }), sessionId, {
|
|
30098
|
+
tool,
|
|
30099
|
+
decision,
|
|
30100
|
+
target: s?.tool,
|
|
30101
|
+
rule: s?.rule,
|
|
30102
|
+
why,
|
|
30103
|
+
head: callHead(input2)
|
|
30104
|
+
});
|
|
30105
|
+
} catch {
|
|
30106
|
+
}
|
|
30107
|
+
};
|
|
30108
|
+
}
|
|
29875
30109
|
async function briefFor(ctx, task) {
|
|
29876
30110
|
const parts = [];
|
|
29877
30111
|
try {
|
package/dist/server.js
CHANGED
|
@@ -13703,7 +13703,7 @@ function chunkBySymbols(code, rel, symbols, opts = DEFAULT_CHUNKER) {
|
|
|
13703
13703
|
const valid = symbols.filter(
|
|
13704
13704
|
(s) => s.startLine >= 1 && s.endLine >= s.startLine && s.endLine <= total
|
|
13705
13705
|
);
|
|
13706
|
-
const
|
|
13706
|
+
const top2 = valid.filter((s) => depthOf(s.name) === 1).sort((a, b) => a.startLine - b.startLine || b.endLine - a.endLine);
|
|
13707
13707
|
const membersOf = (parent) => valid.filter(
|
|
13708
13708
|
(s) => depthOf(s.name) === depthOf(parent.name) + 1 && s.name.startsWith(`${parent.name}.`) && s.startLine >= parent.startLine && s.endLine <= parent.endLine
|
|
13709
13709
|
).sort((a, b) => a.startLine - b.startLine);
|
|
@@ -13732,7 +13732,7 @@ function chunkBySymbols(code, rel, symbols, opts = DEFAULT_CHUNKER) {
|
|
|
13732
13732
|
}
|
|
13733
13733
|
tiny = [];
|
|
13734
13734
|
};
|
|
13735
|
-
for (const sym of
|
|
13735
|
+
for (const sym of top2) {
|
|
13736
13736
|
if (sym.startLine < cursor) continue;
|
|
13737
13737
|
const a = extendUp(sym.startLine, cursor - 1);
|
|
13738
13738
|
const b = sym.endLine;
|
|
@@ -14886,11 +14886,11 @@ async function parsePhp(root, rel, code) {
|
|
|
14886
14886
|
if (file3) {
|
|
14887
14887
|
if (file3 !== rel) addImport(fileId(file3), label, fqn, line, 0.95);
|
|
14888
14888
|
} else {
|
|
14889
|
-
const
|
|
14890
|
-
importedPkgs.add(
|
|
14891
|
-
if (!imports.has(pkgId(
|
|
14892
|
-
nodes.push({ id: pkgId(
|
|
14893
|
-
addImport(pkgId(
|
|
14889
|
+
const top2 = fqn.split("\\")[0];
|
|
14890
|
+
importedPkgs.add(top2);
|
|
14891
|
+
if (!imports.has(pkgId(top2)))
|
|
14892
|
+
nodes.push({ id: pkgId(top2), kind: "package", name: top2, source: "tree-sitter" });
|
|
14893
|
+
addImport(pkgId(top2), label, fqn, line, 1);
|
|
14894
14894
|
}
|
|
14895
14895
|
if (kind) scope.set(local, { id: pkgId(fqn.split("\\")[0]), confidence: 0.6 });
|
|
14896
14896
|
else classUses.set(local, fqn);
|
|
@@ -14997,8 +14997,8 @@ async function parsePhp(root, rel, code) {
|
|
|
14997
14997
|
return;
|
|
14998
14998
|
}
|
|
14999
14999
|
}
|
|
15000
|
-
function insideDecl(n,
|
|
15001
|
-
for (let cur = n.parent; cur && cur.id !==
|
|
15000
|
+
function insideDecl(n, top2) {
|
|
15001
|
+
for (let cur = n.parent; cur && cur.id !== top2.id; cur = cur.parent) {
|
|
15002
15002
|
if (cur.type === "function_definition" || cur.type === "method_declaration") return true;
|
|
15003
15003
|
if (CLASS_KINDS[cur.type]) return true;
|
|
15004
15004
|
}
|
|
@@ -17428,10 +17428,10 @@ function cycles(nodes, edges) {
|
|
|
17428
17428
|
stack.push(start);
|
|
17429
17429
|
onStack.add(start);
|
|
17430
17430
|
while (work.length) {
|
|
17431
|
-
const
|
|
17432
|
-
const succ = adj.get(
|
|
17433
|
-
if (
|
|
17434
|
-
const m = succ[
|
|
17431
|
+
const top2 = work[work.length - 1];
|
|
17432
|
+
const succ = adj.get(top2.n);
|
|
17433
|
+
if (top2.i < succ.length) {
|
|
17434
|
+
const m = succ[top2.i++];
|
|
17435
17435
|
if (!idx.has(m)) {
|
|
17436
17436
|
idx.set(m, counter);
|
|
17437
17437
|
low.set(m, counter);
|
|
@@ -17440,20 +17440,20 @@ function cycles(nodes, edges) {
|
|
|
17440
17440
|
onStack.add(m);
|
|
17441
17441
|
work.push({ n: m, i: 0 });
|
|
17442
17442
|
} else if (onStack.has(m)) {
|
|
17443
|
-
low.set(
|
|
17443
|
+
low.set(top2.n, Math.min(low.get(top2.n), idx.get(m)));
|
|
17444
17444
|
}
|
|
17445
17445
|
} else {
|
|
17446
17446
|
work.pop();
|
|
17447
17447
|
const parent = work[work.length - 1];
|
|
17448
|
-
if (parent) low.set(parent.n, Math.min(low.get(parent.n), low.get(
|
|
17449
|
-
if (low.get(
|
|
17448
|
+
if (parent) low.set(parent.n, Math.min(low.get(parent.n), low.get(top2.n)));
|
|
17449
|
+
if (low.get(top2.n) === idx.get(top2.n)) {
|
|
17450
17450
|
const comp = [];
|
|
17451
17451
|
let m;
|
|
17452
17452
|
do {
|
|
17453
17453
|
m = stack.pop();
|
|
17454
17454
|
onStack.delete(m);
|
|
17455
17455
|
comp.push(m);
|
|
17456
|
-
} while (m !==
|
|
17456
|
+
} while (m !== top2.n);
|
|
17457
17457
|
if (comp.length > 1) out.push(comp.sort());
|
|
17458
17458
|
}
|
|
17459
17459
|
}
|
|
@@ -17608,8 +17608,8 @@ function computeMetrics(db) {
|
|
|
17608
17608
|
function topFolder(paths2) {
|
|
17609
17609
|
const counts = /* @__PURE__ */ new Map();
|
|
17610
17610
|
for (const p of paths2) {
|
|
17611
|
-
const
|
|
17612
|
-
counts.set(
|
|
17611
|
+
const top2 = p.includes("/") ? `${p.split("/")[0]}/` : "(root)";
|
|
17612
|
+
counts.set(top2, (counts.get(top2) ?? 0) + 1);
|
|
17613
17613
|
}
|
|
17614
17614
|
return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]?.[0] ?? "";
|
|
17615
17615
|
}
|
|
@@ -18452,8 +18452,8 @@ function gitignoreHint(ctx) {
|
|
|
18452
18452
|
const file3 = join12(ctx.root, ".gitignore");
|
|
18453
18453
|
if (!existsSync8(file3)) return void 0;
|
|
18454
18454
|
const dir = memoriesRel(ctx);
|
|
18455
|
-
const
|
|
18456
|
-
const ignored = readFileSync8(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l ===
|
|
18455
|
+
const top2 = dir.split("/")[0];
|
|
18456
|
+
const ignored = readFileSync8(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top2 || l === dir || l === `${top2}/**` || l === `${dir}/**`);
|
|
18457
18457
|
return ignored ? `note: .gitignore ignores ${dir}, so memories stay local to this machine (remove that line to share them with the team)` : void 0;
|
|
18458
18458
|
}
|
|
18459
18459
|
var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, PATH_RE, hinted;
|
|
@@ -51219,11 +51219,11 @@ function folderRows(files, languages2) {
|
|
|
51219
51219
|
score: Number((a.score / a.files).toFixed(3)),
|
|
51220
51220
|
languages: a.langs
|
|
51221
51221
|
}));
|
|
51222
|
-
const
|
|
51222
|
+
const top2 = new Map(rows.filter((r) => r.path.split("/").length <= 2).map((r) => [r.path, r]));
|
|
51223
51223
|
return rows.filter((r) => {
|
|
51224
51224
|
const segs = r.path.split("/");
|
|
51225
51225
|
if (segs.length <= 2) return true;
|
|
51226
|
-
return (
|
|
51226
|
+
return (top2.get(`${segs[0]}/`)?.files ?? 0) >= 12;
|
|
51227
51227
|
}).sort((a, b) => a.path.localeCompare(b.path));
|
|
51228
51228
|
}
|
|
51229
51229
|
function entryPoints(root, db) {
|
|
@@ -52869,10 +52869,10 @@ async function search(db, query, opts = {}) {
|
|
|
52869
52869
|
}
|
|
52870
52870
|
}
|
|
52871
52871
|
const fused = rrf(lists);
|
|
52872
|
-
const
|
|
52873
|
-
const chunks = db.getChunks(
|
|
52872
|
+
const top2 = [...fused.entries()].sort((a, b) => b[1] - a[1]).slice(0, Math.max(limit * 4, 20));
|
|
52873
|
+
const chunks = db.getChunks(top2.map(([id]) => id));
|
|
52874
52874
|
const byId = new Map(chunks.map((c) => [c.id, c]));
|
|
52875
|
-
const scored =
|
|
52875
|
+
const scored = top2.map(([id, score]) => ({ chunk: byId.get(id), score, matched: [] })).filter((s) => !!s.chunk);
|
|
52876
52876
|
const allTerms = [...terms, ...parsed.phrases];
|
|
52877
52877
|
const ranked = rerank(scored, allTerms);
|
|
52878
52878
|
const seenHash = /* @__PURE__ */ new Set();
|
|
@@ -57892,12 +57892,12 @@ function renderBranch(m, name, limit = 20) {
|
|
|
57892
57892
|
);
|
|
57893
57893
|
const files = /* @__PURE__ */ new Map();
|
|
57894
57894
|
for (const c of own2) for (const f of c.files) files.set(f.path, (files.get(f.path) ?? 0) + 1);
|
|
57895
|
-
const
|
|
57896
|
-
if (
|
|
57895
|
+
const top2 = [...files.entries()].sort((a, b2) => b2[1] - a[1]).slice(0, 10);
|
|
57896
|
+
if (top2.length)
|
|
57897
57897
|
lines.push(
|
|
57898
|
-
`files touched: ${
|
|
57898
|
+
`files touched: ${top2.map(([p, n]) => n > 1 ? `${p} \xD7${n}` : p).join(", ")}${files.size > 10 ? ` \u2026 +${files.size - 10}` : ""}`
|
|
57899
57899
|
);
|
|
57900
|
-
const risky =
|
|
57900
|
+
const risky = top2.map(([p]) => m.files.get(p)).filter((f) => !!f && f.risk >= 0.3);
|
|
57901
57901
|
if (risky.length)
|
|
57902
57902
|
lines.push(
|
|
57903
57903
|
`touches bug-prone files: ${risky.map((f) => `${f.path} (${f.risk.toFixed(2)})`).join(", ")}`
|
|
@@ -58598,10 +58598,61 @@ function formatRecallStats(title, s) {
|
|
|
58598
58598
|
return lines.join("\n");
|
|
58599
58599
|
}
|
|
58600
58600
|
|
|
58601
|
+
// src/session/routing-stats.ts
|
|
58602
|
+
init_esm_shims();
|
|
58603
|
+
var ROUTING_CATEGORY = "routing";
|
|
58604
|
+
function routingStats(events) {
|
|
58605
|
+
const byTool = {};
|
|
58606
|
+
let decisions = 0;
|
|
58607
|
+
for (const e of events) {
|
|
58608
|
+
if (e.category !== ROUTING_CATEGORY) continue;
|
|
58609
|
+
let p;
|
|
58610
|
+
try {
|
|
58611
|
+
p = JSON.parse(e.payload);
|
|
58612
|
+
} catch {
|
|
58613
|
+
continue;
|
|
58614
|
+
}
|
|
58615
|
+
if (!p.tool) continue;
|
|
58616
|
+
decisions++;
|
|
58617
|
+
let row = byTool[p.tool];
|
|
58618
|
+
if (!row) {
|
|
58619
|
+
row = { calls: 0, denied: 0, annotated: 0, targets: {}, rules: {} };
|
|
58620
|
+
byTool[p.tool] = row;
|
|
58621
|
+
}
|
|
58622
|
+
row.calls++;
|
|
58623
|
+
if (p.decision === "deny") {
|
|
58624
|
+
row.denied++;
|
|
58625
|
+
if (p.target) row.targets[p.target] = (row.targets[p.target] ?? 0) + 1;
|
|
58626
|
+
if (p.rule) row.rules[p.rule] = (row.rules[p.rule] ?? 0) + 1;
|
|
58627
|
+
} else if (p.decision === "annotate") row.annotated++;
|
|
58628
|
+
}
|
|
58629
|
+
return { decisions, byTool };
|
|
58630
|
+
}
|
|
58631
|
+
var top = (m, n = 4) => Object.entries(m).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => `${k} \xD7${v}`).join(", ");
|
|
58632
|
+
function formatRoutingStats(title, s) {
|
|
58633
|
+
if (!s.decisions) return `${title}: no routing decisions recorded yet`;
|
|
58634
|
+
const tools2 = Object.entries(s.byTool).sort((a, b) => b[1].calls - a[1].calls).map(([tool, r]) => {
|
|
58635
|
+
const pct = Math.round(100 * r.denied / r.calls);
|
|
58636
|
+
const parts = [`${tool} ${r.denied}/${r.calls} denied (${pct}%)`];
|
|
58637
|
+
if (r.annotated) parts.push(`${r.annotated} annotated`);
|
|
58638
|
+
return parts.join(", ");
|
|
58639
|
+
});
|
|
58640
|
+
const lines = [`${title}: ${s.decisions} native call(s) \u2014 ${tools2.join(" \xB7 ")}`];
|
|
58641
|
+
const rules = {};
|
|
58642
|
+
const targets = {};
|
|
58643
|
+
for (const r of Object.values(s.byTool)) {
|
|
58644
|
+
for (const [k, v] of Object.entries(r.rules)) rules[k] = (rules[k] ?? 0) + v;
|
|
58645
|
+
for (const [k, v] of Object.entries(r.targets)) targets[k] = (targets[k] ?? 0) + v;
|
|
58646
|
+
}
|
|
58647
|
+
if (Object.keys(targets).length)
|
|
58648
|
+
lines.push(` denied \u2192 ${top(targets)}; by rule: ${top(rules, 6)}`);
|
|
58649
|
+
return lines.join("\n");
|
|
58650
|
+
}
|
|
58651
|
+
|
|
58601
58652
|
// src/tools/em_stats.ts
|
|
58602
58653
|
var emStats = defineTool({
|
|
58603
58654
|
name: "em_stats",
|
|
58604
|
-
description: "Show how much context eminiar has kept out of the conversation: tokens seen vs returned per tool for this session, this project, and lifetime across all projects, plus index/graph sizes and how often proactive recall (memories, docs, history before prompts, reads and edits) fired versus stayed quiet, with the reasons.",
|
|
58655
|
+
description: "Show how much context eminiar has kept out of the conversation: tokens seen vs returned per tool for this session, this project, and lifetime across all projects, plus index/graph sizes, how often routing intercepted native Bash/Read/Grep calls (and by which rule), and how often proactive recall (memories, docs, history before prompts, reads and edits) fired versus stayed quiet, with the reasons.",
|
|
58605
58656
|
schema: {
|
|
58606
58657
|
scope: external_exports.enum(["session", "project", "lifetime", "all"]).optional().describe("default all")
|
|
58607
58658
|
},
|
|
@@ -58612,6 +58663,12 @@ var emStats = defineTool({
|
|
|
58612
58663
|
const parts = [];
|
|
58613
58664
|
if (scope === "session" || scope === "all") {
|
|
58614
58665
|
parts.push(formatStats(`session ${sessionId.slice(0, 8)}`, db.statsBySession(sessionId)));
|
|
58666
|
+
parts.push(
|
|
58667
|
+
formatRoutingStats(
|
|
58668
|
+
"routing (session)",
|
|
58669
|
+
routingStats(db.eventsBySession(sessionId, { categories: [ROUTING_CATEGORY] }))
|
|
58670
|
+
)
|
|
58671
|
+
);
|
|
58615
58672
|
parts.push(
|
|
58616
58673
|
formatRecallStats(
|
|
58617
58674
|
"recall (session)",
|
|
@@ -58621,6 +58678,12 @@ var emStats = defineTool({
|
|
|
58621
58678
|
}
|
|
58622
58679
|
if (scope === "project" || scope === "all") {
|
|
58623
58680
|
parts.push(formatStats(`project ${ctx.root}`, mergeByTool(db.statsAll())));
|
|
58681
|
+
parts.push(
|
|
58682
|
+
formatRoutingStats(
|
|
58683
|
+
"routing (project, last 2000 calls)",
|
|
58684
|
+
routingStats(db.eventsRecent(2e3, [ROUTING_CATEGORY]))
|
|
58685
|
+
)
|
|
58686
|
+
);
|
|
58624
58687
|
parts.push(
|
|
58625
58688
|
formatRecallStats(
|
|
58626
58689
|
"recall (project, last 2000 decisions)",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Local-first context, memory and knowledge-graph plugin for Claude Code. Sandboxed execution, FTS5 + vector search, session continuity, and a code graph — nothing leaves your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|