@optave/codegraph 2.1.1-dev.3c12b64 → 2.2.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 +87 -45
- package/package.json +5 -5
- package/src/builder.js +238 -33
- package/src/cli.js +96 -12
- package/src/config.js +1 -1
- package/src/cycles.js +13 -1
- package/src/db.js +4 -0
- package/src/embedder.js +1 -1
- package/src/export.js +20 -7
- package/src/extractors/csharp.js +6 -1
- package/src/extractors/go.js +6 -1
- package/src/extractors/java.js +4 -1
- package/src/extractors/javascript.js +145 -5
- package/src/extractors/php.js +8 -2
- package/src/extractors/python.js +8 -1
- package/src/extractors/ruby.js +4 -1
- package/src/extractors/rust.js +12 -2
- package/src/index.js +6 -0
- package/src/journal.js +109 -0
- package/src/mcp.js +131 -7
- package/src/parser.js +1 -0
- package/src/queries.js +1143 -38
- package/src/structure.js +21 -7
- package/src/watcher.js +25 -0
package/src/structure.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { normalizePath } from './constants.js';
|
|
3
3
|
import { openReadonlyOrFail } from './db.js';
|
|
4
4
|
import { debug } from './logger.js';
|
|
5
|
+
import { isTestFile } from './queries.js';
|
|
5
6
|
|
|
6
7
|
// ─── Build-time: insert directory nodes, contains edges, and metrics ────
|
|
7
8
|
|
|
@@ -233,6 +234,7 @@ export function structureData(customDbPath, opts = {}) {
|
|
|
233
234
|
const filterDir = opts.directory || null;
|
|
234
235
|
const maxDepth = opts.depth || null;
|
|
235
236
|
const sortBy = opts.sort || 'files';
|
|
237
|
+
const noTests = opts.noTests || false;
|
|
236
238
|
|
|
237
239
|
// Get all directory nodes with their metrics
|
|
238
240
|
let dirs = db
|
|
@@ -263,7 +265,7 @@ export function structureData(customDbPath, opts = {}) {
|
|
|
263
265
|
|
|
264
266
|
// Get file metrics for each directory
|
|
265
267
|
const result = dirs.map((d) => {
|
|
266
|
-
|
|
268
|
+
let files = db
|
|
267
269
|
.prepare(`
|
|
268
270
|
SELECT n.name, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count, nm.fan_in, nm.fan_out
|
|
269
271
|
FROM edges e
|
|
@@ -272,6 +274,7 @@ export function structureData(customDbPath, opts = {}) {
|
|
|
272
274
|
WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
|
|
273
275
|
`)
|
|
274
276
|
.all(d.id);
|
|
277
|
+
if (noTests) files = files.filter((f) => !isTestFile(f.name));
|
|
275
278
|
|
|
276
279
|
const subdirs = db
|
|
277
280
|
.prepare(`
|
|
@@ -282,14 +285,15 @@ export function structureData(customDbPath, opts = {}) {
|
|
|
282
285
|
`)
|
|
283
286
|
.all(d.id);
|
|
284
287
|
|
|
288
|
+
const fileCount = noTests ? files.length : d.file_count || 0;
|
|
285
289
|
return {
|
|
286
290
|
directory: d.name,
|
|
287
|
-
fileCount
|
|
291
|
+
fileCount,
|
|
288
292
|
symbolCount: d.symbol_count || 0,
|
|
289
293
|
fanIn: d.fan_in || 0,
|
|
290
294
|
fanOut: d.fan_out || 0,
|
|
291
295
|
cohesion: d.cohesion,
|
|
292
|
-
density:
|
|
296
|
+
density: fileCount > 0 ? (d.symbol_count || 0) / fileCount : 0,
|
|
293
297
|
files: files.map((f) => ({
|
|
294
298
|
file: f.name,
|
|
295
299
|
lineCount: f.line_count || 0,
|
|
@@ -315,30 +319,40 @@ export function hotspotsData(customDbPath, opts = {}) {
|
|
|
315
319
|
const metric = opts.metric || 'fan-in';
|
|
316
320
|
const level = opts.level || 'file';
|
|
317
321
|
const limit = opts.limit || 10;
|
|
322
|
+
const noTests = opts.noTests || false;
|
|
318
323
|
|
|
319
324
|
const kind = level === 'directory' ? 'directory' : 'file';
|
|
320
325
|
|
|
326
|
+
const testFilter =
|
|
327
|
+
noTests && kind === 'file'
|
|
328
|
+
? `AND n.name NOT LIKE '%.test.%'
|
|
329
|
+
AND n.name NOT LIKE '%.spec.%'
|
|
330
|
+
AND n.name NOT LIKE '%__test__%'
|
|
331
|
+
AND n.name NOT LIKE '%__tests__%'
|
|
332
|
+
AND n.name NOT LIKE '%.stories.%'`
|
|
333
|
+
: '';
|
|
334
|
+
|
|
321
335
|
const HOTSPOT_QUERIES = {
|
|
322
336
|
'fan-in': db.prepare(`
|
|
323
337
|
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
324
338
|
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
325
339
|
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
326
|
-
WHERE n.kind = ? ORDER BY nm.fan_in DESC NULLS LAST LIMIT ?`),
|
|
340
|
+
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_in DESC NULLS LAST LIMIT ?`),
|
|
327
341
|
'fan-out': db.prepare(`
|
|
328
342
|
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
329
343
|
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
330
344
|
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
331
|
-
WHERE n.kind = ? ORDER BY nm.fan_out DESC NULLS LAST LIMIT ?`),
|
|
345
|
+
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_out DESC NULLS LAST LIMIT ?`),
|
|
332
346
|
density: db.prepare(`
|
|
333
347
|
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
334
348
|
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
335
349
|
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
336
|
-
WHERE n.kind = ? ORDER BY nm.symbol_count DESC NULLS LAST LIMIT ?`),
|
|
350
|
+
WHERE n.kind = ? ${testFilter} ORDER BY nm.symbol_count DESC NULLS LAST LIMIT ?`),
|
|
337
351
|
coupling: db.prepare(`
|
|
338
352
|
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
339
353
|
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
340
354
|
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
341
|
-
WHERE n.kind = ? ORDER BY (COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST LIMIT ?`),
|
|
355
|
+
WHERE n.kind = ? ${testFilter} ORDER BY (COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST LIMIT ?`),
|
|
342
356
|
};
|
|
343
357
|
|
|
344
358
|
const stmt = HOTSPOT_QUERIES[metric] || HOTSPOT_QUERIES['fan-in'];
|
package/src/watcher.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
|
|
4
4
|
import { initSchema, openDb } from './db.js';
|
|
5
|
+
import { appendJournalEntries } from './journal.js';
|
|
5
6
|
import { info, warn } from './logger.js';
|
|
6
7
|
import { createParseTreeCache, getActiveEngine, parseFileIncremental } from './parser.js';
|
|
7
8
|
import { resolveImportPath } from './resolve.js';
|
|
@@ -205,6 +206,19 @@ export async function watchProject(rootDir, opts = {}) {
|
|
|
205
206
|
}
|
|
206
207
|
const updates = results;
|
|
207
208
|
|
|
209
|
+
// Append processed files to journal for Tier 0 detection on next build
|
|
210
|
+
if (updates.length > 0) {
|
|
211
|
+
const entries = updates.map((r) => ({
|
|
212
|
+
file: r.file,
|
|
213
|
+
deleted: r.deleted || false,
|
|
214
|
+
}));
|
|
215
|
+
try {
|
|
216
|
+
appendJournalEntries(rootDir, entries);
|
|
217
|
+
} catch {
|
|
218
|
+
/* journal write failure is non-fatal */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
208
222
|
for (const r of updates) {
|
|
209
223
|
const nodeDelta = r.nodesAdded - r.nodesRemoved;
|
|
210
224
|
const nodeStr = nodeDelta >= 0 ? `+${nodeDelta}` : `${nodeDelta}`;
|
|
@@ -234,6 +248,17 @@ export async function watchProject(rootDir, opts = {}) {
|
|
|
234
248
|
process.on('SIGINT', () => {
|
|
235
249
|
console.log('\nStopping watcher...');
|
|
236
250
|
watcher.close();
|
|
251
|
+
// Flush any pending file paths to journal before exit
|
|
252
|
+
if (pending.size > 0) {
|
|
253
|
+
const entries = [...pending].map((filePath) => ({
|
|
254
|
+
file: normalizePath(path.relative(rootDir, filePath)),
|
|
255
|
+
}));
|
|
256
|
+
try {
|
|
257
|
+
appendJournalEntries(rootDir, entries);
|
|
258
|
+
} catch {
|
|
259
|
+
/* best-effort */
|
|
260
|
+
}
|
|
261
|
+
}
|
|
237
262
|
if (cache) cache.clear();
|
|
238
263
|
db.close();
|
|
239
264
|
process.exit(0);
|