sdl-mcp 0.9.2 → 0.9.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/LICENSE +90 -90
- package/README.md +588 -588
- package/config/benchmark.config.example.json +81 -81
- package/config/benchmark.config.json +86 -86
- package/dist/db/ladybug-clusters.js +50 -50
- package/dist/db/ladybug-edges.js +140 -140
- package/dist/db/ladybug-feedback.js +66 -66
- package/dist/db/ladybug-memory.js +56 -56
- package/dist/db/ladybug-metrics.js +61 -61
- package/dist/db/ladybug-processes.js +55 -55
- package/dist/db/ladybug-repos.js +94 -94
- package/dist/db/ladybug-schema.js +236 -236
- package/dist/db/ladybug-symbols.js +280 -280
- package/dist/indexer/adapter/c.js +8 -8
- package/dist/indexer/adapter/kotlin.js +24 -24
- package/dist/indexer/adapter/php.js +41 -41
- package/dist/indexer/adapter/python.js +34 -34
- package/dist/indexer/adapter/rust.js +18 -18
- package/dist/indexer/treesitter/extractCalls.js +69 -69
- package/dist/indexer/treesitter/extractImports.js +6 -6
- package/dist/server.js +2 -2
- package/package.json +153 -153
|
@@ -19,39 +19,39 @@ function toMemoryRow(row) {
|
|
|
19
19
|
deleted: toBoolean(row.deleted),
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
const MEMORY_RETURN_FIELDS = `
|
|
23
|
-
m.memoryId AS memoryId,
|
|
24
|
-
m.repoId AS repoId,
|
|
25
|
-
m.type AS type,
|
|
26
|
-
m.title AS title,
|
|
27
|
-
m.content AS content,
|
|
28
|
-
m.contentHash AS contentHash,
|
|
29
|
-
m.searchText AS searchText,
|
|
30
|
-
m.tagsJson AS tagsJson,
|
|
31
|
-
m.confidence AS confidence,
|
|
32
|
-
m.createdAt AS createdAt,
|
|
33
|
-
m.updatedAt AS updatedAt,
|
|
34
|
-
m.createdByVersion AS createdByVersion,
|
|
35
|
-
m.stale AS stale,
|
|
36
|
-
m.staleVersion AS staleVersion,
|
|
37
|
-
m.sourceFile AS sourceFile,
|
|
22
|
+
const MEMORY_RETURN_FIELDS = `
|
|
23
|
+
m.memoryId AS memoryId,
|
|
24
|
+
m.repoId AS repoId,
|
|
25
|
+
m.type AS type,
|
|
26
|
+
m.title AS title,
|
|
27
|
+
m.content AS content,
|
|
28
|
+
m.contentHash AS contentHash,
|
|
29
|
+
m.searchText AS searchText,
|
|
30
|
+
m.tagsJson AS tagsJson,
|
|
31
|
+
m.confidence AS confidence,
|
|
32
|
+
m.createdAt AS createdAt,
|
|
33
|
+
m.updatedAt AS updatedAt,
|
|
34
|
+
m.createdByVersion AS createdByVersion,
|
|
35
|
+
m.stale AS stale,
|
|
36
|
+
m.staleVersion AS staleVersion,
|
|
37
|
+
m.sourceFile AS sourceFile,
|
|
38
38
|
m.deleted AS deleted`;
|
|
39
39
|
export async function upsertMemory(conn, row) {
|
|
40
|
-
await exec(conn, `MERGE (m:Memory {memoryId: $memoryId})
|
|
41
|
-
SET m.repoId = $repoId,
|
|
42
|
-
m.type = $type,
|
|
43
|
-
m.title = $title,
|
|
44
|
-
m.content = $content,
|
|
45
|
-
m.contentHash = $contentHash,
|
|
46
|
-
m.searchText = $searchText,
|
|
47
|
-
m.tagsJson = $tagsJson,
|
|
48
|
-
m.confidence = $confidence,
|
|
49
|
-
m.createdAt = $createdAt,
|
|
50
|
-
m.updatedAt = $updatedAt,
|
|
51
|
-
m.createdByVersion = $createdByVersion,
|
|
52
|
-
m.stale = $stale,
|
|
53
|
-
m.staleVersion = $staleVersion,
|
|
54
|
-
m.sourceFile = $sourceFile,
|
|
40
|
+
await exec(conn, `MERGE (m:Memory {memoryId: $memoryId})
|
|
41
|
+
SET m.repoId = $repoId,
|
|
42
|
+
m.type = $type,
|
|
43
|
+
m.title = $title,
|
|
44
|
+
m.content = $content,
|
|
45
|
+
m.contentHash = $contentHash,
|
|
46
|
+
m.searchText = $searchText,
|
|
47
|
+
m.tagsJson = $tagsJson,
|
|
48
|
+
m.confidence = $confidence,
|
|
49
|
+
m.createdAt = $createdAt,
|
|
50
|
+
m.updatedAt = $updatedAt,
|
|
51
|
+
m.createdByVersion = $createdByVersion,
|
|
52
|
+
m.stale = $stale,
|
|
53
|
+
m.staleVersion = $staleVersion,
|
|
54
|
+
m.sourceFile = $sourceFile,
|
|
55
55
|
m.deleted = $deleted`, {
|
|
56
56
|
memoryId: row.memoryId,
|
|
57
57
|
repoId: row.repoId,
|
|
@@ -72,13 +72,13 @@ export async function upsertMemory(conn, row) {
|
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
export async function getMemory(conn, memoryId) {
|
|
75
|
-
const row = await querySingle(conn, `MATCH (m:Memory {memoryId: $memoryId})
|
|
75
|
+
const row = await querySingle(conn, `MATCH (m:Memory {memoryId: $memoryId})
|
|
76
76
|
RETURN ${MEMORY_RETURN_FIELDS}`, { memoryId });
|
|
77
77
|
return row ? toMemoryRow(row) : null;
|
|
78
78
|
}
|
|
79
79
|
export async function getMemoryByContentHash(conn, contentHash) {
|
|
80
|
-
const row = await querySingle(conn, `MATCH (m:Memory {contentHash: $contentHash})
|
|
81
|
-
WHERE m.deleted = false
|
|
80
|
+
const row = await querySingle(conn, `MATCH (m:Memory {contentHash: $contentHash})
|
|
81
|
+
WHERE m.deleted = false
|
|
82
82
|
RETURN ${MEMORY_RETURN_FIELDS}`, { contentHash });
|
|
83
83
|
return row ? toMemoryRow(row) : null;
|
|
84
84
|
}
|
|
@@ -88,9 +88,9 @@ export async function queryMemories(conn, options) {
|
|
|
88
88
|
const useSymbolFilter = options.symbolIds && options.symbolIds.length > 0;
|
|
89
89
|
// When filtering by symbolIds, traverse MEMORY_OF edges
|
|
90
90
|
const matchClause = useSymbolFilter
|
|
91
|
-
? `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
91
|
+
? `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
92
92
|
WHERE s.symbolId IN $symbolIds AND m.repoId = $repoId AND m.deleted = false`
|
|
93
|
-
: `MATCH (m:Memory)
|
|
93
|
+
: `MATCH (m:Memory)
|
|
94
94
|
WHERE m.repoId = $repoId AND m.deleted = false`;
|
|
95
95
|
const conditions = [];
|
|
96
96
|
const params = { repoId: options.repoId };
|
|
@@ -115,9 +115,9 @@ export async function queryMemories(conn, options) {
|
|
|
115
115
|
const orderBy = options.sortBy === "confidence"
|
|
116
116
|
? "ORDER BY confidence DESC"
|
|
117
117
|
: "ORDER BY updatedAt DESC";
|
|
118
|
-
const cypher = `${matchClause}
|
|
119
|
-
${whereClause}
|
|
120
|
-
RETURN DISTINCT ${MEMORY_RETURN_FIELDS}
|
|
118
|
+
const cypher = `${matchClause}
|
|
119
|
+
${whereClause}
|
|
120
|
+
RETURN DISTINCT ${MEMORY_RETURN_FIELDS}
|
|
121
121
|
${orderBy}`;
|
|
122
122
|
let rows = await queryAll(conn, cypher, params);
|
|
123
123
|
// Post-filter tags at application level for exact array-element matching
|
|
@@ -135,38 +135,38 @@ export async function queryMemories(conn, options) {
|
|
|
135
135
|
return rows.slice(0, safeLimit).map(toMemoryRow);
|
|
136
136
|
}
|
|
137
137
|
export async function softDeleteMemory(conn, memoryId) {
|
|
138
|
-
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})
|
|
138
|
+
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})
|
|
139
139
|
SET m.deleted = true`, { memoryId });
|
|
140
140
|
}
|
|
141
141
|
export async function createHasMemoryEdge(conn, repoId, memoryId) {
|
|
142
|
-
await exec(conn, `MATCH (r:Repo {repoId: $repoId}), (m:Memory {memoryId: $memoryId})
|
|
142
|
+
await exec(conn, `MATCH (r:Repo {repoId: $repoId}), (m:Memory {memoryId: $memoryId})
|
|
143
143
|
MERGE (r)-[:HAS_MEMORY]->(m)`, { repoId, memoryId });
|
|
144
144
|
}
|
|
145
145
|
export async function createMemoryOfEdge(conn, memoryId, symbolId) {
|
|
146
|
-
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId}), (s:Symbol {symbolId: $symbolId})
|
|
146
|
+
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId}), (s:Symbol {symbolId: $symbolId})
|
|
147
147
|
MERGE (m)-[:MEMORY_OF]->(s)`, { memoryId, symbolId });
|
|
148
148
|
}
|
|
149
149
|
export async function createMemoryOfFileEdge(conn, memoryId, fileId) {
|
|
150
|
-
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId}), (f:File {fileId: $fileId})
|
|
150
|
+
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId}), (f:File {fileId: $fileId})
|
|
151
151
|
MERGE (m)-[:MEMORY_OF_FILE]->(f)`, { memoryId, fileId });
|
|
152
152
|
}
|
|
153
153
|
export async function deleteMemoryEdges(conn, memoryId) {
|
|
154
154
|
// Delete HAS_MEMORY edges (Repo->Memory)
|
|
155
|
-
await exec(conn, `MATCH (r:Repo)-[e:HAS_MEMORY]->(m:Memory {memoryId: $memoryId})
|
|
155
|
+
await exec(conn, `MATCH (r:Repo)-[e:HAS_MEMORY]->(m:Memory {memoryId: $memoryId})
|
|
156
156
|
DELETE e`, { memoryId });
|
|
157
157
|
// Delete MEMORY_OF edges (Memory->Symbol)
|
|
158
|
-
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})-[e:MEMORY_OF]->(s:Symbol)
|
|
158
|
+
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})-[e:MEMORY_OF]->(s:Symbol)
|
|
159
159
|
DELETE e`, { memoryId });
|
|
160
160
|
// Delete MEMORY_OF_FILE edges (Memory->File)
|
|
161
|
-
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})-[e:MEMORY_OF_FILE]->(f:File)
|
|
161
|
+
await exec(conn, `MATCH (m:Memory {memoryId: $memoryId})-[e:MEMORY_OF_FILE]->(f:File)
|
|
162
162
|
DELETE e`, { memoryId });
|
|
163
163
|
}
|
|
164
164
|
export async function getMemoriesForSymbols(conn, symbolIds, limit) {
|
|
165
165
|
assertSafeInt(limit, "limit");
|
|
166
|
-
const rows = await queryAll(conn, `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
167
|
-
WHERE s.symbolId IN $symbolIds AND m.deleted = false
|
|
168
|
-
RETURN ${MEMORY_RETURN_FIELDS},
|
|
169
|
-
s.symbolId AS linkedSymbolId
|
|
166
|
+
const rows = await queryAll(conn, `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
167
|
+
WHERE s.symbolId IN $symbolIds AND m.deleted = false
|
|
168
|
+
RETURN ${MEMORY_RETURN_FIELDS},
|
|
169
|
+
s.symbolId AS linkedSymbolId
|
|
170
170
|
ORDER BY confidence DESC`, { symbolIds });
|
|
171
171
|
return rows.slice(0, limit).map((row) => ({
|
|
172
172
|
...toMemoryRow(row),
|
|
@@ -175,18 +175,18 @@ export async function getMemoriesForSymbols(conn, symbolIds, limit) {
|
|
|
175
175
|
}
|
|
176
176
|
export async function getRepoMemories(conn, repoId, limit) {
|
|
177
177
|
assertSafeInt(limit, "limit");
|
|
178
|
-
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})-[:HAS_MEMORY]->(m:Memory)
|
|
179
|
-
WHERE m.deleted = false
|
|
180
|
-
RETURN ${MEMORY_RETURN_FIELDS}
|
|
178
|
+
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})-[:HAS_MEMORY]->(m:Memory)
|
|
179
|
+
WHERE m.deleted = false
|
|
180
|
+
RETURN ${MEMORY_RETURN_FIELDS}
|
|
181
181
|
ORDER BY updatedAt DESC`, { repoId });
|
|
182
182
|
return rows.slice(0, limit).map(toMemoryRow);
|
|
183
183
|
}
|
|
184
184
|
export async function flagMemoriesStale(conn, symbolIds, staleVersion) {
|
|
185
185
|
if (symbolIds.length === 0)
|
|
186
186
|
return 0;
|
|
187
|
-
const rows = await queryAll(conn, `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
188
|
-
WHERE s.symbolId IN $symbolIds AND m.deleted = false AND m.stale = false
|
|
189
|
-
SET m.stale = true, m.staleVersion = $staleVersion
|
|
187
|
+
const rows = await queryAll(conn, `MATCH (m:Memory)-[:MEMORY_OF]->(s:Symbol)
|
|
188
|
+
WHERE s.symbolId IN $symbolIds AND m.deleted = false AND m.stale = false
|
|
189
|
+
SET m.stale = true, m.staleVersion = $staleVersion
|
|
190
190
|
RETURN DISTINCT m.memoryId AS memoryId`, { symbolIds, staleVersion });
|
|
191
191
|
return rows.length;
|
|
192
192
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { exec, queryAll, querySingle, toNumber, assertSafeInt, withTransaction, } from "./ladybug-core.js";
|
|
2
2
|
export async function upsertMetrics(conn, metrics) {
|
|
3
|
-
await exec(conn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
4
|
-
SET m.fanIn = $fanIn,
|
|
5
|
-
m.fanOut = $fanOut,
|
|
6
|
-
m.churn30d = $churn30d,
|
|
7
|
-
m.testRefsJson = $testRefsJson,
|
|
8
|
-
m.canonicalTestJson = $canonicalTestJson,
|
|
3
|
+
await exec(conn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
4
|
+
SET m.fanIn = $fanIn,
|
|
5
|
+
m.fanOut = $fanOut,
|
|
6
|
+
m.churn30d = $churn30d,
|
|
7
|
+
m.testRefsJson = $testRefsJson,
|
|
8
|
+
m.canonicalTestJson = $canonicalTestJson,
|
|
9
9
|
m.updatedAt = $updatedAt`, {
|
|
10
10
|
symbolId: metrics.symbolId,
|
|
11
11
|
fanIn: metrics.fanIn,
|
|
@@ -25,12 +25,12 @@ export async function upsertMetricsBatch(conn, rows) {
|
|
|
25
25
|
return;
|
|
26
26
|
await withTransaction(conn, async (txConn) => {
|
|
27
27
|
for (const metrics of rows) {
|
|
28
|
-
await exec(txConn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
29
|
-
SET m.fanIn = $fanIn,
|
|
30
|
-
m.fanOut = $fanOut,
|
|
31
|
-
m.churn30d = $churn30d,
|
|
32
|
-
m.testRefsJson = $testRefsJson,
|
|
33
|
-
m.canonicalTestJson = $canonicalTestJson,
|
|
28
|
+
await exec(txConn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
29
|
+
SET m.fanIn = $fanIn,
|
|
30
|
+
m.fanOut = $fanOut,
|
|
31
|
+
m.churn30d = $churn30d,
|
|
32
|
+
m.testRefsJson = $testRefsJson,
|
|
33
|
+
m.canonicalTestJson = $canonicalTestJson,
|
|
34
34
|
m.updatedAt = $updatedAt`, {
|
|
35
35
|
symbolId: metrics.symbolId,
|
|
36
36
|
fanIn: metrics.fanIn,
|
|
@@ -53,8 +53,8 @@ export async function upsertCanonicalTestBatch(conn, rows) {
|
|
|
53
53
|
return;
|
|
54
54
|
await withTransaction(conn, async (txConn) => {
|
|
55
55
|
for (const row of rows) {
|
|
56
|
-
await exec(txConn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
57
|
-
SET m.canonicalTestJson = $canonicalTestJson,
|
|
56
|
+
await exec(txConn, `MERGE (m:Metrics {symbolId: $symbolId})
|
|
57
|
+
SET m.canonicalTestJson = $canonicalTestJson,
|
|
58
58
|
m.updatedAt = $updatedAt`, {
|
|
59
59
|
symbolId: row.symbolId,
|
|
60
60
|
canonicalTestJson: row.canonicalTestJson,
|
|
@@ -64,13 +64,13 @@ export async function upsertCanonicalTestBatch(conn, rows) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
export async function getMetrics(conn, symbolId) {
|
|
67
|
-
const row = await querySingle(conn, `MATCH (m:Metrics {symbolId: $symbolId})
|
|
68
|
-
RETURN m.symbolId AS symbolId,
|
|
69
|
-
m.fanIn AS fanIn,
|
|
70
|
-
m.fanOut AS fanOut,
|
|
71
|
-
m.churn30d AS churn30d,
|
|
72
|
-
m.testRefsJson AS testRefsJson,
|
|
73
|
-
m.canonicalTestJson AS canonicalTestJson,
|
|
67
|
+
const row = await querySingle(conn, `MATCH (m:Metrics {symbolId: $symbolId})
|
|
68
|
+
RETURN m.symbolId AS symbolId,
|
|
69
|
+
m.fanIn AS fanIn,
|
|
70
|
+
m.fanOut AS fanOut,
|
|
71
|
+
m.churn30d AS churn30d,
|
|
72
|
+
m.testRefsJson AS testRefsJson,
|
|
73
|
+
m.canonicalTestJson AS canonicalTestJson,
|
|
74
74
|
m.updatedAt AS updatedAt`, { symbolId });
|
|
75
75
|
if (!row)
|
|
76
76
|
return null;
|
|
@@ -87,14 +87,14 @@ export async function getMetrics(conn, symbolId) {
|
|
|
87
87
|
export async function getMetricsBySymbolIds(conn, symbolIds) {
|
|
88
88
|
if (symbolIds.length === 0)
|
|
89
89
|
return new Map();
|
|
90
|
-
const rows = await queryAll(conn, `MATCH (m:Metrics)
|
|
91
|
-
WHERE m.symbolId IN $symbolIds
|
|
92
|
-
RETURN m.symbolId AS symbolId,
|
|
93
|
-
m.fanIn AS fanIn,
|
|
94
|
-
m.fanOut AS fanOut,
|
|
95
|
-
m.churn30d AS churn30d,
|
|
96
|
-
m.testRefsJson AS testRefsJson,
|
|
97
|
-
m.canonicalTestJson AS canonicalTestJson,
|
|
90
|
+
const rows = await queryAll(conn, `MATCH (m:Metrics)
|
|
91
|
+
WHERE m.symbolId IN $symbolIds
|
|
92
|
+
RETURN m.symbolId AS symbolId,
|
|
93
|
+
m.fanIn AS fanIn,
|
|
94
|
+
m.fanOut AS fanOut,
|
|
95
|
+
m.churn30d AS churn30d,
|
|
96
|
+
m.testRefsJson AS testRefsJson,
|
|
97
|
+
m.canonicalTestJson AS canonicalTestJson,
|
|
98
98
|
m.updatedAt AS updatedAt`, { symbolIds });
|
|
99
99
|
const result = new Map();
|
|
100
100
|
for (const row of rows) {
|
|
@@ -114,13 +114,13 @@ export async function getTopSymbolsByFanIn(conn, repoId, limit = 10) {
|
|
|
114
114
|
assertSafeInt(limit, "limit");
|
|
115
115
|
limit = Math.max(0, Math.min(limit, 1000));
|
|
116
116
|
const maxFetch = Math.min(limit, 10000);
|
|
117
|
-
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
118
|
-
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
119
|
-
RETURN s.symbolId AS symbolId,
|
|
120
|
-
m.fanIn AS fanIn,
|
|
121
|
-
m.fanOut AS fanOut,
|
|
122
|
-
m.churn30d AS churn30d
|
|
123
|
-
ORDER BY m.fanIn DESC
|
|
117
|
+
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
118
|
+
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
119
|
+
RETURN s.symbolId AS symbolId,
|
|
120
|
+
m.fanIn AS fanIn,
|
|
121
|
+
m.fanOut AS fanOut,
|
|
122
|
+
m.churn30d AS churn30d
|
|
123
|
+
ORDER BY m.fanIn DESC
|
|
124
124
|
LIMIT $limit`, { repoId, limit: maxFetch });
|
|
125
125
|
return rows.map((row) => ({
|
|
126
126
|
symbolId: row.symbolId,
|
|
@@ -130,14 +130,14 @@ export async function getTopSymbolsByFanIn(conn, repoId, limit = 10) {
|
|
|
130
130
|
}));
|
|
131
131
|
}
|
|
132
132
|
export async function getMetricsByRepo(conn, repoId) {
|
|
133
|
-
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
134
|
-
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
135
|
-
RETURN m.symbolId AS symbolId,
|
|
136
|
-
m.fanIn AS fanIn,
|
|
137
|
-
m.fanOut AS fanOut,
|
|
138
|
-
m.churn30d AS churn30d,
|
|
139
|
-
m.testRefsJson AS testRefsJson,
|
|
140
|
-
m.canonicalTestJson AS canonicalTestJson,
|
|
133
|
+
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
134
|
+
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
135
|
+
RETURN m.symbolId AS symbolId,
|
|
136
|
+
m.fanIn AS fanIn,
|
|
137
|
+
m.fanOut AS fanOut,
|
|
138
|
+
m.churn30d AS churn30d,
|
|
139
|
+
m.testRefsJson AS testRefsJson,
|
|
140
|
+
m.canonicalTestJson AS canonicalTestJson,
|
|
141
141
|
m.updatedAt AS updatedAt`, { repoId });
|
|
142
142
|
const result = new Map();
|
|
143
143
|
for (const row of rows) {
|
|
@@ -157,13 +157,13 @@ export async function getTopSymbolsByChurn(conn, repoId, limit = 10) {
|
|
|
157
157
|
assertSafeInt(limit, "limit");
|
|
158
158
|
limit = Math.max(0, Math.min(limit, 1000));
|
|
159
159
|
const maxFetch = Math.min(limit, 10000);
|
|
160
|
-
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
161
|
-
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
162
|
-
RETURN s.symbolId AS symbolId,
|
|
163
|
-
m.fanIn AS fanIn,
|
|
164
|
-
m.fanOut AS fanOut,
|
|
165
|
-
m.churn30d AS churn30d
|
|
166
|
-
ORDER BY m.churn30d DESC
|
|
160
|
+
const rows = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:SYMBOL_IN_REPO]-(s:Symbol)
|
|
161
|
+
MATCH (m:Metrics {symbolId: s.symbolId})
|
|
162
|
+
RETURN s.symbolId AS symbolId,
|
|
163
|
+
m.fanIn AS fanIn,
|
|
164
|
+
m.fanOut AS fanOut,
|
|
165
|
+
m.churn30d AS churn30d
|
|
166
|
+
ORDER BY m.churn30d DESC
|
|
167
167
|
LIMIT $limit`, { repoId, limit: maxFetch });
|
|
168
168
|
return rows.map((row) => ({
|
|
169
169
|
symbolId: row.symbolId,
|
|
@@ -173,10 +173,10 @@ export async function getTopSymbolsByChurn(conn, repoId, limit = 10) {
|
|
|
173
173
|
}));
|
|
174
174
|
}
|
|
175
175
|
export async function computeFanInOut(conn, symbolId) {
|
|
176
|
-
const row = await querySingle(conn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
177
|
-
OPTIONAL MATCH (s)<-[i:DEPENDS_ON]-(:Symbol)
|
|
178
|
-
WITH s, count(i) AS fanIn
|
|
179
|
-
OPTIONAL MATCH (s)-[o:DEPENDS_ON]->(:Symbol)
|
|
176
|
+
const row = await querySingle(conn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
177
|
+
OPTIONAL MATCH (s)<-[i:DEPENDS_ON]-(:Symbol)
|
|
178
|
+
WITH s, count(i) AS fanIn
|
|
179
|
+
OPTIONAL MATCH (s)-[o:DEPENDS_ON]->(:Symbol)
|
|
180
180
|
RETURN fanIn AS fanIn, count(o) AS fanOut`, { symbolId });
|
|
181
181
|
if (!row)
|
|
182
182
|
return { fanIn: 0, fanOut: 0 };
|
|
@@ -195,18 +195,18 @@ export async function batchComputeFanInOut(conn, symbolIds) {
|
|
|
195
195
|
// NOTE: Ladybug can produce incorrect counts for large UNWIND lists, especially
|
|
196
196
|
// when the input list contains many missing symbols. Prefer WHERE ... IN and
|
|
197
197
|
// fill missing IDs in JS.
|
|
198
|
-
const fanInRows = await queryAll(conn, `MATCH (s:Symbol)
|
|
199
|
-
WHERE s.symbolId IN $symbolIds
|
|
200
|
-
OPTIONAL MATCH (s)<-[i:DEPENDS_ON]-(:Symbol)
|
|
198
|
+
const fanInRows = await queryAll(conn, `MATCH (s:Symbol)
|
|
199
|
+
WHERE s.symbolId IN $symbolIds
|
|
200
|
+
OPTIONAL MATCH (s)<-[i:DEPENDS_ON]-(:Symbol)
|
|
201
201
|
RETURN s.symbolId AS symbolId, count(i) AS fanIn`, { symbolIds });
|
|
202
202
|
for (const row of fanInRows) {
|
|
203
203
|
const entry = result.get(row.symbolId);
|
|
204
204
|
if (entry)
|
|
205
205
|
entry.fanIn = toNumber(row.fanIn);
|
|
206
206
|
}
|
|
207
|
-
const fanOutRows = await queryAll(conn, `MATCH (s:Symbol)
|
|
208
|
-
WHERE s.symbolId IN $symbolIds
|
|
209
|
-
OPTIONAL MATCH (s)-[o:DEPENDS_ON]->(:Symbol)
|
|
207
|
+
const fanOutRows = await queryAll(conn, `MATCH (s:Symbol)
|
|
208
|
+
WHERE s.symbolId IN $symbolIds
|
|
209
|
+
OPTIONAL MATCH (s)-[o:DEPENDS_ON]->(:Symbol)
|
|
210
210
|
RETURN s.symbolId AS symbolId, count(o) AS fanOut`, { symbolIds });
|
|
211
211
|
for (const row of fanOutRows) {
|
|
212
212
|
const entry = result.get(row.symbolId);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { exec, queryAll, querySingle, toNumber, withTransaction, } from "./ladybug-core.js";
|
|
2
2
|
export async function upsertProcess(conn, row) {
|
|
3
|
-
await exec(conn, `MATCH (r:Repo {repoId: $repoId})
|
|
4
|
-
MERGE (p:Process {processId: $processId})
|
|
5
|
-
SET p.repoId = $repoId,
|
|
6
|
-
p.entrySymbolId = $entrySymbolId,
|
|
7
|
-
p.label = $label,
|
|
8
|
-
p.depth = $depth,
|
|
9
|
-
p.versionId = $versionId,
|
|
10
|
-
p.createdAt = $createdAt
|
|
3
|
+
await exec(conn, `MATCH (r:Repo {repoId: $repoId})
|
|
4
|
+
MERGE (p:Process {processId: $processId})
|
|
5
|
+
SET p.repoId = $repoId,
|
|
6
|
+
p.entrySymbolId = $entrySymbolId,
|
|
7
|
+
p.label = $label,
|
|
8
|
+
p.depth = $depth,
|
|
9
|
+
p.versionId = $versionId,
|
|
10
|
+
p.createdAt = $createdAt
|
|
11
11
|
MERGE (p)-[:PROCESS_IN_REPO]->(r)`, {
|
|
12
12
|
processId: row.processId,
|
|
13
13
|
repoId: row.repoId,
|
|
@@ -19,10 +19,10 @@ export async function upsertProcess(conn, row) {
|
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
export async function upsertProcessStep(conn, row) {
|
|
22
|
-
await exec(conn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
23
|
-
MATCH (p:Process {processId: $processId})
|
|
24
|
-
MERGE (s)-[r:PARTICIPATES_IN]->(p)
|
|
25
|
-
SET r.stepOrder = $stepOrder,
|
|
22
|
+
await exec(conn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
23
|
+
MATCH (p:Process {processId: $processId})
|
|
24
|
+
MERGE (s)-[r:PARTICIPATES_IN]->(p)
|
|
25
|
+
SET r.stepOrder = $stepOrder,
|
|
26
26
|
r.role = $role`, {
|
|
27
27
|
processId: row.processId,
|
|
28
28
|
symbolId: row.symbolId,
|
|
@@ -35,10 +35,10 @@ export async function upsertProcessStepsBatch(conn, steps) {
|
|
|
35
35
|
return;
|
|
36
36
|
await withTransaction(conn, async (txConn) => {
|
|
37
37
|
for (const step of steps) {
|
|
38
|
-
await exec(txConn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
39
|
-
MATCH (p:Process {processId: $processId})
|
|
40
|
-
MERGE (s)-[r:PARTICIPATES_IN]->(p)
|
|
41
|
-
SET r.stepOrder = $stepOrder,
|
|
38
|
+
await exec(txConn, `MATCH (s:Symbol {symbolId: $symbolId})
|
|
39
|
+
MATCH (p:Process {processId: $processId})
|
|
40
|
+
MERGE (s)-[r:PARTICIPATES_IN]->(p)
|
|
41
|
+
SET r.stepOrder = $stepOrder,
|
|
42
42
|
r.role = $role`, {
|
|
43
43
|
processId: step.processId,
|
|
44
44
|
symbolId: step.symbolId,
|
|
@@ -49,13 +49,13 @@ export async function upsertProcessStepsBatch(conn, steps) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
export async function getProcessesForSymbol(conn, symbolId) {
|
|
52
|
-
const rows = await queryAll(conn, `MATCH (s:Symbol {symbolId: $symbolId})-[r:PARTICIPATES_IN]->(p:Process)
|
|
53
|
-
RETURN p.processId AS processId,
|
|
54
|
-
p.entrySymbolId AS entrySymbolId,
|
|
55
|
-
p.label AS label,
|
|
56
|
-
p.depth AS depth,
|
|
57
|
-
r.stepOrder AS stepOrder,
|
|
58
|
-
r.role AS role
|
|
52
|
+
const rows = await queryAll(conn, `MATCH (s:Symbol {symbolId: $symbolId})-[r:PARTICIPATES_IN]->(p:Process)
|
|
53
|
+
RETURN p.processId AS processId,
|
|
54
|
+
p.entrySymbolId AS entrySymbolId,
|
|
55
|
+
p.label AS label,
|
|
56
|
+
p.depth AS depth,
|
|
57
|
+
r.stepOrder AS stepOrder,
|
|
58
|
+
r.role AS role
|
|
59
59
|
ORDER BY r.stepOrder ASC, processId ASC`, { symbolId });
|
|
60
60
|
return rows.map((row) => ({
|
|
61
61
|
processId: row.processId,
|
|
@@ -69,15 +69,15 @@ export async function getProcessesForSymbol(conn, symbolId) {
|
|
|
69
69
|
export async function getProcessesForSymbols(conn, symbolIds) {
|
|
70
70
|
if (symbolIds.length === 0)
|
|
71
71
|
return new Map();
|
|
72
|
-
const rows = await queryAll(conn, `MATCH (s:Symbol)-[r:PARTICIPATES_IN]->(p:Process)
|
|
73
|
-
WHERE s.symbolId IN $symbolIds
|
|
74
|
-
RETURN s.symbolId AS symbolId,
|
|
75
|
-
p.processId AS processId,
|
|
76
|
-
p.entrySymbolId AS entrySymbolId,
|
|
77
|
-
p.label AS label,
|
|
78
|
-
p.depth AS depth,
|
|
79
|
-
r.stepOrder AS stepOrder,
|
|
80
|
-
r.role AS role
|
|
72
|
+
const rows = await queryAll(conn, `MATCH (s:Symbol)-[r:PARTICIPATES_IN]->(p:Process)
|
|
73
|
+
WHERE s.symbolId IN $symbolIds
|
|
74
|
+
RETURN s.symbolId AS symbolId,
|
|
75
|
+
p.processId AS processId,
|
|
76
|
+
p.entrySymbolId AS entrySymbolId,
|
|
77
|
+
p.label AS label,
|
|
78
|
+
p.depth AS depth,
|
|
79
|
+
r.stepOrder AS stepOrder,
|
|
80
|
+
r.role AS role
|
|
81
81
|
ORDER BY symbolId ASC, r.stepOrder ASC, processId ASC`, { symbolIds });
|
|
82
82
|
const map = new Map();
|
|
83
83
|
for (const row of rows) {
|
|
@@ -95,15 +95,15 @@ export async function getProcessesForSymbols(conn, symbolIds) {
|
|
|
95
95
|
return map;
|
|
96
96
|
}
|
|
97
97
|
export async function getProcessOverviewStats(conn, repoId) {
|
|
98
|
-
const agg = await querySingle(conn, `MATCH (r:Repo {repoId: $repoId})<-[:PROCESS_IN_REPO]-(p:Process)
|
|
99
|
-
RETURN COUNT(p) AS totalProcesses,
|
|
100
|
-
AVG(p.depth) AS averageDepth,
|
|
98
|
+
const agg = await querySingle(conn, `MATCH (r:Repo {repoId: $repoId})<-[:PROCESS_IN_REPO]-(p:Process)
|
|
99
|
+
RETURN COUNT(p) AS totalProcesses,
|
|
100
|
+
AVG(p.depth) AS averageDepth,
|
|
101
101
|
COUNT(DISTINCT p.entrySymbolId) AS entryPoints`, { repoId });
|
|
102
|
-
const top = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:PROCESS_IN_REPO]-(p:Process)
|
|
103
|
-
RETURN p.processId AS processId,
|
|
104
|
-
p.label AS label,
|
|
105
|
-
p.depth AS depth
|
|
106
|
-
ORDER BY depth DESC, processId ASC
|
|
102
|
+
const top = await queryAll(conn, `MATCH (r:Repo {repoId: $repoId})<-[:PROCESS_IN_REPO]-(p:Process)
|
|
103
|
+
RETURN p.processId AS processId,
|
|
104
|
+
p.label AS label,
|
|
105
|
+
p.depth AS depth
|
|
106
|
+
ORDER BY depth DESC, processId ASC
|
|
107
107
|
LIMIT 5`, { repoId });
|
|
108
108
|
return {
|
|
109
109
|
totalProcesses: toNumber(agg?.totalProcesses ?? 0),
|
|
@@ -117,10 +117,10 @@ export async function getProcessOverviewStats(conn, repoId) {
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
export async function getProcessFlow(conn, processId) {
|
|
120
|
-
const rows = await queryAll(conn, `MATCH (s:Symbol)-[r:PARTICIPATES_IN]->(p:Process {processId: $processId})
|
|
121
|
-
RETURN s.symbolId AS symbolId,
|
|
122
|
-
r.stepOrder AS stepOrder,
|
|
123
|
-
r.role AS role
|
|
120
|
+
const rows = await queryAll(conn, `MATCH (s:Symbol)-[r:PARTICIPATES_IN]->(p:Process {processId: $processId})
|
|
121
|
+
RETURN s.symbolId AS symbolId,
|
|
122
|
+
r.stepOrder AS stepOrder,
|
|
123
|
+
r.role AS role
|
|
124
124
|
ORDER BY r.stepOrder ASC, symbolId ASC`, { processId });
|
|
125
125
|
return rows.map((row) => ({
|
|
126
126
|
symbolId: row.symbolId,
|
|
@@ -129,13 +129,13 @@ export async function getProcessFlow(conn, processId) {
|
|
|
129
129
|
}));
|
|
130
130
|
}
|
|
131
131
|
export async function getProcessStepsAfterSymbol(conn, processId, symbolId) {
|
|
132
|
-
const rows = await queryAll(conn, `MATCH (s:Symbol {symbolId: $symbolId})-[r:PARTICIPATES_IN]->(p:Process {processId: $processId})
|
|
133
|
-
WITH p, r.stepOrder AS startOrder
|
|
134
|
-
MATCH (s2:Symbol)-[r2:PARTICIPATES_IN]->(p)
|
|
135
|
-
WHERE r2.stepOrder > startOrder
|
|
136
|
-
RETURN s2.symbolId AS symbolId,
|
|
137
|
-
r2.stepOrder AS stepOrder,
|
|
138
|
-
r2.role AS role
|
|
132
|
+
const rows = await queryAll(conn, `MATCH (s:Symbol {symbolId: $symbolId})-[r:PARTICIPATES_IN]->(p:Process {processId: $processId})
|
|
133
|
+
WITH p, r.stepOrder AS startOrder
|
|
134
|
+
MATCH (s2:Symbol)-[r2:PARTICIPATES_IN]->(p)
|
|
135
|
+
WHERE r2.stepOrder > startOrder
|
|
136
|
+
RETURN s2.symbolId AS symbolId,
|
|
137
|
+
r2.stepOrder AS stepOrder,
|
|
138
|
+
r2.role AS role
|
|
139
139
|
ORDER BY r2.stepOrder ASC, symbolId ASC`, { processId, symbolId });
|
|
140
140
|
return rows.map((row) => ({
|
|
141
141
|
symbolId: row.symbolId,
|
|
@@ -145,12 +145,12 @@ export async function getProcessStepsAfterSymbol(conn, processId, symbolId) {
|
|
|
145
145
|
}
|
|
146
146
|
export async function deleteProcessesByRepo(conn, repoId) {
|
|
147
147
|
await withTransaction(conn, async (txConn) => {
|
|
148
|
-
await exec(txConn, `MATCH (p:Process {repoId: $repoId})
|
|
149
|
-
OPTIONAL MATCH (:Symbol)-[m:PARTICIPATES_IN]->(p)
|
|
148
|
+
await exec(txConn, `MATCH (p:Process {repoId: $repoId})
|
|
149
|
+
OPTIONAL MATCH (:Symbol)-[m:PARTICIPATES_IN]->(p)
|
|
150
150
|
DELETE m`, { repoId });
|
|
151
|
-
await exec(txConn, `MATCH (p:Process {repoId: $repoId})-[rel:PROCESS_IN_REPO]->(:Repo {repoId: $repoId})
|
|
151
|
+
await exec(txConn, `MATCH (p:Process {repoId: $repoId})-[rel:PROCESS_IN_REPO]->(:Repo {repoId: $repoId})
|
|
152
152
|
DELETE rel`, { repoId });
|
|
153
|
-
await exec(txConn, `MATCH (p:Process {repoId: $repoId})
|
|
153
|
+
await exec(txConn, `MATCH (p:Process {repoId: $repoId})
|
|
154
154
|
DELETE p`, { repoId });
|
|
155
155
|
});
|
|
156
156
|
}
|