mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 06:12:50 -07:00
179 lines
7.2 KiB
Python
179 lines
7.2 KiB
Python
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
import os
|
|
import concurrent.futures
|
|
import subprocess
|
|
import sys
|
|
|
|
API_KEY = "REDACTED_API_KEY"
|
|
MODEL = "gemini-3-flash-preview"
|
|
URL = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent?key={API_KEY}"
|
|
|
|
BUGS_FILE = 'data/bugs.json'
|
|
|
|
with open(BUGS_FILE, 'r') as f:
|
|
bugs = json.load(f)
|
|
|
|
tools = [
|
|
{
|
|
"functionDeclarations": [
|
|
{
|
|
"name": "search_codebase",
|
|
"description": "Search the gemini-cli packages directory for a string using grep. Returns matching lines and file paths.",
|
|
"parameters": {
|
|
"type": "OBJECT",
|
|
"properties": {
|
|
"pattern": {"type": "STRING", "description": "The text pattern to search for"}
|
|
},
|
|
"required": ["pattern"]
|
|
}
|
|
},
|
|
{
|
|
"name": "read_file",
|
|
"description": "Read a specific file to understand its context.",
|
|
"parameters": {
|
|
"type": "OBJECT",
|
|
"properties": {
|
|
"filepath": {"type": "STRING", "description": "The path to the file"}
|
|
},
|
|
"required": ["filepath"]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
|
|
def call_gemini(messages):
|
|
data = {
|
|
"contents": messages,
|
|
"tools": tools,
|
|
"generationConfig": {"temperature": 0.1}
|
|
}
|
|
req = urllib.request.Request(URL, data=json.dumps(data).encode('utf-8'), headers={'Content-Type': 'application/json'})
|
|
with urllib.request.urlopen(req) as response:
|
|
return json.loads(response.read().decode('utf-8'))
|
|
|
|
def execute_tool(call):
|
|
name = call['name']
|
|
args = call.get('args', {})
|
|
print(f" [TOOL CALL] {name}({args})", flush=True)
|
|
|
|
if name == 'search_codebase':
|
|
pattern = args.get('pattern', '')
|
|
pattern = pattern.replace('"', '\\"')
|
|
try:
|
|
cmd = f'grep -rn "{pattern}" ../../packages | grep -vE "node_modules|dist|build" | head -n 30'
|
|
res = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.STDOUT)
|
|
return res if res else "No matches found."
|
|
except subprocess.CalledProcessError as e:
|
|
return e.output if e.output else "No matches found."
|
|
elif name == 'read_file':
|
|
filepath = args.get('filepath', '')
|
|
if not filepath.startswith('/'):
|
|
filepath = os.path.join('../../packages', filepath)
|
|
try:
|
|
if not os.path.exists(filepath):
|
|
return f"File {filepath} not found."
|
|
cmd = f'head -n 200 "{filepath}"'
|
|
res = subprocess.check_output(cmd, shell=True, text=True, stderr=subprocess.STDOUT)
|
|
return res
|
|
except Exception as e:
|
|
return str(e)
|
|
return "Unknown tool"
|
|
|
|
def analyze_issue(issue):
|
|
system_instruction = """You are a senior software engineer analyzing bug reports for the gemini-cli codebase.
|
|
You MUST use the provided tools to investigate the codebase and pinpoint exactly which files and logic are responsible for the bug.
|
|
DO NOT GUESS. You should explore the packages directory to find the relevant code.
|
|
|
|
Rating Effort Level:
|
|
- small (1 day): Bug is easy to reproduce, the cause is clear, and the fix is localized to 1-2 files.
|
|
- medium (2-3 days): Bug is hard to reproduce (specific platform/setup), requires significant investigation, or touches multiple components.
|
|
- large (>3 days): Requires architectural changes, deep refactoring, or affects core protocols.
|
|
|
|
CRITICAL REPRODUCTION RULE:
|
|
If a bug is hard to reproduce (e.g. needs specific OS like Windows/WSL2, complex external service setup, or is described as intermittent/rare), it MUST NOT be rated as small.
|
|
|
|
Output format (ONLY valid JSON, no markdown):
|
|
{
|
|
"analysis": "technical analysis of root cause and fix",
|
|
"effort_level": "small|medium|large",
|
|
"reasoning": "justification with specific files/logic you found using the tools",
|
|
"recommended_implementation": "code snippets or specific logic changes (only if small)"
|
|
}
|
|
"""
|
|
|
|
prompt = f"{system_instruction}\n\nBug Title: {issue.get('title')}\nBug Body: {issue.get('body', '')[:1000]}"
|
|
|
|
messages = [{"role": "user", "parts": [{"text": prompt}]}]
|
|
|
|
for turn in range(8):
|
|
try:
|
|
res = call_gemini(messages)
|
|
candidate = res['candidates'][0]['content']
|
|
parts = candidate.get('parts', [])
|
|
|
|
if 'role' not in candidate:
|
|
candidate['role'] = 'model'
|
|
messages.append(candidate)
|
|
|
|
function_calls = [p for p in parts if 'functionCall' in p]
|
|
|
|
if function_calls:
|
|
tool_responses = []
|
|
for fcall in function_calls:
|
|
call_data = fcall['functionCall']
|
|
result = execute_tool(call_data)
|
|
tool_responses.append({
|
|
"functionResponse": {
|
|
"name": call_data['name'],
|
|
"response": {"result": result[:5000]}
|
|
}
|
|
})
|
|
messages.append({"role": "user", "parts": tool_responses})
|
|
else:
|
|
text = parts[0].get('text', '')
|
|
if not text:
|
|
continue
|
|
if '```json' in text:
|
|
text = text.split('```json')[1].split('```')[0]
|
|
elif '```' in text:
|
|
text = text.split('```')[1].split('```')[0]
|
|
|
|
return json.loads(text.strip())
|
|
except Exception as e:
|
|
# print(f"Error on turn {turn}: {e}")
|
|
break
|
|
|
|
return {"analysis": "Failed to analyze autonomously", "effort_level": "medium", "reasoning": "Agent loop exceeded turn limit or errored."}
|
|
|
|
def process_issue(issue):
|
|
# Only skip if we have a real analysis and it's not "Failed..."
|
|
if 'analysis' in issue and issue['analysis'] and issue['analysis'] != "Failed to analyze autonomously":
|
|
return issue
|
|
print(f"Analyzing Bug #{issue['number']}...", flush=True)
|
|
result = analyze_issue(issue)
|
|
issue['analysis'] = result.get('analysis', '')
|
|
issue['effort_level'] = result.get('effort_level', 'medium')
|
|
issue['reasoning'] = result.get('reasoning', '')
|
|
if 'recommended_implementation' in result:
|
|
issue['recommended_implementation'] = result['recommended_implementation']
|
|
print(f"Completed Bug #{issue['number']} -> {issue['effort_level']}", flush=True)
|
|
return issue
|
|
|
|
def main():
|
|
print(f"Starting agentic analysis for {len(bugs)} bugs...", flush=True)
|
|
|
|
# Using small concurrency to avoid rate limits and keep logs readable
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
futures = {executor.submit(process_issue, issue): issue for issue in bugs[5:]}
|
|
for future in concurrent.futures.as_completed(futures):
|
|
with open(BUGS_FILE, 'w') as f:
|
|
json.dump(bugs, f, indent=2)
|
|
|
|
print("Agentic analysis complete. `bugs.json` is updated.", flush=True)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|