@zuvia-software-solutions/code-mapper 1.4.0 → 2.0.1
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/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +73 -82
- package/dist/cli/augment.js +0 -2
- package/dist/cli/eval-server.d.ts +2 -2
- package/dist/cli/eval-server.js +6 -6
- package/dist/cli/index.js +6 -10
- package/dist/cli/mcp.d.ts +1 -3
- package/dist/cli/mcp.js +3 -3
- package/dist/cli/refresh.d.ts +2 -2
- package/dist/cli/refresh.js +24 -29
- package/dist/cli/status.js +4 -13
- package/dist/cli/tool.d.ts +5 -4
- package/dist/cli/tool.js +8 -10
- package/dist/config/ignore-service.js +14 -34
- package/dist/core/augmentation/engine.js +53 -83
- package/dist/core/db/adapter.d.ts +99 -0
- package/dist/core/db/adapter.js +402 -0
- package/dist/core/db/graph-loader.d.ts +27 -0
- package/dist/core/db/graph-loader.js +148 -0
- package/dist/core/db/queries.d.ts +160 -0
- package/dist/core/db/queries.js +441 -0
- package/dist/core/db/schema.d.ts +108 -0
- package/dist/core/db/schema.js +136 -0
- package/dist/core/embeddings/embedder.d.ts +21 -12
- package/dist/core/embeddings/embedder.js +104 -50
- package/dist/core/embeddings/embedding-pipeline.d.ts +48 -22
- package/dist/core/embeddings/embedding-pipeline.js +220 -262
- package/dist/core/embeddings/text-generator.js +4 -19
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/graph.d.ts +1 -1
- package/dist/core/graph/graph.js +1 -0
- package/dist/core/graph/types.d.ts +11 -9
- package/dist/core/graph/types.js +4 -1
- package/dist/core/incremental/refresh.d.ts +46 -0
- package/dist/core/incremental/refresh.js +503 -0
- package/dist/core/incremental/types.d.ts +2 -1
- package/dist/core/incremental/types.js +42 -44
- package/dist/core/ingestion/ast-cache.js +1 -0
- package/dist/core/ingestion/call-processor.d.ts +15 -3
- package/dist/core/ingestion/call-processor.js +448 -60
- package/dist/core/ingestion/cluster-enricher.d.ts +1 -1
- package/dist/core/ingestion/cluster-enricher.js +2 -0
- package/dist/core/ingestion/community-processor.d.ts +1 -1
- package/dist/core/ingestion/community-processor.js +8 -3
- package/dist/core/ingestion/export-detection.d.ts +1 -1
- package/dist/core/ingestion/export-detection.js +1 -1
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/heritage-processor.d.ts +2 -2
- package/dist/core/ingestion/heritage-processor.js +22 -11
- package/dist/core/ingestion/import-processor.d.ts +2 -2
- package/dist/core/ingestion/import-processor.js +24 -9
- package/dist/core/ingestion/language-config.js +7 -4
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +23 -11
- package/dist/core/ingestion/named-binding-extraction.js +5 -5
- package/dist/core/ingestion/parsing-processor.d.ts +4 -4
- package/dist/core/ingestion/parsing-processor.js +26 -18
- package/dist/core/ingestion/pipeline.d.ts +4 -2
- package/dist/core/ingestion/pipeline.js +50 -20
- package/dist/core/ingestion/process-processor.d.ts +2 -2
- package/dist/core/ingestion/process-processor.js +28 -14
- package/dist/core/ingestion/resolution-context.d.ts +1 -1
- package/dist/core/ingestion/resolution-context.js +14 -4
- package/dist/core/ingestion/resolvers/csharp.js +4 -3
- package/dist/core/ingestion/resolvers/go.js +3 -1
- package/dist/core/ingestion/resolvers/jvm.js +13 -4
- package/dist/core/ingestion/resolvers/standard.js +2 -2
- package/dist/core/ingestion/resolvers/utils.js +6 -2
- package/dist/core/ingestion/route-stitcher.d.ts +15 -0
- package/dist/core/ingestion/route-stitcher.js +92 -0
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +3 -2
- package/dist/core/ingestion/symbol-table.d.ts +2 -0
- package/dist/core/ingestion/symbol-table.js +5 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +177 -0
- package/dist/core/ingestion/type-env.js +20 -0
- package/dist/core/ingestion/type-extractors/csharp.js +4 -3
- package/dist/core/ingestion/type-extractors/go.js +23 -12
- package/dist/core/ingestion/type-extractors/php.js +18 -10
- package/dist/core/ingestion/type-extractors/ruby.js +15 -3
- package/dist/core/ingestion/type-extractors/rust.js +3 -2
- package/dist/core/ingestion/type-extractors/shared.js +3 -2
- package/dist/core/ingestion/type-extractors/typescript.js +11 -5
- package/dist/core/ingestion/utils.d.ts +27 -4
- package/dist/core/ingestion/utils.js +145 -100
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +97 -29
- package/dist/core/ingestion/workers/worker-pool.js +3 -0
- package/dist/core/search/bm25-index.d.ts +15 -8
- package/dist/core/search/bm25-index.js +48 -98
- package/dist/core/search/hybrid-search.d.ts +9 -3
- package/dist/core/search/hybrid-search.js +30 -25
- package/dist/core/search/reranker.js +9 -7
- package/dist/core/search/types.d.ts +0 -4
- package/dist/core/semantic/tsgo-service.d.ts +7 -1
- package/dist/core/semantic/tsgo-service.js +165 -66
- package/dist/lib/tsgo-test.d.ts +2 -0
- package/dist/lib/tsgo-test.js +6 -0
- package/dist/lib/type-utils.d.ts +25 -0
- package/dist/lib/type-utils.js +22 -0
- package/dist/lib/utils.d.ts +3 -2
- package/dist/lib/utils.js +3 -2
- package/dist/mcp/compatible-stdio-transport.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +29 -56
- package/dist/mcp/local/local-backend.js +808 -1118
- package/dist/mcp/resources.js +35 -25
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +5 -5
- package/dist/mcp/tools.js +24 -25
- package/dist/storage/repo-manager.d.ts +2 -12
- package/dist/storage/repo-manager.js +1 -47
- package/dist/types/pipeline.d.ts +8 -5
- package/dist/types/pipeline.js +5 -0
- package/package.json +18 -11
- package/dist/cli/serve.d.ts +0 -5
- package/dist/cli/serve.js +0 -8
- package/dist/core/incremental/child-process.d.ts +0 -8
- package/dist/core/incremental/child-process.js +0 -649
- package/dist/core/incremental/refresh-coordinator.d.ts +0 -32
- package/dist/core/incremental/refresh-coordinator.js +0 -147
- package/dist/core/lbug/csv-generator.d.ts +0 -28
- package/dist/core/lbug/csv-generator.js +0 -355
- package/dist/core/lbug/lbug-adapter.d.ts +0 -96
- package/dist/core/lbug/lbug-adapter.js +0 -753
- package/dist/core/lbug/schema.d.ts +0 -46
- package/dist/core/lbug/schema.js +0 -402
- package/dist/mcp/core/embedder.d.ts +0 -24
- package/dist/mcp/core/embedder.js +0 -168
- package/dist/mcp/core/lbug-adapter.d.ts +0 -29
- package/dist/mcp/core/lbug-adapter.js +0 -330
- package/dist/server/api.d.ts +0 -5
- package/dist/server/api.js +0 -340
- package/dist/server/mcp-http.d.ts +0 -7
- package/dist/server/mcp-http.js +0 -95
- package/models/mlx-embedder.py +0 -185
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Typed SQL boundary for joined queries, traversals, and aggregation.
|
|
3
|
+
*
|
|
4
|
+
* All multi-table SQL lives here. `local-backend.ts` never touches SQL
|
|
5
|
+
* directly for joined or traversal queries -- it calls typed functions
|
|
6
|
+
* from this module or from adapter.ts for simple CRUD.
|
|
7
|
+
*
|
|
8
|
+
* Design invariants:
|
|
9
|
+
* - schema.ts is the ONLY source of truth for types
|
|
10
|
+
* - Every ID is branded (NodeId, EdgeId)
|
|
11
|
+
* - Every edge type is a literal union (EdgeType)
|
|
12
|
+
* - Synchronous API (better-sqlite3 is sync)
|
|
13
|
+
* - Fail-fast: log and throw, never swallow
|
|
14
|
+
*/
|
|
15
|
+
import type Database from 'better-sqlite3';
|
|
16
|
+
import type { NodeLabel, EdgeType, NodeRow, EdgeRow } from './schema.js';
|
|
17
|
+
import { type NodeId } from './schema.js';
|
|
18
|
+
export { getStats } from './adapter.js';
|
|
19
|
+
/**
|
|
20
|
+
* Find incoming edges to a target node, joined with their source nodes.
|
|
21
|
+
* Use case: "who calls this function?" (callers / upstream deps).
|
|
22
|
+
*/
|
|
23
|
+
export declare function findIncomingEdges(db: Database.Database, targetId: NodeId, type?: EdgeType, opts?: {
|
|
24
|
+
minConfidence?: number;
|
|
25
|
+
limit?: number;
|
|
26
|
+
}): Array<{
|
|
27
|
+
edge: EdgeRow;
|
|
28
|
+
node: NodeRow;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Find outgoing edges from a source node, joined with their target nodes.
|
|
32
|
+
* Use case: "what does this function call?" (callees / downstream deps).
|
|
33
|
+
*/
|
|
34
|
+
export declare function findOutgoingEdges(db: Database.Database, sourceId: NodeId, type?: EdgeType, opts?: {
|
|
35
|
+
minConfidence?: number;
|
|
36
|
+
limit?: number;
|
|
37
|
+
}): Array<{
|
|
38
|
+
edge: EdgeRow;
|
|
39
|
+
node: NodeRow;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Find all processes that a given node participates in.
|
|
43
|
+
* Looks for STEP_IN_PROCESS edges from nodeId to Process nodes.
|
|
44
|
+
*/
|
|
45
|
+
export declare function findProcessesForNode(db: Database.Database, nodeId: NodeId): Array<{
|
|
46
|
+
processId: NodeId;
|
|
47
|
+
label: string;
|
|
48
|
+
heuristicLabel: string;
|
|
49
|
+
processType: string;
|
|
50
|
+
stepCount: number;
|
|
51
|
+
step: number;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Find the community a node belongs to (via MEMBER_OF edge).
|
|
55
|
+
* Returns null if the node has no community membership.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findCommunityForNode(db: Database.Database, nodeId: NodeId): {
|
|
58
|
+
communityId: NodeId;
|
|
59
|
+
label: string;
|
|
60
|
+
cohesion: number;
|
|
61
|
+
} | null;
|
|
62
|
+
/**
|
|
63
|
+
* Batch: find process participation for multiple nodes at once.
|
|
64
|
+
* More efficient than calling findProcessesForNode in a loop.
|
|
65
|
+
*/
|
|
66
|
+
export declare function batchFindProcesses(db: Database.Database, nodeIds: readonly NodeId[]): Array<{
|
|
67
|
+
nodeId: NodeId;
|
|
68
|
+
processId: NodeId;
|
|
69
|
+
label: string;
|
|
70
|
+
heuristicLabel: string;
|
|
71
|
+
processType: string;
|
|
72
|
+
stepCount: number;
|
|
73
|
+
step: number;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Batch: find community membership for multiple nodes at once.
|
|
77
|
+
*/
|
|
78
|
+
export declare function batchFindCommunities(db: Database.Database, nodeIds: readonly NodeId[]): Array<{
|
|
79
|
+
nodeId: NodeId;
|
|
80
|
+
communityId: NodeId;
|
|
81
|
+
module: string;
|
|
82
|
+
cohesion: number;
|
|
83
|
+
}>;
|
|
84
|
+
export interface ImpactNode {
|
|
85
|
+
readonly depth: number;
|
|
86
|
+
readonly id: NodeId;
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly label: NodeLabel;
|
|
89
|
+
readonly filePath: string;
|
|
90
|
+
readonly relationType: EdgeType;
|
|
91
|
+
readonly confidence: number;
|
|
92
|
+
}
|
|
93
|
+
export interface ImpactResult {
|
|
94
|
+
readonly nodes: readonly ImpactNode[];
|
|
95
|
+
readonly truncated: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Multi-hop BFS traversal for impact analysis.
|
|
99
|
+
*
|
|
100
|
+
* `upstream` = follow edges backward (find callers, importers -- who depends on this)
|
|
101
|
+
* `downstream` = follow edges forward (find callees -- what does this depend on)
|
|
102
|
+
*
|
|
103
|
+
* Algorithm: iterative frontier expansion per depth level.
|
|
104
|
+
* Terminates when maxDepth is reached, frontier is empty, or maxImpacted nodes collected.
|
|
105
|
+
*/
|
|
106
|
+
export declare function traverseImpact(db: Database.Database, startId: NodeId, direction: 'upstream' | 'downstream', opts: {
|
|
107
|
+
maxDepth: number;
|
|
108
|
+
edgeTypes: readonly EdgeType[];
|
|
109
|
+
minConfidence: number;
|
|
110
|
+
includeTests: boolean;
|
|
111
|
+
maxImpacted?: number;
|
|
112
|
+
maxFrontierPerDepth?: number;
|
|
113
|
+
}): ImpactResult;
|
|
114
|
+
/**
|
|
115
|
+
* List all Community nodes, ordered by symbolCount descending.
|
|
116
|
+
*/
|
|
117
|
+
export declare function listCommunities(db: Database.Database, limit?: number): NodeRow[];
|
|
118
|
+
/**
|
|
119
|
+
* List all Process nodes, ordered by stepCount descending.
|
|
120
|
+
*/
|
|
121
|
+
export declare function listProcesses(db: Database.Database, limit?: number): NodeRow[];
|
|
122
|
+
/**
|
|
123
|
+
* Get members of a community (nodes with MEMBER_OF edges to the community).
|
|
124
|
+
* Matches by community name or heuristicLabel.
|
|
125
|
+
*/
|
|
126
|
+
export declare function getCommunityMembers(db: Database.Database, communityName: string, limit?: number): NodeRow[];
|
|
127
|
+
/**
|
|
128
|
+
* Get ordered steps of a process.
|
|
129
|
+
* Returns nodes participating in the process, sorted by step number.
|
|
130
|
+
*/
|
|
131
|
+
export declare function getProcessSteps(db: Database.Database, processId: NodeId): Array<{
|
|
132
|
+
node: NodeRow;
|
|
133
|
+
step: number;
|
|
134
|
+
}>;
|
|
135
|
+
/**
|
|
136
|
+
* Find communities by name (matches against name or heuristicLabel).
|
|
137
|
+
*/
|
|
138
|
+
export declare function findCommunitiesByName(db: Database.Database, name: string): NodeRow[];
|
|
139
|
+
/**
|
|
140
|
+
* Find processes by name (matches against name or heuristicLabel).
|
|
141
|
+
*/
|
|
142
|
+
export declare function findProcessesByName(db: Database.Database, name: string): NodeRow[];
|
|
143
|
+
/**
|
|
144
|
+
* Fetch multiple nodes by their IDs. Useful for batch content retrieval
|
|
145
|
+
* (e.g., reranker signature extraction).
|
|
146
|
+
*/
|
|
147
|
+
export declare function findNodesByIds(db: Database.Database, ids: readonly NodeId[]): NodeRow[];
|
|
148
|
+
/**
|
|
149
|
+
* Get ALL steps for multiple processes at once.
|
|
150
|
+
* Returns flattened list of steps across all requested processes.
|
|
151
|
+
*/
|
|
152
|
+
export declare function batchGetProcessSteps(db: Database.Database, processIds: readonly NodeId[]): Array<{
|
|
153
|
+
processId: NodeId;
|
|
154
|
+
nodeId: NodeId;
|
|
155
|
+
name: string;
|
|
156
|
+
label: string;
|
|
157
|
+
filePath: string;
|
|
158
|
+
startLine: number | null;
|
|
159
|
+
step: number;
|
|
160
|
+
}>;
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// code-mapper/src/core/db/queries.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file Typed SQL boundary for joined queries, traversals, and aggregation.
|
|
4
|
+
*
|
|
5
|
+
* All multi-table SQL lives here. `local-backend.ts` never touches SQL
|
|
6
|
+
* directly for joined or traversal queries -- it calls typed functions
|
|
7
|
+
* from this module or from adapter.ts for simple CRUD.
|
|
8
|
+
*
|
|
9
|
+
* Design invariants:
|
|
10
|
+
* - schema.ts is the ONLY source of truth for types
|
|
11
|
+
* - Every ID is branded (NodeId, EdgeId)
|
|
12
|
+
* - Every edge type is a literal union (EdgeType)
|
|
13
|
+
* - Synchronous API (better-sqlite3 is sync)
|
|
14
|
+
* - Fail-fast: log and throw, never swallow
|
|
15
|
+
*/
|
|
16
|
+
import { toNodeId, assertNodeLabel, assertEdgeType } from './schema.js';
|
|
17
|
+
export { getStats } from './adapter.js';
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Test-file detection (inlined -- small, pure, no external deps)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const TEST_PATH_PATTERN = /(?:^|[/\\])(?:__tests?__|tests?|spec|__mocks__)[/\\]|\.(?:test|spec|e2e|integration)\.[^/\\]+$/i;
|
|
22
|
+
function isTestFilePath(filePath) {
|
|
23
|
+
return TEST_PATH_PATTERN.test(filePath);
|
|
24
|
+
}
|
|
25
|
+
/** Convert a prefixed join row to typed { edge, node } pair. */
|
|
26
|
+
function parseEdgeNodeJoin(raw) {
|
|
27
|
+
// Validate string-union fields at the DB boundary — fail fast on invalid data
|
|
28
|
+
assertEdgeType(raw.e_type);
|
|
29
|
+
assertNodeLabel(raw.n_label);
|
|
30
|
+
return {
|
|
31
|
+
edge: {
|
|
32
|
+
id: raw.e_id,
|
|
33
|
+
sourceId: toNodeId(raw.e_sourceId),
|
|
34
|
+
targetId: toNodeId(raw.e_targetId),
|
|
35
|
+
type: raw.e_type,
|
|
36
|
+
confidence: raw.e_confidence,
|
|
37
|
+
reason: raw.e_reason,
|
|
38
|
+
step: raw.e_step,
|
|
39
|
+
callLine: raw.e_callLine,
|
|
40
|
+
},
|
|
41
|
+
node: {
|
|
42
|
+
id: toNodeId(raw.n_id),
|
|
43
|
+
label: raw.n_label,
|
|
44
|
+
name: raw.n_name,
|
|
45
|
+
filePath: raw.n_filePath,
|
|
46
|
+
startLine: raw.n_startLine,
|
|
47
|
+
endLine: raw.n_endLine,
|
|
48
|
+
isExported: raw.n_isExported,
|
|
49
|
+
content: raw.n_content,
|
|
50
|
+
description: raw.n_description,
|
|
51
|
+
heuristicLabel: raw.n_heuristicLabel,
|
|
52
|
+
cohesion: raw.n_cohesion,
|
|
53
|
+
symbolCount: raw.n_symbolCount,
|
|
54
|
+
keywords: raw.n_keywords,
|
|
55
|
+
enrichedBy: raw.n_enrichedBy,
|
|
56
|
+
processType: raw.n_processType,
|
|
57
|
+
stepCount: raw.n_stepCount,
|
|
58
|
+
communities: raw.n_communities,
|
|
59
|
+
entryPointId: raw.n_entryPointId,
|
|
60
|
+
terminalId: raw.n_terminalId,
|
|
61
|
+
parameterCount: raw.n_parameterCount,
|
|
62
|
+
returnType: raw.n_returnType,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Shared SQL fragments
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
const NODE_COLUMNS_PREFIXED = `
|
|
70
|
+
n.id AS n_id, n.label AS n_label, n.name AS n_name, n.filePath AS n_filePath,
|
|
71
|
+
n.startLine AS n_startLine, n.endLine AS n_endLine, n.isExported AS n_isExported,
|
|
72
|
+
n.content AS n_content, n.description AS n_description,
|
|
73
|
+
n.heuristicLabel AS n_heuristicLabel, n.cohesion AS n_cohesion,
|
|
74
|
+
n.symbolCount AS n_symbolCount, n.keywords AS n_keywords, n.enrichedBy AS n_enrichedBy,
|
|
75
|
+
n.processType AS n_processType, n.stepCount AS n_stepCount,
|
|
76
|
+
n.communities AS n_communities, n.entryPointId AS n_entryPointId,
|
|
77
|
+
n.terminalId AS n_terminalId, n.parameterCount AS n_parameterCount,
|
|
78
|
+
n.returnType AS n_returnType`;
|
|
79
|
+
const EDGE_COLUMNS_PREFIXED = `
|
|
80
|
+
e.id AS e_id, e.sourceId AS e_sourceId, e.targetId AS e_targetId,
|
|
81
|
+
e.type AS e_type, e.confidence AS e_confidence, e.reason AS e_reason,
|
|
82
|
+
e.step AS e_step, e.callLine AS e_callLine`;
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// 1. Edge traversals with joined nodes
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Find incoming edges to a target node, joined with their source nodes.
|
|
88
|
+
* Use case: "who calls this function?" (callers / upstream deps).
|
|
89
|
+
*/
|
|
90
|
+
export function findIncomingEdges(db, targetId, type, opts) {
|
|
91
|
+
const minConf = opts?.minConfidence ?? 0;
|
|
92
|
+
const limit = opts?.limit ?? 500;
|
|
93
|
+
const sql = type
|
|
94
|
+
? `SELECT ${EDGE_COLUMNS_PREFIXED}, ${NODE_COLUMNS_PREFIXED}
|
|
95
|
+
FROM edges e JOIN nodes n ON n.id = e.sourceId
|
|
96
|
+
WHERE e.targetId = ? AND e.type = ? AND e.confidence >= ?
|
|
97
|
+
LIMIT ?`
|
|
98
|
+
: `SELECT ${EDGE_COLUMNS_PREFIXED}, ${NODE_COLUMNS_PREFIXED}
|
|
99
|
+
FROM edges e JOIN nodes n ON n.id = e.sourceId
|
|
100
|
+
WHERE e.targetId = ? AND e.confidence >= ?
|
|
101
|
+
LIMIT ?`;
|
|
102
|
+
const rows = type
|
|
103
|
+
? db.prepare(sql).all(targetId, type, minConf, limit)
|
|
104
|
+
: db.prepare(sql).all(targetId, minConf, limit);
|
|
105
|
+
return rows.map(parseEdgeNodeJoin);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find outgoing edges from a source node, joined with their target nodes.
|
|
109
|
+
* Use case: "what does this function call?" (callees / downstream deps).
|
|
110
|
+
*/
|
|
111
|
+
export function findOutgoingEdges(db, sourceId, type, opts) {
|
|
112
|
+
const minConf = opts?.minConfidence ?? 0;
|
|
113
|
+
const limit = opts?.limit ?? 500;
|
|
114
|
+
const sql = type
|
|
115
|
+
? `SELECT ${EDGE_COLUMNS_PREFIXED}, ${NODE_COLUMNS_PREFIXED}
|
|
116
|
+
FROM edges e JOIN nodes n ON n.id = e.targetId
|
|
117
|
+
WHERE e.sourceId = ? AND e.type = ? AND e.confidence >= ?
|
|
118
|
+
LIMIT ?`
|
|
119
|
+
: `SELECT ${EDGE_COLUMNS_PREFIXED}, ${NODE_COLUMNS_PREFIXED}
|
|
120
|
+
FROM edges e JOIN nodes n ON n.id = e.targetId
|
|
121
|
+
WHERE e.sourceId = ? AND e.confidence >= ?
|
|
122
|
+
LIMIT ?`;
|
|
123
|
+
const rows = type
|
|
124
|
+
? db.prepare(sql).all(sourceId, type, minConf, limit)
|
|
125
|
+
: db.prepare(sql).all(sourceId, minConf, limit);
|
|
126
|
+
return rows.map(parseEdgeNodeJoin);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Find all processes that a given node participates in.
|
|
130
|
+
* Looks for STEP_IN_PROCESS edges from nodeId to Process nodes.
|
|
131
|
+
*/
|
|
132
|
+
export function findProcessesForNode(db, nodeId) {
|
|
133
|
+
const rows = db.prepare(`
|
|
134
|
+
SELECT p.id AS processId, p.name AS label, p.heuristicLabel, p.processType, p.stepCount, e.step
|
|
135
|
+
FROM edges e
|
|
136
|
+
JOIN nodes p ON p.id = e.targetId
|
|
137
|
+
WHERE e.sourceId = ? AND e.type = 'STEP_IN_PROCESS' AND p.label = 'Process'
|
|
138
|
+
`).all(nodeId);
|
|
139
|
+
return rows.map(r => ({
|
|
140
|
+
processId: toNodeId(r.processId),
|
|
141
|
+
label: r.label,
|
|
142
|
+
heuristicLabel: r.heuristicLabel,
|
|
143
|
+
processType: r.processType,
|
|
144
|
+
stepCount: r.stepCount,
|
|
145
|
+
step: r.step,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Find the community a node belongs to (via MEMBER_OF edge).
|
|
150
|
+
* Returns null if the node has no community membership.
|
|
151
|
+
*/
|
|
152
|
+
export function findCommunityForNode(db, nodeId) {
|
|
153
|
+
const row = db.prepare(`
|
|
154
|
+
SELECT c.id AS communityId, c.heuristicLabel AS label, c.cohesion
|
|
155
|
+
FROM edges e
|
|
156
|
+
JOIN nodes c ON c.id = e.targetId
|
|
157
|
+
WHERE e.sourceId = ? AND e.type = 'MEMBER_OF' AND c.label = 'Community'
|
|
158
|
+
LIMIT 1
|
|
159
|
+
`).get(nodeId);
|
|
160
|
+
if (!row)
|
|
161
|
+
return null;
|
|
162
|
+
return {
|
|
163
|
+
communityId: toNodeId(row.communityId),
|
|
164
|
+
label: row.label,
|
|
165
|
+
cohesion: row.cohesion,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Batch: find process participation for multiple nodes at once.
|
|
170
|
+
* More efficient than calling findProcessesForNode in a loop.
|
|
171
|
+
*/
|
|
172
|
+
export function batchFindProcesses(db, nodeIds) {
|
|
173
|
+
if (nodeIds.length === 0)
|
|
174
|
+
return [];
|
|
175
|
+
const ph = nodeIds.map(() => '?').join(',');
|
|
176
|
+
const rows = db.prepare(`
|
|
177
|
+
SELECT e.sourceId AS nodeId, p.id AS processId, p.name AS label,
|
|
178
|
+
p.heuristicLabel, p.processType, p.stepCount, e.step
|
|
179
|
+
FROM edges e
|
|
180
|
+
JOIN nodes p ON p.id = e.targetId
|
|
181
|
+
WHERE e.sourceId IN (${ph}) AND e.type = 'STEP_IN_PROCESS' AND p.label = 'Process'
|
|
182
|
+
`).all(...nodeIds);
|
|
183
|
+
return rows.map(r => ({
|
|
184
|
+
nodeId: toNodeId(r.nodeId),
|
|
185
|
+
processId: toNodeId(r.processId),
|
|
186
|
+
label: r.label,
|
|
187
|
+
heuristicLabel: r.heuristicLabel,
|
|
188
|
+
processType: r.processType,
|
|
189
|
+
stepCount: r.stepCount,
|
|
190
|
+
step: r.step,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Batch: find community membership for multiple nodes at once.
|
|
195
|
+
*/
|
|
196
|
+
export function batchFindCommunities(db, nodeIds) {
|
|
197
|
+
if (nodeIds.length === 0)
|
|
198
|
+
return [];
|
|
199
|
+
const ph = nodeIds.map(() => '?').join(',');
|
|
200
|
+
const rows = db.prepare(`
|
|
201
|
+
SELECT e.sourceId AS nodeId, c.id AS communityId, c.heuristicLabel AS module, c.cohesion
|
|
202
|
+
FROM edges e
|
|
203
|
+
JOIN nodes c ON c.id = e.targetId
|
|
204
|
+
WHERE e.sourceId IN (${ph}) AND e.type = 'MEMBER_OF' AND c.label = 'Community'
|
|
205
|
+
`).all(...nodeIds);
|
|
206
|
+
return rows.map(r => ({
|
|
207
|
+
nodeId: toNodeId(r.nodeId),
|
|
208
|
+
communityId: toNodeId(r.communityId),
|
|
209
|
+
module: r.module,
|
|
210
|
+
cohesion: r.cohesion,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Multi-hop BFS traversal for impact analysis.
|
|
215
|
+
*
|
|
216
|
+
* `upstream` = follow edges backward (find callers, importers -- who depends on this)
|
|
217
|
+
* `downstream` = follow edges forward (find callees -- what does this depend on)
|
|
218
|
+
*
|
|
219
|
+
* Algorithm: iterative frontier expansion per depth level.
|
|
220
|
+
* Terminates when maxDepth is reached, frontier is empty, or maxImpacted nodes collected.
|
|
221
|
+
*/
|
|
222
|
+
export function traverseImpact(db, startId, direction, opts) {
|
|
223
|
+
const maxImpacted = opts.maxImpacted ?? 500;
|
|
224
|
+
const maxFrontierPerDepth = opts.maxFrontierPerDepth ?? 200;
|
|
225
|
+
const visited = new Set([startId]);
|
|
226
|
+
const result = [];
|
|
227
|
+
let frontier = [startId];
|
|
228
|
+
let truncated = false;
|
|
229
|
+
// Build the type filter clause: e.type IN ('CALLS', 'IMPORTS', ...)
|
|
230
|
+
// Use parameterized OR chain since SQLite IN with params can be awkward for mixed params
|
|
231
|
+
const typeFilter = opts.edgeTypes.map(() => 'e.type = ?').join(' OR ');
|
|
232
|
+
// Direction determines join orientation:
|
|
233
|
+
// upstream: edges pointing TO current frontier nodes, join SOURCE node
|
|
234
|
+
// downstream: edges pointing FROM current frontier nodes, join TARGET node
|
|
235
|
+
const buildSql = (placeholderCount) => {
|
|
236
|
+
const idPh = Array.from({ length: placeholderCount }, () => '?').join(',');
|
|
237
|
+
if (direction === 'upstream') {
|
|
238
|
+
return `
|
|
239
|
+
SELECT n.id, n.name, n.label, n.filePath, e.type AS edgeType, e.confidence
|
|
240
|
+
FROM edges e
|
|
241
|
+
JOIN nodes n ON n.id = e.sourceId
|
|
242
|
+
WHERE e.targetId IN (${idPh})
|
|
243
|
+
AND (${typeFilter})
|
|
244
|
+
AND e.confidence >= ?`;
|
|
245
|
+
}
|
|
246
|
+
return `
|
|
247
|
+
SELECT n.id, n.name, n.label, n.filePath, e.type AS edgeType, e.confidence
|
|
248
|
+
FROM edges e
|
|
249
|
+
JOIN nodes n ON n.id = e.targetId
|
|
250
|
+
WHERE e.sourceId IN (${idPh})
|
|
251
|
+
AND (${typeFilter})
|
|
252
|
+
AND e.confidence >= ?`;
|
|
253
|
+
};
|
|
254
|
+
for (let depth = 1; depth <= opts.maxDepth; depth++) {
|
|
255
|
+
if (frontier.length === 0)
|
|
256
|
+
break;
|
|
257
|
+
// Truncate frontier to cap per-depth expansion
|
|
258
|
+
if (frontier.length > maxFrontierPerDepth) {
|
|
259
|
+
frontier = frontier.slice(0, maxFrontierPerDepth);
|
|
260
|
+
truncated = true;
|
|
261
|
+
}
|
|
262
|
+
const sql = buildSql(frontier.length);
|
|
263
|
+
const params = [
|
|
264
|
+
...frontier,
|
|
265
|
+
...opts.edgeTypes,
|
|
266
|
+
opts.minConfidence,
|
|
267
|
+
];
|
|
268
|
+
const rows = db.prepare(sql).all(...params);
|
|
269
|
+
const nextFrontier = [];
|
|
270
|
+
for (const row of rows) {
|
|
271
|
+
if (visited.has(row.id))
|
|
272
|
+
continue;
|
|
273
|
+
visited.add(row.id);
|
|
274
|
+
// Skip test files unless includeTests is true
|
|
275
|
+
if (!opts.includeTests && isTestFilePath(row.filePath))
|
|
276
|
+
continue;
|
|
277
|
+
// Validate string-union fields at the DB boundary — fail fast on invalid data
|
|
278
|
+
assertNodeLabel(row.label);
|
|
279
|
+
assertEdgeType(row.edgeType);
|
|
280
|
+
const node = {
|
|
281
|
+
depth,
|
|
282
|
+
id: toNodeId(row.id),
|
|
283
|
+
name: row.name,
|
|
284
|
+
label: row.label,
|
|
285
|
+
filePath: row.filePath,
|
|
286
|
+
relationType: row.edgeType,
|
|
287
|
+
confidence: row.confidence,
|
|
288
|
+
};
|
|
289
|
+
result.push(node);
|
|
290
|
+
nextFrontier.push(toNodeId(row.id));
|
|
291
|
+
if (result.length >= maxImpacted) {
|
|
292
|
+
truncated = true;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (result.length >= maxImpacted)
|
|
297
|
+
break;
|
|
298
|
+
frontier = nextFrontier;
|
|
299
|
+
}
|
|
300
|
+
return { nodes: result, truncated };
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// 7. Community / Process listing
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
/**
|
|
306
|
+
* List all Community nodes, ordered by symbolCount descending.
|
|
307
|
+
*/
|
|
308
|
+
export function listCommunities(db, limit = 100) {
|
|
309
|
+
return db.prepare(`
|
|
310
|
+
SELECT * FROM nodes
|
|
311
|
+
WHERE label = 'Community'
|
|
312
|
+
ORDER BY symbolCount DESC
|
|
313
|
+
LIMIT ?
|
|
314
|
+
`).all(limit);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* List all Process nodes, ordered by stepCount descending.
|
|
318
|
+
*/
|
|
319
|
+
export function listProcesses(db, limit = 100) {
|
|
320
|
+
return db.prepare(`
|
|
321
|
+
SELECT * FROM nodes
|
|
322
|
+
WHERE label = 'Process'
|
|
323
|
+
ORDER BY stepCount DESC
|
|
324
|
+
LIMIT ?
|
|
325
|
+
`).all(limit);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get members of a community (nodes with MEMBER_OF edges to the community).
|
|
329
|
+
* Matches by community name or heuristicLabel.
|
|
330
|
+
*/
|
|
331
|
+
export function getCommunityMembers(db, communityName, limit = 200) {
|
|
332
|
+
return db.prepare(`
|
|
333
|
+
SELECT DISTINCT n.* FROM edges e
|
|
334
|
+
JOIN nodes n ON n.id = e.sourceId
|
|
335
|
+
JOIN nodes c ON c.id = e.targetId
|
|
336
|
+
WHERE e.type = 'MEMBER_OF' AND c.label = 'Community'
|
|
337
|
+
AND (c.name = ? OR c.heuristicLabel = ?)
|
|
338
|
+
LIMIT ?
|
|
339
|
+
`).all(communityName, communityName, limit);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get ordered steps of a process.
|
|
343
|
+
* Returns nodes participating in the process, sorted by step number.
|
|
344
|
+
*/
|
|
345
|
+
export function getProcessSteps(db, processId) {
|
|
346
|
+
const rows = db.prepare(`
|
|
347
|
+
SELECT e.step,
|
|
348
|
+
${NODE_COLUMNS_PREFIXED}
|
|
349
|
+
FROM edges e
|
|
350
|
+
JOIN nodes n ON n.id = e.sourceId
|
|
351
|
+
WHERE e.targetId = ? AND e.type = 'STEP_IN_PROCESS'
|
|
352
|
+
ORDER BY e.step ASC
|
|
353
|
+
`).all(processId);
|
|
354
|
+
return rows.map(r => {
|
|
355
|
+
// Validate string-union field at the DB boundary — fail fast on invalid data
|
|
356
|
+
assertNodeLabel(r.n_label);
|
|
357
|
+
return {
|
|
358
|
+
step: r.step,
|
|
359
|
+
node: {
|
|
360
|
+
id: toNodeId(r.n_id),
|
|
361
|
+
label: r.n_label,
|
|
362
|
+
name: r.n_name,
|
|
363
|
+
filePath: r.n_filePath,
|
|
364
|
+
startLine: r.n_startLine,
|
|
365
|
+
endLine: r.n_endLine,
|
|
366
|
+
isExported: r.n_isExported,
|
|
367
|
+
content: r.n_content,
|
|
368
|
+
description: r.n_description,
|
|
369
|
+
heuristicLabel: r.n_heuristicLabel,
|
|
370
|
+
cohesion: r.n_cohesion,
|
|
371
|
+
symbolCount: r.n_symbolCount,
|
|
372
|
+
keywords: r.n_keywords,
|
|
373
|
+
enrichedBy: r.n_enrichedBy,
|
|
374
|
+
processType: r.n_processType,
|
|
375
|
+
stepCount: r.n_stepCount,
|
|
376
|
+
communities: r.n_communities,
|
|
377
|
+
entryPointId: r.n_entryPointId,
|
|
378
|
+
terminalId: r.n_terminalId,
|
|
379
|
+
parameterCount: r.n_parameterCount,
|
|
380
|
+
returnType: r.n_returnType,
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Find communities by name (matches against name or heuristicLabel).
|
|
387
|
+
*/
|
|
388
|
+
export function findCommunitiesByName(db, name) {
|
|
389
|
+
return db.prepare(`
|
|
390
|
+
SELECT * FROM nodes
|
|
391
|
+
WHERE label = 'Community' AND (name = ? OR heuristicLabel = ?)
|
|
392
|
+
`).all(name, name);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Find processes by name (matches against name or heuristicLabel).
|
|
396
|
+
*/
|
|
397
|
+
export function findProcessesByName(db, name) {
|
|
398
|
+
return db.prepare(`
|
|
399
|
+
SELECT * FROM nodes
|
|
400
|
+
WHERE label = 'Process' AND (name = ? OR heuristicLabel = ?)
|
|
401
|
+
`).all(name, name);
|
|
402
|
+
}
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
// 8. Batch content fetch
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
/**
|
|
407
|
+
* Fetch multiple nodes by their IDs. Useful for batch content retrieval
|
|
408
|
+
* (e.g., reranker signature extraction).
|
|
409
|
+
*/
|
|
410
|
+
export function findNodesByIds(db, ids) {
|
|
411
|
+
if (ids.length === 0)
|
|
412
|
+
return [];
|
|
413
|
+
const ph = ids.map(() => '?').join(',');
|
|
414
|
+
return db.prepare(`SELECT * FROM nodes WHERE id IN (${ph})`).all(...ids);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get ALL steps for multiple processes at once.
|
|
418
|
+
* Returns flattened list of steps across all requested processes.
|
|
419
|
+
*/
|
|
420
|
+
export function batchGetProcessSteps(db, processIds) {
|
|
421
|
+
if (processIds.length === 0)
|
|
422
|
+
return [];
|
|
423
|
+
const ph = processIds.map(() => '?').join(',');
|
|
424
|
+
const rows = db.prepare(`
|
|
425
|
+
SELECT e.targetId AS processId, n.id AS nodeId, n.name, n.label,
|
|
426
|
+
n.filePath, n.startLine, e.step
|
|
427
|
+
FROM edges e
|
|
428
|
+
JOIN nodes n ON n.id = e.sourceId
|
|
429
|
+
WHERE e.targetId IN (${ph}) AND e.type = 'STEP_IN_PROCESS'
|
|
430
|
+
ORDER BY e.targetId, e.step ASC
|
|
431
|
+
`).all(...processIds);
|
|
432
|
+
return rows.map(r => ({
|
|
433
|
+
processId: toNodeId(r.processId),
|
|
434
|
+
nodeId: toNodeId(r.nodeId),
|
|
435
|
+
name: r.name,
|
|
436
|
+
label: r.label,
|
|
437
|
+
filePath: r.filePath,
|
|
438
|
+
startLine: r.startLine,
|
|
439
|
+
step: r.step,
|
|
440
|
+
}));
|
|
441
|
+
}
|