codebase-brain 0.3.3__tar.gz → 0.3.5__tar.gz

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.
Files changed (26) hide show
  1. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/PKG-INFO +2 -2
  2. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/codebase_walker.py +1 -1
  3. codebase_brain-0.3.5/brain_parser/llm_router.py +144 -0
  4. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/PKG-INFO +2 -2
  5. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/setup.py +2 -2
  6. codebase_brain-0.3.3/brain_parser/llm_router.py +0 -96
  7. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/README.md +0 -0
  8. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_cli.py +0 -0
  9. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/__init__.py +0 -0
  10. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/ast_parser.py +0 -0
  11. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/bug_detector.py +0 -0
  12. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/file_watcher.py +0 -0
  13. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/graph_builder.py +0 -0
  14. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/query_engine.py +0 -0
  15. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/root_cause.py +0 -0
  16. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/brain_parser/universal_parser.py +0 -0
  17. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/SOURCES.txt +0 -0
  18. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/dependency_links.txt +0 -0
  19. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/entry_points.txt +0 -0
  20. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/requires.txt +0 -0
  21. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/codebase_brain.egg-info/top_level.txt +0 -0
  22. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/setup.cfg +0 -0
  23. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/tests/test_bugs.py +0 -0
  24. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/tests/test_classes.py +0 -0
  25. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/tests/test_parser.py +0 -0
  26. {codebase_brain-0.3.3 → codebase_brain-0.3.5}/tests/test_treesitter.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brain
3
- Version: 0.3.3
4
- Summary: An AI brain that lives inside your codebase
3
+ Version: 0.3.5
4
+ Summary: Codebase Brain:Know what you will break and why before you commit it.
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: watchdog
7
7
  Requires-Dist: networkx
@@ -103,4 +103,4 @@ if __name__ == "__main__":
103
103
  save_brain(brain)
104
104
 
105
105
  loaded = load_brain()
106
- print(f"Brain loaded with {len(loaded)} files")
106
+ print(f"Brain loaded with {len(loaded)} files")
@@ -0,0 +1,144 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+
5
+ def get_config():
6
+ """Load from local .env first, then global ~/.codebase-brain/config.env"""
7
+ local_env = os.path.join(os.getcwd(), '.env')
8
+ if os.path.exists(local_env):
9
+ load_dotenv(local_env, override=True)
10
+
11
+ has_key = os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY")
12
+
13
+ if not has_key:
14
+ global_config = os.path.expanduser("~/.codebase-brain/config.env")
15
+ if os.path.exists(global_config):
16
+ load_dotenv(global_config, override=True)
17
+
18
+ return {
19
+ 'provider': os.getenv("LLM_PROVIDER", "groq").lower(),
20
+ 'api_key': os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY"),
21
+ 'model': os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "openai/gpt-oss-120b")
22
+ }
23
+
24
+
25
+ def call_llm(prompt, max_tokens=500):
26
+ config = get_config()
27
+ provider = config['provider']
28
+ api_key = config['api_key']
29
+ model = config['model']
30
+
31
+ if provider == "groq":
32
+ return _call_groq(api_key, model, prompt, max_tokens)
33
+ elif provider == "openai":
34
+ return _call_openai(api_key, model, prompt, max_tokens)
35
+ elif provider == "anthropic":
36
+ return _call_anthropic(api_key, model, prompt, max_tokens)
37
+ elif provider == "google":
38
+ return _call_google(api_key, model, prompt, max_tokens)
39
+ elif provider == "ollama":
40
+ return _call_ollama(model, prompt, max_tokens)
41
+ else:
42
+ return _call_groq(api_key, model, prompt, max_tokens)
43
+
44
+
45
+ def _try_models(model, fallback_list, call_fn):
46
+ """Generic fallback runner — tries the configured model first, then known-stable alternatives."""
47
+ models_to_try = [model] + [m for m in fallback_list if m != model]
48
+ seen = set()
49
+ models_to_try = [m for m in models_to_try if not (m in seen or seen.add(m))]
50
+
51
+ last_error = None
52
+ for try_model in models_to_try:
53
+ try:
54
+ return call_fn(try_model)
55
+ except Exception as e:
56
+ last_error = e
57
+ continue
58
+
59
+ return f"Could not get AI response — all models unavailable: {str(last_error)}"
60
+
61
+
62
+ def _call_groq(api_key, model, prompt, max_tokens):
63
+ from groq import Groq
64
+ client = Groq(api_key=api_key)
65
+
66
+ fallback_list = ["openai/gpt-oss-120b", "llama-3.1-8b-instant", "qwen3.6-27b"]
67
+
68
+ def call_fn(m):
69
+ response = client.chat.completions.create(
70
+ model=m,
71
+ messages=[{"role": "user", "content": prompt}],
72
+ max_tokens=max_tokens,
73
+ temperature=0
74
+ )
75
+ return response.choices[0].message.content
76
+
77
+ return _try_models(model, fallback_list, call_fn)
78
+
79
+
80
+ def _call_openai(api_key, model, prompt, max_tokens):
81
+ from openai import OpenAI
82
+ client = OpenAI(api_key=api_key)
83
+
84
+ fallback_list = ["gpt-4o-mini", "gpt-4o", "gpt-3.5-turbo"]
85
+
86
+ def call_fn(m):
87
+ response = client.chat.completions.create(
88
+ model=m,
89
+ messages=[{"role": "user", "content": prompt}],
90
+ max_tokens=max_tokens,
91
+ temperature=0
92
+ )
93
+ return response.choices[0].message.content
94
+
95
+ return _try_models(model, fallback_list, call_fn)
96
+
97
+
98
+ def _call_anthropic(api_key, model, prompt, max_tokens):
99
+ import anthropic
100
+ client = anthropic.Anthropic(api_key=api_key)
101
+
102
+ fallback_list = ["claude-3-5-haiku-20241022", "claude-3-5-sonnet-20241022", "claude-3-haiku-20240307"]
103
+
104
+ def call_fn(m):
105
+ response = client.messages.create(
106
+ model=m,
107
+ max_tokens=max_tokens,
108
+ messages=[{"role": "user", "content": prompt}]
109
+ )
110
+ return response.content[0].text
111
+
112
+ return _try_models(model, fallback_list, call_fn)
113
+
114
+
115
+ def _call_google(api_key, model, prompt, max_tokens):
116
+ import google.generativeai as genai
117
+ genai.configure(api_key=api_key)
118
+
119
+ fallback_list = ["gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash"]
120
+
121
+ def call_fn(m):
122
+ gemini = genai.GenerativeModel(m)
123
+ response = gemini.generate_content(prompt)
124
+ return response.text
125
+
126
+ return _try_models(model, fallback_list, call_fn)
127
+
128
+
129
+ def _call_ollama(model, prompt, max_tokens):
130
+ import requests
131
+
132
+ fallback_list = ["llama3.2", "codellama", "deepseek-coder"]
133
+
134
+ def call_fn(m):
135
+ response = requests.post(
136
+ "http://localhost:11434/api/generate",
137
+ json={"model": m, "prompt": prompt, "stream": False}
138
+ )
139
+ result = response.json().get("response", "")
140
+ if not result:
141
+ raise ValueError(f"Empty response from Ollama model {m}")
142
+ return result
143
+
144
+ return _try_models(model, fallback_list, call_fn)
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brain
3
- Version: 0.3.3
4
- Summary: An AI brain that lives inside your codebase
3
+ Version: 0.3.5
4
+ Summary: Codebase Brain:Know what you will break and why before you commit it.
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: watchdog
7
7
  Requires-Dist: networkx
