Repository navigation
๐ DevOS AI V2: CLI AI Codebase Analyzer #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from llm.provider import ask_llm | ||
|
|
||
|
|
||
| def debug_error(file_path, model): | ||
| try: | ||
| with open(file_path, "r") as f: | ||
| error = f.read().strip() | ||
| except FileNotFoundError: | ||
| return f"โ File not found: {file_path}" | ||
|
|
||
| if not error: | ||
| return "โ Error file is empty" | ||
|
|
||
| prompt = f""" | ||
| You are a senior software engineer. | ||
|
|
||
| Debug this error: | ||
|
|
||
| {error} | ||
|
|
||
| Explain clearly: | ||
|
|
||
| 1. Root cause | ||
| 2. Why it happens | ||
| 3. Step-by-step fix | ||
| 4. Example fix | ||
| """ | ||
|
|
||
| result = ask_llm(prompt, model) | ||
|
|
||
| if not result: | ||
| return "โ No response from LLM" | ||
|
|
||
| return result |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| from core.parser import get_code_files | ||
| from llm.provider import ask_llm | ||
|
|
||
|
|
||
| def search_code(repo_path, query, model): | ||
| files = get_code_files(repo_path) | ||
|
|
||
| matches = [] | ||
|
|
||
| for file in files: | ||
| try: | ||
| with open(file, "r", encoding="utf-8") as f: | ||
| lines = f.readlines() | ||
|
|
||
| for i, line in enumerate(lines): | ||
| if query.lower() in line.lower(): | ||
| snippet = "".join(lines[max(0, i-2):i+3]) | ||
|
|
||
| matches.append(f""" | ||
| FILE: {file} | ||
| LINE: {i+1} | ||
| CODE: | ||
| {snippet} | ||
| """) | ||
|
|
||
| if len(matches) >= 5: | ||
| break | ||
|
|
||
| except: | ||
| continue | ||
|
|
||
| if not matches: | ||
| return "โ No matches found" | ||
|
|
||
| context = "\n\n".join(matches) | ||
|
|
||
| prompt = f""" | ||
| You are a senior software engineer analyzing search results from a codebase. | ||
|
|
||
| User query: "{query}" | ||
|
|
||
| Code snippets: | ||
| {context} | ||
|
|
||
| IMPORTANT: | ||
| - Do NOT describe prompts or text | ||
| - Focus on actual code logic | ||
| - Ignore placeholder strings | ||
|
|
||
| Give a professional answer: | ||
|
|
||
| 1. Relevant functionality found | ||
| - What real functionality matches the query | ||
|
|
||
| 2. Where it exists | ||
| - File names and modules | ||
|
|
||
| 3. How it works | ||
| - Explain logic of code (not text) | ||
|
|
||
| 4. Summary | ||
| - What developer should understand from this | ||
|
|
||
| Be precise, technical, and useful. | ||
| """ | ||
|
|
||
| result = ask_llm(prompt, model) | ||
|
|
||
| if not result or "โ" in result: | ||
| return "โ No meaningful response from LLM for search" | ||
|
|
||
| return result | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import sys | ||
| from core.engine import build_context, build_prompt | ||
| from llm.provider import ask_llm | ||
| from agents.debug_agent import debug_error | ||
| from agents.search_agent import search_code | ||
|
|
||
|
|
||
| def explain(path, model): | ||
| context = build_context(path) | ||
| prompt = build_prompt(context, "Explain this codebase") | ||
| return ask_llm(prompt, model) | ||
|
|
||
|
|
||
| def main(): | ||
| if len(sys.argv) < 3: | ||
| print(""" | ||
| Usage: | ||
| explain <path> [--model openrouter|google|auto] | ||
| search <path> "<query>" [--model openrouter|google|auto] | ||
| debug <file> [--model openrouter|google|auto] | ||
| """) | ||
| return | ||
|
|
||
| command = sys.argv[1] | ||
| model = "auto" | ||
|
|
||
| if "--model" in sys.argv: | ||
| idx = sys.argv.index("--model") | ||
| if idx + 1 < len(sys.argv): | ||
| model = sys.argv[idx + 1] | ||
|
|
||
| print("๐ Processing...\n") | ||
|
|
||
| if command == "explain": | ||
| result = explain(sys.argv[2], model) | ||
|
|
||
| elif command == "search": | ||
| result = search_code(sys.argv[2], sys.argv[3], model) | ||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Argument parsing for This relies on the query always being at |
||
|
|
||
| elif command == "debug": | ||
| result = debug_error(sys.argv[2], model) | ||
|
|
||
| else: | ||
| result = "โ Unknown command" | ||
|
|
||
| print("๐ก Result:\n") | ||
| print(result) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| from core.parser import get_code_files, read_files | ||
|
|
||
| PRIORITY = ["main", "app", "engine", "cli", "api"] | ||
|
|
||
|
|
||
| def rank_files(files): | ||
| scored = [] | ||
|
|
||
| for f in files: | ||
| score = 0 | ||
| name = f.lower() | ||
|
|
||
| for p in PRIORITY: | ||
| if p in name: | ||
| score += 5 | ||
|
|
||
| if "core" in name or "llm" in name: | ||
| score += 3 | ||
|
|
||
| scored.append((score, f)) | ||
|
|
||
| scored.sort(reverse=True) | ||
| return [f for _, f in scored] | ||
|
|
||
|
|
||
| def build_context(repo_path): | ||
| files = get_code_files(repo_path) | ||
| ranked = rank_files(files) | ||
|
|
||
| top_files = ranked[:5] | ||
| return read_files(top_files) | ||
|
|
||
|
|
||
| def build_prompt(context, question): | ||
| return f""" | ||
| You are a senior software engineer analyzing a real codebase. | ||
|
|
||
| STRICT RULES: | ||
| - Use ONLY the given code context | ||
| - Do NOT assume missing features | ||
| - Do NOT mention technologies not present in context | ||
|
|
||
| CONTEXT: | ||
| {context} | ||
|
|
||
| TASK: | ||
| {question} | ||
|
|
||
| Respond in this format: | ||
|
|
||
| 1. Purpose | ||
| - What this system actually does (based only on code) | ||
|
|
||
| 2. Architecture | ||
| - Real structure (modules, flow) | ||
|
|
||
| 3. Key Components | ||
| - Explain actual files/modules found | ||
|
|
||
| 4. Execution Flow | ||
| - Step-by-step how system works | ||
|
|
||
| Keep answer precise, technical, and grounded in code. | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import os | ||
|
|
||
| IGNORE_DIRS = { | ||
| ".venv", "__pycache__", ".git", "node_modules", | ||
| "dist", "build", | ||
| "apps", | ||
| "frontend", | ||
| "infrastructure" | ||
| } | ||
|
|
||
| EXTENSIONS = (".py", ".js", ".ts", ".tsx") | ||
|
|
||
|
|
||
| def get_code_files(repo_path): | ||
| code_files = [] | ||
|
|
||
| for root, dirs, files in os.walk(repo_path): | ||
| dirs[:] = [d for d in dirs if d not in IGNORE_DIRS] | ||
|
|
||
| for file in files: | ||
| if file.endswith(EXTENSIONS): | ||
| code_files.append(os.path.join(root, file)) | ||
|
|
||
| return code_files | ||
|
|
||
|
|
||
| def read_files(files, max_chars=800): | ||
| contents = [] | ||
|
|
||
| for file in files: | ||
| try: | ||
| with open(file, "r", encoding="utf-8") as f: | ||
| text = f.read()[:max_chars] | ||
| contents.append(f"\n# FILE: {file}\n{text}") | ||
| except: | ||
| continue | ||
|
|
||
| return "\n\n".join(contents) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import os | ||
| import requests | ||
| from dotenv import load_dotenv | ||
|
|
||
| load_dotenv() | ||
|
|
||
| OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY") | ||
| GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") | ||
|
|
||
|
|
||
| def ask_openrouter(prompt): | ||
| if not OPENROUTER_API_KEY: | ||
| return None | ||
|
|
||
| try: | ||
| res = requests.post( | ||
| "https://openrouter.ai/api/v1/chat/completions", | ||
| headers={ | ||
| "Authorization": f"Bearer {OPENROUTER_API_KEY}", | ||
| "Content-Type": "application/json" | ||
| }, | ||
| json={ | ||
| "model": "openai/gpt-3.5-turbo", | ||
| "messages": [{"role": "user", "content": prompt}] | ||
| }, | ||
| timeout=20 | ||
| ) | ||
|
|
||
| data = res.json() | ||
|
|
||
| if "choices" in data: | ||
| return data["choices"][0]["message"]["content"] | ||
|
|
||
| return None | ||
|
|
||
| except: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Catch more specific exceptions and handle HTTP/JSON errors explicitly. A bare Suggested implementation: except (requests.RequestException, ValueError) as exc:
# Network/HTTP or JSON parsing error: skip this iteration but keep
# programmer errors (TypeError, KeyError, etc.) visible.
continue
|
||
| return None | ||
|
|
||
|
|
||
| def ask_google(prompt): | ||
| if not GOOGLE_API_KEY: | ||
| return None | ||
|
|
||
| try: | ||
| res = requests.post( | ||
| f"https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent?key={GOOGLE_API_KEY}", | ||
| headers={"Content-Type": "application/json"}, | ||
| json={ | ||
| "contents": [{"parts": [{"text": prompt}]}] | ||
| }, | ||
| timeout=30 | ||
| ) | ||
|
|
||
| data = res.json() | ||
|
|
||
| if "candidates" in data: | ||
| return data["candidates"][0]["content"]["parts"][0]["text"] | ||
|
|
||
| return None | ||
|
|
||
| except: | ||
| return None | ||
|
|
||
|
|
||
| def ask_llm(prompt, model="auto"): | ||
|
|
||
| def clean(res): | ||
| if not res: | ||
| return None | ||
| res = str(res).strip() | ||
| return res if len(res) > 10 else None # IMPORTANT | ||
|
|
||
| # -------- OPENROUTER -------- | ||
| if model == "openrouter": | ||
| res = clean(ask_openrouter(prompt)) | ||
| return res if res else "โ OpenRouter returned empty response" | ||
|
|
||
| # -------- GOOGLE -------- | ||
| if model == "google": | ||
| res = clean(ask_google(prompt)) | ||
| return res if res else "โ Google returned empty response" | ||
|
|
||
| # -------- AUTO MODE -------- | ||
| if model == "auto": | ||
| res = clean(ask_openrouter(prompt)) | ||
| if res: | ||
| return res | ||
|
|
||
| res = clean(ask_google(prompt)) | ||
| if res: | ||
| return res | ||
|
|
||
| return "โ All LLM providers failed or returned empty" | ||
|
|
||
| return "โ Invalid model" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Bare
excepthides all read errors when scanning files.Catching every exception here will hide real read issues (e.g., permission, corruption, encoding) and make failures hard to diagnose. Instead, catch specific exceptions like
OSError/IOErrorandUnicodeDecodeError, and log or record skipped files so these errors remain visible.Suggested implementation:
loggingis imported at the top ofagents/search_agent.py:import logging