@renseiai/agentfactory-code-intelligence 0.8.4

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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/__tests__/types.test.d.ts +2 -0
  3. package/dist/src/__tests__/types.test.d.ts.map +1 -0
  4. package/dist/src/__tests__/types.test.js +187 -0
  5. package/dist/src/index.d.ts +26 -0
  6. package/dist/src/index.d.ts.map +1 -0
  7. package/dist/src/index.js +29 -0
  8. package/dist/src/indexing/__tests__/indexing.test.d.ts +2 -0
  9. package/dist/src/indexing/__tests__/indexing.test.d.ts.map +1 -0
  10. package/dist/src/indexing/__tests__/indexing.test.js +193 -0
  11. package/dist/src/indexing/change-detector.d.ts +16 -0
  12. package/dist/src/indexing/change-detector.d.ts.map +1 -0
  13. package/dist/src/indexing/change-detector.js +38 -0
  14. package/dist/src/indexing/git-hash-provider.d.ts +11 -0
  15. package/dist/src/indexing/git-hash-provider.d.ts.map +1 -0
  16. package/dist/src/indexing/git-hash-provider.js +25 -0
  17. package/dist/src/indexing/incremental-indexer.d.ts +38 -0
  18. package/dist/src/indexing/incremental-indexer.d.ts.map +1 -0
  19. package/dist/src/indexing/incremental-indexer.js +122 -0
  20. package/dist/src/indexing/merkle-tree.d.ts +33 -0
  21. package/dist/src/indexing/merkle-tree.d.ts.map +1 -0
  22. package/dist/src/indexing/merkle-tree.js +107 -0
  23. package/dist/src/memory/__tests__/dedup.test.d.ts +2 -0
  24. package/dist/src/memory/__tests__/dedup.test.d.ts.map +1 -0
  25. package/dist/src/memory/__tests__/dedup.test.js +173 -0
  26. package/dist/src/memory/dedup-pipeline.d.ts +24 -0
  27. package/dist/src/memory/dedup-pipeline.d.ts.map +1 -0
  28. package/dist/src/memory/dedup-pipeline.js +73 -0
  29. package/dist/src/memory/memory-store.d.ts +22 -0
  30. package/dist/src/memory/memory-store.d.ts.map +1 -0
  31. package/dist/src/memory/memory-store.js +32 -0
  32. package/dist/src/memory/simhash.d.ts +16 -0
  33. package/dist/src/memory/simhash.d.ts.map +1 -0
  34. package/dist/src/memory/simhash.js +67 -0
  35. package/dist/src/memory/xxhash.d.ts +3 -0
  36. package/dist/src/memory/xxhash.d.ts.map +1 -0
  37. package/dist/src/memory/xxhash.js +13 -0
  38. package/dist/src/parser/__tests__/multi-language.test.d.ts +2 -0
  39. package/dist/src/parser/__tests__/multi-language.test.d.ts.map +1 -0
  40. package/dist/src/parser/__tests__/multi-language.test.js +350 -0
  41. package/dist/src/parser/__tests__/symbol-extractor.test.d.ts +2 -0
  42. package/dist/src/parser/__tests__/symbol-extractor.test.d.ts.map +1 -0
  43. package/dist/src/parser/__tests__/symbol-extractor.test.js +188 -0
  44. package/dist/src/parser/go-extractor.d.ts +8 -0
  45. package/dist/src/parser/go-extractor.d.ts.map +1 -0
  46. package/dist/src/parser/go-extractor.js +127 -0
  47. package/dist/src/parser/python-extractor.d.ts +8 -0
  48. package/dist/src/parser/python-extractor.d.ts.map +1 -0
  49. package/dist/src/parser/python-extractor.js +92 -0
  50. package/dist/src/parser/rust-extractor.d.ts +8 -0
  51. package/dist/src/parser/rust-extractor.d.ts.map +1 -0
  52. package/dist/src/parser/rust-extractor.js +168 -0
  53. package/dist/src/parser/symbol-extractor.d.ts +14 -0
  54. package/dist/src/parser/symbol-extractor.d.ts.map +1 -0
  55. package/dist/src/parser/symbol-extractor.js +47 -0
  56. package/dist/src/parser/typescript-extractor.d.ts +13 -0
  57. package/dist/src/parser/typescript-extractor.d.ts.map +1 -0
  58. package/dist/src/parser/typescript-extractor.js +229 -0
  59. package/dist/src/plugin/__tests__/plugin.test.d.ts +2 -0
  60. package/dist/src/plugin/__tests__/plugin.test.d.ts.map +1 -0
  61. package/dist/src/plugin/__tests__/plugin.test.js +48 -0
  62. package/dist/src/plugin/code-intelligence-plugin.d.ts +15 -0
  63. package/dist/src/plugin/code-intelligence-plugin.d.ts.map +1 -0
  64. package/dist/src/plugin/code-intelligence-plugin.js +102 -0
  65. package/dist/src/repo-map/__tests__/repo-map.test.d.ts +2 -0
  66. package/dist/src/repo-map/__tests__/repo-map.test.d.ts.map +1 -0
  67. package/dist/src/repo-map/__tests__/repo-map.test.js +186 -0
  68. package/dist/src/repo-map/dependency-graph.d.ts +30 -0
  69. package/dist/src/repo-map/dependency-graph.d.ts.map +1 -0
  70. package/dist/src/repo-map/dependency-graph.js +105 -0
  71. package/dist/src/repo-map/pagerank.d.ts +20 -0
  72. package/dist/src/repo-map/pagerank.d.ts.map +1 -0
  73. package/dist/src/repo-map/pagerank.js +68 -0
  74. package/dist/src/repo-map/repo-map-generator.d.ts +20 -0
  75. package/dist/src/repo-map/repo-map-generator.d.ts.map +1 -0
  76. package/dist/src/repo-map/repo-map-generator.js +66 -0
  77. package/dist/src/search/__tests__/search.test.d.ts +2 -0
  78. package/dist/src/search/__tests__/search.test.d.ts.map +1 -0
  79. package/dist/src/search/__tests__/search.test.js +191 -0
  80. package/dist/src/search/bm25.d.ts +24 -0
  81. package/dist/src/search/bm25.d.ts.map +1 -0
  82. package/dist/src/search/bm25.js +44 -0
  83. package/dist/src/search/inverted-index.d.ts +31 -0
  84. package/dist/src/search/inverted-index.d.ts.map +1 -0
  85. package/dist/src/search/inverted-index.js +72 -0
  86. package/dist/src/search/search-engine.d.ts +22 -0
  87. package/dist/src/search/search-engine.d.ts.map +1 -0
  88. package/dist/src/search/search-engine.js +76 -0
  89. package/dist/src/search/tokenizer.d.ts +11 -0
  90. package/dist/src/search/tokenizer.d.ts.map +1 -0
  91. package/dist/src/search/tokenizer.js +48 -0
  92. package/dist/src/types.d.ts +242 -0
  93. package/dist/src/types.d.ts.map +1 -0
  94. package/dist/src/types.js +96 -0
  95. package/package.json +74 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SimHash implementation for near-duplicate detection.
