From 33a0f36d4af358d3f5935804c0a85b82743c96f3 Mon Sep 17 00:00:00 2001 From: Sanju_Sarad Date: Sat, 8 Aug 2026 09:55:51 +0530 Subject: [PATCH] Submit Saradwanth project to Mutagent Hackathon --- submissions/Saradwanth/Dockerfile.backend | 9 + submissions/Saradwanth/Dockerfile.frontend | 9 + submissions/Saradwanth/README.md | 28 + submissions/Saradwanth/agentspec.yaml | 17 + submissions/Saradwanth/backend/.env.example | 41 + submissions/Saradwanth/backend/README.md | 114 + .../Saradwanth/backend/b2b/__init__.py | 13 + .../Saradwanth/backend/b2b/governance.py | 74 + submissions/Saradwanth/backend/b2b/roster.py | 29 + submissions/Saradwanth/backend/b2b/store.py | 322 ++ .../Saradwanth/backend/clients/__init__.py | 1 + .../backend/clients/github_client.py | 70 + .../Saradwanth/backend/clients/llm_client.py | 115 + submissions/Saradwanth/backend/config.py | 87 + submissions/Saradwanth/backend/count_files.py | 45 + .../Saradwanth/backend/debug_github.py | 18 + submissions/Saradwanth/backend/embeddings.py | 61 + .../Saradwanth/backend/github_client.py | 124 + .../Saradwanth/backend/graph/__init__.py | 10 + .../Saradwanth/backend/graph/blast_radius.py | 208 + .../Saradwanth/backend/graph/extract.py | 481 +++ .../Saradwanth/backend/graph/query_dsl.py | 306 ++ .../backend/graph/reviewer_routing.py | 140 + submissions/Saradwanth/backend/graph/store.py | 203 + .../backend/graph/test_selection.py | 121 + .../Saradwanth/backend/hybrid_retriever.py | 130 + submissions/Saradwanth/backend/indexing.py | 90 + .../backend/issue_recommendation.py | 63 + .../Saradwanth/backend/learning_roadmap.py | 55 + submissions/Saradwanth/backend/main.py | 472 +++ .../Saradwanth/backend/maintainer_health.py | 107 + .../Saradwanth/backend/mutagent/__init__.py | 0 .../backend/mutagent/datasets/.gitkeep | 0 .../backend/mutagent/datasets/council.json | 146 + .../mutagent/datasets/graph_query.json | 1405 +++++++ .../mutagent/datasets/issue_health.json | 200 + .../backend/mutagent/datasets/issue_rec.json | 266 ++ .../backend/mutagent/datasets/pr_check.json | 209 + .../backend/mutagent/datasets/router.json | 140 + .../backend/mutagent/gen_graph_dataset.py | 163 + .../graph_fixtures/build_sample_graph.py | 120 + .../Saradwanth/backend/mutagent/harness.py | 399 ++ .../backend/mutagent/prompts/.gitkeep | 0 .../backend/mutagent/prompts/council.txt | 21 + .../backend/mutagent/prompts/graph_query.txt | 10 + .../backend/mutagent/prompts/hyde.txt | 6 + .../backend/mutagent/prompts/issue_health.txt | 11 + .../backend/mutagent/prompts/issue_rec.txt | 11 + .../backend/mutagent/prompts/pr_check.txt | 9 + .../backend/mutagent/prompts/roadmap.txt | 11 + .../backend/mutagent/reports/.gitkeep | 0 .../mutagent/reports/council.delta.json | 45 + .../mutagent/reports/issue_rec.delta.json | 30 + .../backend/mutagent/rubrics/council.json | 42 + .../backend/mutagent/rubrics/graph_query.json | 15 + .../mutagent/rubrics/issue_health.json | 26 + .../backend/mutagent/rubrics/issue_rec.json | 26 + .../backend/mutagent/rubrics/pr_check.json | 26 + .../backend/mutagent/rubrics/router.json | 34 + .../Saradwanth/backend/mutagent/run.py | 253 ++ .../backend/mutagent/run_graph_query.py | 162 + .../backend/mutagent/traces/.gitkeep | 0 .../backend/mutagent/traces/council.jsonl | 218 + .../backend/mutagent/traces/graph_query.jsonl | 3 + .../mutagent/traces/issue_health.jsonl | 72 + .../backend/mutagent/traces/issue_rec.jsonl | 177 + .../backend/mutagent/traces/pr_check.jsonl | 3 + .../backend/mutagent/traces/roadmap.jsonl | 7 + .../backend/observability/__init__.py | 1 + .../backend/observability/metrics.py | 69 + .../backend/observability/tracer.py | 34 + .../Saradwanth/backend/pipeline/__init__.py | 1 + .../Saradwanth/backend/pipeline/gate.py | 73 + .../Saradwanth/backend/pr_readiness.py | 80 + submissions/Saradwanth/backend/rag_qa.py | 206 + .../Saradwanth/backend/requirements.txt | 13 + submissions/Saradwanth/backend/reranker.py | 49 + submissions/Saradwanth/backend/run_stages.py | 33 + .../backend/test_architecture_flow.py | 64 + submissions/Saradwanth/backend/test_css.py | 24 + submissions/Saradwanth/backend/test_graph.py | 33 + submissions/Saradwanth/backend/test_html.py | 39 + submissions/Saradwanth/backend/test_ts.py | 43 + .../Saradwanth/backend/vector_store.py | 62 + submissions/Saradwanth/docker-compose.yml | 22 + submissions/Saradwanth/package.json | 88 + submissions/Saradwanth/run_mutagent.bat | 15 + submissions/Saradwanth/scripts/ship.py | 74 + .../src/components/ui/accordion.tsx | 51 + .../src/components/ui/alert-dialog.tsx | 115 + .../Saradwanth/src/components/ui/alert.tsx | 49 + .../src/components/ui/aspect-ratio.tsx | 5 + .../Saradwanth/src/components/ui/avatar.tsx | 47 + .../Saradwanth/src/components/ui/badge.tsx | 32 + .../src/components/ui/breadcrumb.tsx | 101 + .../Saradwanth/src/components/ui/button.tsx | 49 + .../Saradwanth/src/components/ui/calendar.tsx | 177 + .../Saradwanth/src/components/ui/card.tsx | 55 + .../Saradwanth/src/components/ui/carousel.tsx | 240 ++ .../Saradwanth/src/components/ui/chart.tsx | 331 ++ .../Saradwanth/src/components/ui/checkbox.tsx | 26 + .../src/components/ui/collapsible.tsx | 11 + .../Saradwanth/src/components/ui/command.tsx | 143 + .../src/components/ui/context-menu.tsx | 187 + .../Saradwanth/src/components/ui/dialog.tsx | 104 + .../Saradwanth/src/components/ui/drawer.tsx | 98 + .../src/components/ui/dropdown-menu.tsx | 188 + .../Saradwanth/src/components/ui/form.tsx | 171 + .../src/components/ui/hover-card.tsx | 27 + .../src/components/ui/input-otp.tsx | 69 + .../Saradwanth/src/components/ui/input.tsx | 22 + .../Saradwanth/src/components/ui/label.tsx | 21 + .../Saradwanth/src/components/ui/menubar.tsx | 229 ++ .../src/components/ui/navigation-menu.tsx | 120 + .../src/components/ui/pagination.tsx | 98 + .../Saradwanth/src/components/ui/popover.tsx | 31 + .../Saradwanth/src/components/ui/progress.tsx | 25 + .../src/components/ui/radio-group.tsx | 36 + .../src/components/ui/resizable.tsx | 37 + .../src/components/ui/scroll-area.tsx | 44 + .../Saradwanth/src/components/ui/select.tsx | 152 + .../src/components/ui/separator.tsx | 24 + .../Saradwanth/src/components/ui/sheet.tsx | 122 + .../Saradwanth/src/components/ui/sidebar.tsx | 744 ++++ .../Saradwanth/src/components/ui/skeleton.tsx | 7 + .../Saradwanth/src/components/ui/slider.tsx | 23 + .../Saradwanth/src/components/ui/sonner.tsx | 23 + .../Saradwanth/src/components/ui/switch.tsx | 27 + .../Saradwanth/src/components/ui/table.tsx | 94 + .../Saradwanth/src/components/ui/tabs.tsx | 53 + .../Saradwanth/src/components/ui/textarea.tsx | 21 + .../src/components/ui/toggle-group.tsx | 57 + .../Saradwanth/src/components/ui/toggle.tsx | 42 + .../Saradwanth/src/components/ui/tooltip.tsx | 32 + .../Saradwanth/src/hooks/use-mobile.tsx | 19 + .../Saradwanth/src/lib/error-capture.ts | 27 + submissions/Saradwanth/src/lib/error-page.ts | 30 + .../src/lib/lovable-error-reporting.ts | 57 + submissions/Saradwanth/src/lib/utils.ts | 6 + submissions/Saradwanth/src/routeTree.gen.ts | 69 + submissions/Saradwanth/src/router.tsx | 16 + submissions/Saradwanth/src/routes/README.md | 21 + submissions/Saradwanth/src/routes/__root.tsx | 130 + submissions/Saradwanth/src/routes/index.tsx | 2397 +++++++++++ submissions/Saradwanth/src/server.ts | 61 + submissions/Saradwanth/src/start.ts | 22 + submissions/Saradwanth/src/styles.css | 109 + submissions/Saradwanth/traces/council.jsonl | 218 + .../Saradwanth/traces/graph_query.jsonl | 3 + .../Saradwanth/traces/issue_health.jsonl | 72 + submissions/Saradwanth/traces/issue_rec.jsonl | 177 + submissions/Saradwanth/traces/pr_check.jsonl | 3 + submissions/Saradwanth/traces/roadmap.jsonl | 7 + .../transcripts/antigravity_session.jsonl | 3661 +++++++++++++++++ 154 files changed, 20317 insertions(+) create mode 100644 submissions/Saradwanth/Dockerfile.backend create mode 100644 submissions/Saradwanth/Dockerfile.frontend create mode 100644 submissions/Saradwanth/README.md create mode 100644 submissions/Saradwanth/agentspec.yaml create mode 100644 submissions/Saradwanth/backend/.env.example create mode 100644 submissions/Saradwanth/backend/README.md create mode 100644 submissions/Saradwanth/backend/b2b/__init__.py create mode 100644 submissions/Saradwanth/backend/b2b/governance.py create mode 100644 submissions/Saradwanth/backend/b2b/roster.py create mode 100644 submissions/Saradwanth/backend/b2b/store.py create mode 100644 submissions/Saradwanth/backend/clients/__init__.py create mode 100644 submissions/Saradwanth/backend/clients/github_client.py create mode 100644 submissions/Saradwanth/backend/clients/llm_client.py create mode 100644 submissions/Saradwanth/backend/config.py create mode 100644 submissions/Saradwanth/backend/count_files.py create mode 100644 submissions/Saradwanth/backend/debug_github.py create mode 100644 submissions/Saradwanth/backend/embeddings.py create mode 100644 submissions/Saradwanth/backend/github_client.py create mode 100644 submissions/Saradwanth/backend/graph/__init__.py create mode 100644 submissions/Saradwanth/backend/graph/blast_radius.py create mode 100644 submissions/Saradwanth/backend/graph/extract.py create mode 100644 submissions/Saradwanth/backend/graph/query_dsl.py create mode 100644 submissions/Saradwanth/backend/graph/reviewer_routing.py create mode 100644 submissions/Saradwanth/backend/graph/store.py create mode 100644 submissions/Saradwanth/backend/graph/test_selection.py create mode 100644 submissions/Saradwanth/backend/hybrid_retriever.py create mode 100644 submissions/Saradwanth/backend/indexing.py create mode 100644 submissions/Saradwanth/backend/issue_recommendation.py create mode 100644 submissions/Saradwanth/backend/learning_roadmap.py create mode 100644 submissions/Saradwanth/backend/main.py create mode 100644 submissions/Saradwanth/backend/maintainer_health.py create mode 100644 submissions/Saradwanth/backend/mutagent/__init__.py create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/.gitkeep create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/council.json create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/graph_query.json create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/issue_health.json create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/issue_rec.json create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/pr_check.json create mode 100644 submissions/Saradwanth/backend/mutagent/datasets/router.json create mode 100644 submissions/Saradwanth/backend/mutagent/gen_graph_dataset.py create mode 100644 submissions/Saradwanth/backend/mutagent/graph_fixtures/build_sample_graph.py create mode 100644 submissions/Saradwanth/backend/mutagent/harness.py create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/.gitkeep create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/council.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/graph_query.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/hyde.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/issue_health.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/issue_rec.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/pr_check.txt create mode 100644 submissions/Saradwanth/backend/mutagent/prompts/roadmap.txt create mode 100644 submissions/Saradwanth/backend/mutagent/reports/.gitkeep create mode 100644 submissions/Saradwanth/backend/mutagent/reports/council.delta.json create mode 100644 submissions/Saradwanth/backend/mutagent/reports/issue_rec.delta.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/council.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/graph_query.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/issue_health.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/issue_rec.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/pr_check.json create mode 100644 submissions/Saradwanth/backend/mutagent/rubrics/router.json create mode 100644 submissions/Saradwanth/backend/mutagent/run.py create mode 100644 submissions/Saradwanth/backend/mutagent/run_graph_query.py create mode 100644 submissions/Saradwanth/backend/mutagent/traces/.gitkeep create mode 100644 submissions/Saradwanth/backend/mutagent/traces/council.jsonl create mode 100644 submissions/Saradwanth/backend/mutagent/traces/graph_query.jsonl create mode 100644 submissions/Saradwanth/backend/mutagent/traces/issue_health.jsonl create mode 100644 submissions/Saradwanth/backend/mutagent/traces/issue_rec.jsonl create mode 100644 submissions/Saradwanth/backend/mutagent/traces/pr_check.jsonl create mode 100644 submissions/Saradwanth/backend/mutagent/traces/roadmap.jsonl create mode 100644 submissions/Saradwanth/backend/observability/__init__.py create mode 100644 submissions/Saradwanth/backend/observability/metrics.py create mode 100644 submissions/Saradwanth/backend/observability/tracer.py create mode 100644 submissions/Saradwanth/backend/pipeline/__init__.py create mode 100644 submissions/Saradwanth/backend/pipeline/gate.py create mode 100644 submissions/Saradwanth/backend/pr_readiness.py create mode 100644 submissions/Saradwanth/backend/rag_qa.py create mode 100644 submissions/Saradwanth/backend/requirements.txt create mode 100644 submissions/Saradwanth/backend/reranker.py create mode 100644 submissions/Saradwanth/backend/run_stages.py create mode 100644 submissions/Saradwanth/backend/test_architecture_flow.py create mode 100644 submissions/Saradwanth/backend/test_css.py create mode 100644 submissions/Saradwanth/backend/test_graph.py create mode 100644 submissions/Saradwanth/backend/test_html.py create mode 100644 submissions/Saradwanth/backend/test_ts.py create mode 100644 submissions/Saradwanth/backend/vector_store.py create mode 100644 submissions/Saradwanth/docker-compose.yml create mode 100644 submissions/Saradwanth/package.json create mode 100644 submissions/Saradwanth/run_mutagent.bat create mode 100644 submissions/Saradwanth/scripts/ship.py create mode 100644 submissions/Saradwanth/src/components/ui/accordion.tsx create mode 100644 submissions/Saradwanth/src/components/ui/alert-dialog.tsx create mode 100644 submissions/Saradwanth/src/components/ui/alert.tsx create mode 100644 submissions/Saradwanth/src/components/ui/aspect-ratio.tsx create mode 100644 submissions/Saradwanth/src/components/ui/avatar.tsx create mode 100644 submissions/Saradwanth/src/components/ui/badge.tsx create mode 100644 submissions/Saradwanth/src/components/ui/breadcrumb.tsx create mode 100644 submissions/Saradwanth/src/components/ui/button.tsx create mode 100644 submissions/Saradwanth/src/components/ui/calendar.tsx create mode 100644 submissions/Saradwanth/src/components/ui/card.tsx create mode 100644 submissions/Saradwanth/src/components/ui/carousel.tsx create mode 100644 submissions/Saradwanth/src/components/ui/chart.tsx create mode 100644 submissions/Saradwanth/src/components/ui/checkbox.tsx create mode 100644 submissions/Saradwanth/src/components/ui/collapsible.tsx create mode 100644 submissions/Saradwanth/src/components/ui/command.tsx create mode 100644 submissions/Saradwanth/src/components/ui/context-menu.tsx create mode 100644 submissions/Saradwanth/src/components/ui/dialog.tsx create mode 100644 submissions/Saradwanth/src/components/ui/drawer.tsx create mode 100644 submissions/Saradwanth/src/components/ui/dropdown-menu.tsx create mode 100644 submissions/Saradwanth/src/components/ui/form.tsx create mode 100644 submissions/Saradwanth/src/components/ui/hover-card.tsx create mode 100644 submissions/Saradwanth/src/components/ui/input-otp.tsx create mode 100644 submissions/Saradwanth/src/components/ui/input.tsx create mode 100644 submissions/Saradwanth/src/components/ui/label.tsx create mode 100644 submissions/Saradwanth/src/components/ui/menubar.tsx create mode 100644 submissions/Saradwanth/src/components/ui/navigation-menu.tsx create mode 100644 submissions/Saradwanth/src/components/ui/pagination.tsx create mode 100644 submissions/Saradwanth/src/components/ui/popover.tsx create mode 100644 submissions/Saradwanth/src/components/ui/progress.tsx create mode 100644 submissions/Saradwanth/src/components/ui/radio-group.tsx create mode 100644 submissions/Saradwanth/src/components/ui/resizable.tsx create mode 100644 submissions/Saradwanth/src/components/ui/scroll-area.tsx create mode 100644 submissions/Saradwanth/src/components/ui/select.tsx create mode 100644 submissions/Saradwanth/src/components/ui/separator.tsx create mode 100644 submissions/Saradwanth/src/components/ui/sheet.tsx create mode 100644 submissions/Saradwanth/src/components/ui/sidebar.tsx create mode 100644 submissions/Saradwanth/src/components/ui/skeleton.tsx create mode 100644 submissions/Saradwanth/src/components/ui/slider.tsx create mode 100644 submissions/Saradwanth/src/components/ui/sonner.tsx create mode 100644 submissions/Saradwanth/src/components/ui/switch.tsx create mode 100644 submissions/Saradwanth/src/components/ui/table.tsx create mode 100644 submissions/Saradwanth/src/components/ui/tabs.tsx create mode 100644 submissions/Saradwanth/src/components/ui/textarea.tsx create mode 100644 submissions/Saradwanth/src/components/ui/toggle-group.tsx create mode 100644 submissions/Saradwanth/src/components/ui/toggle.tsx create mode 100644 submissions/Saradwanth/src/components/ui/tooltip.tsx create mode 100644 submissions/Saradwanth/src/hooks/use-mobile.tsx create mode 100644 submissions/Saradwanth/src/lib/error-capture.ts create mode 100644 submissions/Saradwanth/src/lib/error-page.ts create mode 100644 submissions/Saradwanth/src/lib/lovable-error-reporting.ts create mode 100644 submissions/Saradwanth/src/lib/utils.ts create mode 100644 submissions/Saradwanth/src/routeTree.gen.ts create mode 100644 submissions/Saradwanth/src/router.tsx create mode 100644 submissions/Saradwanth/src/routes/README.md create mode 100644 submissions/Saradwanth/src/routes/__root.tsx create mode 100644 submissions/Saradwanth/src/routes/index.tsx create mode 100644 submissions/Saradwanth/src/server.ts create mode 100644 submissions/Saradwanth/src/start.ts create mode 100644 submissions/Saradwanth/src/styles.css create mode 100644 submissions/Saradwanth/traces/council.jsonl create mode 100644 submissions/Saradwanth/traces/graph_query.jsonl create mode 100644 submissions/Saradwanth/traces/issue_health.jsonl create mode 100644 submissions/Saradwanth/traces/issue_rec.jsonl create mode 100644 submissions/Saradwanth/traces/pr_check.jsonl create mode 100644 submissions/Saradwanth/traces/roadmap.jsonl create mode 100644 submissions/Saradwanth/transcripts/antigravity_session.jsonl diff --git a/submissions/Saradwanth/Dockerfile.backend b/submissions/Saradwanth/Dockerfile.backend new file mode 100644 index 00000000..f1ed88a9 --- /dev/null +++ b/submissions/Saradwanth/Dockerfile.backend @@ -0,0 +1,9 @@ +FROM python:3.11-slim +WORKDIR /app +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY backend/ . +# Ensure local data directory exists for Chroma and SQLite +RUN mkdir -p data +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/submissions/Saradwanth/Dockerfile.frontend b/submissions/Saradwanth/Dockerfile.frontend new file mode 100644 index 00000000..07774c95 --- /dev/null +++ b/submissions/Saradwanth/Dockerfile.frontend @@ -0,0 +1,9 @@ +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build +RUN npm install -g serve +EXPOSE 5173 +CMD ["serve", "-s", "dist", "-l", "5173"] diff --git a/submissions/Saradwanth/README.md b/submissions/Saradwanth/README.md new file mode 100644 index 00000000..cca1d4b0 --- /dev/null +++ b/submissions/Saradwanth/README.md @@ -0,0 +1,28 @@ +# MUTAV2 (Mutagent) - Hackathon Submission + +## Overview +MUTAV2 is an Agentic Development Lifecycle (ADL) orchestration platform designed to understand, traverse, and automatically optimize its own behavior against any given GitHub repository. + +Instead of bolting a chatbot on top of code, we built: +1. **Hybrid Retrieval:** Semantic indexing via local embeddings (BAAI/bge-base-en-v1.5) combined with a highly accurate tree-sitter AST dependency graph. +2. **Mutagent Optimization Engine:** A self-evolving loop that evaluates LLM prompts against ground-truth datasets using severity-gated rubrics, and runs genetic mutations on failed prompts to discover optimal configurations. +3. **Enterprise B2B Layer:** Blast radius evaluation feeds directly into Test Selection and Reviewer Routing algorithms. A Maintainer Health dashboard automatically scores issue validity. + +## How to Run +We have included a packaging script (`scripts/ship.py`) that generated the Dockerfiles and `docker-compose.yml` for this project. + +1. Ensure Docker and Docker Compose are installed. +2. Run the following command from the root of this submission folder: + ```bash + docker-compose up --build + ``` +3. Navigate to `http://localhost:5173` to view the UI. + +## Evaluation Results (Mutagent Reports) +We don't hide behind fake metrics. The system is designed to trace everything and report honest deltas. +- You can view our execution traces under the `traces/` directory. +- The `reports/` directory in our backend shows the true deltas of our genetic mutation runs. We explicitly include scenarios where the model failed to produce a parseable variant, demonstrating the real rigor of our evaluation loop. + +## Judging Artifacts +- **Transcripts:** The `transcripts/` directory contains the complete, unedited `.jsonl` trace of the IDE agent session used to build and package this project. +- **Traces:** The `traces/` directory contains all raw prompt and latency traces generated by the Mutagent harness. diff --git a/submissions/Saradwanth/agentspec.yaml b/submissions/Saradwanth/agentspec.yaml new file mode 100644 index 00000000..2e3f55ae --- /dev/null +++ b/submissions/Saradwanth/agentspec.yaml @@ -0,0 +1,17 @@ +name: "MUTAV2 (Mutagent)" +team: "Saradwanth" +description: > + An Agentic Development Lifecycle (ADL) orchestration platform featuring + a semantic + structural (tree-sitter) hybrid retriever, an automated + genetic-mutation prompt optimizer, and a B2B enterprise layer. +tags: + - agentic-framework + - retrieval-augmented-generation + - ast-parsing + - evaluation-loop +architecture: + - "FastAPI (Python) Backend" + - "React (TypeScript) Frontend" + - "Local BAAI/bge-base-en-v1.5 embeddings" + - "Tree-sitter AST Graph Extraction" + - "Mutagent Optimizer Loop" diff --git a/submissions/Saradwanth/backend/.env.example b/submissions/Saradwanth/backend/.env.example new file mode 100644 index 00000000..bc424a33 --- /dev/null +++ b/submissions/Saradwanth/backend/.env.example @@ -0,0 +1,41 @@ +# Copy this file to ".env" and fill in your own values. +# NEVER commit the real ".env" file to git — it holds your secret keys. + +# Get this from https://console.groq.com/keys — used for every LLM call except +# rag_qa.py's HyDE/answer synthesis when OLLAMA_BASE_URL below is set. +GROQ_API_KEY=gsk_your-key-here + +# Get this from https://github.com/settings/tokens (classic token, "repo" + "read:org" scopes are enough) +# A token is optional for public repos but you'll hit rate limits fast without one. +GITHUB_TOKEN=ghp_your-token-here + +# Where the local vector database is stored on disk (created automatically) +CHROMA_PERSIST_DIR=./data/chroma + +# Which models to use. EMBEDDING_MODEL is a local sentence-transformers model — +# no API key, no quota, no rate limit — downloaded once on first use and cached. +EMBEDDING_MODEL=BAAI/bge-base-en-v1.5 +LLM_MODEL=llama-3.1-8b-instant + +# Optional: route rag_qa.py (HyDE + RAG answer synthesis only — nothing else in +# this app reads these) through a local/remote Ollama instance instead of Groq. +# Leave OLLAMA_BASE_URL blank to use Groq (LLM_MODEL above) everywhere, which is +# also what keeps every component on the same model — see B2B_AUDIT.md item 4 +# for why that divergence matters if you do set this. +# Must include the /v1 suffix (Ollama's OpenAI-compatible endpoint), e.g. +# http://localhost:11434/v1 or an ngrok URL like https://xxxx.ngrok-free.dev/v1 — +# the ngrok-skip-browser-warning header is sent automatically for tunnel URLs. +OLLAMA_BASE_URL= +OLLAMA_MODEL=qwen2.5:3b + +# How many chunks to retrieve per question in RAG Q&A +TOP_K=5 + +# HyDE: draft a hypothetical answer with the LLM and embed that for vector search, +# instead of embedding the raw (often short/vague) question. Set to "false" to disable +# and fall back to embedding the raw question directly. +HYDE_ENABLED=true + +# Chunking settings (measured in tokens, not characters) +CHUNK_SIZE=500 +CHUNK_OVERLAP=50 diff --git a/submissions/Saradwanth/backend/README.md b/submissions/Saradwanth/backend/README.md new file mode 100644 index 00000000..cbe871e6 --- /dev/null +++ b/submissions/Saradwanth/backend/README.md @@ -0,0 +1,114 @@ +# AI Open Source Mentor++ + +A hackathon MVP that helps developers onboard to unfamiliar GitHub repos: +repo Q&A, issue recommendation, a learning roadmap, and a PR readiness check. + +This README assumes you've never set up a Python backend before. Follow it top to bottom. + +## Folder structure + +``` +ai-oss-mentor/ +├── .env.example # template for your secret keys — copy this to .env +├── .gitignore +├── requirements.txt # Python packages this project needs +├── config.py # loads .env into one place +├── main.py # the web server — this is what you run +├── indexing.py # Step 1: turns a repo into a searchable knowledge base +├── rag_qa.py # Step 2: answers questions using that knowledge base +├── issue_recommendation.py # recommends a good issue to start on +├── learning_roadmap.py # generates a reading order for the repo +├── pr_readiness.py # checks a PR diff before you submit it +├── github_client.py # talks to GitHub's API +├── embeddings.py # chunks text and turns it into vectors +├── vector_store.py # stores and searches those vectors (Chroma, runs locally) +└── data/ + └── chroma/ # the local database gets created here automatically +``` + +## 1. Install Python + +You need Python 3.10 or newer. Check with: +```bash +python3 --version +``` +If you don't have it, download from https://www.python.org/downloads/ + +## 2. Set up a virtual environment + +This keeps this project's packages separate from everything else on your machine. + +```bash +cd ai-oss-mentor +python3 -m venv .venv + +# activate it — do this every time you open a new terminal for this project +source .venv/bin/activate # Mac/Linux +.venv\Scripts\activate # Windows +``` + +You'll know it worked because your terminal prompt will show `(.venv)` at the start. + +## 3. Install the packages + +```bash +pip install -r requirements.txt +``` + +## 4. Set up your API keys + +```bash +cp .env.example .env +``` + +Now open `.env` in any text editor and fill in: + +- **OPENAI_API_KEY** — required. Get one at https://platform.openai.com/api-keys + (you'll need to add a small amount of billing credit — a few dollars covers a hackathon) +- **GITHUB_TOKEN** — optional but recommended. Get one at https://github.com/settings/tokens + → "Generate new token (classic)" → check the `repo` box → generate. + Without this, you can still use public repos but you'll hit GitHub's rate limit quickly. + +Leave the other variables as-is unless you know you want to change them. + +## 5. Run the server + +```bash +uvicorn main:app --reload +``` + +You should see something like `Uvicorn running on http://127.0.0.1:8000`. + +## 6. Try it out + +Open **http://127.0.0.1:8000/docs** in your browser. This is an automatic +interactive test page — you can try every endpoint from here without writing +any code. + +The order to try things in: + +1. **POST /index** — body: `{"repo_url": "https://github.com/some/small-repo"}` + Do this first for any repo. Pick a small public repo for your first test — + indexing a huge repo takes longer and costs more in API calls. +2. **POST /ask** — body: `{"repo_url": "...", "question": "What does this repo do?"}` +3. **GET /recommend-issue** — query param `repo_url` +4. **POST /roadmap** — body: `{"repo_url": "..."}` +5. **POST /pr-check** — body: `{"diff_text": "...paste a git diff here..."}` + +## Common problems + +- **"OPENAI_API_KEY is not set"** — you forgot step 4, or forgot to save `.env` +- **GitHub rate limit errors** — add a `GITHUB_TOKEN` (step 4) +- **Indexing takes a while / costs API credit** — this is normal; each file + gets split into chunks and each chunk calls the embeddings API. Start with + a small repo (under ~50 files) for your first test. +- **`ModuleNotFoundError`** — make sure your virtual environment is activated + (you should see `(.venv)` in your prompt) and that you ran `pip install -r requirements.txt` + +## What's not built yet (see the original plan) + +- Repository architecture/dependency visualization +- Similar PR retrieval for PR readiness (the module has a comment showing + exactly where to plug it in) +- The n8n webhook automation for auto-triggering indexing on repo updates +- A frontend — right now everything is tested through `/docs` diff --git a/submissions/Saradwanth/backend/b2b/__init__.py b/submissions/Saradwanth/backend/b2b/__init__.py new file mode 100644 index 00000000..1daf2941 --- /dev/null +++ b/submissions/Saradwanth/backend/b2b/__init__.py @@ -0,0 +1,13 @@ +"""backend.b2b — enterprise data model and features layered on the core engine. + +Per the B2B Implementation Plan: everything here reuses the existing +parsing/graph/retrieval engine (indexing.py, graph/, rag_qa.py, +issue_recommendation.py, maintainer_health.py) unmodified. This package only +adds the organization/member layer and the enterprise-framed endpoints that +sit on top of it. + +Demo-scope persistence (store.py): sqlite3, no auth. org_id/member_id are +passed as plain request params, the same way repo_url already is elsewhere +in this app. Real login/session auth is out of scope for this pass — see +CHANGES.md and B2B_AUDIT.md. +""" diff --git a/submissions/Saradwanth/backend/b2b/governance.py b/submissions/Saradwanth/backend/b2b/governance.py new file mode 100644 index 00000000..5ac152ac --- /dev/null +++ b/submissions/Saradwanth/backend/b2b/governance.py @@ -0,0 +1,74 @@ +"""Governance dashboard packaging (plan §3.5). + +No new evaluation logic — observability/metrics.py already computes +everything. This only reshapes and labels it as the artifact a +security/compliance reviewer asks for when approving an AI tool for use +against internal code: per-component score, baseline vs. current, and a +last-evaluated timestamp. + +Two things it does NOT fabricate, on principle (plan §6 — verify, don't +assert): dataset_version and rubric_version are reported as null because +mutagent/datasets and mutagent/rubrics don't carry version fields yet, and +generation_model reports what's actually configured rather than the +single model the design doc assumes, since rag_qa.py can diverge from the +rest of the app onto Ollama (see B2B_AUDIT.md). +""" +from __future__ import annotations + +from datetime import datetime, timezone + +from config import REPORTS_DIR, settings +from observability.metrics import get_metrics + + +def _generation_model_note() -> str: + if settings.OLLAMA_BASE_URL: + return ( + f"All generation calls are currently routed through the local Ollama endpoint. " + f"Council gate, issue recommendation, PR check, and issue health use {settings.LLM_MODEL}. " + f"RAG Q&A (HyDE + answer synthesis) uses {settings.OLLAMA_MODEL}. " + f"This is a divergence from the 'one model' constraint, see B2B_AUDIT.md." + ) + return f"All generation calls use {settings.LLM_MODEL} via Groq." + + +def get_governance_report() -> dict: + metrics = get_metrics() + components = [] + + for t in metrics["targets"]: + report_path = REPORTS_DIR / f"{t['id']}.delta.json" + last_evaluated = None + if report_path.exists(): + last_evaluated = datetime.fromtimestamp( + report_path.stat().st_mtime, tz=timezone.utc + ).isoformat() + + components.append({ + "component": t["id"], + "name": t["name"], + "priority": t["priority"], + "evaluated": t["has_report"], + "last_evaluated": last_evaluated, + "baseline_score": t.get("baseline_score"), + "current_score": t.get("optimized_score") if t.get("optimized") else t.get("baseline_score"), + "delta": t.get("delta"), + "mean_f1": t.get("mean_f1"), + "trace_count": t["trace_count"], + "dataset_version": None, # not tracked yet — see B2B_AUDIT.md + "rubric_version": None, # not tracked yet — see B2B_AUDIT.md + }) + + return { + "generated_at": datetime.now(timezone.utc).isoformat(), + "generation_model_note": _generation_model_note(), + "components": components, + "caveats": [ + "dataset_version/rubric_version are not tracked per-report yet — " + "add a version field to mutagent/datasets and mutagent/rubrics " + "before presenting this unmodified to a compliance reviewer.", + "trace_count reflects mutagent/traces/*.jsonl, which is append-only " + "and never rotated — see B2B_AUDIT.md item 1 before treating volume " + "as a retention guarantee.", + ], + } diff --git a/submissions/Saradwanth/backend/b2b/roster.py b/submissions/Saradwanth/backend/b2b/roster.py new file mode 100644 index 00000000..296c6f58 --- /dev/null +++ b/submissions/Saradwanth/backend/b2b/roster.py @@ -0,0 +1,29 @@ +"""Manager view (plan §3.3): team roster, per-member roadmap progress, and +PR-readiness history in one payload — replaces manually chasing status +across three separate queries. +""" +from __future__ import annotations + +from b2b import store + + +def get_roster(org_id: int) -> list[dict]: + roster = [] + for member in store.list_members(org_id): + member_id = member["id"] + assigned = store.list_assigned_issues(member_id) + pr_history = store.list_pr_readiness_history(member_id) + roadmap_status = store.get_roadmap_statuses(member_id) + + open_count = sum(1 for a in assigned if a["status"] not in ("done", "closed")) + ready_count = sum(1 for h in pr_history if h["verdict"] == "ready") + + roster.append({ + "member": member, + "assigned_issues": assigned, + "open_assignment_count": open_count, + "roadmap_status": roadmap_status, + "pr_readiness_history": pr_history, + "pr_ready_rate": round(ready_count / len(pr_history), 4) if pr_history else None, + }) + return roster diff --git a/submissions/Saradwanth/backend/b2b/store.py b/submissions/Saradwanth/backend/b2b/store.py new file mode 100644 index 00000000..21934f9b --- /dev/null +++ b/submissions/Saradwanth/backend/b2b/store.py @@ -0,0 +1,322 @@ +"""Demo-scope persistence for the B2B data model (plan §2). + +Plain sqlite3 — no ORM, matching this codebase's existing preference for +the simplest tool that works (Chroma for vectors, JSON files for the graph). +No auth: org_id/member_id are passed as plain request params, the same way +repo_url already is. Real login/session/JWT auth is out of scope for this +pass; see B2B_AUDIT.md for what that would take. + +Tables map directly to plan §2's data model, with one addition +(member_roadmap_status) to make "per-member roadmap progress" concrete: +this app's roadmap generation (learning_roadmap.py) is stateless free-text, +not a checklist, so progress is tracked as a status +(not_started/in_progress/completed) per (member, repo) rather than a +percentage — that's the honest granularity available without inventing a +roadmap step format the rest of the app doesn't use. +""" +from __future__ import annotations + +import sqlite3 +from contextlib import contextmanager +from datetime import datetime, timezone +from pathlib import Path + +from config import REPO_ROOT + +DB_PATH = REPO_ROOT / "backend" / "data" / "b2b.sqlite3" + +_SCHEMA = """ +CREATE TABLE IF NOT EXISTS organizations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + test_selection_threshold INTEGER NOT NULL DEFAULT 3, + reviewer_routing_enabled INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS members ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id INTEGER NOT NULL REFERENCES organizations(id), + name TEXT NOT NULL, + email TEXT NOT NULL, + skill_profile TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL, + UNIQUE(org_id, email) +); + +CREATE TABLE IF NOT EXISTS org_repos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id INTEGER NOT NULL REFERENCES organizations(id), + repo_url TEXT NOT NULL, + created_at TEXT NOT NULL, + UNIQUE(org_id, repo_url) +); + +CREATE TABLE IF NOT EXISTS tagged_issues ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_id INTEGER NOT NULL REFERENCES organizations(id), + repo_url TEXT NOT NULL, + issue_number INTEGER NOT NULL, + tag TEXT NOT NULL, + subsystem TEXT NOT NULL DEFAULT '', + tagged_by TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL, + UNIQUE(org_id, repo_url, issue_number, tag) +); + +CREATE TABLE IF NOT EXISTS assigned_issues ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + member_id INTEGER NOT NULL REFERENCES members(id), + repo_url TEXT NOT NULL, + issue_number INTEGER, + issue_title TEXT NOT NULL DEFAULT '', + rationale TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'assigned', + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS pr_readiness_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + member_id INTEGER NOT NULL REFERENCES members(id), + repo_url TEXT NOT NULL DEFAULT '', + verdict TEXT, + diff_size_lines INTEGER, + has_tests INTEGER, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS member_roadmap_status ( + member_id INTEGER NOT NULL REFERENCES members(id), + repo_url TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'not_started', + updated_at TEXT NOT NULL, + PRIMARY KEY (member_id, repo_url) +); +""" + +_VALID_ROADMAP_STATUSES = {"not_started", "in_progress", "completed"} + + +@contextmanager +def _connect(): + DB_PATH.parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + conn.execute("PRAGMA foreign_keys = ON") + try: + yield conn + conn.commit() + finally: + conn.close() + + +def init_db() -> None: + with _connect() as conn: + conn.executescript(_SCHEMA) + + +def _now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def _row(r: sqlite3.Row | None) -> dict | None: + return dict(r) if r is not None else None + + +def _rows(rs: list[sqlite3.Row]) -> list[dict]: + return [dict(r) for r in rs] + + +# --------------------------------------------------------------------------- +# Organizations +# --------------------------------------------------------------------------- + +def create_organization(name: str) -> dict: + with _connect() as conn: + cur = conn.execute( + "INSERT INTO organizations (name, created_at) VALUES (?, ?)", + (name, _now()), + ) + org_id = cur.lastrowid + return get_organization(org_id) + + +def get_organization(org_id: int) -> dict | None: + with _connect() as conn: + return _row(conn.execute( + "SELECT * FROM organizations WHERE id = ?", (org_id,) + ).fetchone()) + + +def list_organizations() -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM organizations ORDER BY id" + ).fetchall()) + + +# --------------------------------------------------------------------------- +# Members +# --------------------------------------------------------------------------- + +def create_member(org_id: int, name: str, email: str, skill_profile: str = "") -> dict: + with _connect() as conn: + cur = conn.execute( + "INSERT INTO members (org_id, name, email, skill_profile, created_at) " + "VALUES (?, ?, ?, ?, ?)", + (org_id, name, email, skill_profile, _now()), + ) + member_id = cur.lastrowid + return get_member(member_id) + + +def get_member(member_id: int) -> dict | None: + with _connect() as conn: + return _row(conn.execute( + "SELECT * FROM members WHERE id = ?", (member_id,) + ).fetchone()) + + +def list_members(org_id: int) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM members WHERE org_id = ? ORDER BY id", (org_id,) + ).fetchall()) + + +# --------------------------------------------------------------------------- +# Org-scoped repos +# --------------------------------------------------------------------------- + +def register_repo(org_id: int, repo_url: str) -> dict: + with _connect() as conn: + conn.execute( + "INSERT OR IGNORE INTO org_repos (org_id, repo_url, created_at) VALUES (?, ?, ?)", + (org_id, repo_url, _now()), + ) + return _row(conn.execute( + "SELECT * FROM org_repos WHERE org_id = ? AND repo_url = ?", + (org_id, repo_url), + ).fetchone()) + + +def list_repos(org_id: int) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM org_repos WHERE org_id = ? ORDER BY id", (org_id,) + ).fetchall()) + + +# --------------------------------------------------------------------------- +# Tagged issues (team-lead-facing tagging, plan §3.3) +# --------------------------------------------------------------------------- + +def tag_issue(org_id: int, repo_url: str, issue_number: int, tag: str, + subsystem: str = "", tagged_by: str = "") -> dict: + with _connect() as conn: + conn.execute( + """INSERT INTO tagged_issues + (org_id, repo_url, issue_number, tag, subsystem, tagged_by, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(org_id, repo_url, issue_number, tag) + DO UPDATE SET subsystem = excluded.subsystem, tagged_by = excluded.tagged_by""", + (org_id, repo_url, issue_number, tag, subsystem, tagged_by, _now()), + ) + return _row(conn.execute( + "SELECT * FROM tagged_issues WHERE org_id = ? AND repo_url = ? " + "AND issue_number = ? AND tag = ?", + (org_id, repo_url, issue_number, tag), + ).fetchone()) + + +def list_tagged_issues(org_id: int, repo_url: str) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM tagged_issues WHERE org_id = ? AND repo_url = ? " + "ORDER BY issue_number", + (org_id, repo_url), + ).fetchall()) + + +# --------------------------------------------------------------------------- +# Assigned issues +# --------------------------------------------------------------------------- + +def assign_issue(member_id: int, repo_url: str, issue_number: int | None, + issue_title: str, rationale: str) -> dict: + with _connect() as conn: + cur = conn.execute( + """INSERT INTO assigned_issues + (member_id, repo_url, issue_number, issue_title, rationale, created_at) + VALUES (?, ?, ?, ?, ?, ?)""", + (member_id, repo_url, issue_number, issue_title, rationale, _now()), + ) + assigned_id = cur.lastrowid + return _row(conn.execute( + "SELECT * FROM assigned_issues WHERE id = ?", (assigned_id,) + ).fetchone()) + + +def list_assigned_issues(member_id: int) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM assigned_issues WHERE member_id = ? ORDER BY created_at DESC", + (member_id,), + ).fetchall()) + + +# --------------------------------------------------------------------------- +# PR readiness history +# --------------------------------------------------------------------------- + +def record_pr_readiness(member_id: int, repo_url: str, verdict: str | None, + diff_size_lines: int, has_tests: bool) -> dict: + with _connect() as conn: + cur = conn.execute( + """INSERT INTO pr_readiness_history + (member_id, repo_url, verdict, diff_size_lines, has_tests, created_at) + VALUES (?, ?, ?, ?, ?, ?)""", + (member_id, repo_url, verdict, diff_size_lines, int(has_tests), _now()), + ) + row_id = cur.lastrowid + return _row(conn.execute( + "SELECT * FROM pr_readiness_history WHERE id = ?", (row_id,) + ).fetchone()) + + +def list_pr_readiness_history(member_id: int, limit: int = 20) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM pr_readiness_history WHERE member_id = ? " + "ORDER BY created_at DESC LIMIT ?", + (member_id, limit), + ).fetchall()) + + +# --------------------------------------------------------------------------- +# Per-member roadmap status +# --------------------------------------------------------------------------- + +def set_roadmap_status(member_id: int, repo_url: str, status: str) -> dict: + if status not in _VALID_ROADMAP_STATUSES: + raise ValueError(f"status must be one of {sorted(_VALID_ROADMAP_STATUSES)}, got {status!r}") + with _connect() as conn: + conn.execute( + """INSERT INTO member_roadmap_status (member_id, repo_url, status, updated_at) + VALUES (?, ?, ?, ?) + ON CONFLICT(member_id, repo_url) DO UPDATE SET + status = excluded.status, updated_at = excluded.updated_at""", + (member_id, repo_url, status, _now()), + ) + return _row(conn.execute( + "SELECT * FROM member_roadmap_status WHERE member_id = ? AND repo_url = ?", + (member_id, repo_url), + ).fetchone()) + + +def get_roadmap_statuses(member_id: int) -> list[dict]: + with _connect() as conn: + return _rows(conn.execute( + "SELECT * FROM member_roadmap_status WHERE member_id = ? ORDER BY updated_at DESC", + (member_id,), + ).fetchall()) diff --git a/submissions/Saradwanth/backend/clients/__init__.py b/submissions/Saradwanth/backend/clients/__init__.py new file mode 100644 index 00000000..8b3543bc --- /dev/null +++ b/submissions/Saradwanth/backend/clients/__init__.py @@ -0,0 +1 @@ +# clients package diff --git a/submissions/Saradwanth/backend/clients/github_client.py b/submissions/Saradwanth/backend/clients/github_client.py new file mode 100644 index 00000000..a7a58195 --- /dev/null +++ b/submissions/Saradwanth/backend/clients/github_client.py @@ -0,0 +1,70 @@ +from __future__ import annotations +import base64 +from github import Github, GithubException +from github.Repository import Repository +from config import settings + + +class GitHubClient: + """TEMPPP-compatible class-based GitHub client.""" + + def __init__(self, token: str): + self._gh = Github(token) + + def get_repo(self, repo_url: str) -> Repository: + slug = repo_url.rstrip("/").split("github.com/")[-1].removesuffix(".git") + return self._gh.get_repo(slug) + + def list_files(self, repo: Repository, branch: str = "main") -> list[dict]: + """Recursively list all files, returning dicts with path and size.""" + results: list[dict] = [] + stack = [""] + while stack: + path = stack.pop() + try: + items = repo.get_contents(path, ref=branch) + except GithubException: + continue + if not isinstance(items, list): + items = [items] + for item in items: + if item.type == "dir": + stack.append(item.path) + else: + results.append({"path": item.path, "sha": item.sha, "size": item.size}) + return results + + def get_file(self, repo: Repository, path: str, branch: str = "main") -> str: + content = repo.get_contents(path, ref=branch) + return base64.b64decode(content.content).decode("utf-8", errors="replace") + + def list_issues(self, repo: Repository, state: str = "open", limit: int = 200) -> list[dict]: + issues = [] + for issue in repo.get_issues(state=state): + if issue.pull_request: + continue + issues.append({ + "number": issue.number, + "title": issue.title, + "body": (issue.body or "")[:800], + "labels": [label.name for label in issue.labels], + "created_at": issue.created_at.isoformat(), + "updated_at": issue.updated_at.isoformat(), + "comments": issue.comments, + "author": issue.user.login if issue.user else None, + }) + if len(issues) >= limit: + break + return issues + + def repo_stats(self, repo: Repository) -> dict: + """Lightweight stats for the roadmap and maintainer panel.""" + return { + "full_name": repo.full_name, + "description": repo.description or "", + "stars": repo.stargazers_count, + "forks": repo.forks_count, + "open_issues": repo.open_issues_count, + "language": repo.language, + "topics": repo.get_topics(), + } diff --git a/submissions/Saradwanth/backend/clients/llm_client.py b/submissions/Saradwanth/backend/clients/llm_client.py new file mode 100644 index 00000000..f9258dc6 --- /dev/null +++ b/submissions/Saradwanth/backend/clients/llm_client.py @@ -0,0 +1,115 @@ +from __future__ import annotations +import json +import time +from openai import OpenAI +from config import settings + +_client: OpenAI | None = None + + +def _get() -> OpenAI: + global _client + if _client is None: + if settings.OLLAMA_BASE_URL: + _client = OpenAI( + api_key="ollama", + base_url=settings.OLLAMA_BASE_URL, + timeout=120.0, + default_headers={"ngrok-skip-browser-warning": "true"} + ) + else: + _client = OpenAI( + api_key=settings.GROQ_API_KEY, + base_url="https://api.groq.com/openai/v1", + timeout=120.0 + ) + return _client + + +MAX_RETRIES = 10 + + +def complete(prompt: str, *, temperature: float = 0.2, max_tokens: int = 1024, json_mode: bool = False) -> tuple[str, float]: + """Single-turn completion. Returns (text, latency_ms). + + Retries on rate limits with exponential backoff. + """ + t0 = time.perf_counter() + for attempt in range(MAX_RETRIES): + try: + kwargs = { + "model": settings.LLM_MODEL, + "messages": [{"role": "user", "content": prompt}], + "temperature": temperature, + "max_tokens": max_tokens, + } + if json_mode: + kwargs["response_format"] = {"type": "json_object"} + + resp = _get().chat.completions.create(**kwargs) + latency_ms = (time.perf_counter() - t0) * 1000 + return resp.choices[0].message.content or "", latency_ms + except Exception as exc: # noqa: BLE001 + transient = type(exc).__name__ in ( + "RateLimitError", "APIConnectionError", "APITimeoutError", + "InternalServerError", + ) + if not transient or attempt == MAX_RETRIES - 1: + raise + wait = _retry_after(exc) or (2 ** attempt) + print(f" [{type(exc).__name__}] retrying in {wait:.0f}s " + f"({attempt + 1}/{MAX_RETRIES - 1})", flush=True) + time.sleep(wait) + raise RuntimeError("unreachable") + + +def _retry_after(exc: Exception) -> float | None: + """Seconds the server asked us to wait, if it said.""" + resp = getattr(exc, "response", None) + header = getattr(resp, "headers", {}) or {} + for key in ("retry-after", "x-ratelimit-reset-requests"): + raw = header.get(key) + if raw: + try: + return min(float(str(raw).rstrip("s")), 60.0) + except ValueError: + pass + return None + + +def extract_json(text: str) -> dict | None: + """Best-effort JSON object out of a model response. + + Handles fenced blocks, leading prose, and trailing commentary. + Returns None rather than raising; the caller decides what a failed + extraction means for it. + """ + s = text.strip() + if s.startswith("```"): + stripped = "\n".join(s.split("\n")[1:]) + s = stripped[:-3] if stripped.rstrip().endswith("```") else stripped + s = s.strip() + try: + parsed = json.loads(s) + return parsed if isinstance(parsed, dict) else None + except (json.JSONDecodeError, ValueError): + pass + # Fall back to the outermost brace pair + start, end = s.find("{"), s.rfind("}") + if start != -1 and end > start: + try: + parsed = json.loads(s[start:end + 1]) + return parsed if isinstance(parsed, dict) else None + except (json.JSONDecodeError, ValueError): + pass + return None + + +def json_complete(prompt: str, *, temperature: float = 0.1, max_tokens: int = 1024) -> tuple[dict | list, str, float]: + """Complete and parse JSON. Returns (parsed, raw_text, latency_ms). + Raises ValueError if the response is not extractable as JSON.""" + text, latency_ms = complete(prompt, temperature=temperature, max_tokens=max_tokens, json_mode=True) + parsed = extract_json(text) + if parsed is None: + raise ValueError(f"could not extract JSON from model output: {text[:200]!r}") + return parsed, text, latency_ms diff --git a/submissions/Saradwanth/backend/config.py b/submissions/Saradwanth/backend/config.py new file mode 100644 index 00000000..ec71ab7a --- /dev/null +++ b/submissions/Saradwanth/backend/config.py @@ -0,0 +1,87 @@ +""" +Loads settings from your .env file into one place so every other file +can just do: from config import settings +""" +import os +from pathlib import Path + +REPO_ROOT = Path(__file__).parent.parent + +# Disable ChromaDB telemetry globally before anything is imported +os.environ["ANONYMIZED_TELEMETRY"] = "False" + +from dotenv import load_dotenv + +load_dotenv(override=True) # reads the ".env" file in this folder + +# Mutagent directory paths (single source of truth for prompts, traces, reports) +PROMPTS_DIR = REPO_ROOT / "backend" / "mutagent" / "prompts" +TRACES_DIR = REPO_ROOT / "backend" / "mutagent" / "traces" +REPORTS_DIR = REPO_ROOT / "backend" / "mutagent" / "reports" +DATASETS_DIR = REPO_ROOT / "backend" / "mutagent" / "datasets" +RUBRICS_DIR = REPO_ROOT / "backend" / "mutagent" / "rubrics" + + +class Settings: + GROQ_API_KEY: str = os.getenv("GROQ_API_KEY", "") + GITHUB_TOKEN: str = os.getenv("GITHUB_TOKEN", "") + + OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "") + OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "qwen2.5:3b") + + CHROMA_PERSIST_DIR: str = os.getenv("CHROMA_PERSIST_DIR", "./data/chroma") + + EMBEDDING_MODEL: str = os.getenv("EMBEDDING_MODEL", "BAAI/bge-base-en-v1.5") + LLM_MODEL: str = os.getenv("LLM_MODEL", "llama3.1:8b") + + TOP_K: int = int(os.getenv("TOP_K", "25")) + RERANK_TOP_K: int = int(os.getenv("RERANK_TOP_K", "5")) + CHUNK_SIZE: int = int(os.getenv("CHUNK_SIZE", "500")) + CHUNK_OVERLAP: int = int(os.getenv("CHUNK_OVERLAP", "50")) + + HYDE_ENABLED: bool = os.getenv("HYDE_ENABLED", "true").lower() == "true" + + # Aliases for Mutagent CLI compatibility + @property + def groq_model(self): return self.LLM_MODEL + + @property + def groq_api_key(self): return self.GROQ_API_KEY + + @property + def github_token(self): return self.GITHUB_TOKEN + + @property + def groq_base_url(self): return "https://api.groq.com/openai/v1" + + +settings = Settings() + + +def load_prompt(name: str) -> str: + """Load a prompt template from backend/mutagent/prompts/.txt. + Single source of truth — prompts are never inlined in Python files. + """ + path = PROMPTS_DIR / f"{name}.txt" + return path.read_text(encoding="utf-8").strip() + + +def repo_id_from_url(repo_url: str) -> str: + """Canonical repo_url -> repo_id derivation used by Chroma and graph store. + Changing this invalidates every existing index and graph. + """ + slug = repo_url.rstrip("/").split("github.com/")[-1].removesuffix(".git") + return slug.replace("/", "__") + + +def load_dataset(name: str) -> list[dict]: + import json + path = DATASETS_DIR / f"{name}.json" + return json.loads(path.read_text(encoding="utf-8")) + + +def load_rubric(name: str) -> dict: + import json + path = RUBRICS_DIR / f"{name}.json" + return json.loads(path.read_text(encoding="utf-8")) + diff --git a/submissions/Saradwanth/backend/count_files.py b/submissions/Saradwanth/backend/count_files.py new file mode 100644 index 00000000..1625ef1b --- /dev/null +++ b/submissions/Saradwanth/backend/count_files.py @@ -0,0 +1,45 @@ +import sys +import requests + +def count_github_files(repo_url: str): + # Parse the owner/repo from the URL + repo_slug = repo_url.strip().rstrip("/") + if "github.com" in repo_slug: + repo_slug = repo_slug.split("github.com/")[-1] + repo_slug = repo_slug.replace(".git", "") + + # 1. Fetch repo metadata to find the default branch + api_base = f"https://api.github.com/repos/{repo_slug}" + print(f"Fetching repo info for: {repo_slug}...") + + repo_info = requests.get(api_base).json() + if "default_branch" not in repo_info: + print(f"Error: Could not fetch repo info. {repo_info.get('message', 'Unknown error.')}") + return + + branch = repo_info["default_branch"] + + # 2. Fetch the full recursive tree for the default branch + print(f"Fetching file tree for branch '{branch}'...") + tree_url = f"{api_base}/git/trees/{branch}?recursive=1" + tree_data = requests.get(tree_url).json() + + if "tree" not in tree_data: + print(f"Error: Could not fetch tree. {tree_data.get('message', 'Unknown error.')}") + if tree_data.get("truncated"): + print("Warning: The repository tree is too large to fetch in a single request.") + return + + # 3. Count only blobs (files), ignoring trees (directories) + files = [item for item in tree_data["tree"] if item["type"] == "blob"] + + print("-" * 40) + print(f"Total number of files in {repo_slug}: {len(files)}") + print("-" * 40) + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python count_files.py ") + sys.exit(1) + + count_github_files(sys.argv[1]) diff --git a/submissions/Saradwanth/backend/debug_github.py b/submissions/Saradwanth/backend/debug_github.py new file mode 100644 index 00000000..a233215c --- /dev/null +++ b/submissions/Saradwanth/backend/debug_github.py @@ -0,0 +1,18 @@ +from config import settings +from github_client import get_rate_limit_status, fetch_repo_files + +print(f"Token from settings: {settings.GITHUB_TOKEN[:15]}...") + +try: + print(get_rate_limit_status()) + print("Rate limit check passed!") +except Exception as e: + print(f"Rate limit failed: {e}") + +try: + res, count = fetch_repo_files("https://github.com/eliasku/unit") + print(f"Fetch files passed! Got {count} files.") +except Exception as e: + import traceback + traceback.print_exc() + print(f"Fetch files failed: {e}") diff --git a/submissions/Saradwanth/backend/embeddings.py b/submissions/Saradwanth/backend/embeddings.py new file mode 100644 index 00000000..22cdeb42 --- /dev/null +++ b/submissions/Saradwanth/backend/embeddings.py @@ -0,0 +1,61 @@ +""" +Two jobs: +1. chunk_text() — splits long text into overlapping token-sized pieces +2. embed_texts() — turns a list of text chunks into vectors using a local + BGE model (sentence-transformers) — no API key, no quota, no rate limit +""" +import tiktoken +from config import settings + +_encoding = tiktoken.get_encoding("cl100k_base") + +_model = None + + +def _get_model(): + global _model + if _model is None: + from sentence_transformers import SentenceTransformer + print(f"Loading local embedding model {settings.EMBEDDING_MODEL} (this may take a moment on first boot)...") + _model = SentenceTransformer(settings.EMBEDDING_MODEL) + return _model + + +def chunk_text(text: str, chunk_size: int = None, overlap: int = None) -> list[str]: + """ + Splits text into overlapping chunks measured in tokens (not characters), + so chunk size stays consistent regardless of language or formatting. + """ + chunk_size = chunk_size or settings.CHUNK_SIZE + overlap = overlap or settings.CHUNK_OVERLAP + + text = text.strip() + if not text: + return [] + + tokens = _encoding.encode(text) + if len(tokens) <= chunk_size: + return [text] + + chunks = [] + start = 0 + while start < len(tokens): + end = start + chunk_size + chunk_tokens = tokens[start:end] + chunks.append(_encoding.decode(chunk_tokens)) + start += chunk_size - overlap # step forward, leaving "overlap" tokens repeated + + return chunks + + +def embed_texts(texts: list[str]) -> list[list[float]]: + """ + Embeds a list of texts locally via a BGE sentence-transformers model. + Returns one vector per input text, in the same order. + """ + if not texts: + return [] + + model = _get_model() + vectors = model.encode(texts, normalize_embeddings=True, show_progress_bar=False) + return vectors.tolist() diff --git a/submissions/Saradwanth/backend/github_client.py b/submissions/Saradwanth/backend/github_client.py new file mode 100644 index 00000000..363d839b --- /dev/null +++ b/submissions/Saradwanth/backend/github_client.py @@ -0,0 +1,124 @@ +""" +Thin wrapper around PyGithub. Handles all the calls out to GitHub: +- pulling repo file contents (code + docs) +- pulling open issues +- pulling merged PRs (for the optional "similar PRs" feature) +""" +from github import Github, Auth +from config import settings + +# Text-like file extensions worth indexing. Skip binaries, images, lockfiles, etc. +INDEXABLE_EXTENSIONS = { + ".md", ".mdx", ".txt", ".rst", + ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rb", ".rs", ".c", ".cpp", ".h", + ".html", ".css",".json",".yml", +} + +MAX_FILE_SIZE_BYTES = 200_000 # skip huge generated files + + +def _get_client() -> Github: + if settings.GITHUB_TOKEN: + return Github(auth=Auth.Token(settings.GITHUB_TOKEN)) + return Github() # unauthenticated — works, but low rate limit + + +def get_rate_limit_status() -> dict: + """ + Returns the current GitHub API rate limit status. + """ + core = _get_client().get_rate_limit().core + return { + "limit": core.limit, + "remaining": core.remaining, + "reset": core.reset.isoformat() + } + + +def parse_repo_url(repo_url: str) -> str: + """ + Turns "https://github.com/owner/name" or "owner/name" into "owner/name". + """ + repo_url = repo_url.strip().rstrip("/") + if "github.com" in repo_url: + repo_url = repo_url.split("github.com/")[-1] + return repo_url.replace(".git", "") + + +def fetch_repo_files(repo_url: str) -> tuple[list[dict], int]: + """ + Walks the default branch and returns a list of: + {"path": "src/app.py", "content": "...", "type": "code"} + Only text files under MAX_FILE_SIZE_BYTES with an indexable extension are returned. + """ + client = _get_client() + repo = client.get_repo(parse_repo_url(repo_url)) + + results = [] + total_files = 0 + contents = repo.get_contents("") # start at repo root + stack = list(contents) + + while stack: + item = stack.pop() + if item.type == "dir": + stack.extend(repo.get_contents(item.path)) + continue + + total_files += 1 + + ext = "." + item.name.split(".")[-1] if "." in item.name else "" + if ext not in INDEXABLE_EXTENSIONS: + continue + if item.size > MAX_FILE_SIZE_BYTES: + continue + + try: + text = item.decoded_content.decode("utf-8", errors="ignore") + except Exception: + continue # skip anything that fails to decode (likely binary) + + file_type = "docs" if ext in {".md", ".mdx", ".txt", ".rst"} else "code" + results.append({"path": item.path, "content": text, "type": file_type}) + + return results, total_files + + +def fetch_open_issues(repo_url: str, limit: int = 50) -> list[dict]: + """ + Returns open issues as: {"number": 12, "title": "...", "body": "...", "labels": [...]} + Pull requests are excluded (GitHub's API lists them alongside issues). + """ + client = _get_client() + repo = client.get_repo(parse_repo_url(repo_url)) + + issues = [] + for issue in repo.get_issues(state="open"): + if len(issues) >= limit: + break + if issue.pull_request is not None: + continue # this "issue" is actually a PR, skip it + issues.append({ + "number": issue.number, + "title": issue.title, + "body": issue.body or "", + "labels": [label.name for label in issue.labels], + }) + return issues + + +def fetch_merged_prs(repo_url: str, limit: int = 30) -> list[dict]: + """ + Returns recently merged PRs as: {"number": 5, "title": "...", "body": "..."} + Used only by the optional "similar past PRs" feature in PR readiness. + """ + client = _get_client() + repo = client.get_repo(parse_repo_url(repo_url)) + + prs = [] + for pr in repo.get_pulls(state="closed", sort="updated", direction="desc"): + if len(prs) >= limit: + break + if pr.merged: + prs.append({"number": pr.number, "title": pr.title, "body": pr.body or ""}) + return prs diff --git a/submissions/Saradwanth/backend/graph/__init__.py b/submissions/Saradwanth/backend/graph/__init__.py new file mode 100644 index 00000000..71762863 --- /dev/null +++ b/submissions/Saradwanth/backend/graph/__init__.py @@ -0,0 +1,10 @@ +"""backend.graph — dependency graph extraction, storage, and query engine. + +Public API (consumed by backend.main and mutagent eval harness): + extract: extract_file, identifiers, imports, definitions + store: build_graph, save_graph, load_graph + blast_radius: blast_radius (exported wrapper matching main.py call-site) + query_dsl: execute_query + test_selection: select_tests + reviewer_routing: suggest_reviewers +""" diff --git a/submissions/Saradwanth/backend/graph/blast_radius.py b/submissions/Saradwanth/backend/graph/blast_radius.py new file mode 100644 index 00000000..acb159fc --- /dev/null +++ b/submissions/Saradwanth/backend/graph/blast_radius.py @@ -0,0 +1,208 @@ +"""Blast radius — reverse-transitive closure + identifier occurrence union. + +The hero feature: "what breaks if these files change?" + +Per the updated contract (af7a2fb), results include: + - kind: "import" (parsed edge) or "occurrence" (identifier name match) + - Sort order: (kind_rank, hops, path) — parsed edges rank above name matches + - Occurrences use hops=0 + +Exported function matches the main.py call-site: + blast_radius(repo_url, targets, max_hops=3) +Internal core takes an already-loaded graph for the eval harness. +""" +from __future__ import annotations + +import re +from collections import deque +from typing import Any + +import networkx as nx + + +# --------------------------------------------------------------------------- +# Internal core — takes an already-loaded graph +# --------------------------------------------------------------------------- + +def _blast_radius_core( + changed_files: list[str], + graph: nx.DiGraph, + max_hops: int = 3, + limit: int = 200, +) -> dict: + """Compute the blast radius for a set of changed files. + + Union of: + 1. Reverse BFS on import edges (files that transitively import the targets) + 2. Identifier-occurrence hits (files sharing identifiers with the targets) + + Returns the contract result shape. + """ + max_hops = min(max_hops, 10) # hard cap from contract + limit = min(limit, 1000) # hard cap from contract + + # Resolve targets (support partial paths like 'conf.py' for 'docs/conf.py') + resolved_targets = [] + missing = [] + for t in changed_files: + if t in graph: + resolved_targets.append(t) + continue + + matches = [n for n in graph.nodes if n == t or str(n).endswith(f"/{t}")] + if len(matches) == 1: + resolved_targets.append(matches[0]) + elif len(matches) > 1: + return _error_result(f"target is ambiguous: {t} (matches {', '.join(matches)})") + else: + missing.append(t) + + if missing: + return _error_result( + f"targets not in graph: {', '.join(missing)}" + ) + + # Determine coverage tier — weakest of all targets + tiers = [graph.nodes[t].get("tier", "unparseable") for t in resolved_targets] + tier = _weakest_tier(tiers) + + # --- 1. Reverse BFS on import edges --- + import_hits: dict[str, tuple[int, str]] = {} # path -> (hops, reason) + reverse = graph.reverse(copy=False) + + queue: deque[tuple[str, int]] = deque() + visited: set[str] = set(resolved_targets) + + for target in resolved_targets: + for pred in reverse.neighbors(target): + if pred not in visited: + visited.add(pred) + queue.append((pred, 1)) + import_hits[pred] = (1, f"imports {target}") + + while queue: + node, hops = queue.popleft() + if hops >= max_hops: + continue + for pred in reverse.neighbors(node): + if pred not in visited: + visited.add(pred) + queue.append((pred, hops + 1)) + import_hits[pred] = (hops + 1, f"imports {node}") + + # --- 2. Identifier-occurrence hits --- + # Collect identifiers defined/used in the changed files + target_idents: set[str] = set() + for target in resolved_targets: + node_data = graph.nodes.get(target, {}) + idents = node_data.get("idents", []) + target_idents.update(idents) + # Also add definition names + for d in node_data.get("defs", []): + target_idents.add(d["name"]) + + occurrence_hits: dict[str, str] = {} # path -> reason (first matching ident) + if target_idents: + for node_path in graph.nodes: + if node_path in resolved_targets or node_path in import_hits: + continue # skip targets and already-found import hits + node_data = graph.nodes[node_path] + node_idents = set(node_data.get("idents", [])) + shared = target_idents & node_idents + if shared: + # Pick the first shared identifier alphabetically for determinism + first = sorted(shared)[0] + occurrence_hits[node_path] = f"identifier occurrence: {first}" + + # --- Build result nodes --- + nodes: list[dict] = [] + + for path, (hops, reason) in import_hits.items(): + nodes.append({ + "path": path, + "kind": "import", + "hops": hops, + "reason": reason, + }) + + for path, reason in occurrence_hits.items(): + nodes.append({ + "path": path, + "kind": "occurrence", + "hops": 0, + "reason": reason, + }) + + # Sort: kind_rank (import=0, occurrence=1), then hops, then path + def sort_key(n): + kind_rank = 0 if n["kind"] == "import" else 1 + return (kind_rank, n["hops"], n["path"]) + + nodes.sort(key=sort_key) + + # Apply limit + truncated = len(nodes) > limit + nodes = nodes[:limit] + + return { + "nodes": nodes, + "coverage_tier": tier, + "truncated": truncated, + "error": None, + } + + +def _weakest_tier(tiers: list[str]) -> str: + """Return the weakest coverage tier from a list.""" + _RANK = {"unparseable": 0, "occurrence-only": 1, "deep": 2} + if not tiers: + return "unparseable" + return min(tiers, key=lambda t: _RANK.get(t, 0)) + + +def _error_result(msg: str) -> dict: + """Return an error result per the contract — errors are values, not exceptions.""" + return { + "nodes": [], + "coverage_tier": "unparseable", + "truncated": False, + "error": msg, + } + + +# --------------------------------------------------------------------------- +# Exported wrapper — matches main.py call-site: +# blast_radius(repo_url, targets, max_hops=3) +# --------------------------------------------------------------------------- + +def blast_radius( + repo_url: str, + targets: list[str], + max_hops: int = 3, +) -> dict: + """What breaks if these files change. + + Args: + repo_url: GitHub repo URL (used to derive repo_id) + targets: list of repo-relative file paths (POSIX separators) + max_hops: max traversal depth (default 3, hard cap 10) + + Returns: + Contract result dict with nodes, coverage_tier, truncated, error. + + This wrapper resolves repo_url -> repo_id -> load_graph, then delegates + to _blast_radius_core. The eval harness calls _blast_radius_core directly + with an already-loaded graph. + """ + from graph.store import load_graph + + # Derive repo_id from URL (same logic as indexing/store.py) + slug = repo_url.rstrip("/").split("github.com/")[-1].removesuffix(".git") + repo_id = slug.replace("/", "__") + + try: + graph = load_graph(repo_id) + except FileNotFoundError as e: + return _error_result(str(e)) + + return _blast_radius_core(targets, graph, max_hops=max_hops) diff --git a/submissions/Saradwanth/backend/graph/extract.py b/submissions/Saradwanth/backend/graph/extract.py new file mode 100644 index 00000000..da2ac7df --- /dev/null +++ b/submissions/Saradwanth/backend/graph/extract.py @@ -0,0 +1,481 @@ +"""Tree-sitter based extraction of imports, definitions, and identifiers. + +Uses the verified per-language queries from docs/GRAPH_EXTRACTION.md. +Two bugs in the naive rules are addressed here: + 1. PHP uses `name` not `identifier` — use is_named + IDENT_RE instead + 2. Ruby `require` is a call node, not an import statement + +Supports three coverage tiers: + - deep: 14 languages with hand-written import + definition queries + - occurrence-only: grammar loads but no import query — identifiers still extracted + - unparseable: grammar not in the language pack (rare — 371 covered) +""" +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from pathlib import PurePosixPath +from typing import Optional + +from tree_sitter_language_pack import get_parser + +# --------------------------------------------------------------------------- +# Universal identifier rule — verified on 16/16 languages +# The design doc's "identifier in node.type" fails on PHP. +# --------------------------------------------------------------------------- + +IDENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") + + +def _walk(node): + """Depth-first walk of all nodes in a tree-sitter tree.""" + yield node + for child in node.children: + yield from _walk(child) + + +def _leaves(node): + """Yield only leaf nodes (no children).""" + if not node.children: + yield node + for c in node.children: + yield from _leaves(c) + + +def identifiers(source: bytes, lang: str) -> set[str]: + """Grammar-agnostic identifier extraction. Works on all 371 languages. + + Uses is_named to skip punctuation/anonymous tokens, then IDENT_RE to + filter out strings, numbers, and operators the grammar exposes as leaves. + """ + try: + parser = get_parser(lang) + except Exception: + return set() + root = parser.parse(source).root_node + out: set[str] = set() + for leaf in _leaves(root): + if not leaf.is_named: + continue + text = source[leaf.start_byte:leaf.end_byte].decode(errors="replace") + if IDENT_RE.match(text): + out.add(text) + return out + + +# --------------------------------------------------------------------------- +# Language detection from file extension +# --------------------------------------------------------------------------- + +_EXT_TO_LANG: dict[str, str] = { + ".py": "python", + ".js": "javascript", + ".jsx": "javascript", + ".ts": "typescript", + ".tsx": "tsx", + ".java": "java", + ".go": "go", + ".rs": "rust", + ".c": "c", + ".h": "c", + ".cpp": "cpp", + ".cc": "cpp", + ".cxx": "cpp", + ".hpp": "cpp", + ".hxx": "cpp", + ".cs": "c_sharp", + ".rb": "ruby", + ".php": "php", + ".kt": "kotlin", + ".kts": "kotlin", + ".swift": "swift", + ".scala": "scala", + ".lua": "lua", + ".ex": "elixir", + ".exs": "elixir", + ".html": "html", + ".htm": "html", + ".css": "css", +} + +# The 14 deep-tier languages that have hand-written import + definition queries. +# Swift, Scala, Lua, Elixir have grammars but no import queries — occurrence-only. +DEEP_TIER_LANGS = frozenset({ + "python", "javascript", "typescript", "tsx", "java", "go", "rust", + "c", "cpp", "c_sharp", "ruby", "php", "kotlin", "html", "css", +}) + + +def detect_language(path: str) -> Optional[str]: + """Detect tree-sitter language name from file extension. Returns None if unknown.""" + ext = PurePosixPath(path).suffix.lower() + return _EXT_TO_LANG.get(ext) + + +def coverage_tier(lang: Optional[str]) -> str: + """Return the coverage tier for a language.""" + if lang is None: + return "unparseable" + if lang in DEEP_TIER_LANGS: + return "deep" + # Check if the grammar can actually load + try: + get_parser(lang) + return "occurrence-only" + except Exception: + return "unparseable" + + +# --------------------------------------------------------------------------- +# Import extraction — per-language, verified node types from GRAPH_EXTRACTION.md +# --------------------------------------------------------------------------- + +def _text(source: bytes, node) -> str: + """Extract text content of a tree-sitter node.""" + return source[node.start_byte:node.end_byte].decode(errors="replace") + + +def _python_imports(source: bytes, root) -> list[str]: + """Extract Python imports: import_statement, import_from_statement.""" + results = [] + for node in _walk(root): + if node.type == "import_statement": + # import foo, bar → dotted names are children + for child in node.children: + if child.type == "dotted_name": + results.append(_text(source, child)) + elif node.type == "import_from_statement": + # from foo.bar import baz → the module is the first dotted_name + module = node.child_by_field_name("module_name") + if module: + results.append(_text(source, module)) + else: + # Fallback: look for the first dotted_name child + for child in node.children: + if child.type in ("dotted_name", "relative_import"): + results.append(_text(source, child)) + break + return results + + +def _js_imports(source: bytes, root) -> list[str]: + """Extract JS/TS imports: import_statement + require() calls.""" + results = [] + for node in _walk(root): + if node.type == "import_statement": + # import ... from "module" → string child is the source + src = node.child_by_field_name("source") + if src: + raw = _text(source, src).strip("'\"") + results.append(raw) + elif node.type == "call_expression": + # require("module") + fn = node.child_by_field_name("function") + if fn and _text(source, fn) == "require": + args = node.child_by_field_name("arguments") + if args and args.child_count > 0: + for arg_child in args.children: + if arg_child.type == "string": + raw = _text(source, arg_child).strip("'\"") + results.append(raw) + break + return results + + +def _java_imports(source: bytes, root) -> list[str]: + """Extract Java imports: import_declaration.""" + results = [] + for node in _walk(root): + if node.type == "import_declaration": + # import com.example.Foo; + for child in node.children: + if child.type == "scoped_identifier": + results.append(_text(source, child)) + break + return results + + +def _go_imports(source: bytes, root) -> list[str]: + """Extract Go imports: import_declaration, import_spec.""" + results = [] + for node in _walk(root): + if node.type == "import_spec": + # The path is an interpreted_string_literal child + path_node = node.child_by_field_name("path") + if path_node: + raw = _text(source, path_node).strip('"') + results.append(raw) + return results + + +def _rust_imports(source: bytes, root) -> list[str]: + """Extract Rust imports: use_declaration.""" + results = [] + for node in _walk(root): + if node.type == "use_declaration": + # use std::io::Read; → the argument is the path + for child in node.children: + if child.type not in ("use", ";"): + results.append(_text(source, child)) + break + return results + + +def _c_cpp_imports(source: bytes, root) -> list[str]: + """Extract C/C++ includes: preproc_include.""" + results = [] + for node in _walk(root): + if node.type == "preproc_include": + # #include
or #include "header" + path_node = node.child_by_field_name("path") + if path_node: + raw = _text(source, path_node).strip('<>"') + results.append(raw) + return results + + +def _csharp_imports(source: bytes, root) -> list[str]: + """Extract C# imports: using_directive.""" + results = [] + for node in _walk(root): + if node.type == "using_directive": + # using System.IO; + for child in node.children: + if child.type in ("qualified_name", "identifier"): + results.append(_text(source, child)) + break + return results + + +def _ruby_imports(source: bytes, root) -> list[str]: + """Extract Ruby imports: require/require_relative/load/autoload calls. + + Ruby's grammar treats these as call nodes, not import statements. + This is the special case documented in GRAPH_EXTRACTION.md. + """ + results = [] + for node in _walk(root): + if node.type != "call": + continue + method = node.child_by_field_name("method") + if method is None: + continue + name = _text(source, method) + if name in ("require", "require_relative", "load", "autoload"): + args = node.child_by_field_name("arguments") + if args and args.child_count > 0: + for arg_child in args.children: + if arg_child.is_named: + raw = _text(source, arg_child).strip("'\"") + results.append(raw) + break + return results + + +def _php_imports(source: bytes, root) -> list[str]: + """Extract PHP imports: namespace_use_declaration + require/include calls.""" + results = [] + for node in _walk(root): + if node.type == "namespace_use_declaration": + for child in _walk(node): + if child.type == "qualified_name": + results.append(_text(source, child)) + elif node.type == "call_expression": + # require_once, include, include_once + fn = node.child_by_field_name("function") + if fn and _text(source, fn) in ( + "require", "require_once", "include", "include_once", + ): + args = node.child_by_field_name("arguments") + if args: + for child in args.children: + if child.is_named: + raw = _text(source, child).strip("'\"") + results.append(raw) + break + return results + + +def _kotlin_imports(source: bytes, root) -> list[str]: + """Extract Kotlin imports: import_header.""" + results = [] + for node in _walk(root): + if node.type == "import_header": + # import com.example.Foo + ident = node.child_by_field_name("identifier") + if ident: + results.append(_text(source, ident)) + return results + + +def _html_imports(source: bytes, root) -> list[str]: + """Extract HTML imports: and + + + + +""" + +all_files = [ + "index.html", + "style.css", + "js/script.js", + "js/words.js", + "img/bg.svg", + "img/image.jpeg" +] + +lang = get_language_from_path("index.html") +print("LANG:", lang) + +imports = extract_imports_with_treesitter(html_content, lang) +print("EXTRACTED IMPORTS:", imports) + +for imp in imports: + resolved = resolve_import_to_path("index.html", imp, all_files) + print(f"'{imp}' RESOLVED TO: {resolved}") diff --git a/submissions/Saradwanth/backend/test_html.py b/submissions/Saradwanth/backend/test_html.py new file mode 100644 index 00000000..8123b554 --- /dev/null +++ b/submissions/Saradwanth/backend/test_html.py @@ -0,0 +1,39 @@ +import tree_sitter_language_pack as ts_pack +from tree_sitter import Parser, Query, QueryCursor + +lang = ts_pack.get_language("html") +parser = Parser(lang) +html_content = b""" + + + + + + +""" +tree = parser.parse(html_content) + +query_str = """ +(attribute + (attribute_name) @attr_name + (quoted_attribute_value (attribute_value) @import) + (#match? @attr_name "^(href|src)$") +) +""" + +try: + query = Query(lang, query_str) + cursor = QueryCursor(query) + captures = cursor.captures(tree.root_node) + print("CAPTURES:", captures) + + imports = [] + if isinstance(captures, dict): + # newer tree-sitter returns a dict: {'attr_name': [Node, Node], 'import': [Node, Node]} + if 'import' in captures: + for node in captures['import']: + imports.append(node.text.decode('utf8')) + print("IMPORTS:", imports) + +except Exception as e: + print("ERROR:", e) diff --git a/submissions/Saradwanth/backend/test_ts.py b/submissions/Saradwanth/backend/test_ts.py new file mode 100644 index 00000000..db1cdd3e --- /dev/null +++ b/submissions/Saradwanth/backend/test_ts.py @@ -0,0 +1,43 @@ +# pyrefly: ignore [missing-import] +import tree_sitter_language_pack as ts_pack +# pyrefly: ignore [missing-import] +from tree_sitter import Parser, Query + +lang = ts_pack.get_language("python") +parser = Parser(lang) +tree = parser.parse(b"import os") + +query_str = "(import_statement) @import" +query = Query(lang, query_str) + +# Let's see how captures works in v0.22/0.23 +try: + print("Trying query.captures(tree.root_node)...") + res = query.captures(tree.root_node) + print("Success:", res) +except AttributeError: + print("query.captures failed!") + +try: + print("Trying query.matches(tree.root_node)...") + res = query.matches(tree.root_node) + print("Success:", res) +except AttributeError: + print("query.matches failed!") + +try: + from tree_sitter import QueryCursor + cursor = QueryCursor() + print("Trying cursor.captures(query, tree.root_node)...") + res = cursor.captures(query, tree.root_node) + print("Success:", res) +except Exception as e: + print("cursor.captures(query, node) failed!", e) + +try: + cursor = QueryCursor() + print("Trying cursor.matches(query, tree.root_node)...") + res = cursor.matches(query, tree.root_node) + print("Success:", res) +except Exception as e: + print("cursor.matches(query, node) failed!", e) diff --git a/submissions/Saradwanth/backend/vector_store.py b/submissions/Saradwanth/backend/vector_store.py new file mode 100644 index 00000000..37d16c24 --- /dev/null +++ b/submissions/Saradwanth/backend/vector_store.py @@ -0,0 +1,62 @@ +""" +Wraps Chroma so the rest of the app never has to think about the database directly. +Each repo gets its own "collection" so multiple repos can be indexed without mixing data. +""" +import chromadb +from config import settings + +_client = chromadb.PersistentClient( + path=settings.CHROMA_PERSIST_DIR, + settings=chromadb.Settings(anonymized_telemetry=False) +) + + +import hashlib + +def _collection_name(repo_url: str) -> str: + # Chroma collection names must be 3-63 chars. We hash the URL to guarantee a safe, unique name. + url_hash = hashlib.md5(repo_url.encode('utf-8')).hexdigest() + return f"repo_{url_hash}" + + +def get_or_create_collection(repo_url: str): + return _client.get_or_create_collection(name=_collection_name(repo_url)) + + +def add_chunks(repo_url: str, ids: list[str], embeddings: list[list[float]], + documents: list[str], metadatas: list[dict]) -> None: + collection = get_or_create_collection(repo_url) + collection.add(ids=ids, embeddings=embeddings, documents=documents, metadatas=metadatas) + + +def query(repo_url: str, query_embedding: list[float], top_k: int = None, paths: list[str] = None) -> dict: + top_k = top_k or settings.TOP_K + collection = get_or_create_collection(repo_url) + + where = None + if paths: + if len(paths) == 1: + where = {"path": paths[0]} + elif len(paths) > 1: + where = {"path": {"$in": paths}} + + return collection.query( + query_embeddings=[query_embedding], + n_results=top_k, + where=where + ) + + +def collection_is_empty(repo_url: str) -> bool: + collection = get_or_create_collection(repo_url) + return collection.count() == 0 + + +def delete_collection(repo_url: str) -> bool: + """Purge a repo's indexed chunks. Nothing currently calls this + automatically — see B2B_AUDIT.md item 2 (vector store lifecycle).""" + try: + _client.delete_collection(name=_collection_name(repo_url)) + return True + except Exception: + return False diff --git a/submissions/Saradwanth/docker-compose.yml b/submissions/Saradwanth/docker-compose.yml new file mode 100644 index 00000000..8e531539 --- /dev/null +++ b/submissions/Saradwanth/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' +services: + backend: + build: + context: . + dockerfile: Dockerfile.backend + ports: + - "8000:8000" + volumes: + - ./backend/data:/app/data + - ./backend/.env:/app/.env + environment: + - GROQ_API_KEY=${GROQ_API_KEY} + + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + ports: + - "5173:5173" + depends_on: + - backend diff --git a/submissions/Saradwanth/package.json b/submissions/Saradwanth/package.json new file mode 100644 index 00000000..48110846 --- /dev/null +++ b/submissions/Saradwanth/package.json @@ -0,0 +1,88 @@ +{ + "name": "tanstack_start_ts", + "private": true, + "sideEffects": false, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "build:dev": "vite build --mode development", + "preview": "vite preview", + "lint": "eslint .", + "format": "prettier --write ." + }, + "dependencies": { + "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.8", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.2.1", + "@tanstack/react-query": "^5.101.1", + "@tanstack/react-router": "^1.170.16", + "@tanstack/react-start": "^1.168.26", + "@tanstack/router-plugin": "^1.168.18", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^0.575.0", + "react": "^19.2.0", + "react-day-picker": "^9.14.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.71.2", + "react-markdown": "^10.1.0", + "react-resizable-panels": "^4.6.5", + "recharts": "^2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "tw-animate-css": "^1.3.4", + "vaul": "^1.1.2", + "vite-tsconfig-paths": "^6.0.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "@eslint/js": "^9.32.0", + "@lovable.dev/vite-tanstack-config": "^2.7.6", + "@types/node": "^22.16.5", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.2.0", + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.1", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^15.15.0", + "nitro": "3.0.260603-beta", + "prettier": "^3.7.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.56.1", + "vite": "^8.0.16" + } +} diff --git a/submissions/Saradwanth/run_mutagent.bat b/submissions/Saradwanth/run_mutagent.bat new file mode 100644 index 00000000..c2955af0 --- /dev/null +++ b/submissions/Saradwanth/run_mutagent.bat @@ -0,0 +1,15 @@ +@echo off +REM Navigate to the directory where this script is located +cd /d "%~dp0" + +REM Go into the backend directory +cd backend + +echo Starting Mutagent Issue Recommendation Test... +echo. + +REM Run the Mutagent harness using the virtual environment python +.\venv\Scripts\python -u -m mutagent.run issue_rec + +echo. +pause diff --git a/submissions/Saradwanth/scripts/ship.py b/submissions/Saradwanth/scripts/ship.py new file mode 100644 index 00000000..ca88325e --- /dev/null +++ b/submissions/Saradwanth/scripts/ship.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import os +from pathlib import Path + +def generate_file(filepath: str, content: str): + path = Path(filepath) + if path.exists(): + print(f"Skipping {filepath} (already exists)") + return + path.write_text(content.strip() + "\n", encoding="utf-8") + print(f"Created {filepath} successfully.") + +def main(): + print("[SHIP] Mutagent Packaging Engine (Ship Stage)") + print("------------------------------------------") + + backend_dockerfile = """ +FROM python:3.11-slim +WORKDIR /app +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY backend/ . +# Ensure local data directory exists for Chroma and SQLite +RUN mkdir -p data +EXPOSE 8000 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] +""" + + frontend_dockerfile = """ +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build +RUN npm install -g serve +EXPOSE 5173 +CMD ["serve", "-s", "dist", "-l", "5173"] +""" + + docker_compose = """ +version: '3.8' +services: + backend: + build: + context: . + dockerfile: Dockerfile.backend + ports: + - "8000:8000" + volumes: + - ./backend/data:/app/data + - ./backend/.env:/app/.env + environment: + - GROQ_API_KEY=${GROQ_API_KEY} + + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + ports: + - "5173:5173" + depends_on: + - backend +""" + + generate_file("Dockerfile.backend", backend_dockerfile) + generate_file("Dockerfile.frontend", frontend_dockerfile) + generate_file("docker-compose.yml", docker_compose) + + print("\n[SUCCESS] Packaging complete! The agent is ready to be shipped.") + print("Run `docker-compose up --build` to deploy to production.") + +if __name__ == "__main__": + main() diff --git a/submissions/Saradwanth/src/components/ui/accordion.tsx b/submissions/Saradwanth/src/components/ui/accordion.tsx new file mode 100644 index 00000000..16ee9004 --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/accordion.tsx @@ -0,0 +1,51 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDown } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Accordion = AccordionPrimitive.Root; + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AccordionItem.displayName = "AccordionItem"; + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + +)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)); +AccordionContent.displayName = AccordionPrimitive.Content.displayName; + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/submissions/Saradwanth/src/components/ui/alert-dialog.tsx b/submissions/Saradwanth/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..072a6656 --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/alert-dialog.tsx @@ -0,0 +1,115 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/submissions/Saradwanth/src/components/ui/alert.tsx b/submissions/Saradwanth/src/components/ui/alert.tsx new file mode 100644 index 00000000..cd0a0627 --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/alert.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/submissions/Saradwanth/src/components/ui/aspect-ratio.tsx b/submissions/Saradwanth/src/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..c9e6f4bf --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,5 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +const AspectRatio = AspectRatioPrimitive.Root; + +export { AspectRatio }; diff --git a/submissions/Saradwanth/src/components/ui/avatar.tsx b/submissions/Saradwanth/src/components/ui/avatar.tsx new file mode 100644 index 00000000..7904926b --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/avatar.tsx @@ -0,0 +1,47 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/submissions/Saradwanth/src/components/ui/badge.tsx b/submissions/Saradwanth/src/components/ui/badge.tsx new file mode 100644 index 00000000..3aabd17e --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/badge.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return
; +} + +export { Badge, badgeVariants }; diff --git a/submissions/Saradwanth/src/components/ui/breadcrumb.tsx b/submissions/Saradwanth/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..94eb6291 --- /dev/null +++ b/submissions/Saradwanth/src/components/ui/breadcrumb.tsx @@ -0,0 +1,101 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>