codegraph-nav 0.1.0__py3-none-any.whl

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 (41) hide show
  1. codegraph_nav/__init__.py +194 -0
  2. codegraph_nav/ast_grep_analyzer.py +448 -0
  3. codegraph_nav/cli.py +223 -0
  4. codegraph_nav/code_navigator.py +1328 -0
  5. codegraph_nav/code_search.py +1009 -0
  6. codegraph_nav/colors.py +209 -0
  7. codegraph_nav/completions.py +354 -0
  8. codegraph_nav/dart_analyzer.py +301 -0
  9. codegraph_nav/dependency_graph.py +814 -0
  10. codegraph_nav/domain/__init__.py +20 -0
  11. codegraph_nav/domain/routes.py +337 -0
  12. codegraph_nav/domain/schemas.py +229 -0
  13. codegraph_nav/domain/tags.py +87 -0
  14. codegraph_nav/exporters.py +563 -0
  15. codegraph_nav/go_analyzer.py +273 -0
  16. codegraph_nav/graph/__init__.py +72 -0
  17. codegraph_nav/graph/builder.py +409 -0
  18. codegraph_nav/graph/communities.py +402 -0
  19. codegraph_nav/graph/flows.py +311 -0
  20. codegraph_nav/graph/query.py +380 -0
  21. codegraph_nav/graph/schema.py +266 -0
  22. codegraph_nav/graph/search.py +257 -0
  23. codegraph_nav/graph/store.py +517 -0
  24. codegraph_nav/hints.py +195 -0
  25. codegraph_nav/import_resolver.py +891 -0
  26. codegraph_nav/js_ts_analyzer.py +564 -0
  27. codegraph_nav/line_reader.py +664 -0
  28. codegraph_nav/mcp/__init__.py +39 -0
  29. codegraph_nav/mcp/__main__.py +5 -0
  30. codegraph_nav/mcp/server.py +2228 -0
  31. codegraph_nav/py.typed +2 -0
  32. codegraph_nav/ruby_analyzer.py +259 -0
  33. codegraph_nav/rust_analyzer.py +379 -0
  34. codegraph_nav/token_efficient_renderer.py +743 -0
  35. codegraph_nav/watcher.py +382 -0
  36. codegraph_nav-0.1.0.dist-info/METADATA +487 -0
  37. codegraph_nav-0.1.0.dist-info/RECORD +41 -0
  38. codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
  39. codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
  40. codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
  41. codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env python3
