@optave/codegraph 3.1.4 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -72
- package/package.json +10 -8
- package/src/ast-analysis/engine.js +260 -246
- package/src/ast-analysis/shared.js +2 -14
- package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
- package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
- package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
- package/src/cli/commands/ast.js +4 -7
- package/src/cli/commands/audit.js +11 -11
- package/src/cli/commands/batch.js +6 -5
- package/src/cli/commands/branch-compare.js +1 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/build.js +1 -1
- package/src/cli/commands/cfg.js +5 -8
- package/src/cli/commands/check.js +28 -36
- package/src/cli/commands/children.js +9 -7
- package/src/cli/commands/co-change.js +5 -3
- package/src/cli/commands/communities.js +2 -6
- package/src/cli/commands/complexity.js +5 -3
- package/src/cli/commands/context.js +9 -8
- package/src/cli/commands/cycles.js +12 -8
- package/src/cli/commands/dataflow.js +5 -8
- package/src/cli/commands/deps.js +9 -8
- package/src/cli/commands/diff-impact.js +2 -6
- package/src/cli/commands/embed.js +1 -1
- package/src/cli/commands/export.js +34 -31
- package/src/cli/commands/exports.js +2 -6
- package/src/cli/commands/flow.js +5 -8
- package/src/cli/commands/fn-impact.js +9 -8
- package/src/cli/commands/impact.js +2 -6
- package/src/cli/commands/info.js +2 -2
- package/src/cli/commands/map.js +1 -1
- package/src/cli/commands/mcp.js +1 -1
- package/src/cli/commands/models.js +1 -1
- package/src/cli/commands/owners.js +5 -3
- package/src/cli/commands/path.js +2 -2
- package/src/cli/commands/plot.js +40 -31
- package/src/cli/commands/query.js +9 -8
- package/src/cli/commands/registry.js +2 -2
- package/src/cli/commands/roles.js +5 -8
- package/src/cli/commands/search.js +9 -3
- package/src/cli/commands/sequence.js +5 -8
- package/src/cli/commands/snapshot.js +6 -1
- package/src/cli/commands/stats.js +1 -1
- package/src/cli/commands/structure.js +5 -4
- package/src/cli/commands/triage.js +41 -30
- package/src/cli/commands/watch.js +1 -1
- package/src/cli/commands/where.js +2 -6
- package/src/cli/index.js +11 -5
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +22 -2
- package/src/cli.js +1 -1
- package/src/db/connection.js +140 -11
- package/src/{db.js → db/index.js} +12 -5
- package/src/db/migrations.js +42 -65
- package/src/db/query-builder.js +72 -9
- package/src/db/repository/base.js +1 -1
- package/src/db/repository/graph-read.js +3 -3
- package/src/db/repository/in-memory-repository.js +30 -28
- package/src/db/repository/nodes.js +10 -17
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +392 -0
- package/src/domain/analysis/dependencies.js +395 -0
- package/src/{analysis → domain/analysis}/exports.js +11 -6
- package/src/domain/analysis/impact.js +581 -0
- package/src/domain/analysis/module-map.js +348 -0
- package/src/{analysis → domain/analysis}/roles.js +12 -9
- package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
- package/src/{builder → domain/graph/builder}/helpers.js +4 -4
- package/src/{builder → domain/graph/builder}/incremental.js +119 -93
- package/src/domain/graph/builder/pipeline.js +156 -0
- package/src/domain/graph/builder/stages/build-edges.js +376 -0
- package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
- package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
- package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
- package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
- package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
- package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/{cycles.js → domain/graph/cycles.js} +4 -4
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +2 -2
- package/src/{watcher.js → domain/graph/watcher.js} +7 -7
- package/src/{parser.js → domain/parser.js} +24 -15
- package/src/{queries.js → domain/queries.js} +17 -16
- package/src/{embeddings → domain/search}/generator.js +3 -3
- package/src/{embeddings → domain/search}/models.js +2 -2
- package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
- package/src/{embeddings → domain/search}/search/filters.js +9 -5
- package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
- package/src/{embeddings → domain/search}/search/keyword.js +13 -6
- package/src/{embeddings → domain/search}/search/prepare.js +15 -7
- package/src/{embeddings → domain/search}/search/semantic.js +1 -1
- package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
- package/src/extractors/csharp.js +224 -207
- package/src/extractors/go.js +176 -172
- package/src/extractors/hcl.js +94 -78
- package/src/extractors/java.js +213 -207
- package/src/extractors/javascript.js +275 -305
- package/src/extractors/php.js +234 -221
- package/src/extractors/python.js +252 -250
- package/src/extractors/ruby.js +192 -185
- package/src/extractors/rust.js +182 -167
- package/src/{ast.js → features/ast.js} +13 -11
- package/src/{audit.js → features/audit.js} +20 -46
- package/src/{batch.js → features/batch.js} +5 -5
- package/src/{boundaries.js → features/boundaries.js} +100 -85
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +141 -150
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +72 -57
- package/src/{complexity.js → features/complexity.js} +154 -143
- package/src/{dataflow.js → features/dataflow.js} +155 -158
- package/src/{export.js → features/export.js} +6 -6
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/{viewer.js → features/graph-enrichment.js} +8 -8
- package/src/{manifesto.js → features/manifesto.js} +15 -12
- package/src/{owners.js → features/owners.js} +6 -5
- package/src/features/sequence.js +300 -0
- package/src/features/shared/find-nodes.js +31 -0
- package/src/{snapshot.js → features/snapshot.js} +3 -3
- package/src/{structure.js → features/structure.js} +139 -108
- package/src/features/triage.js +141 -0
- package/src/graph/builders/dependency.js +33 -14
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.cjs +16 -0
- package/src/index.js +40 -39
- package/src/{native.js → infrastructure/native.js} +1 -1
- package/src/mcp/middleware.js +1 -1
- package/src/mcp/server.js +68 -59
- package/src/mcp/tool-registry.js +15 -2
- package/src/mcp/tools/ast-query.js +1 -1
- package/src/mcp/tools/audit.js +1 -1
- package/src/mcp/tools/batch-query.js +1 -1
- package/src/mcp/tools/branch-compare.js +3 -1
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/cfg.js +1 -1
- package/src/mcp/tools/check.js +3 -3
- package/src/mcp/tools/co-changes.js +1 -1
- package/src/mcp/tools/code-owners.js +1 -1
- package/src/mcp/tools/communities.js +1 -1
- package/src/mcp/tools/complexity.js +1 -1
- package/src/mcp/tools/dataflow.js +2 -2
- package/src/mcp/tools/execution-flow.js +2 -2
- package/src/mcp/tools/export-graph.js +2 -2
- package/src/mcp/tools/find-cycles.js +2 -2
- package/src/mcp/tools/index.js +2 -0
- package/src/mcp/tools/list-repos.js +1 -1
- package/src/mcp/tools/sequence.js +1 -1
- package/src/mcp/tools/structure.js +1 -1
- package/src/mcp/tools/triage.js +2 -2
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/presentation/brief.js +51 -0
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +2 -2
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +1 -1
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +53 -0
- package/src/presentation/queries-cli/impact.js +214 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +329 -0
- package/src/presentation/queries-cli/overview.js +196 -0
- package/src/presentation/queries-cli/path.js +65 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +126 -3
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +1 -1
- package/src/presentation/table.js +0 -8
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/file-utils.js +2 -2
- package/src/shared/generators.js +9 -5
- package/src/shared/hierarchy.js +1 -1
- package/src/{kinds.js → shared/kinds.js} +1 -1
- package/src/analysis/context.js +0 -408
- package/src/analysis/dependencies.js +0 -341
- package/src/analysis/impact.js +0 -463
- package/src/analysis/module-map.js +0 -322
- package/src/builder/pipeline.js +0 -130
- package/src/builder/stages/build-edges.js +0 -297
- package/src/builder/stages/insert-nodes.js +0 -195
- package/src/mcp.js +0 -2
- package/src/queries-cli.js +0 -866
- package/src/sequence.js +0 -289
- package/src/triage.js +0 -126
- /package/src/{builder → domain/graph/builder}/context.js +0 -0
- /package/src/{builder.js → domain/graph/builder.js} +0 -0
- /package/src/{embeddings → domain/search}/index.js +0 -0
- /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
- /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{errors.js → shared/errors.js} +0 -0
- /package/src/{paginate.js → shared/paginate.js} +0 -0
|
@@ -1,77 +1,45 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { isTestFile } from '
|
|
5
|
-
import {
|
|
6
|
-
import { paginateResult } from '
|
|
7
|
-
|
|
8
|
-
// ─── Build-time
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const insertNode = db.prepare(
|
|
22
|
-
'INSERT OR IGNORE INTO nodes (name, kind, file, line, end_line) VALUES (?, ?, ?, ?, ?)',
|
|
23
|
-
);
|
|
24
|
-
const getNodeIdStmt = {
|
|
25
|
-
get: (name, kind, file, line) => {
|
|
26
|
-
const id = getNodeId(db, name, kind, file, line);
|
|
27
|
-
return id != null ? { id } : undefined;
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
const insertEdge = db.prepare(
|
|
31
|
-
'INSERT INTO edges (source_id, target_id, kind, confidence, dynamic) VALUES (?, ?, ?, ?, ?)',
|
|
32
|
-
);
|
|
33
|
-
const upsertMetric = db.prepare(`
|
|
34
|
-
INSERT OR REPLACE INTO node_metrics
|
|
35
|
-
(node_id, line_count, symbol_count, import_count, export_count, fan_in, fan_out, cohesion, file_count)
|
|
36
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
37
|
-
`);
|
|
38
|
-
|
|
39
|
-
const isIncremental = changedFiles != null && changedFiles.length > 0;
|
|
2
|
+
import { getNodeId, openReadonlyOrFail, testFilterSQL } from '../db/index.js';
|
|
3
|
+
import { debug } from '../infrastructure/logger.js';
|
|
4
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
5
|
+
import { normalizePath } from '../shared/constants.js';
|
|
6
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
7
|
+
|
|
8
|
+
// ─── Build-time helpers ───────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function getAncestorDirs(filePaths) {
|
|
11
|
+
const dirs = new Set();
|
|
12
|
+
for (const f of filePaths) {
|
|
13
|
+
let d = normalizePath(path.dirname(f));
|
|
14
|
+
while (d && d !== '.') {
|
|
15
|
+
dirs.add(d);
|
|
16
|
+
d = normalizePath(path.dirname(d));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return dirs;
|
|
20
|
+
}
|
|
40
21
|
|
|
22
|
+
function cleanupPreviousData(db, getNodeIdStmt, isIncremental, changedFiles) {
|
|
41
23
|
if (isIncremental) {
|
|
42
|
-
|
|
43
|
-
const affectedDirs = new Set();
|
|
44
|
-
for (const f of changedFiles) {
|
|
45
|
-
let d = normalizePath(path.dirname(f));
|
|
46
|
-
while (d && d !== '.') {
|
|
47
|
-
affectedDirs.add(d);
|
|
48
|
-
d = normalizePath(path.dirname(d));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
24
|
+
const affectedDirs = getAncestorDirs(changedFiles);
|
|
51
25
|
const deleteContainsForDir = db.prepare(
|
|
52
26
|
"DELETE FROM edges WHERE kind = 'contains' AND source_id IN (SELECT id FROM nodes WHERE name = ? AND kind = 'directory')",
|
|
53
27
|
);
|
|
54
28
|
const deleteMetricForNode = db.prepare('DELETE FROM node_metrics WHERE node_id = ?');
|
|
55
29
|
db.transaction(() => {
|
|
56
|
-
// Delete contains edges only from affected directories
|
|
57
30
|
for (const dir of affectedDirs) {
|
|
58
31
|
deleteContainsForDir.run(dir);
|
|
59
32
|
}
|
|
60
|
-
// Delete metrics for changed files
|
|
61
33
|
for (const f of changedFiles) {
|
|
62
34
|
const fileRow = getNodeIdStmt.get(f, 'file', f, 0);
|
|
63
35
|
if (fileRow) deleteMetricForNode.run(fileRow.id);
|
|
64
36
|
}
|
|
65
|
-
// Delete metrics for affected directories
|
|
66
37
|
for (const dir of affectedDirs) {
|
|
67
38
|
const dirRow = getNodeIdStmt.get(dir, 'directory', dir, 0);
|
|
68
39
|
if (dirRow) deleteMetricForNode.run(dirRow.id);
|
|
69
40
|
}
|
|
70
41
|
})();
|
|
71
42
|
} else {
|
|
72
|
-
// Full rebuild: clean previous directory nodes/edges (idempotent)
|
|
73
|
-
// Scope contains-edge delete to directory-sourced edges only,
|
|
74
|
-
// preserving symbol-level contains edges (file→def, class→method, etc.)
|
|
75
43
|
db.exec(`
|
|
76
44
|
DELETE FROM edges WHERE kind = 'contains'
|
|
77
45
|
AND source_id IN (SELECT id FROM nodes WHERE kind = 'directory');
|
|
@@ -79,8 +47,9 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
79
47
|
DELETE FROM nodes WHERE kind = 'directory';
|
|
80
48
|
`);
|
|
81
49
|
}
|
|
50
|
+
}
|
|
82
51
|
|
|
83
|
-
|
|
52
|
+
function collectAllDirectories(directories, fileSymbols) {
|
|
84
53
|
const allDirs = new Set();
|
|
85
54
|
for (const dir of directories) {
|
|
86
55
|
let d = dir;
|
|
@@ -89,7 +58,6 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
89
58
|
d = normalizePath(path.dirname(d));
|
|
90
59
|
}
|
|
91
60
|
}
|
|
92
|
-
// Also add dirs derived from file paths
|
|
93
61
|
for (const relPath of fileSymbols.keys()) {
|
|
94
62
|
let d = normalizePath(path.dirname(relPath));
|
|
95
63
|
while (d && d !== '.') {
|
|
@@ -97,37 +65,17 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
97
65
|
d = normalizePath(path.dirname(d));
|
|
98
66
|
}
|
|
99
67
|
}
|
|
68
|
+
return allDirs;
|
|
69
|
+
}
|
|
100
70
|
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
insertNode.run(dir, 'directory', dir, 0, null);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
insertDirs();
|
|
108
|
-
|
|
109
|
-
// Step 3: Insert 'contains' edges (dir → file, dir → subdirectory)
|
|
110
|
-
// On incremental, only re-insert for affected directories (others are intact)
|
|
111
|
-
const affectedDirs = isIncremental
|
|
112
|
-
? (() => {
|
|
113
|
-
const dirs = new Set();
|
|
114
|
-
for (const f of changedFiles) {
|
|
115
|
-
let d = normalizePath(path.dirname(f));
|
|
116
|
-
while (d && d !== '.') {
|
|
117
|
-
dirs.add(d);
|
|
118
|
-
d = normalizePath(path.dirname(d));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return dirs;
|
|
122
|
-
})()
|
|
123
|
-
: null;
|
|
71
|
+
function insertContainsEdges(db, insertEdge, getNodeIdStmt, fileSymbols, allDirs, changedFiles) {
|
|
72
|
+
const isIncremental = changedFiles != null && changedFiles.length > 0;
|
|
73
|
+
const affectedDirs = isIncremental ? getAncestorDirs(changedFiles) : null;
|
|
124
74
|
|
|
125
|
-
|
|
126
|
-
// dir → file
|
|
75
|
+
db.transaction(() => {
|
|
127
76
|
for (const relPath of fileSymbols.keys()) {
|
|
128
77
|
const dir = normalizePath(path.dirname(relPath));
|
|
129
78
|
if (!dir || dir === '.') continue;
|
|
130
|
-
// On incremental, skip dirs whose contains edges are intact
|
|
131
79
|
if (affectedDirs && !affectedDirs.has(dir)) continue;
|
|
132
80
|
const dirRow = getNodeIdStmt.get(dir, 'directory', dir, 0);
|
|
133
81
|
const fileRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
@@ -135,11 +83,9 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
135
83
|
insertEdge.run(dirRow.id, fileRow.id, 'contains', 1.0, 0);
|
|
136
84
|
}
|
|
137
85
|
}
|
|
138
|
-
// dir → subdirectory
|
|
139
86
|
for (const dir of allDirs) {
|
|
140
87
|
const parent = normalizePath(path.dirname(dir));
|
|
141
88
|
if (!parent || parent === '.' || parent === dir) continue;
|
|
142
|
-
// On incremental, skip parent dirs whose contains edges are intact
|
|
143
89
|
if (affectedDirs && !affectedDirs.has(parent)) continue;
|
|
144
90
|
const parentRow = getNodeIdStmt.get(parent, 'directory', parent, 0);
|
|
145
91
|
const childRow = getNodeIdStmt.get(dir, 'directory', dir, 0);
|
|
@@ -147,11 +93,10 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
147
93
|
insertEdge.run(parentRow.id, childRow.id, 'contains', 1.0, 0);
|
|
148
94
|
}
|
|
149
95
|
}
|
|
150
|
-
});
|
|
151
|
-
|
|
96
|
+
})();
|
|
97
|
+
}
|
|
152
98
|
|
|
153
|
-
|
|
154
|
-
// Pre-compute fan-in/fan-out per file from import edges
|
|
99
|
+
function computeImportEdgeMaps(db) {
|
|
155
100
|
const fanInMap = new Map();
|
|
156
101
|
const fanOutMap = new Map();
|
|
157
102
|
const importEdges = db
|
|
@@ -169,14 +114,24 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
169
114
|
fanOutMap.set(source_file, (fanOutMap.get(source_file) || 0) + 1);
|
|
170
115
|
fanInMap.set(target_file, (fanInMap.get(target_file) || 0) + 1);
|
|
171
116
|
}
|
|
117
|
+
return { fanInMap, fanOutMap, importEdges };
|
|
118
|
+
}
|
|
172
119
|
|
|
173
|
-
|
|
120
|
+
function computeFileMetrics(
|
|
121
|
+
db,
|
|
122
|
+
upsertMetric,
|
|
123
|
+
getNodeIdStmt,
|
|
124
|
+
fileSymbols,
|
|
125
|
+
lineCountMap,
|
|
126
|
+
fanInMap,
|
|
127
|
+
fanOutMap,
|
|
128
|
+
) {
|
|
129
|
+
db.transaction(() => {
|
|
174
130
|
for (const [relPath, symbols] of fileSymbols) {
|
|
175
131
|
const fileRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
176
132
|
if (!fileRow) continue;
|
|
177
133
|
|
|
178
134
|
const lineCount = lineCountMap.get(relPath) || 0;
|
|
179
|
-
// Deduplicate definitions by name+kind+line
|
|
180
135
|
const seen = new Set();
|
|
181
136
|
let symbolCount = 0;
|
|
182
137
|
for (const d of symbols.definitions) {
|
|
@@ -203,11 +158,17 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
203
158
|
null,
|
|
204
159
|
);
|
|
205
160
|
}
|
|
206
|
-
});
|
|
207
|
-
|
|
161
|
+
})();
|
|
162
|
+
}
|
|
208
163
|
|
|
209
|
-
|
|
210
|
-
|
|
164
|
+
function computeDirectoryMetrics(
|
|
165
|
+
db,
|
|
166
|
+
upsertMetric,
|
|
167
|
+
getNodeIdStmt,
|
|
168
|
+
fileSymbols,
|
|
169
|
+
allDirs,
|
|
170
|
+
importEdges,
|
|
171
|
+
) {
|
|
211
172
|
const dirFiles = new Map();
|
|
212
173
|
for (const dir of allDirs) {
|
|
213
174
|
dirFiles.set(dir, []);
|
|
@@ -222,7 +183,6 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
222
183
|
}
|
|
223
184
|
}
|
|
224
185
|
|
|
225
|
-
// Build reverse index: file → set of ancestor directories (O(files × depth))
|
|
226
186
|
const fileToAncestorDirs = new Map();
|
|
227
187
|
for (const [dir, files] of dirFiles) {
|
|
228
188
|
for (const f of files) {
|
|
@@ -231,7 +191,6 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
231
191
|
}
|
|
232
192
|
}
|
|
233
193
|
|
|
234
|
-
// Single O(E) pass: pre-aggregate edge counts per directory
|
|
235
194
|
const dirEdgeCounts = new Map();
|
|
236
195
|
for (const dir of allDirs) {
|
|
237
196
|
dirEdgeCounts.set(dir, { intra: 0, fanIn: 0, fanOut: 0 });
|
|
@@ -241,7 +200,6 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
241
200
|
const tgtDirs = fileToAncestorDirs.get(target_file);
|
|
242
201
|
if (!srcDirs && !tgtDirs) continue;
|
|
243
202
|
|
|
244
|
-
// For each directory that contains the source file
|
|
245
203
|
if (srcDirs) {
|
|
246
204
|
for (const dir of srcDirs) {
|
|
247
205
|
const counts = dirEdgeCounts.get(dir);
|
|
@@ -253,10 +211,9 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
253
211
|
}
|
|
254
212
|
}
|
|
255
213
|
}
|
|
256
|
-
// For each directory that contains the target but NOT the source
|
|
257
214
|
if (tgtDirs) {
|
|
258
215
|
for (const dir of tgtDirs) {
|
|
259
|
-
if (srcDirs?.has(dir)) continue;
|
|
216
|
+
if (srcDirs?.has(dir)) continue;
|
|
260
217
|
const counts = dirEdgeCounts.get(dir);
|
|
261
218
|
if (!counts) continue;
|
|
262
219
|
counts.fanIn++;
|
|
@@ -264,7 +221,7 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
264
221
|
}
|
|
265
222
|
}
|
|
266
223
|
|
|
267
|
-
|
|
224
|
+
db.transaction(() => {
|
|
268
225
|
for (const [dir, files] of dirFiles) {
|
|
269
226
|
const dirRow = getNodeIdStmt.get(dir, 'directory', dir, 0);
|
|
270
227
|
if (!dirRow) continue;
|
|
@@ -286,7 +243,6 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
286
243
|
}
|
|
287
244
|
}
|
|
288
245
|
|
|
289
|
-
// O(1) lookup from pre-aggregated edge counts
|
|
290
246
|
const counts = dirEdgeCounts.get(dir) || { intra: 0, fanIn: 0, fanOut: 0 };
|
|
291
247
|
const totalEdges = counts.intra + counts.fanIn + counts.fanOut;
|
|
292
248
|
const cohesion = totalEdges > 0 ? counts.intra / totalEdges : null;
|
|
@@ -303,19 +259,77 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
|
|
|
303
259
|
fileCount,
|
|
304
260
|
);
|
|
305
261
|
}
|
|
306
|
-
});
|
|
307
|
-
|
|
262
|
+
})();
|
|
263
|
+
}
|
|
308
264
|
|
|
309
|
-
|
|
310
|
-
|
|
265
|
+
// ─── Build-time: insert directory nodes, contains edges, and metrics ────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Build directory structure nodes, containment edges, and compute metrics.
|
|
269
|
+
* Called from builder.js after edge building.
|
|
270
|
+
*
|
|
271
|
+
* @param {import('better-sqlite3').Database} db - Open read-write database
|
|
272
|
+
* @param {Map<string, object>} fileSymbols - Map of relPath → { definitions, imports, exports, calls }
|
|
273
|
+
* @param {string} rootDir - Absolute root directory
|
|
274
|
+
* @param {Map<string, number>} lineCountMap - Map of relPath → line count
|
|
275
|
+
* @param {Set<string>} directories - Set of relative directory paths
|
|
276
|
+
*/
|
|
277
|
+
export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, directories, changedFiles) {
|
|
278
|
+
const insertNode = db.prepare(
|
|
279
|
+
'INSERT OR IGNORE INTO nodes (name, kind, file, line, end_line) VALUES (?, ?, ?, ?, ?)',
|
|
280
|
+
);
|
|
281
|
+
const getNodeIdStmt = {
|
|
282
|
+
get: (name, kind, file, line) => {
|
|
283
|
+
const id = getNodeId(db, name, kind, file, line);
|
|
284
|
+
return id != null ? { id } : undefined;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
const insertEdge = db.prepare(
|
|
288
|
+
'INSERT INTO edges (source_id, target_id, kind, confidence, dynamic) VALUES (?, ?, ?, ?, ?)',
|
|
289
|
+
);
|
|
290
|
+
const upsertMetric = db.prepare(`
|
|
291
|
+
INSERT OR REPLACE INTO node_metrics
|
|
292
|
+
(node_id, line_count, symbol_count, import_count, export_count, fan_in, fan_out, cohesion, file_count)
|
|
293
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
294
|
+
`);
|
|
295
|
+
|
|
296
|
+
const isIncremental = changedFiles != null && changedFiles.length > 0;
|
|
297
|
+
|
|
298
|
+
cleanupPreviousData(db, getNodeIdStmt, isIncremental, changedFiles);
|
|
299
|
+
|
|
300
|
+
const allDirs = collectAllDirectories(directories, fileSymbols);
|
|
301
|
+
|
|
302
|
+
db.transaction(() => {
|
|
303
|
+
for (const dir of allDirs) {
|
|
304
|
+
insertNode.run(dir, 'directory', dir, 0, null);
|
|
305
|
+
}
|
|
306
|
+
})();
|
|
307
|
+
|
|
308
|
+
insertContainsEdges(db, insertEdge, getNodeIdStmt, fileSymbols, allDirs, changedFiles);
|
|
309
|
+
|
|
310
|
+
const { fanInMap, fanOutMap, importEdges } = computeImportEdgeMaps(db);
|
|
311
|
+
|
|
312
|
+
computeFileMetrics(
|
|
313
|
+
db,
|
|
314
|
+
upsertMetric,
|
|
315
|
+
getNodeIdStmt,
|
|
316
|
+
fileSymbols,
|
|
317
|
+
lineCountMap,
|
|
318
|
+
fanInMap,
|
|
319
|
+
fanOutMap,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
computeDirectoryMetrics(db, upsertMetric, getNodeIdStmt, fileSymbols, allDirs, importEdges);
|
|
323
|
+
|
|
324
|
+
debug(`Structure: ${allDirs.size} directories, ${fileSymbols.size} files with metrics`);
|
|
311
325
|
}
|
|
312
326
|
|
|
313
327
|
// ─── Node role classification ─────────────────────────────────────────
|
|
314
328
|
|
|
315
329
|
// Re-export from classifier for backward compatibility
|
|
316
|
-
export { FRAMEWORK_ENTRY_PREFIXES } from '
|
|
330
|
+
export { FRAMEWORK_ENTRY_PREFIXES } from '../graph/classifiers/roles.js';
|
|
317
331
|
|
|
318
|
-
import { classifyRoles } from '
|
|
332
|
+
import { classifyRoles } from '../graph/classifiers/roles.js';
|
|
319
333
|
|
|
320
334
|
export function classifyNodeRoles(db) {
|
|
321
335
|
const rows = db
|
|
@@ -335,7 +349,7 @@ export function classifyNodeRoles(db) {
|
|
|
335
349
|
.all();
|
|
336
350
|
|
|
337
351
|
if (rows.length === 0) {
|
|
338
|
-
return { entry: 0, core: 0, utility: 0, adapter: 0, dead: 0, leaf: 0 };
|
|
352
|
+
return { entry: 0, core: 0, utility: 0, adapter: 0, dead: 0, 'test-only': 0, leaf: 0 };
|
|
339
353
|
}
|
|
340
354
|
|
|
341
355
|
const exportedIds = new Set(
|
|
@@ -351,6 +365,22 @@ export function classifyNodeRoles(db) {
|
|
|
351
365
|
.map((r) => r.target_id),
|
|
352
366
|
);
|
|
353
367
|
|
|
368
|
+
// Compute production fan-in (excluding callers in test files)
|
|
369
|
+
const prodFanInMap = new Map();
|
|
370
|
+
const prodRows = db
|
|
371
|
+
.prepare(
|
|
372
|
+
`SELECT e.target_id, COUNT(*) AS cnt
|
|
373
|
+
FROM edges e
|
|
374
|
+
JOIN nodes caller ON e.source_id = caller.id
|
|
375
|
+
WHERE e.kind = 'calls'
|
|
376
|
+
${testFilterSQL('caller.file')}
|
|
377
|
+
GROUP BY e.target_id`,
|
|
378
|
+
)
|
|
379
|
+
.all();
|
|
380
|
+
for (const r of prodRows) {
|
|
381
|
+
prodFanInMap.set(r.target_id, r.cnt);
|
|
382
|
+
}
|
|
383
|
+
|
|
354
384
|
// Delegate classification to the pure-logic classifier
|
|
355
385
|
const classifierInput = rows.map((r) => ({
|
|
356
386
|
id: String(r.id),
|
|
@@ -358,12 +388,13 @@ export function classifyNodeRoles(db) {
|
|
|
358
388
|
fanIn: r.fan_in,
|
|
359
389
|
fanOut: r.fan_out,
|
|
360
390
|
isExported: exportedIds.has(r.id),
|
|
391
|
+
productionFanIn: prodFanInMap.get(r.id) || 0,
|
|
361
392
|
}));
|
|
362
393
|
|
|
363
394
|
const roleMap = classifyRoles(classifierInput);
|
|
364
395
|
|
|
365
396
|
// Build summary and updates
|
|
366
|
-
const summary = { entry: 0, core: 0, utility: 0, adapter: 0, dead: 0, leaf: 0 };
|
|
397
|
+
const summary = { entry: 0, core: 0, utility: 0, adapter: 0, dead: 0, 'test-only': 0, leaf: 0 };
|
|
367
398
|
const updates = [];
|
|
368
399
|
for (const row of rows) {
|
|
369
400
|
const role = roleMap.get(String(row.id)) || 'leaf';
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { openRepo } from '../db/index.js';
|
|
2
|
+
import { DEFAULT_WEIGHTS, scoreRisk } from '../graph/classifiers/risk.js';
|
|
3
|
+
import { warn } from '../infrastructure/logger.js';
|
|
4
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
5
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
6
|
+
|
|
7
|
+
// ─── Scoring ─────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const SORT_FNS = {
|
|
10
|
+
risk: (a, b) => b.riskScore - a.riskScore,
|
|
11
|
+
complexity: (a, b) => b.cognitive - a.cognitive,
|
|
12
|
+
churn: (a, b) => b.churn - a.churn,
|
|
13
|
+
'fan-in': (a, b) => b.fanIn - a.fanIn,
|
|
14
|
+
mi: (a, b) => a.maintainabilityIndex - b.maintainabilityIndex,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build scored triage items from raw rows and risk metrics.
|
|
19
|
+
* @param {object[]} rows - Raw DB rows
|
|
20
|
+
* @param {object[]} riskMetrics - Per-row risk metric objects from scoreRisk
|
|
21
|
+
* @returns {object[]}
|
|
22
|
+
*/
|
|
23
|
+
function buildTriageItems(rows, riskMetrics) {
|
|
24
|
+
return rows.map((r, i) => ({
|
|
25
|
+
name: r.name,
|
|
26
|
+
kind: r.kind,
|
|
27
|
+
file: r.file,
|
|
28
|
+
line: r.line,
|
|
29
|
+
role: r.role || null,
|
|
30
|
+
fanIn: r.fan_in,
|
|
31
|
+
cognitive: r.cognitive,
|
|
32
|
+
churn: r.churn,
|
|
33
|
+
maintainabilityIndex: r.mi,
|
|
34
|
+
normFanIn: riskMetrics[i].normFanIn,
|
|
35
|
+
normComplexity: riskMetrics[i].normComplexity,
|
|
36
|
+
normChurn: riskMetrics[i].normChurn,
|
|
37
|
+
normMI: riskMetrics[i].normMI,
|
|
38
|
+
roleWeight: riskMetrics[i].roleWeight,
|
|
39
|
+
riskScore: riskMetrics[i].riskScore,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compute signal coverage and summary statistics.
|
|
45
|
+
* @param {object[]} filtered - All filtered rows
|
|
46
|
+
* @param {object[]} scored - Scored and filtered items
|
|
47
|
+
* @param {object} weights - Active weights
|
|
48
|
+
* @returns {object}
|
|
49
|
+
*/
|
|
50
|
+
function computeTriageSummary(filtered, scored, weights) {
|
|
51
|
+
const signalCoverage = {
|
|
52
|
+
complexity: round4(filtered.filter((r) => r.cognitive > 0).length / filtered.length),
|
|
53
|
+
churn: round4(filtered.filter((r) => r.churn > 0).length / filtered.length),
|
|
54
|
+
fanIn: round4(filtered.filter((r) => r.fan_in > 0).length / filtered.length),
|
|
55
|
+
mi: round4(filtered.filter((r) => r.mi > 0).length / filtered.length),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const scores = scored.map((it) => it.riskScore);
|
|
59
|
+
const avgScore =
|
|
60
|
+
scores.length > 0 ? round4(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
|
61
|
+
const maxScore = scores.length > 0 ? round4(Math.max(...scores)) : 0;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
total: filtered.length,
|
|
65
|
+
analyzed: scored.length,
|
|
66
|
+
avgScore,
|
|
67
|
+
maxScore,
|
|
68
|
+
weights,
|
|
69
|
+
signalCoverage,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Data Function ────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
const EMPTY_SUMMARY = (weights) => ({
|
|
76
|
+
total: 0,
|
|
77
|
+
analyzed: 0,
|
|
78
|
+
avgScore: 0,
|
|
79
|
+
maxScore: 0,
|
|
80
|
+
weights,
|
|
81
|
+
signalCoverage: {},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Compute composite risk scores for all symbols.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} [customDbPath] - Path to graph.db
|
|
88
|
+
* @param {object} [opts]
|
|
89
|
+
* @returns {{ items: object[], summary: object, _pagination?: object }}
|
|
90
|
+
*/
|
|
91
|
+
export function triageData(customDbPath, opts = {}) {
|
|
92
|
+
const { repo, close } = openRepo(customDbPath, opts);
|
|
93
|
+
try {
|
|
94
|
+
const noTests = opts.noTests || false;
|
|
95
|
+
const minScore = opts.minScore != null ? Number(opts.minScore) : null;
|
|
96
|
+
const sort = opts.sort || 'risk';
|
|
97
|
+
const weights = { ...DEFAULT_WEIGHTS, ...(opts.weights || {}) };
|
|
98
|
+
|
|
99
|
+
let rows;
|
|
100
|
+
try {
|
|
101
|
+
rows = repo.findNodesForTriage({
|
|
102
|
+
noTests,
|
|
103
|
+
file: opts.file || null,
|
|
104
|
+
kind: opts.kind || null,
|
|
105
|
+
role: opts.role || null,
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
warn(`triage query failed: ${err.message}`);
|
|
109
|
+
return { items: [], summary: EMPTY_SUMMARY(weights) };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
|
|
113
|
+
if (filtered.length === 0) {
|
|
114
|
+
return { items: [], summary: EMPTY_SUMMARY(weights) };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const riskMetrics = scoreRisk(filtered, weights);
|
|
118
|
+
const items = buildTriageItems(filtered, riskMetrics);
|
|
119
|
+
|
|
120
|
+
const scored = minScore != null ? items.filter((it) => it.riskScore >= minScore) : items;
|
|
121
|
+
scored.sort(SORT_FNS[sort] || SORT_FNS.risk);
|
|
122
|
+
|
|
123
|
+
const result = {
|
|
124
|
+
items: scored,
|
|
125
|
+
summary: computeTriageSummary(filtered, scored, weights),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return paginateResult(result, 'items', {
|
|
129
|
+
limit: opts.limit,
|
|
130
|
+
offset: opts.offset,
|
|
131
|
+
});
|
|
132
|
+
} finally {
|
|
133
|
+
close();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Utilities ────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
function round4(n) {
|
|
140
|
+
return Math.round(n * 10000) / 10000;
|
|
141
|
+
}
|
|
@@ -3,32 +3,39 @@
|
|
|
3
3
|
* Replaces inline graph construction in cycles.js, communities.js, viewer.js, export.js.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getCallableNodes,
|
|
8
|
+
getCallEdges,
|
|
9
|
+
getFileNodesAll,
|
|
10
|
+
getImportEdges,
|
|
11
|
+
Repository,
|
|
12
|
+
} from '../../db/index.js';
|
|
7
13
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
8
14
|
import { CodeGraph } from '../model.js';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
|
-
* @param {object}
|
|
17
|
+
* @param {object} dbOrRepo - Open better-sqlite3 database (readonly) or a Repository instance
|
|
12
18
|
* @param {object} [opts]
|
|
13
19
|
* @param {boolean} [opts.fileLevel=true] - File-level (imports) or function-level (calls)
|
|
14
20
|
* @param {boolean} [opts.noTests=false] - Exclude test files
|
|
15
21
|
* @param {number} [opts.minConfidence] - Minimum edge confidence (function-level only)
|
|
16
22
|
* @returns {CodeGraph}
|
|
17
23
|
*/
|
|
18
|
-
export function buildDependencyGraph(
|
|
24
|
+
export function buildDependencyGraph(dbOrRepo, opts = {}) {
|
|
19
25
|
const fileLevel = opts.fileLevel !== false;
|
|
20
26
|
const noTests = opts.noTests || false;
|
|
21
27
|
|
|
22
28
|
if (fileLevel) {
|
|
23
|
-
return buildFileLevelGraph(
|
|
29
|
+
return buildFileLevelGraph(dbOrRepo, noTests);
|
|
24
30
|
}
|
|
25
|
-
return buildFunctionLevelGraph(
|
|
31
|
+
return buildFunctionLevelGraph(dbOrRepo, noTests, opts.minConfidence);
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
function buildFileLevelGraph(
|
|
34
|
+
function buildFileLevelGraph(dbOrRepo, noTests) {
|
|
29
35
|
const graph = new CodeGraph();
|
|
36
|
+
const isRepo = dbOrRepo instanceof Repository;
|
|
30
37
|
|
|
31
|
-
let nodes = getFileNodesAll(
|
|
38
|
+
let nodes = isRepo ? dbOrRepo.getFileNodesAll() : getFileNodesAll(dbOrRepo);
|
|
32
39
|
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
33
40
|
|
|
34
41
|
const nodeIds = new Set();
|
|
@@ -37,7 +44,7 @@ function buildFileLevelGraph(db, noTests) {
|
|
|
37
44
|
nodeIds.add(n.id);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
|
-
const edges = getImportEdges(
|
|
47
|
+
const edges = isRepo ? dbOrRepo.getImportEdges() : getImportEdges(dbOrRepo);
|
|
41
48
|
for (const e of edges) {
|
|
42
49
|
if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
|
|
43
50
|
const src = String(e.source_id);
|
|
@@ -51,10 +58,11 @@ function buildFileLevelGraph(db, noTests) {
|
|
|
51
58
|
return graph;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
function buildFunctionLevelGraph(
|
|
61
|
+
function buildFunctionLevelGraph(dbOrRepo, noTests, minConfidence) {
|
|
55
62
|
const graph = new CodeGraph();
|
|
63
|
+
const isRepo = dbOrRepo instanceof Repository;
|
|
56
64
|
|
|
57
|
-
let nodes = getCallableNodes(
|
|
65
|
+
let nodes = isRepo ? dbOrRepo.getCallableNodes() : getCallableNodes(dbOrRepo);
|
|
58
66
|
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
59
67
|
|
|
60
68
|
const nodeIds = new Set();
|
|
@@ -70,11 +78,22 @@ function buildFunctionLevelGraph(db, noTests, minConfidence) {
|
|
|
70
78
|
|
|
71
79
|
let edges;
|
|
72
80
|
if (minConfidence != null) {
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
.all
|
|
81
|
+
if (isRepo) {
|
|
82
|
+
// Trade-off: Repository.getCallEdges() returns all call edges, so we
|
|
83
|
+
// filter in JS. This is O(all call edges) rather than the SQL path's
|
|
84
|
+
// indexed WHERE clause. Acceptable for current data sizes; a dedicated
|
|
85
|
+
// getCallEdgesByMinConfidence(threshold) method on the Repository
|
|
86
|
+
// interface would be the proper fix if this becomes a bottleneck.
|
|
87
|
+
edges = dbOrRepo
|
|
88
|
+
.getCallEdges()
|
|
89
|
+
.filter((e) => e.confidence != null && e.confidence >= minConfidence);
|
|
90
|
+
} else {
|
|
91
|
+
edges = dbOrRepo
|
|
92
|
+
.prepare("SELECT source_id, target_id FROM edges WHERE kind = 'calls' AND confidence >= ?")
|
|
93
|
+
.all(minConfidence);
|
|
94
|
+
}
|
|
76
95
|
} else {
|
|
77
|
-
edges = getCallEdges(
|
|
96
|
+
edges = isRepo ? dbOrRepo.getCallEdges() : getCallEdges(dbOrRepo);
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
for (const e of edges) {
|
|
@@ -16,14 +16,15 @@ export const DEFAULT_WEIGHTS = {
|
|
|
16
16
|
|
|
17
17
|
// Role weights reflect structural importance: core modules are central to the
|
|
18
18
|
// dependency graph, utilities are widely imported, entry points are API
|
|
19
|
-
// surfaces. Adapters bridge subsystems but are replaceable. Leaves
|
|
20
|
-
// code have minimal downstream impact.
|
|
19
|
+
// surfaces. Adapters bridge subsystems but are replaceable. Leaves, dead
|
|
20
|
+
// code, and test-only symbols have minimal downstream impact.
|
|
21
21
|
export const ROLE_WEIGHTS = {
|
|
22
22
|
core: 1.0,
|
|
23
23
|
utility: 0.9,
|
|
24
24
|
entry: 0.8,
|
|
25
25
|
adapter: 0.5,
|
|
26
26
|
leaf: 0.2,
|
|
27
|
+
'test-only': 0.1,
|
|
27
28
|
dead: 0.1,
|
|
28
29
|
};
|
|
29
30
|
|