@optave/codegraph 3.1.2 → 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 +19 -21
- 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 -1472
- 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 -1514
- 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/complexity.js +1 -1
- 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/cached-stmt.js +19 -0
- package/src/db/repository/cfg.js +27 -38
- package/src/db/repository/cochange.js +16 -3
- package/src/db/repository/complexity.js +11 -6
- package/src/db/repository/dataflow.js +6 -1
- package/src/db/repository/edges.js +120 -98
- package/src/db/repository/embeddings.js +14 -3
- package/src/db/repository/graph-read.js +32 -9
- package/src/db/repository/in-memory-repository.js +584 -0
- package/src/db/repository/index.js +6 -1
- package/src/db/repository/nodes.js +110 -40
- 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 -204
- 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 +34 -10
- package/src/parser.js +53 -2
- 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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a co-change (temporal) graph weighted by Jaccard similarity.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CodeGraph } from '../model.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} db - Open better-sqlite3 database (readonly)
|
|
9
|
+
* @param {{ minJaccard?: number }} [opts]
|
|
10
|
+
* @returns {CodeGraph} Undirected graph weighted by Jaccard similarity
|
|
11
|
+
*/
|
|
12
|
+
export function buildTemporalGraph(db, opts = {}) {
|
|
13
|
+
const minJaccard = opts.minJaccard ?? 0.0;
|
|
14
|
+
const graph = new CodeGraph({ directed: false });
|
|
15
|
+
|
|
16
|
+
// Check if co_changes table exists
|
|
17
|
+
const tableCheck = db
|
|
18
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='co_changes'")
|
|
19
|
+
.get();
|
|
20
|
+
if (!tableCheck) return graph;
|
|
21
|
+
|
|
22
|
+
const rows = db
|
|
23
|
+
.prepare('SELECT file_a, file_b, jaccard FROM co_changes WHERE jaccard >= ?')
|
|
24
|
+
.all(minJaccard);
|
|
25
|
+
|
|
26
|
+
for (const r of rows) {
|
|
27
|
+
if (!graph.hasNode(r.file_a)) graph.addNode(r.file_a, { label: r.file_a });
|
|
28
|
+
if (!graph.hasNode(r.file_b)) graph.addNode(r.file_b, { label: r.file_b });
|
|
29
|
+
graph.addEdge(r.file_a, r.file_b, { jaccard: r.jaccard });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return graph;
|
|
33
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk scoring — pure logic, no DB.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Weights sum to 1.0. Complexity gets the highest weight because cognitive load
|
|
6
|
+
// is the strongest predictor of defect density. Fan-in and churn are next as
|
|
7
|
+
// they reflect coupling and volatility. Role adds architectural context, and MI
|
|
8
|
+
// (maintainability index) is a weaker composite signal, so it gets the least.
|
|
9
|
+
export const DEFAULT_WEIGHTS = {
|
|
10
|
+
fanIn: 0.25,
|
|
11
|
+
complexity: 0.3,
|
|
12
|
+
churn: 0.2,
|
|
13
|
+
role: 0.15,
|
|
14
|
+
mi: 0.1,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Role weights reflect structural importance: core modules are central to the
|
|
18
|
+
// dependency graph, utilities are widely imported, entry points are API
|
|
19
|
+
// surfaces. Adapters bridge subsystems but are replaceable. Leaves and dead
|
|
20
|
+
// code have minimal downstream impact.
|
|
21
|
+
export const ROLE_WEIGHTS = {
|
|
22
|
+
core: 1.0,
|
|
23
|
+
utility: 0.9,
|
|
24
|
+
entry: 0.8,
|
|
25
|
+
adapter: 0.5,
|
|
26
|
+
leaf: 0.2,
|
|
27
|
+
dead: 0.1,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DEFAULT_ROLE_WEIGHT = 0.5;
|
|
31
|
+
|
|
32
|
+
/** Min-max normalize an array of numbers. All-equal → all zeros. */
|
|
33
|
+
export function minMaxNormalize(values) {
|
|
34
|
+
const min = Math.min(...values);
|
|
35
|
+
const max = Math.max(...values);
|
|
36
|
+
if (max === min) return values.map(() => 0);
|
|
37
|
+
const range = max - min;
|
|
38
|
+
return values.map((v) => (v - min) / range);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function round4(n) {
|
|
42
|
+
return Math.round(n * 10000) / 10000;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Score risk for a list of items.
|
|
47
|
+
*
|
|
48
|
+
* @param {{ fan_in: number, cognitive: number, churn: number, mi: number, role: string|null }[]} items
|
|
49
|
+
* @param {object} [weights] - Override DEFAULT_WEIGHTS
|
|
50
|
+
* @returns {{ normFanIn: number, normComplexity: number, normChurn: number, normMI: number, roleWeight: number, riskScore: number }[]}
|
|
51
|
+
* Parallel array with risk metrics for each input item.
|
|
52
|
+
*/
|
|
53
|
+
export function scoreRisk(items, weights = {}) {
|
|
54
|
+
const w = { ...DEFAULT_WEIGHTS, ...weights };
|
|
55
|
+
|
|
56
|
+
const fanIns = items.map((r) => r.fan_in);
|
|
57
|
+
const cognitives = items.map((r) => r.cognitive);
|
|
58
|
+
const churns = items.map((r) => r.churn);
|
|
59
|
+
const mis = items.map((r) => r.mi);
|
|
60
|
+
|
|
61
|
+
const normFanIns = minMaxNormalize(fanIns);
|
|
62
|
+
const normCognitives = minMaxNormalize(cognitives);
|
|
63
|
+
const normChurns = minMaxNormalize(churns);
|
|
64
|
+
const normMIsRaw = minMaxNormalize(mis);
|
|
65
|
+
const normMIs = normMIsRaw.map((v) => round4(1 - v));
|
|
66
|
+
|
|
67
|
+
return items.map((r, i) => {
|
|
68
|
+
const roleWeight = ROLE_WEIGHTS[r.role] ?? DEFAULT_ROLE_WEIGHT;
|
|
69
|
+
const riskScore =
|
|
70
|
+
w.fanIn * normFanIns[i] +
|
|
71
|
+
w.complexity * normCognitives[i] +
|
|
72
|
+
w.churn * normChurns[i] +
|
|
73
|
+
w.role * roleWeight +
|
|
74
|
+
w.mi * normMIs[i];
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
normFanIn: round4(normFanIns[i]),
|
|
78
|
+
normComplexity: round4(normCognitives[i]),
|
|
79
|
+
normChurn: round4(normChurns[i]),
|
|
80
|
+
normMI: round4(normMIs[i]),
|
|
81
|
+
roleWeight,
|
|
82
|
+
riskScore: round4(riskScore),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node role classification — pure logic, no DB.
|
|
3
|
+
*
|
|
4
|
+
* Roles: entry, core, utility, adapter, leaf, dead
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const FRAMEWORK_ENTRY_PREFIXES = ['route:', 'event:', 'command:'];
|
|
8
|
+
|
|
9
|
+
function median(sorted) {
|
|
10
|
+
if (sorted.length === 0) return 0;
|
|
11
|
+
const mid = Math.floor(sorted.length / 2);
|
|
12
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Classify nodes into architectural roles based on fan-in/fan-out metrics.
|
|
17
|
+
*
|
|
18
|
+
* @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean }[]} nodes
|
|
19
|
+
* @returns {Map<string, string>} nodeId → role
|
|
20
|
+
*/
|
|
21
|
+
export function classifyRoles(nodes) {
|
|
22
|
+
if (nodes.length === 0) return new Map();
|
|
23
|
+
|
|
24
|
+
const nonZeroFanIn = nodes
|
|
25
|
+
.filter((n) => n.fanIn > 0)
|
|
26
|
+
.map((n) => n.fanIn)
|
|
27
|
+
.sort((a, b) => a - b);
|
|
28
|
+
const nonZeroFanOut = nodes
|
|
29
|
+
.filter((n) => n.fanOut > 0)
|
|
30
|
+
.map((n) => n.fanOut)
|
|
31
|
+
.sort((a, b) => a - b);
|
|
32
|
+
|
|
33
|
+
const medFanIn = median(nonZeroFanIn);
|
|
34
|
+
const medFanOut = median(nonZeroFanOut);
|
|
35
|
+
|
|
36
|
+
const result = new Map();
|
|
37
|
+
|
|
38
|
+
for (const node of nodes) {
|
|
39
|
+
const highIn = node.fanIn >= medFanIn && node.fanIn > 0;
|
|
40
|
+
const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
|
|
41
|
+
|
|
42
|
+
let role;
|
|
43
|
+
const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
|
|
44
|
+
if (isFrameworkEntry) {
|
|
45
|
+
role = 'entry';
|
|
46
|
+
} else if (node.fanIn === 0 && !node.isExported) {
|
|
47
|
+
role = 'dead';
|
|
48
|
+
} else if (node.fanIn === 0 && node.isExported) {
|
|
49
|
+
role = 'entry';
|
|
50
|
+
} else if (highIn && !highOut) {
|
|
51
|
+
role = 'core';
|
|
52
|
+
} else if (highIn && highOut) {
|
|
53
|
+
role = 'utility';
|
|
54
|
+
} else if (!highIn && highOut) {
|
|
55
|
+
role = 'adapter';
|
|
56
|
+
} else {
|
|
57
|
+
role = 'leaf';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
result.set(node.id, role);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Graph subsystem barrel export
|
|
2
|
+
|
|
3
|
+
export { bfs, fanInOut, louvainCommunities, shortestPath, tarjan } from './algorithms/index.js';
|
|
4
|
+
export { buildDependencyGraph, buildStructureGraph, buildTemporalGraph } from './builders/index.js';
|
|
5
|
+
export {
|
|
6
|
+
classifyRoles,
|
|
7
|
+
DEFAULT_WEIGHTS,
|
|
8
|
+
FRAMEWORK_ENTRY_PREFIXES,
|
|
9
|
+
minMaxNormalize,
|
|
10
|
+
ROLE_WEIGHTS,
|
|
11
|
+
scoreRisk,
|
|
12
|
+
} from './classifiers/index.js';
|
|
13
|
+
export { CodeGraph } from './model.js';
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified in-memory graph model.
|
|
3
|
+
*
|
|
4
|
+
* Stores directed (default) or undirected graphs with node/edge attributes.
|
|
5
|
+
* Node IDs are always strings. DB integer IDs should be stringified before use.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Graph from 'graphology';
|
|
9
|
+
|
|
10
|
+
export class CodeGraph {
|
|
11
|
+
/**
|
|
12
|
+
* @param {{ directed?: boolean }} [opts]
|
|
13
|
+
*/
|
|
14
|
+
constructor(opts = {}) {
|
|
15
|
+
this._directed = opts.directed !== false;
|
|
16
|
+
/** @type {Map<string, object>} */
|
|
17
|
+
this._nodes = new Map();
|
|
18
|
+
/** @type {Map<string, Map<string, object>>} node → (target → edgeAttrs) */
|
|
19
|
+
this._successors = new Map();
|
|
20
|
+
/** @type {Map<string, Map<string, object>>} node → (source → edgeAttrs) */
|
|
21
|
+
this._predecessors = new Map();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get directed() {
|
|
25
|
+
return this._directed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get nodeCount() {
|
|
29
|
+
return this._nodes.size;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get edgeCount() {
|
|
33
|
+
let count = 0;
|
|
34
|
+
for (const targets of this._successors.values()) count += targets.size;
|
|
35
|
+
// Undirected graphs store each edge twice (a→b and b→a)
|
|
36
|
+
return this._directed ? count : count / 2;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Node operations ────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
addNode(id, attrs = {}) {
|
|
42
|
+
const key = String(id);
|
|
43
|
+
this._nodes.set(key, attrs);
|
|
44
|
+
if (!this._successors.has(key)) this._successors.set(key, new Map());
|
|
45
|
+
if (!this._predecessors.has(key)) this._predecessors.set(key, new Map());
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
hasNode(id) {
|
|
50
|
+
return this._nodes.has(String(id));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getNodeAttrs(id) {
|
|
54
|
+
return this._nodes.get(String(id));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @returns {IterableIterator<[string, object]>} */
|
|
58
|
+
nodes() {
|
|
59
|
+
return this._nodes.entries();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @returns {string[]} */
|
|
63
|
+
nodeIds() {
|
|
64
|
+
return [...this._nodes.keys()];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Edge operations ────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
addEdge(source, target, attrs = {}) {
|
|
70
|
+
const src = String(source);
|
|
71
|
+
const tgt = String(target);
|
|
72
|
+
// Auto-add nodes if missing
|
|
73
|
+
if (!this._nodes.has(src)) this.addNode(src);
|
|
74
|
+
if (!this._nodes.has(tgt)) this.addNode(tgt);
|
|
75
|
+
|
|
76
|
+
this._successors.get(src).set(tgt, attrs);
|
|
77
|
+
this._predecessors.get(tgt).set(src, attrs);
|
|
78
|
+
|
|
79
|
+
if (!this._directed) {
|
|
80
|
+
this._successors.get(tgt).set(src, attrs);
|
|
81
|
+
this._predecessors.get(src).set(tgt, attrs);
|
|
82
|
+
}
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
hasEdge(source, target) {
|
|
87
|
+
const src = String(source);
|
|
88
|
+
const tgt = String(target);
|
|
89
|
+
return this._successors.has(src) && this._successors.get(src).has(tgt);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getEdgeAttrs(source, target) {
|
|
93
|
+
const src = String(source);
|
|
94
|
+
const tgt = String(target);
|
|
95
|
+
return this._successors.get(src)?.get(tgt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @yields {[string, string, object]} source, target, attrs */
|
|
99
|
+
*edges() {
|
|
100
|
+
const seen = this._directed ? null : new Set();
|
|
101
|
+
for (const [src, targets] of this._successors) {
|
|
102
|
+
for (const [tgt, attrs] of targets) {
|
|
103
|
+
if (!this._directed) {
|
|
104
|
+
// \0 is safe as separator — node IDs are file paths/symbols, never contain null bytes
|
|
105
|
+
const key = src < tgt ? `${src}\0${tgt}` : `${tgt}\0${src}`;
|
|
106
|
+
if (seen.has(key)) continue;
|
|
107
|
+
seen.add(key);
|
|
108
|
+
}
|
|
109
|
+
yield [src, tgt, attrs];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Adjacency ──────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/** Direct successors of a node (outgoing edges). */
|
|
117
|
+
successors(id) {
|
|
118
|
+
const key = String(id);
|
|
119
|
+
const map = this._successors.get(key);
|
|
120
|
+
return map ? [...map.keys()] : [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Direct predecessors of a node (incoming edges). */
|
|
124
|
+
predecessors(id) {
|
|
125
|
+
const key = String(id);
|
|
126
|
+
const map = this._predecessors.get(key);
|
|
127
|
+
return map ? [...map.keys()] : [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** All neighbors (union of successors + predecessors). */
|
|
131
|
+
neighbors(id) {
|
|
132
|
+
const key = String(id);
|
|
133
|
+
const set = new Set();
|
|
134
|
+
const succ = this._successors.get(key);
|
|
135
|
+
if (succ) for (const k of succ.keys()) set.add(k);
|
|
136
|
+
const pred = this._predecessors.get(key);
|
|
137
|
+
if (pred) for (const k of pred.keys()) set.add(k);
|
|
138
|
+
return [...set];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
outDegree(id) {
|
|
142
|
+
const map = this._successors.get(String(id));
|
|
143
|
+
return map ? map.size : 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
inDegree(id) {
|
|
147
|
+
const map = this._predecessors.get(String(id));
|
|
148
|
+
return map ? map.size : 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── Filtering ──────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/** Return a new graph containing only nodes matching the predicate. */
|
|
154
|
+
subgraph(predicate) {
|
|
155
|
+
const g = new CodeGraph({ directed: this._directed });
|
|
156
|
+
for (const [id, attrs] of this._nodes) {
|
|
157
|
+
if (predicate(id, attrs)) g.addNode(id, { ...attrs });
|
|
158
|
+
}
|
|
159
|
+
for (const [src, tgt, attrs] of this.edges()) {
|
|
160
|
+
if (g.hasNode(src) && g.hasNode(tgt)) {
|
|
161
|
+
g.addEdge(src, tgt, { ...attrs });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return g;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Return a new graph containing only edges matching the predicate. */
|
|
168
|
+
filterEdges(predicate) {
|
|
169
|
+
const g = new CodeGraph({ directed: this._directed });
|
|
170
|
+
for (const [id, attrs] of this._nodes) {
|
|
171
|
+
g.addNode(id, { ...attrs });
|
|
172
|
+
}
|
|
173
|
+
for (const [src, tgt, attrs] of this.edges()) {
|
|
174
|
+
if (predicate(src, tgt, attrs)) {
|
|
175
|
+
g.addEdge(src, tgt, { ...attrs });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return g;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Conversion ─────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/** Convert to flat edge array for native Rust interop. */
|
|
184
|
+
toEdgeArray() {
|
|
185
|
+
const result = [];
|
|
186
|
+
for (const [source, target] of this.edges()) {
|
|
187
|
+
result.push({ source, target });
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Convert to graphology instance (for Louvain etc). */
|
|
193
|
+
toGraphology(opts = {}) {
|
|
194
|
+
const type = opts.type || (this._directed ? 'directed' : 'undirected');
|
|
195
|
+
const g = new Graph({ type });
|
|
196
|
+
for (const [id] of this._nodes) {
|
|
197
|
+
g.addNode(id);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const [src, tgt] of this.edges()) {
|
|
201
|
+
if (src === tgt) continue;
|
|
202
|
+
if (!g.hasEdge(src, tgt)) g.addEdge(src, tgt);
|
|
203
|
+
}
|
|
204
|
+
return g;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ─── Utilities ──────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
clone() {
|
|
210
|
+
const g = new CodeGraph({ directed: this._directed });
|
|
211
|
+
for (const [id, attrs] of this._nodes) {
|
|
212
|
+
g.addNode(id, { ...attrs });
|
|
213
|
+
}
|
|
214
|
+
for (const [src, tgt, attrs] of this.edges()) {
|
|
215
|
+
g.addEdge(src, tgt, { ...attrs });
|
|
216
|
+
}
|
|
217
|
+
return g;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Merge another graph into this one. Nodes/edges from other override on conflict. */
|
|
221
|
+
merge(other) {
|
|
222
|
+
for (const [id, attrs] of other.nodes()) {
|
|
223
|
+
this.addNode(id, attrs);
|
|
224
|
+
}
|
|
225
|
+
for (const [src, tgt, attrs] of other.edges()) {
|
|
226
|
+
this.addEdge(src, tgt, attrs);
|
|
227
|
+
}
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,237 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* codegraph — Programmatic API
|
|
3
3
|
*
|
|
4
|
+
* Curated public surface: *Data() query functions, graph building,
|
|
5
|
+
* export formats, and essential constants. CLI formatters and internal
|
|
6
|
+
* utilities are not exported — import them directly if needed.
|
|
7
|
+
*
|
|
4
8
|
* Usage:
|
|
5
|
-
* import { buildGraph, queryNameData, findCycles, exportDOT } from 'codegraph';
|
|
9
|
+
* import { buildGraph, queryNameData, findCycles, exportDOT } from '@optave/codegraph';
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
export { AST_NODE_KINDS, astQuery, astQueryData } from './ast.js';
|
|
10
|
-
// Audit (composite report)
|
|
12
|
+
export { astQueryData } from './ast.js';
|
|
11
13
|
export { auditData } from './audit.js';
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
multiBatchData,
|
|
17
|
-
splitTargets,
|
|
18
|
-
} from './batch.js';
|
|
19
|
-
// Architecture boundary rules
|
|
20
|
-
export { evaluateBoundaries, PRESETS, validateBoundaryConfig } from './boundaries.js';
|
|
21
|
-
// Branch comparison
|
|
22
|
-
export { branchCompareData, branchCompareMermaid } from './branch-compare.js';
|
|
23
|
-
// Graph building
|
|
24
|
-
export { buildGraph, collectFiles, loadPathAliases, resolveImportPath } from './builder.js';
|
|
25
|
-
// Control flow graph (intraprocedural)
|
|
26
|
-
export {
|
|
27
|
-
buildCFGData,
|
|
28
|
-
buildFunctionCFG,
|
|
29
|
-
CFG_RULES,
|
|
30
|
-
cfgData,
|
|
31
|
-
cfgToDOT,
|
|
32
|
-
cfgToMermaid,
|
|
33
|
-
} from './cfg.js';
|
|
34
|
-
// Check (CI validation predicates)
|
|
14
|
+
export { batchData } from './batch.js';
|
|
15
|
+
export { branchCompareData } from './branch-compare.js';
|
|
16
|
+
export { buildGraph } from './builder.js';
|
|
17
|
+
export { cfgData } from './cfg.js';
|
|
35
18
|
export { checkData } from './check.js';
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
|
|
39
|
-
coChangeData,
|
|
40
|
-
coChangeForFiles,
|
|
41
|
-
coChangeTopData,
|
|
42
|
-
computeCoChanges,
|
|
43
|
-
scanGitHistory,
|
|
44
|
-
} from './cochange.js';
|
|
45
|
-
export { audit } from './commands/audit.js';
|
|
46
|
-
export { batch, batchQuery } from './commands/batch.js';
|
|
47
|
-
export { cfg } from './commands/cfg.js';
|
|
48
|
-
export { check } from './commands/check.js';
|
|
49
|
-
export { communities } from './commands/communities.js';
|
|
50
|
-
export { complexity } from './commands/complexity.js';
|
|
51
|
-
export { dataflow } from './commands/dataflow.js';
|
|
52
|
-
export { manifesto } from './commands/manifesto.js';
|
|
53
|
-
export { owners } from './commands/owners.js';
|
|
54
|
-
export { sequence } from './commands/sequence.js';
|
|
55
|
-
export { formatHotspots, formatModuleBoundaries, formatStructure } from './commands/structure.js';
|
|
56
|
-
export { triage } from './commands/triage.js';
|
|
57
|
-
// Community detection
|
|
58
|
-
export { communitiesData, communitySummaryForStats } from './communities.js';
|
|
59
|
-
// Complexity metrics
|
|
60
|
-
export {
|
|
61
|
-
COMPLEXITY_RULES,
|
|
62
|
-
complexityData,
|
|
63
|
-
computeFunctionComplexity,
|
|
64
|
-
computeHalsteadMetrics,
|
|
65
|
-
computeLOCMetrics,
|
|
66
|
-
computeMaintainabilityIndex,
|
|
67
|
-
findFunctionNode,
|
|
68
|
-
HALSTEAD_RULES,
|
|
69
|
-
iterComplexity,
|
|
70
|
-
} from './complexity.js';
|
|
71
|
-
// Configuration
|
|
19
|
+
export { coChangeData } from './cochange.js';
|
|
20
|
+
export { communitiesData } from './communities.js';
|
|
21
|
+
export { complexityData } from './complexity.js';
|
|
72
22
|
export { loadConfig } from './config.js';
|
|
73
|
-
|
|
74
|
-
export {
|
|
75
|
-
|
|
76
|
-
export { findCycles, formatCycles } from './cycles.js';
|
|
77
|
-
// Dataflow analysis
|
|
78
|
-
export {
|
|
79
|
-
buildDataflowEdges,
|
|
80
|
-
dataflowData,
|
|
81
|
-
dataflowImpactData,
|
|
82
|
-
dataflowPathData,
|
|
83
|
-
extractDataflow,
|
|
84
|
-
} from './dataflow.js';
|
|
85
|
-
// Database utilities
|
|
86
|
-
export {
|
|
87
|
-
countEdges,
|
|
88
|
-
countFiles,
|
|
89
|
-
countNodes,
|
|
90
|
-
fanInJoinSQL,
|
|
91
|
-
fanOutJoinSQL,
|
|
92
|
-
findDbPath,
|
|
93
|
-
findNodesForTriage,
|
|
94
|
-
findNodesWithFanIn,
|
|
95
|
-
getBuildMeta,
|
|
96
|
-
initSchema,
|
|
97
|
-
iterateFunctionNodes,
|
|
98
|
-
kindInClause,
|
|
99
|
-
listFunctionNodes,
|
|
100
|
-
NodeQuery,
|
|
101
|
-
openDb,
|
|
102
|
-
openReadonlyOrFail,
|
|
103
|
-
setBuildMeta,
|
|
104
|
-
testFilterSQL,
|
|
105
|
-
} from './db.js';
|
|
106
|
-
// Embeddings
|
|
23
|
+
export { EXTENSIONS, IGNORE_DIRS } from './constants.js';
|
|
24
|
+
export { findCycles } from './cycles.js';
|
|
25
|
+
export { dataflowData } from './dataflow.js';
|
|
107
26
|
export {
|
|
108
27
|
buildEmbeddings,
|
|
109
|
-
cosineSim,
|
|
110
|
-
DEFAULT_MODEL,
|
|
111
|
-
disposeModel,
|
|
112
|
-
EMBEDDING_STRATEGIES,
|
|
113
|
-
embed,
|
|
114
|
-
estimateTokens,
|
|
115
|
-
ftsSearchData,
|
|
116
28
|
hybridSearchData,
|
|
117
|
-
MODELS,
|
|
118
29
|
multiSearchData,
|
|
119
|
-
search,
|
|
120
30
|
searchData,
|
|
121
|
-
} from './
|
|
122
|
-
// Export (DOT/Mermaid/JSON/GraphML/GraphSON/Neo4j CSV)
|
|
31
|
+
} from './embeddings/index.js';
|
|
123
32
|
export {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
export {
|
|
135
|
-
|
|
136
|
-
export {
|
|
137
|
-
|
|
138
|
-
export { setVerbose } from './logger.js';
|
|
139
|
-
// Manifesto rule engine
|
|
140
|
-
export { manifestoData, RULE_DEFS } from './manifesto.js';
|
|
141
|
-
// Native engine
|
|
142
|
-
export { isNativeAvailable } from './native.js';
|
|
143
|
-
// Ownership (CODEOWNERS)
|
|
144
|
-
export { matchOwners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
|
|
145
|
-
// Pagination utilities
|
|
146
|
-
export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult, printNdjson } from './paginate.js';
|
|
147
|
-
// Unified parser API
|
|
148
|
-
export { getActiveEngine, isWasmAvailable, parseFileAuto, parseFilesAuto } from './parser.js';
|
|
149
|
-
// Query functions (data-returning)
|
|
33
|
+
AnalysisError,
|
|
34
|
+
BoundaryError,
|
|
35
|
+
CodegraphError,
|
|
36
|
+
ConfigError,
|
|
37
|
+
DbError,
|
|
38
|
+
EngineError,
|
|
39
|
+
ParseError,
|
|
40
|
+
ResolutionError,
|
|
41
|
+
} from './errors.js';
|
|
42
|
+
export { exportDOT, exportJSON, exportMermaid } from './export.js';
|
|
43
|
+
export { flowData, listEntryPointsData } from './flow.js';
|
|
44
|
+
export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './kinds.js';
|
|
45
|
+
export { manifestoData } from './manifesto.js';
|
|
46
|
+
export { ownersData } from './owners.js';
|
|
150
47
|
export {
|
|
151
|
-
ALL_SYMBOL_KINDS,
|
|
152
|
-
CORE_EDGE_KINDS,
|
|
153
|
-
CORE_SYMBOL_KINDS,
|
|
154
48
|
childrenData,
|
|
155
49
|
contextData,
|
|
156
50
|
diffImpactData,
|
|
157
|
-
diffImpactMermaid,
|
|
158
|
-
EVERY_EDGE_KIND,
|
|
159
|
-
EVERY_SYMBOL_KIND,
|
|
160
|
-
EXTENDED_SYMBOL_KINDS,
|
|
161
51
|
explainData,
|
|
162
52
|
exportsData,
|
|
163
|
-
FALSE_POSITIVE_CALLER_THRESHOLD,
|
|
164
|
-
FALSE_POSITIVE_NAMES,
|
|
165
53
|
fileDepsData,
|
|
166
54
|
fnDepsData,
|
|
167
55
|
fnImpactData,
|
|
168
56
|
impactAnalysisData,
|
|
169
|
-
iterListFunctions,
|
|
170
|
-
iterRoles,
|
|
171
|
-
iterWhere,
|
|
172
|
-
kindIcon,
|
|
173
57
|
moduleMapData,
|
|
174
|
-
normalizeSymbol,
|
|
175
58
|
pathData,
|
|
176
59
|
queryNameData,
|
|
177
60
|
rolesData,
|
|
178
|
-
STRUCTURAL_EDGE_KINDS,
|
|
179
61
|
statsData,
|
|
180
|
-
VALID_ROLES,
|
|
181
62
|
whereData,
|
|
182
63
|
} from './queries.js';
|
|
183
|
-
|
|
184
|
-
export {
|
|
185
|
-
children,
|
|
186
|
-
context,
|
|
187
|
-
diffImpact,
|
|
188
|
-
explain,
|
|
189
|
-
fileDeps,
|
|
190
|
-
fileExports,
|
|
191
|
-
fnDeps,
|
|
192
|
-
fnImpact,
|
|
193
|
-
impactAnalysis,
|
|
194
|
-
moduleMap,
|
|
195
|
-
queryName,
|
|
196
|
-
roles,
|
|
197
|
-
stats,
|
|
198
|
-
symbolPath,
|
|
199
|
-
where,
|
|
200
|
-
} from './queries-cli.js';
|
|
201
|
-
// Registry (multi-repo)
|
|
202
|
-
export {
|
|
203
|
-
listRepos,
|
|
204
|
-
loadRegistry,
|
|
205
|
-
pruneRegistry,
|
|
206
|
-
REGISTRY_PATH,
|
|
207
|
-
registerRepo,
|
|
208
|
-
resolveRepoDbPath,
|
|
209
|
-
saveRegistry,
|
|
210
|
-
unregisterRepo,
|
|
211
|
-
} from './registry.js';
|
|
212
|
-
// Sequence diagram generation
|
|
213
|
-
export { sequenceData, sequenceToMermaid } from './sequence.js';
|
|
214
|
-
// Snapshot management
|
|
215
|
-
export {
|
|
216
|
-
snapshotDelete,
|
|
217
|
-
snapshotList,
|
|
218
|
-
snapshotRestore,
|
|
219
|
-
snapshotSave,
|
|
220
|
-
snapshotsDir,
|
|
221
|
-
validateSnapshotName,
|
|
222
|
-
} from './snapshot.js';
|
|
223
|
-
// Structure analysis
|
|
224
|
-
export {
|
|
225
|
-
buildStructure,
|
|
226
|
-
classifyNodeRoles,
|
|
227
|
-
FRAMEWORK_ENTRY_PREFIXES,
|
|
228
|
-
hotspotsData,
|
|
229
|
-
moduleBoundariesData,
|
|
230
|
-
structureData,
|
|
231
|
-
} from './structure.js';
|
|
232
|
-
// Triage — composite risk audit
|
|
64
|
+
export { sequenceData } from './sequence.js';
|
|
65
|
+
export { hotspotsData, moduleBoundariesData, structureData } from './structure.js';
|
|
233
66
|
export { triageData } from './triage.js';
|
|
234
|
-
// Interactive HTML viewer
|
|
235
|
-
export { generatePlotHTML, loadPlotConfig } from './viewer.js';
|
|
236
|
-
// Watch mode
|
|
237
|
-
export { watchProject } from './watcher.js';
|