codegraphcontext 0.1.30__tar.gz → 0.1.32__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {codegraphcontext-0.1.30/src/codegraphcontext.egg-info → codegraphcontext-0.1.32}/PKG-INFO +2 -2
  2. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/README.md +1 -1
  3. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/pyproject.toml +1 -1
  4. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/cli_helpers.py +5 -1
  5. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/main.py +210 -100
  6. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/__init__.py +3 -3
  7. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/server.py +2 -1
  8. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/code_finder.py +103 -29
  9. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/graph_builder.py +4 -4
  10. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/c.py +1 -1
  11. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/cpp.py +7 -7
  12. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/go.py +1 -1
  13. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/javascript.py +1 -1
  14. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/python.py +3 -3
  15. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/ruby.py +3 -3
  16. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/rust.py +3 -3
  17. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/typescript.py +3 -3
  18. codegraphcontext-0.1.32/src/codegraphcontext/tools/languages/typescriptjsx.py +138 -0
  19. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32/src/codegraphcontext.egg-info}/PKG-INFO +2 -2
  20. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext.egg-info/SOURCES.txt +1 -0
  21. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_end_to_end.py +21 -1
  22. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/LICENSE +0 -0
  23. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/MANIFEST.in +0 -0
  24. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/setup.cfg +0 -0
  25. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/__init__.py +0 -0
  26. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/__main__.py +0 -0
  27. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/__init__.py +0 -0
  28. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/config_manager.py +0 -0
  29. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/setup_macos.py +0 -0
  30. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/cli/setup_wizard.py +0 -0
  31. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/database.py +0 -0
  32. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/database_falkordb.py +0 -0
  33. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/falkor_worker.py +0 -0
  34. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/jobs.py +0 -0
  35. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/core/watcher.py +0 -0
  36. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/prompts.py +0 -0
  37. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/__init__.py +0 -0
  38. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/advanced_language_query_tool.py +0 -0
  39. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/csharp.py +0 -0
  40. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/java.py +0 -0
  41. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/kotlin.py +0 -0
  42. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/php.py +0 -0
  43. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/scala.py +0 -0
  44. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/languages/swift.py +0 -0
  45. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/package_resolver.py +0 -0
  46. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/c_toolkit.py +0 -0
  47. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +0 -0
  48. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +0 -0
  49. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/go_toolkit.py +0 -0
  50. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/java_toolkit.py +0 -0
  51. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +0 -0
  52. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/python_toolkit.py +0 -0
  53. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +0 -0
  54. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/rust_toolkit.py +0 -0
  55. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/scala_toolkit.py +0 -0
  56. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/swift_toolkit.py +0 -0
  57. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +0 -0
  58. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/tools/system.py +0 -0
  59. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/utils/debug_log.py +0 -0
  60. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext/utils/tree_sitter_manager.py +0 -0
  61. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
  62. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
  63. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext.egg-info/requires.txt +0 -0
  64. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/src/codegraphcontext.egg-info/top_level.txt +0 -0
  65. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_cpp_parser.py +0 -0
  66. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_database_validation.py +0 -0
  67. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_graph_indexing.py +0 -0
  68. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_graph_indexing_js.py +0 -0
  69. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_kotlin_parser.py +0 -0
  70. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_swift_parser.py +0 -0
  71. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_tree_sitter_manager.py +0 -0
  72. {codegraphcontext-0.1.30 → codegraphcontext-0.1.32}/tests/test_typescript_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegraphcontext
3
- Version: 0.1.30
3
+ Version: 0.1.32
4
4
  Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
5
5
  Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
6
6
  License: MIT License
