eminiar 0.10.0 → 0.11.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/dist/cli.js CHANGED
@@ -21270,6 +21270,14 @@ function findOnPath(bin) {
21270
21270
  }
21271
21271
  return void 0;
21272
21272
  }
21273
+ function requirePreamble(code) {
21274
+ if (!/\brequire\s*\(/.test(code)) return void 0;
21275
+ if (/\b(?:const|let|var|function)\s+require\b/.test(code) || /\bcreateRequire\b/.test(code)) {
21276
+ return void 0;
21277
+ }
21278
+ return `${REQUIRE_PREAMBLE}
21279
+ `;
21280
+ }
21273
21281
  function preferredShell() {
21274
21282
  const sh = process.env.SHELL ?? "";
21275
21283
  if (/\/(zsh|bash)$/.test(sh) && existsSync5(sh)) return sh;
@@ -21290,13 +21298,14 @@ function availableLanguages() {
21290
21298
  return { id: l.id, bin };
21291
21299
  });
21292
21300
  }
21293
- var require3, tsxPath, LANGUAGES;
21301
+ var require3, REQUIRE_PREAMBLE, tsxPath, LANGUAGES;
21294
21302
  var init_languages = __esm({
21295
21303
  "src/sandbox/languages.ts"() {
21296
21304
  "use strict";
21297
21305
  init_esm_shims();
21298
21306
  init_paths();
21299
21307
  require3 = createRequire2(import.meta.url);
21308
+ 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);';
21300
21309
  LANGUAGES = [
21301
21310
  {
21302
21311
  id: "shell",
@@ -21310,14 +21319,16 @@ var init_languages = __esm({
21310
21319
  aliases: ["js", "node", "mjs"],
21311
21320
  ext: ".mjs",
21312
21321
  detect: () => process.execPath,
21313
- argv: (bin, file3) => [bin, "--no-warnings", file3]
21322
+ argv: (bin, file3) => [bin, "--no-warnings", file3],
21323
+ preamble: requirePreamble
21314
21324
  },
21315
21325
  {
21316
21326
  id: "typescript",
21317
21327
  aliases: ["ts"],
21318
21328
  ext: ".ts",
21319
21329
  detect: () => process.execPath,
21320
- argv: (bin, file3) => [bin, "--no-warnings", tsxCli(), file3]
21330
+ argv: (bin, file3) => [bin, "--no-warnings", tsxCli(), file3],
21331
+ preamble: requirePreamble
21321
21332
  },
21322
21333
  {
21323
21334
  id: "python",
@@ -21524,6 +21535,10 @@ var init_terms = __esm({
21524
21535
  });
21525
21536
 
21526
21537
  // src/storage/sqlite/schema.ts
21538
+ function hasColumn(db, table, column) {
21539
+ const cols = db.pragma(`table_info(${table})`);
21540
+ return cols.some((c) => c.name === column);
21541
+ }
21527
21542
  function vecTableSql(dims) {
21528
21543
  return `CREATE VIRTUAL TABLE IF NOT EXISTS chunk_vec USING vec0(chunk_id INTEGER PRIMARY KEY, embedding float[${dims}])`;
21529
21544
  }
@@ -21866,7 +21881,6 @@ CREATE TABLE IF NOT EXISTS transcript_files (
21866
21881
  offset INTEGER NOT NULL DEFAULT 0,
21867
21882
  size INTEGER NOT NULL DEFAULT 0,
21868
21883
  mtime INTEGER NOT NULL DEFAULT 0,
21869
- head_hash TEXT,
21870
21884
  ingested_at INTEGER NOT NULL
21871
21885
  );
21872
21886
 
@@ -21894,6 +21908,16 @@ CREATE TABLE IF NOT EXISTS statusline_snapshots (
21894
21908
  CREATE INDEX IF NOT EXISTS statusline_snapshots_session_ts ON statusline_snapshots(session_id, ts);
21895
21909
  CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
21896
21910
  `
21911
+ },
21912
+ {
21913
+ // transcript_files.head_hash (fingerprint of the file head, so a rewrite with the same
21914
+ // size is noticed) was first added to v5 after v5 had already run on real databases, so
21915
+ // version-5 databases exist both with and without the column. Add it only when missing.
21916
+ version: 6,
21917
+ up(db) {
21918
+ if (!hasColumn(db, "transcript_files", "head_hash"))
21919
+ db.exec("ALTER TABLE transcript_files ADD COLUMN head_hash TEXT");
21920
+ }
21897
21921
  }
21898
21922
  ];
21899
21923
  }
@@ -22013,7 +22037,8 @@ var init_adapter = __esm({
22013
22037
  if (pending2.length) {
22014
22038
  this.transaction(() => {
22015
22039
  for (const m of pending2) {
22016
- this.db.exec(m.sql);
22040
+ if (m.sql) this.db.exec(m.sql);
22041
+ m.up?.(this.db);
22017
22042
  this.metaSet("schema_version", String(m.version));
22018
22043
  }
22019
22044
  });
@@ -22232,18 +22257,42 @@ var init_adapter = __esm({
22232
22257
  }
22233
22258
  vecSearch(embedding, k, filters) {
22234
22259
  if (!this.vectors) return [];
22235
- const raw = this.db.prepare(
22236
- "SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ORDER BY distance"
22237
- ).all(embedding, BigInt(filters ? k * 4 : k));
22238
- if (!filters || !raw.length) return raw.map((r) => ({ chunkId: r.id, distance: r.distance }));
22239
22260
  const { where, params } = filterSql(filters);
22240
- const allowed = new Set(
22241
- this.db.prepare(
22242
- `SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
22243
- WHERE c.id IN (${raw.map(() => "?").join(",")})${where}`
22244
- ).all(...raw.map((r) => r.id), ...params).map((r) => r.id)
22245
- );
22246
- return raw.filter((r) => allowed.has(r.id)).slice(0, k).map((r) => ({ chunkId: r.id, distance: r.distance }));
22261
+ if (!where) return this.knn(embedding, k);
22262
+ try {
22263
+ return this.knn(
22264
+ embedding,
22265
+ k,
22266
+ `AND chunk_id IN (SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id WHERE 1=1${where})`,
22267
+ params
22268
+ );
22269
+ } catch {
22270
+ return this.vecSearchWidening(embedding, k, where, params);
22271
+ }
22272
+ }
22273
+ knn(embedding, k, extra = "", params = []) {
22274
+ return this.db.prepare(
22275
+ `SELECT chunk_id id, distance FROM chunk_vec WHERE embedding MATCH ? AND k = ? ${extra} ORDER BY distance`
22276
+ ).all(embedding, BigInt(k), ...params).map((r) => ({ chunkId: r.id, distance: r.distance }));
22277
+ }
22278
+ /** Fallback filtered KNN: fetch k*4, k*16, ... nearest until k pass the filter or all were seen. */
22279
+ vecSearchWidening(embedding, k, where, params) {
22280
+ const total = this.vecCount();
22281
+ for (let fetch2 = k * 4; ; fetch2 *= 4) {
22282
+ const raw = this.knn(embedding, Math.min(fetch2, total));
22283
+ if (!raw.length) return [];
22284
+ const allowed = /* @__PURE__ */ new Set();
22285
+ for (let i = 0; i < raw.length; i += 400) {
22286
+ const ids = raw.slice(i, i + 400).map((r) => r.chunkId);
22287
+ for (const r of this.db.prepare(
22288
+ `SELECT c.id FROM chunks c JOIN documents d ON d.id = c.document_id
22289
+ WHERE c.id IN (${ids.map(() => "?").join(",")})${where}`
22290
+ ).all(...ids, ...params))
22291
+ allowed.add(r.id);
22292
+ }
22293
+ const hits = raw.filter((r) => allowed.has(r.chunkId)).slice(0, k);
22294
+ if (hits.length >= k || fetch2 >= total) return hits;
22295
+ }
22247
22296
  }
22248
22297
  unembeddedChunkIds(limit) {
22249
22298
  return this.db.prepare("SELECT id FROM chunks WHERE embedded = 0 ORDER BY id LIMIT ?").all(limit).map((r) => r.id);
@@ -22522,6 +22571,26 @@ var init_adapter = __esm({
22522
22571
  }
22523
22572
  return out;
22524
22573
  }
22574
+ graphDependentFileCounts(paths2, kinds) {
22575
+ const out = /* @__PURE__ */ new Map();
22576
+ if (!paths2.length || !kinds.length) return out;
22577
+ const kph = kinds.map(() => "?").join(",");
22578
+ for (let i = 0; i < paths2.length; i += 400) {
22579
+ const batch = paths2.slice(i, i + 400);
22580
+ const pph = batch.map(() => "?").join(",");
22581
+ for (const r of this.db.prepare(
22582
+ `SELECT d.path p, COUNT(DISTINCT s.path) n
22583
+ FROM graph_nodes d
22584
+ JOIN graph_edges e ON e.dst = d.id AND e.kind IN (${kph})
22585
+ JOIN graph_nodes s ON s.id = e.src
22586
+ WHERE d.path IN (${pph}) AND s.path IS NOT NULL AND s.path <> d.path
22587
+ GROUP BY d.path`
22588
+ ).all(...kinds, ...batch)) {
22589
+ out.set(r.p, r.n);
22590
+ }
22591
+ }
22592
+ return out;
22593
+ }
22525
22594
  edgesWhere(col, ids, kinds) {
22526
22595
  if (!ids.length) return [];
22527
22596
  const out = [];
@@ -23152,6 +23221,24 @@ var init_storage = __esm({
23152
23221
  }
23153
23222
  });
23154
23223
 
23224
+ // src/hooks/cursor.ts
23225
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
23226
+ import { homedir as homedir3 } from "os";
23227
+ import { join as join10 } from "path";
23228
+ function cursorDir() {
23229
+ return process.env.CURSOR_CONFIG_DIR || join10(homedir3(), ".cursor");
23230
+ }
23231
+ var PENDING_TTL_MS;
23232
+ var init_cursor = __esm({
23233
+ "src/hooks/cursor.ts"() {
23234
+ "use strict";
23235
+ init_esm_shims();
23236
+ init_context();
23237
+ init_paths();
23238
+ PENDING_TTL_MS = 10 * 6e4;
23239
+ }
23240
+ });
23241
+
23155
23242
  // src/install/shell.ts
23156
23243
  import { spawnSync } from "child_process";
23157
23244
  import { createInterface } from "readline";
@@ -23200,194 +23287,20 @@ var init_shell = __esm({
23200
23287
  }
23201
23288
  });
23202
23289
 
23203
- // src/install/materialize.ts
23204
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync6, rmSync as rmSync2 } from "fs";
23205
- import { join as join10 } from "path";
23206
- function stableRoot() {
23207
- return join10(paths().plugin, "node_modules", "eminiar");
23208
- }
23209
- function stableVersion() {
23210
- try {
23211
- return JSON.parse(readFileSync6(join10(stableRoot(), "package.json"), "utf8")).version;
23212
- } catch {
23213
- return void 0;
23214
- }
23215
- }
23216
- function materialize(spec, force = false) {
23217
- const prefix = paths().plugin;
23218
- mkdirSync5(prefix, { recursive: true });
23219
- const root = stableRoot();
23220
- const here = pluginRoot();
23221
- if (!force && !spec && here === root) return { root, fromSpec: "self" };
23222
- if (!force && !spec && stableVersion() === version2() && existsSync7(join10(root, "dist", "server.js"))) {
23223
- log.ok(`stable copy already at v${version2()} (${root})`);
23224
- return { root, fromSpec: `pack:${here}` };
23225
- }
23226
- let target = spec;
23227
- if (!target) {
23228
- if (!existsSync7(join10(here, "dist", "server.js"))) {
23229
- throw new Error(`${here} has no dist/ \u2014 run \`npm run build\` first`);
23230
- }
23231
- const dest = paths().tmp;
23232
- mkdirSync5(dest, { recursive: true });
23233
- const pack = run(
23234
- npmCli(),
23235
- ["pack", "--ignore-scripts", "--pack-destination", dest, "--silent"],
23236
- { cwd: here }
23237
- );
23238
- if (!pack.ok) throw new Error(`npm pack failed: ${pack.stderr || pack.stdout}`);
23239
- const tarball = pack.stdout.trim().split("\n").pop();
23240
- target = join10(dest, tarball);
23241
- }
23242
- const install2 = run(
23243
- npmCli(),
23244
- [
23245
- "install",
23246
- "--prefix",
23247
- prefix,
23248
- "--omit=dev",
23249
- "--no-audit",
23250
- "--no-fund",
23251
- "--loglevel=error",
23252
- target
23253
- ],
23254
- {
23255
- env: { ...process.env, npm_config_update_notifier: "false" }
23256
- }
23257
- );
23258
- if (!install2.ok) throw new Error(`npm install failed: ${install2.stderr || install2.stdout}`);
23259
- if (!spec && target.endsWith(".tgz")) rmSync2(target, { force: true });
23260
- for (const must of [
23261
- "dist/server.js",
23262
- "dist/hook.js",
23263
- "bin/em-hook",
23264
- ".claude-plugin/plugin.json",
23265
- "hooks/hooks.json"
23266
- ]) {
23267
- if (!existsSync7(join10(root, must))) throw new Error(`stable copy is missing ${must}`);
23268
- }
23269
- log.ok(
23270
- `installed v${stableVersion()} into ${root} (${readdirSync3(join10(prefix, "node_modules")).length} packages)`
23271
- );
23272
- return { root, fromSpec: spec ?? `pack:${here}` };
23273
- }
23274
- function nativeCheck(node2, root) {
23275
- const script = `
23276
- const { createRequire } = require("node:module");
23277
- const req = createRequire(${JSON.stringify(join10(root, "package.json"))});
23278
- const out = [];
23279
- for (const m of ["better-sqlite3", "sqlite-vec", "web-tree-sitter", "@huggingface/transformers"]) {
23280
- try { const x = req(m); if (m === "better-sqlite3") { const db = new x(":memory:"); req("sqlite-vec").load(db); db.close(); } }
23281
- catch (e) { out.push(m + ": " + (e && e.message ? e.message.split("\\n")[0] : e)); }
23282
- }
23283
- process.stdout.write(JSON.stringify(out));
23284
- `;
23285
- const r = run(node2, ["-e", script], { cwd: root });
23286
- if (!r.ok) return [`native check could not run: ${r.stderr.trim().split("\n").pop()}`];
23287
- try {
23288
- return JSON.parse(r.stdout);
23289
- } catch {
23290
- return [`native check returned garbage: ${r.stdout.slice(0, 200)}`];
23291
- }
23292
- }
23293
- function rebuildNative(root) {
23294
- const r = run(
23295
- npmCli(),
23296
- ["rebuild", "better-sqlite3", "--prefix", paths().plugin, "--loglevel=error"],
23297
- { cwd: root }
23298
- );
23299
- return r.ok;
23300
- }
23301
- function downloadModel(node2, root, model2) {
23302
- const script = `
23303
- (async () => {
23304
- const tf = await import("@huggingface/transformers");
23305
- tf.env.cacheDir = ${JSON.stringify(paths().models)};
23306
- const p = await tf.pipeline("feature-extraction", ${JSON.stringify(model2)}, { dtype: "q8", device: "cpu" });
23307
- const r = await p(["warm up"], { pooling: "cls", normalize: true });
23308
- process.stdout.write(String(r.tolist()[0].length));
23309
- })().catch((e) => { process.stderr.write(String(e && e.message || e)); process.exit(1); });
23310
- `;
23311
- const r = run(node2, ["--input-type=module", "-e", script], { cwd: root });
23312
- return r.ok ? void 0 : r.stderr.trim().split("\n").pop();
23313
- }
23314
- var init_materialize = __esm({
23315
- "src/install/materialize.ts"() {
23316
- "use strict";
23317
- init_esm_shims();
23318
- init_paths();
23319
- init_version();
23320
- init_shell();
23321
- }
23322
- });
23323
-
23324
- // src/install/node-resolve.ts
23325
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
23326
- import { join as join11 } from "path";
23327
- function versionOf(bin) {
23328
- const r = run(bin, ["-p", "process.versions.node"]);
23329
- return r.ok ? r.stdout.trim() : void 0;
23330
- }
23331
- function nodeOk(version3) {
23332
- const [maj = 0, min = 0] = version3.split(".").map(Number);
23333
- return maj > 22 || maj === 22 && min >= 12;
23334
- }
23335
- function pinNode(explicit) {
23336
- const candidate = explicit ?? process.execPath;
23337
- accessSync3(candidate, constants3.X_OK);
23338
- const version3 = versionOf(candidate);
23339
- if (!version3) throw new Error(`${candidate} does not look like a node binary`);
23340
- if (!nodeOk(version3))
23341
- throw new Error(`node ${version3} at ${candidate} is too old; need >= 22.12`);
23342
- writeFileSync4(ensureDirs().nodePathFile, candidate);
23343
- return { path: candidate, version: version3 };
23344
- }
23345
- function pinnedNode() {
23346
- try {
23347
- const path3 = readFileSync7(ensureDirs().nodePathFile, "utf8").trim();
23348
- const version3 = versionOf(path3);
23349
- return version3 ? { path: path3, version: version3 } : void 0;
23350
- } catch {
23351
- return void 0;
23352
- }
23353
- }
23354
- function pinRoot(root) {
23355
- if (!existsSync8(join11(root, "dist", "server.js")))
23356
- throw new Error(`${root} has no dist/server.js; cannot pin it as the stable root`);
23357
- writeFileSync4(ensureDirs().rootPathFile, root);
23358
- return root;
23359
- }
23360
- function pinnedRoot() {
23361
- try {
23362
- const root = readFileSync7(ensureDirs().rootPathFile, "utf8").trim();
23363
- return root && existsSync8(join11(root, "dist", "server.js")) ? root : void 0;
23364
- } catch {
23365
- return void 0;
23366
- }
23367
- }
23368
- var init_node_resolve = __esm({
23369
- "src/install/node-resolve.ts"() {
23370
- "use strict";
23371
- init_esm_shims();
23372
- init_paths();
23373
- init_shell();
23374
- }
23375
- });
23376
-
23377
23290
  // src/install/register.ts
23378
23291
  import { createHash as createHash2 } from "crypto";
23379
- import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
23380
- import { join as join12 } from "path";
23292
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync3 } from "fs";
23293
+ import { join as join11 } from "path";
23381
23294
  function fileHash(file3) {
23382
23295
  try {
23383
- return createHash2("sha1").update(readFileSync8(file3)).digest("hex");
23296
+ return createHash2("sha1").update(readFileSync7(file3)).digest("hex");
23384
23297
  } catch {
23385
23298
  return void 0;
23386
23299
  }
23387
23300
  }
23388
23301
  function probeServer(root) {
23389
- const bin = join12(root, "bin", "em-server");
23390
- if (!existsSync9(bin)) return "bin/em-server is missing";
23302
+ const bin = join11(root, "bin", "em-server");
23303
+ if (!existsSync8(bin)) return "bin/em-server is missing";
23391
23304
  const r = run(bin, ["--probe"], { timeoutMs: 2e4 });
23392
23305
  if (r.ok && / ok /.test(r.stdout)) return void 0;
23393
23306
  const lines = (r.stderr || r.stdout).trim().split("\n").filter(Boolean);
@@ -23397,11 +23310,11 @@ function probeServer(root) {
23397
23310
  function pluginCacheStale(stable) {
23398
23311
  const cache3 = pluginInstallPath("eminiar@eminiar");
23399
23312
  if (!cache3 || cache3 === stable) return void 0;
23400
- if (!existsSync9(cache3)) return `${cache3} is missing`;
23313
+ if (!existsSync8(cache3)) return `${cache3} is missing`;
23401
23314
  for (const rel of CACHE_FINGERPRINT) {
23402
- if (fileHash(join12(cache3, rel)) !== fileHash(join12(stable, rel))) return `${rel} differs`;
23315
+ if (fileHash(join11(cache3, rel)) !== fileHash(join11(stable, rel))) return `${rel} differs`;
23403
23316
  }
23404
- if (existsSync9(join12(cache3, ".mcp.json")) && !existsSync9(join12(stable, ".mcp.json")))
23317
+ if (existsSync8(join11(cache3, ".mcp.json")) && !existsSync8(join11(stable, ".mcp.json")))
23405
23318
  return ".mcp.json is present in the cache but not in the stable copy";
23406
23319
  const probe3 = probeServer(cache3);
23407
23320
  if (probe3) return `cached copy ${probe3}`;
@@ -23458,7 +23371,7 @@ function isOurs(cmd2, stable) {
23458
23371
  function registerSettings(stable) {
23459
23372
  const settings = readJson2(settingsPath());
23460
23373
  const hooks = settings.hooks ?? {};
23461
- const hookCmd = `"${join12(stable, "bin", "em-hook")}"`;
23374
+ const hookCmd = `"${join11(stable, "bin", "em-hook")}"`;
23462
23375
  for (const ev2 of HOOK_EVENTS) {
23463
23376
  const list2 = hooks[ev2] ?? [];
23464
23377
  const filtered = list2.filter(
@@ -23479,17 +23392,17 @@ function registerSettings(stable) {
23479
23392
  const servers = cj.mcpServers ?? {};
23480
23393
  servers.eminiar = {
23481
23394
  type: "stdio",
23482
- command: join12(stable, "bin", "em-server"),
23395
+ command: join11(stable, "bin", "em-server"),
23483
23396
  args: [],
23484
23397
  env: { EMINIAR_PLUGIN_ROOT: stable }
23485
23398
  };
23486
23399
  cj.mcpServers = servers;
23487
23400
  writeJsonWithBackup(claudeJsonPath(), cj);
23488
23401
  const skills = [];
23489
- const skillsDir = join12(settingsPath(), "..", "skills");
23402
+ const skillsDir = join11(settingsPath(), "..", "skills");
23490
23403
  mkdirSync6(skillsDir, { recursive: true });
23491
- for (const name of readdirSync4(join12(stable, "skills"))) {
23492
- cpSync(join12(stable, "skills", name), join12(skillsDir, name), { recursive: true });
23404
+ for (const name of readdirSync3(join11(stable, "skills"))) {
23405
+ cpSync(join11(stable, "skills", name), join11(skillsDir, name), { recursive: true });
23493
23406
  skills.push(name);
23494
23407
  }
23495
23408
  return { hookEvents: HOOK_EVENTS, mcpServer: true, skills };
@@ -23520,8 +23433,8 @@ function unregisterSettings(stable, wrote) {
23520
23433
  }
23521
23434
  }
23522
23435
  for (const name of wrote.skills ?? []) {
23523
- const dir = join12(settingsPath(), "..", "skills", name);
23524
- if (existsSync9(dir)) rmSync3(dir, { recursive: true, force: true });
23436
+ const dir = join11(settingsPath(), "..", "skills", name);
23437
+ if (existsSync8(dir)) rmSync3(dir, { recursive: true, force: true });
23525
23438
  }
23526
23439
  }
23527
23440
  function setStatusline(stable) {
@@ -23535,7 +23448,7 @@ function setStatusline(stable) {
23535
23448
  }
23536
23449
  settings.statusLine = {
23537
23450
  type: "command",
23538
- command: `"${join12(stable, "bin", "eminiar")}" statusline`
23451
+ command: `"${join11(stable, "bin", "eminiar")}" statusline`
23539
23452
  };
23540
23453
  writeJsonWithBackup(settingsPath(), settings);
23541
23454
  return true;
@@ -23577,14 +23490,304 @@ var init_register = __esm({
23577
23490
  }
23578
23491
  });
23579
23492
 
23493
+ // src/install/cursor.ts
23494
+ import { cpSync as cpSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync8, rmSync as rmSync4 } from "fs";
23495
+ import { join as join12 } from "path";
23496
+ function cursorPresent() {
23497
+ return existsSync9(cursorDir()) || existsSync9("/Applications/Cursor.app") || !!findOnPath("cursor") || !!findOnPath("cursor-agent");
23498
+ }
23499
+ function cursorHookCommand(stable) {
23500
+ return `"${join12(stable, "bin", "em-hook")}" --client cursor`;
23501
+ }
23502
+ function registerCursor(stable) {
23503
+ const cfg = readJson2(cursorHooksPath());
23504
+ if (typeof cfg.version !== "number") cfg.version = 1;
23505
+ const hooks = cfg.hooks ?? {};
23506
+ const command = cursorHookCommand(stable);
23507
+ for (const ev2 of CURSOR_HOOK_EVENTS) {
23508
+ const list2 = (Array.isArray(hooks[ev2]) ? hooks[ev2] : []).filter(
23509
+ (h) => !isOurs(h.command, stable)
23510
+ );
23511
+ const entry = { command, timeout: TIMEOUTS[ev2] ?? 5 };
23512
+ if (ev2 === "preToolUse") entry.matcher = CURSOR_PRE_TOOL_MATCHER;
23513
+ list2.push(entry);
23514
+ hooks[ev2] = list2;
23515
+ }
23516
+ cfg.hooks = hooks;
23517
+ writeJsonWithBackup(cursorHooksPath(), cfg);
23518
+ const mcp = readJson2(cursorMcpPath());
23519
+ const servers = mcp.mcpServers ?? {};
23520
+ servers.eminiar = {
23521
+ command: join12(stable, "bin", "em-server"),
23522
+ args: [],
23523
+ env: { EMINIAR_PLUGIN_ROOT: stable }
23524
+ };
23525
+ mcp.mcpServers = servers;
23526
+ writeJsonWithBackup(cursorMcpPath(), mcp);
23527
+ const skills = [];
23528
+ const src = join12(stable, "skills");
23529
+ if (existsSync9(src)) {
23530
+ mkdirSync7(cursorSkillsDir(), { recursive: true });
23531
+ for (const name of readdirSync4(src)) {
23532
+ cpSync2(join12(src, name), join12(cursorSkillsDir(), name), { recursive: true });
23533
+ skills.push(name);
23534
+ }
23535
+ }
23536
+ return { hookEvents: [...CURSOR_HOOK_EVENTS], mcpServer: true, skills };
23537
+ }
23538
+ function unregisterCursor(stable, wrote) {
23539
+ if (!wrote) return;
23540
+ if (wrote.hookEvents.length && existsSync9(cursorHooksPath())) {
23541
+ const cfg = readJson2(cursorHooksPath());
23542
+ const hooks = cfg.hooks ?? {};
23543
+ for (const ev2 of wrote.hookEvents) {
23544
+ if (!Array.isArray(hooks[ev2])) continue;
23545
+ const kept = hooks[ev2].filter((h) => !isOurs(h.command, stable));
23546
+ if (kept.length) hooks[ev2] = kept;
23547
+ else delete hooks[ev2];
23548
+ }
23549
+ cfg.hooks = hooks;
23550
+ writeJsonWithBackup(cursorHooksPath(), cfg);
23551
+ }
23552
+ if (wrote.mcpServer && existsSync9(cursorMcpPath())) {
23553
+ const mcp = readJson2(cursorMcpPath());
23554
+ const servers = mcp.mcpServers;
23555
+ if (servers?.eminiar && isOurs(servers.eminiar.command, stable)) {
23556
+ delete servers.eminiar;
23557
+ writeJsonWithBackup(cursorMcpPath(), mcp);
23558
+ }
23559
+ }
23560
+ for (const name of wrote.skills) {
23561
+ const dir = join12(cursorSkillsDir(), name);
23562
+ if (existsSync9(dir)) rmSync4(dir, { recursive: true, force: true });
23563
+ }
23564
+ }
23565
+ function cursorStatus() {
23566
+ const read2 = (f) => existsSync9(f) ? readFileSync8(f, "utf8") : "";
23567
+ const hooksRaw = read2(cursorHooksPath());
23568
+ let events = [];
23569
+ try {
23570
+ const cfg = hooksRaw ? JSON.parse(hooksRaw) : {};
23571
+ const hooks = cfg.hooks ?? {};
23572
+ events = Object.keys(hooks).filter(
23573
+ (ev2) => Array.isArray(hooks[ev2]) && hooks[ev2].some(
23574
+ (h) => typeof h.command === "string" && h.command.includes("em-hook")
23575
+ )
23576
+ );
23577
+ } catch {
23578
+ }
23579
+ return { hooks: events.length > 0, mcp: read2(cursorMcpPath()).includes("em-server"), events };
23580
+ }
23581
+ var cursorHooksPath, cursorMcpPath, cursorSkillsDir, CURSOR_HOOK_EVENTS, CURSOR_PRE_TOOL_MATCHER, TIMEOUTS;
23582
+ var init_cursor2 = __esm({
23583
+ "src/install/cursor.ts"() {
23584
+ "use strict";
23585
+ init_esm_shims();
23586
+ init_cursor();
23587
+ init_languages();
23588
+ init_claude_config();
23589
+ init_register();
23590
+ cursorHooksPath = () => join12(cursorDir(), "hooks.json");
23591
+ cursorMcpPath = () => join12(cursorDir(), "mcp.json");
23592
+ cursorSkillsDir = () => join12(cursorDir(), "skills");
23593
+ CURSOR_HOOK_EVENTS = [
23594
+ "sessionStart",
23595
+ "beforeSubmitPrompt",
23596
+ "preToolUse",
23597
+ "postToolUse",
23598
+ "postToolUseFailure",
23599
+ "subagentStop",
23600
+ "preCompact",
23601
+ "afterAgentResponse",
23602
+ "sessionEnd"
23603
+ ];
23604
+ CURSOR_PRE_TOOL_MATCHER = "Shell|Read|Write|Edit|Grep|Glob|WebFetch|WebSearch|Task";
23605
+ TIMEOUTS = { sessionStart: 10, preCompact: 15, postToolUse: 10 };
23606
+ }
23607
+ });
23608
+
23609
+ // src/install/materialize.ts
23610
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync9, rmSync as rmSync5 } from "fs";
23611
+ import { join as join13 } from "path";
23612
+ function stableRoot() {
23613
+ return join13(paths().plugin, "node_modules", "eminiar");
23614
+ }
23615
+ function stableVersion() {
23616
+ try {
23617
+ return JSON.parse(readFileSync9(join13(stableRoot(), "package.json"), "utf8")).version;
23618
+ } catch {
23619
+ return void 0;
23620
+ }
23621
+ }
23622
+ function materialize(spec, force = false) {
23623
+ const prefix = paths().plugin;
23624
+ mkdirSync8(prefix, { recursive: true });
23625
+ const root = stableRoot();
23626
+ const here = pluginRoot();
23627
+ if (!force && !spec && here === root) return { root, fromSpec: "self" };
23628
+ if (!force && !spec && stableVersion() === version2() && existsSync10(join13(root, "dist", "server.js"))) {
23629
+ log.ok(`stable copy already at v${version2()} (${root})`);
23630
+ return { root, fromSpec: `pack:${here}` };
23631
+ }
23632
+ let target = spec;
23633
+ if (!target) {
23634
+ if (!existsSync10(join13(here, "dist", "server.js"))) {
23635
+ throw new Error(`${here} has no dist/ \u2014 run \`npm run build\` first`);
23636
+ }
23637
+ const dest = paths().tmp;
23638
+ mkdirSync8(dest, { recursive: true });
23639
+ const pack = run(
23640
+ npmCli(),
23641
+ ["pack", "--ignore-scripts", "--pack-destination", dest, "--silent"],
23642
+ { cwd: here }
23643
+ );
23644
+ if (!pack.ok) throw new Error(`npm pack failed: ${pack.stderr || pack.stdout}`);
23645
+ const tarball = pack.stdout.trim().split("\n").pop();
23646
+ target = join13(dest, tarball);
23647
+ }
23648
+ const install2 = run(
23649
+ npmCli(),
23650
+ [
23651
+ "install",
23652
+ "--prefix",
23653
+ prefix,
23654
+ "--omit=dev",
23655
+ "--no-audit",
23656
+ "--no-fund",
23657
+ "--loglevel=error",
23658
+ target
23659
+ ],
23660
+ {
23661
+ env: { ...process.env, npm_config_update_notifier: "false" }
23662
+ }
23663
+ );
23664
+ if (!install2.ok) throw new Error(`npm install failed: ${install2.stderr || install2.stdout}`);
23665
+ if (!spec && target.endsWith(".tgz")) rmSync5(target, { force: true });
23666
+ for (const must of [
23667
+ "dist/server.js",
23668
+ "dist/hook.js",
23669
+ "bin/em-hook",
23670
+ ".claude-plugin/plugin.json",
23671
+ "hooks/hooks.json"
23672
+ ]) {
23673
+ if (!existsSync10(join13(root, must))) throw new Error(`stable copy is missing ${must}`);
23674
+ }
23675
+ log.ok(
23676
+ `installed v${stableVersion()} into ${root} (${readdirSync5(join13(prefix, "node_modules")).length} packages)`
23677
+ );
23678
+ return { root, fromSpec: spec ?? `pack:${here}` };
23679
+ }
23680
+ function nativeCheck(node2, root) {
23681
+ const script = `
23682
+ const { createRequire } = require("node:module");
23683
+ const req = createRequire(${JSON.stringify(join13(root, "package.json"))});
23684
+ const out = [];
23685
+ for (const m of ["better-sqlite3", "sqlite-vec", "web-tree-sitter", "@huggingface/transformers"]) {
23686
+ try { const x = req(m); if (m === "better-sqlite3") { const db = new x(":memory:"); req("sqlite-vec").load(db); db.close(); } }
23687
+ catch (e) { out.push(m + ": " + (e && e.message ? e.message.split("\\n")[0] : e)); }
23688
+ }
23689
+ process.stdout.write(JSON.stringify(out));
23690
+ `;
23691
+ const r = run(node2, ["-e", script], { cwd: root });
23692
+ if (!r.ok) return [`native check could not run: ${r.stderr.trim().split("\n").pop()}`];
23693
+ try {
23694
+ return JSON.parse(r.stdout);
23695
+ } catch {
23696
+ return [`native check returned garbage: ${r.stdout.slice(0, 200)}`];
23697
+ }
23698
+ }
23699
+ function rebuildNative(root) {
23700
+ const r = run(
23701
+ npmCli(),
23702
+ ["rebuild", "better-sqlite3", "--prefix", paths().plugin, "--loglevel=error"],
23703
+ { cwd: root }
23704
+ );
23705
+ return r.ok;
23706
+ }
23707
+ function downloadModel(node2, root, model2) {
23708
+ const script = `
23709
+ (async () => {
23710
+ const tf = await import("@huggingface/transformers");
23711
+ tf.env.cacheDir = ${JSON.stringify(paths().models)};
23712
+ const p = await tf.pipeline("feature-extraction", ${JSON.stringify(model2)}, { dtype: "q8", device: "cpu" });
23713
+ const r = await p(["warm up"], { pooling: "cls", normalize: true });
23714
+ process.stdout.write(String(r.tolist()[0].length));
23715
+ })().catch((e) => { process.stderr.write(String(e && e.message || e)); process.exit(1); });
23716
+ `;
23717
+ const r = run(node2, ["--input-type=module", "-e", script], { cwd: root });
23718
+ return r.ok ? void 0 : r.stderr.trim().split("\n").pop();
23719
+ }
23720
+ var init_materialize = __esm({
23721
+ "src/install/materialize.ts"() {
23722
+ "use strict";
23723
+ init_esm_shims();
23724
+ init_paths();
23725
+ init_version();
23726
+ init_shell();
23727
+ }
23728
+ });
23729
+
23730
+ // src/install/node-resolve.ts
23731
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
23732
+ import { join as join14 } from "path";
23733
+ function versionOf(bin) {
23734
+ const r = run(bin, ["-p", "process.versions.node"]);
23735
+ return r.ok ? r.stdout.trim() : void 0;
23736
+ }
23737
+ function nodeOk(version3) {
23738
+ const [maj = 0, min = 0] = version3.split(".").map(Number);
23739
+ return maj > 22 || maj === 22 && min >= 12;
23740
+ }
23741
+ function pinNode(explicit) {
23742
+ const candidate = explicit ?? process.execPath;
23743
+ accessSync3(candidate, constants3.X_OK);
23744
+ const version3 = versionOf(candidate);
23745
+ if (!version3) throw new Error(`${candidate} does not look like a node binary`);
23746
+ if (!nodeOk(version3))
23747
+ throw new Error(`node ${version3} at ${candidate} is too old; need >= 22.12`);
23748
+ writeFileSync5(ensureDirs().nodePathFile, candidate);
23749
+ return { path: candidate, version: version3 };
23750
+ }
23751
+ function pinnedNode() {
23752
+ try {
23753
+ const path3 = readFileSync10(ensureDirs().nodePathFile, "utf8").trim();
23754
+ const version3 = versionOf(path3);
23755
+ return version3 ? { path: path3, version: version3 } : void 0;
23756
+ } catch {
23757
+ return void 0;
23758
+ }
23759
+ }
23760
+ function pinRoot(root) {
23761
+ if (!existsSync11(join14(root, "dist", "server.js")))
23762
+ throw new Error(`${root} has no dist/server.js; cannot pin it as the stable root`);
23763
+ writeFileSync5(ensureDirs().rootPathFile, root);
23764
+ return root;
23765
+ }
23766
+ function pinnedRoot() {
23767
+ try {
23768
+ const root = readFileSync10(ensureDirs().rootPathFile, "utf8").trim();
23769
+ return root && existsSync11(join14(root, "dist", "server.js")) ? root : void 0;
23770
+ } catch {
23771
+ return void 0;
23772
+ }
23773
+ }
23774
+ var init_node_resolve = __esm({
23775
+ "src/install/node-resolve.ts"() {
23776
+ "use strict";
23777
+ init_esm_shims();
23778
+ init_paths();
23779
+ init_shell();
23780
+ }
23781
+ });
23782
+
23580
23783
  // src/install/doctor.ts
23581
23784
  var doctor_exports = {};
23582
23785
  __export(doctor_exports, {
23583
23786
  fullDoctor: () => fullDoctor
23584
23787
  });
23585
23788
  import { spawn } from "child_process";
23586
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
23587
- import { join as join13 } from "path";
23789
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
23790
+ import { join as join15 } from "path";
23588
23791
  function mcpHandshake(bin, ms = 15e3) {
23589
23792
  return new Promise((resolve9) => {
23590
23793
  const child = spawn(bin, [], { stdio: ["pipe", "pipe", "pipe"] });
@@ -23619,8 +23822,8 @@ function mcpHandshake(bin, ms = 15e3) {
23619
23822
  async function fullDoctor(opts = {}) {
23620
23823
  const rec = readInstallRecord();
23621
23824
  const stable = rec?.stableRoot ?? stableRoot();
23622
- const hasStable = existsSync10(join13(stable, "dist", "server.js"));
23623
- const bins = hasStable ? stable : join13(process.argv[1] ?? "", "..", "..");
23825
+ const hasStable = existsSync12(join15(stable, "dist", "server.js"));
23826
+ const bins = hasStable ? stable : join15(process.argv[1] ?? "", "..", "..");
23624
23827
  const fixed = [];
23625
23828
  if (opts.fix && hasStable) {
23626
23829
  try {
@@ -23645,7 +23848,7 @@ async function fullDoctor(opts = {}) {
23645
23848
  checks.push({
23646
23849
  name: "install",
23647
23850
  ok: !!rec,
23648
- detail: rec ? `v${rec.version} via ${rec.method} at ${rec.installedAt.slice(0, 10)} (${rec.stableRoot})` : "not installed (run `npx eminiar install`)"
23851
+ detail: rec ? `v${rec.version} via ${rec.method === "none" ? "cursor" : rec.method}${rec.wrote.cursor && rec.method !== "none" ? " + cursor" : ""} at ${rec.installedAt.slice(0, 10)} (${rec.stableRoot})` : "not installed (run `npx eminiar install`)"
23649
23852
  });
23650
23853
  const pinned = pinnedNode();
23651
23854
  checks.push({
@@ -23682,14 +23885,29 @@ async function fullDoctor(opts = {}) {
23682
23885
  detail: `${lines.join(" \xB7 ")}${drift}${liveOk ? "" : " \u2014 run `eminiar doctor --fix`, then `eminiar upgrade` if it still fails"}`
23683
23886
  });
23684
23887
  }
23685
- } else {
23686
- const s = existsSync10(join13(process.env.HOME ?? "", ".claude", "settings.json")) ? readFileSync9(join13(process.env.HOME ?? "", ".claude", "settings.json"), "utf8") : "";
23888
+ } else if (rec.method === "settings") {
23889
+ const s = existsSync12(join15(process.env.HOME ?? "", ".claude", "settings.json")) ? readFileSync11(join15(process.env.HOME ?? "", ".claude", "settings.json"), "utf8") : "";
23687
23890
  checks.push({
23688
23891
  name: "hooks in settings.json",
23689
23892
  ok: s.includes("em-hook"),
23690
23893
  detail: s.includes("em-hook") ? "present" : "missing"
23691
23894
  });
23692
23895
  }
23896
+ if (rec?.wrote.cursor || cursorPresent()) {
23897
+ const cs = cursorStatus();
23898
+ const registered = !!rec?.wrote.cursor;
23899
+ checks.push({
23900
+ name: "Cursor",
23901
+ ok: registered ? cs.hooks && cs.mcp : true,
23902
+ detail: registered ? `${cs.hooks ? `hooks on ${cs.events.length} events` : "hooks missing from ~/.cursor/hooks.json"} \xB7 ${cs.mcp ? "MCP server in ~/.cursor/mcp.json" : "MCP server missing from ~/.cursor/mcp.json"}${cs.hooks && cs.mcp ? "" : " \u2014 run `npx eminiar install --cursor`"}` : cs.hooks || cs.mcp ? "partly configured by hand; `npx eminiar install --cursor` completes and tracks it" : "installed but not registered; `npx eminiar install --cursor` adds hooks, MCP server and skills"
23903
+ });
23904
+ if (registered && rec?.method === "settings")
23905
+ checks.push({
23906
+ name: "Cursor third-party import",
23907
+ ok: false,
23908
+ detail: "Cursor also runs the Claude Code hooks in ~/.claude/settings.json (Settings \u2192 Agents \u2192 Third-Party Imports, on by default), so each event fires twice there; turn that setting off or reinstall Claude Code with `--method plugin`"
23909
+ });
23910
+ }
23693
23911
  const failed = needsAuthEntry();
23694
23912
  if (failed && opts.fix && clearNeedsAuthEntry()) fixed.push("cached launch failure cleared");
23695
23913
  const stillFailed = needsAuthEntry();
@@ -23700,8 +23918,8 @@ async function fullDoctor(opts = {}) {
23700
23918
  });
23701
23919
  if (opts.fix)
23702
23920
  checks.push({ name: "fix", ok: true, detail: fixed.join(", ") || "nothing to fix" });
23703
- const hookBin = join13(bins, "bin", "em-hook");
23704
- if (existsSync10(hookBin)) {
23921
+ const hookBin = join15(bins, "bin", "em-hook");
23922
+ if (existsSync12(hookBin)) {
23705
23923
  const r = run(hookBin, ["--selftest"], { env: { ...process.env } });
23706
23924
  checks.push({
23707
23925
  name: "hook self-test",
@@ -23709,8 +23927,8 @@ async function fullDoctor(opts = {}) {
23709
23927
  detail: r.ok ? r.stdout.trim() : r.stderr.trim().slice(-200) || "failed"
23710
23928
  });
23711
23929
  }
23712
- const serverBin = join13(bins, "bin", "em-server");
23713
- if (existsSync10(serverBin)) {
23930
+ const serverBin = join15(bins, "bin", "em-server");
23931
+ if (existsSync12(serverBin)) {
23714
23932
  const reply = await mcpHandshake(serverBin);
23715
23933
  checks.push({ name: "MCP handshake", ok: /^eminiar v|^ok$/.test(reply), detail: reply });
23716
23934
  }
@@ -23722,10 +23940,10 @@ async function fullDoctor(opts = {}) {
23722
23940
  ok: true,
23723
23941
  detail: `${ctx.root} \xB7 ${db.countDocuments()} docs \xB7 ${db.memoryCount()} memories \xB7 graph ${db.graphCounts().nodes} nodes`
23724
23942
  });
23725
- const cfgFile = join13(ctx.root, "eminiar.config.json");
23726
- if (existsSync10(cfgFile)) {
23943
+ const cfgFile = join15(ctx.root, "eminiar.config.json");
23944
+ if (existsSync12(cfgFile)) {
23727
23945
  try {
23728
- const parsed = ConfigSchema.safeParse(JSON.parse(readFileSync9(cfgFile, "utf8")));
23946
+ const parsed = ConfigSchema.safeParse(JSON.parse(readFileSync11(cfgFile, "utf8")));
23729
23947
  checks.push({
23730
23948
  name: "eminiar.config.json",
23731
23949
  ok: parsed.success,
@@ -23748,9 +23966,9 @@ async function fullDoctor(opts = {}) {
23748
23966
  ok: true,
23749
23967
  detail: live2.length ? `running for this project (pid ${live2.join(", ")})` : "none running for this project right now; enforce routing annotates instead of denying until one connects"
23750
23968
  });
23751
- const projectMcp = join13(ctx.root, ".mcp.json");
23752
- if (existsSync10(projectMcp)) {
23753
- const raw = readFileSync9(projectMcp, "utf8");
23969
+ const projectMcp = join15(ctx.root, ".mcp.json");
23970
+ if (existsSync12(projectMcp)) {
23971
+ const raw = readFileSync11(projectMcp, "utf8");
23754
23972
  const bad = raw.includes(`\${CLAUDE_PLUGIN_ROOT}`) && raw.includes("em-server");
23755
23973
  checks.push({
23756
23974
  name: "project .mcp.json",
@@ -23780,6 +23998,7 @@ var init_doctor2 = __esm({
23780
23998
  init_server_alive();
23781
23999
  init_storage();
23782
24000
  init_claude_config();
24001
+ init_cursor2();
23783
24002
  init_materialize();
23784
24003
  init_node_resolve();
23785
24004
  init_register();
@@ -23880,8 +24099,8 @@ __export(install_exports, {
23880
24099
  install: () => install,
23881
24100
  writeTeamFiles: () => writeTeamFiles
23882
24101
  });
23883
- import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "fs";
23884
- import { join as join14 } from "path";
24102
+ import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
24103
+ import { join as join16 } from "path";
23885
24104
  async function install(opts) {
23886
24105
  console.log(`eminiar v${version2()} installer`);
23887
24106
  log.step("node");
@@ -23911,8 +24130,8 @@ async function install(opts) {
23911
24130
  }
23912
24131
  log.step("data directory");
23913
24132
  const p = ensureDirs();
23914
- if (!existsSync11(p.configFile))
23915
- writeFileSync5(p.configFile, `${JSON.stringify(ConfigSchema.parse({}), null, 2)}
24133
+ if (!existsSync13(p.configFile))
24134
+ writeFileSync6(p.configFile, `${JSON.stringify(ConfigSchema.parse({}), null, 2)}
23916
24135
  `);
23917
24136
  globalDb();
23918
24137
  log.ok(p.home);
@@ -23922,18 +24141,23 @@ async function install(opts) {
23922
24141
  if (err) log.warn(`download failed (${err}); it will be retried lazily on first hybrid search`);
23923
24142
  else log.ok("ready");
23924
24143
  }
23925
- log.step("register with Claude Code");
23926
- const method = opts.method ?? (claudeCli() ? "plugin" : "settings");
24144
+ const claude = claudeCli();
24145
+ const wantClaude = !opts.cursor || !!claude || !!opts.method;
24146
+ const method = opts.method ?? (claude ? "plugin" : "settings");
24147
+ const targets = [wantClaude ? `Claude Code (${method} method)` : "", opts.cursor ? "Cursor" : ""].filter(Boolean).join(" and ");
24148
+ log.step(`register with ${targets}`);
24149
+ if (opts.cursor && !cursorPresent())
24150
+ log.warn(`${cursorDirHint()} not found; Cursor files are written anyway for its first launch`);
23927
24151
  if (!await confirm(
23928
- `Register eminiar with Claude Code using the ${method} method? This edits your user-level Claude config.`,
24152
+ `Register eminiar with ${targets}? This edits your user-level ${[wantClaude ? "Claude" : "", opts.cursor ? "Cursor" : ""].filter(Boolean).join(" and ")} config.`,
23929
24153
  !!opts.yes
23930
24154
  )) {
23931
24155
  log.warn("skipped registration; re-run with --yes or confirm interactively");
23932
24156
  return 1;
23933
24157
  }
23934
24158
  let wrote = {};
23935
- let usedMethod = method;
23936
- if (method === "plugin") {
24159
+ let usedMethod = wantClaude ? method : "none";
24160
+ if (usedMethod === "plugin") {
23937
24161
  const r = registerPlugin(stable);
23938
24162
  if (r.ok) {
23939
24163
  log.ok(r.detail);
@@ -23949,15 +24173,27 @@ async function install(opts) {
23949
24173
  "hooks written to ~/.claude/settings.json, MCP server to ~/.claude.json, skills to ~/.claude/skills"
23950
24174
  );
23951
24175
  }
23952
- if (needsAuthEntry() && clearNeedsAuthEntry())
23953
- log.ok("cleared Claude Code's cached connection failure for the plugin server");
23954
- const wantStatusline = opts.statusline ?? (!!opts.yes && !opts.team);
23955
- if (wantStatusline) {
23956
- if (setStatusline(stable)) {
23957
- wrote.statusline = true;
23958
- log.ok("statusLine set to `eminiar statusline`");
24176
+ if (wantClaude) {
24177
+ if (needsAuthEntry() && clearNeedsAuthEntry())
24178
+ log.ok("cleared Claude Code's cached connection failure for the plugin server");
24179
+ const wantStatusline = opts.statusline ?? (!!opts.yes && !opts.team);
24180
+ if (wantStatusline) {
24181
+ if (setStatusline(stable)) {
24182
+ wrote.statusline = true;
24183
+ log.ok("statusLine set to `eminiar statusline`");
24184
+ }
23959
24185
  }
23960
24186
  }
24187
+ if (opts.cursor) {
24188
+ wrote.cursor = registerCursor(stable);
24189
+ log.ok(
24190
+ `hooks written to ${cursorDirHint()}/hooks.json, MCP server to ${cursorDirHint()}/mcp.json, skills to ${cursorDirHint()}/skills`
24191
+ );
24192
+ if (usedMethod === "settings")
24193
+ log.warn(
24194
+ "Cursor also imports Claude Code hooks from ~/.claude/settings.json (Settings \u2192 Agents \u2192 Third-Party Imports); turn that off or each event fires twice in Cursor"
24195
+ );
24196
+ }
23961
24197
  writeInstallRecord({
23962
24198
  version: version2(),
23963
24199
  method: usedMethod,
@@ -23969,21 +24205,25 @@ async function install(opts) {
23969
24205
  });
23970
24206
  if (opts.team) {
23971
24207
  log.step("team defaults");
23972
- writeTeamFiles(resolveProjectRoot(), opts.marketplace);
24208
+ writeTeamFiles(resolveProjectRoot(), opts.marketplace, { cursor: !!opts.cursor, stable });
23973
24209
  }
23974
24210
  log.step("doctor");
23975
24211
  const checks = await fullDoctor();
23976
24212
  console.log(formatDoctor(checks));
24213
+ const restart = [wantClaude ? "Claude Code" : "", opts.cursor ? "Cursor" : ""].filter(Boolean).join(" and ");
23977
24214
  console.log(
23978
24215
  `
23979
- Restart Claude Code to load eminiar. Try /em-doctor, then em_execute with an intent.`
24216
+ Restart ${restart} to load eminiar. Try ${wantClaude ? "/em-doctor, then " : ""}em_execute with an intent.`
23980
24217
  );
23981
24218
  return checks.every((c) => c.ok) ? 0 : 1;
23982
24219
  }
23983
- function writeTeamFiles(root, marketplace) {
23984
- const cfg = join14(root, "eminiar.config.json");
23985
- if (!existsSync11(cfg)) {
23986
- writeFileSync5(
24220
+ function cursorDirHint() {
24221
+ return process.env.CURSOR_CONFIG_DIR || "~/.cursor";
24222
+ }
24223
+ function writeTeamFiles(root, marketplace, extra = {}) {
24224
+ const cfg = join16(root, "eminiar.config.json");
24225
+ if (!existsSync13(cfg)) {
24226
+ writeFileSync6(
23987
24227
  cfg,
23988
24228
  `${JSON.stringify(
23989
24229
  {
@@ -24013,7 +24253,7 @@ function writeTeamFiles(root, marketplace) {
24013
24253
  }
24014
24254
  if (marketplace) {
24015
24255
  const m = /^github:(.+)$/.exec(marketplace);
24016
- const file3 = join14(root, ".claude", "settings.json");
24256
+ const file3 = join16(root, ".claude", "settings.json");
24017
24257
  const settings = readJson2(file3);
24018
24258
  const known = settings.extraKnownMarketplaces ?? {};
24019
24259
  known.eminiar = m ? { source: { source: "github", repo: m[1] } } : { source: { source: "url", url: marketplace } };
@@ -24026,7 +24266,19 @@ function writeTeamFiles(root, marketplace) {
24026
24266
  `${file3}: marketplace + enabledPlugins added \u2014 teammates are prompted to install on next launch`
24027
24267
  );
24028
24268
  } else {
24029
- log.info("teammates: `npx eminiar install` (reads the committed eminiar.config.json)");
24269
+ log.info(
24270
+ `teammates: \`npx eminiar install${extra.cursor ? " --cursor" : ""}\` (reads the committed eminiar.config.json)`
24271
+ );
24272
+ }
24273
+ if (extra.cursor) {
24274
+ const src = join16(extra.stable ?? "", "rules", "eminiar.mdc");
24275
+ const dst = join16(root, ".cursor", "rules", "eminiar.mdc");
24276
+ if (existsSync13(dst)) log.ok(`${dst} already exists`);
24277
+ else if (existsSync13(src)) {
24278
+ mkdirSync9(join16(root, ".cursor", "rules"), { recursive: true });
24279
+ copyFileSync2(src, dst);
24280
+ log.ok(`wrote ${dst} \u2014 commit it`);
24281
+ }
24030
24282
  }
24031
24283
  }
24032
24284
  var init_install = __esm({
@@ -24040,6 +24292,7 @@ var init_install = __esm({
24040
24292
  init_global();
24041
24293
  init_version();
24042
24294
  init_claude_config();
24295
+ init_cursor2();
24043
24296
  init_doctor2();
24044
24297
  init_materialize();
24045
24298
  init_node_resolve();
@@ -24062,12 +24315,14 @@ async function upgrade() {
24062
24315
  const fromCheckout = rec.fromSpec.startsWith("pack:") || rec.fromSpec === "self";
24063
24316
  return install({
24064
24317
  yes: true,
24065
- method: rec.method,
24318
+ // "none" means Cursor-only last time; let install() decide again from what is present.
24319
+ method: rec.method === "none" ? void 0 : rec.method,
24066
24320
  noModel: false,
24067
24321
  spec: fromCheckout ? void 0 : "eminiar@latest",
24068
24322
  force: fromCheckout,
24069
24323
  node: rec.node,
24070
- statusline: !!rec.wrote.statusline
24324
+ statusline: !!rec.wrote.statusline,
24325
+ cursor: !!rec.wrote.cursor
24071
24326
  });
24072
24327
  }
24073
24328
  var init_upgrade = __esm({
@@ -24085,13 +24340,14 @@ var uninstall_exports = {};
24085
24340
  __export(uninstall_exports, {
24086
24341
  uninstall: () => uninstall
24087
24342
  });
24088
- import { existsSync as existsSync12, rmSync as rmSync4 } from "fs";
24343
+ import { existsSync as existsSync14, rmSync as rmSync6 } from "fs";
24089
24344
  async function uninstall(opts) {
24090
24345
  const rec = readInstallRecord();
24091
24346
  const p = paths();
24092
24347
  const plan = [
24093
24348
  rec?.method === "plugin" || !rec ? "claude plugin uninstall eminiar@eminiar + marketplace remove" : "",
24094
24349
  rec?.method === "settings" ? "remove eminiar hook entries from ~/.claude/settings.json, mcpServers.eminiar from ~/.claude.json, em-* skills" : "",
24350
+ rec?.wrote.cursor ? "remove eminiar hook entries from ~/.cursor/hooks.json, mcpServers.eminiar from ~/.cursor/mcp.json, em-* skills from ~/.cursor/skills" : "",
24095
24351
  rec?.wrote.statusline ? "clear the eminiar statusLine" : "",
24096
24352
  `delete ${p.plugin}`,
24097
24353
  opts.purgeData ? `delete ${p.home} (ALL indexes, memories, stats)` : `keep ${p.home} (indexes, memories, stats)`
@@ -24102,11 +24358,13 @@ async function uninstall(opts) {
24102
24358
  const stable = rec?.stableRoot ?? `${p.plugin}/node_modules/eminiar`;
24103
24359
  if (!rec || rec.method === "plugin") unregisterPlugin();
24104
24360
  if (rec?.method === "settings") unregisterSettings(stable, rec.wrote);
24361
+ if (rec?.wrote.cursor) unregisterCursor(stable, rec.wrote.cursor);
24105
24362
  if (rec?.wrote.statusline) clearStatusline(stable);
24106
- if (existsSync12(p.plugin)) rmSync4(p.plugin, { recursive: true, force: true });
24107
- if (opts.purgeData) rmSync4(p.home, { recursive: true, force: true });
24108
- else if (existsSync12(p.installFile)) rmSync4(p.installFile, { force: true });
24109
- log.ok("eminiar removed from Claude Code; restart it to finish");
24363
+ if (existsSync14(p.plugin)) rmSync6(p.plugin, { recursive: true, force: true });
24364
+ if (opts.purgeData) rmSync6(p.home, { recursive: true, force: true });
24365
+ else if (existsSync14(p.installFile)) rmSync6(p.installFile, { force: true });
24366
+ const from = [rec?.method !== "none" ? "Claude Code" : "", rec?.wrote.cursor ? "Cursor" : ""].filter(Boolean).join(" and ");
24367
+ log.ok(`eminiar removed from ${from || "Claude Code"}; restart it to finish`);
24110
24368
  return 0;
24111
24369
  }
24112
24370
  var init_uninstall = __esm({
@@ -24115,6 +24373,7 @@ var init_uninstall = __esm({
24115
24373
  init_esm_shims();
24116
24374
  init_paths();
24117
24375
  init_claude_config();
24376
+ init_cursor2();
24118
24377
  init_register();
24119
24378
  init_shell();
24120
24379
  }
@@ -30413,7 +30672,7 @@ function chunkMarkdown(text3, baseTitle, opts = DEFAULT_CHUNKER) {
30413
30672
  if (/^\s*(```|~~~)/.test(line)) inFence = !inFence;
30414
30673
  const m = !inFence && /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
30415
30674
  if (m) {
30416
- if (current.lines.length) sections.push(current);
30675
+ sections.push(current);
30417
30676
  const level = m[1].length;
30418
30677
  while (stack.length && stack[stack.length - 1].level >= level) stack.pop();
30419
30678
  stack.push({ level, text: m[2] });
@@ -30422,14 +30681,23 @@ function chunkMarkdown(text3, baseTitle, opts = DEFAULT_CHUNKER) {
30422
30681
  }
30423
30682
  current.lines.push(line);
30424
30683
  });
30425
- if (current.lines.length) sections.push(current);
30684
+ sections.push(current);
30426
30685
  const out = [];
30427
- for (const s of sections) {
30686
+ sections.forEach((s, k) => {
30428
30687
  const title = s.path.length ? `${baseTitle} > ${s.path.join(" > ")}` : baseTitle;
30429
30688
  const body = s.lines.join("\n");
30689
+ if (!body.trim()) {
30690
+ const next = sections[k + 1];
30691
+ const isParent = next !== void 0 && next.path.length > s.path.length && s.path.every((p, j) => next.path[j] === p);
30692
+ if (s.path.length && !isParent) {
30693
+ const heading = s.path[s.path.length - 1];
30694
+ out.push({ title, body: heading, startLine: s.startLine, endLine: s.startLine });
30695
+ }
30696
+ return;
30697
+ }
30430
30698
  if (body.length <= opts.maxChars) {
30431
30699
  out.push({ title, body, startLine: s.startLine, endLine: s.startLine + s.lines.length - 1 });
30432
- continue;
30700
+ return;
30433
30701
  }
30434
30702
  let fence = false;
30435
30703
  const fenceState = s.lines.map((l) => {
@@ -30445,7 +30713,7 @@ function chunkMarkdown(text3, baseTitle, opts = DEFAULT_CHUNKER) {
30445
30713
  s.startLine - 1
30446
30714
  )
30447
30715
  );
30448
- }
30716
+ });
30449
30717
  return out;
30450
30718
  }
30451
30719
  var init_markdown = __esm({
@@ -30553,11 +30821,24 @@ __export(indexer_exports, {
30553
30821
  indexContent: () => indexContent,
30554
30822
  indexPath: () => indexPath
30555
30823
  });
30556
- import { readFileSync as readFileSync10, statSync } from "fs";
30824
+ import { readFileSync as readFileSync12, statSync } from "fs";
30557
30825
  import { relative, resolve as resolve2 } from "path";
30558
30826
  function indexContent(db, input2) {
30559
30827
  const kind = input2.kind ?? (input2.contentType === "code" ? "code" : input2.contentType === "json" ? "json" : void 0);
30560
- const { chunks, kind: k } = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
30828
+ const cut = input2.chunks ? { chunks: finalize2(input2.chunks, input2.contentType ?? "code"), kind: kind ?? "code" } : chunk(input2.content, input2.title, kind, input2.uri);
30829
+ const k = cut.kind;
30830
+ const fallback = cut.chunks.length === 0 && input2.content.trim().length > 0;
30831
+ const chunks = fallback ? finalize2(
30832
+ [
30833
+ {
30834
+ title: input2.title,
30835
+ body: input2.content,
30836
+ startLine: 1,
30837
+ endLine: input2.content.split("\n").length
30838
+ }
30839
+ ],
30840
+ kindToContentType(k)
30841
+ ) : cut.chunks;
30561
30842
  const contentHash = hashText(input2.content);
30562
30843
  const existing = input2.uri ? db.findDocumentByUri(input2.uri) : void 0;
30563
30844
  if (existing && existing.content_hash === contentHash) {
@@ -30567,7 +30848,8 @@ function indexContent(db, input2) {
30567
30848
  created: false,
30568
30849
  chunks: chunks.length,
30569
30850
  chars: input2.content.length,
30570
- kind: k
30851
+ kind: k,
30852
+ fallback
30571
30853
  };
30572
30854
  }
30573
30855
  db.deleteDocument(existing.id);
@@ -30584,11 +30866,25 @@ function indexContent(db, input2) {
30584
30866
  },
30585
30867
  chunks
30586
30868
  );
30587
- return { documentId: id, created, chunks: chunks.length, chars: input2.content.length, kind: k };
30869
+ return {
30870
+ documentId: id,
30871
+ created,
30872
+ chunks: chunks.length,
30873
+ chars: input2.content.length,
30874
+ kind: k,
30875
+ fallback
30876
+ };
30588
30877
  }
30589
30878
  function indexPath(db, root, target, opts = {}) {
30590
30879
  const abs = resolve2(root, target);
30591
- const summary = { files: 0, indexed: 0, unchanged: 0, skipped: [], chunks: 0 };
30880
+ const summary = {
30881
+ files: 0,
30882
+ indexed: 0,
30883
+ unchanged: 0,
30884
+ skipped: [],
30885
+ chunks: 0,
30886
+ fallbacks: 0
30887
+ };
30592
30888
  const st = statSync(abs);
30593
30889
  const files = st.isDirectory() ? import_fast_glob.default.sync(opts.globs ?? ["**/*.md"], {
30594
30890
  cwd: abs,
@@ -30607,7 +30903,7 @@ function indexPath(db, root, target, opts = {}) {
30607
30903
  summary.skipped.push({ path: rel, reason: `too large (${size} bytes)` });
30608
30904
  continue;
30609
30905
  }
30610
- const content = readFileSync10(file3, "utf8");
30906
+ const content = readFileSync12(file3, "utf8");
30611
30907
  if (content.includes("\0")) {
30612
30908
  summary.skipped.push({ path: rel, reason: "binary" });
30613
30909
  continue;
@@ -30623,6 +30919,7 @@ function indexPath(db, root, target, opts = {}) {
30623
30919
  if (r.created) summary.indexed++;
30624
30920
  else summary.unchanged++;
30625
30921
  summary.chunks += r.chunks;
30922
+ if (r.fallback) summary.fallbacks++;
30626
30923
  } catch (err) {
30627
30924
  summary.skipped.push({ path: rel, reason: err.message });
30628
30925
  }
@@ -30698,7 +30995,7 @@ var init_em_index = __esm({
30698
30995
  });
30699
30996
  kickEmbedding(ctx, db);
30700
30997
  return text(
30701
- `${r.created ? "indexed" : "unchanged"} "${args.title}" as doc ${r.documentId}: ${r.chunks} chunks, ${r.chars} chars (${r.kind})`
30998
+ `${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)" : ""}`
30702
30999
  );
30703
31000
  }
30704
31001
  if (!args.path) return errorResult("provide `path` or `content`");
@@ -30708,7 +31005,7 @@ var init_em_index = __esm({
30708
31005
  recursive: args.recursive ?? true
30709
31006
  });
30710
31007
  const lines = [
30711
- `${args.path}: ${s.files} file(s), ${s.indexed} indexed, ${s.unchanged} unchanged, ${s.chunks} chunks`
31008
+ `${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)` : ""}`
30712
31009
  ];
30713
31010
  for (const sk of s.skipped.slice(0, 10)) lines.push(` skipped ${sk.path}: ${sk.reason}`);
30714
31011
  if (s.skipped.length > 10) lines.push(` \u2026 ${s.skipped.length - 10} more skipped`);
@@ -32221,8 +32518,8 @@ var init_routes = __esm({
32221
32518
  });
32222
32519
 
32223
32520
  // src/graph/languages/parser.ts
32224
- import { existsSync as existsSync13 } from "fs";
32225
- import { extname as extname2, join as join15 } from "path";
32521
+ import { existsSync as existsSync15 } from "fs";
32522
+ import { extname as extname2, join as join17 } from "path";
32226
32523
  function grammarFor(path3) {
32227
32524
  switch (extname2(path3).toLowerCase()) {
32228
32525
  case ".ts":
@@ -32255,8 +32552,8 @@ async function getParser(grammar) {
32255
32552
  }
32256
32553
  let lang = languages.get(grammar);
32257
32554
  if (!lang) {
32258
- const file3 = join15(pluginRoot(), "vendor", "grammars", `${grammar}.wasm`);
32259
- if (!existsSync13(file3)) throw new Error(`grammar missing: ${file3}`);
32555
+ const file3 = join17(pluginRoot(), "vendor", "grammars", `${grammar}.wasm`);
32556
+ if (!existsSync15(file3)) throw new Error(`grammar missing: ${file3}`);
32260
32557
  lang = await parserMod.Language.load(file3);
32261
32558
  languages.set(grammar, lang);
32262
32559
  }
@@ -32314,8 +32611,8 @@ var init_parser = __esm({
32314
32611
  });
32315
32612
 
32316
32613
  // src/graph/languages/go.ts
32317
- import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
32318
- import { dirname as dirname4, join as join16, normalize, relative as relative2, resolve as resolve3 } from "path";
32614
+ import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync13 } from "fs";
32615
+ import { dirname as dirname4, join as join18, normalize, relative as relative2, resolve as resolve3 } from "path";
32319
32616
  function goModule(root, fromAbs) {
32320
32617
  let dir = dirname4(fromAbs);
32321
32618
  while (dir.startsWith(root)) {
@@ -32323,9 +32620,9 @@ function goModule(root, fromAbs) {
32323
32620
  if (hit !== void 0) {
32324
32621
  if (hit) return hit;
32325
32622
  } else {
32326
- const file3 = join16(dir, "go.mod");
32327
- if (existsSync14(file3)) {
32328
- const m = /^module\s+(\S+)/m.exec(readFileSync11(file3, "utf8"));
32623
+ const file3 = join18(dir, "go.mod");
32624
+ if (existsSync16(file3)) {
32625
+ const m = /^module\s+(\S+)/m.exec(readFileSync13(file3, "utf8"));
32329
32626
  const info = m ? { module: m[1], dir } : null;
32330
32627
  goModCache.set(dir, info);
32331
32628
  if (info) return info;
@@ -32342,11 +32639,11 @@ function resolveGoPackage(root, fromRel, importPath) {
32342
32639
  let dir;
32343
32640
  if (importPath === mod.module) dir = mod.dir;
32344
32641
  else if (importPath.startsWith(`${mod.module}/`))
32345
- dir = join16(mod.dir, importPath.slice(mod.module.length + 1));
32346
- if (!dir || !existsSync14(dir)) return [];
32642
+ dir = join18(mod.dir, importPath.slice(mod.module.length + 1));
32643
+ if (!dir || !existsSync16(dir)) return [];
32347
32644
  try {
32348
- return readdirSync5(dir).filter((f) => f.endsWith(".go") && !f.endsWith("_test.go")).sort().map(
32349
- (f) => normalize(relative2(root, join16(dir, f))).split("\\").join("/")
32645
+ return readdirSync6(dir).filter((f) => f.endsWith(".go") && !f.endsWith("_test.go")).sort().map(
32646
+ (f) => normalize(relative2(root, join18(dir, f))).split("\\").join("/")
32350
32647
  );
32351
32648
  } catch {
32352
32649
  return [];
@@ -32748,13 +33045,13 @@ var init_go = __esm({
32748
33045
  });
32749
33046
 
32750
33047
  // src/graph/languages/php.ts
32751
- import { readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
32752
- import { dirname as dirname5, join as join17, normalize as normalize2, relative as relative3, resolve as resolve4 } from "path";
33048
+ import { readdirSync as readdirSync7, readFileSync as readFileSync14, statSync as statSync2 } from "fs";
33049
+ import { dirname as dirname5, join as join19, normalize as normalize2, relative as relative3, resolve as resolve4 } from "path";
32753
33050
  function readComposer(dir) {
32754
- const file3 = join17(dir, "composer.json");
33051
+ const file3 = join19(dir, "composer.json");
32755
33052
  if (!isFile(file3)) return null;
32756
33053
  try {
32757
- const json3 = JSON.parse(readFileSync12(file3, "utf8"));
33054
+ const json3 = JSON.parse(readFileSync14(file3, "utf8"));
32758
33055
  const info = { dir, psr4: [], psr0: [], classmap: [], files: [], require: [] };
32759
33056
  for (const key of ["autoload", "autoload-dev"]) {
32760
33057
  const section = json3[key];
@@ -32796,7 +33093,7 @@ function phpFilesUnder(abs, budget) {
32796
33093
  if (isFile(abs)) return abs.endsWith(".php") ? [abs] : [];
32797
33094
  let entries;
32798
33095
  try {
32799
- entries = readdirSync6(abs, { recursive: true, encoding: "utf8" });
33096
+ entries = readdirSync7(abs, { recursive: true, encoding: "utf8" });
32800
33097
  } catch {
32801
33098
  return [];
32802
33099
  }
@@ -32804,7 +33101,7 @@ function phpFilesUnder(abs, budget) {
32804
33101
  for (const e of entries) {
32805
33102
  if (budget.left <= 0) break;
32806
33103
  if (!e.endsWith(".php") || e.split(/[\\/]/).includes("vendor")) continue;
32807
- out.push(join17(abs, e));
33104
+ out.push(join19(abs, e));
32808
33105
  budget.left--;
32809
33106
  }
32810
33107
  return out;
@@ -32814,11 +33111,11 @@ function classmapClasses(root, auto) {
32814
33111
  const classes = /* @__PURE__ */ new Map();
32815
33112
  const budget = { left: CLASSMAP_MAX_FILES };
32816
33113
  for (const entry of auto.classmap) {
32817
- for (const f of phpFilesUnder(join17(auto.dir, entry), budget)) {
33114
+ for (const f of phpFilesUnder(join19(auto.dir, entry), budget)) {
32818
33115
  if (relative3(root, f).startsWith("..")) continue;
32819
33116
  let code;
32820
33117
  try {
32821
- code = readFileSync12(f, "utf8");
33118
+ code = readFileSync14(f, "utf8");
32822
33119
  } catch {
32823
33120
  continue;
32824
33121
  }
@@ -32838,11 +33135,11 @@ function autoloadedFunctions(root, auto) {
32838
33135
  if (auto.functions) return auto.functions;
32839
33136
  const functions = /* @__PURE__ */ new Map();
32840
33137
  for (const entry of auto.files) {
32841
- const f = join17(auto.dir, entry);
33138
+ const f = join19(auto.dir, entry);
32842
33139
  if (!isFile(f) || relative3(root, f).startsWith("..")) continue;
32843
33140
  let code;
32844
33141
  try {
32845
- code = readFileSync12(f, "utf8");
33142
+ code = readFileSync14(f, "utf8");
32846
33143
  } catch {
32847
33144
  continue;
32848
33145
  }
@@ -32862,14 +33159,14 @@ function resolvePhpClass(root, fromRel, fqcn) {
32862
33159
  for (const { prefix, dirs } of auto.psr4) {
32863
33160
  if (!name.startsWith(prefix)) continue;
32864
33161
  const rest2 = name.slice(prefix.length).split("\\");
32865
- for (const d of dirs) candidates2.push(join17(auto.dir, d, ...rest2));
33162
+ for (const d of dirs) candidates2.push(join19(auto.dir, d, ...rest2));
32866
33163
  }
32867
33164
  for (const { prefix, dirs } of auto.psr0) {
32868
33165
  if (!name.startsWith(prefix)) continue;
32869
33166
  const cut = name.lastIndexOf("\\");
32870
33167
  const ns = cut === -1 ? [] : name.slice(0, cut).split("\\");
32871
33168
  const cls = name.slice(cut + 1).split("_");
32872
- for (const d of dirs) candidates2.push(join17(auto.dir, d, ...ns, ...cls));
33169
+ for (const d of dirs) candidates2.push(join19(auto.dir, d, ...ns, ...cls));
32873
33170
  }
32874
33171
  } else {
32875
33172
  const parts = name.split("\\");
@@ -33106,7 +33403,7 @@ async function parsePhp(root, rel, code) {
33106
33403
  for (let i = left.includes("__FILE__") ? 1 : 0; i < Number(up[1] ?? 1); i++)
33107
33404
  base = dirname5(base);
33108
33405
  }
33109
- if (base && right !== void 0) candidates2.push(join17(base, right));
33406
+ if (base && right !== void 0) candidates2.push(join19(base, right));
33110
33407
  }
33111
33408
  const hit = candidates2.find((c) => isFile(c) && !relative3(root, c).startsWith(".."));
33112
33409
  if (!hit) continue;
@@ -33815,25 +34112,25 @@ var init_php = __esm({
33815
34112
  });
33816
34113
 
33817
34114
  // src/graph/languages/python.ts
33818
- import { existsSync as existsSync15, statSync as statSync3 } from "fs";
33819
- import { dirname as dirname6, join as join18, normalize as normalize3, relative as relative4, resolve as resolve5 } from "path";
34115
+ import { existsSync as existsSync17, statSync as statSync3 } from "fs";
34116
+ import { dirname as dirname6, join as join20, normalize as normalize3, relative as relative4, resolve as resolve5 } from "path";
33820
34117
  function resolvePython(root, fromRel, module, level) {
33821
34118
  const parts = module ? module.split(".") : [];
33822
34119
  const candidates2 = [];
33823
34120
  if (level > 0) {
33824
34121
  let base = dirname6(resolve5(root, fromRel));
33825
34122
  for (let i = 1; i < level; i++) base = dirname6(base);
33826
- candidates2.push(join18(base, ...parts));
34123
+ candidates2.push(join20(base, ...parts));
33827
34124
  } else {
33828
34125
  candidates2.push(resolve5(root, ...parts), resolve5(root, "src", ...parts));
33829
34126
  let dir = dirname6(resolve5(root, fromRel));
33830
34127
  while (dir.startsWith(root) && dir !== root) {
33831
- candidates2.push(join18(dir, ...parts));
34128
+ candidates2.push(join20(dir, ...parts));
33832
34129
  dir = dirname6(dir);
33833
34130
  }
33834
34131
  }
33835
34132
  for (const c of candidates2) {
33836
- for (const f of [`${c}.py`, join18(c, "__init__.py"), `${c}.pyi`]) {
34133
+ for (const f of [`${c}.py`, join20(c, "__init__.py"), `${c}.pyi`]) {
33837
34134
  if (isFile2(f) && !relative4(root, f).startsWith(".."))
33838
34135
  return normalize3(relative4(root, f)).split("\\").join("/");
33839
34136
  }
@@ -33872,7 +34169,7 @@ async function parsePython(root, rel, code) {
33872
34169
  walkRefs(stmt);
33873
34170
  }
33874
34171
  edges.push(...refs.edges());
33875
- if (!existsSync15(join18(root, rel))) {
34172
+ if (!existsSync17(join20(root, rel))) {
33876
34173
  }
33877
34174
  const errors = tree.rootNode.hasError ? tree.rootNode.descendantsOfType("ERROR").length : 0;
33878
34175
  fileNode.meta = {
@@ -34219,16 +34516,16 @@ var init_python = __esm({
34219
34516
  });
34220
34517
 
34221
34518
  // src/graph/resolve.ts
34222
- import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
34223
- import { dirname as dirname7, join as join19, normalize as normalize4, relative as relative5, resolve as resolve6 } from "path";
34519
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync15, statSync as statSync4 } from "fs";
34520
+ import { dirname as dirname7, join as join21, normalize as normalize4, relative as relative5, resolve as resolve6 } from "path";
34224
34521
  function loadTsPaths(root) {
34225
34522
  const hit = tsPathsCache.get(root);
34226
34523
  if (hit !== void 0) return hit;
34227
34524
  let out = null;
34228
34525
  try {
34229
- const file3 = join19(root, "tsconfig.json");
34230
- if (existsSync16(file3)) {
34231
- const raw = readFileSync13(file3, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
34526
+ const file3 = join21(root, "tsconfig.json");
34527
+ if (existsSync18(file3)) {
34528
+ const raw = readFileSync15(file3, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
34232
34529
  const cfg = JSON.parse(raw);
34233
34530
  const co = cfg.compilerOptions ?? {};
34234
34531
  if (co.paths) {
@@ -34266,16 +34563,16 @@ function probe(abs) {
34266
34563
  if (tryFile(abs + ext)) return abs + ext;
34267
34564
  }
34268
34565
  for (const ext of EXTS) {
34269
- const idx = join19(abs, `index${ext}`);
34566
+ const idx = join21(abs, `index${ext}`);
34270
34567
  if (tryFile(idx)) return idx;
34271
34568
  }
34272
34569
  return void 0;
34273
34570
  }
34274
34571
  function readPackageJson(dir) {
34275
34572
  try {
34276
- const file3 = join19(dir, "package.json");
34277
- if (!existsSync16(file3)) return void 0;
34278
- return JSON.parse(readFileSync13(file3, "utf8"));
34573
+ const file3 = join21(dir, "package.json");
34574
+ if (!existsSync18(file3)) return void 0;
34575
+ return JSON.parse(readFileSync15(file3, "utf8"));
34279
34576
  } catch {
34280
34577
  return void 0;
34281
34578
  }
@@ -34291,9 +34588,9 @@ function loadWorkspaces(root) {
34291
34588
  if (Array.isArray(ws)) globs.push(...ws);
34292
34589
  else if (ws?.packages) globs.push(...ws.packages);
34293
34590
  try {
34294
- const yaml = join19(root, "pnpm-workspace.yaml");
34295
- if (existsSync16(yaml)) {
34296
- for (const m of readFileSync13(yaml, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
34591
+ const yaml = join21(root, "pnpm-workspace.yaml");
34592
+ if (existsSync18(yaml)) {
34593
+ for (const m of readFileSync15(yaml, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
34297
34594
  globs.push(m[1].trim());
34298
34595
  }
34299
34596
  } catch {
@@ -34306,8 +34603,8 @@ function loadWorkspaces(root) {
34306
34603
  else {
34307
34604
  const base = resolve6(root, g.slice(0, star));
34308
34605
  try {
34309
- for (const d of readdirSync7(base, { withFileTypes: true })) {
34310
- if (d.isDirectory()) dirs.push(join19(base, d.name));
34606
+ for (const d of readdirSync8(base, { withFileTypes: true })) {
34607
+ if (d.isDirectory()) dirs.push(join21(base, d.name));
34311
34608
  }
34312
34609
  } catch {
34313
34610
  }
@@ -35450,8 +35747,8 @@ var init_link = __esm({
35450
35747
  });
35451
35748
 
35452
35749
  // src/graph/reindex.ts
35453
- import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
35454
- import { join as join20 } from "path";
35750
+ import { readFileSync as readFileSync16, statSync as statSync5 } from "fs";
35751
+ import { join as join22 } from "path";
35455
35752
  function listCodeFiles(ctx) {
35456
35753
  return import_fast_glob2.default.sync(ctx.config.graph.include, {
35457
35754
  cwd: ctx.root,
@@ -35462,7 +35759,7 @@ function listCodeFiles(ctx) {
35462
35759
  });
35463
35760
  }
35464
35761
  async function indexOne(ctx, db, rel, force) {
35465
- const abs = join20(ctx.root, rel);
35762
+ const abs = join22(ctx.root, rel);
35466
35763
  const st = statSync5(abs);
35467
35764
  const backend = getCodeBackend(ctx);
35468
35765
  const language = backend.languageOf(rel) ?? null;
@@ -35482,7 +35779,7 @@ async function indexOne(ctx, db, rel, force) {
35482
35779
  }
35483
35780
  return "skipped";
35484
35781
  }
35485
- const code = readFileSync14(abs, "utf8");
35782
+ const code = readFileSync16(abs, "utf8");
35486
35783
  const hash2 = hashText(code);
35487
35784
  const prev = db.fileIndexGet(rel);
35488
35785
  if (!force && prev?.hash === hash2 && prev.status !== "skipped") return "unchanged";
@@ -35563,7 +35860,7 @@ async function reindexCode(ctx, db, opts = {}) {
35563
35860
  summary.unsupported++;
35564
35861
  if (db.fileIndexGet(rel)?.status === "unsupported") continue;
35565
35862
  try {
35566
- const st = statSync5(join20(ctx.root, rel));
35863
+ const st = statSync5(join22(ctx.root, rel));
35567
35864
  db.fileIndexSet({
35568
35865
  path: rel,
35569
35866
  hash: "",
@@ -35579,7 +35876,7 @@ async function reindexCode(ctx, db, opts = {}) {
35579
35876
  }
35580
35877
  for (const rel of targets) {
35581
35878
  try {
35582
- if (!statSafe(join20(ctx.root, rel))) {
35879
+ if (!statSafe(join22(ctx.root, rel))) {
35583
35880
  if (db.fileIndexGet(rel)) {
35584
35881
  removeOne(db, rel);
35585
35882
  summary.removed++;
@@ -35591,7 +35888,7 @@ async function reindexCode(ctx, db, opts = {}) {
35591
35888
  const error62 = err.message;
35592
35889
  summary.errors.push({ path: rel, error: error62 });
35593
35890
  try {
35594
- const st = statSync5(join20(ctx.root, rel));
35891
+ const st = statSync5(join22(ctx.root, rel));
35595
35892
  db.fileIndexSet({
35596
35893
  path: rel,
35597
35894
  hash: "",
@@ -35694,10 +35991,10 @@ function indexFreshness(ctx, db) {
35694
35991
  const dirty = [];
35695
35992
  for (const r of rows.slice(0, 400)) {
35696
35993
  try {
35697
- const abs = join20(ctx.root, r.path);
35994
+ const abs = join22(ctx.root, r.path);
35698
35995
  const st = statSync5(abs);
35699
35996
  if (r.mtime !== null && Math.abs(st.mtimeMs - r.mtime) < 1 && st.size === r.size) continue;
35700
- if (hashText(readFileSync14(abs, "utf8")) !== r.hash) dirty.push(r.path);
35997
+ if (hashText(readFileSync16(abs, "utf8")) !== r.hash) dirty.push(r.path);
35701
35998
  } catch {
35702
35999
  dirty.push(r.path);
35703
36000
  }
@@ -35731,10 +36028,10 @@ var init_reindex = __esm({
35731
36028
  });
35732
36029
 
35733
36030
  // src/session/current.ts
35734
- import { readFileSync as readFileSync15, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
35735
- import { join as join21 } from "path";
36031
+ import { readFileSync as readFileSync17, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
36032
+ import { join as join23 } from "path";
35736
36033
  function file2(ctx) {
35737
- return join21(projectDir(ctx.id), "current-session");
36034
+ return join23(projectDir(ctx.id), "current-session");
35738
36035
  }
35739
36036
  function getCurrentSession(ctx) {
35740
36037
  if (process.env.EMINIAR_SESSION_ID) return process.env.EMINIAR_SESSION_ID;
@@ -35746,7 +36043,7 @@ function getCurrentSession(ctx) {
35746
36043
  return "unknown";
35747
36044
  }
35748
36045
  if (cached4 && cached4.file === f && cached4.mtimeMs === mtimeMs) return cached4.id;
35749
- const id = readFileSync15(f, "utf8").trim() || "unknown";
36046
+ const id = readFileSync17(f, "utf8").trim() || "unknown";
35750
36047
  cached4 = { file: f, mtimeMs, id };
35751
36048
  return id;
35752
36049
  }
@@ -35917,15 +36214,15 @@ __export(store_exports, {
35917
36214
  write: () => write
35918
36215
  });
35919
36216
  import {
35920
- existsSync as existsSync17,
35921
- mkdirSync as mkdirSync7,
35922
- readFileSync as readFileSync16,
36217
+ existsSync as existsSync19,
36218
+ mkdirSync as mkdirSync10,
36219
+ readFileSync as readFileSync18,
35923
36220
  renameSync,
35924
36221
  statSync as statSync7,
35925
36222
  unlinkSync,
35926
- writeFileSync as writeFileSync7
36223
+ writeFileSync as writeFileSync8
35927
36224
  } from "fs";
35928
- import { dirname as dirname8, join as join22, relative as relative6, sep } from "path";
36225
+ import { dirname as dirname8, join as join24, relative as relative6, sep } from "path";
35929
36226
  function isStatus(s) {
35930
36227
  return typeof s === "string" && MEMORY_STATUSES.includes(s);
35931
36228
  }
@@ -35939,13 +36236,13 @@ function memoriesRel(ctx) {
35939
36236
  return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
35940
36237
  }
35941
36238
  function memoriesDir(ctx) {
35942
- return join22(ctx.root, memoriesRel(ctx));
36239
+ return join24(ctx.root, memoriesRel(ctx));
35943
36240
  }
35944
36241
  function relOf(ctx, name) {
35945
36242
  return `${memoriesRel(ctx)}/${name}.md`;
35946
36243
  }
35947
36244
  function absOf(ctx, name) {
35948
- return join22(ctx.root, relOf(ctx, name));
36245
+ return join24(ctx.root, relOf(ctx, name));
35949
36246
  }
35950
36247
  function nameOfFile(ctx, abs) {
35951
36248
  const rel = relative6(memoriesDir(ctx), abs).split(sep).join("/");
@@ -36003,7 +36300,7 @@ function impliedFileLinks(root, body, skipPrefix) {
36003
36300
  if (!p || p.startsWith("..") || out.has(p) || skipPrefix && p.startsWith(`${skipPrefix}/`))
36004
36301
  continue;
36005
36302
  try {
36006
- if (statSync7(join22(root, p)).isFile()) out.add(p);
36303
+ if (statSync7(join24(root, p)).isFile()) out.add(p);
36007
36304
  } catch {
36008
36305
  }
36009
36306
  if (out.size >= 30) break;
@@ -36012,8 +36309,8 @@ function impliedFileLinks(root, body, skipPrefix) {
36012
36309
  }
36013
36310
  function writeMemoryFile(ctx, name, meta4, body) {
36014
36311
  const abs = absOf(ctx, name);
36015
- mkdirSync7(dirname8(abs), { recursive: true });
36016
- writeFileSync7(abs, serializeMemoryFile(meta4, `
36312
+ mkdirSync10(dirname8(abs), { recursive: true });
36313
+ writeFileSync8(abs, serializeMemoryFile(meta4, `
36017
36314
  ${body.trim()}
36018
36315
  `));
36019
36316
  }
@@ -36051,13 +36348,13 @@ function forgetInDb(db, name, rel) {
36051
36348
  function syncFile(ctx, db, name, opts = {}) {
36052
36349
  const abs = absOf(ctx, name);
36053
36350
  const rel = relOf(ctx, name);
36054
- if (!existsSync17(abs)) return void 0;
36351
+ if (!existsSync19(abs)) return void 0;
36055
36352
  let st = statSync7(abs);
36056
36353
  const row = db.memoryGetByName(name);
36057
36354
  const mtime = Math.floor(st.mtimeMs);
36058
36355
  if (!opts.force && row && row.file_mtime === mtime && row.file_size === st.size)
36059
36356
  return { changed: false, row };
36060
- let text3 = readFileSync16(abs, "utf8");
36357
+ let text3 = readFileSync18(abs, "utf8");
36061
36358
  let hash2 = hashText(text3);
36062
36359
  if (!opts.force && row && row.file_hash === hash2) {
36063
36360
  db.memoryUpsertByName({
@@ -36095,7 +36392,7 @@ function syncFile(ctx, db, name, opts = {}) {
36095
36392
  parsed.hadFrontmatter ? parsed.body : `
36096
36393
  ${parsed.body.replace(/^\s*\n/, "")}`
36097
36394
  );
36098
- writeFileSync7(abs, text3);
36395
+ writeFileSync8(abs, text3);
36099
36396
  st = statSync7(abs);
36100
36397
  hash2 = hashText(text3);
36101
36398
  }
@@ -36137,15 +36434,15 @@ tags: ${tags.join(", ")}` : ""}`,
36137
36434
  }
36138
36435
  function setMeta(ctx, db, name, patch) {
36139
36436
  const abs = absOf(ctx, name);
36140
- if (!existsSync17(abs)) return false;
36141
- const parsed = parseMemoryFile(readFileSync16(abs, "utf8"));
36437
+ if (!existsSync19(abs)) return false;
36438
+ const parsed = parseMemoryFile(readFileSync18(abs, "utf8"));
36142
36439
  const meta4 = { ...parsed.meta };
36143
36440
  for (const [k, v] of Object.entries(patch)) {
36144
36441
  if (v === void 0) delete meta4[k];
36145
36442
  else meta4[k] = v;
36146
36443
  }
36147
36444
  meta4.updated = (/* @__PURE__ */ new Date()).toISOString();
36148
- writeFileSync7(abs, serializeMemoryFile(meta4, parsed.body));
36445
+ writeFileSync8(abs, serializeMemoryFile(meta4, parsed.body));
36149
36446
  syncFile(ctx, db, name, { force: true });
36150
36447
  return true;
36151
36448
  }
@@ -36154,10 +36451,10 @@ function migrateLegacy(ctx, db) {
36154
36451
  for (const row of db.memoryLegacyRows()) {
36155
36452
  const kind = isKind(row.kind) ? row.kind : "note";
36156
36453
  const base = `${kind}/${slugify2(row.content)}`;
36157
- const taken = existsSync17(absOf(ctx, base)) || !!db.memoryGetByName(base);
36454
+ const taken = existsSync19(absOf(ctx, base)) || !!db.memoryGetByName(base);
36158
36455
  const name = taken ? `${base}-${row.id}` : base;
36159
36456
  const created = new Date(row.created_at).toISOString();
36160
- if (!existsSync17(absOf(ctx, name))) {
36457
+ if (!existsSync19(absOf(ctx, name))) {
36161
36458
  writeMemoryFile(
36162
36459
  ctx,
36163
36460
  name,
@@ -36182,7 +36479,7 @@ function reconcile(ctx, db) {
36182
36479
  const out = { files: 0, synced: 0, removed: 0, migrated: 0, skipped: [] };
36183
36480
  out.migrated = migrateLegacy(ctx, db);
36184
36481
  const dir = memoriesDir(ctx);
36185
- const files = existsSync17(dir) ? import_fast_glob3.default.sync("**/*.md", { cwd: dir, absolute: true, onlyFiles: true, dot: true }) : [];
36482
+ const files = existsSync19(dir) ? import_fast_glob3.default.sync("**/*.md", { cwd: dir, absolute: true, onlyFiles: true, dot: true }) : [];
36186
36483
  const seen = /* @__PURE__ */ new Set();
36187
36484
  for (const f of files) {
36188
36485
  const name = nameOfFile(ctx, f);
@@ -36210,7 +36507,7 @@ function write(ctx, db, input2) {
36210
36507
  );
36211
36508
  const abs = absOf(ctx, name);
36212
36509
  const prev = db.memoryGetByName(name);
36213
- const exists = !!prev || existsSync17(abs);
36510
+ const exists = !!prev || existsSync19(abs);
36214
36511
  if (exists && !input2.replace)
36215
36512
  throw new Error(
36216
36513
  `memory "${name}" already exists; pass replace: true to overwrite it, or em_memory {action: "edit"} to change part of it`
@@ -36219,7 +36516,7 @@ function write(ctx, db, input2) {
36219
36516
  let extra = {};
36220
36517
  if (exists) {
36221
36518
  try {
36222
- extra = parseMemoryFile(readFileSync16(abs, "utf8")).meta;
36519
+ extra = parseMemoryFile(readFileSync18(abs, "utf8")).meta;
36223
36520
  } catch {
36224
36521
  }
36225
36522
  }
@@ -36246,7 +36543,7 @@ function write(ctx, db, input2) {
36246
36543
  function read(ctx, db, name) {
36247
36544
  const synced = syncFile(ctx, db, name);
36248
36545
  if (!synced) return void 0;
36249
- const parsed = parseMemoryFile(readFileSync16(absOf(ctx, name), "utf8"));
36546
+ const parsed = parseMemoryFile(readFileSync18(absOf(ctx, name), "utf8"));
36250
36547
  return {
36251
36548
  name,
36252
36549
  kind: synced.row.kind,
@@ -36271,14 +36568,14 @@ function escapeRe(s) {
36271
36568
  }
36272
36569
  function edit(ctx, db, name, opts) {
36273
36570
  const abs = absOf(ctx, name);
36274
- if (!existsSync17(abs)) throw new Error(`memory "${name}" does not exist`);
36571
+ if (!existsSync19(abs)) throw new Error(`memory "${name}" does not exist`);
36275
36572
  syncFile(ctx, db, name);
36276
- const parsed = parseMemoryFile(readFileSync16(abs, "utf8"));
36573
+ const parsed = parseMemoryFile(readFileSync18(abs, "utf8"));
36277
36574
  const re = new RegExp(opts.regex ? opts.find : escapeRe(opts.find), "g");
36278
36575
  const count = (parsed.body.match(re) ?? []).length;
36279
36576
  if (!count) return 0;
36280
36577
  const body = parsed.body.replace(re, opts.replace);
36281
- writeFileSync7(
36578
+ writeFileSync8(
36282
36579
  abs,
36283
36580
  serializeMemoryFile({ ...parsed.meta, updated: (/* @__PURE__ */ new Date()).toISOString() }, body)
36284
36581
  );
@@ -36289,12 +36586,12 @@ function rename(ctx, db, from, to) {
36289
36586
  if (!isValidName(to)) throw new Error(`invalid memory name "${to}"`);
36290
36587
  const absFrom = absOf(ctx, from);
36291
36588
  const absTo = absOf(ctx, to);
36292
- if (!existsSync17(absFrom)) throw new Error(`memory "${from}" does not exist`);
36293
- if (existsSync17(absTo) || db.memoryGetByName(to)) throw new Error(`memory "${to}" already exists`);
36294
- mkdirSync7(dirname8(absTo), { recursive: true });
36589
+ if (!existsSync19(absFrom)) throw new Error(`memory "${from}" does not exist`);
36590
+ if (existsSync19(absTo) || db.memoryGetByName(to)) throw new Error(`memory "${to}" already exists`);
36591
+ mkdirSync10(dirname8(absTo), { recursive: true });
36295
36592
  renameSync(absFrom, absTo);
36296
- const parsed = parseMemoryFile(readFileSync16(absTo, "utf8"));
36297
- writeFileSync7(
36593
+ const parsed = parseMemoryFile(readFileSync18(absTo, "utf8"));
36594
+ writeFileSync8(
36298
36595
  absTo,
36299
36596
  serializeMemoryFile(
36300
36597
  { ...parsed.meta, name: to, updated: (/* @__PURE__ */ new Date()).toISOString() },
@@ -36308,11 +36605,11 @@ function rename(ctx, db, from, to) {
36308
36605
  for (const row of db.memoryList({ limit: 1e5 })) {
36309
36606
  if (!row.name || row.name === to) continue;
36310
36607
  const abs = absOf(ctx, row.name);
36311
- if (!existsSync17(abs)) continue;
36312
- const text3 = readFileSync16(abs, "utf8");
36608
+ if (!existsSync19(abs)) continue;
36609
+ const text3 = readFileSync18(abs, "utf8");
36313
36610
  if (!ref.test(text3)) continue;
36314
36611
  ref.lastIndex = 0;
36315
- writeFileSync7(abs, text3.replace(ref, `mem:${to}`));
36612
+ writeFileSync8(abs, text3.replace(ref, `mem:${to}`));
36316
36613
  syncFile(ctx, db, row.name, { force: true });
36317
36614
  refsUpdated++;
36318
36615
  }
@@ -36320,19 +36617,19 @@ function rename(ctx, db, from, to) {
36320
36617
  }
36321
36618
  function remove(ctx, db, name) {
36322
36619
  const abs = absOf(ctx, name);
36323
- const existed = existsSync17(abs) || !!db.memoryGetByName(name);
36324
- if (existsSync17(abs)) unlinkSync(abs);
36620
+ const existed = existsSync19(abs) || !!db.memoryGetByName(name);
36621
+ if (existsSync19(abs)) unlinkSync(abs);
36325
36622
  forgetInDb(db, name, relOf(ctx, name));
36326
36623
  return existed;
36327
36624
  }
36328
36625
  function gitignoreHint(ctx) {
36329
36626
  if (hinted.has(ctx.root)) return void 0;
36330
36627
  hinted.add(ctx.root);
36331
- const file3 = join22(ctx.root, ".gitignore");
36332
- if (!existsSync17(file3)) return void 0;
36628
+ const file3 = join24(ctx.root, ".gitignore");
36629
+ if (!existsSync19(file3)) return void 0;
36333
36630
  const dir = memoriesRel(ctx);
36334
36631
  const top2 = dir.split("/")[0];
36335
- const ignored = readFileSync16(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top2 || l === dir || l === `${top2}/**` || l === `${dir}/**`);
36632
+ const ignored = readFileSync18(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top2 || l === dir || l === `${top2}/**` || l === `${dir}/**`);
36336
36633
  return ignored ? `note: .gitignore ignores ${dir}, so memories stay local to this machine (remove that line to share them with the team)` : void 0;
36337
36634
  }
36338
36635
  var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, MEM_LINK_RE, PATH_RE, hinted;
@@ -36962,12 +37259,12 @@ var init_em_graph_changes = __esm({
36962
37259
  });
36963
37260
 
36964
37261
  // src/graph/architecture.ts
36965
- import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
36966
- import { join as join23 } from "path";
37262
+ import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
37263
+ import { join as join25 } from "path";
36967
37264
  function pkgJson(root) {
36968
37265
  try {
36969
- const f = join23(root, "package.json");
36970
- return existsSync18(f) ? JSON.parse(readFileSync17(f, "utf8")) : {};
37266
+ const f = join25(root, "package.json");
37267
+ return existsSync20(f) ? JSON.parse(readFileSync19(f, "utf8")) : {};
36971
37268
  } catch {
36972
37269
  return {};
36973
37270
  }
@@ -38319,11 +38616,14 @@ var init_catalog = __esm({
38319
38616
  });
38320
38617
 
38321
38618
  // src/docs/manifest.ts
38322
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
38323
- import { dirname as dirname9, join as join24 } from "path";
38619
+ import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
38620
+ import { dirname as dirname9, join as join26 } from "path";
38324
38621
  function docsTag(name) {
38325
38622
  return `${DOCS_PREFIX}${name}`;
38326
38623
  }
38624
+ function nameFromTag(tag) {
38625
+ return tag.startsWith(DOCS_PREFIX) ? tag.slice(DOCS_PREFIX.length) : void 0;
38626
+ }
38327
38627
  function isValidName2(name) {
38328
38628
  return NAME_RE2.test(name);
38329
38629
  }
@@ -38332,7 +38632,7 @@ function slugify3(input2) {
38332
38632
  return (s || "source").slice(0, 64);
38333
38633
  }
38334
38634
  function manifestPathAt(root, rel = ".eminiar/docs.json") {
38335
- return join24(root, rel);
38635
+ return join26(root, rel);
38336
38636
  }
38337
38637
  function manifestPath(ctx) {
38338
38638
  return manifestPathAt(ctx.root, ctx.config.docs.manifest);
@@ -38360,9 +38660,9 @@ function normalize5(raw) {
38360
38660
  return { version: 1, sources: out };
38361
38661
  }
38362
38662
  function readManifestAt(file3) {
38363
- if (!existsSync19(file3)) return empty();
38663
+ if (!existsSync21(file3)) return empty();
38364
38664
  try {
38365
- return normalize5(JSON.parse(readFileSync18(file3, "utf8")));
38665
+ return normalize5(JSON.parse(readFileSync20(file3, "utf8")));
38366
38666
  } catch {
38367
38667
  return empty();
38368
38668
  }
@@ -38372,12 +38672,12 @@ function readManifest(ctx) {
38372
38672
  }
38373
38673
  function writeManifest(ctx, m) {
38374
38674
  const file3 = manifestPath(ctx);
38375
- mkdirSync8(dirname9(file3), { recursive: true });
38675
+ mkdirSync11(dirname9(file3), { recursive: true });
38376
38676
  const sorted = {
38377
38677
  version: 1,
38378
38678
  sources: [...m.sources].sort((a, b) => a.name.localeCompare(b.name))
38379
38679
  };
38380
- writeFileSync8(file3, `${JSON.stringify(sorted, null, 2)}
38680
+ writeFileSync9(file3, `${JSON.stringify(sorted, null, 2)}
38381
38681
  `);
38382
38682
  return file3;
38383
38683
  }
@@ -38473,11 +38773,11 @@ var init_manifest = __esm({
38473
38773
  });
38474
38774
 
38475
38775
  // src/docs/discover.ts
38476
- import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
38477
- import { basename, join as join25, relative as relative7 } from "path";
38776
+ import { existsSync as existsSync22, readFileSync as readFileSync21, statSync as statSync8 } from "fs";
38777
+ import { basename, join as join27, relative as relative7 } from "path";
38478
38778
  function readText(file3) {
38479
38779
  try {
38480
- return readFileSync19(file3, "utf8");
38780
+ return readFileSync21(file3, "utf8");
38481
38781
  } catch {
38482
38782
  return void 0;
38483
38783
  }
@@ -38531,7 +38831,7 @@ function findProjects(root, opts) {
38531
38831
  const found = /* @__PURE__ */ new Set();
38532
38832
  for (const m of markers) {
38533
38833
  for (const f of glob(root, [`*/${m}`, `*/*/${m}`], 3)) {
38534
- const dir = join25(f, "..");
38834
+ const dir = join27(f, "..");
38535
38835
  const rel = relative7(root, dir);
38536
38836
  if (!rel || rel.split("/").some((p) => p.startsWith(".") || p === "node_modules")) continue;
38537
38837
  found.add(dir);
@@ -38540,7 +38840,7 @@ function findProjects(root, opts) {
38540
38840
  const max = opts.maxProjects ?? 40;
38541
38841
  const seenNames = /* @__PURE__ */ new Set();
38542
38842
  const nameOf = (dir) => {
38543
- const pkg = readJson3(join25(dir, "package.json"));
38843
+ const pkg = readJson3(join27(dir, "package.json"));
38544
38844
  return typeof pkg?.name === "string" ? pkg.name : void 0;
38545
38845
  };
38546
38846
  const rootName = nameOf(root);
@@ -38558,7 +38858,7 @@ function findProjects(root, opts) {
38558
38858
  return out;
38559
38859
  }
38560
38860
  function npmSignals(dir, includeDev, project) {
38561
- const pkg = readJson3(join25(dir, "package.json"));
38861
+ const pkg = readJson3(join27(dir, "package.json"));
38562
38862
  if (!pkg) return [];
38563
38863
  const sections = ["dependencies", ...includeDev ? ["devDependencies"] : []];
38564
38864
  const out = [];
@@ -38575,13 +38875,13 @@ function npmSignals(dir, includeDev, project) {
38575
38875
  }
38576
38876
  function pythonSignals(dir, project) {
38577
38877
  const out = [];
38578
- const py = readText(join25(dir, "pyproject.toml"));
38878
+ const py = readText(join27(dir, "pyproject.toml"));
38579
38879
  if (py) {
38580
38880
  const block = /dependencies\s*=\s*\[([\s\S]*?)\]/.exec(py)?.[1] ?? "";
38581
38881
  for (const m of block.matchAll(/["']([A-Za-z0-9_.-]+)/g))
38582
38882
  out.push({ kind: "pypi", key: m[1].toLowerCase(), project });
38583
38883
  }
38584
- const req = readText(join25(dir, "requirements.txt"));
38884
+ const req = readText(join27(dir, "requirements.txt"));
38585
38885
  if (req) {
38586
38886
  for (const line of req.split("\n")) {
38587
38887
  const name = /^\s*([A-Za-z0-9_.-]+)/.exec(line)?.[1];
@@ -38592,7 +38892,7 @@ function pythonSignals(dir, project) {
38592
38892
  return out;
38593
38893
  }
38594
38894
  function goSignals(dir, project) {
38595
- const mod = readText(join25(dir, "go.mod"));
38895
+ const mod = readText(join27(dir, "go.mod"));
38596
38896
  if (!mod) return [];
38597
38897
  const out = [];
38598
38898
  const block = /require\s*\(([\s\S]*?)\)/g;
@@ -38789,8 +39089,8 @@ function localCandidates(root, dir, project, isRegistered) {
38789
39089
  why: "project readme and top-level guides"
38790
39090
  });
38791
39091
  for (const d of ["docs", "doc", "documentation"]) {
38792
- const abs = join25(dir, d);
38793
- if (!existsSync20(abs)) continue;
39092
+ const abs = join27(dir, d);
39093
+ if (!existsSync22(abs)) continue;
38794
39094
  const files = glob(abs, ["**/*.md", "**/*.mdx", "**/*.rst", "**/*.txt"]).filter(
38795
39095
  (f) => !SKIP_EXT.has(f.slice(f.lastIndexOf(".")))
38796
39096
  );
@@ -38813,8 +39113,8 @@ function localCandidates(root, dir, project, isRegistered) {
38813
39113
  "docs/decisions",
38814
39114
  "decisions"
38815
39115
  ]) {
38816
- const abs = join25(dir, d);
38817
- if (!existsSync20(abs)) continue;
39116
+ const abs = join27(dir, d);
39117
+ if (!existsSync22(abs)) continue;
38818
39118
  if (out.some((c) => c.path && rel(abs).startsWith(`${c.path}/`))) break;
38819
39119
  const files = glob(abs, ["**/*.md"]);
38820
39120
  push({
@@ -39611,7 +39911,7 @@ var init_category = __esm({
39611
39911
  });
39612
39912
 
39613
39913
  // src/docs/graph.ts
39614
- import { existsSync as existsSync21, statSync as statSync9 } from "fs";
39914
+ import { existsSync as existsSync23, statSync as statSync9 } from "fs";
39615
39915
  import { dirname as dirname10, posix, relative as relative8, sep as sep2 } from "path";
39616
39916
  function manifestRel(ctx) {
39617
39917
  return relative8(ctx.root, manifestPath(ctx)).split(sep2).join("/");
@@ -39709,7 +40009,7 @@ function localDocPages(ctx, db) {
39709
40009
  }
39710
40010
  function stampOf(ctx, db) {
39711
40011
  const file3 = manifestPath(ctx);
39712
- const st = existsSync21(file3) ? statSync9(file3) : void 0;
40012
+ const st = existsSync23(file3) ? statSync9(file3) : void 0;
39713
40013
  const counts = db.graphNodeCounts();
39714
40014
  const syncs = readManifest(ctx).sources.map((s) => syncState(db, s.name)?.at ?? 0).join(",");
39715
40015
  return `${st ? `${Math.floor(st.mtimeMs)}:${st.size}` : "none"}|${counts.file ?? 0}|${counts.package ?? 0}|${syncs}`;
@@ -40848,7 +41148,7 @@ function removeDocSource(ctx, db, name) {
40848
41148
  const removed = removeSource(ctx, name);
40849
41149
  const documents = db.deleteDocumentsBySource(docsTag(name));
40850
41150
  clearSyncState(db, name);
40851
- if (removed) syncDocsGraphQuietly(ctx, db);
41151
+ if (removed || documents) syncDocsGraphQuietly(ctx, db);
40852
41152
  return { removed, documents };
40853
41153
  }
40854
41154
  function formatAdd(o) {
@@ -40903,7 +41203,8 @@ var init_add = __esm({
40903
41203
  var em_docs_exports = {};
40904
41204
  __export(em_docs_exports, {
40905
41205
  emDocs: () => emDocs,
40906
- formatDiscovery: () => formatDiscovery
41206
+ formatDiscovery: () => formatDiscovery,
41207
+ orphanedDocSources: () => orphanedDocSources
40907
41208
  });
40908
41209
  function toInput(a) {
40909
41210
  return {
@@ -40930,6 +41231,26 @@ function ago(ts) {
40930
41231
  if (s < 172800) return `${Math.round(s / 3600)}h ago`;
40931
41232
  return `${Math.round(s / 86400)}d ago`;
40932
41233
  }
41234
+ function orphanedDocSources(db, registered, stats = db.documentSourceStats(DOCS_PREFIX)) {
41235
+ const known = new Set(registered);
41236
+ const out = [];
41237
+ for (const s of stats) {
41238
+ const name = nameFromTag(s.source);
41239
+ if (name === void 0 || known.has(name)) continue;
41240
+ out.push({ name, documents: s.documents, chunks: s.chunks, latest: s.latest });
41241
+ }
41242
+ return out;
41243
+ }
41244
+ function formatOrphans(orphans2) {
41245
+ const names = orphans2.map((o) => o.name);
41246
+ return [
41247
+ `# Orphaned in the index (${orphans2.length}; pages without a manifest entry)`,
41248
+ ...orphans2.map(
41249
+ (o) => `- ${o.name} \xB7 ${o.documents} doc(s) \xB7 ${o.chunks} chunks \xB7 indexed ${ago(o.latest)}`
41250
+ ),
41251
+ `em_docs {action:"remove", names:${JSON.stringify(names)}} drops them; {action:"add", pick:"recommended"} or {action:"add", name, url|path} re-registers.`
41252
+ ];
41253
+ }
40933
41254
  function formatDiscovery(d, max = 40) {
40934
41255
  const lines = [];
40935
41256
  lines.push(
@@ -41112,19 +41433,30 @@ var init_em_docs = __esm({
41112
41433
  case "remove": {
41113
41434
  const names = [...args.name ? [args.name] : [], ...args.names ?? []];
41114
41435
  if (!names.length) return errorResult("remove needs `name` or `names`");
41436
+ let unknown2 = 0;
41115
41437
  const lines = names.map((n) => {
41116
41438
  const r = removeDocSource(ctx, db, n);
41117
- return r.removed ? `removed ${n} (${r.documents} document(s) dropped)` : `${n}: not registered${r.documents ? ` (${r.documents} orphan document(s) dropped)` : ""}`;
41439
+ if (r.removed) return `removed ${n} (${r.documents} document(s) dropped)`;
41440
+ if (r.documents) return `removed ${n} (orphaned; ${r.documents} document(s) dropped)`;
41441
+ unknown2++;
41442
+ return `${n}: not registered and not in the index; em_docs {action:"list"} shows both`;
41118
41443
  });
41119
- return done(lines.join("\n"));
41444
+ const out = lines.join("\n");
41445
+ return unknown2 === names.length ? errorResult(out) : done(out);
41120
41446
  }
41121
41447
  case "list": {
41122
41448
  const m = readManifest(ctx);
41123
- if (!m.sources.length)
41124
- return done(
41125
- `no documentation sources registered (${manifestPath(ctx)}). em_docs {action:"discover"} finds local docs and the libraries, services and APIs this project uses.`
41126
- );
41127
- const stats = new Map(db.documentSourceStats(DOCS_PREFIX).map((s) => [s.source, s]));
41449
+ const sourceStats = db.documentSourceStats(DOCS_PREFIX);
41450
+ const orphans2 = orphanedDocSources(
41451
+ db,
41452
+ m.sources.map((s) => s.name),
41453
+ sourceStats
41454
+ );
41455
+ if (!m.sources.length) {
41456
+ const none = `no documentation sources registered (${manifestPath(ctx)}). em_docs {action:"discover"} finds local docs and the libraries, services and APIs this project uses.`;
41457
+ return done(orphans2.length ? [none, "", ...formatOrphans(orphans2)].join("\n") : none);
41458
+ }
41459
+ const stats = new Map(sourceStats.map((s) => [s.source, s]));
41128
41460
  const catalog = mergedCatalog(ctx.config.docs.catalog);
41129
41461
  const categorised = m.sources.map((s) => ({
41130
41462
  s,
@@ -41155,9 +41487,10 @@ var init_em_docs = __esm({
41155
41487
  `- ${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}`
41156
41488
  );
41157
41489
  }
41490
+ if (orphans2.length) lines.push("", ...formatOrphans(orphans2));
41158
41491
  lines.push(
41159
41492
  "",
41160
- `${m.sources.length} source(s); em_docs {action:"lookup", query} searches them all, {action:"sync"} refreshes.`
41493
+ `${m.sources.length} source(s)${orphans2.length ? ` + ${orphans2.length} orphaned` : ""}; em_docs {action:"lookup", query} searches them all, {action:"sync"} refreshes.`
41161
41494
  );
41162
41495
  return done(lines.join("\n"));
41163
41496
  }
@@ -41203,10 +41536,18 @@ ${r.urls.map((u) => ` ${u}`).join("\n")}
41203
41536
  raw += r.hits.reduce((n, h) => n + h.chunk.body.length, 0);
41204
41537
  responses.push(r);
41205
41538
  }
41206
- const out = formatResults(
41207
- responses,
41208
- args.max_tokens ?? ctx.config.search.defaultMaxTokens
41539
+ let out = formatResults(responses, args.max_tokens ?? ctx.config.search.defaultMaxTokens);
41540
+ const hitSources = new Set(
41541
+ responses.flatMap((r) => r.hits.map((h) => h.chunk.document.source))
41209
41542
  );
41543
+ const orphaned = orphanedDocSources(
41544
+ db,
41545
+ m.sources.map((s) => s.name)
41546
+ ).filter((o) => hitSources.has(docsTag(o.name)));
41547
+ if (orphaned.length)
41548
+ out += `
41549
+
41550
+ note: ${orphaned.map((o) => o.name).join(", ")} ${orphaned.length === 1 ? "is" : "are"} not in the manifest (orphaned pages); em_docs {action:"list"} explains`;
41210
41551
  recordStat(ctx, "em_docs", {
41211
41552
  rawChars: raw,
41212
41553
  returnedChars: out.length,
@@ -42467,6 +42808,9 @@ function memoriesForFile(db, rel, opts = {}) {
42467
42808
  trace
42468
42809
  };
42469
42810
  }
42811
+ function dependentFiles(db, paths2) {
42812
+ return db.graphDependentFileCounts(paths2, DEPENDENCY_KINDS);
42813
+ }
42470
42814
  function riskLine(path3, f) {
42471
42815
  if (f.risk < RISKY && !f.bugOrigins) return void 0;
42472
42816
  const parts = [`${f.fixes}/${f.commits} commits were fixes`];
@@ -42494,10 +42838,10 @@ function fileContext(db, rel, opts = {}) {
42494
42838
  }
42495
42839
  const fanKey = `fanin:${rel}`;
42496
42840
  if (opts.edit && !seen.has(fanKey)) {
42497
- const fanIn = db.metricsGet([fileId(rel)])[0]?.fan_in ?? 0;
42498
- if (fanIn >= FAN_IN) {
42841
+ const dependents = dependentFiles(db, [rel]).get(rel) ?? 0;
42842
+ if (dependents >= FAN_IN) {
42499
42843
  lines.push(
42500
- `eminiar graph: ${rel} is imported by ${fanIn} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
42844
+ `eminiar graph: ${rel} is imported by ${dependents} files; em_graph_impact {node:"${fileId(rel)}"} lists them before you change an exported signature.`
42501
42845
  );
42502
42846
  keys.push(fanKey);
42503
42847
  }
@@ -42554,6 +42898,7 @@ var init_recall = __esm({
42554
42898
  "use strict";
42555
42899
  init_esm_shims();
42556
42900
  init_ids();
42901
+ init_metrics();
42557
42902
  init_model();
42558
42903
  init_pipeline();
42559
42904
  init_query();
@@ -44231,14 +44576,14 @@ __export(ingest_exports, {
44231
44576
  usageEnabled: () => usageEnabled
44232
44577
  });
44233
44578
  import { createHash as createHash4 } from "crypto";
44234
- import { closeSync, existsSync as existsSync22, openSync, readdirSync as readdirSync8, readSync, statSync as statSync10 } from "fs";
44235
- import { homedir as homedir3 } from "os";
44236
- import { basename as basename2, dirname as dirname11, join as join26, resolve as resolve7, sep as sep3 } from "path";
44579
+ import { closeSync, existsSync as existsSync24, openSync, readdirSync as readdirSync9, readSync, statSync as statSync10 } from "fs";
44580
+ import { homedir as homedir4 } from "os";
44581
+ import { basename as basename2, dirname as dirname11, join as join28, resolve as resolve7, sep as sep3 } from "path";
44237
44582
  function isValidSessionId(id) {
44238
44583
  return SESSION_ID_RE.test(id);
44239
44584
  }
44240
44585
  function claudeDir2() {
44241
- return process.env.EMINIAR_CLAUDE_DIR || join26(homedir3(), ".claude");
44586
+ return process.env.EMINIAR_CLAUDE_DIR || join28(homedir4(), ".claude");
44242
44587
  }
44243
44588
  function insideDir(path3, dir) {
44244
44589
  const d = resolve7(dir);
@@ -44257,7 +44602,7 @@ function transcriptSlug(root) {
44257
44602
  return root.replace(/[^A-Za-z0-9]/g, "-");
44258
44603
  }
44259
44604
  function transcriptDirFor(root) {
44260
- return join26(claudeDir2(), "projects", transcriptSlug(root));
44605
+ return join28(claudeDir2(), "projects", transcriptSlug(root));
44261
44606
  }
44262
44607
  function usageEnabled(ctx) {
44263
44608
  return ctx.config.usage.enabled !== false;
@@ -44427,7 +44772,7 @@ function add(a, b) {
44427
44772
  }
44428
44773
  function listJsonl(dir) {
44429
44774
  try {
44430
- return readdirSync8(dir).filter((f) => f.endsWith(".jsonl")).map((f) => join26(dir, f));
44775
+ return readdirSync9(dir).filter((f) => f.endsWith(".jsonl")).map((f) => join28(dir, f));
44431
44776
  } catch {
44432
44777
  return [];
44433
44778
  }
@@ -44447,7 +44792,7 @@ function firstEntrySessionId(path3) {
44447
44792
  function sidechainFilesFor(db, dir, sessionId) {
44448
44793
  if (!isValidSessionId(sessionId)) return [];
44449
44794
  const inside = (p) => insideDir(p, dir) && p.endsWith(".jsonl");
44450
- const out = listJsonl(join26(dir, sessionId, "subagents")).filter(inside);
44795
+ const out = listJsonl(join28(dir, sessionId, "subagents")).filter(inside);
44451
44796
  for (const path3 of listJsonl(dir)) {
44452
44797
  if (!inside(path3) || !basename2(path3).startsWith("agent-")) continue;
44453
44798
  const known = db.transcriptFileGet(path3)?.session_id;
@@ -44491,11 +44836,11 @@ function ingestSession(db, root, sessionId, transcriptPath, opts = {}) {
44491
44836
  function ingestProject(db, root, opts = {}) {
44492
44837
  const dir = transcriptDirFor(root);
44493
44838
  const files = new Set(listJsonl(dir));
44494
- if (existsSync22(dir)) {
44839
+ if (existsSync24(dir)) {
44495
44840
  try {
44496
- for (const entry of readdirSync8(dir, { withFileTypes: true })) {
44841
+ for (const entry of readdirSync9(dir, { withFileTypes: true })) {
44497
44842
  if (entry.isDirectory())
44498
- for (const sub of listJsonl(join26(dir, entry.name, "subagents"))) files.add(sub);
44843
+ for (const sub of listJsonl(join28(dir, entry.name, "subagents"))) files.add(sub);
44499
44844
  }
44500
44845
  } catch {
44501
44846
  }
@@ -44546,12 +44891,17 @@ function usageLine(ctx, sessionId) {
44546
44891
  if (!usageEnabled(ctx)) return void 0;
44547
44892
  try {
44548
44893
  const db = getStorage(ctx);
44549
- ingestSession(db, ctx.root, sessionId, void 0, { overrides: ctx.config.usage.pricing });
44894
+ let failed;
44895
+ try {
44896
+ ingestSession(db, ctx.root, sessionId, void 0, { overrides: ctx.config.usage.pricing });
44897
+ } catch (err) {
44898
+ failed = `usage: ingest failed: ${err.message}`;
44899
+ }
44550
44900
  const b = usageBrief(db, ctx, { sessionId });
44551
- if (!b.todayUsd && !b.sessionUsd) return void 0;
44552
- return `usage: today ${formatUsd(b.todayUsd)} \xB7 session ${formatUsd(b.sessionUsd)} \xB7 see em_usage`;
44553
- } catch {
44554
- return void 0;
44901
+ const line = b.todayUsd || b.sessionUsd ? `usage: today ${formatUsd(b.todayUsd)} \xB7 session ${formatUsd(b.sessionUsd)} \xB7 see em_usage` : void 0;
44902
+ return [line, failed].filter((s) => s).join("\n") || void 0;
44903
+ } catch (err) {
44904
+ return `usage: unavailable: ${err.message}`;
44555
44905
  }
44556
44906
  }
44557
44907
  function mergeByTool(rows) {
@@ -45825,7 +46175,10 @@ var init_em_search = __esm({
45825
46175
  }
45826
46176
  }
45827
46177
  const out = formatResults(responses, args.max_tokens ?? ctx.config.search.defaultMaxTokens);
45828
- const note = mode !== "lexical" && mode !== "regex" && !vectorSearch && ctx.config.embeddings.enabled ? "\n\n(embeddings: model still loading \u2014 lexical results only this time)" : "";
46178
+ let note = mode !== "lexical" && mode !== "regex" && !vectorSearch && ctx.config.embeddings.enabled ? "\n\n(embeddings: model still loading \u2014 lexical results only this time)" : "";
46179
+ const sources = Array.isArray(args.source) ? args.source : args.source ? [args.source] : [];
46180
+ if (mode === "semantic" && !ctx.config.docs.embed && sources.some((s) => s.startsWith("docs:")) && responses.every((r) => r.hits.length === 0))
46181
+ 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})';
45829
46182
  recordStat(ctx, "em_search", {
45830
46183
  rawChars: responses.reduce(
45831
46184
  (a, r) => a + r.hits.reduce((b, h) => b + h.chunk.body.length, 0),
@@ -45841,14 +46194,14 @@ var init_em_search = __esm({
45841
46194
  });
45842
46195
 
45843
46196
  // src/ui/api.ts
45844
- import { existsSync as existsSync23, readdirSync as readdirSync9 } from "fs";
45845
- import { join as join27, relative as relative9 } from "path";
46197
+ import { existsSync as existsSync25, readdirSync as readdirSync10 } from "fs";
46198
+ import { join as join29, relative as relative9 } from "path";
45846
46199
  function listProjects() {
45847
46200
  const dir = paths().projects;
45848
46201
  const out = [];
45849
- for (const id of readdirSync9(dir, { withFileTypes: true })) {
46202
+ for (const id of readdirSync10(dir, { withFileTypes: true })) {
45850
46203
  if (!id.isDirectory()) continue;
45851
- const db = join27(dir, id.name, "eminiar.db");
46204
+ const db = join29(dir, id.name, "eminiar.db");
45852
46205
  try {
45853
46206
  const a = new SqliteAdapter(db, { vectors: false });
45854
46207
  out.push({ id: id.name, root: a.sessionLatest()?.project_root ?? null });
@@ -45874,8 +46227,8 @@ function statsPayload(ctx) {
45874
46227
  const db = getStorage(ctx);
45875
46228
  const sessionId = getCurrentSession(ctx);
45876
46229
  const g = globalDb();
45877
- const onboardingDir = join27(memoriesDir(ctx), "onboarding");
45878
- const onboarding = existsSync23(onboardingDir) ? readdirSync9(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
46230
+ const onboardingDir = join29(memoriesDir(ctx), "onboarding");
46231
+ const onboarding = existsSync25(onboardingDir) ? readdirSync10(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
45879
46232
  return {
45880
46233
  project: ctx.root,
45881
46234
  projectId: ctx.id,
@@ -46477,8 +46830,8 @@ var init_namepath = __esm({
46477
46830
  });
46478
46831
 
46479
46832
  // src/code/symbols.ts
46480
- import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
46481
- import { join as join28 } from "path";
46833
+ import { existsSync as existsSync26, readFileSync as readFileSync22 } from "fs";
46834
+ import { join as join30 } from "path";
46482
46835
  function isExported2(n) {
46483
46836
  if (!n.meta) return false;
46484
46837
  try {
@@ -46514,7 +46867,7 @@ var init_symbols2 = __esm({
46514
46867
  });
46515
46868
 
46516
46869
  // src/ui/graph-api.ts
46517
- import { readFileSync as readFileSync21, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
46870
+ import { readFileSync as readFileSync23, realpathSync as realpathSync2, statSync as statSync11 } from "fs";
46518
46871
  import { relative as relative10, resolve as resolve8 } from "path";
46519
46872
  function metaOf2(row) {
46520
46873
  if (!row?.meta) return {};
@@ -46600,7 +46953,7 @@ function filePayload(ctx, rel) {
46600
46953
  }));
46601
46954
  if (st.size > MAX_CODE_BYTES)
46602
46955
  return { path: clean, tooLarge: true, size: st.size, status: row?.status ?? null, symbols };
46603
- const content = readFileSync21(real, "utf8");
46956
+ const content = readFileSync23(real, "utf8");
46604
46957
  return {
46605
46958
  path: clean,
46606
46959
  size: st.size,
@@ -47286,8 +47639,8 @@ var init_lineage = __esm({
47286
47639
  });
47287
47640
 
47288
47641
  // src/ui/history-graph-api.ts
47289
- import { existsSync as existsSync25 } from "fs";
47290
- import { join as join29 } from "path";
47642
+ import { existsSync as existsSync27 } from "fs";
47643
+ import { join as join31 } from "path";
47291
47644
  function parseList(raw) {
47292
47645
  if (!raw) return [];
47293
47646
  try {
@@ -47526,7 +47879,7 @@ function buildHistoryGraph(ctx, db, m, opts = {}) {
47526
47879
  path: p,
47527
47880
  line: null,
47528
47881
  root: id === focusId,
47529
- missing: existsSync25(join29(ctx.root, p)) ? void 0 : true,
47882
+ missing: existsSync27(join31(ctx.root, p)) ? void 0 : true,
47530
47883
  risk: h?.risk,
47531
47884
  fixes: h?.fixes
47532
47885
  });
@@ -47885,9 +48238,9 @@ __export(server_exports, {
47885
48238
  uiDir: () => uiDir
47886
48239
  });
47887
48240
  import { spawn as spawn2 } from "child_process";
47888
- import { existsSync as existsSync26, readFileSync as readFileSync22, statSync as statSync12 } from "fs";
48241
+ import { existsSync as existsSync28, readFileSync as readFileSync24, statSync as statSync12 } from "fs";
47889
48242
  import { createServer } from "http";
47890
- import { extname as extname3, join as join30, normalize as normalize6 } from "path";
48243
+ import { extname as extname3, join as join32, normalize as normalize6 } from "path";
47891
48244
  import { URL as URL2 } from "url";
47892
48245
  function json2(res, status, body) {
47893
48246
  res.writeHead(status, {
@@ -47909,7 +48262,7 @@ function strList(v) {
47909
48262
  return void 0;
47910
48263
  }
47911
48264
  function uiDir() {
47912
- return process.env.EMINIAR_UI_DIR || join30(pluginRoot(), "dist", "ui");
48265
+ return process.env.EMINIAR_UI_DIR || join32(pluginRoot(), "dist", "ui");
47913
48266
  }
47914
48267
  function originAllowed(req, port) {
47915
48268
  const host = req.headers.host ?? "";
@@ -47933,25 +48286,25 @@ function originAllowed(req, port) {
47933
48286
  function serveStatic(res, p) {
47934
48287
  const dir = uiDir();
47935
48288
  const rel = p === "/" ? "index.html" : p.replace(/^\/+/, "");
47936
- const file3 = normalize6(join30(dir, rel));
48289
+ const file3 = normalize6(join32(dir, rel));
47937
48290
  if (!file3.startsWith(normalize6(dir))) return false;
47938
- if (!existsSync26(file3) || !statSync12(file3).isFile()) return false;
48291
+ if (!existsSync28(file3) || !statSync12(file3).isFile()) return false;
47939
48292
  const type = MIME[extname3(file3).toLowerCase()] ?? "application/octet-stream";
47940
48293
  if (rel === "index.html") {
47941
48294
  const stamp = (name) => {
47942
48295
  try {
47943
- return `/${name}?v=${Math.floor(statSync12(join30(dir, name)).mtimeMs)}`;
48296
+ return `/${name}?v=${Math.floor(statSync12(join32(dir, name)).mtimeMs)}`;
47944
48297
  } catch {
47945
48298
  return `/${name}`;
47946
48299
  }
47947
48300
  };
47948
- const html = readFileSync22(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
48301
+ const html = readFileSync24(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
47949
48302
  res.writeHead(200, { "content-type": type, "cache-control": "no-store" });
47950
48303
  res.end(html);
47951
48304
  return true;
47952
48305
  }
47953
48306
  res.writeHead(200, { "content-type": type, "cache-control": "no-cache" });
47954
- res.end(readFileSync22(file3));
48307
+ res.end(readFileSync24(file3));
47955
48308
  return true;
47956
48309
  }
47957
48310
  async function serve(opts) {
@@ -48390,7 +48743,7 @@ __export(statusline_exports, {
48390
48743
  renderStatusline: () => renderStatusline,
48391
48744
  usageSegments: () => usageSegments
48392
48745
  });
48393
- import { existsSync as existsSync27, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
48746
+ import { existsSync as existsSync29, readFileSync as readFileSync25, writeFileSync as writeFileSync10 } from "fs";
48394
48747
  function epochMs(v) {
48395
48748
  if (typeof v === "number" && Number.isFinite(v)) return v < 1e12 ? v * 1e3 : v;
48396
48749
  if (typeof v === "string") {
@@ -48452,8 +48805,8 @@ function renderStatusline(raw) {
48452
48805
  const key = cacheKey(input2);
48453
48806
  const cache3 = paths().statuslineCache;
48454
48807
  try {
48455
- if (existsSync27(cache3)) {
48456
- const c = JSON.parse(readFileSync23(cache3, "utf8"));
48808
+ if (existsSync29(cache3)) {
48809
+ const c = JSON.parse(readFileSync25(cache3, "utf8"));
48457
48810
  if (Date.now() - c.at < CACHE_TTL_MS && c.key === key) return c.line;
48458
48811
  }
48459
48812
  } catch {
@@ -48484,7 +48837,7 @@ function renderStatusline(raw) {
48484
48837
  line = `eminiar \xB7 unavailable (${err.message.slice(0, 40)})`;
48485
48838
  }
48486
48839
  try {
48487
- writeFileSync9(cache3, JSON.stringify({ at: Date.now(), line, key }));
48840
+ writeFileSync10(cache3, JSON.stringify({ at: Date.now(), line, key }));
48488
48841
  } catch {
48489
48842
  }
48490
48843
  return line;
@@ -48518,9 +48871,11 @@ var USAGE = `eminiar v${version2()} \u2014 local-first context, memory & knowled
48518
48871
  Usage: eminiar <command> [options]
48519
48872
 
48520
48873
  Setup
48521
- install [--no-model] [--method plugin|settings] [--team] [--yes] [--force] Install into Claude Code
48874
+ install [--no-model] [--method plugin|settings] [--cursor] [--team] [--yes] [--force]
48875
+ Install into Claude Code; --cursor also registers with Cursor
48876
+ (hooks, MCP server, skills under ~/.cursor)
48522
48877
  upgrade Update to the latest version and re-register
48523
- uninstall [--purge-data] Remove from Claude Code (data kept unless --purge-data)
48878
+ uninstall [--purge-data] Remove from Claude Code and Cursor (data kept unless --purge-data)
48524
48879
  doctor [--json] [--fix] Check installation health; --fix re-pins node/root and clears
48525
48880
  Claude Code's cached launch failure
48526
48881
  replace-context-mode [--yes] Remove the context-mode plugin after parity sign-off
@@ -48548,7 +48903,7 @@ Use
48548
48903
  ui [--port 7331] [--no-open] Web dashboard: stats, memories, graph, search, sessions, actions
48549
48904
  (alias: serve)
48550
48905
  statusline Claude Code statusLine command (reads JSON on stdin)
48551
- hook [--event E] Run the hook handler manually (reads JSON on stdin)
48906
+ hook [--event E] [--client claude|cursor] Run the hook handler manually (reads JSON on stdin)
48552
48907
  version | help
48553
48908
  `;
48554
48909
  function text2(r) {
@@ -48578,6 +48933,7 @@ var commands = {
48578
48933
  node: { type: "string" },
48579
48934
  yes: { type: "boolean" },
48580
48935
  statusline: { type: "boolean" },
48936
+ cursor: { type: "boolean" },
48581
48937
  force: { type: "boolean" }
48582
48938
  },
48583
48939
  strict: false
@@ -48585,6 +48941,7 @@ var commands = {
48585
48941
  const { install: install2 } = await Promise.resolve().then(() => (init_install(), install_exports));
48586
48942
  return install2({
48587
48943
  force: !!values.force,
48944
+ cursor: !!values.cursor,
48588
48945
  noModel: !!values["no-model"],
48589
48946
  method: values.method === "settings" ? "settings" : values.method === "plugin" ? "plugin" : void 0,
48590
48947
  team: !!values.team,
@@ -49037,7 +49394,8 @@ try {
49037
49394
  } catch (err) {
49038
49395
  console.error(`eminiar ${cmd} failed: ${err.message}`);
49039
49396
  if (process.env.EMINIAR_DEBUG) console.error(err.stack);
49040
- if (cmd !== "doctor") console.error(formatDoctor(runDoctor()));
49397
+ if (cmd === "usage") console.error("run `eminiar doctor` to check the installation");
49398
+ else if (cmd !== "doctor") console.error(formatDoctor(runDoctor()));
49041
49399
  process.exit(1);
49042
49400
  }
49043
49401
  /*! Bundled license information: