@optave/codegraph 3.1.5 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/package.json +7 -7
- package/src/ast-analysis/engine.js +252 -258
- package/src/ast-analysis/shared.js +0 -12
- package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
- package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
- package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
- package/src/cli/commands/ast.js +2 -1
- package/src/cli/commands/audit.js +2 -1
- package/src/cli/commands/batch.js +2 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/cfg.js +2 -1
- package/src/cli/commands/check.js +20 -23
- package/src/cli/commands/children.js +6 -1
- package/src/cli/commands/complexity.js +2 -1
- package/src/cli/commands/context.js +6 -1
- package/src/cli/commands/dataflow.js +2 -1
- package/src/cli/commands/deps.js +8 -3
- package/src/cli/commands/flow.js +2 -1
- package/src/cli/commands/fn-impact.js +6 -1
- package/src/cli/commands/owners.js +4 -2
- package/src/cli/commands/query.js +6 -1
- package/src/cli/commands/roles.js +2 -1
- package/src/cli/commands/search.js +8 -2
- package/src/cli/commands/sequence.js +2 -1
- package/src/cli/commands/triage.js +38 -27
- package/src/db/connection.js +18 -12
- package/src/db/migrations.js +41 -64
- package/src/db/query-builder.js +60 -4
- package/src/db/repository/in-memory-repository.js +27 -16
- package/src/db/repository/nodes.js +8 -10
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +174 -190
- package/src/domain/analysis/dependencies.js +200 -146
- package/src/domain/analysis/exports.js +3 -2
- package/src/domain/analysis/impact.js +267 -152
- package/src/domain/analysis/module-map.js +247 -221
- package/src/domain/analysis/roles.js +8 -5
- package/src/domain/analysis/symbol-lookup.js +7 -5
- package/src/domain/graph/builder/helpers.js +1 -1
- package/src/domain/graph/builder/incremental.js +116 -90
- package/src/domain/graph/builder/pipeline.js +106 -80
- package/src/domain/graph/builder/stages/build-edges.js +318 -239
- package/src/domain/graph/builder/stages/detect-changes.js +198 -177
- package/src/domain/graph/builder/stages/insert-nodes.js +147 -139
- package/src/domain/graph/watcher.js +2 -2
- package/src/domain/parser.js +20 -11
- package/src/domain/queries.js +1 -0
- package/src/domain/search/search/filters.js +9 -5
- package/src/domain/search/search/keyword.js +12 -5
- package/src/domain/search/search/prepare.js +13 -5
- package/src/extractors/csharp.js +224 -207
- package/src/extractors/go.js +176 -172
- package/src/extractors/hcl.js +94 -78
- package/src/extractors/java.js +213 -207
- package/src/extractors/javascript.js +274 -304
- package/src/extractors/php.js +234 -221
- package/src/extractors/python.js +252 -250
- package/src/extractors/ruby.js +192 -185
- package/src/extractors/rust.js +182 -167
- package/src/features/ast.js +5 -3
- package/src/features/audit.js +4 -2
- package/src/features/boundaries.js +98 -83
- package/src/features/cfg.js +134 -143
- package/src/features/communities.js +68 -53
- package/src/features/complexity.js +143 -132
- package/src/features/dataflow.js +146 -149
- package/src/features/export.js +3 -3
- package/src/features/graph-enrichment.js +2 -2
- package/src/features/manifesto.js +9 -6
- package/src/features/owners.js +4 -3
- package/src/features/sequence.js +152 -141
- package/src/features/shared/find-nodes.js +31 -0
- package/src/features/structure.js +130 -99
- package/src/features/triage.js +83 -68
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.js +1 -0
- package/src/mcp/server.js +65 -56
- package/src/mcp/tool-registry.js +13 -0
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/index.js +2 -0
- package/src/presentation/brief.js +51 -0
- package/src/presentation/queries-cli/exports.js +21 -14
- package/src/presentation/queries-cli/impact.js +55 -39
- package/src/presentation/queries-cli/inspect.js +184 -189
- package/src/presentation/queries-cli/overview.js +57 -58
- package/src/presentation/queries-cli/path.js +36 -29
- package/src/presentation/table.js +0 -8
- package/src/shared/generators.js +7 -3
- package/src/shared/kinds.js +1 -1
package/src/mcp/server.js
CHANGED
|
@@ -21,45 +21,84 @@ import { TOOL_HANDLERS } from './tools/index.js';
|
|
|
21
21
|
* @param {boolean} [options.multiRepo] - Enable multi-repo access (default: false)
|
|
22
22
|
* @param {string[]} [options.allowedRepos] - Restrict access to these repo names only
|
|
23
23
|
*/
|
|
24
|
-
|
|
25
|
-
const { allowedRepos } = options;
|
|
26
|
-
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
27
|
-
let Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema;
|
|
24
|
+
async function loadMCPSdk() {
|
|
28
25
|
try {
|
|
29
26
|
const sdk = await import('@modelcontextprotocol/sdk/server/index.js');
|
|
30
|
-
Server = sdk.Server;
|
|
31
27
|
const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
32
|
-
StdioServerTransport = transport.StdioServerTransport;
|
|
33
28
|
const types = await import('@modelcontextprotocol/sdk/types.js');
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
return {
|
|
30
|
+
Server: sdk.Server,
|
|
31
|
+
StdioServerTransport: transport.StdioServerTransport,
|
|
32
|
+
ListToolsRequestSchema: types.ListToolsRequestSchema,
|
|
33
|
+
CallToolRequestSchema: types.CallToolRequestSchema,
|
|
34
|
+
};
|
|
36
35
|
} catch {
|
|
37
36
|
throw new ConfigError(
|
|
38
37
|
'MCP server requires @modelcontextprotocol/sdk.\nInstall it with: npm install @modelcontextprotocol/sdk',
|
|
39
38
|
);
|
|
40
39
|
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
44
|
-
// are still loading. These are lazy-loaded on the first tool call
|
|
45
|
-
// and cached for subsequent calls.
|
|
42
|
+
function createLazyLoaders() {
|
|
46
43
|
let _queries;
|
|
47
44
|
let _Database;
|
|
45
|
+
return {
|
|
46
|
+
async getQueries() {
|
|
47
|
+
if (!_queries) _queries = await import('../domain/queries.js');
|
|
48
|
+
return _queries;
|
|
49
|
+
},
|
|
50
|
+
getDatabase() {
|
|
51
|
+
if (!_Database) {
|
|
52
|
+
const require = createRequire(import.meta.url);
|
|
53
|
+
_Database = require('better-sqlite3');
|
|
54
|
+
}
|
|
55
|
+
return _Database;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
async function resolveDbPath(customDbPath, args, allowedRepos) {
|
|
61
|
+
let dbPath = customDbPath || undefined;
|
|
62
|
+
if (args.repo) {
|
|
63
|
+
if (allowedRepos && !allowedRepos.includes(args.repo)) {
|
|
64
|
+
throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
|
|
52
65
|
}
|
|
53
|
-
|
|
66
|
+
const { resolveRepoDbPath } = await import('../infrastructure/registry.js');
|
|
67
|
+
const resolved = resolveRepoDbPath(args.repo);
|
|
68
|
+
if (!resolved)
|
|
69
|
+
throw new ConfigError(
|
|
70
|
+
`Repository "${args.repo}" not found in registry or its database is missing.`,
|
|
71
|
+
);
|
|
72
|
+
dbPath = resolved;
|
|
54
73
|
}
|
|
74
|
+
return dbPath;
|
|
75
|
+
}
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
function validateMultiRepoAccess(multiRepo, name, args) {
|
|
78
|
+
if (!multiRepo && args.repo) {
|
|
79
|
+
throw new ConfigError(
|
|
80
|
+
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (!multiRepo && name === 'list_repos') {
|
|
84
|
+
throw new ConfigError(
|
|
85
|
+
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
|
|
86
|
+
);
|
|
62
87
|
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function startMCPServer(customDbPath, options = {}) {
|
|
91
|
+
const { allowedRepos } = options;
|
|
92
|
+
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
93
|
+
|
|
94
|
+
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
95
|
+
await loadMCPSdk();
|
|
96
|
+
|
|
97
|
+
// Connect transport FIRST so the server can receive the client's
|
|
98
|
+
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
99
|
+
// are still loading. These are lazy-loaded on the first tool call
|
|
100
|
+
// and cached for subsequent calls.
|
|
101
|
+
const { getQueries, getDatabase } = createLazyLoaders();
|
|
63
102
|
|
|
64
103
|
const server = new Server(
|
|
65
104
|
{ name: 'codegraph', version: '1.0.0' },
|
|
@@ -73,47 +112,17 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
73
112
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
74
113
|
const { name, arguments: args } = request.params;
|
|
75
114
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
if (!multiRepo && name === 'list_repos') {
|
|
82
|
-
throw new ConfigError(
|
|
83
|
-
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to list repositories.',
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let dbPath = customDbPath || undefined;
|
|
88
|
-
if (args.repo) {
|
|
89
|
-
if (allowedRepos && !allowedRepos.includes(args.repo)) {
|
|
90
|
-
throw new ConfigError(`Repository "${args.repo}" is not in the allowed repos list.`);
|
|
91
|
-
}
|
|
92
|
-
const { resolveRepoDbPath } = await import('../infrastructure/registry.js');
|
|
93
|
-
const resolved = resolveRepoDbPath(args.repo);
|
|
94
|
-
if (!resolved)
|
|
95
|
-
throw new ConfigError(
|
|
96
|
-
`Repository "${args.repo}" not found in registry or its database is missing.`,
|
|
97
|
-
);
|
|
98
|
-
dbPath = resolved;
|
|
99
|
-
}
|
|
115
|
+
validateMultiRepoAccess(multiRepo, name, args);
|
|
116
|
+
const dbPath = await resolveDbPath(customDbPath, args, allowedRepos);
|
|
100
117
|
|
|
101
118
|
const toolEntry = TOOL_HANDLERS.get(name);
|
|
102
119
|
if (!toolEntry) {
|
|
103
120
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
const ctx = {
|
|
107
|
-
dbPath,
|
|
108
|
-
getQueries,
|
|
109
|
-
getDatabase,
|
|
110
|
-
findDbPath,
|
|
111
|
-
allowedRepos,
|
|
112
|
-
MCP_MAX_LIMIT,
|
|
113
|
-
};
|
|
114
|
-
|
|
123
|
+
const ctx = { dbPath, getQueries, getDatabase, findDbPath, allowedRepos, MCP_MAX_LIMIT };
|
|
115
124
|
const result = await toolEntry.handler(args, ctx);
|
|
116
|
-
if (result?.content) return result;
|
|
125
|
+
if (result?.content) return result;
|
|
117
126
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
118
127
|
} catch (err) {
|
|
119
128
|
const code = err instanceof CodegraphError ? err.code : 'UNKNOWN_ERROR';
|
package/src/mcp/tool-registry.js
CHANGED
|
@@ -99,6 +99,19 @@ const BASE_TOOLS = [
|
|
|
99
99
|
required: ['file'],
|
|
100
100
|
},
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
name: 'brief',
|
|
104
|
+
description:
|
|
105
|
+
'Token-efficient file summary: symbols with roles and transitive caller counts, importer counts, and file risk tier (high/medium/low). Designed for context injection.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
110
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
111
|
+
},
|
|
112
|
+
required: ['file'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
102
115
|
{
|
|
103
116
|
name: 'file_exports',
|
|
104
117
|
description:
|
package/src/mcp/tools/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as astQuery from './ast-query.js';
|
|
|
6
6
|
import * as audit from './audit.js';
|
|
7
7
|
import * as batchQuery from './batch-query.js';
|
|
8
8
|
import * as branchCompare from './branch-compare.js';
|
|
9
|
+
import * as brief from './brief.js';
|
|
9
10
|
import * as cfg from './cfg.js';
|
|
10
11
|
import * as check from './check.js';
|
|
11
12
|
import * as coChanges from './co-changes.js';
|
|
@@ -67,5 +68,6 @@ export const TOOL_HANDLERS = new Map([
|
|
|
67
68
|
[dataflow.name, dataflow],
|
|
68
69
|
[check.name, check],
|
|
69
70
|
[astQuery.name, astQuery],
|
|
71
|
+
[brief.name, brief],
|
|
70
72
|
[listRepos.name, listRepos],
|
|
71
73
|
]);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { briefData } from '../domain/analysis/brief.js';
|
|
2
|
+
import { outputResult } from './result-formatter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format a compact brief for hook context injection.
|
|
6
|
+
* Single-block, token-efficient output.
|
|
7
|
+
*
|
|
8
|
+
* Example:
|
|
9
|
+
* src/domain/graph/builder.js [HIGH RISK]
|
|
10
|
+
* Symbols: buildGraph [core, 12 callers], collectFiles [leaf, 2 callers]
|
|
11
|
+
* Imports: src/db/index.js, src/domain/parser.js
|
|
12
|
+
* Imported by: src/cli/commands/build.js (+8 transitive)
|
|
13
|
+
*/
|
|
14
|
+
export function brief(file, customDbPath, opts = {}) {
|
|
15
|
+
const data = briefData(file, customDbPath, opts);
|
|
16
|
+
if (outputResult(data, 'results', opts)) return;
|
|
17
|
+
|
|
18
|
+
if (data.results.length === 0) {
|
|
19
|
+
console.log(`No file matching "${file}" in graph`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const r of data.results) {
|
|
24
|
+
console.log(`${r.file} [${r.risk.toUpperCase()} RISK]`);
|
|
25
|
+
|
|
26
|
+
// Symbols line
|
|
27
|
+
if (r.symbols.length > 0) {
|
|
28
|
+
const parts = r.symbols.map((s) => {
|
|
29
|
+
const tags = [];
|
|
30
|
+
if (s.role) tags.push(s.role);
|
|
31
|
+
tags.push(`${s.callerCount} caller${s.callerCount !== 1 ? 's' : ''}`);
|
|
32
|
+
return `${s.name} [${tags.join(', ')}]`;
|
|
33
|
+
});
|
|
34
|
+
console.log(` Symbols: ${parts.join(', ')}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Imports line
|
|
38
|
+
if (r.imports.length > 0) {
|
|
39
|
+
console.log(` Imports: ${r.imports.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Imported by line with transitive count
|
|
43
|
+
if (r.importedBy.length > 0) {
|
|
44
|
+
const transitive = r.totalImporterCount - r.importedBy.length;
|
|
45
|
+
const suffix = transitive > 0 ? ` (+${transitive} transitive)` : '';
|
|
46
|
+
console.log(` Imported by: ${r.importedBy.join(', ')}${suffix}`);
|
|
47
|
+
} else if (r.totalImporterCount > 0) {
|
|
48
|
+
console.log(` Imported by: ${r.totalImporterCount} transitive importers`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import { exportsData, kindIcon } from '../../domain/queries.js';
|
|
2
2
|
import { outputResult } from '../../infrastructure/result-formatter.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const data = exportsData(file, customDbPath, opts);
|
|
6
|
-
if (outputResult(data, 'results', opts)) return;
|
|
7
|
-
|
|
8
|
-
if (data.results.length === 0) {
|
|
9
|
-
if (opts.unused) {
|
|
10
|
-
console.log(`No unused exports found for "${file}".`);
|
|
11
|
-
} else {
|
|
12
|
-
console.log(`No exported symbols found for "${file}". Run "codegraph build" first.`);
|
|
13
|
-
}
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
4
|
+
function printExportHeader(data, opts) {
|
|
17
5
|
if (opts.unused) {
|
|
18
6
|
console.log(
|
|
19
7
|
`\n# ${data.file} — ${data.totalUnused} unused export${data.totalUnused !== 1 ? 's' : ''} (of ${data.totalExported} exported)\n`,
|
|
@@ -24,8 +12,10 @@ export function fileExports(file, customDbPath, opts = {}) {
|
|
|
24
12
|
`\n# ${data.file} — ${data.totalExported} exported${unusedNote}, ${data.totalInternal} internal\n`,
|
|
25
13
|
);
|
|
26
14
|
}
|
|
15
|
+
}
|
|
27
16
|
|
|
28
|
-
|
|
17
|
+
function printExportSymbols(results) {
|
|
18
|
+
for (const sym of results) {
|
|
29
19
|
const icon = kindIcon(sym.kind);
|
|
30
20
|
const sig = sym.signature?.params ? `(${sym.signature.params})` : '';
|
|
31
21
|
const role = sym.role ? ` [${sym.role}]` : '';
|
|
@@ -38,6 +28,23 @@ export function fileExports(file, customDbPath, opts = {}) {
|
|
|
38
28
|
}
|
|
39
29
|
}
|
|
40
30
|
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function fileExports(file, customDbPath, opts = {}) {
|
|
34
|
+
const data = exportsData(file, customDbPath, opts);
|
|
35
|
+
if (outputResult(data, 'results', opts)) return;
|
|
36
|
+
|
|
37
|
+
if (data.results.length === 0) {
|
|
38
|
+
if (opts.unused) {
|
|
39
|
+
console.log(`No unused exports found for "${file}".`);
|
|
40
|
+
} else {
|
|
41
|
+
console.log(`No exported symbols found for "${file}". Run "codegraph build" first.`);
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
printExportHeader(data, opts);
|
|
47
|
+
printExportSymbols(data.results);
|
|
41
48
|
|
|
42
49
|
if (data.reexports.length > 0) {
|
|
43
50
|
console.log(`\n Re-exports: ${data.reexports.map((r) => r.file).join(', ')}`);
|
|
@@ -132,6 +132,56 @@ export function fnImpact(name, customDbPath, opts = {}) {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function printDiffFunctions(data) {
|
|
136
|
+
console.log(`\ndiff-impact: ${data.changedFiles} files changed\n`);
|
|
137
|
+
console.log(` ${data.affectedFunctions.length} functions changed:\n`);
|
|
138
|
+
for (const fn of data.affectedFunctions) {
|
|
139
|
+
console.log(` ${kindIcon(fn.kind)} ${fn.name} -- ${fn.file}:${fn.line}`);
|
|
140
|
+
if (fn.transitiveCallers > 0) console.log(` ^ ${fn.transitiveCallers} transitive callers`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function printDiffCoupled(data) {
|
|
145
|
+
if (!data.historicallyCoupled?.length) return;
|
|
146
|
+
console.log('\n Historically coupled (not in static graph):\n');
|
|
147
|
+
for (const c of data.historicallyCoupled) {
|
|
148
|
+
const pct = `${(c.jaccard * 100).toFixed(0)}%`;
|
|
149
|
+
console.log(
|
|
150
|
+
` ${c.file} <- coupled with ${c.coupledWith} (${pct}, ${c.commitCount} commits)`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function printDiffOwnership(data) {
|
|
156
|
+
if (!data.ownership) return;
|
|
157
|
+
console.log(`\n Affected owners: ${data.ownership.affectedOwners.join(', ')}`);
|
|
158
|
+
console.log(` Suggested reviewers: ${data.ownership.suggestedReviewers.join(', ')}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function printDiffBoundaries(data) {
|
|
162
|
+
if (!data.boundaryViolations?.length) return;
|
|
163
|
+
console.log(`\n Boundary violations (${data.boundaryViolationCount}):\n`);
|
|
164
|
+
for (const v of data.boundaryViolations) {
|
|
165
|
+
console.log(` [${v.name}] ${v.file} -> ${v.targetFile}`);
|
|
166
|
+
if (v.message) console.log(` ${v.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function printDiffSummary(summary) {
|
|
171
|
+
if (!summary) return;
|
|
172
|
+
let line = `\n Summary: ${summary.functionsChanged} functions changed -> ${summary.callersAffected} callers affected across ${summary.filesAffected} files`;
|
|
173
|
+
if (summary.historicallyCoupledCount > 0) {
|
|
174
|
+
line += `, ${summary.historicallyCoupledCount} historically coupled`;
|
|
175
|
+
}
|
|
176
|
+
if (summary.ownersAffected > 0) {
|
|
177
|
+
line += `, ${summary.ownersAffected} owners affected`;
|
|
178
|
+
}
|
|
179
|
+
if (summary.boundaryViolationCount > 0) {
|
|
180
|
+
line += `, ${summary.boundaryViolationCount} boundary violations`;
|
|
181
|
+
}
|
|
182
|
+
console.log(`${line}\n`);
|
|
183
|
+
}
|
|
184
|
+
|
|
135
185
|
export function diffImpact(customDbPath, opts = {}) {
|
|
136
186
|
if (opts.format === 'mermaid') {
|
|
137
187
|
console.log(diffImpactMermaid(customDbPath, opts));
|
|
@@ -156,43 +206,9 @@ export function diffImpact(customDbPath, opts = {}) {
|
|
|
156
206
|
return;
|
|
157
207
|
}
|
|
158
208
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
if (data.historicallyCoupled && data.historicallyCoupled.length > 0) {
|
|
166
|
-
console.log('\n Historically coupled (not in static graph):\n');
|
|
167
|
-
for (const c of data.historicallyCoupled) {
|
|
168
|
-
const pct = `${(c.jaccard * 100).toFixed(0)}%`;
|
|
169
|
-
console.log(
|
|
170
|
-
` ${c.file} <- coupled with ${c.coupledWith} (${pct}, ${c.commitCount} commits)`,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (data.ownership) {
|
|
175
|
-
console.log(`\n Affected owners: ${data.ownership.affectedOwners.join(', ')}`);
|
|
176
|
-
console.log(` Suggested reviewers: ${data.ownership.suggestedReviewers.join(', ')}`);
|
|
177
|
-
}
|
|
178
|
-
if (data.boundaryViolations && data.boundaryViolations.length > 0) {
|
|
179
|
-
console.log(`\n Boundary violations (${data.boundaryViolationCount}):\n`);
|
|
180
|
-
for (const v of data.boundaryViolations) {
|
|
181
|
-
console.log(` [${v.name}] ${v.file} -> ${v.targetFile}`);
|
|
182
|
-
if (v.message) console.log(` ${v.message}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (data.summary) {
|
|
186
|
-
let summaryLine = `\n Summary: ${data.summary.functionsChanged} functions changed -> ${data.summary.callersAffected} callers affected across ${data.summary.filesAffected} files`;
|
|
187
|
-
if (data.summary.historicallyCoupledCount > 0) {
|
|
188
|
-
summaryLine += `, ${data.summary.historicallyCoupledCount} historically coupled`;
|
|
189
|
-
}
|
|
190
|
-
if (data.summary.ownersAffected > 0) {
|
|
191
|
-
summaryLine += `, ${data.summary.ownersAffected} owners affected`;
|
|
192
|
-
}
|
|
193
|
-
if (data.summary.boundaryViolationCount > 0) {
|
|
194
|
-
summaryLine += `, ${data.summary.boundaryViolationCount} boundary violations`;
|
|
195
|
-
}
|
|
196
|
-
console.log(`${summaryLine}\n`);
|
|
197
|
-
}
|
|
209
|
+
printDiffFunctions(data);
|
|
210
|
+
printDiffCoupled(data);
|
|
211
|
+
printDiffOwnership(data);
|
|
212
|
+
printDiffBoundaries(data);
|
|
213
|
+
printDiffSummary(data.summary);
|
|
198
214
|
}
|