3
+ * Produces a 64-bit fingerprint where similar content produces similar hashes.
4
+ */
5
+ /** Simple string hash for tokens (FNV-1a-like, 64-bit safe). */
6
+ function tokenHash(token) {
7
+ let hash = 0xcbf29ce484222325n;
8
+ for (let i = 0; i < token.length; i++) {
9
+ hash ^= BigInt(token.charCodeAt(i));
10
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
11
+ }
12
+ return hash;
13
+ }
14
+ export class SimHash {
15
+ bits;
16
+ constructor(bits = 64) {
17
+ this.bits = bits;
18
+ }
19
+ /** Compute a SimHash fingerprint for the given text. */
20
+ compute(text) {
21
+ const tokens = this.tokenize(text);
22
+ if (tokens.length === 0)
23
+ return 0n;
24
+ // Weight vector: one entry per bit
25
+ const weights = new Float64Array(this.bits);
26
+ for (const token of tokens) {
27
+ const hash = tokenHash(token);
28
+ for (let i = 0; i < this.bits; i++) {
29
+ if ((hash >> BigInt(i)) & 1n) {
30
+ weights[i] += 1;
31
+ }
32
+ else {
33
+ weights[i] -= 1;
34
+ }
35
+ }
36
+ }
37
+ // Convert weight vector to fingerprint
38
+ let fingerprint = 0n;
39
+ for (let i = 0; i < this.bits; i++) {
40
+ if (weights[i] > 0) {
41
+ fingerprint |= 1n << BigInt(i);
42
+ }
43
+ }
44
+ return fingerprint;
45
+ }
46
+ /** Compute Hamming distance between two fingerprints. */
47
+ hammingDistance(a, b) {
48
+ let xor = a ^ b;
49
+ let count = 0;
50
+ while (xor > 0n) {
51
+ count += Number(xor & 1n);
52
+ xor >>= 1n;
53
+ }
54
+ return count;
55
+ }
56
+ /** Check if two fingerprints are near-duplicates within threshold. */
57
+ isNearDuplicate(a, b, threshold = 3) {
58
+ return this.hammingDistance(a, b) <= threshold;
59
+ }
60
+ tokenize(text) {
61
+ // Split on whitespace and punctuation, lowercase, filter short tokens
62
+ return text
63
+ .toLowerCase()
64
+ .split(/[\s\p{P}]+/u)
65
+ .filter(t => t.length >= 2);
66
+ }
67
+ }
@@ -0,0 +1,3 @@
1
+ /** Compute a 64-bit xxHash hex string from content. */
2
+ export declare function xxhash64(content: string): Promise<string>;
3
+ //# sourceMappingURL=xxhash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xxhash.d.ts","sourceRoot":"","sources":["../../../src/memory/xxhash.ts"],"names":[],"mappings":"AAWA,uDAAuD;AACvD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/D"}
@@ -0,0 +1,13 @@
1
+ import xxhashWasm from 'xxhash-wasm';
2
+ let hasherPromise;
3
+ function getHasher() {
4
+ if (!hasherPromise) {
5
+ hasherPromise = xxhashWasm();
6
+ }
7
+ return hasherPromise;
8
+ }
9
+ /** Compute a 64-bit xxHash hex string from content. */
10
+ export async function xxhash64(content) {
11
+ const hasher = await getHasher();
12
+ return hasher.h64ToString(content);
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=multi-language.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-language.test.d.ts","sourceRoot":"","sources":["../../../../src/parser/__tests__/multi-language.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,350 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { PythonExtractor } from '../python-extractor.js';
3
+ import { GoExtractor } from '../go-extractor.js';
4
+ import { RustExtractor } from '../rust-extractor.js';
5
+ import { SymbolExtractor } from '../symbol-extractor.js';
6
+ // ── Python ────────────────────────────────────────────────────────────
7
+ const samplePython = `
8
+ import os
9
+ from typing import List, Optional
10
+ from dataclasses import dataclass
11
+
12
+ @dataclass
13
+ class User:
14
+ name: str
15
+ email: str
16
+
17
+ def full_name(self) -> str:
18
+ return self.name
19
+
20
+ class _InternalHelper:
21
+ pass
22
+
23
+ def process_data(items: List[str]) -> List[str]:
24
+ return [item.strip() for item in items]
25
+
26
+ async def fetch_users(url: str) -> List[User]:
27
+ pass
28
+
29
+ _private_func = lambda x: x
30
+
31
+ MAX_RETRIES = 3
32
+ `;
33
+ describe('PythonExtractor', () => {
34
+ const extractor = new PythonExtractor();
35
+ it('extracts imports', () => {
36
+ const result = extractor.extract(samplePython, 'main.py');
37
+ expect(result.imports).toContain('os');
38
+ expect(result.imports).toContain('typing');
39
+ });
40
+ it('extracts classes', () => {
41
+ const result = extractor.extract(samplePython, 'main.py');
42
+ const user = result.symbols.find(s => s.name === 'User' && s.kind === 'class');
43
+ expect(user).toBeDefined();
44
+ expect(user.exported).toBe(true);
45
+ });
46
+ it('marks internal classes as not exported', () => {
47
+ const result = extractor.extract(samplePython, 'main.py');
48
+ const internal = result.symbols.find(s => s.name === '_InternalHelper');
49
+ expect(internal).toBeDefined();
50
+ expect(internal.exported).toBe(false);
51
+ });
52
+ it('extracts functions', () => {
53
+ const result = extractor.extract(samplePython, 'main.py');
54
+ const fn = result.symbols.find(s => s.name === 'process_data' && s.kind === 'function');
55
+ expect(fn).toBeDefined();
56
+ expect(fn.exported).toBe(true);
57
+ });
58
+ it('extracts async functions', () => {
59
+ const result = extractor.extract(samplePython, 'main.py');
60
+ const fn = result.symbols.find(s => s.name === 'fetch_users' && s.kind === 'function');
61
+ expect(fn).toBeDefined();
62
+ });
63
+ it('extracts methods', () => {
64
+ const result = extractor.extract(samplePython, 'main.py');
65
+ const method = result.symbols.find(s => s.name === 'full_name' && s.kind === 'method');
66
+ expect(method).toBeDefined();
67
+ });
68
+ it('extracts decorators', () => {
69
+ const result = extractor.extract(samplePython, 'main.py');
70
+ const dec = result.symbols.find(s => s.name === 'dataclass' && s.kind === 'decorator');
71
+ expect(dec).toBeDefined();
72
+ });
73
+ it('sets language to python', () => {
74
+ const result = extractor.extract(samplePython, 'main.py');
75
+ expect(result.language).toBe('python');
76
+ for (const s of result.symbols) {
77
+ expect(s.language).toBe('python');
78
+ }
79
+ });
80
+ });
81
+ // ── Go ────────────────────────────────────────────────────────────────
82
+ const sampleGo = `
83
+ package main
84
+
85
+ import (
86
+ "fmt"
87
+ "net/http"
88
+ )
89
+
90
+ // Server represents an HTTP server
91
+ type Server struct {
92
+ Port int
93
+ Host string
94
+ }
95
+
96
+ // Handler is the request handler interface
97
+ type Handler interface {
98
+ ServeHTTP(w http.ResponseWriter, r *http.Request)
99
+ }
100
+
101
+ type myInternal struct{}
102
+
103
+ // NewServer creates a new Server instance
104
+ func NewServer(port int) *Server {
105
+ return &Server{Port: port}
106
+ }
107
+
108
+ // Start starts the server
109
+ func (s *Server) Start() error {
110
+ return http.ListenAndServe(fmt.Sprintf("%s:%d", s.Host, s.Port), nil)
111
+ }
112
+
113
+ func (s *Server) internal() {}
114
+
115
+ var DefaultPort = 8080
116
+ const Version = "1.0"
117
+ `;
118
+ describe('GoExtractor', () => {
119
+ const extractor = new GoExtractor();
120
+ it('extracts imports', () => {
121
+ const result = extractor.extract(sampleGo, 'main.go');
122
+ expect(result.imports).toContain('fmt');
123
+ expect(result.imports).toContain('net/http');
124
+ });
125
+ it('extracts structs', () => {
126
+ const result = extractor.extract(sampleGo, 'main.go');
127
+ const srv = result.symbols.find(s => s.name === 'Server' && s.kind === 'struct');
128
+ expect(srv).toBeDefined();
129
+ expect(srv.exported).toBe(true);
130
+ });
131
+ it('extracts interfaces', () => {
132
+ const result = extractor.extract(sampleGo, 'main.go');
133
+ const handler = result.symbols.find(s => s.name === 'Handler' && s.kind === 'interface');
134
+ expect(handler).toBeDefined();
135
+ expect(handler.exported).toBe(true);
136
+ });
137
+ it('marks unexported types', () => {
138
+ const result = extractor.extract(sampleGo, 'main.go');
139
+ const internal = result.symbols.find(s => s.name === 'myInternal');
140
+ expect(internal).toBeDefined();
141
+ expect(internal.exported).toBe(false);
142
+ });
143
+ it('extracts functions', () => {
144
+ const result = extractor.extract(sampleGo, 'main.go');
145
+ const fn = result.symbols.find(s => s.name === 'NewServer' && s.kind === 'function');
146
+ expect(fn).toBeDefined();
147
+ expect(fn.exported).toBe(true);
148
+ expect(fn.documentation).toContain('creates a new Server instance');
149
+ });
150
+ it('extracts methods with receivers', () => {
151
+ const result = extractor.extract(sampleGo, 'main.go');
152
+ const method = result.symbols.find(s => s.name === 'Start' && s.kind === 'method');
153
+ expect(method).toBeDefined();
154
+ expect(method.parentName).toBe('Server');
155
+ expect(method.exported).toBe(true);
156
+ });
157
+ it('marks unexported methods', () => {
158
+ const result = extractor.extract(sampleGo, 'main.go');
159
+ const method = result.symbols.find(s => s.name === 'internal' && s.kind === 'method');
160
+ expect(method).toBeDefined();
161
+ expect(method.exported).toBe(false);
162
+ });
163
+ it('extracts variables and constants', () => {
164
+ const result = extractor.extract(sampleGo, 'main.go');
165
+ const v = result.symbols.find(s => s.name === 'DefaultPort');
166
+ expect(v).toBeDefined();
167
+ const c = result.symbols.find(s => s.name === 'Version');
168
+ expect(c).toBeDefined();
169
+ });
170
+ it('sets language to go', () => {
171
+ const result = extractor.extract(sampleGo, 'main.go');
172
+ expect(result.language).toBe('go');
173
+ });
174
+ });
175
+ // ── Rust ──────────────────────────────────────────────────────────────
176
+ const sampleRust = `
177
+ use std::io::{self, Read};
178
+ use serde::{Serialize, Deserialize};
179
+
180
+ /// A configuration struct
181
+ pub struct Config {
182
+ pub name: String,
183
+ pub version: u32,
184
+ }
185
+
186
+ struct InternalState {
187
+ data: Vec<u8>,
188
+ }
189
+
190
+ /// The main trait for services
191
+ pub trait Service {
192
+ fn handle(&self, request: Request) -> Response;
193
+ }
194
+
195
+ impl Service for Config {
196
+ fn handle(&self, request: Request) -> Response {
197
+ unimplemented!()
198
+ }
199
+ }
200
+
201
+ impl Config {
202
+ pub fn new(name: String) -> Self {
203
+ Config { name, version: 1 }
204
+ }
205
+ }
206
+
207
+ /// Process incoming data
208
+ pub async fn process(data: &[u8]) -> io::Result<Vec<u8>> {
209
+ Ok(data.to_vec())
210
+ }
211
+
212
+ fn internal_helper() -> bool {
213
+ true
214
+ }
215
+
216
+ pub enum Status {
217
+ Active,
218
+ Inactive,
219
+ }
220
+
221
+ macro_rules! log_error {
222
+ ($msg:expr) => {
223
+ eprintln!("ERROR: {}", $msg);
224
+ };
225
+ }
226
+
227
+ pub const MAX_SIZE: usize = 1024;
228
+ pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
229
+ pub mod utils;
230
+ `;
231
+ describe('RustExtractor', () => {
232
+ const extractor = new RustExtractor();
233
+ it('extracts use statements', () => {
234
+ const result = extractor.extract(sampleRust, 'lib.rs');
235
+ expect(result.imports).toContain('std::io::{self, Read}');
236
+ expect(result.imports).toContain('serde::{Serialize, Deserialize}');
237
+ });
238
+ it('extracts pub structs', () => {
239
+ const result = extractor.extract(sampleRust, 'lib.rs');
240
+ const cfg = result.symbols.find(s => s.name === 'Config' && s.kind === 'struct');
241
+ expect(cfg).toBeDefined();
242
+ expect(cfg.exported).toBe(true);
243
+ expect(cfg.documentation).toContain('configuration struct');
244
+ });
245
+ it('marks private structs', () => {
246
+ const result = extractor.extract(sampleRust, 'lib.rs');
247
+ const internal = result.symbols.find(s => s.name === 'InternalState');
248
+ expect(internal).toBeDefined();
249
+ expect(internal.exported).toBe(false);
250
+ });
251
+ it('extracts traits', () => {
252
+ const result = extractor.extract(sampleRust, 'lib.rs');
253
+ const trait_ = result.symbols.find(s => s.name === 'Service' && s.kind === 'trait');
254
+ expect(trait_).toBeDefined();
255
+ expect(trait_.exported).toBe(true);
256
+ });
257
+ it('extracts impl blocks', () => {
258
+ const result = extractor.extract(sampleRust, 'lib.rs');
259
+ const impls = result.symbols.filter(s => s.kind === 'impl');
260
+ expect(impls.length).toBeGreaterThanOrEqual(2);
261
+ const traitImpl = impls.find(s => s.name.includes('Service for Config'));
262
+ expect(traitImpl).toBeDefined();
263
+ });
264
+ it('extracts functions', () => {
265
+ const result = extractor.extract(sampleRust, 'lib.rs');
266
+ const fn_ = result.symbols.find(s => s.name === 'process' && s.kind === 'function');
267
+ expect(fn_).toBeDefined();
268
+ expect(fn_.exported).toBe(true);
269
+ expect(fn_.documentation).toContain('Process incoming data');
270
+ });
271
+ it('marks private functions', () => {
272
+ const result = extractor.extract(sampleRust, 'lib.rs');
273
+ const fn_ = result.symbols.find(s => s.name === 'internal_helper');
274
+ expect(fn_).toBeDefined();
275
+ expect(fn_.exported).toBe(false);
276
+ });
277
+ it('extracts enums', () => {
278
+ const result = extractor.extract(sampleRust, 'lib.rs');
279
+ const en = result.symbols.find(s => s.name === 'Status' && s.kind === 'enum');
280
+ expect(en).toBeDefined();
281
+ expect(en.exported).toBe(true);
282
+ });
283
+ it('extracts macros', () => {
284
+ const result = extractor.extract(sampleRust, 'lib.rs');
285
+ const macro_ = result.symbols.find(s => s.name === 'log_error' && s.kind === 'macro');
286
+ expect(macro_).toBeDefined();
287
+ });
288
+ it('extracts const/type/mod', () => {
289
+ const result = extractor.extract(sampleRust, 'lib.rs');
290
+ expect(result.symbols.find(s => s.name === 'MAX_SIZE' && s.kind === 'variable')).toBeDefined();
291
+ expect(result.symbols.find(s => s.name === 'Result' && s.kind === 'type')).toBeDefined();
292
+ expect(result.symbols.find(s => s.name === 'utils' && s.kind === 'module')).toBeDefined();
293
+ });
294
+ it('sets language to rust', () => {
295
+ const result = extractor.extract(sampleRust, 'lib.rs');
296
+ expect(result.language).toBe('rust');
297
+ });
298
+ });
299
+ // ── Multi-language registration ───────────────────────────────────────
300
+ describe('SymbolExtractor multi-language', () => {
301
+ it('registers additional extractors', () => {
302
+ const extractor = new SymbolExtractor();
303
+ extractor.registerExtractor(new PythonExtractor());
304
+ extractor.registerExtractor(new GoExtractor());
305
+ extractor.registerExtractor(new RustExtractor());
306
+ expect(extractor.supportsLanguage('python')).toBe(true);
307
+ expect(extractor.supportsLanguage('go')).toBe(true);
308
+ expect(extractor.supportsLanguage('rust')).toBe(true);
309
+ });
310
+ it('extracts Python from source', () => {
311
+ const extractor = new SymbolExtractor();
312
+ extractor.registerExtractor(new PythonExtractor());
313
+ const result = extractor.extractFromSource('def hello(): pass', 'test.py');
314
+ expect(result.symbols.length).toBeGreaterThan(0);
315
+ expect(result.language).toBe('python');
316
+ });
317
+ it('extracts Go from source', () => {
318
+ const extractor = new SymbolExtractor();
319
+ extractor.registerExtractor(new GoExtractor());
320
+ const result = extractor.extractFromSource('func main() {}', 'main.go');
321
+ expect(result.symbols.length).toBeGreaterThan(0);
322
+ expect(result.language).toBe('go');
323
+ });
324
+ it('extracts Rust from source', () => {
325
+ const extractor = new SymbolExtractor();
326
+ extractor.registerExtractor(new RustExtractor());
327
+ const result = extractor.extractFromSource('pub fn main() {}', 'main.rs');
328
+ expect(result.symbols.length).toBeGreaterThan(0);
329
+ expect(result.language).toBe('rust');
330
+ });
331
+ it('all languages map to unified SymbolKind types', () => {
332
+ const validKinds = new Set([
333
+ 'function', 'class', 'interface', 'type', 'variable', 'method',
334
+ 'property', 'import', 'export', 'enum', 'struct', 'trait',
335
+ 'impl', 'macro', 'decorator', 'module',
336
+ ]);
337
+ const extractor = new SymbolExtractor();
338
+ extractor.registerExtractor(new PythonExtractor());
339
+ extractor.registerExtractor(new GoExtractor());
340
+ extractor.registerExtractor(new RustExtractor());
341
+ const py = extractor.extractFromSource(samplePython, 'test.py');
342
+ const go = extractor.extractFromSource(sampleGo, 'test.go');
343
+ const rs = extractor.extractFromSource(sampleRust, 'test.rs');
344
+ for (const ast of [py, go, rs]) {
345
+ for (const symbol of ast.symbols) {
346
+ expect(validKinds.has(symbol.kind)).toBe(true);
347
+ }
348
+ }
349
+ });
350
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=symbol-extractor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-extractor.test.d.ts","sourceRoot":"","sources":["../../../../src/parser/__tests__/symbol-extractor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SymbolExtractor } from '../symbol-extractor.js';
3
+ import { TypeScriptExtractor } from '../typescript-extractor.js';
4
+ const sampleTS = `
5
+ import { z } from 'zod'
6
+ import type { Foo } from './foo'
7
+
8
+ /** Configuration interface */
9
+ export interface Config {
10
+ name: string
11
+ version: number
12
+ }
13
+
14
+ export type Result<T> = { data: T; error?: string }
15
+
16
+ export const DEFAULT_TIMEOUT = 5000
17
+
18
+ /** Main handler function */
19
+ export async function handleRequest(req: Request): Promise<Response> {
20
+ return new Response('ok')
21
+ }
22
+
23
+ export const processData = (input: string) => {
24
+ return input.trim()
25
+ }
26
+
27
+ export class UserService {
28
+ private db: Database
29
+
30
+ constructor(db: Database) {
31
+ this.db = db
32
+ }
33
+
34
+ async getUser(id: string): Promise<User> {
35
+ return this.db.find(id)
36
+ }
37
+
38
+ async deleteUser(id: string): Promise<void> {
39
+ await this.db.delete(id)
40
+ }
41
+ }
42
+
43
+ export enum Status {
44
+ Active = 'active',
45
+ Inactive = 'inactive',
46
+ }
47
+
48
+ @deprecated
49
+ class OldService {}
50
+ `;
51
+ const sampleJS = `
52
+ const express = require('express')
53
+ const { Router } = require('express')
54
+
55
+ function createServer(port) {
56
+ const app = express()
57
+ return app.listen(port)
58
+ }
59
+
60
+ class APIClient {
61
+ constructor(baseUrl) {
62
+ this.baseUrl = baseUrl
63
+ }
64
+
65
+ async fetch(path) {
66
+ return fetch(this.baseUrl + path)
67
+ }
68
+ }
69
+
70
+ const helper = (x) => x * 2
71
+
72
+ module.exports = { createServer, APIClient }
73
+ `;
74
+ describe('TypeScriptExtractor', () => {
75
+ const extractor = new TypeScriptExtractor();
76
+ it('extracts imports', () => {
77
+ const result = extractor.extract(sampleTS, 'sample.ts');
78
+ expect(result.imports).toContain('zod');
79
+ expect(result.imports).toContain('./foo');
80
+ });
81
+ it('extracts interface', () => {
82
+ const result = extractor.extract(sampleTS, 'sample.ts');
83
+ const iface = result.symbols.find(s => s.name === 'Config' && s.kind === 'interface');
84
+ expect(iface).toBeDefined();
85
+ expect(iface.exported).toBe(true);
86
+ });
87
+ it('extracts type alias', () => {
88
+ const result = extractor.extract(sampleTS, 'sample.ts');
89
+ const type = result.symbols.find(s => s.name === 'Result' && s.kind === 'type');
90
+ expect(type).toBeDefined();
91
+ expect(type.exported).toBe(true);
92
+ });
93
+ it('extracts exported variable', () => {
94
+ const result = extractor.extract(sampleTS, 'sample.ts');
95
+ const v = result.symbols.find(s => s.name === 'DEFAULT_TIMEOUT' && s.kind === 'variable');
96
+ expect(v).toBeDefined();
97
+ expect(v.exported).toBe(true);
98
+ });
99
+ it('extracts async function with JSDoc', () => {
100
+ const result = extractor.extract(sampleTS, 'sample.ts');
101
+ const fn = result.symbols.find(s => s.name === 'handleRequest' && s.kind === 'function');
102
+ expect(fn).toBeDefined();
103
+ expect(fn.exported).toBe(true);
104
+ expect(fn.documentation).toContain('Main handler function');
105
+ });
106
+ it('extracts arrow function', () => {
107
+ const result = extractor.extract(sampleTS, 'sample.ts');
108
+ const fn = result.symbols.find(s => s.name === 'processData' && s.kind === 'function');
109
+ expect(fn).toBeDefined();
110
+ expect(fn.exported).toBe(true);
111
+ });
112
+ it('extracts class with methods', () => {
113
+ const result = extractor.extract(sampleTS, 'sample.ts');
114
+ const cls = result.symbols.find(s => s.name === 'UserService' && s.kind === 'class');
115
+ expect(cls).toBeDefined();
116
+ expect(cls.exported).toBe(true);
117
+ const methods = result.symbols.filter(s => s.parentName === 'UserService' && s.kind === 'method');
118
+ const methodNames = methods.map(m => m.name);
119
+ expect(methodNames).toContain('getUser');
120
+ expect(methodNames).toContain('deleteUser');
121
+ });
122
+ it('extracts enum', () => {
123
+ const result = extractor.extract(sampleTS, 'sample.ts');
124
+ const en = result.symbols.find(s => s.name === 'Status' && s.kind === 'enum');
125
+ expect(en).toBeDefined();
126
+ });
127
+ it('extracts decorators', () => {
128
+ const result = extractor.extract(sampleTS, 'sample.ts');
129
+ const dec = result.symbols.find(s => s.name === 'deprecated' && s.kind === 'decorator');
130
+ expect(dec).toBeDefined();
131
+ });
132
+ it('tracks exports', () => {
133
+ const result = extractor.extract(sampleTS, 'sample.ts');
134
+ expect(result.exports).toContain('Config');
135
+ expect(result.exports).toContain('handleRequest');
136
+ expect(result.exports).toContain('UserService');
137
+ });
138
+ it('sets correct language for .ts files', () => {
139
+ const result = extractor.extract(sampleTS, 'sample.ts');
140
+ expect(result.language).toBe('typescript');
141
+ });
142
+ it('sets correct language for .js files', () => {
143
+ const result = extractor.extract(sampleJS, 'sample.js');
144
+ expect(result.language).toBe('javascript');
145
+ });
146
+ it('extracts JavaScript functions and classes', () => {
147
+ const result = extractor.extract(sampleJS, 'sample.js');
148
+ const fn = result.symbols.find(s => s.name === 'createServer' && s.kind === 'function');
149
+ expect(fn).toBeDefined();
150
+ const cls = result.symbols.find(s => s.name === 'APIClient' && s.kind === 'class');
151
+ expect(cls).toBeDefined();
152
+ });
153
+ it('extracts JS arrow functions', () => {
154
+ const result = extractor.extract(sampleJS, 'sample.js');
155
+ const fn = result.symbols.find(s => s.name === 'helper' && s.kind === 'function');
156
+ expect(fn).toBeDefined();
157
+ });
158
+ });
159
+ describe('SymbolExtractor', () => {
160
+ const extractor = new SymbolExtractor();
161
+ it('detects typescript language', () => {
162
+ expect(extractor.detectLanguage('src/main.ts')).toBe('typescript');
163
+ expect(extractor.detectLanguage('src/app.tsx')).toBe('typescript');
164
+ });
165
+ it('detects javascript language', () => {
166
+ expect(extractor.detectLanguage('lib/utils.js')).toBe('javascript');
167
+ expect(extractor.detectLanguage('lib/utils.mjs')).toBe('javascript');
168
+ });
169
+ it('detects unknown language', () => {
170
+ expect(extractor.detectLanguage('main.py')).toBe('python');
171
+ expect(extractor.detectLanguage('readme.md')).toBe('unknown');
172
+ });
173
+ it('supports typescript and javascript', () => {
174
+ expect(extractor.supportsLanguage('typescript')).toBe(true);
175
+ expect(extractor.supportsLanguage('javascript')).toBe(true);
176
+ expect(extractor.supportsLanguage('python')).toBe(false);
177
+ });
178
+ it('extracts from source', () => {
179
+ const result = extractor.extractFromSource(sampleTS, 'test.ts');
180
+ expect(result.symbols.length).toBeGreaterThan(0);
181
+ expect(result.language).toBe('typescript');
182
+ });
183
+ it('returns empty for unsupported language', () => {
184
+ const result = extractor.extractFromSource('def hello(): pass', 'test.py');
185
+ expect(result.symbols).toHaveLength(0);
186
+ expect(result.language).toBe('python');
187
+ });
188
+ });
@@ -0,0 +1,8 @@
1
+ import type { FileAST } from '../types.js';
2
+ import type { LanguageExtractor } from './symbol-extractor.js';
3
+ /** Regex-based Go symbol extractor. */
4
+ export declare class GoExtractor implements LanguageExtractor {
5
+ languages: string[];
6
+ extract(source: string, filePath: string): FileAST;
7
+ }
8
+ //# sourceMappingURL=go-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"go-extractor.d.ts","sourceRoot":"","sources":["../../../src/parser/go-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,OAAO,EAAc,MAAM,aAAa,CAAA;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE9D,uCAAuC;AACvC,qBAAa,WAAY,YAAW,iBAAiB;IACnD,SAAS,WAAS;IAElB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;CAgInD"}