codeseeker 1.7.2 → 1.7.3
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/README.md +6 -1
- package/dist/cli/commands/handlers/setup-command-handler.js +205 -205
- package/dist/cli/commands/services/context-aware-clarification-service.js +3 -3
- package/dist/cli/services/analysis/deduplication/code-consolidation-handler.js +41 -41
- package/dist/shared/analysis-repository.js +28 -28
- package/dist/shared/documentation-rag-service.js +40 -40
- package/dist/storage/embedded/sqlite-vector-store.js +57 -57
- package/dist/storage/server/neo4j-graph-store.js +81 -81
- package/dist/storage/server/postgres-vector-store.js +65 -65
- package/package.json +3 -2
- package/scripts/postinstall.js +458 -451
|
@@ -22,10 +22,10 @@ class AnalysisRepository {
|
|
|
22
22
|
try {
|
|
23
23
|
const pgClient = await this.dbConnections.getPostgresConnection();
|
|
24
24
|
const id = (0, uuid_1.v4)();
|
|
25
|
-
const query = `
|
|
26
|
-
INSERT INTO analysis_results (id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues)
|
|
27
|
-
VALUES ($1, $2, $3, NOW(), $4, $5, $6, $7)
|
|
28
|
-
RETURNING id
|
|
25
|
+
const query = `
|
|
26
|
+
INSERT INTO analysis_results (id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues)
|
|
27
|
+
VALUES ($1, $2, $3, NOW(), $4, $5, $6, $7)
|
|
28
|
+
RETURNING id
|
|
29
29
|
`;
|
|
30
30
|
const values = [
|
|
31
31
|
id,
|
|
@@ -51,10 +51,10 @@ class AnalysisRepository {
|
|
|
51
51
|
async getAnalysisHistory(projectId, toolName, limit = 10) {
|
|
52
52
|
try {
|
|
53
53
|
const pgClient = await this.dbConnections.getPostgresConnection();
|
|
54
|
-
let query = `
|
|
55
|
-
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
56
|
-
FROM analysis_results
|
|
57
|
-
WHERE project_id = $1
|
|
54
|
+
let query = `
|
|
55
|
+
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
56
|
+
FROM analysis_results
|
|
57
|
+
WHERE project_id = $1
|
|
58
58
|
`;
|
|
59
59
|
const values = [projectId];
|
|
60
60
|
if (toolName) {
|
|
@@ -92,12 +92,12 @@ class AnalysisRepository {
|
|
|
92
92
|
async getLatestAnalysis(projectId, toolName) {
|
|
93
93
|
try {
|
|
94
94
|
const pgClient = await this.dbConnections.getPostgresConnection();
|
|
95
|
-
const query = `
|
|
96
|
-
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
97
|
-
FROM analysis_results
|
|
98
|
-
WHERE project_id = $1 AND tool_name = $2
|
|
99
|
-
ORDER BY timestamp DESC
|
|
100
|
-
LIMIT 1
|
|
95
|
+
const query = `
|
|
96
|
+
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
97
|
+
FROM analysis_results
|
|
98
|
+
WHERE project_id = $1 AND tool_name = $2
|
|
99
|
+
ORDER BY timestamp DESC
|
|
100
|
+
LIMIT 1
|
|
101
101
|
`;
|
|
102
102
|
const result = await pgClient.query(query, [projectId, toolName]);
|
|
103
103
|
if (result.rows.length === 0) {
|
|
@@ -126,17 +126,17 @@ class AnalysisRepository {
|
|
|
126
126
|
async searchAnalysis(projectId, query) {
|
|
127
127
|
try {
|
|
128
128
|
const pgClient = await this.dbConnections.getPostgresConnection();
|
|
129
|
-
const searchQuery = `
|
|
130
|
-
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
131
|
-
FROM analysis_results
|
|
132
|
-
WHERE project_id = $1
|
|
133
|
-
AND (
|
|
134
|
-
tool_name ILIKE $2
|
|
135
|
-
OR summary ILIKE $2
|
|
136
|
-
OR analysis::text ILIKE $2
|
|
137
|
-
)
|
|
138
|
-
ORDER BY timestamp DESC
|
|
139
|
-
LIMIT 50
|
|
129
|
+
const searchQuery = `
|
|
130
|
+
SELECT id, project_id, tool_name, timestamp, analysis, summary, file_count, has_issues
|
|
131
|
+
FROM analysis_results
|
|
132
|
+
WHERE project_id = $1
|
|
133
|
+
AND (
|
|
134
|
+
tool_name ILIKE $2
|
|
135
|
+
OR summary ILIKE $2
|
|
136
|
+
OR analysis::text ILIKE $2
|
|
137
|
+
)
|
|
138
|
+
ORDER BY timestamp DESC
|
|
139
|
+
LIMIT 50
|
|
140
140
|
`;
|
|
141
141
|
const searchPattern = `%${query}%`;
|
|
142
142
|
const result = await pgClient.query(searchQuery, [projectId, searchPattern]);
|
|
@@ -164,9 +164,9 @@ class AnalysisRepository {
|
|
|
164
164
|
async deleteProjectAnalysis(projectId) {
|
|
165
165
|
try {
|
|
166
166
|
const pgClient = await this.dbConnections.getPostgresConnection();
|
|
167
|
-
const query = `
|
|
168
|
-
DELETE FROM analysis_results
|
|
169
|
-
WHERE project_id = $1
|
|
167
|
+
const query = `
|
|
168
|
+
DELETE FROM analysis_results
|
|
169
|
+
WHERE project_id = $1
|
|
170
170
|
`;
|
|
171
171
|
const result = await pgClient.query(query, [projectId]);
|
|
172
172
|
this.logger.info(`Deleted ${result.rowCount} analysis records for project ${projectId}`);
|
|
@@ -452,28 +452,28 @@ class DocumentationRAGService {
|
|
|
452
452
|
// Database operations
|
|
453
453
|
async createDocumentationTables() {
|
|
454
454
|
const client = await this.dbConnections.getPostgresConnection();
|
|
455
|
-
await client.query(`
|
|
456
|
-
CREATE TABLE IF NOT EXISTS documentation_chunks (
|
|
457
|
-
id VARCHAR(64) PRIMARY KEY,
|
|
458
|
-
file_path TEXT NOT NULL,
|
|
459
|
-
title TEXT NOT NULL,
|
|
460
|
-
content TEXT NOT NULL,
|
|
461
|
-
chunk_index INTEGER NOT NULL,
|
|
462
|
-
hash VARCHAR(64) NOT NULL,
|
|
463
|
-
document_type VARCHAR(32) NOT NULL,
|
|
464
|
-
metadata JSONB NOT NULL,
|
|
465
|
-
embedding vector(384),
|
|
466
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
467
|
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_embedding ON documentation_chunks
|
|
471
|
-
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
|
472
|
-
|
|
473
|
-
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_type ON documentation_chunks (document_type);
|
|
474
|
-
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_file_path ON documentation_chunks (file_path);
|
|
475
|
-
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_tech_stack ON documentation_chunks
|
|
476
|
-
USING gin ((metadata->'techStack'));
|
|
455
|
+
await client.query(`
|
|
456
|
+
CREATE TABLE IF NOT EXISTS documentation_chunks (
|
|
457
|
+
id VARCHAR(64) PRIMARY KEY,
|
|
458
|
+
file_path TEXT NOT NULL,
|
|
459
|
+
title TEXT NOT NULL,
|
|
460
|
+
content TEXT NOT NULL,
|
|
461
|
+
chunk_index INTEGER NOT NULL,
|
|
462
|
+
hash VARCHAR(64) NOT NULL,
|
|
463
|
+
document_type VARCHAR(32) NOT NULL,
|
|
464
|
+
metadata JSONB NOT NULL,
|
|
465
|
+
embedding vector(384),
|
|
466
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
467
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_embedding ON documentation_chunks
|
|
471
|
+
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
|
|
472
|
+
|
|
473
|
+
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_type ON documentation_chunks (document_type);
|
|
474
|
+
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_file_path ON documentation_chunks (file_path);
|
|
475
|
+
CREATE INDEX IF NOT EXISTS idx_documentation_chunks_tech_stack ON documentation_chunks
|
|
476
|
+
USING gin ((metadata->'techStack'));
|
|
477
477
|
`);
|
|
478
478
|
this.logger.debug('📚 Documentation tables created/verified');
|
|
479
479
|
}
|
|
@@ -481,15 +481,15 @@ class DocumentationRAGService {
|
|
|
481
481
|
const client = await this.dbConnections.getPostgresConnection();
|
|
482
482
|
// Generate embedding
|
|
483
483
|
const embedding = await this.embeddingService.generateEmbedding(`${chunk.title}\n${chunk.content}`, chunk.filePath);
|
|
484
|
-
await client.query(`
|
|
485
|
-
INSERT INTO documentation_chunks (
|
|
486
|
-
id, file_path, title, content, chunk_index, hash, document_type, metadata, embedding
|
|
487
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
488
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
489
|
-
content = EXCLUDED.content,
|
|
490
|
-
metadata = EXCLUDED.metadata,
|
|
491
|
-
embedding = EXCLUDED.embedding,
|
|
492
|
-
updated_at = CURRENT_TIMESTAMP
|
|
484
|
+
await client.query(`
|
|
485
|
+
INSERT INTO documentation_chunks (
|
|
486
|
+
id, file_path, title, content, chunk_index, hash, document_type, metadata, embedding
|
|
487
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
488
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
489
|
+
content = EXCLUDED.content,
|
|
490
|
+
metadata = EXCLUDED.metadata,
|
|
491
|
+
embedding = EXCLUDED.embedding,
|
|
492
|
+
updated_at = CURRENT_TIMESTAMP
|
|
493
493
|
`, [
|
|
494
494
|
chunk.id,
|
|
495
495
|
chunk.filePath,
|
|
@@ -517,15 +517,15 @@ class DocumentationRAGService {
|
|
|
517
517
|
params.push(techStack);
|
|
518
518
|
paramIndex++;
|
|
519
519
|
}
|
|
520
|
-
const query = `
|
|
521
|
-
SELECT
|
|
522
|
-
id, file_path, title, content, chunk_index, hash, document_type, metadata,
|
|
523
|
-
1 - (embedding <=> $1::vector) as similarity
|
|
524
|
-
FROM documentation_chunks
|
|
525
|
-
WHERE 1 - (embedding <=> $1::vector) > ${minSimilarity}
|
|
526
|
-
${whereClause}
|
|
527
|
-
ORDER BY embedding <=> $1::vector
|
|
528
|
-
LIMIT $2
|
|
520
|
+
const query = `
|
|
521
|
+
SELECT
|
|
522
|
+
id, file_path, title, content, chunk_index, hash, document_type, metadata,
|
|
523
|
+
1 - (embedding <=> $1::vector) as similarity
|
|
524
|
+
FROM documentation_chunks
|
|
525
|
+
WHERE 1 - (embedding <=> $1::vector) > ${minSimilarity}
|
|
526
|
+
${whereClause}
|
|
527
|
+
ORDER BY embedding <=> $1::vector
|
|
528
|
+
LIMIT $2
|
|
529
529
|
`;
|
|
530
530
|
const result = await client.query(query, params);
|
|
531
531
|
return result.rows.map(row => ({
|
|
@@ -77,20 +77,20 @@ class SQLiteVectorStore {
|
|
|
77
77
|
}
|
|
78
78
|
initializeSchema() {
|
|
79
79
|
// Main documents table
|
|
80
|
-
this.db.exec(`
|
|
81
|
-
CREATE TABLE IF NOT EXISTS documents (
|
|
82
|
-
id TEXT PRIMARY KEY,
|
|
83
|
-
project_id TEXT NOT NULL,
|
|
84
|
-
file_path TEXT NOT NULL,
|
|
85
|
-
content TEXT NOT NULL,
|
|
86
|
-
embedding BLOB NOT NULL,
|
|
87
|
-
metadata TEXT,
|
|
88
|
-
created_at TEXT NOT NULL,
|
|
89
|
-
updated_at TEXT NOT NULL
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
CREATE INDEX IF NOT EXISTS idx_documents_project ON documents(project_id);
|
|
93
|
-
CREATE INDEX IF NOT EXISTS idx_documents_file ON documents(file_path);
|
|
80
|
+
this.db.exec(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
project_id TEXT NOT NULL,
|
|
84
|
+
file_path TEXT NOT NULL,
|
|
85
|
+
content TEXT NOT NULL,
|
|
86
|
+
embedding BLOB NOT NULL,
|
|
87
|
+
metadata TEXT,
|
|
88
|
+
created_at TEXT NOT NULL,
|
|
89
|
+
updated_at TEXT NOT NULL
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_documents_project ON documents(project_id);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_documents_file ON documents(file_path);
|
|
94
94
|
`);
|
|
95
95
|
// Note: FTS5 virtual table removed - now using MiniSearch for text search
|
|
96
96
|
// MiniSearch provides better reliability (no sync issues) and synonym support
|
|
@@ -139,16 +139,16 @@ class SQLiteVectorStore {
|
|
|
139
139
|
const now = new Date().toISOString();
|
|
140
140
|
const embeddingBlob = this.serializeEmbedding(doc.embedding);
|
|
141
141
|
const metadata = doc.metadata ? JSON.stringify(doc.metadata) : null;
|
|
142
|
-
const stmt = this.db.prepare(`
|
|
143
|
-
INSERT INTO documents (id, project_id, file_path, content, embedding, metadata, created_at, updated_at)
|
|
144
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
145
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
146
|
-
project_id = excluded.project_id,
|
|
147
|
-
file_path = excluded.file_path,
|
|
148
|
-
content = excluded.content,
|
|
149
|
-
embedding = excluded.embedding,
|
|
150
|
-
metadata = excluded.metadata,
|
|
151
|
-
updated_at = excluded.updated_at
|
|
142
|
+
const stmt = this.db.prepare(`
|
|
143
|
+
INSERT INTO documents (id, project_id, file_path, content, embedding, metadata, created_at, updated_at)
|
|
144
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
145
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
146
|
+
project_id = excluded.project_id,
|
|
147
|
+
file_path = excluded.file_path,
|
|
148
|
+
content = excluded.content,
|
|
149
|
+
embedding = excluded.embedding,
|
|
150
|
+
metadata = excluded.metadata,
|
|
151
|
+
updated_at = excluded.updated_at
|
|
152
152
|
`);
|
|
153
153
|
stmt.run(doc.id, doc.projectId, doc.filePath, doc.content, embeddingBlob, metadata, now, now);
|
|
154
154
|
// Also index in MiniSearch for text search
|
|
@@ -163,16 +163,16 @@ class SQLiteVectorStore {
|
|
|
163
163
|
}
|
|
164
164
|
async upsertMany(docs) {
|
|
165
165
|
const now = new Date().toISOString();
|
|
166
|
-
const stmt = this.db.prepare(`
|
|
167
|
-
INSERT INTO documents (id, project_id, file_path, content, embedding, metadata, created_at, updated_at)
|
|
168
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
170
|
-
project_id = excluded.project_id,
|
|
171
|
-
file_path = excluded.file_path,
|
|
172
|
-
content = excluded.content,
|
|
173
|
-
embedding = excluded.embedding,
|
|
174
|
-
metadata = excluded.metadata,
|
|
175
|
-
updated_at = excluded.updated_at
|
|
166
|
+
const stmt = this.db.prepare(`
|
|
167
|
+
INSERT INTO documents (id, project_id, file_path, content, embedding, metadata, created_at, updated_at)
|
|
168
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
170
|
+
project_id = excluded.project_id,
|
|
171
|
+
file_path = excluded.file_path,
|
|
172
|
+
content = excluded.content,
|
|
173
|
+
embedding = excluded.embedding,
|
|
174
|
+
metadata = excluded.metadata,
|
|
175
|
+
updated_at = excluded.updated_at
|
|
176
176
|
`);
|
|
177
177
|
const insertMany = this.db.transaction((documents) => {
|
|
178
178
|
for (const doc of documents) {
|
|
@@ -194,10 +194,10 @@ class SQLiteVectorStore {
|
|
|
194
194
|
}
|
|
195
195
|
async searchByVector(embedding, projectId, limit = 10) {
|
|
196
196
|
// Get all documents for this project
|
|
197
|
-
const stmt = this.db.prepare(`
|
|
198
|
-
SELECT id, project_id, file_path, content, embedding, metadata, created_at, updated_at
|
|
199
|
-
FROM documents
|
|
200
|
-
WHERE project_id = ?
|
|
197
|
+
const stmt = this.db.prepare(`
|
|
198
|
+
SELECT id, project_id, file_path, content, embedding, metadata, created_at, updated_at
|
|
199
|
+
FROM documents
|
|
200
|
+
WHERE project_id = ?
|
|
201
201
|
`);
|
|
202
202
|
const rows = stmt.all(projectId);
|
|
203
203
|
// Compute similarities
|
|
@@ -231,10 +231,10 @@ class SQLiteVectorStore {
|
|
|
231
231
|
// Get full document data with embeddings from SQLite
|
|
232
232
|
const results = [];
|
|
233
233
|
for (const textResult of textResults) {
|
|
234
|
-
const row = this.db.prepare(`
|
|
235
|
-
SELECT id, project_id, file_path, content, embedding, metadata, created_at, updated_at
|
|
236
|
-
FROM documents
|
|
237
|
-
WHERE id = ?
|
|
234
|
+
const row = this.db.prepare(`
|
|
235
|
+
SELECT id, project_id, file_path, content, embedding, metadata, created_at, updated_at
|
|
236
|
+
FROM documents
|
|
237
|
+
WHERE id = ?
|
|
238
238
|
`).get(textResult.document.id);
|
|
239
239
|
if (row) {
|
|
240
240
|
results.push({
|
|
@@ -402,15 +402,15 @@ class SQLiteVectorStore {
|
|
|
402
402
|
return 0;
|
|
403
403
|
// Get IDs of documents to delete for MiniSearch removal
|
|
404
404
|
const placeholders = filePaths.map(() => '?').join(',');
|
|
405
|
-
const selectStmt = this.db.prepare(`
|
|
406
|
-
SELECT id FROM documents
|
|
407
|
-
WHERE project_id = ? AND file_path IN (${placeholders})
|
|
405
|
+
const selectStmt = this.db.prepare(`
|
|
406
|
+
SELECT id FROM documents
|
|
407
|
+
WHERE project_id = ? AND file_path IN (${placeholders})
|
|
408
408
|
`);
|
|
409
409
|
const docsToDelete = selectStmt.all(projectId, ...filePaths);
|
|
410
410
|
// Delete from SQLite
|
|
411
|
-
const deleteStmt = this.db.prepare(`
|
|
412
|
-
DELETE FROM documents
|
|
413
|
-
WHERE project_id = ? AND file_path IN (${placeholders})
|
|
411
|
+
const deleteStmt = this.db.prepare(`
|
|
412
|
+
DELETE FROM documents
|
|
413
|
+
WHERE project_id = ? AND file_path IN (${placeholders})
|
|
414
414
|
`);
|
|
415
415
|
const result = deleteStmt.run(projectId, ...filePaths);
|
|
416
416
|
// Also remove from MiniSearch
|
|
@@ -443,10 +443,10 @@ class SQLiteVectorStore {
|
|
|
443
443
|
// Returns hash + indexedAt for two-stage change detection:
|
|
444
444
|
// 1. Quick mtime check (~0.1ms) - if file mtime <= indexedAt, skip
|
|
445
445
|
// 2. Hash check only if mtime changed (~1-5ms)
|
|
446
|
-
const stmt = this.db.prepare(`
|
|
447
|
-
SELECT metadata FROM documents
|
|
448
|
-
WHERE project_id = ? AND file_path = ?
|
|
449
|
-
LIMIT 1
|
|
446
|
+
const stmt = this.db.prepare(`
|
|
447
|
+
SELECT metadata FROM documents
|
|
448
|
+
WHERE project_id = ? AND file_path = ?
|
|
449
|
+
LIMIT 1
|
|
450
450
|
`);
|
|
451
451
|
const result = stmt.get(projectId, filePath);
|
|
452
452
|
if (!result?.metadata) {
|
|
@@ -466,11 +466,11 @@ class SQLiteVectorStore {
|
|
|
466
466
|
async getFileHashes(projectId) {
|
|
467
467
|
// Get all unique file paths and their hashes for incremental indexing
|
|
468
468
|
// Uses DISTINCT ON equivalent via GROUP BY to get one row per file
|
|
469
|
-
const stmt = this.db.prepare(`
|
|
470
|
-
SELECT file_path, metadata
|
|
471
|
-
FROM documents
|
|
472
|
-
WHERE project_id = ?
|
|
473
|
-
GROUP BY file_path
|
|
469
|
+
const stmt = this.db.prepare(`
|
|
470
|
+
SELECT file_path, metadata
|
|
471
|
+
FROM documents
|
|
472
|
+
WHERE project_id = ?
|
|
473
|
+
GROUP BY file_path
|
|
474
474
|
`);
|
|
475
475
|
const results = stmt.all(projectId);
|
|
476
476
|
const hashes = new Map();
|
|
@@ -38,17 +38,17 @@ class Neo4jGraphStore {
|
|
|
38
38
|
const session = this.driver.session({ database: this.database });
|
|
39
39
|
try {
|
|
40
40
|
// Create indexes for fast lookups
|
|
41
|
-
await session.run(`
|
|
42
|
-
CREATE INDEX node_id IF NOT EXISTS FOR (n:CodeNode) ON (n.id)
|
|
41
|
+
await session.run(`
|
|
42
|
+
CREATE INDEX node_id IF NOT EXISTS FOR (n:CodeNode) ON (n.id)
|
|
43
43
|
`);
|
|
44
|
-
await session.run(`
|
|
45
|
-
CREATE INDEX node_project IF NOT EXISTS FOR (n:CodeNode) ON (n.projectId)
|
|
44
|
+
await session.run(`
|
|
45
|
+
CREATE INDEX node_project IF NOT EXISTS FOR (n:CodeNode) ON (n.projectId)
|
|
46
46
|
`);
|
|
47
|
-
await session.run(`
|
|
48
|
-
CREATE INDEX node_type IF NOT EXISTS FOR (n:CodeNode) ON (n.type)
|
|
47
|
+
await session.run(`
|
|
48
|
+
CREATE INDEX node_type IF NOT EXISTS FOR (n:CodeNode) ON (n.type)
|
|
49
49
|
`);
|
|
50
|
-
await session.run(`
|
|
51
|
-
CREATE INDEX node_filepath IF NOT EXISTS FOR (n:CodeNode) ON (n.filePath)
|
|
50
|
+
await session.run(`
|
|
51
|
+
CREATE INDEX node_filepath IF NOT EXISTS FOR (n:CodeNode) ON (n.filePath)
|
|
52
52
|
`);
|
|
53
53
|
this.initialized = true;
|
|
54
54
|
}
|
|
@@ -60,13 +60,13 @@ class Neo4jGraphStore {
|
|
|
60
60
|
await this.initialize();
|
|
61
61
|
const session = this.driver.session({ database: this.database });
|
|
62
62
|
try {
|
|
63
|
-
await session.run(`
|
|
64
|
-
MERGE (n:CodeNode {id: $id})
|
|
65
|
-
SET n.type = $type,
|
|
66
|
-
n.name = $name,
|
|
67
|
-
n.filePath = $filePath,
|
|
68
|
-
n.projectId = $projectId,
|
|
69
|
-
n.properties = $properties
|
|
63
|
+
await session.run(`
|
|
64
|
+
MERGE (n:CodeNode {id: $id})
|
|
65
|
+
SET n.type = $type,
|
|
66
|
+
n.name = $name,
|
|
67
|
+
n.filePath = $filePath,
|
|
68
|
+
n.projectId = $projectId,
|
|
69
|
+
n.properties = $properties
|
|
70
70
|
`, {
|
|
71
71
|
id: node.id,
|
|
72
72
|
type: node.type,
|
|
@@ -86,11 +86,11 @@ class Neo4jGraphStore {
|
|
|
86
86
|
try {
|
|
87
87
|
// Use dynamic relationship type
|
|
88
88
|
const relType = edge.type.toUpperCase();
|
|
89
|
-
await session.run(`
|
|
90
|
-
MATCH (source:CodeNode {id: $sourceId})
|
|
91
|
-
MATCH (target:CodeNode {id: $targetId})
|
|
92
|
-
MERGE (source)-[r:${relType} {id: $edgeId}]->(target)
|
|
93
|
-
SET r.properties = $properties
|
|
89
|
+
await session.run(`
|
|
90
|
+
MATCH (source:CodeNode {id: $sourceId})
|
|
91
|
+
MATCH (target:CodeNode {id: $targetId})
|
|
92
|
+
MERGE (source)-[r:${relType} {id: $edgeId}]->(target)
|
|
93
|
+
SET r.properties = $properties
|
|
94
94
|
`, {
|
|
95
95
|
sourceId: edge.source,
|
|
96
96
|
targetId: edge.target,
|
|
@@ -108,13 +108,13 @@ class Neo4jGraphStore {
|
|
|
108
108
|
try {
|
|
109
109
|
await session.executeWrite(async (tx) => {
|
|
110
110
|
for (const node of nodes) {
|
|
111
|
-
await tx.run(`
|
|
112
|
-
MERGE (n:CodeNode {id: $id})
|
|
113
|
-
SET n.type = $type,
|
|
114
|
-
n.name = $name,
|
|
115
|
-
n.filePath = $filePath,
|
|
116
|
-
n.projectId = $projectId,
|
|
117
|
-
n.properties = $properties
|
|
111
|
+
await tx.run(`
|
|
112
|
+
MERGE (n:CodeNode {id: $id})
|
|
113
|
+
SET n.type = $type,
|
|
114
|
+
n.name = $name,
|
|
115
|
+
n.filePath = $filePath,
|
|
116
|
+
n.projectId = $projectId,
|
|
117
|
+
n.properties = $properties
|
|
118
118
|
`, {
|
|
119
119
|
id: node.id,
|
|
120
120
|
type: node.type,
|
|
@@ -137,11 +137,11 @@ class Neo4jGraphStore {
|
|
|
137
137
|
await session.executeWrite(async (tx) => {
|
|
138
138
|
for (const edge of edges) {
|
|
139
139
|
const relType = edge.type.toUpperCase();
|
|
140
|
-
await tx.run(`
|
|
141
|
-
MATCH (source:CodeNode {id: $sourceId})
|
|
142
|
-
MATCH (target:CodeNode {id: $targetId})
|
|
143
|
-
MERGE (source)-[r:${relType} {id: $edgeId}]->(target)
|
|
144
|
-
SET r.properties = $properties
|
|
140
|
+
await tx.run(`
|
|
141
|
+
MATCH (source:CodeNode {id: $sourceId})
|
|
142
|
+
MATCH (target:CodeNode {id: $targetId})
|
|
143
|
+
MERGE (source)-[r:${relType} {id: $edgeId}]->(target)
|
|
144
|
+
SET r.properties = $properties
|
|
145
145
|
`, {
|
|
146
146
|
sourceId: edge.source,
|
|
147
147
|
targetId: edge.target,
|
|
@@ -162,18 +162,18 @@ class Neo4jGraphStore {
|
|
|
162
162
|
let query;
|
|
163
163
|
let params;
|
|
164
164
|
if (type) {
|
|
165
|
-
query = `
|
|
166
|
-
MATCH (n:CodeNode)
|
|
167
|
-
WHERE n.projectId = $projectId AND n.type = $type
|
|
168
|
-
RETURN n
|
|
165
|
+
query = `
|
|
166
|
+
MATCH (n:CodeNode)
|
|
167
|
+
WHERE n.projectId = $projectId AND n.type = $type
|
|
168
|
+
RETURN n
|
|
169
169
|
`;
|
|
170
170
|
params = { projectId, type };
|
|
171
171
|
}
|
|
172
172
|
else {
|
|
173
|
-
query = `
|
|
174
|
-
MATCH (n:CodeNode)
|
|
175
|
-
WHERE n.projectId = $projectId
|
|
176
|
-
RETURN n
|
|
173
|
+
query = `
|
|
174
|
+
MATCH (n:CodeNode)
|
|
175
|
+
WHERE n.projectId = $projectId
|
|
176
|
+
RETURN n
|
|
177
177
|
`;
|
|
178
178
|
params = { projectId };
|
|
179
179
|
}
|
|
@@ -188,9 +188,9 @@ class Neo4jGraphStore {
|
|
|
188
188
|
await this.initialize();
|
|
189
189
|
const session = this.driver.session({ database: this.database });
|
|
190
190
|
try {
|
|
191
|
-
const result = await session.run(`
|
|
192
|
-
MATCH (n:CodeNode {id: $id})
|
|
193
|
-
RETURN n
|
|
191
|
+
const result = await session.run(`
|
|
192
|
+
MATCH (n:CodeNode {id: $id})
|
|
193
|
+
RETURN n
|
|
194
194
|
`, { id });
|
|
195
195
|
if (result.records.length === 0)
|
|
196
196
|
return null;
|
|
@@ -206,25 +206,25 @@ class Neo4jGraphStore {
|
|
|
206
206
|
try {
|
|
207
207
|
let query;
|
|
208
208
|
if (direction === 'out') {
|
|
209
|
-
query = `
|
|
210
|
-
MATCH (n:CodeNode {id: $nodeId})-[r]->(target)
|
|
211
|
-
RETURN r, n.id as sourceId, target.id as targetId, type(r) as relType
|
|
209
|
+
query = `
|
|
210
|
+
MATCH (n:CodeNode {id: $nodeId})-[r]->(target)
|
|
211
|
+
RETURN r, n.id as sourceId, target.id as targetId, type(r) as relType
|
|
212
212
|
`;
|
|
213
213
|
}
|
|
214
214
|
else if (direction === 'in') {
|
|
215
|
-
query = `
|
|
216
|
-
MATCH (source)-[r]->(n:CodeNode {id: $nodeId})
|
|
217
|
-
RETURN r, source.id as sourceId, n.id as targetId, type(r) as relType
|
|
215
|
+
query = `
|
|
216
|
+
MATCH (source)-[r]->(n:CodeNode {id: $nodeId})
|
|
217
|
+
RETURN r, source.id as sourceId, n.id as targetId, type(r) as relType
|
|
218
218
|
`;
|
|
219
219
|
}
|
|
220
220
|
else {
|
|
221
|
-
query = `
|
|
222
|
-
MATCH (n:CodeNode {id: $nodeId})-[r]-(other)
|
|
223
|
-
WITH r,
|
|
224
|
-
CASE WHEN startNode(r).id = $nodeId THEN startNode(r).id ELSE endNode(r).id END as sourceId,
|
|
225
|
-
CASE WHEN startNode(r).id = $nodeId THEN endNode(r).id ELSE startNode(r).id END as targetId,
|
|
226
|
-
type(r) as relType
|
|
227
|
-
RETURN DISTINCT r, sourceId, targetId, relType
|
|
221
|
+
query = `
|
|
222
|
+
MATCH (n:CodeNode {id: $nodeId})-[r]-(other)
|
|
223
|
+
WITH r,
|
|
224
|
+
CASE WHEN startNode(r).id = $nodeId THEN startNode(r).id ELSE endNode(r).id END as sourceId,
|
|
225
|
+
CASE WHEN startNode(r).id = $nodeId THEN endNode(r).id ELSE startNode(r).id END as targetId,
|
|
226
|
+
type(r) as relType
|
|
227
|
+
RETURN DISTINCT r, sourceId, targetId, relType
|
|
228
228
|
`;
|
|
229
229
|
}
|
|
230
230
|
const result = await session.run(query, { nodeId });
|
|
@@ -242,15 +242,15 @@ class Neo4jGraphStore {
|
|
|
242
242
|
const params = { nodeId };
|
|
243
243
|
if (edgeType) {
|
|
244
244
|
const relType = edgeType.toUpperCase();
|
|
245
|
-
query = `
|
|
246
|
-
MATCH (n:CodeNode {id: $nodeId})-[:${relType}]-(neighbor)
|
|
247
|
-
RETURN DISTINCT neighbor
|
|
245
|
+
query = `
|
|
246
|
+
MATCH (n:CodeNode {id: $nodeId})-[:${relType}]-(neighbor)
|
|
247
|
+
RETURN DISTINCT neighbor
|
|
248
248
|
`;
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
|
-
query = `
|
|
252
|
-
MATCH (n:CodeNode {id: $nodeId})--(neighbor)
|
|
253
|
-
RETURN DISTINCT neighbor
|
|
251
|
+
query = `
|
|
252
|
+
MATCH (n:CodeNode {id: $nodeId})--(neighbor)
|
|
253
|
+
RETURN DISTINCT neighbor
|
|
254
254
|
`;
|
|
255
255
|
}
|
|
256
256
|
const result = await session.run(query, params);
|
|
@@ -264,11 +264,11 @@ class Neo4jGraphStore {
|
|
|
264
264
|
await this.initialize();
|
|
265
265
|
const session = this.driver.session({ database: this.database });
|
|
266
266
|
try {
|
|
267
|
-
const result = await session.run(`
|
|
268
|
-
MATCH path = shortestPath(
|
|
269
|
-
(source:CodeNode {id: $sourceId})-[*1..${maxDepth}]-(target:CodeNode {id: $targetId})
|
|
270
|
-
)
|
|
271
|
-
RETURN nodes(path) as pathNodes
|
|
267
|
+
const result = await session.run(`
|
|
268
|
+
MATCH path = shortestPath(
|
|
269
|
+
(source:CodeNode {id: $sourceId})-[*1..${maxDepth}]-(target:CodeNode {id: $targetId})
|
|
270
|
+
)
|
|
271
|
+
RETURN nodes(path) as pathNodes
|
|
272
272
|
`, { sourceId, targetId });
|
|
273
273
|
if (result.records.length === 0)
|
|
274
274
|
return [];
|
|
@@ -283,10 +283,10 @@ class Neo4jGraphStore {
|
|
|
283
283
|
await this.initialize();
|
|
284
284
|
const session = this.driver.session({ database: this.database });
|
|
285
285
|
try {
|
|
286
|
-
const result = await session.run(`
|
|
287
|
-
MATCH (n:CodeNode {projectId: $projectId})
|
|
288
|
-
DETACH DELETE n
|
|
289
|
-
RETURN count(n) as deleted
|
|
286
|
+
const result = await session.run(`
|
|
287
|
+
MATCH (n:CodeNode {projectId: $projectId})
|
|
288
|
+
DETACH DELETE n
|
|
289
|
+
RETURN count(n) as deleted
|
|
290
290
|
`, { projectId });
|
|
291
291
|
return result.records[0]?.get('deleted')?.toNumber() || 0;
|
|
292
292
|
}
|
|
@@ -302,13 +302,13 @@ class Neo4jGraphStore {
|
|
|
302
302
|
try {
|
|
303
303
|
// Delete nodes that match any of the file paths
|
|
304
304
|
// Handles both filePath property and relativePath property
|
|
305
|
-
const result = await session.run(`
|
|
306
|
-
MATCH (n:CodeNode {projectId: $projectId})
|
|
307
|
-
WHERE n.filePath IN $filePaths
|
|
308
|
-
OR n.relativePath IN $filePaths
|
|
309
|
-
OR ANY(fp IN $filePaths WHERE n.id CONTAINS replace(replace(fp, '/', '-'), '\\\\', '-'))
|
|
310
|
-
DETACH DELETE n
|
|
311
|
-
RETURN count(n) as deleted
|
|
305
|
+
const result = await session.run(`
|
|
306
|
+
MATCH (n:CodeNode {projectId: $projectId})
|
|
307
|
+
WHERE n.filePath IN $filePaths
|
|
308
|
+
OR n.relativePath IN $filePaths
|
|
309
|
+
OR ANY(fp IN $filePaths WHERE n.id CONTAINS replace(replace(fp, '/', '-'), '\\\\', '-'))
|
|
310
|
+
DETACH DELETE n
|
|
311
|
+
RETURN count(n) as deleted
|
|
312
312
|
`, { projectId, filePaths });
|
|
313
313
|
return result.records[0]?.get('deleted')?.toNumber() || 0;
|
|
314
314
|
}
|
|
@@ -320,9 +320,9 @@ class Neo4jGraphStore {
|
|
|
320
320
|
await this.initialize();
|
|
321
321
|
const session = this.driver.session({ database: this.database });
|
|
322
322
|
try {
|
|
323
|
-
const result = await session.run(`
|
|
324
|
-
MATCH (n:CodeNode {projectId: $projectId})
|
|
325
|
-
RETURN count(n) as count
|
|
323
|
+
const result = await session.run(`
|
|
324
|
+
MATCH (n:CodeNode {projectId: $projectId})
|
|
325
|
+
RETURN count(n) as count
|
|
326
326
|
`, { projectId });
|
|
327
327
|
return result.records[0]?.get('count')?.toNumber() || 0;
|
|
328
328
|
}
|