@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
package/dist/vector.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SymbolSkeleton } from './parser.js';
|
|
2
|
+
export interface VectorSymbol {
|
|
3
|
+
id: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
symbolName: string;
|
|
6
|
+
kind: string;
|
|
7
|
+
signature: string;
|
|
8
|
+
docstring?: string;
|
|
9
|
+
embedding: number[];
|
|
10
|
+
}
|
|
11
|
+
export declare class VectorStore {
|
|
12
|
+
private extractor;
|
|
13
|
+
private symbols;
|
|
14
|
+
private isInitialized;
|
|
15
|
+
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* Initializes the embedding model.
|
|
18
|
+
*/
|
|
19
|
+
init(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Generates embedding vector for a given string.
|
|
22
|
+
*/
|
|
23
|
+
generateEmbedding(text: string): Promise<number[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Helper to format symbol for vectorization following spec template.
|
|
26
|
+
*/
|
|
27
|
+
formatSymbolForEmbedding(filePath: string, symbol: SymbolSkeleton): string;
|
|
28
|
+
/**
|
|
29
|
+
* Adds or updates a symbol in the vector store.
|
|
30
|
+
*/
|
|
31
|
+
addSymbol(filePath: string, symbol: SymbolSkeleton): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Removes symbols belonging to a specific file.
|
|
34
|
+
*/
|
|
35
|
+
removeFileSymbols(filePath: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Removes a specific symbol.
|
|
38
|
+
*/
|
|
39
|
+
removeSymbol(filePath: string, symbolName: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Clears the vector store.
|
|
42
|
+
*/
|
|
43
|
+
clear(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Computes dot product (cosine similarity since embeddings are normalized).
|
|
46
|
+
*/
|
|
47
|
+
private cosineSimilarity;
|
|
48
|
+
/**
|
|
49
|
+
* Performs hybrid search (semantic similarity + exact substring matching).
|
|
50
|
+
*/
|
|
51
|
+
search(query: string, limit?: number): Promise<Array<{
|
|
52
|
+
symbol: Omit<VectorSymbol, 'embedding'>;
|
|
53
|
+
score: number;
|
|
54
|
+
}>>;
|
|
55
|
+
/**
|
|
56
|
+
* Retrieves all loaded symbols.
|
|
57
|
+
*/
|
|
58
|
+
getAllSymbols(): {
|
|
59
|
+
id: string;
|
|
60
|
+
filePath: string;
|
|
61
|
+
symbolName: string;
|
|
62
|
+
kind: string;
|
|
63
|
+
signature: string;
|
|
64
|
+
docstring: string | undefined;
|
|
65
|
+
}[];
|
|
66
|
+
}
|
package/dist/vector.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VectorStore = void 0;
|
|
4
|
+
const transformers_1 = require("@xenova/transformers");
|
|
5
|
+
class VectorStore {
|
|
6
|
+
extractor = null;
|
|
7
|
+
symbols = [];
|
|
8
|
+
isInitialized = false;
|
|
9
|
+
constructor() { }
|
|
10
|
+
/**
|
|
11
|
+
* Initializes the embedding model.
|
|
12
|
+
*/
|
|
13
|
+
async init() {
|
|
14
|
+
if (this.isInitialized)
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
// Disable remote downloading if you want pure local, but first-time execution
|
|
18
|
+
// needs to fetch the 90MB all-MiniLM-L6-v2 model from Hugging Face Xenova repository.
|
|
19
|
+
// After the first download, it will be cached locally and run entirely offline.
|
|
20
|
+
this.extractor = await (0, transformers_1.pipeline)('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
|
|
21
|
+
this.isInitialized = true;
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error('Failed to initialize local embedding engine:', err);
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generates embedding vector for a given string.
|
|
30
|
+
*/
|
|
31
|
+
async generateEmbedding(text) {
|
|
32
|
+
if (!this.isInitialized) {
|
|
33
|
+
await this.init();
|
|
34
|
+
}
|
|
35
|
+
const output = await this.extractor(text, { pooling: 'mean', normalize: true });
|
|
36
|
+
return Array.from(output.data);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Helper to format symbol for vectorization following spec template.
|
|
40
|
+
*/
|
|
41
|
+
formatSymbolForEmbedding(filePath, symbol) {
|
|
42
|
+
return `File: ${filePath} | Symbol: ${symbol.name} (${symbol.kind}) | Signature: ${symbol.signature} | Doc: ${symbol.docstring || ''}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Adds or updates a symbol in the vector store.
|
|
46
|
+
*/
|
|
47
|
+
async addSymbol(filePath, symbol) {
|
|
48
|
+
const id = `${filePath}:${symbol.name}`;
|
|
49
|
+
this.removeSymbol(filePath, symbol.name);
|
|
50
|
+
const textToEmbed = this.formatSymbolForEmbedding(filePath, symbol);
|
|
51
|
+
try {
|
|
52
|
+
const embedding = await this.generateEmbedding(textToEmbed);
|
|
53
|
+
this.symbols.push({
|
|
54
|
+
id,
|
|
55
|
+
filePath,
|
|
56
|
+
symbolName: symbol.name,
|
|
57
|
+
kind: symbol.kind,
|
|
58
|
+
signature: symbol.signature,
|
|
59
|
+
docstring: symbol.docstring,
|
|
60
|
+
embedding,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`Error embedding symbol ${id}:`, err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Removes symbols belonging to a specific file.
|
|
69
|
+
*/
|
|
70
|
+
removeFileSymbols(filePath) {
|
|
71
|
+
this.symbols = this.symbols.filter(s => s.filePath !== filePath);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Removes a specific symbol.
|
|
75
|
+
*/
|
|
76
|
+
removeSymbol(filePath, symbolName) {
|
|
77
|
+
const id = `${filePath}:${symbolName}`;
|
|
78
|
+
this.symbols = this.symbols.filter(s => s.id !== id);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clears the vector store.
|
|
82
|
+
*/
|
|
83
|
+
clear() {
|
|
84
|
+
this.symbols = [];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Computes dot product (cosine similarity since embeddings are normalized).
|
|
88
|
+
*/
|
|
89
|
+
cosineSimilarity(a, b) {
|
|
90
|
+
let dotProduct = 0;
|
|
91
|
+
for (let i = 0; i < a.length; i++) {
|
|
92
|
+
dotProduct += a[i] * b[i];
|
|
93
|
+
}
|
|
94
|
+
return dotProduct;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Performs hybrid search (semantic similarity + exact substring matching).
|
|
98
|
+
*/
|
|
99
|
+
async search(query, limit = 5) {
|
|
100
|
+
if (!query.trim())
|
|
101
|
+
return [];
|
|
102
|
+
// 1. Generate query embedding
|
|
103
|
+
const queryEmbedding = await this.generateEmbedding(query);
|
|
104
|
+
// 2. Compute similarity for all indexed symbols
|
|
105
|
+
const results = this.symbols.map(s => {
|
|
106
|
+
let score = this.cosineSimilarity(queryEmbedding, s.embedding);
|
|
107
|
+
// Boost score if query is a substring of the symbol name (lexical matching)
|
|
108
|
+
const nameLower = s.symbolName.toLowerCase();
|
|
109
|
+
const queryLower = query.toLowerCase();
|
|
110
|
+
if (nameLower.includes(queryLower)) {
|
|
111
|
+
score += 0.25; // boost for direct match
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
symbol: {
|
|
115
|
+
id: s.id,
|
|
116
|
+
filePath: s.filePath,
|
|
117
|
+
symbolName: s.symbolName,
|
|
118
|
+
kind: s.kind,
|
|
119
|
+
signature: s.signature,
|
|
120
|
+
docstring: s.docstring,
|
|
121
|
+
},
|
|
122
|
+
score,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
// 3. Sort by score descending and limit results
|
|
126
|
+
return results
|
|
127
|
+
.sort((a, b) => b.score - a.score)
|
|
128
|
+
.slice(0, limit);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Retrieves all loaded symbols.
|
|
132
|
+
*/
|
|
133
|
+
getAllSymbols() {
|
|
134
|
+
return this.symbols.map(s => ({
|
|
135
|
+
id: s.id,
|
|
136
|
+
filePath: s.filePath,
|
|
137
|
+
symbolName: s.symbolName,
|
|
138
|
+
kind: s.kind,
|
|
139
|
+
signature: s.signature,
|
|
140
|
+
docstring: s.docstring,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.VectorStore = VectorStore;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface WatcherOptions {
|
|
2
|
+
projectPath: string;
|
|
3
|
+
onChange: (changedFiles: string[]) => void;
|
|
4
|
+
debounceMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class GitAwareWatcher {
|
|
7
|
+
private projectPath;
|
|
8
|
+
private onChange;
|
|
9
|
+
private debounceMs;
|
|
10
|
+
private watcher;
|
|
11
|
+
private ig;
|
|
12
|
+
private changedQueue;
|
|
13
|
+
private debounceTimeout;
|
|
14
|
+
constructor(options: WatcherOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Loads and parses .gitignore rules.
|
|
17
|
+
*/
|
|
18
|
+
private loadGitignore;
|
|
19
|
+
/**
|
|
20
|
+
* Helper to check if a relative path is ignored.
|
|
21
|
+
*/
|
|
22
|
+
isIgnored(relativeFilePath: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Runs `git status --porcelain` to get the list of modified/untracked files.
|
|
25
|
+
*/
|
|
26
|
+
getGitDeltas(): Promise<string[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Starts watching the filesystem.
|
|
29
|
+
*/
|
|
30
|
+
start(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Stops the filesystem watcher.
|
|
33
|
+
*/
|
|
34
|
+
stop(): Promise<void>;
|
|
35
|
+
private triggerDebounce;
|
|
36
|
+
}
|
package/dist/watcher.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.GitAwareWatcher = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const util_1 = require("util");
|
|
44
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
45
|
+
const ignore_1 = __importDefault(require("ignore"));
|
|
46
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
47
|
+
class GitAwareWatcher {
|
|
48
|
+
projectPath;
|
|
49
|
+
onChange;
|
|
50
|
+
debounceMs;
|
|
51
|
+
watcher = null;
|
|
52
|
+
ig;
|
|
53
|
+
changedQueue = new Set();
|
|
54
|
+
debounceTimeout = null;
|
|
55
|
+
constructor(options) {
|
|
56
|
+
this.projectPath = path.resolve(options.projectPath);
|
|
57
|
+
this.onChange = options.onChange;
|
|
58
|
+
this.debounceMs = options.debounceMs ?? 1500;
|
|
59
|
+
this.ig = (0, ignore_1.default)();
|
|
60
|
+
this.loadGitignore();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Loads and parses .gitignore rules.
|
|
64
|
+
*/
|
|
65
|
+
loadGitignore() {
|
|
66
|
+
const gitignorePath = path.join(this.projectPath, '.gitignore');
|
|
67
|
+
this.ig = (0, ignore_1.default)();
|
|
68
|
+
// Always ignore standard node_modules and .git folders
|
|
69
|
+
this.ig.add(['node_modules', '.git', 'dist', '.graph-indexer']);
|
|
70
|
+
if (fs.existsSync(gitignorePath)) {
|
|
71
|
+
try {
|
|
72
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
73
|
+
this.ig.add(content);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error('Error reading .gitignore file:', err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Helper to check if a relative path is ignored.
|
|
82
|
+
*/
|
|
83
|
+
isIgnored(relativeFilePath) {
|
|
84
|
+
// Normalize path separators to forward slashes for the ignore library
|
|
85
|
+
const normalized = relativeFilePath.replace(/\\/g, '/');
|
|
86
|
+
if (!normalized || normalized === '.')
|
|
87
|
+
return true;
|
|
88
|
+
return this.ig.ignores(normalized);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Runs `git status --porcelain` to get the list of modified/untracked files.
|
|
92
|
+
*/
|
|
93
|
+
async getGitDeltas() {
|
|
94
|
+
try {
|
|
95
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: this.projectPath });
|
|
96
|
+
const deltas = [];
|
|
97
|
+
for (const line of stdout.split('\n')) {
|
|
98
|
+
if (!line.trim())
|
|
99
|
+
continue;
|
|
100
|
+
// git status --porcelain outputs: XY path/to/file -> path/to/file or XY path/to/file
|
|
101
|
+
// We capture everything after the first 3 characters
|
|
102
|
+
const filePath = line.substring(3).trim();
|
|
103
|
+
const relativePath = path.relative(this.projectPath, path.resolve(this.projectPath, filePath));
|
|
104
|
+
if (!this.isIgnored(relativePath)) {
|
|
105
|
+
deltas.push(relativePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return deltas;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// If not a git repo or git not found, return empty array
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Starts watching the filesystem.
|
|
117
|
+
*/
|
|
118
|
+
start() {
|
|
119
|
+
this.loadGitignore();
|
|
120
|
+
this.watcher = chokidar_1.default.watch(this.projectPath, {
|
|
121
|
+
ignored: (filePath) => {
|
|
122
|
+
const relativePath = path.relative(this.projectPath, filePath);
|
|
123
|
+
return this.isIgnored(relativePath);
|
|
124
|
+
},
|
|
125
|
+
persistent: true,
|
|
126
|
+
ignoreInitial: true,
|
|
127
|
+
});
|
|
128
|
+
const handleFileChange = (filePath) => {
|
|
129
|
+
const relativePath = path.relative(this.projectPath, filePath);
|
|
130
|
+
if (this.isIgnored(relativePath))
|
|
131
|
+
return;
|
|
132
|
+
this.changedQueue.add(relativePath);
|
|
133
|
+
this.triggerDebounce();
|
|
134
|
+
};
|
|
135
|
+
this.watcher
|
|
136
|
+
.on('add', handleFileChange)
|
|
137
|
+
.on('change', handleFileChange)
|
|
138
|
+
.on('unlink', handleFileChange);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Stops the filesystem watcher.
|
|
142
|
+
*/
|
|
143
|
+
async stop() {
|
|
144
|
+
if (this.watcher) {
|
|
145
|
+
await this.watcher.close();
|
|
146
|
+
this.watcher = null;
|
|
147
|
+
}
|
|
148
|
+
if (this.debounceTimeout) {
|
|
149
|
+
clearTimeout(this.debounceTimeout);
|
|
150
|
+
this.debounceTimeout = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
triggerDebounce() {
|
|
154
|
+
if (this.debounceTimeout) {
|
|
155
|
+
clearTimeout(this.debounceTimeout);
|
|
156
|
+
}
|
|
157
|
+
this.debounceTimeout = setTimeout(() => {
|
|
158
|
+
const changed = Array.from(this.changedQueue);
|
|
159
|
+
this.changedQueue.clear();
|
|
160
|
+
if (changed.length > 0) {
|
|
161
|
+
this.onChange(changed);
|
|
162
|
+
}
|
|
163
|
+
}, this.debounceMs);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
exports.GitAwareWatcher = GitAwareWatcher;
|