codebase-brain 0.1.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.
@@ -0,0 +1,101 @@
1
+ import os
2
+ import json
3
+ from brain_parser.universal_parser import parse_file
4
+
5
+ SKIP_FOLDERS = {
6
+ 'lib', '.idea', '__pycache__',
7
+ 'codebase_brain.egg-info', 'node_modules',
8
+ '.git', 'venv', '.env', 'tests', 'temp'
9
+ }
10
+ SKIP_FILES = {
11
+ 'brain.json', 'brain_map.html'
12
+ }
13
+ def should_skip(path):
14
+ parts = path.replace('\\', '/').split('/')
15
+ for part in parts:
16
+ if part in SKIP_FOLDERS:
17
+ return True
18
+ return False
19
+ def walk_codebase(root_path):
20
+ # load existing brain to check hashes
21
+ existing_brain = load_brain() or {}
22
+ brain = {}
23
+
24
+ for folder, subfolders, files in os.walk(root_path):
25
+ subfolders[:] = [s for s in subfolders if s not in SKIP_FOLDERS]
26
+
27
+ for file in files:
28
+ if file in SKIP_FILES:
29
+ continue
30
+
31
+ full_path = os.path.join(folder, file)
32
+
33
+ # read content to generate hash
34
+ try:
35
+ with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
36
+ content = f.read()
37
+ except:
38
+ continue
39
+
40
+ from brain_parser.universal_parser import get_file_hash
41
+ current_hash = get_file_hash(content)
42
+
43
+ # if file exists in brain and hash matches → skip re-parsing
44
+ if full_path in existing_brain:
45
+ if existing_brain[full_path].get('hash') == current_hash:
46
+ brain[full_path] = existing_brain[full_path]
47
+ continue
48
+
49
+ # file is new or changed → parse and summarize
50
+ result = parse_file(full_path)
51
+ if result is not None and result['language'] != 'unknown':
52
+ brain[full_path] = result
53
+
54
+ return brain
55
+
56
+ BRAIN_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'brain.json')
57
+
58
+ def save_brain(brain, output_path=BRAIN_PATH):
59
+ with open(output_path, "w") as f:
60
+ json.dump(brain, f, indent=4)
61
+ print(f"Brain saved to {output_path}")
62
+
63
+ def load_brain(input_path=BRAIN_PATH):
64
+ if not os.path.exists(input_path):
65
+ print("No brain found. Run walker first.")
66
+ return None
67
+ with open(input_path, "r") as f:
68
+ return json.load(f)
69
+
70
+
71
+ def summarize_high_risk_files(brain, risk):
72
+ from brain_parser.universal_parser import summarize_file
73
+
74
+ summarized = 0
75
+ for filepath, data in brain.items():
76
+ file_risk = risk.get(filepath, 'LOW')
77
+ content = data.get('content', '')
78
+
79
+ if file_risk == 'HIGH' and content:
80
+ print(f"Summarizing HIGH risk file: {filepath}")
81
+ data['summary'] = summarize_file(filepath, content, data.get('language', ''))
82
+ data.pop('content', None)
83
+ summarized += 1
84
+ else:
85
+ # low/medium risk - generate simple summary from structure
86
+ functions = [f['name'] for f in data.get('functions', [])]
87
+ classes = [c['name'] for c in data.get('classes', [])]
88
+ imports = [i['name'] for i in data.get('imports', [])]
89
+ data[
90
+ 'summary'] = f"File with {len(functions)} functions: {', '.join(functions[:5])}. Classes: {', '.join(classes[:3])}. Imports: {', '.join(imports[:5])}"
91
+ data.pop('content', None)
92
+
93
+ print(f"Summarized {summarized} HIGH risk files with Groq. Rest used structure only.")
94
+ return brain
95
+
96
+ if __name__ == "__main__":
97
+ brain = walk_codebase(".")
98
+ save_brain(brain)
99
+
100
+ loaded = load_brain()
101
+ print(f"Brain loaded with {len(loaded)} files")
@@ -0,0 +1,81 @@
1
+ import time
2
+ from watchdog.observers import Observer
3
+ from watchdog.events import FileSystemEventHandler
4
+ from brain_parser.universal_parser import parse_file
5
+ from brain_parser.codebase_walker import walk_codebase, save_brain, load_brain
6
+
7
+ SKIP_WATCH = {'.git', '.idea', '__pycache__', 'node_modules', 'lib'}
8
+
9
+ class BrainEventHandler(FileSystemEventHandler):
10
+ def __init__(self):
11
+ self.brain = load_brain() or {}
12
+
13
+
14
+ def should_skip(self,path):
15
+ # skip git, ide, temp files
16
+ if path.endswith('~'):
17
+ return True
18
+ for skip in SKIP_WATCH:
19
+ if skip in path.replace('\\', '/'):
20
+ return True
21
+ return False
22
+
23
+ def on_modified(self, event):
24
+ if event.is_directory:
25
+ return
26
+ if self.should_skip(event.src_path):
27
+ return
28
+ # skip brain output files
29
+
30
+ if event.src_path.endswith('brain.json') or event.src_path.endswith('brain_map.html'):
31
+ return
32
+ print(f"Modified: {event.src_path}")
33
+ result = parse_file(event.src_path)
34
+ if result and result['language'] != 'unknown':
35
+ self.brain[event.src_path] = result
36
+ save_brain(self.brain)
37
+
38
+ def on_created(self, event):
39
+ if self.should_skip(event.src_path):
40
+ return
41
+ if not event.is_directory:
42
+ print(f"New file: {event.src_path}")
43
+ result = parse_file(event.src_path)
44
+ if result and result['language'] != 'unknown':
45
+ self.brain[event.src_path] = result
46
+ save_brain(self.brain)
47
+
48
+ def on_deleted(self, event):
49
+ if self.should_skip(event.src_path):
50
+ return
51
+ if not event.is_directory:
52
+ print(f"Deleted: {event.src_path}")
53
+ if event.src_path in self.brain:
54
+ del self.brain[event.src_path]
55
+ save_brain(self.brain)
56
+
57
+
58
+ def start_watching(path="."):
59
+ brain = load_brain() or {}
60
+ if not brain:
61
+ print("No brain found. Building first...")
62
+ brain = walk_codebase(path)
63
+ save_brain(brain)
64
+
65
+ event_handler = BrainEventHandler()
66
+ observer = Observer()
67
+ observer.schedule(event_handler, path, recursive=True)
68
+ observer.start()
69
+ print(f"Brain is watching: {path}")
70
+
71
+ try:
72
+ while True:
73
+ time.sleep(1)
74
+ except KeyboardInterrupt:
75
+ observer.stop()
76
+ print("Brain stopped.")
77
+ observer.join()
78
+
79
+
80
+ if __name__ == "__main__":
81
+ start_watching(".")
@@ -0,0 +1,190 @@
1
+ import networkx as nx
2
+ import matplotlib.pyplot as plt
3
+ from brain_parser.codebase_walker import load_brain
4
+ from pyvis.network import Network
5
+
6
+
7
+ def build_graph(brain):
8
+ G = nx.DiGraph()
9
+
10
+ for filepath, data in brain.items():
11
+ G.add_node(filepath)
12
+
13
+ for imp in data.get('imports', []):
14
+ raw = imp.get('name', '')
15
+
16
+ # extract module from "from x import y" or "import x"
17
+ if raw.startswith('from '):
18
+ module = raw.split('from ')[1].split(' import')[0].strip()
19
+ elif raw.startswith('import '):
20
+ module = raw.replace('import ', '').split(' as ')[0].strip()
21
+ else:
22
+ continue
23
+
24
+ # convert module path to file path format
25
+ # tests.circular_test.file_b → tests/circular_test/file_b
26
+ module_as_path = module.replace('.', '/').lower()
27
+
28
+ for other_file in brain.keys():
29
+ other_normalized = other_file.replace('\\', '/').lower()
30
+ # match whole filename not partial
31
+ filename = other_normalized.split('/')[-1].replace('.py', '').replace('.js', '').replace('.java',
32
+ '').replace(
33
+ '.ts', '')
34
+ if module_as_path == filename or other_normalized.endswith(module_as_path + '.py'):
35
+ G.add_edge(filepath, other_file)
36
+
37
+ return G
38
+
39
+
40
+ def visualize_graph(G):
41
+ risk = calculate_risk(G)
42
+
43
+ color_map = {
44
+ "HIGH": "red",
45
+ "MEDIUM": "orange",
46
+ "LOW": "lightgreen"
47
+ }
48
+
49
+ node_colors = [color_map[risk[node]] for node in G.nodes()]
50
+
51
+ plt.figure(figsize=(10, 7))
52
+ pos = nx.spring_layout(G)
53
+
54
+ nx.draw(G, pos,
55
+ with_labels=True,
56
+ node_color=node_colors,
57
+ node_size=2000,
58
+ font_size=8,
59
+ arrows=True,
60
+ edge_color='gray',
61
+ font_weight='bold'
62
+ )
63
+
64
+ plt.title("Live Codebase Brain - Risk Map")
65
+ plt.savefig("brain_graph.png")
66
+ plt.show()
67
+
68
+
69
+ def calculate_risk(G):
70
+ risk = {}
71
+
72
+ for node in G.nodes():
73
+ in_degree = G.in_degree(node)
74
+
75
+ if in_degree >= 2:
76
+ risk[node] = "HIGH"
77
+ elif in_degree == 1:
78
+ risk[node] = "MEDIUM"
79
+ else:
80
+ risk[node] = "LOW"
81
+
82
+ return risk
83
+
84
+ def visualize_interactive(G):
85
+ risk = calculate_risk(G)\
86
+
87
+ color_map = {
88
+ "HIGH": "#ff4444",
89
+ "MEDIUM": "#ffa500",
90
+ "LOW": "#44cc44"
91
+ }
92
+
93
+ net = Network(
94
+ height="750px",
95
+ width="100%",
96
+ bgcolor="#1a1a2e",
97
+ font_color="white",
98
+ directed=True
99
+ )
100
+
101
+ for node in G.nodes():
102
+ risk_level = risk[node]
103
+ color = color_map[risk_level]
104
+ label = node.split("\\")[-1].split("/")[-1]
105
+
106
+ net.add_node(
107
+ node,
108
+ 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)}"
112
+ )
113
+
114
+ for edge in G.edges():
115
+ net.add_edge(edge[0], edge[1])
116
+
117
+ net.set_options("""
118
+ {
119
+ "nodes": {
120
+ "borderWidth": 2,
121
+ "shadow": true
122
+ },
123
+ "edges": {
124
+ "smooth": {
125
+ "type": "dynamic"
126
+ },
127
+ "shadow": true
128
+ },
129
+ "interaction": {
130
+ "hover": true,
131
+ "navigationButtons": true,
132
+ "hideEdgesOnDrag": true
133
+ },
134
+ "physics": {
135
+ "stabilization": true,
136
+ "barnesHut": {
137
+ "gravitationalConstant": -8000,
138
+ "springLength": 200,
139
+ "springConstant": 0.02
140
+ }
141
+ }
142
+ }
143
+ """)
144
+ net.save_graph("brain_map.html")
145
+ print(f"Nodes in graph: {len(G.nodes())}")
146
+ 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")
150
+
151
+
152
+ def get_impact(G, filepath):
153
+ import networkx as nx
154
+ import os
155
+
156
+ # Normalize incoming path
157
+ target_norm = os.path.abspath(filepath).replace('\\', '/')
158
+ target_rel = filepath.replace('\\', '/').lstrip('./')
159
+
160
+ # Find matching node — handles both absolute and relative keys
161
+ matched = None
162
+
163
+ for node in G.nodes():
164
+ node_norm = os.path.abspath(node).replace('\\', '/')
165
+ if node_norm == target_norm:
166
+ matched = node
167
+ break
168
+
169
+ if not matched:
170
+ return None
171
+
172
+ direct = list(G.predecessors(matched))
173
+ all_affected = nx.ancestors(G, matched)
174
+ indirect = [f for f in all_affected if f not in direct]
175
+
176
+ return {
177
+ 'target': matched,
178
+ 'direct': direct,
179
+ 'indirect': indirect,
180
+ 'total_affected': len(direct) + len(indirect)
181
+ }
182
+
183
+ if __name__ == "__main__":
184
+ brain = load_brain()
185
+ G = build_graph(brain)
186
+ visualize_interactive(G)
187
+
188
+ print("\nRisk Report:")
189
+ for node, level in calculate_risk(G).items():
190
+ print(f" {node.split(chr(92))[-1]}: {level}")
@@ -0,0 +1,75 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+
7
+ def call_llm(prompt, max_tokens=500):
8
+ provider = os.getenv("LLM_PROVIDER", "groq").lower()
9
+ api_key = os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY")
10
+ model = os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
11
+
12
+ if provider == "groq":
13
+ return _call_groq(api_key, model, prompt, max_tokens)
14
+ elif provider == "openai":
15
+ return _call_openai(api_key, model, prompt, max_tokens)
16
+ elif provider == "anthropic":
17
+ return _call_anthropic(api_key, model, prompt, max_tokens)
18
+ elif provider == "google":
19
+ return _call_google(api_key, model, prompt, max_tokens)
20
+ elif provider == "ollama":
21
+ return _call_ollama(model, prompt, max_tokens)
22
+ else:
23
+ return _call_groq(api_key, model, prompt, max_tokens)
24
+
25
+
26
+ def _call_groq(api_key, model, prompt, max_tokens):
27
+ from groq import Groq
28
+ client = Groq(api_key=api_key)
29
+ response = client.chat.completions.create(
30
+ model=model,
31
+ messages=[{"role": "user", "content": prompt}],
32
+ max_tokens=max_tokens,
33
+ temperature=0
34
+ )
35
+ return response.choices[0].message.content
36
+
37
+
38
+ def _call_openai(api_key, model, prompt, max_tokens):
39
+ from openai import OpenAI
40
+ client = OpenAI(api_key=api_key)
41
+ response = client.chat.completions.create(
42
+ model=model,
43
+ messages=[{"role": "user", "content": prompt}],
44
+ max_tokens=max_tokens,
45
+ temperature=0
46
+ )
47
+ return response.choices[0].message.content
48
+
49
+
50
+ def _call_anthropic(api_key, model, prompt, max_tokens):
51
+ import anthropic
52
+ client = anthropic.Anthropic(api_key=api_key)
53
+ response = client.messages.create(
54
+ model=model,
55
+ max_tokens=max_tokens,
56
+ messages=[{"role": "user", "content": prompt}]
57
+ )
58
+ return response.content[0].text
59
+
60
+
61
+ def _call_google(api_key, model, prompt, max_tokens):
62
+ import google.generativeai as genai
63
+ genai.configure(api_key=api_key)
64
+ gemini = genai.GenerativeModel(model)
65
+ response = gemini.generate_content(prompt)
66
+ return response.text
67
+
68
+
69
+ def _call_ollama(model, prompt, max_tokens):
70
+ import requests
71
+ response = requests.post(
72
+ "http://localhost:11434/api/generate",
73
+ json={"model": model, "prompt": prompt, "stream": False}
74
+ )
75
+ return response.json().get("response", "")
@@ -0,0 +1,127 @@
1
+ import json
2
+ import os
3
+ from brain_parser.codebase_walker import load_brain
4
+ from dotenv import load_dotenv
5
+
6
+
7
+ def get_client():
8
+ from brain_parser.llm_router import call_llm
9
+ # returns a wrapper so existing code doesn't break
10
+ load_dotenv()
11
+ model = os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
12
+ return None, model
13
+
14
+
15
+ def find_relevant_context(brain, question):
16
+ from brain_parser.graph_builder import build_graph, calculate_risk
17
+
18
+ # build graph and get risk scores
19
+ G = build_graph(brain)
20
+ risk = calculate_risk(G)
21
+
22
+ scores = {}
23
+ question_lower = question.lower()
24
+ question_words = set(question_lower.split())
25
+
26
+ for filepath, data in brain.items():
27
+ score = 0
28
+
29
+ filename = filepath.lower()
30
+ for word in question_words:
31
+ if word in filename:
32
+ score += 3
33
+
34
+ for func in data.get('functions', []):
35
+ if func['name'].lower() in question_lower:
36
+ score += 3
37
+
38
+ for cls in data.get('classes', []):
39
+ if cls['name'].lower() in question_lower:
40
+ score += 3
41
+
42
+ for imp in data.get('imports', []):
43
+ name = imp.get('name', '').lower()
44
+ if any(word in name for word in question_words):
45
+ score += 2
46
+
47
+ summary = data.get('summary', '').lower()
48
+ for word in question_words:
49
+ if word in summary:
50
+ score += 1
51
+
52
+ # boost score for HIGH risk files when question is about risk
53
+ if risk.get(filepath) == 'HIGH':
54
+ score += 2
55
+
56
+ scores[filepath] = score
57
+
58
+ sorted_files = sorted(scores.items(), key=lambda x: x[1], reverse=True)
59
+
60
+ # build context with risk scores included
61
+ top_5 = {}
62
+ for f, s in sorted_files[:5]:
63
+ if f in brain:
64
+ file_data = dict(brain[f])
65
+ file_data['risk'] = risk.get(f, 'LOW')
66
+ file_data['dependents'] = len(list(G.predecessors(f)))
67
+ file_data.pop('content', None) # strip raw content — too many tokens
68
+ top_5[f] = file_data
69
+
70
+ if not top_5:
71
+ for f, s in sorted_files[:5]:
72
+ if f in brain:
73
+ file_data = dict(brain[f])
74
+ file_data['risk'] = risk.get(f, 'LOW')
75
+ file_data['dependents'] = len(list(G.predecessors(f)))
76
+ file_data.pop('content', None)
77
+ top_5[f] = file_data
78
+
79
+ return top_5
80
+
81
+
82
+ def ask_brain(question):
83
+ from brain_parser.llm_router import call_llm
84
+
85
+ brain = load_brain()
86
+ if not brain:
87
+ return "Brain is empty. Run walker first."
88
+
89
+ context = find_relevant_context(brain, question)
90
+ if len(context) > 5:
91
+ context = dict(list(context.items())[:5])
92
+ context_str = json.dumps(context, indent=2)
93
+
94
+ prompt = f"""You are an intelligent codebase brain.
95
+ You have deep knowledge of this codebase structure.
96
+ Here is what you know:
97
+
98
+ {context_str}
99
+
100
+ Answer this question precisely and clearly:
101
+ {question}"""
102
+
103
+ return call_llm(prompt, max_tokens=500)
104
+
105
+ def calculate_risk(G):
106
+ risk = {}
107
+
108
+ for node in G.nodes():
109
+ in_degree = G.in_degree(node)
110
+
111
+ if in_degree >= 2:
112
+ risk[node] = "HIGH"
113
+ elif in_degree == 1:
114
+ risk[node] = "MEDIUM"
115
+ else:
116
+ risk[node] = "LOW"
117
+
118
+ return risk
119
+
120
+
121
+ if __name__ == "__main__":
122
+ while True:
123
+ question = input("\nAsk your brain: ")
124
+ if question == "exit":
125
+ break
126
+ answer = ask_brain(question)
127
+ print(f"\nBrain: {answer}")