@optave/codegraph 2.5.1 → 3.0.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 +216 -89
- package/package.json +8 -7
- package/src/ast.js +392 -0
- package/src/audit.js +423 -0
- package/src/batch.js +180 -0
- package/src/boundaries.js +346 -0
- package/src/builder.js +375 -92
- package/src/cfg.js +1451 -0
- package/src/change-journal.js +130 -0
- package/src/check.js +432 -0
- package/src/cli.js +734 -107
- package/src/cochange.js +5 -2
- package/src/communities.js +7 -1
- package/src/complexity.js +124 -17
- package/src/config.js +10 -0
- package/src/dataflow.js +1187 -0
- package/src/db.js +96 -0
- package/src/embedder.js +359 -47
- package/src/export.js +305 -0
- package/src/extractors/csharp.js +64 -1
- package/src/extractors/go.js +66 -1
- package/src/extractors/hcl.js +22 -0
- package/src/extractors/java.js +61 -1
- package/src/extractors/javascript.js +142 -0
- package/src/extractors/php.js +79 -0
- package/src/extractors/python.js +134 -0
- package/src/extractors/ruby.js +89 -0
- package/src/extractors/rust.js +71 -1
- package/src/flow.js +4 -4
- package/src/index.js +78 -3
- package/src/manifesto.js +69 -1
- package/src/mcp.js +702 -193
- package/src/owners.js +359 -0
- package/src/paginate.js +37 -2
- package/src/parser.js +8 -0
- package/src/queries.js +590 -50
- package/src/snapshot.js +149 -0
- package/src/structure.js +9 -3
- package/src/triage.js +273 -0
- package/src/viewer.js +948 -0
- package/src/watcher.js +36 -1
package/src/cochange.js
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import { normalizePath } from './constants.js';
|
|
12
12
|
import { closeDb, findDbPath, initSchema, openDb, openReadonlyOrFail } from './db.js';
|
|
13
13
|
import { warn } from './logger.js';
|
|
14
|
+
import { paginateResult } from './paginate.js';
|
|
14
15
|
import { isTestFile } from './queries.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -313,7 +314,8 @@ export function coChangeData(file, customDbPath, opts = {}) {
|
|
|
313
314
|
const meta = getCoChangeMeta(db);
|
|
314
315
|
closeDb(db);
|
|
315
316
|
|
|
316
|
-
|
|
317
|
+
const base = { file: resolvedFile, partners, meta };
|
|
318
|
+
return paginateResult(base, 'partners', { limit: opts.limit, offset: opts.offset });
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
/**
|
|
@@ -365,7 +367,8 @@ export function coChangeTopData(customDbPath, opts = {}) {
|
|
|
365
367
|
const meta = getCoChangeMeta(db);
|
|
366
368
|
closeDb(db);
|
|
367
369
|
|
|
368
|
-
|
|
370
|
+
const base = { pairs, meta };
|
|
371
|
+
return paginateResult(base, 'pairs', { limit: opts.limit, offset: opts.offset });
|
|
369
372
|
}
|
|
370
373
|
|
|
371
374
|
/**
|
package/src/communities.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import Graph from 'graphology';
|
|
3
3
|
import louvain from 'graphology-communities-louvain';
|
|
4
4
|
import { openReadonlyOrFail } from './db.js';
|
|
5
|
+
import { paginateResult, printNdjson } from './paginate.js';
|
|
5
6
|
import { isTestFile } from './queries.js';
|
|
6
7
|
|
|
7
8
|
// ─── Graph Construction ───────────────────────────────────────────────
|
|
@@ -201,7 +202,7 @@ export function communitiesData(customDbPath, opts = {}) {
|
|
|
201
202
|
|
|
202
203
|
const driftScore = Math.round(((splitRatio + mergeRatio) / 2) * 100);
|
|
203
204
|
|
|
204
|
-
|
|
205
|
+
const base = {
|
|
205
206
|
communities: opts.drift ? [] : communities,
|
|
206
207
|
modularity: +modularity.toFixed(4),
|
|
207
208
|
drift: { splitCandidates, mergeCandidates },
|
|
@@ -212,6 +213,7 @@ export function communitiesData(customDbPath, opts = {}) {
|
|
|
212
213
|
driftScore,
|
|
213
214
|
},
|
|
214
215
|
};
|
|
216
|
+
return paginateResult(base, 'communities', { limit: opts.limit, offset: opts.offset });
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
/**
|
|
@@ -238,6 +240,10 @@ export function communitySummaryForStats(customDbPath, opts = {}) {
|
|
|
238
240
|
export function communities(customDbPath, opts = {}) {
|
|
239
241
|
const data = communitiesData(customDbPath, opts);
|
|
240
242
|
|
|
243
|
+
if (opts.ndjson) {
|
|
244
|
+
printNdjson(data, 'communities');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
241
247
|
if (opts.json) {
|
|
242
248
|
console.log(JSON.stringify(data, null, 2));
|
|
243
249
|
return;
|
package/src/complexity.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { loadConfig } from './config.js';
|
|
4
4
|
import { openReadonlyOrFail } from './db.js';
|
|
5
5
|
import { info } from './logger.js';
|
|
6
|
+
import { paginateResult, printNdjson } from './paginate.js';
|
|
6
7
|
import { LANGUAGE_REGISTRY } from './parser.js';
|
|
7
8
|
import { isTestFile } from './queries.js';
|
|
8
9
|
|
|
@@ -182,7 +183,7 @@ const CSHARP_RULES = {
|
|
|
182
183
|
'if_statement',
|
|
183
184
|
'else_clause',
|
|
184
185
|
'for_statement',
|
|
185
|
-
'
|
|
186
|
+
'foreach_statement',
|
|
186
187
|
'while_statement',
|
|
187
188
|
'do_statement',
|
|
188
189
|
'catch_clause',
|
|
@@ -196,7 +197,7 @@ const CSHARP_RULES = {
|
|
|
196
197
|
nestingNodes: new Set([
|
|
197
198
|
'if_statement',
|
|
198
199
|
'for_statement',
|
|
199
|
-
'
|
|
200
|
+
'foreach_statement',
|
|
200
201
|
'while_statement',
|
|
201
202
|
'do_statement',
|
|
202
203
|
'catch_clause',
|
|
@@ -210,9 +211,9 @@ const CSHARP_RULES = {
|
|
|
210
211
|
'local_function_statement',
|
|
211
212
|
]),
|
|
212
213
|
ifNodeType: 'if_statement',
|
|
213
|
-
elseNodeType:
|
|
214
|
+
elseNodeType: null,
|
|
214
215
|
elifNodeType: null,
|
|
215
|
-
elseViaAlternative:
|
|
216
|
+
elseViaAlternative: true,
|
|
216
217
|
switchLikeNodes: new Set(['switch_statement']),
|
|
217
218
|
};
|
|
218
219
|
|
|
@@ -290,7 +291,7 @@ export const COMPLEXITY_RULES = new Map([
|
|
|
290
291
|
['go', GO_RULES],
|
|
291
292
|
['rust', RUST_RULES],
|
|
292
293
|
['java', JAVA_RULES],
|
|
293
|
-
['
|
|
294
|
+
['csharp', CSHARP_RULES],
|
|
294
295
|
['ruby', RUBY_RULES],
|
|
295
296
|
['php', PHP_RULES],
|
|
296
297
|
]);
|
|
@@ -1025,7 +1026,7 @@ export const HALSTEAD_RULES = new Map([
|
|
|
1025
1026
|
['go', GO_HALSTEAD],
|
|
1026
1027
|
['rust', RUST_HALSTEAD],
|
|
1027
1028
|
['java', JAVA_HALSTEAD],
|
|
1028
|
-
['
|
|
1029
|
+
['csharp', CSHARP_HALSTEAD],
|
|
1029
1030
|
['ruby', RUBY_HALSTEAD],
|
|
1030
1031
|
['php', PHP_HALSTEAD],
|
|
1031
1032
|
]);
|
|
@@ -1115,7 +1116,7 @@ const COMMENT_PREFIXES = new Map([
|
|
|
1115
1116
|
['go', C_STYLE_PREFIXES],
|
|
1116
1117
|
['rust', C_STYLE_PREFIXES],
|
|
1117
1118
|
['java', C_STYLE_PREFIXES],
|
|
1118
|
-
['
|
|
1119
|
+
['csharp', C_STYLE_PREFIXES],
|
|
1119
1120
|
['python', ['#']],
|
|
1120
1121
|
['ruby', ['#']],
|
|
1121
1122
|
['php', ['//', '#', '/*', '*', '*/']],
|
|
@@ -1573,7 +1574,7 @@ export function computeAllMetrics(functionNode, langId) {
|
|
|
1573
1574
|
/**
|
|
1574
1575
|
* Find the function body node in a parse tree that matches a given line range.
|
|
1575
1576
|
*/
|
|
1576
|
-
function findFunctionNode(rootNode, startLine, _endLine, rules) {
|
|
1577
|
+
export function findFunctionNode(rootNode, startLine, _endLine, rules) {
|
|
1577
1578
|
// tree-sitter lines are 0-indexed
|
|
1578
1579
|
const targetStart = startLine - 1;
|
|
1579
1580
|
|
|
@@ -1799,7 +1800,6 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
|
|
|
1799
1800
|
*/
|
|
1800
1801
|
export function complexityData(customDbPath, opts = {}) {
|
|
1801
1802
|
const db = openReadonlyOrFail(customDbPath);
|
|
1802
|
-
const limit = opts.limit || 20;
|
|
1803
1803
|
const sort = opts.sort || 'cognitive';
|
|
1804
1804
|
const noTests = opts.noTests || false;
|
|
1805
1805
|
const aboveThreshold = opts.aboveThreshold || false;
|
|
@@ -1887,13 +1887,19 @@ export function complexityData(customDbPath, opts = {}) {
|
|
|
1887
1887
|
FROM function_complexity fc
|
|
1888
1888
|
JOIN nodes n ON fc.node_id = n.id
|
|
1889
1889
|
${where} ${having}
|
|
1890
|
-
ORDER BY ${orderBy}
|
|
1891
|
-
LIMIT ?`,
|
|
1890
|
+
ORDER BY ${orderBy}`,
|
|
1892
1891
|
)
|
|
1893
|
-
.all(...params
|
|
1892
|
+
.all(...params);
|
|
1894
1893
|
} catch {
|
|
1894
|
+
// Check if graph has nodes even though complexity table is missing/empty
|
|
1895
|
+
let hasGraph = false;
|
|
1896
|
+
try {
|
|
1897
|
+
hasGraph = db.prepare('SELECT COUNT(*) as c FROM nodes').get().c > 0;
|
|
1898
|
+
} catch {
|
|
1899
|
+
/* ignore */
|
|
1900
|
+
}
|
|
1895
1901
|
db.close();
|
|
1896
|
-
return { functions: [], summary: null, thresholds };
|
|
1902
|
+
return { functions: [], summary: null, thresholds, hasGraph };
|
|
1897
1903
|
}
|
|
1898
1904
|
|
|
1899
1905
|
// Post-filter test files if needed (belt-and-suspenders for isTestFile)
|
|
@@ -1979,8 +1985,99 @@ export function complexityData(customDbPath, opts = {}) {
|
|
|
1979
1985
|
/* ignore */
|
|
1980
1986
|
}
|
|
1981
1987
|
|
|
1988
|
+
// When summary is null (no complexity rows), check if graph has nodes
|
|
1989
|
+
let hasGraph = false;
|
|
1990
|
+
if (summary === null) {
|
|
1991
|
+
try {
|
|
1992
|
+
hasGraph = db.prepare('SELECT COUNT(*) as c FROM nodes').get().c > 0;
|
|
1993
|
+
} catch {
|
|
1994
|
+
/* ignore */
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1982
1998
|
db.close();
|
|
1983
|
-
|
|
1999
|
+
const base = { functions, summary, thresholds, hasGraph };
|
|
2000
|
+
return paginateResult(base, 'functions', { limit: opts.limit, offset: opts.offset });
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/**
|
|
2004
|
+
* Generator: stream complexity rows one-by-one using .iterate() for memory efficiency.
|
|
2005
|
+
* @param {string} [customDbPath]
|
|
2006
|
+
* @param {object} [opts]
|
|
2007
|
+
* @param {boolean} [opts.noTests]
|
|
2008
|
+
* @param {string} [opts.file]
|
|
2009
|
+
* @param {string} [opts.target]
|
|
2010
|
+
* @param {string} [opts.kind]
|
|
2011
|
+
* @param {string} [opts.sort]
|
|
2012
|
+
* @yields {{ name: string, kind: string, file: string, line: number, cognitive: number, cyclomatic: number, maxNesting: number, loc: number, sloc: number }}
|
|
2013
|
+
*/
|
|
2014
|
+
export function* iterComplexity(customDbPath, opts = {}) {
|
|
2015
|
+
const db = openReadonlyOrFail(customDbPath);
|
|
2016
|
+
try {
|
|
2017
|
+
const noTests = opts.noTests || false;
|
|
2018
|
+
const sort = opts.sort || 'cognitive';
|
|
2019
|
+
|
|
2020
|
+
let where = "WHERE n.kind IN ('function','method')";
|
|
2021
|
+
const params = [];
|
|
2022
|
+
|
|
2023
|
+
if (noTests) {
|
|
2024
|
+
where += ` AND n.file NOT LIKE '%.test.%'
|
|
2025
|
+
AND n.file NOT LIKE '%.spec.%'
|
|
2026
|
+
AND n.file NOT LIKE '%__test__%'
|
|
2027
|
+
AND n.file NOT LIKE '%__tests__%'
|
|
2028
|
+
AND n.file NOT LIKE '%.stories.%'`;
|
|
2029
|
+
}
|
|
2030
|
+
if (opts.target) {
|
|
2031
|
+
where += ' AND n.name LIKE ?';
|
|
2032
|
+
params.push(`%${opts.target}%`);
|
|
2033
|
+
}
|
|
2034
|
+
if (opts.file) {
|
|
2035
|
+
where += ' AND n.file LIKE ?';
|
|
2036
|
+
params.push(`%${opts.file}%`);
|
|
2037
|
+
}
|
|
2038
|
+
if (opts.kind) {
|
|
2039
|
+
where += ' AND n.kind = ?';
|
|
2040
|
+
params.push(opts.kind);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
const orderMap = {
|
|
2044
|
+
cognitive: 'fc.cognitive DESC',
|
|
2045
|
+
cyclomatic: 'fc.cyclomatic DESC',
|
|
2046
|
+
nesting: 'fc.max_nesting DESC',
|
|
2047
|
+
mi: 'fc.maintainability_index ASC',
|
|
2048
|
+
volume: 'fc.halstead_volume DESC',
|
|
2049
|
+
effort: 'fc.halstead_effort DESC',
|
|
2050
|
+
bugs: 'fc.halstead_bugs DESC',
|
|
2051
|
+
loc: 'fc.loc DESC',
|
|
2052
|
+
};
|
|
2053
|
+
const orderBy = orderMap[sort] || 'fc.cognitive DESC';
|
|
2054
|
+
|
|
2055
|
+
const stmt = db.prepare(
|
|
2056
|
+
`SELECT n.name, n.kind, n.file, n.line, n.end_line,
|
|
2057
|
+
fc.cognitive, fc.cyclomatic, fc.max_nesting, fc.loc, fc.sloc
|
|
2058
|
+
FROM function_complexity fc
|
|
2059
|
+
JOIN nodes n ON fc.node_id = n.id
|
|
2060
|
+
${where}
|
|
2061
|
+
ORDER BY ${orderBy}`,
|
|
2062
|
+
);
|
|
2063
|
+
for (const r of stmt.iterate(...params)) {
|
|
2064
|
+
if (noTests && isTestFile(r.file)) continue;
|
|
2065
|
+
yield {
|
|
2066
|
+
name: r.name,
|
|
2067
|
+
kind: r.kind,
|
|
2068
|
+
file: r.file,
|
|
2069
|
+
line: r.line,
|
|
2070
|
+
endLine: r.end_line || null,
|
|
2071
|
+
cognitive: r.cognitive,
|
|
2072
|
+
cyclomatic: r.cyclomatic,
|
|
2073
|
+
maxNesting: r.max_nesting,
|
|
2074
|
+
loc: r.loc || 0,
|
|
2075
|
+
sloc: r.sloc || 0,
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
} finally {
|
|
2079
|
+
db.close();
|
|
2080
|
+
}
|
|
1984
2081
|
}
|
|
1985
2082
|
|
|
1986
2083
|
/**
|
|
@@ -1989,6 +2086,10 @@ export function complexityData(customDbPath, opts = {}) {
|
|
|
1989
2086
|
export function complexity(customDbPath, opts = {}) {
|
|
1990
2087
|
const data = complexityData(customDbPath, opts);
|
|
1991
2088
|
|
|
2089
|
+
if (opts.ndjson) {
|
|
2090
|
+
printNdjson(data, 'functions');
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
1992
2093
|
if (opts.json) {
|
|
1993
2094
|
console.log(JSON.stringify(data, null, 2));
|
|
1994
2095
|
return;
|
|
@@ -1996,9 +2097,15 @@ export function complexity(customDbPath, opts = {}) {
|
|
|
1996
2097
|
|
|
1997
2098
|
if (data.functions.length === 0) {
|
|
1998
2099
|
if (data.summary === null) {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2100
|
+
if (data.hasGraph) {
|
|
2101
|
+
console.log(
|
|
2102
|
+
'\nNo complexity data found, but a graph exists. Run "codegraph build --no-incremental" to populate complexity metrics.\n',
|
|
2103
|
+
);
|
|
2104
|
+
} else {
|
|
2105
|
+
console.log(
|
|
2106
|
+
'\nNo complexity data found. Run "codegraph build" first to analyze your codebase.\n',
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2002
2109
|
} else {
|
|
2003
2110
|
console.log('\nNo functions match the given filters.\n');
|
|
2004
2111
|
}
|
package/src/config.js
CHANGED
|
@@ -14,6 +14,7 @@ export const DEFAULTS = {
|
|
|
14
14
|
build: {
|
|
15
15
|
incremental: true,
|
|
16
16
|
dbPath: '.codegraph/graph.db',
|
|
17
|
+
driftThreshold: 0.2,
|
|
17
18
|
},
|
|
18
19
|
query: {
|
|
19
20
|
defaultDepth: 3,
|
|
@@ -36,7 +37,16 @@ export const DEFAULTS = {
|
|
|
36
37
|
fanIn: { warn: null, fail: null },
|
|
37
38
|
fanOut: { warn: null, fail: null },
|
|
38
39
|
noCycles: { warn: null, fail: null },
|
|
40
|
+
boundaries: { warn: null, fail: null },
|
|
39
41
|
},
|
|
42
|
+
boundaries: null,
|
|
43
|
+
},
|
|
44
|
+
check: {
|
|
45
|
+
cycles: true,
|
|
46
|
+
blastRadius: null,
|
|
47
|
+
signatures: true,
|
|
48
|
+
boundaries: true,
|
|
49
|
+
depth: 3,
|
|
40
50
|
},
|
|
41
51
|
coChange: {
|
|
42
52
|
since: '1 year ago',
|