index.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /**
  2. * CodeGraph
  3. *
  4. * A local-first code intelligence system that builds a semantic
  5. * knowledge graph from any codebase.
  6. */
  7. import * as path from 'path';
  8. import {
  9. CodeGraphConfig,
  10. Node,
  11. Edge,
  12. FileRecord,
  13. ExtractionResult,
  14. Subgraph,
  15. TraversalOptions,
  16. SearchOptions,
  17. SearchResult,
  18. Context,
  19. GraphStats,
  20. TaskInput,
  21. TaskContext,
  22. BuildContextOptions,
  23. FindRelevantContextOptions,
  24. } from './types';
  25. import { DatabaseConnection, getDatabasePath } from './db';
  26. import { QueryBuilder } from './db/queries';
  27. import { loadConfig, saveConfig, createDefaultConfig } from './config';
  28. import {
  29. isInitialized,
  30. createDirectory,
  31. removeDirectory,
  32. validateDirectory,
  33. } from './directory';
  34. import {
  35. ExtractionOrchestrator,
  36. IndexProgress,
  37. IndexResult,
  38. SyncResult,
  39. extractFromSource,
  40. } from './extraction';
  41. import {
  42. ReferenceResolver,
  43. createResolver,
  44. ResolutionResult,
  45. } from './resolution';
  46. import { GraphTraverser, GraphQueryManager } from './graph';
  47. import { VectorManager, createVectorManager, EmbeddingProgress } from './vectors';
  48. import { ContextBuilder, createContextBuilder } from './context';
  49. import { GitHooksManager, createGitHooksManager, HookInstallResult, HookRemoveResult } from './sync';
  50. import { Mutex } from './utils';
  51. // Re-export types for consumers
  52. export * from './types';
  53. export { getDatabasePath } from './db';
  54. export { getConfigPath } from './config';
  55. export { getCodeGraphDir, isInitialized } from './directory';
  56. export { IndexProgress, IndexResult, SyncResult } from './extraction';
  57. export { detectLanguage, isLanguageSupported, getSupportedLanguages } from './extraction';
  58. export { ResolutionResult } from './resolution';
  59. export { EmbeddingProgress } from './vectors';
  60. export { HookInstallResult, HookRemoveResult } from './sync';
  61. export {
  62. CodeGraphError,
  63. FileError,
  64. ParseError,
  65. DatabaseError,
  66. SearchError,
  67. VectorError,
  68. ConfigError,
  69. Logger,
  70. setLogger,
  71. getLogger,
  72. silentLogger,
  73. defaultLogger,
  74. } from './errors';
  75. export { Mutex, processInBatches, debounce, throttle, MemoryMonitor } from './utils';
  76. export { MCPServer } from './mcp';
  77. /**
  78. * Options for initializing a new CodeGraph project
  79. */
  80. export interface InitOptions {
  81. /** Custom configuration overrides */
  82. config?: Partial<CodeGraphConfig>;
  83. /** Whether to run initial indexing after init */
  84. index?: boolean;
  85. /** Progress callback for indexing */
  86. onProgress?: (progress: IndexProgress) => void;
  87. }
  88. /**
  89. * Options for opening an existing CodeGraph project
  90. */
  91. export interface OpenOptions {
  92. /** Whether to run sync if files have changed */
  93. sync?: boolean;
  94. /** Whether to run in read-only mode */
  95. readOnly?: boolean;
  96. }
  97. /**
  98. * Options for indexing
  99. */
  100. export interface IndexOptions {
  101. /** Progress callback */
  102. onProgress?: (progress: IndexProgress) => void;
  103. /** Abort signal for cancellation */
  104. signal?: AbortSignal;
  105. }
  106. /**
  107. * Main CodeGraph class
  108. *
  109. * Provides the primary interface for interacting with the code knowledge graph.
  110. */
  111. export class CodeGraph {
  112. private db: DatabaseConnection;
  113. private queries: QueryBuilder;
  114. private config: CodeGraphConfig;
  115. private projectRoot: string;
  116. private orchestrator: ExtractionOrchestrator;
  117. private resolver: ReferenceResolver;
  118. private graphManager: GraphQueryManager;
  119. private traverser: GraphTraverser;
  120. private vectorManager: VectorManager | null = null;
  121. private contextBuilder: ContextBuilder;
  122. private gitHooksManager: GitHooksManager;
  123. // Mutex for preventing concurrent indexing operations
  124. private indexMutex = new Mutex();
  125. private constructor(
  126. db: DatabaseConnection,
  127. queries: QueryBuilder,
  128. config: CodeGraphConfig,
  129. projectRoot: string
  130. ) {
  131. this.db = db;
  132. this.queries = queries;
  133. this.config = config;
  134. this.projectRoot = projectRoot;
  135. this.orchestrator = new ExtractionOrchestrator(projectRoot, config, queries);
  136. this.resolver = createResolver(projectRoot, queries);
  137. this.graphManager = new GraphQueryManager(queries);
  138. this.traverser = new GraphTraverser(queries);
  139. // Vector manager is created lazily when embeddings are enabled
  140. if (config.enableEmbeddings) {
  141. this.vectorManager = createVectorManager(db.getDb(), queries, {
  142. embedder: {
  143. cacheDir: path.join(projectRoot, '.codegraph', 'models'),
  144. },
  145. });
  146. }
  147. // Context builder (uses vector manager if available)
  148. this.contextBuilder = createContextBuilder(
  149. projectRoot,
  150. queries,
  151. this.traverser,
  152. this.vectorManager
  153. );
  154. // Git hooks manager
  155. this.gitHooksManager = createGitHooksManager(projectRoot);
  156. }
  157. // ===========================================================================
  158. // Lifecycle Methods
  159. // ===========================================================================
  160. /**
  161. * Initialize a new CodeGraph project
  162. *
  163. * Creates the .codegraph directory, database, and configuration.
  164. *
  165. * @param projectRoot - Path to the project root directory
  166. * @param options - Initialization options
  167. * @returns A new CodeGraph instance
  168. */
  169. static async init(projectRoot: string, options: InitOptions = {}): Promise<CodeGraph> {
  170. const resolvedRoot = path.resolve(projectRoot);
  171. // Check if already initialized
  172. if (isInitialized(resolvedRoot)) {
  173. throw new Error(`CodeGraph already initialized in ${resolvedRoot}`);
  174. }
  175. // Create directory structure
  176. createDirectory(resolvedRoot);
  177. // Create and save configuration
  178. const config = createDefaultConfig(resolvedRoot);
  179. if (options.config) {
  180. Object.assign(config, options.config);
  181. }
  182. saveConfig(resolvedRoot, config);
  183. // Initialize database
  184. const dbPath = getDatabasePath(resolvedRoot);
  185. const db = DatabaseConnection.initialize(dbPath);
  186. const queries = new QueryBuilder(db.getDb());
  187. const instance = new CodeGraph(db, queries, config, resolvedRoot);
  188. // Run initial indexing if requested
  189. if (options.index) {
  190. await instance.indexAll({ onProgress: options.onProgress });
  191. }
  192. return instance;
  193. }
  194. /**
  195. * Initialize synchronously (without indexing)
  196. */
  197. static initSync(projectRoot: string, options: Omit<InitOptions, 'index' | 'onProgress'> = {}): CodeGraph {
  198. const resolvedRoot = path.resolve(projectRoot);
  199. // Check if already initialized
  200. if (isInitialized(resolvedRoot)) {
  201. throw new Error(`CodeGraph already initialized in ${resolvedRoot}`);
  202. }
  203. // Create directory structure
  204. createDirectory(resolvedRoot);
  205. // Create and save configuration
  206. const config = createDefaultConfig(resolvedRoot);
  207. if (options.config) {
  208. Object.assign(config, options.config);
  209. }
  210. saveConfig(resolvedRoot, config);
  211. // Initialize database
  212. const dbPath = getDatabasePath(resolvedRoot);
  213. const db = DatabaseConnection.initialize(dbPath);
  214. const queries = new QueryBuilder(db.getDb());
  215. return new CodeGraph(db, queries, config, resolvedRoot);
  216. }
  217. /**
  218. * Open an existing CodeGraph project
  219. *
  220. * @param projectRoot - Path to the project root directory
  221. * @param options - Open options
  222. * @returns A CodeGraph instance
  223. */
  224. static async open(projectRoot: string, options: OpenOptions = {}): Promise<CodeGraph> {
  225. const resolvedRoot = path.resolve(projectRoot);
  226. // Check if initialized
  227. if (!isInitialized(resolvedRoot)) {
  228. throw new Error(`CodeGraph not initialized in ${resolvedRoot}. Run init() first.`);
  229. }
  230. // Validate directory structure
  231. const validation = validateDirectory(resolvedRoot);
  232. if (!validation.valid) {
  233. throw new Error(`Invalid CodeGraph directory: ${validation.errors.join(', ')}`);
  234. }
  235. // Load configuration
  236. const config = loadConfig(resolvedRoot);
  237. // Open database
  238. const dbPath = getDatabasePath(resolvedRoot);
  239. const db = DatabaseConnection.open(dbPath);
  240. const queries = new QueryBuilder(db.getDb());
  241. const instance = new CodeGraph(db, queries, config, resolvedRoot);
  242. // Sync if requested
  243. if (options.sync) {
  244. await instance.sync();
  245. }
  246. return instance;
  247. }
  248. /**
  249. * Open synchronously (without sync)
  250. */
  251. static openSync(projectRoot: string): CodeGraph {
  252. const resolvedRoot = path.resolve(projectRoot);
  253. // Check if initialized
  254. if (!isInitialized(resolvedRoot)) {
  255. throw new Error(`CodeGraph not initialized in ${resolvedRoot}. Run init() first.`);
  256. }
  257. // Validate directory structure
  258. const validation = validateDirectory(resolvedRoot);
  259. if (!validation.valid) {
  260. throw new Error(`Invalid CodeGraph directory: ${validation.errors.join(', ')}`);
  261. }
  262. // Load configuration
  263. const config = loadConfig(resolvedRoot);
  264. // Open database
  265. const dbPath = getDatabasePath(resolvedRoot);
  266. const db = DatabaseConnection.open(dbPath);
  267. const queries = new QueryBuilder(db.getDb());
  268. return new CodeGraph(db, queries, config, resolvedRoot);
  269. }
  270. /**
  271. * Check if a directory has been initialized as a CodeGraph project
  272. */
  273. static isInitialized(projectRoot: string): boolean {
  274. return isInitialized(path.resolve(projectRoot));
  275. }
  276. /**
  277. * Close the CodeGraph instance and release resources
  278. */
  279. close(): void {
  280. this.db.close();
  281. }
  282. // ===========================================================================
  283. // Configuration
  284. // ===========================================================================
  285. /**
  286. * Get the current configuration
  287. */
  288. getConfig(): CodeGraphConfig {
  289. return { ...this.config };
  290. }
  291. /**
  292. * Update configuration
  293. */
  294. updateConfig(updates: Partial<CodeGraphConfig>): void {
  295. Object.assign(this.config, updates);
  296. saveConfig(this.projectRoot, this.config);
  297. // Recreate orchestrator and resolver with new config
  298. this.orchestrator = new ExtractionOrchestrator(
  299. this.projectRoot,
  300. this.config,
  301. this.queries
  302. );
  303. this.resolver = createResolver(this.projectRoot, this.queries);
  304. }
  305. /**
  306. * Get the project root directory
  307. */
  308. getProjectRoot(): string {
  309. return this.projectRoot;
  310. }
  311. // ===========================================================================
  312. // Indexing
  313. // ===========================================================================
  314. /**
  315. * Index all files in the project
  316. *
  317. * Uses a mutex to prevent concurrent indexing operations.
  318. */
  319. async indexAll(options: IndexOptions = {}): Promise<IndexResult> {
  320. return this.indexMutex.withLock(async () => {
  321. const result = await this.orchestrator.indexAll(options.onProgress, options.signal);
  322. // Resolve references to create call/import/extends edges
  323. if (result.success && result.filesIndexed > 0) {
  324. options.onProgress?.({
  325. phase: 'resolving',
  326. current: 0,
  327. total: 1,
  328. });
  329. this.resolveReferences();
  330. }
  331. return result;
  332. });
  333. }
  334. /**
  335. * Index specific files
  336. *
  337. * Uses a mutex to prevent concurrent indexing operations.
  338. */
  339. async indexFiles(filePaths: string[]): Promise<IndexResult> {
  340. return this.indexMutex.withLock(async () => {
  341. return this.orchestrator.indexFiles(filePaths);
  342. });
  343. }
  344. /**
  345. * Sync with current file state (incremental update)
  346. *
  347. * Uses a mutex to prevent concurrent indexing operations.
  348. */
  349. async sync(options: IndexOptions = {}): Promise<SyncResult> {
  350. return this.indexMutex.withLock(async () => {
  351. const result = await this.orchestrator.sync(options.onProgress);
  352. // Resolve references if files were updated
  353. if (result.filesAdded > 0 || result.filesModified > 0) {
  354. this.resolveReferences();
  355. }
  356. return result;
  357. });
  358. }
  359. /**
  360. * Check if an indexing operation is currently in progress
  361. */
  362. isIndexing(): boolean {
  363. return this.indexMutex.isLocked();
  364. }
  365. /**
  366. * Get files that have changed since last index
  367. */
  368. getChangedFiles(): { added: string[]; modified: string[]; removed: string[] } {
  369. return this.orchestrator.getChangedFiles();
  370. }
  371. /**
  372. * Extract nodes and edges from source code (without storing)
  373. */
  374. extractFromSource(filePath: string, source: string): ExtractionResult {
  375. return extractFromSource(filePath, source);
  376. }
  377. // ===========================================================================
  378. // Reference Resolution
  379. // ===========================================================================
  380. /**
  381. * Resolve unresolved references and create edges
  382. *
  383. * This method takes unresolved references from extraction and attempts
  384. * to resolve them using multiple strategies:
  385. * - Framework-specific patterns (React, Express, Laravel)
  386. * - Import-based resolution
  387. * - Name-based symbol matching
  388. */
  389. resolveReferences(): ResolutionResult {
  390. // Get all unresolved references from the database
  391. const unresolvedRefs = this.queries.getUnresolvedReferences();
  392. return this.resolver.resolveAndPersist(unresolvedRefs);
  393. }
  394. /**
  395. * Get detected frameworks in the project
  396. */
  397. getDetectedFrameworks(): string[] {
  398. return this.resolver.getDetectedFrameworks();
  399. }
  400. /**
  401. * Re-initialize the resolver (useful after adding new files)
  402. */
  403. reinitializeResolver(): void {
  404. this.resolver.initialize();
  405. }
  406. // ===========================================================================
  407. // Graph Statistics
  408. // ===========================================================================
  409. /**
  410. * Get statistics about the knowledge graph
  411. */
  412. getStats(): GraphStats {
  413. const stats = this.queries.getStats();
  414. stats.dbSizeBytes = this.db.getSize();
  415. return stats;
  416. }
  417. // ===========================================================================
  418. // Node Operations
  419. // ===========================================================================
  420. /**
  421. * Get a node by ID
  422. */
  423. getNode(id: string): Node | null {
  424. return this.queries.getNodeById(id);
  425. }
  426. /**
  427. * Get all nodes in a file
  428. */
  429. getNodesInFile(filePath: string): Node[] {
  430. return this.queries.getNodesByFile(filePath);
  431. }
  432. /**
  433. * Get all nodes of a specific kind
  434. */
  435. getNodesByKind(kind: Node['kind']): Node[] {
  436. return this.queries.getNodesByKind(kind);
  437. }
  438. /**
  439. * Search nodes by text
  440. */
  441. searchNodes(query: string, options?: SearchOptions): SearchResult[] {
  442. return this.queries.searchNodes(query, options);
  443. }
  444. // ===========================================================================
  445. // Edge Operations
  446. // ===========================================================================
  447. /**
  448. * Get outgoing edges from a node
  449. */
  450. getOutgoingEdges(nodeId: string): Edge[] {
  451. return this.queries.getOutgoingEdges(nodeId);
  452. }
  453. /**
  454. * Get incoming edges to a node
  455. */
  456. getIncomingEdges(nodeId: string): Edge[] {
  457. return this.queries.getIncomingEdges(nodeId);
  458. }
  459. // ===========================================================================
  460. // File Operations
  461. // ===========================================================================
  462. /**
  463. * Get a file record by path
  464. */
  465. getFile(filePath: string): FileRecord | null {
  466. return this.queries.getFileByPath(filePath);
  467. }
  468. /**
  469. * Get all tracked files
  470. */
  471. getFiles(): FileRecord[] {
  472. return this.queries.getAllFiles();
  473. }
  474. // ===========================================================================
  475. // Graph Query Methods
  476. // ===========================================================================
  477. /**
  478. * Get the context for a node (ancestors, children, references)
  479. *
  480. * Returns comprehensive context about a node including its containment
  481. * hierarchy, children, incoming/outgoing references, type information,
  482. * and relevant imports.
  483. *
  484. * @param nodeId - ID of the focal node
  485. * @returns Context object with all related information
  486. */
  487. getContext(nodeId: string): Context {
  488. return this.graphManager.getContext(nodeId);
  489. }
  490. /**
  491. * Traverse the graph from a starting node
  492. *
  493. * Uses breadth-first search by default. Supports filtering by edge types,
  494. * node types, and traversal direction.
  495. *
  496. * @param startId - Starting node ID
  497. * @param options - Traversal options
  498. * @returns Subgraph containing traversed nodes and edges
  499. */
  500. traverse(startId: string, options?: TraversalOptions): Subgraph {
  501. return this.traverser.traverseBFS(startId, options);
  502. }
  503. /**
  504. * Get the call graph for a function
  505. *
  506. * Returns both callers (functions that call this function) and
  507. * callees (functions called by this function) up to the specified depth.
  508. *
  509. * @param nodeId - ID of the function/method node
  510. * @param depth - Maximum depth in each direction (default: 2)
  511. * @returns Subgraph containing the call graph
  512. */
  513. getCallGraph(nodeId: string, depth: number = 2): Subgraph {
  514. return this.traverser.getCallGraph(nodeId, depth);
  515. }
  516. /**
  517. * Get the type hierarchy for a class/interface
  518. *
  519. * Returns both ancestors (types this extends/implements) and
  520. * descendants (types that extend/implement this).
  521. *
  522. * @param nodeId - ID of the class/interface node
  523. * @returns Subgraph containing the type hierarchy
  524. */
  525. getTypeHierarchy(nodeId: string): Subgraph {
  526. return this.traverser.getTypeHierarchy(nodeId);
  527. }
  528. /**
  529. * Find all usages of a symbol
  530. *
  531. * Returns all nodes that reference the specified symbol through
  532. * any edge type (calls, references, type_of, etc.).
  533. *
  534. * @param nodeId - ID of the symbol node
  535. * @returns Array of nodes and edges that reference this symbol
  536. */
  537. findUsages(nodeId: string): Array<{ node: Node; edge: Edge }> {
  538. return this.traverser.findUsages(nodeId);
  539. }
  540. /**
  541. * Get callers of a function/method
  542. *
  543. * @param nodeId - ID of the function/method node
  544. * @param maxDepth - Maximum depth to traverse (default: 1)
  545. * @returns Array of nodes that call this function
  546. */
  547. getCallers(nodeId: string, maxDepth: number = 1): Array<{ node: Node; edge: Edge }> {
  548. return this.traverser.getCallers(nodeId, maxDepth);
  549. }
  550. /**
  551. * Get callees of a function/method
  552. *
  553. * @param nodeId - ID of the function/method node
  554. * @param maxDepth - Maximum depth to traverse (default: 1)
  555. * @returns Array of nodes called by this function
  556. */
  557. getCallees(nodeId: string, maxDepth: number = 1): Array<{ node: Node; edge: Edge }> {
  558. return this.traverser.getCallees(nodeId, maxDepth);
  559. }
  560. /**
  561. * Calculate the impact radius of a node
  562. *
  563. * Returns all nodes that could be affected by changes to this node.
  564. *
  565. * @param nodeId - ID of the node
  566. * @param maxDepth - Maximum depth to traverse (default: 3)
  567. * @returns Subgraph containing potentially impacted nodes
  568. */
  569. getImpactRadius(nodeId: string, maxDepth: number = 3): Subgraph {
  570. return this.traverser.getImpactRadius(nodeId, maxDepth);
  571. }
  572. /**
  573. * Find the shortest path between two nodes
  574. *
  575. * @param fromId - Starting node ID
  576. * @param toId - Target node ID
  577. * @param edgeKinds - Edge types to consider (all if empty)
  578. * @returns Array of nodes and edges forming the path, or null if no path exists
  579. */
  580. findPath(
  581. fromId: string,
  582. toId: string,
  583. edgeKinds?: Edge['kind'][]
  584. ): Array<{ node: Node; edge: Edge | null }> | null {
  585. return this.traverser.findPath(fromId, toId, edgeKinds);
  586. }
  587. /**
  588. * Get ancestors of a node in the containment hierarchy
  589. *
  590. * @param nodeId - ID of the node
  591. * @returns Array of ancestor nodes from immediate parent to root
  592. */
  593. getAncestors(nodeId: string): Node[] {
  594. return this.traverser.getAncestors(nodeId);
  595. }
  596. /**
  597. * Get immediate children of a node
  598. *
  599. * @param nodeId - ID of the node
  600. * @returns Array of child nodes
  601. */
  602. getChildren(nodeId: string): Node[] {
  603. return this.traverser.getChildren(nodeId);
  604. }
  605. /**
  606. * Get dependencies of a file
  607. *
  608. * @param filePath - Path to the file
  609. * @returns Array of file paths this file depends on
  610. */
  611. getFileDependencies(filePath: string): string[] {
  612. return this.graphManager.getFileDependencies(filePath);
  613. }
  614. /**
  615. * Get dependents of a file
  616. *
  617. * @param filePath - Path to the file
  618. * @returns Array of file paths that depend on this file
  619. */
  620. getFileDependents(filePath: string): string[] {
  621. return this.graphManager.getFileDependents(filePath);
  622. }
  623. /**
  624. * Find circular dependencies in the codebase
  625. *
  626. * @returns Array of cycles, each cycle is an array of file paths
  627. */
  628. findCircularDependencies(): string[][] {
  629. return this.graphManager.findCircularDependencies();
  630. }
  631. /**
  632. * Find dead code (unreferenced symbols)
  633. *
  634. * @param kinds - Node kinds to check (default: functions, methods, classes)
  635. * @returns Array of unreferenced nodes
  636. */
  637. findDeadCode(kinds?: Node['kind'][]): Node[] {
  638. return this.graphManager.findDeadCode(kinds);
  639. }
  640. /**
  641. * Get complexity metrics for a node
  642. *
  643. * @param nodeId - ID of the node
  644. * @returns Object containing various complexity metrics
  645. */
  646. getNodeMetrics(nodeId: string): {
  647. incomingEdgeCount: number;
  648. outgoingEdgeCount: number;
  649. callCount: number;
  650. callerCount: number;
  651. childCount: number;
  652. depth: number;
  653. } {
  654. return this.graphManager.getNodeMetrics(nodeId);
  655. }
  656. // ===========================================================================
  657. // Semantic Search (Vector Embeddings)
  658. // ===========================================================================
  659. /**
  660. * Initialize the embedding system
  661. *
  662. * This downloads the embedding model on first use and initializes
  663. * the vector search system. Must be called before using semantic search.
  664. */
  665. async initializeEmbeddings(): Promise<void> {
  666. if (!this.vectorManager) {
  667. this.vectorManager = createVectorManager(this.db.getDb(), this.queries, {
  668. embedder: {
  669. showProgress: true,
  670. },
  671. });
  672. }
  673. await this.vectorManager.initialize();
  674. }
  675. /**
  676. * Check if embeddings are initialized
  677. */
  678. isEmbeddingsInitialized(): boolean {
  679. return this.vectorManager?.isInitialized() ?? false;
  680. }
  681. /**
  682. * Generate embeddings for all eligible nodes
  683. *
  684. * @param onProgress - Optional progress callback
  685. * @returns Number of nodes embedded
  686. */
  687. async generateEmbeddings(
  688. onProgress?: (progress: EmbeddingProgress) => void
  689. ): Promise<number> {
  690. if (!this.vectorManager) {
  691. await this.initializeEmbeddings();
  692. }
  693. return this.vectorManager!.embedAllNodes(onProgress);
  694. }
  695. /**
  696. * Semantic search using embeddings
  697. *
  698. * Searches for code nodes semantically similar to the query.
  699. * Requires embeddings to be initialized first.
  700. *
  701. * @param query - Natural language search query
  702. * @param limit - Maximum number of results (default: 10)
  703. * @returns Array of search results with similarity scores
  704. */
  705. async semanticSearch(query: string, limit: number = 10): Promise<SearchResult[]> {
  706. if (!this.vectorManager || !this.vectorManager.isInitialized()) {
  707. throw new Error(
  708. 'Embeddings not initialized. Call initializeEmbeddings() first.'
  709. );
  710. }
  711. return this.vectorManager.search(query, { limit });
  712. }
  713. /**
  714. * Find similar code blocks
  715. *
  716. * Finds nodes semantically similar to a given node.
  717. * Requires embeddings to be initialized first.
  718. *
  719. * @param nodeId - ID of the node to find similar nodes for
  720. * @param limit - Maximum number of results (default: 10)
  721. * @returns Array of similar nodes with similarity scores
  722. */
  723. async findSimilar(nodeId: string, limit: number = 10): Promise<SearchResult[]> {
  724. if (!this.vectorManager || !this.vectorManager.isInitialized()) {
  725. throw new Error(
  726. 'Embeddings not initialized. Call initializeEmbeddings() first.'
  727. );
  728. }
  729. return this.vectorManager.findSimilar(nodeId, { limit });
  730. }
  731. /**
  732. * Get vector embedding statistics
  733. */
  734. getEmbeddingStats(): {
  735. totalVectors: number;
  736. vssEnabled: boolean;
  737. modelId: string;
  738. dimension: number;
  739. } | null {
  740. if (!this.vectorManager) {
  741. return null;
  742. }
  743. return this.vectorManager.getStats();
  744. }
  745. // ===========================================================================
  746. // Context Building
  747. // ===========================================================================
  748. /**
  749. * Get the source code for a node
  750. *
  751. * Reads the file and extracts the code between startLine and endLine.
  752. *
  753. * @param nodeId - ID of the node
  754. * @returns Code string or null if not found
  755. */
  756. async getCode(nodeId: string): Promise<string | null> {
  757. return this.contextBuilder.getCode(nodeId);
  758. }
  759. /**
  760. * Find relevant subgraph for a query
  761. *
  762. * Combines semantic search with graph traversal to find the most
  763. * relevant nodes and their relationships for a given query.
  764. *
  765. * @param query - Natural language query describing the task
  766. * @param options - Search and traversal options
  767. * @returns Subgraph of relevant nodes and edges
  768. */
  769. async findRelevantContext(
  770. query: string,
  771. options?: FindRelevantContextOptions
  772. ): Promise<Subgraph> {
  773. // Update context builder with current vector manager
  774. this.contextBuilder = createContextBuilder(
  775. this.projectRoot,
  776. this.queries,
  777. this.traverser,
  778. this.vectorManager
  779. );
  780. return this.contextBuilder.findRelevantContext(query, options);
  781. }
  782. /**
  783. * Build context for a task
  784. *
  785. * Creates comprehensive context by:
  786. * 1. Running semantic search to find entry points
  787. * 2. Expanding the graph around entry points
  788. * 3. Extracting code blocks for key nodes
  789. * 4. Formatting output for Claude
  790. *
  791. * @param input - Task description (string or {title, description})
  792. * @param options - Build options (maxNodes, includeCode, format, etc.)
  793. * @returns TaskContext object or formatted string (markdown/JSON)
  794. */
  795. async buildContext(
  796. input: TaskInput,
  797. options?: BuildContextOptions
  798. ): Promise<TaskContext | string> {
  799. // Update context builder with current vector manager
  800. this.contextBuilder = createContextBuilder(
  801. this.projectRoot,
  802. this.queries,
  803. this.traverser,
  804. this.vectorManager
  805. );
  806. return this.contextBuilder.buildContext(input, options);
  807. }
  808. // ===========================================================================
  809. // Git Integration
  810. // ===========================================================================
  811. /**
  812. * Check if the project is a git repository
  813. */
  814. isGitRepository(): boolean {
  815. return this.gitHooksManager.isGitRepository();
  816. }
  817. /**
  818. * Check if the CodeGraph git hook is installed
  819. */
  820. isGitHookInstalled(): boolean {
  821. return this.gitHooksManager.isHookInstalled();
  822. }
  823. /**
  824. * Install git hooks for automatic incremental indexing
  825. *
  826. * Installs a post-commit hook that automatically runs `codegraph sync`
  827. * after each commit to keep the graph up-to-date.
  828. *
  829. * If a post-commit hook already exists:
  830. * - If it's a CodeGraph hook, it will be updated
  831. * - If it's a user hook, it will be backed up before installing
  832. *
  833. * @returns Result indicating success/failure and any messages
  834. */
  835. installGitHooks(): HookInstallResult {
  836. return this.gitHooksManager.installHook();
  837. }
  838. /**
  839. * Remove CodeGraph git hooks
  840. *
  841. * Removes the CodeGraph post-commit hook. If a backup of a previous
  842. * user hook exists, it will be restored.
  843. *
  844. * @returns Result indicating success/failure and any messages
  845. */
  846. removeGitHooks(): HookRemoveResult {
  847. return this.gitHooksManager.removeHook();
  848. }
  849. // ===========================================================================
  850. // Database Management
  851. // ===========================================================================
  852. /**
  853. * Optimize the database (vacuum and analyze)
  854. */
  855. optimize(): void {
  856. this.db.optimize();
  857. }
  858. /**
  859. * Clear all data from the graph
  860. */
  861. clear(): void {
  862. this.queries.clear();
  863. }
  864. /**
  865. * Alias for close() for backwards compatibility.
  866. * @deprecated Use close() instead
  867. */
  868. destroy(): void {
  869. this.close();
  870. }
  871. /**
  872. * Completely remove CodeGraph from the project.
  873. * This closes the database and deletes the .codegraph directory.
  874. *
  875. * WARNING: This permanently deletes all CodeGraph data for the project.
  876. */
  877. uninitialize(): void {
  878. this.db.close();
  879. removeDirectory(this.projectRoot);
  880. }
  881. }
  882. // Default export
  883. export default CodeGraph;