@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.
- package/.agents/rules/antigravity-rtk-rules.md +32 -0
- package/README.md +116 -0
- package/dist/graph.d.ts +32 -0
- package/dist/graph.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +227 -0
- package/dist/mcp_server.d.ts +48 -0
- package/dist/mcp_server.js +433 -0
- package/dist/parser.d.ts +42 -0
- package/dist/parser.js +529 -0
- package/dist/stats.d.ts +43 -0
- package/dist/stats.js +146 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +1003 -0
- package/dist/vector.d.ts +66 -0
- package/dist/vector.js +144 -0
- package/dist/wasm/tree-sitter-c.wasm +0 -0
- package/dist/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/wasm/tree-sitter-go.wasm +0 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/wasm/tree-sitter.wasm +0 -0
- package/dist/watcher.d.ts +36 -0
- package/dist/watcher.js +166 -0
- package/mcp-config-example.json +12 -0
- package/mcp_smart_indexer_implementation_spec.md +366 -0
- package/package.json +35 -0
- package/src/graph.ts +93 -0
- package/src/index.ts +216 -0
- package/src/mcp_server.ts +454 -0
- package/src/parser.ts +484 -0
- package/src/stats.ts +156 -0
- package/src/ui.ts +956 -0
- package/src/vector.ts +166 -0
- package/src/watcher.ts +144 -0
- package/test_project/App.java +16 -0
- package/test_project/BillingService.cs +31 -0
- package/test_project/auth.ts +18 -0
- package/test_project/config.ts +11 -0
- package/test_project/database.ts +21 -0
- package/test_project/index.ts +13 -0
- package/test_project/main.go +11 -0
- package/test_project/main.py +21 -0
- package/test_project/processor.py +12 -0
- package/test_project/utils.py +13 -0
- package/tsconfig.json +16 -0
- package/wasm/tree-sitter-c.wasm +0 -0
- package/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/wasm/tree-sitter-cpp.wasm +0 -0
- package/wasm/tree-sitter-go.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-ruby.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
- package/wasm/tree-sitter-typescript.wasm +0 -0
- package/wasm/tree-sitter.wasm +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# RTK - Rust Token Killer (Google Antigravity)
|
|
2
|
+
|
|
3
|
+
**Usage**: Token-optimized CLI proxy for shell commands.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
Always prefix shell commands with `rtk` to minimize token consumption.
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
rtk git status
|
|
13
|
+
rtk cargo test
|
|
14
|
+
rtk ls src/
|
|
15
|
+
rtk grep "pattern" src/
|
|
16
|
+
rtk find "*.rs" .
|
|
17
|
+
rtk docker ps
|
|
18
|
+
rtk gh pr list
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Meta Commands
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
rtk gain # Show token savings
|
|
25
|
+
rtk gain --history # Command history with savings
|
|
26
|
+
rtk discover # Find missed RTK opportunities
|
|
27
|
+
rtk proxy <cmd> # Run raw (no filtering, for debugging)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk <cmd>` instead of raw commands.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# smart-graph-indexer 🚀
|
|
2
|
+
|
|
3
|
+
A fast, language-agnostic, syntax-aware structural code indexer and **Model Context Protocol (MCP)** server.
|
|
4
|
+
|
|
5
|
+
It indexes project structure, extracts AST signature skeletons, computes file-to-file dependency graphs, and provides local hybrid search (semantic vector embeddings + exact substring search) for **9 major programming languages**:
|
|
6
|
+
*TypeScript/JavaScript, Python, C#, Java, Go, Rust, Ruby, C, and C++.*
|
|
7
|
+
|
|
8
|
+
Additionally, it tracks **Token Savings (RTK Token Killer)** and session logs, giving you a detailed breakdown of how much context (and API cost) you are saving when utilizing AI assistants with this MCP server.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Key Features
|
|
13
|
+
|
|
14
|
+
1. **Syntax-Aware Structural Parsing (AST Skeletons)**:
|
|
15
|
+
Extracts public class, method, function signatures, and docstrings using Tree-Sitter WASM grammars. Internal logic blocks are discarded, creating a lightweight "skeleton" map of your project.
|
|
16
|
+
|
|
17
|
+
2. **Dynamic Workspace Discovery**:
|
|
18
|
+
Uses the MCP `roots/list` protocol handshake to dynamically query the IDE for the active project path. Zero configuration needed for CWD path variations across different IDE hosts.
|
|
19
|
+
|
|
20
|
+
3. **Local Hybrid Search**:
|
|
21
|
+
Performs fast cosine-similarity search using a local ONNX embedding model (`all-MiniLM-L6-v2` via `@xenova/transformers`) running entirely on your CPU, blended with substring lexical fallback.
|
|
22
|
+
|
|
23
|
+
4. **Interactive Dependency Graph (`graph-indexer view`)**:
|
|
24
|
+
Generates a visually stunning Vis.js network visualization (`.graph-indexer/graph.html`) of your file imports. Use the `--watch` flag to auto-update the visualization as you save code.
|
|
25
|
+
|
|
26
|
+
5. **Dormant Mode by Default**:
|
|
27
|
+
To prevent overhead when opening arbitrary folders, the server runs in a dormant mode (0% CPU, minimum RAM) unless a `.graph-indexer/` directory exists in the workspace. Toggle it on-demand with `graph-indexer init`.
|
|
28
|
+
|
|
29
|
+
6. **Console-Style RTK Session Dashboard**:
|
|
30
|
+
A visual dashboard in the HTML interface that mimics the popular `RTK` (Rust Token Killer) console, tracking requests, used tokens, alternative raw tokens, execution times, and efficiency ratios.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Install the package globally using npm:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Install globally from source
|
|
40
|
+
npm install -g .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## CLI Commands
|
|
46
|
+
|
|
47
|
+
### 1. Initialize a Project (`init`)
|
|
48
|
+
Enables the MCP indexing engine for the current directory by creating the `.graph-indexer` folder:
|
|
49
|
+
```bash
|
|
50
|
+
graph-indexer init
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> [!TIP]
|
|
54
|
+
> **Gitignore Configuration**:
|
|
55
|
+
> It is highly recommended to add the configuration folder to your project's `.gitignore` to avoid committing local stats and generated HTML views:
|
|
56
|
+
> ```gitignore
|
|
57
|
+
> # Exclude local Graph Indexer assets
|
|
58
|
+
> .graph-indexer/
|
|
59
|
+
> ```
|
|
60
|
+
|
|
61
|
+
### 2. Visualize Graph & Stats (`view`)
|
|
62
|
+
Generates the interactive HTML network graph and opens it in your default browser:
|
|
63
|
+
```bash
|
|
64
|
+
graph-indexer view
|
|
65
|
+
```
|
|
66
|
+
To monitor code modifications and regenerate the graph automatically, add the watch flag:
|
|
67
|
+
```bash
|
|
68
|
+
graph-indexer view --watch
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Quick Index Export (`index`)
|
|
72
|
+
Indexes the project once and exports the structural skeletons report directly to stdout as JSON:
|
|
73
|
+
```bash
|
|
74
|
+
graph-indexer index
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## IDE MCP Server Integration
|
|
80
|
+
|
|
81
|
+
Add the server to your editor's global Model Context Protocol configuration (e.g., `mcp_config.json` in Antigravity, Claude Desktop, Cursor, or VS Code):
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"smart-graph-indexer": {
|
|
87
|
+
"command": "graph-indexer",
|
|
88
|
+
"args": [
|
|
89
|
+
"start",
|
|
90
|
+
"--path",
|
|
91
|
+
"."
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
*Note: Since the server queries the editor for active workspace roots dynamically, `--path "."` works as a safe fallback.*
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Available MCP Tools
|
|
103
|
+
|
|
104
|
+
Once connected, your AI assistant will automatically use the following tools:
|
|
105
|
+
|
|
106
|
+
* `search_symbols(query, limit)`: Performs a hybrid search over indexed structural signatures.
|
|
107
|
+
* `get_file_skeleton(file_path)`: Retrieves the clean AST skeleton (methods, properties, docstrings) of a file without internal logic blocks.
|
|
108
|
+
* `get_dependencies(file_path, direction)`: Retrieves direct imports or dependants of a file.
|
|
109
|
+
* `read_symbol_body(file_path, symbol_name)`: JIT-fetches the actual body of a class/method from disk when requested.
|
|
110
|
+
* `get_token_savings()`: Returns the detailed JSON log of token metrics.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
ISC License. Open Source.
|
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare class DependencyGraph {
|
|
2
|
+
private adjacencyList;
|
|
3
|
+
private reverseAdjacencyList;
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Adds or updates a file and its dependencies in the graph.
|
|
7
|
+
*/
|
|
8
|
+
updateFileDependencies(filePath: string, dependencies: string[]): void;
|
|
9
|
+
/**
|
|
10
|
+
* Removes a file and its edges from the graph.
|
|
11
|
+
*/
|
|
12
|
+
removeFile(filePath: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Clears the entire graph.
|
|
15
|
+
*/
|
|
16
|
+
clear(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Gets direct dependencies (files/namespaces this file imports).
|
|
19
|
+
*/
|
|
20
|
+
getOutgoing(filePath: string): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Gets dependants (files that import this file/namespace).
|
|
23
|
+
*/
|
|
24
|
+
getIncoming(filePathOrNamespace: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Exports the entire graph for debugging/serialization.
|
|
27
|
+
*/
|
|
28
|
+
serialize(): Record<string, {
|
|
29
|
+
imports: string[];
|
|
30
|
+
importedBy: string[];
|
|
31
|
+
}>;
|
|
32
|
+
}
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DependencyGraph = void 0;
|
|
4
|
+
class DependencyGraph {
|
|
5
|
+
// Map of file relative path -> set of other files/namespaces this file imports (outgoing)
|
|
6
|
+
adjacencyList = new Map();
|
|
7
|
+
// Map of file/namespace -> set of files that import it (incoming)
|
|
8
|
+
reverseAdjacencyList = new Map();
|
|
9
|
+
constructor() { }
|
|
10
|
+
/**
|
|
11
|
+
* Adds or updates a file and its dependencies in the graph.
|
|
12
|
+
*/
|
|
13
|
+
updateFileDependencies(filePath, dependencies) {
|
|
14
|
+
// Clean old dependencies if file already existed
|
|
15
|
+
this.removeFile(filePath);
|
|
16
|
+
// Set outgoing dependencies
|
|
17
|
+
const outgoing = new Set(dependencies);
|
|
18
|
+
this.adjacencyList.set(filePath, outgoing);
|
|
19
|
+
// Set incoming dependencies
|
|
20
|
+
for (const dep of dependencies) {
|
|
21
|
+
if (!this.reverseAdjacencyList.has(dep)) {
|
|
22
|
+
this.reverseAdjacencyList.set(dep, new Set());
|
|
23
|
+
}
|
|
24
|
+
this.reverseAdjacencyList.get(dep).add(filePath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Removes a file and its edges from the graph.
|
|
29
|
+
*/
|
|
30
|
+
removeFile(filePath) {
|
|
31
|
+
const oldDeps = this.adjacencyList.get(filePath);
|
|
32
|
+
if (oldDeps) {
|
|
33
|
+
for (const dep of oldDeps) {
|
|
34
|
+
const incoming = this.reverseAdjacencyList.get(dep);
|
|
35
|
+
if (incoming) {
|
|
36
|
+
incoming.delete(filePath);
|
|
37
|
+
if (incoming.size === 0) {
|
|
38
|
+
this.reverseAdjacencyList.delete(dep);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.adjacencyList.delete(filePath);
|
|
43
|
+
}
|
|
44
|
+
// Also remove from reverse list (in case this file was imported by others)
|
|
45
|
+
this.reverseAdjacencyList.delete(filePath);
|
|
46
|
+
for (const [node, deps] of this.adjacencyList.entries()) {
|
|
47
|
+
if (deps.has(filePath)) {
|
|
48
|
+
deps.delete(filePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Clears the entire graph.
|
|
54
|
+
*/
|
|
55
|
+
clear() {
|
|
56
|
+
this.adjacencyList.clear();
|
|
57
|
+
this.reverseAdjacencyList.clear();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets direct dependencies (files/namespaces this file imports).
|
|
61
|
+
*/
|
|
62
|
+
getOutgoing(filePath) {
|
|
63
|
+
return Array.from(this.adjacencyList.get(filePath) || []);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Gets dependants (files that import this file/namespace).
|
|
67
|
+
*/
|
|
68
|
+
getIncoming(filePathOrNamespace) {
|
|
69
|
+
return Array.from(this.reverseAdjacencyList.get(filePathOrNamespace) || []);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Exports the entire graph for debugging/serialization.
|
|
73
|
+
*/
|
|
74
|
+
serialize() {
|
|
75
|
+
const serialized = {};
|
|
76
|
+
const allNodes = new Set([...this.adjacencyList.keys(), ...this.reverseAdjacencyList.keys()]);
|
|
77
|
+
for (const node of allNodes) {
|
|
78
|
+
serialized[node] = {
|
|
79
|
+
imports: this.getOutgoing(node),
|
|
80
|
+
importedBy: this.getIncoming(node),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return serialized;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.DependencyGraph = DependencyGraph;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const mcp_server_js_1 = require("./mcp_server.js");
|
|
39
|
+
const ui_js_1 = require("./ui.js");
|
|
40
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
41
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const program = new commander_1.Command();
|
|
46
|
+
program
|
|
47
|
+
.name('graph-indexer')
|
|
48
|
+
.description('Smart Graph Code Indexer MCP CLI')
|
|
49
|
+
.version('1.0.0');
|
|
50
|
+
program
|
|
51
|
+
.command('start')
|
|
52
|
+
.description('Starts the MCP server via stdio')
|
|
53
|
+
.option('-p, --path <directory>', 'Target project directory to index', '.')
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
try {
|
|
56
|
+
const projectPath = path.resolve(options.path);
|
|
57
|
+
console.error(`[Start] Initializing SmartGraphIndexerMCP for: ${projectPath}`);
|
|
58
|
+
const engine = new mcp_server_js_1.IndexerEngine(projectPath);
|
|
59
|
+
const server = (0, mcp_server_js_1.createMCPServer)(engine);
|
|
60
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
61
|
+
await server.connect(transport);
|
|
62
|
+
console.error('[Start] MCP Server successfully connected via Stdio.');
|
|
63
|
+
// Dynamic workspace path discovery via client roots list
|
|
64
|
+
let resolvedPath = projectPath;
|
|
65
|
+
let rootsResult = "none";
|
|
66
|
+
let rootsError = "none";
|
|
67
|
+
try {
|
|
68
|
+
const response = await server.request({ method: 'roots/list' }, types_js_1.ListRootsResultSchema);
|
|
69
|
+
rootsResult = JSON.stringify(response);
|
|
70
|
+
if (response && response.roots && response.roots.length > 0) {
|
|
71
|
+
const rootUri = response.roots[0].uri;
|
|
72
|
+
if (rootUri.startsWith('file:///')) {
|
|
73
|
+
// Remove file:/// prefix
|
|
74
|
+
let pathFromUri = rootUri.substring(8);
|
|
75
|
+
pathFromUri = decodeURIComponent(pathFromUri);
|
|
76
|
+
// Normalize Windows/Unix path
|
|
77
|
+
resolvedPath = path.resolve(pathFromUri);
|
|
78
|
+
engine.updateProjectPath(resolvedPath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
rootsError = err?.message || String(err);
|
|
84
|
+
}
|
|
85
|
+
await engine.startIndexing();
|
|
86
|
+
// Handle termination gracefully
|
|
87
|
+
process.on('SIGINT', async () => {
|
|
88
|
+
console.error('[Shutdown] Closing watcher...');
|
|
89
|
+
await engine.shutdown();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
});
|
|
92
|
+
process.on('SIGTERM', async () => {
|
|
93
|
+
console.error('[Shutdown] Closing watcher...');
|
|
94
|
+
await engine.shutdown();
|
|
95
|
+
process.exit(0);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error('Critical error starting the MCP server:', err);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
program
|
|
104
|
+
.command('index')
|
|
105
|
+
.description('Performs only the initial indexing of the project and exports a JSON structural report')
|
|
106
|
+
.option('-p, --path <directory>', 'Target project directory to index', '.')
|
|
107
|
+
.action(async (options) => {
|
|
108
|
+
try {
|
|
109
|
+
const projectPath = path.resolve(options.path);
|
|
110
|
+
console.log(`[Index] Indexing project at: ${projectPath}`);
|
|
111
|
+
const engine = new mcp_server_js_1.IndexerEngine(projectPath);
|
|
112
|
+
await engine.initialize();
|
|
113
|
+
await engine.scanDir(projectPath);
|
|
114
|
+
const report = engine.getReport();
|
|
115
|
+
console.log(JSON.stringify(report, null, 2));
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error('Critical error during indexing:', err);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
program
|
|
124
|
+
.command('view')
|
|
125
|
+
.description('Maps the project and opens an interactive web graph of dependencies')
|
|
126
|
+
.option('-p, --path <directory>', 'Target project directory to map', '.')
|
|
127
|
+
.option('-w, --watch', 'Monitors filesystem changes in real time and regenerates the graph automatically')
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
try {
|
|
130
|
+
const projectPath = path.resolve(options.path);
|
|
131
|
+
console.log(`[View] Mapping project dependencies at: ${projectPath}`);
|
|
132
|
+
let engine;
|
|
133
|
+
const writeHtml = () => {
|
|
134
|
+
const htmlContent = (0, ui_js_1.generateGraphHtml)(engine);
|
|
135
|
+
const mcpDir = path.join(projectPath, '.graph-indexer');
|
|
136
|
+
if (!fs.existsSync(mcpDir)) {
|
|
137
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
const htmlPath = path.join(mcpDir, 'graph.html');
|
|
140
|
+
fs.writeFileSync(htmlPath, htmlContent, 'utf-8');
|
|
141
|
+
console.log(`[View] Interactive graph updated at: ${htmlPath}`);
|
|
142
|
+
};
|
|
143
|
+
if (options.watch) {
|
|
144
|
+
engine = new mcp_server_js_1.IndexerEngine(projectPath, () => {
|
|
145
|
+
writeHtml();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
engine = new mcp_server_js_1.IndexerEngine(projectPath);
|
|
150
|
+
}
|
|
151
|
+
await engine.initialize();
|
|
152
|
+
await engine.scanDir(projectPath);
|
|
153
|
+
// Write initial HTML
|
|
154
|
+
writeHtml();
|
|
155
|
+
const mcpDir = path.join(projectPath, '.graph-indexer');
|
|
156
|
+
const htmlPath = path.join(mcpDir, 'graph.html');
|
|
157
|
+
// Open in the default browser based on platform
|
|
158
|
+
console.log('[View] Opening interactive graph in default browser...');
|
|
159
|
+
const platform = process.platform;
|
|
160
|
+
const fileUrl = `file:///${htmlPath.replace(/\\/g, '/')}`;
|
|
161
|
+
if (platform === 'win32') {
|
|
162
|
+
(0, child_process_1.exec)(`start "" "${fileUrl}"`);
|
|
163
|
+
}
|
|
164
|
+
else if (platform === 'darwin') {
|
|
165
|
+
(0, child_process_1.exec)(`open "${fileUrl}"`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
(0, child_process_1.exec)(`xdg-open "${fileUrl}"`);
|
|
169
|
+
}
|
|
170
|
+
if (options.watch) {
|
|
171
|
+
console.log('[View] Real-time monitoring (--watch) active. Press Ctrl+C to terminate.');
|
|
172
|
+
engine.startWatcher();
|
|
173
|
+
process.on('SIGINT', async () => {
|
|
174
|
+
console.log('[Shutdown] Closing watcher...');
|
|
175
|
+
await engine.shutdown();
|
|
176
|
+
process.exit(0);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error('Critical error generating graph view:', err);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
program
|
|
189
|
+
.command('init')
|
|
190
|
+
.description('Initializes the current project directory for SmartGraphIndexer by creating the .graph-indexer config folder')
|
|
191
|
+
.option('-p, --path <directory>', 'Target directory to initialize', '.')
|
|
192
|
+
.action(async (options) => {
|
|
193
|
+
try {
|
|
194
|
+
const projectPath = path.resolve(options.path);
|
|
195
|
+
const mcpDir = path.join(projectPath, '.graph-indexer');
|
|
196
|
+
if (fs.existsSync(mcpDir)) {
|
|
197
|
+
console.log(`[Init] Project is already initialized at: ${projectPath} (.graph-indexer directory exists).`);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
201
|
+
// Create initial empty stats file
|
|
202
|
+
const statsPath = path.join(mcpDir, 'stats.json');
|
|
203
|
+
const initialStats = {
|
|
204
|
+
totalCalls: 0,
|
|
205
|
+
totalTokensSaved: 0,
|
|
206
|
+
totalTokensActual: 0,
|
|
207
|
+
totalTokensAlternative: 0,
|
|
208
|
+
efficiencyPercentage: 0,
|
|
209
|
+
callsByType: {
|
|
210
|
+
search_symbols: { calls: 0, saved: 0, actual: 0, alternative: 0, totalTimeMs: 0 },
|
|
211
|
+
get_file_skeleton: { calls: 0, saved: 0, actual: 0, alternative: 0, totalTimeMs: 0 },
|
|
212
|
+
get_dependencies: { calls: 0, saved: 0, actual: 0, alternative: 0, totalTimeMs: 0 },
|
|
213
|
+
read_symbol_body: { calls: 0, saved: 0, actual: 0, alternative: 0, totalTimeMs: 0 }
|
|
214
|
+
},
|
|
215
|
+
history: []
|
|
216
|
+
};
|
|
217
|
+
fs.writeFileSync(statsPath, JSON.stringify(initialStats, null, 2), 'utf-8');
|
|
218
|
+
console.log(`[Init] Successfully initialized SmartGraphIndexer workspace at: ${projectPath}`);
|
|
219
|
+
console.log('[Init] The MCP server will now index and analyze this project.');
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
console.error('Critical error during initialization:', err);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { FileSkeleton } from './parser.js';
|
|
3
|
+
import { DependencyGraph } from './graph.js';
|
|
4
|
+
import { VectorStore } from './vector.js';
|
|
5
|
+
import { StatsManager } from './stats.js';
|
|
6
|
+
export declare class IndexerEngine {
|
|
7
|
+
projectPath: string;
|
|
8
|
+
private parser;
|
|
9
|
+
private graph;
|
|
10
|
+
private vectorStore;
|
|
11
|
+
private watcher;
|
|
12
|
+
private fileSkeletons;
|
|
13
|
+
private isLoaded;
|
|
14
|
+
private stats;
|
|
15
|
+
constructor(projectPath: string, onUpdate?: () => void);
|
|
16
|
+
updateProjectPath(newPath: string, onUpdate?: () => void): void;
|
|
17
|
+
startWatcher(): void;
|
|
18
|
+
initialize(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Scans and indexes the entire directory recursively.
|
|
21
|
+
*/
|
|
22
|
+
startIndexing(): Promise<void>;
|
|
23
|
+
private scanDir;
|
|
24
|
+
/**
|
|
25
|
+
* Indexes a single file.
|
|
26
|
+
*/
|
|
27
|
+
indexFile(relativeFilePath: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Gracefully updates a file on demand (for JIT read operations).
|
|
30
|
+
*/
|
|
31
|
+
ensureFileUpToDate(relativeFilePath: string): Promise<FileSkeleton | undefined>;
|
|
32
|
+
getFileSkeleton(relativeFilePath: string): FileSkeleton | undefined;
|
|
33
|
+
getGraph(): DependencyGraph;
|
|
34
|
+
getVectorStore(): VectorStore;
|
|
35
|
+
getStatsManager(): StatsManager;
|
|
36
|
+
shutdown(): Promise<void>;
|
|
37
|
+
getReport(): {
|
|
38
|
+
totalFiles: number;
|
|
39
|
+
files: string[];
|
|
40
|
+
symbolsCount: number;
|
|
41
|
+
dependencyGraph: Record<string, {
|
|
42
|
+
imports: string[];
|
|
43
|
+
importedBy: string[];
|
|
44
|
+
}>;
|
|
45
|
+
tokenStats: import("./stats.js").TokenStats;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export declare function createMCPServer(engine: IndexerEngine): Server;
|