@optave/codegraph 2.1.1-dev.3c12b64 → 2.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/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
- const files = db
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: d.file_count || 0,
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: d.file_count > 0 ? (d.symbol_count || 0) / d.file_count : 0,
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);