@rpcajr/smart-graph-indexer 1.0.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 (60) hide show
  1. package/.agents/rules/antigravity-rtk-rules.md +32 -0
  2. package/README.md +116 -0
  3. package/dist/graph.d.ts +32 -0
  4. package/dist/graph.js +86 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +227 -0
  7. package/dist/mcp_server.d.ts +48 -0
  8. package/dist/mcp_server.js +433 -0
  9. package/dist/parser.d.ts +42 -0
  10. package/dist/parser.js +529 -0
  11. package/dist/stats.d.ts +43 -0
  12. package/dist/stats.js +146 -0
  13. package/dist/ui.d.ts +2 -0
  14. package/dist/ui.js +1003 -0
  15. package/dist/vector.d.ts +66 -0
  16. package/dist/vector.js +144 -0
  17. package/dist/wasm/tree-sitter-c.wasm +0 -0
  18. package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
  19. package/dist/wasm/tree-sitter-cpp.wasm +0 -0
  20. package/dist/wasm/tree-sitter-go.wasm +0 -0
  21. package/dist/wasm/tree-sitter-java.wasm +0 -0
  22. package/dist/wasm/tree-sitter-python.wasm +0 -0
  23. package/dist/wasm/tree-sitter-ruby.wasm +0 -0
  24. package/dist/wasm/tree-sitter-rust.wasm +0 -0
  25. package/dist/wasm/tree-sitter-typescript.wasm +0 -0
  26. package/dist/wasm/tree-sitter.wasm +0 -0
  27. package/dist/watcher.d.ts +36 -0
  28. package/dist/watcher.js +166 -0
  29. package/mcp-config-example.json +12 -0
  30. package/mcp_smart_indexer_implementation_spec.md +366 -0
  31. package/package.json +35 -0
  32. package/src/graph.ts +93 -0
  33. package/src/index.ts +216 -0
  34. package/src/mcp_server.ts +454 -0
  35. package/src/parser.ts +484 -0
  36. package/src/stats.ts +156 -0
  37. package/src/ui.ts +956 -0
  38. package/src/vector.ts +166 -0
  39. package/src/watcher.ts +144 -0
  40. package/test_project/App.java +16 -0
  41. package/test_project/BillingService.cs +31 -0
  42. package/test_project/auth.ts +18 -0
  43. package/test_project/config.ts +11 -0
  44. package/test_project/database.ts +21 -0
  45. package/test_project/index.ts +13 -0
  46. package/test_project/main.go +11 -0
  47. package/test_project/main.py +21 -0
  48. package/test_project/processor.py +12 -0
  49. package/test_project/utils.py +13 -0
  50. package/tsconfig.json +16 -0
  51. package/wasm/tree-sitter-c.wasm +0 -0
  52. package/wasm/tree-sitter-c_sharp.wasm +0 -0
  53. package/wasm/tree-sitter-cpp.wasm +0 -0
  54. package/wasm/tree-sitter-go.wasm +0 -0
  55. package/wasm/tree-sitter-java.wasm +0 -0
  56. package/wasm/tree-sitter-python.wasm +0 -0
  57. package/wasm/tree-sitter-ruby.wasm +0 -0
  58. package/wasm/tree-sitter-rust.wasm +0 -0
  59. package/wasm/tree-sitter-typescript.wasm +0 -0
  60. package/wasm/tree-sitter.wasm +0 -0
