typegraph-mcp 0.9.42 → 0.9.43
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/benchmark.js +15 -0
- package/dist/check.js +3 -1
- package/dist/cli.js +26 -2
- package/dist/module-graph.js +3 -1
- package/dist/server.js +26 -2
- package/dist/smoke-test.js +15 -0
- package/dist/tsserver-client.js +15 -0
- package/engine-sync-test.ts +236 -0
- package/module-graph.ts +7 -1
- package/package.json +2 -2
- package/server.ts +9 -1
- package/tsserver-client.ts +17 -0
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
|
);
|
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
|
);
|
|
@@ -1087,6 +1089,21 @@ var init_tsserver_client = __esm({
|
|
|
1087
1089
|
this.sendNotification("open", { file: absPath2 });
|
|
1088
1090
|
await new Promise((r) => setTimeout(r, 50));
|
|
1089
1091
|
}
|
|
1092
|
+
async reloadOpenFile(file) {
|
|
1093
|
+
const absPath2 = this.resolvePath(file);
|
|
1094
|
+
if (!this.openFiles.has(absPath2)) return false;
|
|
1095
|
+
await this.sendRequest("reload", {
|
|
1096
|
+
file: absPath2,
|
|
1097
|
+
tmpfile: absPath2
|
|
1098
|
+
});
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1101
|
+
closeFile(file) {
|
|
1102
|
+
const absPath2 = this.resolvePath(file);
|
|
1103
|
+
if (!this.openFiles.delete(absPath2)) return false;
|
|
1104
|
+
this.sendNotification("close", { file: absPath2 });
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1090
1107
|
// ─── Public API ────────────────────────────────────────────────────────
|
|
1091
1108
|
async definition(file, line, offset) {
|
|
1092
1109
|
const absPath2 = this.resolvePath(file);
|
|
@@ -2638,7 +2655,14 @@ async function main4() {
|
|
|
2638
2655
|
]);
|
|
2639
2656
|
moduleGraph = graphResult.graph;
|
|
2640
2657
|
moduleResolver = graphResult.resolver;
|
|
2641
|
-
startWatcher(projectRoot2, moduleGraph, graphResult.resolver
|
|
2658
|
+
startWatcher(projectRoot2, moduleGraph, graphResult.resolver, {
|
|
2659
|
+
onFileUpdated: (filePath) => client.reloadOpenFile(filePath).catch((err) => {
|
|
2660
|
+
log3(`Failed to reload open file ${relPath2(filePath)}:`, err);
|
|
2661
|
+
}),
|
|
2662
|
+
onFileDeleted: (filePath) => {
|
|
2663
|
+
client.closeFile(filePath);
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2642
2666
|
const transport = new StdioServerTransport();
|
|
2643
2667
|
await mcpServer.connect(transport);
|
|
2644
2668
|
log3("MCP server connected and ready");
|
package/dist/module-graph.js
CHANGED
|
@@ -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");
|
package/dist/smoke-test.js
CHANGED
|
@@ -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);
|
package/dist/tsserver-client.js
CHANGED
|
@@ -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
|
+
});
|
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.
|
|
3
|
+
"version": "0.9.43",
|
|
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);
|
package/tsserver-client.ts
CHANGED
|
@@ -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[]> {
|