ast-mcp-server 0.6.0 → 0.7.0

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 +27 -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
@@ -0,0 +1,475 @@
1
+ export const MAX_PROJECT_OPERATION_COUNTER = 2_147_483_647;
2
+ export const MAX_PROJECT_OPERATION_DURATION_MS = 86_400_000;
3
+ const ERROR_MESSAGES = Object.freeze({
4
+ PROJECT_QUEUE_FULL: "Project operation queue is full.",
5
+ QUEUE_WAIT_TIMEOUT: "Project operation queue wait timed out.",
6
+ REQUEST_CANCELLED: "Project operation was cancelled.",
7
+ OPERATION_DEADLINE_EXCEEDED: "Project operation deadline exceeded.",
8
+ SERVER_SHUTTING_DOWN: "Server is shutting down.",
9
+ });
10
+ export class ProjectOperationSchedulerError extends Error {
11
+ code;
12
+ constructor(code) {
13
+ super(ERROR_MESSAGES[code]);
14
+ this.code = code;
15
+ this.name = "ProjectOperationSchedulerError";
16
+ }
17
+ }
18
+ export function addProjectOperationCount(current, increment = 1) {
19
+ if (!Number.isSafeInteger(current) || current < 0)
20
+ return MAX_PROJECT_OPERATION_COUNTER;
21
+ if (!Number.isSafeInteger(increment) || increment < 0)
22
+ return MAX_PROJECT_OPERATION_COUNTER;
23
+ return Math.min(MAX_PROJECT_OPERATION_COUNTER, current + increment);
24
+ }
25
+ function boundedDuration(value) {
26
+ if (!Number.isFinite(value) || value < 0)
27
+ return 0;
28
+ return Math.min(MAX_PROJECT_OPERATION_DURATION_MS, Math.floor(value));
29
+ }
30
+ function assertIntegerRange(name, value, minimum, maximum) {
31
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
32
+ throw new RangeError(`${name} must be an integer in ${minimum}..${maximum}.`);
33
+ }
34
+ }
35
+ export class ProjectOperationScheduler {
36
+ #queueCapacity;
37
+ #queueWaitTimeoutMs;
38
+ #operationDeadlineMs;
39
+ #now;
40
+ #admission = "open";
41
+ #head;
42
+ #tail;
43
+ #running;
44
+ #waiting = 0;
45
+ #idleWaiters = new Set();
46
+ #completionCriticalWaiters = new Set();
47
+ #nextSequence = 0;
48
+ #queueAbortListeners = 0;
49
+ #queueTimers = 0;
50
+ #executionAbortListeners = 0;
51
+ #executionTimers = 0;
52
+ #rejectedOperations = 0;
53
+ #cancelledOperations = 0;
54
+ #queueTimeoutOperations = 0;
55
+ #deadlineExceededOperations = 0;
56
+ #lastOutcome = "none";
57
+ #maxQueueWaitMs = 0;
58
+ #maxExecutionMs = 0;
59
+ constructor(options) {
60
+ assertIntegerRange("queueCapacity", options.queueCapacity, 1, 256);
61
+ assertIntegerRange("queueWaitTimeoutMs", options.queueWaitTimeoutMs, 100, 300_000);
62
+ assertIntegerRange("operationDeadlineMs", options.operationDeadlineMs, 1_000, 900_000);
63
+ this.#queueCapacity = options.queueCapacity;
64
+ this.#queueWaitTimeoutMs = options.queueWaitTimeoutMs;
65
+ this.#operationDeadlineMs = options.operationDeadlineMs;
66
+ this.#now = options.now ?? (() => performance.now());
67
+ }
68
+ run(operation, options = {}) {
69
+ if (this.#admission === "closed") {
70
+ return this.#rejectAdmission("SERVER_SHUTTING_DOWN");
71
+ }
72
+ if (this.#waiting >= this.#queueCapacity) {
73
+ return this.#rejectAdmission("PROJECT_QUEUE_FULL");
74
+ }
75
+ const clientSignal = options.signal ?? new AbortController().signal;
76
+ const sequence = this.#nextOperationSequence();
77
+ let node;
78
+ const result = new Promise((resolve, reject) => {
79
+ node = {
80
+ sequence,
81
+ clientSignal,
82
+ enqueuedAt: this.#readNow(),
83
+ previous: undefined,
84
+ next: undefined,
85
+ phase: "queued",
86
+ operation: operation,
87
+ resolve: (value) => resolve(value),
88
+ reject,
89
+ queueAbortListener: undefined,
90
+ queueTimer: undefined,
91
+ executionAbortListener: undefined,
92
+ executionTimer: undefined,
93
+ executionController: undefined,
94
+ executionStartedAt: undefined,
95
+ abortKind: undefined,
96
+ };
97
+ });
98
+ this.#append(node);
99
+ this.#installQueueResources(node);
100
+ if (clientSignal.aborted) {
101
+ this.#rejectQueued(node, "REQUEST_CANCELLED");
102
+ }
103
+ this.#startNext();
104
+ return result;
105
+ }
106
+ closeAdmission() {
107
+ this.#admission = "closed";
108
+ }
109
+ beginShutdown() {
110
+ this.#admission = "closed";
111
+ let queued = this.#head;
112
+ while (queued) {
113
+ const next = queued.next;
114
+ this.#rejectQueued(queued, "SERVER_SHUTTING_DOWN");
115
+ queued = next;
116
+ }
117
+ const running = this.#running;
118
+ if (running && running.phase !== "completion_critical" && running.phase !== "complete") {
119
+ running.abortKind ??= "shutdown";
120
+ this.#clearExecutionResources(running);
121
+ running.executionController?.abort();
122
+ }
123
+ this.#notifyIdleIfNeeded();
124
+ }
125
+ waitForIdle() {
126
+ if (!this.#running && this.#waiting === 0)
127
+ return Promise.resolve();
128
+ return new Promise((resolve) => {
129
+ this.#idleWaiters.add(resolve);
130
+ });
131
+ }
132
+ waitForCompletionCriticalOperationsToDrain() {
133
+ if (this.#running?.phase !== "completion_critical")
134
+ return Promise.resolve();
135
+ return new Promise((resolve) => {
136
+ this.#completionCriticalWaiters.add(resolve);
137
+ });
138
+ }
139
+ shutdownSnapshot() {
140
+ return Object.freeze({
141
+ active_operations: this.#running ? 1 : 0,
142
+ queued_operations: this.#waiting,
143
+ completion_critical_operations: this.#running?.phase === "completion_critical" ? 1 : 0,
144
+ });
145
+ }
146
+ snapshot() {
147
+ return Object.freeze({
148
+ admission: this.#admission,
149
+ queue_capacity: this.#queueCapacity,
150
+ active_operations: this.#running ? 1 : 0,
151
+ queued_operations: this.#waiting,
152
+ retained_queue_nodes: this.#waiting,
153
+ queue_abort_listeners: this.#queueAbortListeners,
154
+ queue_timers: this.#queueTimers,
155
+ execution_abort_listeners: this.#executionAbortListeners,
156
+ execution_timers: this.#executionTimers,
157
+ rejected_operations: this.#rejectedOperations,
158
+ cancelled_operations: this.#cancelledOperations,
159
+ queue_timeout_operations: this.#queueTimeoutOperations,
160
+ deadline_exceeded_operations: this.#deadlineExceededOperations,
161
+ last_outcome: this.#lastOutcome,
162
+ max_queue_wait_ms: this.#maxQueueWaitMs,
163
+ max_execution_ms: this.#maxExecutionMs,
164
+ });
165
+ }
166
+ #readNow() {
167
+ const value = this.#now();
168
+ return Number.isFinite(value) ? value : 0;
169
+ }
170
+ #nextOperationSequence() {
171
+ if (this.#nextSequence >= Number.MAX_SAFE_INTEGER) {
172
+ throw new ProjectOperationSchedulerError("PROJECT_QUEUE_FULL");
173
+ }
174
+ this.#nextSequence += 1;
175
+ return this.#nextSequence;
176
+ }
177
+ #rejectAdmission(code) {
178
+ this.#rejectedOperations = addProjectOperationCount(this.#rejectedOperations);
179
+ this.#lastOutcome = "rejected";
180
+ return Promise.reject(new ProjectOperationSchedulerError(code));
181
+ }
182
+ #append(node) {
183
+ node.previous = this.#tail;
184
+ if (this.#tail)
185
+ this.#tail.next = node;
186
+ else
187
+ this.#head = node;
188
+ this.#tail = node;
189
+ this.#waiting += 1;
190
+ }
191
+ #unlink(node) {
192
+ if (node.previous)
193
+ node.previous.next = node.next;
194
+ else
195
+ this.#head = node.next;
196
+ if (node.next)
197
+ node.next.previous = node.previous;
198
+ else
199
+ this.#tail = node.previous;
200
+ node.previous = undefined;
201
+ node.next = undefined;
202
+ this.#waiting -= 1;
203
+ }
204
+ #installQueueResources(node) {
205
+ const abortListener = () => {
206
+ this.#rejectQueued(node, "REQUEST_CANCELLED");
207
+ this.#startNext();
208
+ };
209
+ node.queueAbortListener = abortListener;
210
+ node.clientSignal.addEventListener("abort", abortListener, { once: true });
211
+ this.#queueAbortListeners += 1;
212
+ node.queueTimer = setTimeout(() => {
213
+ this.#rejectQueued(node, "QUEUE_WAIT_TIMEOUT");
214
+ this.#startNext();
215
+ }, this.#queueWaitTimeoutMs);
216
+ this.#queueTimers += 1;
217
+ }
218
+ #clearQueueResources(node) {
219
+ if (node.queueAbortListener) {
220
+ node.clientSignal.removeEventListener("abort", node.queueAbortListener);
221
+ node.queueAbortListener = undefined;
222
+ this.#queueAbortListeners -= 1;
223
+ }
224
+ if (node.queueTimer) {
225
+ clearTimeout(node.queueTimer);
226
+ node.queueTimer = undefined;
227
+ this.#queueTimers -= 1;
228
+ }
229
+ }
230
+ #rejectQueued(node, code) {
231
+ if (node.phase !== "queued")
232
+ return;
233
+ const reject = node.reject;
234
+ this.#recordQueueWait(node);
235
+ this.#unlink(node);
236
+ this.#clearQueueResources(node);
237
+ node.phase = "complete";
238
+ node.operation = undefined;
239
+ node.resolve = undefined;
240
+ node.reject = undefined;
241
+ if (code === "REQUEST_CANCELLED" || code === "SERVER_SHUTTING_DOWN") {
242
+ this.#cancelledOperations = addProjectOperationCount(this.#cancelledOperations);
243
+ this.#lastOutcome = "cancelled";
244
+ }
245
+ else {
246
+ this.#queueTimeoutOperations = addProjectOperationCount(this.#queueTimeoutOperations);
247
+ this.#lastOutcome = "queue_timeout";
248
+ }
249
+ reject?.(new ProjectOperationSchedulerError(code));
250
+ this.#notifyIdleIfNeeded();
251
+ }
252
+ #recordQueueWait(node) {
253
+ const duration = boundedDuration(this.#readNow() - node.enqueuedAt);
254
+ this.#maxQueueWaitMs = Math.max(this.#maxQueueWaitMs, duration);
255
+ }
256
+ #startNext() {
257
+ if (this.#running)
258
+ return;
259
+ let node = this.#head;
260
+ while (node && this.#readNow() - node.enqueuedAt >= this.#queueWaitTimeoutMs) {
261
+ this.#rejectQueued(node, "QUEUE_WAIT_TIMEOUT");
262
+ node = this.#head;
263
+ }
264
+ if (!node)
265
+ return;
266
+ this.#recordQueueWait(node);
267
+ this.#unlink(node);
268
+ this.#clearQueueResources(node);
269
+ node.phase = "synchronizing";
270
+ node.executionStartedAt = this.#readNow();
271
+ this.#running = node;
272
+ this.#installExecutionResources(node);
273
+ void this.#execute(node);
274
+ }
275
+ #installExecutionResources(node) {
276
+ const controller = new AbortController();
277
+ node.executionController = controller;
278
+ const abortListener = () => {
279
+ this.#clearExecutionAbortListener(node);
280
+ if (node.phase === "complete")
281
+ return;
282
+ if (node.phase === "completion_critical") {
283
+ this.#cancelledOperations = addProjectOperationCount(this.#cancelledOperations);
284
+ return;
285
+ }
286
+ node.abortKind ??= "client";
287
+ controller.abort();
288
+ };
289
+ node.executionAbortListener = abortListener;
290
+ node.clientSignal.addEventListener("abort", abortListener, { once: true });
291
+ this.#executionAbortListeners += 1;
292
+ node.executionTimer = setTimeout(() => {
293
+ this.#expireExecutionDeadline(node);
294
+ }, this.#operationDeadlineMs);
295
+ this.#executionTimers += 1;
296
+ if (node.clientSignal.aborted)
297
+ abortListener();
298
+ }
299
+ #clearExecutionResources(node) {
300
+ this.#clearExecutionAbortListener(node);
301
+ this.#clearExecutionTimer(node);
302
+ }
303
+ #clearExecutionAbortListener(node) {
304
+ if (node.executionAbortListener) {
305
+ node.clientSignal.removeEventListener("abort", node.executionAbortListener);
306
+ node.executionAbortListener = undefined;
307
+ this.#executionAbortListeners -= 1;
308
+ }
309
+ }
310
+ #clearExecutionTimer(node) {
311
+ if (node.executionTimer) {
312
+ clearTimeout(node.executionTimer);
313
+ node.executionTimer = undefined;
314
+ this.#executionTimers -= 1;
315
+ }
316
+ }
317
+ #expireExecutionDeadline(node) {
318
+ if (node.phase === "completion_critical" || node.phase === "complete" || node.abortKind)
319
+ return;
320
+ node.abortKind = "deadline";
321
+ this.#clearExecutionResources(node);
322
+ node.executionController?.abort();
323
+ }
324
+ #checkpoint(node) {
325
+ if (node.phase === "completion_critical")
326
+ return;
327
+ const startedAt = node.executionStartedAt;
328
+ if (node.abortKind === undefined &&
329
+ startedAt !== undefined &&
330
+ this.#readNow() - startedAt >= this.#operationDeadlineMs) {
331
+ this.#expireExecutionDeadline(node);
332
+ }
333
+ if (node.abortKind === "deadline") {
334
+ throw new ProjectOperationSchedulerError("OPERATION_DEADLINE_EXCEEDED");
335
+ }
336
+ if (node.abortKind === "shutdown") {
337
+ throw new ProjectOperationSchedulerError("SERVER_SHUTTING_DOWN");
338
+ }
339
+ if (node.abortKind === "client" || node.clientSignal.aborted) {
340
+ throw new ProjectOperationSchedulerError("REQUEST_CANCELLED");
341
+ }
342
+ }
343
+ #createContext(node) {
344
+ return Object.freeze({
345
+ sequence: node.sequence,
346
+ get signal() {
347
+ if (!node.executionController)
348
+ throw new Error("Operation signal is unavailable.");
349
+ return node.executionController.signal;
350
+ },
351
+ get phase() {
352
+ return node.phase;
353
+ },
354
+ checkpoint: () => {
355
+ this.#checkpoint(node);
356
+ },
357
+ markExecuting: () => {
358
+ this.#checkpoint(node);
359
+ if (node.phase !== "synchronizing") {
360
+ throw new Error("Operation can enter executing only from synchronizing.");
361
+ }
362
+ node.phase = "executing";
363
+ },
364
+ enterCompletionCritical: () => {
365
+ this.#checkpoint(node);
366
+ if (node.phase !== "executing") {
367
+ throw new Error("Operation can enter completion-critical only from executing.");
368
+ }
369
+ node.phase = "completion_critical";
370
+ this.#clearExecutionTimer(node);
371
+ },
372
+ });
373
+ }
374
+ async #execute(node) {
375
+ const operation = node.operation;
376
+ const resolve = node.resolve;
377
+ const reject = node.reject;
378
+ if (!operation || !resolve || !reject) {
379
+ this.#finishExecution(node, "internal_error");
380
+ reject?.(new Error("Operation state is unavailable."));
381
+ return;
382
+ }
383
+ const context = this.#createContext(node);
384
+ try {
385
+ context.checkpoint();
386
+ const value = await operation(context);
387
+ context.checkpoint();
388
+ this.#lastOutcome = "succeeded";
389
+ resolve(value);
390
+ }
391
+ catch (error) {
392
+ const failure = this.#classifyExecutionFailure(node, error);
393
+ reject(failure.error);
394
+ this.#lastOutcome = failure.outcome;
395
+ if (failure.outcome === "cancelled") {
396
+ this.#cancelledOperations = addProjectOperationCount(this.#cancelledOperations);
397
+ }
398
+ else if (failure.outcome === "deadline_exceeded") {
399
+ this.#deadlineExceededOperations = addProjectOperationCount(this.#deadlineExceededOperations);
400
+ }
401
+ }
402
+ finally {
403
+ this.#finishExecution(node, this.#lastOutcome);
404
+ }
405
+ }
406
+ #classifyExecutionFailure(node, error) {
407
+ if (node.phase !== "completion_critical") {
408
+ if (node.abortKind === "deadline") {
409
+ return {
410
+ error: new ProjectOperationSchedulerError("OPERATION_DEADLINE_EXCEEDED"),
411
+ outcome: "deadline_exceeded",
412
+ };
413
+ }
414
+ if (node.abortKind === "shutdown") {
415
+ return {
416
+ error: new ProjectOperationSchedulerError("SERVER_SHUTTING_DOWN"),
417
+ outcome: "cancelled",
418
+ };
419
+ }
420
+ if (node.abortKind === "client" || node.clientSignal.aborted) {
421
+ return {
422
+ error: new ProjectOperationSchedulerError("REQUEST_CANCELLED"),
423
+ outcome: "cancelled",
424
+ };
425
+ }
426
+ }
427
+ if (error instanceof ProjectOperationSchedulerError) {
428
+ if (error.code === "REQUEST_CANCELLED")
429
+ return { error, outcome: "cancelled" };
430
+ if (error.code === "OPERATION_DEADLINE_EXCEEDED") {
431
+ return { error, outcome: "deadline_exceeded" };
432
+ }
433
+ }
434
+ return { error, outcome: "failed" };
435
+ }
436
+ #finishExecution(node, outcome) {
437
+ const wasCompletionCritical = node.phase === "completion_critical";
438
+ const startedAt = node.executionStartedAt ?? this.#readNow();
439
+ const duration = boundedDuration(this.#readNow() - startedAt);
440
+ this.#maxExecutionMs = Math.max(this.#maxExecutionMs, duration);
441
+ this.#clearExecutionResources(node);
442
+ node.phase = "complete";
443
+ node.operation = undefined;
444
+ node.resolve = undefined;
445
+ node.reject = undefined;
446
+ node.executionController = undefined;
447
+ node.executionStartedAt = undefined;
448
+ node.abortKind = undefined;
449
+ if (this.#running === node)
450
+ this.#running = undefined;
451
+ this.#lastOutcome = outcome;
452
+ this.#startNext();
453
+ if (wasCompletionCritical)
454
+ this.#notifyCompletionCriticalWaiters();
455
+ this.#notifyIdleIfNeeded();
456
+ }
457
+ #notifyCompletionCriticalWaiters() {
458
+ if (this.#running?.phase === "completion_critical" ||
459
+ this.#completionCriticalWaiters.size === 0) {
460
+ return;
461
+ }
462
+ const waiters = [...this.#completionCriticalWaiters];
463
+ this.#completionCriticalWaiters.clear();
464
+ for (const resolve of waiters)
465
+ resolve();
466
+ }
467
+ #notifyIdleIfNeeded() {
468
+ if (this.#running || this.#waiting !== 0 || this.#idleWaiters.size === 0)
469
+ return;
470
+ const waiters = [...this.#idleWaiters];
471
+ this.#idleWaiters.clear();
472
+ for (const resolve of waiters)
473
+ resolve();
474
+ }
475
+ }