typegraph-mcp 0.9.42 → 0.9.44

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/check.ts CHANGED
@@ -170,6 +170,64 @@ function hasTrustedCodexProject(projectRoot: string): boolean | null {
170
170
  return matchesTrustedProject();
171
171
  }
172
172
 
173
+ function hasCompleteJsonTypegraphRegistration(
174
+ config: unknown,
175
+ serverName: string,
176
+ projectRoot: string
177
+ ): boolean {
178
+ if (typeof config !== "object" || config === null) return false;
179
+ const servers = (config as Record<string, unknown>)["mcpServers"];
180
+ if (typeof servers !== "object" || servers === null) return false;
181
+ const entry = (servers as Record<string, unknown>)[serverName];
182
+ if (typeof entry !== "object" || entry === null) return false;
183
+
184
+ const record = entry as Record<string, unknown>;
185
+ const command = record["command"];
186
+ const args = record["args"];
187
+ const env = record["env"];
188
+ const serializedArgs = JSON.stringify(args ?? []);
189
+
190
+ return (
191
+ typeof command === "string" &&
192
+ command.length > 0 &&
193
+ Array.isArray(args) &&
194
+ serializedArgs.includes("server.ts") &&
195
+ serializedArgs.includes("tsx") &&
196
+ typeof env === "object" &&
197
+ env !== null &&
198
+ (env as Record<string, unknown>)["TYPEGRAPH_PROJECT_ROOT"] === projectRoot &&
199
+ typeof (env as Record<string, unknown>)["TYPEGRAPH_TSCONFIG"] === "string"
200
+ );
201
+ }
202
+
203
+ function readJsonConfig(configPath: string): unknown | null {
204
+ if (!fs.existsSync(configPath)) return null;
205
+ try {
206
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+
212
+ function getAntigravityMcpConfigPaths(): string[] {
213
+ const home = process.env.HOME || "";
214
+ return [
215
+ path.join(home, ".gemini/antigravity/mcp_config.json"),
216
+ path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json"),
217
+ ];
218
+ }
219
+
220
+ function findAntigravityRegistration(projectRoot: string): string | null {
221
+ const home = process.env.HOME || "";
222
+ for (const configPath of getAntigravityMcpConfigPaths()) {
223
+ const config = readJsonConfig(configPath);
224
+ if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot)) {
225
+ return configPath.replace(home, "~");
226
+ }
227
+ }
228
+ return null;
229
+ }
230
+
173
231
  function readProjectPackageJson(projectRoot: string): Record<string, unknown> | null {
174
232
  const packageJsonPath = path.resolve(projectRoot, "package.json");
175
233
  if (!fs.existsSync(packageJsonPath)) return null;
@@ -346,6 +404,7 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
346
404
  encoding: "utf-8",
347
405
  });
348
406
  const hasGlobalCodexRegistration = codexGet.status === 0;
407
+ const antigravityRegistrationPath = findAntigravityRegistration(projectRoot);
349
408
  if (process.env.CLAUDE_PLUGIN_ROOT) {
350
409
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
351
410
  } else if (hasPluginMcp) {
@@ -382,6 +441,8 @@ export async function main(configOverride?: TypegraphConfig): Promise<CheckResul
382
441
  }
