@optave/codegraph 3.1.0 → 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.
Files changed (47) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/rules/csharp.js +201 -0
  5. package/src/ast-analysis/rules/go.js +182 -0
  6. package/src/ast-analysis/rules/index.js +82 -0
  7. package/src/ast-analysis/rules/java.js +175 -0
  8. package/src/ast-analysis/rules/javascript.js +246 -0
  9. package/src/ast-analysis/rules/php.js +219 -0
  10. package/src/ast-analysis/rules/python.js +196 -0
  11. package/src/ast-analysis/rules/ruby.js +204 -0
  12. package/src/ast-analysis/rules/rust.js +173 -0
  13. package/src/ast-analysis/shared.js +223 -0
  14. package/src/ast.js +15 -28
  15. package/src/audit.js +4 -5
  16. package/src/boundaries.js +1 -1
  17. package/src/branch-compare.js +84 -79
  18. package/src/builder.js +0 -5
  19. package/src/cfg.js +106 -338
  20. package/src/check.js +3 -3
  21. package/src/cli.js +99 -179
  22. package/src/cochange.js +1 -1
  23. package/src/communities.js +13 -16
  24. package/src/complexity.js +196 -1239
  25. package/src/cycles.js +1 -1
  26. package/src/dataflow.js +269 -694
  27. package/src/db/connection.js +88 -0
  28. package/src/db/migrations.js +312 -0
  29. package/src/db/query-builder.js +280 -0
  30. package/src/db/repository.js +134 -0
  31. package/src/db.js +19 -399
  32. package/src/embedder.js +145 -141
  33. package/src/export.js +1 -1
  34. package/src/flow.js +161 -162
  35. package/src/index.js +34 -1
  36. package/src/kinds.js +49 -0
  37. package/src/manifesto.js +3 -8
  38. package/src/mcp.js +37 -20
  39. package/src/owners.js +132 -132
  40. package/src/queries-cli.js +866 -0
  41. package/src/queries.js +1323 -2267
  42. package/src/result-formatter.js +21 -0
  43. package/src/sequence.js +177 -182
  44. package/src/structure.js +200 -199
  45. package/src/test-filter.js +7 -0
  46. package/src/triage.js +120 -162
  47. package/src/viewer.js +1 -1
package/src/owners.js CHANGED
@@ -1,7 +1,8 @@
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 './queries.js';
4
+ import { outputResult } from './result-formatter.js';
5
+ import { isTestFile } from './test-filter.js';
5
6
 
6
7
  // ─── CODEOWNERS Parsing ──────────────────────────────────────────────
7
8
 
@@ -163,142 +164,144 @@ export function ownersForFiles(filePaths, repoRoot) {
163
164
  */
164
165
  export function ownersData(customDbPath, opts = {}) {
165
166
  const db = openReadonlyOrFail(customDbPath);
166
- const dbPath = findDbPath(customDbPath);
167
- const repoRoot = path.resolve(path.dirname(dbPath), '..');
168
-
169
- const parsed = parseCodeowners(repoRoot);
170
- if (!parsed) {
171
- db.close();
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
- }
167
+ try {
168
+ const dbPath = findDbPath(customDbPath);
169
+ const repoRoot = path.resolve(path.dirname(dbPath), '..');
170
+
171
+ const parsed = parseCodeowners(repoRoot);
172
+ if (!parsed) {
173
+ return {
174
+ codeownersFile: null,
175
+ files: [],
176
+ symbols: [],
177
+ boundaries: [],
178
+ summary: {
179
+ totalFiles: 0,
180
+ ownedFiles: 0,
181
+ unownedFiles: 0,
182
+ coveragePercent: 0,
183
+ ownerCount: 0,
184
+ byOwner: [],
185
+ },
186
+ };
187
+ }
187
188
 
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);
189
+ // Get all distinct files from nodes
190
+ let allFiles = db
191
+ .prepare('SELECT DISTINCT file FROM nodes')
192
+ .all()
193
+ .map((r) => r.file);
193
194
 
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));
198
- }
195
+ if (opts.noTests) allFiles = allFiles.filter((f) => !isTestFile(f));
196
+ if (opts.file) {
197
+ const filter = opts.file;
198
+ allFiles = allFiles.filter((f) => f.includes(filter));
199
+ }
199
200
 
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);
201
+ // Map files to owners
202
+ const fileOwners = allFiles.map((file) => ({
203
+ file,
204
+ owners: matchOwners(file, parsed.rules),
205
+ }));
206
+
207
+ // Build owner-to-files index
208
+ const ownerIndex = new Map();
209
+ let ownedCount = 0;
210
+ for (const fo of fileOwners) {
211
+ if (fo.owners.length > 0) ownedCount++;
212
+ for (const o of fo.owners) {
213
+ if (!ownerIndex.has(o)) ownerIndex.set(o, []);
214
+ ownerIndex.get(o).push(fo.file);
215
+ }
214
216
  }
