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.
- package/CHANGELOG.md +27 -0
- package/README.md +47 -15
- package/SECURITY.md +31 -0
- package/dist/cli.js +0 -0
- package/dist/index.js +129 -10
- package/dist/server.js +21 -2
- package/dist/services/agent-setup.js +36 -117
- package/dist/services/agent-targets.js +122 -0
- package/dist/services/context-builder.js +52 -24
- package/dist/services/diagnostics.js +10 -5
- package/dist/services/file-fingerprints.js +104 -0
- package/dist/services/file-snapshot.js +8 -1
- package/dist/services/impact.js +334 -0
- package/dist/services/operations.js +343 -254
- package/dist/services/outline.js +94 -21
- package/dist/services/pagination.js +10 -4
- package/dist/services/project-operation-scheduler.js +475 -0
- package/dist/services/project-status.js +134 -52
- package/dist/services/project-watcher.js +230 -0
- package/dist/services/project.js +646 -66
- package/dist/services/public-errors.js +255 -0
- package/dist/services/references.js +7 -1
- package/dist/services/relationships.js +1198 -0
- package/dist/services/request-context.js +42 -0
- package/dist/services/runtime-activity.js +65 -0
- package/dist/services/runtime-logger.js +50 -0
- package/dist/services/runtime-policy.js +47 -0
- package/dist/services/setup-wizard.js +4 -7
- package/dist/services/shutdown.js +93 -0
- package/dist/services/symbol-index-limits.js +5 -0
- package/dist/services/symbol-index-order.js +13 -0
- package/dist/services/symbol-index-policy.js +91 -0
- package/dist/services/symbol-index-sqlite.js +850 -0
- package/dist/services/symbol-index.js +447 -0
- package/dist/services/symbols.js +403 -79
- package/dist/services/test-candidates.js +209 -0
- package/dist/services/workspace.js +18 -7
- package/dist/tools/apply_operation.js +8 -4
- package/dist/tools/explore.js +8 -6
- package/dist/tools/find_references.js +12 -13
- package/dist/tools/get_diagnostics.js +10 -7
- package/dist/tools/get_file.js +23 -11
- package/dist/tools/get_impact.js +134 -0
- package/dist/tools/get_operation_preview.js +7 -4
- package/dist/tools/get_outline.js +8 -6
- package/dist/tools/get_project_status.js +77 -8
- package/dist/tools/get_symbol_source.js +8 -6
- package/dist/tools/list_files.js +11 -6
- package/dist/tools/rename_symbol.js +6 -4
- package/dist/tools/replace_symbol_body.js +6 -4
- package/dist/tools/result.js +27 -17
- package/dist/tools/scaffold_class.js +6 -4
- package/dist/tools/search_symbols.js +15 -14
- package/docs/support.md +80 -0
- package/package.json +16 -5
- package/skills/structural-code-editing/SKILL.md +37 -10
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createTruncationMetadata, createSourceLocation, } from "./read-contracts.js";
|
|
3
|
+
import { getSourceFileOrThrow } from "./project.js";
|
|
4
|
+
import { findLocatedDeclaration } from "./symbols.js";
|
|
5
|
+
import { NO_REQUEST_CONTEXT } from "./request-context.js";
|
|
6
|
+
import { createCompilerRelationshipResolver, RELATIONSHIP_EDGE_KINDS, } from "./relationships.js";
|
|
7
|
+
export const IMPACT_DIRECTIONS = Object.freeze(["incoming", "outgoing", "both"]);
|
|
8
|
+
export const DEFAULT_IMPACT_MAX_DEPTH = 3;
|
|
9
|
+
export const DEFAULT_IMPACT_MAX_NODES = 100;
|
|
10
|
+
export const DEFAULT_IMPACT_MAX_EDGES = 200;
|
|
11
|
+
export const MAX_IMPACT_DEPTH = 32;
|
|
12
|
+
export const MAX_IMPACT_NODES = 1_000;
|
|
13
|
+
export const MAX_IMPACT_EDGES = 5_000;
|
|
14
|
+
export function isExactImpactEdge(edge) {
|
|
15
|
+
return (typeof edge === "object" &&
|
|
16
|
+
edge !== null &&
|
|
17
|
+
edge.provenance === "compiler" &&
|
|
18
|
+
edge.confidence === "exact" &&
|
|
19
|
+
edge.resolution === "resolved" &&
|
|
20
|
+
edge.compiler_authoritative === true &&
|
|
21
|
+
typeof edge.freshness === "object" &&
|
|
22
|
+
edge.freshness !== null &&
|
|
23
|
+
edge.freshness.state === "fresh" &&
|
|
24
|
+
Array.isArray(edge.freshness.causes) &&
|
|
25
|
+
edge.freshness.causes.length === 0);
|
|
26
|
+
}
|
|
27
|
+
export function assertExactImpactEvidence(impact) {
|
|
28
|
+
if (typeof impact !== "object" || impact === null || !Array.isArray(impact.edges)) {
|
|
29
|
+
throw new Error("Impact evidence is invalid.");
|
|
30
|
+
}
|
|
31
|
+
if (impact.incomplete) {
|
|
32
|
+
throw new Error("Test candidates require complete impact evidence.");
|
|
33
|
+
}
|
|
34
|
+
if (!impact.edges.every(isExactImpactEdge)) {
|
|
35
|
+
throw new Error("Test candidates require fresh exact compiler impact evidence.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const IMPACT_PROBE_WORK_ITEMS = 1_024;
|
|
39
|
+
const IMPACT_RELATIONSHIP_WORK_ITEMS = 100_000;
|
|
40
|
+
function assertIntegerInRange(value, minimum, maximum, name) {
|
|
41
|
+
if (!Number.isInteger(value) || value < minimum || value > maximum) {
|
|
42
|
+
throw new Error(`${name} must be an integer from ${minimum} to ${maximum}.`);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function assertDirection(value) {
|
|
47
|
+
if (!IMPACT_DIRECTIONS.includes(value)) {
|
|
48
|
+
throw new Error("Impact direction is invalid.");
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
function normalizeRelationshipKinds(kinds) {
|
|
53
|
+
if (kinds === undefined)
|
|
54
|
+
return [...RELATIONSHIP_EDGE_KINDS];
|
|
55
|
+
if (!Array.isArray(kinds))
|
|
56
|
+
throw new Error("Impact relationship_kinds must be an array.");
|
|
57
|
+
const unique = new Set();
|
|
58
|
+
for (const kind of kinds) {
|
|
59
|
+
if (!RELATIONSHIP_EDGE_KINDS.includes(kind)) {
|
|
60
|
+
throw new Error("Impact relationship kind is invalid.");
|
|
61
|
+
}
|
|
62
|
+
unique.add(kind);
|
|
63
|
+
}
|
|
64
|
+
return [...unique].sort((left, right) => RELATIONSHIP_EDGE_KINDS.indexOf(left) - RELATIONSHIP_EDGE_KINDS.indexOf(right));
|
|
65
|
+
}
|
|
66
|
+
function normalizeOptions(options = {}) {
|
|
67
|
+
if (typeof options !== "object" || options === null) {
|
|
68
|
+
throw new Error("Impact traversal options are invalid.");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
direction: assertDirection(options.direction ?? "both"),
|
|
72
|
+
max_depth: assertIntegerInRange(options.max_depth ?? DEFAULT_IMPACT_MAX_DEPTH, 0, MAX_IMPACT_DEPTH, "Impact max_depth"),
|
|
73
|
+
max_nodes: assertIntegerInRange(options.max_nodes ?? DEFAULT_IMPACT_MAX_NODES, 1, MAX_IMPACT_NODES, "Impact max_nodes"),
|
|
74
|
+
max_edges: assertIntegerInRange(options.max_edges ?? DEFAULT_IMPACT_MAX_EDGES, 1, MAX_IMPACT_EDGES, "Impact max_edges"),
|
|
75
|
+
relationship_kinds: normalizeRelationshipKinds(options.relationship_kinds),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function projectRelativeFile(projectRoot, filePath) {
|
|
79
|
+
return path.relative(projectRoot, filePath).split(path.sep).join("/");
|
|
80
|
+
}
|
|
81
|
+
function nodeRange(node) {
|
|
82
|
+
const sourceFile = node.getSourceFile();
|
|
83
|
+
const start = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
84
|
+
const end = sourceFile.getLineAndColumnAtPos(Math.max(node.getStart(), node.getEnd() - 1));
|
|
85
|
+
return {
|
|
86
|
+
start_line: start.line,
|
|
87
|
+
start_column: start.column,
|
|
88
|
+
end_line: end.line,
|
|
89
|
+
end_column: end.column,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function resolveImpactRoot(project, projectRoot, request, requestContext = NO_REQUEST_CONTEXT) {
|
|
93
|
+
requestContext.checkpoint();
|
|
94
|
+
if (typeof request !== "object" ||
|
|
95
|
+
request === null ||
|
|
96
|
+
typeof request.file_path !== "string" ||
|
|
97
|
+
typeof request.symbol_path !== "string") {
|
|
98
|
+
throw new Error("Impact root is invalid.");
|
|
99
|
+
}
|
|
100
|
+
const sourceFile = getSourceFileOrThrow(project, request.file_path);
|
|
101
|
+
const located = findLocatedDeclaration(sourceFile, request.symbol_path, () => requestContext.checkpoint());
|
|
102
|
+
const node = located.node;
|
|
103
|
+
const location = createSourceLocation(projectRelativeFile(projectRoot, sourceFile.getFilePath()), nodeRange(node));
|
|
104
|
+
const { file, ...range } = location;
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
file,
|
|
107
|
+
symbol_path: located.symbolPath,
|
|
108
|
+
selector: `${located.symbolPath}@${located.line}`,
|
|
109
|
+
range,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function endpointKey(endpoint) {
|
|
113
|
+
return `${endpoint.file}\u0000${endpoint.symbol_path}\u0000${endpoint.selector}`;
|
|
114
|
+
}
|
|
115
|
+
function edgeOrder(left, right) {
|
|
116
|
+
return (left.kind.localeCompare(right.kind) ||
|
|
117
|
+
left.source.file.localeCompare(right.source.file) ||
|
|
118
|
+
left.source.selector.localeCompare(right.source.selector) ||
|
|
119
|
+
left.target.file.localeCompare(right.target.file) ||
|
|
120
|
+
left.target.selector.localeCompare(right.target.selector) ||
|
|
121
|
+
left.relationship_id.localeCompare(right.relationship_id));
|
|
122
|
+
}
|
|
123
|
+
function neighborOrder(left, right) {
|
|
124
|
+
return (left.endpoint.file.localeCompare(right.endpoint.file) ||
|
|
125
|
+
left.endpoint.selector.localeCompare(right.endpoint.selector) ||
|
|
126
|
+
left.endpoint.symbol_path.localeCompare(right.endpoint.symbol_path) ||
|
|
127
|
+
edgeOrder(left.edge, right.edge));
|
|
128
|
+
}
|
|
129
|
+
function collectNeighbors(current, edges, options, requestContext) {
|
|
130
|
+
const currentKey = endpointKey(current);
|
|
131
|
+
const kinds = new Set(options.relationship_kinds);
|
|
132
|
+
const neighbors = new Map();
|
|
133
|
+
const orderedEdges = [...edges].sort(edgeOrder);
|
|
134
|
+
for (const edge of orderedEdges) {
|
|
135
|
+
requestContext.checkpoint();
|
|
136
|
+
if (!kinds.has(edge.kind))
|
|
137
|
+
continue;
|
|
138
|
+
const matchesSource = endpointKey(edge.source) === currentKey;
|
|
139
|
+
const matchesTarget = endpointKey(edge.target) === currentKey;
|
|
140
|
+
if ((options.direction === "outgoing" || options.direction === "both") && matchesSource) {
|
|
141
|
+
neighbors.set(`${edge.relationship_id}\u0000${endpointKey(edge.target)}`, {
|
|
142
|
+
edge,
|
|
143
|
+
endpoint: edge.target,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if ((options.direction === "incoming" || options.direction === "both") && matchesTarget) {
|
|
147
|
+
neighbors.set(`${edge.relationship_id}\u0000${endpointKey(edge.source)}`, {
|
|
148
|
+
edge,
|
|
149
|
+
endpoint: edge.source,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...neighbors.values()].sort(neighborOrder);
|
|
154
|
+
}
|
|
155
|
+
function traverseWithNeighborProvider(root, neighborsFor, options = {}, requestContext = NO_REQUEST_CONTEXT) {
|
|
156
|
+
requestContext.checkpoint();
|
|
157
|
+
if (typeof root !== "object" || root === null) {
|
|
158
|
+
throw new Error("Impact traversal input is invalid.");
|
|
159
|
+
}
|
|
160
|
+
const normalized = normalizeOptions(options);
|
|
161
|
+
const nodes = new Map([
|
|
162
|
+
[endpointKey(root), { endpoint: root, depth: 0, direct: false }],
|
|
163
|
+
]);
|
|
164
|
+
const selectedEdges = new Map();
|
|
165
|
+
const queue = [{ key: endpointKey(root), endpoint: root, depth: 0 }];
|
|
166
|
+
const truncationReasons = new Set();
|
|
167
|
+
let maxDepthReached = 0;
|
|
168
|
+
const classifyObservedNeighbor = (current, neighbor, edgeBlocked) => {
|
|
169
|
+
const nextKey = endpointKey(neighbor.endpoint);
|
|
170
|
+
const isKnownNode = nodes.has(nextKey);
|
|
171
|
+
if (edgeBlocked)
|
|
172
|
+
truncationReasons.add("edge_limit");
|
|
173
|
+
if (!isKnownNode && current.depth + 1 > normalized.max_depth) {
|
|
174
|
+
truncationReasons.add("depth_limit");
|
|
175
|
+
}
|
|
176
|
+
if (!isKnownNode && nodes.size >= normalized.max_nodes) {
|
|
177
|
+
truncationReasons.add("record_limit");
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
while (queue.length > 0) {
|
|
181
|
+
requestContext.checkpoint();
|
|
182
|
+
const current = queue.shift();
|
|
183
|
+
const remainingEdges = normalized.max_edges - selectedEdges.size;
|
|
184
|
+
if (remainingEdges <= 0) {
|
|
185
|
+
const probe = neighborsFor(current.endpoint, normalized, requestContext, 1, true, undefined, new Set(selectedEdges.keys()), IMPACT_PROBE_WORK_ITEMS);
|
|
186
|
+
for (const neighbor of probe.neighbors) {
|
|
187
|
+
classifyObservedNeighbor(current, neighbor, true);
|
|
188
|
+
}
|
|
189
|
+
if (probe.incomplete && !probe.workLimitReached)
|
|
190
|
+
truncationReasons.add("edge_limit");
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const restrictionReason = current.depth >= normalized.max_depth
|
|
194
|
+
? "depth_limit"
|
|
195
|
+
: nodes.size >= normalized.max_nodes
|
|
196
|
+
? "record_limit"
|
|
197
|
+
: undefined;
|
|
198
|
+
const allowedNeighborKeys = restrictionReason ? new Set(nodes.keys()) : undefined;
|
|
199
|
+
const batch = neighborsFor(current.endpoint, normalized, requestContext, remainingEdges, false, allowedNeighborKeys, new Set(selectedEdges.keys()));
|
|
200
|
+
if (batch.excludedNeighbors && restrictionReason)
|
|
201
|
+
truncationReasons.add(restrictionReason);
|
|
202
|
+
if (batch.workLimitReached)
|
|
203
|
+
truncationReasons.add("record_limit");
|
|
204
|
+
let skippedNewNode = false;
|
|
205
|
+
for (const neighbor of batch.neighbors) {
|
|
206
|
+
requestContext.checkpoint();
|
|
207
|
+
const nextKey = endpointKey(neighbor.endpoint);
|
|
208
|
+
const nextDepth = current.depth + 1;
|
|
209
|
+
const isKnownNode = nodes.has(nextKey);
|
|
210
|
+
if (nextDepth > normalized.max_depth && !isKnownNode) {
|
|
211
|
+
truncationReasons.add("depth_limit");
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!isKnownNode && nodes.size >= normalized.max_nodes) {
|
|
215
|
+
truncationReasons.add("record_limit");
|
|
216
|
+
skippedNewNode = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (!selectedEdges.has(neighbor.edge.relationship_id)) {
|
|
220
|
+
if (selectedEdges.size >= normalized.max_edges) {
|
|
221
|
+
truncationReasons.add("edge_limit");
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
selectedEdges.set(neighbor.edge.relationship_id, neighbor.edge);
|
|
225
|
+
}
|
|
226
|
+
if (isKnownNode)
|
|
227
|
+
continue;
|
|
228
|
+
nodes.set(nextKey, {
|
|
229
|
+
endpoint: neighbor.endpoint,
|
|
230
|
+
depth: nextDepth,
|
|
231
|
+
direct: nextDepth === 1,
|
|
232
|
+
});
|
|
233
|
+
queue.push({ key: nextKey, endpoint: neighbor.endpoint, depth: nextDepth });
|
|
234
|
+
maxDepthReached = Math.max(maxDepthReached, nextDepth);
|
|
235
|
+
}
|
|
236
|
+
if (restrictionReason) {
|
|
237
|
+
if (batch.incomplete && !batch.workLimitReached)
|
|
238
|
+
truncationReasons.add("edge_limit");
|
|
239
|
+
const probe = neighborsFor(current.endpoint, normalized, requestContext, 1, true, undefined, new Set(selectedEdges.keys()), IMPACT_PROBE_WORK_ITEMS);
|
|
240
|
+
for (const neighbor of probe.neighbors) {
|
|
241
|
+
classifyObservedNeighbor(current, neighbor, selectedEdges.size >= normalized.max_edges);
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if ((skippedNewNode || nodes.size >= normalized.max_nodes) &&
|
|
246
|
+
selectedEdges.size < normalized.max_edges) {
|
|
247
|
+
const knownBatch = neighborsFor(current.endpoint, normalized, requestContext, normalized.max_edges - selectedEdges.size, false, new Set(nodes.keys()), new Set(selectedEdges.keys()));
|
|
248
|
+
if (knownBatch.excludedNeighbors)
|
|
249
|
+
truncationReasons.add("record_limit");
|
|
250
|
+
if (knownBatch.workLimitReached)
|
|
251
|
+
truncationReasons.add("record_limit");
|
|
252
|
+
for (const neighbor of knownBatch.neighbors) {
|
|
253
|
+
requestContext.checkpoint();
|
|
254
|
+
if (selectedEdges.size >= normalized.max_edges) {
|
|
255
|
+
truncationReasons.add("edge_limit");
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
selectedEdges.set(neighbor.edge.relationship_id, neighbor.edge);
|
|
259
|
+
}
|
|
260
|
+
if (knownBatch.incomplete && !knownBatch.workLimitReached) {
|
|
261
|
+
truncationReasons.add("edge_limit");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else if (batch.incomplete && !batch.workLimitReached) {
|
|
265
|
+
truncationReasons.add("edge_limit");
|
|
266
|
+
const overflowProbe = neighborsFor(current.endpoint, normalized, requestContext, 1, true, undefined, new Set(selectedEdges.keys()), IMPACT_PROBE_WORK_ITEMS);
|
|
267
|
+
for (const neighbor of overflowProbe.neighbors) {
|
|
268
|
+
classifyObservedNeighbor(current, neighbor, true);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const orderedReasons = ["depth_limit", "record_limit", "edge_limit"].filter((reason) => truncationReasons.has(reason));
|
|
273
|
+
const truncated = orderedReasons.length > 0;
|
|
274
|
+
requestContext.checkpoint();
|
|
275
|
+
return {
|
|
276
|
+
root,
|
|
277
|
+
direction: normalized.direction,
|
|
278
|
+
relationship_kinds: normalized.relationship_kinds,
|
|
279
|
+
nodes: [...nodes.values()].sort((left, right) => left.depth - right.depth ||
|
|
280
|
+
left.endpoint.file.localeCompare(right.endpoint.file) ||
|
|
281
|
+
left.endpoint.selector.localeCompare(right.endpoint.selector) ||
|
|
282
|
+
left.endpoint.symbol_path.localeCompare(right.endpoint.symbol_path)),
|
|
283
|
+
edges: [...selectedEdges.values()].sort(edgeOrder),
|
|
284
|
+
visited_nodes: nodes.size,
|
|
285
|
+
visited_edges: selectedEdges.size,
|
|
286
|
+
max_depth_reached: maxDepthReached,
|
|
287
|
+
max_depth: normalized.max_depth,
|
|
288
|
+
max_nodes: normalized.max_nodes,
|
|
289
|
+
max_edges: normalized.max_edges,
|
|
290
|
+
incomplete: truncated,
|
|
291
|
+
truncation: createTruncationMetadata(truncated, truncated ? orderedReasons[0] : null),
|
|
292
|
+
truncation_reasons: orderedReasons,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
export function traverseImpact(root, edges, options = {}, requestContext = NO_REQUEST_CONTEXT) {
|
|
296
|
+
if (!Array.isArray(edges))
|
|
297
|
+
throw new Error("Impact traversal input is invalid.");
|
|
298
|
+
return traverseWithNeighborProvider(root, (current, normalized, _context, maxEdges, _stopAfterFirst, allowedNeighborKeys, excludedRelationshipIds) => {
|
|
299
|
+
const allNeighbors = collectNeighbors(current, edges, normalized, requestContext).filter((neighbor) => !excludedRelationshipIds?.has(neighbor.edge.relationship_id));
|
|
300
|
+
const neighbors = allowedNeighborKeys
|
|
301
|
+
? allNeighbors.filter((neighbor) => allowedNeighborKeys.has(endpointKey(neighbor.endpoint)))
|
|
302
|
+
: allNeighbors;
|
|
303
|
+
return {
|
|
304
|
+
neighbors: neighbors.slice(0, maxEdges),
|
|
305
|
+
incomplete: neighbors.length > maxEdges,
|
|
306
|
+
excludedNeighbors: allowedNeighborKeys
|
|
307
|
+
? allNeighbors.some((neighbor) => !allowedNeighborKeys.has(endpointKey(neighbor.endpoint)))
|
|
308
|
+
: false,
|
|
309
|
+
workLimitReached: false,
|
|
310
|
+
};
|
|
311
|
+
}, options, requestContext);
|
|
312
|
+
}
|
|
313
|
+
export function traverseCompilerImpact(project, projectRoot, root, freshness, options = {}, requestContext = NO_REQUEST_CONTEXT) {
|
|
314
|
+
const resolver = createCompilerRelationshipResolver(project, projectRoot, freshness, requestContext);
|
|
315
|
+
return traverseWithNeighborProvider(root, (current, normalized, _context, maxEdges, stopAfterFirst, allowedNeighborKeys, excludedRelationshipIds, maxWorkItems) => {
|
|
316
|
+
const resolution = resolver.edgesFor(current, {
|
|
317
|
+
direction: normalized.direction,
|
|
318
|
+
relationship_kinds: normalized.relationship_kinds,
|
|
319
|
+
max_edges: maxEdges,
|
|
320
|
+
max_work_items: maxWorkItems ?? IMPACT_RELATIONSHIP_WORK_ITEMS,
|
|
321
|
+
allowed_neighbor_keys: allowedNeighborKeys ? [...allowedNeighborKeys] : undefined,
|
|
322
|
+
excluded_relationship_ids: excludedRelationshipIds
|
|
323
|
+
? [...excludedRelationshipIds]
|
|
324
|
+
: undefined,
|
|
325
|
+
stop_after_first: stopAfterFirst,
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
neighbors: collectNeighbors(current, resolution.edges, normalized, requestContext),
|
|
329
|
+
incomplete: resolution.incomplete,
|
|
330
|
+
excludedNeighbors: resolution.excluded_neighbors,
|
|
331
|
+
workLimitReached: resolution.work_limit_reached,
|
|
332
|
+
};
|
|
333
|
+
}, options, requestContext);
|
|
334
|
+
}
|