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.
- codegraph/__init__.py +1 -1
- codegraph/agent_brain.py +172 -2
- codegraph/agent_capabilities.py +564 -6
- codegraph/agent_rules.py +1 -1
- codegraph/api_drift.py +455 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +992 -3
- codegraph/cli_output.py +17 -2
- codegraph/context.py +38 -11
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +14 -0
- codegraph/database/extractor.py +712 -17
- codegraph/database/interrogation.py +388 -2
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +7 -0
- codegraph/evidence_contract.py +21 -0
- codegraph/frameworks.py +171 -6
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +2 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +2 -0
- codegraph/git_state.py +545 -0
- codegraph/indexing/__init__.py +7 -1
- codegraph/indexing/indexer.py +291 -38
- codegraph/indexing/models.py +6 -0
- codegraph/indexing/parser.py +239 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +6 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/interrogation.py +128 -20
- codegraph/mcp/server.py +258 -27
- codegraph/models.py +7 -0
- codegraph/monorepo.py +3 -0
- codegraph/optimizer.py +42 -5
- codegraph/process_lifecycle.py +31 -13
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +403 -0
- codegraph/resolver.py +1202 -159
- codegraph/retrieval_policy.py +6 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +19 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +30 -10
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/runner.py +155 -6
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/hybrid.py +26 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +34 -3
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +715 -0
- codegraph/watcher.py +323 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/METADATA +257 -39
- codegraph_engine-3.0.0.dist-info/RECORD +129 -0
- codegraph_engine-2.2.1.dist-info/RECORD +0 -87
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/WHEEL +0 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/entry_points.txt +0 -0
- {codegraph_engine-2.2.1.dist-info → codegraph_engine-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {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",
|
codegraph/evidence_contract.py
CHANGED
|
@@ -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
|
|
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*(
|
|
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
|
-
|
|
706
|
-
handler_name =
|
|
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 {
|
|
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
|
)
|