#
tokens: 40425/50000 3/422 files (page 17/22)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 17 of 22. Use http://codebase.md/shashankss1205/codegraphcontext?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cgcignore
├── .github
│   ├── FUNDING.yml
│   └── workflows
│       ├── e2e-tests.yml
│       ├── post_discord_invite.yml
│       ├── test.yml
│       └── update-contributors.yml
├── .gitignore
├── CLI_Commands.md
├── CONTRIBUTING.md
├── contributors.md
├── docs
│   ├── docs
│   │   ├── architecture.md
│   │   ├── cli.md
│   │   ├── contributing_languages.md
│   │   ├── contributing.md
│   │   ├── cookbook.md
│   │   ├── core.md
│   │   ├── future_work.md
│   │   ├── images
│   │   │   ├── 1.png
│   │   │   ├── 11.png
│   │   │   ├── 12.png
│   │   │   ├── 13.png
│   │   │   ├── 14.png
│   │   │   ├── 16.png
│   │   │   ├── 19.png
│   │   │   ├── 2.png
│   │   │   ├── 20.png
│   │   │   ├── 21.png
│   │   │   ├── 22.png
│   │   │   ├── 23.png
│   │   │   ├── 24.png
│   │   │   ├── 26.png
│   │   │   ├── 28.png
│   │   │   ├── 29.png
│   │   │   ├── 3.png
│   │   │   ├── 30.png
│   │   │   ├── 31.png
│   │   │   ├── 32.png
│   │   │   ├── 33.png
│   │   │   ├── 34.png
│   │   │   ├── 35.png
│   │   │   ├── 36.png
│   │   │   ├── 38.png
│   │   │   ├── 39.png
│   │   │   ├── 4.png
│   │   │   ├── 40.png
│   │   │   ├── 41.png
│   │   │   ├── 42.png
│   │   │   ├── 43.png
│   │   │   ├── 44.png
│   │   │   ├── 5.png
│   │   │   ├── 6.png
│   │   │   ├── 7.png
│   │   │   ├── 8.png
│   │   │   ├── 9.png
│   │   │   ├── Indexing.gif
│   │   │   ├── tool_images
│   │   │   │   ├── 1.png
│   │   │   │   ├── 2.png
│   │   │   │   └── 3.png
│   │   │   └── Usecase.gif
│   │   ├── index.md
│   │   ├── installation.md
│   │   ├── license.md
│   │   ├── server.md
│   │   ├── tools.md
│   │   ├── troubleshooting.md
│   │   ├── use_cases.md
│   │   └── watching.md
│   ├── mkdocs.yml
│   └── site
│       ├── 404.html
│       ├── architecture
│       │   └── index.html
│       ├── assets
│       │   ├── images
│       │   │   └── favicon.png
│       │   ├── javascripts
│       │   │   ├── bundle.79ae519e.min.js
│       │   │   ├── bundle.79ae519e.min.js.map
│       │   │   ├── lunr
│       │   │   │   ├── min
│       │   │   │   │   ├── lunr.ar.min.js
│       │   │   │   │   ├── lunr.da.min.js
│       │   │   │   │   ├── lunr.de.min.js
│       │   │   │   │   ├── lunr.du.min.js
│       │   │   │   │   ├── lunr.el.min.js
│       │   │   │   │   ├── lunr.es.min.js
│       │   │   │   │   ├── lunr.fi.min.js
│       │   │   │   │   ├── lunr.fr.min.js
│       │   │   │   │   ├── lunr.he.min.js
│       │   │   │   │   ├── lunr.hi.min.js
│       │   │   │   │   ├── lunr.hu.min.js
│       │   │   │   │   ├── lunr.hy.min.js
│       │   │   │   │   ├── lunr.it.min.js
│       │   │   │   │   ├── lunr.ja.min.js
│       │   │   │   │   ├── lunr.jp.min.js
│       │   │   │   │   ├── lunr.kn.min.js
│       │   │   │   │   ├── lunr.ko.min.js
│       │   │   │   │   ├── lunr.multi.min.js
│       │   │   │   │   ├── lunr.nl.min.js
│       │   │   │   │   ├── lunr.no.min.js
│       │   │   │   │   ├── lunr.pt.min.js
│       │   │   │   │   ├── lunr.ro.min.js
│       │   │   │   │   ├── lunr.ru.min.js
│       │   │   │   │   ├── lunr.sa.min.js
│       │   │   │   │   ├── lunr.stemmer.support.min.js
│       │   │   │   │   ├── lunr.sv.min.js
│       │   │   │   │   ├── lunr.ta.min.js
│       │   │   │   │   ├── lunr.te.min.js
│       │   │   │   │   ├── lunr.th.min.js
│       │   │   │   │   ├── lunr.tr.min.js
│       │   │   │   │   ├── lunr.vi.min.js
│       │   │   │   │   └── lunr.zh.min.js
│       │   │   │   ├── tinyseg.js
│       │   │   │   └── wordcut.js
│       │   │   └── workers
│       │   │       ├── search.2c215733.min.js
│       │   │       └── search.2c215733.min.js.map
│       │   └── stylesheets
│       │       ├── main.484c7ddc.min.css
│       │       ├── main.484c7ddc.min.css.map
│       │       ├── palette.ab4e12ef.min.css
│       │       └── palette.ab4e12ef.min.css.map
│       ├── cli
│       │   └── index.html
│       ├── contributing
│       │   └── index.html
│       ├── contributing_languages
│       │   └── index.html
│       ├── cookbook
│       │   └── index.html
│       ├── core
│       │   └── index.html
│       ├── future_work
│       │   └── index.html
│       ├── images
│       │   ├── 1.png
│       │   ├── 11.png
│       │   ├── 12.png
│       │   ├── 13.png
│       │   ├── 14.png
│       │   ├── 16.png
│       │   ├── 19.png
│       │   ├── 2.png
│       │   ├── 20.png
│       │   ├── 21.png
│       │   ├── 22.png
│       │   ├── 23.png
│       │   ├── 24.png
│       │   ├── 26.png
│       │   ├── 28.png
│       │   ├── 29.png
│       │   ├── 3.png
│       │   ├── 30.png
│       │   ├── 31.png
│       │   ├── 32.png
│       │   ├── 33.png
│       │   ├── 34.png
│       │   ├── 35.png
│       │   ├── 36.png
│       │   ├── 38.png
│       │   ├── 39.png
│       │   ├── 4.png
│       │   ├── 40.png
│       │   ├── 41.png
│       │   ├── 42.png
│       │   ├── 43.png
│       │   ├── 44.png
│       │   ├── 5.png
│       │   ├── 6.png
│       │   ├── 7.png
│       │   ├── 8.png
│       │   ├── 9.png
│       │   ├── Indexing.gif
│       │   ├── tool_images
│       │   │   ├── 1.png
│       │   │   ├── 2.png
│       │   │   └── 3.png
│       │   └── Usecase.gif
│       ├── index.html
│       ├── installation
│       │   └── index.html
│       ├── license
│       │   └── index.html
│       ├── search
│       │   └── search_index.json
│       ├── server
│       │   └── index.html
│       ├── sitemap.xml
│       ├── sitemap.xml.gz
│       ├── tools
│       │   └── index.html
│       ├── troubleshooting
│       │   └── index.html
│       ├── use_cases
│       │   └── index.html
│       └── watching
│           └── index.html
├── funding.json
├── images
│   ├── 1.png
│   ├── 11.png
│   ├── 12.png
│   ├── 13.png
│   ├── 14.png
│   ├── 16.png
│   ├── 19.png
│   ├── 2.png
│   ├── 20.png
│   ├── 21.png
│   ├── 22.png
│   ├── 23.png
│   ├── 24.png
│   ├── 26.png
│   ├── 28.png
│   ├── 29.png
│   ├── 3.png
│   ├── 30.png
│   ├── 31.png
│   ├── 32.png
│   ├── 33.png
│   ├── 34.png
│   ├── 35.png
│   ├── 36.png
│   ├── 38.png
│   ├── 39.png
│   ├── 4.png
│   ├── 40.png
│   ├── 41.png
│   ├── 42.png
│   ├── 43.png
│   ├── 44.png
│   ├── 5.png
│   ├── 6.png
│   ├── 7.png
│   ├── 8.png
│   ├── 9.png
│   ├── Indexing.gif
│   ├── tool_images
│   │   ├── 1.png
│   │   ├── 2.png
│   │   └── 3.png
│   └── Usecase.gif
├── LICENSE
├── MANIFEST.in
├── organizer
│   ├── CONTRIBUTING_LANGUAGES.md
│   ├── cookbook.md
│   ├── docs.md
│   ├── language_specific_nodes.md
│   ├── Tools_Exploration.md
│   └── troubleshoot.md
├── pyproject.toml
├── README.md
├── scripts
│   ├── generate_lang_contributors.py
│   ├── post_install_fix.sh
│   ├── test_all_parsers.py
│   └── update_language_parsers.py
├── SECURITY.md
├── src
│   └── codegraphcontext
│       ├── __init__.py
│       ├── __main__.py
│       ├── cli
│       │   ├── __init__.py
│       │   ├── cli_helpers.py
│       │   ├── config_manager.py
│       │   ├── main.py
│       │   ├── setup_macos.py
│       │   └── setup_wizard.py
│       ├── core
│       │   ├── __init__.py
│       │   ├── database_falkordb.py
│       │   ├── database.py
│       │   ├── falkor_worker.py
│       │   ├── jobs.py
│       │   └── watcher.py
│       ├── prompts.py
│       ├── server.py
│       ├── tools
│       │   ├── __init__.py
│       │   ├── advanced_language_query_tool.py
│       │   ├── code_finder.py
│       │   ├── graph_builder.py
│       │   ├── languages
│       │   │   ├── c.py
│       │   │   ├── cpp.py
│       │   │   ├── csharp.py
│       │   │   ├── go.py
│       │   │   ├── java.py
│       │   │   ├── javascript.py
│       │   │   ├── kotlin.py
│       │   │   ├── php.py
│       │   │   ├── python.py
│       │   │   ├── ruby.py
│       │   │   ├── rust.py
│       │   │   ├── scala.py
│       │   │   ├── swift.py
│       │   │   ├── typescript.py
│       │   │   └── typescriptjsx.py
│       │   ├── package_resolver.py
│       │   ├── query_tool_languages
│       │   │   ├── c_toolkit.py
│       │   │   ├── cpp_toolkit.py
│       │   │   ├── csharp_toolkit.py
│       │   │   ├── go_toolkit.py
│       │   │   ├── java_toolkit.py
│       │   │   ├── javascript_toolkit.py
│       │   │   ├── python_toolkit.py
│       │   │   ├── ruby_toolkit.py
│       │   │   ├── rust_toolkit.py
│       │   │   ├── scala_toolkit.py
│       │   │   ├── swift_toolkit.py
│       │   │   └── typescript_toolkit.py
│       │   └── system.py
│       └── utils
│           ├── debug_log.py
│           └── tree_sitter_manager.py
├── tests
│   ├── __init__.py
│   ├── conftest.py
│   ├── sample_project
│   │   ├── advanced_calls.py
│   │   ├── advanced_classes.py
│   │   ├── advanced_classes2.py
│   │   ├── advanced_functions.py
│   │   ├── advanced_imports.py
│   │   ├── async_features.py
│   │   ├── callbacks_decorators.py
│   │   ├── circular1.py
│   │   ├── circular2.py
│   │   ├── class_instantiation.py
│   │   ├── cli_and_dunder.py
│   │   ├── complex_classes.py
│   │   ├── comprehensions_generators.py
│   │   ├── context_managers.py
│   │   ├── control_flow.py
│   │   ├── datatypes.py
│   │   ├── dynamic_dispatch.py
│   │   ├── dynamic_imports.py
│   │   ├── edge_cases
│   │   │   ├── comments_only.py
│   │   │   ├── docstring_only.py
│   │   │   ├── empty.py
│   │   │   ├── hardcoded_secrets.py
│   │   │   ├── long_functions.py
│   │   │   └── syntax_error.py
│   │   ├── function_chains.py
│   │   ├── generators.py
│   │   ├── import_reexports.py
│   │   ├── mapping_calls.py
│   │   ├── module_a.py
│   │   ├── module_b.py
│   │   ├── module_c
│   │   │   ├── __init__.py
│   │   │   ├── submodule1.py
│   │   │   └── submodule2.py
│   │   ├── namespace_pkg
│   │   │   └── ns_module.py
│   │   ├── pattern_matching.py
│   │   └── typing_examples.py
│   ├── sample_project_c
│   │   ├── cgc_sample
│   │   ├── include
│   │   │   ├── config.h
│   │   │   ├── math
│   │   │   │   └── vec.h
│   │   │   ├── module.h
│   │   │   ├── platform.h
│   │   │   └── util.h
│   │   ├── Makefile
│   │   ├── README.md
│   │   └── src
│   │       ├── main.c
│   │       ├── math
│   │       │   └── vec.c
│   │       ├── module.c
│   │       └── util.c
│   ├── sample_project_cpp
│   │   ├── class_features.cpp
│   │   ├── classes.cpp
│   │   ├── control_flow.cpp
│   │   ├── edge_cases.cpp
│   │   ├── enum_struct_union.cpp
│   │   ├── exceptions.cpp
│   │   ├── file_io.cpp
│   │   ├── function_chain.cpp
│   │   ├── function_chain.h
│   │   ├── function_types.cpp
│   │   ├── main.cpp
│   │   ├── main.exe
│   │   ├── namespaces.cpp
│   │   ├── raii_example.cpp
│   │   ├── README.md
│   │   ├── sample_project.exe
│   │   ├── stl_usage.cpp
│   │   ├── templates.cpp
│   │   └── types_variable_assignments.cpp
│   ├── sample_project_csharp
│   │   ├── README.md
│   │   └── src
│   │       └── Example.App
│   │           ├── Attributes
│   │           │   └── CustomAttributes.cs
│   │           ├── Example.App.csproj
│   │           ├── Models
│   │           │   ├── Person.cs
│   │           │   ├── Point.cs
│   │           │   ├── Role.cs
│   │           │   └── User.cs
│   │           ├── OuterClass.cs
│   │           ├── Program.cs
│   │           ├── Services
│   │           │   ├── GreetingService.cs
│   │           │   ├── IGreetingService.cs
│   │           │   └── LegacyService.cs
│   │           └── Utils
│   │               ├── CollectionHelper.cs
│   │               └── FileHelper.cs
│   ├── sample_project_go
│   │   ├── advanced_types.go
│   │   ├── basic_functions.go
│   │   ├── embedded_composition.go
│   │   ├── error_handling.go
│   │   ├── generics.go
│   │   ├── go.mod
│   │   ├── goroutines_channels.go
│   │   ├── interfaces.go
│   │   ├── packages_imports.go
│   │   ├── README.md
│   │   ├── structs_methods.go
│   │   └── util
│   │       └── helpers.go
│   ├── sample_project_java
│   │   ├── out
│   │   │   └── com
│   │   │       └── example
│   │   │           └── app
│   │   │               ├── annotations
│   │   │               │   └── Logged.class
│   │   │               ├── Main.class
│   │   │               ├── misc
│   │   │               │   ├── Outer.class
│   │   │               │   └── Outer$Inner.class
│   │   │               ├── model
│   │   │               │   ├── Role.class
│   │   │               │   └── User.class
│   │   │               ├── service
│   │   │               │   ├── AbstractGreeter.class
│   │   │               │   ├── GreetingService.class
│   │   │               │   └── impl
│   │   │               │       └── GreetingServiceImpl.class
│   │   │               └── util
│   │   │                   ├── CollectionUtils.class
│   │   │                   └── IOHelper.class
│   │   ├── README.md
│   │   ├── sources.txt
│   │   └── src
│   │       └── com
│   │           └── example
│   │               └── app
│   │                   ├── annotations
│   │                   │   └── Logged.java
│   │                   ├── Main.java
│   │                   ├── misc
│   │                   │   └── Outer.java
│   │                   ├── model
│   │                   │   ├── Role.java
│   │                   │   └── User.java
│   │                   ├── service
│   │                   │   ├── AbstractGreeter.java
│   │                   │   ├── GreetingService.java
│   │                   │   └── impl
│   │                   │       └── GreetingServiceImpl.java
│   │                   └── util
│   │                       ├── CollectionUtils.java
│   │                       └── IOHelper.java
│   ├── sample_project_javascript
│   │   ├── arrays.js
│   │   ├── asyncAwait.js
│   │   ├── classes.js
│   │   ├── dom.js
│   │   ├── errorHandling.js
│   │   ├── events.js
│   │   ├── exporter.js
│   │   ├── fetchAPI.js
│   │   ├── fixtures
│   │   │   └── js
│   │   │       └── accessors.js
│   │   ├── functions.js
│   │   ├── importer.js
│   │   ├── objects.js
│   │   ├── promises.js
│   │   ├── README.md
│   │   └── variables.js
│   ├── sample_project_kotlin
│   │   ├── AdvancedClasses.kt
│   │   ├── Annotations.kt
│   │   ├── Coroutines.kt
│   │   ├── EdgeCases.kt
│   │   ├── Functions.kt
│   │   ├── Main.kt
│   │   ├── Properties.kt
│   │   └── User.kt
│   ├── sample_project_misc
│   │   ├── index.html
│   │   ├── README.md
│   │   ├── styles.css
│   │   ├── tables.css
│   │   └── tables.html
│   ├── sample_project_php
│   │   ├── classes_objects.php
│   │   ├── database.php
│   │   ├── edgecases.php
│   │   ├── error_handling.php
│   │   ├── file_handling.php
│   │   ├── functions.php
│   │   ├── generators_iterators.php
│   │   ├── globals_superglobals.php
│   │   ├── Inheritance.php
│   │   ├── interface_traits.php
│   │   └── README.md
│   ├── sample_project_ruby
│   │   ├── class_example.rb
│   │   ├── enumerables.rb
│   │   ├── error_handling.rb
│   │   ├── file_io.rb
│   │   ├── inheritance_example.rb
│   │   ├── main.rb
│   │   ├── metaprogramming.rb
│   │   ├── mixins_example.rb
│   │   ├── module_example.rb
│   │   └── tests
│   │       ├── test_mixins.py
│   │       └── test_sample.rb
│   ├── sample_project_rust
│   │   ├── Cargo.toml
│   │   ├── README.md
│   │   └── src
│   │       ├── basic_functions.rs
│   │       ├── concurrency.rs
│   │       ├── error_handling.rs
│   │       ├── generics.rs
│   │       ├── iterators_closures.rs
│   │       ├── lib.rs
│   │       ├── lifetimes_references.rs
│   │       ├── modules.rs
│   │       ├── smart_pointers.rs
│   │       ├── structs_enums.rs
│   │       └── traits.rs
│   ├── sample_project_scala
│   │   ├── Animals.scala
│   │   ├── Complex.scala
│   │   ├── Functional.scala
│   │   ├── Geometry.scala
│   │   ├── Main.scala
│   │   ├── PackageObject.scala
│   │   ├── Script.sc
│   │   ├── Services.scala
│   │   ├── Shapes.scala
│   │   ├── Utils.scala
│   │   └── Variables.scala
│   ├── sample_project_swift
│   │   ├── Generics.swift
│   │   ├── Main.swift
│   │   ├── README.md
│   │   ├── Shapes.swift
│   │   ├── User.swift
│   │   └── Vehicles.swift
│   ├── sample_project_typescript
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── sample_tsx.tsx
│   │   ├── src
│   │   │   ├── advanced-types.ts
│   │   │   ├── async-promises.ts
│   │   │   ├── classes-inheritance.ts
│   │   │   ├── decorators-metadata.ts
│   │   │   ├── error-validation.ts
│   │   │   ├── functions-generics.ts
│   │   │   ├── index.ts
│   │   │   ├── modules-namespaces.ts
│   │   │   ├── types-interfaces.ts
│   │   │   └── utilities-helpers.ts
│   │   └── tsconfig.json
│   ├── test_cpp_parser.py
│   ├── test_database_validation.py
│   ├── test_end_to_end.py
│   ├── test_graph_indexing_js.py
│   ├── test_graph_indexing.py
│   ├── test_kotlin_parser.py
│   ├── test_swift_parser.py
│   ├── test_tree_sitter
│   │   ├── __init__.py
│   │   ├── class_instantiation.py
│   │   ├── complex_classes.py
│   │   └── test_file.py
│   ├── test_tree_sitter_manager.py
│   └── test_typescript_parser.py
├── visualize_graph.py
├── website
│   ├── .example.env
│   ├── .gitignore
│   ├── api
│   │   └── pypi.ts
│   ├── bun.lockb
│   ├── components.json
│   ├── eslint.config.js
│   ├── index.html
│   ├── package-lock.json
│   ├── package.json
│   ├── postcss.config.js
│   ├── public
│   │   ├── favicon.ico
│   │   ├── placeholder.svg
│   │   └── robots.txt
│   ├── README.md
│   ├── src
│   │   ├── App.css
│   │   ├── App.tsx
│   │   ├── assets
│   │   │   ├── function-calls.png
│   │   │   ├── graph-total.png
│   │   │   ├── hero-graph.jpg
│   │   │   └── hierarchy.png
│   │   ├── components
│   │   │   ├── ComparisonTable.tsx
│   │   │   ├── CookbookSection.tsx
│   │   │   ├── DemoSection.tsx
│   │   │   ├── ExamplesSection.tsx
│   │   │   ├── FeaturesSection.tsx
│   │   │   ├── Footer.tsx
│   │   │   ├── HeroSection.tsx
│   │   │   ├── InstallationSection.tsx
│   │   │   ├── MoveToTop.tsx
│   │   │   ├── ShowDownloads.tsx
│   │   │   ├── ShowStarGraph.tsx
│   │   │   ├── SocialMentionsTimeline.tsx
│   │   │   ├── TestimonialSection.tsx
│   │   │   ├── ThemeProvider.tsx
│   │   │   ├── ThemeToggle.tsx
│   │   │   └── ui
│   │   │       ├── accordion.tsx
│   │   │       ├── alert-dialog.tsx
│   │   │       ├── alert.tsx
│   │   │       ├── aspect-ratio.tsx
│   │   │       ├── avatar.tsx
│   │   │       ├── badge.tsx
│   │   │       ├── breadcrumb.tsx
│   │   │       ├── button.tsx
│   │   │       ├── calendar.tsx
│   │   │       ├── card.tsx
│   │   │       ├── carousel.tsx
│   │   │       ├── chart.tsx
│   │   │       ├── checkbox.tsx
│   │   │       ├── collapsible.tsx
│   │   │       ├── command.tsx
│   │   │       ├── context-menu.tsx
│   │   │       ├── dialog.tsx
│   │   │       ├── drawer.tsx
│   │   │       ├── dropdown-menu.tsx
│   │   │       ├── form.tsx
│   │   │       ├── hover-card.tsx
│   │   │       ├── input-otp.tsx
│   │   │       ├── input.tsx
│   │   │       ├── label.tsx
│   │   │       ├── menubar.tsx
│   │   │       ├── navigation-menu.tsx
│   │   │       ├── orbiting-circles.tsx
│   │   │       ├── pagination.tsx
│   │   │       ├── popover.tsx
│   │   │       ├── progress.tsx
│   │   │       ├── radio-group.tsx
│   │   │       ├── resizable.tsx
│   │   │       ├── scroll-area.tsx
│   │   │       ├── select.tsx
│   │   │       ├── separator.tsx
│   │   │       ├── sheet.tsx
│   │   │       ├── sidebar.tsx
│   │   │       ├── skeleton.tsx
│   │   │       ├── slider.tsx
│   │   │       ├── sonner.tsx
│   │   │       ├── switch.tsx
│   │   │       ├── table.tsx
│   │   │       ├── tabs.tsx
│   │   │       ├── textarea.tsx
│   │   │       ├── toast.tsx
│   │   │       ├── toaster.tsx
│   │   │       ├── toggle-group.tsx
│   │   │       ├── toggle.tsx
│   │   │       ├── tooltip.tsx
│   │   │       └── use-toast.ts
│   │   ├── hooks
│   │   │   ├── use-mobile.tsx
│   │   │   └── use-toast.ts
│   │   ├── index.css
│   │   ├── lib
│   │   │   └── utils.ts
│   │   ├── main.tsx
│   │   ├── pages
│   │   │   ├── Index.tsx
│   │   │   └── NotFound.tsx
│   │   └── vite-env.d.ts
│   ├── tailwind.config.ts
│   ├── tsconfig.app.json
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   ├── vercel.json
│   └── vite.config.ts
└── windows_setup_guide.md
```

# Files

--------------------------------------------------------------------------------
/tests/test_graph_indexing.py:
--------------------------------------------------------------------------------

```python
  1 | 
  2 | import pytest
  3 | import os
  4 | 
  5 | from .conftest import SAMPLE_PROJECT_PATH
  6 | 
  7 | # ==============================================================================
  8 | # == EXPECTED RELATIONSHIPS
  9 | # ==============================================================================
 10 | 
 11 | EXPECTED_STRUCTURE = [
 12 |     ("module_a.py", "foo", "Function"),
 13 |     ("module_a.py", "bar", "Function"),
 14 |     ("module_a.py", "outer", "Function"),
 15 |     ("module_b.py", "helper", "Function"),
 16 |     ("module_b.py", "process_data", "Function"),
 17 |     ("module_b.py", "factorial", "Function"),
 18 |     ("advanced_classes.py", "A", "Class"),
 19 |     ("advanced_classes.py", "B", "Class"),
 20 |     ("advanced_classes.py", "C", "Class"),
 21 |     ("module_a.py", "nested", "Function"),
 22 |     ("advanced_calls.py", "square", "Function"),
 23 |     ("advanced_calls.py", "calls", "Function"),
 24 |     ("advanced_calls.py", "Dummy", "Class"),
 25 |     ("advanced_classes2.py", "Base", "Class"),
 26 |     ("advanced_classes2.py", "Mid", "Class"),
 27 |     ("advanced_classes2.py", "Final", "Class"),
 28 |     ("advanced_classes2.py", "Mixin1", "Class"),
 29 |     ("advanced_classes2.py", "Mixin2", "Class"),
 30 |     ("advanced_classes2.py", "Combined", "Class"),
 31 |     ("advanced_classes2.py", "Point", "Class"),
 32 |     ("advanced_classes2.py", "Color", "Class"),
 33 |     ("advanced_classes2.py", "handle", "Function"),
 34 |     ("advanced_functions.py", "with_defaults", "Function"),
 35 |     ("advanced_functions.py", "with_args_kwargs", "Function"),
 36 |     ("advanced_functions.py", "higher_order", "Function"),
 37 |     ("advanced_functions.py", "return_function", "Function"),
 38 |     ("advanced_imports.py", "outer_import", "Function"),
 39 |     ("advanced_imports.py", "use_random", "Function"),
 40 |     ("async_features.py", "fetch_data", "Function"),
 41 |     ("async_features.py", "main", "Function"),
 42 |     ("callbacks_decorators.py", "executor", "Function"),
 43 |     ("callbacks_decorators.py", "square", "Function"),
 44 |     ("callbacks_decorators.py", "log_decorator", "Function"),
 45 |     ("callbacks_decorators.py", "hello", "Function"),
 46 |     ("class_instantiation.py", "A", "Class"),
 47 |     ("class_instantiation.py", "B", "Class"),
 48 |     ("class_instantiation.py", "Fluent", "Class"),
 49 |     ("function_chains.py", "f1", "Function"),
 50 |     ("function_chains.py", "f2", "Function"),
 51 |     ("function_chains.py", "f3", "Function"),
 52 |     ("function_chains.py", "make_adder", "Function"),
 53 |     ("generators.py", "gen_numbers", "Function"),
 54 |     ("generators.py", "agen_numbers", "Function"),
 55 |     ("generators.py", "async_with_example", "Function"),
 56 |     ("generators.py", "AsyncCM", "Class"),
 57 |     ("datatypes.py", "Point", "Class"),
 58 |     ("datatypes.py", "Color", "Class"),
 59 |     ("complex_classes.py", "Base", "Class"),
 60 |     ("complex_classes.py", "Child", "Class"),
 61 |     ("complex_classes.py", "decorator", "Function"),
 62 |     ("complex_classes.py", "decorated_function", "Function"),
 63 |     ("control_flow.py", "choose_path", "Function"),
 64 |     ("control_flow.py", "ternary", "Function"),
 65 |     ("control_flow.py", "try_except_finally", "Function"),
 66 |     ("control_flow.py", "conditional_inner_import", "Function"),
 67 |     ("control_flow.py", "env_based_import", "Function"),
 68 |     ("circular1.py", "func1", "Function"),
 69 |     ("circular2.py", "func2", "Function"),
 70 |     ("cli_and_dunder.py", "run", "Function"),
 71 |     ("comprehensions_generators.py", "double", "Function"),
 72 |     ("context_managers.py", "FileOpener", "Class"),
 73 |     ("context_managers.py", "use_file", "Function"),
 74 |     ("dynamic_dispatch.py", "add", "Function"),
 75 |     ("dynamic_dispatch.py", "sub", "Function"),
 76 |     ("dynamic_dispatch.py", "mul", "Function"),
 77 |     ("dynamic_dispatch.py", "dispatch_by_key", "Function"),
 78 |     ("dynamic_dispatch.py", "dispatch_by_string", "Function"),
 79 |     ("dynamic_dispatch.py", "partial_example", "Function"),
 80 |     ("dynamic_dispatch.py", "C", "Class"),
 81 |     ("dynamic_dispatch.py", "methodcaller_example", "Function"),
 82 |     ("dynamic_dispatch.py", "dynamic_import_call", "Function"),
 83 |     ("dynamic_imports.py", "import_optional", "Function"),
 84 |     ("dynamic_imports.py", "import_by___import__", "Function"),
 85 |     ("dynamic_imports.py", "importlib_runtime", "Function"),
 86 |     ("import_reexports.py", "core_function", "Function"),
 87 |     ("import_reexports.py", "reexport", "Function"),
 88 |     ("mapping_calls.py", "Dispatcher", "Class"),
 89 |     ("mapping_calls.py", "use_dispatcher", "Function"),
 90 |     ("module_c/submodule1.py", "call_helper_twice", "Function"),
 91 |     ("module_c/submodule2.py", "wrapper", "Function"),
 92 |     ("namespace_pkg/ns_module.py", "ns_func", "Function"),
 93 |     ("pattern_matching.py", "matcher", "Function"),
 94 |     ("typing_examples.py", "typed_func", "Function"),
 95 |     ("typing_examples.py", "union_func", "Function"),
 96 |     ("typing_examples.py", "dict_func", "Function"),
 97 | ]
 98 | 
 99 | EXPECTED_INHERITANCE = [
100 |     pytest.param("C", "advanced_classes.py", "A", "advanced_classes.py", id="C inherits from A"),
101 |     pytest.param("C", "advanced_classes.py", "B", "advanced_classes.py", id="C inherits from B"),
102 |     pytest.param("ConcreteThing", "advanced_classes.py", "AbstractThing", "advanced_classes.py", id="ConcreteThing inherits from AbstractThing"),
103 |     pytest.param("Mid", "advanced_classes2.py", "Base", "advanced_classes2.py", id="Mid inherits from Base"),
104 |     pytest.param("Final", "advanced_classes2.py", "Mid", "advanced_classes2.py", id="Final inherits from Mid"),
105 |     pytest.param("Combined", "advanced_classes2.py", "Mixin1", "advanced_classes2.py", id="Combined inherits from Mixin1"),
106 |     pytest.param("Combined", "advanced_classes2.py", "Mixin2", "advanced_classes2.py", id="Combined inherits from Mixin2"),
107 |     pytest.param("B", "class_instantiation.py", "A", "class_instantiation.py", id="B inherits from A"),
108 |     pytest.param("B", "class_instantiation.py", "A", "class_instantiation.py", marks=pytest.mark.skip(reason="Indexer does not support inheritance via super() calls"), id="B inherits from A via super()"),
109 |     pytest.param("Child", "complex_classes.py", "Base", "complex_classes.py", id="Child inherits from Base"),
110 | ]
111 | 
112 | EXPECTED_CALLS = [
113 |     pytest.param("foo", "module_a.py", None, "helper", "module_b.py", None, id="module_a.foo->module_b.helper"),
114 |     pytest.param("foo", "module_a.py", None, "process_data", "module_b.py", None, id="module_a.foo->module_b.process_data"),
115 |     pytest.param("factorial", "module_b.py", None, "factorial", "module_b.py", None, id="module_b.factorial->recursive"),
116 |     pytest.param("calls", "advanced_calls.py", None, "square", "advanced_calls.py", None, id="advanced_calls.calls->square"),
117 |     pytest.param("call_helper_twice", "module_c/submodule1.py", None, "helper", "module_b.py", None, id="submodule1.call_helper_twice->module_b.helper"),
118 |     pytest.param("wrapper", "module_c/submodule2.py", None, "call_helper_twice", "module_c/submodule1.py", None, id="submodule2.wrapper->submodule1.call_helper_twice"),
119 |     pytest.param("main", "async_features.py", None, "fetch_data", "async_features.py", None, id="async.main->fetch_data"),
120 |     pytest.param("func1", "circular1.py", None, "func2", "circular2.py", None, id="circular1.func1->circular2.func2"),
121 |     pytest.param("run", "cli_and_dunder.py", None, "with_defaults", "advanced_functions.py", None, id="cli.run->with_defaults"),
122 |     pytest.param("use_dispatcher", "mapping_calls.py", None, "call", "mapping_calls.py", None, id="mapping.use_dispatcher->call"),
123 |     pytest.param("calls", "advanced_calls.py", None, "method", "advanced_calls.py", "Dummy", marks=pytest.mark.skip(reason="Dynamic call with getattr is not supported"), id="advanced_calls.calls->Dummy.method"),
124 |     pytest.param("both", "advanced_classes2.py", "Combined", "m1", "advanced_classes2.py", "Mixin1", id="advanced_classes2.both->m1"),
125 |     pytest.param("both", "advanced_classes2.py", "Combined", "m2", "advanced_classes2.py", "Mixin2", id="advanced_classes2.both->m2"),
126 |     pytest.param("executor", "callbacks_decorators.py", None, "square", "callbacks_decorators.py", None, marks=pytest.mark.skip(reason="Dynamic call passing function as argument is not supported"), id="callbacks.executor->square"),
127 |     pytest.param("reexport", "import_reexports.py", None, "core_function", "import_reexports.py", None, id="reexport->core_function"),
128 |     pytest.param("greet", "class_instantiation.py", "B", "greet", "class_instantiation.py", "A", marks=pytest.mark.skip(reason="super() calls are not supported yet"), id="B.greet->A.greet"),
129 |     pytest.param("greet", "complex_classes.py", "Child", "greet", "complex_classes.py", "Base", marks=pytest.mark.skip(reason="super() calls are not supported yet"), id="Child.greet->Base.greet"),
130 |     pytest.param("class_method", "complex_classes.py", "Child", "greet", "complex_classes.py", "Child", id="Child.class_method->Child.greet"),
131 |     pytest.param("use_file", "context_managers.py", None, "__enter__", "context_managers.py", "FileOpener", marks=pytest.mark.skip(reason="Implicit context manager calls not supported"), id="use_file->FileOpener.__enter__"),
132 |     pytest.param("partial_example", "dynamic_dispatch.py", None, "add", "dynamic_dispatch.py", None, marks=pytest.mark.skip(reason="Calls via functools.partial not supported yet"), id="partial_example->add"),
133 |     pytest.param("async_with_example", "generators.py", None, "__aenter__", "generators.py", "AsyncCM", marks=pytest.mark.skip(reason="Implicit async context manager calls not supported"), id="async_with_example->AsyncCM.__aenter__"),
134 |     pytest.param("call", "mapping_calls.py", "Dispatcher", "start", "mapping_calls.py", "Dispatcher", marks=pytest.mark.skip(reason="Dynamic call via dict lookup not supported"), id="Dispatcher.call->start"),
135 |     pytest.param("greet", "class_instantiation.py", None, "greet", "class_instantiation.py", "A", marks=pytest.mark.skip(reason="Indexer does not capture calls to methods of instantiated objects within the same file"), id="A.greet called"),
136 |     pytest.param("greet", "class_instantiation.py", None, "greet", "class_instantiation.py", "B", marks=pytest.mark.skip(reason="Indexer does not capture calls to methods of instantiated objects within the same file"), id="B.greet called"),
137 |     pytest.param("step1", "class_instantiation.py", "Fluent", "step1", "class_instantiation.py", "Fluent", marks=pytest.mark.skip(reason="Indexer does not capture method chaining calls"), id="Fluent.step1 called"),
138 |     pytest.param("step2", "class_instantiation.py", "Fluent", "step2", "class_instantiation.py", "Fluent", marks=pytest.mark.skip(reason="Indexer does not capture method chaining calls"), id="Fluent.step2 called"),
139 |     pytest.param("step3", "class_instantiation.py", "Fluent", "step3", "class_instantiation.py", "Fluent", marks=pytest.mark.skip(reason="Indexer does not capture method chaining calls"), id="Fluent.step3 called"),
140 |     pytest.param("dynamic", "class_instantiation.py", "B", "lambda", "class_instantiation.py", None, marks=pytest.mark.skip(reason="Dynamic attribute assignment and lambda calls not supported"), id="B.dynamic called"),
141 |     pytest.param("add_argument", "cli_and_dunder.py", None, "add_argument", None, None, marks=pytest.mark.skip(reason="Calls to external library methods not fully supported"), id="ArgumentParser.add_argument called"),
142 |     pytest.param("parse_args", "cli_and_dunder.py", None, "parse_args", None, None, marks=pytest.mark.skip(reason="Calls to external library methods not fully supported"), id="ArgumentParser.parse_args called"),
143 |     pytest.param("print", "cli_and_dunder.py", None, "print", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="print called"),
144 |     pytest.param("double", "comprehensions_generators.py", None, "double", "comprehensions_generators.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls within comprehensions/generators"), id="double called in list comprehension"),
145 |     pytest.param("range", "comprehensions_generators.py", None, "range", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="range called in list comprehension"),
146 |     pytest.param("double", "comprehensions_generators.py", None, "double", "comprehensions_generators.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls within comprehensions/generators"), id="double called in generator expression"),
147 |     pytest.param("range", "comprehensions_generators.py", None, "range", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="range called in generator expression"),
148 |     pytest.param("list", "comprehensions_generators.py", None, "list", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="list called"),
149 |     pytest.param("sorted", "comprehensions_generators.py", None, "sorted", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="sorted called"),
150 |     pytest.param("len", "comprehensions_generators.py", None, "len", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="len called"),
151 |     pytest.param("open", "comprehensions_generators.py", None, "open", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="open called for write"),
152 |     pytest.param("write", "comprehensions_generators.py", None, "write", None, None, marks=pytest.mark.skip(reason="Method calls on built-in types not explicitly indexed"), id="write called"),
153 |     pytest.param("open", "comprehensions_generators.py", None, "open", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="open called for read"),
154 |     pytest.param("read", "comprehensions_generators.py", None, "read", None, None, marks=pytest.mark.skip(reason="Method calls on built-in types not explicitly indexed"), id="read called"),
155 |     pytest.param("ValueError", "control_flow.py", None, "ValueError", None, None, marks=pytest.mark.skip(reason="Built-in exception constructors not explicitly indexed"), id="ValueError called"),
156 |     pytest.param("str", "control_flow.py", None, "str", None, None, marks=pytest.mark.skip(reason="Built-in type constructors not explicitly indexed"), id="str called"),
157 |     pytest.param("getenv", "control_flow.py", None, "getenv", None, None, marks=pytest.mark.skip(reason="Calls to external library methods not fully supported"), id="os.getenv called"),
158 |     pytest.param("dumps", "control_flow.py", None, "dumps", None, None, marks=pytest.mark.skip(reason="Calls to external library methods not fully supported"), id="json.dumps called"),
159 |     pytest.param("namedtuple", "datatypes.py", None, "namedtuple", None, None, marks=pytest.mark.skip(reason="Calls to external library functions not fully supported"), id="namedtuple called"),
160 |     pytest.param("DISPATCH", "dynamic_dispatch.py", None, "add", "dynamic_dispatch.py", None, marks=pytest.mark.skip(reason="Dynamic dispatch via dictionary lookup not supported"), id="dispatch_by_key calls add dynamically"),
161 |     pytest.param("DISPATCH", "dynamic_dispatch.py", None, "sub", "dynamic_dispatch.py", None, marks=pytest.mark.skip(reason="Dynamic dispatch via dictionary lookup not supported"), id="dispatch_by_key calls sub dynamically"),
162 |     pytest.param("DISPATCH", "dynamic_dispatch.py", None, "mul", "dynamic_dispatch.py", None, marks=pytest.mark.skip(reason="Dynamic dispatch via dictionary lookup not supported"), id="dispatch_by_key calls mul dynamically"),
163 |     pytest.param("get", "dynamic_dispatch.py", None, "get", None, None, marks=pytest.mark.skip(reason="Calls to built-in dictionary methods not explicitly indexed"), id="globals().get called"),
164 |     pytest.param("callable", "dynamic_dispatch.py", None, "callable", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="callable called"),
165 |     pytest.param("partial", "dynamic_dispatch.py", None, "partial", None, None, marks=pytest.mark.skip(reason="Calls to external library functions not fully supported"), id="partial called"),
166 |     pytest.param("methodcaller", "dynamic_dispatch.py", None, "methodcaller", None, None, marks=pytest.mark.skip(reason="Calls to external library functions not fully supported"), id="methodcaller called"),
167 |     pytest.param("method", "dynamic_dispatch.py", "C", "method", "dynamic_dispatch.py", "C", marks=pytest.mark.skip(reason="Dynamic call via operator.methodcaller not supported"), id="C.method called via methodcaller"),
168 |     pytest.param("import_module", "dynamic_dispatch.py", None, "import_module", None, None, marks=pytest.mark.skip(reason="Calls to external library functions not fully supported"), id="importlib.import_module called"),
169 |     pytest.param("getattr", "dynamic_dispatch.py", None, "getattr", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="getattr called"),
170 |     pytest.param("dumps", "dynamic_imports.py", None, "dumps", None, None, marks=pytest.mark.skip(reason="Calls to external library methods not fully supported"), id="json.dumps called in import_optional"),
171 |     pytest.param("__import__", "dynamic_imports.py", None, "__import__", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="__import__ called"),
172 |     pytest.param("getattr", "dynamic_imports.py", None, "getattr", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="getattr called in import_by___import__"),
173 |     pytest.param("import_module", "dynamic_imports.py", None, "import_module", None, None, marks=pytest.mark.skip(reason="Calls to external library functions not fully supported"), id="importlib.import_module called in importlib_runtime"),
174 |     pytest.param("getattr", "dynamic_imports.py", None, "getattr", None, None, marks=pytest.mark.skip(reason="Built-in function calls not explicitly indexed"), id="getattr called in importlib_runtime"),
175 |     pytest.param("f3", "function_chains.py", None, "f3", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture chained function calls"), id="f3 called"),
176 |     pytest.param("f2", "function_chains.py", None, "f2", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture chained function calls"), id="f2 called"),
177 |     pytest.param("f1", "function_chains.py", None, "f1", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture chained function calls"), id="f1 called"),
178 |     pytest.param("strip", "function_chains.py", None, "strip", None, None, marks=pytest.mark.skip(reason="Method calls on built-in types not explicitly indexed"), id="strip called"),
179 |     pytest.param("lower", "function_chains.py", None, "lower", None, None, marks=pytest.mark.skip(reason="Method calls on built-in types not explicitly indexed"), id="lower called"),
180 |     pytest.param("replace", "function_chains.py", None, "replace", None, None, marks=pytest.mark.skip(reason="Method calls on built-in types not explicitly indexed"), id="replace called"),
181 |     pytest.param("make_adder", "function_chains.py", None, "make_adder", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls to functions that return functions"), id="make_adder called"),
182 |     pytest.param("adder", "function_chains.py", None, "adder", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls to inner functions returned by other functions"), id="adder called"),
183 |     pytest.param("make_adder", "function_chains.py", None, "make_adder", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls to functions that return functions in a chain"), id="make_adder called in chain"),
184 |     pytest.param("adder", "function_chains.py", None, "adder", "function_chains.py", None, marks=pytest.mark.skip(reason="Indexer does not capture calls to inner functions returned by other functions in a chain"), id="adder called in chain"),
185 |     pytest.param("sqrt", "import_reexports.py", None, "sqrt", None, None, marks=pytest.mark.skip(reason="Calls to aliased imported functions not fully supported"), id="m.sqrt called"),
186 |     pytest.param("Dispatcher", "mapping_calls.py", None, "Dispatcher", "mapping_calls.py", None, marks=pytest.mark.skip(reason="Indexer does not capture class constructor calls"), id="Dispatcher constructor called"),
187 |     pytest.param("str", "pattern_matching.py", None, "str", None, None, marks=pytest.mark.skip(reason="Built-in type constructors not explicitly indexed"), id="str called in pattern matching"),
188 | ]
189 | 
190 | EXPECTED_IMPORTS = [
191 |     pytest.param("module_a.py", "math", id="module_a imports math"),
192 |     pytest.param("module_a.py", "module_b", id="module_a imports module_b"),
193 |     pytest.param("advanced_imports.py", "math", id="advanced_imports imports math"),
194 |     pytest.param("advanced_imports.py", "random", id="advanced_imports imports random"),
195 |     pytest.param("advanced_imports.py", "sys", id="advanced_imports imports sys"),
196 |     pytest.param("async_features.py", "asyncio", id="async_features imports asyncio"),
197 |     pytest.param("circular1.py", "func2", id="circular1 imports func2"),
198 |     pytest.param("circular2.py", "func1", id="circular2 imports func1"),
199 |     pytest.param("cli_and_dunder.py", "argparse", id="cli_and_dunder imports argparse"),
200 |     pytest.param("cli_and_dunder.py", "advanced_functions", id="cli_and_dunder imports advanced_functions"),
201 |     pytest.param("control_flow.py", "os", id="control_flow imports os"),
202 |     pytest.param("datatypes.py", "dataclass", id="datatypes imports dataclass"),
203 |     pytest.param("datatypes.py", "enum", id="datatypes imports enum"),
204 |     pytest.param("datatypes.py", "namedtuple", id="datatypes imports namedtuple"),
205 |     pytest.param("dynamic_dispatch.py", "partial", id="dynamic_dispatch imports partial"),
206 |     pytest.param("dynamic_dispatch.py", "operator", id="dynamic_dispatch imports operator"),
207 |     pytest.param("dynamic_dispatch.py", "importlib", id="dynamic_dispatch imports importlib"),
208 |     pytest.param("dynamic_imports.py", "importlib", id="dynamic_imports imports importlib"),
209 |     pytest.param("import_reexports.py", "math", marks=pytest.mark.skip(reason="Indexer does not support aliased imports (e.g., 'import math as m')"), id="import_reexports imports math"),
210 |     pytest.param("module_c/submodule1.py", "helper", id="submodule1 imports helper"),
211 |     pytest.param("module_c/submodule2.py", "call_helper_twice", id="submodule2 imports call_helper_twice"),
212 |     pytest.param("typing_examples.py", "List", id="typing_examples imports List"),
213 |     pytest.param("typing_examples.py", "Dict", marks=pytest.mark.skip(reason="Indexer does not capture imports after ','"), id="typing_examples imports Dict"),
214 |     pytest.param("typing_examples.py", "Union", marks=pytest.mark.skip(reason="Indexer does not capture imports after ','"), id="typing_examples imports Union"),
215 |     pytest.param("control_flow.py", "numpy", marks=pytest.mark.skip(reason="Indexer does not capture conditional imports"), id="control_flow imports numpy (conditional)"),
216 |     pytest.param("control_flow.py", "ujson", marks=pytest.mark.skip(reason="Indexer does not capture conditional imports"), id="control_flow imports ujson (conditional)"),
217 |     pytest.param("control_flow.py", "json", id="control_flow imports json (conditional)"),
218 |     pytest.param("dynamic_imports.py", "ujson", marks=pytest.mark.skip(reason="Indexer does not capture conditional imports"), id="dynamic_imports imports ujson (conditional)"),
219 |     pytest.param("dynamic_imports.py", "json", id="dynamic_imports imports json (conditional)"),
220 | ]
221 | 
222 | EXPECTED_PARAMETERS = [
223 |     pytest.param("foo", "module_a.py", "x", id="foo has parameter x"),
224 |     pytest.param("helper", "module_b.py", "x", id="helper has parameter x"),
225 |     pytest.param("process_data", "module_b.py", "data", id="process_data has parameter data"),
226 |     pytest.param("factorial", "module_b.py", "n", id="factorial has parameter n"),
227 |     pytest.param("square", "advanced_calls.py", "x", id="square has parameter x"),
228 |     pytest.param("method", "advanced_calls.py", "x", id="Dummy.method has parameter x"),
229 |     pytest.param("with_defaults", "advanced_functions.py", "a", id="with_defaults has parameter a"),
230 |     pytest.param("with_defaults", "advanced_functions.py", "b", id="with_defaults has parameter b"),
231 |     pytest.param("with_defaults", "advanced_functions.py", "c", id="with_defaults has parameter c"),
232 |     pytest.param("higher_order", "advanced_functions.py", "func", id="higher_order has parameter func"),
233 |     pytest.param("higher_order", "advanced_functions.py", "data", id="higher_order has parameter data"),
234 |     pytest.param("return_function", "advanced_functions.py", "x", id="return_function has parameter x"),
235 |     pytest.param("executor", "callbacks_decorators.py", "func", id="executor has parameter func"),
236 |     pytest.param("executor", "callbacks_decorators.py", "val", id="executor has parameter val"),
237 |     pytest.param("square", "callbacks_decorators.py", "x", id="square has parameter x"),
238 |     pytest.param("log_decorator", "callbacks_decorators.py", "fn", id="log_decorator has parameter fn"),
239 |     pytest.param("hello", "callbacks_decorators.py", "name", id="hello has parameter name"),
240 |     pytest.param("greet", "class_instantiation.py", "self", id="A.greet has parameter self"),
241 |     pytest.param("greet", "class_instantiation.py", "self", id="B.greet has parameter self"),
242 |     pytest.param("step1", "class_instantiation.py", "self", id="Fluent.step1 has parameter self"),
243 |     pytest.param("step2", "class_instantiation.py", "self", id="Fluent.step2 has parameter self"),
244 |     pytest.param("step3", "class_instantiation.py", "self", id="Fluent.step3 has parameter self"),
245 |     pytest.param("run", "cli_and_dunder.py", "argv", id="run has parameter argv"),
246 |     pytest.param("greet", "complex_classes.py", "self", id="Base.greet has self"),
247 |     pytest.param("greet", "complex_classes.py", "self", id="Child.greet has self"),
248 |     pytest.param("static_method", "complex_classes.py", "x", id="Child.static_method has x"),
249 |     pytest.param("class_method", "complex_classes.py", "cls", id="Child.class_method has cls"),
250 |     pytest.param("class_method", "complex_classes.py", "y", id="Child.class_method has y"),
251 |     pytest.param("decorator", "complex_classes.py", "func", id="decorator has func"),
252 |     pytest.param("decorated_function", "complex_classes.py", "x", id="decorated_function has x"),
253 |     pytest.param("double", "comprehensions_generators.py", "x", id="double has parameter x"),
254 |     pytest.param("__enter__", "context_managers.py", "self", id="FileOpener.__enter__ has self"),
255 |     pytest.param("__exit__", "context_managers.py", "self", id="FileOpener.__exit__ has self"),
256 |     pytest.param("__exit__", "context_managers.py", "exc_type", id="FileOpener.__exit__ has exc_type"),
257 |     pytest.param("__exit__", "context_managers.py", "exc_val", id="FileOpener.__exit__ has exc_val"),
258 |     pytest.param("__exit__", "context_managers.py", "exc_tb", id="FileOpener.__exit__ has exc_tb"),
259 |     pytest.param("choose_path", "control_flow.py", "x", id="choose_path has parameter x"),
260 |     pytest.param("ternary", "control_flow.py", "x", id="ternary has parameter x"),
261 |     pytest.param("try_except_finally", "control_flow.py", "x", id="try_except_finally has parameter x"),
262 |     pytest.param("conditional_inner_import", "control_flow.py", "use_numpy", id="conditional_inner_import has use_numpy"),
263 |     pytest.param("add", "dynamic_dispatch.py", "a", id="add has a"),
264 |     pytest.param("add", "dynamic_dispatch.py", "b", id="add has b"),
265 |     pytest.param("sub", "dynamic_dispatch.py", "a", id="sub has a"),
266 |     pytest.param("sub", "dynamic_dispatch.py", "b", id="sub has b"),
267 |     pytest.param("mul", "dynamic_dispatch.py", "a", id="mul has a"),
268 |     pytest.param("mul", "dynamic_dispatch.py", "b", id="mul has b"),
269 |     pytest.param("dispatch_by_key", "dynamic_dispatch.py", "name", id="dispatch_by_key has name"),
270 |     pytest.param("dispatch_by_key", "dynamic_dispatch.py", "a", id="dispatch_by_key has a"),
271 |     pytest.param("dispatch_by_key", "dynamic_dispatch.py", "b", id="dispatch_by_key has b"),
272 |     pytest.param("method", "dynamic_dispatch.py", "x", id="C.method has x"),
273 |     pytest.param("methodcaller_example", "dynamic_dispatch.py", "x", id="methodcaller_example has x"),
274 |     pytest.param("import_by___import__", "dynamic_imports.py", "name", id="import_by___import__ has name"),
275 |     pytest.param("importlib_runtime", "dynamic_imports.py", "name", id="importlib_runtime has name"),
276 |     pytest.param("importlib_runtime", "dynamic_imports.py", "attr", id="importlib_runtime has attr"),
277 |     pytest.param("f1", "function_chains.py", "x", id="f1 has x"),
278 |     pytest.param("f2", "function_chains.py", "x", id="f2 has x"),
279 |     pytest.param("f3", "function_chains.py", "x", id="f3 has x"),
280 |     pytest.param("make_adder", "function_chains.py", "n", id="make_adder has n"),
281 |     pytest.param("gen_numbers", "generators.py", "n", id="gen_numbers has n"),
282 |     pytest.param("agen_numbers", "generators.py", "n", id="agen_numbers has n"),
283 |     pytest.param("call", "mapping_calls.py", "cmd", id="Dispatcher.call has cmd"),
284 |     pytest.param("use_dispatcher", "mapping_calls.py", "cmd", id="use_dispatcher has cmd"),
285 |     pytest.param("call_helper_twice", "module_c/submodule1.py", "x", id="call_helper_twice has x"),
286 |     pytest.param("wrapper", "module_c/submodule2.py", "x", id="wrapper has x"),
287 |     pytest.param("matcher", "pattern_matching.py", "x", id="matcher has x"),
288 |     pytest.param("typed_func", "typing_examples.py", "a", marks=pytest.mark.skip(reason="Indexer does not support parameters with type hints"), id="typed_func has a"),
289 |     pytest.param("typed_func", "typing_examples.py", "b", marks=pytest.mark.skip(reason="Indexer does not support parameters with type hints"), id="typed_func has b"),
290 |     pytest.param("union_func", "typing_examples.py", "x", marks=pytest.mark.skip(reason="Indexer does not support parameters with type hints"), id="union_func has x"),
291 |     pytest.param("dict_func", "typing_examples.py", "d", marks=pytest.mark.skip(reason="Indexer does not support parameters with type hints"), id="dict_func has d"),
292 |     pytest.param("wrapper", "complex_classes.py", "*args", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (*args)"), id="wrapper has *args"),
293 |     pytest.param("wrapper", "complex_classes.py", "**kwargs", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (**kwargs)"), id="wrapper has **kwargs"),
294 |     pytest.param("dispatch_by_string", "dynamic_dispatch.py", "*args", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (*args)"), id="dispatch_by_string has *args"),
295 |     pytest.param("dispatch_by_string", "dynamic_dispatch.py", "**kwargs", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (**kwargs)"), id="dispatch_by_string has **kwargs"),
296 |     pytest.param("dynamic_import_call", "dynamic_dispatch.py", "*args", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (*args)"), id="dynamic_import_call has *args"),
297 |     pytest.param("dynamic_import_call", "dynamic_dispatch.py", "**kwargs", marks=pytest.mark.skip(reason="Indexer does not capture variadic parameters (**kwargs)"), id="dynamic_import_call has **kwargs"),
298 |     pytest.param("adder", "function_chains.py", "x", id="adder has x"),
299 |     pytest.param("__aenter__", "generators.py", "self", id="AsyncCM.__aenter__ has self"),
300 |     pytest.param("__aexit__", "generators.py", "self", id="AsyncCM.__aexit__ has self"),
301 |     pytest.param("__aexit__", "generators.py", "exc_type", id="AsyncCM.__aexit__ has exc_type"),
302 |     pytest.param("__aexit__", "generators.py", "exc_val", id="AsyncCM.__aexit__ has exc_val"),
303 |     pytest.param("__aexit__", "generators.py", "exc_tb", id="AsyncCM.__aexit__ has exc_tb"),
304 |     pytest.param("__init__", "mapping_calls.py", "self", id="Dispatcher.__init__ has self"),
305 |     pytest.param("start", "mapping_calls.py", "self", id="Dispatcher.start has self"),
306 |     pytest.param("stop", "mapping_calls.py", "self", id="Dispatcher.stop has self"),
307 |     pytest.param("ns_func", "namespace_pkg/ns_module.py", None, marks=pytest.mark.skip(reason="Functions with no parameters are not explicitly tested for parameter existence"), id="ns_func has no parameters"),
308 | ]
309 | 
310 | EXPECTED_CLASS_METHODS = [
311 |     pytest.param("A", "advanced_classes.py", "foo", id="A contains foo"),
312 |     pytest.param("B", "advanced_classes.py", "foo", id="B contains foo"),
313 |     pytest.param("C", "advanced_classes.py", "bar", id="C contains bar"),
314 |     pytest.param("AbstractThing", "advanced_classes.py", "do", id="AbstractThing contains do"),
315 |     pytest.param("ConcreteThing", "advanced_classes.py", "do", id="ConcreteThing contains do"),
316 |     pytest.param("Dummy", "advanced_calls.py", "method", id="Dummy contains method"),
317 |     pytest.param("Mixin1", "advanced_classes2.py", "m1", id="Mixin1 contains m1"),
318 |     pytest.param("Mixin2", "advanced_classes2.py", "m2", id="Mixin2 contains m2"),
319 |     pytest.param("Combined", "advanced_classes2.py", "both", id="Combined contains both"),
320 |     pytest.param("Point", "advanced_classes2.py", "magnitude", id="Point contains magnitude"),
321 |     pytest.param("Color", "advanced_classes2.py", "is_primary", id="Color contains is_primary"),
322 |     pytest.param("A", "class_instantiation.py", "greet", id="A contains greet"),
323 |     pytest.param("B", "class_instantiation.py", "greet", id="B contains greet"),
324 |     pytest.param("Fluent", "class_instantiation.py", "step1", id="Fluent contains step1"),
325 |     pytest.param("Fluent", "class_instantiation.py", "step2", id="Fluent contains step2"),
326 |     pytest.param("Fluent", "class_instantiation.py", "step3", id="Fluent contains step3"),
327 |     pytest.param("AsyncCM", "generators.py", "__aenter__", id="AsyncCM contains __aenter__"),
328 |     pytest.param("AsyncCM", "generators.py", "__aexit__", id="AsyncCM contains __aexit__"),
329 |     pytest.param("Base", "complex_classes.py", "greet", id="Base contains greet"),
330 |     pytest.param("Child", "complex_classes.py", "greet", id="Child contains greet"),
331 |     pytest.param("Child", "complex_classes.py", "static_method", id="Child contains static_method"),
332 |     pytest.param("Child", "complex_classes.py", "class_method", id="Child contains class_method"),
333 |     pytest.param("FileOpener", "context_managers.py", "__enter__", id="FileOpener contains __enter__"),
334 |     pytest.param("FileOpener", "context_managers.py", "__exit__", id="FileOpener contains __exit__"),
335 |     pytest.param("C", "dynamic_dispatch.py", "method", id="C contains method"),
336 |     pytest.param("Dispatcher", "mapping_calls.py", "__init__", id="Dispatcher contains __init__"),
337 |     pytest.param("Dispatcher", "mapping_calls.py", "start", id="Dispatcher contains start"),
338 |     pytest.param("Dispatcher", "mapping_calls.py", "stop", id="Dispatcher contains stop"),
339 |     pytest.param("Dispatcher", "mapping_calls.py", "call", id="Dispatcher contains call"),
340 | ]
341 | 
342 | EXPECTED_FUNCTION_CONTAINS = [
343 |     pytest.param("return_function", "advanced_functions.py", "inner", id="return_function contains inner"),
344 |     pytest.param("log_decorator", "callbacks_decorators.py", "wrapper", id="log_decorator contains wrapper"),
345 |     pytest.param("make_adder", "function_chains.py", "adder", id="make_adder contains adder"),
346 |     pytest.param("decorator", "complex_classes.py", "wrapper", id="decorator contains wrapper"),
347 | ]
348 | 
349 | EXPECTED_DECORATORS = [
350 |     pytest.param("decorated_function", "complex_classes.py", "decorator", "complex_classes.py", id="decorated_function decorated by decorator"),
351 |     pytest.param("hello", "callbacks_decorators.py", "log_decorator", "callbacks_decorators.py", id="hello decorated by log_decorator"),
352 | ]
353 | 
354 | EXPECTED_EMPTY_FILES = [
355 |     pytest.param("edge_cases/comments_only.py", id="comments_only.py is empty"),
356 |     pytest.param("edge_cases/docstring_only.py", id="docstring_only.py is empty"),
357 |     pytest.param("edge_cases/empty.py", id="empty.py is empty"),
358 |     pytest.param("edge_cases/syntax_error.py", marks=pytest.mark.skip(reason="File with syntax error should be skipped or handled gracefully"), id="syntax_error.py is skipped"),
359 |     pytest.param("module_c/__init__.py", id="module_c/__init__.py is empty"),
360 | ]
361 | 
362 | 
363 | 
364 | # ==============================================================================
365 | # == TEST IMPLEMENTATIONS
366 | # ==============================================================================
367 | 
368 | def check_query(graph, query, description):
369 |     """Helper function to execute a Cypher query and assert that a match is found."""
370 |     try:
371 |         result = graph.query(query)
372 |     except Exception as e:
373 |         pytest.fail(f"Query failed for {description} with error: {e}\nQuery was:\n{query}")
374 | 
375 |     assert result is not None, f"Query for {description} returned None.\nQuery was:\n{query}"
376 |     assert len(result) > 0, f"Query for {description} returned no records.\nQuery was:\n{query}"
377 |     assert result[0].get('count', 0) > 0, f"No match found for {description}.\nQuery was:\n{query}"
378 | 
379 | @pytest.mark.parametrize("file_name, item_name, item_label", EXPECTED_STRUCTURE)
380 | def test_file_contains_item(graph, file_name, item_name, item_label):
381 |     """Verifies that a File node correctly CONTAINS a Function or Class node."""
382 |     description = f"CONTAINS from [{file_name}] to [{item_name}]"
383 |     abs_file_path = os.path.join(SAMPLE_PROJECT_PATH, file_name)
384 |     query = f"""
385 |     MATCH (f:File {{path: '{abs_file_path}'}})-[:CONTAINS]->(item:{item_label} {{name: '{item_name}'}})
386 |     RETURN count(*) AS count
387 |     """
388 |     check_query(graph, query, description)
389 | 
390 | @pytest.mark.parametrize("child_name, child_file, parent_name, parent_file", EXPECTED_INHERITANCE)
391 | def test_inheritance_relationship(graph, child_name, child_file, parent_name, parent_file):
392 |     """Verifies that an INHERITS relationship exists between two classes."""
393 |     description = f"INHERITS from [{child_name}] to [{parent_name}]"
394 |     child_path = os.path.join(SAMPLE_PROJECT_PATH, child_file)
395 |     parent_path = os.path.join(SAMPLE_PROJECT_PATH, parent_file)
396 |     query = f"""
397 |     MATCH (child:Class {{name: '{child_name}', file_path: '{child_path}'}})-[:INHERITS]->(parent:Class {{name: '{parent_name}', file_path: '{parent_path}'}})
398 |     RETURN count(*) as count
399 |     """
400 |     check_query(graph, query, description)
401 | 
402 | @pytest.mark.parametrize("caller_name, caller_file, caller_class, callee_name, callee_file, callee_class", EXPECTED_CALLS)
403 | def test_function_call_relationship(graph, caller_name, caller_file, caller_class, callee_name, callee_file, callee_class):
404 |     """Verifies that a CALLS relationship exists by checking for nodes first, then the relationship."""
405 |     caller_path = os.path.join(SAMPLE_PROJECT_PATH, caller_file)
406 |     callee_path = os.path.join(SAMPLE_PROJECT_PATH, callee_file)
407 | 
408 |     # Build match clauses for caller and callee
409 |     if caller_class:
410 |         caller_match = f"(caller_class:Class {{name: '{caller_class}', file_path: '{caller_path}'}})-[:CONTAINS]->(caller:Function {{name: '{caller_name}'}})"
411 |     else:
412 |         caller_match = f"(caller:Function {{name: '{caller_name}', file_path: '{caller_path}'}})"
413 | 
414 |     if callee_class:
415 |         callee_match = f"(callee_class:Class {{name: '{callee_class}', file_path: '{callee_path}'}})-[:CONTAINS]->(callee:Function {{name: '{callee_name}'}})"
416 |     else:
417 |         callee_match = f"(callee:Function {{name: '{callee_name}', file_path: '{callee_path}'}})"
418 | 
419 |     # 1. Check that the caller node exists
420 |     caller_description = f"existence of caller {caller_class or 'Function'} {{name: '{caller_name}'}} in [{caller_file}]"
421 |     caller_query = f"""
422 |     MATCH {caller_match}
423 |     RETURN count(caller) as count
424 |     """
425 |     check_query(graph, caller_query, caller_description)
426 | 
427 |     # 2. Check that the callee node exists
428 |     callee_description = f"existence of callee {callee_class or 'Function'} {{name: '{callee_name}'}} in [{callee_file}]"
429 |     callee_query = f"""
430 |     MATCH {callee_match}
431 |     RETURN count(callee) as count
432 |     """
433 |     check_query(graph, callee_query, callee_description)
434 | 
435 |     # 3. Check that the CALLS relationship exists between them
436 |     relationship_description = f"CALLS from [{caller_name}] to [{callee_name}]"
437 |     relationship_query = f"""
438 |     MATCH {caller_match}
439 |     MATCH {callee_match}
440 |     MATCH (caller)-[:CALLS]->(callee)
441 |     RETURN count(*) as count
442 |     """
443 |     check_query(graph, relationship_query, relationship_description)
444 | 
445 | @pytest.mark.parametrize("file_name, module_name", EXPECTED_IMPORTS)
446 | def test_import_relationship(graph, file_name, module_name):
447 |     """Verifies that an IMPORTS relationship exists between a file and a module."""
448 |     description = f"IMPORTS from [{file_name}] to [{module_name}]"
449 |     abs_file_path = os.path.join(SAMPLE_PROJECT_PATH, file_name)
450 |     query = f"""
451 |     MATCH (f:File {{path: '{abs_file_path}'}})-[:IMPORTS]->(m:Module {{name: '{module_name}'}})
452 |     RETURN count(*) as count
453 |     """
454 |     check_query(graph, query, description)
455 | 
456 | @pytest.mark.parametrize("function_name, file_name, parameter_name", EXPECTED_PARAMETERS)
457 | def test_parameter_relationship(graph, function_name, file_name, parameter_name):
458 |     """Verifies that a HAS_PARAMETER relationship exists between a function and a parameter."""
459 |     description = f"HAS_PARAMETER from [{function_name}] to [{parameter_name}]"
460 |     abs_file_path = os.path.join(SAMPLE_PROJECT_PATH, file_name)
461 |     query = f"""
462 |     MATCH (f:Function {{name: '{function_name}', file_path: '{abs_file_path}'}})-[:HAS_PARAMETER]->(p:Parameter {{name: '{parameter_name}'}})
463 |     RETURN count(*) as count
464 |     """
465 |     check_query(graph, query, description)
466 | 
467 | @pytest.mark.parametrize("class_name, file_name, method_name", EXPECTED_CLASS_METHODS)
468 | def test_class_method_relationship(graph, class_name, file_name, method_name):
469 |     """Verifies that a CONTAINS relationship exists between a class and a method."""
470 |     description = f"CONTAINS from [{class_name}] to [{method_name}]"
471 |     abs_file_path = os.path.join(SAMPLE_PROJECT_PATH, file_name)
472 |     query = f"""
473 |     MATCH (c:Class {{name: '{class_name}', file_path: '{abs_file_path}'}})-[:CONTAINS]->(m:Function {{name: '{method_name}'}})
474 |     RETURN count(*) as count
475 |     """
476 |     check_query(graph, query, description)
477 | 
478 | @pytest.mark.parametrize("outer_function_name, file_name, inner_function_name", EXPECTED_FUNCTION_CONTAINS)
479 | def test_function_contains_relationship(graph, outer_function_name, file_name, inner_function_name):
480 |     """Verifies that a CONTAINS relationship exists between an outer function and an inner function."""
481 |     description = f"CONTAINS from [{outer_function_name}] to [{inner_function_name}]"
482 |     abs_file_path = os.path.join(SAMPLE_PROJECT_PATH, file_name)
483 |     query = f"""
484 |     MATCH (outer:Function {{name: '{outer_function_name}', file_path: '{abs_file_path}'}})-[:CONTAINS]->(inner:Function {{name: '{inner_function_name}'}})
485 |     RETURN count(*) as count
486 |     """
487 |     check_query(graph, query, description)
488 | 
```

--------------------------------------------------------------------------------
/src/codegraphcontext/server.py:
--------------------------------------------------------------------------------

```python
  1 | # src/codegraphcontext/server.py
  2 | import urllib.parse
  3 | import asyncio
  4 | import json
  5 | import importlib
  6 | import stdlibs
  7 | import sys
  8 | import traceback
  9 | import os
 10 | import re
 11 | from datetime import datetime
 12 | from pathlib import Path
 13 | from neo4j.exceptions import CypherSyntaxError
 14 | from dataclasses import asdict
 15 | 
 16 | from typing import Any, Dict, Coroutine, Optional
 17 | 
 18 | from .prompts import LLM_SYSTEM_PROMPT
 19 | from .core import get_database_manager
 20 | from .core.jobs import JobManager, JobStatus
 21 | from .core.watcher import CodeWatcher
 22 | from .tools.graph_builder import GraphBuilder
 23 | from .tools.code_finder import CodeFinder
 24 | from .tools.package_resolver import get_local_package_path
 25 | from .utils.debug_log import debug_log, info_logger, error_logger, warning_logger, debug_logger
 26 | 
 27 | DEFAULT_EDIT_DISTANCE = 2
 28 | DEFAULT_FUZZY_SEARCH = False
 29 | 
 30 | class MCPServer:
 31 |     """
 32 |     The main MCP Server class.
 33 |     
 34 |     This class orchestrates all the major components of the application, including:
 35 |     - Database connection management (`DatabaseManager` or `FalkorDBManager`)
 36 |     - Background job tracking (`JobManager`)
 37 |     - File system watching for live updates (`CodeWatcher`)
 38 |     - Tool handlers for graph building, code searching, etc.
 39 |     - The main JSON-RPC communication loop for interacting with an AI assistant.
 40 |     """
 41 | 
 42 |     def __init__(self, loop=None):
 43 |         """
 44 |         Initializes the MCP server and its components. 
 45 |         
 46 |         Args:
 47 |             loop: The asyncio event loop to use. If not provided, it gets the current
 48 |                   running loop or creates a new one.
 49 |         """
 50 |         try:
 51 |             # Initialize the database manager (Neo4j or FalkorDB Lite based on env var)
 52 |             # to fail fast if credentials/configuration are wrong.
 53 |             self.db_manager = get_database_manager()
 54 |             self.db_manager.get_driver() 
 55 |         except ValueError as e:
 56 |             raise ValueError(f"Database configuration error: {e}")
 57 | 
 58 |         # Initialize managers for jobs and file watching.
 59 |         self.job_manager = JobManager()
 60 |         
 61 |         # Get the current event loop to pass to thread-sensitive components like the graph builder.
 62 |         if loop is None:
 63 |             try:
 64 |                 loop = asyncio.get_running_loop()
 65 |             except RuntimeError:
 66 |                 loop = asyncio.new_event_loop()
 67 |                 asyncio.set_event_loop(loop)
 68 |         self.loop = loop
 69 | 
 70 |         # Initialize all the tool handlers, passing them the necessary managers and the event loop.
 71 |         self.graph_builder = GraphBuilder(self.db_manager, self.job_manager, loop)
 72 |         self.code_finder = CodeFinder(self.db_manager)
 73 |         self.code_watcher = CodeWatcher(self.graph_builder, self.job_manager)
 74 |         
 75 |         # Define the tool manifest that will be exposed to the AI assistant.
 76 |         self._init_tools()
 77 | 
 78 |     def _init_tools(self):
 79 |         """
 80 |         Defines the complete tool manifest for the LLM.
 81 |         This dictionary contains the schema for every tool the AI can call,
 82 |         including its name, description, and input parameters.
 83 |         """
 84 |         self.tools = {
 85 |             "add_code_to_graph": {
 86 |                 "name": "add_code_to_graph",
 87 |                 "description": "Performs a one-time scan of a local folder to add its code to the graph. Ideal for indexing libraries, dependencies, or projects not being actively modified. Returns a job ID for background processing.",
 88 |                 "inputSchema": {
 89 |                     "type": "object",
 90 |                     "properties": {
 91 |                         "path": {"type": "string", "description": "Path to the directory or file to add."},
 92 |                         "is_dependency": {"type": "boolean", "description": "Whether this code is a dependency.", "default": False}
 93 |                     },
 94 |                     "required": ["path"]
 95 |                 }
 96 |             },
 97 |             "check_job_status": {
 98 |                 "name": "check_job_status",
 99 |                 "description": "Check the status and progress of a background job.",
100 |                 "inputSchema": {
101 |                     "type": "object",
102 |                     "properties": { "job_id": {"type": "string", "description": "Job ID from a previous tool call"} },
103 |                     "required": ["job_id"]
104 |                 }
105 |             },
106 |             "list_jobs": {
107 |                 "name": "list_jobs",
108 |                 "description": "List all background jobs and their current status.",
109 |                 "inputSchema": {"type": "object", "properties": {}}
110 |             },
111 |            "find_code": {
112 |                 "name": "find_code",
113 |                 "description": "Find relevant code snippets related to a keyword (e.g., function name, class name, or content).",
114 |                 "inputSchema": {
115 |                     "type": "object",
116 |                     "properties": { "query": {"type": "string", "description": "Keyword or phrase to search for"}, "fuzzy_search": {"type": "boolean", "description": "Whether to use fuzzy search", "default": False}, "edit_distance": {"type": "number", "description": "Edit distance for fuzzy search (between 0-2)", "default": 2}}, 
117 |                     "required": ["query"]
118 |                 }
119 |             },
120 | 
121 |             "analyze_code_relationships": {
122 |                 "name": "analyze_code_relationships",
123 |                 "description": "Analyze code relationships like 'who calls this function' or 'class hierarchy'. Supported query types include: find_callers, find_callees, find_all_callers, find_all_callees, find_importers, who_modifies, class_hierarchy, overrides, dead_code, call_chain, module_deps, variable_scope, find_complexity, find_functions_by_argument, find_functions_by_decorator.",
124 |                 "inputSchema": {
125 |                     "type": "object",
126 |                     "properties": {
127 |                         "query_type": {"type": "string", "description": "Type of relationship query to run.", "enum": ["find_callers", "find_callees", "find_all_callers", "find_all_callees", "find_importers", "who_modifies", "class_hierarchy", "overrides", "dead_code", "call_chain", "module_deps", "variable_scope", "find_complexity", "find_functions_by_argument", "find_functions_by_decorator"]},
128 |                         "target": {"type": "string", "description": "The function, class, or module to analyze."},
129 |                         "context": {"type": "string", "description": "Optional: specific file path for precise results."} 
130 |                     },
131 |                     "required": ["query_type", "target"]
132 |                 }
133 |             },
134 |             "watch_directory": {
135 |                 "name": "watch_directory",
136 |                 "description": "Performs an initial scan of a directory and then continuously monitors it for changes, automatically keeping the graph up-to-date. Ideal for projects under active development. Returns a job ID for the initial scan.",
137 |                 "inputSchema": {
138 |                     "type": "object",
139 |                     "properties": { "path": {"type": "string", "description": "Path to directory to watch"} },
140 |                     "required": ["path"]
141 |                 }
142 |             },
143 |             "execute_cypher_query": {
144 |                 "name": "execute_cypher_query",
145 |                 "description": "Fallback tool to run a direct, read-only Cypher query against the code graph. Use this for complex questions not covered by other tools. The graph contains nodes representing code structures and relationships between them. **Schema Overview:**\n- **Nodes:** `Repository`, `File`, `Module`, `Class`, `Function`.\n- **Properties:** Nodes have properties like `name`, `path`, `cyclomatic_complexity` (on Function nodes), and `code`.\n- **Relationships:** `CONTAINS` (e.g., File-[:CONTAINS]->Function), `CALLS` (Function-[:CALLS]->Function or File-[:CALLS]->Function), `IMPORTS` (File-[:IMPORTS]->Module), `INHERITS` (Class-[:INHERITS]->Class).",
146 |                 "inputSchema": {
147 |                     "type": "object",
148 |                     "properties": { "cypher_query": {"type": "string", "description": "The read-only Cypher query to execute."} },
149 |                     "required": ["cypher_query"]
150 |                 }
151 |             },
152 |             "add_package_to_graph": {
153 |                 "name": "add_package_to_graph",
154 |                 "description": "Add a package to the graph by discovering its location. Supports multiple languages. Returns immediately with a job ID.",
155 |                 "inputSchema": {
156 |                     "type": "object",
157 |                     "properties": {
158 |                         "package_name": {"type": "string", "description": "Name of the package to add (e.g., 'requests', 'express', 'moment', 'lodash')."},
159 |                         "language": {"type": "string", "description": "The programming language of the package.", "enum": ["python", "javascript", "typescript", "java", "c", "go", "ruby", "php","cpp"]},
160 |                         "is_dependency": {"type": "boolean", "description": "Mark as a dependency.", "default": True}
161 |                     },
162 |                     "required": ["package_name", "language"]
163 |                 }
164 |             },
165 |             "find_dead_code": {
166 |                 "name": "find_dead_code",
167 |                 "description": "Find potentially unused functions (dead code) across the entire indexed codebase, optionally excluding functions with specific decorators.",
168 |                 "inputSchema": {
169 |                     "type": "object",
170 |                     "properties": {
171 |                         "exclude_decorated_with": {"type": "array", "items": {"type": "string"}, "description": "Optional: A list of decorator names (e.g., '@app.route') to exclude from dead code detection.", "default": []}
172 |                     }
173 |                 }
174 |             },
175 |             "calculate_cyclomatic_complexity": {
176 |                 "name": "calculate_cyclomatic_complexity",
177 |                 "description": "Calculate the cyclomatic complexity of a specific function to measure its complexity.",
178 |                 "inputSchema": {
179 |                     "type": "object",
180 |                     "properties": {
181 |                         "function_name": {"type": "string", "description": "The name of the function to analyze."},
182 |                         "file_path": {"type": "string", "description": "Optional: The full path to the file containing the function for a more specific query."} 
183 |                     },
184 |                     "required": ["function_name"]
185 |                 }
186 |             },
187 |             "find_most_complex_functions": {
188 |                 "name": "find_most_complex_functions",
189 |                 "description": "Find the most complex functions in the codebase based on cyclomatic complexity.",
190 |                 "inputSchema": {
191 |                     "type": "object",
192 |                     "properties": {
193 |                         "limit": {"type": "integer", "description": "The maximum number of complex functions to return.", "default": 10}
194 |                     }
195 |                 }
196 |             },
197 |             "list_indexed_repositories": {
198 |                 "name": "list_indexed_repositories",
199 |                 "description": "List all indexed repositories.",
200 |                 "inputSchema": {
201 |                     "type": "object",
202 |                     "properties": {}
203 |                 }
204 |             },
205 |             "delete_repository": {
206 |                 "name": "delete_repository",
207 |                 "description": "Delete an indexed repository from the graph.",
208 |                 "inputSchema": {
209 |                     "type": "object",
210 |                     "properties": {
211 |                         "repo_path": {"type": "string", "description": "The path of the repository to delete."} 
212 |                     },
213 |                     "required": ["repo_path"]
214 |                 }
215 |             },
216 |             "visualize_graph_query": {
217 |                 "name": "visualize_graph_query",
218 |                 "description": "Generates a URL to visualize the results of a Cypher query in the Neo4j Browser. The user can open this URL in their web browser to see the graph visualization.",
219 |                 "inputSchema": {
220 |                     "type": "object",
221 |                     "properties": {
222 |                         "cypher_query": {"type": "string", "description": "The Cypher query to visualize."}
223 |                     },
224 |                     "required": ["cypher_query"]
225 |                 }
226 |             },
227 |             "list_watched_paths": {
228 |                 "name": "list_watched_paths",
229 |                 "description": "Lists all directories currently being watched for live file changes.",
230 |                 "inputSchema": {"type": "object", "properties": {}}
231 |             },
232 |             "unwatch_directory": {
233 |                 "name": "unwatch_directory",
234 |                 "description": "Stops watching a directory for live file changes.",
235 |                 "inputSchema": {
236 |                     "type": "object",
237 |                     "properties": {
238 |                         "path": {"type": "string", "description": "The absolute path of the directory to stop watching."}
239 |                     },
240 |                     "required": ["path"]
241 |                 }
242 |             }
243 |         }    
244 | 
245 |     def get_database_status(self) -> dict:
246 |         """Returns the current connection status of the Neo4j database."""
247 |         return {"connected": self.db_manager.is_connected()}
248 |         
249 | 
250 |     def execute_cypher_query_tool(self, **args) -> Dict[str, Any]:
251 |         """
252 |         Tool implementation for executing a read-only Cypher query.
253 |         
254 |         Important: Includes a safety check to prevent any database modification
255 |         by disallowing keywords like CREATE, MERGE, DELETE, etc.
256 |         """
257 |         cypher_query = args.get("cypher_query")
258 |         if not cypher_query:
259 |             return {"error": "Cypher query cannot be empty."}
260 | 
261 |         # Safety Check: Prevent any write operations to the database.
262 |         # This check first removes all string literals and then checks for forbidden keywords.
263 |         forbidden_keywords = ['CREATE', 'MERGE', 'DELETE', 'SET', 'REMOVE', 'DROP', 'CALL apoc']
264 |         
265 |         # Regex to match single or double quoted strings, handling escaped quotes.
266 |         string_literal_pattern = r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\''
267 |         
268 |         # Remove all string literals from the query.
269 |         query_without_strings = re.sub(string_literal_pattern, '', cypher_query)
270 |         
271 |         # Now, check for forbidden keywords in the query without strings.
272 |         for keyword in forbidden_keywords:
273 |             if re.search(r'\b' + keyword + r'\b', query_without_strings, re.IGNORECASE):
274 |                 return {
275 |                     "error": "This tool only supports read-only queries. Prohibited keywords like CREATE, MERGE, DELETE, SET, etc., are not allowed."
276 |                 }
277 | 
278 |         try:
279 |             debug_log(f"Executing Cypher query: {cypher_query}")
280 |             with self.db_manager.get_driver().session() as session:
281 |                 result = session.run(cypher_query)
282 |                 # Convert results to a list of dictionaries for clean JSON serialization.
283 |                 records = [record.data() for record in result]
284 |                 
285 |                 return {
286 |                     "success": True,
287 |                     "query": cypher_query,
288 |                     "record_count": len(records),
289 |                     "results": records
290 |                 }
291 |         
292 |         except CypherSyntaxError as e:
293 |             debug_log(f"Cypher syntax error: {str(e)}")
294 |             return {
295 |                 "error": "Cypher syntax error.",
296 |                 "details": str(e),
297 |                 "query": cypher_query
298 |             }
299 |         except Exception as e:
300 |             debug_log(f"Error executing Cypher query: {str(e)}")
301 |             return {
302 |                 "error": "An unexpected error occurred while executing the query.",
303 |                 "details": str(e)
304 |             }
305 |     
306 |     def find_dead_code_tool(self, **args) -> Dict[str, Any]:
307 |         """Tool to find potentially dead code across the entire project."""
308 |         exclude_decorated_with = args.get("exclude_decorated_with", [])
309 |         try:
310 |             debug_log("Finding dead code.")
311 |             results = self.code_finder.find_dead_code(exclude_decorated_with=exclude_decorated_with)
312 |             
313 |             return {
314 |                 "success": True,
315 |                 "query_type": "dead_code",
316 |                 "results": results
317 |             }
318 |         except Exception as e:
319 |             debug_log(f"Error finding dead code: {str(e)}")
320 |             return {"error": f"Failed to find dead code: {str(e)}"}
321 | 
322 |     def calculate_cyclomatic_complexity_tool(self, **args) -> Dict[str, Any]:
323 |         """Tool to calculate cyclomatic complexity for a given function."""
324 |         function_name = args.get("function_name")
325 |         file_path = args.get("file_path")
326 | 
327 |         try:
328 |             debug_log(f"Calculating cyclomatic complexity for function: {function_name}")
329 |             results = self.code_finder.get_cyclomatic_complexity(function_name, file_path)
330 |             
331 |             response = {
332 |                 "success": True,
333 |                 "function_name": function_name,
334 |                 "results": results
335 |             }
336 |             if file_path:
337 |                 response["file_path"] = file_path
338 |             
339 |             return response
340 |         except Exception as e:
341 |             debug_log(f"Error calculating cyclomatic complexity: {str(e)}")
342 |             return {"error": f"Failed to calculate cyclomatic complexity: {str(e)}"}
343 | 
344 |     def find_most_complex_functions_tool(self, **args) -> Dict[str, Any]:
345 |         """Tool to find the most complex functions."""
346 |         limit = args.get("limit", 10)
347 |         try:
348 |             debug_log(f"Finding the top {limit} most complex functions.")
349 |             results = self.code_finder.find_most_complex_functions(limit)
350 |             return {
351 |                 "success": True,
352 |                 "limit": limit,
353 |                 "results": results
354 |             }
355 |         except Exception as e:
356 |             debug_log(f"Error finding most complex functions: {str(e)}")
357 |             return {"error": f"Failed to find most complex functions: {str(e)}"}
358 | 
359 |     def list_indexed_repositories_tool(self, **args) -> Dict[str, Any]:
360 |         """Tool to list indexed repositories."""
361 |         try:
362 |             debug_log("Listing indexed repositories.")
363 |             results = self.code_finder.list_indexed_repositories()
364 |             return {
365 |                 "success": True,
366 |                 "repositories": results
367 |             }
368 |         except Exception as e:
369 |             debug_log(f"Error listing indexed repositories: {str(e)}")
370 |             return {"error": f"Failed to list indexed repositories: {str(e)}"}
371 | 
372 |     def delete_repository_tool(self, **args) -> Dict[str, Any]:
373 |         """Tool to delete a repository from the graph."""
374 |         repo_path = args.get("repo_path")
375 |         try:
376 |             debug_log(f"Deleting repository: {repo_path}")
377 |             if self.graph_builder.delete_repository_from_graph(repo_path):
378 |                 return {
379 |                     "success": True,
380 |                     "message": f"Repository '{repo_path}' deleted successfully."
381 |                 }
382 |             else:
383 |                  return {
384 |                     "success": False,
385 |                     "message": f"Repository '{repo_path}' not found in the graph."
386 |                 }
387 |         except Exception as e:
388 |             debug_log(f"Error deleting repository: {str(e)}")
389 |             return {"error": f"Failed to delete repository: {str(e)}"}
390 | 
391 |     def visualize_graph_query_tool(self, **args) -> Dict[str, Any]:
392 |         """Tool to generate a visualization URL (Neo4j URL or FalkorDB HTML file)."""
393 |         cypher_query = args.get("cypher_query")
394 |         if not cypher_query:
395 |             return {"error": "Cypher query cannot be empty."}
396 | 
397 |         # Check DB Type: FalkorDBManager vs DatabaseManager
398 |         is_falkor = "FalkorDB" in self.db_manager.__class__.__name__
399 | 
400 |         if is_falkor:
401 |             try:
402 |                 data_nodes = []
403 |                 data_edges = []
404 |                 seen_nodes = set()
405 | 
406 |                 with self.db_manager.get_driver().session() as session:
407 |                     result = session.run(cypher_query)
408 |                     for record in result:
409 |                         # Iterate all values in the record to find Nodes and Relationships
410 |                         # record is a FalkorDBRecord (dict-like), values() works
411 |                         for val in record.values():
412 |                             # Process Node
413 |                             if hasattr(val, 'labels') and hasattr(val, 'id'):
414 |                                 nid = val.id
415 |                                 if nid not in seen_nodes:
416 |                                     seen_nodes.add(nid)
417 |                                     lbl = list(val.labels)[0] if val.labels else "Node"
418 |                                     props = getattr(val, 'properties', {}) or {}
419 |                                     name = props.get('name', str(nid))
420 |                                     
421 |                                     color = "#97c2fc"
422 |                                     if "Repository" in val.labels: color = "#ffb3ba"
423 |                                     elif "File" in val.labels: color = "#baffc9"
424 |                                     elif "Class" in val.labels: color = "#bae1ff"
425 |                                     elif "Function" in val.labels: color = "#ffffba"
426 |                                     
427 |                                     data_nodes.append({
428 |                                         "id": nid, "label": name, "group": lbl, 
429 |                                         "title": str(props), "color": color
430 |                                     })
431 |                             
432 |                             # Process Relationship
433 |                             # FalkorDB client relationship objects have src_node/dest_node (ids) and relation (type)
434 |                             # We check for both standard names just in case
435 |                             src = getattr(val, 'src_node', None)
436 |                             if src is None: src = getattr(val, 'start_node', None)
437 |                             
438 |                             dst = getattr(val, 'dest_node', None)
439 |                             if dst is None: dst = getattr(val, 'end_node', None)
440 | 
441 |                             if src is not None and dst is not None:
442 |                                 lbl = getattr(val, 'relation', None) or getattr(val, 'type', 'REL')
443 |                                 data_edges.append({
444 |                                     "from": src,
445 |                                     "to": dst,
446 |                                     "label": lbl,
447 |                                     "arrows": "to"
448 |                                 })
449 | 
450 |                 # Generate HTML
451 |                 html_content = f"""
452 | <!DOCTYPE html>
453 | <html>
454 | <head>
455 |   <title>CodeGraphContext Visualization</title>
456 |   <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
457 |   <style type="text/css">
458 |     #mynetwork {{ width: 100%; height: 100vh; border: 1px solid lightgray; }}
459 |   </style>
460 | </head>
461 | <body>
462 |   <div id="mynetwork"></div>
463 |   <script type="text/javascript">
464 |     var nodes = new vis.DataSet({json.dumps(data_nodes)});
465 |     var edges = new vis.DataSet({json.dumps(data_edges)});
466 |     var container = document.getElementById('mynetwork');
467 |     var data = {{ nodes: nodes, edges: edges }};
468 |     var options = {{
469 |         nodes: {{ shape: 'dot', size: 16 }},
470 |         physics: {{ stabilization: false }},
471 |         layout: {{ improvedLayout: false }}
472 |     }};
473 |     var network = new vis.Network(container, data, options);
474 |   </script>
475 | </body>
476 | </html>
477 | """
478 |                 # filename = f"codegraph_viz_{int(datetime.now().timestamp())}.html"
479 |                 filename = f"codegraph_viz.html"
480 |                 out_path = Path(os.getcwd()) / filename
481 |                 with open(out_path, "w") as f:
482 |                     f.write(html_content)
483 |                 
484 |                 return {
485 |                     "success": True,
486 |                     "visualization_url": f"file://{out_path}",
487 |                     "message": f"Visualization generated at {out_path}. Open this file in your browser."
488 |                 }
489 | 
490 |             except Exception as e:
491 |                 debug_log(f"Error generating FalkorDB visualization: {str(e)}")
492 |                 return {"error": f"Failed to generate visualization: {str(e)}"}
493 | 
494 |         else:
495 |             # Neo4j fallback
496 |             try:
497 |                 encoded_query = urllib.parse.quote(cypher_query)
498 |                 visualization_url = f"http://localhost:7474/browser/?cmd=edit&arg={encoded_query}"
499 |                 
500 |                 return {
501 |                     "success": True,
502 |                     "visualization_url": visualization_url,
503 |                     "message": "Open the URL in your browser to visualize the graph query. The query will be pre-filled for editing."
504 |                 }
505 |             except Exception as e:
506 |                 debug_log(f"Error generating visualization URL: {str(e)}")
507 |                 return {"error": f"Failed to generate visualization URL: {str(e)}"}
508 | 
509 |     def list_watched_paths_tool(self, **args) -> Dict[str, Any]:
510 |         """Tool to list all currently watched directory paths."""
511 |         try:
512 |             paths = self.code_watcher.list_watched_paths()
513 |             return {"success": True, "watched_paths": paths}
514 |         except Exception as e:
515 |             return {"error": f"Failed to list watched paths: {str(e)}"}
516 | 
517 |     def unwatch_directory_tool(self, **args) -> Dict[str, Any]:
518 |         """Tool to stop watching a directory."""
519 |         path = args.get("path")
520 |         if not path:
521 |             return {"error": "Path is a required argument."}
522 |         # The watcher class handles the logic of checking if the path is watched
523 |         # and returns an error dictionary if not, so we can just call it.
524 |         return self.code_watcher.unwatch_directory(path)
525 | 
526 |     def watch_directory_tool(self, **args) -> Dict[str, Any]:
527 |         """
528 |         Tool implementation to start watching a directory for changes.
529 |         This tool is now smart: it checks if the path exists and if it has already been indexed.
530 |         """
531 |         path = args.get("path")
532 |         if not path:
533 |             return {"error": "Path is a required argument."}
534 | 
535 |         path_obj = Path(path).resolve()
536 |         path_str = str(path_obj)
537 | 
538 |         # 1. Validate the path before the try...except block
539 |         if not path_obj.is_dir():
540 |             return {
541 |                 "success": True,
542 |                 "status": "path_not_found",
543 |                 "message": f"Path '{path_str}' does not exist or is not a directory."
544 |             }
545 |         try:
546 |             # Check if already watching
547 |             if path_str in self.code_watcher.watched_paths:
548 |                 return {"success": True, "message": f"Already watching directory: {path_str}"}
549 | 
550 |             # 2. Check if the repository is already indexed
551 |             indexed_repos = self.list_indexed_repositories_tool().get("repositories", [])
552 |             is_already_indexed = any(Path(repo["path"]).resolve() == path_obj for repo in indexed_repos)
553 | 
554 |             # 3. Decide whether to perform an initial scan
555 |             if is_already_indexed:
556 |                 # If already indexed, just start the watcher without a scan
557 |                 self.code_watcher.watch_directory(path_str, perform_initial_scan=False)
558 |                 return {
559 |                     "success": True,
560 |                     "message": f"Path '{path_str}' is already indexed. Now watching for live changes."
561 |                 }
562 |             else:
563 |                 # If not indexed, perform the scan AND start the watcher
564 |                 scan_job_result = self.add_code_to_graph_tool(path=path_str, is_dependency=False)
565 |                 if "error" in scan_job_result:
566 |                     return scan_job_result
567 |                 
568 |                 self.code_watcher.watch_directory(path_str, perform_initial_scan=True)
569 |                 
570 |                 return {
571 |                     "success": True,
572 |                     "message": f"Path '{path_str}' was not indexed. Started initial scan and now watching for live changes.",
573 |                     "job_id": scan_job_result.get("job_id"),
574 |                     "details": "Use check_job_status to monitor the initial scan."
575 |                 }
576 |             
577 |         except Exception as e:
578 |             error_logger(f"Failed to start watching directory {path}: {e}")
579 |             return {"error": f"Failed to start watching directory: {str(e)}"}        
580 |     
581 |     def add_code_to_graph_tool(self, **args) -> Dict[str, Any]:
582 |         """
583 |         Tool implementation to index a directory of code.
584 | 
585 |         This creates a background job and runs the indexing asynchronously
586 |         so the AI assistant can continue to be responsive.
587 |         """
588 |         path = args.get("path")
589 |         is_dependency = args.get("is_dependency", False)
590 |         
591 |         try:
592 |             path_obj = Path(path).resolve()
593 | 
594 |             if not path_obj.exists():
595 |                 return {
596 |                     "success": True,
597 |                     "status": "path_not_found",
598 |                     "message": f"Path '{path}' does not exist."
599 |                 }
600 | 
601 |             # Prevent re-indexing the same repository.
602 |             indexed_repos = self.list_indexed_repositories_tool().get("repositories", [])
603 |             for repo in indexed_repos:
604 |                 if Path(repo["path"]).resolve() == path_obj:
605 |                     return {
606 |                         "success": False,
607 |                         "message": f"Repository '{path}' is already indexed."
608 |                     }
609 |             
610 |             # Estimate time and create a job for the user to track.
611 |             total_files, estimated_time = self.graph_builder.estimate_processing_time(path_obj)
612 |             job_id = self.job_manager.create_job(str(path_obj), is_dependency)
613 |             self.job_manager.update_job(job_id, total_files=total_files, estimated_duration=estimated_time)
614 |             
615 |             # Create the coroutine for the background task and schedule it on the main event loop.
616 |             coro = self.graph_builder.build_graph_from_path_async(
617 |                 path_obj, is_dependency, job_id
618 |             )
619 |             asyncio.run_coroutine_threadsafe(coro, self.loop)
620 |             
621 |             debug_log(f"Started background job {job_id} for path: {str(path_obj)}, is_dependency: {is_dependency}")
622 |             
623 |             return {
624 |                 "success": True, "job_id": job_id,
625 |                 "message": f"Background processing started for {str(path_obj)}",
626 |                 "estimated_files": total_files,
627 |                 "estimated_duration_seconds": round(estimated_time, 2),
628 |                 "estimated_duration_human": f"{int(estimated_time // 60)}m {int(estimated_time % 60)}s" if estimated_time >= 60 else f"{int(estimated_time)}s",
629 |                 "instructions": f"Use 'check_job_status' with job_id '{job_id}' to monitor progress"
630 |             }
631 |         
632 |         except Exception as e:
633 |             debug_log(f"Error creating background job: {str(e)}")
634 |             return {"error": f"Failed to start background processing: {str(e)}"}
635 |     
636 |     def add_package_to_graph_tool(self, **args) -> Dict[str, Any]:
637 |         """Tool to add a package to the graph by auto-discovering its location"""
638 |         package_name = args.get("package_name")
639 |         language = args.get("language")
640 |         is_dependency = args.get("is_dependency", True)
641 | 
642 |         if not language:
643 |             return {"error": "The 'language' parameter is required."}
644 | 
645 |         try:
646 |             # Check if the package is already indexed
647 |             indexed_repos = self.list_indexed_repositories_tool().get("repositories", [])
648 |             for repo in indexed_repos:
649 |                 if repo.get("is_dependency") and (repo.get("name") == package_name or repo.get("name") == f"{package_name}.py"):
650 |                     return {
651 |                         "success": False,
652 |                         "message": f"Package '{package_name}' is already indexed."
653 |                     }
654 | 
655 |             package_path = get_local_package_path(package_name, language)
656 |             
657 |             if not package_path:
658 |                 return {"error": f"Could not find package '{package_name}' for language '{language}'. Make sure it's installed."}
659 |             
660 |             if not os.path.exists(package_path):
661 |                 return {"error": f"Package path '{package_path}' does not exist"}
662 |             
663 |             path_obj = Path(package_path)
664 |             
665 |             total_files, estimated_time = self.graph_builder.estimate_processing_time(path_obj)
666 |             
667 |             job_id = self.job_manager.create_job(package_path, is_dependency)
668 |             
669 |             self.job_manager.update_job(job_id, total_files=total_files, estimated_duration=estimated_time)
670 |             
671 |             coro = self.graph_builder.build_graph_from_path_async(
672 |                 path_obj, is_dependency, job_id
673 |             )
674 |             asyncio.run_coroutine_threadsafe(coro, self.loop)
675 |             
676 |             debug_log(f"Started background job {job_id} for package: {package_name} at {package_path}, is_dependency: {is_dependency}")
677 |             
678 |             return {
679 |                 "success": True, "job_id": job_id, "package_name": package_name,
680 |                 "discovered_path": package_path,
681 |                 "message": f"Background processing started for package '{package_name}'",
682 |                 "estimated_files": total_files,
683 |                 "estimated_duration_seconds": round(estimated_time, 2),
684 |                 "estimated_duration_human": f"{int(estimated_time // 60)}m {int(estimated_time % 60)}s" if estimated_time >= 60 else f"{int(estimated_time)}s",
685 |                 "instructions": f"Use 'check_job_status' with job_id '{job_id}' to monitor progress"
686 |             }
687 |         
688 |         except Exception as e:
689 |             debug_log(f"Error creating background job for package {package_name}: {str(e)}")
690 |             return {"error": f"Failed to start background processing for package '{package_name}': {str(e)}"}
691 |     
692 |     def check_job_status_tool(self, **args) -> Dict[str, Any]:
693 |         """Tool to check job status"""
694 |         job_id = args.get("job_id")
695 |         if not job_id:
696 |             return {"error": "Job ID is a required argument."}
697 |                 
698 |         try:
699 |             job = self.job_manager.get_job(job_id)
700 |             
701 |             if not job:
702 |                 return {
703 |                     "success": True, # Return success to avoid generic error wrapper
704 |                     "status": "not_found",
705 |                     "message": f"Job with ID '{job_id}' not found. The ID may be incorrect or the job may have been cleared after a server restart."
706 |                 }
707 |             
708 |             job_dict = asdict(job)
709 |             
710 |             if job.status == JobStatus.RUNNING:
711 |                 if job.estimated_time_remaining:
712 |                     remaining = job.estimated_time_remaining
713 |                     job_dict["estimated_time_remaining_human"] = (
714 |                         f"{int(remaining // 60)}m {int(remaining % 60)}s" 
715 |                         if remaining >= 60 else f"{int(remaining)}s"
716 |                     )
717 |                 
718 |                 if job.start_time:
719 |                     elapsed = (datetime.now() - job.start_time).total_seconds()
720 |                     job_dict["elapsed_time_human"] = (
721 |                         f"{int(elapsed // 60)}m {int(elapsed % 60)}s" 
722 |                         if elapsed >= 60 else f"{int(elapsed)}s"
723 |                     )
724 |             
725 |             elif job.status == JobStatus.COMPLETED and job.start_time and job.end_time:
726 |                 duration = (job.end_time - job.start_time).total_seconds()
727 |                 job_dict["actual_duration_human"] = (
728 |                     f"{int(duration // 60)}m {int(duration % 60)}s" 
729 |                     if duration >= 60 else f"{int(duration)}s"
730 |                 )
731 |             
732 |             job_dict["start_time"] = job.start_time.strftime("%Y-%m-%d %H:%M:%S")
733 |             if job.end_time:
734 |                 job_dict["end_time"] = job.end_time.strftime("%Y-%m-%d %H:%M:%S")
735 |             
736 |             job_dict["status"] = job.status.value
737 |             
738 |             return {"success": True, "job": job_dict}
739 |         
740 |         except Exception as e:
741 |             debug_log(f"Error checking job status: {str(e)}")
742 |             return {"error": f"Failed to check job status: {str(e)}"}
743 |     
744 |     def list_jobs_tool(self) -> Dict[str, Any]:
745 |         """Tool to list all jobs"""
746 |         try:
747 |             jobs = self.job_manager.list_jobs()
748 |             
749 |             jobs_data = []
750 |             for job in jobs:
751 |                 job_dict = asdict(job)
752 |                 job_dict["status"] = job.status.value
753 |                 job_dict["start_time"] = job.start_time.strftime("%Y-%m-%d %H:%M:%S")
754 |                 if job.end_time:
755 |                     job_dict["end_time"] = job.end_time.strftime("%Y-%m-%d %H:%M:%S")
756 |                 jobs_data.append(job_dict)
757 |             
758 |             jobs_data.sort(key=lambda x: x["start_time"], reverse=True)
759 |             
760 |             return {"success": True, "jobs": jobs_data, "total_jobs": len(jobs_data)}
761 |         
762 |         except Exception as e:
763 |             debug_log(f"Error listing jobs: {str(e)}")
764 |             return {"error": f"Failed to list jobs: {str(e)}"}
765 |     
766 |     def analyze_code_relationships_tool(self, **args) -> Dict[str, Any]:
767 |         """Tool to analyze code relationships"""
768 |         query_type = args.get("query_type")
769 |         target = args.get("target")
770 |         context = args.get("context")
771 | 
772 |         if not query_type or not target:
773 |             return {
774 |                 "error": "Both 'query_type' and 'target' are required",
775 |                 "supported_query_types": [
776 |                     "find_callers", "find_callees", "find_importers", "who_modifies",
777 |                     "class_hierarchy", "overrides", "dead_code", "call_chain",
778 |                     "module_deps", "variable_scope", "find_complexity"
779 |                 ]
780 |             }
781 |         
782 |         try:
783 |             debug_log(f"Analyzing relationships: {query_type} for {target}")
784 |             results = self.code_finder.analyze_code_relationships(query_type, target, context)
785 |             
786 |             return {
787 |                 "success": True, "query_type": query_type, "target": target,
788 |                 "context": context, "results": results
789 |             }
790 |         
791 |         except Exception as e:
792 |             debug_log(f"Error analyzing relationships: {str(e)}")
793 |             return {"error": f"Failed to analyze relationships: {str(e)}"}
794 |         
795 |     @staticmethod
796 |     def _normalize(text: str) -> str:
797 |         return text.lower().replace("_", " ").strip()
798 | 
799 |     def find_code_tool(self, **args) -> Dict[str, Any]:
800 |         """Tool to find relevant code snippets"""
801 |         query = args.get("query")
802 |         fuzzy_search = args.get("fuzzy_search", DEFAULT_FUZZY_SEARCH)
803 |         edit_distance = args.get("edit_distance", DEFAULT_EDIT_DISTANCE)
804 | 
805 |         if fuzzy_search:
806 |             query = self._normalize(query)
807 |             
808 |         try:
809 |             debug_log(f"Finding code for query: {query} with fuzzy_search={fuzzy_search}, edit_distance={edit_distance}")
810 |             results = self.code_finder.find_related_code(query, fuzzy_search, edit_distance)
811 | 
812 |             return {"success": True, "query": query, "results": results}
813 |         
814 |         except Exception as e:
815 |             debug_log(f"Error finding code: {str(e)}")
816 |             return {"error": f"Failed to find code: {str(e)}"}
817 |     
818 | 
819 |     async def handle_tool_call(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
820 |         """
821 |         Routes a tool call from the AI assistant to the appropriate handler function. 
822 |         
823 |         Args:
824 |             tool_name: The name of the tool to execute.
825 |             args: A dictionary of arguments for the tool.
826 | 
827 |         Returns:
828 |             A dictionary containing the result of the tool execution.
829 |         """
830 |         tool_map: Dict[str, Coroutine] = {
831 |             "add_package_to_graph": self.add_package_to_graph_tool,
832 |             "find_dead_code": self.find_dead_code_tool,
833 |             "find_code": self.find_code_tool,
834 |             "analyze_code_relationships": self.analyze_code_relationships_tool,
835 |             "watch_directory": self.watch_directory_tool,
836 |             "execute_cypher_query": self.execute_cypher_query_tool,
837 |             "add_code_to_graph": self.add_code_to_graph_tool,
838 |             "check_job_status": self.check_job_status_tool,
839 |             "list_jobs": self.list_jobs_tool,
840 |             "calculate_cyclomatic_complexity": self.calculate_cyclomatic_complexity_tool,
841 |             "find_most_complex_functions": self.find_most_complex_functions_tool,
842 |             "list_indexed_repositories": self.list_indexed_repositories_tool,
843 |             "delete_repository": self.delete_repository_tool,
844 |             "visualize_graph_query": self.visualize_graph_query_tool,
845 |             "list_watched_paths": self.list_watched_paths_tool,
846 |             "unwatch_directory": self.unwatch_directory_tool
847 |         }
848 |         handler = tool_map.get(tool_name)
849 |         if handler:
850 |             # Run the synchronous tool function in a separate thread to avoid
851 |             # blocking the main asyncio event loop.
852 |             return await asyncio.to_thread(handler, **args)
853 |         else:
854 |             return {"error": f"Unknown tool: {tool_name}"}
855 | 
856 |     async def run(self):
857 |         """
858 |         Runs the main server loop, listening for JSON-RPC requests from stdin.
859 |         
860 |         This loop continuously reads lines from stdin, parses them as JSON-RPC
861 |         requests, and routes them to the appropriate handlers (e.g., initialize,
862 |         tools/list, tools/call). The response is then printed to stdout.
863 |         """
864 |         # info_logger("MCP Server is running. Waiting for requests...")
865 |         print("MCP Server is running. Waiting for requests...", file=sys.stderr, flush=True)
866 |         self.code_watcher.start()
867 |         
868 |         loop = asyncio.get_event_loop()
869 |         while True:
870 |             try:
871 |                 # Read a request from the standard input.
872 |                 line = await loop.run_in_executor(None, sys.stdin.readline)
873 |                 if not line:
874 |                     debug_logger("Client disconnected (EOF received). Shutting down.")
875 |                     break
876 |                 
877 |                 request = json.loads(line.strip())
878 |                 method = request.get('method')
879 |                 params = request.get('params', {})
880 |                 request_id = request.get('id')
881 |                 
882 |                 response = {}
883 |                 # Route the request based on the JSON-RPC method.
884 |                 if method == 'initialize':
885 |                     response = {
886 |                         "jsonrpc": "2.0", "id": request_id,
887 |                         "result": {
888 |                             "protocolVersion": "2025-03-26",
889 |                             "serverInfo": {
890 |                                 "name": "CodeGraphContext", "version": "0.1.0",
891 |                                 "systemPrompt": LLM_SYSTEM_PROMPT
892 |                             },
893 |                             "capabilities": {"tools": {"listTools": True}},
894 |                         }
895 |                     }
896 |                 elif method == 'tools/list':
897 |                     # Return the list of tools defined in _init_tools.
898 |                     response = {
899 |                         "jsonrpc": "2.0", "id": request_id,
900 |                         "result": {"tools": list(self.tools.values())}
901 |                     }
902 |                 elif method == 'tools/call':
903 |                     # Execute a tool call and return the result.
904 |                     tool_name = params.get('name')
905 |                     args = params.get('arguments', {})
906 |                     result = await self.handle_tool_call(tool_name, args)
907 |                     
908 |                     if "error" in result:
909 |                         response = {
910 |                             "jsonrpc": "2.0", "id": request_id,
911 |                             "error": {"code": -32000, "message": "Tool execution error", "data": result}
912 |                         }
913 |                     else:
914 |                         response = {
915 |                             "jsonrpc": "2.0", "id": request_id,
916 |                             "result": {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
917 |                         }
918 |                 elif method == 'notifications/initialized':
919 |                     # This is a notification, no response needed.
920 |                     pass
921 |                 else:
922 |                     # Handle unknown methods.
923 |                     if request_id is not None:
924 |                         response = {
925 |                             "jsonrpc": "2.0", "id": request_id,
926 |                             "error": {"code": -32601, "message": f"Method not found: {method}"}
927 |                         }
928 |                 
929 |                 # Send the response to standard output if it's not a notification.
930 |                 if request_id is not None and response:
931 |                     print(json.dumps(response), flush=True)
932 | 
933 |             except Exception as e:
934 |                 error_logger(f"Error processing request: {e}\n{traceback.format_exc()}")
935 |                 request_id = "unknown"
936 |                 if 'request' in locals() and isinstance(request, dict):
937 |                     request_id = request.get('id', "unknown")
938 | 
939 |                 error_response = {
940 |                     "jsonrpc": "2.0", "id": request_id,
941 |                     "error": {"code": -32603, "message": f"Internal error: {str(e)}", "data": traceback.format_exc()}
942 |                 }
943 |                 print(json.dumps(error_response), flush=True)
944 | 
945 |     def shutdown(self):
946 |         """Gracefully shuts down the server and its components."""
947 |         debug_logger("Shutting down server...")
948 |         self.code_watcher.stop()
949 |         self.db_manager.close_driver()
950 | 
```

--------------------------------------------------------------------------------
/src/codegraphcontext/tools/code_finder.py:
--------------------------------------------------------------------------------

```python
  1 | # src/codegraphcontext/tools/code_finder.py
  2 | import logging
  3 | import re
  4 | from typing import Any, Dict, List, Literal, Optional
  5 | from pathlib import Path
  6 | 
  7 | from ..core.database import DatabaseManager
  8 | 
  9 | logger = logging.getLogger(__name__)
 10 | 
 11 | class CodeFinder:
 12 |     """Module for finding relevant code snippets and analyzing relationships."""
 13 | 
 14 |     def __init__(self, db_manager: DatabaseManager):
 15 |         self.db_manager = db_manager
 16 |         self.driver = self.db_manager.get_driver()
 17 | 
 18 |     def format_query(self, find_by: Literal["Class", "Function"], fuzzy_search:bool) -> str:
 19 |         """Format the search query based on the search type and fuzzy search settings."""
 20 |         return f"""
 21 |             CALL db.index.fulltext.queryNodes("code_search_index", $search_term) YIELD node, score
 22 |                 WITH node, score
 23 |                 WHERE node:{find_by} {'AND node.name CONTAINS $search_term' if not fuzzy_search else ''}
 24 |                 RETURN node.name as name, node.file_path as file_path, node.line_number as line_number,
 25 |                     node.source as source, node.docstring as docstring, node.is_dependency as is_dependency
 26 |                 ORDER BY score DESC
 27 |                 LIMIT 20
 28 |             """
 29 | 
 30 |     def find_by_function_name(self, search_term: str, fuzzy_search: bool) -> List[Dict]:
 31 |         """Find functions by name matching."""
 32 |         with self.driver.session() as session:
 33 |             if not fuzzy_search:
 34 |                 # Use simple match for exact search to avoid fulltext index dependency
 35 |                 result = session.run("""
 36 |                     MATCH (node:Function {name: $name})
 37 |                     RETURN node.name as name, node.file_path as file_path, node.line_number as line_number,
 38 |                            node.source as source, node.docstring as docstring, node.is_dependency as is_dependency
 39 |                     LIMIT 20
 40 |                 """, name=search_term)
 41 |                 return result.data()
 42 |             
 43 |             # Fuzzy search using fulltext index
 44 |             formatted_search_term = f"name:{search_term}"
 45 |             result = session.run(self.format_query("Function", fuzzy_search), search_term=formatted_search_term)
 46 |             return result.data()
 47 | 
 48 |     def find_by_class_name(self, search_term: str, fuzzy_search: bool) -> List[Dict]:
 49 |         """Find classes by name matching."""
 50 |         with self.driver.session() as session:
 51 |             if not fuzzy_search:
 52 |                 # Use simple match for exact search to avoid fulltext index dependency
 53 |                 result = session.run("""
 54 |                     MATCH (node:Class {name: $name})
 55 |                     RETURN node.name as name, node.file_path as file_path, node.line_number as line_number,
 56 |                            node.source as source, node.docstring as docstring, node.is_dependency as is_dependency
 57 |                     LIMIT 20
 58 |                 """, name=search_term)
 59 |                 return result.data()
 60 | 
 61 |             # Fuzzy search using fulltext index
 62 |             formatted_search_term = f"name:{search_term}"
 63 |             result = session.run(self.format_query("Class", fuzzy_search), search_term=formatted_search_term)
 64 |             return result.data()
 65 | 
 66 |     def find_by_variable_name(self, search_term: str) -> List[Dict]:
 67 |         """Find variables by name matching"""
 68 |         with self.driver.session() as session:
 69 |             result = session.run("""
 70 |                 MATCH (v:Variable)
 71 |                 WHERE v.name CONTAINS $search_term
 72 |                 RETURN v.name as name, v.file_path as file_path, v.line_number as line_number,
 73 |                        v.value as value, v.context as context, v.is_dependency as is_dependency
 74 |                 ORDER BY v.is_dependency ASC, v.name
 75 |                 LIMIT 20
 76 |             """, search_term=search_term)
 77 |             
 78 |             return result.data()
 79 | 
 80 |     def find_by_content(self, search_term: str) -> List[Dict]:
 81 |         """Find code by content matching in source or docstrings using the full-text index."""
 82 |         with self.driver.session() as session:
 83 |             result = session.run("""
 84 |                 CALL db.index.fulltext.queryNodes("code_search_index", $search_term) YIELD node, score
 85 |                 WITH node, score
 86 |                 WHERE node:Function OR node:Class OR node:Variable
 87 |                 RETURN
 88 |                     CASE 
 89 |                         WHEN node:Function THEN 'function'
 90 |                         WHEN node:Class THEN 'class'
 91 |                         ELSE 'variable' 
 92 |                     END as type,
 93 |                     node.name as name, node.file_path as file_path,
 94 |                     node.line_number as line_number, node.source as source,
 95 |                     node.docstring as docstring, node.is_dependency as is_dependency
 96 |                 ORDER BY score DESC
 97 |                 LIMIT 20
 98 |             """, search_term=search_term)
 99 |             return result.data()
100 |     
101 |     def find_by_module_name(self, search_term: str) -> List[Dict]:
102 |         """Find modules by name matching"""
103 |         with self.driver.session() as session:
104 |             result = session.run("""
105 |                 MATCH (m:Module)
106 |                 WHERE m.name CONTAINS $search_term
107 |                 RETURN m.name as name, m.lang as lang
108 |                 ORDER BY m.name
109 |                 LIMIT 20
110 |             """, search_term=search_term)
111 |             return result.data()
112 | 
113 |     def find_imports(self, search_term: str) -> List[Dict]:
114 |         """Find imported symbols (aliases or original names)."""
115 |         with self.driver.session() as session:
116 |             result = session.run("""
117 |                 MATCH (f:File)-[r:IMPORTS]->(m:Module)
118 |                 WHERE r.alias = $search_term OR r.imported_name = $search_term
119 |                 RETURN 
120 |                     r.alias as alias, 
121 |                     r.imported_name as imported_name, 
122 |                     m.name as module_name, 
123 |                     f.path as file_path, 
124 |                     r.line_number as line_number
125 |                 ORDER BY f.path
126 |                 LIMIT 20
127 |             """, search_term=search_term)
128 |             return result.data()
129 | 
130 |     def find_related_code(self, user_query: str, fuzzy_search: bool, edit_distance: int) -> Dict[str, Any]:
131 |         """Find code related to a query using multiple search strategies"""
132 |         if fuzzy_search:
133 |             user_query_normalized = " ".join(map(lambda x: f"{x}~{edit_distance}", user_query.split(" ")))
134 |         else:
135 |             user_query_normalized = user_query
136 | 
137 |         results = {
138 |             "query": user_query_normalized,
139 |             "functions_by_name": self.find_by_function_name(user_query_normalized, fuzzy_search),
140 |             "classes_by_name": self.find_by_class_name(user_query_normalized, fuzzy_search),
141 |             "variables_by_name": self.find_by_variable_name(user_query),  # no fuzzy for variables as they are not using full-text index
142 |             "content_matches": self.find_by_content(user_query_normalized)
143 |         }
144 |         
145 |         all_results = []
146 |         
147 |         for func in results["functions_by_name"]:
148 |             func["search_type"] = "function_name"
149 |             func["relevance_score"] = 0.9 if not func["is_dependency"] else 0.7
150 |             all_results.append(func)
151 |         
152 |         for cls in results["classes_by_name"]:
153 |             cls["search_type"] = "class_name"
154 |             cls["relevance_score"] = 0.8 if not cls["is_dependency"] else 0.6
155 |             all_results.append(cls)
156 | 
157 |         for var in results["variables_by_name"]:
158 |             var["search_type"] = "variable_name"
159 |             var["relevance_score"] = 0.7 if not var["is_dependency"] else 0.5
160 |             all_results.append(var)
161 |         
162 |         for content in results["content_matches"]:
163 |             content["search_type"] = "content"
164 |             content["relevance_score"] = 0.6 if not content["is_dependency"] else 0.4
165 |             all_results.append(content)
166 |         
167 |         all_results.sort(key=lambda x: x["relevance_score"], reverse=True)
168 |         
169 |         results["ranked_results"] = all_results[:15]
170 |         results["total_matches"] = len(all_results)
171 |         
172 |         return results
173 |     
174 |     def find_functions_by_argument(self, argument_name: str, file_path: str = None) -> List[Dict]:
175 |         """Find functions that take a specific argument name."""
176 |         with self.driver.session() as session:
177 |             if file_path:
178 |                 query = """
179 |                     MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
180 |                     WHERE p.name = $argument_name AND f.file_path = $file_path
181 |                     RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
182 |                            f.docstring AS docstring, f.is_dependency AS is_dependency
183 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
184 |                     LIMIT 20
185 |                 """
186 |                 result = session.run(query, argument_name=argument_name, file_path=file_path)
187 |             else:
188 |                 query = """
189 |                     MATCH (f:Function)-[:HAS_PARAMETER]->(p:Parameter)
190 |                     WHERE p.name = $argument_name
191 |                     RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
192 |                            f.docstring AS docstring, f.is_dependency AS is_dependency
193 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
194 |                     LIMIT 20
195 |                 """
196 |                 result = session.run(query, argument_name=argument_name)
197 |             return result.data()
198 | 
199 |     def find_functions_by_decorator(self, decorator_name: str, file_path: str = None) -> List[Dict]:
200 |         """Find functions that have a specific decorator applied to them."""
201 |         with self.driver.session() as session:
202 |             if file_path:
203 |                 query = """
204 |                     MATCH (f:Function)
205 |                     WHERE f.file_path = $file_path AND $decorator_name IN f.decorators
206 |                     RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
207 |                            f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
208 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
209 |                     LIMIT 20
210 |                 """
211 |                 result = session.run(query, decorator_name=decorator_name, file_path=file_path)
212 |             else:
213 |                 query = """
214 |                     MATCH (f:Function)
215 |                     WHERE $decorator_name IN f.decorators
216 |                     RETURN f.name AS function_name, f.file_path AS file_path, f.line_number AS line_number,
217 |                            f.docstring AS docstring, f.is_dependency AS is_dependency, f.decorators AS decorators
218 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
219 |                     LIMIT 20
220 |                 """
221 |                 result = session.run(query, decorator_name=decorator_name)
222 |             return result.data()
223 |     
224 |     def who_calls_function(self, function_name: str, file_path: str = None) -> List[Dict]:
225 |         """Find what functions call a specific function using CALLS relationships with improved matching"""
226 |         with self.driver.session() as session:
227 |             if file_path:
228 |                 result = session.run("""
229 |                     MATCH (caller:Function)-[call:CALLS]->(target:Function {name: $function_name, file_path: $file_path})
230 |                     OPTIONAL MATCH (caller_file:File)-[:CONTAINS]->(caller)
231 |                     RETURN DISTINCT
232 |                         caller.name as caller_function,
233 |                         caller.file_path as caller_file_path,
234 |                         caller.line_number as caller_line_number,
235 |                         caller.docstring as caller_docstring,
236 |                         caller.is_dependency as caller_is_dependency,
237 |                         call.line_number as call_line_number,
238 |                         call.args as call_args,
239 |                         call.full_call_name as full_call_name,
240 |                         target.file_path as target_file_path
241 |                     ORDER BY caller.is_dependency ASC, caller.file_path, caller.line_number
242 |                     LIMIT 20
243 |                 """, function_name=function_name, file_path=file_path)
244 |                 
245 |                 results = [dict(record) for record in result]
246 |                 if not results:
247 |                     result = session.run("""
248 |                         MATCH (target:Function {name: $function_name})
249 |                         MATCH (caller:Function)-[call:CALLS]->(target)
250 |                         OPTIONAL MATCH (caller_file:File)-[:CONTAINS]->(caller)
251 |                         RETURN DISTINCT
252 |                             caller.name as caller_function,
253 |                             caller.file_path as caller_file_path,
254 |                             caller.line_number as caller_line_number,
255 |                             caller.docstring as caller_docstring,
256 |                             caller.is_dependency as caller_is_dependency,
257 |                             call.line_number as call_line_number,
258 |                             call.args as call_args,
259 |                             call.full_call_name as full_call_name,
260 |                             target.file_path as target_file_path
261 |                         ORDER BY caller.is_dependency ASC, caller.file_path, caller.line_number
262 |                         LIMIT 20
263 |                     """, function_name=function_name)
264 |                     results = [dict(record) for record in result]
265 |             else:
266 |                 result = session.run("""
267 |                     MATCH (target:Function {name: $function_name})
268 |                     MATCH (caller:Function)-[call:CALLS]->(target)
269 |                     OPTIONAL MATCH (caller_file:File)-[:CONTAINS]->(caller)
270 |                     RETURN DISTINCT
271 |                         caller.name as caller_function,
272 |                         caller.file_path as caller_file_path,
273 |                         caller.line_number as caller_line_number,
274 |                         caller.docstring as caller_docstring,
275 |                         caller.is_dependency as caller_is_dependency,
276 |                         call.line_number as call_line_number,
277 |                         call.args as call_args,
278 |                         call.full_call_name as full_call_name,
279 |                         target.file_path as target_file_path
280 |                     ORDER BY caller.is_dependency ASC, caller.file_path, caller.line_number
281 |                     LIMIT 20
282 |                 """, function_name=function_name)
283 |                 results = [dict(record) for record in result]
284 |             
285 |             return results
286 |     
287 |     def what_does_function_call(self, function_name: str, file_path: str = None) -> List[Dict]:
288 |         """Find what functions a specific function calls using CALLS relationships"""
289 |         with self.driver.session() as session:
290 |             if file_path:
291 |                 # Convert file_path to absolute path
292 |                 absolute_file_path = str(Path(file_path).resolve())
293 |                 result = session.run("""
294 |                     MATCH (caller:Function {name: $function_name, file_path: $absolute_file_path})
295 |                     MATCH (caller)-[call:CALLS]->(called:Function)
296 |                     OPTIONAL MATCH (called_file:File)-[:CONTAINS]->(called)
297 |                     RETURN DISTINCT
298 |                         called.name as called_function,
299 |                         called.file_path as called_file_path,
300 |                         called.line_number as called_line_number,
301 |                         called.docstring as called_docstring,
302 |                         called.is_dependency as called_is_dependency,
303 |                         call.line_number as call_line_number,
304 |                         call.args as call_args,
305 |                         call.full_call_name as full_call_name
306 |                     ORDER BY called.is_dependency ASC, called.name
307 |                     LIMIT 20
308 |                 """, function_name=function_name, absolute_file_path=absolute_file_path)
309 |             else:
310 |                 result = session.run("""
311 |                     MATCH (caller:Function {name: $function_name})
312 |                     MATCH (caller)-[call:CALLS]->(called:Function)
313 |                     OPTIONAL MATCH (called_file:File)-[:CONTAINS]->(called)
314 |                     RETURN DISTINCT
315 |                         called.name as called_function,
316 |                         called.file_path as called_file_path,
317 |                         called.line_number as called_line_number,
318 |                         called.docstring as called_docstring,
319 |                         called.is_dependency as called_is_dependency,
320 |                         call.line_number as call_line_number,
321 |                         call.args as call_args,
322 |                         call.full_call_name as full_call_name
323 |                     ORDER BY called.is_dependency ASC, called.name
324 |                     LIMIT 20
325 |                 """, function_name=function_name)
326 |             
327 |             return result.data()
328 |     
329 |     def who_imports_module(self, module_name: str) -> List[Dict]:
330 |         """Find what files import a specific module using IMPORTS relationships"""
331 |         with self.driver.session() as session:
332 |             result = session.run("""
333 |                 MATCH (file:File)-[imp:IMPORTS]->(module:Module)
334 |                 WHERE module.name = $module_name OR module.full_import_name CONTAINS $module_name
335 |                 OPTIONAL MATCH (repo:Repository)-[:CONTAINS]->(file)
336 |                 WITH file, repo, COLLECT({
337 |                     imported_module: module.name,
338 |                     import_alias: module.alias,
339 |                     full_import_name: module.full_import_name
340 |                 }) AS imports
341 |                 RETURN
342 |                     file.name AS file_name,
343 |                     file.path AS file_path,
344 |                     file.relative_path AS file_relative_path,
345 |                     file.is_dependency AS file_is_dependency,
346 |                     repo.name AS repository_name,
347 |                     imports
348 |                 ORDER BY file.is_dependency ASC, file.path
349 |                 LIMIT 20
350 |             """, module_name=module_name)
351 |             
352 |             return result.data()
353 |     
354 |     def who_modifies_variable(self, variable_name: str) -> List[Dict]:
355 |         """Find what functions contain or modify a specific variable"""
356 |         with self.driver.session() as session:
357 |             result = session.run("""
358 |                 MATCH (var:Variable {name: $variable_name})
359 |                 MATCH (container)-[:CONTAINS]->(var)
360 |                 WHERE container:Function OR container:Class OR container:File
361 |                 OPTIONAL MATCH (file:File)-[:CONTAINS]->(container)
362 |                 RETURN DISTINCT
363 |                     CASE 
364 |                         WHEN container:Function THEN container.name
365 |                         WHEN container:Class THEN container.name
366 |                         ELSE 'file_level'
367 |                     END as container_name,
368 |                     CASE 
369 |                         WHEN container:Function THEN 'function'
370 |                         WHEN container:Class THEN 'class'
371 |                         ELSE 'file'
372 |                     END as container_type,
373 |                     COALESCE(container.file_path, file.path) as file_path,
374 |                     container.line_number as container_line_number,
375 |                     var.line_number as variable_line_number,
376 |                     var.value as variable_value,
377 |                     var.context as variable_context,
378 |                     COALESCE(container.is_dependency, file.is_dependency, false) as is_dependency
379 |                 ORDER BY is_dependency ASC, file_path, variable_line_number
380 |                 LIMIT 20
381 |             """, variable_name=variable_name)
382 |             
383 |             return result.data()
384 |     
385 |     def find_class_hierarchy(self, class_name: str, file_path: str = None) -> Dict[str, Any]:
386 |         """Find class inheritance relationships using INHERITS relationships"""
387 |         with self.driver.session() as session:
388 |             if file_path:
389 |                 match_clause = "MATCH (child:Class {name: $class_name, file_path: $file_path})"
390 |             else:
391 |                 match_clause = "MATCH (child:Class {name: $class_name})"
392 | 
393 |             parents_query = f"""
394 |                 {match_clause}
395 |                 MATCH (child)-[:INHERITS]->(parent:Class)
396 |                 OPTIONAL MATCH (parent_file:File)-[:CONTAINS]->(parent)
397 |                 RETURN DISTINCT
398 |                     parent.name as parent_class,
399 |                     parent.file_path as parent_file_path,
400 |                     parent.line_number as parent_line_number,
401 |                     parent.docstring as parent_docstring,
402 |                     parent.is_dependency as parent_is_dependency
403 |                 ORDER BY parent.is_dependency ASC, parent.name
404 |             """
405 |             parents_result = session.run(parents_query, class_name=class_name, file_path=file_path)
406 |             
407 |             children_query = f"""
408 |                 {match_clause}
409 |                 MATCH (grandchild:Class)-[:INHERITS]->(child)
410 |                 OPTIONAL MATCH (child_file:File)-[:CONTAINS]->(grandchild)
411 |                 RETURN DISTINCT
412 |                     grandchild.name as child_class,
413 |                     grandchild.file_path as child_file_path,
414 |                     grandchild.line_number as child_line_number,
415 |                     grandchild.docstring as child_docstring,
416 |                     grandchild.is_dependency as child_is_dependency
417 |                 ORDER BY grandchild.is_dependency ASC, grandchild.name
418 |             """
419 |             children_result = session.run(children_query, class_name=class_name, file_path=file_path)
420 |             
421 |             methods_query = f"""
422 |                 {match_clause}
423 |                 MATCH (child)-[:CONTAINS]->(method:Function)
424 |                 RETURN DISTINCT
425 |                     method.name as method_name,
426 |                     method.file_path as method_file_path,
427 |                     method.line_number as method_line_number,
428 |                     method.args as method_args,
429 |                     method.docstring as method_docstring,
430 |                     method.is_dependency as method_is_dependency
431 |                 ORDER BY method.is_dependency ASC, method.line_number
432 |             """
433 |             methods_result = session.run(methods_query, class_name=class_name, file_path=file_path)
434 |             
435 |             return {
436 |                 "class_name": class_name,
437 |                 "parent_classes": [dict(record) for record in parents_result],
438 |                 "child_classes": [dict(record) for record in children_result],
439 |                 "methods": [dict(record) for record in methods_result]
440 |             }
441 |     
442 |     def find_function_overrides(self, function_name: str) -> List[Dict]:
443 |         """Find all implementations of a function across different classes"""
444 |         with self.driver.session() as session:
445 |             result = session.run("""
446 |                 MATCH (class:Class)-[:CONTAINS]->(func:Function {name: $function_name})
447 |                 OPTIONAL MATCH (file:File)-[:CONTAINS]->(class)
448 |                 RETURN DISTINCT
449 |                     class.name as class_name,
450 |                     class.file_path as class_file_path,
451 |                     func.name as function_name,
452 |                     func.line_number as function_line_number,
453 |                     func.args as function_args,
454 |                     func.docstring as function_docstring,
455 |                     func.is_dependency as is_dependency,
456 |                     file.name as file_name
457 |                 ORDER BY func.is_dependency ASC, class.name
458 |                 LIMIT 20
459 |             """, function_name=function_name)
460 |             
461 |             return result.data()
462 |     
463 |     def find_dead_code(self, exclude_decorated_with: List[str] = None) -> Dict[str, Any]:
464 |         """Find potentially unused functions (not called by other functions in the project), optionally excluding those with specific decorators."""
465 |         if exclude_decorated_with is None:
466 |             exclude_decorated_with = []
467 | 
468 |         with self.driver.session() as session:
469 |             result = session.run("""
470 |                 MATCH (func:Function)
471 |                 WHERE func.is_dependency = false
472 |                   AND NOT func.name IN ['main', '__init__', '__main__', 'setup', 'run', '__new__', '__del__']
473 |                   AND NOT func.name STARTS WITH '_test'
474 |                   AND NOT func.name STARTS WITH 'test_'
475 |                   AND ALL(decorator_name IN $exclude_decorated_with WHERE NOT decorator_name IN func.decorators)
476 |                 WITH func
477 |                 OPTIONAL MATCH (caller:Function)-[:CALLS]->(func)
478 |                 WHERE caller.is_dependency = false
479 |                 WITH func, count(caller) as caller_count
480 |                 WHERE caller_count = 0
481 |                 OPTIONAL MATCH (file:File)-[:CONTAINS]->(func)
482 |                 RETURN
483 |                     func.name as function_name,
484 |                     func.file_path as file_path,
485 |                     func.line_number as line_number,
486 |                     func.docstring as docstring,
487 |                     func.context as context,
488 |                     file.name as file_name
489 |                 ORDER BY func.file_path, func.line_number
490 |                 LIMIT 50
491 |             """, exclude_decorated_with=exclude_decorated_with)
492 |             
493 |             return {
494 |                 "potentially_unused_functions": [dict(record) for record in result],
495 |                 "note": "These functions might be unused, but could be entry points, callbacks, or called dynamically"
496 |             }
497 |     
498 |     def find_all_callers(self, function_name: str, file_path: str = None) -> List[Dict]:
499 |         """Find all direct and indirect callers of a specific function."""
500 |         with self.driver.session() as session:
501 |             if file_path:
502 |                 # Find functions within the specified file_path that call the target function
503 |                 query = """
504 |                     MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name, file_path: $file_path})
505 |                     RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
506 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
507 |                     LIMIT 50
508 |                 """
509 |                 result = session.run(query, function_name=function_name, file_path=file_path)
510 |             else:
511 |                 # If no file_path (context) is provided, find all callers of the function by name
512 |                 query = """
513 |                     MATCH (f:Function)-[:CALLS*]->(target:Function {name: $function_name})
514 |                     RETURN DISTINCT f.name AS caller_name, f.file_path AS caller_file_path, f.line_number AS caller_line_number, f.is_dependency AS caller_is_dependency
515 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
516 |                     LIMIT 50
517 |                 """
518 |                 result = session.run(query, function_name=function_name)
519 |             return result.data()
520 | 
521 |     def find_all_callees(self, function_name: str, file_path: str = None) -> List[Dict]:
522 |         """Find all direct and indirect callees of a specific function."""
523 |         with self.driver.session() as session:
524 |             if file_path:
525 |                 query = """
526 |                     MATCH (caller:Function {name: $function_name, file_path: $file_path})
527 |                     MATCH (caller)-[:CALLS*]->(f:Function)
528 |                     RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
529 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
530 |                     LIMIT 50
531 |                 """
532 |                 result = session.run(query, function_name=function_name, file_path=file_path)
533 |             else:
534 |                 query = """
535 |                     MATCH (caller:Function {name: $function_name})
536 |                     MATCH (caller)-[:CALLS*]->(f:Function)
537 |                     RETURN DISTINCT f.name AS callee_name, f.file_path AS callee_file_path, f.line_number AS callee_line_number, f.is_dependency AS callee_is_dependency
538 |                     ORDER BY f.is_dependency ASC, f.file_path, f.line_number
539 |                     LIMIT 50
540 |                 """
541 |                 result = session.run(query, function_name=function_name)
542 |             return result.data()
543 | 
544 |     def find_function_call_chain(self, start_function: str, end_function: str, max_depth: int = 5, start_file: str = None, end_file: str = None) -> List[Dict]:
545 |         """Find call chains between two functions"""
546 |         with self.driver.session() as session:
547 |             # Build match clauses based on whether files are specified
548 |             start_props = "{name: $start_function" + (", file_path: $start_file}" if start_file else "}")
549 |             end_props = "{name: $end_function" + (", file_path: $end_file}" if end_file else "}")
550 | 
551 |             query = f"""
552 |                 MATCH (start:Function {start_props}), (end:Function {end_props})
553 |                 WITH start, end
554 |                 MATCH path = (start)-[:CALLS*1..{max_depth}]->(end)
555 |                 WHERE path IS NOT NULL
556 |                 WITH path, nodes(path) as func_nodes, relationships(path) as call_rels
557 |                 RETURN 
558 |                     [node in func_nodes | {{
559 |                         name: node.name,
560 |                         file_path: node.file_path,
561 |                         line_number: node.line_number,
562 |                         is_dependency: node.is_dependency
563 |                     }}] as function_chain,
564 |                     [rel in call_rels | {{
565 |                         call_line: rel.line_number,
566 |                         args: rel.args,
567 |                         full_call_name: rel.full_call_name
568 |                     }}] as call_details,
569 |                     length(path) as chain_length
570 |                 ORDER BY chain_length ASC
571 |                 LIMIT 20
572 |             """
573 |             
574 |             # Prepare parameters
575 |             params = {
576 |                 "start_function": start_function,
577 |                 "end_function": end_function,
578 |                 "start_file": start_file,
579 |                 "end_file": end_file
580 |             }
581 |             
582 |             result = session.run(query, **params)
583 |             return result.data()
584 | 
585 |     def find_by_type(self, element_type: str, limit: int = 50) -> List[Dict]:
586 |         """Find all elements of a specific type (Function, Class, File, Module)."""
587 |         # Map input type to node label
588 |         type_map = {
589 |             "function": "Function",
590 |             "class": "Class",
591 |             "file": "File",
592 |             "module": "Module"
593 |         }
594 |         label = type_map.get(element_type.lower())
595 |         
596 |         if not label:
597 |             return []
598 |             
599 |         with self.driver.session() as session:
600 |             if label == "File":
601 |                 query = f"""
602 |                     MATCH (n:File)
603 |                     RETURN n.name as name, n.path as file_path, n.is_dependency as is_dependency
604 |                     ORDER BY n.path
605 |                     LIMIT $limit
606 |                 """
607 |             elif label == "Module":
608 |                 query = f"""
609 |                     MATCH (n:Module)
610 |                     RETURN n.name as name, n.name as file_path, false as is_dependency
611 |                     ORDER BY n.name
612 |                     LIMIT $limit
613 |                 """
614 |             else:
615 |                 query = f"""
616 |                     MATCH (n:{label})
617 |                     RETURN n.name as name, n.file_path as file_path, n.line_number as line_number, n.is_dependency as is_dependency
618 |                     ORDER BY n.is_dependency ASC, n.name
619 |                     LIMIT $limit
620 |                 """
621 |             
622 |             result = session.run(query, limit=limit)
623 |             return result.data()
624 |     
625 |     def find_module_dependencies(self, module_name: str) -> Dict[str, Any]:
626 |         """Find all dependencies and dependents of a module"""
627 |         with self.driver.session() as session:
628 |             # Find files that import this module (who imports this module)
629 |             importers_result = session.run("""
630 |                 MATCH (file:File)-[imp:IMPORTS]->(module:Module {name: $module_name})
631 |                 OPTIONAL MATCH (repo:Repository)-[:CONTAINS]->(file)
632 |                 RETURN DISTINCT
633 |                     file.path as importer_file_path,
634 |                     imp.line_number as import_line_number,
635 |                     file.is_dependency as file_is_dependency,
636 |                     repo.name as repository_name
637 |                 ORDER BY file.is_dependency ASC, file.path
638 |                 LIMIT 50
639 |             """, module_name=module_name)
640 |             
641 |             # Find modules that are imported by files that also import the target module
642 |             # This helps understand what this module is typically used with
643 |             imports_result = session.run("""
644 |                 MATCH (file:File)-[:IMPORTS]->(target_module:Module {name: $module_name})
645 |                 MATCH (file)-[imp:IMPORTS]->(other_module:Module)
646 |                 WHERE other_module <> target_module
647 |                 RETURN DISTINCT
648 |                     other_module.name as imported_module,
649 |                     imp.alias as import_alias
650 |                 ORDER BY other_module.name
651 |                 LIMIT 50
652 |             """, module_name=module_name)
653 |             
654 |             return {
655 |                 "module_name": module_name,
656 |                 "importers": [dict(record) for record in importers_result],
657 |                 "imports": [dict(record) for record in imports_result]
658 |             }
659 |     
660 |     def find_variable_usage_scope(self, variable_name: str, file_path: str = None) -> Dict[str, Any]:
661 |         """Find the scope and usage patterns of a variable, optional file path filtering"""
662 |         with self.driver.session() as session:
663 |             if file_path:
664 |                 variable_instances = session.run("""
665 |                     MATCH (var:Variable {name: $variable_name})
666 |                     WHERE var.file_path ENDS WITH $file_path OR var.file_path = $file_path
667 |                     OPTIONAL MATCH (container)-[:CONTAINS]->(var)
668 |                     WHERE container:Function OR container:Class OR container:File
669 |                     OPTIONAL MATCH (file:File)-[:CONTAINS]->(var)
670 |                     RETURN DISTINCT
671 |                         var.name as variable_name,
672 |                         var.value as variable_value,
673 |                         var.line_number as line_number,
674 |                         var.context as context,
675 |                         COALESCE(var.file_path, file.path) as file_path,
676 |                         CASE 
677 |                         WHEN container:Function THEN 'function'
678 |                         WHEN container:Class THEN 'class'
679 |                         ELSE 'module'
680 |                     END as scope_type,
681 |                     CASE 
682 |                         WHEN container:Function THEN container.name
683 |                         WHEN container:Class THEN container.name
684 |                         ELSE 'module_level'
685 |                     END as scope_name,
686 |                     var.is_dependency as is_dependency
687 |                 ORDER BY var.is_dependency ASC, file_path, line_number
688 |             """, variable_name=variable_name, file_path=file_path)
689 |             else:
690 |                 variable_instances = session.run("""
691 |                     MATCH (var:Variable {name: $variable_name})
692 |                     OPTIONAL MATCH (container)-[:CONTAINS]->(var)
693 |                     WHERE container:Function OR container:Class OR container:File
694 |                     OPTIONAL MATCH (file:File)-[:CONTAINS]->(var)
695 |                     RETURN DISTINCT
696 |                         var.name as variable_name,
697 |                         var.value as variable_value,
698 |                         var.line_number as line_number,
699 |                         var.context as context,
700 |                         COALESCE(var.file_path, file.path) as file_path,
701 |                         CASE 
702 |                             WHEN container:Function THEN 'function'
703 |                             WHEN container:Class THEN 'class'
704 |                             ELSE 'module'
705 |                         END as scope_type,
706 |                         CASE 
707 |                             WHEN container:Function THEN container.name
708 |                             WHEN container:Class THEN container.name
709 |                             ELSE 'module_level'
710 |                         END as scope_name,
711 |                         var.is_dependency as is_dependency
712 |                     ORDER BY var.is_dependency ASC, file_path, line_number
713 |                 """, variable_name=variable_name)
714 |             
715 |             return {
716 |                 "variable_name": variable_name,
717 |                 "instances": [dict(record) for record in variable_instances]
718 |             }
719 |     
720 |     def analyze_code_relationships(self, query_type: str, target: str, context: str = None) -> Dict[str, Any]:
721 |         """Main method to analyze different types of code relationships with fixed return types"""
722 |         query_type = query_type.lower().strip()
723 |         
724 |         try:
725 |             if query_type == "find_callers":
726 |                 results = self.who_calls_function(target, context)
727 |                 return {
728 |                     "query_type": "find_callers", "target": target, "context": context, "results": results,
729 |                     "summary": f"Found {len(results)} functions that call '{target}'"
730 |                 }
731 |             
732 |             elif query_type == "find_callees":
733 |                 results = self.what_does_function_call(target, context)
734 |                 return {
735 |                     "query_type": "find_callees", "target": target, "context": context, "results": results,
736 |                     "summary": f"Function '{target}' calls {len(results)} other functions"
737 |                 }
738 |                 
739 |             elif query_type == "find_importers":
740 |                 results = self.who_imports_module(target)
741 |                 return {
742 |                     "query_type": "find_importers", "target": target, "results": results,
743 |                     "summary": f"Found {len(results)} files that import '{target}'"
744 |                 }
745 |                 
746 |             elif query_type == "find_functions_by_argument":
747 |                 results = self.find_functions_by_argument(target, context)
748 |                 return {
749 |                     "query_type": "find_functions_by_argument", "target": target, "context": context, "results": results,
750 |                     "summary": f"Found {len(results)} functions that take '{target}' as an argument"
751 |                 }
752 |             
753 |             elif query_type == "find_functions_by_decorator":
754 |                 results = self.find_functions_by_decorator(target, context)
755 |                 return {
756 |                     "query_type": "find_functions_by_decorator", "target": target, "context": context, "results": results,
757 |                     "summary": f"Found {len(results)} functions decorated with '{target}'"
758 |                 }
759 |                 
760 |             elif query_type in ["who_modifies", "modifies", "mutations", "changes", "variable_usage"]:
761 |                 results = self.who_modifies_variable(target)
762 |                 return {
763 |                     "query_type": "who_modifies", "target": target, "results": results,
764 |                     "summary": f"Found {len(results)} containers that hold variable '{target}'"
765 |                 }
766 |             
767 |             elif query_type in ["class_hierarchy", "inheritance", "extends"]:
768 |                 results = self.find_class_hierarchy(target, context)
769 |                 return {
770 |                     "query_type": "class_hierarchy", "target": target, "results": results,
771 |                     "summary": f"Class '{target}' has {len(results['parent_classes'])} parents, {len(results['child_classes'])} children, and {len(results['methods'])} methods"
772 |                 }
773 |             
774 |             elif query_type in ["overrides", "implementations", "polymorphism"]:
775 |                 results = self.find_function_overrides(target)
776 |                 return {
777 |                     "query_type": "overrides", "target": target, "results": results,
778 |                     "summary": f"Found {len(results)} implementations of function '{target}'"
779 |                 }
780 |             
781 |             elif query_type in ["dead_code", "unused", "unreachable"]:
782 |                 results = self.find_dead_code()
783 |                 return {
784 |                     "query_type": "dead_code", "results": results,
785 |                     "summary": f"Found {len(results['potentially_unused_functions'])} potentially unused functions"
786 |                 }
787 |             
788 |             elif query_type == "find_complexity":
789 |                 limit = int(context) if context and context.isdigit() else 10
790 |                 results = self.find_most_complex_functions(limit)
791 |                 return {
792 |                     "query_type": "find_complexity", "limit": limit, "results": results,
793 |                     "summary": f"Found the top {len(results)} most complex functions"
794 |                 }
795 |             
796 |             elif query_type == "find_all_callers":
797 |                 results = self.find_all_callers(target, context)
798 |                 return {
799 |                     "query_type": "find_all_callers", "target": target, "context": context, "results": results,
800 |                     "summary": f"Found {len(results)} direct and indirect callers of '{target}'"
801 |                 }
802 | 
803 |             elif query_type == "find_all_callees":
804 |                 results = self.find_all_callees(target, context)
805 |                 return {
806 |                     "query_type": "find_all_callees", "target": target, "context": context, "results": results,
807 |                     "summary": f"Found {len(results)} direct and indirect callees of '{target}'"
808 |                 }
809 |                 
810 |             elif query_type in ["call_chain", "path", "chain"]:
811 |                 if '->' in target:
812 |                     start_func, end_func = target.split('->', 1)
813 |                     # max_depth can be passed as context, default to 5 if not provided or invalid
814 |                     max_depth = int(context) if context and context.isdigit() else 5
815 |                     results = self.find_function_call_chain(start_func.strip(), end_func.strip(), max_depth)
816 |                     return {
817 |                         "query_type": "call_chain", "target": target, "results": results,
818 |                         "summary": f"Found {len(results)} call chains from '{start_func.strip()}' to '{end_func.strip()}' (max depth: {max_depth})"
819 |                     }
820 |                 else:
821 |                     return {
822 |                         "error": "For call_chain queries, use format 'start_function->end_function'",
823 |                         "example": "main->process_data"
824 |                     }
825 |             
826 |             elif query_type in ["module_deps", "module_dependencies", "module_usage"]:
827 |                 results = self.find_module_dependencies(target)
828 |                 return {
829 |                     "query_type": "module_dependencies", "target": target, "results": results,
830 |                     "summary": f"Module '{target}' is imported by {len(results['imported_by_files'])} files"
831 |                 }
832 |             
833 |             elif query_type in ["variable_scope", "var_scope", "variable_usage_scope"]:
834 |                 results = self.find_variable_usage_scope(target)
835 |                 return {
836 |                     "query_type": "variable_scope", "target": target, "results": results,
837 |                     "summary": f"Variable '{target}' has {len(results['instances'])} instances across different scopes"
838 |                 }
839 |             
840 |             else:
841 |                 return {
842 |                     "error": f"Unknown query type: {query_type}",
843 |                     "supported_types": [
844 |                         "find_callers", "find_callees", "find_importers", "who_modifies",
845 |                         "class_hierarchy", "overrides", "dead_code", "call_chain",
846 |                         "module_deps", "variable_scope", "find_complexity"
847 |                     ]
848 |                 }
849 |         
850 |         except Exception as e:
851 |             return {
852 |                 "error": f"Error executing relationship query: {str(e)}",
853 |                 "query_type": query_type,
854 |                 "target": target
855 |             }
856 | 
857 |     def get_cyclomatic_complexity(self, function_name: str, file_path: str = None) -> Optional[Dict]:
858 |         """Get the cyclomatic complexity of a function."""
859 |         with self.driver.session() as session:
860 |             if file_path:
861 |                 # Use ENDS WITH for flexible path matching, or exact match
862 |                 query = """
863 |                     MATCH (f:Function {name: $function_name})
864 |                     WHERE f.file_path ENDS WITH $file_path OR f.file_path = $file_path
865 |                     RETURN f.name as function_name, f.cyclomatic_complexity as complexity,
866 |                            f.file_path as file_path, f.line_number as line_number
867 |                 """
868 |                 result = session.run(query, function_name=function_name, file_path=file_path)
869 |             else:
870 |                 query = """
871 |                     MATCH (f:Function {name: $function_name})
872 |                     RETURN f.name as function_name, f.cyclomatic_complexity as complexity,
873 |                            f.file_path as file_path, f.line_number as line_number
874 |                 """
875 |                 result = session.run(query, function_name=function_name)
876 |             
877 |             result_data = result.data()
878 |             if result_data:
879 |                 return result_data[0]
880 |             return None
881 | 
882 |     def find_most_complex_functions(self, limit: int = 10) -> List[Dict]:
883 |         """Find the most complex functions based on cyclomatic complexity."""
884 |         with self.driver.session() as session:
885 |             query = """
886 |                 MATCH (f:Function)
887 |                 WHERE f.cyclomatic_complexity IS NOT NULL AND f.is_dependency = false
888 |                 RETURN f.name as function_name, f.file_path as file_path, f.cyclomatic_complexity as complexity, f.line_number as line_number
889 |                 ORDER BY f.cyclomatic_complexity DESC
890 |                 LIMIT $limit
891 |             """
892 |             result = session.run(query, limit=limit)
893 |             return result.data()
894 | 
895 |     def list_indexed_repositories(self) -> List[Dict]:
896 |         """List all indexed repositories."""
897 |         with self.driver.session() as session:
898 |             result = session.run("""
899 |                 MATCH (r:Repository)
900 |                 RETURN r.name as name, r.path as path, r.is_dependency as is_dependency
901 |                 ORDER BY r.name
902 |             """)
903 |             return result.data()
904 | 
```
Page 17/22FirstPrevNextLast