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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/dist/src/cli.js +199 -0
  4. package/dist/src/indexer/index.js +325 -0
  5. package/dist/src/languages/adapter.js +23 -0
  6. package/dist/src/languages/go/index.js +15 -0
  7. package/dist/src/languages/go/parse.js +443 -0
  8. package/dist/src/languages/go/resolve.js +313 -0
  9. package/dist/src/languages/java/enterprise/dependency-injection.js +175 -0
  10. package/dist/src/languages/java/enterprise/jpa-entity.js +89 -0
  11. package/dist/src/languages/java/enterprise/registry.js +22 -0
  12. package/dist/src/languages/java/enterprise/spring-data.js +239 -0
  13. package/dist/src/languages/java/enterprise/spring-mvc.js +183 -0
  14. package/dist/src/languages/java/enterprise/transactions.js +110 -0
  15. package/dist/src/languages/java.js +84 -0
  16. package/dist/src/languages/javascript/index.js +25 -0
  17. package/dist/src/languages/python/index.js +29 -0
  18. package/dist/src/languages/python/parse.js +415 -0
  19. package/dist/src/languages/python/resolve.js +413 -0
  20. package/dist/src/languages/rust/calls-resolve.js +1405 -0
  21. package/dist/src/languages/rust/index.js +12 -0
  22. package/dist/src/languages/rust/parse.js +545 -0
  23. package/dist/src/languages/rust/resolve.js +284 -0
  24. package/dist/src/languages/typescript/index.js +25 -0
  25. package/dist/src/languages/typescript/parse.js +793 -0
  26. package/dist/src/languages/typescript/resolve.js +463 -0
  27. package/dist/src/package-info.js +16 -0
  28. package/dist/src/parser/java-parser.js +339 -0
  29. package/dist/src/planner/budget.js +1 -0
  30. package/dist/src/planner/composition.js +372 -0
  31. package/dist/src/planner/rank.js +10 -0
  32. package/dist/src/render/compact-context.js +11 -0
  33. package/dist/src/server/mcp-server.js +154 -0
  34. package/dist/src/storage/sqlite.js +105 -0
  35. package/dist/src/types/enterprise.js +1 -0
  36. package/dist/src/types/model.js +1 -0
  37. package/dist/src/workflow/errors.js +10 -0
  38. package/dist/src/workflow/preview.js +220 -0
  39. package/dist/src/workflow/repository.js +56 -0
  40. package/mcp.json +10 -0
  41. package/package.json +76 -0
  42. package/plugin.json +15 -0
  43. package/queries/java/annotations.scm +1 -0
  44. package/queries/java/calls.scm +1 -0
  45. package/queries/java/imports.scm +1 -0
  46. package/queries/java/symbols.scm +2 -0
  47. package/skills/context-slice/SKILL.md +60 -0
