neuronlayer 0.1.7 → 0.1.9
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 +101 -216
- package/dist/index.js +1031 -171
- package/package.json +2 -2
- package/src/cli/commands.ts +88 -27
- package/src/core/adr-exporter.ts +4 -4
- package/src/core/engine.ts +60 -10
- package/src/core/ghost-mode.ts +54 -1
- package/src/core/project-manager.ts +7 -3
- package/src/core/refresh/index.ts +1 -1
- package/src/index.ts +44 -3
- package/src/indexing/ast.ts +356 -51
- package/src/indexing/indexer.ts +28 -3
- package/src/server/gateways/index.ts +473 -473
- package/src/server/gateways/memory-ghost.ts +343 -343
- package/src/server/gateways/memory-query.ts +452 -452
- package/src/server/gateways/memory-record.ts +346 -346
- package/src/server/gateways/memory-review.ts +410 -410
- package/src/server/gateways/memory-status.ts +517 -517
- package/src/server/gateways/memory-verify.ts +392 -392
- package/src/server/http.ts +228 -0
- package/src/server/mcp.ts +7 -7
- package/src/server/resources.ts +85 -85
- package/src/server/tools.ts +2460 -2331
- package/src/storage/tier2.ts +212 -4
- package/src/types/index.ts +2 -2
- package/src/utils/config.ts +5 -4
- package/real-benchmark.mjs +0 -322
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuronlayer",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "MCP server that gives AI assistants persistent understanding of your codebase",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
package/src/cli/commands.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function listProjects(): CommandResult {
|
|
|
22
22
|
if (projects.length === 0) {
|
|
23
23
|
return {
|
|
24
24
|
success: true,
|
|
25
|
-
message: 'No projects registered. Use "
|
|
25
|
+
message: 'No projects registered. Use "neuronlayer projects add <path>" to add one.'
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -123,7 +123,7 @@ export function discoverProjects(): CommandResult {
|
|
|
123
123
|
lines.push(` ${path}`);
|
|
124
124
|
lines.push('');
|
|
125
125
|
}
|
|
126
|
-
lines.push('Use "
|
|
126
|
+
lines.push('Use "neuronlayer projects add <path>" to register a project.');
|
|
127
127
|
|
|
128
128
|
return {
|
|
129
129
|
success: true,
|
|
@@ -144,7 +144,7 @@ export function exportDecisions(
|
|
|
144
144
|
if (!activeProject) {
|
|
145
145
|
return {
|
|
146
146
|
success: false,
|
|
147
|
-
message: 'No project specified and no active project. Use "
|
|
147
|
+
message: 'No project specified and no active project. Use "neuronlayer projects switch <id>" first.'
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
targetPath = activeProject.path;
|
|
@@ -155,17 +155,23 @@ export function exportDecisions(
|
|
|
155
155
|
if (!projectInfo) {
|
|
156
156
|
return {
|
|
157
157
|
success: false,
|
|
158
|
-
message: `Project not registered: ${targetPath}. Use "
|
|
158
|
+
message: `Project not registered: ${targetPath}. Use "neuronlayer projects add ${targetPath}" first.`
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
// Open database and get decisions
|
|
163
|
-
|
|
162
|
+
// Open database and get decisions (check both new and old names)
|
|
163
|
+
let dbPath = join(projectInfo.dataDir, 'neuronlayer.db');
|
|
164
164
|
if (!existsSync(dbPath)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
// Fall back to old name for backwards compatibility
|
|
166
|
+
const oldDbPath = join(projectInfo.dataDir, 'memorylayer.db');
|
|
167
|
+
if (existsSync(oldDbPath)) {
|
|
168
|
+
dbPath = oldDbPath;
|
|
169
|
+
} else {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
message: `Project database not found. Has the project been indexed?`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
const db = initializeDatabase(dbPath);
|
|
@@ -210,7 +216,7 @@ export function showProject(projectId?: string): CommandResult {
|
|
|
210
216
|
success: false,
|
|
211
217
|
message: projectId
|
|
212
218
|
? `Project not found: ${projectId}`
|
|
213
|
-
: 'No active project. Use "
|
|
219
|
+
: 'No active project. Use "neuronlayer projects switch <id>" first.'
|
|
214
220
|
};
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -272,6 +278,40 @@ function configureMCPClient(
|
|
|
272
278
|
}
|
|
273
279
|
}
|
|
274
280
|
|
|
281
|
+
// Helper to configure project-local .mcp.json for Claude Code
|
|
282
|
+
function configureProjectMCP(
|
|
283
|
+
configPath: string,
|
|
284
|
+
projectPath: string
|
|
285
|
+
): { success: boolean; message: string } {
|
|
286
|
+
let config: { mcpServers?: Record<string, unknown> } = { mcpServers: {} };
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
if (existsSync(configPath)) {
|
|
290
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
291
|
+
config = JSON.parse(content);
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
// Config doesn't exist or is invalid, start fresh
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!config.mcpServers) {
|
|
298
|
+
config.mcpServers = {};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Use simple name since this is project-specific
|
|
302
|
+
config.mcpServers['neuronlayer'] = {
|
|
303
|
+
command: 'npx',
|
|
304
|
+
args: ['-y', 'neuronlayer', '--project', '.']
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
309
|
+
return { success: true, message: `Claude Code: ${configPath} (project-local)` };
|
|
310
|
+
} catch (err) {
|
|
311
|
+
return { success: false, message: `Claude Code: Failed - ${err instanceof Error ? err.message : String(err)}` };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
275
315
|
// Initialize neuronlayer for current project + auto-configure Claude Desktop & OpenCode
|
|
276
316
|
export function initProject(projectPath?: string): CommandResult {
|
|
277
317
|
const targetPath = projectPath || process.cwd();
|
|
@@ -315,14 +355,29 @@ export function initProject(projectPath?: string): CommandResult {
|
|
|
315
355
|
failedClients.push(openCodeResult.message);
|
|
316
356
|
}
|
|
317
357
|
|
|
318
|
-
// 4. Configure Claude Code (CLI) -
|
|
319
|
-
//
|
|
320
|
-
const claudeCodeConfigPath = join(
|
|
321
|
-
const claudeCodeResult =
|
|
358
|
+
// 4. Configure Claude Code (CLI) - use project-local .mcp.json
|
|
359
|
+
// This ensures only the current project's NeuronLayer connects
|
|
360
|
+
const claudeCodeConfigPath = join(targetPath, '.mcp.json');
|
|
361
|
+
const claudeCodeResult = configureProjectMCP(claudeCodeConfigPath, targetPath);
|
|
322
362
|
if (claudeCodeResult.success) {
|
|
323
363
|
configuredClients.push(claudeCodeResult.message);
|
|
324
364
|
}
|
|
325
365
|
|
|
366
|
+
// 5. Configure Cursor
|
|
367
|
+
let cursorConfigPath: string;
|
|
368
|
+
if (platform === 'win32') {
|
|
369
|
+
cursorConfigPath = join(homedir(), 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');
|
|
370
|
+
} else if (platform === 'darwin') {
|
|
371
|
+
cursorConfigPath = join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');
|
|
372
|
+
} else {
|
|
373
|
+
cursorConfigPath = join(homedir(), '.config', 'Cursor', 'User', 'globalStorage', 'cursor.mcp', 'mcp.json');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const cursorResult = configureMCPClient('Cursor', cursorConfigPath, serverName, targetPath);
|
|
377
|
+
if (cursorResult.success) {
|
|
378
|
+
configuredClients.push(cursorResult.message);
|
|
379
|
+
}
|
|
380
|
+
|
|
326
381
|
// Build result message
|
|
327
382
|
let message = `
|
|
328
383
|
NeuronLayer initialized!
|
|
@@ -351,14 +406,15 @@ ${configuredClients.map(c => ' ✓ ' + c).join('\n')}
|
|
|
351
406
|
// Print help
|
|
352
407
|
export function printHelp(): void {
|
|
353
408
|
console.log(`
|
|
354
|
-
|
|
409
|
+
NeuronLayer CLI - Code Intelligence for AI Coding Assistants
|
|
355
410
|
|
|
356
411
|
USAGE:
|
|
357
|
-
|
|
412
|
+
neuronlayer [command] [options]
|
|
358
413
|
|
|
359
414
|
COMMANDS:
|
|
360
|
-
init [path] Initialize project + auto-configure
|
|
361
|
-
|
|
415
|
+
init [path] Initialize project + auto-configure AI tools
|
|
416
|
+
serve [options] Start HTTP API server (for non-MCP tools)
|
|
417
|
+
(no command) Start MCP server
|
|
362
418
|
projects list List all registered projects
|
|
363
419
|
projects add <path> Add a project to the registry
|
|
364
420
|
projects remove <id> Remove a project from the registry
|
|
@@ -370,6 +426,7 @@ COMMANDS:
|
|
|
370
426
|
|
|
371
427
|
OPTIONS:
|
|
372
428
|
--project, -p <path> Path to the project directory
|
|
429
|
+
--port <number> Port for HTTP server (default: 3333)
|
|
373
430
|
--output, -o <dir> Output directory for exports
|
|
374
431
|
--format <type> ADR format: madr, nygard, simple
|
|
375
432
|
|
|
@@ -382,19 +439,23 @@ EXAMPLES:
|
|
|
382
439
|
neuronlayer --project /path/to/project
|
|
383
440
|
|
|
384
441
|
# List all projects
|
|
385
|
-
|
|
442
|
+
neuronlayer projects list
|
|
386
443
|
|
|
387
444
|
# Add a new project
|
|
388
|
-
|
|
445
|
+
neuronlayer projects add /path/to/my-project
|
|
389
446
|
|
|
390
447
|
# Switch active project
|
|
391
|
-
|
|
448
|
+
neuronlayer projects switch abc123
|
|
392
449
|
|
|
393
450
|
# Export decisions to ADR files
|
|
394
|
-
|
|
451
|
+
neuronlayer export --format madr
|
|
395
452
|
|
|
396
453
|
# Discover projects
|
|
397
|
-
|
|
454
|
+
neuronlayer projects discover
|
|
455
|
+
|
|
456
|
+
# Start HTTP API server (for tools without MCP support)
|
|
457
|
+
neuronlayer serve --project /path/to/project
|
|
458
|
+
neuronlayer serve --port 8080
|
|
398
459
|
|
|
399
460
|
For more information, visit: https://github.com/abhisavakar/neuronlayer
|
|
400
461
|
`);
|
|
@@ -429,7 +490,7 @@ export function executeCLI(args: string[]): void {
|
|
|
429
490
|
const path = args[2];
|
|
430
491
|
if (!path) {
|
|
431
492
|
console.error('Error: Project path required.');
|
|
432
|
-
console.error('Usage:
|
|
493
|
+
console.error('Usage: neuronlayer projects add <path>');
|
|
433
494
|
process.exit(1);
|
|
434
495
|
}
|
|
435
496
|
const result = addProject(path);
|
|
@@ -441,7 +502,7 @@ export function executeCLI(args: string[]): void {
|
|
|
441
502
|
const id = args[2];
|
|
442
503
|
if (!id) {
|
|
443
504
|
console.error('Error: Project ID required.');
|
|
444
|
-
console.error('Usage:
|
|
505
|
+
console.error('Usage: neuronlayer projects remove <id>');
|
|
445
506
|
process.exit(1);
|
|
446
507
|
}
|
|
447
508
|
const result = removeProject(id);
|
|
@@ -453,7 +514,7 @@ export function executeCLI(args: string[]): void {
|
|
|
453
514
|
const id = args[2];
|
|
454
515
|
if (!id) {
|
|
455
516
|
console.error('Error: Project ID required.');
|
|
456
|
-
console.error('Usage:
|
|
517
|
+
console.error('Usage: neuronlayer projects switch <id>');
|
|
457
518
|
process.exit(1);
|
|
458
519
|
}
|
|
459
520
|
const result = switchProject(id);
|
package/src/core/adr-exporter.ts
CHANGED
|
@@ -107,7 +107,7 @@ export class ADRExporter {
|
|
|
107
107
|
lines.push('');
|
|
108
108
|
lines.push('---');
|
|
109
109
|
lines.push('');
|
|
110
|
-
lines.push('*Generated by
|
|
110
|
+
lines.push('*Generated by NeuronLayer*');
|
|
111
111
|
|
|
112
112
|
writeFileSync(indexPath, lines.join('\n'));
|
|
113
113
|
return indexPath;
|
|
@@ -157,7 +157,7 @@ ${decision.supersededBy ? `## Superseded By
|
|
|
157
157
|
This decision has been superseded by [ADR ${decision.supersededBy}](./${decision.supersededBy}.md).
|
|
158
158
|
` : ''}
|
|
159
159
|
---
|
|
160
|
-
*Exported from
|
|
160
|
+
*Exported from NeuronLayer*
|
|
161
161
|
`;
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -192,7 +192,7 @@ ${decision.files.length > 0 ? `## Related Files
|
|
|
192
192
|
${decision.files.map(f => `- ${f}`).join('\n')}
|
|
193
193
|
` : ''}
|
|
194
194
|
---
|
|
195
|
-
*Exported from
|
|
195
|
+
*Exported from NeuronLayer*
|
|
196
196
|
`;
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -216,7 +216,7 @@ ${decision.files.length > 0 ? `## Related Files
|
|
|
216
216
|
${decision.files.map(f => `- \`${f}\``).join('\n')}
|
|
217
217
|
` : ''}
|
|
218
218
|
---
|
|
219
|
-
*Exported from
|
|
219
|
+
*Exported from NeuronLayer*
|
|
220
220
|
`;
|
|
221
221
|
}
|
|
222
222
|
|
package/src/core/engine.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join, basename } from 'path';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync, renameSync } from 'fs';
|
|
3
3
|
import { initializeDatabase, closeDatabase } from '../storage/database.js';
|
|
4
4
|
import { Tier1Storage } from '../storage/tier1.js';
|
|
5
5
|
import { Tier2Storage } from '../storage/tier2.js';
|
|
@@ -24,15 +24,15 @@ import { DejaVuDetector, type DejaVuMatch } from './deja-vu.js';
|
|
|
24
24
|
import { CodeVerifier, type VerificationResult, type VerificationCheck, type ImportVerification, type SecurityScanResult, type DependencyCheckResult } from './code-verifier.js';
|
|
25
25
|
import { GitStalenessChecker, ActivityGate } from './refresh/index.js';
|
|
26
26
|
import { detectLanguage, getPreview, countLines } from '../utils/files.js';
|
|
27
|
-
import type {
|
|
27
|
+
import type { NeuronLayerConfig, AssembledContext, Decision, ProjectSummary, SearchResult, CodeSymbol, SymbolKind, ActiveFeatureContext, HotContext } from '../types/index.js';
|
|
28
28
|
import type { ArchitectureDoc, ComponentDoc, DailyChangelog, ChangelogOptions, ValidationResult, ActivityResult, UndocumentedItem, ContextHealth, CompactionResult, CompactionOptions, CriticalContext, DriftResult, ConfidenceResult, ConfidenceLevel, ConfidenceSources, ConflictResult, ChangeQueryResult, ChangeQueryOptions, Diagnosis, PastBug, FixSuggestion, Change, Pattern, PatternCategory, PatternValidationResult, ExistingFunction, TestInfo, TestFramework, TestValidationResult, TestUpdate, TestCoverage } from '../types/documentation.js';
|
|
29
29
|
import type Database from 'better-sqlite3';
|
|
30
30
|
|
|
31
31
|
// Re-export types for external use
|
|
32
32
|
export type { GhostInsight, ConflictWarning, DejaVuMatch, ResurrectedContext, VerificationResult, VerificationCheck, ImportVerification, SecurityScanResult, DependencyCheckResult };
|
|
33
33
|
|
|
34
|
-
export class
|
|
35
|
-
private config:
|
|
34
|
+
export class NeuronLayerEngine {
|
|
35
|
+
private config: NeuronLayerConfig;
|
|
36
36
|
private db: Database.Database;
|
|
37
37
|
private tier1: Tier1Storage;
|
|
38
38
|
private tier2: Tier2Storage;
|
|
@@ -60,7 +60,7 @@ export class MemoryLayerEngine {
|
|
|
60
60
|
private initializationStatus: 'pending' | 'indexing' | 'ready' | 'error' = 'pending';
|
|
61
61
|
private indexingProgress: { indexed: number; total: number } = { indexed: 0, total: 0 };
|
|
62
62
|
|
|
63
|
-
constructor(config:
|
|
63
|
+
constructor(config: NeuronLayerConfig) {
|
|
64
64
|
this.config = config;
|
|
65
65
|
|
|
66
66
|
// Ensure data directory exists
|
|
@@ -68,8 +68,22 @@ export class MemoryLayerEngine {
|
|
|
68
68
|
mkdirSync(config.dataDir, { recursive: true });
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// Initialize database
|
|
72
|
-
|
|
71
|
+
// Initialize database (with migration from old name)
|
|
72
|
+
let dbPath = join(config.dataDir, 'neuronlayer.db');
|
|
73
|
+
const oldDbPath = join(config.dataDir, 'memorylayer.db');
|
|
74
|
+
|
|
75
|
+
// Migrate from old database name if it exists
|
|
76
|
+
if (!existsSync(dbPath) && existsSync(oldDbPath)) {
|
|
77
|
+
try {
|
|
78
|
+
console.error('Migrating database from memorylayer.db to neuronlayer.db...');
|
|
79
|
+
renameSync(oldDbPath, dbPath);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// If rename fails (file locked), use the old database path
|
|
82
|
+
console.error('Migration skipped (file in use), using existing database');
|
|
83
|
+
dbPath = oldDbPath;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
73
87
|
this.db = initializeDatabase(dbPath);
|
|
74
88
|
|
|
75
89
|
// Initialize storage tiers
|
|
@@ -214,6 +228,16 @@ export class MemoryLayerEngine {
|
|
|
214
228
|
this.updateProjectStats();
|
|
215
229
|
// Extract decisions from git and comments
|
|
216
230
|
this.extractDecisions().catch(err => console.error('Decision extraction error:', err));
|
|
231
|
+
|
|
232
|
+
// Index tests after code indexing is complete
|
|
233
|
+
try {
|
|
234
|
+
const testResult = this.testAwareness.refreshIndex();
|
|
235
|
+
if (testResult.testsIndexed > 0) {
|
|
236
|
+
console.error(`Test index: ${testResult.testsIndexed} tests (${testResult.framework})`);
|
|
237
|
+
}
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.error('Test indexing error:', err);
|
|
240
|
+
}
|
|
217
241
|
});
|
|
218
242
|
|
|
219
243
|
this.indexer.on('fileIndexed', (path) => {
|
|
@@ -224,6 +248,22 @@ export class MemoryLayerEngine {
|
|
|
224
248
|
this.summarizer.invalidateSummaryByPath(path);
|
|
225
249
|
});
|
|
226
250
|
|
|
251
|
+
this.indexer.on('fileImpact', (impact: { file: string; affectedFiles: string[]; affectedCount: number }) => {
|
|
252
|
+
// Log impact warning for file changes
|
|
253
|
+
if (impact.affectedCount > 0) {
|
|
254
|
+
console.error(`[Impact] ${impact.file} changed → ${impact.affectedCount} file(s) may be affected`);
|
|
255
|
+
if (impact.affectedCount <= 5) {
|
|
256
|
+
impact.affectedFiles.forEach(f => console.error(` → ${f}`));
|
|
257
|
+
} else {
|
|
258
|
+
impact.affectedFiles.slice(0, 3).forEach(f => console.error(` → ${f}`));
|
|
259
|
+
console.error(` ... and ${impact.affectedCount - 3} more`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Track impact in ghost mode for proactive warnings
|
|
263
|
+
this.ghostMode.onFileImpact(impact.file, impact.affectedFiles);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
227
267
|
this.indexer.on('error', (error) => {
|
|
228
268
|
console.error('Indexer error:', error);
|
|
229
269
|
});
|
|
@@ -232,7 +272,7 @@ export class MemoryLayerEngine {
|
|
|
232
272
|
async initialize(): Promise<void> {
|
|
233
273
|
if (this.initialized) return;
|
|
234
274
|
|
|
235
|
-
console.error(`Initializing
|
|
275
|
+
console.error(`Initializing NeuronLayer for: ${this.config.projectPath}`);
|
|
236
276
|
|
|
237
277
|
try {
|
|
238
278
|
// Perform initial indexing
|
|
@@ -274,7 +314,7 @@ export class MemoryLayerEngine {
|
|
|
274
314
|
|
|
275
315
|
this.initialized = true;
|
|
276
316
|
this.initializationStatus = 'ready';
|
|
277
|
-
console.error('
|
|
317
|
+
console.error('NeuronLayer initialized');
|
|
278
318
|
} catch (error) {
|
|
279
319
|
this.initializationStatus = 'error';
|
|
280
320
|
throw error;
|
|
@@ -727,6 +767,16 @@ export class MemoryLayerEngine {
|
|
|
727
767
|
return { imports, importedBy, symbols };
|
|
728
768
|
}
|
|
729
769
|
|
|
770
|
+
// Find circular dependencies in the project
|
|
771
|
+
findCircularDependencies(): Array<string[]> {
|
|
772
|
+
return this.tier2.findCircularDependencies();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Get transitive dependents (all files affected by changing a file)
|
|
776
|
+
getTransitiveDependents(filePath: string, maxDepth: number = 3): Array<{ file: string; depth: number; imports: string[] }> {
|
|
777
|
+
return this.tier2.getTransitiveDependents(filePath, maxDepth);
|
|
778
|
+
}
|
|
779
|
+
|
|
730
780
|
// Phase 2: Get symbol count
|
|
731
781
|
getSymbolCount(): number {
|
|
732
782
|
return this.tier2.getSymbolCount();
|
|
@@ -1586,7 +1636,7 @@ export class MemoryLayerEngine {
|
|
|
1586
1636
|
}
|
|
1587
1637
|
|
|
1588
1638
|
shutdown(): void {
|
|
1589
|
-
console.error('Shutting down
|
|
1639
|
+
console.error('Shutting down NeuronLayer...');
|
|
1590
1640
|
this.indexer.stopWatching();
|
|
1591
1641
|
this.activityGate.shutdown();
|
|
1592
1642
|
this.tier1.save();
|
package/src/core/ghost-mode.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Silently tracks what files Claude reads/writes. When code is written that
|
|
5
5
|
* touches a file with recorded decisions, automatically checks for conflicts.
|
|
6
|
-
* Makes
|
|
6
|
+
* Makes NeuronLayer feel "telepathic" by surfacing relevant context proactively.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Decision } from '../types/index.js';
|
|
@@ -64,9 +64,16 @@ const TECH_PATTERNS = [
|
|
|
64
64
|
{ pattern: /\b(sass|scss|less)\b/i, category: 'styling', term: 'CSS preprocessor' },
|
|
65
65
|
];
|
|
66
66
|
|
|
67
|
+
export interface FileImpact {
|
|
68
|
+
changedFile: string;
|
|
69
|
+
affectedFiles: string[];
|
|
70
|
+
timestamp: Date;
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
export class GhostMode {
|
|
68
74
|
private activeFiles: Map<string, FileContext> = new Map();
|
|
69
75
|
private recentDecisions: Map<string, Decision[]> = new Map();
|
|
76
|
+
private recentImpacts: Map<string, FileImpact> = new Map(); // Track file impacts
|
|
70
77
|
private tier2: Tier2Storage;
|
|
71
78
|
private embeddingGenerator: EmbeddingGenerator;
|
|
72
79
|
|
|
@@ -74,12 +81,58 @@ export class GhostMode {
|
|
|
74
81
|
private readonly MAX_ACTIVE_FILES = 20;
|
|
75
82
|
private readonly FILE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
76
83
|
private readonly DECISION_CACHE_SIZE = 50;
|
|
84
|
+
private readonly IMPACT_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
77
85
|
|
|
78
86
|
constructor(tier2: Tier2Storage, embeddingGenerator: EmbeddingGenerator) {
|
|
79
87
|
this.tier2 = tier2;
|
|
80
88
|
this.embeddingGenerator = embeddingGenerator;
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Called when a file change impacts other files
|
|
93
|
+
*/
|
|
94
|
+
onFileImpact(changedFile: string, affectedFiles: string[]): void {
|
|
95
|
+
const impact: FileImpact = {
|
|
96
|
+
changedFile,
|
|
97
|
+
affectedFiles,
|
|
98
|
+
timestamp: new Date()
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Store impact for each affected file
|
|
102
|
+
for (const file of affectedFiles) {
|
|
103
|
+
this.recentImpacts.set(file, impact);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Evict old impacts
|
|
107
|
+
this.evictStaleImpacts();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a file was recently impacted by changes to another file
|
|
112
|
+
*/
|
|
113
|
+
getImpactWarning(filePath: string): FileImpact | null {
|
|
114
|
+
const impact = this.recentImpacts.get(filePath);
|
|
115
|
+
if (!impact) return null;
|
|
116
|
+
|
|
117
|
+
// Check if still within TTL
|
|
118
|
+
const age = Date.now() - impact.timestamp.getTime();
|
|
119
|
+
if (age > this.IMPACT_TTL_MS) {
|
|
120
|
+
this.recentImpacts.delete(filePath);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return impact;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private evictStaleImpacts(): void {
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
for (const [file, impact] of this.recentImpacts) {
|
|
130
|
+
if (now - impact.timestamp.getTime() > this.IMPACT_TTL_MS) {
|
|
131
|
+
this.recentImpacts.delete(file);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
83
136
|
/**
|
|
84
137
|
* Called when any file is read - silently track and pre-fetch decisions
|
|
85
138
|
*/
|
|
@@ -3,7 +3,7 @@ import { join, basename, resolve } from 'path';
|
|
|
3
3
|
import { createHash } from 'crypto';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
|
-
import type {
|
|
6
|
+
import type { NeuronLayerConfig } from '../types/index.js';
|
|
7
7
|
|
|
8
8
|
export interface ProjectInfo {
|
|
9
9
|
id: string;
|
|
@@ -177,7 +177,7 @@ export class ProjectManager {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// Get config for a project
|
|
180
|
-
getProjectConfig(projectPath: string):
|
|
180
|
+
getProjectConfig(projectPath: string): NeuronLayerConfig {
|
|
181
181
|
const normalizedPath = resolve(projectPath);
|
|
182
182
|
const dataDir = this.getProjectDataDir(normalizedPath);
|
|
183
183
|
|
|
@@ -269,7 +269,11 @@ export class ProjectManager {
|
|
|
269
269
|
const result: Array<{ project: ProjectInfo; db: Database.Database }> = [];
|
|
270
270
|
|
|
271
271
|
for (const project of this.listProjects()) {
|
|
272
|
-
|
|
272
|
+
// Check both new and old database names
|
|
273
|
+
let dbPath = join(project.dataDir, 'neuronlayer.db');
|
|
274
|
+
if (!existsSync(dbPath)) {
|
|
275
|
+
dbPath = join(project.dataDir, 'memorylayer.db');
|
|
276
|
+
}
|
|
273
277
|
|
|
274
278
|
if (existsSync(dbPath)) {
|
|
275
279
|
try {
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { MCPServer } from './server/mcp.js';
|
|
2
|
+
import { HTTPServer } from './server/http.js';
|
|
2
3
|
import { getDefaultConfig, parseArgs } from './utils/config.js';
|
|
3
4
|
import { executeCLI, printHelp } from './cli/commands.js';
|
|
4
5
|
|
|
6
|
+
function parseServeArgs(args: string[]): { projectPath: string; port: number } {
|
|
7
|
+
let projectPath = process.cwd();
|
|
8
|
+
let port = 3333;
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < args.length; i++) {
|
|
11
|
+
const arg = args[i];
|
|
12
|
+
const nextArg = args[i + 1];
|
|
13
|
+
if ((arg === '--project' || arg === '-p') && nextArg) {
|
|
14
|
+
projectPath = nextArg;
|
|
15
|
+
i++;
|
|
16
|
+
} else if (arg === '--port' && nextArg) {
|
|
17
|
+
port = parseInt(nextArg) || 3333;
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { projectPath, port };
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
async function main(): Promise<void> {
|
|
6
26
|
const args = process.argv.slice(2);
|
|
7
27
|
|
|
@@ -15,10 +35,31 @@ async function main(): Promise<void> {
|
|
|
15
35
|
return;
|
|
16
36
|
}
|
|
17
37
|
|
|
38
|
+
// Handle serve command - start HTTP API server
|
|
39
|
+
if (firstArg === 'serve') {
|
|
40
|
+
const { projectPath, port } = parseServeArgs(args.slice(1));
|
|
41
|
+
const config = getDefaultConfig(projectPath);
|
|
42
|
+
|
|
43
|
+
console.log('NeuronLayer HTTP API starting...');
|
|
44
|
+
console.log(`Project: ${config.projectPath}`);
|
|
45
|
+
console.log(`Data directory: ${config.dataDir}`);
|
|
46
|
+
console.log('');
|
|
47
|
+
|
|
48
|
+
const server = new HTTPServer(config, port);
|
|
49
|
+
try {
|
|
50
|
+
await server.start();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Failed to start HTTP server:', error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
// No arguments and not piped - show help
|
|
19
59
|
if (args.length === 0 && process.stdin.isTTY) {
|
|
20
60
|
printHelp();
|
|
21
|
-
console.log('\nTo start as MCP server, use:
|
|
61
|
+
console.log('\nTo start as MCP server, use: neuronlayer --project <path>');
|
|
62
|
+
console.log('To start HTTP API, use: neuronlayer serve --project <path>\n');
|
|
22
63
|
return;
|
|
23
64
|
}
|
|
24
65
|
|
|
@@ -28,7 +69,7 @@ async function main(): Promise<void> {
|
|
|
28
69
|
// Get configuration
|
|
29
70
|
const config = getDefaultConfig(projectPath);
|
|
30
71
|
|
|
31
|
-
console.error('
|
|
72
|
+
console.error('NeuronLayer starting...');
|
|
32
73
|
console.error(`Project: ${config.projectPath}`);
|
|
33
74
|
console.error(`Data directory: ${config.dataDir}`);
|
|
34
75
|
|
|
@@ -38,7 +79,7 @@ async function main(): Promise<void> {
|
|
|
38
79
|
try {
|
|
39
80
|
await server.start();
|
|
40
81
|
} catch (error) {
|
|
41
|
-
console.error('Failed to start
|
|
82
|
+
console.error('Failed to start NeuronLayer:', error);
|
|
42
83
|
process.exit(1);
|
|
43
84
|
}
|
|
44
85
|
}
|