ast-mcp-server 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +47 -15
  3. package/SECURITY.md +31 -0
  4. package/dist/cli.js +0 -0
  5. package/dist/index.js +129 -10
  6. package/dist/server.js +21 -2
  7. package/dist/services/agent-setup.js +36 -117
  8. package/dist/services/agent-targets.js +122 -0
  9. package/dist/services/context-builder.js +52 -24
  10. package/dist/services/diagnostics.js +10 -5
  11. package/dist/services/file-fingerprints.js +104 -0
  12. package/dist/services/file-snapshot.js +8 -1
  13. package/dist/services/impact.js +334 -0
  14. package/dist/services/operations.js +343 -254
  15. package/dist/services/outline.js +94 -21
  16. package/dist/services/pagination.js +10 -4
  17. package/dist/services/project-operation-scheduler.js +475 -0
  18. package/dist/services/project-status.js +134 -52
  19. package/dist/services/project-watcher.js +230 -0
  20. package/dist/services/project.js +646 -66
  21. package/dist/services/public-errors.js +255 -0
  22. package/dist/services/references.js +7 -1
  23. package/dist/services/relationships.js +1198 -0
  24. package/dist/services/request-context.js +42 -0
  25. package/dist/services/runtime-activity.js +65 -0
  26. package/dist/services/runtime-logger.js +50 -0
  27. package/dist/services/runtime-policy.js +47 -0
  28. package/dist/services/setup-wizard.js +4 -7
  29. package/dist/services/shutdown.js +93 -0
  30. package/dist/services/symbol-index-limits.js +5 -0
  31. package/dist/services/symbol-index-order.js +13 -0
  32. package/dist/services/symbol-index-policy.js +91 -0
  33. package/dist/services/symbol-index-sqlite.js +850 -0
  34. package/dist/services/symbol-index.js +447 -0
  35. package/dist/services/symbols.js +403 -79
  36. package/dist/services/test-candidates.js +209 -0
  37. package/dist/services/workspace.js +18 -7
  38. package/dist/tools/apply_operation.js +8 -4
  39. package/dist/tools/explore.js +8 -6
  40. package/dist/tools/find_references.js +12 -13
  41. package/dist/tools/get_diagnostics.js +10 -7
  42. package/dist/tools/get_file.js +23 -11
  43. package/dist/tools/get_impact.js +134 -0
  44. package/dist/tools/get_operation_preview.js +7 -4
  45. package/dist/tools/get_outline.js +8 -6
  46. package/dist/tools/get_project_status.js +77 -8
  47. package/dist/tools/get_symbol_source.js +8 -6
  48. package/dist/tools/list_files.js +11 -6
  49. package/dist/tools/rename_symbol.js +6 -4
  50. package/dist/tools/replace_symbol_body.js +6 -4
  51. package/dist/tools/result.js +27 -17
  52. package/dist/tools/scaffold_class.js +6 -4
  53. package/dist/tools/search_symbols.js +15 -14
  54. package/docs/support.md +80 -0
  55. package/package.json +16 -5
  56. package/skills/structural-code-editing/SKILL.md +37 -10
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { Node, } from "ts-morph";
3
+ import { NO_REQUEST_CONTEXT } from "./request-context.js";
3
4
  function joinPresent(parts) {
4
5
  return parts.filter((part) => Boolean(part)).join(" ");
5
6
  }
@@ -80,20 +81,34 @@ function symbolOf(node, symbolPath, name, signature) {
80
81
  endLine: node.getEndLineNumber(),
81
82
  };
82
83
  }
