typegraph-mcp 0.9.46 → 0.9.48

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.
@@ -18,8 +18,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
18
18
  "coverage"
19
19
  ]);
20
20
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
21
- function discoverFiles(rootDir) {
21
+ function normalizeExcludedPaths(rootDir, excludedPaths) {
22
+ return excludedPaths.map(
23
+ (excludedPath) => path.resolve(rootDir, excludedPath)
24
+ );
25
+ }
26
+ function isExcluded(filePath, excludedPaths) {
27
+ return excludedPaths.some(
28
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path.sep)
29
+ );
30
+ }
31
+ function discoverFiles(rootDir, excludedPaths = []) {
22
32
  const files = [];
33
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
23
34
  function walk(dir) {
24
35
  let entries;
25
36
  try {
@@ -31,7 +42,9 @@ function discoverFiles(rootDir) {
31
42
  if (entry.isDirectory()) {
32
43
  if (SKIP_DIRS.has(entry.name)) continue;
33
44
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
34
- walk(path.join(dir, entry.name));
45
+ const childDir = path.join(dir, entry.name);
46
+ if (isExcluded(childDir, normalizedExclusions)) continue;
47
+ walk(childDir);
35
48
  } else if (entry.isFile()) {
36
49
  const name = entry.name;
37
50
  if (SKIP_FILES.has(name)) continue;
@@ -132,11 +145,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
132
145
  return null;
133
146
  }
134
147
  function createResolver(projectRoot, tsconfigPath) {
148
+ const configFile = path.resolve(projectRoot, tsconfigPath);
135
149
  return new ResolverFactory({
136
- tsconfig: {
137
- configFile: path.resolve(projectRoot, tsconfigPath),
138
- references: "auto"
139
- },
150
+ ...fs.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
140
151
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
141
152
  extensionAlias: {
142
153
  ".js": [".ts", ".tsx", ".js"],
@@ -202,10 +213,10 @@ function buildReverseMap(forward) {
202
213
  }
203
214
  return reverse;
204
215
  }
205
- async function buildGraph(projectRoot, tsconfigPath) {
216
+ async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
206
217
  const startTime = performance.now();
207
218
  const resolver = createResolver(projectRoot, tsconfigPath);
208
- const fileList = discoverFiles(projectRoot);
219
+ const fileList = discoverFiles(projectRoot, excludedPaths);
209
220
  log(`Discovered ${fileList.length} source files`);
210
221
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
211
222
  const reverse = buildReverseMap(forward);
@@ -297,8 +308,9 @@ function removeFile(graph, filePath) {
297
308
  graph.reverse.delete(filePath);
298
309
  graph.files.delete(filePath);
299
310
  }
300
- function startWatcher(projectRoot, graph, resolver, hooks) {
311
+ function startWatcher(projectRoot, graph, resolver, hooks, excludedPaths = []) {
301
312
  try {
313
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot, excludedPaths);
302
314
  const watcher = fs.watch(
303
315
  projectRoot,
304
316
  { recursive: true },
@@ -312,6 +324,7 @@ function startWatcher(projectRoot, graph, resolver, hooks) {
312
324
  if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
313
325
  return;
314
326
  const absPath = path.resolve(projectRoot, filename);
327
+ if (isExcluded(absPath, normalizedExclusions)) return;
315
328
  if (fs.existsSync(absPath)) {
316
329
  updateFile(graph, absPath, resolver, projectRoot);
317
330
  void hooks?.onFileUpdated?.(absPath);
package/dist/server.js CHANGED
@@ -12,6 +12,30 @@ import * as fs from "fs";
12
12
  import { createRequire } from "module";
13
13
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
14
14
  var REQUEST_TIMEOUT_MS = 1e4;
15
+ function readPackageVersion(packagePath) {
16
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
17
+ return pkg.version ?? "unknown";
18
+ }
19
+ function resolveTsServer(projectRoot2) {
20
+ const projectRequire = createRequire(path.resolve(projectRoot2, "package.json"));
21
+ let projectVersion;
22
+ try {
23
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
24
+ projectVersion = readPackageVersion(packagePath2);
25
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
26
+ return { path: serverPath2, version: projectVersion, source: "project" };
27
+ } catch {
28
+ }
29
+ const toolRequire = createRequire(import.meta.url);
30
+ const packagePath = toolRequire.resolve("typescript/package.json");
31
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
32
+ return {
33
+ path: serverPath,
34
+ version: readPackageVersion(packagePath),
35
+ source: "typegraph",
36
+ projectVersion
37
+ };
38
+ }
15
39
  var TsServerClient = class {
16
40
  constructor(projectRoot2, tsconfigPath2 = "./tsconfig.json") {
17
41
  this.projectRoot = projectRoot2;
@@ -48,9 +72,12 @@ var TsServerClient = class {
48
72
  // ─── Lifecycle ──────────────────────────────────────────────────────────
49
73
  async start() {
50
74
  if (this.child) return;
51
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
52
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
53
- log(`Spawning tsserver: ${tsserverPath}`);
75
+ const resolution = resolveTsServer(this.projectRoot);
76
+ const tsserverPath = resolution.path;
77
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
78
+ log(
79
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
80
+ );
54
81
  log(`Project root: ${this.projectRoot}`);
55
82
  log(`tsconfig: ${this.tsconfigPath}`);
56
83
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -79,12 +106,9 @@ var TsServerClient = class {
79
106
  }
80
107
  });
81
108
  const warmStart = performance.now();
82
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
83
- if (fs.existsSync(tsconfigAbs)) {
84
- await this.sendRequest("compilerOptionsForInferredProjects", {
85
- options: { allowJs: true, checkJs: false }
86
- });
87
- }
109
+ await this.sendRequest("compilerOptionsForInferredProjects", {
110
+ options: { allowJs: true, checkJs: false }
111
+ });
88
112
  this.ready = true;
89
113
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
90
114
  }
@@ -328,8 +352,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
328
352
  "coverage"
329
353
  ]);
330
354
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
331
- function discoverFiles(rootDir) {
355
+ function normalizeExcludedPaths(rootDir, excludedPaths2) {
356
+ return excludedPaths2.map(
357
+ (excludedPath) => path2.resolve(rootDir, excludedPath)
358
+ );
359
+ }
360
+ function isExcluded(filePath, excludedPaths2) {
361
+ return excludedPaths2.some(
362
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
363
+ );
364
+ }
365
+ function discoverFiles(rootDir, excludedPaths2 = []) {
332
366
  const files = [];
367
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths2);
333
368
  function walk(dir) {
334
369
  let entries;
335
370
  try {
@@ -341,7 +376,9 @@ function discoverFiles(rootDir) {
341
376
  if (entry.isDirectory()) {
342
377
  if (SKIP_DIRS.has(entry.name)) continue;
343
378
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
344
- walk(path2.join(dir, entry.name));
379
+ const childDir = path2.join(dir, entry.name);
380
+ if (isExcluded(childDir, normalizedExclusions)) continue;
381
+ walk(childDir);
345
382
  } else if (entry.isFile()) {
346
383
  const name = entry.name;
347
384
  if (SKIP_FILES.has(name)) continue;
@@ -442,11 +479,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot2) {
442
479
  return null;
443
480
  }
444
481
  function createResolver(projectRoot2, tsconfigPath2) {
482
+ const configFile = path2.resolve(projectRoot2, tsconfigPath2);
445
483
  return new ResolverFactory({
446
- tsconfig: {
447
- configFile: path2.resolve(projectRoot2, tsconfigPath2),
448
- references: "auto"
449
- },
484
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
450
485
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
451
486
  extensionAlias: {
452
487
  ".js": [".ts", ".tsx", ".js"],
@@ -512,10 +547,10 @@ function buildReverseMap(forward) {
512
547
  }
513
548
  return reverse;
514
549
  }
515
- async function buildGraph(projectRoot2, tsconfigPath2) {
550
+ async function buildGraph(projectRoot2, tsconfigPath2, excludedPaths2 = []) {
516
551
  const startTime = performance.now();
517
552
  const resolver = createResolver(projectRoot2, tsconfigPath2);
518
- const fileList = discoverFiles(projectRoot2);
553
+ const fileList = discoverFiles(projectRoot2, excludedPaths2);
519
554
  log2(`Discovered ${fileList.length} source files`);
520
555
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot2);
521
556
  const reverse = buildReverseMap(forward);
@@ -607,8 +642,9 @@ function removeFile(graph, filePath) {
607
642
  graph.reverse.delete(filePath);
608
643
  graph.files.delete(filePath);
609
644
  }
610
- function startWatcher(projectRoot2, graph, resolver, hooks) {
645
+ function startWatcher(projectRoot2, graph, resolver, hooks, excludedPaths2 = []) {
611
646
  try {
647
+ const normalizedExclusions = normalizeExcludedPaths(projectRoot2, excludedPaths2);
612
648
  const watcher = fs2.watch(
613
649
  projectRoot2,
614
650
  { recursive: true },
@@ -622,6 +658,7 @@ function startWatcher(projectRoot2, graph, resolver, hooks) {
622
658
  if (filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts"))
623
659
  return;
624
660
  const absPath2 = path2.resolve(projectRoot2, filename);
661
+ if (isExcluded(absPath2, normalizedExclusions)) return;
625
662
  if (fs2.existsSync(absPath2)) {
626
663
  updateFile(graph, absPath2, resolver, projectRoot2);
627
664
  void hooks?.onFileUpdated?.(absPath2);
@@ -917,17 +954,18 @@ import * as path5 from "path";
917
954
 
918
955
  // config.ts
919
956
  import * as path4 from "path";
920
- function resolveConfig(toolDir) {
957
+ function resolveConfig(toolDir2) {
921
958
  const cwd = process.cwd();
922
- const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(toolDir)) === "plugins" ? path4.resolve(toolDir, "../..") : cwd;
959
+ const projectRoot2 = process.env["TYPEGRAPH_PROJECT_ROOT"] ? path4.resolve(cwd, process.env["TYPEGRAPH_PROJECT_ROOT"]) : path4.basename(path4.dirname(toolDir2)) === "plugins" ? path4.resolve(toolDir2, "../..") : cwd;
923
960
  const tsconfigPath2 = process.env["TYPEGRAPH_TSCONFIG"] || "./tsconfig.json";
924
- const toolIsEmbedded = toolDir.startsWith(projectRoot2 + path4.sep);
925
- const toolRelPath = toolIsEmbedded ? path4.relative(projectRoot2, toolDir) : toolDir;
926
- return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir, toolIsEmbedded, toolRelPath };
961
+ const toolIsEmbedded2 = toolDir2.startsWith(projectRoot2 + path4.sep);
962
+ const toolRelPath = toolIsEmbedded2 ? path4.relative(projectRoot2, toolDir2) : toolDir2;
963
+ return { projectRoot: projectRoot2, tsconfigPath: tsconfigPath2, toolDir: toolDir2, toolIsEmbedded: toolIsEmbedded2, toolRelPath };
927
964
  }
928
965
 
929
966
  // server.ts
930
- var { projectRoot, tsconfigPath } = resolveConfig(import.meta.dirname);
967
+ var { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = resolveConfig(import.meta.dirname);
968
+ var excludedPaths = toolIsEmbedded ? [toolDir] : [];
931
969
  var log3 = (...args) => console.error("[typegraph]", ...args);
932
970
  var client = new TsServerClient(projectRoot, tsconfigPath);
933
971
  var moduleGraph;
@@ -1714,25 +1752,31 @@ async function main() {
1714
1752
  log3(`tsconfig: ${tsconfigPath}`);
1715
1753
  const [, graphResult] = await Promise.all([
1716
1754
  client.start(),
1717
- buildGraph(projectRoot, tsconfigPath)
1755
+ buildGraph(projectRoot, tsconfigPath, excludedPaths)
1718
1756
  ]);
1719
1757
  moduleGraph = graphResult.graph;
1720
1758
  moduleResolver = graphResult.resolver;
1721
- startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
1722
- onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
1723
- (wasOpen) => {
1724
- if (!wasOpen) client.markProjectsDirty();
1725
- },
1726
- (err) => {
1759
+ startWatcher(
1760
+ projectRoot,
1761
+ moduleGraph,
1762
+ graphResult.resolver,
1763
+ {
1764
+ onFileUpdated: (filePath) => client.reloadOpenFile(filePath).then(
1765
+ (wasOpen) => {
1766
+ if (!wasOpen) client.markProjectsDirty();
1767
+ },
1768
+ (err) => {
1769
+ client.markProjectsDirty();
1770
+ log3(`Failed to reload open file ${relPath(filePath)}:`, err);
1771
+ }
1772
+ ),
1773
+ onFileDeleted: (filePath) => {
1774
+ client.closeFile(filePath);
1727
1775
  client.markProjectsDirty();
1728
- log3(`Failed to reload open file ${relPath(filePath)}:`, err);
1729
1776
  }
1730
- ),
1731
- onFileDeleted: (filePath) => {
1732
- client.closeFile(filePath);
1733
- client.markProjectsDirty();
1734
- }
1735
- });
1777
+ },
1778
+ excludedPaths
1779
+ );
1736
1780
  const transport = new StdioServerTransport();
1737
1781
  await mcpServer.connect(transport);
1738
1782
  log3("MCP server connected and ready");
@@ -10,6 +10,30 @@ import * as fs from "fs";
10
10
  import { createRequire } from "module";
11
11
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
12
12
  var REQUEST_TIMEOUT_MS = 1e4;
13
+ function readPackageVersion(packagePath) {
14
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
15
+ return pkg.version ?? "unknown";
16
+ }
17
+ function resolveTsServer(projectRoot) {
18
+ const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
19
+ let projectVersion;
20
+ try {
21
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
22
+ projectVersion = readPackageVersion(packagePath2);
23
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
24
+ return { path: serverPath2, version: projectVersion, source: "project" };
25
+ } catch {
26
+ }
27
+ const toolRequire = createRequire(import.meta.url);
28
+ const packagePath = toolRequire.resolve("typescript/package.json");
29
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
30
+ return {
31
+ path: serverPath,
32
+ version: readPackageVersion(packagePath),
33
+ source: "typegraph",
34
+ projectVersion
35
+ };
36
+ }
13
37
  var TsServerClient = class {
14
38
  constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
15
39
  this.projectRoot = projectRoot;
@@ -46,9 +70,12 @@ var TsServerClient = class {
46
70
  // ─── Lifecycle ──────────────────────────────────────────────────────────
47
71
  async start() {
48
72
  if (this.child) return;
49
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
50
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
51
- log(`Spawning tsserver: ${tsserverPath}`);
73
+ const resolution = resolveTsServer(this.projectRoot);
74
+ const tsserverPath = resolution.path;
75
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
76
+ log(
77
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
78
+ );
52
79
  log(`Project root: ${this.projectRoot}`);
53
80
  log(`tsconfig: ${this.tsconfigPath}`);
54
81
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -77,12 +104,9 @@ var TsServerClient = class {
77
104
  }
78
105
  });
79
106
  const warmStart = performance.now();
80
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
81
- if (fs.existsSync(tsconfigAbs)) {
82
- await this.sendRequest("compilerOptionsForInferredProjects", {
83
- options: { allowJs: true, checkJs: false }
84
- });
85
- }
107
+ await this.sendRequest("compilerOptionsForInferredProjects", {
108
+ options: { allowJs: true, checkJs: false }
109
+ });
86
110
  this.ready = true;
87
111
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
88
112
  }
@@ -177,12 +201,12 @@ var TsServerClient = class {
177
201
  command,
178
202
  arguments: args
179
203
  };
180
- return new Promise((resolve5, reject) => {
204
+ return new Promise((resolve6, reject) => {
181
205
  const timer = setTimeout(() => {
182
206
  this.pending.delete(seq);
183
207
  reject(new Error(`tsserver ${command} timed out after ${REQUEST_TIMEOUT_MS}ms`));
184
208
  }, REQUEST_TIMEOUT_MS);
185
- this.pending.set(seq, { resolve: resolve5, reject, timer, command });
209
+ this.pending.set(seq, { resolve: resolve6, reject, timer, command });
186
210
  this.child.stdin.write(JSON.stringify(request) + "\n");
187
211
  });
188
212
  }
@@ -326,8 +350,19 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
326
350
  "coverage"
327
351
  ]);
328
352
  var SKIP_FILES = /* @__PURE__ */ new Set(["routeTree.gen.ts"]);
329
- function discoverFiles(rootDir) {
353
+ function normalizeExcludedPaths(rootDir, excludedPaths) {
354
+ return excludedPaths.map(
355
+ (excludedPath) => path2.resolve(rootDir, excludedPath)
356
+ );
357
+ }
358
+ function isExcluded(filePath, excludedPaths) {
359
+ return excludedPaths.some(
360
+ (excludedPath) => filePath === excludedPath || filePath.startsWith(excludedPath + path2.sep)
361
+ );
362
+ }
363
+ function discoverFiles(rootDir, excludedPaths = []) {
330
364
  const files = [];
365
+ const normalizedExclusions = normalizeExcludedPaths(rootDir, excludedPaths);
331
366
  function walk(dir) {
332
367
  let entries;
333
368
  try {
@@ -339,7 +374,9 @@ function discoverFiles(rootDir) {
339
374
  if (entry.isDirectory()) {
340
375
  if (SKIP_DIRS.has(entry.name)) continue;
341
376
  if (entry.name.startsWith(".") && dir !== rootDir) continue;
342
- walk(path2.join(dir, entry.name));
377
+ const childDir = path2.join(dir, entry.name);
378
+ if (isExcluded(childDir, normalizedExclusions)) continue;
379
+ walk(childDir);
343
380
  } else if (entry.isFile()) {
344
381
  const name = entry.name;
345
382
  if (SKIP_FILES.has(name)) continue;
@@ -440,11 +477,9 @@ function resolveProjectImport(resolver, fromDir, specifier, projectRoot) {
440
477
  return null;
441
478
  }
442
479
  function createResolver(projectRoot, tsconfigPath) {
480
+ const configFile = path2.resolve(projectRoot, tsconfigPath);
443
481
  return new ResolverFactory({
444
- tsconfig: {
445
- configFile: path2.resolve(projectRoot, tsconfigPath),
446
- references: "auto"
447
- },
482
+ ...fs2.existsSync(configFile) ? { tsconfig: { configFile, references: "auto" } } : {},
448
483
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
449
484
  extensionAlias: {
450
485
  ".js": [".ts", ".tsx", ".js"],
@@ -510,10 +545,10 @@ function buildReverseMap(forward) {
510
545
  }
511
546
  return reverse;
512
547
  }
513
- async function buildGraph(projectRoot, tsconfigPath) {
548
+ async function buildGraph(projectRoot, tsconfigPath, excludedPaths = []) {
514
549
  const startTime = performance.now();
515
550
  const resolver = createResolver(projectRoot, tsconfigPath);
516
- const fileList = discoverFiles(projectRoot);
551
+ const fileList = discoverFiles(projectRoot, excludedPaths);
517
552
  log2(`Discovered ${fileList.length} source files`);
518
553
  const { forward, parseFailures } = buildForwardEdges(fileList, resolver, projectRoot);
519
554
  const reverse = buildReverseMap(forward);
@@ -827,6 +862,47 @@ function findInNavBar(items, predicate) {
827
862
  }
828
863
  return null;
829
864
  }
865
+ function symbolPositions(symbol, sourceLines) {
866
+ const span = symbol.spans[0];
867
+ const positions = [];
868
+ const lineText = sourceLines[span.start.line - 1];
869
+ if (lineText) {
870
+ const nameIndex = lineText.indexOf(symbol.text, Math.max(0, span.start.offset - 1));
871
+ if (nameIndex >= 0) {
872
+ positions.push({ line: span.start.line, offset: nameIndex + 1 });
873
+ }
874
+ }
875
+ if (!positions.some((position) => position.offset === span.start.offset)) {
876
+ positions.push(span.start);
877
+ }
878
+ return positions;
879
+ }
880
+ async function probeQuickInfo(client, file, symbol, sourceLines) {
881
+ for (const position of symbolPositions(symbol, sourceLines)) {
882
+ const info = await client.quickinfo(file, position.line, position.offset);
883
+ if (info) return { info, position };
884
+ }
885
+ return null;
886
+ }
887
+ async function selectQuickInfoSymbol(client, file, symbols, sourceLines) {
888
+ const kindPriority = /* @__PURE__ */ new Map([
889
+ ["const", 0],
890
+ ["let", 1],
891
+ ["var", 2],
892
+ ["class", 3],
893
+ ["enum", 4],
894
+ ["function", 5],
895
+ ["method", 6]
896
+ ]);
897
+ const concreteSymbols = symbols.map((symbol, index) => ({ symbol, index })).filter(({ symbol }) => kindPriority.has(symbol.kind)).sort(
898
+ (a, b) => kindPriority.get(a.symbol.kind) - kindPriority.get(b.symbol.kind) || a.index - b.index
899
+ ).map(({ symbol }) => symbol);
900
+ for (const symbol of concreteSymbols) {
901
+ const probe = await probeQuickInfo(client, file, symbol, sourceLines);
902
+ if (probe) return { symbol, ...probe };
903
+ }
904
+ return null;
905
+ }
830
906
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
831
907
  "node_modules",
832
908
  "dist",
@@ -836,8 +912,9 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
836
912
  "coverage",
837
913
  "out"
838
914
  ]);
839
- function findTestFile(rootDir) {
915
+ function findTestFile(rootDir, excludedPaths = []) {
840
916
  const candidates = [];
917
+ const normalizedExclusions = excludedPaths.map((excludedPath) => path5.resolve(excludedPath));
841
918
  function walk(dir, depth) {
842
919
  if (depth > 5 || candidates.length >= 30) return;
843
920
  let entries;
@@ -849,7 +926,11 @@ function findTestFile(rootDir) {
849
926
  for (const entry of entries) {
850
927
  if (entry.isDirectory()) {
851
928
  if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
852
- walk(path5.join(dir, entry.name), depth + 1);
929
+ const childDir = path5.join(dir, entry.name);
930
+ if (normalizedExclusions.some(
931
+ (excludedPath) => childDir === excludedPath || childDir.startsWith(excludedPath + path5.sep)
932
+ )) continue;
933
+ walk(childDir, depth + 1);
853
934
  } else if (entry.isFile()) {
854
935
  const name = entry.name;
855
936
  if (name.endsWith(".d.ts") || name.endsWith(".test.ts") || name.endsWith(".spec.ts"))
@@ -857,7 +938,7 @@ function findTestFile(rootDir) {
857
938
  if (!name.endsWith(".ts") && !name.endsWith(".tsx")) continue;
858
939
  try {
859
940
  const stat = fs4.statSync(path5.join(dir, name));
860
- if (stat.size > 200 && stat.size < 5e4) {
941
+ if (stat.size > 0 && stat.size < 5e4) {
861
942
  candidates.push({ file: path5.join(dir, name), size: stat.size });
862
943
  }
863
944
  } catch {
@@ -881,7 +962,8 @@ function findImporter(graph, file) {
881
962
  return (preferred ?? revEdges[0]).target;
882
963
  }
883
964
  async function main(configOverride) {
884
- const { projectRoot, tsconfigPath } = configOverride ?? resolveConfig(import.meta.dirname);
965
+ const { projectRoot, tsconfigPath, toolDir, toolIsEmbedded } = configOverride ?? resolveConfig(import.meta.dirname);
966
+ const excludedPaths = toolIsEmbedded ? [toolDir] : [];
885
967
  let passed = 0;
886
968
  let failed = 0;
887
969
  let skipped = 0;
@@ -904,7 +986,7 @@ async function main(configOverride) {
904
986
  console.log("=====================");
905
987
  console.log(`Project root: ${projectRoot}`);
906
988
  console.log("");
907
- const testFile = findTestFile(projectRoot);
989
+ const testFile = findTestFile(projectRoot, excludedPaths);
908
990
  if (!testFile) {
909
991
  console.log(" No suitable .ts file found in project. Cannot run smoke tests.");
910
992
  return { passed, failed: failed + 1, skipped };
@@ -917,7 +999,7 @@ async function main(configOverride) {
917
999
  let t0;
918
1000
  t0 = performance.now();
919
1001
  try {
920
- const result = await buildGraph(projectRoot, tsconfigPath);
1002
+ const result = await buildGraph(projectRoot, tsconfigPath, excludedPaths);
921
1003
  graph = result.graph;
922
1004
  const ms = performance.now() - t0;
923
1005
  const edgeCount = [...graph.forward.values()].reduce((s, e) => s + e.length, 0);
@@ -1034,8 +1116,17 @@ async function main(configOverride) {
1034
1116
  } else {
1035
1117
  fail("navbar", `No symbols found in ${testFileRel}`, navbarMs);
1036
1118
  }
1037
- const concreteKinds = /* @__PURE__ */ new Set(["const", "function", "class", "var", "let", "enum"]);
1038
- const sym = allSymbols.find((s) => concreteKinds.has(s.kind)) ?? allSymbols[0];
1119
+ const source = fs4.readFileSync(testFile, "utf-8");
1120
+ const sourceLines = source.split(/\r?\n/);
1121
+ const quickInfoSelection = await selectQuickInfoSymbol(
1122
+ client,
1123
+ testFileRel,
1124
+ allSymbols,
1125
+ sourceLines
1126
+ );
1127
+ const sym = quickInfoSelection?.symbol ?? allSymbols[0];
1128
+ const selectedQuickInfo = quickInfoSelection?.info ?? null;
1129
+ const selectedPosition = quickInfoSelection?.position ?? sym?.spans[0]?.start;
1039
1130
  if (!sym) {
1040
1131
  const toolNames = [
1041
1132
  "find_symbol",
@@ -1050,6 +1141,7 @@ async function main(configOverride) {
1050
1141
  for (const name of toolNames) skip(name, "No symbol discovered");
1051
1142
  } else {
1052
1143
  const span = sym.spans[0];
1144
+ const point = selectedPosition ?? span.start;
1053
1145
  t0 = performance.now();
1054
1146
  const found = findInNavBar(bar, (item) => item.text === sym.text && item.kind === sym.kind);
1055
1147
  if (found && found.spans.length > 0) {
@@ -1062,7 +1154,7 @@ async function main(configOverride) {
1062
1154
  fail("find_symbol", `Could not re-find ${sym.text}`, performance.now() - t0);
1063
1155
  }
1064
1156
  t0 = performance.now();
1065
- const defs = await client.definition(testFileRel, span.start.line, span.start.offset);
1157
+ const defs = await client.definition(testFileRel, point.line, point.offset);
1066
1158
  if (defs.length > 0) {
1067
1159
  const def = defs[0];
1068
1160
  pass("definition", `${sym.text} -> ${def.file}:${def.start.line}`, performance.now() - t0);
@@ -1070,7 +1162,7 @@ async function main(configOverride) {
1070
1162
  pass("definition", `${sym.text} is its own definition`, performance.now() - t0);
1071
1163
  }
1072
1164
  t0 = performance.now();
1073
- const refs = await client.references(testFileRel, span.start.line, span.start.offset);
1165
+ const refs = await client.references(testFileRel, point.line, point.offset);
1074
1166
  const refFiles = new Set(refs.map((r) => r.file));
1075
1167
  pass(
1076
1168
  "references",
@@ -1078,7 +1170,10 @@ async function main(configOverride) {
1078
1170
  performance.now() - t0
1079
1171
  );
1080
1172
  t0 = performance.now();
1081
- let info = await client.quickinfo(testFileRel, span.start.line, span.start.offset);
1173
+ let info = selectedQuickInfo;
1174
+ if (!info) {
1175
+ info = await client.quickinfo(testFileRel, point.line, point.offset);
1176
+ }
1082
1177
  if (!info && defs.length > 0) {
1083
1178
  const def = defs[0];
1084
1179
  info = await client.quickinfo(testFileRel, def.start.line, def.start.offset);
@@ -1087,7 +1182,7 @@ async function main(configOverride) {
1087
1182
  const typeStr = info.displayString.length > 80 ? info.displayString.slice(0, 80) + "..." : info.displayString;
1088
1183
  pass("type_info", typeStr, performance.now() - t0);
1089
1184
  } else {
1090
- fail("type_info", `No type info for ${sym.text}`, performance.now() - t0);
1185
+ skip("type_info", `No discovered symbol returned type info`);
1091
1186
  }
1092
1187
  t0 = performance.now();
1093
1188
  const navItems = await client.navto(sym.text, 5);
@@ -1119,7 +1214,6 @@ async function main(configOverride) {
1119
1214
  const exportSymbols = topItems.filter((item) => symbolKinds.has(item.kind));
1120
1215
  pass("module_exports", `${exportSymbols.length} top-level symbol(s)`, performance.now() - t0);
1121
1216
  t0 = performance.now();
1122
- const source = fs4.readFileSync(testFile, "utf-8");
1123
1217
  const importMatch = source.match(/^import\s+\{([^}]+)\}\s+from\s+["']([^"']+)["']/m);
1124
1218
  if (importMatch) {
1125
1219
  const firstName = importMatch[1].split(",")[0].replace(/^type\s+/, "").trim();
@@ -1180,5 +1274,6 @@ async function main(configOverride) {
1180
1274
  return { passed, failed, skipped };
1181
1275
  }
1182
1276
  export {
1183
- main
1277
+ main,
1278
+ selectQuickInfoSymbol
1184
1279
  };
@@ -5,6 +5,30 @@ import * as fs from "fs";
5
5
  import { createRequire } from "module";
6
6
  var log = (...args) => console.error("[typegraph/tsserver]", ...args);
7
7
  var REQUEST_TIMEOUT_MS = 1e4;
8
+ function readPackageVersion(packagePath) {
9
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
10
+ return pkg.version ?? "unknown";
11
+ }
12
+ function resolveTsServer(projectRoot) {
13
+ const projectRequire = createRequire(path.resolve(projectRoot, "package.json"));
14
+ let projectVersion;
15
+ try {
16
+ const packagePath2 = projectRequire.resolve("typescript/package.json");
17
+ projectVersion = readPackageVersion(packagePath2);
18
+ const serverPath2 = projectRequire.resolve("typescript/lib/tsserver.js");
19
+ return { path: serverPath2, version: projectVersion, source: "project" };
20
+ } catch {
21
+ }
22
+ const toolRequire = createRequire(import.meta.url);
23
+ const packagePath = toolRequire.resolve("typescript/package.json");
24
+ const serverPath = toolRequire.resolve("typescript/lib/tsserver.js");
25
+ return {
26
+ path: serverPath,
27
+ version: readPackageVersion(packagePath),
28
+ source: "typegraph",
29
+ projectVersion
30
+ };
31
+ }
8
32
  var TsServerClient = class {
9
33
  constructor(projectRoot, tsconfigPath = "./tsconfig.json") {
10
34
  this.projectRoot = projectRoot;
@@ -41,9 +65,12 @@ var TsServerClient = class {
41
65
  // ─── Lifecycle ──────────────────────────────────────────────────────────
42
66
  async start() {
43
67
  if (this.child) return;
44
- const require2 = createRequire(path.resolve(this.projectRoot, "package.json"));
45
- const tsserverPath = require2.resolve("typescript/lib/tsserver.js");
46
- log(`Spawning tsserver: ${tsserverPath}`);
68
+ const resolution = resolveTsServer(this.projectRoot);
69
+ const tsserverPath = resolution.path;
70
+ const fallbackNote = resolution.projectVersion ? `; project TypeScript ${resolution.projectVersion} has no legacy tsserver` : "";
71
+ log(
72
+ `Spawning tsserver: ${tsserverPath} (TypeScript ${resolution.version}, ${resolution.source}${fallbackNote})`
73
+ );
47
74
  log(`Project root: ${this.projectRoot}`);
48
75
  log(`tsconfig: ${this.tsconfigPath}`);
49
76
  this.child = spawn("node", [tsserverPath, "--disableAutomaticTypingAcquisition"], {
@@ -72,12 +99,9 @@ var TsServerClient = class {
72
99
  }
73
100
  });
74
101
  const warmStart = performance.now();
75
- const tsconfigAbs = this.resolvePath(this.tsconfigPath);
76
- if (fs.existsSync(tsconfigAbs)) {
77
- await this.sendRequest("compilerOptionsForInferredProjects", {
78
- options: { allowJs: true, checkJs: false }
79
- });
80
- }
102
+ await this.sendRequest("compilerOptionsForInferredProjects", {
103
+ options: { allowJs: true, checkJs: false }
104
+ });
81
105
  this.ready = true;
82
106
  log(`Ready [${(performance.now() - warmStart).toFixed(0)}ms configure]`);
83
107
  }
@@ -301,5 +325,6 @@ var TsServerClient = class {
301
325
  }
302
326
  };
303
327
  export {
304
- TsServerClient
328
+ TsServerClient,
329
+ resolveTsServer
305
330
  };