@optave/codegraph 3.1.3 → 3.1.4
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 +17 -19
- package/package.json +10 -7
- package/src/analysis/context.js +408 -0
- package/src/analysis/dependencies.js +341 -0
- package/src/analysis/exports.js +130 -0
- package/src/analysis/impact.js +463 -0
- package/src/analysis/module-map.js +322 -0
- package/src/analysis/roles.js +45 -0
- package/src/analysis/symbol-lookup.js +232 -0
- package/src/ast-analysis/shared.js +5 -4
- package/src/batch.js +2 -1
- package/src/builder/context.js +85 -0
- package/src/builder/helpers.js +218 -0
- package/src/builder/incremental.js +178 -0
- package/src/builder/pipeline.js +130 -0
- package/src/builder/stages/build-edges.js +297 -0
- package/src/builder/stages/build-structure.js +113 -0
- package/src/builder/stages/collect-files.js +44 -0
- package/src/builder/stages/detect-changes.js +413 -0
- package/src/builder/stages/finalize.js +139 -0
- package/src/builder/stages/insert-nodes.js +195 -0
- package/src/builder/stages/parse-files.js +28 -0
- package/src/builder/stages/resolve-imports.js +143 -0
- package/src/builder/stages/run-analyses.js +44 -0
- package/src/builder.js +10 -1485
- package/src/cfg.js +1 -2
- package/src/cli/commands/ast.js +26 -0
- package/src/cli/commands/audit.js +46 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +30 -0
- package/src/cli/commands/check.js +79 -0
- package/src/cli/commands/children.js +31 -0
- package/src/cli/commands/co-change.js +65 -0
- package/src/cli/commands/communities.js +23 -0
- package/src/cli/commands/complexity.js +45 -0
- package/src/cli/commands/context.js +34 -0
- package/src/cli/commands/cycles.js +28 -0
- package/src/cli/commands/dataflow.js +32 -0
- package/src/cli/commands/deps.js +16 -0
- package/src/cli/commands/diff-impact.js +30 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +75 -0
- package/src/cli/commands/exports.js +18 -0
- package/src/cli/commands/flow.js +36 -0
- package/src/cli/commands/fn-impact.js +30 -0
- package/src/cli/commands/impact.js +16 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +80 -0
- package/src/cli/commands/query.js +49 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +34 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +32 -0
- package/src/cli/commands/snapshot.js +61 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +32 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +24 -0
- package/src/cli/index.js +118 -0
- package/src/cli/shared/options.js +39 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1522
- package/src/commands/check.js +5 -5
- package/src/commands/manifesto.js +3 -3
- package/src/commands/structure.js +1 -1
- package/src/communities.js +15 -87
- package/src/cycles.js +30 -85
- package/src/dataflow.js +1 -2
- package/src/db/connection.js +4 -4
- package/src/db/migrations.js +41 -0
- package/src/db/query-builder.js +6 -5
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/graph-read.js +5 -2
- package/src/db/repository/in-memory-repository.js +584 -0
- package/src/db/repository/index.js +5 -1
- package/src/db/repository/nodes.js +63 -4
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/db.js +5 -0
- package/src/embeddings/generator.js +163 -0
- package/src/embeddings/index.js +13 -0
- package/src/embeddings/models.js +218 -0
- package/src/embeddings/search/cli-formatter.js +151 -0
- package/src/embeddings/search/filters.js +46 -0
- package/src/embeddings/search/hybrid.js +121 -0
- package/src/embeddings/search/keyword.js +68 -0
- package/src/embeddings/search/prepare.js +66 -0
- package/src/embeddings/search/semantic.js +145 -0
- package/src/embeddings/stores/fts5.js +27 -0
- package/src/embeddings/stores/sqlite-blob.js +24 -0
- package/src/embeddings/strategies/source.js +14 -0
- package/src/embeddings/strategies/structured.js +43 -0
- package/src/embeddings/strategies/text-utils.js +43 -0
- package/src/errors.js +78 -0
- package/src/export.js +217 -520
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +38 -1
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +91 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.js +33 -210
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/mcp/tool-registry.js +801 -0
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +10 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/mcp.js +2 -1470
- package/src/native.js +3 -1
- package/src/presentation/colors.js +44 -0
- package/src/presentation/export.js +444 -0
- package/src/presentation/result-formatter.js +21 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/presentation/table.js +47 -0
- package/src/presentation/viewer.js +634 -0
- package/src/queries.js +35 -2276
- package/src/resolve.js +1 -1
- package/src/sequence.js +2 -38
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/snapshot.js +6 -5
- package/src/structure.js +15 -40
- package/src/triage.js +20 -72
- package/src/viewer.js +35 -656
- package/src/watcher.js +8 -148
- package/src/embedder.js +0 -1097
package/src/extractors/csharp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from C# files.
|
|
@@ -133,6 +133,7 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
133
133
|
line: node.startPosition.row + 1,
|
|
134
134
|
endLine: nodeEndLine(node),
|
|
135
135
|
children: params.length > 0 ? params : undefined,
|
|
136
|
+
visibility: extractModifierVisibility(node),
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
break;
|
|
@@ -150,6 +151,7 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
150
151
|
line: node.startPosition.row + 1,
|
|
151
152
|
endLine: nodeEndLine(node),
|
|
152
153
|
children: params.length > 0 ? params : undefined,
|
|
154
|
+
visibility: extractModifierVisibility(node),
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
157
|
break;
|
|
@@ -165,6 +167,7 @@ export function extractCSharpSymbols(tree, _filePath) {
|
|
|
165
167
|
kind: 'property',
|
|
166
168
|
line: node.startPosition.row + 1,
|
|
167
169
|
endLine: nodeEndLine(node),
|
|
170
|
+
visibility: extractModifierVisibility(node),
|
|
168
171
|
});
|
|
169
172
|
}
|
|
170
173
|
break;
|
|
@@ -260,7 +263,12 @@ function extractCSharpClassFields(classNode) {
|
|
|
260
263
|
if (!child || child.type !== 'variable_declarator') continue;
|
|
261
264
|
const nameNode = child.childForFieldName('name');
|
|
262
265
|
if (nameNode) {
|
|
263
|
-
fields.push({
|
|
266
|
+
fields.push({
|
|
267
|
+
name: nameNode.text,
|
|
268
|
+
kind: 'property',
|
|
269
|
+
line: member.startPosition.row + 1,
|
|
270
|
+
visibility: extractModifierVisibility(member),
|
|
271
|
+
});
|
|
264
272
|
}
|
|
265
273
|
}
|
|
266
274
|
}
|
package/src/extractors/go.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { findChild, goVisibility, nodeEndLine } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from Go files.
|
|
@@ -22,6 +22,7 @@ export function extractGoSymbols(tree, _filePath) {
|
|
|
22
22
|
line: node.startPosition.row + 1,
|
|
23
23
|
endLine: nodeEndLine(node),
|
|
24
24
|
children: params.length > 0 ? params : undefined,
|
|
25
|
+
visibility: goVisibility(nameNode.text),
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
break;
|
|
@@ -55,6 +56,7 @@ export function extractGoSymbols(tree, _filePath) {
|
|
|
55
56
|
line: node.startPosition.row + 1,
|
|
56
57
|
endLine: nodeEndLine(node),
|
|
57
58
|
children: params.length > 0 ? params : undefined,
|
|
59
|
+
visibility: goVisibility(nameNode.text),
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
break;
|
|
@@ -9,3 +9,74 @@ export function findChild(node, type) {
|
|
|
9
9
|
}
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract visibility from a node by scanning its children for modifier keywords.
|
|
15
|
+
* Works for Java, C#, PHP, and similar languages where modifiers are child nodes.
|
|
16
|
+
* @param {object} node - tree-sitter node
|
|
17
|
+
* @param {Set<string>} [modifierTypes] - node types that indicate modifiers
|
|
18
|
+
* @returns {'public'|'private'|'protected'|undefined}
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_MODIFIER_TYPES = new Set([
|
|
21
|
+
'modifiers',
|
|
22
|
+
'modifier',
|
|
23
|
+
'visibility_modifier',
|
|
24
|
+
'accessibility_modifier',
|
|
25
|
+
]);
|
|
26
|
+
const VISIBILITY_KEYWORDS = new Set(['public', 'private', 'protected']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Python convention: __name → private, _name → protected, else undefined.
|
|
30
|
+
*/
|
|
31
|
+
export function pythonVisibility(name) {
|
|
32
|
+
if (name.startsWith('__') && name.endsWith('__')) return undefined; // dunder — public
|
|
33
|
+
if (name.startsWith('__')) return 'private';
|
|
34
|
+
if (name.startsWith('_')) return 'protected';
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Go convention: uppercase first letter → public, lowercase → private.
|
|
40
|
+
*/
|
|
41
|
+
export function goVisibility(name) {
|
|
42
|
+
if (!name) return undefined;
|
|
43
|
+
// Strip receiver prefix (e.g., "Receiver.Method" → check "Method")
|
|
44
|
+
const bare = name.includes('.') ? name.split('.').pop() : name;
|
|
45
|
+
if (!bare) return undefined;
|
|
46
|
+
return bare[0] === bare[0].toUpperCase() && bare[0] !== bare[0].toLowerCase()
|
|
47
|
+
? 'public'
|
|
48
|
+
: 'private';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Rust: check for `visibility_modifier` child (pub, pub(crate), etc.).
|
|
53
|
+
*/
|
|
54
|
+
export function rustVisibility(node) {
|
|
55
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
56
|
+
const child = node.child(i);
|
|
57
|
+
if (!child) continue;
|
|
58
|
+
if (child.type === 'visibility_modifier') {
|
|
59
|
+
return 'public'; // pub, pub(crate), pub(super) all mean "visible"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return 'private';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function extractModifierVisibility(node, modifierTypes = DEFAULT_MODIFIER_TYPES) {
|
|
66
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
67
|
+
const child = node.child(i);
|
|
68
|
+
if (!child) continue;
|
|
69
|
+
// Direct keyword match (e.g., PHP visibility_modifier = "public")
|
|
70
|
+
if (modifierTypes.has(child.type)) {
|
|
71
|
+
const text = child.text;
|
|
72
|
+
if (VISIBILITY_KEYWORDS.has(text)) return text;
|
|
73
|
+
// C# 'private protected' — accessible to derived types in same assembly → protected
|
|
74
|
+
if (text === 'private protected') return 'protected';
|
|
75
|
+
// Compound modifiers node (Java: "public static") — scan its text for a keyword
|
|
76
|
+
for (const kw of VISIBILITY_KEYWORDS) {
|
|
77
|
+
if (text.includes(kw)) return kw;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
package/src/extractors/java.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from Java files.
|
|
@@ -165,6 +165,7 @@ export function extractJavaSymbols(tree, _filePath) {
|
|
|
165
165
|
line: node.startPosition.row + 1,
|
|
166
166
|
endLine: nodeEndLine(node),
|
|
167
167
|
children: params.length > 0 ? params : undefined,
|
|
168
|
+
visibility: extractModifierVisibility(node),
|
|
168
169
|
});
|
|
169
170
|
}
|
|
170
171
|
break;
|
|
@@ -182,6 +183,7 @@ export function extractJavaSymbols(tree, _filePath) {
|
|
|
182
183
|
line: node.startPosition.row + 1,
|
|
183
184
|
endLine: nodeEndLine(node),
|
|
184
185
|
children: params.length > 0 ? params : undefined,
|
|
186
|
+
visibility: extractModifierVisibility(node),
|
|
185
187
|
});
|
|
186
188
|
}
|
|
187
189
|
break;
|
|
@@ -267,7 +269,12 @@ function extractClassFields(classNode) {
|
|
|
267
269
|
if (!child || child.type !== 'variable_declarator') continue;
|
|
268
270
|
const nameNode = child.childForFieldName('name');
|
|
269
271
|
if (nameNode) {
|
|
270
|
-
fields.push({
|
|
272
|
+
fields.push({
|
|
273
|
+
name: nameNode.text,
|
|
274
|
+
kind: 'property',
|
|
275
|
+
line: member.startPosition.row + 1,
|
|
276
|
+
visibility: extractModifierVisibility(member),
|
|
277
|
+
});
|
|
271
278
|
}
|
|
272
279
|
}
|
|
273
280
|
}
|
|
@@ -77,12 +77,14 @@ function extractSymbolsQuery(tree, query) {
|
|
|
77
77
|
const parentClass = findParentClass(c.meth_node);
|
|
78
78
|
const fullName = parentClass ? `${parentClass}.${methName}` : methName;
|
|
79
79
|
const methChildren = extractParameters(c.meth_node);
|
|
80
|
+
const methVis = extractVisibility(c.meth_node);
|
|
80
81
|
definitions.push({
|
|
81
82
|
name: fullName,
|
|
82
83
|
kind: 'method',
|
|
83
84
|
line: c.meth_node.startPosition.row + 1,
|
|
84
85
|
endLine: nodeEndLine(c.meth_node),
|
|
85
86
|
children: methChildren.length > 0 ? methChildren : undefined,
|
|
87
|
+
visibility: methVis,
|
|
86
88
|
});
|
|
87
89
|
} else if (c.iface_node) {
|
|
88
90
|
// interface_declaration (TS/TSX only)
|
|
@@ -375,12 +377,14 @@ function extractSymbolsWalk(tree) {
|
|
|
375
377
|
const parentClass = findParentClass(node);
|
|
376
378
|
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
377
379
|
const methChildren = extractParameters(node);
|
|
380
|
+
const methVis = extractVisibility(node);
|
|
378
381
|
definitions.push({
|
|
379
382
|
name: fullName,
|
|
380
383
|
kind: 'method',
|
|
381
384
|
line: node.startPosition.row + 1,
|
|
382
385
|
endLine: nodeEndLine(node),
|
|
383
386
|
children: methChildren.length > 0 ? methChildren : undefined,
|
|
387
|
+
visibility: methVis,
|
|
384
388
|
});
|
|
385
389
|
}
|
|
386
390
|
break;
|
|
@@ -701,13 +705,46 @@ function extractClassProperties(classNode) {
|
|
|
701
705
|
nameNode.type === 'identifier' ||
|
|
702
706
|
nameNode.type === 'private_property_identifier')
|
|
703
707
|
) {
|
|
704
|
-
|
|
708
|
+
// Private # fields: nameNode.type is 'private_property_identifier'
|
|
709
|
+
// TS modifiers: accessibility_modifier child on the field_definition
|
|
710
|
+
const vis =
|
|
711
|
+
nameNode.type === 'private_property_identifier' ? 'private' : extractVisibility(child);
|
|
712
|
+
props.push({
|
|
713
|
+
name: nameNode.text,
|
|
714
|
+
kind: 'property',
|
|
715
|
+
line: child.startPosition.row + 1,
|
|
716
|
+
visibility: vis,
|
|
717
|
+
});
|
|
705
718
|
}
|
|
706
719
|
}
|
|
707
720
|
}
|
|
708
721
|
return props;
|
|
709
722
|
}
|
|
710
723
|
|
|
724
|
+
/**
|
|
725
|
+
* Extract visibility modifier from a class member node.
|
|
726
|
+
* Checks for TS access modifiers (public/private/protected) and JS private (#) fields.
|
|
727
|
+
* Returns 'public' | 'private' | 'protected' | undefined.
|
|
728
|
+
*/
|
|
729
|
+
function extractVisibility(node) {
|
|
730
|
+
// Check for TS accessibility modifiers (accessibility_modifier child)
|
|
731
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
732
|
+
const child = node.child(i);
|
|
733
|
+
if (!child) continue;
|
|
734
|
+
if (child.type === 'accessibility_modifier') {
|
|
735
|
+
const text = child.text;
|
|
736
|
+
if (text === 'private' || text === 'protected' || text === 'public') return text;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Check for JS private name (# prefix) — try multiple field names
|
|
740
|
+
const nameNode =
|
|
741
|
+
node.childForFieldName('name') || node.childForFieldName('property') || node.child(0);
|
|
742
|
+
if (nameNode && nameNode.type === 'private_property_identifier') {
|
|
743
|
+
return 'private';
|
|
744
|
+
}
|
|
745
|
+
return undefined;
|
|
746
|
+
}
|
|
747
|
+
|
|
711
748
|
function isConstantValue(valueNode) {
|
|
712
749
|
if (!valueNode) return false;
|
|
713
750
|
const t = valueNode.type;
|
package/src/extractors/php.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
function extractPhpParameters(fnNode) {
|
|
4
4
|
const params = [];
|
|
@@ -35,6 +35,7 @@ function extractPhpClassChildren(classNode) {
|
|
|
35
35
|
name: varNode.text,
|
|
36
36
|
kind: 'property',
|
|
37
37
|
line: member.startPosition.row + 1,
|
|
38
|
+
visibility: extractModifierVisibility(member),
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
}
|
|
@@ -231,6 +232,7 @@ export function extractPHPSymbols(tree, _filePath) {
|
|
|
231
232
|
line: node.startPosition.row + 1,
|
|
232
233
|
endLine: nodeEndLine(node),
|
|
233
234
|
children: params.length > 0 ? params : undefined,
|
|
235
|
+
visibility: extractModifierVisibility(node),
|
|
234
236
|
});
|
|
235
237
|
}
|
|
236
238
|
break;
|
package/src/extractors/python.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { findChild, nodeEndLine, pythonVisibility } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from Python files.
|
|
@@ -30,6 +30,7 @@ export function extractPythonSymbols(tree, _filePath) {
|
|
|
30
30
|
endLine: nodeEndLine(node),
|
|
31
31
|
decorators,
|
|
32
32
|
children: fnChildren.length > 0 ? fnChildren : undefined,
|
|
33
|
+
visibility: pythonVisibility(nameNode.text),
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
break;
|
|
@@ -209,7 +210,12 @@ export function extractPythonSymbols(tree, _filePath) {
|
|
|
209
210
|
const left = assignment.childForFieldName('left');
|
|
210
211
|
if (left && left.type === 'identifier' && !seen.has(left.text)) {
|
|
211
212
|
seen.add(left.text);
|
|
212
|
-
props.push({
|
|
213
|
+
props.push({
|
|
214
|
+
name: left.text,
|
|
215
|
+
kind: 'property',
|
|
216
|
+
line: child.startPosition.row + 1,
|
|
217
|
+
visibility: pythonVisibility(left.text),
|
|
218
|
+
});
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
}
|
|
@@ -262,7 +268,12 @@ export function extractPythonSymbols(tree, _filePath) {
|
|
|
262
268
|
!seen.has(attr.text)
|
|
263
269
|
) {
|
|
264
270
|
seen.add(attr.text);
|
|
265
|
-
props.push({
|
|
271
|
+
props.push({
|
|
272
|
+
name: attr.text,
|
|
273
|
+
kind: 'property',
|
|
274
|
+
line: stmt.startPosition.row + 1,
|
|
275
|
+
visibility: pythonVisibility(attr.text),
|
|
276
|
+
});
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
}
|
package/src/extractors/rust.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findChild, nodeEndLine } from './helpers.js';
|
|
1
|
+
import { findChild, nodeEndLine, rustVisibility } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from Rust files.
|
|
@@ -37,6 +37,7 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
37
37
|
line: node.startPosition.row + 1,
|
|
38
38
|
endLine: nodeEndLine(node),
|
|
39
39
|
children: params.length > 0 ? params : undefined,
|
|
40
|
+
visibility: rustVisibility(node),
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
break;
|
|
@@ -52,6 +53,7 @@ export function extractRustSymbols(tree, _filePath) {
|
|
|
52
53
|
line: node.startPosition.row + 1,
|
|
53
54
|
endLine: nodeEndLine(node),
|
|
54
55
|
children: fields.length > 0 ? fields : undefined,
|
|
56
|
+
visibility: rustVisibility(node),
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
break;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breadth-first traversal on a CodeGraph.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('../model.js').CodeGraph} graph
|
|
5
|
+
* @param {string|string[]} startIds - One or more starting node IDs
|
|
6
|
+
* @param {{ maxDepth?: number, direction?: 'forward'|'backward'|'both' }} [opts]
|
|
7
|
+
* @returns {Map<string, number>} nodeId → depth from nearest start node
|
|
8
|
+
*/
|
|
9
|
+
export function bfs(graph, startIds, opts = {}) {
|
|
10
|
+
const maxDepth = opts.maxDepth ?? Infinity;
|
|
11
|
+
const direction = opts.direction ?? 'forward';
|
|
12
|
+
const starts = Array.isArray(startIds) ? startIds : [startIds];
|
|
13
|
+
|
|
14
|
+
const depths = new Map();
|
|
15
|
+
const queue = [];
|
|
16
|
+
|
|
17
|
+
for (const id of starts) {
|
|
18
|
+
const key = String(id);
|
|
19
|
+
if (graph.hasNode(key)) {
|
|
20
|
+
depths.set(key, 0);
|
|
21
|
+
queue.push(key);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let head = 0;
|
|
26
|
+
while (head < queue.length) {
|
|
27
|
+
const current = queue[head++];
|
|
28
|
+
const depth = depths.get(current);
|
|
29
|
+
if (depth >= maxDepth) continue;
|
|
30
|
+
|
|
31
|
+
let neighbors;
|
|
32
|
+
if (direction === 'forward') {
|
|
33
|
+
neighbors = graph.successors(current);
|
|
34
|
+
} else if (direction === 'backward') {
|
|
35
|
+
neighbors = graph.predecessors(current);
|
|
36
|
+
} else {
|
|
37
|
+
neighbors = graph.neighbors(current);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const n of neighbors) {
|
|
41
|
+
if (!depths.has(n)) {
|
|
42
|
+
depths.set(n, depth + 1);
|
|
43
|
+
queue.push(n);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return depths;
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fan-in / fan-out centrality for all nodes in a CodeGraph.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('../model.js').CodeGraph} graph
|
|
5
|
+
* @returns {Map<string, { fanIn: number, fanOut: number }>}
|
|
6
|
+
*/
|
|
7
|
+
export function fanInOut(graph) {
|
|
8
|
+
const result = new Map();
|
|
9
|
+
for (const id of graph.nodeIds()) {
|
|
10
|
+
result.set(id, {
|
|
11
|
+
fanIn: graph.inDegree(id),
|
|
12
|
+
fanOut: graph.outDegree(id),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Louvain community detection via graphology.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('../model.js').CodeGraph} graph
|
|
5
|
+
* @param {{ resolution?: number }} [opts]
|
|
6
|
+
* @returns {{ assignments: Map<string, number>, modularity: number }}
|
|
7
|
+
*/
|
|
8
|
+
import graphologyLouvain from 'graphology-communities-louvain';
|
|
9
|
+
|
|
10
|
+
export function louvainCommunities(graph, opts = {}) {
|
|
11
|
+
const gy = graph.toGraphology({ type: 'undirected' });
|
|
12
|
+
|
|
13
|
+
if (gy.order === 0 || gy.size === 0) {
|
|
14
|
+
return { assignments: new Map(), modularity: 0 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const resolution = opts.resolution ?? 1.0;
|
|
18
|
+
const details = graphologyLouvain.detailed(gy, { resolution });
|
|
19
|
+
|
|
20
|
+
const assignments = new Map();
|
|
21
|
+
for (const [nodeId, communityId] of Object.entries(details.communities)) {
|
|
22
|
+
assignments.set(nodeId, communityId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { assignments, modularity: details.modularity };
|
|
26
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFS-based shortest path on a CodeGraph.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('../model.js').CodeGraph} graph
|
|
5
|
+
* @param {string} fromId
|
|
6
|
+
* @param {string} toId
|
|
7
|
+
* @returns {string[]|null} Path from fromId to toId (inclusive), or null if unreachable
|
|
8
|
+
*/
|
|
9
|
+
export function shortestPath(graph, fromId, toId) {
|
|
10
|
+
const from = String(fromId);
|
|
11
|
+
const to = String(toId);
|
|
12
|
+
|
|
13
|
+
if (!graph.hasNode(from) || !graph.hasNode(to)) return null;
|
|
14
|
+
if (from === to) return [from];
|
|
15
|
+
|
|
16
|
+
const parent = new Map();
|
|
17
|
+
parent.set(from, null);
|
|
18
|
+
const queue = [from];
|
|
19
|
+
let head = 0;
|
|
20
|
+
|
|
21
|
+
while (head < queue.length) {
|
|
22
|
+
const current = queue[head++];
|
|
23
|
+
for (const neighbor of graph.successors(current)) {
|
|
24
|
+
if (parent.has(neighbor)) continue;
|
|
25
|
+
parent.set(neighbor, current);
|
|
26
|
+
if (neighbor === to) {
|
|
27
|
+
// Reconstruct path
|
|
28
|
+
const path = [];
|
|
29
|
+
let node = to;
|
|
30
|
+
while (node !== null) {
|
|
31
|
+
path.push(node);
|
|
32
|
+
node = parent.get(node);
|
|
33
|
+
}
|
|
34
|
+
return path.reverse();
|
|
35
|
+
}
|
|
36
|
+
queue.push(neighbor);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tarjan's strongly connected components algorithm.
|
|
3
|
+
* Operates on a CodeGraph instance.
|
|
4
|
+
*
|
|
5
|
+
* @param {import('../model.js').CodeGraph} graph
|
|
6
|
+
* @returns {string[][]} SCCs with length > 1 (cycles)
|
|
7
|
+
*/
|
|
8
|
+
export function tarjan(graph) {
|
|
9
|
+
let index = 0;
|
|
10
|
+
const stack = [];
|
|
11
|
+
const onStack = new Set();
|
|
12
|
+
const indices = new Map();
|
|
13
|
+
const lowlinks = new Map();
|
|
14
|
+
const sccs = [];
|
|
15
|
+
|
|
16
|
+
function strongconnect(v) {
|
|
17
|
+
indices.set(v, index);
|
|
18
|
+
lowlinks.set(v, index);
|
|
19
|
+
index++;
|
|
20
|
+
stack.push(v);
|
|
21
|
+
onStack.add(v);
|
|
22
|
+
|
|
23
|
+
for (const w of graph.successors(v)) {
|
|
24
|
+
if (!indices.has(w)) {
|
|
25
|
+
strongconnect(w);
|
|
26
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
27
|
+
} else if (onStack.has(w)) {
|
|
28
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
33
|
+
const scc = [];
|
|
34
|
+
let w;
|
|
35
|
+
do {
|
|
36
|
+
w = stack.pop();
|
|
37
|
+
onStack.delete(w);
|
|
38
|
+
scc.push(w);
|
|
39
|
+
} while (w !== v);
|
|
40
|
+
if (scc.length > 1) sccs.push(scc);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const id of graph.nodeIds()) {
|
|
45
|
+
if (!indices.has(id)) strongconnect(id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return sccs;
|
|
49
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a CodeGraph from the SQLite database.
|
|
3
|
+
* Replaces inline graph construction in cycles.js, communities.js, viewer.js, export.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getCallableNodes, getCallEdges, getFileNodesAll, getImportEdges } from '../../db.js';
|
|
7
|
+
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
8
|
+
import { CodeGraph } from '../model.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} db - Open better-sqlite3 database (readonly)
|
|
12
|
+
* @param {object} [opts]
|
|
13
|
+
* @param {boolean} [opts.fileLevel=true] - File-level (imports) or function-level (calls)
|
|
14
|
+
* @param {boolean} [opts.noTests=false] - Exclude test files
|
|
15
|
+
* @param {number} [opts.minConfidence] - Minimum edge confidence (function-level only)
|
|
16
|
+
* @returns {CodeGraph}
|
|
17
|
+
*/
|
|
18
|
+
export function buildDependencyGraph(db, opts = {}) {
|
|
19
|
+
const fileLevel = opts.fileLevel !== false;
|
|
20
|
+
const noTests = opts.noTests || false;
|
|
21
|
+
|
|
22
|
+
if (fileLevel) {
|
|
23
|
+
return buildFileLevelGraph(db, noTests);
|
|
24
|
+
}
|
|
25
|
+
return buildFunctionLevelGraph(db, noTests, opts.minConfidence);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildFileLevelGraph(db, noTests) {
|
|
29
|
+
const graph = new CodeGraph();
|
|
30
|
+
|
|
31
|
+
let nodes = getFileNodesAll(db);
|
|
32
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
33
|
+
|
|
34
|
+
const nodeIds = new Set();
|
|
35
|
+
for (const n of nodes) {
|
|
36
|
+
graph.addNode(String(n.id), { label: n.file, file: n.file, dbId: n.id });
|
|
37
|
+
nodeIds.add(n.id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const edges = getImportEdges(db);
|
|
41
|
+
for (const e of edges) {
|
|
42
|
+
if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
|
|
43
|
+
const src = String(e.source_id);
|
|
44
|
+
const tgt = String(e.target_id);
|
|
45
|
+
if (src === tgt) continue;
|
|
46
|
+
if (!graph.hasEdge(src, tgt)) {
|
|
47
|
+
graph.addEdge(src, tgt, { kind: 'imports' });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return graph;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildFunctionLevelGraph(db, noTests, minConfidence) {
|
|
55
|
+
const graph = new CodeGraph();
|
|
56
|
+
|
|
57
|
+
let nodes = getCallableNodes(db);
|
|
58
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
59
|
+
|
|
60
|
+
const nodeIds = new Set();
|
|
61
|
+
for (const n of nodes) {
|
|
62
|
+
graph.addNode(String(n.id), {
|
|
63
|
+
label: n.name,
|
|
64
|
+
file: n.file,
|
|
65
|
+
kind: n.kind,
|
|
66
|
+
dbId: n.id,
|
|
67
|
+
});
|
|
68
|
+
nodeIds.add(n.id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let edges;
|
|
72
|
+
if (minConfidence != null) {
|
|
73
|
+
edges = db
|
|
74
|
+
.prepare("SELECT source_id, target_id FROM edges WHERE kind = 'calls' AND confidence >= ?")
|
|
75
|
+
.all(minConfidence);
|
|
76
|
+
} else {
|
|
77
|
+
edges = getCallEdges(db);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const e of edges) {
|
|
81
|
+
if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
|
|
82
|
+
const src = String(e.source_id);
|
|
83
|
+
const tgt = String(e.target_id);
|
|
84
|
+
if (src === tgt) continue;
|
|
85
|
+
if (!graph.hasEdge(src, tgt)) {
|
|
86
|
+
graph.addEdge(src, tgt, { kind: 'calls' });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return graph;
|
|
91
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a containment graph (directory → file) from the SQLite database.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CodeGraph } from '../model.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} db - Open better-sqlite3 database (readonly)
|
|
9
|
+
* @returns {CodeGraph} Directed graph with directory→file containment edges
|
|
10
|
+
*/
|
|
11
|
+
export function buildStructureGraph(db) {
|
|
12
|
+
const graph = new CodeGraph();
|
|
13
|
+
|
|
14
|
+
const dirs = db.prepare("SELECT id, name FROM nodes WHERE kind = 'directory'").all();
|
|
15
|
+
|
|
16
|
+
for (const d of dirs) {
|
|
17
|
+
graph.addNode(String(d.id), { label: d.name, kind: 'directory' });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const files = db.prepare("SELECT id, name, file FROM nodes WHERE kind = 'file'").all();
|
|
21
|
+
|
|
22
|
+
for (const f of files) {
|
|
23
|
+
graph.addNode(String(f.id), { label: f.name, kind: 'file', file: f.file });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const containsEdges = db
|
|
27
|
+
.prepare(`
|
|
28
|
+
SELECT e.source_id, e.target_id
|
|
29
|
+
FROM edges e
|
|
30
|
+
JOIN nodes n ON e.source_id = n.id
|
|
31
|
+
WHERE e.kind = 'contains' AND n.kind = 'directory'
|
|
32
|
+
`)
|
|
33
|
+
.all();
|
|
34
|
+
|
|
35
|
+
for (const e of containsEdges) {
|
|
36
|
+
graph.addEdge(String(e.source_id), String(e.target_id), { kind: 'contains' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return graph;
|
|
40
|
+
}
|