83
- function formatClass(statement, prefix = "") {
84
+ function classDeclarationSignature(statement) {
84
85
  const name = statement.getName() ?? "<anonymous>";
85
- const symbolPath = prefix ? `${prefix}.${name}` : name;
86
86
  const heritage = statement.getExtends()?.getText();
87
87
  const implementations = statement.getImplements().map((item) => item.getText());
88
- const declaration = joinPresent([
88
+ return joinPresent([
89
89
  modifiersOf(statement),
90
90
  `class ${name}${typeParametersOf(statement)}`,
91
91
  heritage ? `extends ${heritage}` : undefined,
92
92
  implementations.length > 0 ? `implements ${implementations.join(", ")}` : undefined,
93
93
  ]);
94
+ }
95
+ function interfaceDeclarationSignature(statement) {
96
+ const name = statement.getName();
97
+ const extensions = statement.getExtends().map((item) => item.getText());
98
+ return joinPresent([
99
+ modifiersOf(statement),
100
+ `interface ${name}${typeParametersOf(statement)}`,
101
+ extensions.length > 0 ? `extends ${extensions.join(", ")}` : undefined,
102
+ ]);
103
+ }
104
+ function formatClass(statement, prefix = "", requestContext = NO_REQUEST_CONTEXT) {
105
+ const name = statement.getName() ?? "<anonymous>";
106
+ const symbolPath = prefix ? `${prefix}.${name}` : name;
107
+ const declaration = classDeclarationSignature(statement);
94
108
  const lines = [`${declaration} {`];
95
109
  const symbols = [symbolOf(statement, symbolPath, name, declaration)];
96
110
  for (const member of statement.getMembers()) {
111
+ requestContext.checkpoint();
97
112
  const signature = memberSignature(member);
98
113
  if (!signature)
99
114
  continue;
@@ -104,18 +119,14 @@ function formatClass(statement, prefix = "") {
104
119
  lines.push("}");
105
120
  return { lines, symbols };
106
121
  }
107
- function formatInterface(statement, prefix = "") {
122
+ function formatInterface(statement, prefix = "", requestContext = NO_REQUEST_CONTEXT) {
108
123
  const name = statement.getName();
109
124
  const symbolPath = prefix ? `${prefix}.${name}` : name;
110
- const extensions = statement.getExtends().map((item) => item.getText());
111
- const declaration = joinPresent([
112
- modifiersOf(statement),
113
- `interface ${name}${typeParametersOf(statement)}`,
114
- extensions.length > 0 ? `extends ${extensions.join(", ")}` : undefined,
115
- ]);
125
+ const declaration = interfaceDeclarationSignature(statement);
116
126
  const lines = [`${declaration} {`];
117
127
  const symbols = [symbolOf(statement, symbolPath, name, declaration)];
118
128
  for (const member of statement.getMembers()) {
129
+ requestContext.checkpoint();
119
130
  const signature = member.getText().trim();
120
131
  lines.push(` ${signature}`);
121
132
  const memberName = "getName" in member ? member.getName() : member.getKindName();
@@ -124,12 +135,13 @@ function formatInterface(statement, prefix = "") {
124
135
  lines.push("}");
125
136
  return { lines, symbols };
126
137
  }
127
- function formatVariable(statement) {
138
+ function formatVariable(statement, requestContext = NO_REQUEST_CONTEXT) {
128
139
  const lines = [];
129
140
  const symbols = [];
130
141
  const keyword = statement.getDeclarationKind();
131
142
  const modifiers = modifiersOf(statement);
132
143
  for (const declaration of statement.getDeclarations()) {
144
+ requestContext.checkpoint();
133
145
  const name = declaration.getName();
134
146
  const explicitType = declaration.getTypeNode()?.getText();
135
147
  const initializer = declaration.getInitializer();
@@ -155,11 +167,13 @@ function moduleStatements(module) {
155
167
  return [body];
156
168
  return [];
157
169
  }
158
- function formatStatement(statement, prefix = "") {
170
+ function formatStatement(statement, prefix = "", requestContext = NO_REQUEST_CONTEXT) {
171
+ requestContext.checkpoint();
159
172
  if (Node.isClassDeclaration(statement))
160
- return formatClass(statement, prefix);
161
- if (Node.isInterfaceDeclaration(statement))
162
- return formatInterface(statement, prefix);
173
+ return formatClass(statement, prefix, requestContext);
174
+ if (Node.isInterfaceDeclaration(statement)) {
175
+ return formatInterface(statement, prefix, requestContext);
176
+ }
163
177
  if (Node.isFunctionDeclaration(statement)) {
164
178
  const name = statement.getName() ?? "<anonymous>";
165
179
  const symbolPath = prefix ? `${prefix}.${name}` : name;
@@ -180,13 +194,16 @@ function formatStatement(statement, prefix = "") {
180
194
  const symbolPath = prefix ? `${prefix}.${name}` : name;
181
195
  const members = statement
182
196
  .getMembers()
183
- .map((member) => member.getName())
197
+ .map((member) => {
198
+ requestContext.checkpoint();
199
+ return member.getName();
200
+ })
184
201
  .join(", ");
185
202
  const signature = joinPresent([modifiersOf(statement), `enum ${name} { ${members} }`]);
186
203
  return { lines: [signature], symbols: [symbolOf(statement, symbolPath, name, signature)] };
187
204
  }
188
205
  if (Node.isVariableStatement(statement))
189
- return formatVariable(statement);
206
+ return formatVariable(statement, requestContext);
190
207
  if (Node.isModuleDeclaration(statement)) {
191
208
  const name = statement.getName();
192
209
  const symbolPath = prefix ? `${prefix}.${name}` : name;
@@ -194,7 +211,8 @@ function formatStatement(statement, prefix = "") {
194
211
  const lines = [`${declaration} {`];
195
212
  const symbols = [symbolOf(statement, symbolPath, name, declaration)];
196
213
  for (const child of moduleStatements(statement)) {
197
- const formatted = formatStatement(child, symbolPath);
214
+ requestContext.checkpoint();
215
+ const formatted = formatStatement(child, symbolPath, requestContext);
198
216
  if (!formatted)
199
217
  continue;
200
218
  lines.push(...formatted.lines.map((line) => ` ${line}`));
@@ -205,11 +223,65 @@ function formatStatement(statement, prefix = "") {
205
223
  }
206
224
  return undefined;
207
225
  }
208
- export function buildFileOutline(sourceFile) {
226
+ export function declarationSignature(node, requestContext = NO_REQUEST_CONTEXT) {
227
+ requestContext.checkpoint();
228
+ if (Node.isFunctionDeclaration(node))
229
+ return functionSignature(node);
230
+ if (Node.isMethodDeclaration(node))
231
+ return methodSignature(node);
232
+ if (Node.isConstructorDeclaration(node) ||
233
+ Node.isGetAccessorDeclaration(node) ||
234
+ Node.isSetAccessorDeclaration(node) ||
235
+ Node.isPropertyDeclaration(node)) {
236
+ return memberSignature(node) ?? node.getText().trim();
237
+ }
238
+ if (Node.isMethodSignature(node) || Node.isPropertySignature(node)) {
239
+ return node.getText().trim();
240
+ }
241
+ if (Node.isClassDeclaration(node))
242
+ return classDeclarationSignature(node);
243
+ if (Node.isInterfaceDeclaration(node))
244
+ return interfaceDeclarationSignature(node);
245
+ if (Node.isTypeAliasDeclaration(node)) {
246
+ return joinPresent([
247
+ modifiersOf(node),
248
+ `type ${node.getName()}${typeParametersOf(node)} = ${node.getTypeNode()?.getText() ?? "unknown"};`,
249
+ ]);
250
+ }
251
+ if (Node.isEnumDeclaration(node)) {
252
+ const members = node.getMembers().map((member) => {
253
+ requestContext.checkpoint();
254
+ return member.getName();
255
+ });
256
+ return joinPresent([modifiersOf(node), `enum ${node.getName()} { ${members.join(", ")} }`]);
257
+ }
258
+ if (Node.isModuleDeclaration(node)) {
259
+ return joinPresent([modifiersOf(node), `namespace ${node.getName()}`]);
260
+ }
261
+ if (Node.isVariableDeclaration(node)) {
262
+ let statement = node.getParent();
263
+ while (statement && !Node.isVariableStatement(statement))
264
+ statement = statement.getParent();
265
+ if (statement && Node.isVariableStatement(statement)) {
266
+ const explicitType = node.getTypeNode()?.getText();
267
+ const initializer = node.getInitializer();
268
+ const callableType = initializer && (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))
269
+ ? callableExpressionSignature(initializer)
270
+ : undefined;
271
+ return joinPresent([
272
+ modifiersOf(statement),
273
+ `${statement.getDeclarationKind()} ${node.getName()}${explicitType || callableType ? `: ${explicitType ?? callableType}` : ""};`,
274
+ ]);
275
+ }
276
+ }
277
+ return node.getText().trim();
278
+ }
279
+ export function buildFileOutline(sourceFile, requestContext = NO_REQUEST_CONTEXT) {
209
280
  const lines = [];
210
281
  const symbols = [];
211
282
  for (const statement of sourceFile.getStatements()) {
212
- const formatted = formatStatement(statement);
283
+ requestContext.checkpoint();
284
+ const formatted = formatStatement(statement, "", requestContext);
213
285
  if (!formatted)
214
286
  continue;
215
287
  lines.push(...formatted.lines);
@@ -220,7 +292,8 @@ export function buildFileOutline(sourceFile) {
220
292
  export function fileOutline(sourceFile) {
221
293
  return buildFileOutline(sourceFile).text;
222
294
  }
223
- export function nodeSourceWithLocation(node, projectRoot) {
295
+ export function nodeSourceWithLocation(node, projectRoot, requestContext = NO_REQUEST_CONTEXT) {
296
+ requestContext.checkpoint();
224
297
  const sourceFile = node.getSourceFile();
225
298
  return {
226
299
  file: projectRoot
@@ -2,11 +2,17 @@ import { z } from "zod";
2
2
  export const DEFAULT_PAGE_LIMIT = 100;
3
3
  export const MAX_PAGE_LIMIT = 500;
4
4
  export function createPaginationInputSchema(defaultLimit = DEFAULT_PAGE_LIMIT) {
5
- if (!Number.isInteger(defaultLimit) || defaultLimit < 1 || defaultLimit > MAX_PAGE_LIMIT) {
5
+ if (!Number.isSafeInteger(defaultLimit) || defaultLimit < 1 || defaultLimit > MAX_PAGE_LIMIT) {
6
6
  throw new Error(`Default page limit must be an integer from 1 to ${MAX_PAGE_LIMIT}.`);
7
7
  }
8
8
  return {
9
- offset: z.number().int().min(0).default(0).describe("Zero-based result offset."),
9
+ offset: z
10
+ .number()
11
+ .int()
12
+ .min(0)
13
+ .max(Number.MAX_SAFE_INTEGER)
14
+ .default(0)
15
+ .describe("Zero-based result offset."),
10
16
  limit: z
11
17
  .number()
12
18
  .int()
@@ -18,11 +24,11 @@ export function createPaginationInputSchema(defaultLimit = DEFAULT_PAGE_LIMIT) {
18
24
  }
19
25
  export const PaginationInputSchema = createPaginationInputSchema();
20
26
  export const PaginationOutputSchema = {
21
- offset: z.number().int().min(0),
27
+ offset: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
22
28
  limit: z.number().int().min(1),
23
29
  total: z.number().int().min(0),
24
30
  has_more: z.boolean(),
25
- next_offset: z.number().int().min(0).nullable(),
31
+ next_offset: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
26
32
  };
27
33
  export function paginate(items, offset, limit) {
28
34
  const pageItems = items.slice(offset, offset + limit);