@optave/codegraph 3.1.0 → 3.1.2
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 +5 -5
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +8 -9
- package/src/ast-analysis/engine.js +365 -0
- package/src/ast-analysis/metrics.js +118 -0
- 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-analysis/visitor-utils.js +176 -0
- package/src/ast-analysis/visitor.js +162 -0
- package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
- package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
- package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
- package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
- package/src/ast.js +26 -166
- package/src/audit.js +2 -88
- package/src/batch.js +0 -25
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +82 -172
- package/src/builder.js +48 -184
- package/src/cfg.js +148 -1174
- package/src/check.js +1 -84
- package/src/cli.js +118 -197
- package/src/cochange.js +1 -39
- package/src/commands/audit.js +88 -0
- package/src/commands/batch.js +26 -0
- package/src/commands/branch-compare.js +97 -0
- package/src/commands/cfg.js +55 -0
- package/src/commands/check.js +82 -0
- package/src/commands/cochange.js +37 -0
- package/src/commands/communities.js +69 -0
- package/src/commands/complexity.js +77 -0
- package/src/commands/dataflow.js +110 -0
- package/src/commands/flow.js +70 -0
- package/src/commands/manifesto.js +77 -0
- package/src/commands/owners.js +52 -0
- package/src/commands/query.js +21 -0
- package/src/commands/sequence.js +33 -0
- package/src/commands/structure.js +64 -0
- package/src/commands/triage.js +49 -0
- package/src/communities.js +22 -96
- package/src/complexity.js +234 -1591
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -1352
- 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/build-stmts.js +104 -0
- package/src/db/repository/cfg.js +83 -0
- package/src/db/repository/cochange.js +41 -0
- package/src/db/repository/complexity.js +15 -0
- package/src/db/repository/dataflow.js +12 -0
- package/src/db/repository/edges.js +259 -0
- package/src/db/repository/embeddings.js +40 -0
- package/src/db/repository/graph-read.js +39 -0
- package/src/db/repository/index.js +42 -0
- package/src/db/repository/nodes.js +236 -0
- package/src/db.js +58 -399
- package/src/embedder.js +158 -174
- package/src/export.js +1 -1
- package/src/extractors/javascript.js +130 -5
- package/src/flow.js +153 -222
- package/src/index.js +53 -16
- package/src/infrastructure/result-formatter.js +21 -0
- package/src/infrastructure/test-filter.js +7 -0
- package/src/kinds.js +50 -0
- package/src/manifesto.js +1 -82
- package/src/mcp.js +37 -20
- package/src/owners.js +127 -182
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1271 -2416
- package/src/sequence.js +179 -223
- package/src/structure.js +211 -269
- package/src/triage.js +117 -212
- package/src/viewer.js +1 -1
- package/src/watcher.js +7 -4
package/src/manifesto.js
CHANGED
|
@@ -3,7 +3,7 @@ import { loadConfig } from './config.js';
|
|
|
3
3
|
import { findCycles } from './cycles.js';
|
|
4
4
|
import { openReadonlyOrFail } from './db.js';
|
|
5
5
|
import { debug } from './logger.js';
|
|
6
|
-
import { paginateResult
|
|
6
|
+
import { paginateResult } from './paginate.js';
|
|
7
7
|
|
|
8
8
|
// ─── Rule Definitions ─────────────────────────────────────────────────
|
|
9
9
|
|
|
@@ -427,84 +427,3 @@ export function manifestoData(customDbPath, opts = {}) {
|
|
|
427
427
|
db.close();
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* CLI formatter — prints manifesto results and exits with code 1 on failure.
|
|
433
|
-
*/
|
|
434
|
-
export function manifesto(customDbPath, opts = {}) {
|
|
435
|
-
const data = manifestoData(customDbPath, opts);
|
|
436
|
-
|
|
437
|
-
if (opts.ndjson) {
|
|
438
|
-
printNdjson(data, 'violations');
|
|
439
|
-
if (!data.passed) process.exit(1);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
if (opts.json) {
|
|
443
|
-
console.log(JSON.stringify(data, null, 2));
|
|
444
|
-
if (!data.passed) process.exit(1);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
console.log('\n# Manifesto Rules\n');
|
|
449
|
-
|
|
450
|
-
// Rules table
|
|
451
|
-
console.log(
|
|
452
|
-
` ${'Rule'.padEnd(20)} ${'Level'.padEnd(10)} ${'Status'.padEnd(8)} ${'Warn'.padStart(6)} ${'Fail'.padStart(6)} ${'Violations'.padStart(11)}`,
|
|
453
|
-
);
|
|
454
|
-
console.log(
|
|
455
|
-
` ${'─'.repeat(20)} ${'─'.repeat(10)} ${'─'.repeat(8)} ${'─'.repeat(6)} ${'─'.repeat(6)} ${'─'.repeat(11)}`,
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
for (const rule of data.rules) {
|
|
459
|
-
const warn = rule.thresholds.warn != null ? String(rule.thresholds.warn) : '—';
|
|
460
|
-
const fail = rule.thresholds.fail != null ? String(rule.thresholds.fail) : '—';
|
|
461
|
-
const statusIcon = rule.status === 'pass' ? 'pass' : rule.status === 'warn' ? 'WARN' : 'FAIL';
|
|
462
|
-
console.log(
|
|
463
|
-
` ${rule.name.padEnd(20)} ${rule.level.padEnd(10)} ${statusIcon.padEnd(8)} ${warn.padStart(6)} ${fail.padStart(6)} ${String(rule.violationCount).padStart(11)}`,
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Summary
|
|
468
|
-
const s = data.summary;
|
|
469
|
-
console.log(
|
|
470
|
-
`\n ${s.total} rules | ${s.passed} passed | ${s.warned} warned | ${s.failed} failed | ${s.violationCount} violations`,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
// Violations detail
|
|
474
|
-
if (data.violations.length > 0) {
|
|
475
|
-
const failViolations = data.violations.filter((v) => v.level === 'fail');
|
|
476
|
-
const warnViolations = data.violations.filter((v) => v.level === 'warn');
|
|
477
|
-
|
|
478
|
-
if (failViolations.length > 0) {
|
|
479
|
-
console.log(`\n## Failures (${failViolations.length})\n`);
|
|
480
|
-
for (const v of failViolations.slice(0, 20)) {
|
|
481
|
-
const loc = v.line ? `${v.file}:${v.line}` : v.file;
|
|
482
|
-
console.log(
|
|
483
|
-
` [FAIL] ${v.rule}: ${v.name} (${v.value}) at ${loc} — threshold ${v.threshold}`,
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
if (failViolations.length > 20) {
|
|
487
|
-
console.log(` ... and ${failViolations.length - 20} more`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (warnViolations.length > 0) {
|
|
492
|
-
console.log(`\n## Warnings (${warnViolations.length})\n`);
|
|
493
|
-
for (const v of warnViolations.slice(0, 20)) {
|
|
494
|
-
const loc = v.line ? `${v.file}:${v.line}` : v.file;
|
|
495
|
-
console.log(
|
|
496
|
-
` [WARN] ${v.rule}: ${v.name} (${v.value}) at ${loc} — threshold ${v.threshold}`,
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
if (warnViolations.length > 20) {
|
|
500
|
-
console.log(` ... and ${warnViolations.length - 20} more`);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
console.log();
|
|
506
|
-
|
|
507
|
-
if (!data.passed) {
|
|
508
|
-
process.exit(1);
|
|
509
|
-
}
|
|
510
|
-
}
|
package/src/mcp.js
CHANGED
|
@@ -836,26 +836,27 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
836
836
|
process.exit(1);
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
fnDepsData,
|
|
846
|
-
fnImpactData,
|
|
847
|
-
pathData,
|
|
848
|
-
contextData,
|
|
849
|
-
childrenData,
|
|
850
|
-
explainData,
|
|
851
|
-
whereData,
|
|
852
|
-
diffImpactData,
|
|
853
|
-
listFunctionsData,
|
|
854
|
-
rolesData,
|
|
855
|
-
} = await import('./queries.js');
|
|
839
|
+
// Connect transport FIRST so the server can receive the client's
|
|
840
|
+
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
841
|
+
// are still loading. These are lazy-loaded on the first tool call
|
|
842
|
+
// and cached for subsequent calls.
|
|
843
|
+
let _queries;
|
|
844
|
+
let _Database;
|
|
856
845
|
|
|
857
|
-
|
|
858
|
-
|
|
846
|
+
async function getQueries() {
|
|
847
|
+
if (!_queries) {
|
|
848
|
+
_queries = await import('./queries.js');
|
|
849
|
+
}
|
|
850
|
+
return _queries;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function getDatabase() {
|
|
854
|
+
if (!_Database) {
|
|
855
|
+
const require = createRequire(import.meta.url);
|
|
856
|
+
_Database = require('better-sqlite3');
|
|
857
|
+
}
|
|
858
|
+
return _Database;
|
|
859
|
+
}
|
|
859
860
|
|
|
860
861
|
const server = new Server(
|
|
861
862
|
{ name: 'codegraph', version: '1.0.0' },
|
|
@@ -868,8 +869,24 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
868
869
|
|
|
869
870
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
870
871
|
const { name, arguments: args } = request.params;
|
|
871
|
-
|
|
872
872
|
try {
|
|
873
|
+
const {
|
|
874
|
+
impactAnalysisData,
|
|
875
|
+
moduleMapData,
|
|
876
|
+
fileDepsData,
|
|
877
|
+
exportsData,
|
|
878
|
+
fnDepsData,
|
|
879
|
+
fnImpactData,
|
|
880
|
+
pathData,
|
|
881
|
+
contextData,
|
|
882
|
+
childrenData,
|
|
883
|
+
explainData,
|
|
884
|
+
whereData,
|
|
885
|
+
diffImpactData,
|
|
886
|
+
listFunctionsData,
|
|
887
|
+
rolesData,
|
|
888
|
+
} = await getQueries();
|
|
889
|
+
const Database = getDatabase();
|
|
873
890
|
if (!multiRepo && args.repo) {
|
|
874
891
|
throw new Error(
|
|
875
892
|
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
package/src/owners.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { findDbPath, openReadonlyOrFail } from './db.js';
|
|
4
|
-
import { isTestFile } from './
|
|
4
|
+
import { isTestFile } from './infrastructure/test-filter.js';
|
|
5
5
|
|
|
6
6
|
// ─── CODEOWNERS Parsing ──────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -163,197 +163,142 @@ export function ownersForFiles(filePaths, repoRoot) {
|
|
|
163
163
|
*/
|
|
164
164
|
export function ownersData(customDbPath, opts = {}) {
|
|
165
165
|
const db = openReadonlyOrFail(customDbPath);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Get all distinct files from nodes
|
|
189
|
-
let allFiles = db
|
|
190
|
-
.prepare('SELECT DISTINCT file FROM nodes')
|
|
191
|
-
.all()
|
|
192
|
-
.map((r) => r.file);
|
|
166
|
+
try {
|
|
167
|
+
const dbPath = findDbPath(customDbPath);
|
|
168
|
+
const repoRoot = path.resolve(path.dirname(dbPath), '..');
|
|
169
|
+
|
|
170
|
+
const parsed = parseCodeowners(repoRoot);
|
|
171
|
+
if (!parsed) {
|
|
172
|
+
return {
|
|
173
|
+
codeownersFile: null,
|
|
174
|
+
files: [],
|
|
175
|
+
symbols: [],
|
|
176
|
+
boundaries: [],
|
|
177
|
+
summary: {
|
|
178
|
+
totalFiles: 0,
|
|
179
|
+
ownedFiles: 0,
|
|
180
|
+
unownedFiles: 0,
|
|
181
|
+
coveragePercent: 0,
|
|
182
|
+
ownerCount: 0,
|
|
183
|
+
byOwner: [],
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
193
187
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
188
|
+
// Get all distinct files from nodes
|
|
189
|
+
let allFiles = db
|
|
190
|
+
.prepare('SELECT DISTINCT file FROM nodes')
|
|
191
|
+
.all()
|
|
192
|
+
.map((r) => r.file);
|
|
199
193
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}));
|
|
205
|
-
|
|
206
|
-
// Build owner-to-files index
|
|
207
|
-
const ownerIndex = new Map();
|
|
208
|
-
let ownedCount = 0;
|
|
209
|
-
for (const fo of fileOwners) {
|
|
210
|
-
if (fo.owners.length > 0) ownedCount++;
|
|
211
|
-
for (const o of fo.owners) {
|
|
212
|
-
if (!ownerIndex.has(o)) ownerIndex.set(o, []);
|
|
213
|
-
ownerIndex.get(o).push(fo.file);
|
|
194
|
+
if (opts.noTests) allFiles = allFiles.filter((f) => !isTestFile(f));
|
|
195
|
+
if (opts.file) {
|
|
196
|
+
const filter = opts.file;
|
|
197
|
+
allFiles = allFiles.filter((f) => f.includes(filter));
|
|
214
198
|
}
|
|
215
|
-
}
|
|
216
199
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (opts.kind) symbols = symbols.filter((s) => s.kind === opts.kind);
|
|
232
|
-
|
|
233
|
-
const symbolsWithOwners = symbols.map((s) => ({
|
|
234
|
-
...s,
|
|
235
|
-
owners: matchOwners(s.file, parsed.rules),
|
|
236
|
-
}));
|
|
237
|
-
|
|
238
|
-
// Boundary analysis — cross-owner call edges
|
|
239
|
-
const boundaries = [];
|
|
240
|
-
if (opts.boundary) {
|
|
241
|
-
const edges = db
|
|
242
|
-
.prepare(
|
|
243
|
-
`SELECT e.id, e.kind AS edgeKind,
|
|
244
|
-
s.name AS srcName, s.kind AS srcKind, s.file AS srcFile, s.line AS srcLine,
|
|
245
|
-
t.name AS tgtName, t.kind AS tgtKind, t.file AS tgtFile, t.line AS tgtLine
|
|
246
|
-
FROM edges e
|
|
247
|
-
JOIN nodes s ON e.source_id = s.id
|
|
248
|
-
JOIN nodes t ON e.target_id = t.id
|
|
249
|
-
WHERE e.kind = 'calls'`,
|
|
250
|
-
)
|
|
251
|
-
.all();
|
|
252
|
-
|
|
253
|
-
for (const e of edges) {
|
|
254
|
-
if (opts.noTests && (isTestFile(e.srcFile) || isTestFile(e.tgtFile))) continue;
|
|
255
|
-
const srcOwners = matchOwners(e.srcFile, parsed.rules);
|
|
256
|
-
const tgtOwners = matchOwners(e.tgtFile, parsed.rules);
|
|
257
|
-
// Cross-boundary: different owner sets
|
|
258
|
-
const srcKey = srcOwners.sort().join(',');
|
|
259
|
-
const tgtKey = tgtOwners.sort().join(',');
|
|
260
|
-
if (srcKey !== tgtKey) {
|
|
261
|
-
boundaries.push({
|
|
262
|
-
from: {
|
|
263
|
-
name: e.srcName,
|
|
264
|
-
kind: e.srcKind,
|
|
265
|
-
file: e.srcFile,
|
|
266
|
-
line: e.srcLine,
|
|
267
|
-
owners: srcOwners,
|
|
268
|
-
},
|
|
269
|
-
to: {
|
|
270
|
-
name: e.tgtName,
|
|
271
|
-
kind: e.tgtKind,
|
|
272
|
-
file: e.tgtFile,
|
|
273
|
-
line: e.tgtLine,
|
|
274
|
-
owners: tgtOwners,
|
|
275
|
-
},
|
|
276
|
-
edgeKind: e.edgeKind,
|
|
277
|
-
});
|
|
200
|
+
// Map files to owners
|
|
201
|
+
const fileOwners = allFiles.map((file) => ({
|
|
202
|
+
file,
|
|
203
|
+
owners: matchOwners(file, parsed.rules),
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
// Build owner-to-files index
|
|
207
|
+
const ownerIndex = new Map();
|
|
208
|
+
let ownedCount = 0;
|
|
209
|
+
for (const fo of fileOwners) {
|
|
210
|
+
if (fo.owners.length > 0) ownedCount++;
|
|
211
|
+
for (const o of fo.owners) {
|
|
212
|
+
if (!ownerIndex.has(o)) ownerIndex.set(o, []);
|
|
213
|
+
ownerIndex.get(o).push(fo.file);
|
|
278
214
|
}
|
|
279
215
|
}
|
|
280
|
-
}
|
|
281
216
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
db.close();
|
|
288
|
-
return {
|
|
289
|
-
codeownersFile: parsed.path,
|
|
290
|
-
files: filteredFiles,
|
|
291
|
-
symbols: symbolsWithOwners,
|
|
292
|
-
boundaries,
|
|
293
|
-
summary: {
|
|
294
|
-
totalFiles: allFiles.length,
|
|
295
|
-
ownedFiles: ownedCount,
|
|
296
|
-
unownedFiles: allFiles.length - ownedCount,
|
|
297
|
-
coveragePercent: allFiles.length > 0 ? Math.round((ownedCount / allFiles.length) * 100) : 0,
|
|
298
|
-
ownerCount: ownerIndex.size,
|
|
299
|
-
byOwner,
|
|
300
|
-
},
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// ─── CLI Display ─────────────────────────────────────────────────────
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* CLI display function for the `owners` command.
|
|
308
|
-
* @param {string} [customDbPath]
|
|
309
|
-
* @param {object} [opts]
|
|
310
|
-
*/
|
|
311
|
-
export function owners(customDbPath, opts = {}) {
|
|
312
|
-
const data = ownersData(customDbPath, opts);
|
|
313
|
-
if (opts.json) {
|
|
314
|
-
console.log(JSON.stringify(data, null, 2));
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (!data.codeownersFile) {
|
|
319
|
-
console.log('No CODEOWNERS file found.');
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
console.log(`\nCODEOWNERS: ${data.codeownersFile}\n`);
|
|
324
|
-
|
|
325
|
-
const s = data.summary;
|
|
326
|
-
console.log(
|
|
327
|
-
` Coverage: ${s.coveragePercent}% (${s.ownedFiles}/${s.totalFiles} files owned, ${s.ownerCount} owners)\n`,
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
if (s.byOwner.length > 0) {
|
|
331
|
-
console.log(' Owners:\n');
|
|
332
|
-
for (const o of s.byOwner) {
|
|
333
|
-
console.log(` ${o.owner} ${o.fileCount} files`);
|
|
217
|
+
// Filter files if --owner specified
|
|
218
|
+
let filteredFiles = fileOwners;
|
|
219
|
+
if (opts.owner) {
|
|
220
|
+
filteredFiles = fileOwners.filter((fo) => fo.owners.includes(opts.owner));
|
|
334
221
|
}
|
|
335
|
-
console.log();
|
|
336
|
-
}
|
|
337
222
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
223
|
+
// Get symbols for filtered files
|
|
224
|
+
const fileSet = new Set(filteredFiles.map((fo) => fo.file));
|
|
225
|
+
let symbols = db
|
|
226
|
+
.prepare('SELECT name, kind, file, line FROM nodes')
|
|
227
|
+
.all()
|
|
228
|
+
.filter((n) => fileSet.has(n.file));
|
|
229
|
+
|
|
230
|
+
if (opts.noTests) symbols = symbols.filter((s) => !isTestFile(s.file));
|
|
231
|
+
if (opts.kind) symbols = symbols.filter((s) => s.kind === opts.kind);
|
|
232
|
+
|
|
233
|
+
const symbolsWithOwners = symbols.map((s) => ({
|
|
234
|
+
...s,
|
|
235
|
+
owners: matchOwners(s.file, parsed.rules),
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
// Boundary analysis — cross-owner call edges
|
|
239
|
+
const boundaries = [];
|
|
240
|
+
if (opts.boundary) {
|
|
241
|
+
const edges = db
|
|
242
|
+
.prepare(
|
|
243
|
+
`SELECT e.id, e.kind AS edgeKind,
|
|
244
|
+
s.name AS srcName, s.kind AS srcKind, s.file AS srcFile, s.line AS srcLine,
|
|
245
|
+
t.name AS tgtName, t.kind AS tgtKind, t.file AS tgtFile, t.line AS tgtLine
|
|
246
|
+
FROM edges e
|
|
247
|
+
JOIN nodes s ON e.source_id = s.id
|
|
248
|
+
JOIN nodes t ON e.target_id = t.id
|
|
249
|
+
WHERE e.kind = 'calls'`,
|
|
250
|
+
)
|
|
251
|
+
.all();
|
|
252
|
+
|
|
253
|
+
for (const e of edges) {
|
|
254
|
+
if (opts.noTests && (isTestFile(e.srcFile) || isTestFile(e.tgtFile))) continue;
|
|
255
|
+
const srcOwners = matchOwners(e.srcFile, parsed.rules);
|
|
256
|
+
const tgtOwners = matchOwners(e.tgtFile, parsed.rules);
|
|
257
|
+
// Cross-boundary: different owner sets
|
|
258
|
+
const srcKey = srcOwners.sort().join(',');
|
|
259
|
+
const tgtKey = tgtOwners.sort().join(',');
|
|
260
|
+
if (srcKey !== tgtKey) {
|
|
261
|
+
boundaries.push({
|
|
262
|
+
from: {
|
|
263
|
+
name: e.srcName,
|
|
264
|
+
kind: e.srcKind,
|
|
265
|
+
file: e.srcFile,
|
|
266
|
+
line: e.srcLine,
|
|
267
|
+
owners: srcOwners,
|
|
268
|
+
},
|
|
269
|
+
to: {
|
|
270
|
+
name: e.tgtName,
|
|
271
|
+
kind: e.tgtKind,
|
|
272
|
+
file: e.tgtFile,
|
|
273
|
+
line: e.tgtLine,
|
|
274
|
+
owners: tgtOwners,
|
|
275
|
+
},
|
|
276
|
+
edgeKind: e.edgeKind,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
342
280
|
}
|
|
343
|
-
console.log();
|
|
344
|
-
}
|
|
345
281
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
282
|
+
// Summary
|
|
283
|
+
const byOwner = [...ownerIndex.entries()]
|
|
284
|
+
.map(([owner, files]) => ({ owner, fileCount: files.length }))
|
|
285
|
+
.sort((a, b) => b.fileCount - a.fileCount);
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
codeownersFile: parsed.path,
|
|
289
|
+
files: filteredFiles,
|
|
290
|
+
symbols: symbolsWithOwners,
|
|
291
|
+
boundaries,
|
|
292
|
+
summary: {
|
|
293
|
+
totalFiles: allFiles.length,
|
|
294
|
+
ownedFiles: ownedCount,
|
|
295
|
+
unownedFiles: allFiles.length - ownedCount,
|
|
296
|
+
coveragePercent: allFiles.length > 0 ? Math.round((ownedCount / allFiles.length) * 100) : 0,
|
|
297
|
+
ownerCount: ownerIndex.size,
|
|
298
|
+
byOwner,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
} finally {
|
|
302
|
+
db.close();
|
|
358
303
|
}
|
|
359
304
|
}
|