neuronlayer 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of neuronlayer might be problematic. Click here for more details.
- package/README.md +3 -2
- package/dist/index.js +172 -90
- package/dist/index.js.map +7 -0
- package/package.json +6 -1
- package/esbuild.config.js +0 -26
- package/src/cli/commands.ts +0 -573
- package/src/core/adr-exporter.ts +0 -253
- package/src/core/architecture/architecture-enforcement.ts +0 -228
- package/src/core/architecture/duplicate-detector.ts +0 -288
- package/src/core/architecture/index.ts +0 -6
- package/src/core/architecture/pattern-learner.ts +0 -306
- package/src/core/architecture/pattern-library.ts +0 -403
- package/src/core/architecture/pattern-validator.ts +0 -324
- package/src/core/change-intelligence/bug-correlator.ts +0 -544
- package/src/core/change-intelligence/change-intelligence.ts +0 -264
- package/src/core/change-intelligence/change-tracker.ts +0 -334
- package/src/core/change-intelligence/fix-suggester.ts +0 -340
- package/src/core/change-intelligence/index.ts +0 -5
- package/src/core/code-verifier.ts +0 -843
- package/src/core/confidence/confidence-scorer.ts +0 -251
- package/src/core/confidence/conflict-checker.ts +0 -289
- package/src/core/confidence/index.ts +0 -5
- package/src/core/confidence/source-tracker.ts +0 -263
- package/src/core/confidence/warning-detector.ts +0 -241
- package/src/core/context-rot/compaction.ts +0 -284
- package/src/core/context-rot/context-health.ts +0 -243
- package/src/core/context-rot/context-rot-prevention.ts +0 -213
- package/src/core/context-rot/critical-context.ts +0 -221
- package/src/core/context-rot/drift-detector.ts +0 -255
- package/src/core/context-rot/index.ts +0 -7
- package/src/core/context.ts +0 -263
- package/src/core/decision-extractor.ts +0 -339
- package/src/core/decisions.ts +0 -69
- package/src/core/deja-vu.ts +0 -421
- package/src/core/engine.ts +0 -1646
- package/src/core/feature-context.ts +0 -726
- package/src/core/ghost-mode.ts +0 -465
- package/src/core/learning.ts +0 -519
- package/src/core/living-docs/activity-tracker.ts +0 -296
- package/src/core/living-docs/architecture-generator.ts +0 -428
- package/src/core/living-docs/changelog-generator.ts +0 -348
- package/src/core/living-docs/component-generator.ts +0 -230
- package/src/core/living-docs/doc-engine.ts +0 -134
- package/src/core/living-docs/doc-validator.ts +0 -282
- package/src/core/living-docs/index.ts +0 -8
- package/src/core/project-manager.ts +0 -301
- package/src/core/refresh/activity-gate.ts +0 -256
- package/src/core/refresh/git-staleness-checker.ts +0 -108
- package/src/core/refresh/index.ts +0 -27
- package/src/core/summarizer.ts +0 -290
- package/src/core/test-awareness/change-validator.ts +0 -499
- package/src/core/test-awareness/index.ts +0 -5
- package/src/index.ts +0 -90
- package/src/indexing/ast.ts +0 -868
- package/src/indexing/embeddings.ts +0 -85
- package/src/indexing/indexer.ts +0 -270
- package/src/indexing/watcher.ts +0 -78
- package/src/server/gateways/aggregator.ts +0 -374
- package/src/server/gateways/index.ts +0 -473
- package/src/server/gateways/memory-ghost.ts +0 -343
- package/src/server/gateways/memory-query.ts +0 -452
- package/src/server/gateways/memory-record.ts +0 -346
- package/src/server/gateways/memory-review.ts +0 -410
- package/src/server/gateways/memory-status.ts +0 -517
- package/src/server/gateways/memory-verify.ts +0 -392
- package/src/server/gateways/router.ts +0 -434
- package/src/server/gateways/types.ts +0 -610
- package/src/server/http.ts +0 -228
- package/src/server/mcp.ts +0 -154
- package/src/server/resources.ts +0 -85
- package/src/server/tools.ts +0 -2460
- package/src/storage/database.ts +0 -271
- package/src/storage/tier1.ts +0 -135
- package/src/storage/tier2.ts +0 -972
- package/src/storage/tier3.ts +0 -123
- package/src/types/documentation.ts +0 -619
- package/src/types/index.ts +0 -222
- package/src/utils/config.ts +0 -194
- package/src/utils/files.ts +0 -117
- package/src/utils/time.ts +0 -37
- package/src/utils/tokens.ts +0 -52
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { pipeline, type FeatureExtractionPipeline } from '@xenova/transformers';
|
|
2
|
-
|
|
3
|
-
export class EmbeddingGenerator {
|
|
4
|
-
private model: FeatureExtractionPipeline | null = null;
|
|
5
|
-
private initialized = false;
|
|
6
|
-
private initializing = false;
|
|
7
|
-
private modelName: string;
|
|
8
|
-
private dimension: number = 384; // Default for MiniLM
|
|
9
|
-
|
|
10
|
-
constructor(modelName: string = 'Xenova/all-MiniLM-L6-v2') {
|
|
11
|
-
this.modelName = modelName;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async initialize(): Promise<void> {
|
|
15
|
-
if (this.initialized) return;
|
|
16
|
-
if (this.initializing) {
|
|
17
|
-
// Wait for initialization to complete
|
|
18
|
-
while (this.initializing) {
|
|
19
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
-
}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.initializing = true;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
console.error(`Loading embedding model: ${this.modelName}...`);
|
|
28
|
-
|
|
29
|
-
this.model = await pipeline('feature-extraction', this.modelName, {
|
|
30
|
-
quantized: true
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
this.initialized = true;
|
|
34
|
-
console.error('Embedding model loaded successfully');
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error('Failed to load embedding model:', error);
|
|
37
|
-
throw error;
|
|
38
|
-
} finally {
|
|
39
|
-
this.initializing = false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async embed(text: string): Promise<Float32Array> {
|
|
44
|
-
await this.initialize();
|
|
45
|
-
|
|
46
|
-
if (!this.model) {
|
|
47
|
-
throw new Error('Embedding model not initialized');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Truncate very long texts
|
|
51
|
-
const maxChars = 8000; // ~2000 tokens
|
|
52
|
-
const truncatedText = text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
53
|
-
|
|
54
|
-
const output = await this.model(truncatedText, {
|
|
55
|
-
pooling: 'mean',
|
|
56
|
-
normalize: true
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Extract the embedding data
|
|
60
|
-
const data = output.data as Float32Array;
|
|
61
|
-
this.dimension = data.length;
|
|
62
|
-
|
|
63
|
-
return new Float32Array(data);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async embedBatch(texts: string[], batchSize: number = 8): Promise<Float32Array[]> {
|
|
67
|
-
const results: Float32Array[] = [];
|
|
68
|
-
|
|
69
|
-
for (let i = 0; i < texts.length; i += batchSize) {
|
|
70
|
-
const batch = texts.slice(i, i + batchSize);
|
|
71
|
-
const embeddings = await Promise.all(batch.map(t => this.embed(t)));
|
|
72
|
-
results.push(...embeddings);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
getDimension(): number {
|
|
79
|
-
return this.dimension;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
isInitialized(): boolean {
|
|
83
|
-
return this.initialized;
|
|
84
|
-
}
|
|
85
|
-
}
|
package/src/indexing/indexer.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { readFileSync, statSync } from 'fs';
|
|
2
|
-
import { glob } from 'glob';
|
|
3
|
-
import { join, relative } from 'path';
|
|
4
|
-
import { EventEmitter } from 'events';
|
|
5
|
-
import { EmbeddingGenerator } from './embeddings.js';
|
|
6
|
-
import { ASTParser } from './ast.js';
|
|
7
|
-
import { FileWatcher, type FileEvent } from './watcher.js';
|
|
8
|
-
import { Tier2Storage } from '../storage/tier2.js';
|
|
9
|
-
import { isCodeFile, detectLanguage, hashContent, getPreview, countLines } from '../utils/files.js';
|
|
10
|
-
import type { NeuronLayerConfig, IndexingProgress } from '../types/index.js';
|
|
11
|
-
|
|
12
|
-
export class Indexer extends EventEmitter {
|
|
13
|
-
private config: NeuronLayerConfig;
|
|
14
|
-
private embeddingGenerator: EmbeddingGenerator;
|
|
15
|
-
private astParser: ASTParser;
|
|
16
|
-
private watcher: FileWatcher;
|
|
17
|
-
private tier2: Tier2Storage;
|
|
18
|
-
private isIndexing = false;
|
|
19
|
-
private pendingFiles: Set<string> = new Set();
|
|
20
|
-
private processTimeout: NodeJS.Timeout | null = null;
|
|
21
|
-
|
|
22
|
-
constructor(config: NeuronLayerConfig, tier2: Tier2Storage) {
|
|
23
|
-
super();
|
|
24
|
-
this.config = config;
|
|
25
|
-
this.tier2 = tier2;
|
|
26
|
-
this.embeddingGenerator = new EmbeddingGenerator(config.embeddingModel);
|
|
27
|
-
this.astParser = new ASTParser(config.dataDir);
|
|
28
|
-
this.watcher = new FileWatcher(config.projectPath, config.watchIgnore);
|
|
29
|
-
|
|
30
|
-
this.setupWatcher();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private setupWatcher(): void {
|
|
34
|
-
this.watcher.on('file', (event: FileEvent) => {
|
|
35
|
-
this.handleFileEvent(event);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
this.watcher.on('ready', () => {
|
|
39
|
-
this.emit('watcherReady');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
this.watcher.on('error', (error) => {
|
|
43
|
-
this.emit('error', error);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private handleFileEvent(event: FileEvent): void {
|
|
48
|
-
// Only process code files
|
|
49
|
-
if (!isCodeFile(event.path)) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (event.type === 'unlink') {
|
|
54
|
-
// File deleted
|
|
55
|
-
this.tier2.deleteFile(event.relativePath);
|
|
56
|
-
this.emit('fileRemoved', event.relativePath);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Add or change - queue for processing
|
|
61
|
-
this.pendingFiles.add(event.path);
|
|
62
|
-
this.schedulePendingProcessing();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private schedulePendingProcessing(): void {
|
|
66
|
-
// Debounce processing
|
|
67
|
-
if (this.processTimeout) {
|
|
68
|
-
clearTimeout(this.processTimeout);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.processTimeout = setTimeout(() => {
|
|
72
|
-
this.processPendingFiles();
|
|
73
|
-
}, 500);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private async processPendingFiles(): Promise<void> {
|
|
77
|
-
if (this.isIndexing || this.pendingFiles.size === 0) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const files = Array.from(this.pendingFiles);
|
|
82
|
-
this.pendingFiles.clear();
|
|
83
|
-
|
|
84
|
-
for (const file of files) {
|
|
85
|
-
try {
|
|
86
|
-
await this.indexFile(file);
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error(`Error indexing ${file}:`, error);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async indexFile(absolutePath: string): Promise<boolean> {
|
|
94
|
-
try {
|
|
95
|
-
const content = readFileSync(absolutePath, 'utf-8');
|
|
96
|
-
const stats = statSync(absolutePath);
|
|
97
|
-
const relativePath = relative(this.config.projectPath, absolutePath);
|
|
98
|
-
|
|
99
|
-
const contentHash = hashContent(content);
|
|
100
|
-
const existingFile = this.tier2.getFile(relativePath);
|
|
101
|
-
|
|
102
|
-
// Skip if content hasn't changed
|
|
103
|
-
if (existingFile && existingFile.contentHash === contentHash) {
|
|
104
|
-
return false; // Not indexed, skipped
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const language = detectLanguage(absolutePath);
|
|
108
|
-
const preview = getPreview(content);
|
|
109
|
-
const lineCount = countLines(content);
|
|
110
|
-
|
|
111
|
-
// Store file metadata
|
|
112
|
-
const fileId = this.tier2.upsertFile(
|
|
113
|
-
relativePath,
|
|
114
|
-
contentHash,
|
|
115
|
-
preview,
|
|
116
|
-
language,
|
|
117
|
-
stats.size,
|
|
118
|
-
lineCount,
|
|
119
|
-
Math.floor(stats.mtimeMs)
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// Generate and store embedding
|
|
123
|
-
const embedding = await this.embeddingGenerator.embed(content);
|
|
124
|
-
this.tier2.upsertEmbedding(fileId, embedding);
|
|
125
|
-
|
|
126
|
-
// Phase 2: Parse AST and extract symbols
|
|
127
|
-
try {
|
|
128
|
-
const parsed = await this.astParser.parseFile(relativePath, content);
|
|
129
|
-
if (parsed) {
|
|
130
|
-
// Clear old symbols/imports/exports for this file
|
|
131
|
-
this.tier2.clearSymbols(fileId);
|
|
132
|
-
this.tier2.clearImports(fileId);
|
|
133
|
-
this.tier2.clearExports(fileId);
|
|
134
|
-
|
|
135
|
-
// Insert new symbols with fileId
|
|
136
|
-
if (parsed.symbols.length > 0) {
|
|
137
|
-
const symbolsWithFileId = parsed.symbols.map(s => ({ ...s, fileId }));
|
|
138
|
-
this.tier2.insertSymbols(symbolsWithFileId);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Insert imports with fileId
|
|
142
|
-
if (parsed.imports.length > 0) {
|
|
143
|
-
const importsWithFileId = parsed.imports.map(i => ({ ...i, fileId }));
|
|
144
|
-
this.tier2.insertImports(importsWithFileId);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Insert exports with fileId
|
|
148
|
-
if (parsed.exports.length > 0) {
|
|
149
|
-
const exportsWithFileId = parsed.exports.map(e => ({ ...e, fileId }));
|
|
150
|
-
this.tier2.insertExports(exportsWithFileId);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Build dependency edges from imports
|
|
154
|
-
if (parsed.imports.length > 0) {
|
|
155
|
-
this.tier2.clearDependencies(fileId);
|
|
156
|
-
for (const imp of parsed.imports) {
|
|
157
|
-
const targetFile = this.tier2.resolveImportToFile(relativePath, imp.importedFrom);
|
|
158
|
-
if (targetFile) {
|
|
159
|
-
this.tier2.addDependency(fileId, targetFile.id, 'imports');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
} catch (astError) {
|
|
165
|
-
// AST parsing is optional, don't fail the whole index
|
|
166
|
-
console.error(`AST parsing failed for ${relativePath}:`, astError);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
this.emit('fileIndexed', relativePath);
|
|
170
|
-
|
|
171
|
-
// Emit impact warning for changed files (not during initial indexing)
|
|
172
|
-
if (!this.isIndexing) {
|
|
173
|
-
const dependents = this.tier2.getFileDependents(relativePath);
|
|
174
|
-
if (dependents.length > 0) {
|
|
175
|
-
this.emit('fileImpact', {
|
|
176
|
-
file: relativePath,
|
|
177
|
-
affectedFiles: dependents.map(d => d.file),
|
|
178
|
-
affectedCount: dependents.length,
|
|
179
|
-
imports: dependents.map(d => ({ file: d.file, symbols: d.imports }))
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return true; // Actually indexed
|
|
185
|
-
} catch (error) {
|
|
186
|
-
console.error(`Error indexing ${absolutePath}:`, error);
|
|
187
|
-
this.emit('indexError', { path: absolutePath, error });
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async performInitialIndex(): Promise<void> {
|
|
193
|
-
if (this.isIndexing) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
this.isIndexing = true;
|
|
198
|
-
this.emit('indexingStarted');
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
// Find all code files
|
|
202
|
-
const patterns = [
|
|
203
|
-
'**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs',
|
|
204
|
-
'**/*.py', '**/*.rb', '**/*.go', '**/*.rs', '**/*.java', '**/*.kt',
|
|
205
|
-
'**/*.cs', '**/*.cpp', '**/*.c', '**/*.h', '**/*.hpp',
|
|
206
|
-
'**/*.php', '**/*.swift', '**/*.vue', '**/*.svelte',
|
|
207
|
-
'**/*.md', '**/*.json', '**/*.yaml', '**/*.yml',
|
|
208
|
-
'**/*.sql', '**/*.sh', '**/*.dockerfile',
|
|
209
|
-
'**/*.prisma', '**/*.graphql'
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
const files: string[] = [];
|
|
213
|
-
|
|
214
|
-
for (const pattern of patterns) {
|
|
215
|
-
const matches = await glob(pattern, {
|
|
216
|
-
cwd: this.config.projectPath,
|
|
217
|
-
ignore: this.config.watchIgnore,
|
|
218
|
-
absolute: true,
|
|
219
|
-
nodir: true
|
|
220
|
-
});
|
|
221
|
-
files.push(...matches);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Deduplicate
|
|
225
|
-
const uniqueFiles = [...new Set(files)];
|
|
226
|
-
|
|
227
|
-
let checked = 0;
|
|
228
|
-
let indexed = 0;
|
|
229
|
-
const total = uniqueFiles.length;
|
|
230
|
-
|
|
231
|
-
// Index files (only shows progress for actually indexed files)
|
|
232
|
-
for (const file of uniqueFiles) {
|
|
233
|
-
try {
|
|
234
|
-
const wasIndexed = await this.indexFile(file);
|
|
235
|
-
checked++;
|
|
236
|
-
if (wasIndexed) {
|
|
237
|
-
indexed++;
|
|
238
|
-
this.emit('progress', { total, indexed, current: relative(this.config.projectPath, file) });
|
|
239
|
-
}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error(`Error indexing ${file}:`, error);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.emit('indexingComplete', {
|
|
246
|
-
total: checked,
|
|
247
|
-
indexed,
|
|
248
|
-
skipped: checked - indexed
|
|
249
|
-
});
|
|
250
|
-
} finally {
|
|
251
|
-
this.isIndexing = false;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
startWatching(): void {
|
|
256
|
-
this.watcher.start();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
stopWatching(): void {
|
|
260
|
-
this.watcher.stop();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
getEmbeddingGenerator(): EmbeddingGenerator {
|
|
264
|
-
return this.embeddingGenerator;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
isCurrentlyIndexing(): boolean {
|
|
268
|
-
return this.isIndexing;
|
|
269
|
-
}
|
|
270
|
-
}
|
package/src/indexing/watcher.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import chokidar, { type FSWatcher } from 'chokidar';
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
|
-
import { relative } from 'path';
|
|
4
|
-
|
|
5
|
-
export interface FileEvent {
|
|
6
|
-
type: 'add' | 'change' | 'unlink';
|
|
7
|
-
path: string;
|
|
8
|
-
relativePath: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class FileWatcher extends EventEmitter {
|
|
12
|
-
private watcher: FSWatcher | null = null;
|
|
13
|
-
private projectPath: string;
|
|
14
|
-
private ignorePatterns: string[];
|
|
15
|
-
|
|
16
|
-
constructor(projectPath: string, ignorePatterns: string[] = []) {
|
|
17
|
-
super();
|
|
18
|
-
this.projectPath = projectPath;
|
|
19
|
-
this.ignorePatterns = ignorePatterns;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
start(): void {
|
|
23
|
-
if (this.watcher) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.watcher = chokidar.watch(this.projectPath, {
|
|
28
|
-
ignored: [
|
|
29
|
-
/(^|[\/\\])\../, // dotfiles
|
|
30
|
-
...this.ignorePatterns
|
|
31
|
-
],
|
|
32
|
-
persistent: true,
|
|
33
|
-
ignoreInitial: false, // We want initial add events for indexing
|
|
34
|
-
awaitWriteFinish: {
|
|
35
|
-
stabilityThreshold: 300,
|
|
36
|
-
pollInterval: 100
|
|
37
|
-
},
|
|
38
|
-
usePolling: false, // Use native events when possible
|
|
39
|
-
depth: 20 // Limit recursion depth
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
this.watcher
|
|
43
|
-
.on('add', (path) => this.handleEvent('add', path))
|
|
44
|
-
.on('change', (path) => this.handleEvent('change', path))
|
|
45
|
-
.on('unlink', (path) => this.handleEvent('unlink', path))
|
|
46
|
-
.on('error', (error) => {
|
|
47
|
-
console.error('File watcher error:', error);
|
|
48
|
-
this.emit('error', error);
|
|
49
|
-
})
|
|
50
|
-
.on('ready', () => {
|
|
51
|
-
console.error('File watcher ready');
|
|
52
|
-
this.emit('ready');
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private handleEvent(type: 'add' | 'change' | 'unlink', path: string): void {
|
|
57
|
-
const relativePath = relative(this.projectPath, path);
|
|
58
|
-
|
|
59
|
-
const event: FileEvent = {
|
|
60
|
-
type,
|
|
61
|
-
path,
|
|
62
|
-
relativePath
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
this.emit('file', event);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
stop(): void {
|
|
69
|
-
if (this.watcher) {
|
|
70
|
-
this.watcher.close();
|
|
71
|
-
this.watcher = null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
isRunning(): boolean {
|
|
76
|
-
return this.watcher !== null;
|
|
77
|
-
}
|
|
78
|
-
}
|