@optave/codegraph 3.0.4 → 3.1.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 +59 -52
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +9 -10
- package/src/ast-analysis/rules/csharp.js +201 -0
- package/src/ast-analysis/rules/go.js +182 -0
- package/src/ast-analysis/rules/index.js +82 -0
- package/src/ast-analysis/rules/java.js +175 -0
- package/src/ast-analysis/rules/javascript.js +246 -0
- package/src/ast-analysis/rules/php.js +219 -0
- package/src/ast-analysis/rules/python.js +196 -0
- package/src/ast-analysis/rules/ruby.js +204 -0
- package/src/ast-analysis/rules/rust.js +173 -0
- package/src/ast-analysis/shared.js +223 -0
- package/src/ast.js +15 -28
- package/src/audit.js +4 -5
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +84 -79
- package/src/builder.js +274 -159
- package/src/cfg.js +111 -341
- package/src/check.js +3 -3
- package/src/cli.js +122 -167
- package/src/cochange.js +1 -1
- package/src/communities.js +13 -16
- package/src/complexity.js +196 -1239
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -697
- package/src/db/connection.js +88 -0
- package/src/db/migrations.js +312 -0
- package/src/db/query-builder.js +280 -0
- package/src/db/repository.js +134 -0
- package/src/db.js +19 -392
- package/src/embedder.js +145 -141
- package/src/export.js +1 -1
- package/src/flow.js +160 -228
- package/src/index.js +36 -2
- package/src/kinds.js +49 -0
- package/src/manifesto.js +3 -8
- package/src/mcp.js +97 -20
- package/src/owners.js +132 -132
- package/src/parser.js +58 -131
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1356 -2261
- package/src/resolve.js +11 -2
- package/src/result-formatter.js +21 -0
- package/src/sequence.js +364 -0
- package/src/structure.js +200 -199
- package/src/test-filter.js +7 -0
- package/src/triage.js +120 -162
- package/src/viewer.js +1 -1
package/src/triage.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { openReadonlyOrFail } from './db.js';
|
|
1
|
+
import { findNodesForTriage, openReadonlyOrFail } from './db.js';
|
|
2
2
|
import { warn } from './logger.js';
|
|
3
|
-
import { paginateResult
|
|
4
|
-
import {
|
|
3
|
+
import { paginateResult } from './paginate.js';
|
|
4
|
+
import { outputResult } from './result-formatter.js';
|
|
5
|
+
import { isTestFile } from './test-filter.js';
|
|
5
6
|
|
|
6
7
|
// ─── Constants ────────────────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -46,165 +47,129 @@ function minMaxNormalize(values) {
|
|
|
46
47
|
*/
|
|
47
48
|
export function triageData(customDbPath, opts = {}) {
|
|
48
49
|
const db = openReadonlyOrFail(customDbPath);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
try {
|
|
51
|
+
const noTests = opts.noTests || false;
|
|
52
|
+
const fileFilter = opts.file || null;
|
|
53
|
+
const kindFilter = opts.kind || null;
|
|
54
|
+
const roleFilter = opts.role || null;
|
|
55
|
+
const minScore = opts.minScore != null ? Number(opts.minScore) : null;
|
|
56
|
+
const sort = opts.sort || 'risk';
|
|
57
|
+
const weights = { ...DEFAULT_WEIGHTS, ...(opts.weights || {}) };
|
|
58
|
+
|
|
59
|
+
let rows;
|
|
60
|
+
try {
|
|
61
|
+
rows = findNodesForTriage(db, {
|
|
62
|
+
noTests,
|
|
63
|
+
file: fileFilter,
|
|
64
|
+
kind: kindFilter,
|
|
65
|
+
role: roleFilter,
|
|
66
|
+
});
|
|
67
|
+
} catch (err) {
|
|
68
|
+
warn(`triage query failed: ${err.message}`);
|
|
69
|
+
return {
|
|
70
|
+
items: [],
|
|
71
|
+
summary: { total: 0, analyzed: 0, avgScore: 0, maxScore: 0, weights, signalCoverage: {} },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const params = [];
|
|
75
|
+
// Post-filter test files (belt-and-suspenders)
|
|
76
|
+
const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
|
|
60
77
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
if (fileFilter) {
|
|
69
|
-
where += ' AND n.file LIKE ?';
|
|
70
|
-
params.push(`%${fileFilter}%`);
|
|
71
|
-
}
|
|
72
|
-
if (kindFilter) {
|
|
73
|
-
where += ' AND n.kind = ?';
|
|
74
|
-
params.push(kindFilter);
|
|
75
|
-
}
|
|
76
|
-
if (roleFilter) {
|
|
77
|
-
where += ' AND n.role = ?';
|
|
78
|
-
params.push(roleFilter);
|
|
79
|
-
}
|
|
78
|
+
if (filtered.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
items: [],
|
|
81
|
+
summary: { total: 0, analyzed: 0, avgScore: 0, maxScore: 0, weights, signalCoverage: {} },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
85
|
+
// Extract raw signal arrays
|
|
86
|
+
const fanIns = filtered.map((r) => r.fan_in);
|
|
87
|
+
const cognitives = filtered.map((r) => r.cognitive);
|
|
88
|
+
const churns = filtered.map((r) => r.churn);
|
|
89
|
+
const mis = filtered.map((r) => r.mi);
|
|
90
|
+
|
|
91
|
+
// Min-max normalize
|
|
92
|
+
const normFanIns = minMaxNormalize(fanIns);
|
|
93
|
+
const normCognitives = minMaxNormalize(cognitives);
|
|
94
|
+
const normChurns = minMaxNormalize(churns);
|
|
95
|
+
// MI: higher is better, so invert: 1 - norm(mi)
|
|
96
|
+
const normMIsRaw = minMaxNormalize(mis);
|
|
97
|
+
const normMIs = normMIsRaw.map((v) => round4(1 - v));
|
|
98
|
+
|
|
99
|
+
// Compute risk scores
|
|
100
|
+
const items = filtered.map((r, i) => {
|
|
101
|
+
const roleWeight = ROLE_WEIGHTS[r.role] ?? DEFAULT_ROLE_WEIGHT;
|
|
102
|
+
const riskScore =
|
|
103
|
+
weights.fanIn * normFanIns[i] +
|
|
104
|
+
weights.complexity * normCognitives[i] +
|
|
105
|
+
weights.churn * normChurns[i] +
|
|
106
|
+
weights.role * roleWeight +
|
|
107
|
+
weights.mi * normMIs[i];
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
name: r.name,
|
|
111
|
+
kind: r.kind,
|
|
112
|
+
file: r.file,
|
|
113
|
+
line: r.line,
|
|
114
|
+
role: r.role || null,
|
|
115
|
+
fanIn: r.fan_in,
|
|
116
|
+
cognitive: r.cognitive,
|
|
117
|
+
churn: r.churn,
|
|
118
|
+
maintainabilityIndex: r.mi,
|
|
119
|
+
normFanIn: round4(normFanIns[i]),
|
|
120
|
+
normComplexity: round4(normCognitives[i]),
|
|
121
|
+
normChurn: round4(normChurns[i]),
|
|
122
|
+
normMI: round4(normMIs[i]),
|
|
123
|
+
roleWeight,
|
|
124
|
+
riskScore: round4(riskScore),
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Apply minScore filter
|
|
129
|
+
const scored = minScore != null ? items.filter((it) => it.riskScore >= minScore) : items;
|
|
130
|
+
|
|
131
|
+
// Sort
|
|
132
|
+
const sortFns = {
|
|
133
|
+
risk: (a, b) => b.riskScore - a.riskScore,
|
|
134
|
+
complexity: (a, b) => b.cognitive - a.cognitive,
|
|
135
|
+
churn: (a, b) => b.churn - a.churn,
|
|
136
|
+
'fan-in': (a, b) => b.fanIn - a.fanIn,
|
|
137
|
+
mi: (a, b) => a.maintainabilityIndex - b.maintainabilityIndex,
|
|
107
138
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
items: [],
|
|
117
|
-
summary: { total: 0, analyzed: 0, avgScore: 0, maxScore: 0, weights, signalCoverage: {} },
|
|
139
|
+
scored.sort(sortFns[sort] || sortFns.risk);
|
|
140
|
+
|
|
141
|
+
// Signal coverage: % of items with non-zero signal
|
|
142
|
+
const signalCoverage = {
|
|
143
|
+
complexity: round4(filtered.filter((r) => r.cognitive > 0).length / filtered.length),
|
|
144
|
+
churn: round4(filtered.filter((r) => r.churn > 0).length / filtered.length),
|
|
145
|
+
fanIn: round4(filtered.filter((r) => r.fan_in > 0).length / filtered.length),
|
|
146
|
+
mi: round4(filtered.filter((r) => r.mi > 0).length / filtered.length),
|
|
118
147
|
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Extract raw signal arrays
|
|
122
|
-
const fanIns = filtered.map((r) => r.fan_in);
|
|
123
|
-
const cognitives = filtered.map((r) => r.cognitive);
|
|
124
|
-
const churns = filtered.map((r) => r.churn);
|
|
125
|
-
const mis = filtered.map((r) => r.mi);
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
weights.role * roleWeight +
|
|
143
|
-
weights.mi * normMIs[i];
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
name: r.name,
|
|
147
|
-
kind: r.kind,
|
|
148
|
-
file: r.file,
|
|
149
|
-
line: r.line,
|
|
150
|
-
role: r.role || null,
|
|
151
|
-
fanIn: r.fan_in,
|
|
152
|
-
cognitive: r.cognitive,
|
|
153
|
-
churn: r.churn,
|
|
154
|
-
maintainabilityIndex: r.mi,
|
|
155
|
-
normFanIn: round4(normFanIns[i]),
|
|
156
|
-
normComplexity: round4(normCognitives[i]),
|
|
157
|
-
normChurn: round4(normChurns[i]),
|
|
158
|
-
normMI: round4(normMIs[i]),
|
|
159
|
-
roleWeight,
|
|
160
|
-
riskScore: round4(riskScore),
|
|
149
|
+
const scores = scored.map((it) => it.riskScore);
|
|
150
|
+
const avgScore =
|
|
151
|
+
scores.length > 0 ? round4(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
|
152
|
+
const maxScore = scores.length > 0 ? round4(Math.max(...scores)) : 0;
|
|
153
|
+
|
|
154
|
+
const result = {
|
|
155
|
+
items: scored,
|
|
156
|
+
summary: {
|
|
157
|
+
total: filtered.length,
|
|
158
|
+
analyzed: scored.length,
|
|
159
|
+
avgScore,
|
|
160
|
+
maxScore,
|
|
161
|
+
weights,
|
|
162
|
+
signalCoverage,
|
|
163
|
+
},
|
|
161
164
|
};
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Apply minScore filter
|
|
165
|
-
const scored = minScore != null ? items.filter((it) => it.riskScore >= minScore) : items;
|
|
166
|
-
|
|
167
|
-
// Sort
|
|
168
|
-
const sortFns = {
|
|
169
|
-
risk: (a, b) => b.riskScore - a.riskScore,
|
|
170
|
-
complexity: (a, b) => b.cognitive - a.cognitive,
|
|
171
|
-
churn: (a, b) => b.churn - a.churn,
|
|
172
|
-
'fan-in': (a, b) => b.fanIn - a.fanIn,
|
|
173
|
-
mi: (a, b) => a.maintainabilityIndex - b.maintainabilityIndex,
|
|
174
|
-
};
|
|
175
|
-
scored.sort(sortFns[sort] || sortFns.risk);
|
|
176
|
-
|
|
177
|
-
// Signal coverage: % of items with non-zero signal
|
|
178
|
-
const signalCoverage = {
|
|
179
|
-
complexity: round4(filtered.filter((r) => r.cognitive > 0).length / filtered.length),
|
|
180
|
-
churn: round4(filtered.filter((r) => r.churn > 0).length / filtered.length),
|
|
181
|
-
fanIn: round4(filtered.filter((r) => r.fan_in > 0).length / filtered.length),
|
|
182
|
-
mi: round4(filtered.filter((r) => r.mi > 0).length / filtered.length),
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const scores = scored.map((it) => it.riskScore);
|
|
186
|
-
const avgScore =
|
|
187
|
-
scores.length > 0 ? round4(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
|
188
|
-
const maxScore = scores.length > 0 ? round4(Math.max(...scores)) : 0;
|
|
189
|
-
|
|
190
|
-
const result = {
|
|
191
|
-
items: scored,
|
|
192
|
-
summary: {
|
|
193
|
-
total: filtered.length,
|
|
194
|
-
analyzed: scored.length,
|
|
195
|
-
avgScore,
|
|
196
|
-
maxScore,
|
|
197
|
-
weights,
|
|
198
|
-
signalCoverage,
|
|
199
|
-
},
|
|
200
|
-
};
|
|
201
165
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
166
|
+
return paginateResult(result, 'items', {
|
|
167
|
+
limit: opts.limit,
|
|
168
|
+
offset: opts.offset,
|
|
169
|
+
});
|
|
170
|
+
} finally {
|
|
171
|
+
db.close();
|
|
172
|
+
}
|
|
208
173
|
}
|
|
209
174
|
|
|
210
175
|
// ─── CLI Formatter ────────────────────────────────────────────────────
|
|
@@ -218,14 +183,7 @@ export function triageData(customDbPath, opts = {}) {
|
|
|
218
183
|
export function triage(customDbPath, opts = {}) {
|
|
219
184
|
const data = triageData(customDbPath, opts);
|
|
220
185
|
|
|
221
|
-
if (opts
|
|
222
|
-
printNdjson(data, 'items');
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (opts.json) {
|
|
226
|
-
console.log(JSON.stringify(data, null, 2));
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
186
|
+
if (outputResult(data, 'items', opts)) return;
|
|
229
187
|
|
|
230
188
|
if (data.items.length === 0) {
|
|
231
189
|
if (data.summary.total === 0) {
|
package/src/viewer.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import Graph from 'graphology';
|
|
4
4
|
import louvain from 'graphology-communities-louvain';
|
|
5
|
-
import { isTestFile } from './
|
|
5
|
+
import { isTestFile } from './test-filter.js';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_MIN_CONFIDENCE = 0.5;
|
|
8
8
|
|