eminiar 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4602,7 +4602,7 @@ function isRef(value) {
4602
4602
  function cloneIssues(issues) {
4603
4603
  return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
4604
4604
  }
4605
- function isRecursive(inst, stack, resolve7) {
4605
+ function isRecursive(inst, stack, resolve8) {
4606
4606
  const cached5 = recursive.get(inst);
4607
4607
  if (cached5 !== void 0)
4608
4608
  return cached5 ? PROVEN : NONE;
@@ -4612,7 +4612,7 @@ function isRecursive(inst, stack, resolve7) {
4612
4612
  let result = NONE;
4613
4613
  const check3 = (child) => {
4614
4614
  if (result !== PROVEN && child?._zod) {
4615
- const answer = isRecursive(child, stack, resolve7);
4615
+ const answer = isRecursive(child, stack, resolve8);
4616
4616
  if (answer > result)
4617
4617
  result = answer;
4618
4618
  }
@@ -4623,7 +4623,7 @@ function isRecursive(inst, stack, resolve7) {
4623
4623
  const desc = Object.getOwnPropertyDescriptor(sh, key);
4624
4624
  if (spread && !desc.enumerable)
4625
4625
  continue;
4626
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve7) : NONE;
4626
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve8) : NONE;
4627
4627
  if (child > answer)
4628
4628
  answer = child;
4629
4629
  }
@@ -4687,7 +4687,7 @@ function isRecursive(inst, stack, resolve7) {
4687
4687
  break;
4688
4688
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
4689
4689
  case "lazy": {
4690
- const inner = def._cachedInner ?? (resolve7 ? inst._zod.innerType : void 0);
4690
+ const inner = def._cachedInner ?? (resolve8 ? inst._zod.innerType : void 0);
4691
4691
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
4692
4692
  break;
4693
4693
  }
@@ -20776,7 +20776,8 @@ var init_config = __esm({
20776
20776
  "**/*.mjs",
20777
20777
  "**/*.cjs",
20778
20778
  "**/*.py",
20779
- "**/*.go"
20779
+ "**/*.go",
20780
+ "**/*.php"
20780
20781
  ]),
20781
20782
  exclude: external_exports.array(external_exports.string()).default([
20782
20783
  "**/node_modules/**",
@@ -20790,7 +20791,8 @@ var init_config = __esm({
20790
20791
  "**/__pycache__/**",
20791
20792
  "**/site-packages/**",
20792
20793
  "**/vendor/**",
20793
- "**/*.min.js"
20794
+ "**/*.min.js",
20795
+ "**/*.blade.php"
20794
20796
  ])
20795
20797
  }).prefault({}),
20796
20798
  git: external_exports.object({
@@ -22397,12 +22399,18 @@ var init_adapter = __esm({
22397
22399
  const ids = this.db.prepare("SELECT id FROM graph_nodes WHERE source = ? AND path = ?").all(source, path3).map((r) => r.id);
22398
22400
  const fileId2 = `file:${path3}`;
22399
22401
  const all = ids.includes(fileId2) ? ids : [...ids, fileId2];
22402
+ this.db.prepare(
22403
+ "DELETE FROM graph_edges WHERE source = ? AND kind = 'handles' AND dst IN (SELECT id FROM graph_nodes WHERE source = ? AND path = ? AND kind = 'route')"
22404
+ ).run(source, source, path3);
22405
+ const foreignRoute = "AND NOT (kind = 'handles' AND dst IN (SELECT id FROM graph_nodes WHERE kind = 'route' AND path IS NOT ?))";
22400
22406
  for (let i = 0; i < all.length; i += 400) {
22401
22407
  const slice = all.slice(i, i + 400);
22402
22408
  const ph = slice.map(() => "?").join(",");
22403
- this.db.prepare(`DELETE FROM graph_edges WHERE source = ? AND src IN (${ph})`).run(source, ...slice);
22409
+ this.db.prepare(`DELETE FROM graph_edges WHERE source = ? AND src IN (${ph}) ${foreignRoute}`).run(source, ...slice, path3);
22404
22410
  }
22405
- this.db.prepare("DELETE FROM graph_edges WHERE source = ? AND src LIKE ? ESCAPE '\\'").run(source, `sym:${path3.replace(/[%_\\]/g, "\\$&")}#%`);
22411
+ this.db.prepare(
22412
+ `DELETE FROM graph_edges WHERE source = ? AND src LIKE ? ESCAPE '\\' ${foreignRoute}`
22413
+ ).run(source, `sym:${path3.replace(/[%_\\]/g, "\\$&")}#%`, path3);
22406
22414
  if (opts.keepFile) {
22407
22415
  this.db.prepare("DELETE FROM graph_nodes WHERE source = ? AND path = ? AND kind != 'file'").run(source, path3);
22408
22416
  } else {
@@ -23192,13 +23200,13 @@ import { spawn } from "child_process";
23192
23200
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
23193
23201
  import { join as join13 } from "path";
23194
23202
  function mcpHandshake(bin, ms = 15e3) {
23195
- return new Promise((resolve7) => {
23203
+ return new Promise((resolve8) => {
23196
23204
  const child = spawn(bin, [], { stdio: ["pipe", "pipe", "pipe"] });
23197
23205
  let buf = "";
23198
23206
  let err = "";
23199
23207
  const timer = setTimeout(() => {
23200
23208
  child.kill();
23201
- resolve7(`no reply in ${ms / 1e3}s${err ? `: ${err.trim().slice(-200)}` : ""}`);
23209
+ resolve8(`no reply in ${ms / 1e3}s${err ? `: ${err.trim().slice(-200)}` : ""}`);
23202
23210
  }, ms);
23203
23211
  child.stdout.on("data", (d) => {
23204
23212
  buf += d.toString();
@@ -23206,7 +23214,7 @@ function mcpHandshake(bin, ms = 15e3) {
23206
23214
  clearTimeout(timer);
23207
23215
  child.kill();
23208
23216
  const m = /"serverInfo":\{"name":"([^"]+)","version":"([^"]+)"/.exec(buf);
23209
- resolve7(m ? `${m[1]} v${m[2]}` : "ok");
23217
+ resolve8(m ? `${m[1]} v${m[2]}` : "ok");
23210
23218
  }
23211
23219
  });
23212
23220
  child.stderr.on("data", (d) => {
@@ -23214,7 +23222,7 @@ function mcpHandshake(bin, ms = 15e3) {
23214
23222
  });
23215
23223
  child.on("error", (e) => {
23216
23224
  clearTimeout(timer);
23217
- resolve7(`spawn failed: ${e.message}`);
23225
+ resolve8(`spawn failed: ${e.message}`);
23218
23226
  });
23219
23227
  child.stdin.write(
23220
23228
  `${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "doctor", version: "0" } } })}
@@ -27473,15 +27481,15 @@ var require_pattern = __commonJS({
27473
27481
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
27474
27482
  function partitionAbsoluteAndRelative(patterns) {
27475
27483
  const absolute = [];
27476
- const relative9 = [];
27484
+ const relative10 = [];
27477
27485
  for (const pattern2 of patterns) {
27478
27486
  if (isAbsolute(pattern2)) {
27479
27487
  absolute.push(pattern2);
27480
27488
  } else {
27481
- relative9.push(pattern2);
27489
+ relative10.push(pattern2);
27482
27490
  }
27483
27491
  }
27484
- return [absolute, relative9];
27492
+ return [absolute, relative10];
27485
27493
  }
27486
27494
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
27487
27495
  function isAbsolute(pattern2) {
@@ -27912,11 +27920,11 @@ var require_out = __commonJS({
27912
27920
  async.read(path3, getSettings(optionsOrSettingsOrCallback), callback);
27913
27921
  }
27914
27922
  exports.stat = stat;
27915
- function statSync10(path3, optionsOrSettings) {
27923
+ function statSync11(path3, optionsOrSettings) {
27916
27924
  const settings = getSettings(optionsOrSettings);
27917
27925
  return sync.read(path3, settings);
27918
27926
  }
27919
- exports.statSync = statSync10;
27927
+ exports.statSync = statSync11;
27920
27928
  function getSettings(settingsOrOptions = {}) {
27921
27929
  if (settingsOrOptions instanceof settings_1.default) {
27922
27930
  return settingsOrOptions;
@@ -28597,41 +28605,41 @@ var require_queue = __commonJS({
28597
28605
  queue.drained = drained;
28598
28606
  return queue;
28599
28607
  function push(value) {
28600
- var p = new Promise(function(resolve7, reject) {
28608
+ var p = new Promise(function(resolve8, reject) {
28601
28609
  pushCb(value, function(err, result) {
28602
28610
  if (err) {
28603
28611
  reject(err);
28604
28612
  return;
28605
28613
  }
28606
- resolve7(result);
28614
+ resolve8(result);
28607
28615
  });
28608
28616
  });
28609
28617
  p.catch(noop);
28610
28618
  return p;
28611
28619
  }
28612
28620
  function unshift(value) {
28613
- var p = new Promise(function(resolve7, reject) {
28621
+ var p = new Promise(function(resolve8, reject) {
28614
28622
  unshiftCb(value, function(err, result) {
28615
28623
  if (err) {
28616
28624
  reject(err);
28617
28625
  return;
28618
28626
  }
28619
- resolve7(result);
28627
+ resolve8(result);
28620
28628
  });
28621
28629
  });
28622
28630
  p.catch(noop);
28623
28631
  return p;
28624
28632
  }
28625
28633
  function drained() {
28626
- var p = new Promise(function(resolve7) {
28634
+ var p = new Promise(function(resolve8) {
28627
28635
  process.nextTick(function() {
28628
28636
  if (queue.idle()) {
28629
- resolve7();
28637
+ resolve8();
28630
28638
  } else {
28631
28639
  var previousDrain = queue.drain;
28632
28640
  queue.drain = function() {
28633
28641
  if (typeof previousDrain === "function") previousDrain();
28634
- resolve7();
28642
+ resolve8();
28635
28643
  queue.drain = previousDrain;
28636
28644
  };
28637
28645
  }
@@ -29128,9 +29136,9 @@ var require_stream3 = __commonJS({
29128
29136
  });
29129
29137
  }
29130
29138
  _getStat(filepath) {
29131
- return new Promise((resolve7, reject) => {
29139
+ return new Promise((resolve8, reject) => {
29132
29140
  this._stat(filepath, this._fsStatSettings, (error62, stats) => {
29133
- return error62 === null ? resolve7(stats) : reject(error62);
29141
+ return error62 === null ? resolve8(stats) : reject(error62);
29134
29142
  });
29135
29143
  });
29136
29144
  }
@@ -29155,10 +29163,10 @@ var require_async5 = __commonJS({
29155
29163
  this._readerStream = new stream_1.default(this._settings);
29156
29164
  }
29157
29165
  dynamic(root, options) {
29158
- return new Promise((resolve7, reject) => {
29166
+ return new Promise((resolve8, reject) => {
29159
29167
  this._walkAsync(root, options, (error62, entries) => {
29160
29168
  if (error62 === null) {
29161
- resolve7(entries);
29169
+ resolve8(entries);
29162
29170
  } else {
29163
29171
  reject(error62);
29164
29172
  }
@@ -29168,10 +29176,10 @@ var require_async5 = __commonJS({
29168
29176
  async static(patterns, options) {
29169
29177
  const entries = [];
29170
29178
  const stream = this._readerStream.static(patterns, options);
29171
- return new Promise((resolve7, reject) => {
29179
+ return new Promise((resolve8, reject) => {
29172
29180
  stream.once("error", reject);
29173
29181
  stream.on("data", (entry) => entries.push(entry));
29174
- stream.once("end", () => resolve7(entries));
29182
+ stream.once("end", () => resolve8(entries));
29175
29183
  });
29176
29184
  }
29177
29185
  };
@@ -31063,6 +31071,7 @@ var init_metrics = __esm({
31063
31071
  "class",
31064
31072
  "method",
31065
31073
  "interface",
31074
+ "trait",
31066
31075
  "type",
31067
31076
  "enum",
31068
31077
  "field",
@@ -31661,6 +31670,40 @@ function parseRestShorthand(rest2, actionName) {
31661
31670
  }
31662
31671
  return [{ method: "ALL", path: joinRoutePath(rest2) }];
31663
31672
  }
31673
+ function phpRoutePath(...parts) {
31674
+ return normalizeRoutePath(
31675
+ joinRoutePath(...parts).replace(/\{(\w+)\s*:[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g, "{$1}").replace(/\{(\w+)<[^>]*>[^}]*\}/g, "{$1}")
31676
+ );
31677
+ }
31678
+ function resourceParam(segment) {
31679
+ return segment.replace(/-/g, "_").replace(/ies$/, "y").replace(/(s|x|z|ch|sh)es$/, "$1").replace(/([^s])s$/, "$1");
31680
+ }
31681
+ function laravelResource(name, api = false, only, except) {
31682
+ const segs = name.split(".").filter(Boolean);
31683
+ const last = segs.pop();
31684
+ if (!last) return [];
31685
+ const base = joinRoutePath(...segs.map((s) => `${s}/:${resourceParam(s)}`), last);
31686
+ const param = `:${resourceParam(last)}`;
31687
+ return RESOURCE_ACTIONS.filter(
31688
+ ([, , action]) => !(api && (action === "create" || action === "edit")) && (!only || only.includes(action)) && !except?.includes(action)
31689
+ ).map(([method, suffix, action]) => ({
31690
+ method,
31691
+ path: `${base === "/" ? "" : base}${suffix.replace("{p}", param)}` || "/",
31692
+ action
31693
+ }));
31694
+ }
31695
+ function parseRouteAnnotations(doc) {
31696
+ const out = [];
31697
+ for (const m of doc.matchAll(/@Route\(([^)]*)\)/g)) {
31698
+ const body = m[1] ?? "";
31699
+ const path3 = /^\s*"([^"]*)"/.exec(body)?.[1] ?? /\bpath\s*=\s*"([^"]*)"/.exec(body)?.[1];
31700
+ if (path3 === void 0) continue;
31701
+ const list2 = /\bmethods\s*=\s*(\{[^}]*\}|"[^"]*")/.exec(body)?.[1] ?? "";
31702
+ const methods = [...list2.matchAll(/"([^"]+)"/g)].map((x) => httpMethod(x[1])).filter((x) => !!x);
31703
+ for (const method of methods.length ? methods : ["ALL"]) out.push({ method, path: path3 });
31704
+ }
31705
+ return out;
31706
+ }
31664
31707
  function frameworkOf(importedPackages) {
31665
31708
  for (const p of importedPackages) {
31666
31709
  const f = FRAMEWORK_PKGS[p];
@@ -31705,7 +31748,7 @@ function fileRoutes(rel, exportedNames, defaultExport) {
31705
31748
  }
31706
31749
  return out;
31707
31750
  }
31708
- var ROUTE_METHODS, JS_METHOD_ALIASES, REST_ALIASES, FRAMEWORK_PKGS, routeId, NEXT_APP_ROUTE, NEXT_PAGES_API, HTTP_EXPORTS;
31751
+ var ROUTE_METHODS, JS_METHOD_ALIASES, REST_ALIASES, RESOURCE_ACTIONS, FRAMEWORK_PKGS, routeId, NEXT_APP_ROUTE, NEXT_PAGES_API, HTTP_EXPORTS;
31709
31752
  var init_routes = __esm({
31710
31753
  "src/graph/routes.ts"() {
31711
31754
  "use strict";
@@ -31729,6 +31772,16 @@ var init_routes = __esm({
31729
31772
  ["PATCH", "/:id", "patch"],
31730
31773
  ["DELETE", "/:id", "remove"]
31731
31774
  ];
31775
+ RESOURCE_ACTIONS = [
31776
+ ["GET", "", "index"],
31777
+ ["GET", "/create", "create"],
31778
+ ["POST", "", "store"],
31779
+ ["GET", "/{p}", "show"],
31780
+ ["GET", "/{p}/edit", "edit"],
31781
+ ["PUT", "/{p}", "update"],
31782
+ ["PATCH", "/{p}", "update"],
31783
+ ["DELETE", "/{p}", "destroy"]
31784
+ ];
31732
31785
  FRAMEWORK_PKGS = {
31733
31786
  express: "express",
31734
31787
  fastify: "fastify",
@@ -31759,7 +31812,18 @@ var init_routes = __esm({
31759
31812
  "github.com/labstack/echo": "echo",
31760
31813
  "github.com/gorilla/mux": "gorilla",
31761
31814
  "github.com/gofiber/fiber": "fiber",
31762
- "github.com/julienschmidt/httprouter": "httprouter"
31815
+ "github.com/julienschmidt/httprouter": "httprouter",
31816
+ // PHP (top-level namespace of a `use` the project cannot resolve)
31817
+ Illuminate: "laravel",
31818
+ Symfony: "symfony",
31819
+ Slim: "slim",
31820
+ CodeIgniter: "codeigniter",
31821
+ // PHP (composer.json `require`, when no `use` in the file names the framework)
31822
+ "laravel/lumen-framework": "lumen",
31823
+ "laravel/framework": "laravel",
31824
+ "symfony/framework-bundle": "symfony",
31825
+ "slim/slim": "slim",
31826
+ "codeigniter4/framework": "codeigniter"
31763
31827
  };
31764
31828
  routeId = (method, path3) => `route:${method} ${path3}`;
31765
31829
  NEXT_APP_ROUTE = /(?:^|\/)app\/(.*?)(?:\/)?route\.(?:ts|tsx|js|jsx|mjs)$/;
@@ -31789,6 +31853,8 @@ function grammarFor(path3) {
31789
31853
  return "python";
31790
31854
  case ".go":
31791
31855
  return "go";
31856
+ case ".php":
31857
+ return "php";
31792
31858
  default:
31793
31859
  return void 0;
31794
31860
  }
@@ -32293,28 +32359,1095 @@ var init_go = __esm({
32293
32359
  }
32294
32360
  });
32295
32361
 
32296
- // src/graph/languages/python.ts
32297
- import { existsSync as existsSync15, statSync as statSync2 } from "fs";
32362
+ // src/graph/languages/php.ts
32363
+ import { readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
32298
32364
  import { dirname as dirname5, join as join17, normalize as normalize2, relative as relative3, resolve as resolve4 } from "path";
32365
+ function readComposer(dir) {
32366
+ const file3 = join17(dir, "composer.json");
32367
+ if (!isFile(file3)) return null;
32368
+ try {
32369
+ const json3 = JSON.parse(readFileSync12(file3, "utf8"));
32370
+ const info = { dir, psr4: [], psr0: [], classmap: [], files: [], require: [] };
32371
+ for (const key of ["autoload", "autoload-dev"]) {
32372
+ const section = json3[key];
32373
+ for (const [prefix, dirs] of Object.entries(section?.["psr-4"] ?? {})) {
32374
+ info.psr4.push({
32375
+ prefix: prefix && !prefix.endsWith("\\") ? `${prefix}\\` : prefix,
32376
+ dirs: asList(dirs)
32377
+ });
32378
+ }
32379
+ for (const [prefix, dirs] of Object.entries(section?.["psr-0"] ?? {}))
32380
+ info.psr0.push({ prefix, dirs: asList(dirs) });
32381
+ info.classmap.push(...section?.classmap ?? []);
32382
+ info.files.push(...section?.files ?? []);
32383
+ }
32384
+ info.require = Object.keys(json3.require ?? {});
32385
+ info.psr4.sort((a, b) => b.prefix.length - a.prefix.length);
32386
+ info.psr0.sort((a, b) => b.prefix.length - a.prefix.length);
32387
+ const empty2 = !info.psr4.length && !info.psr0.length && !info.classmap.length && !info.files.length && !info.require.length;
32388
+ return empty2 ? null : info;
32389
+ } catch {
32390
+ return null;
32391
+ }
32392
+ }
32393
+ function composerAutoload(root, fromAbs) {
32394
+ let dir = dirname5(fromAbs);
32395
+ while (dir.startsWith(root)) {
32396
+ let hit = composerCache.get(dir);
32397
+ if (hit === void 0) {
32398
+ hit = readComposer(dir);
32399
+ composerCache.set(dir, hit);
32400
+ }
32401
+ if (hit) return hit;
32402
+ if (dir === root) break;
32403
+ dir = dirname5(dir);
32404
+ }
32405
+ return void 0;
32406
+ }
32407
+ function phpFilesUnder(abs, budget) {
32408
+ if (isFile(abs)) return abs.endsWith(".php") ? [abs] : [];
32409
+ let entries;
32410
+ try {
32411
+ entries = readdirSync6(abs, { recursive: true, encoding: "utf8" });
32412
+ } catch {
32413
+ return [];
32414
+ }
32415
+ const out = [];
32416
+ for (const e of entries) {
32417
+ if (budget.left <= 0) break;
32418
+ if (!e.endsWith(".php") || e.split(/[\\/]/).includes("vendor")) continue;
32419
+ out.push(join17(abs, e));
32420
+ budget.left--;
32421
+ }
32422
+ return out;
32423
+ }
32424
+ function classmapClasses(root, auto) {
32425
+ if (auto.classes) return auto.classes;
32426
+ const classes = /* @__PURE__ */ new Map();
32427
+ const budget = { left: CLASSMAP_MAX_FILES };
32428
+ for (const entry of auto.classmap) {
32429
+ for (const f of phpFilesUnder(join17(auto.dir, entry), budget)) {
32430
+ if (relative3(root, f).startsWith("..")) continue;
32431
+ let code;
32432
+ try {
32433
+ code = readFileSync12(f, "utf8");
32434
+ } catch {
32435
+ continue;
32436
+ }
32437
+ const ns = /^\s*namespace\s+([\w\\]+)\s*[;{]/m.exec(code)?.[1];
32438
+ for (const m of code.matchAll(
32439
+ /^\s*(?:(?:abstract|final|readonly)\s+)*(?:class|interface|trait|enum)\s+(\w+)/gm
32440
+ )) {
32441
+ const fq = ns ? `${ns}\\${m[1]}` : m[1];
32442
+ if (!classes.has(fq)) classes.set(fq, toRel(root, f));
32443
+ }
32444
+ }
32445
+ }
32446
+ auto.classes = classes;
32447
+ return classes;
32448
+ }
32449
+ function autoloadedFunctions(root, auto) {
32450
+ if (auto.functions) return auto.functions;
32451
+ const functions = /* @__PURE__ */ new Map();
32452
+ for (const entry of auto.files) {
32453
+ const f = join17(auto.dir, entry);
32454
+ if (!isFile(f) || relative3(root, f).startsWith("..")) continue;
32455
+ let code;
32456
+ try {
32457
+ code = readFileSync12(f, "utf8");
32458
+ } catch {
32459
+ continue;
32460
+ }
32461
+ if (/^\s*namespace\s+[\w\\]+\s*[;{]/m.test(code)) continue;
32462
+ for (const m of code.matchAll(/^\s*function\s+&?\s*(\w+)\s*\(/gm))
32463
+ if (!functions.has(m[1])) functions.set(m[1], toRel(root, f));
32464
+ }
32465
+ auto.functions = functions;
32466
+ return functions;
32467
+ }
32468
+ function resolvePhpClass(root, fromRel, fqcn) {
32469
+ const name = fqcn.replace(/^\\+/, "");
32470
+ if (!name) return void 0;
32471
+ const candidates2 = [];
32472
+ const auto = composerAutoload(root, resolve4(root, fromRel));
32473
+ if (auto?.psr4.length || auto?.psr0.length) {
32474
+ for (const { prefix, dirs } of auto.psr4) {
32475
+ if (!name.startsWith(prefix)) continue;
32476
+ const rest2 = name.slice(prefix.length).split("\\");
32477
+ for (const d of dirs) candidates2.push(join17(auto.dir, d, ...rest2));
32478
+ }
32479
+ for (const { prefix, dirs } of auto.psr0) {
32480
+ if (!name.startsWith(prefix)) continue;
32481
+ const cut = name.lastIndexOf("\\");
32482
+ const ns = cut === -1 ? [] : name.slice(0, cut).split("\\");
32483
+ const cls = name.slice(cut + 1).split("_");
32484
+ for (const d of dirs) candidates2.push(join17(auto.dir, d, ...ns, ...cls));
32485
+ }
32486
+ } else {
32487
+ const parts = name.split("\\");
32488
+ candidates2.push(resolve4(root, ...parts), resolve4(root, "src", ...parts));
32489
+ }
32490
+ for (const c of candidates2) {
32491
+ const f = `${c}.php`;
32492
+ if (isFile(f) && !relative3(root, f).startsWith("..")) return toRel(root, f);
32493
+ }
32494
+ return auto?.classmap.length ? classmapClasses(root, auto).get(name) : void 0;
32495
+ }
32496
+ function phpString(n) {
32497
+ if (!n || n.type !== "string" && n.type !== "encapsed_string") return void 0;
32498
+ if (n.namedChildren.some((c) => c && c.type !== "string_content" && c.type !== "escape_sequence"))
32499
+ return void 0;
32500
+ return literalText(n)?.replace(/\\(\\|')/g, "$1");
32501
+ }
32502
+ function argsOf(call) {
32503
+ const list2 = call.childForFieldName("arguments") ?? call.childForFieldName("parameters");
32504
+ return (list2?.namedChildren ?? []).filter((a) => a?.type === "argument").map((a) => a.namedChildren[a.namedChildren.length - 1]).filter((a) => !!a);
32505
+ }
32506
+ function positionalArgs(call) {
32507
+ const list2 = call.childForFieldName("arguments") ?? call.childForFieldName("parameters");
32508
+ return (list2?.namedChildren ?? []).filter((a) => a?.type === "argument" && !a.childForFieldName("name")).map((a) => a.namedChildren[0]).filter((a) => !!a);
32509
+ }
32510
+ function namedArg(call, name) {
32511
+ const list2 = call.childForFieldName("arguments") ?? call.childForFieldName("parameters");
32512
+ for (const a of list2?.namedChildren ?? []) {
32513
+ if (a?.type === "argument" && a.childForFieldName("name")?.text === name)
32514
+ return a.namedChildren[a.namedChildren.length - 1] ?? void 0;
32515
+ }
32516
+ return void 0;
32517
+ }
32518
+ function stringsOf(n) {
32519
+ if (!n) return [];
32520
+ if (n.type !== "array_creation_expression") {
32521
+ const s = phpString(n);
32522
+ return s === void 0 ? [] : [s];
32523
+ }
32524
+ return n.namedChildren.flatMap((el) => {
32525
+ const s = el?.namedChildren.length === 1 ? phpString(el.namedChildren[0]) : void 0;
32526
+ return s === void 0 ? [] : [s];
32527
+ });
32528
+ }
32529
+ function arrayElements(arr2) {
32530
+ const out = [];
32531
+ for (const el of arr2.namedChildren) {
32532
+ if (el?.type !== "array_element_initializer") continue;
32533
+ const kids = el.namedChildren.filter((c) => !!c);
32534
+ if (kids.length === 1) out.push({ value: kids[0] });
32535
+ else if (kids.length === 2) out.push({ key: phpString(kids[0]), value: kids[1] });
32536
+ }
32537
+ return out;
32538
+ }
32539
+ function handlerShaped(a) {
32540
+ if (!a) return false;
32541
+ switch (a.type) {
32542
+ case "anonymous_function":
32543
+ case "arrow_function":
32544
+ case "class_constant_access_expression":
32545
+ return true;
32546
+ case "string":
32547
+ case "encapsed_string":
32548
+ return CONTROLLER_STRING.test(phpString(a) ?? "");
32549
+ case "array_creation_expression":
32550
+ return arrayElements(a).some((e) => e.key ? e.key === "uses" : handlerShaped(e.value));
32551
+ default:
32552
+ return false;
32553
+ }
32554
+ }
32555
+ async function parsePhp(root, rel, code) {
32556
+ const p = await getParser("php");
32557
+ const tree = p.parse(code);
32558
+ if (!tree) return { nodes: [], edges: [], exports: [] };
32559
+ const fid = fileId(rel);
32560
+ const fileNode = {
32561
+ id: fid,
32562
+ kind: "file",
32563
+ name: rel,
32564
+ path: rel,
32565
+ source: "tree-sitter"
32566
+ };
32567
+ const nodes = [fileNode];
32568
+ const edges = [];
32569
+ const exportsList = [];
32570
+ const scope = /* @__PURE__ */ new Map();
32571
+ let classUses = /* @__PURE__ */ new Map();
32572
+ const usesByBlock = /* @__PURE__ */ new Map();
32573
+ const declared = /* @__PURE__ */ new Set();
32574
+ const localSyms = /* @__PURE__ */ new Map();
32575
+ const varTypes = /* @__PURE__ */ new Map();
32576
+ const propTypes = /* @__PURE__ */ new Map();
32577
+ const parents = /* @__PURE__ */ new Map();
32578
+ const imports = /* @__PURE__ */ new Map();
32579
+ const resolvedClasses = /* @__PURE__ */ new Map();
32580
+ const importedPkgs = /* @__PURE__ */ new Set();
32581
+ const refs = new RefCollector();
32582
+ let complexity = 0;
32583
+ let namespace = "";
32584
+ const namespaces = [];
32585
+ const pushEdge = (e) => edges.push({ confidence: 1, weight: 1, ...e });
32586
+ const auto = composerAutoload(root, resolve4(root, rel));
32587
+ const stmts = [];
32588
+ let block = 0;
32589
+ for (const stmt of tree.rootNode.namedChildren) {
32590
+ if (!stmt) continue;
32591
+ if (stmt.type === "namespace_definition") {
32592
+ block++;
32593
+ namespace = stmt.childForFieldName("name")?.text ?? "";
32594
+ if (namespace) addNamespace(stmt, namespace);
32595
+ for (const s of stmt.childForFieldName("body")?.namedChildren ?? [])
32596
+ if (s) stmts.push([s, namespace, block]);
32597
+ } else stmts.push([stmt, namespace, block]);
32598
+ }
32599
+ const enter = (ns, b) => {
32600
+ namespace = ns;
32601
+ let uses = usesByBlock.get(b);
32602
+ if (!uses) {
32603
+ uses = /* @__PURE__ */ new Map();
32604
+ usesByBlock.set(b, uses);
32605
+ }
32606
+ classUses = uses;
32607
+ };
32608
+ for (const [stmt] of stmts) {
32609
+ const name = CLASS_KINDS[stmt.type] ? stmt.childForFieldName("name")?.text : void 0;
32610
+ if (name) declared.add(name);
32611
+ }
32612
+ for (const [stmt, ns, b] of stmts) {
32613
+ enter(ns, b);
32614
+ if (stmt.type === "namespace_use_declaration") handleUse(stmt);
32615
+ }
32616
+ for (const [stmt, ns, b] of stmts) {
32617
+ enter(ns, b);
32618
+ if (stmt.type !== "namespace_use_declaration") collectDecls(stmt);
32619
+ }
32620
+ for (const [stmt, ns, b] of stmts) {
32621
+ enter(ns, b);
32622
+ if (stmt.type !== "namespace_use_declaration") walkRefs(stmt);
32623
+ }
32624
+ handleIncludes(tree.rootNode);
32625
+ edges.push(...imports.values());
32626
+ edges.push(...refs.edges());
32627
+ const errors = tree.rootNode.hasError ? tree.rootNode.descendantsOfType("ERROR").length : 0;
32628
+ fileNode.meta = {
32629
+ loc: tree.rootNode.endPosition.row + 1,
32630
+ complexity,
32631
+ ...namespaces.length ? { php_namespace: namespaces[0] } : {},
32632
+ ...errors ? { parse_errors: errors } : {}
32633
+ };
32634
+ return { nodes, edges, exports: exportsList, errors };
32635
+ function addNamespace(n, name) {
32636
+ if (namespaces.includes(name)) return;
32637
+ namespaces.push(name);
32638
+ const id = symId(rel, name);
32639
+ nodes.push({
32640
+ id,
32641
+ kind: "namespace",
32642
+ name,
32643
+ path: rel,
32644
+ startLine: n.startPosition.row + 1,
32645
+ endLine: n.endPosition.row + 1,
32646
+ source: "tree-sitter"
32647
+ });
32648
+ pushEdge({ src: fid, dst: id, kind: "defines", source: "tree-sitter" });
32649
+ }
32650
+ function addImport(dst, name, spec, line, conf) {
32651
+ const cur = imports.get(dst);
32652
+ if (cur?.meta) {
32653
+ const names = cur.meta.names;
32654
+ if (!names.includes(name)) names.push(name);
32655
+ return;
32656
+ }
32657
+ imports.set(dst, {
32658
+ src: fid,
32659
+ dst,
32660
+ kind: "imports",
32661
+ source: "tree-sitter",
32662
+ confidence: conf,
32663
+ weight: 1,
32664
+ meta: { names: [name], spec, line }
32665
+ });
32666
+ }
32667
+ function handleUse(decl) {
32668
+ const group = decl.childForFieldName("body");
32669
+ const prefix = group ? decl.namedChildren.find((c) => c?.type === "namespace_name")?.text : void 0;
32670
+ const declKind = decl.children.find((c) => c?.type === "function" || c?.type === "const")?.type;
32671
+ for (const c of (group ?? decl).namedChildren) {
32672
+ if (c?.type !== "namespace_use_clause") continue;
32673
+ const alias = c.childForFieldName("alias");
32674
+ const target = c.namedChildren.find(
32675
+ (x) => x && x.id !== alias?.id && (x.type === "qualified_name" || x.type === "name")
32676
+ );
32677
+ if (!target) continue;
32678
+ const fqn = (prefix ? `${prefix}\\${target.text}` : target.text).replace(/^\\+/, "");
32679
+ const local = alias?.text ?? lastSegment(fqn);
32680
+ const kind = c.children.find((x) => x?.type === "function" || x?.type === "const")?.type ?? declKind;
32681
+ const line = c.startPosition.row + 1;
32682
+ const label = alias ? `${fqn} as ${alias.text}` : fqn;
32683
+ const file3 = kind ? void 0 : classFile(fqn);
32684
+ if (file3) {
32685
+ if (file3 !== rel) addImport(fileId(file3), label, fqn, line, 0.95);
32686
+ } else {
32687
+ const top = fqn.split("\\")[0];
32688
+ importedPkgs.add(top);
32689
+ if (!imports.has(pkgId(top)))
32690
+ nodes.push({ id: pkgId(top), kind: "package", name: top, source: "tree-sitter" });
32691
+ addImport(pkgId(top), label, fqn, line, 1);
32692
+ }
32693
+ if (kind) scope.set(local, { id: pkgId(fqn.split("\\")[0]), confidence: 0.6 });
32694
+ else classUses.set(local, fqn);
32695
+ }
32696
+ }
32697
+ function handleIncludes(rootNode) {
32698
+ const here = dirname5(resolve4(root, rel));
32699
+ for (const inc of rootNode.descendantsOfType([
32700
+ "require_expression",
32701
+ "require_once_expression",
32702
+ "include_expression",
32703
+ "include_once_expression"
32704
+ ])) {
32705
+ const arg = inc.namedChildren[0];
32706
+ if (!arg) continue;
32707
+ const candidates2 = [];
32708
+ const plain = phpString(arg);
32709
+ if (plain !== void 0) candidates2.push(resolve4(here, plain), resolve4(root, plain));
32710
+ else if (arg.type === "binary_expression") {
32711
+ const left = arg.childForFieldName("left")?.text.replace(/\s+/g, "") ?? "";
32712
+ const right = phpString(arg.childForFieldName("right"));
32713
+ const up = /^dirname\(__(?:DIR|FILE)__(?:,(\d+))?\)$/.exec(left);
32714
+ let base;
32715
+ if (left === "__DIR__" || left === "dirname(__FILE__)") base = here;
32716
+ else if (up) {
32717
+ base = here;
32718
+ for (let i = left.includes("__FILE__") ? 1 : 0; i < Number(up[1] ?? 1); i++)
32719
+ base = dirname5(base);
32720
+ }
32721
+ if (base && right !== void 0) candidates2.push(join17(base, right));
32722
+ }
32723
+ const hit = candidates2.find((c) => isFile(c) && !relative3(root, c).startsWith(".."));
32724
+ if (!hit) continue;
32725
+ const file3 = toRel(root, hit);
32726
+ if (file3 !== rel) addImport(fileId(file3), "*", arg.text, inc.startPosition.row + 1, 0.9);
32727
+ }
32728
+ }
32729
+ function classFile(fqcn) {
32730
+ let hit = resolvedClasses.get(fqcn);
32731
+ if (hit === void 0) {
32732
+ hit = resolvePhpClass(root, rel, fqcn) ?? null;
32733
+ resolvedClasses.set(fqcn, hit);
32734
+ }
32735
+ return hit ?? void 0;
32736
+ }
32737
+ function fqcnOf(name) {
32738
+ if (name.startsWith("\\")) return name.slice(1);
32739
+ const [head, ...rest2] = name.split("\\");
32740
+ const used = classUses.get(head);
32741
+ if (used) return [used, ...rest2].join("\\");
32742
+ return namespace ? `${namespace}\\${name}` : name;
32743
+ }
32744
+ function classRef(name) {
32745
+ if (!name || RELATIVE.has(name.toLowerCase())) return void 0;
32746
+ if (!name.includes("\\") && !classUses.has(name) && declared.has(name))
32747
+ return { id: symId(rel, name), confidence: 0.9 };
32748
+ const fqcn = fqcnOf(name);
32749
+ const file3 = classFile(fqcn);
32750
+ if (file3) return { id: symId(file3, lastSegment(fqcn)), confidence: file3 === rel ? 0.9 : 0.85 };
32751
+ const used = classUses.get(name.replace(/^\\/, "").split("\\")[0]);
32752
+ if (used && !name.startsWith("\\")) return { id: pkgId(used.split("\\")[0]), confidence: 0.6 };
32753
+ return void 0;
32754
+ }
32755
+ function typeRef(n) {
32756
+ if (!n) return void 0;
32757
+ const named = n.type === "named_type" ? n : n.descendantsOfType("named_type")[0];
32758
+ const t = named ? classRef(named.text) : void 0;
32759
+ return t?.id.startsWith("sym:") ? t.id : void 0;
32760
+ }
32761
+ function collectDecls(n) {
32762
+ if (CLASS_KINDS[n.type]) {
32763
+ collectClass(n);
32764
+ return;
32765
+ }
32766
+ switch (n.type) {
32767
+ case "function_definition": {
32768
+ const name = n.childForFieldName("name")?.text;
32769
+ if (name) addFunction(name, n);
32770
+ return;
32771
+ }
32772
+ case "if_statement": {
32773
+ for (const f of n.descendantsOfType("function_definition")) {
32774
+ const name = f.childForFieldName("name")?.text;
32775
+ if (name && !insideDecl(f, n)) addFunction(name, f);
32776
+ }
32777
+ return;
32778
+ }
32779
+ case "const_declaration": {
32780
+ for (const el of n.namedChildren) {
32781
+ const name = el?.type === "const_element" ? el.namedChildren[0]?.text : void 0;
32782
+ if (name) addSym(name, "variable", el, true);
32783
+ }
32784
+ return;
32785
+ }
32786
+ case "expression_statement": {
32787
+ const call = n.namedChildren[0];
32788
+ if (call?.type !== "function_call_expression") return;
32789
+ if (call.childForFieldName("function")?.text !== "define") return;
32790
+ const name = phpString(argsOf(call)[0]);
32791
+ if (name && /^\w+$/.test(name)) addSym(name, "variable", call, true);
32792
+ return;
32793
+ }
32794
+ default:
32795
+ return;
32796
+ }
32797
+ }
32798
+ function insideDecl(n, top) {
32799
+ for (let cur = n.parent; cur && cur.id !== top.id; cur = cur.parent) {
32800
+ if (cur.type === "function_definition" || cur.type === "method_declaration") return true;
32801
+ if (CLASS_KINDS[cur.type]) return true;
32802
+ }
32803
+ return false;
32804
+ }
32805
+ function addFunction(name, n) {
32806
+ addSym(name, "function", n, true);
32807
+ scope.set(name, { id: symId(rel, name), confidence: 0.9 });
32808
+ }
32809
+ function collectClass(n) {
32810
+ const name = n.childForFieldName("name")?.text;
32811
+ const kind = CLASS_KINDS[n.type];
32812
+ if (!name || !kind) return;
32813
+ addSym(name, kind, n, true);
32814
+ const self = symId(rel, name);
32815
+ for (const h of n.namedChildren) {
32816
+ if (h?.type !== "base_clause" && h?.type !== "class_interface_clause") continue;
32817
+ for (const b of h.namedChildren) {
32818
+ if (b?.type !== "name" && b?.type !== "qualified_name") continue;
32819
+ const target = heritage(self, b.text, h.type === "base_clause" ? "extends" : "implements");
32820
+ if (target && h.type === "base_clause" && kind === "class") parents.set(name, target);
32821
+ }
32822
+ }
32823
+ const body = n.childForFieldName("body");
32824
+ for (const m of body?.namedChildren ?? []) {
32825
+ if (!m) continue;
32826
+ switch (m.type) {
32827
+ case "method_declaration": {
32828
+ const mname = m.childForFieldName("name")?.text;
32829
+ if (!mname) break;
32830
+ addSym(`${name}.${mname}`, "method", m, isPublic(m));
32831
+ const params = m.childForFieldName("parameters")?.namedChildren ?? [];
32832
+ for (const prm of params) {
32833
+ if (prm?.type !== "property_promotion_parameter") continue;
32834
+ const pname = prm.childForFieldName("name")?.text.replace(/^\$/, "");
32835
+ if (!pname) continue;
32836
+ addSym(`${name}.${pname}`, "field", prm, isPublic(prm));
32837
+ const t = typeRef(prm.childForFieldName("type"));
32838
+ if (t) propTypes.set(`${name}.${pname}`, t);
32839
+ }
32840
+ break;
32841
+ }
32842
+ case "property_declaration": {
32843
+ const t = typeRef(m.childForFieldName("type"));
32844
+ for (const el of m.namedChildren) {
32845
+ if (el?.type !== "property_element") continue;
32846
+ const pname = el.childForFieldName("name")?.text.replace(/^\$/, "");
32847
+ if (!pname) continue;
32848
+ addSym(`${name}.${pname}`, "field", el, isPublic(m));
32849
+ if (t) propTypes.set(`${name}.${pname}`, t);
32850
+ }
32851
+ break;
32852
+ }
32853
+ case "const_declaration": {
32854
+ for (const el of m.namedChildren) {
32855
+ const cname = el?.type === "const_element" ? el.namedChildren[0]?.text : void 0;
32856
+ if (cname) addSym(`${name}.${cname}`, "field", el, isPublic(m));
32857
+ }
32858
+ break;
32859
+ }
32860
+ case "enum_case": {
32861
+ const cname = m.childForFieldName("name")?.text;
32862
+ if (cname) addSym(`${name}.${cname}`, "field", m, true);
32863
+ break;
32864
+ }
32865
+ case "use_declaration": {
32866
+ for (const t of m.namedChildren) {
32867
+ if (t?.type === "name" || t?.type === "qualified_name")
32868
+ heritage(self, t.text, "extends", { trait: true });
32869
+ }
32870
+ break;
32871
+ }
32872
+ default:
32873
+ break;
32874
+ }
32875
+ }
32876
+ attributeRoutes(n, name);
32877
+ }
32878
+ function heritage(src, name, kind, meta4) {
32879
+ const ref = classRef(name) ?? (name.startsWith("\\") && !name.slice(1).includes("\\") ? void 0 : { id: symId(rel, lastSegment(name)), confidence: 0.5 });
32880
+ if (!ref) return void 0;
32881
+ pushEdge({
32882
+ src,
32883
+ dst: ref.id,
32884
+ kind,
32885
+ source: "tree-sitter",
32886
+ confidence: ref.confidence,
32887
+ ...meta4 ? { meta: meta4 } : {}
32888
+ });
32889
+ return ref.id.startsWith("sym:") ? ref.id : void 0;
32890
+ }
32891
+ function addSym(qualified, kind, n, exported) {
32892
+ const id = symId(rel, qualified);
32893
+ const c = kind === "function" || kind === "method" ? complexityOf(n, BRANCHES2, BOOL_OPS2) : 0;
32894
+ complexity += c;
32895
+ nodes.push({
32896
+ id,
32897
+ kind,
32898
+ name: qualified,
32899
+ path: rel,
32900
+ startLine: n.startPosition.row + 1,
32901
+ endLine: n.endPosition.row + 1,
32902
+ source: "tree-sitter",
32903
+ meta: {
32904
+ ...exported ? { exported: true } : {},
32905
+ loc: n.endPosition.row - n.startPosition.row + 1,
32906
+ ...c ? { complexity: c } : {}
32907
+ }
32908
+ });
32909
+ pushEdge({ src: fid, dst: id, kind: "defines", source: "tree-sitter" });
32910
+ if (exported) {
32911
+ exportsList.push(qualified);
32912
+ pushEdge({ src: fid, dst: id, kind: "exports", source: "tree-sitter" });
32913
+ }
32914
+ localSyms.set(qualified, id);
32915
+ }
32916
+ function enclosing(n) {
32917
+ let cur = n.parent;
32918
+ while (cur) {
32919
+ if (cur.type === "method_declaration") {
32920
+ const name = cur.childForFieldName("name")?.text;
32921
+ const owner = cur.parent?.parent?.childForFieldName("name")?.text;
32922
+ const q = owner && name ? `${owner}.${name}` : void 0;
32923
+ if (q && localSyms.has(q)) return q;
32924
+ }
32925
+ if (cur.type === "function_definition") {
32926
+ const name = cur.childForFieldName("name")?.text;
32927
+ if (name && localSyms.has(name)) return name;
32928
+ }
32929
+ cur = cur.parent;
32930
+ }
32931
+ return void 0;
32932
+ }
32933
+ function objectType(obj, caller) {
32934
+ const cls = classOf(caller);
32935
+ if (obj.type === "variable_name") {
32936
+ if (obj.text === "$this") return cls ? { id: symId(rel, cls), confidence: 0.9 } : void 0;
32937
+ const t = varTypes.get(`${caller ?? ""}\0${obj.text}`);
32938
+ return t ? { id: t, confidence: 0.8 } : void 0;
32939
+ }
32940
+ if (obj.type === "member_access_expression" || obj.type === "nullsafe_member_access_expression") {
32941
+ const inner = obj.childForFieldName("object");
32942
+ const prop = obj.childForFieldName("name")?.text;
32943
+ if (inner?.text !== "$this" || !cls || !prop) return void 0;
32944
+ const t = propTypes.get(`${cls}.${prop}`);
32945
+ return t ? { id: t, confidence: 0.8 } : void 0;
32946
+ }
32947
+ return void 0;
32948
+ }
32949
+ function scopeType(s, caller) {
32950
+ const cls = classOf(caller);
32951
+ if (s.type === "relative_scope" || RELATIVE.has(s.text.toLowerCase())) {
32952
+ const word = s.text.toLowerCase();
32953
+ if (!cls) return void 0;
32954
+ if (word === "parent") {
32955
+ const p2 = parents.get(cls);
32956
+ return p2 ? { id: p2, confidence: 0.8 } : void 0;
32957
+ }
32958
+ return { id: symId(rel, cls), confidence: 0.9 };
32959
+ }
32960
+ if (s.type === "name" || s.type === "qualified_name") return classRef(s.text);
32961
+ return void 0;
32962
+ }
32963
+ function callTarget(call, caller) {
32964
+ switch (call.type) {
32965
+ case "function_call_expression": {
32966
+ const fn = call.childForFieldName("function");
32967
+ if (fn?.type !== "name") return void 0;
32968
+ const local = scope.get(fn.text);
32969
+ if (local || !auto?.files.length) return local;
32970
+ const file3 = autoloadedFunctions(root, auto).get(fn.text);
32971
+ return file3 && file3 !== rel ? { id: symId(file3, fn.text), confidence: 0.8 } : void 0;
32972
+ }
32973
+ case "member_call_expression":
32974
+ case "nullsafe_member_call_expression": {
32975
+ const obj = call.childForFieldName("object");
32976
+ const m = call.childForFieldName("name");
32977
+ if (!obj || m?.type !== "name") return void 0;
32978
+ const t = objectType(obj, caller);
32979
+ return t ? { id: `${t.id}.${m.text}`, confidence: t.confidence } : void 0;
32980
+ }
32981
+ case "scoped_call_expression": {
32982
+ const s = call.childForFieldName("scope");
32983
+ const m = call.childForFieldName("name");
32984
+ if (!s || m?.type !== "name") return void 0;
32985
+ const t = scopeType(s, caller);
32986
+ if (t?.id.startsWith("pkg:")) return t;
32987
+ return t ? { id: `${t.id}.${m.text}`, confidence: t.confidence } : void 0;
32988
+ }
32989
+ case "object_creation_expression": {
32990
+ const cls = call.namedChildren.find(
32991
+ (c) => c?.type === "name" || c?.type === "qualified_name"
32992
+ );
32993
+ return cls ? classRef(cls.text) : void 0;
32994
+ }
32995
+ default:
32996
+ return void 0;
32997
+ }
32998
+ }
32999
+ function noteVarTypes(n) {
33000
+ for (const prm of n.descendantsOfType("simple_parameter")) {
33001
+ const t = typeRef(prm.childForFieldName("type"));
33002
+ const v = prm.childForFieldName("name")?.text;
33003
+ if (t && v) varTypes.set(`${enclosing(prm) ?? ""}\0${v}`, t);
33004
+ }
33005
+ for (const a of n.descendantsOfType("assignment_expression")) {
33006
+ const left = a.childForFieldName("left");
33007
+ const right = a.childForFieldName("right");
33008
+ if (left?.type !== "variable_name" || right?.type !== "object_creation_expression") continue;
33009
+ const cls = right.namedChildren.find(
33010
+ (c) => c?.type === "name" || c?.type === "qualified_name"
33011
+ );
33012
+ const t = cls ? classRef(cls.text) : void 0;
33013
+ if (t?.id.startsWith("sym:")) varTypes.set(`${enclosing(a) ?? ""}\0${left.text}`, t.id);
33014
+ }
33015
+ }
33016
+ function addRoute(method, path3, framework, line, handlers, confidence, extra = {}) {
33017
+ const id = routeId(method, path3);
33018
+ if (!nodes.some((n) => n.id === id)) {
33019
+ nodes.push({
33020
+ id,
33021
+ kind: "route",
33022
+ name: `${method} ${path3}`,
33023
+ path: rel,
33024
+ startLine: line,
33025
+ endLine: line,
33026
+ source: "tree-sitter",
33027
+ meta: { method, route: path3, framework, ...extra }
33028
+ });
33029
+ pushEdge({ src: fid, dst: id, kind: "defines", source: "tree-sitter" });
33030
+ }
33031
+ for (const h of handlers)
33032
+ pushEdge({ src: h, dst: id, kind: "handles", source: "tree-sitter", confidence });
33033
+ }
33034
+ function controllerRef(name, literal2 = false) {
33035
+ if (!literal2) {
33036
+ const direct = classRef(name);
33037
+ if (direct?.id.startsWith("sym:")) return direct;
33038
+ }
33039
+ const fq = name.replace(/^\\+/, "");
33040
+ const candidates2 = CONTROLLER_NAMESPACES.map((ns) => `${ns}\\${fq}`);
33041
+ if (literal2) candidates2.push(fq);
33042
+ for (const c of candidates2) {
33043
+ const file3 = classFile(c);
33044
+ if (file3) return { id: symId(file3, lastSegment(fq)), confidence: 0.85 };
33045
+ }
33046
+ return void 0;
33047
+ }
33048
+ function classConstant(n) {
33049
+ if (n.type !== "class_constant_access_expression") return void 0;
33050
+ const [cls, member] = n.namedChildren;
33051
+ if (!cls || member?.text !== "class") return void 0;
33052
+ const t = controllerRef(cls.text);
33053
+ return t?.id.startsWith("sym:") ? t : void 0;
33054
+ }
33055
+ function handlersOf(arg, caller, controller) {
33056
+ const handlers = [];
33057
+ let confidence = 0.9;
33058
+ const self = caller ? symId(rel, caller) : fid;
33059
+ const visit2 = (a) => {
33060
+ if (!a) return;
33061
+ if (a.type === "anonymous_function" || a.type === "arrow_function") {
33062
+ handlers.push(self);
33063
+ confidence = Math.min(confidence, 0.7);
33064
+ } else if (a.type === "class_constant_access_expression") {
33065
+ const t = classConstant(a);
33066
+ if (t) handlers.push(t.id);
33067
+ } else if (a.type === "string" || a.type === "encapsed_string") {
33068
+ const s = phpString(a) ?? "";
33069
+ const m = CONTROLLER_STRING.exec(s);
33070
+ const t = m ? controllerRef(m[1], true) : void 0;
33071
+ if (t && m) handlers.push(`${t.id}.${m[2]}`);
33072
+ else if (controller && /^\w+$/.test(s)) handlers.push(`${controller.id}.${s}`);
33073
+ } else if (a.type === "array_creation_expression") {
33074
+ const els = arrayElements(a);
33075
+ const [first, second] = els;
33076
+ const method = second && !second.key ? phpString(second.value) : void 0;
33077
+ const cls = first && !first.key ? classConstant(first.value) : void 0;
33078
+ if (els.length === 2 && cls && method) handlers.push(`${cls.id}.${method}`);
33079
+ else for (const el of els) if (!el.key || el.key === "uses") visit2(el.value);
33080
+ }
33081
+ };
33082
+ visit2(arg);
33083
+ if (!handlers.length) {
33084
+ handlers.push(self);
33085
+ confidence = 0.5;
33086
+ }
33087
+ return { handlers: [...new Set(handlers)], confidence };
33088
+ }
33089
+ function groupContext(n) {
33090
+ const prefixes = [];
33091
+ let controller;
33092
+ for (let cur = n.parent; cur; cur = cur.parent) {
33093
+ if (cur.type !== "anonymous_function" && cur.type !== "arrow_function") continue;
33094
+ const call = cur.parent?.type === "argument" ? cur.parent.parent?.parent : void 0;
33095
+ if (!call || !isCall(call) || call.childForFieldName("name")?.text !== "group") continue;
33096
+ const own2 = [];
33097
+ const first = argsOf(call)[0];
33098
+ const direct = first && first.id !== cur.id ? phpString(first) : void 0;
33099
+ if (direct !== void 0) own2.push(direct);
33100
+ else if (first?.type === "array_creation_expression") {
33101
+ const prefix = arrayElements(first).find((e) => e.key === "prefix");
33102
+ const s = prefix ? phpString(prefix.value) : void 0;
33103
+ if (s !== void 0) own2.push(s);
33104
+ }
33105
+ for (let link = call.childForFieldName("object"); link && isCall(link); ) {
33106
+ const name = link.childForFieldName("name")?.text;
33107
+ const a0 = argsOf(link)[0];
33108
+ if (name === "prefix") {
33109
+ const s = phpString(a0);
33110
+ if (s !== void 0) own2.unshift(s);
33111
+ } else if (name === "controller" && a0 && !controller) {
33112
+ const s = phpString(a0);
33113
+ controller = classConstant(a0) ?? (s ? controllerRef(s, true) : void 0);
33114
+ }
33115
+ link = link.childForFieldName("object");
33116
+ }
33117
+ prefixes.unshift(...own2);
33118
+ }
33119
+ return { prefixes, controller };
33120
+ }
33121
+ function laravelBase() {
33122
+ return /(^|\/)routes\/api\.php$/.test(rel) ? "/api" : void 0;
33123
+ }
33124
+ function routeFromCall(call, caller) {
33125
+ let facade = false;
33126
+ const chained = [];
33127
+ for (let link = call; link && isCall(link); ) {
33128
+ if (link.type === "scoped_call_expression") {
33129
+ facade = lastSegment(link.childForFieldName("scope")?.text ?? "") === "Route";
33130
+ break;
33131
+ }
33132
+ link = link.childForFieldName("object");
33133
+ if (link && isCall(link) && link.childForFieldName("name")?.text === "prefix") {
33134
+ const s = phpString(argsOf(link)[0]);
33135
+ if (s !== void 0) chained.unshift(s);
33136
+ }
33137
+ }
33138
+ const obj = call.type === "member_call_expression" ? call.childForFieldName("object") : null;
33139
+ const member = !facade && obj?.type === "variable_name" && obj.text !== "$this";
33140
+ if (!facade && !member) return;
33141
+ const verb = call.childForFieldName("name")?.text ?? "";
33142
+ const lower = verb.toLowerCase();
33143
+ const args = argsOf(call);
33144
+ if (facade && (verb === "resource" || verb === "apiResource")) {
33145
+ const name = phpString(args[0]);
33146
+ if (name) resourceRoutes(call, verb === "apiResource", name, args, caller, chained);
33147
+ return;
33148
+ }
33149
+ if (facade && (verb === "resources" || verb === "apiResources")) {
33150
+ if (args[0]?.type !== "array_creation_expression") return;
33151
+ for (const el of arrayElements(args[0])) {
33152
+ if (el.key)
33153
+ resourceRoutes(
33154
+ call,
33155
+ verb === "apiResources",
33156
+ el.key,
33157
+ [void 0, el.value, args[1]],
33158
+ caller,
33159
+ chained
33160
+ );
33161
+ }
33162
+ return;
33163
+ }
33164
+ let methods;
33165
+ let rest2;
33166
+ if (facade && lower === "view") {
33167
+ methods = ["GET"];
33168
+ rest2 = args.slice(0, 1);
33169
+ } else if (facade && (lower === "redirect" || lower === "permanentredirect")) {
33170
+ methods = ["ALL"];
33171
+ rest2 = args.slice(0, 1);
33172
+ } else if (lower === "match" || lower === "map") {
33173
+ methods = stringsOf(args[0]).map((m) => httpMethod(m)).filter((m) => !!m);
33174
+ rest2 = args.slice(1);
33175
+ } else if (lower === "any") {
33176
+ methods = ["ALL"];
33177
+ rest2 = args;
33178
+ } else if (ROUTE_METHODS.has(lower) && lower !== "all") {
33179
+ methods = [lower.toUpperCase()];
33180
+ rest2 = args;
33181
+ } else return;
33182
+ const raw = phpString(rest2[0]);
33183
+ if (raw === void 0 || !methods.length) return;
33184
+ if (member && (!handlerShaped(rest2[1]) || !raw.startsWith("/") && !ROUTER_VARS.has(obj.text)))
33185
+ return;
33186
+ const ctx = groupContext(call);
33187
+ const { handlers, confidence } = handlersOf(rest2[1], caller, ctx.controller);
33188
+ const path3 = phpRoutePath(facade ? laravelBase() : void 0, ...ctx.prefixes, ...chained, raw);
33189
+ const framework = facade ? "laravel" : frameworkOf(importedPkgs) ?? frameworkOf(auto?.require ?? []) ?? "http";
33190
+ for (const m of methods)
33191
+ addRoute(m, path3, framework, call.startPosition.row + 1, handlers, confidence);
33192
+ }
33193
+ function resourceRoutes(call, api, name, args, caller, chained) {
33194
+ let only;
33195
+ let except;
33196
+ if (args[2]?.type === "array_creation_expression") {
33197
+ for (const opt of arrayElements(args[2])) {
33198
+ if (opt.key === "only") only = stringsOf(opt.value);
33199
+ if (opt.key === "except") except = stringsOf(opt.value);
33200
+ }
33201
+ }
33202
+ for (let cur = call; cur.parent?.type === "member_call_expression"; cur = cur.parent) {
33203
+ if (cur.parent.childForFieldName("object")?.id !== cur.id) break;
33204
+ const m = cur.parent.childForFieldName("name")?.text;
33205
+ if (m === "only") only = stringsOf(argsOf(cur.parent)[0]);
33206
+ if (m === "except") except = stringsOf(argsOf(cur.parent)[0]);
33207
+ }
33208
+ const ctx = groupContext(call);
33209
+ const literal2 = phpString(args[1]);
33210
+ const cls = args[1] ? classConstant(args[1]) ?? (literal2 ? controllerRef(literal2, true) : void 0) : void 0;
33211
+ const self = caller ? symId(rel, caller) : fid;
33212
+ for (const spec of laravelResource(name, api, only, except)) {
33213
+ addRoute(
33214
+ spec.method,
33215
+ phpRoutePath(laravelBase(), ...ctx.prefixes, ...chained, spec.path),
33216
+ "laravel",
33217
+ call.startPosition.row + 1,
33218
+ [cls ? `${cls.id}.${spec.action}` : self],
33219
+ cls ? 0.9 : 0.5
33220
+ );
33221
+ }
33222
+ }
33223
+ function attributesOf(n) {
33224
+ return (n.childForFieldName("attributes")?.descendantsOfType("attribute") ?? []).map(
33225
+ (attr) => ({ attr, fq: fqcnOf(attr.namedChildren[0]?.text ?? "") })
33226
+ );
33227
+ }
33228
+ function routeSpecs(n) {
33229
+ const out = [];
33230
+ for (const { attr, fq } of attributesOf(n)) {
33231
+ const short = lastSegment(fq);
33232
+ if (fq.startsWith(SPATIE_ATTRIBUTES)) {
33233
+ const verb = short === "Any" ? "ALL" : httpMethod(short);
33234
+ const pos = positionalArgs(attr);
33235
+ const methods2 = short === "Route" ? stringsOf(namedArg(attr, "method") ?? pos.shift()).map((m) => httpMethod(m)).filter((m) => !!m) : verb ? [verb] : [];
33236
+ const path4 = phpString(namedArg(attr, "uri") ?? pos[0]);
33237
+ if (path4 === void 0) continue;
33238
+ for (const method of methods2) out.push({ method, path: path4, spatie: true });
33239
+ continue;
33240
+ }
33241
+ if (short !== "Route") continue;
33242
+ const path3 = phpString(namedArg(attr, "path")) ?? phpString(positionalArgs(attr)[0]);
33243
+ if (path3 === void 0) continue;
33244
+ const methods = stringsOf(namedArg(attr, "methods")).map((m) => httpMethod(m)).filter((m) => !!m);
33245
+ for (const method of methods.length ? methods : ["ALL"]) out.push({ method, path: path3 });
33246
+ }
33247
+ const doc = n.previousNamedSibling;
33248
+ if (doc?.type === "comment" && doc.text.includes("@Route"))
33249
+ out.push(...parseRouteAnnotations(doc.text));
33250
+ return out;
33251
+ }
33252
+ function attributeRoutes(cls, className) {
33253
+ const classSpecs = routeSpecs(cls);
33254
+ let prefix = classSpecs[0]?.path;
33255
+ const framework = frameworkOf(importedPkgs) ?? "symfony";
33256
+ const line = cls.startPosition.row + 1;
33257
+ for (const { attr, fq } of attributesOf(cls)) {
33258
+ if (!fq.startsWith(SPATIE_ATTRIBUTES)) continue;
33259
+ const short = lastSegment(fq);
33260
+ const pos = positionalArgs(attr);
33261
+ if (short === "Prefix") prefix = phpString(namedArg(attr, "prefix") ?? pos[0]) ?? prefix;
33262
+ if (short !== "Resource") continue;
33263
+ const name = phpString(namedArg(attr, "resource") ?? pos[0]);
33264
+ if (!name) continue;
33265
+ const api = (namedArg(attr, "apiResource") ?? pos[1])?.text === "true";
33266
+ const only = namedArg(attr, "only");
33267
+ const except = namedArg(attr, "except");
33268
+ for (const spec of laravelResource(
33269
+ name,
33270
+ api,
33271
+ only ? stringsOf(only) : void 0,
33272
+ except ? stringsOf(except) : void 0
33273
+ ))
33274
+ addRoute(
33275
+ spec.method,
33276
+ phpRoutePath(prefix, spec.path),
33277
+ "laravel",
33278
+ line,
33279
+ [symId(rel, `${className}.${spec.action}`)],
33280
+ 0.95
33281
+ );
33282
+ }
33283
+ for (const m of cls.childForFieldName("body")?.namedChildren ?? []) {
33284
+ if (m?.type !== "method_declaration") continue;
33285
+ const mname = m.childForFieldName("name")?.text;
33286
+ if (!mname) continue;
33287
+ const specs = routeSpecs(m);
33288
+ if (!specs.length && mname === "__invoke" && classSpecs.length) {
33289
+ for (const s of classSpecs)
33290
+ addRoute(
33291
+ s.method,
33292
+ phpRoutePath(s.path),
33293
+ framework,
33294
+ line,
33295
+ [symId(rel, `${className}.${mname}`)],
33296
+ 0.95
33297
+ );
33298
+ continue;
33299
+ }
33300
+ for (const s of specs) {
33301
+ addRoute(
33302
+ s.method,
33303
+ phpRoutePath(prefix, s.path),
33304
+ s.spatie ? "laravel" : framework,
33305
+ m.startPosition.row + 1,
33306
+ [symId(rel, `${className}.${mname}`)],
33307
+ 0.95
33308
+ );
33309
+ }
33310
+ }
33311
+ }
33312
+ function walkRefs(n) {
33313
+ noteVarTypes(n);
33314
+ for (const call of n.descendantsOfType(CALLS)) {
33315
+ const caller = enclosing(call);
33316
+ routeFromCall(call, caller);
33317
+ const target = callTarget(call, caller);
33318
+ if (!target) continue;
33319
+ refs.push(
33320
+ caller ? symId(rel, caller) : fid,
33321
+ target.id,
33322
+ "calls",
33323
+ target.confidence,
33324
+ call.startPosition.row + 1
33325
+ );
33326
+ }
33327
+ const named = [];
33328
+ for (const t of n.descendantsOfType("named_type")) named.push(t);
33329
+ for (const a of n.descendantsOfType([
33330
+ "class_constant_access_expression",
33331
+ "scoped_property_access_expression"
33332
+ ])) {
33333
+ const s = a.namedChildren[0];
33334
+ if (s && (s.type === "name" || s.type === "qualified_name")) named.push(s);
33335
+ }
33336
+ for (const b of n.descendantsOfType("binary_expression")) {
33337
+ const right = b.childForFieldName("right");
33338
+ if (b.childForFieldName("operator")?.type === "instanceof" && right) named.push(right);
33339
+ }
33340
+ for (const t of named) {
33341
+ const target = classRef(t.text);
33342
+ if (!target) continue;
33343
+ const caller = enclosing(t);
33344
+ refs.push(
33345
+ caller ? symId(rel, caller) : fid,
33346
+ target.id,
33347
+ "references",
33348
+ target.confidence * 0.9,
33349
+ t.startPosition.row + 1
33350
+ );
33351
+ }
33352
+ for (const me of n.descendantsOfType([
33353
+ "member_access_expression",
33354
+ "nullsafe_member_access_expression"
33355
+ ])) {
33356
+ if (me.childForFieldName("object")?.text !== "$this") continue;
33357
+ const caller = enclosing(me);
33358
+ const cls = classOf(caller);
33359
+ const prop = me.childForFieldName("name")?.text;
33360
+ if (!cls || !prop || !localSyms.has(`${cls}.${prop}`)) continue;
33361
+ refs.push(
33362
+ symId(rel, caller),
33363
+ symId(rel, `${cls}.${prop}`),
33364
+ "references",
33365
+ 0.81,
33366
+ me.startPosition.row + 1
33367
+ );
33368
+ }
33369
+ }
33370
+ }
33371
+ var BRANCHES2, BOOL_OPS2, CALLS, CLASS_KINDS, RELATIVE, ROUTER_VARS, CONTROLLER_NAMESPACES, SPATIE_ATTRIBUTES, isFile, toRel, CLASSMAP_MAX_FILES, composerCache, asList, CONTROLLER_STRING, isPublic, classOf, lastSegment, isCall;
33372
+ var init_php = __esm({
33373
+ "src/graph/languages/php.ts"() {
33374
+ "use strict";
33375
+ init_esm_shims();
33376
+ init_ids();
33377
+ init_routes();
33378
+ init_parser();
33379
+ BRANCHES2 = [
33380
+ "if_statement",
33381
+ "else_if_clause",
33382
+ "for_statement",
33383
+ "foreach_statement",
33384
+ "while_statement",
33385
+ "do_statement",
33386
+ "case_statement",
33387
+ "catch_clause",
33388
+ "conditional_expression",
33389
+ "match_conditional_expression",
33390
+ "binary_expression"
33391
+ ];
33392
+ BOOL_OPS2 = /* @__PURE__ */ new Set(["&&", "||", "and", "or", "??"]);
33393
+ CALLS = [
33394
+ "function_call_expression",
33395
+ "member_call_expression",
33396
+ "nullsafe_member_call_expression",
33397
+ "scoped_call_expression",
33398
+ "object_creation_expression"
33399
+ ];
33400
+ CLASS_KINDS = {
33401
+ class_declaration: "class",
33402
+ interface_declaration: "interface",
33403
+ trait_declaration: "trait",
33404
+ enum_declaration: "enum"
33405
+ };
33406
+ RELATIVE = /* @__PURE__ */ new Set(["self", "static", "parent"]);
33407
+ ROUTER_VARS = /* @__PURE__ */ new Set(["$app", "$router", "$routes", "$route", "$group", "$r"]);
33408
+ CONTROLLER_NAMESPACES = ["App\\Http\\Controllers", "App\\Controllers"];
33409
+ SPATIE_ATTRIBUTES = "Spatie\\RouteAttributes\\Attributes\\";
33410
+ isFile = (p) => {
33411
+ try {
33412
+ return statSync2(p).isFile();
33413
+ } catch {
33414
+ return false;
33415
+ }
33416
+ };
33417
+ toRel = (root, abs) => normalize2(relative3(root, abs)).split("\\").join("/");
33418
+ CLASSMAP_MAX_FILES = 5e3;
33419
+ composerCache = /* @__PURE__ */ new Map();
33420
+ asList = (v) => Array.isArray(v) ? v : [v];
33421
+ CONTROLLER_STRING = /^\\?([\w\\]+?)(?:@|::?)(\w+)$/;
33422
+ isPublic = (m) => !m.namedChildren.some((c) => c?.type === "visibility_modifier" && c.text !== "public");
33423
+ classOf = (caller) => caller?.includes(".") ? caller.split(".")[0] : void 0;
33424
+ lastSegment = (name) => name.split("\\").pop() ?? name;
33425
+ isCall = (n) => n.type === "member_call_expression" || n.type === "scoped_call_expression";
33426
+ }
33427
+ });
33428
+
33429
+ // src/graph/languages/python.ts
33430
+ import { existsSync as existsSync15, statSync as statSync3 } from "fs";
33431
+ import { dirname as dirname6, join as join18, normalize as normalize3, relative as relative4, resolve as resolve5 } from "path";
32299
33432
  function resolvePython(root, fromRel, module, level) {
32300
33433
  const parts = module ? module.split(".") : [];
32301
33434
  const candidates2 = [];
32302
33435
  if (level > 0) {
32303
- let base = dirname5(resolve4(root, fromRel));
32304
- for (let i = 1; i < level; i++) base = dirname5(base);
32305
- candidates2.push(join17(base, ...parts));
33436
+ let base = dirname6(resolve5(root, fromRel));
33437
+ for (let i = 1; i < level; i++) base = dirname6(base);
33438
+ candidates2.push(join18(base, ...parts));
32306
33439
  } else {
32307
- candidates2.push(resolve4(root, ...parts), resolve4(root, "src", ...parts));
32308
- let dir = dirname5(resolve4(root, fromRel));
33440
+ candidates2.push(resolve5(root, ...parts), resolve5(root, "src", ...parts));
33441
+ let dir = dirname6(resolve5(root, fromRel));
32309
33442
  while (dir.startsWith(root) && dir !== root) {
32310
- candidates2.push(join17(dir, ...parts));
32311
- dir = dirname5(dir);
33443
+ candidates2.push(join18(dir, ...parts));
33444
+ dir = dirname6(dir);
32312
33445
  }
32313
33446
  }
32314
33447
  for (const c of candidates2) {
32315
- for (const f of [`${c}.py`, join17(c, "__init__.py"), `${c}.pyi`]) {
32316
- if (isFile(f) && !relative3(root, f).startsWith(".."))
32317
- return normalize2(relative3(root, f)).split("\\").join("/");
33448
+ for (const f of [`${c}.py`, join18(c, "__init__.py"), `${c}.pyi`]) {
33449
+ if (isFile2(f) && !relative4(root, f).startsWith(".."))
33450
+ return normalize3(relative4(root, f)).split("\\").join("/");
32318
33451
  }
32319
33452
  }
32320
33453
  return void 0;
@@ -32351,7 +33484,7 @@ async function parsePython(root, rel, code) {
32351
33484
  walkRefs(stmt);
32352
33485
  }
32353
33486
  edges.push(...refs.edges());
32354
- if (!existsSync15(join17(root, rel))) {
33487
+ if (!existsSync15(join18(root, rel))) {
32355
33488
  }
32356
33489
  const errors = tree.rootNode.hasError ? tree.rootNode.descendantsOfType("ERROR").length : 0;
32357
33490
  fileNode.meta = {
@@ -32504,7 +33637,7 @@ async function parsePython(root, rel, code) {
32504
33637
  }
32505
33638
  function addSym(qualified, kind, n, exported) {
32506
33639
  const id = symId(rel, qualified);
32507
- const c = kind === "function" || kind === "method" ? complexityOf(n, BRANCHES2, BOOL_OPS2) : 0;
33640
+ const c = kind === "function" || kind === "method" ? complexityOf(n, BRANCHES3, BOOL_OPS3) : 0;
32508
33641
  complexity += c;
32509
33642
  nodes.push({
32510
33643
  id,
@@ -32647,7 +33780,7 @@ async function parsePython(root, rel, code) {
32647
33780
  }
32648
33781
  }
32649
33782
  }
32650
- var BRANCHES2, BOOL_OPS2, isFile, topModule, REF_SKIP_PARENT;
33783
+ var BRANCHES3, BOOL_OPS3, isFile2, topModule, REF_SKIP_PARENT;
32651
33784
  var init_python = __esm({
32652
33785
  "src/graph/languages/python.ts"() {
32653
33786
  "use strict";
@@ -32655,7 +33788,7 @@ var init_python = __esm({
32655
33788
  init_ids();
32656
33789
  init_routes();
32657
33790
  init_parser();
32658
- BRANCHES2 = [
33791
+ BRANCHES3 = [
32659
33792
  "if_statement",
32660
33793
  "elif_clause",
32661
33794
  "for_statement",
@@ -32666,10 +33799,10 @@ var init_python = __esm({
32666
33799
  "case_clause",
32667
33800
  "for_in_clause"
32668
33801
  ];
32669
- BOOL_OPS2 = /* @__PURE__ */ new Set(["and", "or"]);
32670
- isFile = (p) => {
33802
+ BOOL_OPS3 = /* @__PURE__ */ new Set(["and", "or"]);
33803
+ isFile2 = (p) => {
32671
33804
  try {
32672
- return statSync2(p).isFile();
33805
+ return statSync3(p).isFile();
32673
33806
  } catch {
32674
33807
  return false;
32675
33808
  }
@@ -32698,21 +33831,21 @@ var init_python = __esm({
32698
33831
  });
32699
33832
 
32700
33833
  // src/graph/resolve.ts
32701
- import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync3 } from "fs";
32702
- import { dirname as dirname6, join as join18, normalize as normalize3, relative as relative4, resolve as resolve5 } from "path";
33834
+ import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
33835
+ import { dirname as dirname7, join as join19, normalize as normalize4, relative as relative5, resolve as resolve6 } from "path";
32703
33836
  function loadTsPaths(root) {
32704
33837
  const hit = tsPathsCache.get(root);
32705
33838
  if (hit !== void 0) return hit;
32706
33839
  let out = null;
32707
33840
  try {
32708
- const file3 = join18(root, "tsconfig.json");
33841
+ const file3 = join19(root, "tsconfig.json");
32709
33842
  if (existsSync16(file3)) {
32710
- const raw = readFileSync12(file3, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
33843
+ const raw = readFileSync13(file3, "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "").replace(/,(\s*[}\]])/g, "$1");
32711
33844
  const cfg = JSON.parse(raw);
32712
33845
  const co = cfg.compilerOptions ?? {};
32713
33846
  if (co.paths) {
32714
33847
  out = {
32715
- baseUrl: resolve5(root, co.baseUrl ?? "."),
33848
+ baseUrl: resolve6(root, co.baseUrl ?? "."),
32716
33849
  paths: Object.entries(co.paths).map(([k, v]) => {
32717
33850
  const star = k.indexOf("*");
32718
33851
  return star === -1 ? { prefix: k, suffix: "\0", targets: v } : { prefix: k.slice(0, star), suffix: k.slice(star + 1), targets: v };
@@ -32729,7 +33862,7 @@ function loadTsPaths(root) {
32729
33862
  function probe(abs) {
32730
33863
  const tryFile = (p) => {
32731
33864
  try {
32732
- return statSync3(p).isFile() ? p : void 0;
33865
+ return statSync4(p).isFile() ? p : void 0;
32733
33866
  } catch {
32734
33867
  return void 0;
32735
33868
  }
@@ -32745,16 +33878,16 @@ function probe(abs) {
32745
33878
  if (tryFile(abs + ext)) return abs + ext;
32746
33879
  }
32747
33880
  for (const ext of EXTS) {
32748
- const idx = join18(abs, `index${ext}`);
33881
+ const idx = join19(abs, `index${ext}`);
32749
33882
  if (tryFile(idx)) return idx;
32750
33883
  }
32751
33884
  return void 0;
32752
33885
  }
32753
33886
  function readPackageJson(dir) {
32754
33887
  try {
32755
- const file3 = join18(dir, "package.json");
33888
+ const file3 = join19(dir, "package.json");
32756
33889
  if (!existsSync16(file3)) return void 0;
32757
- return JSON.parse(readFileSync12(file3, "utf8"));
33890
+ return JSON.parse(readFileSync13(file3, "utf8"));
32758
33891
  } catch {
32759
33892
  return void 0;
32760
33893
  }
@@ -32770,9 +33903,9 @@ function loadWorkspaces(root) {
32770
33903
  if (Array.isArray(ws)) globs.push(...ws);
32771
33904
  else if (ws?.packages) globs.push(...ws.packages);
32772
33905
  try {
32773
- const yaml = join18(root, "pnpm-workspace.yaml");
33906
+ const yaml = join19(root, "pnpm-workspace.yaml");
32774
33907
  if (existsSync16(yaml)) {
32775
- for (const m of readFileSync12(yaml, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
33908
+ for (const m of readFileSync13(yaml, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
32776
33909
  globs.push(m[1].trim());
32777
33910
  }
32778
33911
  } catch {
@@ -32781,12 +33914,12 @@ function loadWorkspaces(root) {
32781
33914
  if (g.startsWith("!")) continue;
32782
33915
  const star = g.indexOf("*");
32783
33916
  const dirs = [];
32784
- if (star === -1) dirs.push(resolve5(root, g));
33917
+ if (star === -1) dirs.push(resolve6(root, g));
32785
33918
  else {
32786
- const base = resolve5(root, g.slice(0, star));
33919
+ const base = resolve6(root, g.slice(0, star));
32787
33920
  try {
32788
- for (const d of readdirSync6(base, { withFileTypes: true })) {
32789
- if (d.isDirectory()) dirs.push(join18(base, d.name));
33921
+ for (const d of readdirSync7(base, { withFileTypes: true })) {
33922
+ if (d.isDirectory()) dirs.push(join19(base, d.name));
32790
33923
  }
32791
33924
  } catch {
32792
33925
  }
@@ -32840,16 +33973,16 @@ function resolvePackageEntry(dir, sub) {
32840
33973
  } else candidates2.push(sub, `src/${sub}`, `lib/${sub}`);
32841
33974
  for (const c of candidates2) {
32842
33975
  if (c.includes("*")) continue;
32843
- const hit = probe(resolve5(dir, c));
33976
+ const hit = probe(resolve6(dir, c));
32844
33977
  if (hit) return hit;
32845
33978
  }
32846
33979
  return void 0;
32847
33980
  }
32848
33981
  function resolveImport(root, fromRel, spec) {
32849
33982
  if (spec.startsWith(".") || spec.startsWith("/")) {
32850
- const abs = spec.startsWith("/") ? spec : resolve5(root, dirname6(fromRel), spec);
33983
+ const abs = spec.startsWith("/") ? spec : resolve6(root, dirname7(fromRel), spec);
32851
33984
  const hit = probe(abs);
32852
- return hit ? normalize3(relative4(root, hit)) : void 0;
33985
+ return hit ? normalize4(relative5(root, hit)) : void 0;
32853
33986
  }
32854
33987
  const tp = loadTsPaths(root);
32855
33988
  if (tp) {
@@ -32858,8 +33991,8 @@ function resolveImport(root, fromRel, spec) {
32858
33991
  if (exact ? spec === p.prefix : spec.startsWith(p.prefix) && spec.endsWith(p.suffix)) {
32859
33992
  const star = exact ? "" : spec.slice(p.prefix.length, spec.length - p.suffix.length);
32860
33993
  for (const t of p.targets) {
32861
- const hit = probe(resolve5(tp.baseUrl, t.replace("*", star)));
32862
- if (hit) return normalize3(relative4(root, hit));
33994
+ const hit = probe(resolve6(tp.baseUrl, t.replace("*", star)));
33995
+ if (hit) return normalize4(relative5(root, hit));
32863
33996
  }
32864
33997
  }
32865
33998
  }
@@ -32868,7 +34001,7 @@ function resolveImport(root, fromRel, spec) {
32868
34001
  const dir = loadWorkspaces(root).get(name);
32869
34002
  if (dir) {
32870
34003
  const hit = resolvePackageEntry(dir, spec.slice(name.length + 1));
32871
- if (hit && !relative4(root, hit).startsWith("..")) return normalize3(relative4(root, hit));
34004
+ if (hit && !relative5(root, hit).startsWith("..")) return normalize4(relative5(root, hit));
32872
34005
  }
32873
34006
  return void 0;
32874
34007
  }
@@ -32932,6 +34065,7 @@ async function parseFile(root, rel, code) {
32932
34065
  if (!grammar) return { nodes: [], edges: [], exports: [] };
32933
34066
  if (grammar === "python") return parsePython(root, rel, code);
32934
34067
  if (grammar === "go") return parseGo(root, rel, code);
34068
+ if (grammar === "php") return parsePhp(root, rel, code);
32935
34069
  const p = await getParser(grammar);
32936
34070
  const tree = p.parse(code);
32937
34071
  if (!tree) return { nodes: [], edges: [], exports: [] };
@@ -33524,6 +34658,7 @@ var init_treesitter = __esm({
33524
34658
  init_ids();
33525
34659
  init_go();
33526
34660
  init_parser();
34661
+ init_php();
33527
34662
  init_python();
33528
34663
  init_resolve();
33529
34664
  init_routes();
@@ -33789,6 +34924,11 @@ function inherited(db, classId, member, depth) {
33789
34924
  }
33790
34925
  return void 0;
33791
34926
  }
34927
+ function ownerAncestor(db, path3, name) {
34928
+ const dot = name.indexOf(".");
34929
+ const owner = symId(path3, name.slice(0, dot));
34930
+ return db.graphGetNode(owner) ? inherited(db, owner, name.slice(dot + 1), 0) : void 0;
34931
+ }
33792
34932
  function defaultExportOf(db, path3) {
33793
34933
  const f = db.graphGetNode(fileId(path3));
33794
34934
  if (!f?.meta) return void 0;
@@ -33837,6 +34977,10 @@ function linkGraph(db) {
33837
34977
  direct = true;
33838
34978
  } else target = goSibling(db, path3, head);
33839
34979
  }
34980
+ if (!target && name.includes(".")) {
34981
+ target = ownerAncestor(db, path3, name);
34982
+ direct = true;
34983
+ }
33840
34984
  if (target && name.includes(".") && !direct) {
33841
34985
  const rest3 = name.slice(name.indexOf(".") + 1);
33842
34986
  const member = `${target}.${rest3}`;
@@ -33875,7 +35019,7 @@ function linkGraph(db) {
33875
35019
  const name = meta4.unresolved;
33876
35020
  if (!name) continue;
33877
35021
  const id = symId(e.dst.slice(5), name);
33878
- const target = db.graphGetNode(id) ? id : viaReexports(db, e.dst.slice(5), name.split(".")[0], 0, /* @__PURE__ */ new Set());
35022
+ const target = db.graphGetNode(id) ? id : viaReexports(db, e.dst.slice(5), name.split(".")[0], 0, /* @__PURE__ */ new Set()) ?? (name.includes(".") ? ownerAncestor(db, e.dst.slice(5), name) : void 0);
33879
35023
  if (!target || target === e.src) continue;
33880
35024
  const { unresolved: _u, ...rest2 } = meta4;
33881
35025
  replace(db, e, {
@@ -33918,8 +35062,8 @@ var init_link = __esm({
33918
35062
  });
33919
35063
 
33920
35064
  // src/graph/reindex.ts
33921
- import { readFileSync as readFileSync13, statSync as statSync4 } from "fs";
33922
- import { join as join19 } from "path";
35065
+ import { readFileSync as readFileSync14, statSync as statSync5 } from "fs";
35066
+ import { join as join20 } from "path";
33923
35067
  function listCodeFiles(ctx) {
33924
35068
  return import_fast_glob2.default.sync(ctx.config.graph.include, {
33925
35069
  cwd: ctx.root,
@@ -33930,8 +35074,8 @@ function listCodeFiles(ctx) {
33930
35074
  });
33931
35075
  }
33932
35076
  async function indexOne(ctx, db, rel, force) {
33933
- const abs = join19(ctx.root, rel);
33934
- const st = statSync4(abs);
35077
+ const abs = join20(ctx.root, rel);
35078
+ const st = statSync5(abs);
33935
35079
  const backend = getCodeBackend(ctx);
33936
35080
  const language = backend.languageOf(rel) ?? null;
33937
35081
  if (st.size > MAX_CODE_BYTES) {
@@ -33950,7 +35094,7 @@ async function indexOne(ctx, db, rel, force) {
33950
35094
  }
33951
35095
  return "skipped";
33952
35096
  }
33953
- const code = readFileSync13(abs, "utf8");
35097
+ const code = readFileSync14(abs, "utf8");
33954
35098
  const hash2 = hashText(code);
33955
35099
  const prev = db.fileIndexGet(rel);
33956
35100
  if (!force && prev?.hash === hash2 && prev.status !== "skipped") return "unchanged";
@@ -34031,7 +35175,7 @@ async function reindexCode(ctx, db, opts = {}) {
34031
35175
  summary.unsupported++;
34032
35176
  if (db.fileIndexGet(rel)?.status === "unsupported") continue;
34033
35177
  try {
34034
- const st = statSync4(join19(ctx.root, rel));
35178
+ const st = statSync5(join20(ctx.root, rel));
34035
35179
  db.fileIndexSet({
34036
35180
  path: rel,
34037
35181
  hash: "",
@@ -34047,7 +35191,7 @@ async function reindexCode(ctx, db, opts = {}) {
34047
35191
  }
34048
35192
  for (const rel of targets) {
34049
35193
  try {
34050
- if (!statSafe(join19(ctx.root, rel))) {
35194
+ if (!statSafe(join20(ctx.root, rel))) {
34051
35195
  if (db.fileIndexGet(rel)) {
34052
35196
  removeOne(db, rel);
34053
35197
  summary.removed++;
@@ -34059,7 +35203,7 @@ async function reindexCode(ctx, db, opts = {}) {
34059
35203
  const error62 = err.message;
34060
35204
  summary.errors.push({ path: rel, error: error62 });
34061
35205
  try {
34062
- const st = statSync4(join19(ctx.root, rel));
35206
+ const st = statSync5(join20(ctx.root, rel));
34063
35207
  db.fileIndexSet({
34064
35208
  path: rel,
34065
35209
  hash: "",
@@ -34122,7 +35266,7 @@ async function reindexCode(ctx, db, opts = {}) {
34122
35266
  }
34123
35267
  function statSafe(p) {
34124
35268
  try {
34125
- return statSync4(p).isFile();
35269
+ return statSync5(p).isFile();
34126
35270
  } catch {
34127
35271
  return false;
34128
35272
  }
@@ -34162,10 +35306,10 @@ function indexFreshness(ctx, db) {
34162
35306
  const dirty = [];
34163
35307
  for (const r of rows.slice(0, 400)) {
34164
35308
  try {
34165
- const abs = join19(ctx.root, r.path);
34166
- const st = statSync4(abs);
35309
+ const abs = join20(ctx.root, r.path);
35310
+ const st = statSync5(abs);
34167
35311
  if (r.mtime !== null && Math.abs(st.mtimeMs - r.mtime) < 1 && st.size === r.size) continue;
34168
- if (hashText(readFileSync13(abs, "utf8")) !== r.hash) dirty.push(r.path);
35312
+ if (hashText(readFileSync14(abs, "utf8")) !== r.hash) dirty.push(r.path);
34169
35313
  } catch {
34170
35314
  dirty.push(r.path);
34171
35315
  }
@@ -34199,22 +35343,22 @@ var init_reindex = __esm({
34199
35343
  });
34200
35344
 
34201
35345
  // src/session/current.ts
34202
- import { readFileSync as readFileSync14, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
34203
- import { join as join20 } from "path";
35346
+ import { readFileSync as readFileSync15, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
35347
+ import { join as join21 } from "path";
34204
35348
  function file2(ctx) {
34205
- return join20(projectDir(ctx.id), "current-session");
35349
+ return join21(projectDir(ctx.id), "current-session");
34206
35350
  }
34207
35351
  function getCurrentSession(ctx) {
34208
35352
  if (process.env.EMINIAR_SESSION_ID) return process.env.EMINIAR_SESSION_ID;
34209
35353
  const f = file2(ctx);
34210
35354
  let mtimeMs;
34211
35355
  try {
34212
- mtimeMs = statSync5(f).mtimeMs;
35356
+ mtimeMs = statSync6(f).mtimeMs;
34213
35357
  } catch {
34214
35358
  return "unknown";
34215
35359
  }
34216
35360
  if (cached4 && cached4.file === f && cached4.mtimeMs === mtimeMs) return cached4.id;
34217
- const id = readFileSync14(f, "utf8").trim() || "unknown";
35361
+ const id = readFileSync15(f, "utf8").trim() || "unknown";
34218
35362
  cached4 = { file: f, mtimeMs, id };
34219
35363
  return id;
34220
35364
  }
@@ -34383,13 +35527,13 @@ __export(store_exports, {
34383
35527
  import {
34384
35528
  existsSync as existsSync17,
34385
35529
  mkdirSync as mkdirSync7,
34386
- readFileSync as readFileSync15,
35530
+ readFileSync as readFileSync16,
34387
35531
  renameSync,
34388
- statSync as statSync6,
35532
+ statSync as statSync7,
34389
35533
  unlinkSync,
34390
35534
  writeFileSync as writeFileSync7
34391
35535
  } from "fs";
34392
- import { dirname as dirname7, join as join21, relative as relative5, sep } from "path";
35536
+ import { dirname as dirname8, join as join22, relative as relative6, sep } from "path";
34393
35537
  function isStatus(s) {
34394
35538
  return typeof s === "string" && MEMORY_STATUSES.includes(s);
34395
35539
  }
@@ -34403,16 +35547,16 @@ function memoriesRel(ctx) {
34403
35547
  return ctx.config.memories.dir.replace(/^\.\//, "").replace(/\/+$/, "");
34404
35548
  }
34405
35549
  function memoriesDir(ctx) {
34406
- return join21(ctx.root, memoriesRel(ctx));
35550
+ return join22(ctx.root, memoriesRel(ctx));
34407
35551
  }
34408
35552
  function relOf(ctx, name) {
34409
35553
  return `${memoriesRel(ctx)}/${name}.md`;
34410
35554
  }
34411
35555
  function absOf(ctx, name) {
34412
- return join21(ctx.root, relOf(ctx, name));
35556
+ return join22(ctx.root, relOf(ctx, name));
34413
35557
  }
34414
35558
  function nameOfFile(ctx, abs) {
34415
- const rel = relative5(memoriesDir(ctx), abs).split(sep).join("/");
35559
+ const rel = relative6(memoriesDir(ctx), abs).split(sep).join("/");
34416
35560
  if (!rel.endsWith(".md")) return void 0;
34417
35561
  const name = rel.slice(0, -3);
34418
35562
  return isValidName(name) ? name : void 0;
@@ -34451,7 +35595,7 @@ function normalizeRelated(ids) {
34451
35595
  }
34452
35596
  function writeMemoryFile(ctx, name, meta4, body) {
34453
35597
  const abs = absOf(ctx, name);
34454
- mkdirSync7(dirname7(abs), { recursive: true });
35598
+ mkdirSync7(dirname8(abs), { recursive: true });
34455
35599
  writeFileSync7(abs, serializeMemoryFile(meta4, `
34456
35600
  ${body.trim()}
34457
35601
  `));
@@ -34496,12 +35640,12 @@ function syncFile(ctx, db, name, opts = {}) {
34496
35640
  const abs = absOf(ctx, name);
34497
35641
  const rel = relOf(ctx, name);
34498
35642
  if (!existsSync17(abs)) return void 0;
34499
- let st = statSync6(abs);
35643
+ let st = statSync7(abs);
34500
35644
  const row = db.memoryGetByName(name);
34501
35645
  const mtime = Math.floor(st.mtimeMs);
34502
35646
  if (!opts.force && row && row.file_mtime === mtime && row.file_size === st.size)
34503
35647
  return { changed: false, row };
34504
- let text3 = readFileSync15(abs, "utf8");
35648
+ let text3 = readFileSync16(abs, "utf8");
34505
35649
  let hash2 = hashText(text3);
34506
35650
  if (!opts.force && row && row.file_hash === hash2) {
34507
35651
  db.memoryUpsertByName({
@@ -34540,7 +35684,7 @@ function syncFile(ctx, db, name, opts = {}) {
34540
35684
  ${parsed.body.replace(/^\s*\n/, "")}`
34541
35685
  );
34542
35686
  writeFileSync7(abs, text3);
34543
- st = statSync6(abs);
35687
+ st = statSync7(abs);
34544
35688
  hash2 = hashText(text3);
34545
35689
  }
34546
35690
  const body = parsed.body.trim();
@@ -34578,7 +35722,7 @@ tags: ${tags.join(", ")}` : ""}`,
34578
35722
  function setMeta(ctx, db, name, patch) {
34579
35723
  const abs = absOf(ctx, name);
34580
35724
  if (!existsSync17(abs)) return false;
34581
- const parsed = parseMemoryFile(readFileSync15(abs, "utf8"));
35725
+ const parsed = parseMemoryFile(readFileSync16(abs, "utf8"));
34582
35726
  const meta4 = { ...parsed.meta };
34583
35727
  for (const [k, v] of Object.entries(patch)) {
34584
35728
  if (v === void 0) delete meta4[k];
@@ -34627,7 +35771,7 @@ function reconcile(ctx, db) {
34627
35771
  for (const f of files) {
34628
35772
  const name = nameOfFile(ctx, f);
34629
35773
  if (!name) {
34630
- out.skipped.push(relative5(ctx.root, f));
35774
+ out.skipped.push(relative6(ctx.root, f));
34631
35775
  continue;
34632
35776
  }
34633
35777
  seen.add(name);
@@ -34659,7 +35803,7 @@ function write(ctx, db, input2) {
34659
35803
  let extra = {};
34660
35804
  if (exists) {
34661
35805
  try {
34662
- extra = parseMemoryFile(readFileSync15(abs, "utf8")).meta;
35806
+ extra = parseMemoryFile(readFileSync16(abs, "utf8")).meta;
34663
35807
  } catch {
34664
35808
  }
34665
35809
  }
@@ -34686,7 +35830,7 @@ function write(ctx, db, input2) {
34686
35830
  function read(ctx, db, name) {
34687
35831
  const synced = syncFile(ctx, db, name);
34688
35832
  if (!synced) return void 0;
34689
- const parsed = parseMemoryFile(readFileSync15(absOf(ctx, name), "utf8"));
35833
+ const parsed = parseMemoryFile(readFileSync16(absOf(ctx, name), "utf8"));
34690
35834
  return {
34691
35835
  name,
34692
35836
  kind: synced.row.kind,
@@ -34713,7 +35857,7 @@ function edit(ctx, db, name, opts) {
34713
35857
  const abs = absOf(ctx, name);
34714
35858
  if (!existsSync17(abs)) throw new Error(`memory "${name}" does not exist`);
34715
35859
  syncFile(ctx, db, name);
34716
- const parsed = parseMemoryFile(readFileSync15(abs, "utf8"));
35860
+ const parsed = parseMemoryFile(readFileSync16(abs, "utf8"));
34717
35861
  const re = new RegExp(opts.regex ? opts.find : escapeRe(opts.find), "g");
34718
35862
  const count = (parsed.body.match(re) ?? []).length;
34719
35863
  if (!count) return 0;
@@ -34731,9 +35875,9 @@ function rename(ctx, db, from, to) {
34731
35875
  const absTo = absOf(ctx, to);
34732
35876
  if (!existsSync17(absFrom)) throw new Error(`memory "${from}" does not exist`);
34733
35877
  if (existsSync17(absTo) || db.memoryGetByName(to)) throw new Error(`memory "${to}" already exists`);
34734
- mkdirSync7(dirname7(absTo), { recursive: true });
35878
+ mkdirSync7(dirname8(absTo), { recursive: true });
34735
35879
  renameSync(absFrom, absTo);
34736
- const parsed = parseMemoryFile(readFileSync15(absTo, "utf8"));
35880
+ const parsed = parseMemoryFile(readFileSync16(absTo, "utf8"));
34737
35881
  writeFileSync7(
34738
35882
  absTo,
34739
35883
  serializeMemoryFile(
@@ -34749,7 +35893,7 @@ function rename(ctx, db, from, to) {
34749
35893
  if (!row.name || row.name === to) continue;
34750
35894
  const abs = absOf(ctx, row.name);
34751
35895
  if (!existsSync17(abs)) continue;
34752
- const text3 = readFileSync15(abs, "utf8");
35896
+ const text3 = readFileSync16(abs, "utf8");
34753
35897
  if (!ref.test(text3)) continue;
34754
35898
  ref.lastIndex = 0;
34755
35899
  writeFileSync7(abs, text3.replace(ref, `mem:${to}`));
@@ -34768,11 +35912,11 @@ function remove(ctx, db, name) {
34768
35912
  function gitignoreHint(ctx) {
34769
35913
  if (hinted.has(ctx.root)) return void 0;
34770
35914
  hinted.add(ctx.root);
34771
- const file3 = join21(ctx.root, ".gitignore");
35915
+ const file3 = join22(ctx.root, ".gitignore");
34772
35916
  if (!existsSync17(file3)) return void 0;
34773
35917
  const dir = memoriesRel(ctx);
34774
35918
  const top = dir.split("/")[0];
34775
- const ignored = readFileSync15(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top || l === dir || l === `${top}/**` || l === `${dir}/**`);
35919
+ const ignored = readFileSync16(file3, "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, "").replace(/\/$/, "")).some((l) => l === top || l === dir || l === `${top}/**` || l === `${dir}/**`);
34776
35920
  return ignored ? `note: .gitignore ignores ${dir}, so memories stay local to this machine (remove that line to share them with the team)` : void 0;
34777
35921
  }
34778
35922
  var import_fast_glob3, MEMORY_KINDS, SEGMENT, NAME_RE, hinted;
@@ -34846,7 +35990,7 @@ var init_em_graph = __esm({
34846
35990
  });
34847
35991
  emGraphQuery = defineTool({
34848
35992
  name: "em_graph_query",
34849
- description: 'Look up nodes in the project knowledge graph (files, functions, classes, routes, packages, people, concepts, memories) by name/path, run a preset (hotspots, coupled, orphans, dead_code, routes, cycles, clusters, stats), or run a structured pattern: start at nodes matching `from`, walk `edge.kinds` in `edge.direction` for `edge.depth` hops, keep what matches `to` \u2014 e.g. {from:{kind:"route"}, edge:{kinds:["handles"], direction:"in"}} lists every route handler, {from:{name:"^parseFile$"}, edge:{kinds:["calls"], direction:"in", depth:2}} its callers two levels up. Built from tree-sitter (TS/JS, Python, Go), git history, memories and em_graph_add.',
35993
+ description: 'Look up nodes in the project knowledge graph (files, functions, classes, routes, packages, people, concepts, memories) by name/path, run a preset (hotspots, coupled, orphans, dead_code, routes, cycles, clusters, stats), or run a structured pattern: start at nodes matching `from`, walk `edge.kinds` in `edge.direction` for `edge.depth` hops, keep what matches `to` \u2014 e.g. {from:{kind:"route"}, edge:{kinds:["handles"], direction:"in"}} lists every route handler, {from:{name:"^parseFile$"}, edge:{kinds:["calls"], direction:"in", depth:2}} its callers two levels up. Built from tree-sitter (TS/JS, Python, Go, PHP), git history, memories and em_graph_add.',
34850
35994
  schema: {
34851
35995
  query: external_exports.string().optional().describe("Substring of a name, path or id"),
34852
35996
  kind: external_exports.string().optional().describe(
@@ -35400,12 +36544,12 @@ var init_em_graph_changes = __esm({
35400
36544
  });
35401
36545
 
35402
36546
  // src/graph/architecture.ts
35403
- import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
35404
- import { join as join22 } from "path";
36547
+ import { existsSync as existsSync18, readFileSync as readFileSync17 } from "fs";
36548
+ import { join as join23 } from "path";
35405
36549
  function pkgJson(root) {
35406
36550
  try {
35407
- const f = join22(root, "package.json");
35408
- return existsSync18(f) ? JSON.parse(readFileSync16(f, "utf8")) : {};
36551
+ const f = join23(root, "package.json");
36552
+ return existsSync18(f) ? JSON.parse(readFileSync17(f, "utf8")) : {};
35409
36553
  } catch {
35410
36554
  return {};
35411
36555
  }
@@ -36757,8 +37901,8 @@ var init_catalog = __esm({
36757
37901
  });
36758
37902
 
36759
37903
  // src/docs/manifest.ts
36760
- import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
36761
- import { dirname as dirname8, join as join23 } from "path";
37904
+ import { existsSync as existsSync19, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
37905
+ import { dirname as dirname9, join as join24 } from "path";
36762
37906
  function docsTag(name) {
36763
37907
  return `${DOCS_PREFIX}${name}`;
36764
37908
  }
@@ -36770,7 +37914,7 @@ function slugify3(input2) {
36770
37914
  return (s || "source").slice(0, 64);
36771
37915
  }
36772
37916
  function manifestPathAt(root, rel = ".eminiar/docs.json") {
36773
- return join23(root, rel);
37917
+ return join24(root, rel);
36774
37918
  }
36775
37919
  function manifestPath(ctx) {
36776
37920
  return manifestPathAt(ctx.root, ctx.config.docs.manifest);
@@ -36778,7 +37922,7 @@ function manifestPath(ctx) {
36778
37922
  function empty() {
36779
37923
  return { version: 1, sources: [] };
36780
37924
  }
36781
- function normalize4(raw) {
37925
+ function normalize5(raw) {
36782
37926
  if (!raw || typeof raw !== "object") return empty();
36783
37927
  const sources = Array.isArray(raw.sources) ? raw.sources : [];
36784
37928
  const out = [];
@@ -36800,7 +37944,7 @@ function normalize4(raw) {
36800
37944
  function readManifestAt(file3) {
36801
37945
  if (!existsSync19(file3)) return empty();
36802
37946
  try {
36803
- return normalize4(JSON.parse(readFileSync17(file3, "utf8")));
37947
+ return normalize5(JSON.parse(readFileSync18(file3, "utf8")));
36804
37948
  } catch {
36805
37949
  return empty();
36806
37950
  }
@@ -36810,7 +37954,7 @@ function readManifest(ctx) {
36810
37954
  }
36811
37955
  function writeManifest(ctx, m) {
36812
37956
  const file3 = manifestPath(ctx);
36813
- mkdirSync8(dirname8(file3), { recursive: true });
37957
+ mkdirSync8(dirname9(file3), { recursive: true });
36814
37958
  const sorted = {
36815
37959
  version: 1,
36816
37960
  sources: [...m.sources].sort((a, b) => a.name.localeCompare(b.name))
@@ -36911,11 +38055,11 @@ var init_manifest = __esm({
36911
38055
  });
36912
38056
 
36913
38057
  // src/docs/discover.ts
36914
- import { existsSync as existsSync20, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
36915
- import { basename, join as join24, relative as relative6 } from "path";
38058
+ import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
38059
+ import { basename, join as join25, relative as relative7 } from "path";
36916
38060
  function readText(file3) {
36917
38061
  try {
36918
- return readFileSync18(file3, "utf8");
38062
+ return readFileSync19(file3, "utf8");
36919
38063
  } catch {
36920
38064
  return void 0;
36921
38065
  }
@@ -36933,14 +38077,14 @@ function sizeOf(files) {
36933
38077
  let n = 0;
36934
38078
  for (const f of files) {
36935
38079
  try {
36936
- n += statSync7(f).size;
38080
+ n += statSync8(f).size;
36937
38081
  } catch {
36938
38082
  }
36939
38083
  }
36940
38084
  return n;
36941
38085
  }
36942
38086
  function hiddenPath(cwd, file3) {
36943
- const parts = relative6(cwd, file3).split("/");
38087
+ const parts = relative7(cwd, file3).split("/");
36944
38088
  return parts.slice(0, -1).some((p, i) => {
36945
38089
  if (!p.startsWith(".")) return false;
36946
38090
  if (p === ".claude") return !["agents", "skills", "commands"].includes(parts[i + 1] ?? "");
@@ -36969,8 +38113,8 @@ function findProjects(root, opts) {
36969
38113
  const found = /* @__PURE__ */ new Set();
36970
38114
  for (const m of markers) {
36971
38115
  for (const f of glob(root, [`*/${m}`, `*/*/${m}`], 3)) {
36972
- const dir = join24(f, "..");
36973
- const rel = relative6(root, dir);
38116
+ const dir = join25(f, "..");
38117
+ const rel = relative7(root, dir);
36974
38118
  if (!rel || rel.split("/").some((p) => p.startsWith(".") || p === "node_modules")) continue;
36975
38119
  found.add(dir);
36976
38120
  }
@@ -36978,7 +38122,7 @@ function findProjects(root, opts) {
36978
38122
  const max = opts.maxProjects ?? 40;
36979
38123
  const seenNames = /* @__PURE__ */ new Set();
36980
38124
  const nameOf = (dir) => {
36981
- const pkg = readJson3(join24(dir, "package.json"));
38125
+ const pkg = readJson3(join25(dir, "package.json"));
36982
38126
  return typeof pkg?.name === "string" ? pkg.name : void 0;
36983
38127
  };
36984
38128
  const rootName = nameOf(root);
@@ -36996,7 +38140,7 @@ function findProjects(root, opts) {
36996
38140
  return out;
36997
38141
  }
36998
38142
  function npmSignals(dir, includeDev, project) {
36999
- const pkg = readJson3(join24(dir, "package.json"));
38143
+ const pkg = readJson3(join25(dir, "package.json"));
37000
38144
  if (!pkg) return [];
37001
38145
  const sections = ["dependencies", ...includeDev ? ["devDependencies"] : []];
37002
38146
  const out = [];
@@ -37013,13 +38157,13 @@ function npmSignals(dir, includeDev, project) {
37013
38157
  }
37014
38158
  function pythonSignals(dir, project) {
37015
38159
  const out = [];
37016
- const py = readText(join24(dir, "pyproject.toml"));
38160
+ const py = readText(join25(dir, "pyproject.toml"));
37017
38161
  if (py) {
37018
38162
  const block = /dependencies\s*=\s*\[([\s\S]*?)\]/.exec(py)?.[1] ?? "";
37019
38163
  for (const m of block.matchAll(/["']([A-Za-z0-9_.-]+)/g))
37020
38164
  out.push({ kind: "pypi", key: m[1].toLowerCase(), project });
37021
38165
  }
37022
- const req = readText(join24(dir, "requirements.txt"));
38166
+ const req = readText(join25(dir, "requirements.txt"));
37023
38167
  if (req) {
37024
38168
  for (const line of req.split("\n")) {
37025
38169
  const name = /^\s*([A-Za-z0-9_.-]+)/.exec(line)?.[1];
@@ -37030,7 +38174,7 @@ function pythonSignals(dir, project) {
37030
38174
  return out;
37031
38175
  }
37032
38176
  function goSignals(dir, project) {
37033
- const mod = readText(join24(dir, "go.mod"));
38177
+ const mod = readText(join25(dir, "go.mod"));
37034
38178
  if (!mod) return [];
37035
38179
  const out = [];
37036
38180
  const block = /require\s*\(([\s\S]*?)\)/g;
@@ -37096,18 +38240,18 @@ function isDocLink(host, path3) {
37096
38240
  function apiSignals(dir, project) {
37097
38241
  const hits = /* @__PURE__ */ new Map();
37098
38242
  const docLinks = /* @__PURE__ */ new Map();
37099
- const files = glob(dir, CODE_GLOBS, 8).filter((f) => !/\.min\.(js|mjs|cjs)$/.test(f)).filter((f) => !TEST_PATH_RE.test(relative6(dir, f))).slice(0, 4e3);
38243
+ const files = glob(dir, CODE_GLOBS, 8).filter((f) => !/\.min\.(js|mjs|cjs)$/.test(f)).filter((f) => !TEST_PATH_RE.test(relative7(dir, f))).slice(0, 4e3);
37100
38244
  for (const file3 of files) {
37101
38245
  let size = 0;
37102
38246
  try {
37103
- size = statSync7(file3).size;
38247
+ size = statSync8(file3).size;
37104
38248
  } catch {
37105
38249
  continue;
37106
38250
  }
37107
38251
  if (size > 400 * 1024) continue;
37108
38252
  const text3 = readText(file3);
37109
38253
  if (!text3 || text3.includes("\0")) continue;
37110
- const rel = relative6(dir, file3).split("\\").join("/");
38254
+ const rel = relative7(dir, file3).split("\\").join("/");
37111
38255
  const isEnv = basename(file3).startsWith(".env");
37112
38256
  const lines = text3.split("\n");
37113
38257
  const found = [];
@@ -37197,7 +38341,7 @@ function fileSignals(dir, project) {
37197
38341
  function localCandidates(root, dir, project, isRegistered) {
37198
38342
  const out = [];
37199
38343
  const prefix = project ? `${slugify3(basename(dir))}-` : "";
37200
- const rel = (abs) => relative6(root, abs).split("\\").join("/");
38344
+ const rel = (abs) => relative7(root, abs).split("\\").join("/");
37201
38345
  const push = (c) => {
37202
38346
  if (!c.files) return;
37203
38347
  out.push({ ...c, project, registered: isRegistered(c) });
@@ -37227,7 +38371,7 @@ function localCandidates(root, dir, project, isRegistered) {
37227
38371
  why: "project readme and top-level guides"
37228
38372
  });
37229
38373
  for (const d of ["docs", "doc", "documentation"]) {
37230
- const abs = join24(dir, d);
38374
+ const abs = join25(dir, d);
37231
38375
  if (!existsSync20(abs)) continue;
37232
38376
  const files = glob(abs, ["**/*.md", "**/*.mdx", "**/*.rst", "**/*.txt"]).filter(
37233
38377
  (f) => !SKIP_EXT.has(f.slice(f.lastIndexOf(".")))
@@ -37251,7 +38395,7 @@ function localCandidates(root, dir, project, isRegistered) {
37251
38395
  "docs/decisions",
37252
38396
  "decisions"
37253
38397
  ]) {
37254
- const abs = join24(dir, d);
38398
+ const abs = join25(dir, d);
37255
38399
  if (!existsSync20(abs)) continue;
37256
38400
  if (out.some((c) => c.path && rel(abs).startsWith(`${c.path}/`))) break;
37257
38401
  const files = glob(abs, ["**/*.md"]);
@@ -37311,7 +38455,7 @@ function localCandidates(root, dir, project, isRegistered) {
37311
38455
  push({
37312
38456
  name: `${prefix}api-specs`,
37313
38457
  path: rel(dir) || ".",
37314
- glob: api.map((f) => relative6(dir, f).split("\\").join("/")),
38458
+ glob: api.map((f) => relative7(dir, f).split("\\").join("/")),
37315
38459
  files: api.length,
37316
38460
  bytes: sizeOf(api),
37317
38461
  why: "OpenAPI / Postman / GraphQL / protobuf specs"
@@ -37385,7 +38529,7 @@ function discoverDocs(ctx, opts = {}) {
37385
38529
  const apiHits = /* @__PURE__ */ new Map();
37386
38530
  const docLinks = /* @__PURE__ */ new Map();
37387
38531
  for (const dir of projects) {
37388
- const project = dir === root ? "" : relative6(root, dir).split("\\").join("/");
38532
+ const project = dir === root ? "" : relative7(root, dir).split("\\").join("/");
37389
38533
  const own2 = dir === root && projects.length > 1 ? void 0 : dir;
37390
38534
  local.push(...localCandidates(root, dir, project, isRegistered));
37391
38535
  signals.push(...npmSignals(dir, includeDev, project));
@@ -37395,7 +38539,7 @@ function discoverDocs(ctx, opts = {}) {
37395
38539
  signals.push(...fileSignals(dir, project));
37396
38540
  const api = apiSignals(own2 ?? dir, project);
37397
38541
  const inSubproject = (samples) => own2 === void 0 && projects.some(
37398
- (p) => p !== root && samples.some((s) => s.startsWith(`${relative6(root, p)}/`))
38542
+ (p) => p !== root && samples.some((s) => s.startsWith(`${relative7(root, p)}/`))
37399
38543
  );
37400
38544
  for (const hit of api.hits.values()) {
37401
38545
  if (inSubproject(hit.samples)) continue;
@@ -37585,7 +38729,7 @@ function discoverDocs(ctx, opts = {}) {
37585
38729
  );
37586
38730
  return {
37587
38731
  root,
37588
- projects: projects.map((p) => p === root ? "." : relative6(root, p)),
38732
+ projects: projects.map((p) => p === root ? "." : relative7(root, p)),
37589
38733
  local,
37590
38734
  providers
37591
38735
  };
@@ -39859,14 +41003,14 @@ var init_em_search = __esm({
39859
41003
  });
39860
41004
 
39861
41005
  // src/ui/api.ts
39862
- import { existsSync as existsSync21, readdirSync as readdirSync7 } from "fs";
39863
- import { join as join25, relative as relative7 } from "path";
41006
+ import { existsSync as existsSync21, readdirSync as readdirSync8 } from "fs";
41007
+ import { join as join26, relative as relative8 } from "path";
39864
41008
  function listProjects() {
39865
41009
  const dir = paths().projects;
39866
41010
  const out = [];
39867
- for (const id of readdirSync7(dir, { withFileTypes: true })) {
41011
+ for (const id of readdirSync8(dir, { withFileTypes: true })) {
39868
41012
  if (!id.isDirectory()) continue;
39869
- const db = join25(dir, id.name, "eminiar.db");
41013
+ const db = join26(dir, id.name, "eminiar.db");
39870
41014
  try {
39871
41015
  const a = new SqliteAdapter(db, { vectors: false });
39872
41016
  out.push({ id: id.name, root: a.sessionLatest()?.project_root ?? null });
@@ -39892,8 +41036,8 @@ function statsPayload(ctx) {
39892
41036
  const db = getStorage(ctx);
39893
41037
  const sessionId = getCurrentSession(ctx);
39894
41038
  const g = globalDb();
39895
- const onboardingDir = join25(memoriesDir(ctx), "onboarding");
39896
- const onboarding = existsSync21(onboardingDir) ? readdirSync7(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
41039
+ const onboardingDir = join26(memoriesDir(ctx), "onboarding");
41040
+ const onboarding = existsSync21(onboardingDir) ? readdirSync8(onboardingDir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)) : [];
39897
41041
  return {
39898
41042
  project: ctx.root,
39899
41043
  projectId: ctx.id,
@@ -40085,7 +41229,7 @@ function docsPayload(ctx) {
40085
41229
  const known = new Set(m.sources.map((s) => docsTag(s.name)));
40086
41230
  const orphans2 = [...stats.values()].filter((s) => !known.has(s.source)).map((s) => ({ source: s.source, documents: s.documents, chunks: s.chunks }));
40087
41231
  return {
40088
- manifest: relative7(ctx.root, manifestPath(ctx)),
41232
+ manifest: relative8(ctx.root, manifestPath(ctx)),
40089
41233
  sources,
40090
41234
  orphans: orphans2,
40091
41235
  totals: {
@@ -40146,32 +41290,58 @@ function graphMap(ctx, opts = {}) {
40146
41290
  let files = db.graphFileNodes();
40147
41291
  if (opts.pathFilter)
40148
41292
  files = files.filter((f) => (f.path ?? f.name).includes(opts.pathFilter));
40149
- const ids = files.map((f) => f.id);
40150
- const idSet = new Set(ids);
40151
- const kinds = opts.coChanges === false ? ["imports"] : ["imports", "co_changes"];
40152
- const raw = db.graphEdgesFrom(ids, kinds).filter((e) => idSet.has(e.dst));
41293
+ const fileSet = new Set(files.map((f) => f.id));
41294
+ const kinds = opts.kinds ?? (opts.coChanges === false ? ["imports"] : ["imports", "co_changes"]);
41295
+ const lift = (id) => {
41296
+ const kind = parseId(id).kind;
41297
+ if (kind === "file" || kind === "sym") {
41298
+ const rel = fileOf(id);
41299
+ const f = rel === void 0 ? void 0 : fileId(rel);
41300
+ return f && fileSet.has(f) ? f : void 0;
41301
+ }
41302
+ return kind === "pkg" ? void 0 : id;
41303
+ };
41304
+ const merged = /* @__PURE__ */ new Map();
41305
+ for (const kind of kinds) {
41306
+ for (const e of db.graphEdgesByKind(kind, MAP_EDGES_PER_KIND)) {
41307
+ if (kind === "co_changes" && e.weight < 0.3) continue;
41308
+ const src = lift(e.src);
41309
+ const dst = lift(e.dst);
41310
+ if (!src || !dst || src === dst) continue;
41311
+ if (!fileSet.has(src) && !fileSet.has(dst)) continue;
41312
+ const key = kind === "co_changes" ? `${[src, dst].sort().join("|")}|co` : `${src}|${dst}|${kind}`;
41313
+ const prev = merged.get(key);
41314
+ if (prev)
41315
+ prev.weight = kind === "co_changes" ? Math.max(prev.weight, e.weight) : prev.weight + 1;
41316
+ else merged.set(key, { src, dst, kind, weight: kind === "co_changes" ? e.weight : 1 });
41317
+ }
41318
+ }
40153
41319
  const degree = /* @__PURE__ */ new Map();
40154
- for (const e of raw) {
41320
+ for (const e of merged.values()) {
40155
41321
  degree.set(e.src, (degree.get(e.src) ?? 0) + 1);
40156
41322
  degree.set(e.dst, (degree.get(e.dst) ?? 0) + 1);
40157
41323
  }
40158
41324
  const keep = new Set(
40159
41325
  [...files].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, limit).map((f) => f.id)
40160
41326
  );
40161
- const seenEdge = /* @__PURE__ */ new Set();
40162
41327
  const edges = [];
40163
- for (const e of raw) {
40164
- if (!keep.has(e.src) || !keep.has(e.dst)) continue;
40165
- if (e.kind === "co_changes" && e.weight < 0.3) continue;
40166
- const key = e.kind === "co_changes" ? `${[e.src, e.dst].sort().join("|")}|co` : `${e.src}|${e.dst}|${e.kind}`;
40167
- if (seenEdge.has(key)) continue;
40168
- seenEdge.add(key);
40169
- edges.push({ src: e.src, dst: e.dst, kind: e.kind, weight: e.weight });
40170
- }
41328
+ const extra = /* @__PURE__ */ new Set();
41329
+ for (const e of merged.values()) {
41330
+ const srcOk = keep.has(e.src) || !fileSet.has(e.src) && keep.has(e.dst);
41331
+ const dstOk = keep.has(e.dst) || !fileSet.has(e.dst) && keep.has(e.src);
41332
+ if (!srcOk || !dstOk) continue;
41333
+ if (!keep.has(e.src)) extra.add(e.src);
41334
+ if (!keep.has(e.dst)) extra.add(e.dst);
41335
+ edges.push(e);
41336
+ }
41337
+ const extraRows = new Map(db.graphGetNodes([...extra]).map((n) => [n.id, n]));
40171
41338
  return {
40172
41339
  mode: "map",
40173
41340
  total: files.length,
40174
- nodes: files.filter((f) => keep.has(f.id)).map((f) => visNode(f, f.id)),
41341
+ nodes: [
41342
+ ...files.filter((f) => keep.has(f.id)).map((f) => visNode(f, f.id)),
41343
+ ...[...extra].map((id) => visNode(extraRows.get(id), id))
41344
+ ],
40175
41345
  edges
40176
41346
  };
40177
41347
  }
@@ -40333,7 +41503,7 @@ async function runAction(ctx, input2) {
40333
41503
  return done(false, `unknown action ${String(input2.action)}`);
40334
41504
  }
40335
41505
  }
40336
- var memoryKinds;
41506
+ var memoryKinds, MAP_EDGES_PER_KIND;
40337
41507
  var init_api2 = __esm({
40338
41508
  "src/ui/api.ts"() {
40339
41509
  "use strict";
@@ -40348,6 +41518,7 @@ var init_api2 = __esm({
40348
41518
  init_sync();
40349
41519
  init_queue();
40350
41520
  init_format2();
41521
+ init_ids();
40351
41522
  init_query2();
40352
41523
  init_doctor2();
40353
41524
  init_store();
@@ -40362,6 +41533,7 @@ var init_api2 = __esm({
40362
41533
  init_em_purge();
40363
41534
  init_em_search();
40364
41535
  memoryKinds = [...MEMORY_KINDS];
41536
+ MAP_EDGES_PER_KIND = 5e4;
40365
41537
  }
40366
41538
  });
40367
41539
 
@@ -40403,8 +41575,8 @@ var init_namepath = __esm({
40403
41575
  });
40404
41576
 
40405
41577
  // src/code/symbols.ts
40406
- import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
40407
- import { join as join26 } from "path";
41578
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
41579
+ import { join as join27 } from "path";
40408
41580
  function isExported2(n) {
40409
41581
  if (!n.meta) return false;
40410
41582
  try {
@@ -40440,8 +41612,8 @@ var init_symbols2 = __esm({
40440
41612
  });
40441
41613
 
40442
41614
  // src/ui/graph-api.ts
40443
- import { readFileSync as readFileSync20, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
40444
- import { relative as relative8, resolve as resolve6 } from "path";
41615
+ import { readFileSync as readFileSync21, realpathSync as realpathSync2, statSync as statSync9 } from "fs";
41616
+ import { relative as relative9, resolve as resolve7 } from "path";
40445
41617
  function metaOf2(row) {
40446
41618
  if (!row?.meta) return {};
40447
41619
  try {
@@ -40502,17 +41674,17 @@ function treePayload(ctx) {
40502
41674
  }
40503
41675
  function filePayload(ctx, rel) {
40504
41676
  const clean = rel.replace(/^\.\//, "");
40505
- const abs = resolve6(ctx.root, clean);
40506
- if (relative8(ctx.root, abs).startsWith("..")) throw new Error("path outside the project");
41677
+ const abs = resolve7(ctx.root, clean);
41678
+ if (relative9(ctx.root, abs).startsWith("..")) throw new Error("path outside the project");
40507
41679
  let real = abs;
40508
41680
  try {
40509
41681
  real = realpathSync2(abs);
40510
41682
  } catch {
40511
41683
  return { error: "not found", path: clean };
40512
41684
  }
40513
- if (relative8(realpathSync2(ctx.root), real).startsWith(".."))
41685
+ if (relative9(realpathSync2(ctx.root), real).startsWith(".."))
40514
41686
  throw new Error("path outside the project");
40515
- const st = statSync8(real);
41687
+ const st = statSync9(real);
40516
41688
  if (!st.isFile()) return { error: "not a file", path: clean };
40517
41689
  const db = getStorage(ctx);
40518
41690
  const row = db.fileIndexGet(clean);
@@ -40526,7 +41698,7 @@ function filePayload(ctx, rel) {
40526
41698
  }));
40527
41699
  if (st.size > MAX_CODE_BYTES)
40528
41700
  return { path: clean, tooLarge: true, size: st.size, status: row?.status ?? null, symbols };
40529
- const content = readFileSync20(real, "utf8");
41701
+ const content = readFileSync21(real, "utf8");
40530
41702
  return {
40531
41703
  path: clean,
40532
41704
  size: st.size,
@@ -41248,14 +42420,14 @@ function buildHistory(ctx, db, opts = {}) {
41248
42420
  };
41249
42421
  });
41250
42422
  const bySha = new Map(commits.map((c) => [c.sha, c]));
41251
- const resolve7 = (prefix) => {
42423
+ const resolve8 = (prefix) => {
41252
42424
  if (bySha.has(prefix)) return bySha.get(prefix);
41253
42425
  for (const c of commits) if (c.sha.startsWith(prefix)) return c;
41254
42426
  return void 0;
41255
42427
  };
41256
42428
  for (const c of commits) {
41257
42429
  if (c.kind !== "revert" || !c.revertOf) continue;
41258
- const target = resolve7(c.revertOf);
42430
+ const target = resolve8(c.revertOf);
41259
42431
  if (target) {
41260
42432
  target.revertedBy = c.sha;
41261
42433
  c.origins.push(target.sha);
@@ -42104,9 +43276,9 @@ __export(server_exports, {
42104
43276
  uiDir: () => uiDir
42105
43277
  });
42106
43278
  import { spawn as spawn2 } from "child_process";
42107
- import { existsSync as existsSync23, readFileSync as readFileSync21, statSync as statSync9 } from "fs";
43279
+ import { existsSync as existsSync23, readFileSync as readFileSync22, statSync as statSync10 } from "fs";
42108
43280
  import { createServer } from "http";
42109
- import { extname as extname3, join as join27, normalize as normalize5 } from "path";
43281
+ import { extname as extname3, join as join28, normalize as normalize6 } from "path";
42110
43282
  import { URL as URL2 } from "url";
42111
43283
  function json2(res, status, body) {
42112
43284
  res.writeHead(status, {
@@ -42128,7 +43300,7 @@ function strList(v) {
42128
43300
  return void 0;
42129
43301
  }
42130
43302
  function uiDir() {
42131
- return process.env.EMINIAR_UI_DIR || join27(pluginRoot(), "dist", "ui");
43303
+ return process.env.EMINIAR_UI_DIR || join28(pluginRoot(), "dist", "ui");
42132
43304
  }
42133
43305
  function originAllowed(req, port) {
42134
43306
  const host = req.headers.host ?? "";
@@ -42152,25 +43324,25 @@ function originAllowed(req, port) {
42152
43324
  function serveStatic(res, p) {
42153
43325
  const dir = uiDir();
42154
43326
  const rel = p === "/" ? "index.html" : p.replace(/^\/+/, "");
42155
- const file3 = normalize5(join27(dir, rel));
42156
- if (!file3.startsWith(normalize5(dir))) return false;
42157
- if (!existsSync23(file3) || !statSync9(file3).isFile()) return false;
43327
+ const file3 = normalize6(join28(dir, rel));
43328
+ if (!file3.startsWith(normalize6(dir))) return false;
43329
+ if (!existsSync23(file3) || !statSync10(file3).isFile()) return false;
42158
43330
  const type = MIME[extname3(file3).toLowerCase()] ?? "application/octet-stream";
42159
43331
  if (rel === "index.html") {
42160
43332
  const stamp = (name) => {
42161
43333
  try {
42162
- return `/${name}?v=${Math.floor(statSync9(join27(dir, name)).mtimeMs)}`;
43334
+ return `/${name}?v=${Math.floor(statSync10(join28(dir, name)).mtimeMs)}`;
42163
43335
  } catch {
42164
43336
  return `/${name}`;
42165
43337
  }
42166
43338
  };
42167
- const html = readFileSync21(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
43339
+ const html = readFileSync22(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
42168
43340
  res.writeHead(200, { "content-type": type, "cache-control": "no-store" });
42169
43341
  res.end(html);
42170
43342
  return true;
42171
43343
  }
42172
43344
  res.writeHead(200, { "content-type": type, "cache-control": "no-cache" });
42173
- res.end(readFileSync21(file3));
43345
+ res.end(readFileSync22(file3));
42174
43346
  return true;
42175
43347
  }
42176
43348
  async function serve(opts) {
@@ -42332,6 +43504,7 @@ async function serve(opts) {
42332
43504
  200,
42333
43505
  graphMap(ctx(), {
42334
43506
  limit: num2("limit", 150),
43507
+ kinds: q.has("kinds") ? strList(q.get("kinds")) : void 0,
42335
43508
  coChanges: q.get("cochanges") !== "0",
42336
43509
  pathFilter: str2("path")
42337
43510
  })
@@ -42511,9 +43684,9 @@ async function serve(opts) {
42511
43684
  json2(res, 500, { error: err.message });
42512
43685
  }
42513
43686
  });
42514
- await new Promise((resolve7, reject) => {
43687
+ await new Promise((resolve8, reject) => {
42515
43688
  server.once("error", reject);
42516
- server.listen(opts.port, "127.0.0.1", resolve7);
43689
+ server.listen(opts.port, "127.0.0.1", resolve8);
42517
43690
  });
42518
43691
  const url2 = `http://127.0.0.1:${opts.port}/`;
42519
43692
  console.log(`eminiar dashboard at ${url2} (Ctrl-C to stop)`);
@@ -42554,7 +43727,7 @@ var statusline_exports = {};
42554
43727
  __export(statusline_exports, {
42555
43728
  renderStatusline: () => renderStatusline
42556
43729
  });
42557
- import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
43730
+ import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
42558
43731
  function renderStatusline(raw) {
42559
43732
  let input2 = {};
42560
43733
  try {
@@ -42564,7 +43737,7 @@ function renderStatusline(raw) {
42564
43737
  const cache3 = paths().statuslineCache;
42565
43738
  try {
42566
43739
  if (existsSync24(cache3)) {
42567
- const c = JSON.parse(readFileSync22(cache3, "utf8"));
43740
+ const c = JSON.parse(readFileSync23(cache3, "utf8"));
42568
43741
  if (Date.now() - c.at < 5e3 && c.key === (input2.session_id ?? "")) return c.line;
42569
43742
  }
42570
43743
  } catch {