package/src/vector.ts ADDED
@@ -0,0 +1,166 @@
1
+ import { pipeline } from '@xenova/transformers';
2
+ import { SymbolSkeleton } from './parser.js';
3
+
4
+ export interface VectorSymbol {
5
+ id: string; // filePath + ":" + symbolName
6
+ filePath: string;
7
+ symbolName: string;
8
+ kind: string;
9
+ signature: string;
10
+ docstring?: string;
11
+ embedding: number[];
12
+ }
13
+
14
+ export class VectorStore {
15
+ private extractor: any = null;
16
+ private symbols: VectorSymbol[] = [];
17
+ private isInitialized = false;
18
+
19
+ constructor() {}
20
+
21
+ /**
22
+ * Initializes the embedding model.
23
+ */
24
+ public async init() {
25
+ if (this.isInitialized) return;
26
+
27
+ try {
28
+ // Disable remote downloading if you want pure local, but first-time execution
29
+ // needs to fetch the 90MB all-MiniLM-L6-v2 model from Hugging Face Xenova repository.
30
+ // After the first download, it will be cached locally and run entirely offline.
31
+ this.extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
32
+ this.isInitialized = true;
33
+ } catch (err) {
34
+ console.error('Failed to initialize local embedding engine:', err);
35
+ throw err;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Generates embedding vector for a given string.
41
+ */
42
+ public async generateEmbedding(text: string): Promise<number[]> {
43
+ if (!this.isInitialized) {
44
+ await this.init();
45
+ }
46
+ const output = await this.extractor(text, { pooling: 'mean', normalize: true });
47
+ return Array.from(output.data) as number[];
48
+ }
49
+
50
+ /**
51
+ * Helper to format symbol for vectorization following spec template.
52
+ */
53
+ public formatSymbolForEmbedding(filePath: string, symbol: SymbolSkeleton): string {
54
+ return `File: ${filePath} | Symbol: ${symbol.name} (${symbol.kind}) | Signature: ${symbol.signature} | Doc: ${symbol.docstring || ''}`;
55
+ }
56
+
57
+ /**
58
+ * Adds or updates a symbol in the vector store.
59
+ */
60
+ public async addSymbol(filePath: string, symbol: SymbolSkeleton) {
61
+ const id = `${filePath}:${symbol.name}`;
62
+ this.removeSymbol(filePath, symbol.name);
63
+
64
+ const textToEmbed = this.formatSymbolForEmbedding(filePath, symbol);
65
+ try {
66
+ const embedding = await this.generateEmbedding(textToEmbed);
67
+ this.symbols.push({
68
+ id,
69
+ filePath,
70
+ symbolName: symbol.name,
71
+ kind: symbol.kind,
72
+ signature: symbol.signature,
73
+ docstring: symbol.docstring,
74
+ embedding,
75
+ });
76
+ } catch (err) {
77
+ console.error(`Error embedding symbol ${id}:`, err);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Removes symbols belonging to a specific file.
83
+ */
84
+ public removeFileSymbols(filePath: string) {
85
+ this.symbols = this.symbols.filter(s => s.filePath !== filePath);
86
+ }
87
+
88
+ /**
89
+ * Removes a specific symbol.
90
+ */
91
+ public removeSymbol(filePath: string, symbolName: string) {
92
+ const id = `${filePath}:${symbolName}`;
93
+ this.symbols = this.symbols.filter(s => s.id !== id);
94
+ }
95
+
96
+ /**
97
+ * Clears the vector store.
98
+ */
99
+ public clear() {
100
+ this.symbols = [];
101
+ }
102
+
103
+ /**
104
+ * Computes dot product (cosine similarity since embeddings are normalized).
105
+ */
106
+ private cosineSimilarity(a: number[], b: number[]): number {
107
+ let dotProduct = 0;
108
+ for (let i = 0; i < a.length; i++) {
109
+ dotProduct += a[i] * b[i];
110
+ }
111
+ return dotProduct;
112
+ }
113
+
114
+ /**
115
+ * Performs hybrid search (semantic similarity + exact substring matching).
116
+ */
117
+ public async search(query: string, limit: number = 5): Promise<Array<{ symbol: Omit<VectorSymbol, 'embedding'>; score: number }>> {
118
+ if (!query.trim()) return [];
119
+
120
+ // 1. Generate query embedding
121
+ const queryEmbedding = await this.generateEmbedding(query);
122
+
123
+ // 2. Compute similarity for all indexed symbols
124
+ const results = this.symbols.map(s => {
125
+ let score = this.cosineSimilarity(queryEmbedding, s.embedding);
126
+
127
+ // Boost score if query is a substring of the symbol name (lexical matching)
128
+ const nameLower = s.symbolName.toLowerCase();
129
+ const queryLower = query.toLowerCase();
130
+ if (nameLower.includes(queryLower)) {
131
+ score += 0.25; // boost for direct match
132
+ }
133
+
134
+ return {
135
+ symbol: {
136
+ id: s.id,
137
+ filePath: s.filePath,
138
+ symbolName: s.symbolName,
139
+ kind: s.kind,
140
+ signature: s.signature,
141
+ docstring: s.docstring,
142
+ },
143
+ score,
144
+ };
145
+ });
146
+
147
+ // 3. Sort by score descending and limit results
148
+ return results
149
+ .sort((a, b) => b.score - a.score)
150
+ .slice(0, limit);
151
+ }
152
+
153
+ /**
154
+ * Retrieves all loaded symbols.
155
+ */
156
+ public getAllSymbols() {
157
+ return this.symbols.map(s => ({
158
+ id: s.id,
159
+ filePath: s.filePath,
160
+ symbolName: s.symbolName,
161
+ kind: s.kind,
162
+ signature: s.signature,
163
+ docstring: s.docstring,
164
+ }));
165
+ }
166
+ }
package/src/watcher.ts ADDED
@@ -0,0 +1,144 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import chokidar from 'chokidar';
6
+ import ignore from 'ignore';
7
+
8
+ const execAsync = promisify(exec);
9
+
10
+ export interface WatcherOptions {
11
+ projectPath: string;
12
+ onChange: (changedFiles: string[]) => void;
13
+ debounceMs?: number;
14
+ }
15
+
16
+ export class GitAwareWatcher {
17
+ private projectPath: string;
18
+ private onChange: (changedFiles: string[]) => void;
19
+ private debounceMs: number;
20
+ private watcher: chokidar.FSWatcher | null = null;
21
+ private ig: ReturnType<typeof ignore>;
22
+ private changedQueue: Set<string> = new Set();
23
+ private debounceTimeout: NodeJS.Timeout | null = null;
24
+
25
+ constructor(options: WatcherOptions) {
26
+ this.projectPath = path.resolve(options.projectPath);
27
+ this.onChange = options.onChange;
28
+ this.debounceMs = options.debounceMs ?? 1500;
29
+ this.ig = ignore();
30
+ this.loadGitignore();
31
+ }
32
+
33
+ /**
34
+ * Loads and parses .gitignore rules.
35
+ */
36
+ private loadGitignore() {
37
+ const gitignorePath = path.join(this.projectPath, '.gitignore');
38
+ this.ig = ignore();
39
+ // Always ignore standard node_modules and .git folders
40
+ this.ig.add(['node_modules', '.git', 'dist', '.graph-indexer']);
41
+
42
+ if (fs.existsSync(gitignorePath)) {
43
+ try {
44
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
45
+ this.ig.add(content);
46
+ } catch (err) {
47
+ console.error('Error reading .gitignore file:', err);
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Helper to check if a relative path is ignored.
54
+ */
55
+ public isIgnored(relativeFilePath: string): boolean {
56
+ // Normalize path separators to forward slashes for the ignore library
57
+ const normalized = relativeFilePath.replace(/\\/g, '/');
58
+ if (!normalized || normalized === '.') return true;
59
+ return this.ig.ignores(normalized);
60
+ }
61
+
62
+ /**
63
+ * Runs `git status --porcelain` to get the list of modified/untracked files.
64
+ */
65
+ public async getGitDeltas(): Promise<string[]> {
66
+ try {
67
+ const { stdout } = await execAsync('git status --porcelain', { cwd: this.projectPath });
68
+ const deltas: string[] = [];
69
+
70
+ for (const line of stdout.split('\n')) {
71
+ if (!line.trim()) continue;
72
+ // git status --porcelain outputs: XY path/to/file -> path/to/file or XY path/to/file
73
+ // We capture everything after the first 3 characters
74
+ const filePath = line.substring(3).trim();
75
+ const relativePath = path.relative(this.projectPath, path.resolve(this.projectPath, filePath));
76
+
77
+ if (!this.isIgnored(relativePath)) {
78
+ deltas.push(relativePath);
79
+ }
80
+ }
81
+ return deltas;
82
+ } catch (err) {
83
+ // If not a git repo or git not found, return empty array
84
+ return [];
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Starts watching the filesystem.
90
+ */
91
+ public start() {
92
+ this.loadGitignore();
93
+
94
+ this.watcher = chokidar.watch(this.projectPath, {
95
+ ignored: (filePath) => {
96
+ const relativePath = path.relative(this.projectPath, filePath);
97
+ return this.isIgnored(relativePath);
98
+ },
99
+ persistent: true,
100
+ ignoreInitial: true,
101
+ });
102
+
103
+ const handleFileChange = (filePath: string) => {
104
+ const relativePath = path.relative(this.projectPath, filePath);
105
+ if (this.isIgnored(relativePath)) return;
106
+
107
+ this.changedQueue.add(relativePath);
108
+ this.triggerDebounce();
109
+ };
110
+
111
+ this.watcher
112
+ .on('add', handleFileChange)
113
+ .on('change', handleFileChange)
114
+ .on('unlink', handleFileChange);
115
+ }
116
+
117
+ /**
118
+ * Stops the filesystem watcher.
119
+ */
120
+ public async stop() {
121
+ if (this.watcher) {
122
+ await this.watcher.close();
123
+ this.watcher = null;
124
+ }
125
+ if (this.debounceTimeout) {
126
+ clearTimeout(this.debounceTimeout);
127
+ this.debounceTimeout = null;
128
+ }
129
+ }
130
+
131
+ private triggerDebounce() {
132
+ if (this.debounceTimeout) {
133
+ clearTimeout(this.debounceTimeout);
134
+ }
135
+
136
+ this.debounceTimeout = setTimeout(() => {
137
+ const changed = Array.from(this.changedQueue);
138
+ this.changedQueue.clear();
139
+ if (changed.length > 0) {
140
+ this.onChange(changed);
141
+ }
142
+ }, this.debounceMs);
143
+ }
144
+ }
@@ -0,0 +1,16 @@
1
+ package com.example;
2
+
3
+ import java.util.List;
4
+
5
+ /**
6
+ * Main application class.
7
+ */
8
+ public class App {
9
+
10
+ /**
11
+ * Executes the main logic.
12
+ */
13
+ public static void main(String[] args) {
14
+ System.out.println("Hello World");
15
+ }
16
+ }
@@ -0,0 +1,31 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace Shop.Services
5
+ {
6
+ /// <summary>
7
+ /// Servico responsavel pelo faturamento e calculo de taxas.
8
+ /// </summary>
9
+ public class BillingService : IBillingService
10
+ {
11
+ private readonly ITaxCalculator _taxCalculator;
12
+
13
+ public BillingService(ITaxCalculator taxCalculator)
14
+ {
15
+ _taxCalculator = taxCalculator;
16
+ }
17
+
18
+ /// <summary>
19
+ /// Calcula o valor final com taxas baseadas na regiao.
20
+ /// </summary>
21
+ public decimal ProcessOrder(Order order, decimal discount)
22
+ {
23
+ if (order == null) throw new ArgumentNullException(nameof(order));
24
+
25
+ var baseAmount = order.Total - discount;
26
+ var tax = _taxCalculator.CalculateVAT(baseAmount, order.Region);
27
+
28
+ return baseAmount + tax;
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,18 @@
1
+ import { config } from './config.js';
2
+ import { Database } from './database.js';
3
+
4
+ export class AuthService {
5
+ private db: Database;
6
+
7
+ constructor(db: Database) {
8
+ this.db = db;
9
+ }
10
+
11
+ public login(username: string): string {
12
+ const users = this.db.query(`SELECT * FROM users WHERE name = '${username}'`);
13
+ if (users.length > 0) {
14
+ return `token-for-${username}-with-${config.jwtSecret}`;
15
+ }
16
+ throw new Error('User not found');
17
+ }
18
+ }
@@ -0,0 +1,11 @@
1
+ export interface AppConfig {
2
+ dbHost: string;
3
+ dbPort: number;
4
+ jwtSecret: string;
5
+ }
6
+
7
+ export const config: AppConfig = {
8
+ dbHost: 'localhost',
9
+ dbPort: 5432,
10
+ jwtSecret: 'super-secret-key'
11
+ };
@@ -0,0 +1,21 @@
1
+ import { config } from './config.js';
2
+
3
+ export class Database {
4
+ private isConnected = false;
5
+
6
+ constructor() {
7
+ console.log(`Connecting to database at ${config.dbHost}:${config.dbPort}`);
8
+ }
9
+
10
+ public connect(): boolean {
11
+ this.isConnected = true;
12
+ return this.isConnected;
13
+ }
14
+
15
+ public query(sql: string): any[] {
16
+ if (!this.isConnected) {
17
+ throw new Error('Database not connected');
18
+ }
19
+ return [{ id: 1, query: sql }];
20
+ }
21
+ }
@@ -0,0 +1,13 @@
1
+ import { Database } from './database.js';
2
+ import { AuthService } from './auth.js';
3
+
4
+ export function main() {
5
+ const db = new Database();
6
+ db.connect();
7
+
8
+ const auth = new AuthService(db);
9
+ const token = auth.login('admin');
10
+ console.log(`Success: logged in with token ${token}`);
11
+ }
12
+
13
+ main();
@@ -0,0 +1,11 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "math"
6
+ )
7
+
8
+ // CalculateDistance computes Euclidean distance
9
+ func CalculateDistance(x1, y1, x2, y2 float64) float64 {
10
+ return math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))
11
+ }
@@ -0,0 +1,21 @@
1
+ import math
2
+ from processor import Processor
3
+ from utils import log_info
4
+
5
+ class DataProcessor:
6
+ """
7
+ Class designed to process mathematical datasets.
8
+ """
9
+ def __init__(self, precision: int = 2):
10
+ self.precision = precision
11
+ self.processor = Processor()
12
+
13
+ def calculate_average(self, values: list[float]) -> float:
14
+ """
15
+ Computes the arithmetic mean of the provided values.
16
+ """
17
+ if not values:
18
+ return 0.0
19
+ log_info("Calculating average")
20
+ processed = self.processor.run(values)
21
+ return round(sum(processed) / len(processed), self.precision)
@@ -0,0 +1,12 @@
1
+ from utils import log_info
2
+
3
+ class Processor:
4
+ """
5
+ Main processor class.
6
+ """
7
+ def __init__(self):
8
+ log_info("Processor initialized")
9
+
10
+ def run(self, data: list):
11
+ log_info(f"Processing data with length: {len(data)}")
12
+ return [x * 2 for x in data]
@@ -0,0 +1,13 @@
1
+ import datetime
2
+
3
+ def get_timestamp() -> str:
4
+ """
5
+ Returns current timestamp.
6
+ """
7
+ return datetime.datetime.now().isoformat()
8
+
9
+ def log_info(message: str):
10
+ """
11
+ Logs info message.
12
+ """
13
+ print(f"[{get_timestamp()}] INFO: {message}")
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"]
16
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file