skannr 0.1.6 → 0.2.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.
Files changed (58) hide show
  1. package/dist/languages/TreeSitterAdapter.d.ts +30 -0
  2. package/dist/languages/TreeSitterAdapter.d.ts.map +1 -0
  3. package/dist/languages/TreeSitterAdapter.js +466 -0
  4. package/dist/languages/TreeSitterAdapter.js.map +1 -0
  5. package/dist/languages/grammars/tree-sitter-go.wasm +0 -0
  6. package/dist/languages/grammars/tree-sitter-java.wasm +0 -0
  7. package/dist/languages/grammars/tree-sitter-python.wasm +0 -0
  8. package/dist/languages/grammars/tree-sitter-rust.wasm +0 -0
  9. package/dist/languages/lang-config.d.ts +38 -0
  10. package/dist/languages/lang-config.d.ts.map +1 -0
  11. package/dist/languages/lang-config.js +85 -0
  12. package/dist/languages/lang-config.js.map +1 -0
  13. package/dist/languages/registry.d.ts +6 -0
  14. package/dist/languages/registry.d.ts.map +1 -1
  15. package/dist/languages/registry.js +18 -4
  16. package/dist/languages/registry.js.map +1 -1
  17. package/package.json +4 -7
  18. package/src/languages/TreeSitterAdapter.ts +431 -0
  19. package/src/languages/lang-config.ts +113 -0
  20. package/src/languages/registry.ts +65 -49
  21. package/src/languages/PythonAdapter.ts +0 -302
  22. package/tests/agent.tools.test.ts +0 -81
  23. package/tests/benchmark.test.ts +0 -31
  24. package/tests/blast-radius.test.ts +0 -290
  25. package/tests/fixtures/sample.py +0 -17
  26. package/tests/fixtures/sample.ts +0 -13
  27. package/tests/fixtures/src/api/routes.ts +0 -1
  28. package/tests/fixtures/src/auth/permission.ts +0 -3
  29. package/tests/python-adapter.test.ts +0 -31
  30. package/tests/ranker-enhanced.test.ts +0 -70
  31. package/tests/ranker.test.ts +0 -79
  32. package/tests/scanner.scope.test.ts +0 -67
  33. package/tests/setup-fixtures.js +0 -29
  34. package/tests/skeletonizer.test.ts +0 -149
  35. package/uca-landing/index.html +0 -17
  36. package/uca-landing/package.json +0 -23
  37. package/uca-landing/postcss.config.js +0 -6
  38. package/uca-landing/src/App.jsx +0 -45
  39. package/uca-landing/src/components/AgentMode.jsx +0 -45
  40. package/uca-landing/src/components/CliReference.jsx +0 -89
  41. package/uca-landing/src/components/Features.jsx +0 -88
  42. package/uca-landing/src/components/Footer.jsx +0 -35
  43. package/uca-landing/src/components/Hero.jsx +0 -125
  44. package/uca-landing/src/components/HowItWorks.jsx +0 -65
  45. package/uca-landing/src/components/Install.jsx +0 -103
  46. package/uca-landing/src/components/LanguageSupport.jsx +0 -63
  47. package/uca-landing/src/components/Navbar.jsx +0 -86
  48. package/uca-landing/src/components/Problem.jsx +0 -51
  49. package/uca-landing/src/components/Reveal.jsx +0 -40
  50. package/uca-landing/src/components/TokenSavings.jsx +0 -126
  51. package/uca-landing/src/components/WorksWith.jsx +0 -59
  52. package/uca-landing/src/hooks/useScrollNav.js +0 -13
  53. package/uca-landing/src/hooks/useTypewriter.js +0 -41
  54. package/uca-landing/src/index.css +0 -13
  55. package/uca-landing/src/main.jsx +0 -10
  56. package/uca-landing/tailwind.config.js +0 -68
  57. package/uca-landing/vercel.json +0 -4
  58. package/uca-landing/vite.config.js +0 -6
@@ -1,4 +1,10 @@
1
+ /**
2
+ * Language adapter registry.
3
+ * Routes files to the appropriate parser based on extension.
4
+ */
1
5
  import { LanguageAdapter } from './LanguageAdapter';
2
6
  export declare function getAdapter(filePath: string): LanguageAdapter;
