eminiar 0.10.0 → 0.10.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 +214 -50
- package/dist/hook.js +161 -41
- package/dist/server.js +247 -57
- package/package.json +1 -1
- package/skills/em-docs/SKILL.md +3 -1
package/dist/server.js
CHANGED
|
@@ -13518,7 +13518,7 @@ function chunkMarkdown(text2, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
13518
13518
|
if (/^\s*(```|~~~)/.test(line)) inFence = !inFence;
|
|
13519
13519
|
const m = !inFence && /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
13520
13520
|
if (m) {
|
|
13521
|
-
|
|
13521
|
+
sections.push(current);
|
|
13522
13522
|
const level = m[1].length;
|
|
13523
13523
|
while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
|
|
13524
13524
|
stack.push({ level, text: m[2] });
|
|
@@ -13527,14 +13527,23 @@ function chunkMarkdown(text2, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
13527
13527
|
}
|
|
13528
13528
|
current.lines.push(line);
|
|
13529
13529
|
});
|
|
13530
|
-
|
|
13530
|
+
sections.push(current);
|
|
13531
13531
|
const out = [];
|
|
13532
|
-
|
|
13532
|
+
sections.forEach((s, k) => {
|
|
13533
13533
|
const title = s.path.length ? `${baseTitle} > ${s.path.join(" > ")}` : baseTitle;
|
|
13534
13534
|
const body = s.lines.join("\n");
|
|
13535
|
+
if (!body.trim()) {
|
|
13536
|
+
const next = sections[k + 1];
|
|
13537
|
+
const isParent = next !== void 0 && next.path.length > s.path.length && s.path.every((p, j) => next.path[j] === p);
|
|
13538
|
+
if (s.path.length && !isParent) {
|
|
13539
|
+
const heading = s.path[s.path.length - 1];
|
|
13540
|
+
out.push({ title, body: heading, startLine: s.startLine, endLine: s.startLine });
|
|
13541
|
+
}
|
|
13542
|
+
return;
|
|
13543
|
+
}
|
|
13535
13544
|
if (body.length <= opts.maxChars) {
|
|
13536
13545
|
out.push({ title, body, startLine: s.startLine, endLine: s.startLine + s.lines.length - 1 });
|
|
13537
|
-
|
|
13546
|
+
return;
|
|
13538
13547
|
}
|
|
13539
13548
|
let fence2 = false;
|
|
13540
13549
|
const fenceState = s.lines.map((l) => {
|
|
@@ -13550,7 +13559,7 @@ function chunkMarkdown(text2, baseTitle, opts = DEFAULT_CHUNKER) {
|
|
|
13550
13559
|
s.startLine - 1
|
|
13551
13560
|
)
|
|
13552
13561
|
);
|
|
13553
|
-
}
|
|
13562
|
+
});
|
|
13554
13563
|
return out;
|
|
13555
13564
|
}
|
|
13556
13565
|
var init_markdown = __esm({
|
|
@@ -16953,7 +16962,20 @@ import { readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
|
16953
16962
|
import { relative as relative5, resolve as resolve6 } from "path";
|
|
16954
16963
|
function indexContent(db, input2) {
|
|
16955
16964
|
const kind = input2.kind ?? (input2.contentType === "code" ? "code" : input2.contentType === "json" ? "json" : void 0);
|
|
16956
|
-
const
|
|
16965
|
+
const cut = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
|
|
16966
|
+
const k = cut.kind;
|
|
16967
|
+
const fallback = cut.chunks.length === 0 && input2.content.trim().length > 0;
|
|
16968
|
+
const chunks = fallback ? finalize2(
|
|
16969
|
+
[
|
|
16970
|
+
{
|
|
16971
|
+
title: input2.title,
|
|
16972
|
+
body: input2.content,
|
|
16973
|
+
startLine: 1,
|
|
16974
|
+
endLine: input2.content.split("\n").length
|
|
16975
|
+
}
|
|
16976
|
+
],
|
|
16977
|
+
kindToContentType(k)
|
|
16978
|
+
) : cut.chunks;
|
|
16957
16979
|
const contentHash = hashText(input2.content);
|
|
16958
16980
|
const existing = input2.uri ? db.findDocumentByUri(input2.uri) : void 0;
|
|
16959
16981
|
if (existing && existing.content_hash === contentHash) {
|
|
@@ -16963,7 +16985,8 @@ function indexContent(db, input2) {
|
|
|
16963
16985
|
created: false,
|
|
16964
16986
|
chunks: chunks.length,
|
|
16965
16987
|
chars: input2.content.length,
|
|
16966
|
-
kind: k
|
|
16988
|
+
kind: k,
|
|
16989
|
+
fallback
|
|
16967
16990
|
};
|
|
16968
16991
|
}
|
|
16969
16992
|
db.deleteDocument(existing.id);
|
|
@@ -16980,11 +17003,25 @@ function indexContent(db, input2) {
|
|
|
16980
17003
|
},
|
|
16981
17004
|
chunks
|
|
16982
17005
|
);
|
|
16983
|
-
return {
|
|
17006
|
+
return {
|
|
17007
|
+
documentId: id,
|
|
17008
|
+
created,
|
|
17009
|
+
chunks: chunks.length,
|
|
17010
|
+
chars: input2.content.length,
|
|
17011
|
+
kind: k,
|
|
17012
|
+
fallback
|
|
17013
|
+
};
|
|
16984
17014
|
}
|
|
16985
17015
|
function indexPath(db, root, target, opts = {}) {
|
|
16986
17016
|
const abs = resolve6(root, target);
|
|
16987
|
-
const summary = {
|
|
17017
|
+
const summary = {
|
|
17018
|
+
files: 0,
|
|
17019
|
+
indexed: 0,
|
|
17020
|
+
unchanged: 0,
|
|
17021
|
+
skipped: [],
|
|
17022
|
+
chunks: 0,
|
|
17023
|
+
fallbacks: 0
|
|
17024
|
+
};
|
|
16988
17025
|
const st = statSync4(abs);
|
|
16989
17026
|
const files = st.isDirectory() ? import_fast_glob.default.sync(opts.globs ?? ["**/*.md"], {
|
|
16990
17027
|
cwd: abs,
|
|
@@ -17019,6 +17056,7 @@ function indexPath(db, root, target, opts = {}) {
|
|
|
17019
17056
|
if (r.created) summary.indexed++;
|
|
17020
17057
|
else summary.unchanged++;
|
|
17021
17058
|
summary.chunks += r.chunks;
|
|
17059
|
+
if (r.fallback) summary.fallbacks++;
|
|
17022
17060
|
} catch (err) {
|
|
17023
17061
|
summary.skipped.push({ path: rel, reason: err.message });
|
|
17024
17062
|
}
|
|
@@ -49547,6 +49585,10 @@ function topTermsOf(bodies, limit) {
|
|
|
49547
49585
|
|
|
49548
49586
|
// src/storage/sqlite/schema.ts
|
|
49549
49587
|
init_esm_shims();
|
|
49588
|
+
function hasColumn(db, table, column) {
|
|
49589
|
+
const cols = db.pragma(`table_info(${table})`);
|
|
49590
|
+
return cols.some((c) => c.name === column);
|
|
49591
|
+
}
|
|
49550
49592
|
var migrations = [
|
|
49551
49593
|
{
|
|
49552
49594
|
version: 1,
|
|
@@ -49881,7 +49923,6 @@ CREATE TABLE IF NOT EXISTS transcript_files (
|
|
|
49881
49923
|
offset INTEGER NOT NULL DEFAULT 0,
|
|
49882
49924
|
size INTEGER NOT NULL DEFAULT 0,
|
|
49883
49925
|
mtime INTEGER NOT NULL DEFAULT 0,
|
|
49884
|
-
head_hash TEXT,
|
|
49885
49926
|
ingested_at INTEGER NOT NULL
|
|
49886
49927
|
);
|
|
49887
49928
|
|
|
@@ -49909,6 +49950,16 @@ CREATE TABLE IF NOT EXISTS statusline_snapshots (
|
|
|
49909
49950
|
CREATE INDEX IF NOT EXISTS statusline_snapshots_session_ts ON statusline_snapshots(session_id, ts);
|
|
49910
49951
|
CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
|
|
49911
49952
|
`
|
|
49953
|
+
},
|
|
49954
|
+
{
|
|
49955
|
+
// transcript_files.head_hash (fingerprint of the file head, so a rewrite with the same
|
|
49956
|
+
// size is noticed) was first added to v5 after v5 had already run on real databases, so
|
|
49957
|
+
// version-5 databases exist both with and without the column. Add it only when missing.
|
|
49958
|
+
version: 6,
|
|
49959
|
+
up(db) {
|
|
49960
|
+
if (!hasColumn(db, "transcript_files", "head_hash"))
|
|
49961
|
+
db.exec("ALTER TABLE transcript_files ADD COLUMN head_hash TEXT");
|
|
49962
|
+
}
|
|
49912
49963
|
}
|
|
49913
49964
|
];
|
|
49914
49965
|
function vecTableSql(dims) {
|
|
@@ -50020,7 +50071,8 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
50020
50071
|
if (pending2.length) {
|
|
50021
50072
|
this.transaction(() => {
|
|
50022
50073
|
for (const m of pending2) {
|
|
50023
|
-
this.db.exec(m.sql);
|
|
50074
|
+
if (m.sql) this.db.exec(m.sql);
|
|
50075
|
+
m.up?.(this.db);
|
|
50024
50076
|
this.metaSet("schema_version", String(m.version));
|
|
50025
50077
|
}
|
|
50026
50078
|
});
|
|
@@ -50239,18 +50291,42 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
50239
50291
|
}
|
|
50240
50292
|
vecSearch(embedding, k, filters) {
|
|
50241
50293
|
if (!this.vectors) return [];
|
|
50242
|
-
const raw = this.db.prepare(
|
|
50243
|
-
"SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ORDER BY distance"
|
|
50244
|
-
).all(embedding, BigInt(filters ? k * 4 : k));
|
|
50245
|
-
if (!filters || !raw.length) return raw.map((r) => ({ chunkId: r.id, distance: r.distance }));
|
|
50246
50294
|
const { where, params } = filterSql(filters);
|
|
50247
|
-
|
|
50248
|
-
|
|
50249
|
-
|
|
50250
|
-
|
|
50251
|
-
|
|
50252
|
-
|
|
50253
|
-
|
|
50295
|
+
if (!where) return this.knn(embedding, k);
|
|
50296
|
+
try {
|
|
50297
|
+
return this.knn(
|
|
50298
|
+
embedding,
|
|
50299
|
+
k,
|
|
50300
|
+
`AND chunk_id IN (SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id WHERE 1=1${where})`,
|
|
50301
|
+
params
|
|
50302
|
+
);
|
|
50303
|
+
} catch {
|
|
50304
|
+
return this.vecSearchWidening(embedding, k, where, params);
|
|
50305
|
+
}
|
|
50306
|
+
}
|
|
50307
|
+
knn(embedding, k, extra = "", params = []) {
|
|
50308
|
+
return this.db.prepare(
|
|
50309
|
+
`SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ${extra} ORDER BY distance`
|
|
50310
|
+
).all(embedding, BigInt(k), ...params).map((r) => ({ chunkId: r.id, distance: r.distance }));
|
|
50311
|
+
}
|
|
50312
|
+
/** Fallback filtered KNN: fetch k*4, k*16, ... nearest until k pass the filter or all were seen. */
|
|
50313
|
+
vecSearchWidening(embedding, k, where, params) {
|
|
50314
|
+
const total = this.vecCount();
|
|
50315
|
+
for (let fetch2 = k * 4; ; fetch2 *= 4) {
|
|
50316
|
+
const raw = this.knn(embedding, Math.min(fetch2, total));
|
|
50317
|
+
if (!raw.length) return [];
|
|
50318
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
50319
|
+
for (let i = 0; i < raw.length; i += 400) {
|
|
50320
|
+
const ids = raw.slice(i, i + 400).map((r) => r.chunkId);
|
|
50321
|
+
for (const r of this.db.prepare(
|
|
50322
|
+
`SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
|
|
50323
|
+
WHERE c.id IN (${ids.map(() => "?").join(",")})${where}`
|
|
50324
|
+
).all(...ids, ...params))
|
|
50325
|
+
allowed.add(r.id);
|
|
50326
|
+
}
|
|
50327
|
+
const hits = raw.filter((r) => allowed.has(r.chunkId)).slice(0, k);
|
|
50328
|
+
if (hits.length >= k || fetch2 >= total) return hits;
|
|
50329
|
+
}
|
|
50254
50330
|
}
|
|
50255
50331
|
unembeddedChunkIds(limit) {
|
|
50256
50332
|
return this.db.prepare("SELECT id FROM chunks WHERE embedded = 0 ORDER BY id LIMIT ?").all(limit).map((r) => r.id);
|
|
@@ -50529,6 +50605,26 @@ var SqliteAdapter = class _SqliteAdapter {
|
|
|
50529
50605
|
}
|
|
50530
50606
|
return out;
|
|
50531
50607
|
}
|
|
50608
|
+
graphDependentFileCounts(paths2, kinds) {
|
|
50609
|
+
const out = /* @__PURE__ */ new Map();
|
|
50610
|
+
if (!paths2.length || !kinds.length) return out;
|
|
50611
|
+
const kph = kinds.map(() => "?").join(",");
|
|
50612
|
+
for (let i = 0; i < paths2.length; i += 400) {
|
|
50613
|
+
const batch = paths2.slice(i, i + 400);
|
|
50614
|
+
const pph = batch.map(() => "?").join(",");
|
|
50615
|
+
for (const r of this.db.prepare(
|
|
50616
|
+
`SELECT d.path p, COUNT(DISTINCT s.path) n
|
|
50617
|
+
FROM graph_nodes d
|
|
50618
|
+
JOIN graph_edges e ON e.dst = d.id AND e.kind IN (${kph})
|
|
50619
|
+
JOIN graph_nodes s ON s.id = e.src
|
|
50620
|
+
WHERE d.path IN (${pph}) AND s.path IS NOT NULL AND s.path <> d.path
|
|
50621
|
+
GROUP BY d.path`
|
|
50622
|
+
).all(...kinds, ...batch)) {
|
|
50623
|
+
out.set(r.p, r.n);
|
|
50624
|
+
}
|
|
50625
|
+
}
|
|
50626
|
+
return out;
|
|
50627
|
+
}
|
|
50532
50628
|
edgesWhere(col, ids, kinds) {
|
|
50533
50629
|
if (!ids.length) return [];
|
|
50534
50630
|
const out = [];
|
|
@@ -52664,6 +52760,25 @@ function findOnPath(bin) {
|
|
|
52664
52760
|
}
|
|
52665
52761
|
return void 0;
|
|
52666
52762
|
}
|
|
52763
|
+
var REQUIRE_PREAMBLE = 'import { createRequire } from "node:module"; const require = createRequire(process.env.EMINIAR_PROJECT_ROOT ? process.env.EMINIAR_PROJECT_ROOT + "/package.json" : import.meta.url);';
|
|
52764
|
+
function requirePreamble(code) {
|
|
52765
|
+
if (!/\brequire\s*\(/.test(code)) return void 0;
|
|
52766
|
+
if (/\b(?:const|let|var|function)\s+require\b/.test(code) || /\bcreateRequire\b/.test(code)) {
|
|
52767
|
+
return void 0;
|
|
52768
|
+
}
|
|
52769
|
+
return `${REQUIRE_PREAMBLE}
|
|
52770
|
+
`;
|
|
52771
|
+
}
|
|
52772
|
+
function prepareCode(lang, code) {
|
|
52773
|
+
const pre = lang.preamble?.(code);
|
|
52774
|
+
if (!pre) return code;
|
|
52775
|
+
if (code.startsWith("#!")) {
|
|
52776
|
+
const nl = code.indexOf("\n");
|
|
52777
|
+
return nl === -1 ? `${code}
|
|
52778
|
+
${pre}` : code.slice(0, nl + 1) + pre + code.slice(nl + 1);
|
|
52779
|
+
}
|
|
52780
|
+
return pre + code;
|
|
52781
|
+
}
|
|
52667
52782
|
function preferredShell() {
|
|
52668
52783
|
const sh = process.env.SHELL ?? "";
|
|
52669
52784
|
if (/\/(zsh|bash)$/.test(sh) && existsSync11(sh)) return sh;
|
|
@@ -52696,14 +52811,16 @@ var LANGUAGES = [
|
|
|
52696
52811
|
aliases: ["js", "node", "mjs"],
|
|
52697
52812
|
ext: ".mjs",
|
|
52698
52813
|
detect: () => process.execPath,
|
|
52699
|
-
argv: (bin, file3) => [bin, "--no-warnings", file3]
|
|
52814
|
+
argv: (bin, file3) => [bin, "--no-warnings", file3],
|
|
52815
|
+
preamble: requirePreamble
|
|
52700
52816
|
},
|
|
52701
52817
|
{
|
|
52702
52818
|
id: "typescript",
|
|
52703
52819
|
aliases: ["ts"],
|
|
52704
52820
|
ext: ".ts",
|
|
52705
52821
|
detect: () => process.execPath,
|
|
52706
|
-
argv: (bin, file3) => [bin, "--no-warnings", tsxCli(), file3]
|
|
52822
|
+
argv: (bin, file3) => [bin, "--no-warnings", tsxCli(), file3],
|
|
52823
|
+
preamble: requirePreamble
|
|
52707
52824
|
},
|
|
52708
52825
|
{
|
|
52709
52826
|
id: "python",
|
|
@@ -52889,7 +53006,7 @@ async function execute(ctx, input2) {
|
|
|
52889
53006
|
);
|
|
52890
53007
|
}
|
|
52891
53008
|
}
|
|
52892
|
-
const file3 = writeScratch(input2.code, lang.ext);
|
|
53009
|
+
const file3 = writeScratch(prepareCode(lang, input2.code), lang.ext);
|
|
52893
53010
|
const env = buildEnv(ctx.config.sandbox.envAllow, {
|
|
52894
53011
|
EMINIAR_PROJECT_ROOT: ctx.root,
|
|
52895
53012
|
NODE_PATH: [join17(ctx.root, "node_modules"), process.env.NODE_PATH].filter(Boolean).join(":"),
|
|
@@ -53339,10 +53456,22 @@ searchable terms: ${terms.join(", ")}`);
|
|
|
53339
53456
|
}
|
|
53340
53457
|
|
|
53341
53458
|
// src/tools/em_batch_execute.ts
|
|
53342
|
-
var
|
|
53459
|
+
var INLINE_LIMIT_WITH_QUERIES = 1024;
|
|
53460
|
+
var DEFAULT_MAX_TOKENS = 2e3;
|
|
53461
|
+
function chooseInline(lengths, perOutputCap, budgetChars) {
|
|
53462
|
+
const candidates2 = lengths.map((len, i) => ({ len, i })).filter((c) => c.len <= perOutputCap).sort((a, b) => a.len - b.len);
|
|
53463
|
+
const inline = /* @__PURE__ */ new Set();
|
|
53464
|
+
let remaining = budgetChars;
|
|
53465
|
+
for (const c of candidates2) {
|
|
53466
|
+
if (c.len > remaining) break;
|
|
53467
|
+
inline.add(c.i);
|
|
53468
|
+
remaining -= c.len;
|
|
53469
|
+
}
|
|
53470
|
+
return inline;
|
|
53471
|
+
}
|
|
53343
53472
|
var emBatchExecute = defineTool({
|
|
53344
53473
|
name: "em_batch_execute",
|
|
53345
|
-
description: "Run several labelled commands in parallel, index every output under its label, and answer `queries` against all of them in the same round trip. Ideal for research: git status + recent commits + test output + dependency list in one call.
|
|
53474
|
+
description: "Run several labelled commands in parallel, index every output under its label, and answer `queries` against all of them in the same round trip. Ideal for research: git status + recent commits + test output + dependency list in one call. Without `queries`/`intent`, each output up to the index threshold (5 KB) is returned inline, within `max_tokens` overall; with them, only outputs up to 1 KB are inlined and the query hits carry the rest. Every output is indexed; an elided one is fetched with em_search {document_ids: [N]}.",
|
|
53346
53475
|
schema: {
|
|
53347
53476
|
commands: external_exports.array(
|
|
53348
53477
|
external_exports.object({
|
|
@@ -53363,6 +53492,9 @@ var emBatchExecute = defineTool({
|
|
|
53363
53492
|
const concurrency = args.concurrency ?? 4;
|
|
53364
53493
|
const results = [];
|
|
53365
53494
|
let rawChars = 0;
|
|
53495
|
+
const queries = [...args.queries ?? []];
|
|
53496
|
+
if (args.intent && !queries.includes(args.intent)) queries.unshift(args.intent);
|
|
53497
|
+
const maxTokens = args.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
53366
53498
|
const queue = [...args.commands];
|
|
53367
53499
|
const worker = async () => {
|
|
53368
53500
|
for (; ; ) {
|
|
@@ -53384,7 +53516,7 @@ var emBatchExecute = defineTool({
|
|
|
53384
53516
|
results.push({
|
|
53385
53517
|
label: cmd.label,
|
|
53386
53518
|
header: describeRun(r),
|
|
53387
|
-
|
|
53519
|
+
stdout: r.stdout,
|
|
53388
53520
|
docId: p.documentId,
|
|
53389
53521
|
stderr: r.exitCode !== 0 ? r.stderr.trim().slice(-800) : void 0
|
|
53390
53522
|
});
|
|
@@ -53396,15 +53528,21 @@ var emBatchExecute = defineTool({
|
|
|
53396
53528
|
await Promise.all(Array.from({ length: Math.min(concurrency, queue.length) }, worker));
|
|
53397
53529
|
const order = new Map(args.commands.map((c, i) => [c.label, i]));
|
|
53398
53530
|
results.sort((a, b) => (order.get(a.label) ?? 0) - (order.get(b.label) ?? 0));
|
|
53531
|
+
const perOutputCap = queries.length ? INLINE_LIMIT_WITH_QUERIES : ctx.config.sandbox.indexThreshold;
|
|
53532
|
+
const inline = chooseInline(
|
|
53533
|
+
results.map((r) => r.stdout?.length ?? Number.POSITIVE_INFINITY),
|
|
53534
|
+
perOutputCap,
|
|
53535
|
+
maxTokens * 4
|
|
53536
|
+
);
|
|
53399
53537
|
const lines = [];
|
|
53400
|
-
|
|
53401
|
-
|
|
53402
|
-
|
|
53538
|
+
results.forEach((r, i) => {
|
|
53539
|
+
const shown = inline.has(i) && r.stdout !== void 0;
|
|
53540
|
+
const hint = r.docId && !shown ? ` \xB7 em_search {document_ids: [${r.docId}]} for the output` : "";
|
|
53541
|
+
lines.push(`## ${r.label} \u2014 ${r.header}${r.docId ? ` \xB7 doc ${r.docId}` : ""}${hint}`);
|
|
53542
|
+
if (shown) lines.push(r.stdout.trimEnd());
|
|
53403
53543
|
if (r.stderr) lines.push(`stderr: ${r.stderr}`);
|
|
53404
53544
|
lines.push("");
|
|
53405
|
-
}
|
|
53406
|
-
const queries = [...args.queries ?? []];
|
|
53407
|
-
if (args.intent && !queries.includes(args.intent)) queries.unshift(args.intent);
|
|
53545
|
+
});
|
|
53408
53546
|
const docIds = results.map((r) => r.docId).filter((d) => d !== void 0);
|
|
53409
53547
|
if (queries.length && docIds.length) {
|
|
53410
53548
|
const responses = [];
|
|
@@ -53414,7 +53552,7 @@ var emBatchExecute = defineTool({
|
|
|
53414
53552
|
);
|
|
53415
53553
|
}
|
|
53416
53554
|
lines.push("# query hits");
|
|
53417
|
-
lines.push(formatResults(responses,
|
|
53555
|
+
lines.push(formatResults(responses, maxTokens));
|
|
53418
53556
|
}
|
|
53419
53557
|
const out = lines.join("\n");
|
|
53420
53558
|
recordStat(ctx, "em_batch_execute", {
|
|
@@ -54476,6 +54614,9 @@ var NAME_RE2 = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
|
54476
54614
|
function docsTag(name) {
|
|
54477
54615
|
return `${DOCS_PREFIX}${name}`;
|
|
54478
54616
|
}
|
|
54617
|
+
function nameFromTag(tag) {
|
|
54618
|
+
return tag.startsWith(DOCS_PREFIX) ? tag.slice(DOCS_PREFIX.length) : void 0;
|
|
54619
|
+
}
|
|
54479
54620
|
function isValidName2(name) {
|
|
54480
54621
|
return NAME_RE2.test(name);
|
|
54481
54622
|
}
|
|
@@ -56927,7 +57068,7 @@ function removeDocSource(ctx, db, name) {
|
|
|
56927
57068
|
const removed = removeSource(ctx, name);
|
|
56928
57069
|
const documents = db.deleteDocumentsBySource(docsTag(name));
|
|
56929
57070
|
clearSyncState(db, name);
|
|
56930
|
-
if (removed) syncDocsGraphQuietly(ctx, db);
|
|
57071
|
+
if (removed || documents) syncDocsGraphQuietly(ctx, db);
|
|
56931
57072
|
return { removed, documents };
|
|
56932
57073
|
}
|
|
56933
57074
|
function formatAdd(o) {
|
|
@@ -56998,6 +57139,26 @@ function ago(ts) {
|
|
|
56998
57139
|
if (s < 172800) return `${Math.round(s / 3600)}h ago`;
|
|
56999
57140
|
return `${Math.round(s / 86400)}d ago`;
|
|
57000
57141
|
}
|
|
57142
|
+
function orphanedDocSources(db, registered, stats = db.documentSourceStats(DOCS_PREFIX)) {
|
|
57143
|
+
const known = new Set(registered);
|
|
57144
|
+
const out = [];
|
|
57145
|
+
for (const s of stats) {
|
|
57146
|
+
const name = nameFromTag(s.source);
|
|
57147
|
+
if (name === void 0 || known.has(name)) continue;
|
|
57148
|
+
out.push({ name, documents: s.documents, chunks: s.chunks, latest: s.latest });
|
|
57149
|
+
}
|
|
57150
|
+
return out;
|
|
57151
|
+
}
|
|
57152
|
+
function formatOrphans(orphans2) {
|
|
57153
|
+
const names = orphans2.map((o) => o.name);
|
|
57154
|
+
return [
|
|
57155
|
+
`# Orphaned in the index (${orphans2.length}; pages without a manifest entry)`,
|
|
57156
|
+
...orphans2.map(
|
|
57157
|
+
(o) => `- ${o.name} \xB7 ${o.documents} doc(s) \xB7 ${o.chunks} chunks \xB7 indexed ${ago(o.latest)}`
|
|
57158
|
+
),
|
|
57159
|
+
`em_docs {action:"remove", names:${JSON.stringify(names)}} drops them; {action:"add", pick:"recommended"} or {action:"add", name, url|path} re-registers.`
|
|
57160
|
+
];
|
|
57161
|
+
}
|
|
57001
57162
|
function formatDiscovery(d, max = 40) {
|
|
57002
57163
|
const lines = [];
|
|
57003
57164
|
lines.push(
|
|
@@ -57145,19 +57306,30 @@ var emDocs = defineTool({
|
|
|
57145
57306
|
case "remove": {
|
|
57146
57307
|
const names = [...args.name ? [args.name] : [], ...args.names ?? []];
|
|
57147
57308
|
if (!names.length) return errorResult("remove needs `name` or `names`");
|
|
57309
|
+
let unknown2 = 0;
|
|
57148
57310
|
const lines = names.map((n) => {
|
|
57149
57311
|
const r = removeDocSource(ctx, db, n);
|
|
57150
|
-
|
|
57312
|
+
if (r.removed) return `removed ${n} (${r.documents} document(s) dropped)`;
|
|
57313
|
+
if (r.documents) return `removed ${n} (orphaned; ${r.documents} document(s) dropped)`;
|
|
57314
|
+
unknown2++;
|
|
57315
|
+
return `${n}: not registered and not in the index; em_docs {action:"list"} shows both`;
|
|
57151
57316
|
});
|
|
57152
|
-
|
|
57317
|
+
const out = lines.join("\n");
|
|
57318
|
+
return unknown2 === names.length ? errorResult(out) : done(out);
|
|
57153
57319
|
}
|
|
57154
57320
|
case "list": {
|
|
57155
57321
|
const m = readManifest(ctx);
|
|
57156
|
-
|
|
57157
|
-
|
|
57158
|
-
|
|
57159
|
-
)
|
|
57160
|
-
|
|
57322
|
+
const sourceStats = db.documentSourceStats(DOCS_PREFIX);
|
|
57323
|
+
const orphans2 = orphanedDocSources(
|
|
57324
|
+
db,
|
|
57325
|
+
m.sources.map((s) => s.name),
|
|
57326
|
+
sourceStats
|
|
57327
|
+
);
|
|
57328
|
+
if (!m.sources.length) {
|
|
57329
|
+
const none = `no documentation sources registered (${manifestPath(ctx)}). em_docs {action:"discover"} finds local docs and the libraries, services and APIs this project uses.`;
|
|
57330
|
+
return done(orphans2.length ? [none, "", ...formatOrphans(orphans2)].join("\n") : none);
|
|
57331
|
+
}
|
|
57332
|
+
const stats = new Map(sourceStats.map((s) => [s.source, s]));
|
|
57161
57333
|
const catalog = mergedCatalog(ctx.config.docs.catalog);
|
|
57162
57334
|
const categorised = m.sources.map((s) => ({
|
|
57163
57335
|
s,
|
|
@@ -57188,9 +57360,10 @@ var emDocs = defineTool({
|
|
|
57188
57360
|
`- ${s.name} [${s.kind}]${s.provider ? ` \u2190 ${s.provider}` : ""}${s.tags?.length ? ` (${s.tags.join(", ")})` : ""} \xB7 ${st?.documents ?? 0} doc(s) \xB7 ${st?.chunks ?? 0} chunks \xB7 ${state} \xB7 ${where}`
|
|
57189
57361
|
);
|
|
57190
57362
|
}
|
|
57363
|
+
if (orphans2.length) lines.push("", ...formatOrphans(orphans2));
|
|
57191
57364
|
lines.push(
|
|
57192
57365
|
"",
|
|
57193
|
-
`${m.sources.length} source(s); em_docs {action:"lookup", query} searches them all, {action:"sync"} refreshes.`
|
|
57366
|
+
`${m.sources.length} source(s)${orphans2.length ? ` + ${orphans2.length} orphaned` : ""}; em_docs {action:"lookup", query} searches them all, {action:"sync"} refreshes.`
|
|
57194
57367
|
);
|
|
57195
57368
|
return done(lines.join("\n"));
|
|
57196
57369
|
}
|
|
@@ -57236,10 +57409,18 @@ ${r.urls.map((u) => ` ${u}`).join("\n")}
|
|
|
57236
57409
|
raw += r.hits.reduce((n, h) => n + h.chunk.body.length, 0);
|
|
57237
57410
|
responses.push(r);
|
|
57238
57411
|
}
|
|
57239
|
-
|
|
57240
|
-
|
|
57241
|
-
|
|
57412
|
+
let out = formatResults(responses, args.max_tokens ?? ctx.config.search.defaultMaxTokens);
|
|
57413
|
+
const hitSources = new Set(
|
|
57414
|
+
responses.flatMap((r) => r.hits.map((h) => h.chunk.document.source))
|
|
57242
57415
|
);
|
|
57416
|
+
const orphaned = orphanedDocSources(
|
|
57417
|
+
db,
|
|
57418
|
+
m.sources.map((s) => s.name)
|
|
57419
|
+
).filter((o) => hitSources.has(docsTag(o.name)));
|
|
57420
|
+
if (orphaned.length)
|
|
57421
|
+
out += `
|
|
57422
|
+
|
|
57423
|
+
note: ${orphaned.map((o) => o.name).join(", ")} ${orphaned.length === 1 ? "is" : "are"} not in the manifest (orphaned pages); em_docs {action:"list"} explains`;
|
|
57243
57424
|
recordStat(ctx, "em_docs", {
|
|
57244
57425
|
rawChars: raw,
|
|
57245
57426
|
returnedChars: out.length,
|
|
@@ -57470,7 +57651,7 @@ var LANG_ENUM = external_exports.string().describe(
|
|
|
57470
57651
|
);
|
|
57471
57652
|
var emExecute = defineTool({
|
|
57472
57653
|
name: "em_execute",
|
|
57473
|
-
description: "Run code in a sandbox inside the project root and return only stdout. Output over the index threshold (5 KB) is indexed; pass `intent` to get back just the matching sections plus a vocabulary of searchable terms. Use this instead of Bash for anything that prints a lot: git log/diff, test runs, dependency trees, logs, API calls. Shell snippets honour Claude Code's Bash deny rules.",
|
|
57654
|
+
description: "Run code in a sandbox inside the project root and return only stdout. Output over the index threshold (5 KB) is indexed; pass `intent` to get back just the matching sections plus a vocabulary of searchable terms. Use this instead of Bash for anything that prints a lot: git log/diff, test runs, dependency trees, logs, API calls. Shell snippets honour Claude Code's Bash deny rules. javascript and typescript run as ES modules; `import` and `require` both work.",
|
|
57474
57655
|
schema: {
|
|
57475
57656
|
language: LANG_ENUM,
|
|
57476
57657
|
code: external_exports.string().min(1).describe("Program or shell script to run"),
|
|
@@ -57552,7 +57733,7 @@ function refuse(check3, target) {
|
|
|
57552
57733
|
// src/tools/em_execute_file.ts
|
|
57553
57734
|
var emExecuteFile = defineTool({
|
|
57554
57735
|
name: "em_execute_file",
|
|
57555
|
-
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).",
|
|
57736
|
+
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). javascript and typescript run as ES modules; `import` and `require` both work.",
|
|
57556
57737
|
schema: {
|
|
57557
57738
|
path: external_exports.string().describe("File to process, relative to the project root or absolute"),
|
|
57558
57739
|
language: LANG_ENUM.optional(),
|
|
@@ -58528,7 +58709,7 @@ var emIndex = defineTool({
|
|
|
58528
58709
|
});
|
|
58529
58710
|
kickEmbedding(ctx, db);
|
|
58530
58711
|
return text(
|
|
58531
|
-
`${r.created ? "indexed" : "unchanged"} "${args.title}" as doc ${r.documentId}: ${r.chunks} chunks, ${r.chars} chars (${r.kind})`
|
|
58712
|
+
`${r.created ? "indexed" : "unchanged"} "${args.title}" as doc ${r.documentId}: ${r.chunks} chunks, ${r.chars} chars (${r.kind})${r.fallback ? " (no sections found; indexed as one chunk)" : ""}`
|
|
58532
58713
|
);
|
|
58533
58714
|
}
|
|
58534
58715
|
if (!args.path) return errorResult("provide `path` or `content`");
|
|
@@ -58538,7 +58719,7 @@ var emIndex = defineTool({
|
|
|
58538
58719
|
recursive: args.recursive ?? true
|
|
58539
58720
|
});
|
|
58540
58721
|
const lines = [
|
|
58541
|
-
`${args.path}: ${s.files} file(s), ${s.indexed} indexed, ${s.unchanged} unchanged, ${s.chunks} chunks`
|
|
58722
|
+
`${args.path}: ${s.files} file(s), ${s.indexed} indexed, ${s.unchanged} unchanged, ${s.chunks} chunks${s.fallbacks ? ` (${s.fallbacks} with no sections found; indexed as one chunk)` : ""}`
|
|
58542
58723
|
];
|
|
58543
58724
|
for (const sk of s.skipped.slice(0, 10)) lines.push(` skipped ${sk.path}: ${sk.reason}`);
|
|
58544
58725
|
if (s.skipped.length > 10) lines.push(` \u2026 ${s.skipped.length - 10} more skipped`);
|
|
@@ -58914,7 +59095,10 @@ var emSearch = defineTool({
|
|
|
58914
59095
|
}
|
|
58915
59096
|
}
|
|
58916
59097
|
const out = formatResults(responses, args.max_tokens ?? ctx.config.search.defaultMaxTokens);
|
|
58917
|
-
|
|
59098
|
+
let note = mode !== "lexical" && mode !== "regex" && !vectorSearch && ctx.config.embeddings.enabled ? "\n\n(embeddings: model still loading \u2014 lexical results only this time)" : "";
|
|
59099
|
+
const sources = Array.isArray(args.source) ? args.source : args.source ? [args.source] : [];
|
|
59100
|
+
if (mode === "semantic" && !ctx.config.docs.embed && sources.some((s) => s.startsWith("docs:")) && responses.every((r) => r.hits.length === 0))
|
|
59101
|
+
note += '\n\n(docs:* pages are indexed without vectors because docs.embed is off; use mode "hybrid" or "lexical", or set docs.embed: true in eminiar.config.json and em_docs {action:"sync", force:true})';
|
|
58918
59102
|
recordStat(ctx, "em_search", {
|
|
58919
59103
|
rawChars: responses.reduce(
|
|
58920
59104
|
(a, r) => a + r.hits.reduce((b, h) => b + h.chunk.body.length, 0),
|
|
@@ -58933,6 +59117,7 @@ init_esm_shims();
|
|
|
58933
59117
|
// src/session/recall.ts
|
|
58934
59118
|
init_esm_shims();
|
|
58935
59119
|
init_ids();
|
|
59120
|
+
init_metrics();
|
|
58936
59121
|
init_model();
|
|
58937
59122
|
var RECALL_CATEGORY = "recall";
|
|
58938
59123
|
function recallStats(events) {
|
|
@@ -60875,12 +61060,17 @@ function usageLine(ctx, sessionId) {
|
|
|
60875
61060
|
if (!usageEnabled(ctx)) return void 0;
|
|
60876
61061
|
try {
|
|
60877
61062
|
const db = getStorage(ctx);
|
|
60878
|
-
|
|
61063
|
+
let failed;
|
|
61064
|
+
try {
|
|
61065
|
+
ingestSession(db, ctx.root, sessionId, void 0, { overrides: ctx.config.usage.pricing });
|
|
61066
|
+
} catch (err) {
|
|
61067
|
+
failed = `usage: ingest failed: ${err.message}`;
|
|
61068
|
+
}
|
|
60879
61069
|
const b = usageBrief(db, ctx, { sessionId });
|
|
60880
|
-
|
|
60881
|
-
return
|
|
60882
|
-
} catch {
|
|
60883
|
-
return
|
|
61070
|
+
const line = b.todayUsd || b.sessionUsd ? `usage: today ${formatUsd(b.todayUsd)} \xB7 session ${formatUsd(b.sessionUsd)} \xB7 see em_usage` : void 0;
|
|
61071
|
+
return [line, failed].filter((s) => s).join("\n") || void 0;
|
|
61072
|
+
} catch (err) {
|
|
61073
|
+
return `usage: unavailable: ${err.message}`;
|
|
60884
61074
|
}
|
|
60885
61075
|
}
|
|
60886
61076
|
function mergeByTool(rows) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eminiar",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.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",
|
package/skills/em-docs/SKILL.md
CHANGED
|
@@ -20,6 +20,8 @@ Act on `$ARGUMENTS` with `em_docs`; with no arguments, run `list` and, when it i
|
|
|
20
20
|
one source with `name`. Cite the `doc N` ids. Only when the answer is not in the index should
|
|
21
21
|
you fall back to `em_fetch_and_index` for a specific page, then `{action: "add", url}` if it is
|
|
22
22
|
worth keeping.
|
|
23
|
-
- **list** / **remove** → the matching action.
|
|
23
|
+
- **list** / **remove** → the matching action. `list` also shows sources *orphaned in the
|
|
24
|
+
index* (pages indexed under `docs:<name>` with no manifest entry, e.g. the manifest lives on
|
|
25
|
+
another branch); `remove` drops their pages, `add` with the same name re-registers them.
|
|
24
26
|
|
|
25
27
|
The manifest `.eminiar/docs.json` is meant to be committed; say so once when sources were added.
|