215
- }
216
217
 
217
- // Filter files if --owner specified
218
- let filteredFiles = fileOwners;
219
- if (opts.owner) {
220
- filteredFiles = fileOwners.filter((fo) => fo.owners.includes(opts.owner));
221
- }
218
+ // Filter files if --owner specified
219
+ let filteredFiles = fileOwners;
220
+ if (opts.owner) {
221
+ filteredFiles = fileOwners.filter((fo) => fo.owners.includes(opts.owner));
222
+ }
222
223
 
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
- });
224
+ // Get symbols for filtered files
225
+ const fileSet = new Set(filteredFiles.map((fo) => fo.file));
226
+ let symbols = db
227
+ .prepare('SELECT name, kind, file, line FROM nodes')
228
+ .all()
229
+ .filter((n) => fileSet.has(n.file));
230
+
231
+ if (opts.noTests) symbols = symbols.filter((s) => !isTestFile(s.file));
232
+ if (opts.kind) symbols = symbols.filter((s) => s.kind === opts.kind);
233
+
234
+ const symbolsWithOwners = symbols.map((s) => ({
235
+ ...s,
236
+ owners: matchOwners(s.file, parsed.rules),
237
+ }));
238
+
239
+ // Boundary analysis — cross-owner call edges
240
+ const boundaries = [];
241
+ if (opts.boundary) {
242
+ const edges = db
243
+ .prepare(
244
+ `SELECT e.id, e.kind AS edgeKind,
245
+ s.name AS srcName, s.kind AS srcKind, s.file AS srcFile, s.line AS srcLine,
246
+ t.name AS tgtName, t.kind AS tgtKind, t.file AS tgtFile, t.line AS tgtLine
247
+ FROM edges e
248
+ JOIN nodes s ON e.source_id = s.id
249
+ JOIN nodes t ON e.target_id = t.id
250
+ WHERE e.kind = 'calls'`,
251
+ )
252
+ .all();
253
+
254
+ for (const e of edges) {
255
+ if (opts.noTests && (isTestFile(e.srcFile) || isTestFile(e.tgtFile))) continue;
256
+ const srcOwners = matchOwners(e.srcFile, parsed.rules);
257
+ const tgtOwners = matchOwners(e.tgtFile, parsed.rules);
258
+ // Cross-boundary: different owner sets
259
+ const srcKey = srcOwners.sort().join(',');
260
+ const tgtKey = tgtOwners.sort().join(',');
261
+ if (srcKey !== tgtKey) {
262
+ boundaries.push({
263
+ from: {
264
+ name: e.srcName,
265
+ kind: e.srcKind,
266
+ file: e.srcFile,
267
+ line: e.srcLine,
268
+ owners: srcOwners,
269
+ },
270
+ to: {
271
+ name: e.tgtName,
272
+ kind: e.tgtKind,
273
+ file: e.tgtFile,
274
+ line: e.tgtLine,
275
+ owners: tgtOwners,
276
+ },
277
+ edgeKind: e.edgeKind,
278
+ });
279
+ }
278
280
  }
279
281
  }
280
- }
281
282
 
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
- 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
- };
283
+ // Summary
284
+ const byOwner = [...ownerIndex.entries()]
285
+ .map(([owner, files]) => ({ owner, fileCount: files.length }))
286
+ .sort((a, b) => b.fileCount - a.fileCount);
287
+
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
+ } finally {
303
+ db.close();
304
+ }
302
305
  }
303
306
 
304
307
  // ─── CLI Display ─────────────────────────────────────────────────────
@@ -310,10 +313,7 @@ export function ownersData(customDbPath, opts = {}) {
310
313
  */
311
314
  export function owners(customDbPath, opts = {}) {
312
315
  const data = ownersData(customDbPath, opts);
313
- if (opts.json) {
314
- console.log(JSON.stringify(data, null, 2));
315
- return;
316
- }
316
+ if (outputResult(data, null, opts)) return;
317
317
 
318
318
  if (!data.codeownersFile) {
319
319
  console.log('No CODEOWNERS file found.');