@optave/codegraph 3.8.0 → 3.8.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 +9 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +95 -87
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.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 +54 -58
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
- package/dist/ast-analysis/visitors/complexity-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 +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +10 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +17 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +6 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +77 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +3 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +26 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +53 -52
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +255 -105
- 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 +22 -17
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +2 -2
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +0 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +13 -28
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +18 -5
- 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 +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +46 -45
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +10 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +126 -105
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +20 -0
- package/src/db/repository/native-repository.ts +79 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +29 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +67 -76
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +286 -97
- package/src/domain/graph/builder/stages/build-edges.ts +22 -18
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +14 -26
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +17 -4
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +45 -46
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +24 -4
package/src/graph/model.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface CodeGraphOpts {
|
|
|
17
17
|
directed?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Canonical key for an undirected edge — smallest ID first, null-byte separated. */
|
|
21
|
+
function undirectedEdgeKey(a: string, b: string): string {
|
|
22
|
+
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export class CodeGraph {
|
|
21
26
|
private _directed: boolean;
|
|
22
27
|
private _nodes: Map<string, NodeAttrs>;
|
|
@@ -121,7 +126,7 @@ export class CodeGraph {
|
|
|
121
126
|
const seen = new Set<string>();
|
|
122
127
|
for (const [src, targets] of this._successors) {
|
|
123
128
|
for (const [tgt, attrs] of targets) {
|
|
124
|
-
const key = src
|
|
129
|
+
const key = undirectedEdgeKey(src, tgt);
|
|
125
130
|
if (seen.has(key)) continue;
|
|
126
131
|
seen.add(key);
|
|
127
132
|
yield [src, tgt, attrs];
|
|
@@ -178,7 +178,7 @@ export function loadConfig(cwd?: string): CodegraphConfig {
|
|
|
178
178
|
_configCache.set(cwd, structuredClone(result));
|
|
179
179
|
return result;
|
|
180
180
|
} catch (err: unknown) {
|
|
181
|
-
debug(`Failed to parse config ${filePath}: ${(err
|
|
181
|
+
debug(`Failed to parse config ${filePath}: ${toErrorMessage(err)}`);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
}
|
|
@@ -229,7 +229,7 @@ export function resolveSecrets(config: CodegraphConfig): CodegraphConfig {
|
|
|
229
229
|
(config.llm as Record<string, unknown>).apiKey = result;
|
|
230
230
|
}
|
|
231
231
|
} catch (err: unknown) {
|
|
232
|
-
warn(`apiKeyCommand failed: ${(err
|
|
232
|
+
warn(`apiKeyCommand failed: ${toErrorMessage(err)}`);
|
|
233
233
|
}
|
|
234
234
|
return config;
|
|
235
235
|
}
|
|
@@ -252,7 +252,8 @@ function expandWorkspaceGlob(pattern: string, rootDir: string): string[] {
|
|
|
252
252
|
.filter((e) => e.isDirectory())
|
|
253
253
|
.map((e) => path.join(baseDir, e.name))
|
|
254
254
|
.filter((d) => fs.existsSync(path.join(d, 'package.json')));
|
|
255
|
-
} catch {
|
|
255
|
+
} catch (e) {
|
|
256
|
+
debug(`expandGlobDirs: failed to read ${baseDir}: ${toErrorMessage(e)}`);
|
|
256
257
|
return [];
|
|
257
258
|
}
|
|
258
259
|
}
|
|
@@ -265,7 +266,8 @@ function readPackageName(pkgDir: string): string | null {
|
|
|
265
266
|
const raw = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8');
|
|
266
267
|
const pkg = JSON.parse(raw);
|
|
267
268
|
return pkg.name || null;
|
|
268
|
-
} catch {
|
|
269
|
+
} catch (e) {
|
|
270
|
+
debug(`readPackageName: failed for ${pkgDir}: ${toErrorMessage(e)}`);
|
|
269
271
|
return null;
|
|
270
272
|
}
|
|
271
273
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catch-suppression helpers for intentional error swallowing.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `infrastructure/` (not `shared/`) because it depends on the
|
|
5
|
+
* structured logger — keeping `shared/errors.ts` dependency-free.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { toErrorMessage } from '../shared/errors.js';
|
|
9
|
+
import { debug } from './logger.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run `fn` and return its result. If it throws, log a debug message and
|
|
13
|
+
* return `fallback` instead. Use this for intentional catch suppression
|
|
14
|
+
* where the error is expected and non-fatal (e.g. optional file reads,
|
|
15
|
+
* graceful feature probes, cleanup that may fail).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const version = suppressError(() => readPkgVersion(), 'read package version', '');
|
|
19
|
+
*/
|
|
20
|
+
export function suppressError<T>(fn: () => T, context: string, fallback: T): T {
|
|
21
|
+
try {
|
|
22
|
+
return fn();
|
|
23
|
+
} catch (e: unknown) {
|
|
24
|
+
debug(`${context}: ${toErrorMessage(e)}`);
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Async variant of {@link suppressError}. Awaits `fn()` and returns `fallback`
|
|
31
|
+
* on rejection, logging the error via `debug()`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const data = await suppressErrorAsync(() => fetchOptionalData(), 'fetch data', null);
|
|
35
|
+
*/
|
|
36
|
+
export async function suppressErrorAsync<T>(
|
|
37
|
+
fn: () => Promise<T>,
|
|
38
|
+
context: string,
|
|
39
|
+
fallback: T,
|
|
40
|
+
): Promise<T> {
|
|
41
|
+
try {
|
|
42
|
+
return await fn();
|
|
43
|
+
} catch (e: unknown) {
|
|
44
|
+
debug(`${context}: ${toErrorMessage(e)}`);
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -13,7 +13,8 @@ const { version: PKG_VERSION } = require('../../package.json') as { version: str
|
|
|
13
13
|
import { getDatabase } from '../db/better-sqlite3.js';
|
|
14
14
|
import { findDbPath } from '../db/index.js';
|
|
15
15
|
import { loadConfig } from '../infrastructure/config.js';
|
|
16
|
-
import {
|
|
16
|
+
import { debug } from '../infrastructure/logger.js';
|
|
17
|
+
import { CodegraphError, ConfigError, toErrorMessage } from '../shared/errors.js';
|
|
17
18
|
import { MCP_MAX_LIMIT } from '../shared/paginate.js';
|
|
18
19
|
import type { CodegraphConfig, MCPServerOptions } from '../types.js';
|
|
19
20
|
import { initMcpDefaults } from './middleware.js';
|
|
@@ -56,7 +57,8 @@ async function loadMCPSdk(): Promise<{
|
|
|
56
57
|
ListToolsRequestSchema: types.ListToolsRequestSchema,
|
|
57
58
|
CallToolRequestSchema: types.CallToolRequestSchema,
|
|
58
59
|
};
|
|
59
|
-
} catch {
|
|
60
|
+
} catch (e) {
|
|
61
|
+
debug(`MCP SDK import failed: ${toErrorMessage(e)}`);
|
|
60
62
|
throw new ConfigError(
|
|
61
63
|
'MCP server requires @modelcontextprotocol/sdk.\nInstall it with: npm install @modelcontextprotocol/sdk',
|
|
62
64
|
);
|
|
@@ -123,8 +125,10 @@ function registerShutdownHandlers(): void {
|
|
|
123
125
|
const shutdown = async () => {
|
|
124
126
|
try {
|
|
125
127
|
await _activeServer?.close();
|
|
126
|
-
} catch (
|
|
127
|
-
|
|
128
|
+
} catch (shutdownErr: unknown) {
|
|
129
|
+
debug(
|
|
130
|
+
`MCP shutdown close failed (transport may already be gone): ${toErrorMessage(shutdownErr)}`,
|
|
131
|
+
);
|
|
128
132
|
}
|
|
129
133
|
process.exit(0);
|
|
130
134
|
};
|
|
@@ -154,36 +158,13 @@ function registerShutdownHandlers(): void {
|
|
|
154
158
|
process.on('unhandledRejection', silentReject);
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Apply config-based MCP page-size overrides
|
|
165
|
-
const config = options.config || loadConfig();
|
|
166
|
-
initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined);
|
|
167
|
-
|
|
168
|
-
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
169
|
-
await loadMCPSdk();
|
|
170
|
-
|
|
171
|
-
// Connect transport FIRST so the server can receive the client's
|
|
172
|
-
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
173
|
-
// are still loading. These are lazy-loaded on the first tool call
|
|
174
|
-
// and cached for subsequent calls.
|
|
175
|
-
const { getQueries } = createLazyLoaders();
|
|
176
|
-
|
|
177
|
-
const server = new (Server as any)(
|
|
178
|
-
{ name: 'codegraph', version: PKG_VERSION },
|
|
179
|
-
{ capabilities: { tools: {} } },
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
183
|
-
tools: buildToolList(multiRepo),
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
|
|
161
|
+
function createCallToolHandler(
|
|
162
|
+
multiRepo: boolean,
|
|
163
|
+
customDbPath: string | undefined,
|
|
164
|
+
allowedRepos: string[] | undefined,
|
|
165
|
+
getQueries: () => Promise<unknown>,
|
|
166
|
+
) {
|
|
167
|
+
return async (request: any) => {
|
|
187
168
|
const { name, arguments: args } = request.params;
|
|
188
169
|
try {
|
|
189
170
|
validateMultiRepoAccess(multiRepo, name, args);
|
|
@@ -212,10 +193,45 @@ export async function startMCPServer(
|
|
|
212
193
|
const text =
|
|
213
194
|
err instanceof CodegraphError
|
|
214
195
|
? `[${code}] ${err.message}`
|
|
215
|
-
: `Error: ${(err
|
|
196
|
+
: `Error: ${toErrorMessage(err)}`;
|
|
216
197
|
return { content: [{ type: 'text', text }], isError: true };
|
|
217
198
|
}
|
|
218
|
-
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function startMCPServer(
|
|
203
|
+
customDbPath?: string,
|
|
204
|
+
options: MCPServerOptionsInternal = {},
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
const { allowedRepos } = options;
|
|
207
|
+
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
208
|
+
|
|
209
|
+
// Apply config-based MCP page-size overrides
|
|
210
|
+
const config = options.config || loadConfig();
|
|
211
|
+
initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined);
|
|
212
|
+
|
|
213
|
+
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
214
|
+
await loadMCPSdk();
|
|
215
|
+
|
|
216
|
+
// Connect transport FIRST so the server can receive the client's
|
|
217
|
+
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
218
|
+
// are still loading. These are lazy-loaded on the first tool call
|
|
219
|
+
// and cached for subsequent calls.
|
|
220
|
+
const { getQueries } = createLazyLoaders();
|
|
221
|
+
|
|
222
|
+
const server = new (Server as any)(
|
|
223
|
+
{ name: 'codegraph', version: PKG_VERSION },
|
|
224
|
+
{ capabilities: { tools: {} } },
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
228
|
+
tools: buildToolList(multiRepo),
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
server.setRequestHandler(
|
|
232
|
+
CallToolRequestSchema,
|
|
233
|
+
createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries),
|
|
234
|
+
);
|
|
219
235
|
|
|
220
236
|
const transport = new (StdioServerTransport as any)();
|
|
221
237
|
|
|
@@ -235,7 +251,7 @@ export async function startMCPServer(
|
|
|
235
251
|
process.exit(0);
|
|
236
252
|
}
|
|
237
253
|
process.stderr.write(
|
|
238
|
-
`MCP transport connect failed: ${
|
|
254
|
+
`MCP transport connect failed: ${err instanceof Error ? (err.stack ?? err.message) : toErrorMessage(err)}\n`,
|
|
239
255
|
);
|
|
240
256
|
process.exit(1);
|
|
241
257
|
}
|
|
@@ -58,6 +58,53 @@ interface DataflowImpactEntry {
|
|
|
58
58
|
levels: Record<string, Array<{ name: string; file: string; line: number }>>;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function printDataflowFlows(r: DataflowResultEntry): void {
|
|
62
|
+
if (r.flowsTo.length > 0) {
|
|
63
|
+
console.log('\n Data flows TO:');
|
|
64
|
+
for (const f of r.flowsTo) {
|
|
65
|
+
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
66
|
+
console.log(` \u2192 ${f.target} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (r.flowsFrom.length > 0) {
|
|
70
|
+
console.log('\n Data flows FROM:');
|
|
71
|
+
for (const f of r.flowsFrom) {
|
|
72
|
+
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
73
|
+
console.log(` \u2190 ${f.source} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printDataflowReturns(r: DataflowResultEntry): void {
|
|
79
|
+
if (r.returns.length > 0) {
|
|
80
|
+
console.log('\n Return value consumed by:');
|
|
81
|
+
for (const c of r.returns) {
|
|
82
|
+
console.log(` \u2192 ${c.consumer} (${c.file}:${c.line}) ${c.expression}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (r.returnedBy.length > 0) {
|
|
86
|
+
console.log('\n Uses return value of:');
|
|
87
|
+
for (const p of r.returnedBy) {
|
|
88
|
+
console.log(` \u2190 ${p.producer} (${p.file}:${p.line}) ${p.expression}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function printDataflowMutations(r: DataflowResultEntry): void {
|
|
94
|
+
if (r.mutates.length > 0) {
|
|
95
|
+
console.log('\n Mutates:');
|
|
96
|
+
for (const m of r.mutates) {
|
|
97
|
+
console.log(` \u270E ${m.expression} (line ${m.line})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (r.mutatedBy.length > 0) {
|
|
101
|
+
console.log('\n Mutated by:');
|
|
102
|
+
for (const m of r.mutatedBy) {
|
|
103
|
+
console.log(` \u270E ${m.source} \u2014 ${m.expression} (line ${m.line})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
61
108
|
export function dataflow(
|
|
62
109
|
name: string,
|
|
63
110
|
customDbPath: string | undefined,
|
|
@@ -87,50 +134,9 @@ export function dataflow(
|
|
|
87
134
|
for (const r of data.results) {
|
|
88
135
|
console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
|
|
89
136
|
console.log('\u2500'.repeat(60));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for (const f of r.flowsTo) {
|
|
94
|
-
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
95
|
-
console.log(` \u2192 ${f.target} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (r.flowsFrom.length > 0) {
|
|
100
|
-
console.log('\n Data flows FROM:');
|
|
101
|
-
for (const f of r.flowsFrom) {
|
|
102
|
-
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
103
|
-
console.log(` \u2190 ${f.source} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (r.returns.length > 0) {
|
|
108
|
-
console.log('\n Return value consumed by:');
|
|
109
|
-
for (const c of r.returns) {
|
|
110
|
-
console.log(` \u2192 ${c.consumer} (${c.file}:${c.line}) ${c.expression}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (r.returnedBy.length > 0) {
|
|
115
|
-
console.log('\n Uses return value of:');
|
|
116
|
-
for (const p of r.returnedBy) {
|
|
117
|
-
console.log(` \u2190 ${p.producer} (${p.file}:${p.line}) ${p.expression}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (r.mutates.length > 0) {
|
|
122
|
-
console.log('\n Mutates:');
|
|
123
|
-
for (const m of r.mutates) {
|
|
124
|
-
console.log(` \u270E ${m.expression} (line ${m.line})`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (r.mutatedBy.length > 0) {
|
|
129
|
-
console.log('\n Mutated by:');
|
|
130
|
-
for (const m of r.mutatedBy) {
|
|
131
|
-
console.log(` \u270E ${m.source} \u2014 ${m.expression} (line ${m.line})`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
137
|
+
printDataflowFlows(r);
|
|
138
|
+
printDataflowReturns(r);
|
|
139
|
+
printDataflowMutations(r);
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -1,56 +1,48 @@
|
|
|
1
1
|
import { diffImpactData } from '../domain/analysis/diff-impact.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
staged?: boolean;
|
|
9
|
-
ref?: string;
|
|
10
|
-
includeImplementors?: boolean;
|
|
11
|
-
limit?: number;
|
|
12
|
-
offset?: number;
|
|
13
|
-
config?: any;
|
|
14
|
-
} = {},
|
|
15
|
-
): string {
|
|
16
|
-
const data: any = diffImpactData(customDbPath, opts);
|
|
17
|
-
if ('error' in data) return data.error as string;
|
|
18
|
-
if (data.changedFiles === 0 || data.affectedFunctions.length === 0) {
|
|
19
|
-
return 'flowchart TB\n none["No impacted functions detected"]';
|
|
20
|
-
}
|
|
3
|
+
interface MermaidNodeRegistry {
|
|
4
|
+
nodeIdMap: Map<string, string>;
|
|
5
|
+
nodeLabels: Map<string, string>;
|
|
6
|
+
counter: number;
|
|
7
|
+
}
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
interface ImpactEdgeSets {
|
|
10
|
+
allEdges: Set<string>;
|
|
11
|
+
edgeFromNodes: Set<string>;
|
|
12
|
+
edgeToNodes: Set<string>;
|
|
13
|
+
changedKeys: Set<string>;
|
|
14
|
+
}
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return nodeIdMap.get(key)!;
|
|
16
|
+
function createNodeRegistry(): MermaidNodeRegistry {
|
|
17
|
+
return { nodeIdMap: new Map(), nodeLabels: new Map(), counter: 0 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function registerNode(reg: MermaidNodeRegistry, key: string, label?: string): string {
|
|
21
|
+
if (!reg.nodeIdMap.has(key)) {
|
|
22
|
+
reg.nodeIdMap.set(key, `n${reg.counter++}`);
|
|
23
|
+
if (label) reg.nodeLabels.set(key, label);
|
|
35
24
|
}
|
|
25
|
+
return reg.nodeIdMap.get(key)!;
|
|
26
|
+
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
for (const fn of
|
|
39
|
-
|
|
28
|
+
function registerAllNodes(reg: MermaidNodeRegistry, affectedFunctions: any[]): void {
|
|
29
|
+
for (const fn of affectedFunctions) {
|
|
30
|
+
registerNode(reg, `${fn.file}::${fn.name}:${fn.line}`, fn.name);
|
|
40
31
|
for (const callers of Object.values(fn.levels || {})) {
|
|
41
32
|
for (const c of callers as Array<{ name: string; file: string; line: number }>) {
|
|
42
|
-
|
|
33
|
+
registerNode(reg, `${c.file}::${c.name}:${c.line}`, c.name);
|
|
43
34
|
}
|
|
44
35
|
}
|
|
45
36
|
}
|
|
37
|
+
}
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
function collectEdges(affectedFunctions: any[]): ImpactEdgeSets {
|
|
48
40
|
const allEdges = new Set<string>();
|
|
49
41
|
const edgeFromNodes = new Set<string>();
|
|
50
42
|
const edgeToNodes = new Set<string>();
|
|
51
43
|
const changedKeys = new Set<string>();
|
|
52
44
|
|
|
53
|
-
for (const fn of
|
|
45
|
+
for (const fn of affectedFunctions) {
|
|
54
46
|
changedKeys.add(`${fn.file}::${fn.name}:${fn.line}`);
|
|
55
47
|
for (const edge of fn.edges || []) {
|
|
56
48
|
const edgeKey = `${edge.from}|${edge.to}`;
|
|
@@ -62,30 +54,42 @@ export function diffImpactMermaid(
|
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
56
|
|
|
65
|
-
|
|
57
|
+
return { allEdges, edgeFromNodes, edgeToNodes, changedKeys };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function classifyCallerNodes(edges: ImpactEdgeSets): {
|
|
61
|
+
blastRadiusKeys: Set<string>;
|
|
62
|
+
intermediateKeys: Set<string>;
|
|
63
|
+
} {
|
|
66
64
|
const blastRadiusKeys = new Set<string>();
|
|
67
|
-
for (const key of edgeToNodes) {
|
|
68
|
-
if (!edgeFromNodes.has(key) && !changedKeys.has(key)) {
|
|
65
|
+
for (const key of edges.edgeToNodes) {
|
|
66
|
+
if (!edges.edgeFromNodes.has(key) && !edges.changedKeys.has(key)) {
|
|
69
67
|
blastRadiusKeys.add(key);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
// Intermediate callers: not changed, not blast radius
|
|
74
71
|
const intermediateKeys = new Set<string>();
|
|
75
|
-
for (const key of edgeToNodes) {
|
|
76
|
-
if (!changedKeys.has(key) && !blastRadiusKeys.has(key)) {
|
|
72
|
+
for (const key of edges.edgeToNodes) {
|
|
73
|
+
if (!edges.changedKeys.has(key) && !blastRadiusKeys.has(key)) {
|
|
77
74
|
intermediateKeys.add(key);
|
|
78
75
|
}
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
return { blastRadiusKeys, intermediateKeys };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function emitFileSubgraphs(
|
|
82
|
+
lines: string[],
|
|
83
|
+
affectedFunctions: any[],
|
|
84
|
+
newFileSet: Set<string>,
|
|
85
|
+
reg: MermaidNodeRegistry,
|
|
86
|
+
): number {
|
|
87
|
+
const fileGroups = new Map<string, any[]>();
|
|
88
|
+
for (const fn of affectedFunctions) {
|
|
84
89
|
if (!fileGroups.has(fn.file)) fileGroups.set(fn.file, []);
|
|
85
90
|
fileGroups.get(fn.file)!.push(fn);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
// Emit changed-file subgraphs
|
|
89
93
|
let sgCounter = 0;
|
|
90
94
|
for (const [file, fns] of fileGroups) {
|
|
91
95
|
const isNew = newFileSet.has(file);
|
|
@@ -94,33 +98,71 @@ export function diffImpactMermaid(
|
|
|
94
98
|
lines.push(` subgraph ${sgId}["${file} **(${tag})**"]`);
|
|
95
99
|
for (const fn of fns) {
|
|
96
100
|
const key = `${fn.file}::${fn.name}:${fn.line}`;
|
|
97
|
-
lines.push(` ${nodeIdMap.get(key)}["${fn.name}"]`);
|
|
101
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${fn.name}"]`);
|
|
98
102
|
}
|
|
99
103
|
lines.push(' end');
|
|
100
104
|
const style = isNew ? 'fill:#e8f5e9,stroke:#4caf50' : 'fill:#fff3e0,stroke:#ff9800';
|
|
101
105
|
lines.push(` style ${sgId} ${style}`);
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return sgCounter;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function emitBlastRadiusSubgraph(
|
|
112
|
+
lines: string[],
|
|
113
|
+
blastRadiusKeys: Set<string>,
|
|
114
|
+
reg: MermaidNodeRegistry,
|
|
115
|
+
sgCounter: number,
|
|
116
|
+
): void {
|
|
117
|
+
if (blastRadiusKeys.size === 0) return;
|
|
118
|
+
const sgId = `sg${sgCounter}`;
|
|
119
|
+
lines.push(` subgraph ${sgId}["Callers **(blast radius)**"]`);
|
|
120
|
+
for (const key of blastRadiusKeys) {
|
|
121
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${reg.nodeLabels.get(key)}"]`);
|
|
107
122
|
}
|
|
123
|
+
lines.push(' end');
|
|
124
|
+
lines.push(` style ${sgId} fill:#f3e5f5,stroke:#9c27b0`);
|
|
125
|
+
}
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
export function diffImpactMermaid(
|
|
128
|
+
customDbPath: string,
|
|
129
|
+
opts: {
|
|
130
|
+
noTests?: boolean;
|
|
131
|
+
depth?: number;
|
|
132
|
+
staged?: boolean;
|
|
133
|
+
ref?: string;
|
|
134
|
+
includeImplementors?: boolean;
|
|
135
|
+
limit?: number;
|
|
136
|
+
offset?: number;
|
|
137
|
+
config?: any;
|
|
138
|
+
} = {},
|
|
139
|
+
): string {
|
|
140
|
+
const data: any = diffImpactData(customDbPath, opts);
|
|
141
|
+
if ('error' in data) return data.error as string;
|
|
142
|
+
if (data.changedFiles === 0 || data.affectedFunctions.length === 0) {
|
|
143
|
+
return 'flowchart TB\n none["No impacted functions detected"]';
|
|
118
144
|
}
|
|
119
145
|
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
const newFileSet = new Set<string>(data.newFiles || []);
|
|
147
|
+
const lines = ['flowchart TB'];
|
|
148
|
+
|
|
149
|
+
const reg = createNodeRegistry();
|
|
150
|
+
registerAllNodes(reg, data.affectedFunctions);
|
|
151
|
+
|
|
152
|
+
const edges = collectEdges(data.affectedFunctions);
|
|
153
|
+
const { blastRadiusKeys, intermediateKeys } = classifyCallerNodes(edges);
|
|
154
|
+
|
|
155
|
+
const sgCounter = emitFileSubgraphs(lines, data.affectedFunctions, newFileSet, reg);
|
|
156
|
+
|
|
157
|
+
for (const key of intermediateKeys) {
|
|
158
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${reg.nodeLabels.get(key)}"]`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
emitBlastRadiusSubgraph(lines, blastRadiusKeys, reg, sgCounter);
|
|
162
|
+
|
|
163
|
+
for (const edgeKey of edges.allEdges) {
|
|
122
164
|
const [from, to] = edgeKey.split('|') as [string, string];
|
|
123
|
-
lines.push(` ${nodeIdMap.get(from)} --> ${nodeIdMap.get(to)}`);
|
|
165
|
+
lines.push(` ${reg.nodeIdMap.get(from)} --> ${reg.nodeIdMap.get(to)}`);
|
|
124
166
|
}
|
|
125
167
|
|
|
126
168
|
return lines.join('\n');
|
|
@@ -71,28 +71,36 @@ function printExportSymbols(results: ExportSymbol[]): void {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function
|
|
75
|
-
// Group by origin file
|
|
74
|
+
function groupByOriginFile(reexportedSymbols: ReexportedSymbol[]): Map<string, ReexportedSymbol[]> {
|
|
76
75
|
const byOrigin = new Map<string, ReexportedSymbol[]>();
|
|
77
76
|
for (const sym of reexportedSymbols) {
|
|
78
77
|
if (!byOrigin.has(sym.originFile)) byOrigin.set(sym.originFile, []);
|
|
79
78
|
byOrigin.get(sym.originFile)!.push(sym);
|
|
80
79
|
}
|
|
80
|
+
return byOrigin;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printReexportSymbol(sym: ExportSymbol, indent: string): void {
|
|
84
|
+
const icon = kindIcon(sym.kind);
|
|
85
|
+
const sig = sym.signature?.params ? `(${sym.signature.params})` : '';
|
|
86
|
+
const role = sym.role ? ` [${sym.role}]` : '';
|
|
87
|
+
console.log(`${indent}${icon} ${sym.name}${sig}${role} :${sym.line}`);
|
|
88
|
+
if (sym.consumers.length === 0) {
|
|
89
|
+
console.log(`${indent} (no consumers)`);
|
|
90
|
+
} else {
|
|
91
|
+
for (const c of sym.consumers) {
|
|
92
|
+
console.log(`${indent} <- ${c.name} (${c.file}:${c.line})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function printReexportedSymbols(reexportedSymbols: ReexportedSymbol[]): void {
|
|
98
|
+
const byOrigin = groupByOriginFile(reexportedSymbols);
|
|
81
99
|
|
|
82
100
|
for (const [originFile, syms] of byOrigin) {
|
|
83
101
|
console.log(`\n from ${originFile}:`);
|
|
84
102
|
for (const sym of syms) {
|
|
85
|
-
|
|
86
|
-
const sig = sym.signature?.params ? `(${sym.signature.params})` : '';
|
|
87
|
-
const role = sym.role ? ` [${sym.role}]` : '';
|
|
88
|
-
console.log(` ${icon} ${sym.name}${sig}${role} :${sym.line}`);
|
|
89
|
-
if (sym.consumers.length === 0) {
|
|
90
|
-
console.log(' (no consumers)');
|
|
91
|
-
} else {
|
|
92
|
-
for (const c of sym.consumers) {
|
|
93
|
-
console.log(` <- ${c.name} (${c.file}:${c.line})`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
103
|
+
printReexportSymbol(sym, ' ');
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
}
|
|
@@ -228,6 +228,20 @@ export function impactAnalysis(file: string, customDbPath: string, opts: OutputO
|
|
|
228
228
|
console.log(`\n Total: ${data.totalDependents} files transitively depend on "${file}"\n`);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function printFnImpactLevels(levels: Record<string, SymbolRef[]>): void {
|
|
232
|
+
if (Object.keys(levels).length === 0) {
|
|
233
|
+
console.log(` No callers found.`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
for (const [level, fns] of Object.entries(levels).sort((a, b) => Number(a[0]) - Number(b[0]))) {
|
|
237
|
+
const l = parseInt(level, 10);
|
|
238
|
+
console.log(` ${'--'.repeat(l)} Level ${level} (${fns.length} functions):`);
|
|
239
|
+
for (const f of fns.slice(0, 20))
|
|
240
|
+
console.log(` ${' '.repeat(l)}^ ${kindIcon(f.kind)} ${f.name} ${f.file}:${f.line}`);
|
|
241
|
+
if (fns.length > 20) console.log(` ... and ${fns.length - 20} more`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
231
245
|
export function fnImpact(name: string, customDbPath: string, opts: OutputOpts = {}): void {
|
|
232
246
|
const data = fnImpactData(name, customDbPath, opts) as unknown as FnImpactData;
|
|
233
247
|
if (outputResult(data as unknown as Record<string, unknown>, 'results', opts)) return;
|
|
@@ -239,19 +253,7 @@ export function fnImpact(name: string, customDbPath: string, opts: OutputOpts =
|
|
|
239
253
|
|
|
240
254
|
for (const r of data.results) {
|
|
241
255
|
console.log(`\nFunction impact: ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}\n`);
|
|
242
|
-
|
|
243
|
-
console.log(` No callers found.`);
|
|
244
|
-
} else {
|
|
245
|
-
for (const [level, fns] of Object.entries(r.levels).sort(
|
|
246
|
-
(a, b) => Number(a[0]) - Number(b[0]),
|
|
247
|
-
)) {
|
|
248
|
-
const l = parseInt(level, 10);
|
|
249
|
-
console.log(` ${'--'.repeat(l)} Level ${level} (${fns.length} functions):`);
|
|
250
|
-
for (const f of fns.slice(0, 20))
|
|
251
|
-
console.log(` ${' '.repeat(l)}^ ${kindIcon(f.kind)} ${f.name} ${f.file}:${f.line}`);
|
|
252
|
-
if (fns.length > 20) console.log(` ... and ${fns.length - 20} more`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
256
|
+
printFnImpactLevels(r.levels);
|
|
255
257
|
console.log(`\n Total: ${r.totalDependents} functions transitively depend on ${r.name}\n`);
|
|
256
258
|
}
|
|
257
259
|
}
|