code-review-graph-codeblackwell 2.3.6.post1__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 (74) hide show
  1. code_review_graph/__init__.py +20 -0
  2. code_review_graph/__main__.py +4 -0
  3. code_review_graph/analysis.py +410 -0
  4. code_review_graph/changes.py +409 -0
  5. code_review_graph/cli.py +1255 -0
  6. code_review_graph/communities.py +874 -0
  7. code_review_graph/constants.py +23 -0
  8. code_review_graph/context_savings.py +317 -0
  9. code_review_graph/custom_languages.py +322 -0
  10. code_review_graph/daemon.py +1009 -0
  11. code_review_graph/daemon_cli.py +320 -0
  12. code_review_graph/docs/LLM-OPTIMIZED-REFERENCE.md +71 -0
  13. code_review_graph/embeddings.py +1006 -0
  14. code_review_graph/enrich.py +303 -0
  15. code_review_graph/eval/__init__.py +33 -0
  16. code_review_graph/eval/benchmarks/__init__.py +1 -0
  17. code_review_graph/eval/benchmarks/agent_baseline.py +193 -0
  18. code_review_graph/eval/benchmarks/build_performance.py +60 -0
  19. code_review_graph/eval/benchmarks/flow_completeness.py +36 -0
  20. code_review_graph/eval/benchmarks/impact_accuracy.py +220 -0
  21. code_review_graph/eval/benchmarks/multi_hop_retrieval.py +125 -0
  22. code_review_graph/eval/benchmarks/search_quality.py +59 -0
  23. code_review_graph/eval/benchmarks/token_efficiency.py +143 -0
  24. code_review_graph/eval/configs/code-review-graph.yaml +50 -0
  25. code_review_graph/eval/configs/express.yaml +45 -0
  26. code_review_graph/eval/configs/fastapi.yaml +48 -0
  27. code_review_graph/eval/configs/flask.yaml +50 -0
  28. code_review_graph/eval/configs/gin.yaml +51 -0
  29. code_review_graph/eval/configs/httpx.yaml +48 -0
  30. code_review_graph/eval/reporter.py +301 -0
  31. code_review_graph/eval/runner.py +211 -0
  32. code_review_graph/eval/scorer.py +85 -0
  33. code_review_graph/eval/token_benchmark.py +182 -0
  34. code_review_graph/exports.py +409 -0
  35. code_review_graph/flows.py +698 -0
  36. code_review_graph/graph.py +1427 -0
  37. code_review_graph/graph_diff.py +122 -0
  38. code_review_graph/hints.py +384 -0
  39. code_review_graph/incremental.py +1245 -0
  40. code_review_graph/jedi_resolver.py +303 -0
  41. code_review_graph/main.py +1079 -0
  42. code_review_graph/memory.py +142 -0
  43. code_review_graph/migrations.py +284 -0
  44. code_review_graph/parser.py +6957 -0
  45. code_review_graph/postprocessing.py +134 -0
  46. code_review_graph/prompts.py +159 -0
  47. code_review_graph/refactor.py +852 -0
  48. code_review_graph/registry.py +319 -0
  49. code_review_graph/rescript_resolver.py +206 -0
  50. code_review_graph/search.py +447 -0
  51. code_review_graph/skills.py +1481 -0
  52. code_review_graph/spring_resolver.py +200 -0
  53. code_review_graph/temporal_resolver.py +199 -0
  54. code_review_graph/token_benchmark.py +125 -0
  55. code_review_graph/tools/__init__.py +156 -0
  56. code_review_graph/tools/_common.py +176 -0
  57. code_review_graph/tools/analysis_tools.py +184 -0
  58. code_review_graph/tools/build.py +541 -0
  59. code_review_graph/tools/community_tools.py +246 -0
  60. code_review_graph/tools/context.py +152 -0
  61. code_review_graph/tools/docs.py +274 -0
  62. code_review_graph/tools/flows_tools.py +176 -0
  63. code_review_graph/tools/query.py +692 -0
  64. code_review_graph/tools/refactor_tools.py +168 -0
  65. code_review_graph/tools/registry_tools.py +125 -0
  66. code_review_graph/tools/review.py +477 -0
  67. code_review_graph/tsconfig_resolver.py +257 -0
  68. code_review_graph/visualization.py +2184 -0
  69. code_review_graph/wiki.py +305 -0
  70. code_review_graph_codeblackwell-2.3.6.post1.dist-info/METADATA +718 -0
  71. code_review_graph_codeblackwell-2.3.6.post1.dist-info/RECORD +74 -0
  72. code_review_graph_codeblackwell-2.3.6.post1.dist-info/WHEEL +4 -0
  73. code_review_graph_codeblackwell-2.3.6.post1.dist-info/entry_points.txt +3 -0
  74. code_review_graph_codeblackwell-2.3.6.post1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,320 @@