383
442
  } else if (hasGlobalCodexRegistration) {
384
443
  pass("MCP registered in global Codex CLI config");
444
+ } else if (antigravityRegistrationPath !== null) {
445
+ pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
385
446
  } else {
386
447
  const codexConfigPath = path.resolve(projectRoot, ".codex/config.toml");
387
448
  const mcpJsonPath = path.resolve(projectRoot, ".claude/mcp.json");
package/cli.ts CHANGED
@@ -22,7 +22,7 @@ import { resolveConfig } from "./config.js";
22
22
 
23
23
  // ─── Types ───────────────────────────────────────────────────────────────────
24
24
 
25
- type AgentId = "claude-code" | "cursor" | "codex" | "gemini" | "copilot";
25
+ type AgentId = "claude-code" | "cursor" | "codex" | "gemini" | "copilot" | "antigravity";
26
26
 
27
27
  interface AgentDef {
28
28
  name: string;
@@ -80,7 +80,7 @@ const CLAUDE_NODE_PLACEHOLDER = "__TYPEGRAPH_NODE__";
80
80
 
81
81
  const PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
82
82
 
83
- const AGENT_IDS: AgentId[] = ["claude-code", "cursor", "codex", "gemini", "copilot"];
83
+ const AGENT_IDS: AgentId[] = ["claude-code", "cursor", "codex", "gemini", "copilot", "antigravity"];
84
84
 
85
85
  const AGENTS: Record<AgentId, AgentDef> = {
86
86
  "claude-code": {
@@ -129,6 +129,13 @@ const AGENTS: Record<AgentId, AgentDef> = {
129
129
  detect: (root) =>
130
130
  fs.existsSync(path.join(root, ".github/copilot-instructions.md")),
131
131
  },
132
+ antigravity: {
133
+ name: "Antigravity",
134
+ pluginFiles: [],
135
+ agentFile: "AGENTS.md",
136
+ needsAgentsSkills: true,
137
+ detect: (root) => fs.existsSync(path.join(root, ".gemini/antigravity")),
138
+ },
132
139
  };
133
140
 
134
141
  /** Core files always installed (server, modules, config, package manifest) */
@@ -250,6 +257,35 @@ function getCodexConfigPath(projectRoot: string): string {
250
257
  return path.resolve(projectRoot, ".codex/config.toml");
251
258
  }
252
259
 
260
+ function getAntigravityMcpConfigPaths(): string[] {
261
+ const home = process.env.HOME || "";
262
+ return [
263
+ path.join(home, ".gemini/antigravity/mcp_config.json"),
264
+ path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json"),
265
+ ];
266
+ }
267
+
268
+ function ensureAntigravityCliPlugin(): void {
269
+ const home = process.env.HOME || "";
270
+ const pluginDir = path.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp");
271
+ const pluginJsonPath = path.join(pluginDir, "plugin.json");
272
+ if (!fs.existsSync(pluginJsonPath)) {
273
+ fs.mkdirSync(pluginDir, { recursive: true });
274
+ fs.writeFileSync(
275
+ pluginJsonPath,
276
+ JSON.stringify(
277
+ {
278
+ name: "typegraph-mcp",
279
+ version: "1.0.0",
280
+ description: "TypeGraph MCP server for TypeScript navigation",
281
+ },
282
+ null,
283
+ 2
284
+ ) + "\n"
285
+ );
286
+ }
287
+ }
288
+
253
289
  function isTomlSectionGroup(sectionName: string | null, prefix: string): boolean {
254
290
  return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
255
291
  }
@@ -476,6 +512,9 @@ function registerMcpServers(projectRoot: string, selectedAgents: AgentId[]): voi
476
512
  if (selectedAgents.includes("copilot")) {
477
513
  registerJsonMcp(projectRoot, ".vscode/mcp.json", "servers");
478
514
  }
515
+ if (selectedAgents.includes("antigravity")) {
516
+ registerAntigravityMcp(projectRoot);
517
+ }
479
518
  }
480
519
 
481
520
  /** Deregister the typegraph MCP server from all agent config files */
@@ -483,6 +522,7 @@ function deregisterMcpServers(projectRoot: string): void {
483
522
  deregisterJsonMcp(projectRoot, ".cursor/mcp.json", "mcpServers");
484
523
  deregisterCodexMcp(projectRoot);
485
524
  deregisterJsonMcp(projectRoot, ".vscode/mcp.json", "servers");
525
+ deregisterAntigravityMcp(projectRoot);
486
526
  }
487
527
 
488
528
  /** Register MCP server in a JSON config file (Cursor or Copilot format) */
@@ -593,6 +633,73 @@ function deregisterCodexMcp(projectRoot: string): void {
593
633
  }
594
634
  }
595
635
 
636
+ /** Register MCP server in Antigravity's config files */
637
+ function registerAntigravityMcp(projectRoot: string): void {
638
+ const home = process.env.HOME || "";
639
+ const pluginDir = path.resolve(projectRoot, PLUGIN_DIR_NAME);
640
+ const tsConfigPath = path.resolve(projectRoot, "tsconfig.json");
641
+ ensureAntigravityCliPlugin();
642
+
643
+ const entry = {
644
+ command: process.execPath,
645
+ args: [
646
+ path.join(pluginDir, "node_modules/tsx/dist/cli.mjs"),
647
+ path.join(pluginDir, "server.ts"),
648
+ ],
649
+ env: {
650
+ TYPEGRAPH_PROJECT_ROOT: projectRoot,
651
+ TYPEGRAPH_TSCONFIG: tsConfigPath,
652
+ },
653
+ };
654
+
655
+ for (const configPath of getAntigravityMcpConfigPaths()) {
656
+ let config: any = { mcpServers: {} };
657
+ if (fs.existsSync(configPath)) {
658
+ try {
659
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
660
+ } catch {
661
+ p.log.warn(`Could not parse ~${configPath.replace(home, "")} — skipping MCP registration`);
662
+ continue;
663
+ }
664
+ }
665
+ if (!config.mcpServers) config.mcpServers = {};
666
+ config.mcpServers["typegraph-mcp"] = entry;
667
+
668
+ const dir = path.dirname(configPath);
669
+ if (!fs.existsSync(dir)) {
670
+ fs.mkdirSync(dir, { recursive: true });
671
+ }
672
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
673
+ p.log.success(`~${configPath.replace(home, "")}: registered typegraph-mcp server`);
674
+ }
675
+ }
676
+
677
+ /** Deregister MCP server from Antigravity's config files */
678
+ function deregisterAntigravityMcp(projectRoot: string): void {
679
+ const home = process.env.HOME || "";
680
+
681
+ for (const configPath of getAntigravityMcpConfigPaths()) {
682
+ if (!fs.existsSync(configPath)) continue;
683
+ try {
684
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
685
+ if (config.mcpServers && config.mcpServers["typegraph-mcp"]) {
686
+ delete config.mcpServers["typegraph-mcp"];
687
+ if (Object.keys(config.mcpServers).length === 0) {
688
+ delete config.mcpServers;
689
+ }
690
+ if (Object.keys(config).length === 0) {
691
+ fs.unlinkSync(configPath);
692
+ } else {
693
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
694
+ }
695
+ p.log.info(`~${configPath.replace(home, "")}: removed typegraph-mcp server`);
696
+ }
697
+ } catch {
698
+ // Ignore
699
+ }
700
+ }
701
+ }
702
+
596
703
  // ─── TSConfig Exclude ─────────────────────────────────────────────────────────
597
704
 
598
705
  function ensureTsconfigExclude(projectRoot: string): void {
@@ -876,7 +983,7 @@ async function setup(yes: boolean): Promise<void> {
876
983
  // 3. Agent selection
877
984
  const selectedAgents = await selectAgents(projectRoot, yes);
878
985
 
879
- const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor");
986
+ const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor") || selectedAgents.includes("antigravity");
880
987
  const needsAgentsSkills = selectedAgents.some((id) => AGENTS[id].needsAgentsSkills);
881
988
 
882
989
  p.log.step(`Installing to ${PLUGIN_DIR_NAME}/...`);
package/dist/benchmark.js CHANGED
@@ -201,6 +201,21 @@ var TsServerClient = class {
201
201
  this.sendNotification("open", { file: absPath });
202
202
  await new Promise((r) => setTimeout(r, 50));
203
203
  }
204
+ async reloadOpenFile(file) {
205
+ const absPath = this.resolvePath(file);
206
+ if (!this.openFiles.has(absPath)) return false;
207
+ await this.sendRequest("reload", {
208
+ file: absPath,
209
+ tmpfile: absPath
210
+ });
211
+ return true;
212
+ }
213
+ closeFile(file) {
214
+ const absPath = this.resolvePath(file);
215
+ if (!this.openFiles.delete(absPath)) return false;
216
+ this.sendNotification("close", { file: absPath });
217
+ return true;
218
+ }
204
219
  // ─── Public API ────────────────────────────────────────────────────────
205
220
  async definition(file, line, offset) {
206
221
  const absPath = this.resolvePath(file);
package/dist/check.js CHANGED
@@ -301,7 +301,7 @@ function removeFile(graph, filePath) {
301
301
  graph.reverse.delete(filePath);
302
302
  graph.files.delete(filePath);
303
303
  }
304
- function startWatcher(projectRoot, graph, resolver) {
304
+ function startWatcher(projectRoot, graph, resolver, hooks) {
305
305
  try {
306
306
  const watcher = fs.watch(
307
307
  projectRoot,
@@ -318,8 +318,10 @@ function startWatcher(projectRoot, graph, resolver) {
318
318
  const absPath = path2.resolve(projectRoot, filename);
319
319
  if (fs.existsSync(absPath)) {
320
320
  updateFile(graph, absPath, resolver, projectRoot);
321
+ void hooks?.onFileUpdated?.(absPath);
321
322
  } else {
322
323
  removeFile(graph, absPath);
324
+ void hooks?.onFileDeleted?.(absPath);
323
325
  }
324
326
  }
325
327
  );
@@ -476,6 +478,44 @@ function hasTrustedCodexProject(projectRoot) {
476
478
  }
477
479
  return matchesTrustedProject();
478
480
  }
481
+ function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot) {
482
+ if (typeof config !== "object" || config === null) return false;
483
+ const servers = config["mcpServers"];
484
+ if (typeof servers !== "object" || servers === null) return false;
485
+ const entry = servers[serverName];
486
+ if (typeof entry !== "object" || entry === null) return false;
487
+ const record = entry;
488
+ const command = record["command"];
489
+ const args = record["args"];
490
+ const env = record["env"];
491
+ const serializedArgs = JSON.stringify(args ?? []);
492
+ return typeof command === "string" && command.length > 0 && Array.isArray(args) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
493
+ }
494
+ function readJsonConfig(configPath) {
495
+ if (!fs2.existsSync(configPath)) return null;
496
+ try {
497
+ return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
498
+ } catch {
499
+ return null;
500
+ }
501
+ }
502
+ function getAntigravityMcpConfigPaths() {
503
+ const home = process.env.HOME || "";
504
+ return [
505
+ path3.join(home, ".gemini/antigravity/mcp_config.json"),
506
+ path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
507
+ ];
508
+ }
509
+ function findAntigravityRegistration(projectRoot) {
510
+ const home = process.env.HOME || "";
511
+ for (const configPath of getAntigravityMcpConfigPaths()) {
512
+ const config = readJsonConfig(configPath);
513
+ if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot)) {
514
+ return configPath.replace(home, "~");
515
+ }
516
+ }
517
+ return null;
518
+ }
479
519
  function readProjectPackageJson(projectRoot) {
480
520
  const packageJsonPath = path3.resolve(projectRoot, "package.json");
481
521
  if (!fs2.existsSync(packageJsonPath)) return null;
@@ -609,6 +649,7 @@ async function main(configOverride) {
609
649
  encoding: "utf-8"
610
650
  });
611
651
  const hasGlobalCodexRegistration = codexGet.status === 0;
652
+ const antigravityRegistrationPath = findAntigravityRegistration(projectRoot);
612
653
  if (process.env.CLAUDE_PLUGIN_ROOT) {
613
654
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
614
655
  } else if (hasPluginMcp) {
@@ -645,6 +686,8 @@ async function main(configOverride) {
645
686
  }
646
687
  } else if (hasGlobalCodexRegistration) {
647
688
  pass("MCP registered in global Codex CLI config");
689
+ } else if (antigravityRegistrationPath !== null) {
690
+ pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
648
691
  } else {
649
692
  const codexConfigPath = path3.resolve(projectRoot, ".codex/config.toml");
650
693
  const mcpJsonPath = path3.resolve(projectRoot, ".claude/mcp.json");
package/dist/cli.js CHANGED
@@ -317,7 +317,7 @@ function removeFile(graph, filePath) {
317
317
  graph.reverse.delete(filePath);
318
318
  graph.files.delete(filePath);
319
319
  }
320
- function startWatcher(projectRoot3, graph, resolver) {
320
+ function startWatcher(projectRoot3, graph, resolver, hooks) {
321
321
  try {
322
322
  const watcher = fs.watch(
323
323
  projectRoot3,
@@ -334,8 +334,10 @@ function startWatcher(projectRoot3, graph, resolver) {
334
334
  const absPath2 = path2.resolve(projectRoot3, filename);
335
335
  if (fs.existsSync(absPath2)) {
336
336
  updateFile(graph, absPath2, resolver, projectRoot3);
337
+ void hooks?.onFileUpdated?.(absPath2);
337
338
  } else {
338
339
  removeFile(graph, absPath2);
340
+ void hooks?.onFileDeleted?.(absPath2);
339
341
  }
340
342
  }
341
343
  );
@@ -483,6 +485,44 @@ function hasTrustedCodexProject(projectRoot3) {
483
485
  }
484
486
  return matchesTrustedProject();
485
487
  }
488
+ function hasCompleteJsonTypegraphRegistration(config, serverName, projectRoot3) {
489
+ if (typeof config !== "object" || config === null) return false;
490
+ const servers = config["mcpServers"];
491
+ if (typeof servers !== "object" || servers === null) return false;
492
+ const entry = servers[serverName];
493
+ if (typeof entry !== "object" || entry === null) return false;
494
+ const record = entry;
495
+ const command2 = record["command"];
496
+ const args2 = record["args"];
497
+ const env = record["env"];
498
+ const serializedArgs = JSON.stringify(args2 ?? []);
499
+ return typeof command2 === "string" && command2.length > 0 && Array.isArray(args2) && serializedArgs.includes("server.ts") && serializedArgs.includes("tsx") && typeof env === "object" && env !== null && env["TYPEGRAPH_PROJECT_ROOT"] === projectRoot3 && typeof env["TYPEGRAPH_TSCONFIG"] === "string";
500
+ }
501
+ function readJsonConfig(configPath) {
502
+ if (!fs2.existsSync(configPath)) return null;
503
+ try {
504
+ return JSON.parse(fs2.readFileSync(configPath, "utf-8"));
505
+ } catch {
506
+ return null;
507
+ }
508
+ }
509
+ function getAntigravityMcpConfigPaths() {
510
+ const home = process.env.HOME || "";
511
+ return [
512
+ path3.join(home, ".gemini/antigravity/mcp_config.json"),
513
+ path3.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
514
+ ];
515
+ }
516
+ function findAntigravityRegistration(projectRoot3) {
517
+ const home = process.env.HOME || "";
518
+ for (const configPath of getAntigravityMcpConfigPaths()) {
519
+ const config = readJsonConfig(configPath);
520
+ if (hasCompleteJsonTypegraphRegistration(config, "typegraph-mcp", projectRoot3)) {
521
+ return configPath.replace(home, "~");
522
+ }
523
+ }
524
+ return null;
525
+ }
486
526
  function readProjectPackageJson(projectRoot3) {
487
527
  const packageJsonPath = path3.resolve(projectRoot3, "package.json");
488
528
  if (!fs2.existsSync(packageJsonPath)) return null;
@@ -603,6 +643,7 @@ async function main(configOverride) {
603
643
  encoding: "utf-8"
604
644
  });
605
645
  const hasGlobalCodexRegistration = codexGet.status === 0;
646
+ const antigravityRegistrationPath = findAntigravityRegistration(projectRoot3);
606
647
  if (process.env.CLAUDE_PLUGIN_ROOT) {
607
648
  pass("MCP registered via plugin (CLAUDE_PLUGIN_ROOT set)");
608
649
  } else if (hasPluginMcp) {
@@ -639,6 +680,8 @@ async function main(configOverride) {
639
680
  }
640
681
  } else if (hasGlobalCodexRegistration) {
641
682
  pass("MCP registered in global Codex CLI config");
683
+ } else if (antigravityRegistrationPath !== null) {
684
+ pass(`MCP registered in Antigravity config (${antigravityRegistrationPath})`);
642
685
  } else {
643
686
  const codexConfigPath = path3.resolve(projectRoot3, ".codex/config.toml");
644
687
  const mcpJsonPath = path3.resolve(projectRoot3, ".claude/mcp.json");
@@ -1087,6 +1130,21 @@ var init_tsserver_client = __esm({
1087
1130
  this.sendNotification("open", { file: absPath2 });
1088
1131
  await new Promise((r) => setTimeout(r, 50));
1089
1132
  }
1133
+ async reloadOpenFile(file) {
1134
+ const absPath2 = this.resolvePath(file);
1135
+ if (!this.openFiles.has(absPath2)) return false;
1136
+ await this.sendRequest("reload", {
1137
+ file: absPath2,
1138
+ tmpfile: absPath2
1139
+ });
1140
+ return true;
1141
+ }
1142
+ closeFile(file) {
1143
+ const absPath2 = this.resolvePath(file);
1144
+ if (!this.openFiles.delete(absPath2)) return false;
1145
+ this.sendNotification("close", { file: absPath2 });
1146
+ return true;
1147
+ }
1090
1148
  // ─── Public API ────────────────────────────────────────────────────────
1091
1149
  async definition(file, line, offset) {
1092
1150
  const absPath2 = this.resolvePath(file);
@@ -2638,7 +2696,14 @@ async function main4() {
2638
2696
  ]);
2639
2697
  moduleGraph = graphResult.graph;
2640
2698
  moduleResolver = graphResult.resolver;
2641
- startWatcher(projectRoot2, moduleGraph, graphResult.resolver);
2699
+ startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
2700
+ onFileUpdated: (filePath) => client.reloadOpenFile(filePath).catch((err) => {
2701
+ log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
2702
+ }),
2703
+ onFileDeleted: (filePath) => {
2704
+ client.closeFile(filePath);
2705
+ }
2706
+ });
2642
2707
  const transport = new StdioServerTransport();
2643
2708
  await mcpServer.connect(transport);
2644
2709
  log3("MCP server connected and ready");
@@ -3193,7 +3258,7 @@ Practical rule:
3193
3258
  var SNIPPET_MARKER = "## TypeScript Navigation (typegraph-mcp)";
3194
3259
  var CLAUDE_NODE_PLACEHOLDER = "__TYPEGRAPH_NODE__";
3195
3260
  var PLUGIN_DIR_NAME = "plugins/typegraph-mcp";
3196
- var AGENT_IDS = ["claude-code", "cursor", "codex", "gemini", "copilot"];
3261
+ var AGENT_IDS = ["claude-code", "cursor", "codex", "gemini", "copilot", "antigravity"];
3197
3262
  var AGENTS = {
3198
3263
  "claude-code": {
3199
3264
  name: "Claude Code",
@@ -3237,6 +3302,13 @@ var AGENTS = {
3237
3302
  agentFile: ".github/copilot-instructions.md",
3238
3303
  needsAgentsSkills: true,
3239
3304
  detect: (root) => fs8.existsSync(path9.join(root, ".github/copilot-instructions.md"))
3305
+ },
3306
+ antigravity: {
3307
+ name: "Antigravity",
3308
+ pluginFiles: [],
3309
+ agentFile: "AGENTS.md",
3310
+ needsAgentsSkills: true,
3311
+ detect: (root) => fs8.existsSync(path9.join(root, ".gemini/antigravity"))
3240
3312
  }
3241
3313
  };
3242
3314
  var CORE_FILES = [
@@ -3322,6 +3394,33 @@ function getCodexMcpServerEntry(projectRoot3) {
3322
3394
  function getCodexConfigPath(projectRoot3) {
3323
3395
  return path9.resolve(projectRoot3, ".codex/config.toml");
3324
3396
  }
3397
+ function getAntigravityMcpConfigPaths2() {
3398
+ const home = process.env.HOME || "";
3399
+ return [
3400
+ path9.join(home, ".gemini/antigravity/mcp_config.json"),
3401
+ path9.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp/mcp_config.json")
3402
+ ];
3403
+ }
3404
+ function ensureAntigravityCliPlugin() {
3405
+ const home = process.env.HOME || "";
3406
+ const pluginDir = path9.join(home, ".gemini/antigravity-cli/plugins/typegraph-mcp");
3407
+ const pluginJsonPath = path9.join(pluginDir, "plugin.json");
3408
+ if (!fs8.existsSync(pluginJsonPath)) {
3409
+ fs8.mkdirSync(pluginDir, { recursive: true });
3410
+ fs8.writeFileSync(
3411
+ pluginJsonPath,
3412
+ JSON.stringify(
3413
+ {
3414
+ name: "typegraph-mcp",
3415
+ version: "1.0.0",
3416
+ description: "TypeGraph MCP server for TypeScript navigation"
3417
+ },
3418
+ null,
3419
+ 2
3420
+ ) + "\n"
3421
+ );
3422
+ }
3423
+ }
3325
3424
  function isTomlSectionGroup(sectionName, prefix) {
3326
3425
  return sectionName === prefix || sectionName?.startsWith(`${prefix}.`) === true;
3327
3426
  }
@@ -3495,11 +3594,15 @@ function registerMcpServers(projectRoot3, selectedAgents) {
3495
3594
  if (selectedAgents.includes("copilot")) {
3496
3595
  registerJsonMcp(projectRoot3, ".vscode/mcp.json", "servers");
3497
3596
  }
3597
+ if (selectedAgents.includes("antigravity")) {
3598
+ registerAntigravityMcp(projectRoot3);
3599
+ }
3498
3600
  }
3499
3601
  function deregisterMcpServers(projectRoot3) {
3500
3602
  deregisterJsonMcp(projectRoot3, ".cursor/mcp.json", "mcpServers");
3501
3603
  deregisterCodexMcp(projectRoot3);
3502
3604
  deregisterJsonMcp(projectRoot3, ".vscode/mcp.json", "servers");
3605
+ deregisterAntigravityMcp(projectRoot3);
3503
3606
  }
3504
3607
  function registerJsonMcp(projectRoot3, configPath, rootKey) {
3505
3608
  const fullPath = path9.resolve(projectRoot3, configPath);
@@ -3585,6 +3688,64 @@ function deregisterCodexMcp(projectRoot3) {
3585
3688
  }
3586
3689
  }
3587
3690
  }
3691
+ function registerAntigravityMcp(projectRoot3) {
3692
+ const home = process.env.HOME || "";
3693
+ const pluginDir = path9.resolve(projectRoot3, PLUGIN_DIR_NAME);
3694
+ const tsConfigPath = path9.resolve(projectRoot3, "tsconfig.json");
3695
+ ensureAntigravityCliPlugin();
3696
+ const entry = {
3697
+ command: process.execPath,
3698
+ args: [
3699
+ path9.join(pluginDir, "node_modules/tsx/dist/cli.mjs"),
3700
+ path9.join(pluginDir, "server.ts")
3701
+ ],
3702
+ env: {
3703
+ TYPEGRAPH_PROJECT_ROOT: projectRoot3,
3704
+ TYPEGRAPH_TSCONFIG: tsConfigPath
3705
+ }
3706
+ };
3707
+ for (const configPath of getAntigravityMcpConfigPaths2()) {
3708
+ let config = { mcpServers: {} };
3709
+ if (fs8.existsSync(configPath)) {
3710
+ try {
3711
+ config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
3712
+ } catch {
3713
+ p.log.warn(`Could not parse ~${configPath.replace(home, "")} \u2014 skipping MCP registration`);
3714
+ continue;
3715
+ }
3716
+ }
3717
+ if (!config.mcpServers) config.mcpServers = {};
3718
+ config.mcpServers["typegraph-mcp"] = entry;
3719
+ const dir = path9.dirname(configPath);
3720
+ if (!fs8.existsSync(dir)) {
3721
+ fs8.mkdirSync(dir, { recursive: true });
3722
+ }
3723
+ fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
3724
+ p.log.success(`~${configPath.replace(home, "")}: registered typegraph-mcp server`);
3725
+ }
3726
+ }
3727
+ function deregisterAntigravityMcp(projectRoot3) {
3728
+ const home = process.env.HOME || "";
3729
+ for (const configPath of getAntigravityMcpConfigPaths2()) {
3730
+ if (!fs8.existsSync(configPath)) continue;
3731
+ try {
3732
+ const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
3733
+ if (config.mcpServers && config.mcpServers["typegraph-mcp"]) {
3734
+ delete config.mcpServers["typegraph-mcp"];
3735
+ if (Object.keys(config.mcpServers).length === 0) {
3736
+ delete config.mcpServers;
3737
+ }
3738
+ if (Object.keys(config).length === 0) {
3739
+ fs8.unlinkSync(configPath);
3740
+ } else {
3741
+ fs8.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
3742
+ }
3743
+ p.log.info(`~${configPath.replace(home, "")}: removed typegraph-mcp server`);
3744
+ }
3745
+ } catch {
3746
+ }
3747
+ }
3748
+ }
3588
3749
  function ensureTsconfigExclude(projectRoot3) {
3589
3750
  const tsconfigPath3 = path9.resolve(projectRoot3, "tsconfig.json");
3590
3751
  if (!fs8.existsSync(tsconfigPath3)) return;
@@ -3807,7 +3968,7 @@ async function setup(yes2) {
3807
3968
  }
3808
3969
  }
3809
3970
  const selectedAgents = await selectAgents(projectRoot3, yes2);
3810
- const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor");
3971
+ const needsPluginSkills = selectedAgents.includes("claude-code") || selectedAgents.includes("cursor") || selectedAgents.includes("antigravity");
3811
3972
  const needsAgentsSkills = selectedAgents.some((id) => AGENTS[id].needsAgentsSkills);
3812
3973
  p.log.step(`Installing to ${PLUGIN_DIR_NAME}/...`);
3813
3974
  const s = p.spinner();
@@ -297,7 +297,7 @@ function removeFile(graph, filePath) {
297
297
  graph.reverse.delete(filePath);
298
298
  graph.files.delete(filePath);
299
299
  }
300
- function startWatcher(projectRoot, graph, resolver) {
300
+ function startWatcher(projectRoot, graph, resolver, hooks) {
301
301
  try {
302
302
  const watcher = fs.watch(
303
303
  projectRoot,
@@ -314,8 +314,10 @@ function startWatcher(projectRoot, graph, resolver) {
314
314
  const absPath = path.resolve(projectRoot, filename);
315
315
  if (fs.existsSync(absPath)) {
316
316
  updateFile(graph, absPath, resolver, projectRoot);
317
+ void hooks?.onFileUpdated?.(absPath);
317
318
  } else {
318
319
  removeFile(graph, absPath);
320
+ void hooks?.onFileDeleted?.(absPath);
319
321
  }
320
322
  }
321
323
  );
package/dist/server.js CHANGED
@@ -202,6 +202,21 @@ var TsServerClient = class {
202
202
  this.sendNotification("open", { file: absPath2 });
203
203
  await new Promise((r) => setTimeout(r, 50));
204
204
  }
205
+ async reloadOpenFile(file) {
206
+ const absPath2 = this.resolvePath(file);
207
+ if (!this.openFiles.has(absPath2)) return false;
208
+ await this.sendRequest("reload", {
209
+ file: absPath2,
210
+ tmpfile: absPath2
211
+ });
212
+ return true;
213
+ }
214
+ closeFile(file) {
215
+ const absPath2 = this.resolvePath(file);
216
+ if (!this.openFiles.delete(absPath2)) return false;
217
+ this.sendNotification("close", { file: absPath2 });
218
+ return true;
219
+ }
205
220
  // ─── Public API ────────────────────────────────────────────────────────
206
221
  async definition(file, line, offset) {
207
222
  const absPath2 = this.resolvePath(file);
@@ -568,7 +583,7 @@ function removeFile(graph, filePath) {
568
583
  graph.reverse.delete(filePath);
569
584
  graph.files.delete(filePath);
570
585
  }
571
- function startWatcher(projectRoot2, graph, resolver) {
586
+ function startWatcher(projectRoot2, graph, resolver, hooks) {
572
587
  try {
573
588
  const watcher = fs2.watch(
574
589
  projectRoot2,
@@ -585,8 +600,10 @@ function startWatcher(projectRoot2, graph, resolver) {
585
600
  const absPath2 = path2.resolve(projectRoot2, filename);
586
601
  if (fs2.existsSync(absPath2)) {
587
602
  updateFile(graph, absPath2, resolver, projectRoot2);
603
+ void hooks?.onFileUpdated?.(absPath2);
588
604
  } else {
589
605
  removeFile(graph, absPath2);
606
+ void hooks?.onFileDeleted?.(absPath2);
590
607
  }
591
608
  }
592
609
  );
@@ -1677,7 +1694,14 @@ async function main() {
1677
1694
  ]);
1678
1695
  moduleGraph = graphResult.graph;
1679
1696
  moduleResolver = graphResult.resolver;
1680
- startWatcher(projectRoot, moduleGraph, graphResult.resolver);
1697
+ startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
1698
+ onFileUpdated: (filePath) => client.reloadOpenFile(filePath).catch((err) => {
1699
+ log3(`Failed to reload open file ${relPath(filePath)}:`, err);
1700
+ }),
1701
+ onFileDeleted: (filePath) => {
1702
+ client.closeFile(filePath);
1703
+ }
1704
+ });
1681
1705
  const transport = new StdioServerTransport();
1682
1706
  await mcpServer.connect(transport);
1683
1707
  log3("MCP server connected and ready");
@@ -200,6 +200,21 @@ var TsServerClient = class {
200
200
  this.sendNotification("open", { file: absPath });
201
201
  await new Promise((r) => setTimeout(r, 50));
202
202
  }
203
+ async reloadOpenFile(file) {
204
+ const absPath = this.resolvePath(file);
205
+ if (!this.openFiles.has(absPath)) return false;
206
+ await this.sendRequest("reload", {
207
+ file: absPath,
208
+ tmpfile: absPath
209
+ });
210
+ return true;
211
+ }
212
+ closeFile(file) {
213
+ const absPath = this.resolvePath(file);
214
+ if (!this.openFiles.delete(absPath)) return false;
215
+ this.sendNotification("close", { file: absPath });
216
+ return true;
217
+ }
203
218
  // ─── Public API ────────────────────────────────────────────────────────
204
219
  async definition(file, line, offset) {
205
220
  const absPath = this.resolvePath(file);
@@ -195,6 +195,21 @@ var TsServerClient = class {
195
195
  this.sendNotification("open", { file: absPath });
196
196
  await new Promise((r) => setTimeout(r, 50));
197
197
  }
198
+ async reloadOpenFile(file) {
199
+ const absPath = this.resolvePath(file);
200
+ if (!this.openFiles.has(absPath)) return false;
201
+ await this.sendRequest("reload", {
202
+ file: absPath,
203
+ tmpfile: absPath
204
+ });
205
+ return true;
206
+ }
207
+ closeFile(file) {
208
+ const absPath = this.resolvePath(file);
209
+ if (!this.openFiles.delete(absPath)) return false;
210
+ this.sendNotification("close", { file: absPath });
211
+ return true;
212
+ }
198
213
  // ─── Public API ────────────────────────────────────────────────────────
199
214
  async definition(file, line, offset) {
200
215
  const absPath = this.resolvePath(file);
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env npx tsx
2
+
3
+ import * as assert from "node:assert/strict";
4
+ import * as fs from "node:fs";
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9
+ import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ type DependencyTreeResult = {
12
+ root: string;
13
+ nodes: number;
14
+ files: string[];
15
+ };
16
+
17
+ type TypeInfoResult = {
18
+ type: string | null;
19
+ documentation: string | null;
20
+ kind?: string;
21
+ source?: string;
22
+ };
23
+
24
+ type ModuleExportsResult = {
25
+ file: string;
26
+ exports: Array<{
27
+ symbol: string;
28
+ type: string | null;
29
+ }>;
30
+ count: number;
31
+ };
32
+
33
+ function normalize(file: string): string {
34
+ return file.replaceAll("\\", "/");
35
+ }
36
+
37
+ function sleep(ms: number): Promise<void> {
38
+ return new Promise((resolve) => setTimeout(resolve, ms));
39
+ }
40
+
41
+ async function waitFor(
42
+ description: string,
43
+ fn: () => Promise<void>,
44
+ timeoutMs = 5_000,
45
+ intervalMs = 50
46
+ ): Promise<void> {
47
+ const start = Date.now();
48
+ let lastError: unknown;
49
+
50
+ while (Date.now() - start < timeoutMs) {
51
+ try {
52
+ await fn();
53
+ return;
54
+ } catch (err) {
55
+ lastError = err;
56
+ await sleep(intervalMs);
57
+ }
58
+ }
59
+
60
+ throw new Error(
61
+ `${description} did not stabilize within ${timeoutMs}ms: ${String(lastError)}`
62
+ );
63
+ }
64
+
65
+ function writeFile(root: string, relativePath: string, content: string): void {
66
+ const absPath = path.join(root, relativePath);
67
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
68
+ fs.writeFileSync(absPath, content);
69
+ }
70
+
71
+ async function main(): Promise<void> {
72
+ const repoRoot = import.meta.dirname;
73
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-engine-sync-"));
74
+ const projectRoot = path.join(tempRoot, "project");
75
+
76
+ fs.mkdirSync(projectRoot, { recursive: true });
77
+ writeFile(
78
+ projectRoot,
79
+ "package.json",
80
+ JSON.stringify(
81
+ {
82
+ name: "typegraph-engine-sync-fixture",
83
+ private: true,
84
+ type: "module",
85
+ },
86
+ null,
87
+ 2
88
+ ) + "\n"
89
+ );
90
+ writeFile(
91
+ projectRoot,
92
+ "tsconfig.json",
93
+ JSON.stringify(
94
+ {
95
+ compilerOptions: {
96
+ target: "ES2022",
97
+ module: "ESNext",
98
+ moduleResolution: "Bundler",
99
+ strict: true,
100
+ },
101
+ include: ["src/**/*.ts"],
102
+ },
103
+ null,
104
+ 2
105
+ ) + "\n"
106
+ );
107
+
108
+ writeFile(projectRoot, "src/a.ts", 'export const current = "a" as const;\n');
109
+ writeFile(projectRoot, "src/b.ts", 'export const current = "b" as const;\n');
110
+ writeFile(
111
+ projectRoot,
112
+ "src/main.ts",
113
+ 'import { current } from "./a";\nexport const value = current;\n'
114
+ );
115
+ writeFile(projectRoot, "src/test.ts", "export const oldName = 1 as const;\n");
116
+ writeFile(projectRoot, "src/util.ts", "export const helper = 1 as const;\n");
117
+
118
+ fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
119
+ fs.symlinkSync(
120
+ path.join(repoRoot, "node_modules/typescript"),
121
+ path.join(projectRoot, "node_modules/typescript"),
122
+ "dir"
123
+ );
124
+
125
+ const client = new Client({ name: "engine-sync-test", version: "1.0.0" });
126
+ const transport = new StdioClientTransport({
127
+ command: path.join(repoRoot, "node_modules/.bin/tsx"),
128
+ args: [path.join(repoRoot, "server.ts")],
129
+ cwd: projectRoot,
130
+ env: {
131
+ TYPEGRAPH_PROJECT_ROOT: projectRoot,
132
+ TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
133
+ },
134
+ });
135
+
136
+ async function callTool<T>(name: string, args: Record<string, unknown>): Promise<T> {
137
+ const result = await client.request(
138
+ {
139
+ method: "tools/call",
140
+ params: {
141
+ name,
142
+ arguments: args,
143
+ },
144
+ },
145
+ CallToolResultSchema
146
+ );
147
+
148
+ const content = result.content[0];
149
+ assert.ok(content?.type === "text", `Expected text response from ${name}`);
150
+ return JSON.parse(content.text) as T;
151
+ }
152
+
153
+ try {
154
+ await client.connect(transport);
155
+
156
+ const initialType = await callTool<TypeInfoResult>("ts_type_info", {
157
+ file: "src/main.ts",
158
+ line: 2,
159
+ column: 14,
160
+ });
161
+ assert.match(initialType.type ?? "", /"a"/);
162
+
163
+ writeFile(projectRoot, "src/main.ts", 'import { current } from "./b";\nexport const value = current;\n');
164
+
165
+ await waitFor("import swap to synchronize graph and tsserver", async () => {
166
+ const deps = await callTool<DependencyTreeResult>("ts_dependency_tree", {
167
+ file: "src/main.ts",
168
+ });
169
+ const normalizedDeps = deps.files.map(normalize);
170
+ assert.ok(normalizedDeps.includes("src/b.ts"), `Expected src/b.ts in ${normalizedDeps}`);
171
+ assert.ok(!normalizedDeps.includes("src/a.ts"), `Expected src/a.ts to be removed from ${normalizedDeps}`);
172
+
173
+ const typeInfo = await callTool<TypeInfoResult>("ts_type_info", {
174
+ file: "src/main.ts",
175
+ line: 2,
176
+ column: 14,
177
+ });
178
+ assert.match(typeInfo.type ?? "", /"b"/);
179
+ });
180
+
181
+ // Open test.ts through ts_module_exports, then rename the export on disk.
182
+ const initialExports = await callTool<ModuleExportsResult>("ts_module_exports", {
183
+ file: "src/test.ts",
184
+ });
185
+ assert.ok(initialExports.exports.some((item) => item.symbol === "oldName"));
186
+
187
+ writeFile(projectRoot, "src/test.ts", "export const newName = 1 as const;\n");
188
+
189
+ await waitFor("symbol rename to refresh mixed export metadata", async () => {
190
+ const exportsResult = await callTool<ModuleExportsResult>("ts_module_exports", {
191
+ file: "src/test.ts",
192
+ });
193
+ const next = exportsResult.exports.find((item) => item.symbol === "newName");
194
+ assert.ok(next, `Expected newName in ${JSON.stringify(exportsResult.exports)}`);
195
+ assert.match(next.type ?? "", /\bnewName\b/);
196
+ assert.ok(!exportsResult.exports.some((item) => item.symbol === "oldName"));
197
+ });
198
+
199
+ // Open util.ts directly so tsserver tracks it, then delete it from disk.
200
+ const utilInfo = await callTool<TypeInfoResult>("ts_type_info", {
201
+ file: "src/util.ts",
202
+ line: 1,
203
+ column: 14,
204
+ });
205
+ assert.match(utilInfo.type ?? "", /\bhelper: 1\b/);
206
+ fs.rmSync(path.join(projectRoot, "src/util.ts"));
207
+
208
+ await waitFor("deleted file to disappear from semantic answers", async () => {
209
+ const deletedInfo = await callTool<TypeInfoResult>("ts_type_info", {
210
+ file: "src/util.ts",
211
+ line: 1,
212
+ column: 14,
213
+ });
214
+ assert.equal(
215
+ deletedInfo.type,
216
+ null,
217
+ `Expected deleted file to have no type info, got ${JSON.stringify(deletedInfo)}`
218
+ );
219
+ });
220
+
221
+ console.log("");
222
+ console.log("typegraph-mcp Engine Sync Test");
223
+ console.log("==============================");
224
+ console.log(" ✓ import swaps keep dependency_tree and type_info aligned");
225
+ console.log(" ✓ export renames refresh ts_module_exports semantic metadata");
226
+ console.log(" ✓ deleted open files do not survive as tsserver ghost snapshots");
227
+ } finally {
228
+ await transport.close().catch(() => {});
229
+ fs.rmSync(tempRoot, { recursive: true, force: true });
230
+ }
231
+ }
232
+
233
+ main().catch((err) => {
234
+ console.error(err);
235
+ process.exit(1);
236
+ });
@@ -22,13 +22,14 @@ function copyDir(src: string, dest: string): void {
22
22
  function runTsx(
23
23
  toolRoot: string,
24
24
  args: string[],
25
- cwd: string
25
+ cwd: string,
26
+ env: NodeJS.ProcessEnv = process.env
26
27
  ): string {
27
28
  return execFileSync(path.join(toolRoot, "node_modules/.bin/tsx"), args, {
28
29
  cwd,
29
30
  encoding: "utf-8",
30
31
  maxBuffer: 10 * 1024 * 1024,
31
- env: process.env,
32
+ env,
32
33
  });
33
34
  }
34
35
 
@@ -44,6 +45,7 @@ async function main(): Promise<void> {
44
45
  const fixtureRoot = path.join(repoRoot, ".fixtures/install-oxlint");
45
46
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-install-oxlint-"));
46
47
  const projectRoot = path.join(tempRoot, "project");
48
+ const homeRoot = path.join(tempRoot, "home");
47
49
 
48
50
  copyDir(fixtureRoot, projectRoot);
49
51
  fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
@@ -54,7 +56,14 @@ async function main(): Promise<void> {
54
56
  );
55
57
 
56
58
  try {
57
- const setupOutput = runTsx(repoRoot, [path.join(repoRoot, "cli.ts"), "setup", "--yes"], projectRoot);
59
+ fs.mkdirSync(homeRoot, { recursive: true });
60
+ const testEnv = { ...process.env, HOME: homeRoot };
61
+ const setupOutput = runTsx(
62
+ repoRoot,
63
+ [path.join(repoRoot, "cli.ts"), "setup", "--yes"],
64
+ projectRoot,
65
+ testEnv
66
+ );
58
67
  const pluginRoot = path.join(projectRoot, "plugins/typegraph-mcp");
59
68
 
60
69
  const tsconfig = fs.readFileSync(path.join(projectRoot, "tsconfig.json"), "utf-8");
@@ -70,7 +79,12 @@ async function main(): Promise<void> {
70
79
  assertIncludes(setupOutput, 'Added "plugins/**" to .oxlintrc.json ignorePatterns');
71
80
  assertIncludes(setupOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
72
81
 
73
- const checkOutput = runTsx(pluginRoot, [path.join(pluginRoot, "cli.ts"), "check"], projectRoot);
82
+ const checkOutput = runTsx(
83
+ pluginRoot,
84
+ [path.join(pluginRoot, "cli.ts"), "check"],
85
+ projectRoot,
86
+ testEnv
87
+ );
74
88
  assertIncludes(checkOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
75
89
  assert.ok(
76
90
  !checkOutput.includes("Lint config check (no ESLint or Oxlint config found)"),
package/module-graph.ts CHANGED
@@ -460,7 +460,11 @@ export function removeFile(graph: ModuleGraph, filePath: string): void {
460
460
  export function startWatcher(
461
461
  projectRoot: string,
462
462
  graph: ModuleGraph,
463
- resolver: ResolverFactory
463
+ resolver: ResolverFactory,
464
+ hooks?: {
465
+ onFileUpdated?: (filePath: string) => void | Promise<void>;
466
+ onFileDeleted?: (filePath: string) => void | Promise<void>;
467
+ }
464
468
  ): void {
465
469
  try {
466
470
  const watcher = fs.watch(
@@ -489,9 +493,11 @@ export function startWatcher(
489
493
  if (fs.existsSync(absPath)) {
490
494
  // File created or modified
491
495
  updateFile(graph, absPath, resolver, projectRoot);
496
+ void hooks?.onFileUpdated?.(absPath);
492
497
  } else {
493
498
  // File deleted
494
499
  removeFile(graph, absPath);
500
+ void hooks?.onFileDeleted?.(absPath);
495
501
  }
496
502
  }
497
503
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegraph-mcp",
3
- "version": "0.9.42",
3
+ "version": "0.9.44",
4
4
  "description": "Type-aware codebase navigation for AI coding agents — 14 MCP tools powered by tsserver + oxc",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "scripts": {
35
35
  "build": "tsup",
36
36
  "start": "tsx server.ts",
37
- "test": "tsx smoke-test.ts && tsx export-surface-test.ts && tsx install-oxlint-test.ts",
37
+ "test": "tsx smoke-test.ts && tsx export-surface-test.ts && tsx engine-sync-test.ts && tsx install-oxlint-test.ts",
38
38
  "check": "tsx check.ts"
39
39
  },
40
40
  "dependencies": {
package/server.ts CHANGED
@@ -1132,7 +1132,15 @@ async function main() {
1132
1132
 
1133
1133
  moduleGraph = graphResult.graph;
1134
1134
  moduleResolver = graphResult.resolver;
1135
- startWatcher(projectRoot, moduleGraph, graphResult.resolver);
1135
+ startWatcher(projectRoot, moduleGraph, graphResult.resolver, {
1136
+ onFileUpdated: (filePath) =>
1137
+ client.reloadOpenFile(filePath).catch((err) => {
1138
+ log(`Failed to reload open file ${relPath(filePath)}:`, err);
1139
+ }),
1140
+ onFileDeleted: (filePath) => {
1141
+ client.closeFile(filePath);
1142
+ },
1143
+ });
1136
1144
 
1137
1145
  const transport = new StdioServerTransport();
1138
1146
  await mcpServer.connect(transport);
@@ -323,6 +323,23 @@ export class TsServerClient {
323
323
  await new Promise((r) => setTimeout(r, 50));
324
324
  }
325
325
 
326
+ async reloadOpenFile(file: string): Promise<boolean> {
327
+ const absPath = this.resolvePath(file);
328
+ if (!this.openFiles.has(absPath)) return false;
329
+ await this.sendRequest("reload", {
330
+ file: absPath,
331
+ tmpfile: absPath,
332
+ });
333
+ return true;
334
+ }
335
+
336
+ closeFile(file: string): boolean {
337
+ const absPath = this.resolvePath(file);
338
+ if (!this.openFiles.delete(absPath)) return false;
339
+ this.sendNotification("close", { file: absPath });
340
+ return true;
341
+ }
342
+
326
343
  // ─── Public API ────────────────────────────────────────────────────────
327
344
 
328
345
  async definition(file: string, line: number, offset: number): Promise<DefinitionResult[]> {