codegraph-ai 0.2.2__tar.gz → 0.3.0__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.
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/PKG-INFO +4 -1
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/cli.py +218 -0
- codegraph_ai-0.3.0/codegraph/graph_export.py +730 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/__init__.py +54 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/interactive_explorer.py +515 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/pattern_detector.py +758 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/pattern_severity.yml +30 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/pr_explorer.py +464 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/pr_question_generator.py +428 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/pr_question_templates.yml +94 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/question_generator.py +503 -0
- codegraph_ai-0.3.0/codegraph/guided_explorer/question_templates.yml +166 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/models.py +94 -0
- codegraph_ai-0.3.0/codegraph/pr_analysis.py +1431 -0
- codegraph_ai-0.3.0/codegraph/pr_api.py +296 -0
- codegraph_ai-0.3.0/codegraph/pr_labeler.py +616 -0
- codegraph_ai-0.3.0/codegraph/pr_locator.py +764 -0
- codegraph_ai-0.3.0/codegraph/pr_review.py +672 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/PKG-INFO +4 -1
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/SOURCES.txt +17 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/requires.txt +3 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/pyproject.toml +7 -1
- codegraph_ai-0.3.0/tests/test_guided_explorer.py +413 -0
- codegraph_ai-0.3.0/tests/test_pr_review.py +414 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/README.md +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/__init__.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/__main__.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/__init__.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/base.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/c_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/java_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/js_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/adapters/python_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/analyzer.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/bug_locator.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/bug_parser.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/core.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/github_client.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/issue_cache.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/issue_fetcher.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/mcp_server.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph/qa.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/dependency_links.txt +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/entry_points.txt +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/codegraph_ai.egg-info/top_level.txt +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/setup.cfg +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_adapters.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_advanced.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_bug_locator.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_bug_parser.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_core_schema.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_cross_locate.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_impact.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_incremental.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_indexing.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_integration.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_issue_cache.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_java_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_js_adapter.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_models.py +0 -0
- {codegraph_ai-0.2.2 → codegraph_ai-0.3.0}/tests/test_similar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraph-ai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Hybrid graph + vector code intelligence powered by NeuG and zvec
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: neug
|
|
@@ -8,6 +8,9 @@ Requires-Dist: zvec
|
|
|
8
8
|
Requires-Dist: tree-sitter-language-pack
|
|
9
9
|
Requires-Dist: sentence-transformers
|
|
10
10
|
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: jinja2
|
|
12
|
+
Requires-Dist: pyyaml
|
|
13
|
+
Requires-Dist: click
|
|
11
14
|
Provides-Extra: server
|
|
12
15
|
Requires-Dist: fastmcp; extra == "server"
|
|
13
16
|
Provides-Extra: dev
|
|
@@ -164,6 +164,7 @@ def cmd_init(args: argparse.Namespace) -> None:
|
|
|
164
164
|
print()
|
|
165
165
|
print(f"Use 'codegraph status' to see detailed state.")
|
|
166
166
|
print(f"Use 'codegraph query \"<question>\"' to ask questions.")
|
|
167
|
+
print(f"Use 'codegraph pr-review prepare' to review pull requests.")
|
|
167
168
|
finally:
|
|
168
169
|
cs.close()
|
|
169
170
|
|
|
@@ -522,6 +523,192 @@ def cmd_server(args: argparse.Namespace) -> None:
|
|
|
522
523
|
mcp.run()
|
|
523
524
|
|
|
524
525
|
|
|
526
|
+
def cmd_pr_review(args: argparse.Namespace) -> None:
|
|
527
|
+
"""Dispatch to pr-review prepare or pr-review label."""
|
|
528
|
+
action = getattr(args, 'pr_action', None)
|
|
529
|
+
if action == 'label':
|
|
530
|
+
cmd_pr_review_label(args)
|
|
531
|
+
elif action == 'prepare':
|
|
532
|
+
cmd_pr_review_prepare(args)
|
|
533
|
+
else:
|
|
534
|
+
# No subcommand given
|
|
535
|
+
print("Error: 'pr-review' requires a subcommand: 'prepare' for analysis or 'label' for labeling. Note that 'prepare' must be run before 'label'.")
|
|
536
|
+
print(" Usage: codegraph pr-review prepare --db .codegraph")
|
|
537
|
+
print(" codegraph pr-review label --db .codegraph")
|
|
538
|
+
sys.exit(1)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def cmd_pr_review_prepare(args: argparse.Namespace) -> None:
|
|
542
|
+
"""Analyze PRs + detect conflicts + write to graph DB (full rebuild)."""
|
|
543
|
+
from codegraph.pr_review import run_prepare
|
|
544
|
+
|
|
545
|
+
repo_dir = _derive_repo_dir(args.db)
|
|
546
|
+
|
|
547
|
+
# Use explicit --repo or auto-detect from git remote
|
|
548
|
+
repo_name = args.repo
|
|
549
|
+
if not repo_name:
|
|
550
|
+
repo_name = _auto_detect_repo(None, repo_dir)
|
|
551
|
+
if not repo_name:
|
|
552
|
+
print("Error: Cannot auto-detect GitHub repo from git remote. Specify --repo owner/repo")
|
|
553
|
+
return
|
|
554
|
+
|
|
555
|
+
run_prepare(
|
|
556
|
+
db_dir=args.db,
|
|
557
|
+
repo_dir=repo_dir,
|
|
558
|
+
repo=repo_name,
|
|
559
|
+
author=getattr(args, 'author', ''),
|
|
560
|
+
output_dir=getattr(args, 'output', None),
|
|
561
|
+
skip_single_pr=getattr(args, 'skip_single_pr', False),
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def cmd_pr_review_label(args: argparse.Namespace) -> None:
|
|
566
|
+
"""Apply GitHub labels + post conflict comments from graph DB."""
|
|
567
|
+
from codegraph.pr_review import run_label
|
|
568
|
+
|
|
569
|
+
repo_dir = _derive_repo_dir(args.db)
|
|
570
|
+
|
|
571
|
+
# Use explicit --repo or auto-detect from git remote
|
|
572
|
+
repo_name = args.repo
|
|
573
|
+
if not repo_name:
|
|
574
|
+
repo_name = _auto_detect_repo(None, repo_dir)
|
|
575
|
+
if not repo_name:
|
|
576
|
+
print("Error: Cannot auto-detect GitHub repo from git remote. Specify --repo owner/repo")
|
|
577
|
+
return
|
|
578
|
+
|
|
579
|
+
run_label(
|
|
580
|
+
db_dir=args.db,
|
|
581
|
+
repo_dir=repo_dir,
|
|
582
|
+
repo=repo_name,
|
|
583
|
+
dry_run=getattr(args, 'dry_run', False),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _auto_detect_repo(repo_arg, repo_dir: str) -> str | None:
|
|
588
|
+
"""Auto-detect GitHub repo (owner/repo) from git remote if not specified."""
|
|
589
|
+
if repo_arg:
|
|
590
|
+
return repo_arg
|
|
591
|
+
import subprocess
|
|
592
|
+
try:
|
|
593
|
+
result = subprocess.run(
|
|
594
|
+
['git', 'remote', 'get-url', 'origin'],
|
|
595
|
+
cwd=repo_dir, capture_output=True, text=True
|
|
596
|
+
)
|
|
597
|
+
if result.returncode == 0:
|
|
598
|
+
remote_url = result.stdout.strip()
|
|
599
|
+
import re
|
|
600
|
+
m = re.match(r'(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git)?$', remote_url)
|
|
601
|
+
if m:
|
|
602
|
+
detected = m.group(1)
|
|
603
|
+
print(f"[auto] --repo not specified, detected from remote: {detected}")
|
|
604
|
+
return detected
|
|
605
|
+
except Exception:
|
|
606
|
+
pass
|
|
607
|
+
return None
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _derive_repo_dir(db_path: str) -> str:
|
|
611
|
+
"""Derive the local repository path from the --db directory.
|
|
612
|
+
|
|
613
|
+
When --db points to ``<repo>/.codegraph``, returns ``<repo>``;
|
|
614
|
+
otherwise falls back to the current working directory.
|
|
615
|
+
"""
|
|
616
|
+
abs_db = os.path.abspath(db_path)
|
|
617
|
+
if os.path.basename(abs_db) == '.codegraph':
|
|
618
|
+
return os.path.dirname(abs_db)
|
|
619
|
+
return os.getcwd()
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def cmd_explore(args: argparse.Namespace) -> None:
|
|
623
|
+
"""Run guided exploration with interactive question answering."""
|
|
624
|
+
db_dir = _find_db(args.db)
|
|
625
|
+
|
|
626
|
+
if not os.path.isdir(db_dir):
|
|
627
|
+
print(f"Error: database not found at {db_dir}", file=sys.stderr)
|
|
628
|
+
print(f"Run 'codegraph init' first.", file=sys.stderr)
|
|
629
|
+
sys.exit(1)
|
|
630
|
+
|
|
631
|
+
cs = _open_cs(db_dir)
|
|
632
|
+
try:
|
|
633
|
+
from codegraph.guided_explorer import (
|
|
634
|
+
InteractiveExplorer,
|
|
635
|
+
PatternDetector,
|
|
636
|
+
QuestionGenerator,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
# ── General exploration mode ───────────────────────────────────────
|
|
640
|
+
print("\nDetecting code patterns...")
|
|
641
|
+
detector = PatternDetector(neug_conn=cs.conn)
|
|
642
|
+
patterns = detector.detect_all_patterns(cs.conn)
|
|
643
|
+
|
|
644
|
+
if not patterns:
|
|
645
|
+
print("\nNo significant patterns detected. Your codebase appears healthy!")
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
print(f"Found {len(patterns)} patterns")
|
|
649
|
+
|
|
650
|
+
# Pre-filter patterns by --type before question generation,
|
|
651
|
+
# so that rare categories (e.g. pr-review) aren't truncated by top_n
|
|
652
|
+
if args.type != 'all':
|
|
653
|
+
from codegraph.models import PatternType
|
|
654
|
+
_TYPE_TO_PATTERN_TYPES = {
|
|
655
|
+
'architecture': {PatternType.CYCLIC_DEPENDENCY, PatternType.HIGH_COUPLING,
|
|
656
|
+
PatternType.ISOLATED_MODULE, PatternType.STABLE_CORE},
|
|
657
|
+
'risk': {PatternType.HIGH_FAN_IN, PatternType.HIGH_IMPACT_PR,
|
|
658
|
+
PatternType.PR_NO_TEST, PatternType.PR_INTERFACE_CHANGE,
|
|
659
|
+
PatternType.PR_DEAD_CODE},
|
|
660
|
+
'evolution': {PatternType.EVOLUTION_HOTSPOT, PatternType.STABLE_CORE},
|
|
661
|
+
'hotspot': {PatternType.HIGH_FAN_IN, PatternType.EVOLUTION_HOTSPOT},
|
|
662
|
+
'pr-review': {PatternType.HIGH_IMPACT_PR, PatternType.PR_CONFLICT,
|
|
663
|
+
PatternType.PR_NO_TEST, PatternType.PR_INTERFACE_CHANGE,
|
|
664
|
+
PatternType.PR_CROSS_MODULE, PatternType.PR_DEAD_CODE},
|
|
665
|
+
}
|
|
666
|
+
target_types = _TYPE_TO_PATTERN_TYPES.get(args.type)
|
|
667
|
+
if target_types:
|
|
668
|
+
patterns = [p for p in patterns if p.pattern_type in target_types]
|
|
669
|
+
|
|
670
|
+
# Generate questions
|
|
671
|
+
print("\nGenerating guided questions...")
|
|
672
|
+
generator = QuestionGenerator()
|
|
673
|
+
questions = generator.generate_questions(
|
|
674
|
+
patterns,
|
|
675
|
+
role=args.role,
|
|
676
|
+
top_n=args.top if args.top else 10,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Filter by --type (based on question category) — secondary filter
|
|
680
|
+
if args.type != 'all' and questions:
|
|
681
|
+
from codegraph.models import QuestionCategory
|
|
682
|
+
_TYPE_TO_CATEGORY = {
|
|
683
|
+
'architecture': QuestionCategory.ARCHITECTURE,
|
|
684
|
+
'risk': QuestionCategory.RISK,
|
|
685
|
+
'evolution': QuestionCategory.EVOLUTION,
|
|
686
|
+
'hotspot': QuestionCategory.HOTSPOT,
|
|
687
|
+
'pr-review': QuestionCategory.PR_IMPACT,
|
|
688
|
+
}
|
|
689
|
+
target_cat = _TYPE_TO_CATEGORY[args.type]
|
|
690
|
+
questions = [q for q in questions if q.category == target_cat]
|
|
691
|
+
|
|
692
|
+
if not questions:
|
|
693
|
+
print(f"\nNo {args.type} questions generated from detected patterns.")
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
print(f"Generated {len(questions)} questions (type={args.type})")
|
|
697
|
+
|
|
698
|
+
# Run exploration
|
|
699
|
+
explorer = InteractiveExplorer(codescope_instance=cs, neug_conn=cs.conn)
|
|
700
|
+
|
|
701
|
+
if args.top:
|
|
702
|
+
# Non-interactive mode: just show top N questions
|
|
703
|
+
explorer.run_non_interactive(questions, top_n=args.top)
|
|
704
|
+
else:
|
|
705
|
+
# Interactive mode
|
|
706
|
+
explorer.run_interactive_session(questions)
|
|
707
|
+
|
|
708
|
+
finally:
|
|
709
|
+
cs.close()
|
|
710
|
+
|
|
711
|
+
|
|
525
712
|
# =========================================================================
|
|
526
713
|
# Main
|
|
527
714
|
# =========================================================================
|
|
@@ -611,6 +798,35 @@ def main() -> None:
|
|
|
611
798
|
p_server.add_argument("--db", default=None, help=f"Database directory (default: {DB_DEFAULT})")
|
|
612
799
|
p_server.add_argument("--repo", default=None, help="Path to repository")
|
|
613
800
|
|
|
801
|
+
# -- pr-review --
|
|
802
|
+
p_pr = sub.add_parser("pr-review", help="PR review: prepare (analyze + write to DB) or label (apply labels + comments)")
|
|
803
|
+
p_pr_sub = p_pr.add_subparsers(dest="pr_action")
|
|
804
|
+
|
|
805
|
+
# pr-review prepare
|
|
806
|
+
p_pr_prepare = p_pr_sub.add_parser("prepare", help="Analyze PRs + detect conflicts + write to graph DB (full rebuild)")
|
|
807
|
+
p_pr_prepare.add_argument("--db", required=True, help="Path to the codegraph database directory")
|
|
808
|
+
p_pr_prepare.add_argument("--repo", default=None, help="GitHub repository in owner/repo format (default: auto-detect from git remote)")
|
|
809
|
+
p_pr_prepare.add_argument('--author', default="", help="Filter PRs by GitHub login")
|
|
810
|
+
p_pr_prepare.add_argument('--output', default=None, help="Output directory for HTML/MD files")
|
|
811
|
+
p_pr_prepare.add_argument('--skip-single-pr', action='store_true',
|
|
812
|
+
help="Skip per-PR risk scoring; only cross-PR conflict analysis")
|
|
813
|
+
|
|
814
|
+
# pr-review label
|
|
815
|
+
p_pr_label = p_pr_sub.add_parser("label", help="Apply GitHub labels + post conflict comments from graph DB")
|
|
816
|
+
p_pr_label.add_argument("--db", required=True, help="Path to the codegraph database directory")
|
|
817
|
+
p_pr_label.add_argument("--repo", default=None, help="GitHub repository in owner/repo format (default: auto-detect from git remote)")
|
|
818
|
+
p_pr_label.add_argument('--dry-run', action='store_true', help="Preview labels and comments without making API calls")
|
|
819
|
+
|
|
820
|
+
# -- explore --
|
|
821
|
+
p_explore = sub.add_parser("explore", help="Guided exploration with interactive questions (incl. PR follow-ups)")
|
|
822
|
+
p_explore.add_argument("--db", default=None, help=f"Database directory (default: {DB_DEFAULT})")
|
|
823
|
+
p_explore.add_argument("--role", default="architect",
|
|
824
|
+
help="User role: architect, maintainer, reviewer (default: architect)")
|
|
825
|
+
p_explore.add_argument("--top", type=int, default=None,
|
|
826
|
+
help="Show top N questions (non-interactive mode)")
|
|
827
|
+
p_explore.add_argument("--type", choices=['all', 'architecture', 'risk', 'evolution', 'hotspot', 'pr-review'], default='all',
|
|
828
|
+
help="Question type filter: all (default), architecture, risk, evolution, hotspot, pr-review")
|
|
829
|
+
|
|
614
830
|
args = parser.parse_args()
|
|
615
831
|
|
|
616
832
|
if not args.command:
|
|
@@ -629,6 +845,8 @@ def main() -> None:
|
|
|
629
845
|
"analyze-bug": cmd_analyze_bug,
|
|
630
846
|
"analyze-bugs": cmd_analyze_bugs,
|
|
631
847
|
"server": cmd_server,
|
|
848
|
+
"pr-review": cmd_pr_review,
|
|
849
|
+
"explore": cmd_explore,
|
|
632
850
|
}
|
|
633
851
|
dispatch[args.command](args)
|
|
634
852
|
|