2
+ """Terminal color utilities for Code Navigator.
3
+
4
+ Provides ANSI color support with automatic detection of terminal capabilities.
5
+ Respects the NO_COLOR environment variable (https://no-color.org/).
6
+
7
+ Example:
8
+ >>> from codegraph_nav.colors import Colors
9
+ >>> c = Colors()
10
+ >>> print(c.green("Success!"))
11
+ >>> print(c.cyan("Line 42"))
12
+ """
13
+
14
+ import os
15
+ import sys
16
+ import threading
17
+
18
+ __version__ = "0.1.0"
19
+
20
+
21
+ class Colors:
22
+ """ANSI color utility class with automatic terminal detection.
23
+
24
+ Attributes:
25
+ enabled: Whether colors are enabled (auto-detected or manually set).
26
+
27
+ Example:
28
+ >>> c = Colors()
29
+ >>> print(c.green("found") + " in " + c.cyan("file.py"))
30
+ """
31
+
32
+ # ANSI escape codes
33
+ RESET = "\033[0m"
34
+ BOLD = "\033[1m"
35
+ DIM = "\033[2m"
36
+
37
+ # Foreground colors
38
+ BLACK = "\033[30m"
39
+ RED = "\033[31m"
40
+ GREEN = "\033[32m"
41
+ YELLOW = "\033[33m"
42
+ BLUE = "\033[34m"
43
+ MAGENTA = "\033[35m"
44
+ CYAN = "\033[36m"
45
+ WHITE = "\033[37m"
46
+
47
+ # Bright foreground colors
48
+ BRIGHT_BLACK = "\033[90m"
49
+ BRIGHT_RED = "\033[91m"
50
+ BRIGHT_GREEN = "\033[92m"
51
+ BRIGHT_YELLOW = "\033[93m"
52
+ BRIGHT_BLUE = "\033[94m"
53
+ BRIGHT_MAGENTA = "\033[95m"
54
+ BRIGHT_CYAN = "\033[96m"
55
+ BRIGHT_WHITE = "\033[97m"
56
+
57
+ def __init__(self, enabled: bool | None = None):
58
+ """Initialize Colors with optional override.
59
+
60
+ Args:
61
+ enabled: Force colors on/off. If None, auto-detect.
62
+ """
63
+ if enabled is not None:
64
+ self.enabled = enabled
65
+ else:
66
+ self.enabled = self._should_enable_colors()
67
+
68
+ def _should_enable_colors(self) -> bool:
69
+ """Determine if colors should be enabled based on environment."""
70
+ # Respect NO_COLOR environment variable (https://no-color.org/)
71
+ if os.environ.get("NO_COLOR"):
72
+ return False
73
+
74
+ # Check FORCE_COLOR for explicit enable
75
+ if os.environ.get("FORCE_COLOR"):
76
+ return True
77
+
78
+ # Check if stdout is a TTY
79
+ if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
80
+ return False
81
+
82
+ # Check TERM environment variable
83
+ term = os.environ.get("TERM", "")
84
+ if term == "dumb":
85
+ return False
86
+
87
+ # Windows: check for Windows Terminal or newer cmd
88
+ if sys.platform == "win32":
89
+ # Windows Terminal and modern Windows 10+ support ANSI
90
+ return (
91
+ os.environ.get("WT_SESSION") # Windows Terminal
92
+ or os.environ.get("ANSICON") # ANSICON
93
+ or "256color" in term
94
+ or os.environ.get("TERM_PROGRAM") == "vscode"
95
+ )
96
+
97
+ return True
98
+
99
+ def _colorize(self, text: str, color: str) -> str:
100
+ """Apply color to text if colors are enabled."""
101
+ if not self.enabled:
102
+ return text
103
+ return f"{color}{text}{self.RESET}"
104
+
105
+ # Basic colors
106
+ def red(self, text: str) -> str:
107
+ """Red text (errors, warnings)."""
108
+ return self._colorize(text, self.RED)
109
+
110
+ def green(self, text: str) -> str:
111
+ """Green text (success, found items)."""
112
+ return self._colorize(text, self.GREEN)
113
+
114
+ def yellow(self, text: str) -> str:
115
+ """Yellow text (context, warnings)."""
116
+ return self._colorize(text, self.YELLOW)
117
+
118
+ def blue(self, text: str) -> str:
119
+ """Blue text."""
120
+ return self._colorize(text, self.BLUE)
121
+
122
+ def magenta(self, text: str) -> str:
123
+ """Magenta text (types, decorators)."""
124
+ return self._colorize(text, self.MAGENTA)
125
+
126
+ def cyan(self, text: str) -> str:
127
+ """Cyan text (line numbers, file paths)."""
128
+ return self._colorize(text, self.CYAN)
129
+
130
+ def white(self, text: str) -> str:
131
+ """White text."""
132
+ return self._colorize(text, self.WHITE)
133
+
134
+ # Bright colors
135
+ def bright_green(self, text: str) -> str:
136
+ """Bright green text."""
137
+ return self._colorize(text, self.BRIGHT_GREEN)
138
+
139
+ def bright_yellow(self, text: str) -> str:
140
+ """Bright yellow text."""
141
+ return self._colorize(text, self.BRIGHT_YELLOW)
142
+
143
+ def bright_cyan(self, text: str) -> str:
144
+ """Bright cyan text."""
145
+ return self._colorize(text, self.BRIGHT_CYAN)
146
+
147
+ # Styles
148
+ def bold(self, text: str) -> str:
149
+ """Bold text."""
150
+ return self._colorize(text, self.BOLD)
151
+
152
+ def dim(self, text: str) -> str:
153
+ """Dim text (less prominent)."""
154
+ return self._colorize(text, self.DIM)
155
+
156
+ # Combined styles
157
+ def success(self, text: str) -> str:
158
+ """Success message (bold green)."""
159
+ if not self.enabled:
160
+ return text
161
+ return f"{self.BOLD}{self.GREEN}{text}{self.RESET}"
162
+
163
+ def error(self, text: str) -> str:
164
+ """Error message (bold red)."""
165
+ if not self.enabled:
166
+ return text
167
+ return f"{self.BOLD}{self.RED}{text}{self.RESET}"
168
+
169
+ def warning(self, text: str) -> str:
170
+ """Warning message (yellow)."""
171
+ return self.yellow(text)
172
+
173
+ def info(self, text: str) -> str:
174
+ """Info message (cyan)."""
175
+ return self.cyan(text)
176
+
177
+
178
+ # Global instance for convenience (thread-safe singleton)
179
+ _colors = None
180
+ _colors_lock = threading.Lock()
181
+
182
+
183
+ def get_colors(no_color: bool = False) -> Colors:
184
+ """Get a Colors instance, optionally disabling colors.
185
+
186
+ Thread-safe singleton pattern: the global instance is created once
187
+ and reused across all threads.
188
+
189
+ Args:
190
+ no_color: If True, return a new disabled Colors instance
191
+ (not cached, allows per-call override).
192
+
193
+ Returns:
194
+ Colors instance configured appropriately.
195
+ """
196
+ global _colors
197
+
198
+ # For no_color=True, always return a fresh disabled instance
199
+ # This allows callers to override colors on a per-call basis
200
+ if no_color:
201
+ return Colors(enabled=False)
202
+
203
+ # Double-checked locking pattern for thread-safe lazy initialization
204
+ if _colors is None:
205
+ with _colors_lock:
206
+ # Check again inside the lock (another thread may have initialized)
207
+ if _colors is None:
208
+ _colors = Colors()
209
+ return _colors
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env python3
2
+ """Shell completion scripts generator for Code Navigator.
3
+
4
+ Generates bash and zsh completion scripts that provide autocompletion
5
+ for codegraph-nav commands and symbol names from the code map.
6
+
7
+ Example:
8
+ # Generate bash completion
9
+ $ codegraph-nav completion bash > ~/.bash_completion.d/codegraph-nav
10
+
11
+ # Generate zsh completion
12
+ $ codegraph-nav completion zsh > ~/.zfunc/_codegraph_nav
13
+
14
+ # Source directly (bash)
15
+ $ eval "$(codegraph-nav completion bash)"
16
+ """
17
+
18
+ import json
19
+ import sys
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ BASH_COMPLETION_TEMPLATE = """# Bash completion for codegraph-nav
24
+ # Generated by codegraph-nav
25
+
26
+ _codegraph_completions() {
27
+ local cur prev words cword
28
+ _init_completion || return
29
+
30
+ local commands="map search read stats completion watch export"
31
+ local map_opts="-o --output -i --ignore --incremental --git-only --use-gitignore --compact --no-color -h --help"
32
+ local search_opts="-m --map -t --type -f --file --files --structure --deps --stats --check-stale --warn-stale --since-commit -l --limit --no-fuzzy --compact -o --output --no-color -h --help"
33
+ local read_opts="-c --context --symbol -r --root --compact -o --output --no-color -h --help"
34
+ local export_opts="-m --map -f --format -o --output --no-color -h --help"
35
+ local watch_opts="-o --output -i --ignore --git-only --use-gitignore --compact --no-color --debounce -h --help"
36
+ local completion_opts="bash zsh"
37
+ local types="function class method interface struct trait enum type"
38
+
39
+ case "${cword}" in
40
+ 1)
41
+ COMPREPLY=($(compgen -W "${commands}" -- "${cur}"))
42
+ return
43
+ ;;
44
+ esac
45
+
46
+ case "${words[1]}" in
47
+ map)
48
+ if [[ "${cur}" == -* ]]; then
49
+ COMPREPLY=($(compgen -W "${map_opts}" -- "${cur}"))
50
+ else
51
+ _filedir -d
52
+ fi
53
+ ;;
54
+ search)
55
+ case "${prev}" in
56
+ -t|--type)
57
+ COMPREPLY=($(compgen -W "${types}" -- "${cur}"))
58
+ ;;
59
+ -m|--map)
60
+ _filedir json
61
+ ;;
62
+ -o|--output)
63
+ COMPREPLY=($(compgen -W "json table" -- "${cur}"))
64
+ ;;
65
+ *)
66
+ if [[ "${cur}" == -* ]]; then
67
+ COMPREPLY=($(compgen -W "${search_opts}" -- "${cur}"))
68
+ else
69
+ # Try to complete symbol names from codegraph-nav
70
+ _codegraph_complete_symbols
71
+ fi
72
+ ;;
73
+ esac
74
+ ;;
75
+ read)
76
+ if [[ "${cur}" == -* ]]; then
77
+ COMPREPLY=($(compgen -W "${read_opts}" -- "${cur}"))
78
+ else
79
+ _filedir
80
+ fi
81
+ ;;
82
+ stats)
83
+ if [[ "${cur}" == -* ]]; then
84
+ COMPREPLY=($(compgen -W "-m --map --compact -o --output --no-color -h --help" -- "${cur}"))
85
+ fi
86
+ ;;
87
+ completion)
88
+ COMPREPLY=($(compgen -W "${completion_opts}" -- "${cur}"))
89
+ ;;
90
+ watch)
91
+ if [[ "${cur}" == -* ]]; then
92
+ COMPREPLY=($(compgen -W "${watch_opts}" -- "${cur}"))
93
+ else
94
+ _filedir -d
95
+ fi
96
+ ;;
97
+ export)
98
+ case "${prev}" in
99
+ -f|--format)
100
+ COMPREPLY=($(compgen -W "markdown html graphviz" -- "${cur}"))
101
+ ;;
102
+ -m|--map)
103
+ _filedir json
104
+ ;;
105
+ -o|--output)
106
+ _filedir
107
+ ;;
108
+ *)
109
+ if [[ "${cur}" == -* ]]; then
110
+ COMPREPLY=($(compgen -W "${export_opts}" -- "${cur}"))
111
+ fi
112
+ ;;
113
+ esac
114
+ ;;
115
+ esac
116
+ }
117
+
118
+ _codegraph_complete_symbols() {
119
+ # Try to read symbols from .codegraph.json in current directory
120
+ local mapfile=".codegraph.json"
121
+ if [[ -f "${mapfile}" ]]; then
122
+ local symbols=$(python3 -c "
123
+ import json
124
+ try:
125
+ with open('${mapfile}') as f:
126
+ data = json.load(f)
127
+ symbols = set()
128
+ for file_info in data.get('files', {}).values():
129
+ for sym in file_info.get('symbols', []):
130
+ symbols.add(sym['name'])
131
+ print(' '.join(sorted(symbols)[:50]))
132
+ except (OSError, json.JSONDecodeError, KeyError, TypeError):
133
+ pass
134
+ " 2>/dev/null)
135
+ COMPREPLY=($(compgen -W "${symbols}" -- "${cur}"))
136
+ fi
137
+ }
138
+
139
+ complete -F _codegraph_completions codegraph-nav
140
+ """
141
+
142
+ ZSH_COMPLETION_TEMPLATE = """#compdef codegraph-nav
143
+ # Zsh completion for codegraph-nav
144
+ # Generated by codegraph-nav
145
+
146
+ _codegraph_nav() {
147
+ local -a commands
148
+ commands=(
149
+ 'map:Generate a code map of a codebase'
150
+ 'search:Search for symbols in the code map'
151
+ 'read:Read specific lines from files'
152
+ 'stats:Show codebase statistics'
153
+ 'completion:Generate shell completion script'
154
+ 'watch:Watch for changes and auto-update map'
155
+ 'export:Export code map to different formats'
156
+ )
157
+
158
+ local -a types
159
+ types=(function class method interface struct trait enum type)
160
+
161
+ local -a formats
162
+ formats=(markdown html graphviz)
163
+
164
+ _arguments -C \\
165
+ '1:command:->command' \\
166
+ '*::arg:->args'
167
+
168
+ case "$state" in
169
+ command)
170
+ _describe -t commands 'codegraph-nav command' commands
171
+ ;;
172
+ args)
173
+ case "$words[1]" in
174
+ map)
175
+ _arguments \\
176
+ '1:path:_files -/' \\
177
+ '-o[Output file path]:file:_files' \\
178
+ '--output[Output file path]:file:_files' \\
179
+ '*-i[Patterns to ignore]:pattern:' \\
180
+ '*--ignore[Patterns to ignore]:pattern:' \\
181
+ '--incremental[Only update changed files]' \\
182
+ '--git-only[Only scan git-tracked files]' \\
183
+ '--use-gitignore[Use .gitignore patterns]' \\
184
+ '--compact[Output compact JSON]' \\
185
+ '--no-color[Disable colored output]' \\
186
+ '(-h --help)'{-h,--help}'[Show help]'
187
+ ;;
188
+ search)
189
+ _arguments \\
190
+ '1:query:_codegraph_symbols' \\
191
+ '-m[Path to code map]:file:_files -g "*.json"' \\
192
+ '--map[Path to code map]:file:_files -g "*.json"' \\
193
+ '-t[Filter by symbol type]:type:(${types})' \\
194
+ '--type[Filter by symbol type]:type:(${types})' \\
195
+ '-f[Filter by file pattern]:pattern:' \\
196
+ '--file[Filter by file pattern]:pattern:' \\
197
+ '--files[Search for files instead of symbols]' \\
198
+ '--structure[Show structure of a file]:file:_files' \\
199
+ '--deps[Show dependencies of a symbol]:symbol:_codegraph_symbols' \\
200
+ '--stats[Show codebase statistics]' \\
201
+ '--check-stale[Check if files have changed]' \\
202
+ '--warn-stale[Warn if files are stale]' \\
203
+ '--since-commit[Show changes since commit]:commit:' \\
204
+ '-l[Maximum results]:number:' \\
205
+ '--limit[Maximum results]:number:' \\
206
+ '--no-fuzzy[Disable fuzzy matching]' \\
207
+ '--compact[Output compact JSON]' \\
208
+ '-o[Output format]:format:(json table)' \\
209
+ '--output[Output format]:format:(json table)' \\
210
+ '--no-color[Disable colored output]' \\
211
+ '(-h --help)'{-h,--help}'[Show help]'
212
+ ;;
213
+ read)
214
+ _arguments \\
215
+ '1:file:_files' \\
216
+ '2:lines:' \\
217
+ '-c[Context lines]:number:' \\
218
+ '--context[Context lines]:number:' \\
219
+ '--symbol[Read entire symbol]' \\
220
+ '-r[Root path]:path:_files -/' \\
221
+ '--root[Root path]:path:_files -/' \\
222
+ '--compact[Output compact JSON]' \\
223
+ '-o[Output format]:format:(json code)' \\
224
+ '--output[Output format]:format:(json code)' \\
225
+ '--no-color[Disable colored output]' \\
226
+ '(-h --help)'{-h,--help}'[Show help]'
227
+ ;;
228
+ stats)
229
+ _arguments \\
230
+ '-m[Path to code map]:file:_files -g "*.json"' \\
231
+ '--map[Path to code map]:file:_files -g "*.json"' \\
232
+ '--compact[Output compact JSON]' \\
233
+ '-o[Output format]:format:(json table)' \\
234
+ '--output[Output format]:format:(json table)' \\
235
+ '--no-color[Disable colored output]' \\
236
+ '(-h --help)'{-h,--help}'[Show help]'
237
+ ;;
238
+ completion)
239
+ _arguments '1:shell:(bash zsh)'
240
+ ;;
241
+ watch)
242
+ _arguments \\
243
+ '1:path:_files -/' \\
244
+ '-o[Output file path]:file:_files' \\
245
+ '--output[Output file path]:file:_files' \\
246
+ '*-i[Patterns to ignore]:pattern:' \\
247
+ '*--ignore[Patterns to ignore]:pattern:' \\
248
+ '--git-only[Only scan git-tracked files]' \\
249
+ '--use-gitignore[Use .gitignore patterns]' \\
250
+ '--compact[Output compact JSON]' \\
251
+ '--no-color[Disable colored output]' \\
252
+ '--debounce[Debounce time in seconds]:seconds:' \\
253
+ '(-h --help)'{-h,--help}'[Show help]'
254
+ ;;
255
+ export)
256
+ _arguments \\
257
+ '-m[Path to code map]:file:_files -g "*.json"' \\
258
+ '--map[Path to code map]:file:_files -g "*.json"' \\
259
+ '-f[Export format]:format:(${formats})' \\
260
+ '--format[Export format]:format:(${formats})' \\
261
+ '-o[Output file]:file:_files' \\
262
+ '--output[Output file]:file:_files' \\
263
+ '--no-color[Disable colored output]' \\
264
+ '(-h --help)'{-h,--help}'[Show help]'
265
+ ;;
266
+ esac
267
+ ;;
268
+ esac
269
+ }
270
+
271
+ _codegraph_symbols() {
272
+ local mapfile=".codegraph.json"
273
+ if [[ -f "$mapfile" ]]; then
274
+ local -a symbols
275
+ symbols=(${(f)"$(python3 -c "
276
+ import json
277
+ try:
278
+ with open('$mapfile') as f:
279
+ data = json.load(f)
280
+ symbols = set()
281
+ for file_info in data.get('files', {}).values():
282
+ for sym in file_info.get('symbols', []):
283
+ symbols.add(sym['name'])
284
+ print('\\n'.join(sorted(symbols)[:100]))
285
+ except (OSError, json.JSONDecodeError, KeyError, TypeError):
286
+ pass
287
+ " 2>/dev/null)"})
288
+ _describe -t symbols 'symbol' symbols
289
+ fi
290
+ }
291
+
292
+ _codegraph_nav "$@"
293
+ """
294
+
295
+
296
+ def generate_bash_completion() -> str:
297
+ """Generate bash completion script.
298
+
299
+ Returns:
300
+ Bash completion script as a string.
301
+ """
302
+ return BASH_COMPLETION_TEMPLATE
303
+
304
+
305
+ def generate_zsh_completion() -> str:
306
+ """Generate zsh completion script.
307
+
308
+ Returns:
309
+ Zsh completion script as a string.
310
+ """
311
+ return ZSH_COMPLETION_TEMPLATE
312
+
313
+
314
+ def get_symbols_from_map(map_path: str, limit: int = 100) -> list[str]:
315
+ """Extract symbol names from a code map for completion.
316
+
317
+ Args:
318
+ map_path: Path to the .codegraph.json file.
319
+ limit: Maximum number of symbols to return.
320
+
321
+ Returns:
322
+ List of symbol names.
323
+ """
324
+ try:
325
+ with open(map_path, encoding="utf-8") as f:
326
+ data = json.load(f)
327
+
328
+ symbols = set()
329
+ for file_info in data.get("files", {}).values():
330
+ for sym in file_info.get("symbols", []):
331
+ symbols.add(sym["name"])
332
+ if len(symbols) >= limit:
333
+ break
334
+ if len(symbols) >= limit:
335
+ break
336
+
337
+ return sorted(symbols)[:limit]
338
+ except Exception:
339
+ return []
340
+
341
+
342
+ def run_completion(shell: str) -> None:
343
+ """Generate and print completion script for the specified shell.
344
+
345
+ Args:
346
+ shell: Shell type ('bash' or 'zsh').
347
+ """
348
+ if shell == "bash":
349
+ print(generate_bash_completion())
350
+ elif shell == "zsh":
351
+ print(generate_zsh_completion())
352
+ else:
353
+ print(f"Unknown shell: {shell}. Supported: bash, zsh", file=sys.stderr)
354
+ sys.exit(1)