codegraph-engine 2.2.1__py3-none-any.whl → 3.0.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 (82) hide show
  1. codegraph/__init__.py +1 -1
  2. codegraph/agent_brain.py +172 -2
  3. codegraph/agent_capabilities.py +564 -6
  4. codegraph/agent_rules.py +1 -1
  5. codegraph/api_drift.py +455 -0
  6. codegraph/async_queue/__init__.py +50 -0
  7. codegraph/async_queue/detectors/__init__.py +48 -0
  8. codegraph/async_queue/detectors/celery.py +160 -0
  9. codegraph/async_queue/detectors/kafka.py +168 -0
  10. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  11. codegraph/async_queue/detectors/redis.py +130 -0
  12. codegraph/async_queue/detectors/sqs.py +135 -0
  13. codegraph/async_queue/models.py +161 -0
  14. codegraph/async_queue/otel_models.py +74 -0
  15. codegraph/async_queue/otel_parser.py +337 -0
  16. codegraph/async_queue/reconciler.py +427 -0
  17. codegraph/async_queue/scanner.py +291 -0
  18. codegraph/async_queue/traversal.py +447 -0
  19. codegraph/branch_comparison.py +197 -0
  20. codegraph/change_impact.py +547 -0
  21. codegraph/cli.py +992 -3
  22. codegraph/cli_output.py +17 -2
  23. codegraph/context.py +38 -11
  24. codegraph/context_freshness.py +351 -0
  25. codegraph/database/__init__.py +14 -0
  26. codegraph/database/extractor.py +712 -17
  27. codegraph/database/interrogation.py +388 -2
  28. codegraph/database/schema_drift.py +862 -0
  29. codegraph/epistemic.py +7 -0
  30. codegraph/evidence_contract.py +21 -0
  31. codegraph/frameworks.py +171 -6
  32. codegraph/frameworks_nestjs.py +502 -0
  33. codegraph/frameworks_nextjs.py +337 -0
  34. codegraph/frameworks_react.py +457 -0
  35. codegraph/freshness.py +2 -0
  36. codegraph/frontend_html.py +109 -0
  37. codegraph/git.py +2 -0
  38. codegraph/git_state.py +545 -0
  39. codegraph/indexing/__init__.py +7 -1
  40. codegraph/indexing/indexer.py +291 -38
  41. codegraph/indexing/models.py +6 -0
  42. codegraph/indexing/parser.py +239 -0
  43. codegraph/indexing/pool.py +136 -0
  44. codegraph/indexing/scanner.py +6 -0
  45. codegraph/indexing/templates.py +418 -0
  46. codegraph/interrogation.py +128 -20
  47. codegraph/mcp/server.py +258 -27
  48. codegraph/models.py +7 -0
  49. codegraph/monorepo.py +3 -0
  50. codegraph/optimizer.py +42 -5
  51. codegraph/process_lifecycle.py +31 -13
  52. codegraph/refactor/__init__.py +36 -0
  53. codegraph/refactor/models.py +114 -0
  54. codegraph/refactor/renamer.py +844 -0
  55. codegraph/refactor/transactions.py +403 -0
  56. codegraph/resolver.py +1202 -159
  57. codegraph/retrieval_policy.py +6 -0
  58. codegraph/route_topology.py +172 -0
  59. codegraph/runtime/__init__.py +19 -0
  60. codegraph/runtime/collector.py +155 -0
  61. codegraph/runtime/db_watcher.py +191 -0
  62. codegraph/runtime/ingestor.py +30 -10
  63. codegraph/runtime/port_inspector.py +304 -0
  64. codegraph/runtime/runner.py +155 -6
  65. codegraph/runtime/trace_linker.py +175 -0
  66. codegraph/search/hybrid.py +26 -0
  67. codegraph/semantic_conflicts.py +270 -0
  68. codegraph/semantic_decorators.py +34 -3
  69. codegraph/structural_diff.py +542 -0
  70. codegraph/styles.py +144 -0
  71. codegraph/symbol_history.py +277 -0
  72. codegraph/tsconfig.py +236 -0
  73. codegraph/ui/__init__.py +6 -0
  74. codegraph/ui/server.py +715 -0
  75. codegraph/watcher.py +323 -0
  76. {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/METADATA +257 -39
  77. codegraph_engine-3.0.0.dist-info/RECORD +129 -0
  78. codegraph_engine-2.2.1.dist-info/RECORD +0 -87
  79. {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/WHEEL +0 -0
  80. {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/entry_points.txt +0 -0
  81. {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/licenses/LICENSE +0 -0
  82. {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/top_level.txt +0 -0
codegraph/epistemic.py CHANGED
@@ -92,6 +92,11 @@ def classify_relationship_evidence(
92
92
  "EVENT_LISTENER",
93
93
  "TASK_HANDLER",
94
94
  "COMMAND_HANDLER",
95
+ "DISPATCHES_TASK",
96
+ "TRIGGERS_SIGNAL",
97
+ "HANDLES_SIGNAL",
98
+ "RENDERS_TEMPLATE",
99
+ "TOOL_HANDLER",
95
100
  "INJECTS",
96
101
  "DEPENDS_ON_PACKAGE",
97
102
  "TESTS_ROUTE",
@@ -104,6 +109,8 @@ def classify_relationship_evidence(
104
109
  "BINDS_TO",
105
110
  "ALIASED_TO",
106
111
  "DISPATCHES_TO",
112
+ "DISPATCHES_FORWARD",
113
+ "PIPELINE_STEP",
107
114
  "PROVIDES",
108
115
  "RESOLVES_DEPENDENCY",
109
116
  "CONFIGURES",
@@ -122,6 +122,27 @@ ALLOWED_RELATIONSHIP_EVIDENCE_MATRIX: dict[str, frozenset[str]] = {
122
122
  "UNKNOWN_TABLE": frozenset({"UNKNOWN", "AMBIGUOUS"}),
123
123
  # Environment variable dependency tracking (Phase 8 Security)
124
124
  "READS_ENV": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "DATAFLOW_VERIFIED"}),
125
+ # Frontend, UI & Web Architecture (Pillar 4)
126
+ "RENDERS": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
127
+ "USES_HOOK": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
128
+ "IMPORTS_STYLE": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
129
+ "USES_STYLE_CLASS": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
130
+ "FETCHES_ROUTE": frozenset({"AST_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN"}),
131
+ "LOADS_SCRIPT": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
132
+ "LOADS_STYLESHEET": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN"}),
133
+ # NoSQL Document Database & Collections (Mongoose / MongoDB)
134
+ "MAPS_TO_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
135
+ "WRITES_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
136
+ "READS_COLLECTION": frozenset({"AST_VERIFIED", "STATIC_VERIFIED", "FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "RUNTIME_OBSERVED", "RUNTIME_UNOBSERVED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
137
+ # Python Full-Stack (Celery, Django Signals, Templates)
138
+ "DISPATCHES_TASK": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
139
+ "TRIGGERS_SIGNAL": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
140
+ "HANDLES_SIGNAL": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
141
+ "RENDERS_TEMPLATE": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
142
+ # Python AI/ML Stack (PyTorch, LangChain, Ray, LlamaIndex)
143
+ "DISPATCHES_FORWARD": frozenset({"AST_VERIFIED", "DATAFLOW_VERIFIED", "FRAMEWORK_VERIFIED", "POSSIBLE", "UNKNOWN", "AMBIGUOUS"}),
144
+ "TOOL_HANDLER": frozenset({"FRAMEWORK_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
145
+ "PIPELINE_STEP": frozenset({"FRAMEWORK_VERIFIED", "DATAFLOW_VERIFIED", "AST_VERIFIED", "POSSIBLE", "UNKNOWN"}),
125
146
  }
126
147
 
127
148
 
codegraph/frameworks.py CHANGED
@@ -55,12 +55,12 @@ def normalize_route_path(route_path: str, framework: str) -> str:
55
55
  if framework == "flask":
56
56
  # Match <[type:]param>
57
57
  return re.sub(r"<(?:\w+:)?([A-Za-z_][\w]*)>", r"{\1}", raw)
58
- elif framework == "express":
58
+ elif framework in ("express", "nestjs"):
59
59
  # Match :param
60
60
  return re.sub(r":([A-Za-z_][\w]*)", r"{\1}", raw)
61
61
  elif framework == "nextjs":
62
62
  # Match [[...param]] or [...param] or [param]
63
- return re.sub(r"\[(?:\.\.\.)?([A-Za-z_][\w]*)\]", r"{\1}", raw)
63
+ return re.sub(r"\[{1,2}(?:\.\.\.)?([A-Za-z_][\w]*)\]{1,2}", r"{\1}", raw)
64
64
  return raw
65
65
 
66
66
 
@@ -651,12 +651,13 @@ def get_python_analyzers(imports_modules: set[str]) -> list[FrameworkAnalyzer]:
651
651
  return analyzers
652
652
 
653
653
 
654
+ # ---------------------------------------------------------------------------
654
655
  # ---------------------------------------------------------------------------
655
656
  # JavaScript / TypeScript Framework Analyzers
656
657
  # ---------------------------------------------------------------------------
657
658
 
658
659
  _EXPRESS_CALL_RE = re.compile(
659
- r"\b([A-Za-z_$][\w$]*)\.(get|post|put|delete|patch|options|head|all)\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*([A-Za-z_$][\w$.]*)",
660
+ r"\b([A-Za-z_$][\w$]*)\.(get|post|put|delete|patch|options|head|all)\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*(.+)",
660
661
  re.IGNORECASE,
661
662
  )
662
663
 
@@ -676,6 +677,169 @@ _EXPRESS_IGNORE_USE = {
676
677
  "bodyparser",
677
678
  }
678
679
 
680
+ _NON_HANDLER_CALLS = {
681
+ "async",
682
+ "await",
683
+ "function",
684
+ "if",
685
+ "return",
686
+ "try",
687
+ "catch",
688
+ "switch",
689
+ "while",
690
+ "for",
691
+ "throw",
692
+ "res.send",
693
+ "res.json",
694
+ "res.status",
695
+ "res.sendstatus",
696
+ "res.redirect",
697
+ "res.setheader",
698
+ "res.cookie",
699
+ "res.end",
700
+ "next",
701
+ "console.log",
702
+ "console.error",
703
+ "console.warn",
704
+ "console.info",
705
+ "object.assign",
706
+ "promise.all",
707
+ "promise.resolve",
708
+ "promise.reject",
709
+ "require",
710
+ "typeof",
711
+ "then",
712
+ "settimeout",
713
+ "cleartimeout",
714
+ "setinterval",
715
+ "clearinterval",
716
+ }
717
+
718
+
719
+ def split_js_arguments(args_str: str) -> list[str]:
720
+ """Split comma-separated JavaScript arguments respecting nested parens, brackets, braces, and quotes."""
721
+ args: list[str] = []
722
+ current: list[str] = []
723
+ depth_paren = 0
724
+ depth_bracket = 0
725
+ depth_brace = 0
726
+ in_quote = False
727
+ quote_char = ""
728
+ escape = False
729
+
730
+ for ch in args_str:
731
+ if escape:
732
+ current.append(ch)
733
+ escape = False
734
+ continue
735
+ if ch == "\\":
736
+ current.append(ch)
737
+ escape = True
738
+ continue
739
+ if in_quote:
740
+ current.append(ch)
741
+ if ch == quote_char:
742
+ in_quote = False
743
+ continue
744
+ if ch in ('"', "'", "`"):
745
+ in_quote = True
746
+ quote_char = ch
747
+ current.append(ch)
748
+ continue
749
+
750
+ if ch == "(":
751
+ depth_paren += 1
752
+ elif ch == ")":
753
+ if depth_paren == 0 and depth_bracket == 0 and depth_brace == 0:
754
+ # Trailing closing paren of router call reached
755
+ break
756
+ depth_paren = max(0, depth_paren - 1)
757
+ elif ch == "[":
758
+ depth_bracket += 1
759
+ elif ch == "]":
760
+ depth_bracket = max(0, depth_bracket - 1)
761
+ elif ch == "{":
762
+ depth_brace += 1
763
+ elif ch == "}":
764
+ depth_brace = max(0, depth_brace - 1)
765
+
766
+ if ch == "," and depth_paren == 0 and depth_bracket == 0 and depth_brace == 0:
767
+ arg = "".join(current).strip()
768
+ if arg:
769
+ args.append(arg)
770
+ current = []
771
+ else:
772
+ current.append(ch)
773
+
774
+ arg = "".join(current).strip()
775
+ if arg:
776
+ args.append(arg)
777
+ return args
778
+
779
+
780
+ def _clean_middleware_token(raw_tok: str) -> str:
781
+ """Extract clean identifier from middleware expression, e.g. requireRoles('admin') -> requireRoles."""
782
+ tok = raw_tok.strip()
783
+ if tok.startswith("[") and tok.endswith("]"):
784
+ tok = tok[1:-1].strip()
785
+ m = re.match(r"^([A-Za-z_$][\w$]*)\s*(?:\(|$)", tok)
786
+ if m:
787
+ return m.group(1)
788
+ return tok.split("(")[0].strip()
789
+
790
+
791
+ def _fallback_handler_name(route_path: str) -> str:
792
+ """Generate a readable fallback handler name from the route path."""
793
+ clean_parts = [p for p in route_path.strip("/").split("/") if p and not p.startswith(":") and not p.startswith("{")]
794
+ if clean_parts:
795
+ return f"{clean_parts[-1]}_handler"
796
+ return "root_handler"
797
+
798
+
799
+ def _resolve_express_handler_and_middleware(
800
+ raw_args: str,
801
+ route_path: str,
802
+ ) -> tuple[str, str, list[str]]:
803
+ """Look past middleware to resolve the terminal route handler and list of middlewares."""
804
+ args = split_js_arguments(raw_args)
805
+ if not args:
806
+ name = _fallback_handler_name(route_path)
807
+ return name, name, []
808
+
809
+ # Intermediate arguments are middleware
810
+ middlewares: list[str] = []
811
+ for mw in args[:-1]:
812
+ clean_mw = _clean_middleware_token(mw)
813
+ if clean_mw:
814
+ middlewares.append(clean_mw)
815
+
816
+ last_arg = args[-1].strip()
817
+
818
+ # Case 1: Simple named identifier or member access, e.g. uploadInvoiceFile or controller.uploadInvoiceFile
819
+ id_match = re.match(r"^([A-Za-z_$][\w$.]*)$", last_arg)
820
+ if id_match:
821
+ expr = id_match.group(1)
822
+ name = expr.split(".")[-1]
823
+ return name, expr, middlewares
824
+
825
+ # Case 2: Arrow function or function expression, e.g. async (req, res) => uploadInvoiceFile(req, res)
826
+ # Search for business function calls inside the function body
827
+ calls = re.findall(r"\b([A-Za-z_$][\w$.]*)\s*\(", last_arg)
828
+ mw_set = set(middlewares)
829
+ for call in calls:
830
+ clean_call = call.split(".")[-1]
831
+ if (
832
+ call.lower() not in _NON_HANDLER_CALLS
833
+ and clean_call.lower() not in _NON_HANDLER_CALLS
834
+ and clean_call not in mw_set
835
+ ):
836
+ return clean_call, call, middlewares
837
+
838
+ # Case 3: Anonymous inline handler without distinct call
839
+ name = _fallback_handler_name(route_path)
840
+ return name, name, middlewares
841
+
842
+
679
843
  _NEXT_ROUTE_EXPORT_RE = re.compile(
680
844
  r"export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)\s*\("
681
845
  )
@@ -702,10 +866,11 @@ def analyze_js_ts_line(
702
866
  if method == "ALL":
703
867
  method = "ANY"
704
868
  route_path = m.group(3)
705
- handler_expr = m.group(4)
706
- handler_name = handler_expr.split(".")[-1]
869
+ raw_args = m.group(4)
870
+ handler_name, handler_expr, middlewares = _resolve_express_handler_and_middleware(raw_args, route_path)
707
871
  handler_canon = f"{module}.{handler_expr}"
708
872
  norm = normalize_route_path(route_path, "express")
873
+ mw_str = f" (middleware: {', '.join(middlewares)})" if middlewares else ""
709
874
 
710
875
  routes.append(
711
876
  RouteDetection(
@@ -720,7 +885,7 @@ def analyze_js_ts_line(
720
885
  column=m.start(),
721
886
  confidence="HIGH",
722
887
  resolution_status="RESOLVED",
723
- evidence=f"Express route registration {m.group(0)}",
888
+ evidence=f"Express route registration {router_obj}.{method.lower()}('{route_path}', ...){mw_str}",
724
889
  module=module,
725
890
  router_name=router_obj,
726
891
  )