index.ts 29 KB

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