codegraph-brain 0.6.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.
- cgis/__init__.py +10 -0
- cgis/__main__.py +16 -0
- cgis/api/.gitkeep +0 -0
- cgis/api/__init__.py +1 -0
- cgis/api/mcp_server.py +550 -0
- cgis/cli.py +1516 -0
- cgis/core/.gitkeep +0 -0
- cgis/core/models.py +140 -0
- cgis/extractors/.gitkeep +0 -0
- cgis/extractors/_python_ast.py +194 -0
- cgis/extractors/_python_classes.py +126 -0
- cgis/extractors/_python_functions.py +312 -0
- cgis/extractors/_python_imports.py +188 -0
- cgis/extractors/_python_types.py +84 -0
- cgis/extractors/base.py +42 -0
- cgis/extractors/python_extractor.py +235 -0
- cgis/extractors/typescript_extractor.py +310 -0
- cgis/guardian/__init__.py +1 -0
- cgis/guardian/bench.py +199 -0
- cgis/guardian/chunked.py +240 -0
- cgis/guardian/chunker.py +148 -0
- cgis/guardian/collector.py +309 -0
- cgis/guardian/core.py +120 -0
- cgis/guardian/diff_index.py +151 -0
- cgis/guardian/findings.py +70 -0
- cgis/guardian/github_poster.py +133 -0
- cgis/guardian/metrics.py +108 -0
- cgis/guardian/prompts.py +217 -0
- cgis/guardian/providers/__init__.py +1 -0
- cgis/guardian/providers/base.py +112 -0
- cgis/guardian/providers/gemini.py +83 -0
- cgis/guardian/providers/mistral.py +83 -0
- cgis/guardian/providers/ollama.py +99 -0
- cgis/guardian/recording.py +82 -0
- cgis/guardian/render.py +107 -0
- cgis/guardian/runner.py +295 -0
- cgis/guardian/skeptic.py +208 -0
- cgis/pipeline.py +252 -0
- cgis/py.typed +0 -0
- cgis/query/analysis/__init__.py +0 -0
- cgis/query/analysis/analyzer.py +241 -0
- cgis/query/analysis/anomaly.py +34 -0
- cgis/query/analysis/cohesion.py +277 -0
- cgis/query/analysis/health.py +128 -0
- cgis/query/analysis/suggest_service.py +221 -0
- cgis/query/context/__init__.py +0 -0
- cgis/query/context/audit.py +134 -0
- cgis/query/context/context_service.py +129 -0
- cgis/query/context/prompt.py +184 -0
- cgis/query/context/snippet.py +96 -0
- cgis/query/drift/__init__.py +0 -0
- cgis/query/drift/_scc.py +90 -0
- cgis/query/drift/drift.py +867 -0
- cgis/query/drift/drift_service.py +217 -0
- cgis/query/drift/fingerprint.py +255 -0
- cgis/query/drift/fractal.py +292 -0
- cgis/query/drift/ontology_init.py +470 -0
- cgis/query/drift/quotient.py +75 -0
- cgis/query/drift/triads.py +234 -0
- cgis/query/engine.py +170 -0
- cgis/query/fqn.py +65 -0
- cgis/query/render/__init__.py +0 -0
- cgis/query/render/graph_json.py +42 -0
- cgis/query/render/mermaid.py +235 -0
- cgis/query/render/metrics.py +346 -0
- cgis/resolver/.gitkeep +0 -0
- cgis/resolver/__init__.py +1 -0
- cgis/resolver/engine.py +145 -0
- cgis/resolver/indices.py +184 -0
- cgis/resolver/symbols.py +179 -0
- cgis/resolver/uplift.py +263 -0
- cgis/storage/.gitkeep +0 -0
- cgis/storage/sqlite_store.py +589 -0
- codegraph_brain-0.6.0.dist-info/METADATA +240 -0
- codegraph_brain-0.6.0.dist-info/RECORD +78 -0
- codegraph_brain-0.6.0.dist-info/WHEEL +4 -0
- codegraph_brain-0.6.0.dist-info/entry_points.txt +3 -0
- codegraph_brain-0.6.0.dist-info/licenses/LICENSE +21 -0
cgis/resolver/uplift.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Semantic Uplift Engine — enriches the code graph with ontology classes and domain tags."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
import re
|
|
7
|
+
from collections import deque
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from cgis.core.models import (
|
|
14
|
+
VIRTUAL_FILE_PATH,
|
|
15
|
+
Edge,
|
|
16
|
+
EdgeType,
|
|
17
|
+
Node,
|
|
18
|
+
NodeNamespace,
|
|
19
|
+
NodeType,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from cgis.storage.sqlite_store import SQLiteStore
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Ontology class mapping table (Phase 1)
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
_ONTOLOGY_CLASS: dict[NodeType, str] = {
|
|
30
|
+
NodeType.FILE: "File",
|
|
31
|
+
NodeType.MODULE: "Module",
|
|
32
|
+
NodeType.CLASS: "Class",
|
|
33
|
+
NodeType.FUNCTION: "Function",
|
|
34
|
+
NodeType.METHOD: "Method",
|
|
35
|
+
NodeType.VARIABLE: "Variable",
|
|
36
|
+
NodeType.IMPORT: "Import",
|
|
37
|
+
NodeType.EXPORT: "Export",
|
|
38
|
+
NodeType.API_ENDPOINT: "APIEndpoint",
|
|
39
|
+
NodeType.ROUTE_HANDLER: "RouteHandler",
|
|
40
|
+
NodeType.DB_TABLE: "DBTable",
|
|
41
|
+
NodeType.DB_QUERY: "DBQuery",
|
|
42
|
+
NodeType.EVENT: "Event",
|
|
43
|
+
NodeType.DOMAIN_CONCEPT: "DomainConcept",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_STRUCTURAL_EDGE_TYPES = frozenset({EdgeType.CONTAINS, EdgeType.DECLARES})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _load_domains_config(config_path: str) -> dict[str, Any]:
|
|
50
|
+
"""Read and parse a domains.yaml file, returning the `domains` mapping."""
|
|
51
|
+
try:
|
|
52
|
+
with Path(config_path).open(encoding="utf-8") as fh:
|
|
53
|
+
data = yaml.safe_load(fh)
|
|
54
|
+
except OSError as e:
|
|
55
|
+
msg = f"Cannot read domains config {config_path!r}: {e}"
|
|
56
|
+
raise ValueError(msg) from e
|
|
57
|
+
except yaml.YAMLError as e:
|
|
58
|
+
msg = f"Invalid YAML in domains config {config_path!r}: {e}"
|
|
59
|
+
raise ValueError(msg) from e
|
|
60
|
+
if not isinstance(data, dict):
|
|
61
|
+
return {}
|
|
62
|
+
domains: dict[str, Any] = data.get("domains") or {}
|
|
63
|
+
return domains
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class SemanticUpliftEngine:
|
|
67
|
+
"""
|
|
68
|
+
Post-processing pass that enriches the graph with semantic metadata.
|
|
69
|
+
|
|
70
|
+
Run after ResolverEngine has produced fully resolved edges. Operates
|
|
71
|
+
directly on the SQLiteStore — reads, updates, and saves in-place.
|
|
72
|
+
|
|
73
|
+
Phases:
|
|
74
|
+
1. Ontology class mapping — NodeType → ontology_class string
|
|
75
|
+
2. Heuristic domain tagging — file_path / fqn pattern matching from domains.yaml
|
|
76
|
+
3. Structural propagation — domains flow FILE → CLASS → METHOD via CONTAINS/DECLARES
|
|
77
|
+
4. Domain dependency inference — emit DOMAIN_DEPENDS_ON edges from cross-domain CALLS
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
store: SQLiteStore,
|
|
83
|
+
domains_config_path: str | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Bind to a store and optionally load domain heuristics from a YAML config."""
|
|
86
|
+
self._store = store
|
|
87
|
+
self._domains: dict[str, Any] = {}
|
|
88
|
+
if domains_config_path:
|
|
89
|
+
self._domains = _load_domains_config(domains_config_path)
|
|
90
|
+
|
|
91
|
+
def execute_uplift(self) -> None:
|
|
92
|
+
"""Run all phases against the store in a single pass."""
|
|
93
|
+
self._store.delete_semantic_uplift()
|
|
94
|
+
nodes = self._store.get_all_nodes()
|
|
95
|
+
edges = self._store.get_all_edges()
|
|
96
|
+
|
|
97
|
+
original_nodes: dict[str, Node] = {n.id: n for n in nodes}
|
|
98
|
+
nodes_map = dict(original_nodes)
|
|
99
|
+
|
|
100
|
+
nodes_map = _phase1_map_ontology_classes(nodes_map)
|
|
101
|
+
|
|
102
|
+
# Always reset domain tags — ensures deterministic state whether or not a
|
|
103
|
+
# domains config is provided; prevents stale tags from driving phase 4.
|
|
104
|
+
# Only copy nodes that actually carry domains to avoid unnecessary allocations.
|
|
105
|
+
nodes_map = {
|
|
106
|
+
nid: n.model_copy(update={"domains": []}) if n.domains else n
|
|
107
|
+
for nid, n in nodes_map.items()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if self._domains:
|
|
111
|
+
nodes_map = _phase2_apply_heuristic_tagging(nodes_map, self._domains)
|
|
112
|
+
nodes_map = _phase3_propagate_domains(nodes_map, edges)
|
|
113
|
+
|
|
114
|
+
domain_nodes, domain_edges = _phase4_infer_domain_dependencies(nodes_map, edges)
|
|
115
|
+
|
|
116
|
+
changed_nodes = [n for node_id, n in nodes_map.items() if n != original_nodes[node_id]]
|
|
117
|
+
if changed_nodes:
|
|
118
|
+
self._store.upsert_nodes(changed_nodes)
|
|
119
|
+
if domain_nodes:
|
|
120
|
+
self._store.upsert_nodes(domain_nodes)
|
|
121
|
+
if domain_edges:
|
|
122
|
+
self._store.upsert_edges(domain_edges)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Phase implementations (module-level for testability)
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _phase1_map_ontology_classes(nodes_map: dict[str, Node]) -> dict[str, Node]:
|
|
131
|
+
"""Assign ontology_class to every node based on its NodeType."""
|
|
132
|
+
result: dict[str, Node] = {}
|
|
133
|
+
for node_id, node in nodes_map.items():
|
|
134
|
+
ontology_class = _ONTOLOGY_CLASS.get(node.type)
|
|
135
|
+
if ontology_class is not None and node.ontology_class != ontology_class:
|
|
136
|
+
result[node_id] = node.model_copy(update={"ontology_class": ontology_class})
|
|
137
|
+
else:
|
|
138
|
+
result[node_id] = node
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _compile_patterns(patterns: list[str]) -> list[re.Pattern[str]]:
|
|
143
|
+
"""Pre-compile fnmatch glob patterns into regex for efficient repeated matching."""
|
|
144
|
+
return [re.compile(fnmatch.translate(p)) for p in patterns]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _phase2_apply_heuristic_tagging(
|
|
148
|
+
nodes_map: dict[str, Node],
|
|
149
|
+
domains: dict[str, Any],
|
|
150
|
+
) -> dict[str, Node]:
|
|
151
|
+
"""Tag nodes with domain labels using file_path and fqn patterns from domains config."""
|
|
152
|
+
result = dict(nodes_map)
|
|
153
|
+
for domain_name, raw_cfg in domains.items():
|
|
154
|
+
domain_cfg: dict[str, Any] = raw_cfg or {}
|
|
155
|
+
heuristics: dict[str, Any] = domain_cfg.get("heuristics") or {}
|
|
156
|
+
fp_patterns = _compile_patterns(heuristics.get("file_path_patterns") or [])
|
|
157
|
+
fqn_patterns = _compile_patterns(heuristics.get("fqn_patterns") or [])
|
|
158
|
+
|
|
159
|
+
for node_id, node in nodes_map.items():
|
|
160
|
+
if node.namespace != NodeNamespace.INTERNAL:
|
|
161
|
+
continue
|
|
162
|
+
matched = any(p.match(node.file_path) for p in fp_patterns) or any(
|
|
163
|
+
p.match(node.id) for p in fqn_patterns
|
|
164
|
+
)
|
|
165
|
+
if matched and domain_name not in result[node_id].domains:
|
|
166
|
+
result[node_id] = result[node_id].model_copy(
|
|
167
|
+
update={"domains": sorted({*result[node_id].domains, domain_name})}
|
|
168
|
+
)
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _build_children_map(edges: list[Edge]) -> dict[str, list[str]]:
|
|
173
|
+
"""Build a parent → [child, ...] mapping from CONTAINS/DECLARES structural edges."""
|
|
174
|
+
children: dict[str, list[str]] = {}
|
|
175
|
+
for edge in edges:
|
|
176
|
+
if edge.type in _STRUCTURAL_EDGE_TYPES:
|
|
177
|
+
children.setdefault(edge.source, []).append(edge.target)
|
|
178
|
+
return children
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _phase3_propagate_domains(
|
|
182
|
+
nodes_map: dict[str, Node],
|
|
183
|
+
edges: list[Edge],
|
|
184
|
+
) -> dict[str, Node]:
|
|
185
|
+
"""Propagate domains downward through CONTAINS/DECLARES structural edges (O(V+E) BFS)."""
|
|
186
|
+
children = _build_children_map(edges)
|
|
187
|
+
result = dict(nodes_map)
|
|
188
|
+
queue: deque[str] = deque(node_id for node_id, node in result.items() if node.domains)
|
|
189
|
+
in_queue: set[str] = set(queue)
|
|
190
|
+
|
|
191
|
+
while queue:
|
|
192
|
+
node_id = queue.popleft()
|
|
193
|
+
in_queue.discard(node_id)
|
|
194
|
+
node = result[node_id]
|
|
195
|
+
for child_id in children.get(node_id, []):
|
|
196
|
+
child = result.get(child_id)
|
|
197
|
+
if child is None:
|
|
198
|
+
continue
|
|
199
|
+
merged = sorted(set(child.domains) | set(node.domains))
|
|
200
|
+
if merged != child.domains:
|
|
201
|
+
result[child_id] = child.model_copy(update={"domains": merged})
|
|
202
|
+
if child_id not in in_queue:
|
|
203
|
+
queue.append(child_id)
|
|
204
|
+
in_queue.add(child_id)
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _collect_cross_domain_pairs(
|
|
209
|
+
src_domains: list[str],
|
|
210
|
+
tgt_domains: list[str],
|
|
211
|
+
dep_pairs: set[tuple[str, str]],
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Add all cross-domain (src, tgt) pairs where src_domain != tgt_domain."""
|
|
214
|
+
for src_domain in src_domains:
|
|
215
|
+
for tgt_domain in tgt_domains:
|
|
216
|
+
if src_domain != tgt_domain:
|
|
217
|
+
dep_pairs.add((src_domain, tgt_domain))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _phase4_infer_domain_dependencies(
|
|
221
|
+
nodes_map: dict[str, Node],
|
|
222
|
+
edges: list[Edge],
|
|
223
|
+
) -> tuple[list[Node], list[Edge]]:
|
|
224
|
+
"""Emit DOMAIN_DEPENDS_ON edges for every cross-domain CALLS relationship."""
|
|
225
|
+
dep_pairs: set[tuple[str, str]] = set()
|
|
226
|
+
for edge in edges:
|
|
227
|
+
if edge.type != EdgeType.CALLS:
|
|
228
|
+
continue
|
|
229
|
+
src = nodes_map.get(edge.source)
|
|
230
|
+
tgt = nodes_map.get(edge.target)
|
|
231
|
+
if src is None or tgt is None:
|
|
232
|
+
continue
|
|
233
|
+
_collect_cross_domain_pairs(src.domains, tgt.domains, dep_pairs)
|
|
234
|
+
|
|
235
|
+
domain_ids: set[str] = set()
|
|
236
|
+
new_edges: list[Edge] = []
|
|
237
|
+
for src_domain, tgt_domain in dep_pairs:
|
|
238
|
+
domain_ids.add(src_domain)
|
|
239
|
+
domain_ids.add(tgt_domain)
|
|
240
|
+
new_edges.append(
|
|
241
|
+
Edge(
|
|
242
|
+
id=f"domain:{src_domain}->domain:{tgt_domain}",
|
|
243
|
+
type=EdgeType.DOMAIN_DEPENDS_ON,
|
|
244
|
+
source=f"domain:{src_domain}",
|
|
245
|
+
target=f"domain:{tgt_domain}",
|
|
246
|
+
confidence=0.8,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
domain_nodes = [
|
|
251
|
+
Node(
|
|
252
|
+
id=f"domain:{name}",
|
|
253
|
+
type=NodeType.DOMAIN_CONCEPT,
|
|
254
|
+
name=name,
|
|
255
|
+
file_path=VIRTUAL_FILE_PATH,
|
|
256
|
+
start_line=0,
|
|
257
|
+
end_line=0,
|
|
258
|
+
namespace=NodeNamespace.INTERNAL,
|
|
259
|
+
ontology_class="DomainConcept",
|
|
260
|
+
)
|
|
261
|
+
for name in domain_ids
|
|
262
|
+
]
|
|
263
|
+
return domain_nodes, new_edges
|
cgis/storage/.gitkeep
ADDED
|
File without changes
|