@@ -91,7 +91,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
91
91
  ![Using the MCP server](https://github.com/Shashankss1205/CodeGraphContext/blob/main/images/Usecase.gif)
92
92
 
93
93
  ## Project Details
94
- - **Version:** 0.1.30
94
+ - **Version:** 0.1.32
95
95
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
96
96
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
97
97
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -29,7 +29,7 @@ A powerful **MCP server** and **CLI toolkit** that indexes local code into a gra
29
29
  ![Using the MCP server](https://github.com/Shashankss1205/CodeGraphContext/blob/main/images/Usecase.gif)
30
30
 
31
31
  ## Project Details
32
- - **Version:** 0.1.30
32
+ - **Version:** 0.1.32
33
33
  - **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
34
34
  - **License:** MIT License (See [LICENSE](LICENSE) for details)
35
35
  - **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codegraphcontext"
3
- version = "0.1.30"
3
+ version = "0.1.32"
4
4
  description = "An MCP server that indexes local code into a graph database to provide context to AI assistants."
5
5
  authors = [{ name = "Shashank Shekhar Singh", email = "shashankshekharsingh1205@gmail.com" }]
6
6
  readme = "README.md"
@@ -423,9 +423,13 @@ def clean_helper():
423
423
  try:
424
424
  with db_manager.get_driver().session() as session:
425
425
  # Find and delete orphaned nodes (nodes not connected to any repository)
426
+ # Using OPTIONAL MATCH for FalkorDB compatibility
426
427
  query = """
427
428
  MATCH (n)
428
- WHERE NOT (n:Repository) AND NOT EXISTS((n)-[]-(:Repository))
429
+ WHERE NOT (n:Repository)
430
+ OPTIONAL MATCH path = (n)-[*]-(r:Repository)
431
+ WITH n, path
432
+ WHERE path IS NULL
429
433
  WITH n LIMIT 1000
430
434
  DETACH DELETE n
431
435
  RETURN count(n) as deleted
@@ -188,46 +188,83 @@ def neo4j_setup_alias():
188
188
 
189
189
  def _load_credentials():
190
190
  """
191
- Loads Neo4j credentials from various sources into environment variables.
192
- Priority order:
193
- 1. Local `mcp.json`
194
- 2. Global `~/.codegraphcontext/.env`
195
- 3. Any `.env` file found in the directory tree.
191
+ Loads configuration and credentials from various sources into environment variables.
192
+ Uses per-variable precedence - each variable is loaded from the highest priority source.
193
+ Priority order (highest to lowest):
194
+ 1. Local `mcp.json` env vars (highest - explicit MCP server config)
195
+ 2. Local `.env` in project directory (high - project-specific overrides)
196
+ 3. Global `~/.codegraphcontext/.env` (lowest - user defaults)
196
197
  """
197
- # 1. Prefer loading from mcp.json
198
- mcp_file_path = Path.cwd() / "mcp.json"
199
- if mcp_file_path.exists():
200
- try:
201
- with open(mcp_file_path, "r") as f:
202
- mcp_config = json.load(f)
203
- server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
204
- for key, value in server_env.items():
205
- os.environ[key] = value
206
- console.print("[green]Loaded Neo4j credentials from local mcp.json.[/green]")
207
- return
208
- except Exception as e:
209
- console.print(f"[bold red]Error loading mcp.json:[/bold red] {e}")
198
+ from dotenv import dotenv_values
199
+
200
+ # Collect all config sources in reverse priority order (lowest to highest)
201
+ config_sources = []
202
+ config_source_names = []
210
203
 
211
- # 2. Try global .env file
204
+ # 3. Global .env file (lowest priority - user defaults)
212
205
  global_env_path = Path.home() / ".codegraphcontext" / ".env"
213
206
  if global_env_path.exists():
214
207
  try:
215
- load_dotenv(dotenv_path=global_env_path)
216
- console.print(f"[green]Loaded Neo4j credentials from global .env file: {global_env_path}[/green]")
217
- return
208
+ config_sources.append(dotenv_values(str(global_env_path)))
209
+ config_source_names.append(str(global_env_path))
218
210
  except Exception as e:
219
- console.print(f"[bold red]Error loading global .env file from {global_env_path}:[/bold red] {e}")
220
-
221
- # 3. Fallback to any discovered .env
211
+ console.print(f"[yellow]Warning: Could not load global .env: {e}[/yellow]")
212
+
213
+ # 2. Local project .env (higher priority - project-specific overrides)
222
214
  try:
223
215
  dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
224
216
  if dotenv_path:
225
- load_dotenv(dotenv_path)
226
- console.print(f"[green]Loaded Neo4j credentials from discovered .env file: {dotenv_path}[/green]")
227
- else:
228
- console.print("[yellow]No local mcp.json or .env file found. Credentials may not be set.[/yellow]")
217
+ config_sources.append(dotenv_values(dotenv_path))
218
+ config_source_names.append(str(dotenv_path))
229
219
  except Exception as e:
230
- console.print(f"[bold red]Error loading .env file:[/bold red] {e}")
220
+ console.print(f"[yellow]Warning: Could not load .env from current directory: {e}[/yellow]")
221
+
222
+ # 1. Local mcp.json (highest priority - explicit MCP server config)
223
+ mcp_file_path = Path.cwd() / "mcp.json"
224
+ if mcp_file_path.exists():
225
+ try:
226
+ with open(mcp_file_path, "r") as f:
227
+ mcp_config = json.load(f)
228
+ server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
229
+ if server_env:
230
+ config_sources.append(server_env)
231
+ config_source_names.append("mcp.json")
232
+ except Exception as e:
233
+ console.print(f"[yellow]Warning: Could not load mcp.json: {e}[/yellow]")
234
+
235
+ # Merge all configs with proper precedence (later sources override earlier ones)
236
+ merged_config = {}
237
+ for config in config_sources:
238
+ merged_config.update(config)
239
+
240
+ # Apply merged config to environment
241
+ for key, value in merged_config.items():
242
+ if value is not None: # Only set non-None values
243
+ os.environ[key] = str(value)
244
+
245
+ # Report what was loaded
246
+ if config_source_names:
247
+ if len(config_source_names) == 1:
248
+ console.print(f"[dim]Loaded configuration from: {config_source_names[-1]}[/dim]")
249
+ else:
250
+ console.print(f"[dim]Loaded configuration from: {', '.join(config_source_names)} (highest priority: {config_source_names[-1]})[/dim]")
251
+ else:
252
+ console.print("[yellow]No configuration file found. Using defaults.[/yellow]")
253
+
254
+ # Show which database is actually being used
255
+ default_db = os.environ.get("DEFAULT_DATABASE", "falkordb").lower()
256
+ if default_db == "neo4j":
257
+ has_neo4j_creds = all([
258
+ os.environ.get("NEO4J_URI"),
259
+ os.environ.get("NEO4J_USERNAME"),
260
+ os.environ.get("NEO4J_PASSWORD")
261
+ ])
262
+ if has_neo4j_creds:
263
+ console.print("[cyan]Using database: Neo4j[/cyan]")
264
+ else:
265
+ console.print("[yellow]⚠ DEFAULT_DATABASE=neo4j but credentials not found. Falling back to FalkorDB.[/yellow]")
266
+ else:
267
+ console.print("[cyan]Using database: FalkorDB[/cyan]")
231
268
 
232
269
 
233
270
  # ============================================================================
@@ -704,19 +741,26 @@ def find_by_name(
704
741
  results = []
705
742
 
706
743
  # Search based on type filter
707
- if not type:
708
- # Search all
744
+ if type is None or type.lower() == 'all':
709
745
  funcs = code_finder.find_by_function_name(name, fuzzy_search=False)
710
746
  classes = code_finder.find_by_class_name(name, fuzzy_search=False)
711
747
  variables = code_finder.find_by_variable_name(name)
712
-
748
+ modules = code_finder.find_by_module_name(name)
749
+ imports = code_finder.find_imports(name)
750
+
713
751
  for f in funcs: f['type'] = 'Function'
714
752
  for c in classes: c['type'] = 'Class'
715
753
  for v in variables: v['type'] = 'Variable'
754
+ for m in modules: m['type'] = 'Module'; m['file_path'] = m.get('name', 'External') # Modules might differ
755
+ for i in imports:
756
+ i['type'] = 'Import'
757
+ i['name'] = i.get('alias') or i.get('imported_name')
716
758
 
717
759
  results.extend(funcs)
718
760
  results.extend(classes)
719
761
  results.extend(variables)
762
+ results.extend(modules)
763
+ results.extend(imports)
720
764
 
721
765
  elif type.lower() == 'function':
722
766
  results = code_finder.find_by_function_name(name, fuzzy_search=False)
@@ -729,6 +773,12 @@ def find_by_name(
729
773
  elif type.lower() == 'variable':
730
774
  results = code_finder.find_by_variable_name(name)
731
775
  for r in results: r['type'] = 'Variable'
776
+
777
+ elif type.lower() == 'module':
778
+ results = code_finder.find_by_module_name(name)
779
+ for r in results:
780
+ r['type'] = 'Module'
781
+ r['file_path'] = r.get('name')
732
782
 
733
783
  elif type.lower() == 'file':
734
784
  # Quick query for file
@@ -744,16 +794,17 @@ def find_by_name(
744
794
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
745
795
  table.add_column("Name", style="cyan")
746
796
  table.add_column("Type", style="bold blue")
747
- table.add_column("File", style="dim", overflow="fold")
748
- table.add_column("Line", style="green", justify="right")
797
+ table.add_column("Location", style="dim", overflow="fold")
749
798
 
750
799
  for res in results:
751
800
  file_path = res.get('file_path', '') or ''
801
+ line_str = str(res.get('line_number', ''))
802
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
803
+
752
804
  table.add_row(
753
805
  res.get('name', ''),
754
806
  res.get('type', 'Unknown'),
755
- file_path, # No truncation
756
- str(res.get('line_number', ''))
807
+ location_str
757
808
  )
758
809
 
759
810
  console.print(f"[cyan]Found {len(results)} matches for '{name}':[/cyan]")
@@ -825,17 +876,18 @@ def find_by_pattern(
825
876
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
826
877
  table.add_column("Name", style="cyan")
827
878
  table.add_column("Type", style="blue")
828
- table.add_column("File", style="dim", overflow="fold")
829
- table.add_column("Line", style="green", justify="right")
830
- table.add_column("Location", style="yellow")
879
+ table.add_column("Location", style="dim", overflow="fold")
880
+ table.add_column("Source", style="yellow")
831
881
 
832
882
  for res in results:
833
883
  file_path = res.get('file_path', '') or ''
884
+ line_str = str(res.get('line_number', '') if res.get('line_number') is not None else '')
885
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
886
+
834
887
  table.add_row(
835
888
  res.get('name', ''),
836
889
  res.get('type', 'Unknown'),
837
- file_path, # No truncation
838
- str(res.get('line_number', '') if res.get('line_number') is not None else ''),
890
+ location_str,
839
891
  "📦 Dependency" if res.get('is_dependency') else "📝 Project"
840
892
  )
841
893
 
@@ -871,16 +923,17 @@ def find_by_type(
871
923
 
872
924
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
873
925
  table.add_column("Name", style="cyan")
874
- table.add_column("File", style="dim", overflow="fold")
875
- table.add_column("Line", style="green", justify="right")
876
- table.add_column("Location", style="yellow")
926
+ table.add_column("Location", style="dim", overflow="fold")
927
+ table.add_column("Source", style="yellow")
877
928
 
878
929
  for res in results:
879
930
  file_path = res.get('file_path', '') or ''
931
+ line_str = str(res.get('line_number', ''))
932
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
933
+
880
934
  table.add_row(
881
935
  res.get('name', ''),
882
- file_path, # No truncation
883
- str(res.get('line_number', '')),
936
+ location_str,
884
937
  "📦 Dependency" if res.get('is_dependency') else "📝 Project"
885
938
  )
886
939
 
@@ -915,15 +968,17 @@ def find_by_variable(
915
968
 
916
969
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
917
970
  table.add_column("Name", style="cyan")
918
- table.add_column("File", style="dim", overflow="fold")
919
- table.add_column("Line", style="green", justify="right")
971
+ table.add_column("Location", style="dim", overflow="fold")
920
972
  table.add_column("Context", style="yellow")
921
973
 
922
974
  for res in results:
975
+ file_path = res.get('file_path', '') or ''
976
+ line_str = str(res.get('line_number', ''))
977
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
978
+
923
979
  table.add_row(
924
980
  res.get('name', ''),
925
- res.get('file_path', ''),
926
- str(res.get('line_number', '')),
981
+ location_str,
927
982
  res.get('context', '') or 'module'
928
983
  )
929
984
 
@@ -974,15 +1029,17 @@ def find_by_content_search(
974
1029
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
975
1030
  table.add_column("Name", style="cyan")
976
1031
  table.add_column("Type", style="blue")
977
- table.add_column("File", style="dim", overflow="fold")
978
- table.add_column("Line", style="green", justify="right")
1032
+ table.add_column("Location", style="dim", overflow="fold")
979
1033
 
980
1034
  for res in results:
1035
+ file_path = res.get('file_path', '') or ''
1036
+ line_str = str(res.get('line_number', ''))
1037
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1038
+
981
1039
  table.add_row(
982
1040
  res.get('name', ''),
983
1041
  res.get('type', 'Unknown'),
984
- res.get('file_path', ''),
985
- str(res.get('line_number', ''))
1042
+ location_str
986
1043
  )
987
1044
 
988
1045
  console.print(f"[cyan]Found {len(results)} content match(es) for '{query}':[/cyan]")
@@ -1017,16 +1074,18 @@ def find_by_decorator_search(
1017
1074
 
1018
1075
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1019
1076
  table.add_column("Function", style="cyan")
1020
- table.add_column("File", style="dim", overflow="fold")
1021
- table.add_column("Line", style="green", justify="right")
1077
+ table.add_column("Location", style="dim", overflow="fold")
1022
1078
  table.add_column("Decorators", style="yellow")
1023
1079
 
1024
1080
  for res in results:
1025
1081
  decorators_str = ", ".join(res.get('decorators', []))
1082
+ file_path = res.get('file_path', '') or ''
1083
+ line_str = str(res.get('line_number', ''))
1084
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1085
+
1026
1086
  table.add_row(
1027
1087
  res.get('function_name', ''),
1028
- res.get('file_path', ''),
1029
- str(res.get('line_number', '')),
1088
+ location_str,
1030
1089
  decorators_str
1031
1090
  )
1032
1091
 
@@ -1062,14 +1121,16 @@ def find_by_argument_search(
1062
1121
 
1063
1122
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1064
1123
  table.add_column("Function", style="cyan")
1065
- table.add_column("File", style="dim", overflow="fold")
1066
- table.add_column("Line", style="green", justify="right")
1124
+ table.add_column("Location", style="dim", overflow="fold")
1067
1125
 
1068
1126
  for res in results:
1127
+ file_path = res.get('file_path', '') or ''
1128
+ line_str = str(res.get('line_number', ''))
1129
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1130
+
1069
1131
  table.add_row(
1070
1132
  res.get('function_name', ''),
1071
- res.get('file_path', ''),
1072
- str(res.get('line_number', ''))
1133
+ location_str
1073
1134
  )
1074
1135
 
1075
1136
  console.print(f"[cyan]Found {len(results)} function(s) with argument '{argument}':[/cyan]")
@@ -1112,15 +1173,17 @@ def analyze_calls(
1112
1173
 
1113
1174
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1114
1175
  table.add_column("Called Function", style="cyan")
1115
- table.add_column("File", style="dim", overflow="fold")
1116
- table.add_column("Line", style="green", justify="right")
1176
+ table.add_column("Location", style="dim", overflow="fold")
1117
1177
  table.add_column("Type", style="yellow")
1118
1178
 
1119
1179
  for result in results:
1180
+ file_path = result.get("called_file_path", "")
1181
+ line_str = str(result.get("called_line_number", ""))
1182
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1183
+
1120
1184
  table.add_row(
1121
1185
  result.get("called_function", ""),
1122
- result.get("called_file_path", ""), # No truncation
1123
- str(result.get("called_line_number", "")),
1186
+ location_str,
1124
1187
  "📦 Dependency" if result.get("called_is_dependency") else "📝 Project"
1125
1188
  )
1126
1189
 
@@ -1157,17 +1220,21 @@ def analyze_callers(
1157
1220
 
1158
1221
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1159
1222
  table.add_column("Caller Function", style="cyan")
1160
- table.add_column("File", style="dim", overflow="fold")
1161
- table.add_column("Line", style="green", justify="right")
1223
+ table.add_column("Location", style="green")
1162
1224
  table.add_column("Call Type", style="yellow")
1225
+
1163
1226
 
1164
1227
  for result in results:
1228
+ file_path = result.get("caller_file_path", "")
1229
+ line_number = result.get("caller_line_number")
1230
+
1231
+ location = f"{file_path}:{line_number}" if line_number else file_path
1232
+
1165
1233
  table.add_row(
1166
1234
  result.get("caller_function", ""),
1167
- result.get("caller_file_path", ""), # No truncation
1168
- str(result.get("caller_line_number", "")),
1235
+ location,
1169
1236
  "📦 Dependency" if result.get("caller_is_dependency") else "📝 Project"
1170
- )
1237
+ )
1171
1238
 
1172
1239
  console.print(f"\n[bold cyan]Functions that call '{function}':[/bold cyan]")
1173
1240
  console.print(table)
@@ -1179,13 +1246,16 @@ def analyze_callers(
1179
1246
  def analyze_chain(
1180
1247
  from_func: str = typer.Argument(..., help="Starting function"),
1181
1248
  to_func: str = typer.Argument(..., help="Target function"),
1182
- max_depth: int = typer.Option(5, "--depth", "-d", help="Maximum call chain depth")
1249
+ max_depth: int = typer.Option(5, "--depth", "-d", help="Maximum call chain depth"),
1250
+ from_file: Optional[str] = typer.Option(None, "--from-file", help="File for starting function"),
1251
+ to_file: Optional[str] = typer.Option(None, "--to-file", help="File for target function")
1183
1252
  ):
1184
1253
  """
1185
1254
  Show call chain between two functions.
1186
1255
 
1187
1256
  Example:
1188
1257
  cgc analyze chain main process_data --depth 10
1258
+ cgc analyze chain main process --from-file main.py --to-file utils.py
1189
1259
  """
1190
1260
  _load_credentials()
1191
1261
  services = _initialize_services()
@@ -1194,7 +1264,7 @@ def analyze_chain(
1194
1264
  db_manager, graph_builder, code_finder = services
1195
1265
 
1196
1266
  try:
1197
- results = code_finder.find_function_call_chain(from_func, to_func, max_depth)
1267
+ results = code_finder.find_function_call_chain(from_func, to_func, max_depth, from_file, to_file)
1198
1268
 
1199
1269
  if not results:
1200
1270
  console.print(f"[yellow]No call chain found between '{from_func}' and '{to_func}' within depth {max_depth}[/yellow]")
@@ -1204,10 +1274,36 @@ def analyze_chain(
1204
1274
  console.print(f"\n[bold cyan]Call Chain #{idx} (length: {chain.get('chain_length', 0)}):[/bold cyan]")
1205
1275
 
1206
1276
  functions = chain.get('function_chain', [])
1277
+ call_details = chain.get('call_details', [])
1278
+
1207
1279
  for i, func in enumerate(functions):
1208
1280
  indent = " " * i
1209
- arrow = "→ " if i < len(functions) - 1 else ""
1210
- console.print(f"{indent}{arrow}[cyan]{func.get('name', 'Unknown')}[/cyan] [dim]({func.get('file_path', '')}:{func.get('line_number', '')})[/dim]")
1281
+
1282
+ # Print function
1283
+ console.print(f"{indent}[cyan]{func.get('name', 'Unknown')}[/cyan] [dim]({func.get('file_path', '')}:{func.get('line_number', '')})[/dim]")
1284
+
1285
+ # If there is a next step, print the connecting call detail
1286
+ if i < len(functions) - 1 and i < len(call_details):
1287
+ detail = call_details[i]
1288
+ line = detail.get('call_line', '?')
1289
+
1290
+ # Format args for display
1291
+ args_info = ""
1292
+ args_val = detail.get('args', [])
1293
+ if args_val:
1294
+ if isinstance(args_val, list):
1295
+ # Filter legacy punctuation just in case
1296
+ clean_args = [str(a) for a in args_val if str(a) not in ('(', ')', ',')]
1297
+ args_str = ", ".join(clean_args)
1298
+ else:
1299
+ args_str = str(args_val)
1300
+
1301
+ # Truncate if too long
1302
+ if len(args_str) > 50:
1303
+ args_str = args_str[:47] + "..."
1304
+ args_info = f" [dim]({args_str})[/dim]"
1305
+
1306
+ console.print(f"{indent} ⬇ [dim]calls at line {line}[/dim]{args_info}")
1211
1307
  finally:
1212
1308
  db_manager.close_driver()
1213
1309
 
@@ -1240,13 +1336,15 @@ def analyze_dependencies(
1240
1336
  if results.get('importers'):
1241
1337
  console.print(f"\n[bold cyan]Files that import '{target}':[/bold cyan]")
1242
1338
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1243
- table.add_column("File", style="cyan", overflow="fold")
1244
- table.add_column("Line", style="green", justify="right")
1339
+ table.add_column("Location", style="cyan", overflow="fold")
1245
1340
 
1246
1341
  for imp in results['importers']:
1342
+ file_path = imp.get('importer_file_path', '')
1343
+ line_str = str(imp.get('import_line_number', ''))
1344
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1345
+
1247
1346
  table.add_row(
1248
- imp.get('importer_file_path', ''),
1249
- str(imp.get('import_line_number', ''))
1347
+ location_str
1250
1348
  )
1251
1349
  console.print(table)
1252
1350
 
@@ -1323,7 +1421,8 @@ def analyze_inheritance_tree(
1323
1421
  def analyze_complexity(
1324
1422
  path: Optional[str] = typer.Argument(None, help="Specific function name to analyze"),
1325
1423
  threshold: int = typer.Option(10, "--threshold", "-t", help="Complexity threshold for warnings"),
1326
- limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show")
1424
+ limit: int = typer.Option(20, "--limit", "-l", help="Maximum results to show"),
1425
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path (only used when function name is provided)")
1327
1426
  ):
1328
1427
  """
1329
1428
  Show cyclomatic complexity for functions.
@@ -1332,6 +1431,7 @@ def analyze_complexity(
1332
1431
  cgc analyze complexity # Most complex functions
1333
1432
  cgc analyze complexity --threshold 15 # Functions over threshold
1334
1433
  cgc analyze complexity my_function # Specific function
1434
+ cgc analyze complexity my_function -f file.py # Specific function in file
1335
1435
  """
1336
1436
  _load_credentials()
1337
1437
  services = _initialize_services()
@@ -1342,7 +1442,7 @@ def analyze_complexity(
1342
1442
  try:
1343
1443
  if path:
1344
1444
  # Specific function
1345
- result = code_finder.get_cyclomatic_complexity(path)
1445
+ result = code_finder.get_cyclomatic_complexity(path, file)
1346
1446
  if result:
1347
1447
  console.print(f"\n[bold cyan]Complexity for '{path}':[/bold cyan]")
1348
1448
  console.print(f" Cyclomatic Complexity: [yellow]{result.get('complexity', 'N/A')}[/yellow]")
@@ -1361,17 +1461,19 @@ def analyze_complexity(
1361
1461
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1362
1462
  table.add_column("Function", style="cyan")
1363
1463
  table.add_column("Complexity", style="yellow", justify="right")
1364
- table.add_column("File", style="dim", overflow="fold")
1365
- table.add_column("Line", style="green", justify="right")
1464
+ table.add_column("Location", style="dim", overflow="fold")
1366
1465
 
1367
1466
  for func in results:
1368
1467
  complexity = func.get('complexity', 0)
1369
1468
  color = "red" if complexity > threshold else "yellow" if complexity > threshold/2 else "green"
1469
+ file_path = func.get('file_path', '')
1470
+ line_str = str(func.get('line_number', ''))
1471
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1472
+
1370
1473
  table.add_row(
1371
1474
  func.get('function_name', ''),
1372
1475
  f"[{color}]{complexity}[/{color}]",
1373
- func.get('file_path', ''), # No truncation
1374
- str(func.get('line_number', ''))
1476
+ location_str
1375
1477
  )
1376
1478
 
1377
1479
  console.print(f"\n[bold cyan]Most Complex Functions (threshold: {threshold}):[/bold cyan]")
@@ -1410,14 +1512,16 @@ def analyze_dead_code(
1410
1512
 
1411
1513
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1412
1514
  table.add_column("Function", style="cyan")
1413
- table.add_column("File", style="dim", overflow="fold")
1414
- table.add_column("Line", style="green", justify="right")
1515
+ table.add_column("Location", style="dim", overflow="fold")
1415
1516
 
1416
1517
  for func in unused_funcs:
1518
+ file_path = func.get('file_path', '')
1519
+ line_str = str(func.get('line_number', ''))
1520
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1521
+
1417
1522
  table.add_row(
1418
1523
  func.get('function_name', ''),
1419
- func.get('file_path', ''), # No truncation
1420
- str(func.get('line_number', ''))
1524
+ location_str
1421
1525
  )
1422
1526
 
1423
1527
  console.print(f"\n[bold yellow]⚠️ Potentially Unused Functions:[/bold yellow]")
@@ -1456,15 +1560,17 @@ def analyze_overrides(
1456
1560
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1457
1561
  table.add_column("Class", style="cyan")
1458
1562
  table.add_column("Function", style="green")
1459
- table.add_column("File", style="dim", overflow="fold")
1460
- table.add_column("Line", style="yellow", justify="right")
1563
+ table.add_column("Location", style="dim", overflow="fold")
1461
1564
 
1462
1565
  for res in results:
1566
+ file_path = res.get('class_file_path', '')
1567
+ line_str = str(res.get('function_line_number', ''))
1568
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1569
+
1463
1570
  table.add_row(
1464
1571
  res.get('class_name', ''),
1465
1572
  res.get('function_name', ''),
1466
- res.get('class_file_path', ''),
1467
- str(res.get('function_line_number', ''))
1573
+ location_str
1468
1574
  )
1469
1575
 
1470
1576
  console.print(f"\n[bold cyan]Found {len(results)} implementation(s) of '{function_name}':[/bold cyan]")
@@ -1474,7 +1580,8 @@ def analyze_overrides(
1474
1580
 
1475
1581
  @analyze_app.command("variable")
1476
1582
  def analyze_variable_usage(
1477
- variable_name: str = typer.Argument(..., help="Variable name to analyze")
1583
+ variable_name: str = typer.Argument(..., help="Variable name to analyze"),
1584
+ file: Optional[str] = typer.Option(None, "--file", "-f", help="Specific file path")
1478
1585
  ):
1479
1586
  """
1480
1587
  Analyze where a variable is defined and used across the codebase.
@@ -1484,6 +1591,7 @@ def analyze_variable_usage(
1484
1591
  Example:
1485
1592
  cgc analyze variable MAX_RETRIES
1486
1593
  cgc analyze variable config
1594
+ cgc analyze variable x --file math_utils.py
1487
1595
  """
1488
1596
  _load_credentials()
1489
1597
  services = _initialize_services()
@@ -1493,7 +1601,7 @@ def analyze_variable_usage(
1493
1601
 
1494
1602
  try:
1495
1603
  # Get variable usage scope
1496
- scope_results = code_finder.find_variable_usage_scope(variable_name)
1604
+ scope_results = code_finder.find_variable_usage_scope(variable_name, file)
1497
1605
  instances = scope_results.get('instances', [])
1498
1606
 
1499
1607
  if not instances:
@@ -1516,15 +1624,17 @@ def analyze_variable_usage(
1516
1624
 
1517
1625
  table = Table(show_header=True, header_style="bold magenta", box=box.ROUNDED)
1518
1626
  table.add_column("Scope Name", style="cyan")
1519
- table.add_column("File", style="dim", overflow="fold")
1520
- table.add_column("Line", style="green", justify="right")
1627
+ table.add_column("Location", style="dim", overflow="fold")
1521
1628
  table.add_column("Value", style="yellow")
1522
1629
 
1523
1630
  for item in items:
1631
+ file_path = item.get('file_path', '')
1632
+ line_str = str(item.get('line_number', ''))
1633
+ location_str = f"{file_path}:{line_str}" if line_str else file_path
1634
+
1524
1635
  table.add_row(
1525
1636
  item.get('scope_name', ''),
1526
- item.get('file_path', ''),
1527
- str(item.get('line_number', '')),
1637
+ location_str,
1528
1638
  str(item.get('variable_value', ''))[:50] if item.get('variable_value') else '-'
1529
1639
  )
1530
1640