7
+ /** Await full initialization of WASM-based adapters. */
8
+ export declare function initializeAdapters(): Promise<void>;
3
9
  export declare function detectRepoLanguages(root: string): string[];
4
10
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/languages/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAapD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAG5D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/languages/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAmBpD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAG5D;AAED,wDAAwD;AACxD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAExD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ1D"}
@@ -1,4 +1,8 @@
1
1
  "use strict";
2
+ /**
3
+ * Language adapter registry.
4
+ * Routes files to the appropriate parser based on extension.
5
+ */
2
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
7
  if (k2 === undefined) k2 = k;
4
8
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -34,20 +38,30 @@ var __importStar = (this && this.__importStar) || (function () {
34
38
  })();
35
39
  Object.defineProperty(exports, "__esModule", { value: true });
36
40
  exports.getAdapter = getAdapter;
41
+ exports.initializeAdapters = initializeAdapters;
37
42
  exports.detectRepoLanguages = detectRepoLanguages;
38
43
  const TypeScriptAdapter_1 = require("./TypeScriptAdapter");
39
- const PythonAdapter_1 = require("./PythonAdapter");
44
+ const TreeSitterAdapter_1 = require("./TreeSitterAdapter");
40
45
  const GenericAdapter_1 = require("./GenericAdapter");
46
+ const lang_config_1 = require("./lang-config");
41
47
  const path = __importStar(require("path"));
48
+ const tsAdapter = new TypeScriptAdapter_1.TypeScriptAdapter();
49
+ const treeSitterAdapter = new TreeSitterAdapter_1.TreeSitterAdapter(Object.values(lang_config_1.LANG_CONFIGS));
50
+ const generic = new GenericAdapter_1.GenericAdapter();
51
+ // Initialize tree-sitter WASM in background (non-blocking).
52
+ treeSitterAdapter.initializeSync();
42
53
  const adapters = [
43
- new TypeScriptAdapter_1.TypeScriptAdapter(),
44
- new PythonAdapter_1.PythonAdapter(),
54
+ tsAdapter,
55
+ treeSitterAdapter,
45
56
  ];
46
- const generic = new GenericAdapter_1.GenericAdapter();
47
57
  function getAdapter(filePath) {
48
58
  const ext = path.extname(filePath).toLowerCase();
49
59
  return adapters.find((a) => a.extensions.includes(ext)) ?? generic;
50
60
  }
61
+ /** Await full initialization of WASM-based adapters. */
62
+ async function initializeAdapters() {
63
+ await treeSitterAdapter.initialize();
64
+ }
51
65
  function detectRepoLanguages(root) {
52
66
  const fs = require('fs');
53
67
  const extCount = {};
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/languages/registry.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,gCAGC;AAED,kDAQC;AAzBD,2DAAwD;AACxD,mDAAgD;AAChD,qDAAkD;AAClD,2CAA6B;AAE7B,MAAM,QAAQ,GAAsB;IAClC,IAAI,qCAAiB,EAAE;IACvB,IAAI,6BAAa,EAAE;CACpB,CAAC;AAEF,MAAM,OAAO,GAAG,IAAI,+BAAc,EAAE,CAAC;AAErC,SAAgB,UAAU,CAAC,QAAgB;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC;AACrE,CAAC;AAED,SAAgB,mBAAmB,CAAC,IAAY;IAC9C,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;SAC5B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,GAAW,EAAE,MAA8B,EAAE,KAAa,EAAE,QAAgB;IAChG,IAAI,KAAK,GAAG,QAAQ;QAAE,OAAO;IAC7B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;IAC5F,IAAI,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;gBAC9C,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iCAAiC;IACnC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/languages/registry.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skannr",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "A universal tool to analyze any codebase and generate semantic skeletons for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "skannr-mcp": "dist/mcp-cli.js"
12
12
  },
