This is page 13 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
--------------------------------------------------------------------------------
/docs/site/tools/index.html:
--------------------------------------------------------------------------------
```html
1 |
2 | <!doctype html>
3 | <html lang="en" class="no-js">
4 | <head>
5 |
6 | <meta charset="utf-8">
7 | <meta name="viewport" content="width=device-width,initial-scale=1">
8 |
9 |
10 |
11 |
12 | <link rel="prev" href="../core/">
13 |
14 |
15 | <link rel="next" href="../cookbook/">
16 |
17 |
18 |
19 |
20 |
21 | <link rel="icon" href="../assets/images/favicon.png">
22 | <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
23 |
24 |
25 |
26 | <title>Tools - CodeGraphContext</title>
27 |
28 |
29 |
30 | <link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
44 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
45 | <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
46 |
47 |
48 |
49 | <script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
50 |
51 |
52 |
53 |
54 |
55 | </head>
56 |
57 |
58 | <body dir="ltr">
59 |
60 |
61 | <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
62 | <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
63 | <label class="md-overlay" for="__drawer"></label>
64 | <div data-md-component="skip">
65 |
66 |
67 | <a href="#tools" class="md-skip">
68 | Skip to content
69 | </a>
70 |
71 | </div>
72 | <div data-md-component="announce">
73 |
74 | </div>
75 |
76 |
77 |
78 |
79 |
80 |
81 | <header class="md-header md-header--shadow" data-md-component="header">
82 | <nav class="md-header__inner md-grid" aria-label="Header">
83 | <a href=".." title="CodeGraphContext" class="md-header__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
84 |
85 |
86 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
87 |
88 | </a>
89 | <label class="md-header__button md-icon" for="__drawer">
90 |
91 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
92 | </label>
93 | <div class="md-header__title" data-md-component="header-title">
94 | <div class="md-header__ellipsis">
95 | <div class="md-header__topic">
96 | <span class="md-ellipsis">
97 | CodeGraphContext
98 | </span>
99 | </div>
100 | <div class="md-header__topic" data-md-component="header-topic">
101 | <span class="md-ellipsis">
102 |
103 | Tools
104 |
105 | </span>
106 | </div>
107 | </div>
108 | </div>
109 |
110 |
111 | <script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
112 |
113 |
114 |
115 |
116 |
117 | <label class="md-header__button md-icon" for="__search">
118 |
119 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
120 | </label>
121 | <div class="md-search" data-md-component="search" role="dialog">
122 | <label class="md-search__overlay" for="__search"></label>
123 | <div class="md-search__inner" role="search">
124 | <form class="md-search__form" name="search">
125 | <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
126 | <label class="md-search__icon md-icon" for="__search">
127 |
128 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
129 |
130 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
131 | </label>
132 | <nav class="md-search__options" aria-label="Search">
133 |
134 | <button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
135 |
136 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
137 | </button>
138 | </nav>
139 |
140 | </form>
141 | <div class="md-search__output">
142 | <div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
143 | <div class="md-search-result" data-md-component="search-result">
144 | <div class="md-search-result__meta">
145 | Initializing search
146 | </div>
147 | <ol class="md-search-result__list" role="presentation"></ol>
148 | </div>
149 | </div>
150 | </div>
151 | </div>
152 | </div>
153 |
154 |
155 |
156 | </nav>
157 |
158 | </header>
159 |
160 | <div class="md-container" data-md-component="container">
161 |
162 |
163 |
164 |
165 |
166 |
167 | <main class="md-main" data-md-component="main">
168 | <div class="md-main__inner md-grid">
169 |
170 |
171 |
172 | <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
173 | <div class="md-sidebar__scrollwrap">
174 | <div class="md-sidebar__inner">
175 |
176 |
177 |
178 |
179 | <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
180 | <label class="md-nav__title" for="__drawer">
181 | <a href=".." title="CodeGraphContext" class="md-nav__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
182 |
183 |
184 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
185 |
186 | </a>
187 | CodeGraphContext
188 | </label>
189 |
190 | <ul class="md-nav__list" data-md-scrollfix>
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | <li class="md-nav__item">
199 | <a href=".." class="md-nav__link">
200 |
201 |
202 |
203 | <span class="md-ellipsis">
204 |
205 |
206 | Home
207 |
208 |
209 |
210 | </span>
211 |
212 |
213 |
214 | </a>
215 | </li>
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | <li class="md-nav__item">
226 | <a href="../installation/" class="md-nav__link">
227 |
228 |
229 |
230 | <span class="md-ellipsis">
231 |
232 |
233 | Installation
234 |
235 |
236 |
237 | </span>
238 |
239 |
240 |
241 | </a>
242 | </li>
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | <li class="md-nav__item">
253 | <a href="../use_cases/" class="md-nav__link">
254 |
255 |
256 |
257 | <span class="md-ellipsis">
258 |
259 |
260 | Use Cases
261 |
262 |
263 |
264 | </span>
265 |
266 |
267 |
268 | </a>
269 | </li>
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | <li class="md-nav__item">
280 | <a href="../architecture/" class="md-nav__link">
281 |
282 |
283 |
284 | <span class="md-ellipsis">
285 |
286 |
287 | Architecture
288 |
289 |
290 |
291 | </span>
292 |
293 |
294 |
295 | </a>
296 | </li>
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 | <li class="md-nav__item">
307 | <a href="../cli/" class="md-nav__link">
308 |
309 |
310 |
311 | <span class="md-ellipsis">
312 |
313 |
314 | CLI Reference
315 |
316 |
317 |
318 | </span>
319 |
320 |
321 |
322 | </a>
323 | </li>
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 | <li class="md-nav__item">
334 | <a href="../watching/" class="md-nav__link">
335 |
336 |
337 |
338 | <span class="md-ellipsis">
339 |
340 |
341 | Live Watching
342 |
343 |
344 |
345 | </span>
346 |
347 |
348 |
349 | </a>
350 | </li>
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 | <li class="md-nav__item">
361 | <a href="../server/" class="md-nav__link">
362 |
363 |
364 |
365 | <span class="md-ellipsis">
366 |
367 |
368 | Server
369 |
370 |
371 |
372 | </span>
373 |
374 |
375 |
376 | </a>
377 | </li>
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 | <li class="md-nav__item">
388 | <a href="../core/" class="md-nav__link">
389 |
390 |
391 |
392 | <span class="md-ellipsis">
393 |
394 |
395 | Core Concepts
396 |
397 |
398 |
399 | </span>
400 |
401 |
402 |
403 | </a>
404 | </li>
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 | <li class="md-nav__item md-nav__item--active">
417 |
418 | <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
419 |
420 |
421 |
422 |
423 |
424 | <label class="md-nav__link md-nav__link--active" for="__toc">
425 |
426 |
427 |
428 | <span class="md-ellipsis">
429 |
430 |
431 | Tools
432 |
433 |
434 |
435 | </span>
436 |
437 |
438 |
439 | <span class="md-nav__icon md-icon"></span>
440 | </label>
441 |
442 | <a href="./" class="md-nav__link md-nav__link--active">
443 |
444 |
445 |
446 | <span class="md-ellipsis">
447 |
448 |
449 | Tools
450 |
451 |
452 |
453 | </span>
454 |
455 |
456 |
457 | </a>
458 |
459 |
460 |
461 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
462 |
463 |
464 |
465 |
466 |
467 |
468 | <label class="md-nav__title" for="__toc">
469 | <span class="md-nav__icon md-icon"></span>
470 | Table of contents
471 | </label>
472 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
473 |
474 | <li class="md-nav__item">
475 | <a href="#graphbuilder" class="md-nav__link">
476 | <span class="md-ellipsis">
477 |
478 | GraphBuilder
479 |
480 | </span>
481 | </a>
482 |
483 | <nav class="md-nav" aria-label="GraphBuilder">
484 | <ul class="md-nav__list">
485 |
486 | <li class="md-nav__item">
487 | <a href="#treesitterparser" class="md-nav__link">
488 | <span class="md-ellipsis">
489 |
490 | TreeSitterParser
491 |
492 | </span>
493 | </a>
494 |
495 | </li>
496 |
497 | <li class="md-nav__item">
498 | <a href="#graph-building-process" class="md-nav__link">
499 | <span class="md-ellipsis">
500 |
501 | Graph Building Process
502 |
503 | </span>
504 | </a>
505 |
506 | </li>
507 |
508 | </ul>
509 | </nav>
510 |
511 | </li>
512 |
513 | <li class="md-nav__item">
514 | <a href="#codefinder" class="md-nav__link">
515 | <span class="md-ellipsis">
516 |
517 | CodeFinder
518 |
519 | </span>
520 | </a>
521 |
522 | <nav class="md-nav" aria-label="CodeFinder">
523 | <ul class="md-nav__list">
524 |
525 | <li class="md-nav__item">
526 | <a href="#key-methods" class="md-nav__link">
527 | <span class="md-ellipsis">
528 |
529 | Key Methods
530 |
531 | </span>
532 | </a>
533 |
534 | </li>
535 |
536 | </ul>
537 | </nav>
538 |
539 | </li>
540 |
541 | <li class="md-nav__item">
542 | <a href="#importextractor" class="md-nav__link">
543 | <span class="md-ellipsis">
544 |
545 | ImportExtractor
546 |
547 | </span>
548 | </a>
549 |
550 | </li>
551 |
552 | </ul>
553 |
554 | </nav>
555 |
556 | </li>
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 | <li class="md-nav__item">
567 | <a href="../cookbook/" class="md-nav__link">
568 |
569 |
570 |
571 | <span class="md-ellipsis">
572 |
573 |
574 | Cookbook
575 |
576 |
577 |
578 | </span>
579 |
580 |
581 |
582 | </a>
583 | </li>
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 | <li class="md-nav__item md-nav__item--nested">
599 |
600 |
601 |
602 | <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_11" >
603 |
604 |
605 | <label class="md-nav__link" for="__nav_11" id="__nav_11_label" tabindex="0">
606 |
607 |
608 |
609 | <span class="md-ellipsis">
610 |
611 |
612 | Contributing
613 |
614 |
615 |
616 | </span>
617 |
618 |
619 |
620 | <span class="md-nav__icon md-icon"></span>
621 | </label>
622 |
623 | <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_11_label" aria-expanded="false">
624 | <label class="md-nav__title" for="__nav_11">
625 | <span class="md-nav__icon md-icon"></span>
626 |
627 |
628 | Contributing
629 |
630 |
631 | </label>
632 | <ul class="md-nav__list" data-md-scrollfix>
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 | <li class="md-nav__item">
641 | <a href="../contributing/" class="md-nav__link">
642 |
643 |
644 |
645 | <span class="md-ellipsis">
646 |
647 |
648 | Overview
649 |
650 |
651 |
652 | </span>
653 |
654 |
655 |
656 | </a>
657 | </li>
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 | <li class="md-nav__item">
669 | <a href="../contributing_languages/" class="md-nav__link">
670 |
671 |
672 |
673 | <span class="md-ellipsis">
674 |
675 |
676 | Adding New Languages
677 |
678 |
679 |
680 | </span>
681 |
682 |
683 |
684 | </a>
685 | </li>
686 |
687 |
688 |
689 |
690 | </ul>
691 | </nav>
692 |
693 | </li>
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 | <li class="md-nav__item">
704 | <a href="../troubleshooting/" class="md-nav__link">
705 |
706 |
707 |
708 | <span class="md-ellipsis">
709 |
710 |
711 | Troubleshooting
712 |
713 |
714 |
715 | </span>
716 |
717 |
718 |
719 | </a>
720 | </li>
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 | <li class="md-nav__item">
731 | <a href="../future_work/" class="md-nav__link">
732 |
733 |
734 |
735 | <span class="md-ellipsis">
736 |
737 |
738 | Future Work
739 |
740 |
741 |
742 | </span>
743 |
744 |
745 |
746 | </a>
747 | </li>
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 | <li class="md-nav__item">
758 | <a href="../license/" class="md-nav__link">
759 |
760 |
761 |
762 | <span class="md-ellipsis">
763 |
764 |
765 | License
766 |
767 |
768 |
769 | </span>
770 |
771 |
772 |
773 | </a>
774 | </li>
775 |
776 |
777 |
778 | </ul>
779 | </nav>
780 | </div>
781 | </div>
782 | </div>
783 |
784 |
785 |
786 | <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
787 | <div class="md-sidebar__scrollwrap">
788 | <div class="md-sidebar__inner">
789 |
790 |
791 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
792 |
793 |
794 |
795 |
796 |
797 |
798 | <label class="md-nav__title" for="__toc">
799 | <span class="md-nav__icon md-icon"></span>
800 | Table of contents
801 | </label>
802 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
803 |
804 | <li class="md-nav__item">
805 | <a href="#graphbuilder" class="md-nav__link">
806 | <span class="md-ellipsis">
807 |
808 | GraphBuilder
809 |
810 | </span>
811 | </a>
812 |
813 | <nav class="md-nav" aria-label="GraphBuilder">
814 | <ul class="md-nav__list">
815 |
816 | <li class="md-nav__item">
817 | <a href="#treesitterparser" class="md-nav__link">
818 | <span class="md-ellipsis">
819 |
820 | TreeSitterParser
821 |
822 | </span>
823 | </a>
824 |
825 | </li>
826 |
827 | <li class="md-nav__item">
828 | <a href="#graph-building-process" class="md-nav__link">
829 | <span class="md-ellipsis">
830 |
831 | Graph Building Process
832 |
833 | </span>
834 | </a>
835 |
836 | </li>
837 |
838 | </ul>
839 | </nav>
840 |
841 | </li>
842 |
843 | <li class="md-nav__item">
844 | <a href="#codefinder" class="md-nav__link">
845 | <span class="md-ellipsis">
846 |
847 | CodeFinder
848 |
849 | </span>
850 | </a>
851 |
852 | <nav class="md-nav" aria-label="CodeFinder">
853 | <ul class="md-nav__list">
854 |
855 | <li class="md-nav__item">
856 | <a href="#key-methods" class="md-nav__link">
857 | <span class="md-ellipsis">
858 |
859 | Key Methods
860 |
861 | </span>
862 | </a>
863 |
864 | </li>
865 |
866 | </ul>
867 | </nav>
868 |
869 | </li>
870 |
871 | <li class="md-nav__item">
872 | <a href="#importextractor" class="md-nav__link">
873 | <span class="md-ellipsis">
874 |
875 | ImportExtractor
876 |
877 | </span>
878 | </a>
879 |
880 | </li>
881 |
882 | </ul>
883 |
884 | </nav>
885 | </div>
886 | </div>
887 | </div>
888 |
889 |
890 |
891 | <div class="md-content" data-md-component="content">
892 |
893 | <article class="md-content__inner md-typeset">
894 |
895 |
896 |
897 |
898 |
899 | <h1 id="tools">Tools</h1>
900 | <p>The <code>tools</code> directory contains the logic for code analysis, including building the graph, finding code, and extracting imports.</p>
901 | <h2 id="graphbuilder"><code>GraphBuilder</code></h2>
902 | <p>The <code>GraphBuilder</code> class in <code>graph_builder.py</code> is responsible for parsing the source code and building the graph representation that is stored in the Neo4j database.</p>
903 | <h3 id="treesitterparser"><code>TreeSitterParser</code></h3>
904 | <p><code>GraphBuilder</code> uses the <code>TreeSitterParser</code> class, which is a generic parser wrapper for a specific language using the tree-sitter library. This allows CodeGraphContext to support multiple programming languages in a modular way.</p>
905 | <h3 id="graph-building-process">Graph Building Process</h3>
906 | <p>The graph building process consists of several steps:</p>
907 | <ol>
908 | <li><strong>Pre-scan for Imports:</strong> A quick scan of all files to build a global map of where every symbol is defined.</li>
909 | <li><strong>Parse Files:</strong> Each file is parsed in detail to extract its structure, including functions, classes, variables, and imports.</li>
910 | <li><strong>Add Nodes to Graph:</strong> The extracted code elements are added to the graph as nodes.</li>
911 | <li><strong>Create Relationships:</strong> Relationships between the nodes are created, such as <code>CALLS</code> for function calls and <code>INHERITS</code> for class inheritance.</li>
912 | </ol>
913 | <h2 id="codefinder"><code>CodeFinder</code></h2>
914 | <p>The <code>CodeFinder</code> class in <code>code_finder.py</code> provides functionality to search for specific code elements and analyze their relationships within the indexed codebase.</p>
915 | <h3 id="key-methods">Key Methods</h3>
916 | <ul>
917 | <li><code>find_by_function_name()</code>: Finds functions by name.</li>
918 | <li><code>find_by_class_name()</code>: Finds classes by name.</li>
919 | <li><code>find_by_variable_name()</code>: Finds variables by name.</li>
920 | <li><code>find_by_content()</code>: Finds code by content matching in source or docstrings.</li>
921 | <li><code>find_related_code()</code>: Finds code related to a query using multiple search strategies.</li>
922 | <li><code>analyze_code_relationships()</code>: Analyzes different types of code relationships, such as callers, callees, importers, and class hierarchies.</li>
923 | </ul>
924 | <h2 id="importextractor"><code>ImportExtractor</code></h2>
925 | <p>The <code>ImportExtractor</code> class in <code>import_extractor.py</code> is a utility for extracting package and module imports from source code files of various programming languages. It uses the most appropriate parsing technique for each language, such as AST for Python and regular expressions for JavaScript.</p>
926 | <h1 id="tools-exploration">Tools Exploration</h1>
927 | <p>There are a total of 14 tools available to the users, and here we have attached illustrative demos for each one of them.</p>
928 | <h2 id="find_code-tool">find_code Tool</h2>
929 | <p>The <code>find_code</code> tool allows users to search for code snippets, functions, classes, and variables within the codebase using natural language queries. This tool helps developers understand and navigate large codebases efficiently.</p>
930 | <p>Below is an embedded link to a demo video showcasing the usage of the <code>find_code</code> tool in action.
931 | <a href="https://drive.google.com/file/d/1ojCDIIAwcir9e3jgHHIVC5weZ9nuIQcs/view?usp=drive_link"><img alt="Watch the demo video" src="../images/tool_images/1.png" /></a></p>
932 | <hr />
933 | <h2 id="watch_directory-tool">watch_directory Tool</h2>
934 | <p>The <code>watch_directory</code> tool allows users to monitor a specified directory for file changes, additions, or deletions in real-time. It helps developers automate workflows such as triggering scripts, updating indexes, or syncing files whenever changes occur in the directory.</p>
935 | <p>Below is an embedded link to a demo video showcasing the usage of the <code>watch_directory</code> tool in a development environment.
936 | <a href="https://drive.google.com/file/d/1OEjcS2iwwymss99zLidbeBjcblferKBX/view?usp=drive_link"><img alt="Watch the demo" src="../images/tool_images/2.png" /></a> </p>
937 | <hr />
938 | <h2 id="analyze_code_relationships-tool">analyze_code_relationships Tool</h2>
939 | <p>The <code>analyze_code_relationships</code> tool in CodeGraphContext is designed to let users query and explore the various relationships between code elements in a codebase, represented as a graph in Neo4j. </p>
940 | <h3 id="relationship-types-that-can-be-analyzed">Relationship Types That Can Be Analyzed</h3>
941 | <ul>
942 | <li><strong>CALLS:</strong> Finds which functions call or are called by a function.</li>
943 | <li><strong>CALLED_BY:</strong> Finds all functions that directly or indirectly call a target function (inverse of CALLS).</li>
944 | <li><strong>INHERITS_FROM:</strong> Finds class inheritance relationships; which classes inherit from which.</li>
945 | <li><strong>CONTAINS:</strong> Shows containment (which classes/functions are inside which modules or files).</li>
946 | <li><strong>IMPLEMENTS:</strong> Shows which classes implement an interface.</li>
947 | <li><strong>IMPORTS:</strong> Identifies which files or modules import a specific module.</li>
948 | <li><strong>DEFINED_IN:</strong> Locates where an entity (function/class) is defined.</li>
949 | <li><strong>HAS_ARGUMENT:</strong> Shows relationships from functions to their arguments.</li>
950 | <li><strong>DECLARES:</strong> Finds variables declared in functions or classes.</li>
951 | </ul>
952 | <p>Below is an embedded link to a demo video showcasing the usage of the <code>analyse_code_relationships</code> tool.
953 | <a href="https://drive.google.com/file/d/154M_lTPbg9_Gj9bd2ErnAVbJArSbcb2M/view?usp=drive_link"><img alt="Watch the demo" src="../images/tool_images/3.png" /></a> </p>
954 | <hr />
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 | </article>
969 | </div>
970 |
971 |
972 | <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
973 | </div>
974 |
975 | </main>
976 |
977 | <footer class="md-footer">
978 |
979 | <div class="md-footer-meta md-typeset">
980 | <div class="md-footer-meta__inner md-grid">
981 | <div class="md-copyright">
982 |
983 |
984 | Made with
985 | <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
986 | Material for MkDocs
987 | </a>
988 |
989 | </div>
990 |
991 | </div>
992 | </div>
993 | </footer>
994 |
995 | </div>
996 | <div class="md-dialog" data-md-component="dialog">
997 | <div class="md-dialog__inner md-typeset"></div>
998 | </div>
999 |
1000 |
1001 |
1002 |
1003 |
1004 | <script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
1005 |
1006 |
1007 | <script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
1008 |
1009 |
1010 | </body>
1011 | </html>
```
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/javascript.py:
--------------------------------------------------------------------------------
```python
1 | from pathlib import Path
2 | from typing import Any, Dict, Optional, Tuple
3 | import re
4 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger
5 | from codegraphcontext.utils.tree_sitter_manager import execute_query
6 |
7 | # --- Helpers to classify JS methods ---
8 | _GETTER_RE = re.compile(r"^\s*(?:static\s+)?get\b")
9 | _SETTER_RE = re.compile(r"^\s*(?:static\s+)?set\b")
10 | _STATIC_RE = re.compile(r"^\s*static\b")
11 |
12 |
13 | def _first_line_before_body(text: str) -> str:
14 | """
15 | Best-effort header extraction: take text before the first '{'
16 | (covers class/object methods). Fallback to the first line.
17 | """
18 | head = text.split("{", 1)[0]
19 | if not head.strip():
20 | return text.splitlines()[0] if text.splitlines() else text
21 | return head
22 |
23 |
24 | def _classify_method_kind(header: str) -> Optional[str]:
25 | """
26 | Return 'getter' | 'setter' | 'static' | None.
27 | Prefer 'getter'/'setter' over 'static' when both appear.
28 | """
29 | if _GETTER_RE.search(header):
30 | return "getter"
31 | if _SETTER_RE.search(header):
32 | return "setter"
33 | if _STATIC_RE.search(header):
34 | return "static"
35 | return None
36 |
37 |
38 | JS_QUERIES = {
39 | "functions": """
40 | (function_declaration
41 | name: (identifier) @name
42 | parameters: (formal_parameters) @params
43 | ) @function_node
44 |
45 | (variable_declarator
46 | name: (identifier) @name
47 | value: (function_expression
48 | parameters: (formal_parameters) @params
49 | ) @function_node
50 | )
51 |
52 | (variable_declarator
53 | name: (identifier) @name
54 | value: (arrow_function
55 | parameters: (formal_parameters) @params
56 | ) @function_node
57 | )
58 |
59 | (variable_declarator
60 | name: (identifier) @name
61 | value: (arrow_function
62 | parameter: (identifier) @single_param
63 | ) @function_node
64 | )
65 |
66 | (method_definition
67 | name: (property_identifier) @name
68 | parameters: (formal_parameters) @params
69 | ) @function_node
70 |
71 | (assignment_expression
72 | left: (member_expression
73 | property: (property_identifier) @name
74 | )
75 | right: (function_expression
76 | parameters: (formal_parameters) @params
77 | ) @function_node
78 | )
79 |
80 | (assignment_expression
81 | left: (member_expression
82 | property: (property_identifier) @name
83 | )
84 | right: (arrow_function
85 | parameters: (formal_parameters) @params
86 | ) @function_node
87 | )
88 | """,
89 | "classes": """
90 | (class_declaration) @class
91 | (class) @class
92 | """,
93 | "imports": """
94 | (import_statement) @import
95 | (call_expression
96 | function: (identifier) @require_call (#eq? @require_call "require")
97 | ) @import
98 | """,
99 | "calls": """
100 | (call_expression function: (identifier) @name)
101 | (call_expression function: (member_expression property: (property_identifier) @name))
102 | (new_expression constructor: (identifier) @name)
103 | (new_expression constructor: (member_expression property: (property_identifier) @name))
104 | """,
105 | "variables": """
106 | (variable_declarator name: (identifier) @name)
107 | """,
108 | "docstrings": """
109 | (comment) @docstring_comment
110 | """,
111 | }
112 |
113 |
114 | class JavascriptTreeSitterParser:
115 | """A JavaScript-specific parser using tree-sitter, encapsulating language-specific logic."""
116 |
117 | def __init__(self, generic_parser_wrapper):
118 | self.generic_parser_wrapper = generic_parser_wrapper
119 | self.language_name = generic_parser_wrapper.language_name
120 | self.language = generic_parser_wrapper.language
121 | self.parser = generic_parser_wrapper.parser
122 |
123 | def _get_node_text(self, node) -> str:
124 | return node.text.decode('utf-8')
125 |
126 | def _get_parent_context(self, node, types=('function_declaration', 'class_declaration', 'function_expression', 'method_definition', 'arrow_function')):
127 | # JS specific context types
128 | curr = node.parent
129 | while curr:
130 | if curr.type in types:
131 | name_node = curr.child_by_field_name('name')
132 | if not name_node and curr.type in ('function_expression', 'arrow_function'):
133 | # Try to find name from variable declaration
134 | if curr.parent and curr.parent.type == 'variable_declarator':
135 | name_node = curr.parent.child_by_field_name('name')
136 | elif curr.parent and curr.parent.type == 'assignment_expression':
137 | name_node = curr.parent.child_by_field_name('left')
138 | elif curr.parent and curr.parent.type == 'pair': # property: function
139 | name_node = curr.parent.child_by_field_name('key')
140 |
141 | return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
142 | curr = curr.parent
143 | return None, None, None
144 |
145 | def _calculate_complexity(self, node):
146 | # JS specific complexity nodes
147 | complexity_nodes = {
148 | "if_statement", "for_statement", "while_statement", "do_statement",
149 | "switch_statement", "case_statement", "conditional_expression",
150 | "logical_expression", "binary_expression", "catch_clause"
151 | }
152 | count = 1
153 |
154 | def traverse(n):
155 | nonlocal count
156 | if n.type in complexity_nodes:
157 | count += 1
158 | for child in n.children:
159 | traverse(child)
160 |
161 | traverse(node)
162 | return count
163 |
164 | def _get_docstring(self, body_node):
165 | # JS specific docstring extraction (e.g., JSDoc comments)
166 | # This is a placeholder and needs more sophisticated logic
167 | return None
168 |
169 | def parse(self, file_path: Path, is_dependency: bool = False) -> Dict:
170 | """Parses a file and returns its structure in a standardized dictionary format."""
171 | with open(file_path, "r", encoding="utf-8") as f:
172 | source_code = f.read()
173 |
174 | tree = self.parser.parse(bytes(source_code, "utf8"))
175 | root_node = tree.root_node
176 |
177 | functions = self._find_functions(root_node)
178 | classes = self._find_classes(root_node)
179 | imports = self._find_imports(root_node)
180 | function_calls = self._find_calls(root_node)
181 | variables = self._find_variables(root_node)
182 |
183 | return {
184 | "file_path": str(file_path),
185 | "functions": functions,
186 | "classes": classes,
187 | "variables": variables,
188 | "imports": imports,
189 | "function_calls": function_calls,
190 | "is_dependency": is_dependency,
191 | "lang": self.language_name,
192 | }
193 |
194 | def _find_functions(self, root_node):
195 | functions = []
196 | query_str = JS_QUERIES['functions']
197 |
198 | # Local helpers so we don't depend on class attrs being present
199 | def _fn_for_name(name_node):
200 | current = name_node.parent
201 | while current:
202 | if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition', 'function_expression'):
203 | return current
204 | elif current.type in ('variable_declarator', 'assignment_expression'):
205 | for child in current.children:
206 | if child.type in ('function', 'arrow_function', 'function_expression'):
207 | return child
208 | current = current.parent
209 | return None
210 |
211 | def _fn_for_params(params_node):
212 | current = params_node.parent
213 | while current:
214 | if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition', 'function_expression'):
215 | return current
216 | current = current.parent
217 | return None
218 | # stable keys so the same function only gets one bucket
219 | def _key(n): # start/end byte + type is stable across captures
220 | return (n.start_byte, n.end_byte, n.type)
221 |
222 | # Collect captures grouped by function node
223 | captures_by_function = {}
224 | def _bucket_for(node):
225 | fid = _key(node)
226 | return captures_by_function.setdefault(fid, {
227 | 'node': node, 'name': None, 'params': None, 'single_param': None
228 | })
229 |
230 | for node, capture_name in execute_query(self.language, query_str, root_node):
231 | if capture_name == 'function_node':
232 | _bucket_for(node)
233 | elif capture_name == 'name':
234 | fn = _fn_for_name(node)
235 | if fn:
236 | b = _bucket_for(fn)
237 | b['name'] = self._get_node_text(node)
238 | elif capture_name == 'params':
239 | fn = _fn_for_params(node)
240 | if fn:
241 | b = _bucket_for(fn)
242 | b['params'] = node
243 | elif capture_name == 'single_param':
244 | fn = _fn_for_params(node)
245 | if fn:
246 | b = _bucket_for(fn)
247 | b['single_param'] = node
248 |
249 | # Build Function entries
250 | for _, data in captures_by_function.items():
251 | func_node = data['node']
252 |
253 | # Backfill name for method_definition if query didn't capture it
254 | name = data.get('name')
255 | if not name and func_node.type == 'method_definition':
256 | nm = func_node.child_by_field_name('name')
257 | if nm:
258 | name = self._get_node_text(nm)
259 | if not name:
260 | continue # skip nameless functions
261 |
262 | # Parameters
263 | args = []
264 | if data.get('params'):
265 | args = self._extract_parameters(data['params'])
266 | elif data.get('single_param'):
267 | args = [self._get_node_text(data['single_param'])]
268 |
269 | # Context & docstring
270 | context, context_type, _ = self._get_parent_context(func_node)
271 | class_context = context if context_type == 'class_declaration' else None
272 | docstring = self._get_jsdoc_comment(func_node)
273 |
274 | # Classify getter/setter/static (methods only)
275 | js_kind = None
276 | if func_node.type == 'method_definition':
277 | header = _first_line_before_body(self._get_node_text(func_node))
278 | js_kind = _classify_method_kind(header)
279 |
280 | func_data = {
281 | "name": name,
282 | "line_number": func_node.start_point[0] + 1,
283 | "end_line": func_node.end_point[0] + 1,
284 | "args": args,
285 | "source": self._get_node_text(func_node),
286 |
287 | "docstring": docstring,
288 | "cyclomatic_complexity": self._calculate_complexity(func_node),
289 | "context": context,
290 | "context_type": context_type,
291 | "class_context": class_context,
292 | "decorators": [],
293 | "lang": self.language_name,
294 | "is_dependency": False,
295 | }
296 | if js_kind is not None:
297 | func_data["type"] = js_kind
298 |
299 | functions.append(func_data)
300 |
301 | return functions
302 |
303 |
304 |
305 | def _find_function_node_for_name(self, name_node):
306 | """Find the function node that contains this name node."""
307 | current = name_node.parent
308 | while current:
309 | if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition'):
310 | return current
311 | elif current.type in ('variable_declarator', 'assignment_expression'):
312 | # Check if this declarator/assignment contains a function
313 | for child in current.children:
314 | if child.type in ('function', 'arrow_function'):
315 | return child
316 | current = current.parent
317 | return None
318 |
319 |
320 | def _find_function_node_for_params(self, params_node):
321 | """Find the function node that contains this parameters node."""
322 | current = params_node.parent
323 | while current:
324 | if current.type in ('function_declaration', 'function', 'arrow_function', 'method_definition'):
325 | return current
326 | current = current.parent
327 |
328 | return None
329 |
330 |
331 | def _extract_parameters(self, params_node):
332 | """Extract parameter names from formal_parameters node."""
333 | params = []
334 | if params_node.type == 'formal_parameters':
335 | for child in params_node.children:
336 | if child.type == 'identifier':
337 | params.append(self._get_node_text(child))
338 | elif child.type == 'assignment_pattern':
339 | # Default parameter: param = defaultValue
340 | left_child = child.child_by_field_name('left')
341 | if left_child and left_child.type == 'identifier':
342 | params.append(self._get_node_text(left_child))
343 | elif child.type == 'rest_pattern':
344 | # Rest parameter: ...args
345 | argument = child.child_by_field_name('argument')
346 | if argument and argument.type == 'identifier':
347 | params.append(f"...{self._get_node_text(argument)}")
348 | return params
349 |
350 |
351 | def _get_jsdoc_comment(self, func_node):
352 | """Extract JSDoc comment preceding the function."""
353 | # Look for comments before the function
354 | prev_sibling = func_node.prev_sibling
355 | while prev_sibling and prev_sibling.type in ('comment', '\n', ' '):
356 | if prev_sibling.type == 'comment':
357 | comment_text = self._get_node_text(prev_sibling)
358 | if comment_text.startswith('/**') and comment_text.endswith('*/'):
359 | return comment_text.strip()
360 | prev_sibling = prev_sibling.prev_sibling
361 | return None
362 |
363 |
364 | def _find_classes(self, root_node):
365 | classes = []
366 | query_str = JS_QUERIES['classes']
367 | for class_node, capture_name in execute_query(self.language, query_str, root_node):
368 | if capture_name == 'class':
369 | name_node = class_node.child_by_field_name('name')
370 | if not name_node: continue
371 | name = self._get_node_text(name_node)
372 |
373 | bases = []
374 | heritage_node = next((child for child in class_node.children if child.type == 'class_heritage'), None)
375 | if heritage_node:
376 | if heritage_node.named_child_count > 0:
377 | base_expr_node = heritage_node.named_child(0)
378 | bases.append(self._get_node_text(base_expr_node))
379 | elif heritage_node.child_count > 0:
380 | # Fallback for anonymous nodes
381 | base_expr_node = heritage_node.child(heritage_node.child_count - 1)
382 | bases.append(self._get_node_text(base_expr_node))
383 |
384 | class_data = {
385 | "name": name,
386 | "line_number": class_node.start_point[0] + 1,
387 | "end_line": class_node.end_point[0] + 1,
388 | "bases": bases,
389 | "source": self._get_node_text(class_node),
390 | "docstring": self._get_docstring(class_node),
391 | "context": None,
392 | "decorators": [],
393 | "lang": self.language_name,
394 | "is_dependency": False,
395 | }
396 | classes.append(class_data)
397 | return classes
398 |
399 |
400 | def _find_imports(self, root_node):
401 | imports = []
402 | query_str = JS_QUERIES['imports']
403 | for node, capture_name in execute_query(self.language, query_str, root_node):
404 | if capture_name != 'import':
405 | continue
406 |
407 | line_number = node.start_point[0] + 1
408 |
409 | if node.type == 'import_statement':
410 | source = self._get_node_text(node.child_by_field_name('source')).strip('\'"')
411 |
412 | # Look for different import structures
413 | import_clause = node.child_by_field_name('import')
414 | if not import_clause:
415 | imports.append({'name': source, 'source': source, 'alias': None, 'line_number': line_number,
416 | 'lang': self.language_name})
417 | continue
418 |
419 | # Default import: import defaultExport from '...'
420 | if import_clause.type == 'identifier':
421 | alias = self._get_node_text(import_clause)
422 | imports.append({'name': 'default', 'source': source, 'alias': alias, 'line_number': line_number,
423 | 'lang': self.language_name})
424 |
425 | # Namespace import: import * as name from '...'
426 | elif import_clause.type == 'namespace_import':
427 | alias_node = import_clause.child_by_field_name('alias')
428 | if alias_node:
429 | alias = self._get_node_text(alias_node)
430 | imports.append({'name': '*', 'source': source, 'alias': alias, 'line_number': line_number,
431 | 'lang': self.language_name})
432 |
433 | # Named imports: import { name, name as alias } from '...'
434 | elif import_clause.type == 'named_imports':
435 | for specifier in import_clause.children:
436 | if specifier.type == 'import_specifier':
437 | name_node = specifier.child_by_field_name('name')
438 | alias_node = specifier.child_by_field_name('alias')
439 | original_name = self._get_node_text(name_node)
440 | alias = self._get_node_text(alias_node) if alias_node else None
441 | imports.append(
442 | {'name': original_name, 'source': source, 'alias': alias, 'line_number': line_number,
443 | 'lang': self.language_name})
444 |
445 | elif node.type == 'call_expression': # require('...')
446 | args = node.child_by_field_name('arguments')
447 | if not args or args.named_child_count == 0: continue
448 | source_node = args.named_child(0)
449 | if not source_node or source_node.type != 'string': continue
450 | source = self._get_node_text(source_node).strip('\'"')
451 |
452 | alias = None
453 | if node.parent.type == 'variable_declarator':
454 | alias_node = node.parent.child_by_field_name('name')
455 | if alias_node:
456 | alias = self._get_node_text(alias_node)
457 | imports.append({'name': source, 'source': source, 'alias': alias, 'line_number': line_number,
458 | 'lang': self.language_name})
459 |
460 | return imports
461 |
462 |
463 | def _find_calls(self, root_node):
464 | calls = []
465 | query_str = JS_QUERIES['calls']
466 | for node, capture_name in execute_query(self.language, query_str, root_node):
467 | # Placeholder for JS call extraction logic
468 | if capture_name == 'name':
469 | # Traverse up to find the call_expression
470 | call_node = node.parent
471 | while call_node and call_node.type != 'call_expression' and call_node.type != 'program':
472 | call_node = call_node.parent
473 |
474 | name = self._get_node_text(node)
475 |
476 | # Improved args extraction
477 | args = []
478 | arguments_node = None
479 | if call_node and call_node.type in ('call_expression', 'new_expression'):
480 | arguments_node = call_node.child_by_field_name('arguments')
481 |
482 | if arguments_node:
483 | for arg in arguments_node.children:
484 | if arg.type not in ('(', ')', ','):
485 | args.append(self._get_node_text(arg))
486 |
487 | call_data = {
488 | "name": name,
489 | "full_name": self._get_node_text(call_node),
490 | "line_number": node.start_point[0] + 1,
491 | "args": args,
492 | "inferred_obj_type": None,
493 | "context": self._get_parent_context(node),
494 | "class_context": self._get_parent_context(node, types=('class_declaration',))[:2],
495 | "lang": self.language_name,
496 | "is_dependency": False,
497 | }
498 | calls.append(call_data)
499 | return calls
500 |
501 |
502 | def _find_variables(self, root_node):
503 | variables = []
504 | query_str = JS_QUERIES['variables']
505 | for match in execute_query(self.language, query_str, root_node):
506 | capture_name = match[1]
507 | node = match[0]
508 |
509 | if capture_name == 'name':
510 | var_node = node.parent
511 | name = self._get_node_text(node)
512 | value = None
513 | type_text = None
514 |
515 | # Detect if variable assigned to a function
516 | value_node = var_node.child_by_field_name("value") if var_node else None
517 |
518 | if value_node:
519 | value_type = value_node.type
520 |
521 | # --- Skip variables that are assigned a function ---
522 | if value_type in ("function_expression", "arrow_function"):
523 | continue
524 |
525 | # Some grammars might have async_arrow_function or similar
526 | if "function" in value_type or "arrow" in value_type:
527 | continue
528 |
529 | # --- Handle various assignment types ---
530 | if value_type == "call_expression":
531 | func_node = value_node.child_by_field_name("function")
532 | value = self._get_node_text(func_node) if func_node else name
533 | else:
534 | value = self._get_node_text(value_node)
535 |
536 | context, context_type, context_line = self._get_parent_context(node)
537 | class_context = context if context_type == 'class_declaration' else None
538 |
539 | variable_data = {
540 | "name": name,
541 | "line_number": node.start_point[0] + 1,
542 | "value": value,
543 | "type": type_text,
544 | "context": context,
545 | "class_context": class_context,
546 | "lang": self.language_name,
547 | "is_dependency": False,
548 | }
549 | variables.append(variable_data)
550 | return variables
551 |
552 |
553 | def pre_scan_javascript(files: list[Path], parser_wrapper) -> dict:
554 | """Scans JavaScript files to create a map of class/function names to their file paths."""
555 | imports_map = {}
556 | query_str = """
557 | (class_declaration name: (identifier) @name)
558 | (function_declaration name: (identifier) @name)
559 | (variable_declarator name: (identifier) @name value: (function_expression))
560 | (variable_declarator name: (identifier) @name value: (arrow_function))
561 | (method_definition name: (property_identifier) @name)
562 | (assignment_expression
563 | left: (member_expression
564 | property: (property_identifier) @name
565 | )
566 | right: (function_expression)
567 | )
568 | (assignment_expression
569 | left: (member_expression
570 | property: (property_identifier) @name
571 | )
572 | right: (arrow_function)
573 | )
574 | """
575 |
576 |
577 | for file_path in files:
578 | try:
579 | with open(file_path, "r", encoding="utf-8") as f:
580 | tree = parser_wrapper.parser.parse(bytes(f.read(), "utf8"))
581 |
582 | for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
583 | name = capture.text.decode('utf-8')
584 | if name not in imports_map:
585 | imports_map[name] = []
586 | imports_map[name].append(str(file_path.resolve()))
587 | except Exception as e:
588 | warning_logger(f"Tree-sitter pre-scan failed for {file_path}: {e}")
589 | return imports_map
590 |
```
--------------------------------------------------------------------------------
/docs/site/troubleshooting/index.html:
--------------------------------------------------------------------------------
```html
1 |
2 | <!doctype html>
3 | <html lang="en" class="no-js">
4 | <head>
5 |
6 | <meta charset="utf-8">
7 | <meta name="viewport" content="width=device-width,initial-scale=1">
8 |
9 |
10 |
11 |
12 | <link rel="prev" href="../contributing_languages/">
13 |
14 |
15 | <link rel="next" href="../future_work/">
16 |
17 |
18 |
19 |
20 |
21 | <link rel="icon" href="../assets/images/favicon.png">
22 | <meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.1">
23 |
24 |
25 |
26 | <title>Troubleshooting - CodeGraphContext</title>
27 |
28 |
29 |
30 | <link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
44 | <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
45 | <style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
46 |
47 |
48 |
49 | <script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
50 |
51 |
52 |
53 |
54 |
55 | </head>
56 |
57 |
58 | <body dir="ltr">
59 |
60 |
61 | <input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
62 | <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
63 | <label class="md-overlay" for="__drawer"></label>
64 | <div data-md-component="skip">
65 |
66 |
67 | <a href="#codegraphcontext-troubleshooting-guide" class="md-skip">
68 | Skip to content
69 | </a>
70 |
71 | </div>
72 | <div data-md-component="announce">
73 |
74 | </div>
75 |
76 |
77 |
78 |
79 |
80 |
81 | <header class="md-header md-header--shadow" data-md-component="header">
82 | <nav class="md-header__inner md-grid" aria-label="Header">
83 | <a href=".." title="CodeGraphContext" class="md-header__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
84 |
85 |
86 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
87 |
88 | </a>
89 | <label class="md-header__button md-icon" for="__drawer">
90 |
91 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
92 | </label>
93 | <div class="md-header__title" data-md-component="header-title">
94 | <div class="md-header__ellipsis">
95 | <div class="md-header__topic">
96 | <span class="md-ellipsis">
97 | CodeGraphContext
98 | </span>
99 | </div>
100 | <div class="md-header__topic" data-md-component="header-topic">
101 | <span class="md-ellipsis">
102 |
103 | Troubleshooting
104 |
105 | </span>
106 | </div>
107 | </div>
108 | </div>
109 |
110 |
111 | <script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
112 |
113 |
114 |
115 |
116 |
117 | <label class="md-header__button md-icon" for="__search">
118 |
119 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
120 | </label>
121 | <div class="md-search" data-md-component="search" role="dialog">
122 | <label class="md-search__overlay" for="__search"></label>
123 | <div class="md-search__inner" role="search">
124 | <form class="md-search__form" name="search">
125 | <input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
126 | <label class="md-search__icon md-icon" for="__search">
127 |
128 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
129 |
130 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
131 | </label>
132 | <nav class="md-search__options" aria-label="Search">
133 |
134 | <button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
135 |
136 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
137 | </button>
138 | </nav>
139 |
140 | </form>
141 | <div class="md-search__output">
142 | <div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
143 | <div class="md-search-result" data-md-component="search-result">
144 | <div class="md-search-result__meta">
145 | Initializing search
146 | </div>
147 | <ol class="md-search-result__list" role="presentation"></ol>
148 | </div>
149 | </div>
150 | </div>
151 | </div>
152 | </div>
153 |
154 |
155 |
156 | </nav>
157 |
158 | </header>
159 |
160 | <div class="md-container" data-md-component="container">
161 |
162 |
163 |
164 |
165 |
166 |
167 | <main class="md-main" data-md-component="main">
168 | <div class="md-main__inner md-grid">
169 |
170 |
171 |
172 | <div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
173 | <div class="md-sidebar__scrollwrap">
174 | <div class="md-sidebar__inner">
175 |
176 |
177 |
178 |
179 | <nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
180 | <label class="md-nav__title" for="__drawer">
181 | <a href=".." title="CodeGraphContext" class="md-nav__button md-logo" aria-label="CodeGraphContext" data-md-component="logo">
182 |
183 |
184 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
185 |
186 | </a>
187 | CodeGraphContext
188 | </label>
189 |
190 | <ul class="md-nav__list" data-md-scrollfix>
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | <li class="md-nav__item">
199 | <a href=".." class="md-nav__link">
200 |
201 |
202 |
203 | <span class="md-ellipsis">
204 |
205 |
206 | Home
207 |
208 |
209 |
210 | </span>
211 |
212 |
213 |
214 | </a>
215 | </li>
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 | <li class="md-nav__item">
226 | <a href="../installation/" class="md-nav__link">
227 |
228 |
229 |
230 | <span class="md-ellipsis">
231 |
232 |
233 | Installation
234 |
235 |
236 |
237 | </span>
238 |
239 |
240 |
241 | </a>
242 | </li>
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | <li class="md-nav__item">
253 | <a href="../use_cases/" class="md-nav__link">
254 |
255 |
256 |
257 | <span class="md-ellipsis">
258 |
259 |
260 | Use Cases
261 |
262 |
263 |
264 | </span>
265 |
266 |
267 |
268 | </a>
269 | </li>
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | <li class="md-nav__item">
280 | <a href="../architecture/" class="md-nav__link">
281 |
282 |
283 |
284 | <span class="md-ellipsis">
285 |
286 |
287 | Architecture
288 |
289 |
290 |
291 | </span>
292 |
293 |
294 |
295 | </a>
296 | </li>
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 | <li class="md-nav__item">
307 | <a href="../cli/" class="md-nav__link">
308 |
309 |
310 |
311 | <span class="md-ellipsis">
312 |
313 |
314 | CLI Reference
315 |
316 |
317 |
318 | </span>
319 |
320 |
321 |
322 | </a>
323 | </li>
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 | <li class="md-nav__item">
334 | <a href="../watching/" class="md-nav__link">
335 |
336 |
337 |
338 | <span class="md-ellipsis">
339 |
340 |
341 | Live Watching
342 |
343 |
344 |
345 | </span>
346 |
347 |
348 |
349 | </a>
350 | </li>
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 | <li class="md-nav__item">
361 | <a href="../server/" class="md-nav__link">
362 |
363 |
364 |
365 | <span class="md-ellipsis">
366 |
367 |
368 | Server
369 |
370 |
371 |
372 | </span>
373 |
374 |
375 |
376 | </a>
377 | </li>
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 | <li class="md-nav__item">
388 | <a href="../core/" class="md-nav__link">
389 |
390 |
391 |
392 | <span class="md-ellipsis">
393 |
394 |
395 | Core Concepts
396 |
397 |
398 |
399 | </span>
400 |
401 |
402 |
403 | </a>
404 | </li>
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 | <li class="md-nav__item">
415 | <a href="../tools/" class="md-nav__link">
416 |
417 |
418 |
419 | <span class="md-ellipsis">
420 |
421 |
422 | Tools
423 |
424 |
425 |
426 | </span>
427 |
428 |
429 |
430 | </a>
431 | </li>
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 | <li class="md-nav__item">
442 | <a href="../cookbook/" class="md-nav__link">
443 |
444 |
445 |
446 | <span class="md-ellipsis">
447 |
448 |
449 | Cookbook
450 |
451 |
452 |
453 | </span>
454 |
455 |
456 |
457 | </a>
458 | </li>
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 | <li class="md-nav__item md-nav__item--nested">
474 |
475 |
476 |
477 | <input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_11" >
478 |
479 |
480 | <label class="md-nav__link" for="__nav_11" id="__nav_11_label" tabindex="0">
481 |
482 |
483 |
484 | <span class="md-ellipsis">
485 |
486 |
487 | Contributing
488 |
489 |
490 |
491 | </span>
492 |
493 |
494 |
495 | <span class="md-nav__icon md-icon"></span>
496 | </label>
497 |
498 | <nav class="md-nav" data-md-level="1" aria-labelledby="__nav_11_label" aria-expanded="false">
499 | <label class="md-nav__title" for="__nav_11">
500 | <span class="md-nav__icon md-icon"></span>
501 |
502 |
503 | Contributing
504 |
505 |
506 | </label>
507 | <ul class="md-nav__list" data-md-scrollfix>
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 | <li class="md-nav__item">
516 | <a href="../contributing/" class="md-nav__link">
517 |
518 |
519 |
520 | <span class="md-ellipsis">
521 |
522 |
523 | Overview
524 |
525 |
526 |
527 | </span>
528 |
529 |
530 |
531 | </a>
532 | </li>
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 | <li class="md-nav__item">
544 | <a href="../contributing_languages/" class="md-nav__link">
545 |
546 |
547 |
548 | <span class="md-ellipsis">
549 |
550 |
551 | Adding New Languages
552 |
553 |
554 |
555 | </span>
556 |
557 |
558 |
559 | </a>
560 | </li>
561 |
562 |
563 |
564 |
565 | </ul>
566 | </nav>
567 |
568 | </li>
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 | <li class="md-nav__item md-nav__item--active">
581 |
582 | <input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
583 |
584 |
585 |
586 |
587 |
588 | <label class="md-nav__link md-nav__link--active" for="__toc">
589 |
590 |
591 |
592 | <span class="md-ellipsis">
593 |
594 |
595 | Troubleshooting
596 |
597 |
598 |
599 | </span>
600 |
601 |
602 |
603 | <span class="md-nav__icon md-icon"></span>
604 | </label>
605 |
606 | <a href="./" class="md-nav__link md-nav__link--active">
607 |
608 |
609 |
610 | <span class="md-ellipsis">
611 |
612 |
613 | Troubleshooting
614 |
615 |
616 |
617 | </span>
618 |
619 |
620 |
621 | </a>
622 |
623 |
624 |
625 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
626 |
627 |
628 |
629 |
630 |
631 |
632 | <label class="md-nav__title" for="__toc">
633 | <span class="md-nav__icon md-icon"></span>
634 | Table of contents
635 | </label>
636 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
637 |
638 | <li class="md-nav__item">
639 | <a href="#1-prerequisites-at-a-glance" class="md-nav__link">
640 | <span class="md-ellipsis">
641 |
642 | 1. Prerequisites at a glance
643 |
644 | </span>
645 | </a>
646 |
647 | </li>
648 |
649 | <li class="md-nav__item">
650 | <a href="#2-create-and-activate-a-virtual-environment" class="md-nav__link">
651 | <span class="md-ellipsis">
652 |
653 | 2. Create and activate a virtual environment
654 |
655 | </span>
656 | </a>
657 |
658 | </li>
659 |
660 | <li class="md-nav__item">
661 | <a href="#3-run-the-neo4j-setup-wizard-optional-for-unix-required-for-windows" class="md-nav__link">
662 | <span class="md-ellipsis">
663 |
664 | 3. Run the Neo4j setup wizard (optional for Unix, required for Windows)
665 |
666 | </span>
667 | </a>
668 |
669 | </li>
670 |
671 | <li class="md-nav__item">
672 | <a href="#4-start-the-mcp-server" class="md-nav__link">
673 | <span class="md-ellipsis">
674 |
675 | 4. Start the MCP server
676 |
677 | </span>
678 | </a>
679 |
680 | </li>
681 |
682 | <li class="md-nav__item">
683 | <a href="#5-manual-credential-setup-fallback" class="md-nav__link">
684 | <span class="md-ellipsis">
685 |
686 | 5. Manual credential setup (fallback)
687 |
688 | </span>
689 | </a>
690 |
691 | </li>
692 |
693 | <li class="md-nav__item">
694 | <a href="#6-common-issues-fixes" class="md-nav__link">
695 | <span class="md-ellipsis">
696 |
697 | 6. Common issues & fixes
698 |
699 | </span>
700 | </a>
701 |
702 | </li>
703 |
704 | <li class="md-nav__item">
705 | <a href="#7-after-the-server-is-running" class="md-nav__link">
706 | <span class="md-ellipsis">
707 |
708 | 7. After the server is running
709 |
710 | </span>
711 | </a>
712 |
713 | </li>
714 |
715 | </ul>
716 |
717 | </nav>
718 |
719 | </li>
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 | <li class="md-nav__item">
730 | <a href="../future_work/" class="md-nav__link">
731 |
732 |
733 |
734 | <span class="md-ellipsis">
735 |
736 |
737 | Future Work
738 |
739 |
740 |
741 | </span>
742 |
743 |
744 |
745 | </a>
746 | </li>
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 | <li class="md-nav__item">
757 | <a href="../license/" class="md-nav__link">
758 |
759 |
760 |
761 | <span class="md-ellipsis">
762 |
763 |
764 | License
765 |
766 |
767 |
768 | </span>
769 |
770 |
771 |
772 | </a>
773 | </li>
774 |
775 |
776 |
777 | </ul>
778 | </nav>
779 | </div>
780 | </div>
781 | </div>
782 |
783 |
784 |
785 | <div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
786 | <div class="md-sidebar__scrollwrap">
787 | <div class="md-sidebar__inner">
788 |
789 |
790 | <nav class="md-nav md-nav--secondary" aria-label="Table of contents">
791 |
792 |
793 |
794 |
795 |
796 |
797 | <label class="md-nav__title" for="__toc">
798 | <span class="md-nav__icon md-icon"></span>
799 | Table of contents
800 | </label>
801 | <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
802 |
803 | <li class="md-nav__item">
804 | <a href="#1-prerequisites-at-a-glance" class="md-nav__link">
805 | <span class="md-ellipsis">
806 |
807 | 1. Prerequisites at a glance
808 |
809 | </span>
810 | </a>
811 |
812 | </li>
813 |
814 | <li class="md-nav__item">
815 | <a href="#2-create-and-activate-a-virtual-environment" class="md-nav__link">
816 | <span class="md-ellipsis">
817 |
818 | 2. Create and activate a virtual environment
819 |
820 | </span>
821 | </a>
822 |
823 | </li>
824 |
825 | <li class="md-nav__item">
826 | <a href="#3-run-the-neo4j-setup-wizard-optional-for-unix-required-for-windows" class="md-nav__link">
827 | <span class="md-ellipsis">
828 |
829 | 3. Run the Neo4j setup wizard (optional for Unix, required for Windows)
830 |
831 | </span>
832 | </a>
833 |
834 | </li>
835 |
836 | <li class="md-nav__item">
837 | <a href="#4-start-the-mcp-server" class="md-nav__link">
838 | <span class="md-ellipsis">
839 |
840 | 4. Start the MCP server
841 |
842 | </span>
843 | </a>
844 |
845 | </li>
846 |
847 | <li class="md-nav__item">
848 | <a href="#5-manual-credential-setup-fallback" class="md-nav__link">
849 | <span class="md-ellipsis">
850 |
851 | 5. Manual credential setup (fallback)
852 |
853 | </span>
854 | </a>
855 |
856 | </li>
857 |
858 | <li class="md-nav__item">
859 | <a href="#6-common-issues-fixes" class="md-nav__link">
860 | <span class="md-ellipsis">
861 |
862 | 6. Common issues & fixes
863 |
864 | </span>
865 | </a>
866 |
867 | </li>
868 |
869 | <li class="md-nav__item">
870 | <a href="#7-after-the-server-is-running" class="md-nav__link">
871 | <span class="md-ellipsis">
872 |
873 | 7. After the server is running
874 |
875 | </span>
876 | </a>
877 |
878 | </li>
879 |
880 | </ul>
881 |
882 | </nav>
883 | </div>
884 | </div>
885 | </div>
886 |
887 |
888 |
889 | <div class="md-content" data-md-component="content">
890 |
891 | <article class="md-content__inner md-typeset">
892 |
893 |
894 |
895 |
896 |
897 | <h1 id="codegraphcontext-troubleshooting-guide">CodeGraphContext Troubleshooting Guide</h1>
898 | <p>Use this checklist whenever <code>cgc mcp setup</code> or <code>cgc mcp start</code> doesn’t behave as expected. It keeps the happy path short, but includes the fallback steps when something goes wrong.</p>
899 | <h2 id="1-prerequisites-at-a-glance">1. Prerequisites at a glance</h2>
900 | <ul>
901 | <li><strong>Windows + PowerShell</strong> commands below assume the <code>py</code> launcher. Adapt to <code>python3</code> if you're on macOS/Linux.</li>
902 | <li><strong>Python 3.12+</strong> (recommended for FalkorDB Lite support). Run <code>py -3.12 --version</code> to confirm.</li>
903 | <li><strong>Database Options:</strong></li>
904 | <li><strong>FalkorDB Lite</strong> (default for Unix/Linux/macOS, Python 3.12+): No setup required, works out of the box.</li>
905 | <li><strong>Neo4j</strong> (required for Windows, optional for others): Requires Docker, WSL, or native installation. Setup via <code>cgc neo4j setup</code>.</li>
906 | </ul>
907 | <h2 id="2-create-and-activate-a-virtual-environment">2. Create and activate a virtual environment</h2>
908 | <p>From the repository root (<code>CodeGraphContext/</code>):</p>
909 | <pre><code class="language-powershell">py -3.11 -m venv venv
910 | .\venv\Scripts\python.exe -m pip install --upgrade pip
911 | </code></pre>
912 | <ul>
913 | <li>On Windows, Neo4j driver 6.x can crash with <code>AttributeError: socket.EAI_ADDRFAMILY</code>. If you see that, run:
914 | <code>powershell
915 | .\venv\Scripts\python.exe -m pip install "neo4j<6"</code></li>
916 | </ul>
917 | <h2 id="3-run-the-neo4j-setup-wizard-optional-for-unix-required-for-windows">3. Run the Neo4j setup wizard (optional for Unix, required for Windows)</h2>
918 | <p><strong>Note:</strong> If you're on Unix/Linux/macOS with Python 3.12+, FalkorDB Lite is already your default database. You can skip this step unless you prefer Neo4j.</p>
919 | <p><strong>For Windows users or those preferring Neo4j</strong>, launch the wizard:</p>
920 | <pre><code class="language-powershell">.\venv\Scripts\cgc.exe neo4j setup
921 | </code></pre>
922 | <blockquote>
923 | <p><strong>Tip:</strong> If you want the wizard to spin up a local Neo4j instance for you, make sure <strong>Docker Desktop</strong> is installed and running before you launch <code>cgc neo4j setup</code>. If Docker isn't running, the setup wizard will fail when it tries to install Neo4j locally.</p>
924 | </blockquote>
925 | <p>What happens next:</p>
926 | <ul>
927 | <li>The wizard checks for Docker. If it's running, it can auto-provision a local Neo4j instance for you.</li>
928 | <li>Alternatively, you can supply credentials for an existing Neo4j AuraDB database.</li>
929 | <li>At the end, it generates:</li>
930 | <li><code>mcp.json</code> in your project directory (stores the MCP server command + env vars).</li>
931 | <li><code>~/.codegraphcontext/.env</code> containing <code>NEO4J_URI</code>, <code>NEO4J_USERNAME</code>, <code>NEO4J_PASSWORD</code>.</li>
932 | </ul>
933 | <p>Make sure the Docker container (or remote Neo4j) is still running before you start the server.</p>
934 | <h2 id="4-start-the-mcp-server">4. Start the MCP server</h2>
935 | <p>Once the wizard completes successfully:</p>
936 | <pre><code class="language-powershell">.\venv\Scripts\cgc.exe mcp start
937 | </code></pre>
938 | <p>Expected output includes:</p>
939 | <pre><code class="language-text">Starting CodeGraphContext Server...
940 | ...
941 | MCP Server is running. Waiting for requests...
942 | </code></pre>
943 | <p>If you instead see:</p>
944 | <pre><code class="language-text">Configuration Error: Neo4j credentials must be set via environment variables
945 | </code></pre>
946 | <p>then either no credentials were saved, or the wizard was skipped—see the manual alternative below.</p>
947 | <h2 id="5-manual-credential-setup-fallback">5. Manual credential setup (fallback)</h2>
948 | <p>If you prefer not to use the wizard or need to fix a broken configuration:</p>
949 | <ol>
950 | <li>Create a <code>mcp.json</code> (or edit the one that exists) in the repository root:</li>
951 | </ol>
952 | <p><code>json
953 | {
954 | "mcpServers": {
955 | "CodeGraphContext": {
956 | "command": "cgc",
957 | "args": ["mcp", "start"],
958 | "env": {
959 | "NEO4J_URI": "neo4j+s://YOUR-HOSTNAME:7687",
960 | "NEO4J_USERNAME": "neo4j",
961 | "NEO4J_PASSWORD": "super-secret-password"
962 | }
963 | }
964 | }
965 | }</code></p>
966 | <ol>
967 | <li>
968 | <p>(Optional) Also create <code>%USERPROFILE%\.codegraphcontext\.env</code> with the same key/value pairs. The CLI loads that file automatically.</p>
969 | </li>
970 | <li>
971 | <p>Re-run:</p>
972 | </li>
973 | </ol>
974 | <p><code>powershell
975 | .\venv\Scripts\cgc.exe mcp start</code></p>
976 | <h2 id="6-common-issues-fixes">6. Common issues & fixes</h2>
977 | <table>
978 | <thead>
979 | <tr>
980 | <th>Symptom</th>
981 | <th>Likely Cause</th>
982 | <th>Fix</th>
983 | </tr>
984 | </thead>
985 | <tbody>
986 | <tr>
987 | <td><code>Configuration Error: Neo4j credentials must be set…</code></td>
988 | <td><code>mcp.json</code>/<code>.env</code> missing or empty</td>
989 | <td>Run <code>cgc neo4j setup</code> again <strong>with Docker running</strong>, or create the files manually (section 5).</td>
990 | </tr>
991 | <tr>
992 | <td><code>AttributeError: socket.EAI_ADDRFAMILY</code></td>
993 | <td>Neo4j 6.x bug on Windows</td>
994 | <td>Install the 5.x driver: <code>.\venv\Scripts\python.exe -m pip install "neo4j<6"</code> and retry.</td>
995 | </tr>
996 | <tr>
997 | <td>Setup wizard fails while pulling Docker image</td>
998 | <td>Docker Desktop not running or Docker permissions missing</td>
999 | <td>Start Docker Desktop, wait for it to report “Running”, then rerun <code>cgc neo4j setup</code>.</td>
1000 | </tr>
1001 | <tr>
1002 | <td>Server exits immediately with no log</td>
1003 | <td>Neo4j instance is offline</td>
1004 | <td>Check Docker container status or AuraDB dashboard; restart Neo4j and call <code>cgc mcp start</code> again.</td>
1005 | </tr>
1006 | </tbody>
1007 | </table>
1008 | <h2 id="7-after-the-server-is-running">7. After the server is running</h2>
1009 | <ul>
1010 | <li>Keep the virtual environment active whenever you run <code>cgc</code> commands.</li>
1011 | <li>Use <code>pytest</code> from the same env to run tests:</li>
1012 | </ul>
1013 | <p><code>powershell
1014 | .\venv\Scripts\pytest</code></p>
1015 | <ul>
1016 | <li>Front-end website lives under <code>website/</code> if you need to run <code>npm run dev</code>.</li>
1017 | </ul>
1018 | <p>When in doubt, re-run the wizard with Docker active—it regenerates the configuration files without touching your code. Let me know if any section needs clarifying! :)</p>
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 | </article>
1033 | </div>
1034 |
1035 |
1036 | <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
1037 | </div>
1038 |
1039 | </main>
1040 |
1041 | <footer class="md-footer">
1042 |
1043 | <div class="md-footer-meta md-typeset">
1044 | <div class="md-footer-meta__inner md-grid">
1045 | <div class="md-copyright">
1046 |
1047 |
1048 | Made with
1049 | <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
1050 | Material for MkDocs
1051 | </a>
1052 |
1053 | </div>
1054 |
1055 | </div>
1056 | </div>
1057 | </footer>
1058 |
1059 | </div>
1060 | <div class="md-dialog" data-md-component="dialog">
1061 | <div class="md-dialog__inner md-typeset"></div>
1062 | </div>
1063 |
1064 |
1065 |
1066 |
1067 |
1068 | <script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
1069 |
1070 |
1071 | <script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
1072 |
1073 |
1074 | </body>
1075 | </html>
```
--------------------------------------------------------------------------------
/src/codegraphcontext/cli/cli_helpers.py:
--------------------------------------------------------------------------------
```python
1 | # src/codegraphcontext/cli/cli_helpers.py
2 | import asyncio
3 | import json
4 | import urllib.parse
5 | from pathlib import Path
6 | import time
7 | from rich.console import Console
8 | from rich.table import Table
9 |
10 | from ..core import get_database_manager
11 | from ..core.jobs import JobManager
12 | from ..tools.code_finder import CodeFinder
13 | from ..tools.graph_builder import GraphBuilder
14 | from ..tools.package_resolver import get_local_package_path
15 |
16 | console = Console()
17 |
18 |
19 | def _initialize_services():
20 | """Initializes and returns core service managers."""
21 | console.print("[dim]Initializing services and database connection...[/dim]")
22 | try:
23 | db_manager = get_database_manager()
24 | except ValueError as e:
25 | console.print(f"[bold red]Database Configuration Error:[/bold red] {e}")
26 | return None, None, None
27 |
28 | try:
29 | db_manager.get_driver()
30 | except ValueError as e:
31 | console.print(f"[bold red]Database Connection Error:[/bold red] {e}")
32 | console.print("Please ensure your Neo4j credentials are correct and the database is running.")
33 | return None, None, None
34 |
35 | # The GraphBuilder requires an event loop, even for synchronous-style execution
36 | try:
37 | loop = asyncio.get_running_loop()
38 | except RuntimeError:
39 | loop = asyncio.new_event_loop()
40 | asyncio.set_event_loop(loop)
41 |
42 | graph_builder = GraphBuilder(db_manager, JobManager(), loop)
43 | code_finder = CodeFinder(db_manager)
44 | console.print("[dim]Services initialized.[/dim]")
45 | return db_manager, graph_builder, code_finder
46 |
47 |
48 | def index_helper(path: str):
49 | """Synchronously indexes a repository."""
50 | time_start = time.time()
51 | services = _initialize_services()
52 | if not all(services):
53 | return
54 |
55 | db_manager, graph_builder, code_finder = services
56 | path_obj = Path(path).resolve()
57 |
58 | if not path_obj.exists():
59 | console.print(f"[red]Error: Path does not exist: {path_obj}[/red]")
60 | db_manager.close_driver()
61 | return
62 |
63 | indexed_repos = code_finder.list_indexed_repositories()
64 | repo_exists = any(Path(repo["path"]).resolve() == path_obj for repo in indexed_repos)
65 |
66 | if repo_exists:
67 | # Check if the repository actually has files (not just an empty node from interrupted indexing)
68 | try:
69 | with db_manager.get_driver().session() as session:
70 | result = session.run(
71 | "MATCH (r:Repository {path: $path})-[:CONTAINS]->(f:File) RETURN count(f) as file_count",
72 | path=str(path_obj)
73 | )
74 | record = result.single()
75 | file_count = record["file_count"] if record else 0
76 |
77 | if file_count > 0:
78 | console.print(f"[yellow]Repository '{path}' is already indexed with {file_count} files. Skipping.[/yellow]")
79 | console.print("[dim]💡 Tip: Use 'cgc index --force' to re-index[/dim]")
80 | db_manager.close_driver()
81 | return
82 | else:
83 | console.print(f"[yellow]Repository '{path}' exists but has no files (likely interrupted). Re-indexing...[/yellow]")
84 | except Exception as e:
85 | console.print(f"[yellow]Warning: Could not check file count: {e}. Proceeding with indexing...[/yellow]")
86 |
87 | console.print(f"Starting indexing for: {path_obj}")
88 | console.print("[yellow]This may take a few minutes for large repositories...[/yellow]")
89 |
90 | async def do_index():
91 | await graph_builder.build_graph_from_path_async(path_obj, is_dependency=False)
92 |
93 | try:
94 | asyncio.run(do_index())
95 | time_end = time.time()
96 | elapsed = time_end - time_start
97 | console.print(f"[green]Successfully finished indexing: {path} in {elapsed:.2f} seconds[/green]")
98 |
99 | # Check if auto-watch is enabled
100 | try:
101 | from codegraphcontext.cli.config_manager import get_config_value
102 | auto_watch = get_config_value('ENABLE_AUTO_WATCH')
103 | if auto_watch and str(auto_watch).lower() == 'true':
104 | console.print("\n[cyan]🔍 ENABLE_AUTO_WATCH is enabled. Starting watcher...[/cyan]")
105 | db_manager.close_driver() # Close before starting watcher
106 | watch_helper(path) # This will block the terminal
107 | return # watch_helper handles its own cleanup
108 | except Exception as e:
109 | console.print(f"[yellow]Warning: Could not check ENABLE_AUTO_WATCH: {e}[/yellow]")
110 |
111 | except Exception as e:
112 | console.print(f"[bold red]An error occurred during indexing:[/bold red] {e}")
113 | finally:
114 | db_manager.close_driver()
115 |
116 |
117 | def add_package_helper(package_name: str, language: str):
118 | """Synchronously indexes a package."""
119 | services = _initialize_services()
120 | if not all(services):
121 | return
122 |
123 | db_manager, graph_builder, code_finder = services
124 |
125 | package_path_str = get_local_package_path(package_name, language)
126 | if not package_path_str:
127 | console.print(f"[red]Error: Could not find package '{package_name}' for language '{language}'.[/red]")
128 | db_manager.close_driver()
129 | return
130 |
131 | package_path = Path(package_path_str)
132 |
133 | indexed_repos = code_finder.list_indexed_repositories()
134 | if any(repo.get("name") == package_name for repo in indexed_repos if repo.get("is_dependency")):
135 | console.print(f"[yellow]Package '{package_name}' is already indexed. Skipping.[/yellow]")
136 | db_manager.close_driver()
137 | return
138 |
139 | console.print(f"Starting indexing for package '{package_name}' at: {package_path}")
140 | console.print("[yellow]This may take a few minutes...[/yellow]")
141 |
142 | async def do_index():
143 | await graph_builder.build_graph_from_path_async(package_path, is_dependency=True)
144 |
145 | try:
146 | asyncio.run(do_index())
147 | console.print(f"[green]Successfully finished indexing package: {package_name}[/green]")
148 | except Exception as e:
149 | console.print(f"[bold red]An error occurred during package indexing:[/bold red] {e}")
150 | finally:
151 | db_manager.close_driver()
152 |
153 |
154 | def list_repos_helper():
155 | """Lists all indexed repositories."""
156 | services = _initialize_services()
157 | if not all(services):
158 | return
159 |
160 | db_manager, _, code_finder = services
161 |
162 | try:
163 | repos = code_finder.list_indexed_repositories()
164 | if not repos:
165 | console.print("[yellow]No repositories indexed yet.[/yellow]")
166 | return
167 |
168 | table = Table(show_header=True, header_style="bold magenta")
169 | table.add_column("Name", style="dim")
170 | table.add_column("Path")
171 | table.add_column("Type")
172 |
173 | for repo in repos:
174 | repo_type = "Dependency" if repo.get("is_dependency") else "Project"
175 | table.add_row(repo["name"], repo["path"], repo_type)
176 |
177 | console.print(table)
178 | except Exception as e:
179 | console.print(f"[bold red]An error occurred:[/bold red] {e}")
180 | finally:
181 | db_manager.close_driver()
182 |
183 |
184 | def delete_helper(repo_path: str):
185 | """Deletes a repository from the graph."""
186 | services = _initialize_services()
187 | if not all(services):
188 | return
189 |
190 | db_manager, graph_builder, _ = services
191 |
192 | try:
193 | if graph_builder.delete_repository_from_graph(repo_path):
194 | console.print(f"[green]Successfully deleted repository: {repo_path}[/green]")
195 | else:
196 | console.print(f"[yellow]Repository not found in graph: {repo_path}[/yellow]")
197 | console.print("[dim]Tip: Use 'cgc list' to see available repositories.[/dim]")
198 | except Exception as e:
199 | console.print(f"[bold red]An error occurred:[/bold red] {e}")
200 | finally:
201 | db_manager.close_driver()
202 |
203 |
204 | def cypher_helper(query: str):
205 | """Executes a read-only Cypher query."""
206 | services = _initialize_services()
207 | if not all(services):
208 | return
209 |
210 | db_manager, _, _ = services
211 |
212 | # Replicating safety checks from MCPServer
213 | forbidden_keywords = ['CREATE', 'MERGE', 'DELETE', 'SET', 'REMOVE', 'DROP', 'CALL apoc']
214 | if any(keyword in query.upper() for keyword in forbidden_keywords):
215 | console.print("[bold red]Error: This command only supports read-only queries.[/bold red]")
216 | db_manager.close_driver()
217 | return
218 |
219 | try:
220 | with db_manager.get_driver().session() as session:
221 | result = session.run(query)
222 | records = [record.data() for record in result]
223 | console.print(json.dumps(records, indent=2))
224 | except Exception as e:
225 | console.print(f"[bold red]An error occurred while executing query:[/bold red] {e}")
226 | finally:
227 | db_manager.close_driver()
228 |
229 |
230 | import webbrowser
231 |
232 | def visualize_helper(query: str):
233 | """Generates a visualization."""
234 | services = _initialize_services()
235 | if not all(services):
236 | return
237 |
238 | db_manager, _, _ = services
239 |
240 | # Check if FalkorDB
241 | if "FalkorDB" in db_manager.__class__.__name__:
242 | _visualize_falkordb(db_manager)
243 | else:
244 | try:
245 | encoded_query = urllib.parse.quote(query)
246 | visualization_url = f"http://localhost:7474/browser/?cmd=edit&arg={encoded_query}"
247 | console.print("[green]Graph visualization URL:[/green]")
248 | console.print(visualization_url)
249 | console.print("Open the URL in your browser to see the graph.")
250 | except Exception as e:
251 | console.print(f"[bold red]An error occurred while generating URL:[/bold red] {e}")
252 | finally:
253 | db_manager.close_driver()
254 |
255 | def _visualize_falkordb(db_manager):
256 | console.print("[dim]Generating FalkorDB visualization (showing up to 500 relationships)...[/dim]")
257 | try:
258 | data_nodes = []
259 | data_edges = []
260 |
261 | with db_manager.get_driver().session() as session:
262 | # Fetch nodes and edges
263 | q = "MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 500"
264 | result = session.run(q)
265 |
266 | seen_nodes = set()
267 |
268 | for record in result:
269 | # record values are Node/Relationship objects from falkordb client
270 | n = record['n']
271 | r = record['r']
272 | m = record['m']
273 |
274 | # Process Node helper
275 | def process_node(node):
276 | nid = getattr(node, 'id', -1)
277 | labels = getattr(node, 'labels', [])
278 | lbl = list(labels)[0] if labels else "Node"
279 | props = getattr(node, 'properties', {})
280 | name = props.get('name', str(nid))
281 |
282 | if nid not in seen_nodes:
283 | seen_nodes.add(nid)
284 | color = "#97c2fc" # Default blue
285 | if "Repository" in labels: color = "#ffb3ba" # Red
286 | elif "File" in labels: color = "#baffc9" # Green
287 | elif "Class" in labels: color = "#bae1ff" # Light Blue
288 | elif "Function" in labels: color = "#ffffba" # Yellow
289 | elif "Package" in labels: color = "#ffdfba" # Orange
290 |
291 | data_nodes.append({
292 | "id": nid,
293 | "label": name,
294 | "group": lbl,
295 | "title": str(props),
296 | "color": color
297 | })
298 | return nid
299 |
300 | nid = process_node(n)
301 | mid = process_node(m)
302 |
303 | # Check Edge
304 | e_type = getattr(r, 'relation', '') or getattr(r, 'type', 'REL')
305 | data_edges.append({
306 | "from": nid,
307 | "to": mid,
308 | "label": e_type,
309 | "arrows": "to"
310 | })
311 |
312 | filename = "codegraph_viz.html"
313 | html_content = f"""
314 | <!DOCTYPE html>
315 | <html>
316 | <head>
317 | <title>CodeGraphContext Visualization</title>
318 | <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
319 | <style type="text/css">
320 | #mynetwork {{
321 | width: 100%;
322 | height: 100vh;
323 | border: 1px solid lightgray;
324 | }}
325 | </style>
326 | </head>
327 | <body>
328 | <div id="mynetwork"></div>
329 | <script type="text/javascript">
330 | var nodes = new vis.DataSet({json.dumps(data_nodes)});
331 | var edges = new vis.DataSet({json.dumps(data_edges)});
332 | var container = document.getElementById('mynetwork');
333 | var data = {{ nodes: nodes, edges: edges }};
334 | var options = {{
335 | nodes: {{ shape: 'dot', size: 16 }},
336 | physics: {{ stabilization: false }},
337 | layout: {{ improvedLayout: false }}
338 | }};
339 | var network = new vis.Network(container, data, options);
340 | </script>
341 | </body>
342 | </html>
343 | """
344 |
345 | out_path = Path(filename).resolve()
346 | with open(out_path, "w") as f:
347 | f.write(html_content)
348 |
349 | console.print(f"[green]Visualization generated at:[/green] {out_path}")
350 | console.print("Opening in default browser...")
351 | webbrowser.open(f"file://{out_path}")
352 |
353 | except Exception as e:
354 | console.print(f"[bold red]Visualization failed:[/bold red] {e}")
355 | import traceback
356 | traceback.print_exc()
357 | finally:
358 | db_manager.close_driver()
359 |
360 |
361 | def reindex_helper(path: str):
362 | """Force re-index by deleting and rebuilding the repository."""
363 | time_start = time.time()
364 | services = _initialize_services()
365 | if not all(services):
366 | return
367 |
368 | db_manager, graph_builder, code_finder = services
369 | path_obj = Path(path).resolve()
370 |
371 | if not path_obj.exists():
372 | console.print(f"[red]Error: Path does not exist: {path_obj}[/red]")
373 | db_manager.close_driver()
374 | return
375 |
376 | # Check if already indexed
377 | indexed_repos = code_finder.list_indexed_repositories()
378 | repo_exists = any(Path(repo["path"]).resolve() == path_obj for repo in indexed_repos)
379 |
380 | if repo_exists:
381 | console.print(f"[yellow]Deleting existing index for: {path_obj}[/yellow]")
382 | try:
383 | graph_builder.delete_repository_from_graph(str(path_obj))
384 | console.print("[green]✓[/green] Deleted old index")
385 | except Exception as e:
386 | console.print(f"[red]Error deleting old index: {e}[/red]")
387 | db_manager.close_driver()
388 | return
389 |
390 | console.print(f"[cyan]Re-indexing: {path_obj}[/cyan]")
391 | console.print("[yellow]This may take a few minutes for large repositories...[/yellow]")
392 |
393 | async def do_index():
394 | await graph_builder.build_graph_from_path_async(path_obj, is_dependency=False)
395 |
396 | try:
397 | asyncio.run(do_index())
398 | time_end = time.time()
399 | elapsed = time_end - time_start
400 | console.print(f"[green]Successfully re-indexed: {path} in {elapsed:.2f} seconds[/green]")
401 | except Exception as e:
402 | console.print(f"[bold red]An error occurred during re-indexing:[/bold red] {e}")
403 | finally:
404 | db_manager.close_driver()
405 |
406 |
407 | def update_helper(path: str):
408 | """Update/refresh index for a path (alias for reindex)."""
409 | console.print("[cyan]Updating repository index...[/cyan]")
410 | reindex_helper(path)
411 |
412 |
413 | def clean_helper():
414 | """Remove orphaned nodes and relationships from the database."""
415 | services = _initialize_services()
416 | if not all(services):
417 | return
418 |
419 | db_manager, _, _ = services
420 |
421 | console.print("[cyan]🧹 Cleaning database (removing orphaned nodes)...[/cyan]")
422 |
423 | try:
424 | with db_manager.get_driver().session() as session:
425 | # Find and delete orphaned nodes (nodes not connected to any repository)
426 | # Using OPTIONAL MATCH for FalkorDB compatibility
427 | query = """
428 | MATCH (n)
429 | WHERE NOT (n:Repository)
430 | OPTIONAL MATCH path = (n)-[*]-(r:Repository)
431 | WITH n, path
432 | WHERE path IS NULL
433 | WITH n LIMIT 1000
434 | DETACH DELETE n
435 | RETURN count(n) as deleted
436 | """
437 | result = session.run(query)
438 | record = result.single()
439 | deleted_count = record["deleted"] if record else 0
440 |
441 | if deleted_count > 0:
442 | console.print(f"[green]✓[/green] Deleted {deleted_count} orphaned nodes")
443 | else:
444 | console.print("[green]✓[/green] No orphaned nodes found")
445 |
446 | # Clean up any duplicate relationships (if any)
447 | console.print("[dim]Checking for duplicate relationships...[/dim]")
448 | # Note: This is database-specific and might not work for all backends
449 |
450 | console.print("[green]✅ Database cleanup complete![/green]")
451 | except Exception as e:
452 | console.print(f"[bold red]An error occurred during cleanup:[/bold red] {e}")
453 | finally:
454 | db_manager.close_driver()
455 |
456 |
457 | def stats_helper(path: str = None):
458 | """Show indexing statistics for a repository or overall."""
459 | services = _initialize_services()
460 | if not all(services):
461 | return
462 |
463 | db_manager, _, code_finder = services
464 |
465 | try:
466 | if path:
467 | # Stats for specific repository
468 | path_obj = Path(path).resolve()
469 | console.print(f"[cyan]📊 Statistics for: {path_obj}[/cyan]\n")
470 |
471 | with db_manager.get_driver().session() as session:
472 | # Get repository node
473 | repo_query = """
474 | MATCH (r:Repository {path: $path})
475 | RETURN r
476 | """
477 | result = session.run(repo_query, path=str(path_obj))
478 | if not result.single():
479 | console.print(f"[red]Repository not found: {path_obj}[/red]")
480 | return
481 |
482 | # Get stats
483 | stats_query = """
484 | MATCH (r:Repository {path: $path})-[:CONTAINS]->(f:File)
485 | WITH r, count(f) as file_count, f
486 | OPTIONAL MATCH (f)-[:CONTAINS]->(func:Function)
487 | OPTIONAL MATCH (f)-[:CONTAINS]->(cls:Class)
488 | OPTIONAL MATCH (f)-[:IMPORTS]->(m:Module)
489 | RETURN
490 | file_count,
491 | count(DISTINCT func) as function_count,
492 | count(DISTINCT cls) as class_count,
493 | count(DISTINCT m) as module_count
494 | """
495 | result = session.run(stats_query, path=str(path_obj))
496 | record = result.single()
497 |
498 | table = Table(show_header=True, header_style="bold magenta")
499 | table.add_column("Metric", style="cyan")
500 | table.add_column("Count", style="green", justify="right")
501 |
502 | table.add_row("Files", str(record["file_count"] if record else 0))
503 | table.add_row("Functions", str(record["function_count"] if record else 0))
504 | table.add_row("Classes", str(record["class_count"] if record else 0))
505 | table.add_row("Imported Modules", str(record["module_count"] if record else 0))
506 |
507 | console.print(table)
508 | else:
509 | # Overall stats
510 | console.print("[cyan]📊 Overall Database Statistics[/cyan]\n")
511 |
512 | with db_manager.get_driver().session() as session:
513 | # Get overall counts
514 | stats_query = """
515 | MATCH (r:Repository)
516 | WITH count(r) as repo_count
517 | MATCH (f:File)
518 | WITH repo_count, count(f) as file_count
519 | MATCH (func:Function)
520 | WITH repo_count, file_count, count(func) as function_count
521 | MATCH (cls:Class)
522 | WITH repo_count, file_count, function_count, count(cls) as class_count
523 | MATCH (m:Module)
524 | RETURN
525 | repo_count,
526 | file_count,
527 | function_count,
528 | class_count,
529 | count(m) as module_count
530 | """
531 | result = session.run(stats_query)
532 | record = result.single()
533 |
534 | if record:
535 | table = Table(show_header=True, header_style="bold magenta")
536 | table.add_column("Metric", style="cyan")
537 | table.add_column("Count", style="green", justify="right")
538 |
539 | table.add_row("Repositories", str(record["repo_count"]))
540 | table.add_row("Files", str(record["file_count"]))
541 | table.add_row("Functions", str(record["function_count"]))
542 | table.add_row("Classes", str(record["class_count"]))
543 | table.add_row("Modules", str(record["module_count"]))
544 |
545 | console.print(table)
546 | else:
547 | console.print("[yellow]No data indexed yet.[/yellow]")
548 |
549 | except Exception as e:
550 | console.print(f"[bold red]An error occurred:[/bold red] {e}")
551 | finally:
552 | db_manager.close_driver()
553 |
554 |
555 | def watch_helper(path: str):
556 | """Watch a directory for changes and auto-update the graph (blocking mode)."""
557 | import logging
558 | from ..core.watcher import CodeWatcher
559 |
560 | # Suppress verbose watchdog DEBUG logs
561 | logging.getLogger('watchdog').setLevel(logging.WARNING)
562 | logging.getLogger('watchdog.observers').setLevel(logging.WARNING)
563 | logging.getLogger('watchdog.observers.inotify_buffer').setLevel(logging.WARNING)
564 |
565 | services = _initialize_services()
566 | if not all(services):
567 | return
568 |
569 | db_manager, graph_builder, code_finder = services
570 | path_obj = Path(path).resolve()
571 |
572 | if not path_obj.exists():
573 | console.print(f"[red]Error: Path does not exist: {path_obj}[/red]")
574 | db_manager.close_driver()
575 | return
576 |
577 | if not path_obj.is_dir():
578 | console.print(f"[red]Error: Path must be a directory: {path_obj}[/red]")
579 | db_manager.close_driver()
580 | return
581 |
582 | console.print(f"[bold cyan]🔍 Watching {path_obj} for changes...[/bold cyan]")
583 |
584 | # Check if already indexed
585 | indexed_repos = code_finder.list_indexed_repositories()
586 | is_indexed = any(Path(repo["path"]).resolve() == path_obj for repo in indexed_repos)
587 |
588 | # Create watcher instance
589 | job_manager = JobManager()
590 | watcher = CodeWatcher(graph_builder, job_manager)
591 |
592 | try:
593 | # Start the observer thread
594 | watcher.start()
595 |
596 | # Add the directory to watch
597 | if is_indexed:
598 | console.print("[green]✓[/green] Already indexed (no initial scan needed)")
599 | watcher.watch_directory(str(path_obj), perform_initial_scan=False)
600 | else:
601 | console.print("[yellow]⚠[/yellow] Not indexed yet. Performing initial scan...")
602 |
603 | # Index the repository first (like MCP does)
604 | async def do_index():
605 | await graph_builder.build_graph_from_path_async(path_obj, is_dependency=False)
606 |
607 | asyncio.run(do_index())
608 | console.print("[green]✓[/green] Initial scan complete")
609 |
610 | # Now start watching (without another scan)
611 | watcher.watch_directory(str(path_obj), perform_initial_scan=False)
612 |
613 | console.print("[bold green]👀 Monitoring for file changes...[/bold green] (Press Ctrl+C to stop)")
614 | console.print("[dim]💡 Tip: Open a new terminal window to continue working[/dim]\n")
615 |
616 | # Block here and keep the watcher running
617 | import threading
618 | stop_event = threading.Event()
619 |
620 | try:
621 | stop_event.wait() # Wait indefinitely until interrupted
622 | except KeyboardInterrupt:
623 | console.print("\n[yellow]🛑 Stopping watcher...[/yellow]")
624 |
625 | except KeyboardInterrupt:
626 | console.print("\n[yellow]🛑 Stopping watcher...[/yellow]")
627 | except Exception as e:
628 | console.print(f"[bold red]An error occurred:[/bold red] {e}")
629 | finally:
630 | watcher.stop()
631 | db_manager.close_driver()
632 | console.print("[green]✓[/green] Watcher stopped. Graph is up to date.")
633 |
634 |
635 |
636 | def unwatch_helper(path: str):
637 | """Stop watching a directory."""
638 | console.print(f"[yellow]⚠️ Note: 'cgc unwatch' only works when the watcher is running via MCP server.[/yellow]")
639 | console.print(f"[dim]For CLI watch mode, simply press Ctrl+C in the watch terminal.[/dim]")
640 | console.print(f"\n[cyan]Path specified:[/cyan] {Path(path).resolve()}")
641 |
642 |
643 | def list_watching_helper():
644 | """List all directories currently being watched."""
645 | console.print(f"[yellow]⚠️ Note: 'cgc watching' only works when the watcher is running via MCP server.[/yellow]")
646 | console.print(f"[dim]For CLI watch mode, check the terminal where you ran 'cgc watch'.[/dim]")
647 | console.print(f"\n[cyan]To see watched directories in MCP mode:[/cyan]")
648 | console.print(f" 1. Start the MCP server: cgc mcp start")
649 | console.print(f" 2. Use the 'list_watched_paths' MCP tool from your IDE")
650 |
```
--------------------------------------------------------------------------------
/src/codegraphcontext/tools/languages/python.py:
--------------------------------------------------------------------------------
```python
1 | import os
2 | import tempfile
3 | import nbformat
4 | from nbconvert import PythonExporter
5 | from pathlib import Path
6 | from typing import Any, Dict, Optional, Tuple
7 | import ast
8 | import logging
9 | import warnings
10 | from codegraphcontext.utils.debug_log import debug_log, info_logger, error_logger, warning_logger, debug_logger
11 | from codegraphcontext.utils.tree_sitter_manager import execute_query
12 |
13 | # Suppress verbose traitlets/nbconvert DEBUG logs
14 | logging.getLogger('traitlets').setLevel(logging.WARNING)
15 | logging.getLogger('nbconvert').setLevel(logging.WARNING)
16 |
17 | # Suppress IPython UserWarning from nbconvert
18 | warnings.filterwarnings('ignore', message='.*IPython is needed to transform IPython syntax.*')
19 |
20 |
21 | PY_QUERIES = {
22 | "imports": """
23 | (import_statement name: (_) @import)
24 | (import_from_statement) @from_import_stmt
25 | """,
26 | "classes": """
27 | (class_definition
28 | name: (identifier) @name
29 | superclasses: (argument_list)? @superclasses
30 | body: (block) @body)
31 | """,
32 | "functions": """
33 | (function_definition
34 | name: (identifier) @name
35 | parameters: (parameters) @parameters
36 | body: (block) @body
37 | return_type: (_)? @return_type)
38 | """,
39 | "calls": """
40 | (call
41 | function: (identifier) @name)
42 | (call
43 | function: (attribute attribute: (identifier) @name) @full_call)
44 | """,
45 | "variables": """
46 | (assignment
47 | left: (identifier) @name)
48 | """,
49 | "lambda_assignments": """
50 | (assignment
51 | left: (identifier) @name
52 | right: (lambda) @lambda_node)
53 | """,
54 | "docstrings": """
55 | (expression_statement (string) @docstring)
56 | """,
57 | "dict_method_refs": """
58 | (dictionary
59 | (pair
60 | key: (_) @key
61 | value: (attribute) @method_ref))
62 | """,
63 | }
64 |
65 | class PythonTreeSitterParser:
66 | """A Python-specific parser using tree-sitter, encapsulating language-specific logic."""
67 |
68 | def __init__(self, generic_parser_wrapper):
69 | self.generic_parser_wrapper = generic_parser_wrapper
70 | self.language_name = generic_parser_wrapper.language_name
71 | self.language = generic_parser_wrapper.language
72 | self.parser = generic_parser_wrapper.parser
73 |
74 | def _get_node_text(self, node) -> str:
75 | return node.text.decode('utf-8')
76 |
77 | def _get_parent_context(self, node, types=('function_definition', 'class_definition')):
78 | curr = node.parent
79 | while curr:
80 | if curr.type in types:
81 | name_node = curr.child_by_field_name('name')
82 | return self._get_node_text(name_node) if name_node else None, curr.type, curr.start_point[0] + 1
83 | curr = curr.parent
84 | return None, None, None
85 |
86 | def _calculate_complexity(self, node):
87 | complexity_nodes = {
88 | "if_statement", "for_statement", "while_statement", "except_clause",
89 | "with_statement", "boolean_operator", "list_comprehension",
90 | "generator_expression", "case_clause"
91 | }
92 | count = 1
93 |
94 | def traverse(n):
95 | nonlocal count
96 | if n.type in complexity_nodes:
97 | count += 1
98 | for child in n.children:
99 | traverse(child)
100 |
101 | traverse(node)
102 | return count
103 |
104 | def _get_docstring(self, body_node):
105 | if body_node and body_node.child_count > 0:
106 | first_child = body_node.children[0]
107 | if first_child.type == 'expression_statement' and first_child.children[0].type == 'string':
108 | try:
109 | return ast.literal_eval(self._get_node_text(first_child.children[0]))
110 | except (ValueError, SyntaxError):
111 | return self._get_node_text(first_child.children[0])
112 | return None
113 |
114 | def parse(self, file_path: Path, is_dependency: bool = False, is_notebook: bool = False) -> Dict:
115 | """Parses a file and returns its structure in a standardized dictionary format."""
116 | original_file_path = file_path
117 | temp_py_file = None
118 | source_code = None
119 |
120 | try:
121 | if is_notebook:
122 | info_logger(f"Converting notebook {file_path} to temporary Python file.")
123 | with open(file_path, 'r', encoding='utf-8') as f:
124 | notebook_node = nbformat.read(f, as_version=4)
125 |
126 | exporter = PythonExporter()
127 | python_code, _ = exporter.from_notebook_node(notebook_node)
128 |
129 | with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py', encoding='utf-8') as tf:
130 | tf.write(python_code)
131 | temp_py_file = Path(tf.name)
132 |
133 | # The file to be parsed is now the temporary file
134 | file_path = temp_py_file
135 |
136 | with open(file_path, "r", encoding="utf-8") as f:
137 | source_code = f.read()
138 |
139 | tree = self.parser.parse(bytes(source_code, "utf8"))
140 | root_node = tree.root_node
141 |
142 | functions = self._find_functions(root_node)
143 | functions.extend(self._find_lambda_assignments(root_node))
144 | classes = self._find_classes(root_node)
145 | imports = self._find_imports(root_node)
146 | function_calls = self._find_calls(root_node)
147 | variables = self._find_variables(root_node)
148 |
149 | return {
150 | "file_path": str(original_file_path), # Always return the original path
151 | "functions": functions,
152 | "classes": classes,
153 | "variables": variables,
154 | "imports": imports,
155 | "function_calls": function_calls,
156 | "is_dependency": is_dependency,
157 | "lang": self.language_name,
158 | }
159 | except Exception as e:
160 | error_logger(f"Failed to parse {original_file_path}: {e}")
161 | return {"file_path": str(original_file_path), "error": str(e)}
162 | finally:
163 | if temp_py_file and temp_py_file.exists():
164 | os.remove(temp_py_file)
165 | info_logger(f"Removed temporary file: {temp_py_file}")
166 |
167 | def _find_lambda_assignments(self, root_node):
168 | functions = []
169 | query_str = PY_QUERIES.get('lambda_assignments')
170 | if not query_str: return []
171 |
172 | for match in execute_query(self.language, query_str, root_node):
173 | capture_name = match[1]
174 | node = match[0]
175 |
176 | if capture_name == 'name':
177 | assignment_node = node.parent
178 | lambda_node = assignment_node.child_by_field_name('right')
179 | name = self._get_node_text(node)
180 | params_node = lambda_node.child_by_field_name('parameters')
181 |
182 | context, context_type, _ = self._get_parent_context(assignment_node)
183 | class_context, _, _ = self._get_parent_context(assignment_node, types=('class_definition',))
184 |
185 | func_data = {
186 | "name": name,
187 | "line_number": node.start_point[0] + 1,
188 | "end_line": assignment_node.end_point[0] + 1,
189 | "args": [p for p in [self._get_node_text(p) for p in params_node.children if p.type == 'identifier'] if p] if params_node else [],
190 | "source": self._get_node_text(assignment_node),
191 |
192 | "docstring": None,
193 | "cyclomatic_complexity": 1,
194 | "context": context,
195 | "context_type": context_type,
196 | "class_context": class_context,
197 | "decorators": [],
198 | "lang": self.language_name,
199 | "is_dependency": False,
200 | }
201 | functions.append(func_data)
202 | return functions
203 |
204 | def _find_functions(self, root_node):
205 | functions = []
206 | query_str = PY_QUERIES['functions']
207 | for match in execute_query(self.language, query_str, root_node):
208 | capture_name = match[1]
209 | node = match[0]
210 |
211 | if capture_name == 'name':
212 | func_node = node.parent
213 | name = self._get_node_text(node)
214 | params_node = func_node.child_by_field_name('parameters')
215 | body_node = func_node.child_by_field_name('body')
216 |
217 | decorators = [self._get_node_text(child) for child in func_node.children if child.type == 'decorator']
218 |
219 | context, context_type, _ = self._get_parent_context(func_node)
220 | class_context, _, _ = self._get_parent_context(func_node, types=('class_definition',))
221 |
222 | args = []
223 | if params_node:
224 | for p in params_node.children:
225 | arg_text = None
226 | if p.type == 'identifier':
227 | # Simple parameter: def foo(x)
228 | arg_text = self._get_node_text(p)
229 | elif p.type == 'default_parameter':
230 | # Parameter with default: def foo(x=5)
231 | name_node = p.child_by_field_name('name')
232 | if name_node:
233 | arg_text = self._get_node_text(name_node)
234 | elif p.type == 'typed_parameter':
235 | # Typed parameter: def foo(x: int)
236 | name_node = p.child_by_field_name('name')
237 | if name_node:
238 | arg_text = self._get_node_text(name_node)
239 | elif p.type == 'typed_default_parameter':
240 | # Typed parameter with default: def foo(x: int = 5) or def foo(x: str = typer.Argument(...))
241 | name_node = p.child_by_field_name('name')
242 | if name_node:
243 | arg_text = self._get_node_text(name_node)
244 | elif p.type == 'list_splat_pattern' or p.type == 'dictionary_splat_pattern':
245 | # *args or **kwargs
246 | arg_text = self._get_node_text(p)
247 |
248 | if arg_text:
249 | args.append(arg_text)
250 |
251 | func_data = {
252 | "name": name,
253 | "line_number": node.start_point[0] + 1,
254 | "end_line": func_node.end_point[0] + 1,
255 | "args": args,
256 | "source": self._get_node_text(func_node),
257 |
258 | "docstring": self._get_docstring(body_node),
259 | "cyclomatic_complexity": self._calculate_complexity(func_node),
260 | "context": context,
261 | "context_type": context_type,
262 | "class_context": class_context,
263 | "decorators": [d for d in decorators if d],
264 | "lang": self.language_name,
265 | "is_dependency": False,
266 | }
267 | functions.append(func_data)
268 | return functions
269 |
270 | def _find_classes(self, root_node):
271 | classes = []
272 | query_str = PY_QUERIES['classes']
273 | for match in execute_query(self.language, query_str, root_node):
274 | capture_name = match[1]
275 | node = match[0]
276 |
277 | if capture_name == 'name':
278 | class_node = node.parent
279 | name = self._get_node_text(node)
280 | body_node = class_node.child_by_field_name('body')
281 | superclasses_node = class_node.child_by_field_name('superclasses')
282 |
283 | bases = []
284 | if superclasses_node:
285 | bases = [self._get_node_text(child) for child in superclasses_node.children if child.type in ('identifier', 'attribute')]
286 |
287 | decorators = [self._get_node_text(child) for child in class_node.children if child.type == 'decorator']
288 |
289 | context, _, _ = self._get_parent_context(class_node)
290 |
291 | class_data = {
292 | "name": name,
293 | "line_number": node.start_point[0] + 1,
294 | "end_line": class_node.end_point[0] + 1,
295 | "bases": [b for b in bases if b],
296 | "source": self._get_node_text(class_node),
297 | "docstring": self._get_docstring(body_node),
298 | "context": context,
299 | "decorators": [d for d in decorators if d],
300 | "lang": self.language_name,
301 | "is_dependency": False,
302 | }
303 | classes.append(class_data)
304 | return classes
305 |
306 | def _find_imports(self, root_node):
307 | imports = []
308 | seen_modules = set()
309 | query_str = PY_QUERIES['imports']
310 | for node, capture_name in execute_query(self.language, query_str, root_node):
311 | if capture_name in ('import', 'from_import_stmt'):
312 | # For 'import_statement'
313 | if capture_name == 'import':
314 | node_text = self._get_node_text(node)
315 | alias = None
316 | if ' as ' in node_text:
317 | parts = node_text.split(' as ')
318 | full_name = parts[0].strip()
319 | alias = parts[1].strip()
320 | else:
321 | full_name = node_text.strip()
322 |
323 | if full_name in seen_modules:
324 | continue
325 | seen_modules.add(full_name)
326 |
327 | import_data = {
328 | "name": full_name,
329 | "full_import_name": full_name,
330 | "line_number": node.start_point[0] + 1,
331 | "alias": alias,
332 | "context": self._get_parent_context(node)[:2],
333 | "lang": self.language_name,
334 | "is_dependency": False,
335 | }
336 | imports.append(import_data)
337 | # For 'import_from_statement'
338 | elif capture_name == 'from_import_stmt':
339 | module_name_node = node.child_by_field_name('module_name')
340 | if not module_name_node: continue
341 |
342 | module_name = self._get_node_text(module_name_node)
343 |
344 | # Handle 'from ... import ...'
345 | import_list_node = node.child_by_field_name('name')
346 | if import_list_node:
347 | for child in import_list_node.children:
348 | imported_name = None
349 | alias = None
350 | if child.type == 'aliased_import':
351 | name_node = child.child_by_field_name('name')
352 | alias_node = child.child_by_field_name('alias')
353 | if name_node: imported_name = self._get_node_text(name_node)
354 | if alias_node: alias = self._get_node_text(alias_node)
355 | elif child.type == 'dotted_name' or child.type == 'identifier':
356 | imported_name = self._get_node_text(child)
357 |
358 | if imported_name:
359 | full_import_name = f"{module_name}.{imported_name}"
360 | if full_import_name in seen_modules:
361 | continue
362 | seen_modules.add(full_import_name)
363 | imports.append({
364 | "name": imported_name,
365 | "full_import_name": full_import_name,
366 | "line_number": child.start_point[0] + 1,
367 | "alias": alias,
368 | "context": self._get_parent_context(child)[:2],
369 | "lang": self.language_name,
370 | "is_dependency": False,
371 | })
372 |
373 | return imports
374 |
375 | def _find_calls(self, root_node):
376 | calls = []
377 |
378 | # First, find all direct function calls
379 | query_str = PY_QUERIES['calls']
380 | for node, capture_name in execute_query(self.language, query_str, root_node):
381 | if capture_name == 'name':
382 | call_node = node.parent if node.parent.type == 'call' else node.parent.parent
383 | full_call_node = call_node.child_by_field_name('function')
384 |
385 | args = []
386 | arguments_node = call_node.child_by_field_name('arguments')
387 | if arguments_node:
388 | for arg in arguments_node.children:
389 | arg_text = self._get_node_text(arg)
390 | if arg_text and arg_text not in ('(', ')', ','):
391 | args.append(arg_text)
392 |
393 | call_data = {
394 | "name": self._get_node_text(node),
395 | "full_name": self._get_node_text(full_call_node),
396 | "line_number": node.start_point[0] + 1,
397 | "args": args,
398 | "inferred_obj_type": None,
399 | "context": self._get_parent_context(node),
400 | "class_context": self._get_parent_context(node, types=('class_definition',))[:2],
401 | "lang": self.language_name,
402 | "is_dependency": False,
403 | }
404 | calls.append(call_data)
405 |
406 | # Second, find dictionary-based method references (indirect calls)
407 | # This handles patterns like: tool_map = {"name": self.method, ...}
408 | # followed by: handler = tool_map.get(name); handler()
409 | dict_method_calls = self._find_dict_method_references(root_node)
410 | calls.extend(dict_method_calls)
411 |
412 | return calls
413 |
414 | def _find_dict_method_references(self, root_node):
415 | """
416 | Detects indirect function calls through dictionary mappings.
417 |
418 | Example pattern:
419 | tool_map = {
420 | "add_code": self.add_code_to_graph_tool,
421 | "find_code": self.find_code_tool,
422 | }
423 | handler = tool_map.get(tool_name)
424 | if handler:
425 | handler(**args)
426 |
427 | This creates CALLS relationships from the context function to all
428 | methods referenced in the dictionary.
429 | """
430 | calls = []
431 | query_str = PY_QUERIES.get('dict_method_refs')
432 | if not query_str:
433 | return calls
434 |
435 | # Track dictionaries that contain method references
436 | dict_assignments = {} # dict_var_name -> list of method references
437 |
438 | for node, capture_name in execute_query(self.language, query_str, root_node):
439 | if capture_name == 'method_ref':
440 | # Found a method reference in a dictionary value
441 | # Navigate up to find the assignment
442 | dict_node = node.parent # pair node
443 | while dict_node and dict_node.type != 'dictionary':
444 | dict_node = dict_node.parent
445 |
446 | if dict_node:
447 | # Find the assignment node
448 | assignment_node = dict_node.parent
449 | if assignment_node and assignment_node.type == 'assignment':
450 | # Get the variable name being assigned
451 | left_node = assignment_node.child_by_field_name('left')
452 | if left_node:
453 | var_name = self._get_node_text(left_node)
454 | method_ref = self._get_node_text(node)
455 |
456 | # Extract just the method name (remove 'self.')
457 | method_name = method_ref.split('.')[-1] if '.' in method_ref else method_ref
458 |
459 | if var_name not in dict_assignments:
460 | dict_assignments[var_name] = {
461 | 'methods': [],
462 | 'context': self._get_parent_context(assignment_node),
463 | 'line_number': assignment_node.start_point[0] + 1
464 | }
465 |
466 | dict_assignments[var_name]['methods'].append({
467 | 'name': method_name,
468 | 'full_name': method_ref,
469 | 'line_number': node.start_point[0] + 1
470 | })
471 |
472 | # Now create call relationships for each method in the dictionaries
473 | # The context is the function where the dictionary is defined
474 | for dict_var, data in dict_assignments.items():
475 | context, context_type, context_line = data['context']
476 | class_context, _, _ = (None, None, None)
477 |
478 | for method_info in data['methods']:
479 | call_data = {
480 | "name": method_info['name'],
481 | "full_name": method_info['full_name'],
482 | "line_number": method_info['line_number'],
483 | "args": [], # We don't know the args at this point
484 | "inferred_obj_type": None,
485 | "context": (context, context_type, context_line),
486 | "class_context": (class_context, None),
487 | "lang": self.language_name,
488 | "is_dependency": False,
489 | "is_indirect_call": True, # Mark as indirect for debugging
490 | }
491 | calls.append(call_data)
492 |
493 | return calls
494 |
495 | def _find_variables(self, root_node):
496 | variables = []
497 | query_str = PY_QUERIES['variables']
498 | for match in execute_query(self.language, query_str, root_node):
499 | capture_name = match[1]
500 | node = match[0]
501 |
502 | if capture_name == 'name':
503 | assignment_node = node.parent
504 |
505 | # Skip lambda assignments, they are handled by _find_lambda_assignments
506 | right_node = assignment_node.child_by_field_name('right')
507 | if right_node and right_node.type == 'lambda':
508 | continue
509 |
510 | name = self._get_node_text(node)
511 | value = self._get_node_text(right_node) if right_node else None
512 |
513 | type_node = assignment_node.child_by_field_name('type')
514 | type_text = self._get_node_text(type_node) if type_node else None
515 |
516 | context, _, _ = self._get_parent_context(node)
517 | class_context, _, _ = self._get_parent_context(node, types=('class_definition',))
518 |
519 | variable_data = {
520 | "name": name,
521 | "line_number": node.start_point[0] + 1,
522 | "value": value,
523 | "type": type_text,
524 | "context": context,
525 | "class_context": class_context,
526 | "lang": self.language_name,
527 | "is_dependency": False,
528 | }
529 | variables.append(variable_data)
530 | return variables
531 |
532 | def pre_scan_python(files: list[Path], parser_wrapper) -> dict:
533 | """Scans Python files to create a map of class/function names to their file paths."""
534 | imports_map = {}
535 | query_str = """
536 | (class_definition name: (identifier) @name)
537 | (function_definition name: (identifier) @name)
538 | """
539 |
540 | for file_path in files:
541 | temp_py_file = None
542 | try:
543 | source_to_parse = ""
544 | if file_path.suffix == '.ipynb':
545 | with open(file_path, 'r', encoding='utf-8') as f:
546 | notebook_node = nbformat.read(f, as_version=4)
547 | exporter = PythonExporter()
548 | python_code, _ = exporter.from_notebook_node(notebook_node)
549 | with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.py', encoding='utf-8') as tf:
550 | tf.write(python_code)
551 | temp_py_file = Path(tf.name)
552 | with open(temp_py_file, "r", encoding="utf-8") as f:
553 | source_to_parse = f.read()
554 | else:
555 | with open(file_path, "r", encoding="utf-8") as f:
556 | source_to_parse = f.read()
557 |
558 | tree = parser_wrapper.parser.parse(bytes(source_to_parse, "utf8"))
559 |
560 | for capture, _ in execute_query(parser_wrapper.language, query_str, tree.root_node):
561 | name = capture.text.decode('utf-8')
562 | if name not in imports_map:
563 | imports_map[name] = []
564 | imports_map[name].append(str(file_path.resolve()))
565 | except Exception as e:
566 | warning_logger(f"Tree-sitter pre-scan failed for {file_path}: {e}")
567 | finally:
568 | if temp_py_file and temp_py_file.exists():
569 | os.remove(temp_py_file)
570 | return imports_map
```