eminiar 0.8.0 → 0.9.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -1
- package/dist/cli.js +2839 -2147
- package/dist/hook.js +459 -111
- package/dist/server.js +135 -20
- package/eminiar.config.schema.json +6 -0
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -18018,6 +18018,7 @@ __export(store_exports, {
|
|
|
18018
18018
|
autoName: () => autoName,
|
|
18019
18019
|
edit: () => edit,
|
|
18020
18020
|
gitignoreHint: () => gitignoreHint,
|
|
18021
|
+
impliedFileLinks: () => impliedFileLinks,
|
|
18021
18022
|
isKind: () => isKind,
|
|
18022
18023
|
isStatus: () => isStatus,
|
|
18023
18024
|
isValidName: () => isValidName,
|
|
@@ -18116,6 +18117,20 @@ function memoryLinks(body, self) {
|
|
|
18116
18117
|
}
|
|
18117
18118
|
return [...out];
|
|
18118
18119
|
}
|
|
18120
|
+
function impliedFileLinks(root, body, skipPrefix) {
|
|
18121
|
+
const out = /* @__PURE__ */ new Set();
|
|
18122
|
+
for (const m of body.matchAll(PATH_RE)) {
|
|
18123
|
+
const p = m[1].replace(/[.,;:)]+$/, "");
|
|
18124
|
+
if (!p || p.startsWith("..") || out.has(p) || skipPrefix && p.startsWith(`${skipPrefix}/`))
|
|
18125
|
+
continue;
|
|
18126
|
+
try {
|
|
18127
|
+
if (statSync6(join12(root, p)).isFile()) out.add(p);
|
|
18128
|
+
} catch {
|
|
18129
|
+
}
|
|
18130
|
+
if (out.size >= 30) break;
|
|
18131
|
+
}
|
|
18132
|
+
return [...out].map((p) => `file:${p}`);
|
|
18133
|
+
}
|
|
18119
18134
|
function writeMemoryFile(ctx, name, meta4, body) {
|
|
18120
18135
|
const abs = absOf(ctx, name);
|
|
18121
18136
|
mkdirSync3(dirname7(abs), { recursive: true });
|
|
@@ -18138,13 +18153,14 @@ function syncGraph(db, name, rel, g) {
|
|
|
18138
18153
|
]);
|
|
18139
18154
|
const edges = [];
|
|
18140
18155
|
const seen = /* @__PURE__ */ new Set();
|
|
18141
|
-
const push = (dst, kind) => {
|
|
18156
|
+
const push = (dst, kind, confidence = 1) => {
|
|
18142
18157
|
const key = `${kind}|${dst}`;
|
|
18143
18158
|
if (dst === self || seen.has(key)) return;
|
|
18144
18159
|
seen.add(key);
|
|
18145
|
-
edges.push({ src: self, dst, kind, source: "curated", confidence
|
|
18160
|
+
edges.push({ src: self, dst, kind, source: "curated", confidence, weight: 1 });
|
|
18146
18161
|
};
|
|
18147
18162
|
for (const id of g.related) push(relatedTarget(id), "mentions");
|
|
18163
|
+
for (const id of g.implied ?? []) push(id, "mentions", 0.6);
|
|
18148
18164
|
for (const other of memoryLinks(g.body, name)) push(memoryId(other), "links");
|
|
18149
18165
|
if (g.supersedes) push(memoryId(g.supersedes.replace(/^mem(?:ory)?:/, "")), "supersedes");
|
|
18150
18166
|
if (edges.length) db.graphUpsertEdges(edges);
|
|
@@ -18205,6 +18221,9 @@ ${parsed.body.replace(/^\s*\n/, "")}`
|
|
|
18205
18221
|
hash2 = hashText(text2);
|
|
18206
18222
|
}
|
|
18207
18223
|
const body = parsed.body.trim();
|
|
18224
|
+
const implied = impliedFileLinks(ctx.root, body, memoriesRel(ctx)).filter(
|
|
18225
|
+
(id) => !related.includes(id)
|
|
18226
|
+
);
|
|
18208
18227
|
const doc = indexContent(db, {
|
|
18209
18228
|
source: "memory",
|
|
18210
18229
|
uri: `memory:${name}`,
|
|
@@ -18221,7 +18240,8 @@ tags: ${tags.join(", ")}` : ""}`,
|
|
|
18221
18240
|
kind,
|
|
18222
18241
|
content: body,
|
|
18223
18242
|
tags,
|
|
18224
|
-
|
|
18243
|
+
// the header's deliberate list first, then the files the body names
|
|
18244
|
+
nodeIds: [...related, ...implied],
|
|
18225
18245
|
sessionId: opts.sessionId,
|
|
18226
18246
|
documentId: doc.documentId,
|
|
18227
18247
|
path: rel,
|
|
@@ -18233,7 +18253,7 @@ tags: ${tags.join(", ")}` : ""}`,
|
|
|
18233
18253
|
status,
|
|
18234
18254
|
supersedes
|
|
18235
18255
|
});
|
|
18236
|
-
syncGraph(db, name, rel, { kind, tags, related, status, supersedes, body });
|
|
18256
|
+
syncGraph(db, name, rel, { kind, tags, related, implied, status, supersedes, body });
|
|
18237
18257
|
return { changed: true, row: db.memoryGetByName(name) };
|
|
18238
18258
|
}
|
|
18239
18259
|
function setMeta(ctx, db, name, patch) {
|
|
@@ -18436,7 +18456,7 @@ function gitignoreHint(ctx) {
|
|
|
18436
18456
|
const ignored = readFileSync8(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top || l === dir || l === `${top}/**` || l === `${dir}/**`);
|
|
18437
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;
|
|
18438
18458
|
}
|
|
18439
|
-
var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, hinted;
|
|
18459
|
+
var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, PATH_RE, hinted;
|
|
18440
18460
|
var init_store = __esm({
|
|
18441
18461
|
"src/memory/store.ts"() {
|
|
18442
18462
|
"use strict";
|
|
@@ -18459,6 +18479,7 @@ var init_store = __esm({
|
|
|
18459
18479
|
SEGMENT = "[a-z0-9][a-z0-9._-]*";
|
|
18460
18480
|
NAME_RE = new RegExp(`^(?:${SEGMENT}/)?${SEGMENT}$`);
|
|
18461
18481
|
MEM_LINK_RE = /(?<![A-Za-z0-9_./-])mem:([a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)?)/g;
|
|
18482
|
+
PATH_RE = /(?<![\w/.-])(?:\.\/)?((?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8})(?![\w/-])/g;
|
|
18462
18483
|
hinted = /* @__PURE__ */ new Set();
|
|
18463
18484
|
}
|
|
18464
18485
|
});
|
|
@@ -49189,7 +49210,12 @@ var ConfigSchema = external_exports.object({
|
|
|
49189
49210
|
/** Before WebSearch / WebFetch: the indexed doc pages that already answer it. */
|
|
49190
49211
|
onWeb: external_exports.boolean().default(true),
|
|
49191
49212
|
maxMemories: external_exports.number().int().min(0).max(10).default(3),
|
|
49192
|
-
maxDocs: external_exports.number().int().min(0).max(10).default(2)
|
|
49213
|
+
maxDocs: external_exports.number().int().min(0).max(10).default(2),
|
|
49214
|
+
/**
|
|
49215
|
+
* Prompts after which an item already shown in this context window may be injected
|
|
49216
|
+
* again when it matches (0 = once per context window only).
|
|
49217
|
+
*/
|
|
49218
|
+
repeatAfter: external_exports.number().int().min(0).max(1e3).default(10)
|
|
49193
49219
|
}).prefault({}),
|
|
49194
49220
|
docs: external_exports.object({
|
|
49195
49221
|
/** Project-relative manifest of documentation sources (`em_docs`); commit it. */
|
|
@@ -51937,7 +51963,7 @@ import { join as join17 } from "path";
|
|
|
51937
51963
|
|
|
51938
51964
|
// src/permissions/settings.ts
|
|
51939
51965
|
init_esm_shims();
|
|
51940
|
-
import { existsSync as existsSync10, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
|
|
51966
|
+
import { existsSync as existsSync10, readFileSync as readFileSync11, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
|
|
51941
51967
|
import { homedir as homedir2 } from "os";
|
|
51942
51968
|
import { join as join15 } from "path";
|
|
51943
51969
|
|
|
@@ -51978,15 +52004,24 @@ function matchBash(rule, command) {
|
|
|
51978
52004
|
if (rule.pattern === void 0) return true;
|
|
51979
52005
|
return bashRegex(rule.pattern).test(command);
|
|
51980
52006
|
}
|
|
52007
|
+
var readRegexCache = /* @__PURE__ */ new Map();
|
|
52008
|
+
function readRegex(pattern2) {
|
|
52009
|
+
let re = readRegexCache.get(pattern2);
|
|
52010
|
+
if (!re) {
|
|
52011
|
+
if (readRegexCache.size >= 1024) readRegexCache.clear();
|
|
52012
|
+
re = new RegExp(
|
|
52013
|
+
`^${pattern2.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
|
|
52014
|
+
);
|
|
52015
|
+
readRegexCache.set(pattern2, re);
|
|
52016
|
+
}
|
|
52017
|
+
return re;
|
|
52018
|
+
}
|
|
51981
52019
|
function matchRead(rule, absPath) {
|
|
51982
52020
|
if (rule.tool !== "Read") return false;
|
|
51983
52021
|
if (rule.pattern === void 0) return true;
|
|
51984
52022
|
let p = rule.pattern;
|
|
51985
52023
|
if (p.startsWith("~")) p = (process.env.HOME ?? "") + p.slice(1);
|
|
51986
|
-
|
|
51987
|
-
`^${p.split("**").map((seg) => seg.split("*").map(escapeRe2).join("[^/]*")).join(".*")}$`
|
|
51988
|
-
);
|
|
51989
|
-
return re.test(absPath);
|
|
52024
|
+
return readRegex(p).test(absPath);
|
|
51990
52025
|
}
|
|
51991
52026
|
|
|
51992
52027
|
// src/permissions/split.ts
|
|
@@ -52148,6 +52183,17 @@ function checkCommand(rules, command) {
|
|
|
52148
52183
|
}
|
|
52149
52184
|
return { allowed: true };
|
|
52150
52185
|
}
|
|
52186
|
+
function checkRead(rules, absPaths) {
|
|
52187
|
+
for (const kind of ["deny", "ask"]) {
|
|
52188
|
+
for (const r of rules[kind]) {
|
|
52189
|
+
if (absPaths.some((p) => matchRead(r, p))) return { allowed: false, rule: r.raw, kind };
|
|
52190
|
+
}
|
|
52191
|
+
}
|
|
52192
|
+
return { allowed: true };
|
|
52193
|
+
}
|
|
52194
|
+
function checkReadPath(rules, abs) {
|
|
52195
|
+
return checkRead(rules, [.../* @__PURE__ */ new Set([abs, realpathSync2(abs), realpathSync2.native(abs)])]);
|
|
52196
|
+
}
|
|
52151
52197
|
|
|
52152
52198
|
// src/sandbox/env.ts
|
|
52153
52199
|
init_esm_shims();
|
|
@@ -55318,7 +55364,7 @@ function categoryRank(id) {
|
|
|
55318
55364
|
var DOCUMENTS_EDGE = "documents";
|
|
55319
55365
|
var MAX_PAGES = 500;
|
|
55320
55366
|
var MAX_EDGES = 400;
|
|
55321
|
-
var
|
|
55367
|
+
var PATH_RE2 = /(?:^|[\s(`'"[<])((?:\.{1,2}\/)*(?:[\w@.-]+\/)+[\w@.-]+\.[A-Za-z0-9]{1,8})(?=$|[\s)`'"\]>:#,;!?]|\.(?:\s|$))/gm;
|
|
55322
55368
|
function manifestRel(ctx) {
|
|
55323
55369
|
return relative8(ctx.root, manifestPath(ctx)).split(sep2).join("/");
|
|
55324
55370
|
}
|
|
@@ -55338,7 +55384,7 @@ function packageCandidates(provider) {
|
|
|
55338
55384
|
}
|
|
55339
55385
|
function pathsIn(text2, pageRel, files) {
|
|
55340
55386
|
const out = /* @__PURE__ */ new Set();
|
|
55341
|
-
for (const m of text2.matchAll(
|
|
55387
|
+
for (const m of text2.matchAll(PATH_RE2)) {
|
|
55342
55388
|
const raw = m[1];
|
|
55343
55389
|
const tries = [raw.replace(/^(\.\/)+/, "")];
|
|
55344
55390
|
if (pageRel && raw.startsWith("."))
|
|
@@ -57097,16 +57143,18 @@ init_esm_shims();
|
|
|
57097
57143
|
|
|
57098
57144
|
// src/sandbox/file.ts
|
|
57099
57145
|
init_esm_shims();
|
|
57100
|
-
import { realpathSync as
|
|
57146
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
57101
57147
|
import { isAbsolute, relative as relative9, resolve as resolve7 } from "path";
|
|
57102
57148
|
function confinePath(root, target, rules) {
|
|
57103
57149
|
const abs = resolve7(root, target);
|
|
57150
|
+
refuse(checkRead(rules, [abs]), target);
|
|
57104
57151
|
let real;
|
|
57105
57152
|
try {
|
|
57106
|
-
real =
|
|
57153
|
+
real = realpathSync3(abs);
|
|
57107
57154
|
} catch {
|
|
57108
57155
|
throw new Error(`file not found: ${target}`);
|
|
57109
57156
|
}
|
|
57157
|
+
refuse(checkReadPath(rules, abs), target);
|
|
57110
57158
|
const rel = relative9(root, real);
|
|
57111
57159
|
const inside = rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
57112
57160
|
if (inside) return real;
|
|
@@ -57115,11 +57163,15 @@ function confinePath(root, target, rules) {
|
|
|
57115
57163
|
`${target} is outside the project root; add a Read(${real}) rule to permissions.allow to opt it in`
|
|
57116
57164
|
);
|
|
57117
57165
|
}
|
|
57166
|
+
function refuse(check3, target) {
|
|
57167
|
+
if (!check3.allowed)
|
|
57168
|
+
throw new Error(`refused by permission rule ${check3.rule} (${check3.kind}) on path: ${target}`);
|
|
57169
|
+
}
|
|
57118
57170
|
|
|
57119
57171
|
// src/tools/em_execute_file.ts
|
|
57120
57172
|
var emExecuteFile = defineTool({
|
|
57121
57173
|
name: "em_execute_file",
|
|
57122
|
-
description: "Process a file in the sandbox without reading it into context. Your code receives the absolute path as $EM_FILE (shell) / process.env.EM_FILE (node) / os.environ['EM_FILE'] (python); print only what you need. The file must be inside the project root unless a Read(<path>) allow rule opts it in. Omit `code` to get a quick summary (size, lines, head).",
|
|
57174
|
+
description: "Process a file in the sandbox without reading it into context. Your code receives the absolute path as $EM_FILE (shell) / process.env.EM_FILE (node) / os.environ['EM_FILE'] (python); print only what you need. The file must be inside the project root unless a Read(<path>) allow rule opts it in; a matching Read deny or ask rule refuses the path, inside the root or out. Omit `code` to get a quick summary (size, lines, head).",
|
|
57123
57175
|
schema: {
|
|
57124
57176
|
path: external_exports.string().describe("File to process, relative to the project root or absolute"),
|
|
57125
57177
|
language: LANG_ENUM.optional(),
|
|
@@ -57954,7 +58006,7 @@ function parseWhen(s) {
|
|
|
57954
58006
|
}
|
|
57955
58007
|
var emHistory = defineTool({
|
|
57956
58008
|
name: "em_history",
|
|
57957
|
-
description: "Understand the project's git history instead of
|
|
58009
|
+
description: "Understand the project's git history instead of running git log/blame/show (the hooks route those here): commits classified by kind (feat/fix/refactor/\u2026), branches with ahead/behind and merge state, and a fix-origin trace that blames each fix's removed lines back to the commit that wrote them, so changes that later needed fixing are flagged. Use `overview` to orient (it also builds the table the hooks use to flag bug-prone files), `file` before editing a file (its fixes, what broke after past changes, who knows it), `risk` for the files in the working tree (or given paths), `commit` for one change, `branch` to compare a branch with the base, `symbol` for a function's line history, `timeline`/`search` to find when and why something happened. Commit messages are also indexed for em_search (source `git`).",
|
|
57958
58010
|
schema: {
|
|
57959
58011
|
action: external_exports.enum(["overview", "timeline", "search", "file", "commit", "branch", "risk", "symbol"]).optional().describe("default overview"),
|
|
57960
58012
|
path: external_exports.string().optional().describe("file: the file; timeline: restrict to this path prefix"),
|
|
@@ -58310,7 +58362,7 @@ init_queue();
|
|
|
58310
58362
|
init_store();
|
|
58311
58363
|
var emRecall = defineTool({
|
|
58312
58364
|
name: "em_recall",
|
|
58313
|
-
description: '
|
|
58365
|
+
description: 'Call this first when a task starts, a design changes or an approach is chosen: it recalls memories (.eminiar/memories, written by em_remember) and captured session history \u2014 decisions, constraints, errors and their fixes, rejected approaches \u2014 matching a query (hybrid keyword + semantic), so nothing is re-decided or re-broken. Without a query, lists the most recent memories, onboarding first. The hooks also push matching memories on their own ("eminiar recall", "eminiar memory"); this tool searches further, with synonyms. em_memory {action:"read"} shows a whole memory.',
|
|
58314
58366
|
schema: {
|
|
58315
58367
|
query: external_exports.string().optional(),
|
|
58316
58368
|
kind: external_exports.enum(MEMORY_KINDS).optional().describe("Restrict to one memory kind"),
|
|
@@ -58436,7 +58488,7 @@ function sourceFilter(source) {
|
|
|
58436
58488
|
}
|
|
58437
58489
|
var emSearch = defineTool({
|
|
58438
58490
|
name: "em_search",
|
|
58439
|
-
description:
|
|
58491
|
+
description: 'Search everything eminiar has indexed: sandbox outputs, files, fetched pages, session memory, memories, commit messages (source git) and code (chunks named by symbol, e.g. `src/auth.ts > Session.refresh`). Reach for it before Grep or Bash when looking for where something lives, what an earlier command printed, or what a past session found: `source: "code"` answers code questions by symbol, `sort: timeline` orders by recency (use after a resume), `mode: regex` scans indexed content with a JS regular expression and returns the matching lines with context (for ad-hoc greps over files not indexed, use em_execute with rg). Pass several queries at once \u2014 the round trip is paid once. Hybrid BM25 (porter + trigram) with typo correction.',
|
|
58440
58492
|
schema: {
|
|
58441
58493
|
queries: external_exports.array(external_exports.string().min(1)).min(1).describe("One or more search queries"),
|
|
58442
58494
|
source: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional().describe(
|
|
@@ -58496,9 +58548,60 @@ var emSearch = defineTool({
|
|
|
58496
58548
|
|
|
58497
58549
|
// src/tools/em_stats.ts
|
|
58498
58550
|
init_esm_shims();
|
|
58551
|
+
|
|
58552
|
+
// src/session/recall.ts
|
|
58553
|
+
init_esm_shims();
|
|
58554
|
+
init_ids();
|
|
58555
|
+
init_model();
|
|
58556
|
+
var RECALL_CATEGORY = "recall";
|
|
58557
|
+
function recallStats(events) {
|
|
58558
|
+
const byPath = {};
|
|
58559
|
+
const gated = /* @__PURE__ */ new Map();
|
|
58560
|
+
let decisions = 0;
|
|
58561
|
+
for (const e of events) {
|
|
58562
|
+
if (e.category !== RECALL_CATEGORY) continue;
|
|
58563
|
+
let p;
|
|
58564
|
+
try {
|
|
58565
|
+
p = JSON.parse(e.payload);
|
|
58566
|
+
} catch {
|
|
58567
|
+
continue;
|
|
58568
|
+
}
|
|
58569
|
+
decisions++;
|
|
58570
|
+
const path3 = p.path ?? "file";
|
|
58571
|
+
byPath[path3] ??= { fired: 0, quiet: 0, items: 0 };
|
|
58572
|
+
const row = byPath[path3];
|
|
58573
|
+
const n = p.keys?.length ?? 0;
|
|
58574
|
+
if (n) {
|
|
58575
|
+
row.fired++;
|
|
58576
|
+
row.items += n;
|
|
58577
|
+
} else row.quiet++;
|
|
58578
|
+
for (const g of p.gated ?? []) {
|
|
58579
|
+
const why = g.replace(/^\S+\s+/, "").replace(/\d+\/\d+ terms/, "too few terms");
|
|
58580
|
+
gated.set(why, (gated.get(why) ?? 0) + 1);
|
|
58581
|
+
}
|
|
58582
|
+
}
|
|
58583
|
+
return {
|
|
58584
|
+
decisions,
|
|
58585
|
+
byPath,
|
|
58586
|
+
gated: [...gated.entries()].map(([why, n]) => ({ why, n })).sort((a, b) => b.n - a.n).slice(0, 6)
|
|
58587
|
+
};
|
|
58588
|
+
}
|
|
58589
|
+
function formatRecallStats(title, s) {
|
|
58590
|
+
if (!s.decisions) return `${title}: no recall decisions recorded yet`;
|
|
58591
|
+
const paths2 = Object.entries(s.byPath).sort((a, b) => b[1].fired + b[1].quiet - (a[1].fired + a[1].quiet)).map(([path3, r]) => {
|
|
58592
|
+
const total = r.fired + r.quiet;
|
|
58593
|
+
return `${path3} ${r.fired}/${total} fired (${Math.round(100 * r.fired / total)}%, ${r.items} items)`;
|
|
58594
|
+
});
|
|
58595
|
+
const lines = [`${title}: ${s.decisions} decision(s) \u2014 ${paths2.join(" \xB7 ")}`];
|
|
58596
|
+
if (s.gated.length)
|
|
58597
|
+
lines.push(` held back: ${s.gated.map((g) => `${g.why} \xD7${g.n}`).join(", ")}`);
|
|
58598
|
+
return lines.join("\n");
|
|
58599
|
+
}
|
|
58600
|
+
|
|
58601
|
+
// src/tools/em_stats.ts
|
|
58499
58602
|
var emStats = defineTool({
|
|
58500
58603
|
name: "em_stats",
|
|
58501
|
-
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.",
|
|
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.",
|
|
58502
58605
|
schema: {
|
|
58503
58606
|
scope: external_exports.enum(["session", "project", "lifetime", "all"]).optional().describe("default all")
|
|
58504
58607
|
},
|
|
@@ -58509,9 +58612,21 @@ var emStats = defineTool({
|
|
|
58509
58612
|
const parts = [];
|
|
58510
58613
|
if (scope === "session" || scope === "all") {
|
|
58511
58614
|
parts.push(formatStats(`session ${sessionId.slice(0, 8)}`, db.statsBySession(sessionId)));
|
|
58615
|
+
parts.push(
|
|
58616
|
+
formatRecallStats(
|
|
58617
|
+
"recall (session)",
|
|
58618
|
+
recallStats(db.eventsBySession(sessionId, { categories: [RECALL_CATEGORY] }))
|
|
58619
|
+
)
|
|
58620
|
+
);
|
|
58512
58621
|
}
|
|
58513
58622
|
if (scope === "project" || scope === "all") {
|
|
58514
58623
|
parts.push(formatStats(`project ${ctx.root}`, mergeByTool(db.statsAll())));
|
|
58624
|
+
parts.push(
|
|
58625
|
+
formatRecallStats(
|
|
58626
|
+
"recall (project, last 2000 decisions)",
|
|
58627
|
+
recallStats(db.eventsRecent(2e3, [RECALL_CATEGORY]))
|
|
58628
|
+
)
|
|
58629
|
+
);
|
|
58515
58630
|
}
|
|
58516
58631
|
if (scope === "lifetime" || scope === "all") {
|
|
58517
58632
|
const g = globalDb();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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",
|