ast-mcp-server 0.13.0 → 0.14.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 +25 -1
- package/README.md +56 -25
- package/cordis.patch.yml +8 -1
- package/dist/batch/schema.js +1 -1
- package/dist/server.js +15 -1
- package/dist/services/authenticated-publication.js +181 -0
- package/dist/services/call-spines.js +10 -5
- package/dist/services/compiler-worker-host.js +36 -16
- package/dist/services/compiler-worker-protocol.js +70 -2
- package/dist/services/context-builder.js +2 -1
- package/dist/services/diagnostics.js +293 -9
- package/dist/services/h03-timeout-fixture.js +335 -0
- package/dist/services/impact.js +46 -14
- package/dist/services/managed-file.js +3 -50
- package/dist/services/operation-plan-file.js +17 -18
- package/dist/services/operations.js +231 -87
- package/dist/services/project.js +29 -16
- package/dist/services/public-errors.js +7 -5
- package/dist/services/relationships.js +403 -4
- package/dist/services/runtime-policy.js +68 -2
- package/dist/services/test-candidates.js +35 -2
- package/dist/tools/explore.js +5 -3
- package/dist/tools/find_test_candidates.js +24 -3
- package/dist/tools/get_impact.js +12 -3
- package/dist/tools/relationship-schema.js +12 -1
- package/dist/tools/result.js +48 -0
- package/dist/worker-runtime.js +6 -1
- package/docs/support.md +4 -2
- package/package.json +16 -8
- package/skills/structural-code-editing/SKILL.md +8 -0
- package/skills/structural-code-editing/releases.json +1 -1
|
@@ -12,6 +12,41 @@ export const RELATIONSHIP_EDGE_KINDS = Object.freeze([
|
|
|
12
12
|
"call",
|
|
13
13
|
"contains",
|
|
14
14
|
]);
|
|
15
|
+
export const RELATIONSHIP_COVERAGE_STATUSES = Object.freeze([
|
|
16
|
+
"not_applicable",
|
|
17
|
+
"completed",
|
|
18
|
+
"unsupported",
|
|
19
|
+
"unfinished",
|
|
20
|
+
]);
|
|
21
|
+
const COVERAGE_STATUS_PRECEDENCE = {
|
|
22
|
+
not_applicable: 0,
|
|
23
|
+
completed: 1,
|
|
24
|
+
unsupported: 2,
|
|
25
|
+
unfinished: 3,
|
|
26
|
+
};
|
|
27
|
+
function coverageKey(entry) {
|
|
28
|
+
return `${entry.kind}:${entry.direction}:${entry.endpoint_class}`;
|
|
29
|
+
}
|
|
30
|
+
export function canonicalRelationshipCoverage(coverage) {
|
|
31
|
+
const kindOrder = new Map(RELATIONSHIP_EDGE_KINDS.map((kind, index) => [kind, index]));
|
|
32
|
+
return [...coverage].sort((left, right) => kindOrder.get(left.kind) - kindOrder.get(right.kind) ||
|
|
33
|
+
left.direction.localeCompare(right.direction) ||
|
|
34
|
+
left.endpoint_class.localeCompare(right.endpoint_class));
|
|
35
|
+
}
|
|
36
|
+
export function mergeRelationshipCoverage(...observations) {
|
|
37
|
+
const merged = new Map();
|
|
38
|
+
for (const entry of observations.flat()) {
|
|
39
|
+
const current = merged.get(coverageKey(entry));
|
|
40
|
+
if (!current ||
|
|
41
|
+
COVERAGE_STATUS_PRECEDENCE[entry.status] > COVERAGE_STATUS_PRECEDENCE[current.status]) {
|
|
42
|
+
merged.set(coverageKey(entry), entry);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return canonicalRelationshipCoverage([...merged.values()]);
|
|
46
|
+
}
|
|
47
|
+
export function isRelationshipCoverageComplete(coverage) {
|
|
48
|
+
return coverage.every(({ status }) => status === "completed" || status === "not_applicable");
|
|
49
|
+
}
|
|
15
50
|
export const RELATIONSHIP_PROVENANCES = Object.freeze(["compiler", "syntax", "heuristic"]);
|
|
16
51
|
export const RELATIONSHIP_CONFIDENCES = Object.freeze(["exact", "high", "medium", "low"]);
|
|
17
52
|
export const RELATIONSHIP_RESOLUTIONS = Object.freeze([
|
|
@@ -268,6 +303,41 @@ function addModuleEdges(state, requestContext) {
|
|
|
268
303
|
}
|
|
269
304
|
}
|
|
270
305
|
}
|
|
306
|
+
function symbolSupportsCallDirection(node, direction) {
|
|
307
|
+
if (!node)
|
|
308
|
+
return true;
|
|
309
|
+
if (direction === "incoming") {
|
|
310
|
+
return (Node.isFunctionDeclaration(node) ||
|
|
311
|
+
Node.isMethodDeclaration(node) ||
|
|
312
|
+
Node.isConstructorDeclaration(node) ||
|
|
313
|
+
Node.isClassDeclaration(node) ||
|
|
314
|
+
Node.isVariableDeclaration(node));
|
|
315
|
+
}
|
|
316
|
+
return (Node.isFunctionDeclaration(node) ||
|
|
317
|
+
Node.isMethodDeclaration(node) ||
|
|
318
|
+
Node.isConstructorDeclaration(node) ||
|
|
319
|
+
Node.isGetAccessorDeclaration(node) ||
|
|
320
|
+
Node.isSetAccessorDeclaration(node));
|
|
321
|
+
}
|
|
322
|
+
export function relationshipCoverageApplicability(kind, direction, endpointClass, node) {
|
|
323
|
+
if (kind === "contains")
|
|
324
|
+
return true;
|
|
325
|
+
if (endpointClass === "module")
|
|
326
|
+
return kind === "import" || kind === "export";
|
|
327
|
+
if (kind === "reference")
|
|
328
|
+
return true;
|
|
329
|
+
if (kind === "import" || kind === "export")
|
|
330
|
+
return direction === "incoming";
|
|
331
|
+
if (kind === "call")
|
|
332
|
+
return symbolSupportsCallDirection(node, direction);
|
|
333
|
+
if (kind === "extends") {
|
|
334
|
+
return !node || Node.isClassDeclaration(node) || Node.isInterfaceDeclaration(node);
|
|
335
|
+
}
|
|
336
|
+
return (kind === "implements" &&
|
|
337
|
+
(!node ||
|
|
338
|
+
Node.isClassDeclaration(node) ||
|
|
339
|
+
(direction === "incoming" && Node.isInterfaceDeclaration(node))));
|
|
340
|
+
}
|
|
271
341
|
const DEFAULT_COMPILER_RELATIONSHIP_WORK_ITEMS = 100_000;
|
|
272
342
|
class ScopedRelationshipLimitReached extends Error {
|
|
273
343
|
constructor() {
|
|
@@ -945,6 +1015,221 @@ function addScopedIncomingExports(state, edges, target) {
|
|
|
945
1015
|
});
|
|
946
1016
|
}
|
|
947
1017
|
}
|
|
1018
|
+
function scopedInvocationForReference(reference) {
|
|
1019
|
+
for (const ancestor of reference.getAncestors()) {
|
|
1020
|
+
const expression = callLikeExpression(ancestor);
|
|
1021
|
+
if (!expression)
|
|
1022
|
+
continue;
|
|
1023
|
+
if (reference.getStart() < expression.getStart() || reference.getEnd() > expression.getEnd()) {
|
|
1024
|
+
return undefined;
|
|
1025
|
+
}
|
|
1026
|
+
return { invocation: ancestor, direct: unwrapInvocationExpression(expression) === reference };
|
|
1027
|
+
}
|
|
1028
|
+
return undefined;
|
|
1029
|
+
}
|
|
1030
|
+
function scopedLocatedCallTarget(state, collector, declaration) {
|
|
1031
|
+
if (Node.isParameterDeclaration(declaration) || Node.isMethodSignature(declaration))
|
|
1032
|
+
return;
|
|
1033
|
+
const sourceFile = declaration.getSourceFile();
|
|
1034
|
+
if (!isProjectScopedFile(state.projectRoot, sourceFile.getFilePath()))
|
|
1035
|
+
return;
|
|
1036
|
+
const targetNode = Node.isConstructorDeclaration(declaration)
|
|
1037
|
+
? declaration.getFirstAncestorByKind(SyntaxKind.ClassDeclaration)
|
|
1038
|
+
: declaration;
|
|
1039
|
+
if (!targetNode)
|
|
1040
|
+
return;
|
|
1041
|
+
let located;
|
|
1042
|
+
forEachLocatedSymbol(sourceFile, (candidate) => {
|
|
1043
|
+
consumeScopedWork(state, collector);
|
|
1044
|
+
if (candidate.node !== targetNode)
|
|
1045
|
+
return;
|
|
1046
|
+
located = candidate;
|
|
1047
|
+
return false;
|
|
1048
|
+
});
|
|
1049
|
+
return located;
|
|
1050
|
+
}
|
|
1051
|
+
function computedCallAuthority(projectRoot, invocation) {
|
|
1052
|
+
const expression = callLikeExpression(invocation);
|
|
1053
|
+
if (!expression)
|
|
1054
|
+
return { state: "not_computed", workItems: 0 };
|
|
1055
|
+
const invoked = unwrapInvocationExpression(expression);
|
|
1056
|
+
if (!Node.isElementAccessExpression(invoked))
|
|
1057
|
+
return { state: "not_computed", workItems: 0 };
|
|
1058
|
+
const argument = invoked.getArgumentExpression();
|
|
1059
|
+
const keyTypes = argument
|
|
1060
|
+
? argument.getType().isUnion()
|
|
1061
|
+
? argument.getType().getUnionTypes()
|
|
1062
|
+
: [argument.getType()]
|
|
1063
|
+
: [];
|
|
1064
|
+
const keys = keyTypes.flatMap((type) => type.isStringLiteral() && typeof type.getLiteralValue() === "string"
|
|
1065
|
+
? [type.getLiteralValue()]
|
|
1066
|
+
: []);
|
|
1067
|
+
const receiverType = invoked.getExpression().getType();
|
|
1068
|
+
const receiverTypes = receiverType.isUnion() ? receiverType.getUnionTypes() : [receiverType];
|
|
1069
|
+
const workItems = keyTypes.length + receiverTypes.length;
|
|
1070
|
+
if (keys.length === 1 && keyTypes.length === 1 && receiverTypes.length === 1) {
|
|
1071
|
+
return { state: "single_key_eligible", workItems };
|
|
1072
|
+
}
|
|
1073
|
+
const alternatives = new Map();
|
|
1074
|
+
for (const receiver of receiverTypes) {
|
|
1075
|
+
for (const key of keys) {
|
|
1076
|
+
for (const declaration of declarationsForSymbol(receiver.getProperty(key))) {
|
|
1077
|
+
const target = locatedCallTarget(projectRoot, declaration);
|
|
1078
|
+
if (target)
|
|
1079
|
+
alternatives.set(symbolEndpoint(target, projectRoot).selector, target);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const signature = invocation
|
|
1084
|
+
.getSourceFile()
|
|
1085
|
+
.getProject()
|
|
1086
|
+
.getTypeChecker()
|
|
1087
|
+
.getResolvedSignature(invocation);
|
|
1088
|
+
const selected = signature?.getDeclaration();
|
|
1089
|
+
const selectedTarget = selected ? locatedCallTarget(projectRoot, selected) : undefined;
|
|
1090
|
+
if (selectedTarget) {
|
|
1091
|
+
alternatives.set(symbolEndpoint(selectedTarget, projectRoot).selector, selectedTarget);
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
state: "unfinished",
|
|
1095
|
+
alternatives: [...alternatives.values()].sort((left, right) => symbolEndpoint(left, projectRoot).selector.localeCompare(symbolEndpoint(right, projectRoot).selector)),
|
|
1096
|
+
workItems: workItems + alternatives.size,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function scopedComputedCallSites(state, collector) {
|
|
1100
|
+
if (state.computedCallSites) {
|
|
1101
|
+
reserveScopedWork(state, collector, state.computedCallSites.length);
|
|
1102
|
+
return state.computedCallSites;
|
|
1103
|
+
}
|
|
1104
|
+
const sites = [];
|
|
1105
|
+
for (const sourceFile of scopedSourceFiles(state, collector)) {
|
|
1106
|
+
sourceFile.forEachDescendant((node) => {
|
|
1107
|
+
consumeScopedWork(state, collector);
|
|
1108
|
+
const expression = callLikeExpression(node);
|
|
1109
|
+
if (expression && Node.isElementAccessExpression(unwrapInvocationExpression(expression))) {
|
|
1110
|
+
sites.push(node);
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
state.computedCallSites = sites;
|
|
1115
|
+
return sites;
|
|
1116
|
+
}
|
|
1117
|
+
function scopedDirectCallTarget(state, collector, invocation) {
|
|
1118
|
+
const expression = callLikeExpression(invocation);
|
|
1119
|
+
if (!expression)
|
|
1120
|
+
return;
|
|
1121
|
+
const invoked = unwrapInvocationExpression(expression);
|
|
1122
|
+
if (!Node.isIdentifier(invoked))
|
|
1123
|
+
return;
|
|
1124
|
+
const targets = new Map();
|
|
1125
|
+
for (const declaration of scopedDeclarationsForSymbol(state, collector, invoked.getSymbol())) {
|
|
1126
|
+
const target = scopedLocatedCallTarget(state, collector, declaration);
|
|
1127
|
+
if (target)
|
|
1128
|
+
targets.set(endpointKey(symbolEndpoint(target, state.projectRoot)), target);
|
|
1129
|
+
}
|
|
1130
|
+
return targets.size === 1 ? [...targets.values()][0] : undefined;
|
|
1131
|
+
}
|
|
1132
|
+
function scopedReferenceFlowsToUncertainCall(state, collector, reference) {
|
|
1133
|
+
const declaration = reference.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
1134
|
+
const initializer = declaration?.getInitializer();
|
|
1135
|
+
const name = declaration?.getNameNode();
|
|
1136
|
+
if (!initializer ||
|
|
1137
|
+
!name ||
|
|
1138
|
+
!Node.isIdentifier(name) ||
|
|
1139
|
+
reference.getStart() < initializer.getStart() ||
|
|
1140
|
+
reference.getEnd() > initializer.getEnd()) {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
consumeScopedWork(state, collector);
|
|
1144
|
+
const carrierReferences = name.findReferencesAsNodes();
|
|
1145
|
+
reserveScopedWork(state, collector, carrierReferences.length);
|
|
1146
|
+
return carrierReferences.some((carrierReference) => Boolean(scopedInvocationForReference(carrierReference)));
|
|
1147
|
+
}
|
|
1148
|
+
function addScopedIncomingCalls(state, collector, target) {
|
|
1149
|
+
if (!Node.isReferenceFindable(target.node))
|
|
1150
|
+
return;
|
|
1151
|
+
for (const invocation of scopedComputedCallSites(state, collector)) {
|
|
1152
|
+
const authority = computedCallAuthority(state.projectRoot, invocation);
|
|
1153
|
+
reserveScopedWork(state, collector, authority.workItems);
|
|
1154
|
+
if (authority.state === "unfinished" &&
|
|
1155
|
+
authority.alternatives.some((alternative) => alternative.node === target.node)) {
|
|
1156
|
+
collector.semanticUnfinished = true;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
consumeScopedWork(state, collector);
|
|
1160
|
+
const references = target.node.findReferencesAsNodes();
|
|
1161
|
+
reserveScopedWork(state, collector, references.length);
|
|
1162
|
+
references.sort((left, right) => left.getSourceFile().getFilePath().localeCompare(right.getSourceFile().getFilePath()) ||
|
|
1163
|
+
left.getStart() - right.getStart());
|
|
1164
|
+
const candidates = createScopedCandidateSet(collector);
|
|
1165
|
+
for (const reference of references) {
|
|
1166
|
+
const site = scopedInvocationForReference(reference);
|
|
1167
|
+
if (!site) {
|
|
1168
|
+
if (scopedReferenceFlowsToUncertainCall(state, collector, reference)) {
|
|
1169
|
+
collector.unfinished = true;
|
|
1170
|
+
}
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const authority = computedCallAuthority(state.projectRoot, site.invocation);
|
|
1174
|
+
reserveScopedWork(state, collector, authority.workItems);
|
|
1175
|
+
if (authority.state === "unfinished") {
|
|
1176
|
+
collector.semanticUnfinished = true;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
const source = scopedContainingSymbol(state, collector, reference.getSourceFile(), site.invocation);
|
|
1180
|
+
const resolved = site.direct
|
|
1181
|
+
? scopedDirectCallTarget(state, collector, site.invocation)
|
|
1182
|
+
: undefined;
|
|
1183
|
+
if (!source || !resolved || resolved.node !== target.node) {
|
|
1184
|
+
collector.unfinished = true;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
candidates.push(createScopedEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "call"));
|
|
1188
|
+
}
|
|
1189
|
+
addScopedCandidates(collector, candidates);
|
|
1190
|
+
}
|
|
1191
|
+
function isNestedNamedCallOwner(node) {
|
|
1192
|
+
if (Node.isFunctionDeclaration(node) ||
|
|
1193
|
+
Node.isClassDeclaration(node) ||
|
|
1194
|
+
Node.isMethodDeclaration(node) ||
|
|
1195
|
+
Node.isConstructorDeclaration(node) ||
|
|
1196
|
+
Node.isGetAccessorDeclaration(node) ||
|
|
1197
|
+
Node.isSetAccessorDeclaration(node)) {
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
if (!Node.isArrowFunction(node) && !Node.isFunctionExpression(node))
|
|
1201
|
+
return false;
|
|
1202
|
+
const parent = node.getParent();
|
|
1203
|
+
return Boolean(parent &&
|
|
1204
|
+
(Node.isVariableDeclaration(parent) ||
|
|
1205
|
+
Node.isPropertyDeclaration(parent) ||
|
|
1206
|
+
Node.isPropertyAssignment(parent)));
|
|
1207
|
+
}
|
|
1208
|
+
function addScopedOutgoingCalls(state, collector, source) {
|
|
1209
|
+
const candidates = createScopedCandidateSet(collector);
|
|
1210
|
+
source.node.forEachDescendant((node, traversal) => {
|
|
1211
|
+
consumeScopedWork(state, collector);
|
|
1212
|
+
if (isNestedNamedCallOwner(node)) {
|
|
1213
|
+
traversal.skip();
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (!callLikeExpression(node))
|
|
1217
|
+
return;
|
|
1218
|
+
const authority = computedCallAuthority(state.projectRoot, node);
|
|
1219
|
+
reserveScopedWork(state, collector, authority.workItems);
|
|
1220
|
+
if (authority.state === "unfinished") {
|
|
1221
|
+
collector.semanticUnfinished = true;
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const target = scopedDirectCallTarget(state, collector, node);
|
|
1225
|
+
if (!target) {
|
|
1226
|
+
collector.unfinished = true;
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
candidates.push(createScopedEdge(state, symbolEndpoint(source, state.projectRoot), symbolEndpoint(target, state.projectRoot), "call"));
|
|
1230
|
+
});
|
|
1231
|
+
addScopedCandidates(collector, candidates);
|
|
1232
|
+
}
|
|
948
1233
|
function endpointKey(endpoint) {
|
|
949
1234
|
return `${endpoint.file}\u0000${endpoint.symbol_path}\u0000${endpoint.selector}`;
|
|
950
1235
|
}
|
|
@@ -966,6 +1251,45 @@ function addScopedIncomingModuleEdges(state, edges) {
|
|
|
966
1251
|
addScopedModuleEdges(state, edges, sourceFile);
|
|
967
1252
|
}
|
|
968
1253
|
}
|
|
1254
|
+
function requestedCoverageDirections(direction) {
|
|
1255
|
+
return direction === "both" ? ["incoming", "outgoing"] : [direction];
|
|
1256
|
+
}
|
|
1257
|
+
function initialRelationshipCoverage(kinds, direction, endpointClass, node) {
|
|
1258
|
+
return canonicalRelationshipCoverage([...kinds].flatMap((kind) => requestedCoverageDirections(direction).map((coverageDirection) => ({
|
|
1259
|
+
kind,
|
|
1260
|
+
direction: coverageDirection,
|
|
1261
|
+
endpoint_class: endpointClass,
|
|
1262
|
+
status: relationshipCoverageApplicability(kind, coverageDirection, endpointClass, node)
|
|
1263
|
+
? "unsupported"
|
|
1264
|
+
: "not_applicable",
|
|
1265
|
+
}))));
|
|
1266
|
+
}
|
|
1267
|
+
function relationshipWork(budget, maxWorkItems) {
|
|
1268
|
+
return {
|
|
1269
|
+
work_items: budget.consumed,
|
|
1270
|
+
max_work_items: maxWorkItems,
|
|
1271
|
+
work_limit_reached: budget.limitReached,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function hasScopedRelationshipProducer(entry) {
|
|
1275
|
+
if (entry.kind === "contains")
|
|
1276
|
+
return false;
|
|
1277
|
+
if (entry.kind === "call")
|
|
1278
|
+
return entry.endpoint_class === "symbol";
|
|
1279
|
+
if (entry.endpoint_class === "module") {
|
|
1280
|
+
return entry.kind === "import" || entry.kind === "export";
|
|
1281
|
+
}
|
|
1282
|
+
if (entry.kind === "reference")
|
|
1283
|
+
return true;
|
|
1284
|
+
if (entry.kind === "import" || entry.kind === "export")
|
|
1285
|
+
return entry.direction === "incoming";
|
|
1286
|
+
return entry.kind === "extends" || entry.kind === "implements";
|
|
1287
|
+
}
|
|
1288
|
+
function resolveProducerCoverage(coverage, interrupted) {
|
|
1289
|
+
return coverage.map((entry) => entry.status === "unsupported" && hasScopedRelationshipProducer(entry)
|
|
1290
|
+
? { ...entry, status: interrupted ? "unfinished" : "completed" }
|
|
1291
|
+
: entry);
|
|
1292
|
+
}
|
|
969
1293
|
export function createCompilerRelationshipResolver(project, projectRoot, freshness, requestContext = NO_REQUEST_CONTEXT) {
|
|
970
1294
|
requestContext.checkpoint();
|
|
971
1295
|
const state = {
|
|
@@ -987,6 +1311,8 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
987
1311
|
}
|
|
988
1312
|
const direction = assertEnum(query.direction, ["incoming", "outgoing", "both"], "direction");
|
|
989
1313
|
const relationshipKinds = new Set(query.relationship_kinds.map((kind) => assertEnum(kind, RELATIONSHIP_EDGE_KINDS, "edge kind")));
|
|
1314
|
+
const endpointClass = endpoint.symbol_path === "<module>" ? "module" : "symbol";
|
|
1315
|
+
const initialCoverage = initialRelationshipCoverage(relationshipKinds, direction, endpointClass);
|
|
990
1316
|
const workBudget = {
|
|
991
1317
|
remaining: maxWorkItems,
|
|
992
1318
|
consumed: 0,
|
|
@@ -1007,6 +1333,8 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1007
1333
|
throw error;
|
|
1008
1334
|
return {
|
|
1009
1335
|
edges: [],
|
|
1336
|
+
coverage: resolveProducerCoverage(initialCoverage, true),
|
|
1337
|
+
work: relationshipWork(workBudget, maxWorkItems),
|
|
1010
1338
|
incomplete: true,
|
|
1011
1339
|
work_items: workBudget.consumed,
|
|
1012
1340
|
work_limit_reached: true,
|
|
@@ -1028,6 +1356,7 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1028
1356
|
excludedRelationshipIds,
|
|
1029
1357
|
workBudget,
|
|
1030
1358
|
excludedNeighbors: false,
|
|
1359
|
+
unfinished: false,
|
|
1031
1360
|
};
|
|
1032
1361
|
let located;
|
|
1033
1362
|
try {
|
|
@@ -1038,16 +1367,37 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1038
1367
|
throw error;
|
|
1039
1368
|
return {
|
|
1040
1369
|
edges: [],
|
|
1370
|
+
coverage: resolveProducerCoverage(initialCoverage, true),
|
|
1371
|
+
work: relationshipWork(workBudget, maxWorkItems),
|
|
1041
1372
|
incomplete: true,
|
|
1042
1373
|
work_items: workBudget.consumed,
|
|
1043
1374
|
work_limit_reached: true,
|
|
1044
1375
|
excluded_neighbors: false,
|
|
1045
1376
|
};
|
|
1046
1377
|
}
|
|
1378
|
+
const coverage = initialRelationshipCoverage(relationshipKinds, direction, endpointClass, located.symbol?.node);
|
|
1047
1379
|
const outgoing = direction === "outgoing" || direction === "both";
|
|
1048
1380
|
const incoming = direction === "incoming" || direction === "both";
|
|
1049
1381
|
const producers = [];
|
|
1050
1382
|
if (located.symbol) {
|
|
1383
|
+
if (incoming &&
|
|
1384
|
+
relationshipKinds.has("call") &&
|
|
1385
|
+
relationshipCoverageApplicability("call", "incoming", "symbol", located.symbol.node)) {
|
|
1386
|
+
producers.push({
|
|
1387
|
+
kinds: ["call"],
|
|
1388
|
+
callDirection: "incoming",
|
|
1389
|
+
collect: (collector) => addScopedIncomingCalls(state, collector, located.symbol),
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
if (outgoing &&
|
|
1393
|
+
relationshipKinds.has("call") &&
|
|
1394
|
+
relationshipCoverageApplicability("call", "outgoing", "symbol", located.symbol.node)) {
|
|
1395
|
+
producers.push({
|
|
1396
|
+
kinds: ["call"],
|
|
1397
|
+
callDirection: "outgoing",
|
|
1398
|
+
collect: (collector) => addScopedOutgoingCalls(state, collector, located.symbol),
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1051
1401
|
if (outgoing && (relationshipKinds.has("extends") || relationshipKinds.has("implements"))) {
|
|
1052
1402
|
producers.push({
|
|
1053
1403
|
kinds: ["extends", "implements"],
|
|
@@ -1113,7 +1463,9 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1113
1463
|
}
|
|
1114
1464
|
}
|
|
1115
1465
|
const merged = new Map();
|
|
1466
|
+
const callCoverage = new Map(producers.flatMap((producer) => producer.callDirection ? [[producer.callDirection, "unfinished"]] : []));
|
|
1116
1467
|
let incomplete = false;
|
|
1468
|
+
let boundedIncomplete = false;
|
|
1117
1469
|
let excludedNeighbors = false;
|
|
1118
1470
|
for (const producer of producers) {
|
|
1119
1471
|
const collector = {
|
|
@@ -1128,7 +1480,9 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1128
1480
|
excludedRelationshipIds,
|
|
1129
1481
|
workBudget,
|
|
1130
1482
|
excludedNeighbors: false,
|
|
1483
|
+
unfinished: false,
|
|
1131
1484
|
};
|
|
1485
|
+
let interrupted = false;
|
|
1132
1486
|
try {
|
|
1133
1487
|
producer.collect(collector);
|
|
1134
1488
|
}
|
|
@@ -1137,8 +1491,18 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1137
1491
|
!(error instanceof ScopedRelationshipWorkLimitReached)) {
|
|
1138
1492
|
throw error;
|
|
1139
1493
|
}
|
|
1494
|
+
interrupted = true;
|
|
1495
|
+
boundedIncomplete = true;
|
|
1140
1496
|
incomplete = true;
|
|
1141
1497
|
}
|
|
1498
|
+
if (collector.unfinished)
|
|
1499
|
+
incomplete = true;
|
|
1500
|
+
if (producer.callDirection &&
|
|
1501
|
+
!interrupted &&
|
|
1502
|
+
!collector.unfinished &&
|
|
1503
|
+
!collector.semanticUnfinished) {
|
|
1504
|
+
callCoverage.set(producer.callDirection, "completed");
|
|
1505
|
+
}
|
|
1142
1506
|
for (const edge of collector.edges.values())
|
|
1143
1507
|
merged.set(edge.relationship_id, edge);
|
|
1144
1508
|
excludedNeighbors ||= collector.excludedNeighbors;
|
|
@@ -1146,11 +1510,18 @@ export function createCompilerRelationshipResolver(project, projectRoot, freshne
|
|
|
1146
1510
|
break;
|
|
1147
1511
|
}
|
|
1148
1512
|
const ordered = [...merged.values()].sort((left, right) => scopedNeighborOrder(key, left, right));
|
|
1149
|
-
if (ordered.length > query.max_edges)
|
|
1513
|
+
if (ordered.length > query.max_edges) {
|
|
1150
1514
|
incomplete = true;
|
|
1515
|
+
boundedIncomplete = true;
|
|
1516
|
+
}
|
|
1151
1517
|
const selected = ordered.slice(0, query.max_edges);
|
|
1518
|
+
const resolvedCoverage = resolveProducerCoverage(coverage, boundedIncomplete || workBudget.limitReached).map((entry) => entry.kind === "call" && callCoverage.has(entry.direction)
|
|
1519
|
+
? { ...entry, status: callCoverage.get(entry.direction) }
|
|
1520
|
+
: entry);
|
|
1152
1521
|
return {
|
|
1153
1522
|
edges: selected.sort((left, right) => left.relationship_id.localeCompare(right.relationship_id)),
|
|
1523
|
+
coverage: resolvedCoverage,
|
|
1524
|
+
work: relationshipWork(workBudget, maxWorkItems),
|
|
1154
1525
|
incomplete,
|
|
1155
1526
|
work_items: workBudget.consumed,
|
|
1156
1527
|
work_limit_reached: workBudget.limitReached,
|
|
@@ -1203,6 +1574,7 @@ export function collectCompilerCallRelationships(project, projectRoot, freshness
|
|
|
1203
1574
|
const normalizedFreshness = normalizeFreshness(freshness);
|
|
1204
1575
|
const checker = project.getTypeChecker();
|
|
1205
1576
|
const edges = new Map();
|
|
1577
|
+
const unfinishedGaps = [];
|
|
1206
1578
|
let workItems = 0;
|
|
1207
1579
|
let workLimitReached = false;
|
|
1208
1580
|
for (const sourceFile of project
|
|
@@ -1222,6 +1594,20 @@ export function collectCompilerCallRelationships(project, projectRoot, freshness
|
|
|
1222
1594
|
const caller = containingSymbol(sourceFile, node);
|
|
1223
1595
|
if (!caller)
|
|
1224
1596
|
return;
|
|
1597
|
+
const computedAuthority = computedCallAuthority(projectRoot, node);
|
|
1598
|
+
workItems += computedAuthority.workItems;
|
|
1599
|
+
if (workItems > maxWorkItems) {
|
|
1600
|
+
workLimitReached = true;
|
|
1601
|
+
traversal.stop();
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
if (computedAuthority.state === "unfinished") {
|
|
1605
|
+
unfinishedGaps.push({
|
|
1606
|
+
source: symbolEndpoint(caller, projectRoot),
|
|
1607
|
+
alternatives: computedAuthority.alternatives.map((target) => symbolEndpoint(target, projectRoot)),
|
|
1608
|
+
});
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1225
1611
|
const signature = checker.getResolvedSignature(node);
|
|
1226
1612
|
const implicitConstructSignatures = Node.isNewExpression(node)
|
|
1227
1613
|
? invoked.getType().getConstructSignatures()
|
|
@@ -1231,6 +1617,7 @@ export function collectCompilerCallRelationships(project, projectRoot, freshness
|
|
|
1231
1617
|
const signatureDeclaration = signature?.getDeclaration();
|
|
1232
1618
|
const declarations = declarationsForSymbol(invoked.getSymbol());
|
|
1233
1619
|
const targets = new Map();
|
|
1620
|
+
let unfinished = false;
|
|
1234
1621
|
for (const declaration of declarations.length > 0
|
|
1235
1622
|
? declarations
|
|
1236
1623
|
: signatureDeclaration
|
|
@@ -1238,10 +1625,18 @@ export function collectCompilerCallRelationships(project, projectRoot, freshness
|
|
|
1238
1625
|
: []) {
|
|
1239
1626
|
const target = locatedCallTarget(projectRoot, declaration);
|
|
1240
1627
|
if (target)
|
|
1241
|
-
targets.set(symbolEndpoint(target, projectRoot)
|
|
1628
|
+
targets.set(endpointKey(symbolEndpoint(target, projectRoot)), target);
|
|
1629
|
+
else
|
|
1630
|
+
unfinished = true;
|
|
1242
1631
|
}
|
|
1243
|
-
|
|
1632
|
+
// Dropping an unlocatable alternative does not establish a single exact target.
|
|
1633
|
+
if (unfinished || targets.size !== 1) {
|
|
1634
|
+
unfinishedGaps.push({
|
|
1635
|
+
source: symbolEndpoint(caller, projectRoot),
|
|
1636
|
+
alternatives: [...targets.values()].map((target) => symbolEndpoint(target, projectRoot)),
|
|
1637
|
+
});
|
|
1244
1638
|
return;
|
|
1639
|
+
}
|
|
1245
1640
|
const target = [...targets.values()][0];
|
|
1246
1641
|
const edge = createRelationshipEdge({
|
|
1247
1642
|
source: symbolEndpoint(caller, projectRoot),
|
|
@@ -1258,9 +1653,13 @@ export function collectCompilerCallRelationships(project, projectRoot, freshness
|
|
|
1258
1653
|
break;
|
|
1259
1654
|
}
|
|
1260
1655
|
const ordered = [...edges.values()].sort((left, right) => left.relationship_id.localeCompare(right.relationship_id));
|
|
1656
|
+
const boundedIncomplete = workLimitReached || ordered.length > maxEdges;
|
|
1657
|
+
unfinishedGaps.sort((left, right) => endpointKey(left.source).localeCompare(endpointKey(right.source)));
|
|
1261
1658
|
return Object.freeze({
|
|
1262
1659
|
edges: Object.freeze(ordered.slice(0, maxEdges)),
|
|
1263
|
-
|
|
1660
|
+
unfinished_gaps: Object.freeze(unfinishedGaps),
|
|
1661
|
+
bounded_incomplete: boundedIncomplete,
|
|
1662
|
+
incomplete: boundedIncomplete || unfinishedGaps.length > 0,
|
|
1264
1663
|
});
|
|
1265
1664
|
}
|
|
1266
1665
|
export function collectCompilerRelationships(project, projectRoot, freshness, requestContext = NO_REQUEST_CONTEXT) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import packageMetadata from "../../package.json" with { type: "json" };
|
|
2
3
|
export const RUNTIME_POLICY_ENV_KEYS = Object.freeze([
|
|
3
4
|
"AST_MAX_PROJECT_SESSIONS",
|
|
4
5
|
"AST_MAX_QUEUED_OPERATIONS_PER_PROJECT",
|
|
@@ -8,6 +9,7 @@ export const RUNTIME_POLICY_ENV_KEYS = Object.freeze([
|
|
|
8
9
|
"AST_COMPILER_WORKER_MODE",
|
|
9
10
|
"AST_COMPILER_WORKER_IDLE_TTL_MS",
|
|
10
11
|
"AST_MCP_APPLY_GUARD",
|
|
12
|
+
"AST_MCP_TEXT_PROJECTION",
|
|
11
13
|
]);
|
|
12
14
|
function parseBoundedInteger(rawValue, defaultValue, minimum, maximum) {
|
|
13
15
|
if (rawValue === undefined)
|
|
@@ -27,8 +29,8 @@ function parseBoundedInteger(rawValue, defaultValue, minimum, maximum) {
|
|
|
27
29
|
export function parseRuntimePolicy(environment) {
|
|
28
30
|
const maxProjectSessions = parseBoundedInteger(environment.AST_MAX_PROJECT_SESSIONS, 8, 1, 32);
|
|
29
31
|
const maxQueuedOperationsPerProject = parseBoundedInteger(environment.AST_MAX_QUEUED_OPERATIONS_PER_PROJECT, 32, 1, 256);
|
|
30
|
-
const queueWaitTimeoutMs = parseBoundedInteger(environment.AST_QUEUE_WAIT_TIMEOUT_MS,
|
|
31
|
-
const operationDeadlineMs = parseBoundedInteger(environment.AST_OPERATION_DEADLINE_MS,
|
|
32
|
+
const queueWaitTimeoutMs = parseBoundedInteger(environment.AST_QUEUE_WAIT_TIMEOUT_MS, packageMetadata.deepseekHarness.timeoutBudget.queueWaitMs, 100, 300_000);
|
|
33
|
+
const operationDeadlineMs = parseBoundedInteger(environment.AST_OPERATION_DEADLINE_MS, packageMetadata.deepseekHarness.timeoutBudget.executionDeadlineMs, 1_000, 900_000);
|
|
32
34
|
const shutdownDrainTimeoutMs = parseBoundedInteger(environment.AST_SHUTDOWN_DRAIN_TIMEOUT_MS, 10_000, 100, 60_000);
|
|
33
35
|
const rawWorkerMode = environment.AST_COMPILER_WORKER_MODE;
|
|
34
36
|
const compilerWorkerMode = rawWorkerMode === "supervised" ? "supervised" : "in_process";
|
|
@@ -47,6 +49,13 @@ export function parseRuntimePolicy(environment) {
|
|
|
47
49
|
: rawApplyGuard === "allow" || rawApplyGuard === "deny"
|
|
48
50
|
? "configured"
|
|
49
51
|
: "invalid_mode";
|
|
52
|
+
const rawTextProjection = environment.AST_MCP_TEXT_PROJECTION;
|
|
53
|
+
const projectStructuredContentAsText = rawTextProjection === "canonical_json";
|
|
54
|
+
const textProjectionReason = rawTextProjection === undefined
|
|
55
|
+
? "default"
|
|
56
|
+
: projectStructuredContentAsText
|
|
57
|
+
? "configured"
|
|
58
|
+
: "invalid_mode";
|
|
50
59
|
const reasons = Object.freeze({
|
|
51
60
|
AST_MAX_PROJECT_SESSIONS: maxProjectSessions.reason,
|
|
52
61
|
AST_MAX_QUEUED_OPERATIONS_PER_PROJECT: maxQueuedOperationsPerProject.reason,
|
|
@@ -56,6 +65,7 @@ export function parseRuntimePolicy(environment) {
|
|
|
56
65
|
AST_COMPILER_WORKER_MODE: compilerWorkerModeReason,
|
|
57
66
|
AST_COMPILER_WORKER_IDLE_TTL_MS: compilerWorkerIdleTtlMs.reason,
|
|
58
67
|
AST_MCP_APPLY_GUARD: applyGuardReason,
|
|
68
|
+
AST_MCP_TEXT_PROJECTION: textProjectionReason,
|
|
59
69
|
});
|
|
60
70
|
return Object.freeze({
|
|
61
71
|
maxProjectSessions: maxProjectSessions.value,
|
|
@@ -66,6 +76,7 @@ export function parseRuntimePolicy(environment) {
|
|
|
66
76
|
compilerWorkerMode,
|
|
67
77
|
compilerWorkerIdleTtlMs: compilerWorkerIdleTtlMs.value,
|
|
68
78
|
denyApply,
|
|
79
|
+
projectStructuredContentAsText,
|
|
69
80
|
reasons,
|
|
70
81
|
});
|
|
71
82
|
}
|
|
@@ -76,6 +87,46 @@ const WORKER_ENV_KEYS = [
|
|
|
76
87
|
"AST_SYMBOL_INDEX_BUSY_TIMEOUT_MS",
|
|
77
88
|
];
|
|
78
89
|
const MAX_WORKER_ENV_VALUE_LENGTH = 4096;
|
|
90
|
+
function parseClosedFixtureRecord(raw, expectedKeys) {
|
|
91
|
+
if (typeof raw !== "string" || raw.length > 1024 || raw.includes("\0"))
|
|
92
|
+
return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const value = JSON.parse(raw);
|
|
95
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
96
|
+
return undefined;
|
|
97
|
+
const record = value;
|
|
98
|
+
return Object.keys(record).sort().join(",") === expectedKeys ? record : undefined;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function parseH03FixtureDescriptor(raw) {
|
|
105
|
+
const record = parseClosedFixtureRecord(raw, "controlDirectory,generation,nonce");
|
|
106
|
+
if (!record)
|
|
107
|
+
return undefined;
|
|
108
|
+
const { controlDirectory, nonce, generation } = record;
|
|
109
|
+
if (typeof controlDirectory !== "string" ||
|
|
110
|
+
controlDirectory.length > MAX_WORKER_ENV_VALUE_LENGTH ||
|
|
111
|
+
!path.isAbsolute(controlDirectory) ||
|
|
112
|
+
path.normalize(controlDirectory) !== controlDirectory ||
|
|
113
|
+
typeof nonce !== "string" ||
|
|
114
|
+
!/^[A-Za-z0-9_-]{16,64}$/.test(nonce) ||
|
|
115
|
+
!Number.isSafeInteger(generation) ||
|
|
116
|
+
generation < 1)
|
|
117
|
+
return undefined;
|
|
118
|
+
return Object.freeze({ controlDirectory, nonce, generation: generation });
|
|
119
|
+
}
|
|
120
|
+
export function parseH05FixtureDescriptor(raw) {
|
|
121
|
+
const record = parseClosedFixtureRecord(raw, "controlDirectory,generation,nonce,ownerToken");
|
|
122
|
+
if (!record)
|
|
123
|
+
return undefined;
|
|
124
|
+
const { ownerToken, ...baseValue } = record;
|
|
125
|
+
if (typeof ownerToken !== "string" || !/^[A-Za-z0-9_-]{16,64}$/.test(ownerToken))
|
|
126
|
+
return undefined;
|
|
127
|
+
const base = parseH03FixtureDescriptor(JSON.stringify(baseValue));
|
|
128
|
+
return base ? Object.freeze({ ...base, ownerToken }) : undefined;
|
|
129
|
+
}
|
|
79
130
|
function validEnvironmentString(value) {
|
|
80
131
|
return (typeof value === "string" &&
|
|
81
132
|
value.length <= MAX_WORKER_ENV_VALUE_LENGTH &&
|
|
@@ -100,6 +151,20 @@ export function createCompilerWorkerSpawnSpec(workerEntryPath, environment) {
|
|
|
100
151
|
}
|
|
101
152
|
projected[key] = value;
|
|
102
153
|
}
|
|
154
|
+
const fixture = environment.AST_H03_FIXTURE;
|
|
155
|
+
if (fixture !== undefined) {
|
|
156
|
+
const descriptor = parseH03FixtureDescriptor(fixture);
|
|
157
|
+
if (!descriptor)
|
|
158
|
+
return { ok: false, reason: "invalid_environment" };
|
|
159
|
+
projected.AST_H03_FIXTURE = JSON.stringify(descriptor);
|
|
160
|
+
}
|
|
161
|
+
const lifecycleFixture = environment.AST_H05_FIXTURE;
|
|
162
|
+
if (lifecycleFixture !== undefined) {
|
|
163
|
+
const descriptor = parseH05FixtureDescriptor(lifecycleFixture);
|
|
164
|
+
if (!descriptor)
|
|
165
|
+
return { ok: false, reason: "invalid_environment" };
|
|
166
|
+
projected.AST_H05_FIXTURE = JSON.stringify(descriptor);
|
|
167
|
+
}
|
|
103
168
|
const policy = parseRuntimePolicy(environment);
|
|
104
169
|
Object.assign(projected, {
|
|
105
170
|
AST_MAX_PROJECT_SESSIONS: String(policy.maxProjectSessions),
|
|
@@ -110,6 +175,7 @@ export function createCompilerWorkerSpawnSpec(workerEntryPath, environment) {
|
|
|
110
175
|
// Forward the parent's resolved apply-guard policy so the supervised worker
|
|
111
176
|
// registers the same tool surface (deny-by-default fail-closed, allow opt-in).
|
|
112
177
|
AST_MCP_APPLY_GUARD: policy.denyApply ? "deny" : "allow",
|
|
178
|
+
...(policy.projectStructuredContentAsText ? { AST_MCP_TEXT_PROJECTION: "canonical_json" } : {}),
|
|
113
179
|
});
|
|
114
180
|
return {
|
|
115
181
|
ok: true,
|