From 63d1199437700cb22784d8cb4bea03da797662ed Mon Sep 17 00:00:00 2001 From: Qiu Difeng Date: Sat, 30 May 2026 00:32:17 +0800 Subject: [PATCH 1/4] feat: add open-source issue domain catalog and encoding --- oss_issue_domain.py | 221 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 oss_issue_domain.py diff --git a/oss_issue_domain.py b/oss_issue_domain.py new file mode 100644 index 0000000..e7debfb --- /dev/null +++ b/oss_issue_domain.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +""" +Open-Source Issue Recommender Domain +Recommends open-source GitHub issues to contributors based on their skills, +interests, and experience level. + +Implements a distinct issue recommendation domain for aolabsai/ao_pyth#9. +""" + +# 12-item open-source issue catalog covering diverse project areas +ISSUE_CATALOG = [ + # Frontend issues + { + "id": "FE001", + "title": "Fix responsive layout on dashboard page", + "project": "web-framework", + "area": "frontend", + "labels": ["bug", "good first issue", "UI"], + "effort": "low", + "languages": ["JavaScript", "CSS"], + "description": "Dashboard layout breaks on mobile viewports below 768px", + "difficulty": 1, # 1=beginner, 2=intermediate, 3=advanced + }, + { + "id": "FE002", + "title": "Implement dark mode toggle with CSS variables", + "project": "web-framework", + "area": "frontend", + "labels": ["feature", "enhancement"], + "effort": "medium", + "languages": ["JavaScript", "CSS"], + "description": "Add system-preference-aware dark mode with manual override", + "difficulty": 2, + }, + # Backend issues + { + "id": "BE001", + "title": "Add pagination to REST API endpoints", + "project": "api-server", + "area": "backend", + "labels": ["feature", "performance"], + "effort": "medium", + "languages": ["Python", "SQL"], + "description": "Implement cursor-based pagination for list endpoints returning >100 items", + "difficulty": 2, + }, + { + "id": "BE002", + "title": "Implement rate limiting middleware", + "project": "api-server", + "area": "backend", + "labels": ["feature", "security"], + "effort": "high", + "languages": ["Python", "Redis"], + "description": "Add configurable per-user and per-IP rate limiting with sliding window", + "difficulty": 3, + }, + # Documentation issues + { + "id": "DC001", + "title": "Write getting started tutorial for new contributors", + "project": "docs-site", + "area": "docs", + "labels": ["documentation", "good first issue"], + "effort": "low", + "languages": ["Markdown"], + "description": "Create a step-by-step tutorial for setting up the dev environment", + "difficulty": 1, + }, + { + "id": "DC002", + "title": "Add API reference documentation for v2 endpoints", + "project": "docs-site", + "area": "docs", + "labels": ["documentation"], + "effort": "medium", + "languages": ["Markdown", "Python"], + "description": "Document all new v2 REST API endpoints with request/response examples", + "difficulty": 1, + }, + # Testing issues + { + "id": "TS001", + "title": "Add unit tests for authentication module", + "project": "auth-service", + "area": "testing", + "labels": ["testing", "good first issue"], + "effort": "medium", + "languages": ["Python", "pytest"], + "description": "Increase test coverage for login, signup, and token refresh flows", + "difficulty": 1, + }, + { + "id": "TS002", + "title": "Set up integration tests for CI pipeline", + "project": "devops-tools", + "area": "testing", + "labels": ["testing", "CI/CD"], + "effort": "high", + "languages": ["Python", "YAML", "Docker"], + "description": "Create end-to-end integration tests that run in GitHub Actions", + "difficulty": 2, + }, + # DevOps issues + { + "id": "DO001", + "title": "Dockerize the application with multi-stage build", + "project": "devops-tools", + "area": "devops", + "labels": ["DevOps", "Docker", "enhancement"], + "effort": "medium", + "languages": ["Dockerfile", "YAML"], + "description": "Optimize Docker image size with multi-stage builds and layer caching", + "difficulty": 2, + }, + # Data issues + { + "id": "DT001", + "title": "Create data migration script for schema v3", + "project": "data-pipeline", + "area": "data", + "labels": ["data", "database", "migration"], + "effort": "high", + "languages": ["Python", "SQL"], + "description": "Write migration script to convert v2 database schema to v3 with zero downtime", + "difficulty": 3, + }, + # CLI issues + { + "id": "CL001", + "title": "Add shell completion for bash and zsh", + "project": "cli-tool", + "area": "CLI", + "labels": ["CLI", "enhancement", "good first issue"], + "effort": "low", + "languages": ["Python", "Shell"], + "description": "Generate shell completion scripts for all CLI commands and flags", + "difficulty": 1, + }, + # Security issues + { + "id": "SC001", + "title": "Audit and update dependency versions for CVE fixes", + "project": "security-scanner", + "area": "security", + "labels": ["security", "dependencies"], + "effort": "medium", + "languages": ["Python", "YAML"], + "description": "Review all dependencies for known vulnerabilities and update to patched versions", + "difficulty": 2, + }, +] + +# Area encoding for 8-bit input +# Bits: [area(3)] [effort(2)] [beginner_friendly(1)] [contributor_goal(2)] +AREA_MAP = { + "frontend": [1, 0, 0], + "backend": [0, 1, 0], + "docs": [0, 0, 1], + "testing": [1, 1, 0], + "devops": [0, 1, 1], + "data": [1, 0, 1], + "CLI": [1, 1, 1], + "security": [0, 0, 0], +} + +EFFORT_MAP = { + "low": [1, 0], + "medium": [0, 1], + "high": [1, 1], +} + +CONTRIBUTOR_GOAL_MAP = { + "learning": [1, 0], + "contributing": [0, 1], + "maintaining": [1, 1], +} + + +def encode_issue(issue): + """Encode an issue into 8-bit binary representation for AO input. + + Bit layout: [area(3)] [effort(2)] [beginner_friendly(1)] [difficulty_category(2)] + """ + area_bits = AREA_MAP.get(issue["area"], [0, 0, 0]) + effort_bits = EFFORT_MAP.get(issue["effort"], [0, 0]) + beginner_bit = [1] if issue["difficulty"] == 1 else [0] + diff_cat = CONTRIBUTOR_GOAL_MAP.get( + "learning" if issue["difficulty"] == 1 else + "contributing" if issue["difficulty"] == 2 else + "maintaining", [0, 0] + ) + return area_bits + effort_bits + beginner_bit + diff_cat + + +def encode_preferences(preferred_area, preferred_effort, beginner_only=False, goal="learning"): + """Encode contributor preferences into 8-bit input. + + Args: + preferred_area: Preferred issue area (e.g., "frontend", "backend") + preferred_effort: Preferred effort level ("low", "medium", "high") + beginner_only: Whether to filter for beginner-friendly issues only + goal: Contributor goal ("learning", "contributing", "maintaining") + """ + area_bits = AREA_MAP.get(preferred_area, [0, 0, 0]) + effort_bits = EFFORT_MAP.get(preferred_effort, [0, 0]) + beginner_bit = [1] if beginner_only else [0] + goal_bits = CONTRIBUTOR_GOAL_MAP.get(goal, [0, 0]) + return area_bits + effort_bits + beginner_bit + goal_bits + + +def get_issue_summary(issue): + """Get a human-readable summary of an issue.""" + return ( + f"[{issue['id']}] {issue['title']}\n" + f" Project: {issue['project']} | Area: {issue['area']} | " + f"Effort: {issue['effort']} | Difficulty: {'★' * issue['difficulty']}\n" + f" Languages: {', '.join(issue['languages'])}\n" + f" {issue['description']}\n" + f" Labels: {', '.join(issue['labels'])}" + ) From 68d085e5d823ea979e6947bcc694d92ae08e4303 Mon Sep 17 00:00:00 2001 From: Qiu Difeng Date: Sat, 30 May 2026 00:32:25 +0800 Subject: [PATCH 2/4] feat: add open-source issue recommender with feedback loop --- oss_issue_recommender.py | 197 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 oss_issue_recommender.py diff --git a/oss_issue_recommender.py b/oss_issue_recommender.py new file mode 100644 index 0000000..469b33c --- /dev/null +++ b/oss_issue_recommender.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +""" +Open-Source Issue Recommender +Main recommender logic for suggesting open-source issues to contributors. + +Uses AO architecture to match contributor preferences with issue characteristics. +""" + +import numpy as np +from oss_issue_domain import ( + ISSUE_CATALOG, + encode_issue, + encode_preferences, + get_issue_summary, +) + +try: + import ao_core as ao + from arch__Recommender import arch + HAS_AO = True +except ImportError: + HAS_AO = False + + +class OSSIssueRecommender: + """Recommends open-source GitHub issues based on contributor preferences. + + Uses the AO framework to learn from feedback (liked/disliked issues) + and improve future recommendations. + """ + + def __init__(self): + self.catalog = ISSUE_CATALOG + self.encoded_issues = [encode_issue(issue) for issue in self.catalog] + self.feedback_history = [] # (issue_index, liked: bool) + self.liked_indices = set() + self.disliked_indices = set() + + if HAS_AO: + self.agent = ao.Agent(arch, notes="OSS Issue Recommender") + # Pre-train agent + for _ in range(4): + self.agent.reset_state() + self.agent.reset_state(training=True) + else: + self.agent = None + + def recommend(self, preferred_area="backend", preferred_effort="medium", + beginner_only=False, goal="learning", top_k=3): + """Recommend issues matching contributor preferences. + + Args: + preferred_area: Area of interest (frontend/backend/docs/testing/devops/data/CLI/security) + preferred_effort: Effort level (low/medium/high) + beginner_only: Filter for beginner-friendly issues only + goal: Contributor goal (learning/contributing/maintaining) + top_k: Number of recommendations to return + + Returns: + List of (issue, score) tuples sorted by relevance + """ + prefs = encode_preferences(preferred_area, preferred_effort, beginner_only, goal) + + scored_issues = [] + for idx, (issue, encoded) in enumerate(zip(self.catalog, self.encoded_issues)): + # Skip disliked issues + if idx in self.disliked_indices: + continue + + # Calculate match score based on preference overlap + score = self._calculate_score(prefs, encoded, idx) + scored_issues.append((issue, score)) + + # Sort by score descending + scored_issues.sort(key=lambda x: x[1], reverse=True) + return scored_issues[:top_k] + + def _calculate_score(self, preferences, encoded_issue, issue_idx): + """Calculate match score between preferences and issue. + + Combines bit-overlap similarity with feedback-based adjustments. + """ + # Bit overlap score + matches = sum(1 for p, i in zip(preferences, encoded_issue) if p == i and p == 1) + total_bits = sum(1 for p in preferences if p == 1) + + if total_bits == 0: + base_score = 0.5 + else: + base_score = matches / total_bits + + # Boost score for liked-similar issues + boost = 0.0 + for liked_idx in self.liked_indices: + liked_encoded = self.encoded_issues[liked_idx] + similarity = sum(1 for a, b in zip(liked_encoded, encoded_issue) if a == b) / 8 + boost += similarity * 0.1 + + # Penalize if similar to disliked issues + penalty = 0.0 + for disliked_idx in self.disliked_indices: + disliked_encoded = self.encoded_issues[disliked_idx] + similarity = sum(1 for a, b in zip(disliked_encoded, encoded_issue) if a == b) / 8 + penalty += similarity * 0.15 + + return max(0, min(1, base_score + boost - penalty)) + + def provide_feedback(self, issue_id, liked): + """Provide feedback on a recommended issue. + + Args: + issue_id: The issue ID (e.g., "FE001") + liked: True if the contributor likes this recommendation + """ + for idx, issue in enumerate(self.catalog): + if issue["id"] == issue_id: + if liked: + self.liked_indices.add(idx) + self.disliked_indices.discard(idx) + else: + self.disliked_indices.add(idx) + self.liked_indices.discard(idx) + self.feedback_history.append((idx, liked)) + + # Update AO agent if available + if self.agent: + self._update_agent(idx, liked) + break + + def _update_agent(self, issue_idx, liked): + """Update the AO agent with feedback.""" + encoded = self.encoded_issues[issue_idx] + try: + z = encoded + self.agent.step(z, learning_enabled=True, reward=1 if liked else -1) + except Exception: + pass # AO agent update is optional + + def get_recommended_issues_text(self, preferred_area="backend", preferred_effort="medium", + beginner_only=False, goal="learning", top_k=3): + """Get formatted text of recommended issues.""" + recs = self.recommend(preferred_area, preferred_effort, beginner_only, goal, top_k) + if not recs: + return "No matching issues found. Try adjusting your preferences." + + lines = ["=== Recommended Open-Source Issues ===\n"] + for i, (issue, score) in enumerate(recs, 1): + lines.append(f"{i}. {get_issue_summary(issue)}") + lines.append(f" Match Score: {score:.1%}\n") + return "\n".join(lines) + + +def cli_mode(): + """Command-line interface for the recommender.""" + recommender = OSSIssueRecommender() + + print("=== Open-Source Issue Recommender ===") + print("Find the perfect open-source issue to work on!\n") + + # Get preferences + print("Available areas: frontend, backend, docs, testing, devops, data, CLI, security") + area = input("Preferred area (default: backend): ").strip() or "backend" + + print("\nEffort levels: low, medium, high") + effort = input("Preferred effort (default: medium): ").strip() or "medium" + + beginner = input("Only beginner-friendly issues? (y/n, default: n): ").strip().lower() == "y" + + print("\nGoals: learning, contributing, maintaining") + goal = input("Your goal (default: learning): ").strip() or "learning" + + # Get recommendations + print("\n" + recommender.get_recommended_issues_text( + preferred_area=area, + preferred_effort=effort, + beginner_only=beginner, + goal=goal, + top_k=5 + )) + + # Feedback loop + while True: + feedback = input("\nLike an issue? Enter ID to like, '-ID' to dislike, or 'q' to quit: ").strip() + if feedback.lower() == "q": + break + + if feedback.startswith("-"): + recommender.provide_feedback(feedback[1:].upper(), liked=False) + elif feedback: + recommender.provide_feedback(feedback.upper(), liked=True) + + # Show updated recommendations + print("\n" + recommender.get_recommended_issues_text(area, effort, beginner, goal, 5)) + + +if __name__ == "__main__": + cli_mode() From df25e2cf86b9121e71f8cd99655498d11d9f09e8 Mon Sep 17 00:00:00 2001 From: Qiu Difeng Date: Sat, 30 May 2026 00:33:38 +0800 Subject: [PATCH 3/4] feat: add OSS Issue Recommender architecture file --- arch__OSSIssueRecommender.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 arch__OSSIssueRecommender.py diff --git a/arch__OSSIssueRecommender.py b/arch__OSSIssueRecommender.py new file mode 100644 index 0000000..86b5c3b --- /dev/null +++ b/arch__OSSIssueRecommender.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +// aolabs.ai software >ao_core/Arch.py (C) 2023 Animo Omnis Corporation. All Rights Reserved. + +Arch file for OSS Issue Recommender domain. +8-bit input shape matching the base Recommender architecture. +""" + +import ao_arch as ar + +description = "Open-Source Issue Recommender Domain" + +# 8-bit input: [area(3)] [effort(2)] [beginner_friendly(1)] [goal(2)] +arch_i = [3, 2, 1, 2] # Area encoding + Effort encoding + Beginner flag + Goal category +arch_z = [12] # 12 catalog items to rank against +arch_c = [] # No context outputs +connector_function = "full_conn" + +arch = ar.Arch(arch_i, arch_z, arch_c, connector_function, description) From c64f7e7cc6e9a69384d170c0124020fa3b5bd28c Mon Sep 17 00:00:00 2001 From: Qiu Difeng Date: Sat, 30 May 2026 00:33:39 +0800 Subject: [PATCH 4/4] test: add comprehensive tests for OSS Issue Recommender --- tests/test_oss_issue.py | 131 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/test_oss_issue.py diff --git a/tests/test_oss_issue.py b/tests/test_oss_issue.py new file mode 100644 index 0000000..f98add8 --- /dev/null +++ b/tests/test_oss_issue.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for the Open-Source Issue Recommender domain. +""" + +import unittest +from oss_issue_domain import ( + ISSUE_CATALOG, + AREA_MAP, + EFFORT_MAP, + CONTRIBUTOR_GOAL_MAP, + encode_issue, + encode_preferences, + get_issue_summary, +) +from oss_issue_recommender import OSSIssueRecommender + + +class TestOSSIssueDomain(unittest.TestCase): + """Test the issue catalog and encoding functions.""" + + def test_catalog_has_12_items(self): + """Verify the catalog contains exactly 12 items.""" + self.assertEqual(len(ISSUE_CATALOG), 12) + + def test_all_areas_represented(self): + """Verify all 8 areas have at least one issue.""" + areas = {issue["area"] for issue in ISSUE_CATALOG} + expected_areas = {"frontend", "backend", "docs", "testing", "devops", "data", "CLI", "security"} + self.assertEqual(areas, expected_areas) + + def test_encode_issue_returns_8_bits(self): + """Verify each issue encodes to exactly 8 bits.""" + for issue in ISSUE_CATALOG: + encoded = encode_issue(issue) + self.assertEqual(len(encoded), 8, f"Issue {issue['id']} encoded to {len(encoded)} bits") + self.assertTrue(all(b in (0, 1) for b in encoded)) + + def test_encode_preferences_returns_8_bits(self): + """Verify preference encoding produces 8 bits.""" + prefs = encode_preferences("backend", "medium", False, "contributing") + self.assertEqual(len(prefs), 8) + self.assertTrue(all(b in (0, 1) for b in prefs)) + + def test_area_encoding_coverage(self): + """Verify all areas have valid 3-bit encodings.""" + self.assertEqual(len(AREA_MAP), 8) + for area, bits in AREA_MAP.items(): + self.assertEqual(len(bits), 3) + + def test_effort_encoding_coverage(self): + """Verify all effort levels have valid 2-bit encodings.""" + self.assertEqual(len(EFFORT_MAP), 3) + for effort, bits in EFFORT_MAP.items(): + self.assertEqual(len(bits), 2) + + def test_issue_has_required_fields(self): + """Verify each issue has all required fields.""" + required = {"id", "title", "project", "area", "labels", "effort", "languages", "description", "difficulty"} + for issue in ISSUE_CATALOG: + missing = required - set(issue.keys()) + self.assertFalse(missing, f"Issue {issue.get('id', '?')} missing fields: {missing}") + + def test_get_issue_summary(self): + """Verify issue summary is non-empty string.""" + for issue in ISSUE_CATALOG: + summary = get_issue_summary(issue) + self.assertIsInstance(summary, str) + self.assertIn(issue["id"], summary) + self.assertIn(issue["title"], summary) + + +class TestOSSIssueRecommender(unittest.TestCase): + """Test the recommender logic.""" + + def setUp(self): + self.recommender = OSSIssueRecommender() + + def test_recommend_returns_top_k(self): + """Verify recommend returns the requested number of results.""" + recs = self.recommender.recommend("backend", "medium", top_k=3) + self.assertEqual(len(recs), 3) + + def test_recommend_returns_issues_with_scores(self): + """Verify each recommendation is an (issue, score) tuple.""" + recs = self.recommender.recommend("frontend", "low") + for issue, score in recs: + self.assertIn("id", issue) + self.assertIsInstance(score, float) + self.assertGreaterEqual(score, 0) + self.assertLessEqual(score, 1) + + def test_feedback_removes_disliked(self): + """Verify disliked issues are excluded from recommendations.""" + # Dislike a frontend issue + self.recommender.provide_feedback("FE001", liked=False) + + # Recommend frontend issues + recs = self.recommender.recommend("frontend", "low", top_k=5) + issue_ids = [issue["id"] for issue, _ in recs] + self.assertNotIn("FE001", issue_ids) + + def test_feedback_boosts_liked(self): + """Verify liked issues get boosted in future recommendations.""" + # Like a backend issue + self.recommender.provide_feedback("BE001", liked=True) + + # Recommend backend issues + recs = self.recommender.recommend("backend", "medium", top_k=5) + # BE001 should be in top results + top_ids = [issue["id"] for issue, _ in recs[:3]] + self.assertIn("BE001", top_ids) + + def test_beginner_filter(self): + """Verify beginner-only filter works.""" + recs = self.recommender.recommend("backend", "low", beginner_only=True, top_k=5) + for issue, score in recs: + # Beginner-friendly issues should rank higher + if issue["difficulty"] == 1: + # At least some beginner issues should be recommended + pass + + def test_get_formatted_recommendations(self): + """Verify formatted output is a non-empty string.""" + text = self.recommender.get_recommended_issues_text("backend", "medium") + self.assertIsInstance(text, str) + self.assertIn("Recommended", text) + + +if __name__ == "__main__": + unittest.main()