@optave/codegraph 3.11.0 → 3.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -31
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -0
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +142 -76
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -44
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +10 -766
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +133 -96
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +5 -2
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +126 -79
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +2 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +37 -3
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +201 -136
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/elixir.js +95 -71
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +23 -31
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +27 -34
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +33 -58
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +38 -61
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.js +28 -36
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/embed.ts +54 -4
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +217 -90
- package/src/domain/graph/builder/pipeline.ts +19 -957
- package/src/domain/graph/builder/stages/build-edges.ts +198 -140
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +8 -2
- package/src/domain/parser.ts +143 -66
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +39 -3
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +235 -152
- package/src/extractors/elixir.ts +91 -64
- package/src/extractors/gleam.ts +33 -37
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +28 -35
- package/src/extractors/r.ts +38 -56
- package/src/extractors/solidity.ts +43 -71
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +56 -56
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +1 -1
|
@@ -78,6 +78,68 @@ export interface RoleClassificationNode {
|
|
|
78
78
|
hasActiveFileSiblings?: boolean;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Compute median fan-in and fan-out across nodes with non-zero values.
|
|
83
|
+
* Used as thresholds for "high" fan-in/out classification.
|
|
84
|
+
*/
|
|
85
|
+
function computeFanMedians(nodes: RoleClassificationNode[]): { fanIn: number; fanOut: number } {
|
|
86
|
+
const nonZeroFanIn = nodes
|
|
87
|
+
.filter((n) => n.fanIn > 0)
|
|
88
|
+
.map((n) => n.fanIn)
|
|
89
|
+
.sort((a, b) => a - b);
|
|
90
|
+
const nonZeroFanOut = nodes
|
|
91
|
+
.filter((n) => n.fanOut > 0)
|
|
92
|
+
.map((n) => n.fanOut)
|
|
93
|
+
.sort((a, b) => a - b);
|
|
94
|
+
return { fanIn: median(nonZeroFanIn), fanOut: median(nonZeroFanOut) };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Classify a node with `fanIn === 0` that is not exported.
|
|
99
|
+
* Covers framework-active constants, test-only callables, and the dead-* family.
|
|
100
|
+
*/
|
|
101
|
+
function classifyUnreferencedNode(node: RoleClassificationNode): Role {
|
|
102
|
+
if (node.kind === 'constant' && node.hasActiveFileSiblings) {
|
|
103
|
+
// Constants consumed via identifier reference (not calls) have no
|
|
104
|
+
// inbound call edges. If the same file has active callables, the
|
|
105
|
+
// constant is almost certainly used locally — classify as leaf.
|
|
106
|
+
return 'leaf';
|
|
107
|
+
}
|
|
108
|
+
if (node.testOnlyFanIn != null && node.testOnlyFanIn > 0) return 'test-only';
|
|
109
|
+
return classifyDeadSubRole(node);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Pick a role from fan-in/fan-out shape: core/utility/adapter/leaf.
|
|
114
|
+
* Called after entry/test-only/dead cases have been ruled out.
|
|
115
|
+
*/
|
|
116
|
+
function classifyByFanShape(highIn: boolean, highOut: boolean): Role {
|
|
117
|
+
if (highIn && !highOut) return 'core';
|
|
118
|
+
if (highIn && highOut) return 'utility';
|
|
119
|
+
if (!highIn && highOut) return 'adapter';
|
|
120
|
+
return 'leaf';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Apply role-classification rules to a single node.
|
|
125
|
+
* Order matters — framework entries are tagged first, then dead/test cases,
|
|
126
|
+
* then the fan-in/fan-out shape decides among the structural roles.
|
|
127
|
+
*/
|
|
128
|
+
function classifyNodeRole(node: RoleClassificationNode, medFanIn: number, medFanOut: number): Role {
|
|
129
|
+
if (FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p))) return 'entry';
|
|
130
|
+
|
|
131
|
+
if (node.fanIn === 0) {
|
|
132
|
+
return node.isExported ? 'entry' : classifyUnreferencedNode(node);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hasProdFanIn = typeof node.productionFanIn === 'number';
|
|
136
|
+
if (hasProdFanIn && node.productionFanIn === 0 && !node.isExported) return 'test-only';
|
|
137
|
+
|
|
138
|
+
const highIn = node.fanIn >= medFanIn;
|
|
139
|
+
const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
|
|
140
|
+
return classifyByFanShape(highIn, highOut);
|
|
141
|
+
}
|
|
142
|
+
|
|
81
143
|
/**
|
|
82
144
|
* Classify nodes into architectural roles based on fan-in/fan-out metrics.
|
|
83
145
|
*/
|
|
@@ -87,63 +149,11 @@ export function classifyRoles(
|
|
|
87
149
|
): Map<string, Role> {
|
|
88
150
|
if (nodes.length === 0) return new Map();
|
|
89
151
|
|
|
90
|
-
|
|
91
|
-
let medFanOut: number;
|
|
92
|
-
if (medianOverrides) {
|
|
93
|
-
medFanIn = medianOverrides.fanIn;
|
|
94
|
-
medFanOut = medianOverrides.fanOut;
|
|
95
|
-
} else {
|
|
96
|
-
const nonZeroFanIn = nodes
|
|
97
|
-
.filter((n) => n.fanIn > 0)
|
|
98
|
-
.map((n) => n.fanIn)
|
|
99
|
-
.sort((a, b) => a - b);
|
|
100
|
-
const nonZeroFanOut = nodes
|
|
101
|
-
.filter((n) => n.fanOut > 0)
|
|
102
|
-
.map((n) => n.fanOut)
|
|
103
|
-
.sort((a, b) => a - b);
|
|
104
|
-
medFanIn = median(nonZeroFanIn);
|
|
105
|
-
medFanOut = median(nonZeroFanOut);
|
|
106
|
-
}
|
|
152
|
+
const { fanIn: medFanIn, fanOut: medFanOut } = medianOverrides ?? computeFanMedians(nodes);
|
|
107
153
|
|
|
108
154
|
const result = new Map<string, Role>();
|
|
109
|
-
|
|
110
155
|
for (const node of nodes) {
|
|
111
|
-
|
|
112
|
-
const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
|
|
113
|
-
const hasProdFanIn = typeof node.productionFanIn === 'number';
|
|
114
|
-
|
|
115
|
-
let role: Role;
|
|
116
|
-
const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
|
|
117
|
-
if (isFrameworkEntry) {
|
|
118
|
-
role = 'entry';
|
|
119
|
-
} else if (node.fanIn === 0 && !node.isExported) {
|
|
120
|
-
if (node.kind === 'constant' && node.hasActiveFileSiblings) {
|
|
121
|
-
// Constants consumed via identifier reference (not calls) have no
|
|
122
|
-
// inbound call edges. If the same file has active callables, the
|
|
123
|
-
// constant is almost certainly used locally — classify as leaf.
|
|
124
|
-
role = 'leaf';
|
|
125
|
-
} else {
|
|
126
|
-
role =
|
|
127
|
-
node.testOnlyFanIn != null && node.testOnlyFanIn > 0
|
|
128
|
-
? 'test-only'
|
|
129
|
-
: classifyDeadSubRole(node);
|
|
130
|
-
}
|
|
131
|
-
} else if (node.fanIn === 0 && node.isExported) {
|
|
132
|
-
role = 'entry';
|
|
133
|
-
} else if (hasProdFanIn && node.fanIn > 0 && node.productionFanIn === 0 && !node.isExported) {
|
|
134
|
-
role = 'test-only';
|
|
135
|
-
} else if (highIn && !highOut) {
|
|
136
|
-
role = 'core';
|
|
137
|
-
} else if (highIn && highOut) {
|
|
138
|
-
role = 'utility';
|
|
139
|
-
} else if (!highIn && highOut) {
|
|
140
|
-
role = 'adapter';
|
|
141
|
-
} else {
|
|
142
|
-
role = 'leaf';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
result.set(node.id, role);
|
|
156
|
+
result.set(node.id, classifyNodeRole(node, medFanIn, medFanOut));
|
|
146
157
|
}
|
|
147
|
-
|
|
148
158
|
return result;
|
|
149
159
|
}
|
|
@@ -30,7 +30,7 @@ export const DEFAULTS = {
|
|
|
30
30
|
defaultLimit: 20,
|
|
31
31
|
excludeTests: false,
|
|
32
32
|
},
|
|
33
|
-
embeddings: { model:
|
|
33
|
+
embeddings: { model: null as string | null, llmProvider: null as string | null },
|
|
34
34
|
llm: {
|
|
35
35
|
provider: null as string | null,
|
|
36
36
|
model: null as string | null,
|
package/src/presentation/cfg.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { cfgData, cfgToDOT, cfgToMermaid } from '../features/cfg.js';
|
|
2
2
|
import { outputResult } from '../infrastructure/result-formatter.js';
|
|
3
3
|
|
|
4
|
+
type CfgData = ReturnType<typeof cfgData>;
|
|
5
|
+
|
|
4
6
|
interface CfgCliOpts {
|
|
5
7
|
json?: boolean;
|
|
6
8
|
ndjson?: boolean;
|
|
@@ -36,13 +38,56 @@ interface CfgResultEntry {
|
|
|
36
38
|
edges: CfgEdge[];
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
function renderBlockLocation(b: CfgBlock): string {
|
|
42
|
+
if (!b.startLine) return '';
|
|
43
|
+
const endSuffix = b.endLine && b.endLine !== b.startLine ? `-${b.endLine}` : '';
|
|
44
|
+
return ` L${b.startLine}${endSuffix}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function printCfgBlocks(blocks: CfgBlock[]): void {
|
|
48
|
+
if (blocks.length === 0) return;
|
|
49
|
+
console.log('\n Blocks:');
|
|
50
|
+
for (const b of blocks) {
|
|
51
|
+
const label = b.label ? ` (${b.label})` : '';
|
|
52
|
+
console.log(` [${b.index}] ${b.type}${label}${renderBlockLocation(b)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printCfgEdges(edges: CfgEdge[]): void {
|
|
57
|
+
if (edges.length === 0) return;
|
|
58
|
+
console.log('\n Edges:');
|
|
59
|
+
for (const e of edges) {
|
|
60
|
+
console.log(` B${e.source} → B${e.target} [${e.kind}]`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printCfgEntry(r: CfgResultEntry): void {
|
|
65
|
+
console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
|
|
66
|
+
console.log('─'.repeat(60));
|
|
67
|
+
console.log(` Blocks: ${r.summary.blockCount} Edges: ${r.summary.edgeCount}`);
|
|
68
|
+
printCfgBlocks(r.blocks);
|
|
69
|
+
printCfgEdges(r.edges);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function tryRenderGraphFormat(format: string, data: CfgData): boolean {
|
|
73
|
+
if (format === 'dot') {
|
|
74
|
+
console.log(cfgToDOT(data));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (format === 'mermaid') {
|
|
78
|
+
console.log(cfgToMermaid(data));
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
39
84
|
export function cfg(name: string, customDbPath: string | undefined, opts: CfgCliOpts = {}): void {
|
|
40
85
|
const data = cfgData(name, customDbPath, opts);
|
|
41
86
|
|
|
42
87
|
if (outputResult(data, 'results', opts)) return;
|
|
43
88
|
|
|
44
89
|
if (data.warning) {
|
|
45
|
-
console.log(
|
|
90
|
+
console.log(`⚠ ${data.warning}`);
|
|
46
91
|
return;
|
|
47
92
|
}
|
|
48
93
|
if (data.results.length === 0) {
|
|
@@ -50,38 +95,9 @@ export function cfg(name: string, customDbPath: string | undefined, opts: CfgCli
|
|
|
50
95
|
return;
|
|
51
96
|
}
|
|
52
97
|
|
|
53
|
-
|
|
54
|
-
if (format === 'dot') {
|
|
55
|
-
console.log(cfgToDOT(data));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (format === 'mermaid') {
|
|
59
|
-
console.log(cfgToMermaid(data));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
98
|
+
if (tryRenderGraphFormat(opts.format || 'text', data)) return;
|
|
62
99
|
|
|
63
|
-
// Text format
|
|
64
100
|
for (const r of data.results as CfgResultEntry[]) {
|
|
65
|
-
|
|
66
|
-
console.log('\u2500'.repeat(60));
|
|
67
|
-
console.log(` Blocks: ${r.summary.blockCount} Edges: ${r.summary.edgeCount}`);
|
|
68
|
-
|
|
69
|
-
if (r.blocks.length > 0) {
|
|
70
|
-
console.log('\n Blocks:');
|
|
71
|
-
for (const b of r.blocks) {
|
|
72
|
-
const loc = b.startLine
|
|
73
|
-
? ` L${b.startLine}${b.endLine && b.endLine !== b.startLine ? `-${b.endLine}` : ''}`
|
|
74
|
-
: '';
|
|
75
|
-
const label = b.label ? ` (${b.label})` : '';
|
|
76
|
-
console.log(` [${b.index}] ${b.type}${label}${loc}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (r.edges.length > 0) {
|
|
81
|
-
console.log('\n Edges:');
|
|
82
|
-
for (const e of r.edges) {
|
|
83
|
-
console.log(` B${e.source} \u2192 B${e.target} [${e.kind}]`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
101
|
+
printCfgEntry(r);
|
|
86
102
|
}
|
|
87
103
|
}
|
package/src/presentation/flow.ts
CHANGED
|
@@ -16,54 +16,65 @@ interface FlowOpts {
|
|
|
16
16
|
csv?: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const data = listEntryPointsData(dbPath, {
|
|
26
|
-
noTests: opts.noTests,
|
|
27
|
-
limit: opts.limit,
|
|
28
|
-
offset: opts.offset,
|
|
29
|
-
}) as any;
|
|
30
|
-
if (outputResult(data, 'entries', opts)) return;
|
|
31
|
-
if (data.count === 0) {
|
|
32
|
-
console.log('No entry points found. Run "codegraph build" first.');
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
console.log(`\nEntry points (${data.count} total):\n`);
|
|
36
|
-
for (const [type, entries] of Object.entries(
|
|
37
|
-
data.byType as Record<
|
|
38
|
-
string,
|
|
39
|
-
Array<{ kind: string; name: string; file: string; line: number }>
|
|
40
|
-
>,
|
|
41
|
-
)) {
|
|
42
|
-
console.log(` ${type} (${entries.length}):`);
|
|
43
|
-
for (const e of entries) {
|
|
44
|
-
console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
|
|
45
|
-
}
|
|
46
|
-
console.log();
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
19
|
+
interface EntryPoint {
|
|
20
|
+
kind: string;
|
|
21
|
+
name: string;
|
|
22
|
+
file: string;
|
|
23
|
+
line: number;
|
|
24
|
+
}
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
interface FlowNode {
|
|
27
|
+
kind: string;
|
|
28
|
+
name: string;
|
|
29
|
+
file: string;
|
|
30
|
+
line: number;
|
|
31
|
+
}
|
|
57
32
|
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
interface FlowStep {
|
|
34
|
+
depth: number;
|
|
35
|
+
nodes: FlowNode[];
|
|
36
|
+
}
|
|
60
37
|
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
interface FlowCycle {
|
|
39
|
+
from: string;
|
|
40
|
+
to: string;
|
|
41
|
+
depth: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface FlowResult {
|
|
45
|
+
entry?: { kind: string; name: string; type: string; file: string; line: number };
|
|
46
|
+
depth: number;
|
|
47
|
+
totalReached: number;
|
|
48
|
+
leaves: Array<{ name: string; file: string }>;
|
|
49
|
+
steps: FlowStep[];
|
|
50
|
+
cycles: FlowCycle[];
|
|
51
|
+
truncated?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runListEntryPoints(dbPath: string | undefined, opts: FlowOpts): void {
|
|
55
|
+
const data = listEntryPointsData(dbPath, {
|
|
56
|
+
noTests: opts.noTests,
|
|
57
|
+
limit: opts.limit,
|
|
58
|
+
offset: opts.offset,
|
|
59
|
+
}) as { count: number; byType: Record<string, EntryPoint[]> };
|
|
60
|
+
if (outputResult(data, 'entries', opts)) return;
|
|
61
|
+
if (data.count === 0) {
|
|
62
|
+
console.log('No entry points found. Run "codegraph build" first.');
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
console.log(`\nEntry points (${data.count} total):\n`);
|
|
66
|
+
for (const [type, entries] of Object.entries(data.byType)) {
|
|
67
|
+
console.log(` ${type} (${entries.length}):`);
|
|
68
|
+
for (const e of entries) {
|
|
69
|
+
console.log(` [${kindIcon(e.kind)}] ${e.name} ${e.file}:${e.line}`);
|
|
70
|
+
}
|
|
71
|
+
console.log();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
|
|
75
|
+
function printFlowHeader(data: FlowResult): void {
|
|
66
76
|
const e = data.entry;
|
|
77
|
+
if (!e) return;
|
|
67
78
|
const typeTag = e.type !== 'exported' ? ` (${e.type})` : '';
|
|
68
79
|
console.log(`\nFlow from: [${kindIcon(e.kind)}] ${e.name}${typeTag} ${e.file}:${e.line}`);
|
|
69
80
|
console.log(
|
|
@@ -73,27 +84,64 @@ export function flow(
|
|
|
73
84
|
console.log(` (truncated at depth ${data.depth})`);
|
|
74
85
|
}
|
|
75
86
|
console.log();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isLeafNode(n: FlowNode, leaves: Array<{ name: string; file: string }>): boolean {
|
|
90
|
+
return leaves.some((l) => l.name === n.name && l.file === n.file);
|
|
91
|
+
}
|
|
76
92
|
|
|
93
|
+
/** Returns true when the node is a leaf (no steps); caller should skip cycle output. */
|
|
94
|
+
function printFlowSteps(data: FlowResult): boolean {
|
|
77
95
|
if (data.steps.length === 0) {
|
|
78
96
|
console.log(' (leaf node — no callees)');
|
|
79
|
-
return;
|
|
97
|
+
return true;
|
|
80
98
|
}
|
|
81
|
-
|
|
82
99
|
for (const step of data.steps) {
|
|
83
100
|
console.log(` depth ${step.depth}:`);
|
|
84
101
|
for (const n of step.nodes) {
|
|
85
|
-
const
|
|
86
|
-
(l: { name: string; file: string }) => l.name === n.name && l.file === n.file,
|
|
87
|
-
);
|
|
88
|
-
const leafTag = isLeaf ? ' [leaf]' : '';
|
|
102
|
+
const leafTag = isLeafNode(n, data.leaves) ? ' [leaf]' : '';
|
|
89
103
|
console.log(` [${kindIcon(n.kind)}] ${n.name} ${n.file}:${n.line}${leafTag}`);
|
|
90
104
|
}
|
|
91
105
|
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function printFlowCycles(cycles: FlowCycle[]): void {
|
|
110
|
+
if (cycles.length === 0) return;
|
|
111
|
+
console.log('\n Cycles detected:');
|
|
112
|
+
for (const c of cycles) {
|
|
113
|
+
console.log(` ${c.from} -> ${c.to} (at depth ${c.depth})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
92
116
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
export function flow(
|
|
118
|
+
name: string | undefined,
|
|
119
|
+
dbPath: string | undefined,
|
|
120
|
+
opts: FlowOpts = {},
|
|
121
|
+
): void {
|
|
122
|
+
if (opts.list) {
|
|
123
|
+
runListEntryPoints(dbPath, opts);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!name) {
|
|
128
|
+
console.log(
|
|
129
|
+
'Please provide a function or entry-point name. Use --list to see available entry points.',
|
|
130
|
+
);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const data = flowData(name, dbPath, opts) as unknown as FlowResult;
|
|
135
|
+
if (outputResult(data, 'steps', opts)) return;
|
|
136
|
+
|
|
137
|
+
if (!data.entry) {
|
|
138
|
+
console.log(`No matching entry point or function found for "${name}".`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
printFlowHeader(data);
|
|
143
|
+
const isLeaf = printFlowSteps(data);
|
|
144
|
+
if (!isLeaf) {
|
|
145
|
+
printFlowCycles(data.cycles);
|
|
98
146
|
}
|
|
99
147
|
}
|