@colbymchenry/codegraph 0.3.1 → 0.4.8

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.

Potentially problematic release.


This version of @colbymchenry/codegraph might be problematic. Click here for more details.

Files changed (113) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +647 -641
  3. package/dist/bin/codegraph.d.ts +7 -2
  4. package/dist/bin/codegraph.d.ts.map +1 -1
  5. package/dist/bin/codegraph.js +360 -140
  6. package/dist/bin/codegraph.js.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +8 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/context/index.d.ts +17 -4
  11. package/dist/context/index.d.ts.map +1 -1
  12. package/dist/context/index.js +182 -15
  13. package/dist/context/index.js.map +1 -1
  14. package/dist/db/index.d.ts.map +1 -1
  15. package/dist/db/index.js +16 -0
  16. package/dist/db/index.js.map +1 -1
  17. package/dist/db/migrations.d.ts +1 -1
  18. package/dist/db/migrations.d.ts.map +1 -1
  19. package/dist/db/migrations.js +11 -12
  20. package/dist/db/migrations.js.map +1 -1
  21. package/dist/db/queries.d.ts +19 -0
  22. package/dist/db/queries.d.ts.map +1 -1
  23. package/dist/db/queries.js +221 -107
  24. package/dist/db/queries.js.map +1 -1
  25. package/dist/db/schema.sql +154 -149
  26. package/dist/directory.d.ts +13 -1
  27. package/dist/directory.d.ts.map +1 -1
  28. package/dist/directory.js +62 -19
  29. package/dist/directory.js.map +1 -1
  30. package/dist/errors.d.ts +1 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +7 -1
  33. package/dist/errors.js.map +1 -1
  34. package/dist/extraction/grammars.d.ts +9 -4
  35. package/dist/extraction/grammars.d.ts.map +1 -1
  36. package/dist/extraction/grammars.js +133 -65
  37. package/dist/extraction/grammars.js.map +1 -1
  38. package/dist/extraction/index.d.ts.map +1 -1
  39. package/dist/extraction/index.js +119 -7
  40. package/dist/extraction/index.js.map +1 -1
  41. package/dist/extraction/tree-sitter.d.ts +62 -0
  42. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  43. package/dist/extraction/tree-sitter.js +937 -34
  44. package/dist/extraction/tree-sitter.js.map +1 -1
  45. package/dist/graph/traversal.d.ts.map +1 -1
  46. package/dist/graph/traversal.js +6 -2
  47. package/dist/graph/traversal.js.map +1 -1
  48. package/dist/index.d.ts +6 -38
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +85 -73
  51. package/dist/index.js.map +1 -1
  52. package/dist/installer/banner.js +7 -7
  53. package/dist/installer/claude-md-template.js +32 -32
  54. package/dist/installer/config-writer.d.ts +9 -0
  55. package/dist/installer/config-writer.d.ts.map +1 -1
  56. package/dist/installer/config-writer.js +78 -0
  57. package/dist/installer/config-writer.js.map +1 -1
  58. package/dist/installer/index.d.ts.map +1 -1
  59. package/dist/installer/index.js +11 -9
  60. package/dist/installer/index.js.map +1 -1
  61. package/dist/mcp/index.d.ts +14 -3
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +109 -29
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/mcp/tools.d.ts +62 -1
  66. package/dist/mcp/tools.d.ts.map +1 -1
  67. package/dist/mcp/tools.js +414 -43
  68. package/dist/mcp/tools.js.map +1 -1
  69. package/dist/mcp/transport.d.ts.map +1 -1
  70. package/dist/mcp/transport.js +2 -0
  71. package/dist/mcp/transport.js.map +1 -1
  72. package/dist/resolution/frameworks/index.d.ts +1 -0
  73. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  74. package/dist/resolution/frameworks/index.js +5 -1
  75. package/dist/resolution/frameworks/index.js.map +1 -1
  76. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  77. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  78. package/dist/resolution/frameworks/svelte.js +268 -0
  79. package/dist/resolution/frameworks/svelte.js.map +1 -0
  80. package/dist/resolution/index.d.ts +11 -2
  81. package/dist/resolution/index.d.ts.map +1 -1
  82. package/dist/resolution/index.js +94 -13
  83. package/dist/resolution/index.js.map +1 -1
  84. package/dist/sentry.d.ts +22 -0
  85. package/dist/sentry.d.ts.map +1 -0
  86. package/dist/sentry.js +159 -0
  87. package/dist/sentry.js.map +1 -0
  88. package/dist/sync/index.d.ts +4 -2
  89. package/dist/sync/index.d.ts.map +1 -1
  90. package/dist/sync/index.js +3 -5
  91. package/dist/sync/index.js.map +1 -1
  92. package/dist/types.d.ts +5 -1
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +11 -0
  95. package/dist/types.js.map +1 -1
  96. package/dist/utils.d.ts +45 -2
  97. package/dist/utils.d.ts.map +1 -1
  98. package/dist/utils.js +114 -3
  99. package/dist/utils.js.map +1 -1
  100. package/dist/vectors/embedder.d.ts +1 -1
  101. package/dist/vectors/embedder.d.ts.map +1 -1
  102. package/dist/vectors/embedder.js +2 -2
  103. package/dist/vectors/embedder.js.map +1 -1
  104. package/dist/vectors/search.d.ts.map +1 -1
  105. package/dist/vectors/search.js +33 -32
  106. package/dist/vectors/search.js.map +1 -1
  107. package/package.json +72 -67
  108. package/scripts/patch-tree-sitter-dart.js +112 -0
  109. package/scripts/postinstall.js +71 -68
  110. package/dist/sync/git-hooks.d.ts +0 -66
  111. package/dist/sync/git-hooks.d.ts.map +0 -1
  112. package/dist/sync/git-hooks.js +0 -281
  113. package/dist/sync/git-hooks.js.map +0 -1
