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,443 @@
1
+ import Parser from "tree-sitter";
2
+ import Go from "tree-sitter-go";
3
+ export const LANGUAGE_ID = "go";
4
+ let parser;
5
+ function goParser() {
6
+ if (!parser) {
7
+ parser = new Parser();
8
+ parser.setLanguage(Go);
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
+ /**
21
+ * A method's receiver base type name, e.g. "User" for "*User"/"User", or
22
+ * "Stack" for a generic receiver "*Stack[T]"/"Stack[T]" (verified: the
23
+ * receiver's own parameter_declaration has a "type" field that is either a
24
+ * pointer_type node (whose own namedChild(0) is the base type_identifier or
25
+ * a generic_type), a generic_type node directly, or a type_identifier node
26
+ * directly for a plain value receiver. generic_type's base identifier is
27
+ * reachable via its own "type" field, verified empirically — not a "name"
28
+ * field as one might guess).
29
+ */
30
+ function receiverBaseTypeName(methodNode) {
31
+ const receiverList = field(methodNode, "receiver");
32
+ const paramDecl = receiverList?.namedChildren.find((c) => c.type === "parameter_declaration");
33
+ let typeNode = paramDecl ? field(paramDecl, "type") : undefined;
34
+ if (!typeNode)
35
+ return undefined;
36
+ if (typeNode.type === "pointer_type") {
37
+ typeNode = typeNode.namedChild(0) ?? undefined;
38
+ }
39
+ if (!typeNode)
40
+ return undefined;
41
+ if (typeNode.type === "generic_type") {
42
+ const base = field(typeNode, "type");
43
+ return base?.type === "type_identifier" ? base.text : undefined;
44
+ }
45
+ return typeNode.type === "type_identifier" ? typeNode.text : undefined;
46
+ }
47
+ function canonicalId(filePath, kind, name, parameters) {
48
+ return [filePath, kind, name, parameters].filter(Boolean).join("::");
49
+ }
50
+ function modifiersFor(name) {
51
+ return /^[A-Z]/.test(name) ? ["exported"] : [];
52
+ }
53
+ /** Ordered, name-stripped parameter TYPES only, e.g. "(a, b string, c int)" -> "string,string,int"
54
+ * — used only to compare two signatures for interface satisfaction, never displayed. A grouped
55
+ * declaration ("a, b string") is one parameter_declaration node whose namedChildren are every name
56
+ * identifier followed by the shared type; the type is duplicated once per name. A declaration with
57
+ * no name (just a type, legal in both interface methods and func types) has exactly one named
58
+ * child, which is the type. Anything else (e.g. a variadic_parameter_declaration) falls back to its
59
+ * own full text as one opaque "type" — consistent as long as it's compared against itself. */
60
+ function paramTypesOf(parameters) {
61
+ if (!parameters)
62
+ return "";
63
+ const types = [];
64
+ for (const decl of parameters.namedChildren) {
65
+ if (decl.type === "parameter_declaration") {
66
+ const children = decl.namedChildren;
67
+ const type = children.at(-1)?.text ?? "";
68
+ const count = Math.max(1, children.length - 1);
69
+ for (let i = 0; i < count; i++)
70
+ types.push(type);
71
+ }
72
+ else {
73
+ types.push(decl.text);
74
+ }
75
+ }
76
+ return types.join(",");
77
+ }
78
+ function resultTypesOf(result) {
79
+ if (!result)
80
+ return "";
81
+ return result.type === "parameter_list" ? paramTypesOf(result) : result.text;
82
+ }
83
+ /** A normalized "(types)result" signature for a function/method/interface-method node — comparable
84
+ * by exact string equality regardless of the declared parameter NAMES, which Go's own rules never
85
+ * require to match for interface satisfaction. */
86
+ function methodSignatureOf(node) {
87
+ return `(${paramTypesOf(field(node, "parameters"))})${resultTypesOf(field(node, "result"))}`;
88
+ }
89
+ /** A constructor-style function's own declared return type, bare pointer-stripped (e.g. "*Bar" ->
90
+ * "Bar"), used to look up what a "x := NewFoo()" binding actually constructs instead of assuming
91
+ * the type from the function's own name. A multi-value return ("(*Bar, error)") takes the first
92
+ * value, matching Go's constructor convention of returning the created value first. */
93
+ function primaryReturnType(result) {
94
+ if (!result)
95
+ return undefined;
96
+ const first = result.type === "parameter_list" ? result.namedChildren[0] : result;
97
+ if (!first)
98
+ return undefined;
99
+ const typeNode = first.type === "parameter_declaration" ? (first.namedChildren.at(-1) ?? first) : first;
100
+ return typeNode.text.replace(/^\*/, "");
101
+ }
102
+ /**
103
+ * A call_expression's callee name and (when the operand is a plain identifier) its
104
+ * receiver text. selector_expression's "operand"/"field" fields and call_expression's
105
+ * "function"/"arguments" fields verified empirically against tree-sitter-go@0.23.4.
106
+ */
107
+ function buildCallEdge(callNode, filePath, callerId, wrapKind) {
108
+ const fnNode = field(callNode, "function");
109
+ const argsNode = field(callNode, "arguments");
110
+ const argumentCount = argsNode?.namedChildCount ?? 0;
111
+ const evidence = ["syntactic call"];
112
+ if (wrapKind === "go")
113
+ evidence.push("goroutine launch");
114
+ else if (wrapKind === "defer")
115
+ evidence.push("deferred call");
116
+ let calleeName = "";
117
+ let receiverText;
118
+ if (fnNode?.type === "identifier") {
119
+ calleeName = fnNode.text;
120
+ }
121
+ else if (fnNode?.type === "selector_expression") {
122
+ calleeName = text(field(fnNode, "field"));
123
+ const operand = field(fnNode, "operand");
124
+ if (operand?.type === "identifier")
125
+ receiverText = operand.text;
126
+ }
127
+ return {
128
+ callerId,
129
+ calleeName,
130
+ receiverText,
131
+ argumentCount,
132
+ filePath,
133
+ language: LANGUAGE_ID,
134
+ range: range(callNode),
135
+ confidence: "unresolved",
136
+ resolutionKind: "unresolved",
137
+ evidence,
138
+ };
139
+ }
140
+ /**
141
+ * Walks every descendant of `node` for call_expressions, attributing each to `ownerId`
142
+ * regardless of nesting depth (blocks, closures — Go closures have no symbol of their
143
+ * own in this adapter's model, so their calls attribute to the innermost enclosing
144
+ * NAMED function/method). Whether a call is the direct target of a go/defer statement
145
+ * is read off the immediate parent's type during the walk — verified empirically that
146
+ * this correctly tags only the direct target, never a call nested inside its arguments.
147
+ */
148
+ function collectCalls(node, ownerId, filePath, calls) {
149
+ for (const child of node.namedChildren) {
150
+ if (child.type === "call_expression") {
151
+ const wrapKind = node.type === "go_statement" ? "go" : node.type === "defer_statement" ? "defer" : undefined;
152
+ const edge = buildCallEdge(child, filePath, ownerId, wrapKind);
153
+ // Skip edges whose callee couldn't be named (e.g. `go func(){ ... }()`,
154
+ // `getHandler()()`, `(g)()`) rather than emitting a garbage calleeName: "" edge.
155
+ // Still recurse below so calls nested inside the callee/args are found.
156
+ if (edge.calleeName !== "")
157
+ calls.push(edge);
158
+ }
159
+ collectCalls(child, ownerId, filePath, calls);
160
+ }
161
+ }
162
+ export function parseGo(filePath, source) {
163
+ const symbols = [];
164
+ const calls = [];
165
+ const imports = [];
166
+ const exports = [];
167
+ let parseError = false;
168
+ let tree;
169
+ try {
170
+ // tree-sitter's node binding rejects large single-string inputs; feed it in chunks,
171
+ // matching every other adapter in this project (rust, typescript).
172
+ tree = goParser().parse((index) => source.slice(index, index + 4_096));
173
+ }
174
+ catch {
175
+ return { symbols, calls, imports, exports, parseError: true };
176
+ }
177
+ if (tree.rootNode.hasError)
178
+ parseError = true;
179
+ const seenIds = new Set();
180
+ const uniqueId = (id) => {
181
+ if (!seenIds.has(id)) {
182
+ seenIds.add(id);
183
+ return id;
184
+ }
185
+ let n = 2;
186
+ while (seenIds.has(`${id}#${n}`))
187
+ n++;
188
+ const deduped = `${id}#${n}`;
189
+ seenIds.add(deduped);
190
+ return deduped;
191
+ };
192
+ const structByName = new Map();
193
+ const pendingMethods = [];
194
+ const callables = [];
195
+ for (const child of tree.rootNode.namedChildren) {
196
+ if (child.type === "function_declaration") {
197
+ const name = text(field(child, "name"));
198
+ if (!name)
199
+ continue;
200
+ const parameters = text(field(child, "parameters"));
201
+ const id = uniqueId(canonicalId(filePath, "function", name, parameters));
202
+ const returnType = primaryReturnType(field(child, "result"));
203
+ const symbol = {
204
+ id,
205
+ language: LANGUAGE_ID,
206
+ kind: "function",
207
+ name,
208
+ qualifiedName: name,
209
+ canonicalIdentity: id,
210
+ signature: `func ${name}${parameters}`,
211
+ filePath,
212
+ range: range(child),
213
+ bodyRange: field(child, "body") ? range(field(child, "body")) : undefined,
214
+ annotations: [],
215
+ modifiers: modifiersFor(name),
216
+ metadata: returnType ? { returnType } : undefined,
217
+ source: child.text,
218
+ body: field(child, "body")?.text,
219
+ };
220
+ symbols.push(symbol);
221
+ callables.push({ symbol, body: field(child, "body") });
222
+ }
223
+ else if (child.type === "method_declaration") {
224
+ // Deferred: struct symbols may appear later in this same file's top-level
225
+ // children (Go has no forward-declaration ordering requirement), so method
226
+ // linkage runs in a second pass below once every struct in this file is known.
227
+ pendingMethods.push({ node: child, receiverName: receiverBaseTypeName(child) });
228
+ }
229
+ else if (child.type === "type_declaration") {
230
+ for (const spec of child.namedChildren.filter((c) => c.type === "type_spec" || c.type === "type_alias")) {
231
+ const name = text(field(spec, "name"));
232
+ const typeNode = field(spec, "type");
233
+ if (!name || !typeNode)
234
+ continue;
235
+ if (typeNode.type === "struct_type") {
236
+ const id = uniqueId(canonicalId(filePath, "class", name));
237
+ const symbol = {
238
+ id,
239
+ language: LANGUAGE_ID,
240
+ kind: "class",
241
+ name,
242
+ qualifiedName: name,
243
+ canonicalIdentity: id,
244
+ signature: `type ${name} struct`,
245
+ filePath,
246
+ range: range(spec),
247
+ annotations: [],
248
+ modifiers: modifiersFor(name),
249
+ source: spec.text,
250
+ };
251
+ symbols.push(symbol);
252
+ structByName.set(name, symbol);
253
+ // struct_type's field list is a NAMED CHILD (field_declaration_list), not
254
+ // reachable via childForFieldName("body") — verified empirically: field()
255
+ // returns undefined here, so the fallback lookup is required, not optional.
256
+ const fieldList = typeNode.namedChildren.find((c) => c.type === "field_declaration_list");
257
+ for (const fieldDecl of fieldList?.namedChildren.filter((c) => c.type === "field_declaration") ?? []) {
258
+ // An embedded field (e.g. plain "Base") has no "name" field, only "type" —
259
+ // verified empirically: field_declaration's name field is absent for embeds.
260
+ const explicitName = text(field(fieldDecl, "name"));
261
+ const fieldName = explicitName || text(field(fieldDecl, "type"));
262
+ if (!fieldName)
263
+ continue;
264
+ const fieldId = uniqueId(canonicalId(filePath, "field", `${name}.${fieldName}`));
265
+ symbols.push({
266
+ id: fieldId,
267
+ language: LANGUAGE_ID,
268
+ kind: "field",
269
+ name: fieldName,
270
+ qualifiedName: `${name}.${fieldName}`,
271
+ canonicalIdentity: fieldId,
272
+ signature: fieldDecl.text,
273
+ filePath,
274
+ range: range(fieldDecl),
275
+ parentId: id,
276
+ annotations: [],
277
+ modifiers: modifiersFor(fieldName),
278
+ metadata: explicitName ? undefined : { embedded: true },
279
+ source: fieldDecl.text,
280
+ });
281
+ }
282
+ }
283
+ else if (typeNode.type === "interface_type") {
284
+ const id = uniqueId(canonicalId(filePath, "interface", name));
285
+ const methodElems = typeNode.namedChildren.filter((c) => c.type === "method_elem");
286
+ const interfaceMethods = methodElems.map((m) => text(m.namedChild(0))).filter(Boolean);
287
+ const interfaceMethodSignatures = {};
288
+ for (const m of methodElems) {
289
+ const methodName = text(m.namedChild(0));
290
+ if (methodName)
291
+ interfaceMethodSignatures[methodName] = methodSignatureOf(m);
292
+ }
293
+ symbols.push({
294
+ id,
295
+ language: LANGUAGE_ID,
296
+ kind: "interface",
297
+ name,
298
+ qualifiedName: name,
299
+ canonicalIdentity: id,
300
+ signature: `type ${name} interface`,
301
+ filePath,
302
+ range: range(spec),
303
+ annotations: [],
304
+ modifiers: modifiersFor(name),
305
+ metadata: { interfaceMethods, interfaceMethodSignatures },
306
+ source: spec.text,
307
+ });
308
+ }
309
+ else {
310
+ const id = uniqueId(canonicalId(filePath, "type", name));
311
+ symbols.push({
312
+ id,
313
+ language: LANGUAGE_ID,
314
+ kind: "type",
315
+ name,
316
+ qualifiedName: name,
317
+ canonicalIdentity: id,
318
+ signature: `type ${name} ${text(typeNode)}`,
319
+ filePath,
320
+ range: range(spec),
321
+ annotations: [],
322
+ modifiers: modifiersFor(name),
323
+ source: spec.text,
324
+ });
325
+ }
326
+ }
327
+ }
328
+ else if (child.type === "const_declaration" || child.type === "var_declaration") {
329
+ const kind = child.type === "const_declaration" ? "const" : "var";
330
+ const specType = kind === "const" ? "const_spec" : "var_spec";
331
+ for (const spec of child.namedChildren.filter((c) => c.type === specType)) {
332
+ // const_spec/var_spec's "name" field only yields the FIRST identifier in a
333
+ // multi-name spec (e.g. "var a, b int"); Phase 1 scope is single-name specs,
334
+ // the common case — multi-name specs are a known, accepted gap, not a bug to
335
+ // chase here (mirrors this project's established "accepted divergence" pattern
336
+ // for narrow multi-declarator gaps elsewhere, e.g. Java field extraction).
337
+ const name = text(field(spec, "name"));
338
+ if (!name)
339
+ continue;
340
+ const id = uniqueId(canonicalId(filePath, "variable", name));
341
+ symbols.push({
342
+ id,
343
+ language: LANGUAGE_ID,
344
+ kind: "variable",
345
+ name,
346
+ qualifiedName: name,
347
+ canonicalIdentity: id,
348
+ signature: spec.text,
349
+ filePath,
350
+ range: range(spec),
351
+ annotations: [],
352
+ modifiers: modifiersFor(name),
353
+ source: spec.text,
354
+ });
355
+ }
356
+ }
357
+ else if (child.type === "import_declaration") {
358
+ const specs = child.namedChildren.flatMap((c) => c.type === "import_spec_list"
359
+ ? c.namedChildren.filter((s) => s.type === "import_spec")
360
+ : c.type === "import_spec"
361
+ ? [c]
362
+ : []);
363
+ for (const spec of specs) {
364
+ const pathNode = field(spec, "path");
365
+ const module = text(pathNode?.namedChild(0) ?? pathNode).replace(/^["']|["']$/g, "");
366
+ if (!module)
367
+ continue;
368
+ const nameNode = field(spec, "name");
369
+ if (nameNode?.type === "blank_identifier") {
370
+ imports.push({
371
+ filePath,
372
+ language: LANGUAGE_ID,
373
+ module,
374
+ kind: "side-effect",
375
+ typeOnly: false,
376
+ range: range(spec),
377
+ });
378
+ }
379
+ else if (nameNode?.type === "dot") {
380
+ imports.push({
381
+ filePath,
382
+ language: LANGUAGE_ID,
383
+ module,
384
+ kind: "namespace",
385
+ typeOnly: false,
386
+ wildcard: true,
387
+ range: range(spec),
388
+ });
389
+ }
390
+ else {
391
+ imports.push({
392
+ filePath,
393
+ language: LANGUAGE_ID,
394
+ module,
395
+ kind: "namespace",
396
+ typeOnly: false,
397
+ localName: nameNode?.type === "package_identifier" ? nameNode.text : undefined,
398
+ range: range(spec),
399
+ });
400
+ }
401
+ }
402
+ }
403
+ }
404
+ // Second pass: by now structByName holds every struct declared in THIS file (built
405
+ // in the first pass above), so same-file receiver linkage is real here. A method
406
+ // whose receiver type isn't in this file's own structByName correctly gets
407
+ // parentId: undefined with supertypes still set — never dropped, never misattributed.
408
+ for (const { node: methodNode, receiverName } of pendingMethods) {
409
+ const name = text(field(methodNode, "name"));
410
+ if (!name)
411
+ continue;
412
+ const parameters = text(field(methodNode, "parameters"));
413
+ const qualifiedName = receiverName ? `${receiverName}.${name}` : name;
414
+ const id = uniqueId(canonicalId(filePath, "method", qualifiedName, parameters));
415
+ const receiverStruct = receiverName ? structByName.get(receiverName) : undefined;
416
+ const symbol = {
417
+ id,
418
+ language: LANGUAGE_ID,
419
+ kind: "method",
420
+ name,
421
+ qualifiedName,
422
+ canonicalIdentity: id,
423
+ signature: `func (${receiverName ?? ""}) ${name}${parameters}`,
424
+ filePath,
425
+ range: range(methodNode),
426
+ bodyRange: field(methodNode, "body") ? range(field(methodNode, "body")) : undefined,
427
+ parentId: receiverStruct?.id,
428
+ supertypes: receiverName ? [receiverName] : undefined,
429
+ annotations: [],
430
+ modifiers: modifiersFor(name),
431
+ metadata: { methodSignature: methodSignatureOf(methodNode) },
432
+ source: methodNode.text,
433
+ body: field(methodNode, "body")?.text,
434
+ };
435
+ symbols.push(symbol);
436
+ callables.push({ symbol, body: field(methodNode, "body") });
437
+ }
438
+ for (const { symbol, body } of callables) {
439
+ if (body)
440
+ collectCalls(body, symbol.id, filePath, calls);
441
+ }
442
+ return { symbols, calls, imports, exports, parseError };
443
+ }