@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
|
@@ -96,96 +96,7 @@ export function context(name, customDbPath, opts = {}) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
for (const r of data.results) {
|
|
99
|
-
|
|
100
|
-
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
101
|
-
console.log(`\n# ${r.name} (${r.kind})${roleTag} — ${r.file}:${lineRange}\n`);
|
|
102
|
-
|
|
103
|
-
// Signature
|
|
104
|
-
if (r.signature) {
|
|
105
|
-
console.log('## Type/Shape Info');
|
|
106
|
-
if (r.signature.params != null) console.log(` Parameters: (${r.signature.params})`);
|
|
107
|
-
if (r.signature.returnType) console.log(` Returns: ${r.signature.returnType}`);
|
|
108
|
-
console.log();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Children
|
|
112
|
-
if (r.children && r.children.length > 0) {
|
|
113
|
-
console.log(`## Children (${r.children.length})`);
|
|
114
|
-
for (const c of r.children) {
|
|
115
|
-
console.log(` ${kindIcon(c.kind)} ${c.name} :${c.line}`);
|
|
116
|
-
}
|
|
117
|
-
console.log();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Complexity
|
|
121
|
-
if (r.complexity) {
|
|
122
|
-
const cx = r.complexity;
|
|
123
|
-
const miPart = cx.maintainabilityIndex ? ` | MI: ${cx.maintainabilityIndex}` : '';
|
|
124
|
-
console.log('## Complexity');
|
|
125
|
-
console.log(
|
|
126
|
-
` Cognitive: ${cx.cognitive} | Cyclomatic: ${cx.cyclomatic} | Max Nesting: ${cx.maxNesting}${miPart}`,
|
|
127
|
-
);
|
|
128
|
-
console.log();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Source
|
|
132
|
-
if (r.source) {
|
|
133
|
-
console.log('## Source');
|
|
134
|
-
for (const line of r.source.split('\n')) {
|
|
135
|
-
console.log(` ${line}`);
|
|
136
|
-
}
|
|
137
|
-
console.log();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Callees
|
|
141
|
-
if (r.callees.length > 0) {
|
|
142
|
-
console.log(`## Direct Dependencies (${r.callees.length})`);
|
|
143
|
-
for (const c of r.callees) {
|
|
144
|
-
const summary = c.summary ? ` — ${c.summary}` : '';
|
|
145
|
-
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${summary}`);
|
|
146
|
-
if (c.source) {
|
|
147
|
-
for (const line of c.source.split('\n').slice(0, 10)) {
|
|
148
|
-
console.log(` | ${line}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
console.log();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Callers
|
|
156
|
-
if (r.callers.length > 0) {
|
|
157
|
-
console.log(`## Callers (${r.callers.length})`);
|
|
158
|
-
for (const c of r.callers) {
|
|
159
|
-
const via = c.viaHierarchy ? ` (via ${c.viaHierarchy})` : '';
|
|
160
|
-
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${via}`);
|
|
161
|
-
}
|
|
162
|
-
console.log();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Related tests
|
|
166
|
-
if (r.relatedTests.length > 0) {
|
|
167
|
-
console.log('## Related Tests');
|
|
168
|
-
for (const t of r.relatedTests) {
|
|
169
|
-
console.log(` ${t.file} — ${t.testCount} tests`);
|
|
170
|
-
for (const tn of t.testNames) {
|
|
171
|
-
console.log(` - ${tn}`);
|
|
172
|
-
}
|
|
173
|
-
if (t.source) {
|
|
174
|
-
console.log(' Source:');
|
|
175
|
-
for (const line of t.source.split('\n').slice(0, 20)) {
|
|
176
|
-
console.log(` | ${line}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
console.log();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (r.callees.length === 0 && r.callers.length === 0 && r.relatedTests.length === 0) {
|
|
184
|
-
console.log(
|
|
185
|
-
' (no call edges or tests found — may be invoked dynamically or via re-exports)',
|
|
186
|
-
);
|
|
187
|
-
console.log();
|
|
188
|
-
}
|
|
99
|
+
renderContextResult(r);
|
|
189
100
|
}
|
|
190
101
|
}
|
|
191
102
|
|
|
@@ -209,126 +120,210 @@ export function children(name, customDbPath, opts = {}) {
|
|
|
209
120
|
}
|
|
210
121
|
}
|
|
211
122
|
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
123
|
+
function renderContextResult(r) {
|
|
124
|
+
const lineRange = r.endLine ? `${r.line}-${r.endLine}` : `${r.line}`;
|
|
125
|
+
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
126
|
+
console.log(`\n# ${r.name} (${r.kind})${roleTag} — ${r.file}:${lineRange}\n`);
|
|
215
127
|
|
|
216
|
-
if (
|
|
217
|
-
console.log(
|
|
218
|
-
|
|
128
|
+
if (r.signature) {
|
|
129
|
+
console.log('## Type/Shape Info');
|
|
130
|
+
if (r.signature.params != null) console.log(` Parameters: (${r.signature.params})`);
|
|
131
|
+
if (r.signature.returnType) console.log(` Returns: ${r.signature.returnType}`);
|
|
132
|
+
console.log();
|
|
219
133
|
}
|
|
220
134
|
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
` ${lineInfo}${r.symbolCount} symbols (${publicCount} exported, ${internalCount} internal)`,
|
|
229
|
-
);
|
|
135
|
+
if (r.children && r.children.length > 0) {
|
|
136
|
+
console.log(`## Children (${r.children.length})`);
|
|
137
|
+
for (const c of r.children) {
|
|
138
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} :${c.line}`);
|
|
139
|
+
}
|
|
140
|
+
console.log();
|
|
141
|
+
}
|
|
230
142
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
143
|
+
if (r.complexity) {
|
|
144
|
+
const cx = r.complexity;
|
|
145
|
+
const miPart = cx.maintainabilityIndex ? ` | MI: ${cx.maintainabilityIndex}` : '';
|
|
146
|
+
console.log('## Complexity');
|
|
147
|
+
console.log(
|
|
148
|
+
` Cognitive: ${cx.cognitive} | Cyclomatic: ${cx.cyclomatic} | Max Nesting: ${cx.maxNesting}${miPart}`,
|
|
149
|
+
);
|
|
150
|
+
console.log();
|
|
151
|
+
}
|
|
237
152
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
}
|
|
153
|
+
if (r.source) {
|
|
154
|
+
console.log('## Source');
|
|
155
|
+
for (const line of r.source.split('\n')) {
|
|
156
|
+
console.log(` ${line}`);
|
|
157
|
+
}
|
|
158
|
+
console.log();
|
|
159
|
+
}
|
|
247
160
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
161
|
+
if (r.callees.length > 0) {
|
|
162
|
+
console.log(`## Direct Dependencies (${r.callees.length})`);
|
|
163
|
+
for (const c of r.callees) {
|
|
164
|
+
const summary = c.summary ? ` — ${c.summary}` : '';
|
|
165
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${summary}`);
|
|
166
|
+
if (c.source) {
|
|
167
|
+
for (const line of c.source.split('\n').slice(0, 10)) {
|
|
168
|
+
console.log(` | ${line}`);
|
|
255
169
|
}
|
|
256
170
|
}
|
|
171
|
+
}
|
|
172
|
+
console.log();
|
|
173
|
+
}
|
|
257
174
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
console.log();
|
|
175
|
+
if (r.callers.length > 0) {
|
|
176
|
+
console.log(`## Callers (${r.callers.length})`);
|
|
177
|
+
for (const c of r.callers) {
|
|
178
|
+
const via = c.viaHierarchy ? ` (via ${c.viaHierarchy})` : '';
|
|
179
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${via}`);
|
|
265
180
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const lineRange = r.endLine ? `${r.line}-${r.endLine}` : `${r.line}`;
|
|
269
|
-
const lineInfo = r.lineCount ? `${r.lineCount} lines` : '';
|
|
270
|
-
const summaryPart = r.summary ? ` | ${r.summary}` : '';
|
|
271
|
-
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
272
|
-
const depthLevel = r._depth || 0;
|
|
273
|
-
const heading = depthLevel === 0 ? '#' : '##'.padEnd(depthLevel + 2, '#');
|
|
274
|
-
console.log(`\n${indent}${heading} ${r.name} (${r.kind})${roleTag} ${r.file}:${lineRange}`);
|
|
275
|
-
if (lineInfo || r.summary) {
|
|
276
|
-
console.log(`${indent} ${lineInfo}${summaryPart}`);
|
|
277
|
-
}
|
|
278
|
-
if (r.signature) {
|
|
279
|
-
if (r.signature.params != null)
|
|
280
|
-
console.log(`${indent} Parameters: (${r.signature.params})`);
|
|
281
|
-
if (r.signature.returnType) console.log(`${indent} Returns: ${r.signature.returnType}`);
|
|
282
|
-
}
|
|
181
|
+
console.log();
|
|
182
|
+
}
|
|
283
183
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
184
|
+
if (r.relatedTests.length > 0) {
|
|
185
|
+
console.log('## Related Tests');
|
|
186
|
+
for (const t of r.relatedTests) {
|
|
187
|
+
console.log(` ${t.file} — ${t.testCount} tests`);
|
|
188
|
+
for (const tn of t.testNames) {
|
|
189
|
+
console.log(` - ${tn}`);
|
|
290
190
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
|
|
191
|
+
if (t.source) {
|
|
192
|
+
console.log(' Source:');
|
|
193
|
+
for (const line of t.source.split('\n').slice(0, 20)) {
|
|
194
|
+
console.log(` | ${line}`);
|
|
296
195
|
}
|
|
297
196
|
}
|
|
197
|
+
}
|
|
198
|
+
console.log();
|
|
199
|
+
}
|
|
298
200
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
201
|
+
if (r.callees.length === 0 && r.callers.length === 0 && r.relatedTests.length === 0) {
|
|
202
|
+
console.log(' (no call edges or tests found — may be invoked dynamically or via re-exports)');
|
|
203
|
+
console.log();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
305
206
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
207
|
+
function renderFileExplain(r) {
|
|
208
|
+
const publicCount = r.publicApi.length;
|
|
209
|
+
const internalCount = r.internal.length;
|
|
210
|
+
const lineInfo = r.lineCount ? `${r.lineCount} lines, ` : '';
|
|
211
|
+
console.log(`\n# ${r.file}`);
|
|
212
|
+
console.log(
|
|
213
|
+
` ${lineInfo}${r.symbolCount} symbols (${publicCount} exported, ${internalCount} internal)`,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (r.imports.length > 0) {
|
|
217
|
+
console.log(` Imports: ${r.imports.map((i) => i.file).join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
if (r.importedBy.length > 0) {
|
|
220
|
+
console.log(` Imported by: ${r.importedBy.map((i) => i.file).join(', ')}`);
|
|
221
|
+
}
|
|
313
222
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
223
|
+
if (r.publicApi.length > 0) {
|
|
224
|
+
console.log(`\n## Exported`);
|
|
225
|
+
for (const s of r.publicApi) {
|
|
226
|
+
const sig = s.signature?.params != null ? `(${s.signature.params})` : '';
|
|
227
|
+
const roleTag = s.role ? ` [${s.role}]` : '';
|
|
228
|
+
const summary = s.summary ? ` -- ${s.summary}` : '';
|
|
229
|
+
console.log(` ${kindIcon(s.kind)} ${s.name}${sig}${roleTag} :${s.line}${summary}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
319
232
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
233
|
+
if (r.internal.length > 0) {
|
|
234
|
+
console.log(`\n## Internal`);
|
|
235
|
+
for (const s of r.internal) {
|
|
236
|
+
const sig = s.signature?.params != null ? `(${s.signature.params})` : '';
|
|
237
|
+
const roleTag = s.role ? ` [${s.role}]` : '';
|
|
238
|
+
const summary = s.summary ? ` -- ${s.summary}` : '';
|
|
239
|
+
console.log(` ${kindIcon(s.kind)} ${s.name}${sig}${roleTag} :${s.line}${summary}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (r.dataFlow.length > 0) {
|
|
244
|
+
console.log(`\n## Data Flow`);
|
|
245
|
+
for (const df of r.dataFlow) {
|
|
246
|
+
console.log(` ${df.caller} -> ${df.callees.join(', ')}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
console.log();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderFunctionExplain(r, indent = '') {
|
|
253
|
+
const lineRange = r.endLine ? `${r.line}-${r.endLine}` : `${r.line}`;
|
|
254
|
+
const lineInfo = r.lineCount ? `${r.lineCount} lines` : '';
|
|
255
|
+
const summaryPart = r.summary ? ` | ${r.summary}` : '';
|
|
256
|
+
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
257
|
+
const depthLevel = r._depth || 0;
|
|
258
|
+
const heading = depthLevel === 0 ? '#' : '##'.padEnd(depthLevel + 2, '#');
|
|
259
|
+
console.log(`\n${indent}${heading} ${r.name} (${r.kind})${roleTag} ${r.file}:${lineRange}`);
|
|
260
|
+
if (lineInfo || r.summary) {
|
|
261
|
+
console.log(`${indent} ${lineInfo}${summaryPart}`);
|
|
262
|
+
}
|
|
263
|
+
if (r.signature) {
|
|
264
|
+
if (r.signature.params != null) console.log(`${indent} Parameters: (${r.signature.params})`);
|
|
265
|
+
if (r.signature.returnType) console.log(`${indent} Returns: ${r.signature.returnType}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (r.complexity) {
|
|
269
|
+
const cx = r.complexity;
|
|
270
|
+
const miPart = cx.maintainabilityIndex ? ` MI=${cx.maintainabilityIndex}` : '';
|
|
271
|
+
console.log(
|
|
272
|
+
`${indent} Complexity: cognitive=${cx.cognitive} cyclomatic=${cx.cyclomatic} nesting=${cx.maxNesting}${miPart}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (r.callees.length > 0) {
|
|
277
|
+
console.log(`\n${indent} Calls (${r.callees.length}):`);
|
|
278
|
+
for (const c of r.callees) {
|
|
279
|
+
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (r.callers.length > 0) {
|
|
284
|
+
console.log(`\n${indent} Called by (${r.callers.length}):`);
|
|
285
|
+
for (const c of r.callers) {
|
|
286
|
+
console.log(`${indent} ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (r.relatedTests.length > 0) {
|
|
291
|
+
const label = r.relatedTests.length === 1 ? 'file' : 'files';
|
|
292
|
+
console.log(`\n${indent} Tests (${r.relatedTests.length} ${label}):`);
|
|
293
|
+
for (const t of r.relatedTests) {
|
|
294
|
+
console.log(`${indent} ${t.file}`);
|
|
328
295
|
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (r.callees.length === 0 && r.callers.length === 0) {
|
|
299
|
+
console.log(`${indent} (no call edges found -- may be invoked dynamically or via re-exports)`);
|
|
300
|
+
}
|
|
329
301
|
|
|
302
|
+
if (r.depDetails && r.depDetails.length > 0) {
|
|
303
|
+
console.log(`\n${indent} --- Dependencies (depth ${depthLevel + 1}) ---`);
|
|
304
|
+
for (const dep of r.depDetails) {
|
|
305
|
+
renderFunctionExplain(dep, `${indent} `);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
console.log();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function explain(target, customDbPath, opts = {}) {
|
|
312
|
+
const data = explainData(target, customDbPath, opts);
|
|
313
|
+
if (outputResult(data, 'results', opts)) return;
|
|
314
|
+
|
|
315
|
+
if (data.results.length === 0) {
|
|
316
|
+
console.log(`No ${data.kind === 'file' ? 'file' : 'function/symbol'} matching "${target}"`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (data.kind === 'file') {
|
|
321
|
+
for (const r of data.results) {
|
|
322
|
+
renderFileExplain(r);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
330
325
|
for (const r of data.results) {
|
|
331
|
-
|
|
326
|
+
renderFunctionExplain(r);
|
|
332
327
|
}
|
|
333
328
|
}
|
|
334
329
|
}
|
|
@@ -2,64 +2,42 @@ import path from 'node:path';
|
|
|
2
2
|
import { kindIcon, moduleMapData, rolesData, statsData } from '../../domain/queries.js';
|
|
3
3
|
import { outputResult } from '../../infrastructure/result-formatter.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const { communitySummaryForStats } = await import('../../features/communities.js');
|
|
11
|
-
data.communities = communitySummaryForStats(customDbPath, { noTests: opts.noTests });
|
|
12
|
-
} catch {
|
|
13
|
-
/* graphology may not be available */
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (outputResult(data, null, opts)) return;
|
|
17
|
-
|
|
18
|
-
// Human-readable output
|
|
19
|
-
console.log('\n# Codegraph Stats\n');
|
|
20
|
-
|
|
21
|
-
// Nodes
|
|
22
|
-
console.log(`Nodes: ${data.nodes.total} total`);
|
|
23
|
-
const kindEntries = Object.entries(data.nodes.byKind).sort((a, b) => b[1] - a[1]);
|
|
24
|
-
const kindParts = kindEntries.map(([k, v]) => `${k} ${v}`);
|
|
25
|
-
for (let i = 0; i < kindParts.length; i += 3) {
|
|
26
|
-
const row = kindParts
|
|
5
|
+
function printCountGrid(entries, padWidth) {
|
|
6
|
+
const parts = entries.map(([k, v]) => `${k} ${v}`);
|
|
7
|
+
for (let i = 0; i < parts.length; i += 3) {
|
|
8
|
+
const row = parts
|
|
27
9
|
.slice(i, i + 3)
|
|
28
|
-
.map((p) => p.padEnd(
|
|
10
|
+
.map((p) => p.padEnd(padWidth))
|
|
29
11
|
.join('');
|
|
30
12
|
console.log(` ${row}`);
|
|
31
13
|
}
|
|
14
|
+
}
|
|
32
15
|
|
|
33
|
-
|
|
16
|
+
function printNodes(data) {
|
|
17
|
+
console.log(`Nodes: ${data.nodes.total} total`);
|
|
18
|
+
const kindEntries = Object.entries(data.nodes.byKind).sort((a, b) => b[1] - a[1]);
|
|
19
|
+
printCountGrid(kindEntries, 18);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printEdges(data) {
|
|
34
23
|
console.log(`\nEdges: ${data.edges.total} total`);
|
|
35
24
|
const edgeEntries = Object.entries(data.edges.byKind).sort((a, b) => b[1] - a[1]);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const row = edgeParts
|
|
39
|
-
.slice(i, i + 3)
|
|
40
|
-
.map((p) => p.padEnd(18))
|
|
41
|
-
.join('');
|
|
42
|
-
console.log(` ${row}`);
|
|
43
|
-
}
|
|
25
|
+
printCountGrid(edgeEntries, 18);
|
|
26
|
+
}
|
|
44
27
|
|
|
45
|
-
|
|
28
|
+
function printFiles(data) {
|
|
46
29
|
console.log(`\nFiles: ${data.files.total} (${data.files.languages} languages)`);
|
|
47
30
|
const langEntries = Object.entries(data.files.byLanguage).sort((a, b) => b[1] - a[1]);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const row = langParts
|
|
51
|
-
.slice(i, i + 3)
|
|
52
|
-
.map((p) => p.padEnd(18))
|
|
53
|
-
.join('');
|
|
54
|
-
console.log(` ${row}`);
|
|
55
|
-
}
|
|
31
|
+
printCountGrid(langEntries, 18);
|
|
32
|
+
}
|
|
56
33
|
|
|
57
|
-
|
|
34
|
+
function printCycles(data) {
|
|
58
35
|
console.log(
|
|
59
36
|
`\nCycles: ${data.cycles.fileLevel} file-level, ${data.cycles.functionLevel} function-level`,
|
|
60
37
|
);
|
|
38
|
+
}
|
|
61
39
|
|
|
62
|
-
|
|
40
|
+
function printHotspots(data) {
|
|
63
41
|
if (data.hotspots.length > 0) {
|
|
64
42
|
console.log(`\nTop ${data.hotspots.length} coupling hotspots:`);
|
|
65
43
|
for (let i = 0; i < data.hotspots.length; i++) {
|
|
@@ -69,8 +47,9 @@ export async function stats(customDbPath, opts = {}) {
|
|
|
69
47
|
);
|
|
70
48
|
}
|
|
71
49
|
}
|
|
50
|
+
}
|
|
72
51
|
|
|
73
|
-
|
|
52
|
+
function printEmbeddings(data) {
|
|
74
53
|
if (data.embeddings) {
|
|
75
54
|
const e = data.embeddings;
|
|
76
55
|
console.log(
|
|
@@ -79,8 +58,9 @@ export async function stats(customDbPath, opts = {}) {
|
|
|
79
58
|
} else {
|
|
80
59
|
console.log('\nEmbeddings: not built');
|
|
81
60
|
}
|
|
61
|
+
}
|
|
82
62
|
|
|
83
|
-
|
|
63
|
+
function printQuality(data) {
|
|
84
64
|
if (data.quality) {
|
|
85
65
|
const q = data.quality;
|
|
86
66
|
const cc = q.callerCoverage;
|
|
@@ -99,24 +79,18 @@ export async function stats(customDbPath, opts = {}) {
|
|
|
99
79
|
}
|
|
100
80
|
}
|
|
101
81
|
}
|
|
82
|
+
}
|
|
102
83
|
|
|
103
|
-
|
|
84
|
+
function printRoles(data) {
|
|
104
85
|
if (data.roles && Object.keys(data.roles).length > 0) {
|
|
105
86
|
const total = Object.values(data.roles).reduce((a, b) => a + b, 0);
|
|
106
87
|
console.log(`\nRoles: ${total} classified symbols`);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
.map(([k, v]) => `${k} ${v}`);
|
|
110
|
-
for (let i = 0; i < roleParts.length; i += 3) {
|
|
111
|
-
const row = roleParts
|
|
112
|
-
.slice(i, i + 3)
|
|
113
|
-
.map((p) => p.padEnd(18))
|
|
114
|
-
.join('');
|
|
115
|
-
console.log(` ${row}`);
|
|
116
|
-
}
|
|
88
|
+
const roleEntries = Object.entries(data.roles).sort((a, b) => b[1] - a[1]);
|
|
89
|
+
printCountGrid(roleEntries, 18);
|
|
117
90
|
}
|
|
91
|
+
}
|
|
118
92
|
|
|
119
|
-
|
|
93
|
+
function printComplexity(data) {
|
|
120
94
|
if (data.complexity) {
|
|
121
95
|
const cx = data.complexity;
|
|
122
96
|
const miPart = cx.avgMI != null ? ` | avg MI: ${cx.avgMI} | min MI: ${cx.minMI}` : '';
|
|
@@ -124,15 +98,40 @@ export async function stats(customDbPath, opts = {}) {
|
|
|
124
98
|
`\nComplexity: ${cx.analyzed} functions | avg cognitive: ${cx.avgCognitive} | avg cyclomatic: ${cx.avgCyclomatic} | max cognitive: ${cx.maxCognitive}${miPart}`,
|
|
125
99
|
);
|
|
126
100
|
}
|
|
101
|
+
}
|
|
127
102
|
|
|
128
|
-
|
|
103
|
+
function printCommunities(data) {
|
|
129
104
|
if (data.communities) {
|
|
130
105
|
const cm = data.communities;
|
|
131
106
|
console.log(
|
|
132
107
|
`\nCommunities: ${cm.communityCount} detected | modularity: ${cm.modularity} | drift: ${cm.driftScore}%`,
|
|
133
108
|
);
|
|
134
109
|
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function stats(customDbPath, opts = {}) {
|
|
113
|
+
const data = statsData(customDbPath, { noTests: opts.noTests });
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const { communitySummaryForStats } = await import('../../features/communities.js');
|
|
117
|
+
data.communities = communitySummaryForStats(customDbPath, { noTests: opts.noTests });
|
|
118
|
+
} catch {
|
|
119
|
+
/* graphology may not be available */
|
|
120
|
+
}
|
|
135
121
|
|
|
122
|
+
if (outputResult(data, null, opts)) return;
|
|
123
|
+
|
|
124
|
+
console.log('\n# Codegraph Stats\n');
|
|
125
|
+
printNodes(data);
|
|
126
|
+
printEdges(data);
|
|
127
|
+
printFiles(data);
|
|
128
|
+
printCycles(data);
|
|
129
|
+
printHotspots(data);
|
|
130
|
+
printEmbeddings(data);
|
|
131
|
+
printQuality(data);
|
|
132
|
+
printRoles(data);
|
|
133
|
+
printComplexity(data);
|
|
134
|
+
printCommunities(data);
|
|
136
135
|
console.log();
|
|
137
136
|
}
|
|
138
137
|
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { kindIcon, pathData } from '../../domain/queries.js';
|
|
2
2
|
import { outputResult } from '../../infrastructure/result-formatter.js';
|
|
3
3
|
|
|
4
|
+
function printNotFound(from, to, data) {
|
|
5
|
+
const dir = data.reverse ? 'reverse ' : '';
|
|
6
|
+
console.log(`No ${dir}path from "${from}" to "${to}" within ${data.maxDepth} hops.`);
|
|
7
|
+
if (data.fromCandidates.length > 1) {
|
|
8
|
+
console.log(
|
|
9
|
+
`\n "${from}" matched ${data.fromCandidates.length} symbols — using top match: ${data.fromCandidates[0].name} (${data.fromCandidates[0].file}:${data.fromCandidates[0].line})`,
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
if (data.toCandidates.length > 1) {
|
|
13
|
+
console.log(
|
|
14
|
+
` "${to}" matched ${data.toCandidates.length} symbols — using top match: ${data.toCandidates[0].name} (${data.toCandidates[0].file}:${data.toCandidates[0].line})`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function printPathSteps(data) {
|
|
20
|
+
for (let i = 0; i < data.path.length; i++) {
|
|
21
|
+
const n = data.path[i];
|
|
22
|
+
const indent = ' '.repeat(i + 1);
|
|
23
|
+
if (i === 0) {
|
|
24
|
+
console.log(`${indent}${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(
|
|
27
|
+
`${indent}--[${n.edgeKind}]--> ${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (data.alternateCount > 0) {
|
|
32
|
+
console.log(
|
|
33
|
+
`\n (${data.alternateCount} alternate shortest ${data.alternateCount === 1 ? 'path' : 'paths'} at same depth)`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
4
38
|
export function symbolPath(from, to, customDbPath, opts = {}) {
|
|
5
39
|
const data = pathData(from, to, customDbPath, opts);
|
|
6
40
|
if (outputResult(data, null, opts)) return;
|
|
@@ -11,18 +45,7 @@ export function symbolPath(from, to, customDbPath, opts = {}) {
|
|
|
11
45
|
}
|
|
12
46
|
|
|
13
47
|
if (!data.found) {
|
|
14
|
-
|
|
15
|
-
console.log(`No ${dir}path from "${from}" to "${to}" within ${data.maxDepth} hops.`);
|
|
16
|
-
if (data.fromCandidates.length > 1) {
|
|
17
|
-
console.log(
|
|
18
|
-
`\n "${from}" matched ${data.fromCandidates.length} symbols — using top match: ${data.fromCandidates[0].name} (${data.fromCandidates[0].file}:${data.fromCandidates[0].line})`,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
if (data.toCandidates.length > 1) {
|
|
22
|
-
console.log(
|
|
23
|
-
` "${to}" matched ${data.toCandidates.length} symbols — using top match: ${data.toCandidates[0].name} (${data.toCandidates[0].file}:${data.toCandidates[0].line})`,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
48
|
+
printNotFound(from, to, data);
|
|
26
49
|
return;
|
|
27
50
|
}
|
|
28
51
|
|
|
@@ -37,22 +60,6 @@ export function symbolPath(from, to, customDbPath, opts = {}) {
|
|
|
37
60
|
console.log(
|
|
38
61
|
`\nPath from ${from} to ${to} (${data.hops} ${data.hops === 1 ? 'hop' : 'hops'})${dir}:\n`,
|
|
39
62
|
);
|
|
40
|
-
|
|
41
|
-
const n = data.path[i];
|
|
42
|
-
const indent = ' '.repeat(i + 1);
|
|
43
|
-
if (i === 0) {
|
|
44
|
-
console.log(`${indent}${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`);
|
|
45
|
-
} else {
|
|
46
|
-
console.log(
|
|
47
|
-
`${indent}--[${n.edgeKind}]--> ${kindIcon(n.kind)} ${n.name} (${n.kind}) -- ${n.file}:${n.line}`,
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (data.alternateCount > 0) {
|
|
53
|
-
console.log(
|
|
54
|
-
`\n (${data.alternateCount} alternate shortest ${data.alternateCount === 1 ? 'path' : 'paths'} at same depth)`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
63
|
+
printPathSteps(data);
|
|
57
64
|
console.log();
|
|
58
65
|
}
|