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.
@@ -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 clean(path):
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
- return parts[1]
20
- return parts[0]
21
- # fallback — just filename
22
- return p.split('/')[-1]
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
- cycles = list(nx.simple_cycles(G))
26
- for cycle in cycles:
27
- clean_cycle = [clean(f) for f in cycle]
28
- # format as numbered list not one long line
29
- chain = '\n '.join([f"{i+1}. {f}" for i, f in enumerate(clean_cycle)])
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': clean_cycle,
35
- 'message': f"Circular dependency ({len(cycle)} files):\n {chain}",
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
 
@@ -53,18 +53,23 @@ def walk_codebase(root_path):
53
53
 
54
54
  return brain
55
55
 
56
- BRAIN_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'brain.json')
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=BRAIN_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(input_path=BRAIN_PATH):
64
- if not os.path.exists(input_path):
65
- print("No brain found. Run walker first.")
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(input_path, "r") as f:
72
+ with open(output_path, 'r') as f:
68
73
  return json.load(f)
69
74
 
70
75
 
@@ -82,71 +82,185 @@ def calculate_risk(G):
82
82
  return risk
83
83
 
84
84
  def visualize_interactive(G):
85
- risk = calculate_risk(G)\
85
+ from pyvis.network import Network
86
+ import os
86
87
 
87
- color_map = {
88
- "HIGH": "#ff4444",
89
- "MEDIUM": "#ffa500",
90
- "LOW": "#44cc44"
91
- }
88
+ risk = calculate_risk(G)
92
89
 
93
90
  net = Network(
94
- height="750px",
91
+ height="100vh",
95
92
  width="100%",
96
- bgcolor="#1a1a2e",
97
- font_color="white",
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
- risk_level = risk[node]
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.split("\\")[-1].split("/")[-1]
113
+ label = node.replace('\\', '/').split('/')[-1]
114
+ size = 18 + (G.in_degree(node) * 5)
105
115
 
106
116
  net.add_node(
107
- node,
117
+ node_id,
108
118
  label=label,
109
- color=color,
110
- size=20 + (G.in_degree(node) * 3),
111
- title=f"File: {label}\nRisk: {risk_level}\nConnections: {G.in_degree(node)}"
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
- net.add_edge(edge[0], edge[1])
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": true
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": "dynamic"
156
+ "type": "curvedCW",
157
+ "roundness": 0.2
126
158
  },
127
- "shadow": true
159
+ "shadow": false
128
160
  },
129
161
  "interaction": {
130
162
  "hover": true,
131
- "navigationButtons": true,
132
- "hideEdgesOnDrag": true
163
+ "navigationButtons": false,
164
+ "hideEdgesOnDrag": true,
165
+ "tooltipDelay": 100
133
166
  },
134
167
  "physics": {
135
- "stabilization": true,
168
+ "stabilization": {
169
+ "enabled": true,
170
+ "iterations": 200
171
+ },
136
172
  "barnesHut": {
137
- "gravitationalConstant": -8000,
138
- "springLength": 200,
139
- "springConstant": 0.02
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
- net.save_graph("brain_map.html")
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
- import webbrowser
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brain
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: An AI brain that lives inside your codebase
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: watchdog
@@ -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=gvXBUFFk3RYoNQC-mToo80ytR7VZr_t1TfFEiSStmvo,7652
5
- brain_parser/codebase_walker.py,sha256=6uU1yJ6leqmHcdlSm-xcq1DOG1mdv4F0-JupSMZrSwE,3582
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=fpQqY46oq4uqTzysPyhbUAaAwwJGV_yH5kQBW4xtuSs,5218
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.1.0.dist-info/METADATA,sha256=Eep4Qd7cPOarafSDhm6aysPe6lbhMk1fcPWJGqd3UVg,327
13
- codebase_brain-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
- codebase_brain-0.1.0.dist-info/entry_points.txt,sha256=O9yHP4zy_uoVPxmewGbdNjNksmxRYUv34MQJDRXXodc,41
15
- codebase_brain-0.1.0.dist-info/top_level.txt,sha256=RmSvznU63-vmm5zLSKAlYYsFzQ8X3RyPrB-m0q0uQfI,23
16
- codebase_brain-0.1.0.dist-info/RECORD,,
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,,