@@ -9,13 +9,18 @@
9
9
  * codegraph Run interactive installer (when no args)
10
10
  * codegraph install Run interactive installer
11
11
  * codegraph init [path] Initialize CodeGraph in a project
12
+ * codegraph uninit [path] Remove CodeGraph from a project
12
13
  * codegraph index [path] Index all files in the project
13
14
  * codegraph sync [path] Sync changes since last index
14
15
  * codegraph status [path] Show index status
15
16
  * codegraph query <search> Search for symbols
17
+ * codegraph files [options] Show project file structure
16
18
  * codegraph context <task> Build context for a task
17
- * codegraph hooks install Install git hooks
18
- * codegraph hooks remove Remove git hooks
19
+ * codegraph mark-dirty [path] Mark project as needing sync (hooks)
20
+ * codegraph sync-if-dirty [path] Sync if marked dirty (hooks)
21
+ *
22
+ * Note: Git hooks have been removed. CodeGraph sync is triggered automatically
23
+ * through codegraph's Claude Code hooks integration.
19
24
  */
20
25
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
26
  if (k2 === undefined) k2 = k;
@@ -50,18 +55,27 @@ var __importStar = (this && this.__importStar) || (function () {
50
55
  return result;
51
56
  };
52
57
  })();
53
- var __importDefault = (this && this.__importDefault) || function (mod) {
54
- return (mod && mod.__esModule) ? mod : { "default": mod };
55
- };
56
58
  Object.defineProperty(exports, "__esModule", { value: true });
57
59
  const commander_1 = require("commander");
58
60
  const path = __importStar(require("path"));
59
61
  const fs = __importStar(require("fs"));
60
- const index_1 = __importDefault(require("../index"));
62
+ const index_1 = __importStar(require("../index"));
61
63
  const installer_1 = require("../installer");
64
+ const sentry_1 = require("../sentry");
62
65
  // Check if running with no arguments - run installer