@@ -0,0 +1,339 @@
1
+ import Parser from "tree-sitter";
2
+ import Java from "tree-sitter-java";
3
+ // Java grammar node type -> SymbolKind, matching the old regex parser's exact kind values.
4
+ const TYPE_DECL_NODE_TYPES = {
5
+ class_declaration: "class",
6
+ interface_declaration: "interface",
7
+ enum_declaration: "enum",
8
+ record_declaration: "record",
9
+ };
10
+ function point(source, offset) {
11
+ const lines = source.slice(0, offset).split("\n");
12
+ return { line: lines.length, column: lines.at(-1)?.length ?? 0 };
13
+ }
14
+ function sourceRange(source, start, end) {
15
+ const a = point(source, start);
16
+ const b = point(source, end);
17
+ return {
18
+ startLine: a.line,
19
+ startColumn: a.column,
20
+ endLine: b.line,
21
+ endColumn: b.column,
22
+ };
23
+ }
24
+ function canonicalId(filePath, packageName, typeChain, kind, name, parameters = "") {
25
+ return `${filePath}::${packageName || "<default>"}::${typeChain.join(".") || "<file>"}::${kind}::${name}${parameters ? `(${parameters})` : ""}`;
26
+ }
27
+ function parameterSignature(parameters) {
28
+ return parameters
29
+ .replace(/@[A-Za-z_$][\w$]*(?:\([^)]*\))?\s*/g, "")
30
+ .replace(/\b(final|volatile|transient)\s+/g, "")
31
+ .replace(/\s+/g, " ")
32
+ .trim();
33
+ }
34
+ function callArguments(source, open) {
35
+ let depth = 0;
36
+ for (let i = open; i < source.length; i++) {
37
+ if (source[i] === "(")
38
+ depth++;
39
+ else if (source[i] === ")" && --depth === 0)
40
+ return source.slice(open + 1, i);
41
+ }
42
+ return "";
43
+ }
44
+ /** Annotation names + modifier keywords out of a `modifiers` node, in source order (bare annotation names only, no arguments). */
45
+ function modifiersNodeParts(modifiersNode) {
46
+ const annotations = [];
47
+ const modifiers = [];
48
+ for (const child of modifiersNode?.children ?? []) {
49
+ if (child.type === "marker_annotation" || child.type === "annotation") {
50
+ annotations.push(`@${child.childForFieldName("name").text}`);
51
+ }
52
+ else if (!child.isNamed) {
53
+ modifiers.push(child.type);
54
+ }
55
+ }
56
+ return { annotations, modifiers };
57
+ }
58
+ /** Strip generic type arguments (e.g. `JpaRepository<User, Long>` -> `JpaRepository`), matching the old regex parser's bare-identifier-only behavior. */
59
+ function stripGenericArgs(name) {
60
+ const idx = name.indexOf("<");
61
+ return (idx === -1 ? name : name.slice(0, idx)).trim();
62
+ }
63
+ /** class/interface supertypes: class uses `superclass`/`interfaces` fields, interface uses a direct `extends_interfaces` child (no matching field). */
64
+ function supertypesOf(node) {
65
+ if (node.type === "class_declaration") {
66
+ const superclass = node.childForFieldName("superclass");
67
+ const interfaces = node.childForFieldName("interfaces");
68
+ return [
69
+ ...(superclass
70
+ ? [stripGenericArgs(superclass.text.replace(/^extends\s+/, ""))]
71
+ : []),
72
+ ...(interfaces
73
+ ? interfaces
74
+ .text.replace(/^implements\s+/, "")
75
+ .split(/\s*,\s*/)
76
+ .map(stripGenericArgs)
77
+ : []),
78
+ ];
79
+ }
80
+ if (node.type === "interface_declaration") {
81
+ const extendsNode = node.children.find((c) => c.type === "extends_interfaces");
82
+ const typeList = extendsNode?.namedChildren.find((c) => c.type === "type_list");
83
+ return typeList?.namedChildren.map((t) => stripGenericArgs(t.text)) ?? [];
84
+ }
85
+ return [];
86
+ }
87
+ /**
88
+ * The nodes to scan for direct method/constructor members of a type's body. For class/interface/record
89
+ * bodies these are the body node's own namedChildren. For an enum, the grammar nests them one level
90
+ * deeper: `enum_body`'s real members (methods, constructors, fields declared after the constant list)
91
+ * live inside a child `enum_body_declarations` node, not as direct children of `enum_body` itself
92
+ * (an enum with no such members after its constants has no `enum_body_declarations` child at all).
93
+ */
94
+ function memberNodesOf(bodyNode) {
95
+ if (bodyNode.type === "enum_body") {
96
+ const decls = bodyNode.namedChildren.find((c) => c.type === "enum_body_declarations");
97
+ return decls ? decls.namedChildren : [];
98
+ }
99
+ return bodyNode.namedChildren;
100
+ }
101
+ /** Recursive walk over top-level/nested type declarations (class/interface/enum/record), mirroring src/languages/rust/parse.ts's walk(node, parent, chain) shape. */
102
+ function walkTypes(node, parent, chain, filePath, source, packageName, symbols, types) {
103
+ for (const child of node.namedChildren) {
104
+ const kind = TYPE_DECL_NODE_TYPES[child.type];
105
+ if (!kind) {
106
+ walkTypes(child, parent, chain, filePath, source, packageName, symbols, types);
107
+ continue;
108
+ }
109
+ const name = child.childForFieldName("name").text;
110
+ const bodyNode = child.childForFieldName("body");
111
+ const typeChain = [...chain, name];
112
+ const canonicalIdentity = canonicalId(filePath, packageName, typeChain, kind, name);
113
+ const modifiersNode = child.namedChildren.find((c) => c.type === "modifiers");
114
+ const { annotations, modifiers } = modifiersNodeParts(modifiersNode);
115
+ const symbol = {
116
+ id: canonicalIdentity,
117
+ language: "java",
118
+ kind,
119
+ name,
120
+ packageName,
121
+ qualifiedName: `${packageName ? `${packageName}.` : ""}${typeChain.join(".")}`,
122
+ canonicalIdentity,
123
+ signature: `${kind} ${name}`,
124
+ filePath,
125
+ range: sourceRange(source, child.startIndex, child.endIndex),
126
+ bodyRange: bodyNode
127
+ ? sourceRange(source, bodyNode.startIndex, bodyNode.endIndex)
128
+ : undefined,
129
+ parentId: parent?.id,
130
+ supertypes: supertypesOf(child),
131
+ annotations,
132
+ modifiers,
133
+ source: source.slice(child.startIndex, child.endIndex),
134
+ body: bodyNode
135
+ ? source.slice(bodyNode.startIndex, bodyNode.endIndex)
136
+ : undefined,
137
+ };
138
+ symbols.push(symbol);
139
+ types.push(symbol);
140
+ if (bodyNode) {
141
+ for (const member of memberNodesOf(bodyNode)) {
142
+ if (member.type === "method_declaration") {
143
+ symbols.push(methodSymbol(member, symbol, typeChain, filePath, source, packageName));
144
+ }
145
+ else if (member.type === "constructor_declaration") {
146
+ symbols.push(constructorSymbol(member, symbol, typeChain, filePath, source, packageName));
147
+ }
148
+ else if (member.type === "field_declaration" || member.type === "constant_declaration") {
149
+ symbols.push(...fieldSymbols(member, symbol, typeChain, filePath, source, packageName));
150
+ }
151
+ }
152
+ walkTypes(bodyNode, symbol, typeChain, filePath, source, packageName, symbols, types);
153
+ }
154
+ }
155
+ }
156
+ /** `formal_parameters` node text includes its own parens; parameterSignature() expects the bare inner text (matching the old regex's capture group, which never included the parens). */
157
+ function parametersText(parametersNode) {
158
+ const text = parametersNode.text;
159
+ return text.startsWith("(") && text.endsWith(")")
160
+ ? text.slice(1, -1)
161
+ : text;
162
+ }
163
+ function methodSymbol(node, parent, typeChain, filePath, source, packageName) {
164
+ const name = node.childForFieldName("name").text;
165
+ const type = node.childForFieldName("type").text;
166
+ const parametersNode = node.childForFieldName("parameters");
167
+ const bodyNode = node.childForFieldName("body");
168
+ const modifiersNode = node.namedChildren.find((c) => c.type === "modifiers");
169
+ const { annotations, modifiers } = modifiersNodeParts(modifiersNode);
170
+ const parameters = parameterSignature(parametersText(parametersNode));
171
+ const signature = `${name}(${parameters}): ${type}`;
172
+ const canonicalIdentity = canonicalId(filePath, packageName, typeChain, "method", name, parameters);
173
+ return {
174
+ id: canonicalIdentity,
175
+ language: "java",
176
+ kind: "method",
177
+ name,
178
+ packageName,
179
+ qualifiedName: `${packageName ? `${packageName}.` : ""}${[...typeChain, name].join(".")}`,
180
+ canonicalIdentity,
181
+ signature,
182
+ filePath,
183
+ range: sourceRange(source, node.startIndex, node.endIndex),
184
+ bodyRange: bodyNode
185
+ ? sourceRange(source, bodyNode.startIndex, bodyNode.endIndex)
186
+ : undefined,
187
+ parentId: parent.id,
188
+ annotations,
189
+ modifiers,
190
+ source: source.slice(node.startIndex, node.endIndex),
191
+ body: bodyNode ? source.slice(bodyNode.startIndex, bodyNode.endIndex) : undefined,
192
+ };
193
+ }
194
+ function constructorSymbol(node, parent, typeChain, filePath, source, packageName) {
195
+ const name = node.childForFieldName("name").text;
196
+ const parametersNode = node.childForFieldName("parameters");
197
+ const bodyNode = node.childForFieldName("body");
198
+ const modifiersNode = node.namedChildren.find((c) => c.type === "modifiers");
199
+ const { annotations, modifiers } = modifiersNodeParts(modifiersNode);
200
+ const parameters = parameterSignature(parametersText(parametersNode));
201
+ const signature = `${name}(${parameters}): ${name}`;
202
+ const canonicalIdentity = canonicalId(filePath, packageName, typeChain, "constructor", name, parameters);
203
+ return {
204
+ id: canonicalIdentity,
205
+ language: "java",
206
+ kind: "constructor",
207
+ name,
208
+ packageName,
209
+ qualifiedName: `${packageName ? `${packageName}.` : ""}${[...typeChain, name].join(".")}`,
210
+ canonicalIdentity,
211
+ signature,
212
+ filePath,
213
+ range: sourceRange(source, node.startIndex, node.endIndex),
214
+ bodyRange: sourceRange(source, bodyNode.startIndex, bodyNode.endIndex),
215
+ parentId: parent.id,
216
+ annotations,
217
+ modifiers,
218
+ source: source.slice(node.startIndex, node.endIndex),
219
+ body: source.slice(bodyNode.startIndex, bodyNode.endIndex),
220
+ };
221
+ }
222
+ /**
223
+ * A field_declaration (class/enum/record) or constant_declaration (interface — a
224
+ * different node type with an identical internal shape) can carry multiple
225
+ * variable_declarator children (`private int a, b;`), so this returns one SymbolRecord
226
+ * per declarator, all sharing the same declared type/annotations/modifiers. A field has
227
+ * no top-level `type` property on SymbolRecord (only methods put a type-like thing in
228
+ * `.signature`); the declared type goes in `metadata.declaredType`, the same convention
229
+ * the TypeScript and Python adapters already use.
230
+ */
231
+ function fieldSymbols(node, parent, typeChain, filePath, source, packageName) {
232
+ const declaredType = node.childForFieldName("type").text;
233
+ const modifiersNode = node.namedChildren.find((c) => c.type === "modifiers");
234
+ const { annotations, modifiers } = modifiersNodeParts(modifiersNode);
235
+ const declarators = node.namedChildren.filter((c) => c.type === "variable_declarator");
236
+ const single = declarators.length === 1;
237
+ return declarators.map((declarator) => {
238
+ const name = declarator.childForFieldName("name").text;
239
+ const start = single ? node.startIndex : declarator.startIndex;
240
+ const end = single ? node.endIndex : declarator.endIndex;
241
+ const canonicalIdentity = canonicalId(filePath, packageName, typeChain, "field", name);
242
+ return {
243
+ id: canonicalIdentity,
244
+ language: "java",
245
+ kind: "field",
246
+ name,
247
+ packageName,
248
+ qualifiedName: `${packageName ? `${packageName}.` : ""}${[...typeChain, name].join(".")}`,
249
+ canonicalIdentity,
250
+ signature: `${name}: ${declaredType}`,
251
+ filePath,
252
+ range: sourceRange(source, start, end),
253
+ bodyRange: undefined,
254
+ parentId: parent.id,
255
+ annotations,
256
+ modifiers,
257
+ metadata: { declaredType },
258
+ source: source.slice(start, end),
259
+ body: undefined,
260
+ };
261
+ });
262
+ }
263
+ function byRange(a, b) {
264
+ return (a.range.startLine - b.range.startLine ||
265
+ a.range.startColumn - b.range.startColumn);
266
+ }
267
+ /**
268
+ * Mutates each symbol's `id`, appending `#N` for the Nth+ symbol sharing a `canonicalIdentity`
269
+ * within this file. The old regex parser built `symbols` in a fixed global order — all types
270
+ * (via the type walk), then ALL methods across the whole file in document order (one regex pass),
271
+ * then ALL constructors across the whole file in document order (another pass) — and dedup
272
+ * suffixes were assigned by iterating that order. The new AST walk instead pushes each type's
273
+ * own methods/constructors immediately after the type itself (per-type nested order), which can
274
+ * differ from the old flat order when the file has multiple types. To keep dedup suffixes
275
+ * byte-identical to the old parser regardless of that ordering change, this recomputes the
276
+ * old global order (types, then methods sorted by position, then constructors sorted by
277
+ * position) purely for suffix assignment; the returned `symbols` array itself keeps its
278
+ * (unrelated) per-type nested order. Fields (a new symbol kind with no old-parser precedent to match) are included in this same ordering, inserted after types and before methods — the exact position has no behavioral significance since there is no legacy ordering to preserve for fields, but must stay consistent.
279
+ */
280
+ function assignDedupIds(symbols, types) {
281
+ const fields = symbols.filter((s) => s.kind === "field").sort(byRange);
282
+ const methods = symbols.filter((s) => s.kind === "method").sort(byRange);
283
+ const constructors = symbols
284
+ .filter((s) => s.kind === "constructor")
285
+ .sort(byRange);
286
+ const dedupOrder = [...types, ...fields, ...methods, ...constructors];
287
+ const identityCounts = new Map();
288
+ for (const symbol of dedupOrder) {
289
+ const count = identityCounts.get(symbol.canonicalIdentity ?? symbol.id) ?? 0;
290
+ if (count > 0)
291
+ symbol.id = `${symbol.canonicalIdentity}#${count + 1}`;
292
+ identityCounts.set(symbol.canonicalIdentity ?? symbol.id, count + 1);
293
+ }
294
+ }
295
+ export function parseJava(filePath, source) {
296
+ const parser = new Parser();
297
+ parser.setLanguage(Java);
298
+ let parseError = false;
299
+ let tree;
300
+ try {
301
+ tree = parser.parse(source);
302
+ }
303
+ catch {
304
+ parseError = true;
305
+ }
306
+ const symbols = [];
307
+ const types = [];
308
+ const packageDecl = tree?.rootNode.namedChildren.find((c) => c.type === "package_declaration");
309
+ const packageName = packageDecl?.namedChildren[0]?.text ?? "";
310
+ if (tree)
311
+ walkTypes(tree.rootNode, undefined, [], filePath, source, packageName, symbols, types);
312
+ assignDedupIds(symbols, types);
313
+ const calls = [];
314
+ const callable = symbols.filter((s) => s.kind === "method" || s.kind === "constructor");
315
+ for (const caller of callable)
316
+ for (const match of (caller.body ?? "").matchAll(/(?:(\w+)\.)?([A-Za-z_$][\w$]*)\s*\(/g)) {
317
+ const calleeName = match[2];
318
+ const offset = match.index ?? 0;
319
+ const prefix = (caller.body ?? "").slice(Math.max(0, offset - 8), offset);
320
+ if (["if", "for", "while", "switch", "catch", "return"].includes(calleeName))
321
+ continue;
322
+ const argumentsText = callArguments(caller.body ?? "", offset + match[0].lastIndexOf("("));
323
+ const argumentCount = argumentsText.trim()
324
+ ? argumentsText.split(",").length
325
+ : 0;
326
+ calls.push({
327
+ callerId: caller.id,
328
+ receiverText: match[1],
329
+ calleeName,
330
+ argumentCount,
331
+ filePath,
332
+ range: caller.range,
333
+ confidence: "unresolved",
334
+ resolutionKind: prefix.includes("new") ? "constructor" : "unresolved",
335
+ evidence: prefix.includes("new") ? ["syntactic new expression"] : [],
336
+ });
337
+ }
338
+ return { symbols, calls, parseError };
339
+ }
@@ -0,0 +1 @@
1
+ export const estimateTokens = (text) => Math.ceil(text.length / 4);