13
13
  "scripts": {
14
- "build": "tsc",
14
+ "build": "tsc && node -e \"const fs=require('fs'),p=require('path');const s=p.join('src','languages','grammars'),d=p.join('dist','languages','grammars');fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).filter(f=>f.endsWith('.wasm')).forEach(f=>fs.copyFileSync(p.join(s,f),p.join(d,f)));\"",
15
15
  "test": "jest",
16
16
  "lint": "eslint src --ext .ts",
17
17
  "start": "npm run build && node dist/cli.js",
@@ -34,11 +34,8 @@
34
34
  "commander": "^12.1.0",
35
35
  "ignore": "^7.0.5",
36
36
  "parse-diff": "0.12.0",
37
- "ts-morph": "^21.0.1"
38
- },
39
- "optionalDependencies": {
40
- "tree-sitter": "^0.25.0",
41
- "tree-sitter-python": "^0.25.0"
37
+ "ts-morph": "^21.0.1",
38
+ "web-tree-sitter": "0.26.10"
42
39
  },
43
40
  "devDependencies": {
44
41
  "@types/jest": "^29.5.11",
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Universal tree-sitter adapter using web-tree-sitter (WASM).
3
+ * Handles Python, Go, Rust, Java, and any future language by loading
4
+ * the appropriate grammar at runtime. No native compilation required.
5
+ */
6
+
7
+ import * as path from 'path';
8
+ import { LanguageAdapter, SkeletonResult, Symbol } from './LanguageAdapter';
9
+ import { LangConfig, getLangConfigForExt } from './lang-config';
10
+
11
+ // web-tree-sitter types (loaded dynamically to avoid ESM/CJS issues)
12
+ let Parser: any = null;
13
+ let initPromise: Promise<void> | null = null;
14
+ const loadedLanguages = new Map<string, any>();
15
+
16
+ /** Resolve path to a bundled .wasm grammar file. */
17
+ function grammarPath(filename: string): string {
18
+ return path.join(__dirname, 'grammars', filename);
19
+ }
20
+
21
+ /** Initialize the WASM parser (once). */
22
+ async function ensureInit(): Promise<boolean> {
23
+ if (Parser) return true;
24
+ if (initPromise) {
25
+ await initPromise;
26
+ return Parser !== null;
27
+ }
28
+ initPromise = (async () => {
29
+ try {
30
+ const mod = require('web-tree-sitter');
31
+ const P = mod.default ?? mod;
32
+ await P.init();
33
+ Parser = P;
34
+ } catch {
35
+ Parser = null;
36
+ }
37
+ })();
38
+ await initPromise;
39
+ return Parser !== null;
40
+ }
41
+
42
+ /** Load a language grammar (cached). */
43
+ async function loadLanguage(config: LangConfig): Promise<any | null> {
44
+ if (loadedLanguages.has(config.id)) {
45
+ return loadedLanguages.get(config.id);
46
+ }
47
+ try {
48
+ const wasmPath = grammarPath(config.grammarFile);
49
+ const lang = await Parser.Language.load(wasmPath);
50
+ loadedLanguages.set(config.id, lang);
51
+ return lang;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /** Synchronous wrapper — uses cached state if already initialized. */
58
+ function getParserSync(config: LangConfig): { parser: any; language: any } | null {
59
+ if (!Parser) return null;
60
+ const lang = loadedLanguages.get(config.id);
61
+ if (!lang) return null;
62
+ const parser = new Parser();
63
+ parser.setLanguage(lang);
64
+ return { parser, language: lang };
65
+ }
66
+
67
+ /**
68
+ * Extract the text of a node's named field, or null if not present.
69
+ */
70
+ function fieldText(node: any, fieldName: string): string | null {
71
+ const child = node.childForFieldName(fieldName);
72
+ return child ? child.text : null;
73
+ }
74
+
75
+ /**
76
+ * Get the signature of a function/method node (everything except the body).
77
+ */
78
+ function getSignature(node: any, lines: string[], config: LangConfig): string {
79
+ const bodyNode = node.childForFieldName(config.bodyField);
80
+ if (!bodyNode) {
81
+ // No body field — return the whole node text (might be a declaration)
82
+ return node.text;
83
+ }
84
+ const startRow = node.startPosition.row;
85
+ const bodyStartRow = bodyNode.startPosition.row;
86
+ // Return lines from node start to body start (exclusive of body content)
87
+ const sigLines = lines.slice(startRow, bodyStartRow);
88
+ if (sigLines.length === 0) {
89
+ // Body is on the same line as the signature
90
+ const fullLine = lines[startRow] || '';
91
+ const bodyCol = bodyNode.startPosition.column;
92
+ return fullLine.slice(0, bodyCol).trimEnd();
93
+ }
94
+ return sigLines.join('\n');
95
+ }
96
+
97
+ /**
98
+ * Get the indent level of a node based on its start column.
99
+ */
100
+ function indent(node: any): string {
101
+ return ' '.repeat(node.startPosition.column);
102
+ }
103
+
104
+ /**
105
+ * Determine the body placeholder based on language.
106
+ */
107
+ function bodyPlaceholder(config: LangConfig): string {
108
+ if (config.id === 'python') return ' ...';
109
+ return ' /* trimmed */';
110
+ }
111
+
112
+ /**
113
+ * Generate skeleton for a class node.
114
+ */
115
+ function skeletonizeClass(node: any, lines: string[], config: LangConfig): string[] {
116
+ const out: string[] = [];
117
+ const name = fieldText(node, config.nameField) || 'Unknown';
118
+ const bodyNode = node.childForFieldName(config.classBodyField);
119
+
120
+ // Class signature
121
+ const sig = getSignature(node, lines, config);
122
+ out.push(sig);
123
+
124
+ if (!bodyNode) {
125
+ return out;
126
+ }
127
+
128
+ // For Python, add body placeholder for the class opening
129
+ if (config.id === 'python') {
130
+ // Walk methods inside the class body
131
+ for (const child of bodyNode.namedChildren) {
132
+ if (config.methodTypes.includes(child.type)) {
133
+ out.push('');
134
+ const methodSig = getSignature(child, lines, config);
135
+ out.push(methodSig);
136
+ out.push(indent(child) + ' ...');
137
+ }
138
+ }
139
+ } else {
140
+ // For braced languages, output opening brace + method signatures
141
+ if (!sig.includes('{')) {
142
+ out.push(indent(node) + '{');
143
+ }
144
+ for (const child of bodyNode.namedChildren) {
145
+ if (config.methodTypes.includes(child.type)) {
146
+ const methodSig = getSignature(child, lines, config);
147
+ out.push(methodSig);
148
+ out.push(indent(child) + bodyPlaceholder(config));
149
+ if (!config.id.match(/python/)) {
150
+ out.push(indent(child) + '}');
151
+ }
152
+ out.push('');
153
+ } else if (child.type === 'field_declaration' ||
154
+ child.type === 'field_definition' ||
155
+ child.type === 'annotation_type_element_declaration') {
156
+ // Keep field declarations as-is
157
+ out.push(child.text);
158
+ }
159
+ }
160
+ out.push(indent(node) + '}');
161
+ }
162
+
163
+ return out;
164
+ }
165
+
166
+ /**
167
+ * Generate skeleton for a function node.
168
+ */
169
+ function skeletonizeFunction(node: any, lines: string[], config: LangConfig): string[] {
170
+ const out: string[] = [];
171
+ const sig = getSignature(node, lines, config);
172
+ out.push(sig);
173
+
174
+ if (config.id === 'python') {
175
+ out.push(indent(node) + ' ...');
176
+ } else {
177
+ if (!sig.trimEnd().endsWith('{')) {
178
+ out.push(indent(node) + '{');
179
+ }
180
+ out.push(indent(node) + bodyPlaceholder(config));
181
+ out.push(indent(node) + '}');
182
+ }
183
+
184
+ return out;
185
+ }
186
+
187
+ export class TreeSitterAdapter implements LanguageAdapter {
188
+ name: string;
189
+ extensions: string[];
190
+ private configs: LangConfig[];
191
+ private _initDone = false;
192
+
193
+ constructor(configs: LangConfig[]) {
194
+ this.configs = configs;
195
+ this.name = 'tree-sitter';
196
+ this.extensions = configs.flatMap((c) => c.extensions);
197
+ }
198
+
199
+ canHandle(filePath: string): boolean {
200
+ const ext = path.extname(filePath).toLowerCase();
201
+ return this.extensions.includes(ext);
202
+ }
203
+
204
+ /** Ensure WASM parser is initialized. Call at the start of each public method. */
205
+ private ensureReady(ext: string): LangConfig | null {
206
+ const config = getLangConfigForExt(ext);
207
+ if (!config) return null;
208
+ if (!Parser || !loadedLanguages.has(config.id)) return null;
209
+ return config;
210
+ }
211
+
212
+ /** Async initialization — must be called once before using the adapter. */
213
+ async initialize(): Promise<void> {
214
+ if (this._initDone) return;
215
+ const ok = await ensureInit();
216
+ if (!ok) return;
217
+ for (const config of this.configs) {
218
+ await loadLanguage(config);
219
+ }
220
+ this._initDone = true;
221
+ }
222
+
223
+ /** Synchronous initialization attempt (for compat with existing sync API). */
224
+ initializeSync(): void {
225
+ // Trigger async init in background. Methods will return fallbacks
226
+ // until initialization completes.
227
+ if (!this._initDone) {
228
+ this.initialize().catch(() => {});
229
+ }
230
+ }
231
+
232
+ generateSkeleton(content: string, filePath: string): SkeletonResult {
233
+ const ext = path.extname(filePath).toLowerCase();
234
+ const config = this.ensureReady(ext);
235
+ if (!config) {
236
+ return { skeleton: content.split('\n').slice(0, 50).join('\n') };
237
+ }
238
+
239
+ try {
240
+ const ctx = getParserSync(config);
241
+ if (!ctx) return { skeleton: content.split('\n').slice(0, 50).join('\n') };
242
+
243
+ const tree = ctx.parser.parse(content);
244
+ if (!tree) return { skeleton: content.split('\n').slice(0, 50).join('\n') };
245
+
246
+ const lines = content.split(/\r?\n/);
247
+ const skeletonLines: string[] = [];
248
+
249
+ skeletonLines.push(`/* Skeleton of ${path.basename(filePath)} */`);
250
+ skeletonLines.push('');
251
+
252
+ for (const child of tree.rootNode.namedChildren) {
253
+ if (config.importTypes.includes(child.type)) {
254
+ skeletonLines.push(child.text);
255
+ } else if (config.classTypes.includes(child.type)) {
256
+ skeletonLines.push('');
257
+ skeletonLines.push(...skeletonizeClass(child, lines, config));
258
+ } else if (config.functionTypes.includes(child.type)) {
259
+ skeletonLines.push('');
260
+ skeletonLines.push(...skeletonizeFunction(child, lines, config));
261
+ } else if (config.interfaceTypes.includes(child.type)) {
262
+ skeletonLines.push('');
263
+ skeletonLines.push(child.text);
264
+ } else if (config.typeTypes.includes(child.type)) {
265
+ skeletonLines.push('');
266
+ skeletonLines.push(child.text);
267
+ }
268
+ }
269
+
270
+ tree.delete();
271
+ ctx.parser.delete();
272
+
273
+ return { skeleton: skeletonLines.join('\n').trim() };
274
+ } catch {
275
+ return { skeleton: content.split('\n').slice(0, 50).join('\n') };
276
+ }
277
+ }
278
+
279
+ extractSymbols(content: string): Symbol[] {
280
+ // Determine the config from context (we don't have filePath here,
281
+ // so try each config until one parses successfully)
282
+ for (const config of this.configs) {
283
+ const ctx = getParserSync(config);
284
+ if (!ctx) continue;
285
+
286
+ try {
287
+ const tree = ctx.parser.parse(content);
288
+ if (!tree) { ctx.parser.delete(); continue; }
289
+
290
+ const symbols: Symbol[] = [];
291
+ this.walkSymbols(tree.rootNode, symbols, config);
292
+ tree.delete();
293
+ ctx.parser.delete();
294
+
295
+ if (symbols.length > 0) return symbols;
296
+ } catch {
297
+ ctx.parser.delete();
298
+ }
299
+ }
300
+ return [];
301
+ }
302
+
303
+ extractImports(content: string): string[] {
304
+ for (const config of this.configs) {
305
+ const ctx = getParserSync(config);
306
+ if (!ctx) continue;
307
+
308
+ try {
309
+ const tree = ctx.parser.parse(content);
310
+ if (!tree) { ctx.parser.delete(); continue; }
311
+
312
+ const imports: string[] = [];
313
+ for (const child of tree.rootNode.namedChildren) {
314
+ if (config.importTypes.includes(child.type)) {
315
+ // Extract module name from import
316
+ const text = child.text;
317
+ imports.push(text);
318
+ }
319
+ }
320
+ tree.delete();
321
+ ctx.parser.delete();
322
+
323
+ if (imports.length > 0) return imports;
324
+ } catch {
325
+ ctx.parser.delete();
326
+ }
327
+ }
328
+ return [];
329
+ }
330
+
331
+ /** Extract symbols with a file path hint for correct config selection. */
332
+ extractSymbolsForFile(content: string, filePath: string): Symbol[] {
333
+ const ext = path.extname(filePath).toLowerCase();
334
+ const config = this.ensureReady(ext);
335
+ if (!config) return [];
336
+
337
+ const ctx = getParserSync(config);
338
+ if (!ctx) return [];
339
+
340
+ try {
341
+ const tree = ctx.parser.parse(content);
342
+ if (!tree) { ctx.parser.delete(); return []; }
343
+
344
+ const symbols: Symbol[] = [];
345
+ this.walkSymbols(tree.rootNode, symbols, config);
346
+ tree.delete();
347
+ ctx.parser.delete();
348
+ return symbols;
349
+ } catch {
350
+ ctx.parser.delete();
351
+ return [];
352
+ }
353
+ }
354
+
355
+ /** Extract imports with a file path hint for correct config selection. */
356
+ extractImportsForFile(content: string, filePath: string): string[] {
357
+ const ext = path.extname(filePath).toLowerCase();
358
+ const config = this.ensureReady(ext);
359
+ if (!config) return [];
360
+
361
+ const ctx = getParserSync(config);
362
+ if (!ctx) return [];
363
+
364
+ try {
365
+ const tree = ctx.parser.parse(content);
366
+ if (!tree) { ctx.parser.delete(); return []; }
367
+
368
+ const imports: string[] = [];
369
+ for (const child of tree.rootNode.namedChildren) {
370
+ if (config.importTypes.includes(child.type)) {
371
+ imports.push(child.text);
372
+ }
373
+ }
374
+ tree.delete();
375
+ ctx.parser.delete();
376
+ return imports;
377
+ } catch {
378
+ ctx.parser.delete();
379
+ return [];
380
+ }
381
+ }
382
+
383
+ private walkSymbols(node: any, symbols: Symbol[], config: LangConfig): void {
384
+ for (const child of node.namedChildren) {
385
+ const name = fieldText(child, config.nameField);
386
+ if (!name) continue;
387
+
388
+ if (config.functionTypes.includes(child.type)) {
389
+ symbols.push({
390
+ name,
391
+ kind: 'function',
392
+ line: child.startPosition.row + 1,
393
+ });
394
+ } else if (config.classTypes.includes(child.type)) {
395
+ symbols.push({
396
+ name,
397
+ kind: 'class',
398
+ line: child.startPosition.row + 1,
399
+ });
400
+ // Walk class body for methods
401
+ const body = child.childForFieldName(config.classBodyField);
402
+ if (body) {
403
+ for (const member of body.namedChildren) {
404
+ if (config.methodTypes.includes(member.type)) {
405
+ const methodName = fieldText(member, config.nameField);
406
+ if (methodName) {
407
+ symbols.push({
408
+ name: methodName,
409
+ kind: 'function',
410
+ line: member.startPosition.row + 1,
411
+ });
412
+ }
413
+ }
414
+ }
415
+ }
416
+ } else if (config.interfaceTypes.includes(child.type)) {
417
+ symbols.push({
418
+ name,
419
+ kind: 'interface',
420
+ line: child.startPosition.row + 1,
421
+ });
422
+ } else if (config.typeTypes.includes(child.type)) {
423
+ symbols.push({
424
+ name,
425
+ kind: 'type',
426
+ line: child.startPosition.row + 1,
427
+ });
428
+ }
429
+ }
430
+ }
431
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Per-language AST node type mappings for tree-sitter grammars.
3
+ * Adding a new language = adding one entry to this map.
4
+ */
5
+
6
+ export interface LangConfig {
7
+ /** Language identifier. */
8
+ id: string;
9
+ /** File extensions this language handles (lowercase, with dot). */
10
+ extensions: string[];
11
+ /** Filename of the .wasm grammar (relative to grammars/ dir). */
12
+ grammarFile: string;
13
+ /** AST node types that represent top-level function declarations. */
14
+ functionTypes: string[];
15
+ /** AST node types that represent class declarations. */
16
+ classTypes: string[];
17
+ /** AST node types that represent methods inside classes. */
18
+ methodTypes: string[];
19
+ /** AST node types that represent import statements. */
20
+ importTypes: string[];
21
+ /** AST node types that represent interface/protocol declarations. */
22
+ interfaceTypes: string[];
23
+ /** AST node types that represent type alias declarations. */
24
+ typeTypes: string[];
25
+ /** The field name used to extract the symbol name (usually 'name'). */
26
+ nameField: string;
27
+ /** The field name for the function/method body (usually 'body' or 'block'). */
28
+ bodyField: string;
29
+ /** The field name for class body. */
30
+ classBodyField: string;
31
+ /** The field name for function parameters. */
32
+ parametersField: string;
33
+ /** The field name for return type annotation (if the language supports it). */
34
+ returnTypeField: string;
35
+ }
36
+
37
+ export const LANG_CONFIGS: Record<string, LangConfig> = {
38
+ python: {
39
+ id: 'python',
40
+ extensions: ['.py', '.pyi'],
41
+ grammarFile: 'tree-sitter-python.wasm',
42
+ functionTypes: ['function_definition'],
43
+ classTypes: ['class_definition'],
44
+ methodTypes: ['function_definition'],
45
+ importTypes: ['import_statement', 'import_from_statement'],
46
+ interfaceTypes: [],
47
+ typeTypes: [],
48
+ nameField: 'name',
49
+ bodyField: 'body',
50
+ classBodyField: 'body',
51
+ parametersField: 'parameters',
52
+ returnTypeField: 'return_type',
53
+ },
54
+ go: {
55
+ id: 'go',
56
+ extensions: ['.go'],
57
+ grammarFile: 'tree-sitter-go.wasm',
58
+ functionTypes: ['function_declaration'],
59
+ classTypes: ['type_declaration'],
60
+ methodTypes: ['method_declaration'],
61
+ importTypes: ['import_declaration'],
62
+ interfaceTypes: ['type_declaration'],
63
+ typeTypes: ['type_declaration'],
64
+ nameField: 'name',
65
+ bodyField: 'body',
66
+ classBodyField: 'body',
67
+ parametersField: 'parameters',
68
+ returnTypeField: 'result',
69
+ },
70
+ rust: {
71
+ id: 'rust',
72
+ extensions: ['.rs'],
73
+ grammarFile: 'tree-sitter-rust.wasm',
74
+ functionTypes: ['function_item'],
75
+ classTypes: ['struct_item', 'enum_item'],
76
+ methodTypes: ['function_item'],
77
+ importTypes: ['use_declaration'],
78
+ interfaceTypes: ['trait_item'],
79
+ typeTypes: ['type_item'],
80
+ nameField: 'name',
81
+ bodyField: 'body',
82
+ classBodyField: 'body',
83
+ parametersField: 'parameters',
84
+ returnTypeField: 'return_type',
85
+ },
86
+ java: {
87
+ id: 'java',
88
+ extensions: ['.java'],
89
+ grammarFile: 'tree-sitter-java.wasm',
90
+ functionTypes: ['method_declaration', 'constructor_declaration'],
91
+ classTypes: ['class_declaration', 'enum_declaration'],
92
+ methodTypes: ['method_declaration', 'constructor_declaration'],
93
+ importTypes: ['import_declaration'],
94
+ interfaceTypes: ['interface_declaration'],
95
+ typeTypes: [],
96
+ nameField: 'name',
97
+ bodyField: 'body',
98
+ classBodyField: 'body',
99
+ parametersField: 'parameters',
100
+ returnTypeField: 'type',
101
+ },
102
+ };
103
+
104
+ /** Get the language config for a file extension, or null if unsupported. */
105
+ export function getLangConfigForExt(ext: string): LangConfig | null {
106
+ const lower = ext.toLowerCase();
107
+ for (const config of Object.values(LANG_CONFIGS)) {
108
+ if (config.extensions.includes(lower)) {
109
+ return config;
110
+ }
111
+ }
112
+ return null;
113
+ }