66
+ // Read version for Sentry release tag
67
+ const pkgVersion = (() => {
68
+ try {
69
+ return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ })();
75
+ (0, sentry_1.initSentry)({ processName: 'codegraph-cli', version: pkgVersion });
63
76
  if (process.argv.length === 2) {
64
77
  (0, installer_1.runInstaller)().catch((err) => {
78
+ (0, sentry_1.captureException)(err);
65
79
  console.error('Installation failed:', err.message);
66
80
  process.exit(1);
67
81
  });
@@ -70,6 +84,14 @@ else {
70
84
  // Normal CLI flow
71
85
  main();
72
86
  }
87
+ process.on('uncaughtException', (error) => {
88
+ (0, sentry_1.captureException)(error);
89
+ console.error('[CodeGraph] Uncaught exception:', error);
90
+ });
91
+ process.on('unhandledRejection', (reason) => {
92
+ (0, sentry_1.captureException)(reason);
93
+ console.error('[CodeGraph] Unhandled rejection:', reason);
94
+ });
73
95
  function main() {
74
96
  const program = new commander_1.Command();
75
97
  // Version from package.json
@@ -109,9 +131,30 @@ function main() {
109
131
  // =============================================================================
110
132
  /**
111
133
  * Resolve project path from argument or current directory
134
+ * Walks up parent directories to find nearest initialized CodeGraph project
135
+ * (must have .codegraph/codegraph.db, not just .codegraph/lessons.db)
112
136
  */
113
137
  function resolveProjectPath(pathArg) {
114
- return path.resolve(pathArg || process.cwd());
138
+ const absolutePath = path.resolve(pathArg || process.cwd());
139
+ // If exact path is initialized (has codegraph.db), use it
140
+ if (index_1.default.isInitialized(absolutePath)) {
141
+ return absolutePath;
142
+ }
143
+ // Walk up to find nearest parent with CodeGraph initialized
144
+ // Note: findNearestCodeGraphRoot finds any .codegraph folder, but we need one with codegraph.db
145
+ let current = absolutePath;
146
+ const root = path.parse(current).root;
147
+ while (current !== root) {
148
+ const parent = path.dirname(current);
149
+ if (parent === current)
150
+ break;
151
+ current = parent;
152
+ if (index_1.default.isInitialized(current)) {
153
+ return current;
154
+ }
155
+ }
156
+ // Not found - return original path (will fail later with helpful error)
157
+ return absolutePath;
115
158
  }
116
159
  /**
117
160
  * Format a number with commas
@@ -195,7 +238,6 @@ function main() {
195
238
  .command('init [path]')
196
239
  .description('Initialize CodeGraph in a project directory')
197
240
  .option('-i, --index', 'Run initial indexing after initialization')
198
- .option('--no-hooks', 'Skip git hooks installation')
199
241
  .action(async (pathArg, options) => {
200
242
  const projectPath = resolveProjectPath(pathArg);
201
243
  console.log(chalk.bold('\nInitializing CodeGraph...\n'));
@@ -212,16 +254,6 @@ function main() {
212
254
  });
213
255
  success(`Initialized CodeGraph in ${projectPath}`);
214
256
  info(`Created .codegraph/ directory`);
215
- // Install git hooks if requested (default: true)
216
- if (options.hooks !== false && cg.isGitRepository()) {
217
- const hookResult = cg.installGitHooks();
218
- if (hookResult.success) {
219
- success('Installed git post-commit hook for auto-sync');
220
- }
221
- else {
222
- warn(`Could not install git hooks: ${hookResult.message}`);
223
- }
224
- }
225
257
  // Run initial index if requested
226
258
  if (options.index) {
227
259
  console.log('\nIndexing project...\n');
@@ -245,10 +277,48 @@ function main() {
245
277
  cg.destroy();
246
278
  }
247
279
  catch (err) {
280
+ (0, sentry_1.captureException)(err);
248
281
  error(`Failed to initialize: ${err instanceof Error ? err.message : String(err)}`);
249
282
  process.exit(1);
250
283
  }
251
284
  });
285
+ /**
286
+ * codegraph uninit [path]
287
+ */
288
+ program
289
+ .command('uninit [path]')
290
+ .description('Remove CodeGraph from a project (deletes .codegraph/ directory)')
291
+ .option('-f, --force', 'Skip confirmation prompt')
292
+ .action(async (pathArg, options) => {
293
+ const projectPath = resolveProjectPath(pathArg);
294
+ try {
295
+ if (!index_1.default.isInitialized(projectPath)) {
296
+ warn(`CodeGraph is not initialized in ${projectPath}`);
297
+ return;
298
+ }
299
+ if (!options.force) {
300
+ // Confirm with user
301
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
302
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
303
+ const answer = await new Promise((resolve) => {
304
+ rl.question(chalk.yellow('⚠ This will permanently delete all CodeGraph data. Continue? (y/N) '), resolve);
305
+ });
306
+ rl.close();
307
+ if (answer.toLowerCase() !== 'y') {
308
+ info('Cancelled');
309
+ return;
310
+ }
311
+ }
312
+ const cg = index_1.default.openSync(projectPath);
313
+ cg.uninitialize();
314
+ success(`Removed CodeGraph from ${projectPath}`);
315
+ }
316
+ catch (err) {
317
+ (0, sentry_1.captureException)(err);
318
+ error(`Failed to uninitialize: ${err instanceof Error ? err.message : String(err)}`);
319
+ process.exit(1);
320
+ }
321
+ });
252
322
  /**
253
323
  * codegraph index [path]
254
324
  */
@@ -305,6 +375,7 @@ function main() {
305
375
  cg.destroy();
306
376
  }
307
377
  catch (err) {
378
+ (0, sentry_1.captureException)(err);
308
379
  error(`Failed to index: ${err instanceof Error ? err.message : String(err)}`);
309
380
  process.exit(1);
310
381
  }
@@ -355,6 +426,7 @@ function main() {
355
426
  cg.destroy();
356
427
  }
357
428
  catch (err) {
429
+ (0, sentry_1.captureException)(err);
358
430
  if (!options.quiet) {
359
431
  error(`Failed to sync: ${err instanceof Error ? err.message : String(err)}`);
360
432
  }
@@ -367,10 +439,15 @@ function main() {
367
439
  program
368
440
  .command('status [path]')
369
441
  .description('Show index status and statistics')
370
- .action(async (pathArg) => {
442
+ .option('-j, --json', 'Output as JSON')
443
+ .action(async (pathArg, options) => {
371
444
  const projectPath = resolveProjectPath(pathArg);
372
445
  try {
373
446
  if (!index_1.default.isInitialized(projectPath)) {
447
+ if (options.json) {
448
+ console.log(JSON.stringify({ initialized: false, projectPath }));
449
+ return;
450
+ }
374
451
  console.log(chalk.bold('\nCodeGraph Status\n'));
375
452
  info(`Project: ${projectPath}`);
376
453
  warn('Not initialized');
@@ -380,6 +457,26 @@ function main() {
380
457
  const cg = await index_1.default.open(projectPath);
381
458
  const stats = cg.getStats();
382
459
  const changes = cg.getChangedFiles();
460
+ // JSON output mode
461
+ if (options.json) {
462
+ console.log(JSON.stringify({
463
+ initialized: true,
464
+ projectPath,
465
+ fileCount: stats.fileCount,
466
+ nodeCount: stats.nodeCount,
467
+ edgeCount: stats.edgeCount,
468
+ dbSizeBytes: stats.dbSizeBytes,
469
+ nodesByKind: stats.nodesByKind,
470
+ languages: Object.entries(stats.filesByLanguage).filter(([, count]) => count > 0).map(([lang]) => lang),
471
+ pendingChanges: {
472
+ added: changes.added.length,
473
+ modified: changes.modified.length,
474
+ removed: changes.removed.length,
475
+ },
476
+ }));
477
+ cg.destroy();
478
+ return;
479
+ }
383
480
  console.log(chalk.bold('\nCodeGraph Status\n'));
384
481
  // Project info
385
482
  console.log(chalk.cyan('Project:'), projectPath);
@@ -428,20 +525,10 @@ function main() {
428
525
  success('Index is up to date');
429
526
  }
430
527
  console.log();
431
- // Git hooks status
432
- if (cg.isGitRepository()) {
433
- const hookInstalled = cg.isGitHookInstalled();
434
- if (hookInstalled) {
435
- success('Git hooks: installed');
436
- }
437
- else {
438
- warn('Git hooks: not installed');
439
- info('Run "codegraph hooks install" to enable auto-sync');
440
- }
441
- }
442
528
  cg.destroy();
443
529
  }
444
530
  catch (err) {
531
+ (0, sentry_1.captureException)(err);
445
532
  error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
446
533
  process.exit(1);
447
534
  }
@@ -496,54 +583,24 @@ function main() {
496
583
  cg.destroy();
497
584
  }
498
585
  catch (err) {
586
+ (0, sentry_1.captureException)(err);
499
587
  error(`Search failed: ${err instanceof Error ? err.message : String(err)}`);
500
588
  process.exit(1);
501
589
  }
502
590
  });
503
591
  /**
504
- * codegraph context <task>
592
+ * codegraph files [path]
505
593
  */
506
594
  program
507
- .command('context <task>')
508
- .description('Build context for a task (outputs markdown)')
509
- .option('-p, --path <path>', 'Project path')
510
- .option('-n, --max-nodes <number>', 'Maximum nodes to include', '50')
511
- .option('-c, --max-code <number>', 'Maximum code blocks', '10')
512
- .option('--no-code', 'Exclude code blocks')
513
- .option('-f, --format <format>', 'Output format (markdown, json)', 'markdown')
514
- .action(async (task, options) => {
515
- const projectPath = resolveProjectPath(options.path);
516
- try {
517
- if (!index_1.default.isInitialized(projectPath)) {
518
- error(`CodeGraph not initialized in ${projectPath}`);
519
- process.exit(1);
520
- }
521
- const cg = await index_1.default.open(projectPath);
522
- const context = await cg.buildContext(task, {
523
- maxNodes: parseInt(options.maxNodes || '50', 10),
524
- maxCodeBlocks: parseInt(options.maxCode || '10', 10),
525
- includeCode: options.code !== false,
526
- format: options.format,
527
- });
528
- // Output the context
529
- console.log(context);
530
- cg.destroy();
531
- }
532
- catch (err) {
533
- error(`Failed to build context: ${err instanceof Error ? err.message : String(err)}`);
534
- process.exit(1);
535
- }
536
- });
537
- /**
538
- * codegraph hooks <action>
539
- */
540
- const hooksCommand = program
541
- .command('hooks')
542
- .description('Manage git hooks');
543
- hooksCommand
544
- .command('install')
545
- .description('Install git post-commit hook for auto-sync')
595
+ .command('files')
596
+ .description('Show project file structure from the index')
546
597
  .option('-p, --path <path>', 'Project path')
598
+ .option('--filter <dir>', 'Filter to files under this directory')
599
+ .option('--pattern <glob>', 'Filter files matching this glob pattern')
600
+ .option('--format <format>', 'Output format (tree, flat, grouped)', 'tree')
601
+ .option('--max-depth <number>', 'Maximum directory depth for tree format')
602
+ .option('--no-metadata', 'Hide file metadata (language, symbol count)')
603
+ .option('-j, --json', 'Output as JSON')
547
604
  .action(async (options) => {
548
605
  const projectPath = resolveProjectPath(options.path);
549
606
  try {
@@ -552,69 +609,165 @@ function main() {
552
609
  process.exit(1);
553
610
  }
554
611
  const cg = await index_1.default.open(projectPath);
555
- if (!cg.isGitRepository()) {
556
- error('Not a git repository');
612
+ let files = cg.getFiles();
613
+ if (files.length === 0) {
614
+ info('No files indexed. Run "codegraph index" first.');
557
615
  cg.destroy();
558
- process.exit(1);
616
+ return;
559
617
  }
560
- const result = cg.installGitHooks();
561
- if (result.success) {
562
- success(result.message);
563
- if (result.previousHookBackedUp) {
564
- info('Previous hook backed up to post-commit.codegraph-backup');
565
- }
618
+ // Filter by path prefix
619
+ if (options.filter) {
620
+ const filter = options.filter;
621
+ files = files.filter(f => f.path.startsWith(filter) || f.path.startsWith('./' + filter));
566
622
  }
567
- else {
568
- error(result.message);
569
- process.exit(1);
570
- }
571
- cg.destroy();
572
- }
573
- catch (err) {
574
- error(`Failed to install hooks: ${err instanceof Error ? err.message : String(err)}`);
575
- process.exit(1);
576
- }
577
- });
578
- hooksCommand
579
- .command('remove')
580
- .description('Remove git post-commit hook')
581
- .option('-p, --path <path>', 'Project path')
582
- .action(async (options) => {
583
- const projectPath = resolveProjectPath(options.path);
584
- try {
585
- if (!index_1.default.isInitialized(projectPath)) {
586
- error(`CodeGraph not initialized in ${projectPath}`);
587
- process.exit(1);
623
+ // Filter by glob pattern
624
+ if (options.pattern) {
625
+ const regex = globToRegex(options.pattern);
626
+ files = files.filter(f => regex.test(f.path));
588
627
  }
589
- const cg = await index_1.default.open(projectPath);
590
- if (!cg.isGitRepository()) {
591
- error('Not a git repository');
628
+ if (files.length === 0) {
629
+ info('No files found matching the criteria.');
592
630
  cg.destroy();
593
- process.exit(1);
631
+ return;
594
632
  }
595
- const result = cg.removeGitHooks();
596
- if (result.success) {
597
- success(result.message);
598
- if (result.restoredFromBackup) {
599
- info('Restored previous hook from backup');
600
- }
633
+ // JSON output
634
+ if (options.json) {
635
+ const output = files.map(f => ({
636
+ path: f.path,
637
+ language: f.language,
638
+ nodeCount: f.nodeCount,
639
+ size: f.size,
640
+ }));
641
+ console.log(JSON.stringify(output, null, 2));
642
+ cg.destroy();
643
+ return;
601
644
  }
602
- else {
603
- error(result.message);
604
- process.exit(1);
645
+ const includeMetadata = options.metadata !== false;
646
+ const format = options.format || 'tree';
647
+ const maxDepth = options.maxDepth ? parseInt(options.maxDepth, 10) : undefined;
648
+ // Format output
649
+ switch (format) {
650
+ case 'flat':
651
+ console.log(chalk.bold(`\nFiles (${files.length}):\n`));
652
+ for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
653
+ if (includeMetadata) {
654
+ console.log(` ${file.path} ${chalk.dim(`(${file.language}, ${file.nodeCount} symbols)`)}`);
655
+ }
656
+ else {
657
+ console.log(` ${file.path}`);
658
+ }
659
+ }
660
+ break;
661
+ case 'grouped':
662
+ console.log(chalk.bold(`\nFiles by Language (${files.length} total):\n`));
663
+ const byLang = new Map();
664
+ for (const file of files) {
665
+ const existing = byLang.get(file.language) || [];
666
+ existing.push(file);
667
+ byLang.set(file.language, existing);
668
+ }
669
+ const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
670
+ for (const [lang, langFiles] of sortedLangs) {
671
+ console.log(chalk.cyan(`${lang} (${langFiles.length}):`));
672
+ for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
673
+ if (includeMetadata) {
674
+ console.log(` ${file.path} ${chalk.dim(`(${file.nodeCount} symbols)`)}`);
675
+ }
676
+ else {
677
+ console.log(` ${file.path}`);
678
+ }
679
+ }
680
+ console.log();
681
+ }
682
+ break;
683
+ case 'tree':
684
+ default:
685
+ console.log(chalk.bold(`\nProject Structure (${files.length} files):\n`));
686
+ printFileTree(files, includeMetadata, maxDepth, chalk);
687
+ break;
605
688
  }
689
+ console.log();
606
690
  cg.destroy();
607
691
  }
608
692
  catch (err) {
609
- error(`Failed to remove hooks: ${err instanceof Error ? err.message : String(err)}`);
693
+ (0, sentry_1.captureException)(err);
694
+ error(`Failed to list files: ${err instanceof Error ? err.message : String(err)}`);
610
695
  process.exit(1);
611
696
  }
612
697
  });
613
- hooksCommand
614
- .command('status')
615
- .description('Check git hooks status')
698
+ /**
699
+ * Convert glob pattern to regex
700
+ */
701
+ function globToRegex(pattern) {
702
+ const escaped = pattern
703
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
704
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
705
+ .replace(/\*/g, '[^/]*')
706
+ .replace(/\?/g, '[^/]')
707
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*');
708
+ return new RegExp(escaped);
709
+ }
710
+ /**
711
+ * Print files as a tree
712
+ */
713
+ function printFileTree(files, includeMetadata, maxDepth, chalk) {
714
+ const root = { name: '', children: new Map() };
715
+ for (const file of files) {
716
+ const parts = file.path.split('/');
717
+ let current = root;
718
+ for (let i = 0; i < parts.length; i++) {
719
+ const part = parts[i];
720
+ if (!part)
721
+ continue;
722
+ if (!current.children.has(part)) {
723
+ current.children.set(part, { name: part, children: new Map() });
724
+ }
725
+ current = current.children.get(part);
726
+ if (i === parts.length - 1) {
727
+ current.file = { language: file.language, nodeCount: file.nodeCount };
728
+ }
729
+ }
730
+ }
731
+ const renderNode = (node, prefix, isLast, depth) => {
732
+ if (maxDepth !== undefined && depth > maxDepth)
733
+ return;
734
+ const connector = isLast ? '└── ' : '├── ';
735
+ const childPrefix = isLast ? ' ' : '│ ';
736
+ if (node.name) {
737
+ let line = prefix + connector + node.name;
738
+ if (node.file && includeMetadata) {
739
+ line += chalk.dim(` (${node.file.language}, ${node.file.nodeCount} symbols)`);
740
+ }
741
+ console.log(line);
742
+ }
743
+ const children = [...node.children.values()];
744
+ children.sort((a, b) => {
745
+ const aIsDir = a.children.size > 0 && !a.file;
746
+ const bIsDir = b.children.size > 0 && !b.file;
747
+ if (aIsDir !== bIsDir)
748
+ return aIsDir ? -1 : 1;
749
+ return a.name.localeCompare(b.name);
750
+ });
751
+ for (let i = 0; i < children.length; i++) {
752
+ const child = children[i];
753
+ const nextPrefix = node.name ? prefix + childPrefix : prefix;
754
+ renderNode(child, nextPrefix, i === children.length - 1, depth + 1);
755
+ }
756
+ };
757
+ renderNode(root, '', true, 0);
758
+ }
759
+ /**
760
+ * codegraph context <task>
761
+ */
762
+ program
763
+ .command('context <task>')
764
+ .description('Build context for a task (outputs markdown)')
616
765
  .option('-p, --path <path>', 'Project path')
617
- .action(async (options) => {
766
+ .option('-n, --max-nodes <number>', 'Maximum nodes to include', '50')
767
+ .option('-c, --max-code <number>', 'Maximum code blocks', '10')
768
+ .option('--no-code', 'Exclude code blocks')
769
+ .option('-f, --format <format>', 'Output format (markdown, json)', 'markdown')
770
+ .action(async (task, options) => {
618
771
  const projectPath = resolveProjectPath(options.path);
619
772
  try {
620
773
  if (!index_1.default.isInitialized(projectPath)) {
@@ -622,22 +775,19 @@ function main() {
622
775
  process.exit(1);
623
776
  }
624
777
  const cg = await index_1.default.open(projectPath);
625
- if (!cg.isGitRepository()) {
626
- info('Not a git repository');
627
- cg.destroy();
628
- return;
629
- }
630
- if (cg.isGitHookInstalled()) {
631
- success('Git hook is installed');
632
- }
633
- else {
634
- warn('Git hook is not installed');
635
- info('Run "codegraph hooks install" to enable auto-sync');
636
- }
778
+ const context = await cg.buildContext(task, {
779
+ maxNodes: parseInt(options.maxNodes || '50', 10),
780
+ maxCodeBlocks: parseInt(options.maxCode || '10', 10),
781
+ includeCode: options.code !== false,
782
+ format: options.format,
783
+ });
784
+ // Output the context
785
+ console.log(context);
637
786
  cg.destroy();
638
787
  }
639
788
  catch (err) {
640
- error(`Failed to check hooks: ${err instanceof Error ? err.message : String(err)}`);
789
+ (0, sentry_1.captureException)(err);
790
+ error(`Failed to build context: ${err instanceof Error ? err.message : String(err)}`);
641
791
  process.exit(1);
642
792
  }
643
793
  });
@@ -664,15 +814,15 @@ function main() {
664
814
  console.log(chalk.bold('\nCodeGraph MCP Server\n'));
665
815
  info('Use --mcp flag to start the MCP server');
666
816
  console.log('\nTo use with Claude Code, add to your MCP configuration:');
667
- console.log(chalk.dim(`
668
- {
669
- "mcpServers": {
670
- "codegraph": {
671
- "command": "codegraph",
672
- "args": ["serve", "--mcp"]
673
- }
674
- }
675
- }
817
+ console.log(chalk.dim(`
818
+ {
819
+ "mcpServers": {
820
+ "codegraph": {
821
+ "command": "codegraph",
822
+ "args": ["serve", "--mcp"]
823
+ }
824
+ }
825
+ }
676
826
  `));
677
827
  console.log('Available tools:');
678
828
  console.log(chalk.cyan(' codegraph_search') + ' - Search for code symbols');
@@ -681,14 +831,84 @@ function main() {
681
831
  console.log(chalk.cyan(' codegraph_callees') + ' - Find what a symbol calls');
682
832
  console.log(chalk.cyan(' codegraph_impact') + ' - Analyze impact of changes');
683
833
  console.log(chalk.cyan(' codegraph_node') + ' - Get symbol details');
834
+ console.log(chalk.cyan(' codegraph_files') + ' - Get project file structure');
684
835
  console.log(chalk.cyan(' codegraph_status') + ' - Get index status');
685
836
  }
686
837
  }
687
838
  catch (err) {
839
+ (0, sentry_1.captureException)(err);
688
840
  error(`Failed to start server: ${err instanceof Error ? err.message : String(err)}`);
689
841
  process.exit(1);
690
842
  }
691
843
  });
844
+ /**
845
+ * codegraph mark-dirty [path]
846
+ *
847
+ * Touches .codegraph/.dirty to signal that files have changed.
848
+ * Used by Claude Code PostToolUse hooks to batch syncs.
849
+ * Runs silently and always exits 0.
850
+ */
851
+ program
852
+ .command('mark-dirty [path]')
853
+ .description('Mark project as needing sync (used by Claude Code hooks)')
854
+ .action(async (pathArg) => {
855
+ try {
856
+ const startPath = path.resolve(pathArg || process.cwd());
857
+ const projectRoot = (0, index_1.findNearestCodeGraphRoot)(startPath);
858
+ if (!projectRoot) {
859
+ // No .codegraph/ found — exit silently
860
+ process.exit(0);
861
+ }
862
+ const dirtyPath = path.join((0, index_1.getCodeGraphDir)(projectRoot), '.dirty');
863
+ fs.writeFileSync(dirtyPath, Date.now().toString(), 'utf-8');
864
+ }
865
+ catch {
866
+ // Never fail — this runs in the background during edits
867
+ }
868
+ process.exit(0);
869
+ });
870
+ /**
871
+ * codegraph sync-if-dirty [path]
872
+ *
873
+ * Syncs the index only if .codegraph/.dirty exists.
874
+ * Removes the marker BEFORE syncing so edits during sync
875
+ * create a new marker for the next Stop event.
876
+ * Runs silently and always exits 0.
877
+ */
878
+ program
879
+ .command('sync-if-dirty [path]')
880
+ .description('Sync if project was marked dirty (used by Claude Code hooks)')
881
+ .action(async (pathArg) => {
882
+ try {
883
+ const startPath = path.resolve(pathArg || process.cwd());
884
+ const projectRoot = (0, index_1.findNearestCodeGraphRoot)(startPath);
885
+ if (!projectRoot) {
886
+ process.exit(0);
887
+ }
888
+ const dirtyPath = path.join((0, index_1.getCodeGraphDir)(projectRoot), '.dirty');
889
+ // No marker → nothing to do (sub-ms exit)
890
+ if (!fs.existsSync(dirtyPath)) {
891
+ process.exit(0);
892
+ }
893
+ // Remove marker FIRST so edits during sync create a new one
894
+ try {
895
+ fs.unlinkSync(dirtyPath);
896
+ }
897
+ catch { /* ignore */ }
898
+ // If not fully initialized (no DB), exit
899
+ if (!index_1.default.isInitialized(projectRoot)) {
900
+ process.exit(0);
901
+ }
902
+ // Run sync
903
+ const cg = await index_1.default.open(projectRoot);
904
+ await cg.sync();
905
+ cg.destroy();
906
+ }
907
+ catch {
908
+ // Never fail — this runs at the end of Claude responses
909
+ }
910
+ process.exit(0);
911
+ });
692
912
  /**
693
913
  * codegraph install
694
914
  */