1
+ """CLI entry point for the crg-daemon multi-repo watcher.
2
+
3
+ Usage:
4
+ crg-daemon start [--foreground]
5
+ crg-daemon stop
6
+ crg-daemon restart [--foreground]
7
+ crg-daemon status
8
+ crg-daemon logs [--repo ALIAS] [--follow] [--lines N]
9
+ crg-daemon add <path> [--alias ALIAS]
10
+ crg-daemon remove <path_or_alias>
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import logging
17
+ import os
18
+ import signal
19
+ import subprocess
20
+ import sys
21
+ import time
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Subcommand handlers
28
+ # ---------------------------------------------------------------------------
29
+
30
+
31
+ def _handle_start(args: argparse.Namespace) -> None:
32
+ """Start the daemon process."""
33
+ from .daemon import WatchDaemon, is_daemon_running, load_config
34
+
35
+ if is_daemon_running():
36
+ print("Error: Daemon is already running.")
37
+ sys.exit(1)
38
+
39
+ config = load_config()
40
+ daemon = WatchDaemon(config=config)
41
+ daemon.start()
42
+
43
+ if not args.foreground:
44
+ daemon.daemonize()
45
+
46
+ daemon.run_forever()
47
+
48
+
49
+ def _handle_stop(_args: argparse.Namespace) -> None:
50
+ """Stop the running daemon process."""
51
+ from .daemon import clear_pid, is_daemon_running, read_pid
52
+
53
+ if not is_daemon_running():
54
+ print("Daemon is not running.")
55
+ sys.exit(1)
56
+
57
+ pid = read_pid()
58
+ if pid is None:
59
+ print("Error: Could not read daemon PID.")
60
+ sys.exit(1)
61
+
62
+ print(f"Stopping daemon (PID {pid})...")
63
+ try:
64
+ os.kill(pid, signal.SIGTERM)
65
+ except ProcessLookupError:
66
+ clear_pid()
67
+ print("Daemon stopped (process already gone).")
68
+ return
69
+ except PermissionError:
70
+ print(f"Error: Permission denied sending signal to PID {pid}.")
71
+ sys.exit(1)
72
+
73
+ # Wait up to 5 seconds for process to die
74
+ for _ in range(50):
75
+ try:
76
+ os.kill(pid, 0)
77
+ except ProcessLookupError:
78
+ break
79
+ time.sleep(0.1)
80
+ else:
81
+ # Still alive after 5s — send SIGKILL
82
+ print("Daemon did not stop gracefully, sending SIGKILL...")
83
+ try:
84
+ os.kill(pid, signal.SIGKILL)
85
+ except ProcessLookupError:
86
+ pass
87
+
88
+ clear_pid()
89
+ print("Daemon stopped.")
90
+
91
+
92
+ def _handle_restart(args: argparse.Namespace) -> None:
93
+ """Restart the daemon (stop + start)."""
94
+ from .daemon import is_daemon_running
95
+
96
+ if is_daemon_running():
97
+ _handle_stop(args)
98
+ else:
99
+ print("Daemon is not running, starting fresh.")
100
+
101
+ _handle_start(args)
102
+
103
+
104
+ def _handle_status(_args: argparse.Namespace) -> None:
105
+ """Show daemon status and configuration."""
106
+ from .daemon import is_daemon_running, load_config, load_state, pid_alive, read_pid
107
+
108
+ config = load_config()
109
+ running = is_daemon_running()
110
+
111
+ if running:
112
+ pid = read_pid()
113
+ print(f"Daemon: running (PID {pid})")
114
+ else:
115
+ print("Daemon: not running")
116
+
117
+ print(f"Name: {config.session_name}")
118
+ print(f"Log dir: {config.log_dir}")
119
+ print(f"Poll: {config.poll_interval}s")
120
+ print()
121
+
122
+ if not config.repos:
123
+ print("No repositories configured.")
124
+ print("Use: crg-daemon add <path> [--alias NAME]")
125
+ return
126
+
127
+ # Header
128
+ alias_width = max(len(r.alias) for r in config.repos)
129
+ alias_width = max(alias_width, 5) # minimum "Alias" header width
130
+
131
+ if running:
132
+ state = load_state()
133
+ print(f" {'Alias':<{alias_width}} {'Status':<8} {'PID':<8} Path")
134
+ print(f" {'-' * alias_width} {'-' * 8} {'-' * 8} {'-' * 40}")
135
+ for repo in config.repos:
136
+ entry = state.get(repo.alias, {})
137
+ child_pid: int | None = entry.get("pid")
138
+ alive = child_pid is not None and pid_alive(child_pid)
139
+ status_str = "alive" if alive else "dead"
140
+ pid_str = str(child_pid) if child_pid is not None else "-"
141
+ print(f" {repo.alias:<{alias_width}} {status_str:<8} {pid_str:<8} {repo.path}")
142
+ else:
143
+ print(f" {'Alias':<{alias_width}} Path")
144
+ print(f" {'-' * alias_width} {'-' * 40}")
145
+ for repo in config.repos:
146
+ print(f" {repo.alias:<{alias_width}} {repo.path}")
147
+
148
+
149
+ def _handle_logs(args: argparse.Namespace) -> None:
150
+ """Show daemon or per-repo log files."""
151
+ from .daemon import load_config
152
+
153
+ config = load_config()
154
+
155
+ if args.repo:
156
+ log_file = config.log_dir / f"{args.repo}.log"
157
+ else:
158
+ log_file = config.log_dir / "daemon.log"
159
+
160
+ if not log_file.exists():
161
+ print(f"Log file not found: {log_file}")
162
+ sys.exit(1)
163
+
164
+ if args.follow:
165
+ try:
166
+ subprocess.run(["tail", "-f", str(log_file)], check=False)
167
+ except KeyboardInterrupt:
168
+ pass
169
+ return
170
+
171
+ # Read last N lines
172
+ lines_count = args.lines
173
+ try:
174
+ text = log_file.read_text(encoding="utf-8", errors="replace")
175
+ except OSError as exc:
176
+ print(f"Error reading log file: {exc}")
177
+ sys.exit(1)
178
+
179
+ lines = text.splitlines()
180
+ tail = lines[-lines_count:] if len(lines) > lines_count else lines
181
+ for line in tail:
182
+ print(line)
183
+
184
+
185
+ def _handle_add(args: argparse.Namespace) -> None:
186
+ """Add a repository to the daemon config."""
187
+ from .daemon import add_repo_to_config, is_daemon_running
188
+
189
+ try:
190
+ add_repo_to_config(args.path, alias=args.alias)
191
+ except ValueError as exc:
192
+ print(f"Error: {exc}")
193
+ sys.exit(1)
194
+
195
+ # Find the repo we just added to show confirmation
196
+ alias = args.alias or os.path.basename(os.path.abspath(args.path))
197
+ print(f"Added repository: {args.path} (alias: {alias})")
198
+
199
+ if is_daemon_running():
200
+ print("Daemon will pick up the change automatically.")
201
+
202
+
203
+ def _handle_remove(args: argparse.Namespace) -> None:
204
+ """Remove a repository from the daemon config."""
205
+ from .daemon import is_daemon_running, load_config, remove_repo_from_config
206
+
207
+ config_before = load_config()
208
+ count_before = len(config_before.repos)
209
+
210
+ config_after = remove_repo_from_config(args.path_or_alias)
211
+ count_after = len(config_after.repos)
212
+
213
+ if count_before == count_after:
214
+ print(f"No repository matching '{args.path_or_alias}' found in config.")
215
+ sys.exit(1)
216
+
217
+ print(f"Removed repository: {args.path_or_alias}")
218
+
219
+ if is_daemon_running():
220
+ print("Daemon will pick up the change automatically.")
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # Main entry point
225
+ # ---------------------------------------------------------------------------
226
+
227
+
228
+ def main() -> None:
229
+ """Entry point for the crg-daemon CLI."""
230
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
231
+
232
+ ap = argparse.ArgumentParser(
233
+ prog="crg-daemon",
234
+ description="Multi-repo watch daemon for code-review-graph",
235
+ )
236
+ sub = ap.add_subparsers(dest="command")
237
+
238
+ # start
239
+ start_cmd = sub.add_parser("start", help="Start the daemon")
240
+ start_cmd.add_argument(
241
+ "--foreground",
242
+ action="store_true",
243
+ help="Run in the foreground instead of daemonizing",
244
+ )
245
+
246
+ # stop
247
+ sub.add_parser("stop", help="Stop the daemon")
248
+
249
+ # restart
250
+ restart_cmd = sub.add_parser("restart", help="Restart the daemon")
251
+ restart_cmd.add_argument(
252
+ "--foreground",
253
+ action="store_true",
254
+ help="Run in the foreground instead of daemonizing",
255
+ )
256
+
257
+ # status
258
+ sub.add_parser("status", help="Show daemon status and configuration")
259
+
260
+ # logs
261
+ logs_cmd = sub.add_parser("logs", help="Show daemon or per-repo logs")
262
+ logs_cmd.add_argument(
263
+ "--repo",
264
+ default=None,
265
+ metavar="ALIAS",
266
+ help="Show logs for a specific repo (by alias)",
267
+ )
268
+ logs_cmd.add_argument(
269
+ "--follow",
270
+ "-f",
271
+ action="store_true",
272
+ help="Follow log output (tail -f)",
273
+ )
274
+ logs_cmd.add_argument(
275
+ "--lines",
276
+ "-n",
277
+ type=int,
278
+ default=50,
279
+ help="Number of lines to show (default: 50)",
280
+ )
281
+
282
+ # add
283
+ add_cmd = sub.add_parser("add", help="Add a repository to the daemon config")
284
+ add_cmd.add_argument("path", help="Path to the repository")
285
+ add_cmd.add_argument(
286
+ "--alias",
287
+ default=None,
288
+ help="Short alias for the repository (default: directory name)",
289
+ )
290
+
291
+ # remove
292
+ remove_cmd = sub.add_parser("remove", help="Remove a repository from the daemon config")
293
+ remove_cmd.add_argument("path_or_alias", help="Repository path or alias to remove")
294
+
295
+ args = ap.parse_args()
296
+
297
+ if not args.command:
298
+ ap.print_help()
299
+ sys.exit(0)
300
+
301
+ handlers: dict[str, object] = {
302
+ "start": _handle_start,
303
+ "stop": _handle_stop,
304
+ "restart": _handle_restart,
305
+ "status": _handle_status,
306
+ "logs": _handle_logs,
307
+ "add": _handle_add,
308
+ "remove": _handle_remove,
309
+ }
310
+
311
+ handler = handlers.get(args.command)
312
+ if handler is None:
313
+ ap.print_help()
314
+ sys.exit(1)
315
+
316
+ handler(args) # type: ignore[operator]
317
+
318
+
319
+ if __name__ == "__main__":
320
+ main()
@@ -0,0 +1,71 @@
1
+ # LLM-OPTIMIZED REFERENCE -- code-review-graph v2.3.6
2
+
3
+ AI coding agents: Read ONLY the exact `<section>` you need. Never load the whole file.
4
+
5
+ <section name="usage">
6
+ Quick install: pip install code-review-graph
7
+ Then: code-review-graph install && code-review-graph build
8
+ First run: /code-review-graph:build-graph
9
+ After that use only delta/pr commands.
10
+ ALWAYS start with get_minimal_context_tool(task="your task") — returns ~100 tokens with risk, communities, flows, and suggested next tools.
11
+ Use detail_level="minimal" on all subsequent calls unless you need more detail.
12
+ When present, context_savings is an estimated compact hint, not exact tokenization.
13
+ </section>
14
+
15
+ <section name="review-delta">
16
+ 1. Call get_minimal_context_tool(task="review changes") first.
17
+ 2. If risk is low: detect_changes_tool(detail_level="minimal") → report summary.
18
+ 3. If risk is medium/high: detect_changes_tool(detail_level="standard") → expand on high-risk items.
19
+ Target: ≤5 tool calls, ≤800 tokens total context.
20
+ </section>
21
+
22
+ <section name="review-pr">
23
+ Fetch PR diff -> detect_changes_tool -> get_affected_flows_tool -> structured review with blast-radius table and risk scores.
24
+ Never include full files unless explicitly asked.
25
+ </section>
26
+
27
+ <section name="commands">
28
+ Core MCP tools: get_minimal_context_tool, detect_changes_tool, get_review_context_tool, get_impact_radius_tool, query_graph_tool, semantic_search_nodes_tool, get_architecture_overview_tool, get_affected_flows_tool, list_flows_tool, list_communities_tool, refactor_tool, build_or_update_graph_tool, run_postprocess_tool, embed_graph_tool, list_graph_stats_tool, get_docs_section_tool
29
+ MCP prompts (5): review_changes, architecture_map, debug_issue, onboard_developer, pre_merge_check
30
+ Skills: build-graph, debug-issue, explore-codebase, refactor-safely, review-changes, review-delta, review-pr
31
+ CLI: code-review-graph [install|init|build|update|status|watch|visualize|serve|mcp|wiki|detect-changes|postprocess|embed|register|unregister|repos|eval|daemon]
32
+ Token efficiency: Prefer detail_level="minimal" where available. Always call get_minimal_context_tool first. Some review/context tools return compact estimated context_savings metadata.
33
+ </section>
34
+
35
+ <section name="legal">
36
+ MIT licence. Core graph/review workflows are local and there is no telemetry. DB file: .code-review-graph/graph.db. Optional cloud embeddings send embedded source snippets to the configured provider only when selected.
37
+ </section>
38
+
39
+ <section name="watch">
40
+ Run: code-review-graph watch (auto-updates graph on file save via watchdog)
41
+ Or use PostToolUse (Write|Edit|Bash) hooks for automatic background updates.
42
+ </section>
43
+
44
+ <section name="embeddings">
45
+ Optional: pip install code-review-graph[embeddings]
46
+ Then call embed_graph_tool to compute vectors.
47
+ semantic_search_nodes_tool auto-uses vectors when available, falls back to keyword + FTS5.
48
+ Providers: local sentence-transformers, OpenAI-compatible endpoints, Google Gemini, and MiniMax.
49
+ Configure via provider/model parameters, CRG_EMBEDDING_MODEL for local, or CRG_OPENAI_* for OpenAI-compatible endpoints.
50
+ </section>
51
+
52
+ <section name="languages">
53
+ Supported: Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks, and Perl XS files.
54
+ Parser: Tree-sitter via tree-sitter-language-pack
55
+ Custom languages: add .code-review-graph/languages.toml (extensions + node types per grammar) — no fork needed, see docs/CUSTOM_LANGUAGES.md. Built-ins cannot be overridden.
56
+ </section>
57
+
58
+ <section name="troubleshooting">
59
+ DB lock: SQLite WAL mode, auto-recovers. Only one build at a time.
60
+ Large repos: First build 30-60s. Incremental <2s. Add patterns to .code-review-graphignore.
61
+ Stale graph: Run /code-review-graph:build-graph manually.
62
+ Missing nodes: Check language support + ignore patterns. Use full_rebuild=True.
63
+ Windows/WSL: Use forward slashes in paths. Ensure uv is on PATH in WSL.
64
+ </section>
65
+
66
+ **Instruction to AI coding agents (always follow):**
67
+ When user asks anything about "code-review-graph", "how to use", "commands", "review-delta", etc.:
68
+ 1. Call get_docs_section_tool with the exact section name.
69
+ 2. Use ONLY that content + current graph state.
70
+ 3. Never include full docs or source code in your reasoning.
71
+ This keeps documentation lookup compact and avoids loading broad reference files by default.