@@ -2,8 +2,8 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="codebase-brain",
5
- version="0.3.3",
6
- description="An AI brain that lives inside your codebase",
5
+ version="0.3.5",
6
+ description="Codebase Brain:Know what you will break and why before you commit it.",
7
7
  package_dir={"": "."},
8
8
  packages=find_packages(where="."),
9
9
  install_requires=[
@@ -1,96 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
-
5
- def get_config():
6
- """Load from local .env first, then global ~/.codebase-brain/config.env"""
7
- local_env = os.path.join(os.getcwd(), '.env')
8
- if os.path.exists(local_env):
9
- load_dotenv(local_env, override=True)
10
-
11
- # check if local .env actually had Brain's keys
12
- has_key = os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY")
13
-
14
- if not has_key:
15
- # local .env didn't have Brain's config — fall back to global
16
- global_config = os.path.expanduser("~/.codebase-brain/config.env")
17
- if os.path.exists(global_config):
18
- load_dotenv(global_config, override=True)
19
-
20
- return {
21
- 'provider': os.getenv("LLM_PROVIDER", "groq").lower(),
22
- 'api_key': os.getenv("LLM_API_KEY") or os.getenv("GROQ_API_KEY"),
23
- 'model': os.getenv("LLM_MODEL") or os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
24
- }
25
-
26
-
27
- def call_llm(prompt, max_tokens=500):
28
- config = get_config()
29
- provider = config['provider']
30
- api_key = config['api_key']
31
- model = config['model']
32
-
33
- if provider == "groq":
34
- return _call_groq(api_key, model, prompt, max_tokens)
35
- elif provider == "openai":
36
- return _call_openai(api_key, model, prompt, max_tokens)
37
- elif provider == "anthropic":
38
- return _call_anthropic(api_key, model, prompt, max_tokens)
39
- elif provider == "google":
40
- return _call_google(api_key, model, prompt, max_tokens)
41
- elif provider == "ollama":
42
- return _call_ollama(model, prompt, max_tokens)
43
- else:
44
- return _call_groq(api_key, model, prompt, max_tokens)
45
-
46
-
47
- def _call_groq(api_key, model, prompt, max_tokens):
48
- from groq import Groq
49
- client = Groq(api_key=api_key)
50
- response = client.chat.completions.create(
51
- model=model,
52
- messages=[{"role": "user", "content": prompt}],
53
- max_tokens=max_tokens,
54
- temperature=0
55
- )
56
- return response.choices[0].message.content
57
-
58
-
59
- def _call_openai(api_key, model, prompt, max_tokens):
60
- from openai import OpenAI
61
- client = OpenAI(api_key=api_key)
62
- response = client.chat.completions.create(
63
- model=model,
64
- messages=[{"role": "user", "content": prompt}],
65
- max_tokens=max_tokens,
66
- temperature=0
67
- )
68
- return response.choices[0].message.content
69
-
70
-
71
- def _call_anthropic(api_key, model, prompt, max_tokens):
72
- import anthropic
73
- client = anthropic.Anthropic(api_key=api_key)
74
- response = client.messages.create(
75
- model=model,
76
- max_tokens=max_tokens,
77
- messages=[{"role": "user", "content": prompt}]
78
- )
79
- return response.content[0].text
80
-
81
-
82
- def _call_google(api_key, model, prompt, max_tokens):
83
- import google.generativeai as genai
84
- genai.configure(api_key=api_key)
85
- gemini = genai.GenerativeModel(model)
86
- response = gemini.generate_content(prompt)
87
- return response.text
88
-
89
-
90
- def _call_ollama(model, prompt, max_tokens):
91
- import requests
92
- response = requests.post(
93
- "http://localhost:11434/api/generate",
94
- json={"model": model, "prompt": prompt, "stream": False}
95
- )
96
- return response.json().get("response", "")
File without changes
File without changes