context-slice 1.8.2
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/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/src/cli.js +199 -0
- package/dist/src/indexer/index.js +325 -0
- package/dist/src/languages/adapter.js +23 -0
- package/dist/src/languages/go/index.js +15 -0
- package/dist/src/languages/go/parse.js +443 -0
- package/dist/src/languages/go/resolve.js +313 -0
- package/dist/src/languages/java/enterprise/dependency-injection.js +175 -0
- package/dist/src/languages/java/enterprise/jpa-entity.js +89 -0
- package/dist/src/languages/java/enterprise/registry.js +22 -0
- package/dist/src/languages/java/enterprise/spring-data.js +239 -0
- package/dist/src/languages/java/enterprise/spring-mvc.js +183 -0
- package/dist/src/languages/java/enterprise/transactions.js +110 -0
- package/dist/src/languages/java.js +84 -0
- package/dist/src/languages/javascript/index.js +25 -0
- package/dist/src/languages/python/index.js +29 -0
- package/dist/src/languages/python/parse.js +415 -0
- package/dist/src/languages/python/resolve.js +413 -0
- package/dist/src/languages/rust/calls-resolve.js +1405 -0
- package/dist/src/languages/rust/index.js +12 -0
- package/dist/src/languages/rust/parse.js +545 -0
- package/dist/src/languages/rust/resolve.js +284 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/languages/typescript/parse.js +793 -0
- package/dist/src/languages/typescript/resolve.js +463 -0
- package/dist/src/package-info.js +16 -0
- package/dist/src/parser/java-parser.js +339 -0
- package/dist/src/planner/budget.js +1 -0
- package/dist/src/planner/composition.js +372 -0
- package/dist/src/planner/rank.js +10 -0
- package/dist/src/render/compact-context.js +11 -0
- package/dist/src/server/mcp-server.js +154 -0
- package/dist/src/storage/sqlite.js +105 -0
- package/dist/src/types/enterprise.js +1 -0
- package/dist/src/types/model.js +1 -0
- package/dist/src/workflow/errors.js +10 -0
- package/dist/src/workflow/preview.js +220 -0
- package/dist/src/workflow/repository.js +56 -0
- package/mcp.json +10 -0
- package/package.json +76 -0
- package/plugin.json +15 -0
- package/queries/java/annotations.scm +1 -0
- package/queries/java/calls.scm +1 -0
- package/queries/java/imports.scm +1 -0
- package/queries/java/symbols.scm +2 -0
- package/skills/context-slice/SKILL.md +60 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import Parser from "tree-sitter";
|
|
2
|
+
import Python from "tree-sitter-python";
|
|
3
|
+
export const LANGUAGE_ID = "python";
|
|
4
|
+
let parser;
|
|
5
|
+
function pythonParser() {
|
|
6
|
+
if (!parser) {
|
|
7
|
+
parser = new Parser();
|
|
8
|
+
parser.setLanguage(Python);
|
|
9
|
+
}
|
|
10
|
+
return parser;
|
|
11
|
+
}
|
|
12
|
+
const range = (node) => ({
|
|
13
|
+
startLine: node.startPosition.row + 1,
|
|
14
|
+
startColumn: node.startPosition.column,
|
|
15
|
+
endLine: node.endPosition.row + 1,
|
|
16
|
+
endColumn: node.endPosition.column,
|
|
17
|
+
});
|
|
18
|
+
const field = (node, name) => node.childForFieldName(name);
|
|
19
|
+
const text = (node) => node?.text ?? "";
|
|
20
|
+
/** `src/orders/service.py::OrderService::method::create(self, order)` */
|
|
21
|
+
function canonicalId(filePath, chain, kind, name, parameters) {
|
|
22
|
+
return [
|
|
23
|
+
filePath,
|
|
24
|
+
...chain,
|
|
25
|
+
kind,
|
|
26
|
+
parameters === undefined ? name : `${name}(${parameters})`,
|
|
27
|
+
].join("::");
|
|
28
|
+
}
|
|
29
|
+
/** Parameter names only: an added annotation must not change a symbol's identity. */
|
|
30
|
+
function parameterNames(parameters) {
|
|
31
|
+
if (!parameters)
|
|
32
|
+
return "";
|
|
33
|
+
return parameters.namedChildren
|
|
34
|
+
.map((parameter) => {
|
|
35
|
+
if (parameter.type === "identifier")
|
|
36
|
+
return parameter.text;
|
|
37
|
+
const name = field(parameter, "name") ?? parameter.namedChild(0);
|
|
38
|
+
return text(name);
|
|
39
|
+
})
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(", ");
|
|
42
|
+
}
|
|
43
|
+
function parameterSignature(parameters) {
|
|
44
|
+
return parameters ? parameters.text.replace(/^\(|\)$/g, "") : "";
|
|
45
|
+
}
|
|
46
|
+
const decoratorNames = (node) => node.parent?.type === "decorated_definition"
|
|
47
|
+
? node.parent.namedChildren
|
|
48
|
+
.filter((child) => child.type === "decorator")
|
|
49
|
+
.map((child) => child.text.replace(/^@/, "").trim())
|
|
50
|
+
: [];
|
|
51
|
+
const isAsync = (node) => node.children.some((child) => child.type === "async");
|
|
52
|
+
export function parsePython(filePath, source) {
|
|
53
|
+
const symbols = [];
|
|
54
|
+
const calls = [];
|
|
55
|
+
const imports = [];
|
|
56
|
+
const exports = [];
|
|
57
|
+
let parseError = false;
|
|
58
|
+
let tree;
|
|
59
|
+
try {
|
|
60
|
+
// The node binding rejects inputs of 32KB or more, so feed it in chunks.
|
|
61
|
+
tree = pythonParser().parse((index) => source.slice(index, index + 4_096));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { symbols, calls, imports, exports, parseError: true };
|
|
65
|
+
}
|
|
66
|
+
if (tree.rootNode.hasError)
|
|
67
|
+
parseError = true;
|
|
68
|
+
let moduleSymbol;
|
|
69
|
+
/** Python runs real code at module level; those calls need an owner. */
|
|
70
|
+
const moduleOwner = () => {
|
|
71
|
+
if (!moduleSymbol) {
|
|
72
|
+
moduleSymbol = {
|
|
73
|
+
id: `${filePath}::module::${filePath}`,
|
|
74
|
+
language: LANGUAGE_ID,
|
|
75
|
+
kind: "namespace",
|
|
76
|
+
name: filePath,
|
|
77
|
+
qualifiedName: filePath,
|
|
78
|
+
canonicalIdentity: `${filePath}::module::${filePath}`,
|
|
79
|
+
signature: `module ${filePath}`,
|
|
80
|
+
filePath,
|
|
81
|
+
range: range(tree.rootNode),
|
|
82
|
+
annotations: [],
|
|
83
|
+
modifiers: [],
|
|
84
|
+
metadata: { moduleScope: true },
|
|
85
|
+
source: "",
|
|
86
|
+
};
|
|
87
|
+
symbols.push(moduleSymbol);
|
|
88
|
+
}
|
|
89
|
+
return moduleSymbol;
|
|
90
|
+
};
|
|
91
|
+
const addSymbol = (node, kind, name, chain, options = {}) => {
|
|
92
|
+
const id = canonicalId(filePath, chain, kind, name, options.parameters);
|
|
93
|
+
const body = options.bodyNode ?? undefined;
|
|
94
|
+
const symbol = {
|
|
95
|
+
id,
|
|
96
|
+
language: LANGUAGE_ID,
|
|
97
|
+
kind,
|
|
98
|
+
name,
|
|
99
|
+
qualifiedName: [...chain, name].join("."),
|
|
100
|
+
canonicalIdentity: id,
|
|
101
|
+
signature: options.signature ?? `${kind} ${name}`,
|
|
102
|
+
filePath,
|
|
103
|
+
range: range(node),
|
|
104
|
+
bodyRange: body ? range(body) : undefined,
|
|
105
|
+
parentId: options.parentId,
|
|
106
|
+
supertypes: options.supertypes,
|
|
107
|
+
annotations: options.annotations ?? [],
|
|
108
|
+
modifiers: [],
|
|
109
|
+
metadata: options.metadata,
|
|
110
|
+
source: node.text,
|
|
111
|
+
body: body?.text,
|
|
112
|
+
};
|
|
113
|
+
symbols.push(symbol);
|
|
114
|
+
return symbol;
|
|
115
|
+
};
|
|
116
|
+
// ---- imports -------------------------------------------------------------
|
|
117
|
+
const importStatement = (node) => {
|
|
118
|
+
const base = {
|
|
119
|
+
filePath,
|
|
120
|
+
language: LANGUAGE_ID,
|
|
121
|
+
typeOnly: false,
|
|
122
|
+
range: range(node),
|
|
123
|
+
};
|
|
124
|
+
if (node.type === "import_statement") {
|
|
125
|
+
for (const child of node.namedChildren) {
|
|
126
|
+
if (child.type === "dotted_name")
|
|
127
|
+
imports.push({
|
|
128
|
+
...base,
|
|
129
|
+
module: child.text,
|
|
130
|
+
kind: "namespace",
|
|
131
|
+
localName: child.text.split(".")[0],
|
|
132
|
+
});
|
|
133
|
+
else if (child.type === "aliased_import")
|
|
134
|
+
imports.push({
|
|
135
|
+
...base,
|
|
136
|
+
module: text(field(child, "name")),
|
|
137
|
+
kind: "namespace",
|
|
138
|
+
localName: text(field(child, "alias")),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const moduleNode = field(node, "module_name");
|
|
144
|
+
const relativeLevel = moduleNode?.type === "relative_import"
|
|
145
|
+
? (moduleNode.text.match(/^\.+/)?.[0].length ?? 0)
|
|
146
|
+
: 0;
|
|
147
|
+
const module = text(moduleNode);
|
|
148
|
+
const wildcard = node.namedChildren.some((child) => child.type === "wildcard_import");
|
|
149
|
+
if (wildcard) {
|
|
150
|
+
exports.push({
|
|
151
|
+
...base,
|
|
152
|
+
exportedName: "*",
|
|
153
|
+
fromModule: module,
|
|
154
|
+
wildcard: true,
|
|
155
|
+
});
|
|
156
|
+
imports.push({ ...base, module, kind: "side-effect" });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const child of node.namedChildren) {
|
|
160
|
+
if (child === moduleNode)
|
|
161
|
+
continue;
|
|
162
|
+
if (child.type === "dotted_name")
|
|
163
|
+
imports.push({
|
|
164
|
+
...base,
|
|
165
|
+
module,
|
|
166
|
+
importedName: child.text,
|
|
167
|
+
localName: child.text,
|
|
168
|
+
kind: "named",
|
|
169
|
+
});
|
|
170
|
+
else if (child.type === "aliased_import")
|
|
171
|
+
imports.push({
|
|
172
|
+
...base,
|
|
173
|
+
module,
|
|
174
|
+
importedName: text(field(child, "name")),
|
|
175
|
+
localName: text(field(child, "alias")),
|
|
176
|
+
kind: "named",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
void relativeLevel;
|
|
180
|
+
};
|
|
181
|
+
// ---- calls ---------------------------------------------------------------
|
|
182
|
+
const addCall = (node, owner) => {
|
|
183
|
+
const callee = field(node, "function");
|
|
184
|
+
if (!callee)
|
|
185
|
+
return;
|
|
186
|
+
const argumentsNode = field(node, "arguments");
|
|
187
|
+
const argumentCount = argumentsNode
|
|
188
|
+
? argumentsNode.namedChildren.length
|
|
189
|
+
: 0;
|
|
190
|
+
const shared = {
|
|
191
|
+
callerId: owner.id,
|
|
192
|
+
argumentCount,
|
|
193
|
+
filePath,
|
|
194
|
+
language: LANGUAGE_ID,
|
|
195
|
+
range: range(node),
|
|
196
|
+
confidence: "unresolved",
|
|
197
|
+
evidence: [],
|
|
198
|
+
};
|
|
199
|
+
if (callee.type === "identifier") {
|
|
200
|
+
calls.push({
|
|
201
|
+
...shared,
|
|
202
|
+
calleeName: callee.text,
|
|
203
|
+
resolutionKind: /^[A-Z]/.test(callee.text)
|
|
204
|
+
? "constructor"
|
|
205
|
+
: "unresolved",
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (callee.type === "attribute") {
|
|
210
|
+
const object = field(callee, "object");
|
|
211
|
+
const attribute = field(callee, "attribute");
|
|
212
|
+
if (!attribute)
|
|
213
|
+
return;
|
|
214
|
+
// `self.repo.save()` keeps the whole receiver path for field typing.
|
|
215
|
+
const receiverText = object?.type === "identifier"
|
|
216
|
+
? object.text
|
|
217
|
+
: object?.type === "attribute" &&
|
|
218
|
+
field(object, "object")?.type === "identifier"
|
|
219
|
+
? `${text(field(object, "object"))}.${text(field(object, "attribute"))}`
|
|
220
|
+
: undefined;
|
|
221
|
+
calls.push({
|
|
222
|
+
...shared,
|
|
223
|
+
calleeName: attribute.text,
|
|
224
|
+
receiverText,
|
|
225
|
+
resolutionKind: "unresolved",
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
// ---- traversal -----------------------------------------------------------
|
|
230
|
+
const walk = (node, owner, chain) => {
|
|
231
|
+
for (const child of node.namedChildren)
|
|
232
|
+
visit(child, owner, chain);
|
|
233
|
+
};
|
|
234
|
+
function classBody(body, chain, parent) {
|
|
235
|
+
if (!body)
|
|
236
|
+
return;
|
|
237
|
+
for (const statement of body.namedChildren) {
|
|
238
|
+
const definition = statement.type === "decorated_definition"
|
|
239
|
+
? field(statement, "definition")
|
|
240
|
+
: statement;
|
|
241
|
+
if (definition?.type === "function_definition") {
|
|
242
|
+
const name = text(field(definition, "name"));
|
|
243
|
+
const parameters = field(definition, "parameters");
|
|
244
|
+
const decorators = decoratorNames(definition);
|
|
245
|
+
const accessor = decorators.includes("property")
|
|
246
|
+
? "getter"
|
|
247
|
+
: decorators.some((decorator) => decorator.endsWith(".setter"))
|
|
248
|
+
? "setter"
|
|
249
|
+
: undefined;
|
|
250
|
+
const kind = accessor ?? (name === "__init__" ? "constructor" : "method");
|
|
251
|
+
const returns = text(field(definition, "return_type"));
|
|
252
|
+
const symbol = addSymbol(definition, kind, name, chain, {
|
|
253
|
+
parameters: parameterNames(parameters),
|
|
254
|
+
signature: `${name}(${parameterSignature(parameters)})${returns ? ` -> ${returns}` : ""}`,
|
|
255
|
+
parentId: parent.id,
|
|
256
|
+
annotations: decorators.map((decorator) => `@${decorator}`),
|
|
257
|
+
bodyNode: field(definition, "body"),
|
|
258
|
+
metadata: {
|
|
259
|
+
async: isAsync(definition),
|
|
260
|
+
classMethod: decorators.includes("classmethod"),
|
|
261
|
+
staticMethod: decorators.includes("staticmethod"),
|
|
262
|
+
overloadSignature: decorators.some((decorator) => /(^|\.)overload$/.test(decorator)),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
walk(field(definition, "body") ?? definition, symbol, [...chain, name]);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
// Class-level attributes, including dataclass fields.
|
|
269
|
+
const assignment = statement.type === "expression_statement"
|
|
270
|
+
? statement.namedChild(0)
|
|
271
|
+
: undefined;
|
|
272
|
+
if (assignment?.type === "assignment") {
|
|
273
|
+
const left = field(assignment, "left");
|
|
274
|
+
if (left?.type === "identifier")
|
|
275
|
+
addSymbol(assignment, "property", left.text, chain, {
|
|
276
|
+
parentId: parent.id,
|
|
277
|
+
signature: assignment.text.split("\n")[0],
|
|
278
|
+
metadata: {
|
|
279
|
+
declaredType: text(field(assignment, "type")).trim() || undefined,
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function visit(node, owner, chain) {
|
|
286
|
+
switch (node.type) {
|
|
287
|
+
case "import_statement":
|
|
288
|
+
case "import_from_statement":
|
|
289
|
+
importStatement(node);
|
|
290
|
+
return;
|
|
291
|
+
case "decorated_definition":
|
|
292
|
+
visit(field(node, "definition") ?? node, owner, chain);
|
|
293
|
+
return;
|
|
294
|
+
case "class_definition": {
|
|
295
|
+
const name = text(field(node, "name"));
|
|
296
|
+
const bases = field(node, "superclasses");
|
|
297
|
+
const symbol = addSymbol(node, "class", name, chain, {
|
|
298
|
+
signature: `class ${name}${bases ? bases.text : ""}`,
|
|
299
|
+
supertypes: bases ? bases.namedChildren.map((base) => base.text) : [],
|
|
300
|
+
annotations: decoratorNames(node).map((decorator) => `@${decorator}`),
|
|
301
|
+
bodyNode: field(node, "body"),
|
|
302
|
+
});
|
|
303
|
+
classBody(field(node, "body"), [...chain, name], symbol);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case "function_definition": {
|
|
307
|
+
const name = text(field(node, "name"));
|
|
308
|
+
const parameters = field(node, "parameters");
|
|
309
|
+
const returns = text(field(node, "return_type"));
|
|
310
|
+
const symbol = addSymbol(node, "function", name, chain, {
|
|
311
|
+
parameters: parameterNames(parameters),
|
|
312
|
+
signature: `${name}(${parameterSignature(parameters)})${returns ? ` -> ${returns}` : ""}`,
|
|
313
|
+
parentId: owner?.id,
|
|
314
|
+
annotations: decoratorNames(node).map((decorator) => `@${decorator}`),
|
|
315
|
+
bodyNode: field(node, "body"),
|
|
316
|
+
metadata: { async: isAsync(node) },
|
|
317
|
+
});
|
|
318
|
+
walk(field(node, "body") ?? node, symbol, [...chain, name]);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
case "assignment": {
|
|
322
|
+
const left = field(node, "left");
|
|
323
|
+
const right = field(node, "right");
|
|
324
|
+
if (left?.type === "identifier" && right?.type === "lambda") {
|
|
325
|
+
const symbol = addSymbol(node, "function", left.text, chain, {
|
|
326
|
+
parameters: parameterNames(field(right, "parameters")),
|
|
327
|
+
signature: `${left.text} = lambda ${parameterSignature(field(right, "parameters"))}`,
|
|
328
|
+
parentId: owner?.id,
|
|
329
|
+
bodyNode: field(right, "body"),
|
|
330
|
+
});
|
|
331
|
+
walk(right, symbol, chain);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// `__all__` is export metadata when it is a plain list of strings.
|
|
335
|
+
if (left?.type === "identifier" && left.text === "__all__" && right)
|
|
336
|
+
for (const item of right.namedChildren)
|
|
337
|
+
if (item.type === "string")
|
|
338
|
+
exports.push({
|
|
339
|
+
filePath,
|
|
340
|
+
language: LANGUAGE_ID,
|
|
341
|
+
exportedName: item.text.replace(/^['"]|['"]$/g, ""),
|
|
342
|
+
localName: item.text.replace(/^['"]|['"]$/g, ""),
|
|
343
|
+
typeOnly: false,
|
|
344
|
+
range: range(node),
|
|
345
|
+
});
|
|
346
|
+
if (left?.type === "identifier" &&
|
|
347
|
+
left.text !== "__all__" &&
|
|
348
|
+
chain.length === 0 &&
|
|
349
|
+
!owner)
|
|
350
|
+
addSymbol(node, "variable", left.text, chain, {
|
|
351
|
+
signature: node.text.split("\n")[0],
|
|
352
|
+
});
|
|
353
|
+
walk(node, owner, chain);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
case "call":
|
|
357
|
+
addCall(node, owner ?? moduleOwner());
|
|
358
|
+
walk(node, owner, chain);
|
|
359
|
+
return;
|
|
360
|
+
default:
|
|
361
|
+
walk(node, owner, chain);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
walk(tree.rootNode, undefined, []);
|
|
365
|
+
// Two declarations can share a canonical identity (conditional defs, overloads).
|
|
366
|
+
const identityCounts = new Map();
|
|
367
|
+
for (const symbol of symbols) {
|
|
368
|
+
const identity = symbol.canonicalIdentity ?? symbol.id;
|
|
369
|
+
const count = identityCounts.get(identity) ?? 0;
|
|
370
|
+
if (count > 0) {
|
|
371
|
+
const previousId = symbol.id;
|
|
372
|
+
symbol.id = `${identity}#${count + 1}`;
|
|
373
|
+
for (const call of calls)
|
|
374
|
+
if (call.callerId === previousId)
|
|
375
|
+
call.callerId = symbol.id;
|
|
376
|
+
}
|
|
377
|
+
identityCounts.set(identity, count + 1);
|
|
378
|
+
}
|
|
379
|
+
// Module-level definitions are what other modules can import.
|
|
380
|
+
for (const symbol of symbols)
|
|
381
|
+
if (!symbol.parentId && symbol.kind !== "namespace")
|
|
382
|
+
exports.push({
|
|
383
|
+
filePath,
|
|
384
|
+
language: LANGUAGE_ID,
|
|
385
|
+
exportedName: symbol.name,
|
|
386
|
+
localName: symbol.name,
|
|
387
|
+
symbolId: symbol.id,
|
|
388
|
+
typeOnly: false,
|
|
389
|
+
range: symbol.range,
|
|
390
|
+
});
|
|
391
|
+
// `from .x import y` in a package re-exports y.
|
|
392
|
+
for (const record of imports)
|
|
393
|
+
if (record.kind === "named" && record.importedName)
|
|
394
|
+
exports.push({
|
|
395
|
+
filePath,
|
|
396
|
+
language: LANGUAGE_ID,
|
|
397
|
+
exportedName: record.localName ?? record.importedName,
|
|
398
|
+
sourceName: record.importedName,
|
|
399
|
+
fromModule: record.module,
|
|
400
|
+
typeOnly: false,
|
|
401
|
+
range: record.range,
|
|
402
|
+
});
|
|
403
|
+
if (moduleSymbol) {
|
|
404
|
+
const declarations = new Set([
|
|
405
|
+
"function_definition",
|
|
406
|
+
"class_definition",
|
|
407
|
+
"decorated_definition",
|
|
408
|
+
]);
|
|
409
|
+
moduleSymbol.source = tree.rootNode.namedChildren
|
|
410
|
+
.filter((child) => !declarations.has(child.type))
|
|
411
|
+
.map((child) => child.text)
|
|
412
|
+
.join("\n");
|
|
413
|
+
}
|
|
414
|
+
return { symbols, calls, imports, exports, parseError };
|
|
415
|
+
}
|