-
-
Notifications
You must be signed in to change notification settings - Fork 145
feat: Proactive contributor assistance with lightweight pattern detection #283
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
base: main
Are you sure you want to change the base?
Changes from all commits
1e57ac9
3709a9d
00d2679
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,63 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import httpx | ||||||||||||||||||||||||||||||||||||||||||
| from typing import List, Dict | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| GITHUB_API_BASE = "https://api.github.com" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| class IssueSuggestionService: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| def __init__(self, token: str): | ||||||||||||||||||||||||||||||||||||||||||
| self.token = token | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async def fetch_global_beginner_issues( | ||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||
| user_query: str, | ||||||||||||||||||||||||||||||||||||||||||
| limit: int = 5 | ||||||||||||||||||||||||||||||||||||||||||
| ) -> List[Dict]: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| headers = { | ||||||||||||||||||||||||||||||||||||||||||
| "Authorization": f"Bearer {self.token}", | ||||||||||||||||||||||||||||||||||||||||||
| "Accept": "application/vnd.github+json" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Base GitHub search query | ||||||||||||||||||||||||||||||||||||||||||
| search_query = 'label:"good first issue" is:issue state:open' | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| query_lower = user_query.lower() | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Language filter | ||||||||||||||||||||||||||||||||||||||||||
| if "python" in query_lower: | ||||||||||||||||||||||||||||||||||||||||||
| search_query += " language:python" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Org filter | ||||||||||||||||||||||||||||||||||||||||||
| if "django" in query_lower: | ||||||||||||||||||||||||||||||||||||||||||
| search_query += " org:django" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| url = f"{GITHUB_API_BASE}/search/issues?q={search_query}&per_page={limit}" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| print("π GitHub Search Query:", search_query) | ||||||||||||||||||||||||||||||||||||||||||
| print("π GitHub URL:", url) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async with httpx.AsyncClient() as client: | ||||||||||||||||||||||||||||||||||||||||||
| response = await client.get(url, headers=headers) | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+36
to
+42
Contributor
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. π§© Analysis chainπ Script executed: cat -n backend/app/agents/devrel/github/services/issue_suggestion_service.py | head -70Repository: AOSSIE-Org/Devr.AI Length of output: 2196 π Script executed: # Check if there are any other similar files that might show patterns
find . -name "*issue_suggestion_service*" -type fRepository: AOSSIE-Org/Devr.AI Length of output: 187 π Web query:
π‘ Result: In
Recommendedimport httpx
async with httpx.AsyncClient() as client:
r = await client.get(
"https://api.example.com/search",
params={"q": "How HTTP works!", "email": "user@example.com"},
)If you use an f-string/manual URLYou must encode yourself (and avoid double-encoding): from urllib.parse import urlencode
q = urlencode({"q": "How HTTP works!", "email": "user@example.com"})
url = f"https://api.example.com/search?{q}"If an API requires
|
||||||||||||||||||||||||||||||||||||||||||
| url = f"{GITHUB_API_BASE}/search/issues?q={search_query}&per_page={limit}" | |
| print("π GitHub Search Query:", search_query) | |
| print("π GitHub URL:", url) | |
| async with httpx.AsyncClient() as client: | |
| response = await client.get(url, headers=headers) | |
| async with httpx.AsyncClient() as client: | |
| response = await client.get( | |
| f"{GITHUB_API_BASE}/search/issues", | |
| headers=headers, | |
| params={"q": search_query, "per_page": limit}, | |
| ) |
π€ Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/app/agents/devrel/github/services/issue_suggestion_service.py` around
lines 36 - 42, The code builds the GitHub search URL by interpolating
search_query into an f-string (see the url variable and the client.get call in
issue_suggestion_service.py) which sends unencoded spaces/quotes; change the
request to call client.get(GITHUB_API_BASE + "/search/issues", params={"q":
search_query, "per_page": limit}, headers=headers) so httpx handles URL
encoding, remove the manual url f-string, and delete the debug print()
statements present around the search_query/url and response logging.
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.
Direct key access will raise KeyError on partial or unexpected GitHub API responses
All four field accesses on the item dict ("repository_url", "number", "title", "html_url") will raise a KeyError if a response item omits any field, silently aborting the entire result set instead of skipping the malformed entry.
π§ Proposed fix β use `.get()` and skip incomplete entries
for item in data.get("items", []):
- results.append({
- "repo": item["repository_url"].split("/")[-1],
- "number": item["number"],
- "title": item["title"],
- "url": item["html_url"]
- })
+ repo_url = item.get("repository_url", "")
+ number = item.get("number")
+ title = item.get("title")
+ url = item.get("html_url")
+ if not all([repo_url, number, title, url]):
+ continue
+ results.append({
+ "repo": repo_url.split("/")[-1],
+ "number": number,
+ "title": title,
+ "url": url,
+ })π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for item in data.get("items", []): | |
| results.append({ | |
| "repo": item["repository_url"].split("/")[-1], | |
| "number": item["number"], | |
| "title": item["title"], | |
| "url": item["html_url"] | |
| }) | |
| for item in data.get("items", []): | |
| repo_url = item.get("repository_url", "") | |
| number = item.get("number") | |
| title = item.get("title") | |
| url = item.get("html_url") | |
| if not all([repo_url, number, title, url]): | |
| continue | |
| results.append({ | |
| "repo": repo_url.split("/")[-1], | |
| "number": number, | |
| "title": title, | |
| "url": url, | |
| }) |
π€ Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/app/agents/devrel/github/services/issue_suggestion_service.py` around
lines 53 - 59, The loop that builds results from data.get("items", []) uses
direct key access on item["repository_url"], item["number"], item["title"], and
item["html_url"], which can raise KeyError on partial GitHub API responses;
update the loop in issue_suggestion_service.py (where results is appended) to
safely read fields via item.get(...) and validate that required fields
(repository_url, number, title, html_url) are present before appending, skip
(and optionally log) any incomplete entries, and extract the repo name from
repository_url defensively (e.g., handle None or empty string before splitting).
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,18 @@ | ||
| from .v1.github import router as github_router | ||
| from fastapi import APIRouter | ||
| from .v1.auth import router as auth_router | ||
| from .v1.health import router as health_router | ||
| from .v1.integrations import router as integrations_router | ||
|
|
||
| api_router = APIRouter() | ||
|
|
||
| api_router.include_router( | ||
| github_router, | ||
| prefix="/v1/github", | ||
| tags=["GitHub"] | ||
| ) | ||
|
Comment on lines
+1
to
+13
Contributor
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. π§© Analysis chainπ Script executed: #!/bin/bash
# Check if files exist
echo "=== Checking file existence ==="
ls -la backend/app/api/v1/github.py 2>&1 || echo "File not found"
ls -la backend/routes.py 2>&1 || echo "File not found"
echo ""
echo "=== Routes in backend/app/api/v1/github.py ==="
if [ -f backend/app/api/v1/github.py ]; then
rg -n 'beginner|@router|def ' backend/app/api/v1/github.py | head -50
fi
echo ""
echo "=== Routes in backend/routes.py ==="
if [ -f backend/routes.py ]; then
rg -n 'beginner|@router|def ' backend/routes.py | head -50
fiRepository: AOSSIE-Org/Devr.AI Length of output: 904 π Script executed: #!/bin/bash
echo "=== Full content of backend/app/api/v1/github.py ==="
cat -n backend/app/api/v1/github.py
echo ""
echo "=== Full content of backend/routes.py ==="
cat -n backend/routes.pyRepository: AOSSIE-Org/Devr.AI Length of output: 6730 Duplicate beginner-issues endpoints across two routers. This wires π€ Prompt for AI Agents |
||
|
|
||
|
|
||
| api_router.include_router( | ||
| auth_router, | ||
| prefix="/v1/auth", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from fastapi import APIRouter, HTTPException | ||
| from services.github.issue_suggestion_service import IssueSuggestionService | ||
|
Contributor
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. π§© Analysis chainπ Script executed: #!/bin/bash
# Search for fetch_beginner_issues definition
echo "=== Searching for fetch_beginner_issues definition ==="
rg -n "def fetch_beginner_issues" --type py
echo ""
echo "=== Methods on service classes (issue_suggestion_service) ==="
rg -n "def fetch" --type py -g "*issue_suggestion_service*" -A 2
echo ""
echo "=== Locating issue_suggestion_service.py ==="
fd "issue_suggestion_service.py"
echo ""
echo "=== Checking github.py lines 15-25 for actual method call ==="
fd "github.py" -g "*/api/v1/*" -x cat -n {} | sed -n '15,25p'Repository: AOSSIE-Org/Devr.AI Length of output: 1087 π Script executed: #!/bin/bash
# Find the github.py file in api/v1
find . -path "*/api/v1/github.py" -type f
echo ""
echo "=== Reading backend/app/api/v1/github.py ==="
cat -n backend/app/api/v1/github.py | head -30Repository: AOSSIE-Org/Devr.AI Length of output: 1084 π Script executed: #!/bin/bash
cat -n backend/services/github/issue_suggestion_service.pyRepository: AOSSIE-Org/Devr.AI Length of output: 1619
Line 19 calls π€ Prompt for AI Agents |
||
| from config import GITHUB_TOKEN, GITHUB_ORG | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| issue_service = IssueSuggestionService(GITHUB_TOKEN) | ||
|
|
||
|
|
||
| @router.get("/beginner-issues") | ||
| async def get_beginner_issues(repo: str): | ||
| if not GITHUB_TOKEN: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="GitHub token not configured" | ||
| ) | ||
|
Comment on lines
+7
to
+16
Contributor
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. Service instantiated with a potentially Line 7 creates π‘οΈ Suggested fix β lazy initialisation with fail-fast guard-issue_service = IssueSuggestionService(GITHUB_TOKEN)
-
`@router.get`("/beginner-issues")
async def get_beginner_issues(repo: str):
if not GITHUB_TOKEN:
raise HTTPException(
status_code=500,
detail="GitHub token not configured"
)
+
+ issue_service = IssueSuggestionService(GITHUB_TOKEN)
try:π€ Prompt for AI Agents |
||
|
|
||
| try: | ||
| issues = await issue_service.fetch_beginner_issues( | ||
| owner=GITHUB_ORG, | ||
| repo=repo | ||
| ) | ||
|
|
||
| return { | ||
| "repo": repo, | ||
| "count": len(issues), | ||
| "issues": issues | ||
| } | ||
|
|
||
| except Exception as e: | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail=f"Failed to fetch issues: {str(e)}" | ||
| ) | ||
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.
π§© Analysis chain
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 191
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 919
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 2239
π Script executed:
rg "_extract_search_query" -A 10 backend/app/agents/devrel/github/tools/general_github_help.pyRepository: AOSSIE-Org/Devr.AI
Length of output: 1027
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 44
π Script executed:
rg "_extract_search_query" -B 5 -A 15 backend/app/agents/devrel/nodes/handlers/web_search.pyRepository: AOSSIE-Org/Devr.AI
Length of output: 1628
π Script executed:
Repository: AOSSIE-Org/Devr.AI
Length of output: 198
handle_general_github_help(query, None)will crash βllmcannot beNone.The fallback path at line 153 passes
Noneas thellmargument. The function immediately calls_extract_search_query(query, llm), which invokesawait llm.ainvoke(...). This will raiseAttributeError: 'NoneType' object has no attribute 'ainvoke'. Every unclassified query will hit this path and crash.Either initialize an LLM instance in
GitHubToolkitto pass here, or provide a non-LLM fallback for thegeneral_github_helppath.π€ Prompt for AI Agents