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,463 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, posix, resolve as resolvePath } from "node:path";
3
+ const SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".d.ts"];
4
+ /** Imports that can never hold a call target, so they are not "unresolved source". */
5
+ const ASSET_EXTENSIONS = /\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|ico|json|ya?ml|woff2?|ttf|otf|eot|mp[34]|wav|webm|wasm|txt|md|html|graphql|gql)$/i;
6
+ export const isAssetSpecifier = (specifier) => ASSET_EXTENSIONS.test(specifier.split("?")[0]);
7
+ /** `paths`/`baseUrl` from the repository tsconfig; the compiler project system is out of scope. */
8
+ export function readTsconfigAliases(root) {
9
+ for (const name of ["tsconfig.json", "tsconfig.base.json"]) {
10
+ const file = join(root, name);
11
+ if (!existsSync(file))
12
+ continue;
13
+ try {
14
+ const raw = readFileSync(file, "utf8")
15
+ .replace(/\/\*[\s\S]*?\*\//g, "")
16
+ .replace(/(^|[^:])\/\/.*$/gm, "$1")
17
+ .replace(/,(\s*[}\]])/g, "$1");
18
+ const config = JSON.parse(raw);
19
+ const options = config.compilerOptions ?? {};
20
+ return {
21
+ baseUrl: typeof options.baseUrl === "string" ? options.baseUrl : undefined,
22
+ paths: (options.paths ?? {}),
23
+ };
24
+ }
25
+ catch {
26
+ return { baseUrl: undefined, paths: {} };
27
+ }
28
+ }
29
+ return { baseUrl: undefined, paths: {} };
30
+ }
31
+ export const externalPackageName = (specifier) => specifier.startsWith("@")
32
+ ? specifier.split("/").slice(0, 2).join("/")
33
+ : specifier.split("/")[0];
34
+ /**
35
+ * Resolve a module specifier to a repository-relative file.
36
+ * Deliberately smaller than the Node/TypeScript algorithm: relative paths,
37
+ * index files, and simple tsconfig aliases only.
38
+ */
39
+ export function resolveModule(specifier, fromFile, files, aliases) {
40
+ const candidatesFor = (base) => {
41
+ const withoutJs = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
42
+ const bases = base === withoutJs ? [base] : [withoutJs, base];
43
+ return bases.flatMap((item) => [
44
+ item,
45
+ ...SOURCE_EXTENSIONS.map((extension) => `${item}${extension}`),
46
+ ...SOURCE_EXTENSIONS.map((extension) => `${item}/index${extension}`),
47
+ ]);
48
+ };
49
+ const pick = (candidates) => candidates
50
+ .map((candidate) => posix.normalize(candidate).replace(/^\.\//, ""))
51
+ .find((candidate) => files.has(candidate));
52
+ if (specifier.startsWith(".")) {
53
+ const base = posix.join(posix.dirname(fromFile), specifier);
54
+ const file = pick(candidatesFor(base));
55
+ return file ? { file } : {};
56
+ }
57
+ for (const [pattern, targets] of Object.entries(aliases.paths ?? {})) {
58
+ const prefix = pattern.replace(/\*$/, "");
59
+ if (!pattern.endsWith("*")
60
+ ? specifier !== pattern
61
+ : !specifier.startsWith(prefix))
62
+ continue;
63
+ const rest = pattern.endsWith("*") ? specifier.slice(prefix.length) : "";
64
+ for (const target of targets) {
65
+ const base = posix.join(aliases.baseUrl && aliases.baseUrl !== "." ? aliases.baseUrl : "", target.replace(/\*$/, "") + rest);
66
+ const file = pick(candidatesFor(base));
67
+ if (file)
68
+ return { file };
69
+ }
70
+ // A configured alias that does not land on repository source is not a package.
71
+ return {};
72
+ }
73
+ if (aliases.baseUrl) {
74
+ const file = pick(candidatesFor(posix.join(aliases.baseUrl === "." ? "" : aliases.baseUrl, specifier)));
75
+ if (file)
76
+ return { file };
77
+ }
78
+ return { externalPackage: externalPackageName(specifier) };
79
+ }
80
+ export function buildGraph(context) {
81
+ const { symbols, imports, exports, root } = context;
82
+ const aliases = readTsconfigAliases(root);
83
+ const files = new Set(symbols.map((symbol) => symbol.filePath));
84
+ for (const record of [...imports, ...exports])
85
+ files.add(record.filePath);
86
+ const symbolsById = new Map(symbols.map((symbol) => [symbol.id, symbol]));
87
+ const symbolsByFile = new Map();
88
+ for (const symbol of symbols) {
89
+ const list = symbolsByFile.get(symbol.filePath) ?? [];
90
+ list.push(symbol);
91
+ symbolsByFile.set(symbol.filePath, list);
92
+ }
93
+ const importsByFile = new Map();
94
+ const unresolvedImports = [];
95
+ for (const record of imports) {
96
+ if (isAssetSpecifier(record.module)) {
97
+ record.asset = true;
98
+ const list = importsByFile.get(record.filePath) ?? [];
99
+ list.push(record);
100
+ importsByFile.set(record.filePath, list);
101
+ continue;
102
+ }
103
+ const resolved = resolveModule(record.module, record.filePath, files, aliases);
104
+ record.resolvedFile = resolved.file;
105
+ record.externalPackage = resolved.externalPackage;
106
+ if (!resolved.file && !resolved.externalPackage)
107
+ unresolvedImports.push(record);
108
+ const list = importsByFile.get(record.filePath) ?? [];
109
+ list.push(record);
110
+ importsByFile.set(record.filePath, list);
111
+ }
112
+ const exportsByFileRaw = new Map();
113
+ for (const record of exports) {
114
+ const resolved = record.fromModule
115
+ ? resolveModule(record.fromModule, record.filePath, files, aliases)
116
+ : undefined;
117
+ record.resolvedFile = resolved?.file;
118
+ const list = exportsByFileRaw.get(record.filePath) ?? [];
119
+ list.push(record);
120
+ exportsByFileRaw.set(record.filePath, list);
121
+ }
122
+ const cyclicExports = [];
123
+ const exportsByFile = new Map();
124
+ /** Follow re-export chains; `seen` breaks barrel cycles. */
125
+ const lookup = (file, name, seen) => {
126
+ const key = `${file}#${name}`;
127
+ if (seen.has(key)) {
128
+ cyclicExports.push(key);
129
+ return undefined;
130
+ }
131
+ seen.add(key);
132
+ const records = exportsByFileRaw.get(file) ?? [];
133
+ const direct = records.find((record) => record.exportedName === name && !record.fromModule);
134
+ if (direct?.symbolId)
135
+ return direct.symbolId;
136
+ if (direct?.localName) {
137
+ const symbol = (symbolsByFile.get(file) ?? []).find((candidate) => candidate.name === direct.localName && !candidate.parentId);
138
+ if (symbol)
139
+ return symbol.id;
140
+ }
141
+ const reexport = records.find((record) => record.exportedName === name && record.fromModule);
142
+ if (reexport?.resolvedFile)
143
+ return lookup(reexport.resolvedFile, reexport.sourceName ?? name, seen);
144
+ for (const wildcard of records.filter((record) => record.wildcard))
145
+ if (wildcard.resolvedFile) {
146
+ const found = lookup(wildcard.resolvedFile, name, seen);
147
+ if (found)
148
+ return found;
149
+ }
150
+ return undefined;
151
+ };
152
+ const exportedNames = (file, seen = new Set()) => {
153
+ if (seen.has(file))
154
+ return [];
155
+ seen.add(file);
156
+ const records = exportsByFileRaw.get(file) ?? [];
157
+ return [
158
+ ...records
159
+ .filter((record) => !record.wildcard)
160
+ .map((record) => record.exportedName),
161
+ ...records
162
+ .filter((record) => record.wildcard && record.resolvedFile)
163
+ .flatMap((record) => exportedNames(record.resolvedFile, seen)),
164
+ ];
165
+ };
166
+ for (const file of exportsByFileRaw.keys()) {
167
+ const map = new Map();
168
+ for (const name of new Set(exportedNames(file))) {
169
+ const target = lookup(file, name, new Set());
170
+ if (target)
171
+ map.set(name, target);
172
+ }
173
+ exportsByFile.set(file, map);
174
+ }
175
+ return {
176
+ exportsByFile,
177
+ importsByFile,
178
+ symbolsById,
179
+ symbolsByFile,
180
+ cyclicExports: [...new Set(cyclicExports)],
181
+ unresolvedImports,
182
+ };
183
+ }
184
+ const CALLABLE = new Set([
185
+ "function",
186
+ "method",
187
+ "constructor",
188
+ "getter",
189
+ "setter",
190
+ ]);
191
+ export function resolveTypeScriptCalls(context) {
192
+ const graph = buildGraph(context);
193
+ const { symbolsById, symbolsByFile, importsByFile, exportsByFile } = graph;
194
+ const ownerChain = (symbol) => {
195
+ const chain = [];
196
+ let current = symbol;
197
+ while (current?.parentId) {
198
+ current = symbolsById.get(current.parentId);
199
+ if (current)
200
+ chain.push(current);
201
+ }
202
+ return chain;
203
+ };
204
+ const memberOf = (container, name) => (symbolsByFile.get(container.filePath) ?? []).filter((candidate) => candidate.parentId === container.id && candidate.name === name);
205
+ /** Implementation wins over overload signatures (§10). */
206
+ const primary = (candidates) => {
207
+ const implementations = candidates.filter((candidate) => !candidate.metadata?.overloadSignature);
208
+ return implementations.length === 1
209
+ ? implementations[0]
210
+ : implementations.length > 1
211
+ ? undefined
212
+ : candidates.length === 1
213
+ ? candidates[0]
214
+ : undefined;
215
+ };
216
+ /**
217
+ * A bare call resolves to the nearest lexical scope: a callable nested in the
218
+ * caller, a sibling in the same scope, then a module-level declaration.
219
+ */
220
+ const localCallable = (file, name, caller) => {
221
+ const candidates = (symbolsByFile.get(file) ?? []).filter((candidate) => candidate.name === name && CALLABLE.has(candidate.kind));
222
+ const scopes = [
223
+ (candidate) => candidate.parentId === caller?.id,
224
+ (candidate) => Boolean(caller?.parentId) && candidate.parentId === caller?.parentId,
225
+ (candidate) => !candidate.parentId,
226
+ ];
227
+ for (const inScope of scopes) {
228
+ const found = primary(candidates.filter(inScope));
229
+ if (found)
230
+ return found;
231
+ }
232
+ return undefined;
233
+ };
234
+ const localType = (file, name) => (symbolsByFile.get(file) ?? []).find((candidate) => candidate.name === name &&
235
+ !candidate.parentId &&
236
+ (candidate.kind === "class" || candidate.kind === "interface"));
237
+ const importFor = (file, localName) => (importsByFile.get(file) ?? []).find((record) => record.localName === localName && !record.typeOnly);
238
+ const importedSymbol = (record, name) => record.resolvedFile
239
+ ? symbolsById.get(exportsByFile.get(record.resolvedFile)?.get(name) ?? "")
240
+ : undefined;
241
+ /** A type name that may be declared here or imported. */
242
+ const typeSymbol = (file, name) => {
243
+ const local = localType(file, name);
244
+ if (local)
245
+ return local;
246
+ const record = importFor(file, name);
247
+ if (!record)
248
+ return undefined;
249
+ const target = importedSymbol(record, record.kind === "default" ? "default" : (record.importedName ?? name));
250
+ return target && (target.kind === "class" || target.kind === "interface")
251
+ ? target
252
+ : undefined;
253
+ };
254
+ const settle = (call, target, kind, confidence, evidence) => {
255
+ call.declaredTargetId = target.id;
256
+ call.resolvedTargetId = target.id;
257
+ call.resolutionKind = kind;
258
+ call.confidence = confidence;
259
+ call.evidence = [evidence];
260
+ };
261
+ for (const call of context.calls) {
262
+ const caller = symbolsById.get(call.callerId);
263
+ if (!caller)
264
+ continue;
265
+ const file = caller.filePath;
266
+ if (call.resolutionKind === "constructor") {
267
+ const type = typeSymbol(file, call.calleeName);
268
+ if (type) {
269
+ const constructors = memberOf(type, "constructor");
270
+ const target = constructors.length === 1 ? constructors[0] : type;
271
+ settle(call, target, "constructor", "exact", `new ${call.calleeName} resolves to ${target.qualifiedName ?? target.name}`);
272
+ continue;
273
+ }
274
+ const external = importFor(file, call.calleeName)?.externalPackage;
275
+ if (external) {
276
+ call.externalPackage = external;
277
+ call.resolutionKind = "external-package";
278
+ call.evidence = [`constructed from external package ${external}`];
279
+ }
280
+ continue;
281
+ }
282
+ if (call.resolutionKind === "jsx-reference") {
283
+ const local = localCallable(file, call.calleeName, caller);
284
+ if (local) {
285
+ settle(call, local, "jsx-reference", "exact", `JSX element references local component ${local.name}`);
286
+ continue;
287
+ }
288
+ const record = importFor(file, call.calleeName);
289
+ const target = record &&
290
+ importedSymbol(record, record.kind === "default"
291
+ ? "default"
292
+ : (record.importedName ?? call.calleeName));
293
+ if (target)
294
+ settle(call, target, "jsx-reference", "exact", `JSX element references imported component ${target.name}`);
295
+ else if (record?.externalPackage) {
296
+ call.externalPackage = record.externalPackage;
297
+ call.resolutionKind = "external-package";
298
+ call.evidence = [
299
+ `JSX component from external package ${record.externalPackage}`,
300
+ ];
301
+ }
302
+ continue;
303
+ }
304
+ // this.property.method(): use the property's declared or constructed type.
305
+ if (call.receiverText?.startsWith("this.")) {
306
+ const propertyName = call.receiverText.slice("this.".length);
307
+ for (const container of ownerChain(caller)) {
308
+ const property = memberOf(container, propertyName)[0];
309
+ const declared = property?.metadata?.declaredType;
310
+ const type = declared ? typeSymbol(file, declared) : undefined;
311
+ const target = type
312
+ ? primary(memberOf(type, call.calleeName))
313
+ : undefined;
314
+ if (target) {
315
+ settle(call, target, "declared-type", type.kind === "interface" ? "probable" : "exact", `this.${propertyName} is declared ${declared}`);
316
+ break;
317
+ }
318
+ }
319
+ if (!call.resolvedTargetId)
320
+ call.evidence = ["property type is not determined syntactically"];
321
+ continue;
322
+ }
323
+ // this.member()
324
+ if (call.receiverText === "this") {
325
+ for (const container of ownerChain(caller)) {
326
+ const target = primary(memberOf(container, call.calleeName));
327
+ if (target) {
328
+ settle(call, target, "this-member", "exact", `this.${call.calleeName} resolves within ${container.name}`);
329
+ break;
330
+ }
331
+ }
332
+ if (call.resolvedTargetId)
333
+ continue;
334
+ // Inherited members stay unresolved rather than guessed.
335
+ call.evidence = ["this receiver has no member of that name in scope"];
336
+ continue;
337
+ }
338
+ if (call.receiverText) {
339
+ // Namespace import: userService.findById()
340
+ const namespaceImport = (importsByFile.get(file) ?? []).find((record) => record.kind === "namespace" && record.localName === call.receiverText);
341
+ if (namespaceImport) {
342
+ if (namespaceImport.resolvedFile) {
343
+ const target = importedSymbol(namespaceImport, call.calleeName);
344
+ if (target) {
345
+ settle(call, target, "namespace-import", "exact", `namespace import ${namespaceImport.localName} exposes ${call.calleeName} from ${namespaceImport.resolvedFile}`);
346
+ continue;
347
+ }
348
+ }
349
+ else if (namespaceImport.externalPackage) {
350
+ call.externalPackage = namespaceImport.externalPackage;
351
+ call.resolutionKind = "external-package";
352
+ call.evidence = [
353
+ `namespace import from external package ${namespaceImport.externalPackage}`,
354
+ ];
355
+ continue;
356
+ }
357
+ }
358
+ // Declared or constructed receiver type: const service: OrderService
359
+ const receiverType = call.receiverType;
360
+ const container = receiverType
361
+ ? typeSymbol(file, receiverType)
362
+ : undefined;
363
+ if (container) {
364
+ const target = primary(memberOf(container, call.calleeName));
365
+ if (target) {
366
+ settle(call, target, "declared-type", container.kind === "interface" ? "probable" : "exact", `receiver ${call.receiverText} is declared ${receiverType}`);
367
+ continue;
368
+ }
369
+ }
370
+ // Static or namespace-like member on a known type: OrderService.create()
371
+ const staticContainer = /^[A-Z]/.test(call.receiverText)
372
+ ? typeSymbol(file, call.receiverText)
373
+ : undefined;
374
+ if (staticContainer) {
375
+ const target = primary(memberOf(staticContainer, call.calleeName));
376
+ if (target) {
377
+ settle(call, target, "static", "exact", `static member of ${staticContainer.name}`);
378
+ continue;
379
+ }
380
+ }
381
+ // A receiver that is an imported binding from a package is external.
382
+ const record = importFor(file, call.receiverText);
383
+ if (record?.externalPackage) {
384
+ call.externalPackage = record.externalPackage;
385
+ call.resolutionKind = "external-package";
386
+ call.evidence = [
387
+ `receiver imported from external package ${record.externalPackage}`,
388
+ ];
389
+ continue;
390
+ }
391
+ call.evidence = call.evidence.length
392
+ ? call.evidence
393
+ : ["receiver type is not determined syntactically"];
394
+ continue;
395
+ }
396
+ // Bare call: same-file, then imported.
397
+ const local = localCallable(file, call.calleeName, caller);
398
+ if (local) {
399
+ settle(call, local, "same-file", "exact", `unique same-file callable ${local.name}`);
400
+ continue;
401
+ }
402
+ const record = importFor(file, call.calleeName);
403
+ if (record) {
404
+ const exportedName = record.kind === "default"
405
+ ? "default"
406
+ : (record.importedName ?? call.calleeName);
407
+ const target = importedSymbol(record, exportedName);
408
+ if (target) {
409
+ const aliased = record.kind === "named" && record.importedName !== record.localName;
410
+ settle(call, target, aliased
411
+ ? "aliased-import"
412
+ : record.kind === "default"
413
+ ? "default-import"
414
+ : "imported", "exact", aliased
415
+ ? `${record.localName} is an alias of ${record.importedName} from ${record.resolvedFile}`
416
+ : `imported ${exportedName} from ${record.resolvedFile}`);
417
+ continue;
418
+ }
419
+ if (record.externalPackage) {
420
+ call.externalPackage = record.externalPackage;
421
+ call.resolutionKind = "external-package";
422
+ call.evidence = [
423
+ `imported from external package ${record.externalPackage}`,
424
+ ];
425
+ continue;
426
+ }
427
+ call.evidence = [
428
+ `import of ${call.calleeName} did not resolve to source`,
429
+ ];
430
+ continue;
431
+ }
432
+ call.evidence = call.evidence.length
433
+ ? call.evidence
434
+ : ["no same-file or imported callable of that name"];
435
+ }
436
+ return graph;
437
+ }
438
+ export function typeScriptDiagnostics(context) {
439
+ const graph = buildGraph(context);
440
+ const imports = context.imports.filter((record) => !record.asset);
441
+ const exports = context.exports;
442
+ const resolvedImports = imports.filter((record) => record.resolvedFile).length;
443
+ const reexports = exports.filter((record) => record.fromModule);
444
+ const resolvedReexports = reexports.filter((record) => record.resolvedFile).length;
445
+ const aliasImports = imports.filter((record) => record.kind === "named" && record.importedName !== record.localName);
446
+ return {
447
+ assetImports: context.imports.filter((record) => record.asset).length,
448
+ importsTotal: imports.length,
449
+ importsResolved: resolvedImports,
450
+ relativeImportResolutionRate: rate(imports.filter((record) => record.module.startsWith(".") && record.resolvedFile).length, imports.filter((record) => record.module.startsWith(".")).length),
451
+ reexportsTotal: reexports.length,
452
+ reexportsResolved: resolvedReexports,
453
+ reexportResolutionRate: rate(resolvedReexports, reexports.length),
454
+ aliasImportsTotal: aliasImports.length,
455
+ aliasResolutionRate: rate(aliasImports.filter((record) => record.resolvedFile).length, aliasImports.length),
456
+ externalImports: imports.filter((record) => record.externalPackage).length,
457
+ externalPackageRate: rate(imports.filter((record) => record.externalPackage).length, imports.length),
458
+ unresolvedImports: graph.unresolvedImports.length,
459
+ cyclicExportChains: graph.cyclicExports.length,
460
+ };
461
+ }
462
+ const rate = (part, total) => total === 0 ? 1 : Number((part / total).toFixed(4));
463
+ export const typeScriptModulePath = (root, file) => resolvePath(root, file).slice(dirname(root).length);
@@ -0,0 +1,16 @@
1
+ import { readFileSync } from "node:fs";
2
+ function loadPackageInfo() {
3
+ for (const location of [
4
+ new URL("../package.json", import.meta.url),
5
+ new URL("../../package.json", import.meta.url),
6
+ ]) {
7
+ try {
8
+ return JSON.parse(readFileSync(location, "utf8"));
9
+ }
10
+ catch {
11
+ /* try the package root candidate */
12
+ }
13
+ }
14
+ throw new Error("Cannot locate package metadata relative to the installed ContextSlice runtime.");
15
+ }
16
+ export const packageInfo = loadPackageInfo();