codebase-brain 0.1.0__py3-none-any.whl → 0.2.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.
- brain_parser/bug_detector.py +15 -14
- brain_parser/codebase_walker.py +11 -6
- brain_parser/graph_builder.py +144 -30
- {codebase_brain-0.1.0.dist-info → codebase_brain-0.2.0.dist-info}/METADATA +1 -1
- {codebase_brain-0.1.0.dist-info → codebase_brain-0.2.0.dist-info}/RECORD +8 -8
- {codebase_brain-0.1.0.dist-info → codebase_brain-0.2.0.dist-info}/WHEEL +0 -0
- {codebase_brain-0.1.0.dist-info → codebase_brain-0.2.0.dist-info}/entry_points.txt +0 -0
- {codebase_brain-0.1.0.dist-info → codebase_brain-0.2.0.dist-info}/top_level.txt +0 -0
brain_parser/bug_detector.py
CHANGED
|
@@ -9,32 +9,33 @@ def detect_circular_dependencies(brain, temp_path=None):
|
|
|
9
9
|
G = build_graph(brain)
|
|
10
10
|
bugs = []
|
|
11
11
|
|
|
12
|
-
def
|
|
12
|
+
def rel(path):
|
|
13
13
|
p = path.replace('\\', '/')
|
|
14
14
|
idx = p.find('/temp/')
|
|
15
15
|
if idx != -1:
|
|
16
16
|
after_temp = p[idx + 6:]
|
|
17
17
|
parts = after_temp.split('/', 1)
|
|
18
|
-
if len(parts) > 1
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
p = parts[1] if len(parts) > 1 else parts[0]
|
|
19
|
+
if '/src/' in p:
|
|
20
|
+
p = p[p.find('/src/') + 1:]
|
|
21
|
+
elif p.startswith('./'):
|
|
22
|
+
p = p[2:]
|
|
23
|
+
return p
|
|
23
24
|
|
|
24
25
|
try:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
chain = '\n '.join([f"{i+1}. {f}" for i, f in enumerate(
|
|
30
|
-
chain += f"\n → back to {clean_cycle[0]}"
|
|
26
|
+
sccs = [scc for scc in nx.strongly_connected_components(G) if len(scc) > 1]
|
|
27
|
+
|
|
28
|
+
for scc in sccs:
|
|
29
|
+
clean_files = sorted(list(set([rel(f).split('/')[-1] for f in scc])))
|
|
30
|
+
chain = '\n '.join([f"{i+1}. {f}" for i, f in enumerate(clean_files)])
|
|
31
31
|
bugs.append({
|
|
32
32
|
'type': 'circular_dependency',
|
|
33
33
|
'severity': 'HIGH',
|
|
34
|
-
'files':
|
|
35
|
-
'message': f"Circular dependency ({len(
|
|
34
|
+
'files': clean_files,
|
|
35
|
+
'message': f"Circular dependency group ({len(clean_files)} files):\n {chain}",
|
|
36
36
|
'fix': 'Extract shared logic into a separate file that both can import from.'
|
|
37
37
|
})
|
|
38
|
+
|
|
38
39
|
except Exception as e:
|
|
39
40
|
print(f"Cycle detection error: {e}")
|
|
40
41
|
|
brain_parser/codebase_walker.py
CHANGED
|
@@ -53,18 +53,23 @@ def walk_codebase(root_path):
|
|
|
53
53
|
|
|
54
54
|
return brain
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
def get_brain_path():
|
|
57
|
+
"""Always returns brain.json in the current working directory."""
|
|
58
|
+
return os.path.join(os.getcwd(), 'brain.json')
|
|
57
59
|
|
|
58
|
-
def save_brain(brain, output_path=
|
|
60
|
+
def save_brain(brain, output_path=None):
|
|
61
|
+
if output_path is None:
|
|
62
|
+
output_path = get_brain_path()
|
|
59
63
|
with open(output_path, "w") as f:
|
|
60
64
|
json.dump(brain, f, indent=4)
|
|
61
65
|
print(f"Brain saved to {output_path}")
|
|
62
66
|
|
|
63
|
-
def load_brain(
|
|
64
|
-
if
|
|
65
|
-
|
|
67
|
+
def load_brain(output_path=None):
|
|
68
|
+
if output_path is None:
|
|
69
|
+
output_path = get_brain_path()
|
|
70
|
+
if not os.path.exists(output_path):
|
|
66
71
|
return None
|
|
67
|
-
with open(
|
|
72
|
+
with open(output_path, 'r') as f:
|
|
68
73
|
return json.load(f)
|
|
69
74
|
|
|
70
75
|
|
brain_parser/graph_builder.py
CHANGED
|
@@ -82,71 +82,185 @@ def calculate_risk(G):
|
|
|
82
82
|
return risk
|
|
83
83
|
|
|
84
84
|
def visualize_interactive(G):
|
|
85
|
-
|
|
85
|
+
from pyvis.network import Network
|
|
86
|
+
import os
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
"HIGH": "#ff4444",
|
|
89
|
-
"MEDIUM": "#ffa500",
|
|
90
|
-
"LOW": "#44cc44"
|
|
91
|
-
}
|
|
88
|
+
risk = calculate_risk(G)
|
|
92
89
|
|
|
93
90
|
net = Network(
|
|
94
|
-
height="
|
|
91
|
+
height="100vh",
|
|
95
92
|
width="100%",
|
|
96
|
-
bgcolor="#
|
|
97
|
-
font_color="
|
|
98
|
-
directed=True
|
|
93
|
+
bgcolor="#0d0d0d",
|
|
94
|
+
font_color="#ffffff",
|
|
95
|
+
directed=True,
|
|
96
|
+
cdn_resources='in_line'
|
|
99
97
|
)
|
|
100
98
|
|
|
99
|
+
color_map = {
|
|
100
|
+
"HIGH": "#ff3860",
|
|
101
|
+
"MEDIUM": "#ffdd57",
|
|
102
|
+
"LOW": "#23d160"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# build node map first — ensures consistent keys for edges
|
|
106
|
+
node_map = {}
|
|
101
107
|
for node in G.nodes():
|
|
102
|
-
|
|
108
|
+
node_map[node] = str(node)
|
|
109
|
+
|
|
110
|
+
for node, node_id in node_map.items():
|
|
111
|
+
risk_level = risk.get(node, 'LOW')
|
|
103
112
|
color = color_map[risk_level]
|
|
104
|
-
label = node.
|
|
113
|
+
label = node.replace('\\', '/').split('/')[-1]
|
|
114
|
+
size = 18 + (G.in_degree(node) * 5)
|
|
105
115
|
|
|
106
116
|
net.add_node(
|
|
107
|
-
|
|
117
|
+
node_id,
|
|
108
118
|
label=label,
|
|
109
|
-
color=
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
color={
|
|
120
|
+
'background': color,
|
|
121
|
+
'border': color,
|
|
122
|
+
'highlight': {'background': '#ffffff', 'border': color}
|
|
123
|
+
},
|
|
124
|
+
size=size,
|
|
125
|
+
font={'size': 14, 'color': '#ffffff', 'face': 'monospace'},
|
|
126
|
+
borderWidth=2,
|
|
127
|
+
shadow=True,
|
|
128
|
+
title=f"File: {label}\nRisk: {risk_level}\nConnections: {G.in_degree(node)}"
|
|
112
129
|
)
|
|
113
130
|
|
|
114
131
|
for edge in G.edges():
|
|
115
|
-
|
|
132
|
+
src = node_map.get(edge[0], str(edge[0]))
|
|
133
|
+
dst = node_map.get(edge[1], str(edge[1]))
|
|
134
|
+
if src in net.get_nodes() and dst in net.get_nodes():
|
|
135
|
+
net.add_edge(
|
|
136
|
+
src, dst,
|
|
137
|
+
color={'color': '#ffffff33'},
|
|
138
|
+
width=1.5,
|
|
139
|
+
arrows={'to': {'enabled': True, 'scaleFactor': 0.6}}
|
|
140
|
+
)
|
|
116
141
|
|
|
117
142
|
net.set_options("""
|
|
118
143
|
{
|
|
119
144
|
"nodes": {
|
|
120
145
|
"borderWidth": 2,
|
|
121
|
-
"shadow":
|
|
146
|
+
"shadow": {
|
|
147
|
+
"enabled": true,
|
|
148
|
+
"color": "rgba(0,0,0,0.8)",
|
|
149
|
+
"size": 15,
|
|
150
|
+
"x": 0,
|
|
151
|
+
"y": 0
|
|
152
|
+
}
|
|
122
153
|
},
|
|
123
154
|
"edges": {
|
|
124
155
|
"smooth": {
|
|
125
|
-
"type": "
|
|
156
|
+
"type": "curvedCW",
|
|
157
|
+
"roundness": 0.2
|
|
126
158
|
},
|
|
127
|
-
"shadow":
|
|
159
|
+
"shadow": false
|
|
128
160
|
},
|
|
129
161
|
"interaction": {
|
|
130
162
|
"hover": true,
|
|
131
|
-
"navigationButtons":
|
|
132
|
-
"hideEdgesOnDrag": true
|
|
163
|
+
"navigationButtons": false,
|
|
164
|
+
"hideEdgesOnDrag": true,
|
|
165
|
+
"tooltipDelay": 100
|
|
133
166
|
},
|
|
134
167
|
"physics": {
|
|
135
|
-
"stabilization":
|
|
168
|
+
"stabilization": {
|
|
169
|
+
"enabled": true,
|
|
170
|
+
"iterations": 200
|
|
171
|
+
},
|
|
136
172
|
"barnesHut": {
|
|
137
|
-
"gravitationalConstant": -
|
|
138
|
-
"
|
|
139
|
-
"
|
|
173
|
+
"gravitationalConstant": -12000,
|
|
174
|
+
"centralGravity": 0.1,
|
|
175
|
+
"springLength": 250,
|
|
176
|
+
"springConstant": 0.02,
|
|
177
|
+
"damping": 0.09
|
|
140
178
|
}
|
|
141
179
|
}
|
|
142
180
|
}
|
|
143
181
|
""")
|
|
144
|
-
|
|
182
|
+
|
|
183
|
+
output_path = os.path.join(os.getcwd(), 'brain_map.html')
|
|
184
|
+
|
|
185
|
+
html_content = net.generate_html()
|
|
186
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
187
|
+
f.write(html_content)
|
|
188
|
+
|
|
189
|
+
with open(output_path, 'r', encoding='utf-8') as f:
|
|
190
|
+
html = f.read()
|
|
191
|
+
|
|
192
|
+
custom_style = """
|
|
193
|
+
<style>
|
|
194
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
195
|
+
body { background: #0d0d0d; font-family: 'Courier New', monospace; }
|
|
196
|
+
#mynetwork {
|
|
197
|
+
background: radial-gradient(ellipse at center, #1a1a2e 0%, #0d0d0d 100%);
|
|
198
|
+
border: none !important;
|
|
199
|
+
}
|
|
200
|
+
.legend {
|
|
201
|
+
position: fixed;
|
|
202
|
+
top: 20px;
|
|
203
|
+
left: 20px;
|
|
204
|
+
background: rgba(13,13,13,0.9);
|
|
205
|
+
border: 1px solid #333;
|
|
206
|
+
border-radius: 12px;
|
|
207
|
+
padding: 16px 20px;
|
|
208
|
+
color: #fff;
|
|
209
|
+
font-family: monospace;
|
|
210
|
+
font-size: 13px;
|
|
211
|
+
z-index: 1000;
|
|
212
|
+
backdrop-filter: blur(10px);
|
|
213
|
+
}
|
|
214
|
+
.legend-title {
|
|
215
|
+
color: #888;
|
|
216
|
+
font-size: 11px;
|
|
217
|
+
letter-spacing: 2px;
|
|
218
|
+
text-transform: uppercase;
|
|
219
|
+
margin-bottom: 12px;
|
|
220
|
+
}
|
|
221
|
+
.legend-item { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
|
|
222
|
+
.dot { width: 12px; height: 12px; border-radius: 50%; box-shadow: 0 0 8px currentColor; }
|
|
223
|
+
.high { background: #ff3860; color: #ff3860; }
|
|
224
|
+
.medium { background: #ffdd57; color: #ffdd57; }
|
|
225
|
+
.low { background: #23d160; color: #23d160; }
|
|
226
|
+
.brain-title {
|
|
227
|
+
position: fixed;
|
|
228
|
+
top: 20px;
|
|
229
|
+
right: 20px;
|
|
230
|
+
color: #444;
|
|
231
|
+
font-family: monospace;
|
|
232
|
+
font-size: 12px;
|
|
233
|
+
letter-spacing: 3px;
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
z-index: 1000;
|
|
236
|
+
}
|
|
237
|
+
div.vis-tooltip {
|
|
238
|
+
background: #1a1a2e !important;
|
|
239
|
+
border: 1px solid #ff3860 !important;
|
|
240
|
+
border-radius: 8px !important;
|
|
241
|
+
color: #fff !important;
|
|
242
|
+
font-family: monospace !important;
|
|
243
|
+
font-size: 12px !important;
|
|
244
|
+
padding: 10px 14px !important;
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
247
|
+
<div class="legend">
|
|
248
|
+
<div class="legend-title">Risk Level</div>
|
|
249
|
+
<div class="legend-item"><div class="dot high"></div><span>High Risk</span></div>
|
|
250
|
+
<div class="legend-item"><div class="dot medium"></div><span>Medium Risk</span></div>
|
|
251
|
+
<div class="legend-item"><div class="dot low"></div><span>Low Risk</span></div>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="brain-title">Codebase Brain</div>
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
html = html.replace('<body>', '<body>' + custom_style)
|
|
257
|
+
|
|
258
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
259
|
+
f.write(html)
|
|
260
|
+
|
|
145
261
|
print(f"Nodes in graph: {len(G.nodes())}")
|
|
146
262
|
print(f"Edges in graph: {len(G.edges())}")
|
|
147
|
-
|
|
148
|
-
webbrowser.open("brain_map.html")
|
|
149
|
-
print("Interactive graph saved to brain_map.html")
|
|
263
|
+
print(f"Graph saved to {output_path}")
|
|
150
264
|
|
|
151
265
|
|
|
152
266
|
def get_impact(G, filepath):
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
brain_cli.py,sha256=sdJ5r60B4ELMruWR8B9azejpnizFkhdDDv0IrPhMLvE,11580
|
|
2
2
|
brain_parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
brain_parser/ast_parser.py,sha256=3Vy1XXyInjgA2xn6IBYv4K1juEdtFwAo58upqukCHXI,1799
|
|
4
|
-
brain_parser/bug_detector.py,sha256=
|
|
5
|
-
brain_parser/codebase_walker.py,sha256=
|
|
4
|
+
brain_parser/bug_detector.py,sha256=IbEpyXfatL0r1cjaIZ7LXzYPg7z0molyRjg2ULu4828,7653
|
|
5
|
+
brain_parser/codebase_walker.py,sha256=rWAHtHWurpZ7HhHAXLSuySMn07TIfg20LrRJ8wyCxKQ,3714
|
|
6
6
|
brain_parser/file_watcher.py,sha256=cuok95kDJy6uKfsz_HByKWLBvRIPUAz7djAaXQ-2T3Q,2565
|
|
7
|
-
brain_parser/graph_builder.py,sha256=
|
|
7
|
+
brain_parser/graph_builder.py,sha256=gDeh4SCnpbjGGi0n1cX5kaj8uQ_-a6maKOMXK7Kn8z0,8730
|
|
8
8
|
brain_parser/llm_router.py,sha256=f86O4tUw3MV1s2dq6axEzwJHEQfyboaqPr_QDR8CtK0,2447
|
|
9
9
|
brain_parser/query_engine.py,sha256=Lunb_-9z1Lk2VYOE7f_-jF8X2AQtby1tPFXgFUjqnpA,3671
|
|
10
10
|
brain_parser/root_cause.py,sha256=jvRB9OzFREU0oguGhfoU8wV2mCJ7A85y2BohybzIQ-0,5326
|
|
11
11
|
brain_parser/universal_parser.py,sha256=47StXj-GPGJb4g0WzM5VYgM3tIc1tOzwEa8mcL_q9AY,6694
|
|
12
|
-
codebase_brain-0.
|
|
13
|
-
codebase_brain-0.
|
|
14
|
-
codebase_brain-0.
|
|
15
|
-
codebase_brain-0.
|
|
16
|
-
codebase_brain-0.
|
|
12
|
+
codebase_brain-0.2.0.dist-info/METADATA,sha256=fxLp9D79h6tMcXPXsS5T7kD4zJ93t6RgEmWaBRL-MQg,327
|
|
13
|
+
codebase_brain-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
codebase_brain-0.2.0.dist-info/entry_points.txt,sha256=O9yHP4zy_uoVPxmewGbdNjNksmxRYUv34MQJDRXXodc,41
|
|
15
|
+
codebase_brain-0.2.0.dist-info/top_level.txt,sha256=RmSvznU63-vmm5zLSKAlYYsFzQ8X3RyPrB-m0q0uQfI,23
|
|
16
|
+
codebase_brain-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|