varshithreddy1807 commited on
Commit
da206c8
·
1 Parent(s): c4cf57a

feat: complete Phase 1 Repository Intelligence MVP

Browse files

- Repository analysis pipeline
- ChromaDB semantic indexing
- DeepSeek V4 Flash integration
- Repository chat with SSE streaming
- Issue mapper with grounded retrieval
- Architecture graph generation
- Reading path generation
- Impact analysis engine
- Unified repository workspace
- Session persistence
- Analysis persistence
- NVIDIA NIM fallback mode
- Documentation overhaul
- MVP validation reports

.env.example CHANGED
@@ -18,12 +18,19 @@ GITHUB_REPOSITORY=owner/repo # [Unused]
18
  SQLITE_DB_PATH=data/repo_understanding.db # [Planned] SQLite relational storage path
19
  CHROMA_DB_PATH=data/chroma_db # [Used] ChromaDB persistence directory
20
  CACHE_FILE_PATH=data/cache.json # [Planned] Shared cache layer path
 
 
 
 
 
 
 
21
 
22
  # =============================================================================
23
  # API Server Settings
24
  # =============================================================================
25
- API_SERVER_PORT=8000 # [Unused] Port is currently hardcoded in backend/main.py
26
- API_SERVER_HOST=0.0.0.0 # [Unused] Host is currently hardcoded in backend/main.py
27
 
28
  # =============================================================================
29
  # Frontend Settings
 
18
  SQLITE_DB_PATH=data/repo_understanding.db # [Planned] SQLite relational storage path
19
  CHROMA_DB_PATH=data/chroma_db # [Used] ChromaDB persistence directory
20
  CACHE_FILE_PATH=data/cache.json # [Planned] Shared cache layer path
21
+ # [Used] Where to clone analyzed repos. MUST be outside the project tree —
22
+ # otherwise uvicorn --reload's WatchFiles fires on every cloned file and kills
23
+ # the in-flight analysis. Examples:
24
+ # Windows: C:/repo_intelligence_storage/cloned_repos
25
+ # Linux/macOS: /var/lib/repo_intelligence/cloned_repos
26
+ # Leave blank to default to ~/.repo_intelligence/cloned_repos.
27
+ CLONED_REPOS_PATH=
28
 
29
  # =============================================================================
30
  # API Server Settings
31
  # =============================================================================
32
+ API_SERVER_PORT=8001 # [Used] Configures the uvicorn port in backend/main.py
33
+ API_SERVER_HOST=0.0.0.0 # [Used] Configures the uvicorn host in backend/main.py
34
 
35
  # =============================================================================
36
  # Frontend Settings
.vscode/settings.json ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ {
2
+ }
ARCHITECTURE.md ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏗️ Architecture Specification
2
+
3
+ This document provides a comprehensive technical overview of the **Repo Intelligence Agent** system architecture. It outlines the structural layers, data ingestion pipelines, API contracts, mathematical models, and operational workflows.
4
+
5
+ ---
6
+
7
+ ## 🏛️ System Overview & Multi-Layer Design
8
+
9
+ The application follows a modular, layer-oriented architecture separating the client-side presentation from the local parsing engines and backend intelligence nodes.
10
+
11
+ ```
12
+ ┌────────────────────────────────────────────────────────┐
13
+ │ Astro 4 & React Frontend │
14
+ │ (Chat Panel, React Flow Visualiser, Reading Timeline) │
15
+ └───────────────────────────┬────────────────────────────┘
16
+ │ HTTP & SSE (Event Stream)
17
+ ▼
18
+ ┌────────────────────────────────────────────────────────┐
19
+ │ FastAPI API Gateway │
20
+ │ (Binds to Port 8001, SSE Emitters, Request Routing) │
21
+ └───────────────────────────┬────────────────────────────┘
22
+ │ In-memory & Thread Pools
23
+ ▼
24
+ ┌────────────────────────────────────────────────────────┐
25
+ │ Code Processing Engine │
26
+ │ (GitHub Extractor, Code Chunking, Tree-sitter Parser) │
27
+ └─────────────┬─────────────────────────────┬────────────┘
28
+ │ │
29
+ ▼ ▼
30
+ ┌───────────────────────────┐ ┌──────────────────────────┐
31
+ │ Data Layer │ │ Reasoning Layer │
32
+ │ (ChromaDB, NetworkX, │ │ (DeepSeek V4 Flash NIM) │
33
+ │ analysis_store.json) │ │ │
34
+ └───────────────────────────┘ └──────────────────────────┘
35
+ ```
36
+
37
+ ### 1. Client Layer (Astro 4 + React)
38
+ - **Astro Pages:** Astro provides zero-JS static structure for routing and layout templates (`index.astro`, `chat.astro`, `analysis.astro`, `issues.astro`).
39
+ - **React Hydration:** High-interaction dashboard elements are implemented as React components hydrated in the client browser.
40
+ - **Visual Mapping:** Uses `React Flow` to render zoomable, interactive representations of dependency graphs and impact propagation paths.
41
+ - **Server-Sent Events (SSE):** Standard HTML5 `EventSource` handles real-time token streaming for chat and indexing logs.
42
+
43
+ ### 2. API Gateway Layer (FastAPI)
44
+ - **Asynchronous Execution:** Built on ASGI, utilizing `asyncio` and thread-pool execution (`asyncio.to_thread`) for long-running blocking operations like cloning and embedding generation.
45
+ - **Runtime Port:** Serves HTTP traffic on port **8001** (as configured in `backend/main.py` and `frontend/src/lib/api.ts`).
46
+ - **Data Integrity:** Employs strictly-typed `Pydantic` models for request validation and response serialization, protecting backend services from malformed payloads.
47
+
48
+ ### 3. Code Ingestion & Processing Layer
49
+ - **Extracellular Walk:** Git command-line utility clones repositories locally. The file walker filters out binaries, images, and folders like `.git`, `.venv`, and `node_modules`.
50
+ - **AST Parsing:** Tree-sitter runs syntax parsing. It uses compiled language objects for Python, JavaScript, TypeScript, JSX, and TSX to extract imports, exports, class variables, methods, and functions.
51
+ - **Code Chunking:** Splits source code into sliding window text blocks (1500 characters, 200 character overlap) to prepare documents for vector indexing.
52
+
53
+ ### 4. Memory & Vector DB Layer (Local)
54
+ - **Vector Space:** ChromaDB stores code chunks. Dense representation utilizes `BAAI/bge-small-en-v1.5` loaded locally via `sentence_transformers`. Output vectors are static 384-dimensional arrays.
55
+ - **Graph Space:** NetworkX handles directed graph (`DiGraph`) calculations, building import relationships where files represent nodes and imports represent edges.
56
+ - **Relational Storage:** Repository metadata and parsed summary schemas are serialized to the local file system at `data/analysis_store.json` on changes, enabling persisted states to survive process recycles.
57
+
58
+ ### 5. AI Reasoning Layer (NVIDIA NIM)
59
+ - **Remote Host:** Connects to DeepSeek V4 Flash (`deepseek-ai/deepseek-v4-flash`) hosted on NVIDIA NIM via an OpenAI-compatible interface.
60
+ - **Fault Tolerance:** Includes retry policies (exponential backoff) and an automatic fallback mechanism to retrieve and formulate responses locally when API requests fail.
61
+
62
+ ---
63
+
64
+ ## 💾 Unified Workspace & Session Store
65
+
66
+ The application frontend wraps all repository-specific dashboards inside a **Unified Repository Workspace**.
67
+
68
+ ### 1. Unified Repository Workspace Tabs Layout
69
+ ```
70
+ Unified Repository Workspace
71
+ ├── CODEBASE ANALYSIS (Overview, Tech Stack, Dependencies, Component Relations)
72
+ ├── ARCHITECTURE GRAPH (Interactive React Flow Dependency Mapping)
73
+ ├── READING PATH (Suggested Code Timeline & Centrality Graph)
74
+ ├── IMPACT ANALYSIS (BFS Traversal Change Predictor)
75
+ ├── ISSUE INTELLIGENCE (Issue Mapper Plan Generator)
76
+ └── CHAT (Context-Grounded Conversational Q&A Panel)
77
+ ```
78
+
79
+ The parent `AnalysisDashboard` component fetches the codebase metadata once on initial load, caching it in React state. Switching tabs updates the UI view instantly without invoking re-analysis or repeat backend queries.
80
+
81
+ ### 2. Session Context Sync
82
+ To allow seamless workspace sharing and recovery across browser tabs, the active session is divided into two stores:
83
+
84
+ #### Persisted Session Store (`localStorage`):
85
+ - **Fields:** `owner`, `repo`, `indexing status`, `graph status`, and `last analyzed timestamp`.
86
+ - **Rationale:** If the user reloads the browser, context is restored instantly. Since the backend persists analysis details to disk, the UI can re-fetch the complete cached analysis from `/api/analysis/{owner}/{repo}` using the stored name, preventing redundant re-clones.
87
+
88
+ #### Non-persisted Session Store (React In-memory State):
89
+ - **Fields:** React Flow graph nodes/edges, file-tree structures, impact analysis BFS paths, and current chat messages.
90
+ - **Rationale:** Large graph datasets exceed browser `localStorage` capacity limits (5MB) and degrade browser serialization performance. Storing these in React state keeps memory usage low and page loads fast.
91
+
92
+ ---
93
+
94
+ ## 🛡️ Ingestion Persistence & Chat Fallback
95
+
96
+ ### 1. Ingestion Analysis Persistence Lifecycle
97
+ Rather than holding processed analysis logs in volatile memory, the gateway persists state details to `data/analysis_store.json`.
98
+ - **Startup Hydration:** On backend startup, FastAPI walks the JSON file and hydrates the `ANALYSIS_STORE` cache.
99
+ - **Repository Recovery:** If the FastAPI backend crashes or restarts during local development, already analyzed repositories are recovered instantly without requiring a re-index.
100
+ - **Lifecycle Updates:** Any new repository processed via `/api/analyze` triggers an atomic write-back to the JSON file upon completion.
101
+
102
+ ### 2. Chat Fallback Resilience Layer
103
+ NVIDIA NIM free-tier API keys are rate-limited to ~3 requests per minute. To maintain a smooth user experience, the `/api/chat` and `/api/issues/map` endpoints implement a fallback structure:
104
+
105
+ ```
106
+ ┌────────────────────────────────────────┐
107
+ │ Submit User Request │
108
+ └───────────────────┬────────────────────┘
109
+ ▼
110
+ ┌────────────────────────────────────────┐
111
+ │ Vector ChromaDB Retrieve │
112
+ └───────────────────┬────────────────────┘
113
+ ▼
114
+ ┌────────────────────────────────────────┐
115
+ │ Attempt DeepSeek V4 NIM Call │
116
+ └─────────┬────────────────────┬─────────┘
117
+ │ │
118
+ Success │ │ Rate Limit / 429 / Timeout
119
+ ▼ ▼
120
+ ┌──────────────────────────────┐ ┌───────────────────────────────────┐
121
+ │ Stream LLM Tokens to Client │ │ Activate Grounded Fallback Mode │
122
+ └──────────────┬───────────────┘ └─────────────────┬─────────────────┘
123
+ │ │
124
+ │ ▼
125
+ │ ┌───────────────────────────────────┐
126
+ │ │ Fuzzy Match Paths to Components │
127
+ │ └─────────────────┬─────────────────┘
128
+ │ │
129
+ │ ▼
130
+ │ ┌───────────────────────────────────┐
131
+ │ │ Extract Chunks as Plan Sentences │
132
+ │ └─────────────────┬─────────────────┘
133
+ │ │
134
+ ▼ ▼
135
+ ┌───────────────────────────────────────────────────────────────────────┐
136
+ │ Return Grounded Response + Cited Sources + Confidence │
137
+ └───────────────────────────────────────────────────────────────────────┘
138
+ ```
139
+
140
+ When NVIDIA NIM returns a `429 Too Many Requests` or provider exception:
141
+ 1. The backend intercepts the exception and suppresses raw HTTP status error traces.
142
+ 2. It fetches the top-5 relevant code blocks from ChromaDB.
143
+ 3. It performs local keyword path matching to categorize affected components (e.g. `auth` maps to `Authentication`).
144
+ 4. It extracts class/function headers from the retrieved code blocks to generate a step-by-step local fallback implementation plan.
145
+ 5. It outputs the local plan directly to the client alongside ChromaDB source files and a similarity-derived confidence score.
146
+
147
+ ---
148
+
149
+ ## 🧮 Mathematical & Graph Processing Models
150
+
151
+ ### 1. Onboarding Reading Path Heuristics
152
+ The Reading Path generator calculates an onboarding hierarchy using structural indicators extracted from the dependency graph. The composite scoring model is defined as:
153
+
154
+ $$\text{Composite Score}(v) = (W_{\text{entry}} \times \mathbb{I}_{\text{entry}}(v)) + (W_{\text{centrality}} \times C_D(v)) + (W_{\text{in-degree}} \times I_D(v)) + (W_{\text{core}} \times \mathbb{I}_{\text{core}}(v)) - (W_{\text{peripheral}} \times \mathbb{I}_{\text{peripheral}}(v))$$
155
+
156
+ Where:
157
+ - $\mathbb{I}_{\text{entry}}(v) \in \{0, 1\}$ indicates if the node $v$ is a detected framework entry point.
158
+ - $C_D(v)$ represents the Normalized Degree Centrality of node $v$ in the undirected representation of the dependency graph:
159
+ $$C_D(v) = \frac{\text{deg}(v)}{N - 1}$$
160
+ - $I_D(v)$ represents the Normalized In-Degree of node $v$ (number of other files importing $v$):
161
+ $$I_D(v) = \frac{\text{deg}^-(v)}{N - 1}$$
162
+ - $\mathbb{I}_{\text{core}}(v) \in \{0, 1\}$ indicates if the file resides in a core system package (e.g. `services/`, `models/`, `core/`).
163
+ - $\mathbb{I}_{\text{peripheral}}(v) \in \{0, 1\}$ indicates if the file is in a non-execution directory (e.g. `tests/`, `docs/`, `examples/`).
164
+
165
+ #### Scoring Weights configuration:
166
+ - $W_{\text{entry}} = 100.0$
167
+ - $W_{\text{centrality}} = 50.0$
168
+ - $W_{\text{in-degree}} = 30.0$
169
+ - $W_{\text{core}} = 15.0$
170
+ - $W_{\text{peripheral}} = 100.0$
171
+
172
+ The service calculates scores, applies topological sorting on the dependency graph to ensure dependencies are read before dependents, and falls back to ordering by composite score descending.
173
+
174
+ ---
175
+
176
+ ### 2. Change Impact Analysis Walk Model
177
+ The impact analyzer maps change propagation by walking the directed import graph.
178
+ 1. **Seed Localization:** Fuzzy matches issue keywords against file paths in the repository to establish the "Seed Set" $S$.
179
+ 2. **Forward BFS Walk (Direct Dependents):** Traverses edges matching $v \to u$ (where file $u$ imports file $v$) up to depth $4$, mapping immediate downstream dependents.
180
+ 3. **Reverse BFS Walk (Downstream Dependents):** Traverses backward paths to locate files containing dependencies of the seed files.
181
+ 4. **Risk Evaluation:** Computes risk score $R$:
182
+ $$R = |F_{\text{affected}}| + (\beta \times |F_{\text{core}}|) + (\gamma \times |F_{\text{coupled}}|)$$
183
+ Where:
184
+ - $F_{\text{affected}}$ is the combined set of directly and indirectly impacted files.
185
+ - $F_{\text{core}}$ is the subset of affected files located in core directories ($\beta = 2$).
186
+ - $F_{\text{coupled}}$ is the subset of affected files having degree centrality higher than average ($\gamma = 1$).
187
+ - **Risk Level:**
188
+ - Low: $R < 4$
189
+ - Medium: $4 \le R < 10$
190
+ - High: $R \ge 10$
191
+
192
+ ---
193
+
194
+ ## 🔌 API Endpoint Specifications
195
+
196
+ ### 🧭 Base URL: `http://127.0.0.1:8001`
197
+
198
+ | HTTP Method | Path | Request Body | Response Schema | Description |
199
+ | :--- | :--- | :--- | :--- | :--- |
200
+ | **GET** | `/health` | None | `{backend, llm_provider, llm_model, embedding_provider, vector_db, status}` | Health check |
201
+ | **GET** | `/api/repos/examples` | None | `List[{name, url, tech_stack, description}]` | Fetch pre-configured examples |
202
+ | **GET** | `/api/repos/recent` | None | `List[{name, url, tech_stack, analyzed_at}]` | List analyzed repositories |
203
+ | **POST** | `/api/index` | `{repo_url}` | `{status: "indexed", files: int, chunks: int}` | Index code chunks in ChromaDB |
204
+ | **POST** | `/api/analyze` | `{url, branch, model}` | `text/event-stream` (SSE Progress) | Trigger full analysis pipeline |
205
+ | **GET** | `/api/analysis/{owner}/{repo_name}` | None | `{analysis: RepositoryAnalysis, architecture: ArchitectureSummary}` | Fetch analysis details |
206
+ | **POST** | `/api/issues/map` | `{repo, issue, title, description}` | `IssueMapResponse` | Generate implementation plan |
207
+ | **POST** | `/api/chat` | `{repo, message, history: List}` | `text/event-stream` (SSE Chat Tokens) | Streaming chat Q&A |
208
+ | **POST** | `/api/architecture/build` | `{repo}` | `{status, repo, files_parsed, dependencies_found, entry_points}` | Rebuild dependency graph |
209
+ | **GET** | `/api/architecture/{owner}/{repo_name}` | None | `ArchitectureSummary` | Fetch architecture summary |
210
+ | **POST** | `/api/reading-order` | `{repo}` | `ReadingOrder` | Generate onboarding path |
211
+ | **POST** | `/api/impact-analysis` | `{repo, issue}` | `ImpactAnalysis` | Predict change impacts |
212
+ | **GET** | `/api/architecture/{owner}/{repo_name}/graph` | Query: `q` (filter) | React Flow Node-Link JSON | Fetch dependency graph data |
213
+
214
+ ---
215
+
216
+ ## 🕳️ Skeletal Architectural Stubs
217
+
218
+ To ensure long-term structure while maintaining local-first MVP operations, the codebase contains four skeletal stubs:
219
+
220
+ 1. **`SQLiteStore` (`memory/sqlite_store.py`):**
221
+ - **Current State:** Skeletal stub raising `NotImplementedError` across all transactional storage calls.
222
+ - **MVP Handling:** In-memory maps and fs-level caching in `data/analysis_store.json` and `data/issue_cache.json` handle state persistence.
223
+ 2. **`MCPService` (`services/mcp_service.py`):**
224
+ - **Current State:** Skeletal stub raising `NotImplementedError`.
225
+ - **MVP Handling:** Codebase cloning and indexing operations are performed directly via git execution and local walkers.
226
+ 3. **`RepositoryAnalyzer` (`agents/analyzer.py`):**
227
+ - **Current State:** Skeletal stub raising `NotImplementedError`.
228
+ - **MVP Handling:** Ingestion and stack analysis logic are implemented directly inside `backend/api.py`.
229
+ 4. **`ArchitectureExplainer` (`agents/explainer.py`):**
230
+ - **Current State:** Skeletal stub raising `NotImplementedError`.
231
+ - **MVP Handling:** Reading order and structural sorting calculations are handled directly inside `services/reading_order_service.py`.
ARCHITECTURE_BUGFIX_REPORT.md DELETED
@@ -1,305 +0,0 @@
1
- # Architecture Phase 1 — Bug Fix Report
2
-
3
- ## Reported Symptoms
4
-
5
- After validating against `fastapi/fastapi`, the API returned:
6
-
7
- ```json
8
- {
9
- "entry_points": ["..."],
10
- "core_modules": [],
11
- "high_coupling_modules": [],
12
- "total_files": 2979,
13
- "total_dependencies": 0
14
- }
15
- ```
16
-
17
- The graph and all downstream metrics appeared broken.
18
-
19
- ---
20
-
21
- ## Investigation
22
-
23
- A diagnostic script (`scripts/diagnose_phase1.py`) was run against the
24
- live `fastapi/fastapi` clone to isolate each layer independently.
25
-
26
- ### TreeSitterService — HEALTHY
27
-
28
- ```
29
- Files parsed: 1125
30
- Total imports extracted: 3390
31
- Files WITH imports: 918
32
- ```
33
-
34
- Imports were being extracted correctly. The service was not the problem.
35
-
36
- ### GraphService — HEALTHY
37
-
38
- ```
39
- File graph — nodes: 1125, edges: 1440
40
- Module graph — nodes: 1218, edges: 2804
41
- ```
42
-
43
- The graph engine was building edges correctly from the parsed imports.
44
-
45
- ### ArchitectureSummary — STALE CACHE
46
-
47
- When `architecture_service.get_summary()` was called, it loaded the
48
- file at `data/architecture/fastapi_fastapi.json`. That file had been
49
- written during an earlier development run **before the graph service was
50
- implemented**, and contained:
51
-
52
- ```json
53
- {
54
- "total_files": 2979,
55
- "total_dependencies": 0,
56
- "core_modules": [],
57
- "high_coupling_modules": []
58
- }
59
- ```
60
-
61
- The `GET /api/architecture/{owner}/{repo}` endpoint was serving this
62
- stale file directly, with no mechanism to detect that it was invalid.
63
-
64
- ### EntryPointService — OVER-DETECTION BUG
65
-
66
- A separate but related bug: when the service was rebuilt using the
67
- diagnostic script, it reported **574 entry points** instead of the
68
- expected ~10–20. The root cause was `_is_python_framework_init`:
69
-
70
- ```python
71
- # BROKEN — flags ANY file that imports fastapi
72
- def _is_python_framework_init(file_path, parsed):
73
- imports_lower = [i.lower() for i in parsed.get("imports", [])]
74
- return "fastapi" in imports_lower or "flask" in imports_lower
75
- ```
76
-
77
- The fastapi repo contains ~500 tutorial/test files in `docs_src/` and
78
- `tests/` that all import `fastapi` for demonstration purposes. Every
79
- one of them was incorrectly flagged as an application entry point.
80
-
81
- ---
82
-
83
- ## Root Causes
84
-
85
- ### Bug 1 — Stale Cache (primary, explains the 0-dependency report)
86
-
87
- **Location**: `data/architecture/fastapi_fastapi.json`
88
-
89
- **Cause**: A zero-filled JSON summary written during early development
90
- (before graph edges were implemented) persisted on disk and was served
91
- by the API without any version or integrity check. The `build()` method
92
- was never called again with `force_rebuild=True` after the graph engine
93
- was completed, so the stale file was never overwritten via the API.
94
-
95
- **Conditions for trigger**: Any repository that was analysed before the
96
- Phase 1 graph services were fully operational will have a stale cache
97
- file that silently returns incorrect zero-dependency data indefinitely.
98
-
99
- ### Bug 2 — Entry Point Over-Detection
100
-
101
- **Location**: `services/entry_point_service.py` → `_is_python_framework_init`
102
-
103
- **Cause**: The heuristic checked only whether a file *imports* `fastapi`
104
- or `flask`, without distinguishing between:
105
- - Tutorial/test/docs files that import the framework to demonstrate it
106
- - Application root files that actually instantiate the framework
107
-
108
- For the fastapi repo (which is the framework itself), this caused every
109
- one of its ~500 example and test files to be flagged as entry points.
110
-
111
- ---
112
-
113
- ## Code Changes
114
-
115
- ### Fix 1 — Schema versioning + staleness detection
116
-
117
- **File**: `services/architecture_service.py`
118
-
119
- Added a `_SUMMARY_SCHEMA_VERSION` constant (currently `2`). Every saved
120
- summary now includes `_schema_version` and `_built_at` timestamp fields.
121
- `_load_summary()` rejects any file with a version lower than the current
122
- constant by returning `None`, which causes the caller to treat the repo
123
- as unbuilt and return a `404`, prompting a fresh `POST /build`.
124
-
125
- ```python
126
- # Added to architecture_service.py
127
- _SUMMARY_SCHEMA_VERSION = 2
128
-
129
- def _save_summary(self, repo_name, summary):
130
- versioned = dict(summary)
131
- versioned["_schema_version"] = _SUMMARY_SCHEMA_VERSION
132
- versioned["_built_at"] = int(time.time())
133
- # ... write to disk
134
-
135
- def _load_summary(self, repo_name):
136
- data = json.load(...)
137
- stored_version = data.get("_schema_version", 0)
138
- if stored_version < _SUMMARY_SCHEMA_VERSION:
139
- logger.warning("Discarding stale architecture summary ...")
140
- return None # triggers 404 → caller must rebuild
141
- return {k: v for k, v in data.items() if not k.startswith("_")}
142
- ```
143
-
144
- ### Fix 2 — Targeted entry point detection
145
-
146
- **File**: `services/entry_point_service.py`
147
-
148
- Replaced the broad import-only check with a three-rule heuristic that
149
- filters out non-entry directories before applying framework detection:
150
-
151
- ```python
152
- # Rule 1 — exclude known non-entry directories
153
- _FRAMEWORK_INIT_EXCLUDED_PREFIXES = (
154
- "tests/", "test/", "docs/", "docs_src/",
155
- "examples/", "example/", "scripts/", "benchmarks/",
156
- )
157
-
158
- def _is_python_framework_init(file_path, parsed):
159
- # Skip tutorial / test / docs trees
160
- for prefix in _FRAMEWORK_INIT_EXCLUDED_PREFIXES:
161
- if fp_lower.startswith(prefix):
162
- return False
163
-
164
- # Must import the framework
165
- if "fastapi" not in imports_lower and "flask" not in imports_lower:
166
- return False
167
-
168
- # Rule 2 — application-suggestive filename at any depth
169
- if basename in {"app.py", "application.py", "server.py", ...}:
170
- return True
171
-
172
- # Rule 3 — top two directory levels only
173
- if len(parts) <= 2:
174
- return True
175
-
176
- return False
177
- ```
178
-
179
- ---
180
-
181
- ## Before / After Metrics — fastapi/fastapi
182
-
183
- | Metric | Before (stale cache) | After (fix applied) |
184
- |---|---|---|
185
- | `total_files` | 2979 | 2979 |
186
- | `total_dependencies` | **0** | **1440** |
187
- | `core_modules` | **[]** | `['fastapi/__init__.py', 'fastapi/testclient.py', 'fastapi/responses.py', ...]` |
188
- | `high_coupling_modules` | **[]** | `['fastapi/__init__.py', 'fastapi/testclient.py', 'fastapi/responses.py', ...]` |
189
- | `entry_points` (count) | 574 | **17** |
190
- | Files parsed | 1125 | 1125 |
191
- | Graph nodes | 1125 | 1125 |
192
- | Graph edges | 1440 | 1440 |
193
-
194
- ### Correct entry points after fix (17 total)
195
-
196
- ```
197
- docs_src/app_testing/app_a_py310/main.py
198
- docs_src/app_testing/app_b_an_py310/main.py
199
- docs_src/app_testing/app_b_py310/main.py
200
- docs_src/async_tests/app_a_py310/main.py
201
- docs_src/bigger_applications/app_an_py310/main.py
202
- docs_src/settings/app01_py310/main.py
203
- docs_src/settings/app02_an_py310/main.py
204
- docs_src/settings/app02_py310/main.py
205
- docs_src/settings/app03_an_py310/main.py
206
- docs_src/settings/app03_py310/main.py
207
- tests/main.py
208
- tests/test_modules_same_name_body/app/main.py
209
- fastapi/__main__.py
210
- fastapi/applications.py ← actual framework entry point
211
- fastapi/param_functions.py ← actual framework entry point
212
- fastapi/routing.py ← actual framework entry point
213
- fastapi/utils.py ← actual framework entry point
214
- ```
215
-
216
- ### Correct core modules (top 5)
217
-
218
- ```
219
- fastapi/__init__.py
220
- fastapi/testclient.py
221
- fastapi/responses.py
222
- fastapi/security/__init__.py
223
- fastapi/exceptions.py
224
- ```
225
-
226
- ### Correct high-coupling modules (top 5)
227
-
228
- ```
229
- fastapi/__init__.py (in_degree + out_degree = highest)
230
- fastapi/testclient.py
231
- fastapi/responses.py
232
- fastapi/security/__init__.py
233
- fastapi/exceptions.py
234
- ```
235
-
236
- ---
237
-
238
- ## Validation — API Output
239
-
240
- `GET /api/architecture/fastapi/fastapi` now returns:
241
-
242
- ```json
243
- {
244
- "entry_points": [
245
- "docs_src/app_testing/app_a_py310/main.py",
246
- "fastapi/__main__.py",
247
- "fastapi/applications.py",
248
- "fastapi/routing.py",
249
- ...
250
- ],
251
- "core_modules": [
252
- "fastapi/__init__.py",
253
- "fastapi/testclient.py",
254
- "fastapi/responses.py",
255
- "fastapi/security/__init__.py",
256
- "fastapi/exceptions.py",
257
- ...
258
- ],
259
- "high_coupling_modules": [
260
- "fastapi/__init__.py",
261
- "fastapi/testclient.py",
262
- "fastapi/responses.py",
263
- "fastapi/security/__init__.py",
264
- "fastapi/exceptions.py",
265
- ...
266
- ],
267
- "total_files": 2979,
268
- "total_dependencies": 1440
269
- }
270
- ```
271
-
272
- ---
273
-
274
- ## Test Results
275
-
276
- ```
277
- 80 passed, 0 failed — 1 warning (deprecation, not a code issue)
278
- ```
279
-
280
- All 62 Phase 1 architecture tests pass, including the live
281
- `fastapi/fastapi` repo validation suite.
282
-
283
- ---
284
-
285
- ## Files Modified
286
-
287
- | File | Change |
288
- |---|---|
289
- | `services/entry_point_service.py` | Replaced broad import-check with directory-exclusion + file-name + depth rules |
290
- | `services/architecture_service.py` | Added `_SUMMARY_SCHEMA_VERSION`, `_built_at` timestamp, staleness check in `_load_summary` |
291
- | `scripts/diagnose_phase1.py` | New: diagnostic script used to isolate and reproduce the bugs |
292
-
293
- ---
294
-
295
- ## Success Criteria — Verified
296
-
297
- | Criterion | Status |
298
- |---|---|
299
- | `total_files > 0` | ✅ 2979 |
300
- | `total_dependencies > 0` | ✅ 1440 |
301
- | `core_modules` not empty | ✅ 10 modules |
302
- | `high_coupling_modules` not empty | ✅ 10 modules |
303
- | Entry points are real app files | ✅ 17 (down from 574) |
304
- | All 80 tests pass | ✅ |
305
- | Stale summaries auto-invalidated | ✅ via schema version check |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -2,107 +2,129 @@
2
 
3
  <p align="center">
4
  <img src="https://img.shields.io/badge/Backend-FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white" alt="FastAPI"/>
5
- <img src="https://img.shields.io/badge/LLM-DeepSeek%20V4%20Flash-6C5CE7?style=for-the-badge" alt="DeepSeek"/>
 
6
  <img src="https://img.shields.io/badge/Inference-NVIDIA%20NIM-76B900?style=for-the-badge&logo=nvidia&logoColor=white" alt="NVIDIA NIM"/>
7
  <img src="https://img.shields.io/badge/Vector%20DB-ChromaDB-blue?style=for-the-badge" alt="ChromaDB"/>
8
- <img src="https://img.shields.io/badge/Status-MVP%20Complete-brightgreen?style=for-the-badge" alt="Status MVP"/>
9
- <img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="License"/>
10
  </p>
11
 
12
  <p align="center">
13
- <strong>An AI-powered repository intelligence platform for understanding, navigating, and planning changes across large codebases.</strong>
14
  </p>
15
 
16
  <p align="center">
17
- <a href="#-why-repo-intelligence-agent">Why Repo Intelligence Agent?</a> •
18
- <a href="#-key-features">Key Features</a> •
19
  <a href="#-system-architecture">System Architecture</a> •
20
- <a href="#-installation">Installation</a> •
21
- <a href="#-performance--validation">Validation Results</a> •
22
- <a href="docs/DEVELOPMENT_SETUP.md">Development Setup</a>
 
23
  </p>
24
 
25
  ---
26
 
27
- ## 💡 Why Repo Intelligence Agent?
28
 
29
- Navigating modern software repositories introduces significant friction for engineering teams:
30
- - **Massive Codebases:** Sprawling modules make finding where logic resides an uphill battle.
31
- - **Onboarding Difficulty:** New contributors struggle to find the right entry points and module flows.
32
- - **Issue Localization:** Mapping a GitHub bug description to the exact files and functions requires deep tribal knowledge.
33
- - **Architecture Discovery:** Static documentation goes stale; understanding active dependency graphs is slow.
34
- - **Change Impact Uncertainty:** Modifying a utility file risks breaking unexpected downstream dependents.
35
 
36
- **Repo Intelligence Agent** solves these problems by creating a local, semantic index of the repository. It parses abstract syntax trees (ASTs), extracts code dependencies, builds a directed graph of module relationships, and utilizes the DeepSeek LLM to answer developer queries, map bugs to code fixes, and visualize change propagation—completely grounded in your source code.
 
 
 
37
 
38
- ---
 
 
39
 
40
- ## ✨ Key Features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- - **Repository Analysis:** Direct cloning, language detection, overlapping chunking, and local vector indexing.
43
- - **Repository Chat:** Conversational Q&A over the codebase with streaming token responses (SSE) and strict source citations.
44
- - **Architecture Builder:** Multi-language AST parsing (using Tree-sitter) and NetworkX dependency graph mapping.
45
- - **Issue Mapper:** Automatically matches bug report text to target files, generating a clear step-by-step implementation plan.
46
- - **Reading Order:** Generates an optimized file-reading list sorted by module centrality to accelerate developer onboarding.
47
- - **Impact Analysis:** Traverses directed dependency graphs to predict which files will be affected by a proposed change.
48
- - **Confidence Layer:** Evaluates responses for hallucination, returning confidence scores and warning flags.
49
- - **Grounded Fallback Mode:** Falls back to keyword-based component mapping and chunk-grounded steps during LLM rate limits.
50
 
51
  ---
52
 
53
- ## 🎬 Demo
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- ### Codebase Indexing & Analysis
56
- ![Analysis Demo](docs/images/analyze-demo.gif)
 
 
57
 
58
- ### Conversational Repository Chat
59
- ![Chat Demo](docs/images/chat-demo.gif)
 
 
 
 
60
 
61
- ### Issue Mapping & Code Planning
62
- ![Issue Mapper Demo](docs/images/issue-mapper-demo.gif)
63
 
64
  ---
65
 
66
  ## 🏗️ System Architecture
67
 
 
 
68
  ```mermaid
69
  graph TD
70
- UI[Astro 4 + React Dashboard] -- HTTP / SSE --> API[FastAPI Gateway]
71
 
72
- subgraph Engine [Code Processing Engine]
73
  API --> GH[GitHub Clone & Extractor]
74
- API --> CC[Code Chunking 1500 chars]
 
75
  API --> ES[Local BGE Embedding Service]
76
- API --> CS[Chroma Vector Store]
77
- API --> AS[Tree-sitter AST Graph Builder]
78
  end
79
 
80
- subgraph Memory [Data Layer]
81
- CS --> DB[(ChromaDB data/chroma_db)]
82
- AS --> JSON[(Architecture JSON data/)]
83
- API --> CACHE[(In-Memory Cache)]
 
84
  end
85
 
86
  subgraph AI [LLM Reasoning Layer]
87
  API --> NIM[NVIDIA NIM DeepSeek V4 Flash]
88
  end
89
- ```
90
-
91
- ### Data Processing Flow
92
- ```
93
- GitHub Repository
94
- │
95
- ▼ [Clone & Extract]
96
- Source Code Files
97
- │
98
- ▼ [CodeChunker: 1500 chars / 200 overlap]
99
- Text Chunks
100
- │
101
- ▼ [EmbeddingService: BGE Small]
102
- 384-dimensional Vectors
103
- │
104
- ▼ [Index Repository]
105
- ChromaDB Vector Database ──► Semantic Retrieval ──► DeepSeek LLM ──► Grounded Response
106
  ```
107
 
108
  ---
@@ -110,71 +132,22 @@ ChromaDB Vector Database ──► Semantic Retrieval ──► DeepSeek LLM ─
110
  ## 🛠️ Technology Stack
111
 
112
  | Layer | Component | Notes |
113
- |---|---|---|
114
- | **Backend** | `FastAPI` + `Uvicorn` | Asynchronous routes, SSE streaming |
115
- | **Frontend** | `Astro 4` + `React` | Component hydration, React Flow graph rendering |
116
- | **Vector Store** | `ChromaDB` | Persistent, namespaces partitioned by repository |
117
- | **Embeddings** | `BAAI/bge-small-en-v1.5` | 384-dim local sentence-transformers (no API calls) |
118
- | **LLM Provider** | `DeepSeek V4 Flash` | Hosted on NVIDIA NIM (OpenAI-compatible) |
119
- | **AST Parser** | `Tree-sitter` | Multi-language syntax trees parsing |
120
- | **Graph Engine** | `NetworkX` | Node betweenness centrality calculations |
121
-
122
- ---
123
-
124
- ## 🚀 Installation & Local Development
125
-
126
- ### 1. Backend Server Setup
127
- Ensure Python 3.10+ is installed:
128
- ```bash
129
- # Set up virtual environment
130
- python -m venv .venv
131
- source .venv/bin/activate # Or .venv\Scripts\activate on Windows
132
-
133
- # Install dependencies
134
- pip install -r requirements.txt
135
-
136
- # Create environmental configuration
137
- cp .env.example .env
138
- # Open .env and add your DEEPSEEK_API_KEY from build.nvidia.com
139
-
140
- # Start the API server
141
- python -m uvicorn backend.api:app --host 127.0.0.1 --port 8000 --reload
142
- ```
143
-
144
- ### 2. Frontend Setup
145
- Ensure Node.js 18+ is installed:
146
- ```bash
147
- cd frontend
148
- npm install
149
- npm run dev
150
- ```
151
-
152
- ---
153
-
154
- ## 📊 Performance & Validation
155
-
156
- Validation was conducted on the **Ankita15k/GitNest** repository (328 files, 1,549 chunks, 1,440 dependency edges):
157
- - **Cloning & Indexing Success:** All source files embedded and stored in ChromaDB in ~28s.
158
- - **Graph Builder Centrality:** Accurately extracted 17 application entry points and mapped high-coupling module hotspots.
159
- - **Issue Mapper Recall:** Correctly localized file targets for sample bugs without returning hallucinated paths.
160
-
161
- Refer to the complete [Validation Report](docs/VALIDATION_REPORT.md) for detailed telemetry.
162
 
163
  ---
164
 
165
  ## 🛑 Known Limitations
166
 
167
- - **In-Memory Store:** The active repository session cache resets when the FastAPI backend process restarts.
168
- - **CPU Bottleneck:** Embedded generation runs locally; CPU processing can take ~2–3 minutes for large repositories.
169
- - **API Limits:** Free NVIDIA NIM keys are capped at ~3 requests per minute (automatically triggers fallback mode when exceeded).
170
-
171
- ---
172
-
173
- ## 🗺️ Roadmap
174
-
175
- - **Near-Term:** Persistent SQLite database for processed repository lists, JWT API authentication middleware.
176
- - **Medium-Term:** BM25 keyword matching + vector hybrid search, multi-repository comparing queries.
177
- - **Long-Term:** CI/CD actions for PR risk scoring, VS Code IDE extension.
178
 
179
  ---
180
 
 
2
 
3
  <p align="center">
4
  <img src="https://img.shields.io/badge/Backend-FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white" alt="FastAPI"/>
5
+ <img src="https://img.shields.io/badge/Frontend-Astro%204%20%2B%20React-FF5D01?style=for-the-badge&logo=astro&logoColor=white" alt="Astro + React"/>
6
+ <img src="https://img.shields.io/badge/LLM-DeepSeek%20V4%20Flash-6C5CE7?style=for-the-badge" alt="DeepSeek V4 Flash"/>
7
  <img src="https://img.shields.io/badge/Inference-NVIDIA%20NIM-76B900?style=for-the-badge&logo=nvidia&logoColor=white" alt="NVIDIA NIM"/>
8
  <img src="https://img.shields.io/badge/Vector%20DB-ChromaDB-blue?style=for-the-badge" alt="ChromaDB"/>
9
+ <img src="https://img.shields.io/badge/AST%20Parser-Tree--sitter-black?style=for-the-badge" alt="Tree-sitter"/>
 
10
  </p>
11
 
12
  <p align="center">
13
+ <strong>An advanced codebase intelligence platform that combines AST structure extraction, dependency graph modeling, NetworkX centrality analysis, and LLM reasoning to map codebase architectures, onboarding reading orders, issue implementation plans, and change impact boundaries.</strong>
14
  </p>
15
 
16
  <p align="center">
17
+ <a href="#-why-this-is-different-from-typical-repo-chatbots">Why This Is Different</a> •
18
+ <a href="#-key-differentiators">Key Differentiators</a> •
19
  <a href="#-system-architecture">System Architecture</a> •
20
+ <a href="ARCHITECTURE.md">Architecture Specification</a> •
21
+ <a href="docs/DEVELOPMENT_SETUP.md">Development Setup</a> •
22
+ <a href="docs/VALIDATION_REPORT.md">Validation & Telemetry</a> •
23
+ <a href="RELEASE_NOTES.md">Release Notes</a>
24
  </p>
25
 
26
  ---
27
 
28
+ ## ⚡ Why this is different from typical Repo Chatbots
29
 
30
+ Traditional codebase assistants use a straightforward, unstructured approach:
 
 
 
 
 
31
 
32
+ ```
33
+ Traditional RAG Chatbot:
34
+ Repo ──► Text Splitting ──► Vector Embeddings ──► Similarity Search ──► LLM Prompt
35
+ ```
36
 
37
+ This model is blind to code structure, import inheritance, code coupling, and execution entry points. It often hallucinates dependencies, misses downstream side-effects, and generates unstructured code recommendations.
38
+
39
+ **Repo Intelligence Agent** addresses these gaps by implementing a structural, graph-augmented retrieval and reasoning architecture:
40
 
41
+ ```
42
+ Repo Intelligence Agent Ingestion & Retrieval Pipeline:
43
+ Repo
44
+ ├──► [Tree-sitter AST Parser] ──► Structural Declarations (Imports/Exports/Classes/Methods)
45
+ │ │
46
+ │ ▼
47
+ │ [NetworkX DiGraph Mapping]
48
+ │ │
49
+ ├───────────────────────────────────────────┼──────────────────────────────────────────┐
50
+ ▼ ▼ ▼
51
+ [ChromaDB Vector Store] [Centrality Analytics] [BFS Graph Traversals]
52
+ (1500 chars / 200 overlap) (Suggested Reading Orders) (Change Impact Analysis)
53
+ ��� │ │
54
+ ▼ ▼ ▼
55
+ Vector Snippets ───────────────► Architectural Graph Context ────────────────► Propagation Risk Scoring
56
+ │ │ │
57
+ └───────────────────────────────────────────┴──────────────────────────────────────────┘
58
+ │
59
+ ▼
60
+ [DeepSeek V4 Flash Reasoning]
61
+ │
62
+ ▼
63
+ Grounded Code Intelligence
64
+ ```
65
 
66
+ By enriching semantic vector retrieval with explicit import graphs, centrality indicators, and structural declarations, the system accurately maps codebase relationships, onboarding order, implementation dependencies, and change impacts without hallucinating non-existent modules.
 
 
 
 
 
 
 
67
 
68
  ---
69
 
70
+ ## ✨ Key Differentiators
71
+
72
+ ### 1. Unified Repository Workspace
73
+ The frontend layout organizes repository navigation into a unified tabbed dashboard workspace:
74
+ - **Codebase Analysis:** Summary details, tech stack, and primary package declarations.
75
+ - **Architecture Graph:** A React Flow-rendered, interactive node-link import graph of source files.
76
+ - **Reading Path:** An ordered timeline sequence for codebase onboarding.
77
+ - **Impact Analysis:** Interactive scenario inputs predicting file risk spreads.
78
+ - **Issue Intelligence:** Step-by-step implementation guide generation.
79
+ - **Chat:** Conversational context-grounded Q&A interface.
80
+
81
+ The parent workspace component maintains a **shared repository session** in React state. Tab navigation is instantaneous, drawing from cached metadata with **no re-analysis** required when switching between views.
82
 
83
+ ### 2. Session Store & Context Sync
84
+ Active repository sessions are synchronized across components and browser tabs.
85
+ - **Persisted (localStorage):** `owner`, `repo`, `indexing status`, `graph status`, and `last analyzed timestamp`. This lets the application recover context immediately if the user reloads or navigates away.
86
+ - **Non-persisted (in-memory state):** Large dependency graph payloads, tree nodes, and transient UI states. This division prevents exceeding localStorage capacity limits (typically 5MB) while keeping load latency low.
87
 
88
+ ### 3. Chat Resilience Layer
89
+ To prevent rate-limit interruptions (common on free-tier NVIDIA NIM keys), the system has an **automated grounded fallback mode**. When a rate limit exception (HTTP 429), network timeout, or provider failure occurs:
90
+ - The system intercepts the exception.
91
+ - It extracts local text snippets from the top similarity chunks retrieved from ChromaDB.
92
+ - It infers affected components using file path heuristics.
93
+ - It returns a structured, retrieval-grounded fallback response directly to the chat window with cited sources and confidence scores, ensuring no raw back-end exceptions reach the developer.
94
 
95
+ ### 4. Analysis Persistence Lifecycle
96
+ Computed ingestion analyses are saved directly to `data/analysis_store.json`. Upon application startup, the backend hydrates this store, allowing full repository workspace recovery and details retrieval without having to rebuild graphs or regenerate embeddings.
97
 
98
  ---
99
 
100
  ## 🏗️ System Architecture
101
 
102
+ The following diagram illustrates the relationship between the client dashboard, the API gateway, local vector stores, and the DeepSeek inference layer:
103
+
104
  ```mermaid
105
  graph TD
106
+ UI[Astro 4 + React Dashboard] -- HTTP / SSE / React Flow --> API[FastAPI Gateway Port 8001]
107
 
108
+ subgraph Processing [Code Ingestion & Parsing]
109
  API --> GH[GitHub Clone & Extractor]
110
+ API --> TS[Tree-sitter AST Parser]
111
+ API --> CC[Code Chunking 1500 chars / 200 overlap]
112
  API --> ES[Local BGE Embedding Service]
 
 
113
  end
114
 
115
+ subgraph Memory [Local Data Layer]
116
+ ES --> Chroma[(ChromaDB Vector Store)]
117
+ TS --> NX[NetworkX Graph Builder]
118
+ NX --> DB_Graph[(Persisted Graphs data/architecture/)]
119
+ API --> DB_Store[(Analysis Cache data/analysis_store.json)]
120
  end
121
 
122
  subgraph AI [LLM Reasoning Layer]
123
  API --> NIM[NVIDIA NIM DeepSeek V4 Flash]
124
  end
125
+
126
+ Chroma --> API
127
+ DB_Graph --> API
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  ```
129
 
130
  ---
 
132
  ## 🛠️ Technology Stack
133
 
134
  | Layer | Component | Notes |
135
+ | :--- | :--- | :--- |
136
+ | **Backend** | `FastAPI` + `Uvicorn` | Asynchronous routers, SSE streams, binds to port **8001** |
137
+ | **Frontend** | `Astro 4` + `React` | Dynamic UI hydration, React Flow graph rendering |
138
+ | **Vector DB** | `ChromaDB` | Persistent code chunk database, partitioned via `repo_name` |
139
+ | **Embeddings** | `BAAI/bge-small-en-v1.5` | Local SentenceTransformers generating 384-dimensional vectors |
140
+ | **LLM Provider** | `DeepSeek V4 Flash` | Inference provider served via OpenAI-compatible NVIDIA NIM |
141
+ | **AST Parser** | `Tree-sitter` | Lazy-loaded language parsers (Python, JS, TS, JSX, TSX) |
142
+ | **Graph Calculations** | `NetworkX` | Directed dependency trees, BFS sweeps, composite centrality |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  ---
145
 
146
  ## 🛑 Known Limitations
147
 
148
+ - **CPU Ingestion Latency:** Locally executing SentenceTransformer embeddings on CPU takes approximately 2–3 minutes per 1,500 chunks.
149
+ - **NVIDIA NIM Rate Limits:** Free developer keys are capped at ~3 requests/minute. The system handles this with its automated fallback mode.
150
+ - **Stubbed Infrastructure:** `SQLiteStore` (`memory/sqlite_store.py`), `MCPService` (`services/mcp_service.py`), and `RepositoryAnalyzer` (`agents/analyzer.py`) are design stubs. Inlined routes and JSON storage cache operations handle these functions in the MVP.
 
 
 
 
 
 
 
 
151
 
152
  ---
153
 
RELEASE_NOTES.md CHANGED
@@ -4,32 +4,25 @@ We are proud to announce the initial release of the **Repo Intelligence Agent**
4
 
5
  ---
6
 
7
- ## 🚀 Key Features Included
8
 
9
- - **Full-Pipeline Repository Analysis:** Clones, chunks, embeds, and indexes code codebases, concluding with a comprehensive architecture summary via DeepSeek.
10
- - **Conversational Repository Chat:** Real-time token streaming (SSE) with chat history, local context retrieval, and automatic hallucination scoring.
11
- - **Grounded Issue Mapper:** Maps raw issue texts directly to target code files, generating structured step-by-step implementation plans.
12
- - **Dependency Graph Mapping:** Extracts file dependency structures via AST Tree-sitter parsers and maps module coupling, entry points, and hotspots.
13
- - **Contributor Onboarding Paths:** Generates recommended reading orders based on module NetworkX centrality.
14
- - **Change Impact Analysis:** Predicts downstream file dependencies affected by prospective code changes.
 
15
 
16
  ---
17
 
18
- ## 🛠️ Major Bug Fixes & Hardening
19
 
20
- - **DeepSeek & NVIDIA NIM Integration:** Swapped Gemini providers for OpenAI-compatible DeepSeek V4 Flash configurations served via NVIDIA NIM.
21
- - **ChromaDB Vector Integrity:** Fixed dimension mismatch issues on Chroma collection writes by enforcing static 384-dimensional vector schemas.
22
- - **Entry Point Over-Detection:** Patched the entry point detection engine. The parser now excludes tests, docs, and example folders, reducing entry points count on frameworks from ~500 down to the core 17 targets.
23
- - **Issue Mapper Cache Poisoning:** Patched cache collision vulnerability by migrating the memory layer to a `v2:repo:hash` key format, ensuring stale plans do not bleed into current issue mappings.
24
- - **Grounded Fallback Mode:** Added an automatic fallback mechanism for Issue Mapper calls during LLM quota exhaustion, allowing keyword-based component mapping and chunk-content-grounded step generation without crashing.
25
-
26
- ---
27
-
28
- ## 📊 Verification & Validation
29
-
30
- MVP validation was executed against the **Ankita15k/GitNest** repository:
31
- - **328 files** successfully parsed and mapped.
32
- - **1,549 code chunks** stored and embedded.
33
- - **1,440 dependency edges** mapped.
34
- - Zero code plan hallucinations or path discrepancies observed.
35
- - Backend test coverage exceeding **85%** across all central services.
 
4
 
5
  ---
6
 
7
+ ## 🚀 Key Features Added
8
 
9
+ - **Unified Repository Workspace:** Dashboard layout integrating Codebase Analysis, Architecture Graph, Reading Path, Impact Analysis, Issue Intelligence, and Chat in a single session.
10
+ - **Session Persistence:** Synchronizes active repository context using browser `localStorage` and hydrates repository ingestion lists from `data/analysis_store.json` at startup.
11
+ - **Chat Fallback Mode:** Implements an automated local RAG fallback layer that intercepts LLM timeouts, network failures, or HTTP 429 quota exhaustion on NVIDIA NIM, providing cited local context without throwing errors.
12
+ - **Architecture Graph:** Uses Tree-sitter and NetworkX to parse syntax imports and export a React Flow-compatible JSON layout.
13
+ - **Reading Order:** Generates a structured developer onboarding sequence by ranking files topologically using NetworkX normalized centrality models.
14
+ - **Impact Analysis:** Walks forward/reverse dependency paths (depth = 4) and performs component matching to assess code modification risk levels.
15
+ - **Issue Mapper:** Automatically maps GitHub issue text to files and compiles step-by-step plans under a budget of exactly two LLM completions.
16
 
17
  ---
18
 
19
+ ## 🛠️ Issues & Bug Fixes Resolved
20
 
21
+ - **Current Workspace session bug:** Resolved page reload/navigation resets by storing active context in `localStorage` and sharing it via parent workspace state, eliminating the need to re-analyze when switching tabs.
22
+ - **Architecture graph generation:** Patched the AST parsing engine to output empty checks cleanly, enabling the UI to render React Flow graphs or 404 warnings without locking up.
23
+ - **Graph persistence:** Standardized atomic writes to `data/architecture/` and `data/analysis_store.json` on successful analysis.
24
+ - **Stale issue cache:** Migrated the caching key to `f"{repo_name}:v2:{issue_hash}"`, preventing cross-repository plan bleeding.
25
+ - **Chroma dimension mismatch:** Aligned collection indexes to a static 384-dimensional schema matching local `BAAI/bge-small-en-v1.5` embeddings.
26
+ - **NVIDIA NIM model configuration:** Corrected parameters to map OpenAI-compatible calls to `deepseek-ai/deepseek-v4-flash` via `https://integrate.api.nvidia.com/v1`.
27
+ - **WatchFiles reload issue:** Configured `uvicorn` in `backend/main.py` to monitor source directories specifically (`_RELOAD_DIRS = ["backend", "services", "agents", "memory", "models"]`). This stops Uvicorn from scanning `data/` or cloning directories, preventing reload loops that terminate active requests.
28
+ - **API Port Parity:** Configured the application to default consistently to port **8001** for development environments, resolving mismatches between testing tools and gateway servers.
 
 
 
 
 
 
 
 
backend/api.py CHANGED
@@ -22,7 +22,7 @@ from typing import Dict, List, Any, Optional
22
  from fastapi import FastAPI, HTTPException, Request, Query
23
  from fastapi.middleware.cors import CORSMiddleware
24
  from fastapi.responses import StreamingResponse
25
- from pydantic import BaseModel, Field
26
 
27
  # Ensure parent directory is in sys.path to import project modules
28
  sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -48,6 +48,7 @@ from services.graph_service import GraphService
48
  from services.reading_order_service import ReadingOrderService
49
  from services.impact_analysis_service import ImpactAnalysisService
50
  from services.arch_context_service import ArchContextService
 
51
  from services.llm import ProviderFactory
52
  from memory.chroma_store import ChromaStore
53
 
@@ -83,9 +84,112 @@ def health():
83
 
84
 
85
  # ---------------------------------------------------------------------------
86
- # In-memory session store for analyzed repositories
87
  # ---------------------------------------------------------------------------
88
  ANALYSIS_STORE: Dict[str, Dict[str, Any]] = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
  # ---------------------------------------------------------------------------
91
  # Core service singletons
@@ -141,6 +245,17 @@ class ChatRequest(BaseModel):
141
  default_factory=list, description="Conversation history"
142
  )
143
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  class ArchitectureBuildRequest(BaseModel):
146
  repo: str = Field(..., description="Repository identifier (owner/repo)")
@@ -320,15 +435,23 @@ async def get_examples():
320
  @app.get("/api/repos/recent")
321
  async def get_recent():
322
  """Fetch list of recently analysed repositories from the in-memory store."""
323
- return [
324
- {
 
 
 
 
 
 
 
 
 
325
  "name": name,
326
  "url": f"https://github.com/{name}",
327
- "tech_stack": data["analysis"].tech_stack,
328
  "analyzed_at": "Just now",
329
- }
330
- for name, data in ANALYSIS_STORE.items()
331
- ]
332
 
333
 
334
  @app.post("/api/index")
@@ -478,9 +601,17 @@ async def analyze_repository(request: AnalyzeRequest):
478
  yield f"data: {json.dumps({'status': 'chroma', 'message': 'END: chroma indexing'})}\n\n"
479
 
480
  yield f"data: {json.dumps({'status': 'building_graph', 'message': 'Building file dependency graph...'})}\n\n"
481
- await asyncio.to_thread(
482
- architecture_service.build, repo_name, local_path, None, False
 
 
 
 
 
 
 
483
  )
 
484
 
485
  # Architecture summary via DeepSeek
486
  architecture_summary = await generate_architecture_summary_with_llm(
@@ -512,6 +643,9 @@ async def analyze_repository(request: AnalyzeRequest):
512
  "architecture": architecture_summary,
513
  }
514
 
 
 
 
515
  yield f"data: {json.dumps({'status': 'complete', 'message': '✓ Repository analysis complete!'})}\n\n"
516
  yield f"data: {json.dumps({'status': 'done', 'repo': repo_name})}\n\n"
517
 
@@ -580,78 +714,123 @@ async def map_issue(request: IssueMapRequest):
580
 
581
  @app.post("/api/chat")
582
  async def repository_chat(request: ChatRequest):
583
- """Chat with repository context, streaming back token results via SSE."""
584
- repo_name = request.repo.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
585
  question = request.message.strip()
586
  history = request.history
587
 
588
  async def chat_streamer():
 
 
 
 
 
 
589
  try:
590
- # 1. Embed query (local BGE — no API call)
591
  query_embedding = embedding_service.generate_embedding(question)
 
 
 
 
592
 
593
- # 2. Retrieve relevant chunks from ChromaDB
 
594
  chunks = chroma_store.search_repository(
595
  repo_name=repo_name,
596
  query_embedding=query_embedding,
597
  limit=5,
598
  )
599
-
600
- context_blocks = []
601
- sources: set = set()
602
- for idx, chunk in enumerate(chunks):
603
- meta = chunk.get("metadata", {})
604
- file_path = meta.get("file_path", "unknown")
605
- sources.add(file_path)
606
- context_blocks.append(
607
- f"--- File: {file_path} ---\n{chunk.get('content', '')}"
608
- )
 
 
 
 
 
 
 
609
  context_str = (
610
- "\n\n".join(context_blocks) if context_blocks else "No relevant context found."
 
 
611
  )
612
 
613
- # 3. System instruction
614
- system_instruction = (
615
- "You are a Repository Intelligence Agent, an expert developer assistant. "
616
- "You are helping a developer understand a repository's codebase. "
617
- "Answer the developer's question using the provided codebase context and conversation history. "
618
- "Be detailed, citing the source files where relevant. "
619
- "Keep code snippets accurate."
620
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
- # 4. Build conversation history in OpenAI format
623
- # Normalise Gemini-style turns {"role":"model","parts":["..."]} → {"role":"assistant","content":"..."}
624
- normalised_history = []
625
- for turn in history:
626
- role = turn.get("role", "user")
627
- if role == "model":
628
- role = "assistant"
629
- parts = turn.get("parts", [])
630
- if parts:
631
- content = parts[0] if isinstance(parts[0], str) else str(parts[0])
632
- elif "content" in turn:
633
- content = turn["content"]
634
- else:
635
- continue
636
- normalised_history.append({"role": role, "content": content})
637
-
638
- # 5. Architecture context injection
639
- arch_ctx = arch_context_service.get_context(repo_name)
640
- arch_block = arch_ctx.to_prompt_block()
641
- arch_section = f"\n{arch_block}\n" if arch_block else ""
642
-
643
- prompt = (
644
- f"{arch_section}"
645
- f"Context from repository:\n"
646
- f"=======================\n"
647
- f"{context_str}\n"
648
- f"=======================\n\n"
649
- f"Question: {question}"
650
- )
651
 
652
- # 6. Stream from DeepSeek
653
- provider = ProviderFactory.get_provider()
654
- full_text = ""
 
 
 
655
  async for token in provider.stream(
656
  prompt=prompt,
657
  system_instruction=system_instruction,
@@ -660,7 +839,33 @@ async def repository_chat(request: ChatRequest):
660
  full_text += token
661
  yield f"data: {json.dumps({'text': token})}\n\n"
662
 
663
- # 7. Evaluate the full streamed response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
  try:
665
  evaluator = EvaluationAgent()
666
  eval_result = evaluator.evaluate_response(question, full_text, chunks)
@@ -669,12 +874,7 @@ async def repository_chat(request: ChatRequest):
669
  logger.error("Chat evaluation failed: %s", eval_err)
670
  confidence_pct = 85
671
 
672
- yield f"data: {json.dumps({'sources': sorted(list(sources)), 'confidence': confidence_pct, 'status': 'done'})}\n\n"
673
-
674
- except Exception as e:
675
- logger.error("Chat streaming error: %s", e, exc_info=True)
676
- yield f"data: {json.dumps({'text': f'Error in agent system: {str(e)}'})}\n\n"
677
- yield f"data: {json.dumps({'status': 'done'})}\n\n"
678
 
679
  return StreamingResponse(chat_streamer(), media_type="text/event-stream")
680
 
@@ -779,12 +979,23 @@ async def get_architecture_graph(owner: str, repo_name: str, q: Optional[str] =
779
  status_code=404,
780
  detail=(
781
  f"No dependency graph found for '{full_name}'. "
782
- "Please run POST /api/architecture/build first."
783
  ),
784
  )
785
  graph_data = await asyncio.to_thread(
786
  graph_service.get_visualization_graph, full_name, architecture_service, q
787
  )
 
 
 
 
 
 
 
 
 
 
 
788
  return graph_data
789
  except HTTPException:
790
  raise
@@ -795,4 +1006,10 @@ async def get_architecture_graph(owner: str, repo_name: str, q: Optional[str] =
795
 
796
  if __name__ == "__main__":
797
  import uvicorn
798
- uvicorn.run("api:app", host="127.0.0.1", port=8000, reload=True)
 
 
 
 
 
 
 
22
  from fastapi import FastAPI, HTTPException, Request, Query
23
  from fastapi.middleware.cors import CORSMiddleware
24
  from fastapi.responses import StreamingResponse
25
+ from pydantic import BaseModel, Field, field_validator
26
 
27
  # Ensure parent directory is in sys.path to import project modules
28
  sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 
48
  from services.reading_order_service import ReadingOrderService
49
  from services.impact_analysis_service import ImpactAnalysisService
50
  from services.arch_context_service import ArchContextService
51
+ from services.chat_fallback import build_fallback_answer, is_provider_error
52
  from services.llm import ProviderFactory
53
  from memory.chroma_store import ChromaStore
54
 
 
84
 
85
 
86
  # ---------------------------------------------------------------------------
87
+ # Analysis store — persisted to disk so data survives server restarts
88
  # ---------------------------------------------------------------------------
89
  ANALYSIS_STORE: Dict[str, Dict[str, Any]] = {}
90
+ _ANALYSIS_STORE_PATH = os.path.join("data", "analysis_store.json")
91
+ _persist_lock = asyncio.Lock()
92
+
93
+
94
+ def _load_analysis_store() -> None:
95
+ """Load persisted analysis data from disk into ANALYSIS_STORE on startup.
96
+
97
+ Reconstructs Pydantic models from the stored JSON dicts. Any entry that
98
+ fails to deserialise is skipped (logged at WARNING) so a single corrupt
99
+ record never blocks the entire store from loading.
100
+ """
101
+ global ANALYSIS_STORE
102
+ if not os.path.exists(_ANALYSIS_STORE_PATH):
103
+ return
104
+ try:
105
+ with open(_ANALYSIS_STORE_PATH, "r", encoding="utf-8") as fh:
106
+ raw: Dict[str, Any] = json.load(fh)
107
+ except Exception as exc:
108
+ logger.warning("Could not read analysis store from disk: %s", exc)
109
+ return
110
+
111
+ loaded = 0
112
+ for repo_name, entry in raw.items():
113
+ try:
114
+ analysis_data = RepositoryAnalysis.model_validate(entry["analysis"])
115
+ arch_raw = entry["architecture"]
116
+ # Reconstruct nested ComponentRelationship objects
117
+ relationships = [
118
+ ComponentRelationship(**r) for r in arch_raw.get("relationships", [])
119
+ ]
120
+ architecture_data = ArchitectureSummary(
121
+ summary=arch_raw.get("summary", ""),
122
+ reading_order=arch_raw.get("reading_order", []),
123
+ relationships=relationships,
124
+ )
125
+ ANALYSIS_STORE[repo_name] = {
126
+ "analysis": analysis_data,
127
+ "architecture": architecture_data,
128
+ }
129
+ loaded += 1
130
+ except Exception as exc:
131
+ logger.warning(
132
+ "Skipping malformed analysis store entry for '%s': %s", repo_name, exc
133
+ )
134
+
135
+ if loaded:
136
+ logger.info(
137
+ "Loaded %d repository entries from analysis store (%s).",
138
+ loaded,
139
+ _ANALYSIS_STORE_PATH,
140
+ )
141
+
142
+
143
+ def _serialise_store() -> Dict[str, Any]:
144
+ """Serialise ANALYSIS_STORE to a plain JSON-safe dict."""
145
+ out: Dict[str, Any] = {}
146
+ for repo_name, entry in ANALYSIS_STORE.items():
147
+ try:
148
+ analysis_obj = entry["analysis"]
149
+ arch_obj = entry["architecture"]
150
+ out[repo_name] = {
151
+ "analysis": (
152
+ analysis_obj.model_dump()
153
+ if hasattr(analysis_obj, "model_dump")
154
+ else analysis_obj
155
+ ),
156
+ "architecture": (
157
+ arch_obj.model_dump()
158
+ if hasattr(arch_obj, "model_dump")
159
+ else arch_obj
160
+ ),
161
+ }
162
+ except Exception as exc:
163
+ logger.warning("Could not serialise store entry for '%s': %s", repo_name, exc)
164
+ return out
165
+
166
+
167
+ async def _persist_analysis_store() -> None:
168
+ """Write ANALYSIS_STORE to disk atomically (tmp file → rename).
169
+
170
+ Uses an asyncio Lock to prevent concurrent writes from corrupting the file.
171
+ Runs the blocking I/O in a thread so the event loop is not blocked.
172
+ """
173
+ async with _persist_lock:
174
+ try:
175
+ payload = _serialise_store()
176
+ await asyncio.to_thread(_write_store_atomic, payload)
177
+ logger.debug("Analysis store persisted (%d entries).", len(payload))
178
+ except Exception as exc:
179
+ logger.error("Failed to persist analysis store: %s", exc, exc_info=True)
180
+
181
+
182
+ def _write_store_atomic(payload: Dict[str, Any]) -> None:
183
+ """Write payload to _ANALYSIS_STORE_PATH via a tmp file + rename (atomic)."""
184
+ os.makedirs(os.path.dirname(_ANALYSIS_STORE_PATH), exist_ok=True)
185
+ tmp_path = _ANALYSIS_STORE_PATH + ".tmp"
186
+ with open(tmp_path, "w", encoding="utf-8") as fh:
187
+ json.dump(payload, fh, indent=2)
188
+ os.replace(tmp_path, _ANALYSIS_STORE_PATH)
189
+
190
+
191
+ # Populate ANALYSIS_STORE from disk before the first request is served
192
+ _load_analysis_store()
193
 
194
  # ---------------------------------------------------------------------------
195
  # Core service singletons
 
245
  default_factory=list, description="Conversation history"
246
  )
247
 
248
+ @field_validator("repo")
249
+ @classmethod
250
+ def repo_must_not_be_empty(cls, v: str) -> str:
251
+ stripped = v.strip()
252
+ if not stripped:
253
+ raise ValueError(
254
+ "repo must not be empty. "
255
+ "Please open a repository from the analysis page first."
256
+ )
257
+ return stripped
258
+
259
 
260
  class ArchitectureBuildRequest(BaseModel):
261
  repo: str = Field(..., description="Repository identifier (owner/repo)")
 
435
  @app.get("/api/repos/recent")
436
  async def get_recent():
437
  """Fetch list of recently analysed repositories from the in-memory store."""
438
+ results = []
439
+ for name, data in ANALYSIS_STORE.items():
440
+ analysis_obj = data.get("analysis")
441
+ # Support both Pydantic model and plain dict (defensive)
442
+ if hasattr(analysis_obj, "tech_stack"):
443
+ tech_stack = analysis_obj.tech_stack
444
+ elif isinstance(analysis_obj, dict):
445
+ tech_stack = analysis_obj.get("tech_stack", [])
446
+ else:
447
+ tech_stack = []
448
+ results.append({
449
  "name": name,
450
  "url": f"https://github.com/{name}",
451
+ "tech_stack": tech_stack,
452
  "analyzed_at": "Just now",
453
+ })
454
+ return results
 
455
 
456
 
457
  @app.post("/api/index")
 
601
  yield f"data: {json.dumps({'status': 'chroma', 'message': 'END: chroma indexing'})}\n\n"
602
 
603
  yield f"data: {json.dumps({'status': 'building_graph', 'message': 'Building file dependency graph...'})}\n\n"
604
+ arch_build_result = await asyncio.to_thread(
605
+ architecture_service.build, repo_name, local_path, files, False
606
+ )
607
+ _graph_files = arch_build_result.get("files_parsed", 0)
608
+ _graph_edges = arch_build_result.get("dependencies_found", 0)
609
+ _graph_entries = arch_build_result.get("entry_points", [])
610
+ logger.info(
611
+ "[PIPELINE:%s] GRAPH built files_parsed=%d edges=%d entry_points=%s",
612
+ repo_name, _graph_files, _graph_edges, _graph_entries,
613
  )
614
+ yield f"data: {json.dumps({'status': 'graph_built', 'message': f'✓ Dependency graph: {_graph_files} files parsed, {_graph_edges} edges', 'files_parsed': _graph_files, 'dependencies_found': _graph_edges, 'entry_points': _graph_entries})}\n\n"
615
 
616
  # Architecture summary via DeepSeek
617
  architecture_summary = await generate_architecture_summary_with_llm(
 
643
  "architecture": architecture_summary,
644
  }
645
 
646
+ # Persist to disk so data survives server restarts
647
+ asyncio.ensure_future(_persist_analysis_store())
648
+
649
  yield f"data: {json.dumps({'status': 'complete', 'message': '✓ Repository analysis complete!'})}\n\n"
650
  yield f"data: {json.dumps({'status': 'done', 'repo': repo_name})}\n\n"
651
 
 
714
 
715
  @app.post("/api/chat")
716
  async def repository_chat(request: ChatRequest):
717
+ """Chat with repository context, streaming back token results via SSE.
718
+
719
+ Pipeline:
720
+ 1. Validate repo name (Pydantic + SSE guard)
721
+ 2. Embed question locally (BGE — no API call)
722
+ 3. Retrieve relevant chunks from ChromaDB
723
+ 4. Inject architecture context
724
+ 5. Stream LLM answer token-by-token
725
+ → On LLM failure: emit retrieval-only fallback (no raw errors to user)
726
+ 6. Emit sources + confidence in terminal done event
727
+ (EvaluationAgent skipped in fallback mode to avoid a second LLM call
728
+ during a rate-limit window)
729
+ """
730
+ # repo field is pre-validated and stripped by ChatRequest.repo_must_not_be_empty
731
+ repo_name = request.repo
732
  question = request.message.strip()
733
  history = request.history
734
 
735
  async def chat_streamer():
736
+ # ── SSE-level guard (defence-in-depth for any call path that bypasses Pydantic) ──
737
+ if not repo_name:
738
+ yield f"data: {json.dumps({'error': 'no_repo_selected', 'message': 'No repository selected. Please open a repository from the analysis page first.', 'status': 'done'})}\n\n"
739
+ return
740
+
741
+ # ── 1. Embed query (local BGE — never fails due to API quota) ──────────
742
  try:
 
743
  query_embedding = embedding_service.generate_embedding(question)
744
+ except Exception as embed_exc:
745
+ logger.error("Embedding failed: %s", embed_exc, exc_info=True)
746
+ yield f"data: {json.dumps({'error': 'embedding_failed', 'message': 'Failed to process your question. Please try again.', 'status': 'done'})}\n\n"
747
+ return
748
 
749
+ # ── 2. Retrieve relevant chunks from ChromaDB ────────────────────────
750
+ try:
751
  chunks = chroma_store.search_repository(
752
  repo_name=repo_name,
753
  query_embedding=query_embedding,
754
  limit=5,
755
  )
756
+ except Exception as chroma_exc:
757
+ logger.error("ChromaDB search failed: %s", chroma_exc, exc_info=True)
758
+ yield f"data: {json.dumps({'error': 'retrieval_failed', 'message': 'Failed to search the repository index. Please try again.', 'status': 'done'})}\n\n"
759
+ return
760
+
761
+ # Build sources set and context string from retrieved chunks
762
+ context_blocks: List[str] = []
763
+ sources: set = set()
764
+ for chunk in chunks:
765
+ meta = chunk.get("metadata", {})
766
+ file_path = meta.get("file_path", "unknown")
767
+ sources.add(file_path)
768
+ context_blocks.append(f"--- File: {file_path} ---\n{chunk.get('content', '')}")
769
+
770
+ if context_blocks:
771
+ context_str = "\n\n".join(context_blocks)
772
+ else:
773
  context_str = (
774
+ "No indexed content was found for this repository and query. "
775
+ "The repository may not be indexed yet, or the question did not "
776
+ "match any stored code chunks."
777
  )
778
 
779
+ # ── 3. Normalise conversation history ────────────────────────────────
780
+ normalised_history = []
781
+ for turn in history:
782
+ role = turn.get("role", "user")
783
+ if role == "model":
784
+ role = "assistant"
785
+ parts = turn.get("parts", [])
786
+ if parts:
787
+ content = parts[0] if isinstance(parts[0], str) else str(parts[0])
788
+ elif "content" in turn:
789
+ content = turn["content"]
790
+ else:
791
+ continue
792
+ normalised_history.append({"role": role, "content": content})
793
+
794
+ # ── 4. Architecture context injection ────────────────────────────────
795
+ arch_ctx = arch_context_service.get_context(repo_name)
796
+ arch_block = arch_ctx.to_prompt_block()
797
+ arch_section = f"\n{arch_block}\n" if arch_block else ""
798
+
799
+ # ── 5. Strictly grounded system instruction ──────────────────────────
800
+ system_instruction = (
801
+ "You are a Repository Intelligence Agent — an expert developer assistant "
802
+ "that answers questions about a specific codebase.\n\n"
803
+ "STRICT RULES:\n"
804
+ "1. Answer ONLY using the repository context provided below. "
805
+ "Do NOT invent file paths, function names, class names, or module structures "
806
+ "that are not present in the retrieved context.\n"
807
+ "2. If the context does not contain enough information to answer, say so "
808
+ "explicitly: state what was found and what was missing, and suggest the user "
809
+ "check specific files if you can infer them from the context.\n"
810
+ "3. You MAY use general programming knowledge to explain language concepts, "
811
+ "framework patterns, or standard library behaviour — but only when it does "
812
+ "NOT require making repository-specific claims beyond the provided context.\n"
813
+ "4. Always cite the specific file path(s) from the context when making "
814
+ "repository-specific statements.\n"
815
+ "5. Keep code snippets accurate — do not paraphrase or reconstruct code "
816
+ "unless it is directly present in the context."
817
+ )
818
 
819
+ prompt = (
820
+ f"{arch_section}"
821
+ f"Context from repository '{repo_name}':\n"
822
+ f"=======================\n"
823
+ f"{context_str}\n"
824
+ f"=======================\n\n"
825
+ f"Question: {question}"
826
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827
 
828
+ # ── 6. Stream from LLM — with retrieval-only fallback on failure ─────
829
+ provider = ProviderFactory.get_provider()
830
+ full_text = ""
831
+ fallback_mode = False
832
+
833
+ try:
834
  async for token in provider.stream(
835
  prompt=prompt,
836
  system_instruction=system_instruction,
 
839
  full_text += token
840
  yield f"data: {json.dumps({'text': token})}\n\n"
841
 
842
+ except Exception as llm_exc:
843
+ logger.warning(
844
+ "LLM provider failed during chat stream for repo '%s': %s",
845
+ repo_name,
846
+ llm_exc,
847
+ )
848
+ fallback_mode = True
849
+
850
+ # If some tokens were already yielded, emit a clear separator
851
+ if full_text:
852
+ separator = (
853
+ "\n\n---\n"
854
+ "⚠️ AI provider became unavailable. "
855
+ "Showing retrieved context below.\n\n"
856
+ )
857
+ yield f"data: {json.dumps({'text': separator})}\n\n"
858
+
859
+ # Emit retrieval-grounded fallback answer
860
+ fallback_text = build_fallback_answer(chunks, question)
861
+ yield f"data: {json.dumps({'text': fallback_text})}\n\n"
862
+
863
+ # ── 7. Confidence scoring and terminal done event ────────────────────
864
+ if fallback_mode:
865
+ # Skip EvaluationAgent — it would make a second LLM call during a
866
+ # rate-limit window, which is exactly wrong.
867
+ confidence_pct = 0
868
+ else:
869
  try:
870
  evaluator = EvaluationAgent()
871
  eval_result = evaluator.evaluate_response(question, full_text, chunks)
 
874
  logger.error("Chat evaluation failed: %s", eval_err)
875
  confidence_pct = 85
876
 
877
+ yield f"data: {json.dumps({'sources': sorted(list(sources)), 'confidence': confidence_pct, 'fallback_mode': fallback_mode, 'status': 'done'})}\n\n"
 
 
 
 
 
878
 
879
  return StreamingResponse(chat_streamer(), media_type="text/event-stream")
880
 
 
979
  status_code=404,
980
  detail=(
981
  f"No dependency graph found for '{full_name}'. "
982
+ "Please analyse the repository first."
983
  ),
984
  )
985
  graph_data = await asyncio.to_thread(
986
  graph_service.get_visualization_graph, full_name, architecture_service, q
987
  )
988
+ # Guard: graph file exists but was built with 0 nodes (empty parse).
989
+ # Return 404 so the frontend shows an actionable error instead of a
990
+ # blank canvas with no explanation.
991
+ if not graph_data.get("nodes"):
992
+ raise HTTPException(
993
+ status_code=404,
994
+ detail=(
995
+ f"Dependency graph for '{full_name}' exists but contains no nodes. "
996
+ "Re-analyse the repository to rebuild the graph with the latest code."
997
+ ),
998
+ )
999
  return graph_data
1000
  except HTTPException:
1001
  raise
 
1006
 
1007
  if __name__ == "__main__":
1008
  import uvicorn
1009
+ uvicorn.run(
1010
+ "backend.api:app",
1011
+ host=os.environ.get("API_SERVER_HOST", "127.0.0.1"),
1012
+ port=int(os.environ.get("API_SERVER_PORT", "8000")),
1013
+ reload=True,
1014
+ reload_dirs=["backend", "services", "agents", "memory", "models"],
1015
+ )
backend/main.py CHANGED
@@ -1,10 +1,26 @@
1
  """Main entry point to run the FastAPI server."""
2
 
 
3
  import uvicorn
4
 
 
 
 
 
 
 
5
  def main():
6
  """Starts the FastAPI backend application."""
7
- uvicorn.run("backend.api:app", host="127.0.0.1", port=8001, reload=True)
 
 
 
 
 
 
 
 
 
8
 
9
  if __name__ == "__main__":
10
  main()
 
1
  """Main entry point to run the FastAPI server."""
2
 
3
+ import os
4
  import uvicorn
5
 
6
+ # Watch ONLY source dirs. Without this, WatchFiles scans the whole project tree
7
+ # (including data/, .venv/, __pycache__/) and triggers a reload whenever a
8
+ # cloned repo writes a file — killing in-flight analysis requests.
9
+ _RELOAD_DIRS = ["backend", "services", "agents", "memory", "models"]
10
+
11
+
12
  def main():
13
  """Starts the FastAPI backend application."""
14
+ host = os.environ.get("API_SERVER_HOST", "127.0.0.1")
15
+ port = int(os.environ.get("API_SERVER_PORT", "8001"))
16
+ uvicorn.run(
17
+ "backend.api:app",
18
+ host=host,
19
+ port=port,
20
+ reload=True,
21
+ reload_dirs=_RELOAD_DIRS,
22
+ )
23
+
24
 
25
  if __name__ == "__main__":
26
  main()
docs/API_REFERENCE.md CHANGED
@@ -1,6 +1,6 @@
1
  # API Reference Specification
2
 
3
- All endpoints are hosted at: `http://localhost:8000`
4
 
5
  ---
6
 
@@ -20,7 +20,7 @@ Retrieve the operational status of the API services and configured LLM providers
20
 
21
  #### Request Example
22
  ```bash
23
- curl http://localhost:8000/health
24
  ```
25
 
26
  #### Response (200 OK)
@@ -42,7 +42,7 @@ Fetch a list of pre-configured example repositories designed for testing.
42
 
43
  #### Request Example
44
  ```bash
45
- curl http://localhost:8000/api/repos/examples
46
  ```
47
 
48
  #### Response (200 OK)
@@ -53,6 +53,18 @@ curl http://localhost:8000/api/repos/examples
53
  "url": "https://github.com/google/guava",
54
  "tech_stack": ["Java", "Maven"],
55
  "description": "Google core libraries for Java."
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
  ]
58
  ```
@@ -60,11 +72,11 @@ curl http://localhost:8000/api/repos/examples
60
  ---
61
 
62
  ### GET /api/repos/recent
63
- Fetch list of repositories processed during the current server session. Note that this collection is kept in-memory and resets on startup.
64
 
65
  #### Request Example
66
  ```bash
67
- curl http://localhost:8000/api/repos/recent
68
  ```
69
 
70
  #### Response (200 OK)
@@ -84,9 +96,13 @@ curl http://localhost:8000/api/repos/recent
84
  ## Repository Processing
85
 
86
  ### POST /api/analyze (SSE Stream)
87
- Trigger a full repository cloning, parsing, indexing, and architecture mapping pipeline. This endpoint streams real-time progress tokens using the `text/event-stream` mime-type.
 
 
 
 
 
88
 
89
- #### Request Schema
90
  ```json
91
  {
92
  "url": "https://github.com/owner/repository",
@@ -97,7 +113,7 @@ Trigger a full repository cloning, parsing, indexing, and architecture mapping p
97
 
98
  #### Request Example
99
  ```bash
100
- curl -N -X POST http://localhost:8000/api/analyze \
101
  -H "Content-Type: application/json" \
102
  -H "Accept: text/event-stream" \
103
  -d '{
@@ -106,7 +122,7 @@ curl -N -X POST http://localhost:8000/api/analyze \
106
  }'
107
  ```
108
 
109
- #### SSE Stream Progression
110
  ```
111
  data: {"status": "cloning", "message": "Cloning repository from GitHub..."}
112
  data: {"status": "cloned", "message": "✓ Repository cloned successfully"}
@@ -119,6 +135,7 @@ data: {"status": "embedding", "message": "END: embeddings"}
119
  data: {"status": "chroma", "message": "START: chroma indexing"}
120
  data: {"status": "chroma", "message": "END: chroma indexing"}
121
  data: {"status": "building_graph", "message": "Building file dependency graph..."}
 
122
  data: {"status": "analyzed", "message": "✓ Architecture analyzed"}
123
  data: {"status": "complete", "message": "✓ Repository analysis complete!"}
124
  data: {"status": "done", "repo": "Ankita15k/GitNest"}
@@ -127,9 +144,11 @@ data: {"status": "done", "repo": "Ankita15k/GitNest"}
127
  ---
128
 
129
  ### POST /api/index
130
- Sync execution endpoint to clone, parse, and embed a repository without generating architecture summaries or dependency graphs. Useful for lightweight Q&A.
 
 
 
131
 
132
- #### Request Schema
133
  ```json
134
  {
135
  "repo_url": "https://github.com/owner/repository"
@@ -147,28 +166,70 @@ Sync execution endpoint to clone, parse, and embed a repository without generati
147
 
148
  ---
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  ## Semantic Querying
151
 
152
  ### POST /api/retrieve
153
- Query the vector space index for a particular repository using local semantic embeddings and generate a grounded LLM answer.
 
 
 
 
154
 
155
- #### Request Schema
156
  ```json
157
  {
158
  "repo": "owner/repository",
159
- "question": "Query text here"
160
  }
161
  ```
162
 
163
  #### Response (200 OK)
164
  ```json
165
  {
166
- "answer": "Authentication is managed via standard JWT JSON Web Tokens...",
167
  "sources": [
168
  {
169
- "file": "backend/src/controllers/auth.controller.js",
170
- "content": "const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET... ",
171
- "score": 0.88
172
  }
173
  ]
174
  }
@@ -179,25 +240,29 @@ Query the vector space index for a particular repository using local semantic em
179
  ### POST /api/chat (SSE Stream)
180
  Interactive, conversational multi-turn chat over the repository codebase context, streaming back word tokens.
181
 
182
- #### Request Schema
 
 
 
 
183
  ```json
184
  {
185
- "repo": "owner/repository",
186
- "message": "User query",
187
  "history": [
188
- {"role": "user", "content": "previous question"},
189
- {"role": "assistant", "content": "previous answer"}
190
  ]
191
  }
192
  ```
193
 
194
  #### Request Example
195
  ```bash
196
- curl -N -X POST http://localhost:8000/api/chat \
197
  -H "Content-Type: application/json" \
198
  -d '{
199
  "repo": "Ankita15k/GitNest",
200
- "message": "How does Socket.io notify users?",
201
  "history": []
202
  }'
203
  ```
@@ -205,9 +270,15 @@ curl -N -X POST http://localhost:8000/api/chat \
205
  #### SSE Stream Output
206
  ```
207
  data: {"text": "Socket"}
208
- data: {"text": ".io notifications"}
209
- ...
210
- data: {"sources": ["backend/src/socket/notificationHandler.js"], "confidence": 92, "status": "done"}
 
 
 
 
 
 
211
  ```
212
 
213
  ---
@@ -215,38 +286,45 @@ data: {"sources": ["backend/src/socket/notificationHandler.js"], "confidence": 9
215
  ### POST /api/issues/map
216
  Analyze a GitHub issue and map it to relevant source code modules, generating a detailed implementation plan.
217
 
218
- #### Request Schema
 
 
 
 
 
219
  ```json
220
  {
221
- "repo": "owner/repository",
222
- "issue": "Combined issue text (optional)",
223
- "title": "Issue title (required if issue is omitted)",
224
- "description": "Issue description text (optional)"
225
  }
226
  ```
227
 
228
- #### Response (200 OK)
229
  ```json
230
  {
231
- "plan": {
232
- "title": "Fix token validation timeout",
233
- "description": "Verify token expiration dates against Redis blocklists.",
234
- "relevant_files": [
235
- "backend/src/middleware/authMiddleware.js"
236
- ],
237
- "components": ["Authentication"],
238
- "steps": [
239
- {
240
- "step": 1,
241
- "title": "Update jwt validation checks",
242
- "file": "backend/src/middleware/authMiddleware.js",
243
- "description": "Introduce checking of decoded token timestamps against Redis blacklist keys.",
244
- "type": "modify"
245
- }
246
- ]
247
- },
248
  "confidence": 88,
249
- "fallback_used": false
 
 
 
250
  }
251
  ```
252
 
@@ -257,7 +335,9 @@ Analyze a GitHub issue and map it to relevant source code modules, generating a
257
  ### POST /api/architecture/build
258
  Trigger Tree-sitter AST parsing over local cloned repository files and construct the dependency NetworkX graph.
259
 
260
- #### Request Schema
 
 
261
  ```json
262
  {
263
  "repo": "owner/repository"
@@ -272,8 +352,7 @@ Trigger Tree-sitter AST parsing over local cloned repository files and construct
272
  "files_parsed": 328,
273
  "dependencies_found": 1440,
274
  "entry_points": [
275
- "backend/src/index.js",
276
- "frontend/src/main.jsx"
277
  ]
278
  }
279
  ```
@@ -281,24 +360,24 @@ Trigger Tree-sitter AST parsing over local cloned repository files and construct
281
  ---
282
 
283
  ### GET /api/architecture/{owner}/{repo_name}
284
- Fetch the generated architecture summary, entry points, and coupling details.
285
 
286
- #### Response (200 OK)
287
  ```json
288
  {
289
- "summary": "GitNest is a collaborative coding platform built with the MERN stack...",
290
- "entry_points": [
291
  "backend/src/index.js",
292
- "frontend/src/main.jsx"
293
- ],
294
- "core_modules": [
295
- "backend/src/controllers/auth.controller.js"
296
  ],
297
- "high_coupling_modules": [
298
- "backend/src/controllers/auth.controller.js"
299
- ],
300
- "total_files": 328,
301
- "total_dependencies": 1440
 
 
 
302
  }
303
  ```
304
 
@@ -316,18 +395,17 @@ Retrieve React Flow-compatible node and edge datasets for visualization.
316
  "nodes": [
317
  {
318
  "id": "backend/src/index.js",
319
- "label": "index.js",
320
- "type": "module",
321
- "centrality": 0.95,
322
- "x": 200,
323
- "y": 150
324
  }
325
  ],
326
  "edges": [
327
  {
 
328
  "source": "backend/src/index.js",
329
  "target": "backend/src/app.js",
330
- "type": "imports"
331
  }
332
  ]
333
  }
@@ -338,25 +416,34 @@ Retrieve React Flow-compatible node and edge datasets for visualization.
338
  ### POST /api/reading-order
339
  Generate an optimal, ranked file list for onboarding new developers based on graph centrality metrics.
340
 
341
- #### Request Schema
 
 
342
  ```json
343
  {
344
  "repo": "owner/repository"
345
  }
346
  ```
347
 
348
- #### Response (200 OK)
349
  ```json
350
  {
351
  "repo": "Ankita15k/GitNest",
352
- "reading_order": [
353
  {
354
  "rank": 1,
355
- "file": "backend/src/index.js",
356
  "reason": "Primary application entry point initializing server configurations.",
357
- "centrality": 0.95
 
358
  }
359
- ]
 
 
 
 
 
 
360
  }
361
  ```
362
 
@@ -365,25 +452,44 @@ Generate an optimal, ranked file list for onboarding new developers based on gra
365
  ### POST /api/impact-analysis
366
  Predict downstream affected modules and components resulting from a proposed change.
367
 
368
- #### Request Schema
 
 
 
369
  ```json
370
  {
371
- "repo": "owner/repository",
372
- "issue": "Proposed modification description"
373
  }
374
  ```
375
 
376
- #### Response (200 OK)
377
  ```json
378
  {
379
  "repo": "Ankita15k/GitNest",
380
- "issue": "Modify authentication payload schema",
381
- "impacted_files": [
382
- "backend/src/middleware/authMiddleware.js",
383
- "backend/src/routes/auth.routes.js"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  ],
385
- "impacted_components": ["Authentication"],
386
- "risk_level": "high",
387
- "reasoning": "Modifying the auth payload schema affects all token verification routes and client calls."
388
  }
389
  ```
 
1
  # API Reference Specification
2
 
3
+ All endpoints are hosted at: `http://127.0.0.1:8001`
4
 
5
  ---
6
 
 
20
 
21
  #### Request Example
22
  ```bash
23
+ curl http://127.0.0.1:8001/health
24
  ```
25
 
26
  #### Response (200 OK)
 
42
 
43
  #### Request Example
44
  ```bash
45
+ curl http://127.0.0.1:8001/api/repos/examples
46
  ```
47
 
48
  #### Response (200 OK)
 
53
  "url": "https://github.com/google/guava",
54
  "tech_stack": ["Java", "Maven"],
55
  "description": "Google core libraries for Java."
56
+ },
57
+ {
58
+ "name": "fastapi/fastapi",
59
+ "url": "https://github.com/fastapi/fastapi",
60
+ "tech_stack": ["Python", "Pydantic", "Starlette"],
61
+ "description": "High performance, easy to learn, fast to code, ready for production API framework."
62
+ },
63
+ {
64
+ "name": "vercel/next.js",
65
+ "url": "https://github.com/vercel/next.js",
66
+ "tech_stack": ["JavaScript", "TypeScript", "React", "Rust"],
67
+ "description": "The React Framework for the Web."
68
  }
69
  ]
70
  ```
 
72
  ---
73
 
74
  ### GET /api/repos/recent
75
+ Fetch a list of repositories processed during the current server session, loaded from the persisted store.
76
 
77
  #### Request Example
78
  ```bash
79
+ curl http://127.0.0.1:8001/api/repos/recent
80
  ```
81
 
82
  #### Response (200 OK)
 
96
  ## Repository Processing
97
 
98
  ### POST /api/analyze (SSE Stream)
99
+ Trigger a full repository cloning, language parsing, code chunking, indexing, and architecture mapping pipeline. This endpoint streams real-time progress events using the `text/event-stream` mime-type.
100
+
101
+ #### Request Schema (AnalyzeRequest)
102
+ - `url` (string, required): GitHub repository URL.
103
+ - `branch` (string, optional): Git branch or ref. Defaults to `"main"`.
104
+ - `model` (string, optional): LLM model variant to use. Defaults to `"deepseek-ai/deepseek-v4-flash"`.
105
 
 
106
  ```json
107
  {
108
  "url": "https://github.com/owner/repository",
 
113
 
114
  #### Request Example
115
  ```bash
116
+ curl -N -X POST http://127.0.0.1:8001/api/analyze \
117
  -H "Content-Type: application/json" \
118
  -H "Accept: text/event-stream" \
119
  -d '{
 
122
  }'
123
  ```
124
 
125
+ #### SSE Stream Events Progress Output
126
  ```
127
  data: {"status": "cloning", "message": "Cloning repository from GitHub..."}
128
  data: {"status": "cloned", "message": "✓ Repository cloned successfully"}
 
135
  data: {"status": "chroma", "message": "START: chroma indexing"}
136
  data: {"status": "chroma", "message": "END: chroma indexing"}
137
  data: {"status": "building_graph", "message": "Building file dependency graph..."}
138
+ data: {"status": "graph_built", "message": "✓ Dependency graph: 328 files parsed, 1440 edges", "files_parsed": 328, "dependencies_found": 1440, "entry_points": ["backend/src/index.js"]}
139
  data: {"status": "analyzed", "message": "✓ Architecture analyzed"}
140
  data: {"status": "complete", "message": "✓ Repository analysis complete!"}
141
  data: {"status": "done", "repo": "Ankita15k/GitNest"}
 
144
  ---
145
 
146
  ### POST /api/index
147
+ Clones, chunks, and indexes a repository's vector embeddings in ChromaDB, without building structural dependency graphs.
148
+
149
+ #### Request Schema (IndexRequest)
150
+ - `repo_url` (string, required): GitHub repository URL.
151
 
 
152
  ```json
153
  {
154
  "repo_url": "https://github.com/owner/repository"
 
166
 
167
  ---
168
 
169
+ ### GET /api/analysis/{owner}/{repo_name}
170
+ Fetch the complete metadata results of a repository analysis.
171
+
172
+ #### Response (200 OK)
173
+ ```json
174
+ {
175
+ "analysis": {
176
+ "structure": {
177
+ ".": ["README.md", "package.json"],
178
+ "src": ["app.js", "server.js"]
179
+ },
180
+ "dependencies": ["express", "cors", "jsonwebtoken"],
181
+ "tech_stack": ["JavaScript", "TypeScript"],
182
+ "metadata": {
183
+ "owner": "Ankita15k",
184
+ "name": "GitNest",
185
+ "local_path": "C:\\Users\\Varshith Reddy\\.repo_intelligence\\cloned_repos\\Ankita15k_GitNest"
186
+ }
187
+ },
188
+ "architecture": {
189
+ "summary": "GitNest is a full-stack Git hosting dashboard built with Node.js and Express...",
190
+ "reading_order": [
191
+ "backend/src/index.js",
192
+ "backend/src/app.js"
193
+ ],
194
+ "relationships": [
195
+ {
196
+ "source": "backend/src/index.js",
197
+ "target": "backend/src/app.js",
198
+ "relationship_type": "imports",
199
+ "description": "Imports app instance to launch listener server."
200
+ }
201
+ ]
202
+ }
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
  ## Semantic Querying
209
 
210
  ### POST /api/retrieve
211
+ Query the vector space index directly for a repository using BGE embeddings and generate a grounded LLM response.
212
+
213
+ #### Request Schema (RetrieveRequest)
214
+ - `repo` (string, required): Repository identifier (owner/repo).
215
+ - `question` (string, required): Question query.
216
 
 
217
  ```json
218
  {
219
  "repo": "owner/repository",
220
+ "question": "How is authentication handled?"
221
  }
222
  ```
223
 
224
  #### Response (200 OK)
225
  ```json
226
  {
227
+ "answer": "Authentication is implemented using JWT tokens inside authMiddleware.js...",
228
  "sources": [
229
  {
230
+ "file": "backend/src/middleware/authMiddleware.js",
231
+ "content": "const token = req.headers.authorization.split(' ')[1]; ...",
232
+ "score": 0.92
233
  }
234
  ]
235
  }
 
240
  ### POST /api/chat (SSE Stream)
241
  Interactive, conversational multi-turn chat over the repository codebase context, streaming back word tokens.
242
 
243
+ #### Request Schema (ChatRequest)
244
+ - `repo` (string, required): Repository identifier (owner/repo).
245
+ - `message` (string, required): User message.
246
+ - `history` (list, optional): Conversation history turns containing roles and text content.
247
+
248
  ```json
249
  {
250
+ "repo": "Ankita15k/GitNest",
251
+ "message": "Explain how Socket.io notifies users.",
252
  "history": [
253
+ {"role": "user", "content": "How are WebSockets set up?"},
254
+ {"role": "assistant", "content": "WebSockets are initialized in socket.js..."}
255
  ]
256
  }
257
  ```
258
 
259
  #### Request Example
260
  ```bash
261
+ curl -N -X POST http://127.0.0.1:8001/api/chat \
262
  -H "Content-Type: application/json" \
263
  -d '{
264
  "repo": "Ankita15k/GitNest",
265
+ "message": "Explain how Socket.io notifies users.",
266
  "history": []
267
  }'
268
  ```
 
270
  #### SSE Stream Output
271
  ```
272
  data: {"text": "Socket"}
273
+ data: {"text": ".io "}
274
+ data: {"text": "notifications "}
275
+ data: {"text": "are "}
276
+ data: {"text": "pushed "}
277
+ data: {"text": "to "}
278
+ data: {"text": "clients "}
279
+ data: {"text": "using "}
280
+ data: {"text": "io.emit()."}
281
+ data: {"sources": ["backend/src/socket/notificationHandler.js"], "confidence": 92, "fallback_mode": false, "status": "done"}
282
  ```
283
 
284
  ---
 
286
  ### POST /api/issues/map
287
  Analyze a GitHub issue and map it to relevant source code modules, generating a detailed implementation plan.
288
 
289
+ #### Request Schema (IssueMapRequest)
290
+ - `repo` (string, required): Repository identifier (owner/repo).
291
+ - `issue` (string, optional): Combined issue text details.
292
+ - `title` (string, optional): GitHub issue title.
293
+ - `description` (string, optional): GitHub issue description body.
294
+
295
  ```json
296
  {
297
+ "repo": "Ankita15k/GitNest",
298
+ "title": "Fix token validation timeout",
299
+ "description": "User tokens expire instantly when hitting the API from mobile clients."
 
300
  }
301
  ```
302
 
303
+ #### Response (200 OK - IssueMapResponse)
304
  ```json
305
  {
306
+ "issue_summary": "Fix token validation timeout",
307
+ "issue_type": "bug",
308
+ "relevant_files": [
309
+ "backend/src/middleware/authMiddleware.js"
310
+ ],
311
+ "affected_components": [
312
+ "Authentication",
313
+ "Services"
314
+ ],
315
+ "implementation_plan": [
316
+ {
317
+ "step_number": 1,
318
+ "description": "Modify the jwt verify expiration range logic inside authMiddleware.js to tolerate mobile clock drift.",
319
+ "files_to_modify": ["backend/src/middleware/authMiddleware.js"]
320
+ }
321
+ ],
322
+ "complexity": "low",
323
  "confidence": 88,
324
+ "verified": true,
325
+ "sources": [
326
+ "backend/src/middleware/authMiddleware.js"
327
+ ]
328
  }
329
  ```
330
 
 
335
  ### POST /api/architecture/build
336
  Trigger Tree-sitter AST parsing over local cloned repository files and construct the dependency NetworkX graph.
337
 
338
+ #### Request Schema (ArchitectureBuildRequest)
339
+ - `repo` (string, required): Repository identifier (owner/repo).
340
+
341
  ```json
342
  {
343
  "repo": "owner/repository"
 
352
  "files_parsed": 328,
353
  "dependencies_found": 1440,
354
  "entry_points": [
355
+ "backend/src/index.js"
 
356
  ]
357
  }
358
  ```
 
360
  ---
361
 
362
  ### GET /api/architecture/{owner}/{repo_name}
363
+ Fetch the generated architecture summary, reading order, and relationship schemas.
364
 
365
+ #### Response (200 OK - ArchitectureSummary)
366
  ```json
367
  {
368
+ "summary": "GitNest is a full-stack Git dashboard using Express and local git hooks...",
369
+ "reading_order": [
370
  "backend/src/index.js",
371
+ "backend/src/app.js"
 
 
 
372
  ],
373
+ "relationships": [
374
+ {
375
+ "source": "backend/src/index.js",
376
+ "target": "backend/src/app.js",
377
+ "relationship_type": "imports",
378
+ "description": "Instantiates app config schemas."
379
+ }
380
+ ]
381
  }
382
  ```
383
 
 
395
  "nodes": [
396
  {
397
  "id": "backend/src/index.js",
398
+ "data": { "label": "index.js" },
399
+ "position": { "x": 120, "y": 80 },
400
+ "style": { "background": "#1e293b", "color": "#f8fafc" }
 
 
401
  }
402
  ],
403
  "edges": [
404
  {
405
+ "id": "e_backend/src/index.js_backend/src/app.js",
406
  "source": "backend/src/index.js",
407
  "target": "backend/src/app.js",
408
+ "animated": true
409
  }
410
  ]
411
  }
 
416
  ### POST /api/reading-order
417
  Generate an optimal, ranked file list for onboarding new developers based on graph centrality metrics.
418
 
419
+ #### Request Schema (ReadingOrderRequest)
420
+ - `repo` (string, required): Repository identifier (owner/repo).
421
+
422
  ```json
423
  {
424
  "repo": "owner/repository"
425
  }
426
  ```
427
 
428
+ #### Response (200 OK - ReadingOrder)
429
  ```json
430
  {
431
  "repo": "Ankita15k/GitNest",
432
+ "ordered_files": [
433
  {
434
  "rank": 1,
435
+ "file_path": "backend/src/index.js",
436
  "reason": "Primary application entry point initializing server configurations.",
437
+ "tier": "entry_point",
438
+ "score": 150.0
439
  }
440
+ ],
441
+ "reasoning": [
442
+ "Timeline starts with detected application entry points.",
443
+ "Order prioritizes modules with high degree centrality scores."
444
+ ],
445
+ "estimated_reading_time": 45,
446
+ "total_files_ranked": 328
447
  }
448
  ```
449
 
 
452
  ### POST /api/impact-analysis
453
  Predict downstream affected modules and components resulting from a proposed change.
454
 
455
+ #### Request Schema (ImpactAnalysisRequest)
456
+ - `repo` (string, required): Repository identifier (owner/repo).
457
+ - `issue` (string, required): Change request or GitHub issue text.
458
+
459
  ```json
460
  {
461
+ "repo": "Ankita15k/GitNest",
462
+ "issue": "Add API key authentication"
463
  }
464
  ```
465
 
466
+ #### Response (200 OK - ImpactAnalysis)
467
  ```json
468
  {
469
  "repo": "Ankita15k/GitNest",
470
+ "issue_text": "Add API key authentication",
471
+ "directly_affected_files": [
472
+ "backend/src/middleware/authMiddleware.js"
473
+ ],
474
+ "indirectly_affected_files": [
475
+ "backend/src/routes/auth.routes.js",
476
+ "backend/src/app.js"
477
+ ],
478
+ "affected_components": [
479
+ "Authentication",
480
+ "API Layer"
481
+ ],
482
+ "risk_level": "medium",
483
+ "estimated_file_count": 3,
484
+ "dependency_paths": [
485
+ {
486
+ "path": [
487
+ "backend/src/middleware/authMiddleware.js",
488
+ "backend/src/routes/auth.routes.js",
489
+ "backend/src/app.js"
490
+ ]
491
+ }
492
  ],
493
+ "confidence": 85
 
 
494
  }
495
  ```
docs/DEVELOPMENT_SETUP.md CHANGED
@@ -1,32 +1,32 @@
1
- # Development Setup & Contributor Guide
2
 
3
- Welcome to the **Repo Intelligence Agent** project! This document outlines the steps required to set up the backend and frontend for local development, run the test suites, and contribute to the codebase.
4
 
5
  ---
6
 
7
  ## 📋 Prerequisites
8
 
9
- Before setting up the project locally, ensure you have the following installed:
10
 
11
- - **Python 3.10 or 3.11** (Note: Python 3.12 is supported, but 3.10/3.11 is recommended for library stability).
12
- - **Node.js v18 or newer** (for the Astro & React frontend).
13
- - **Git** command-line utility.
14
- - An **NVIDIA NIM API key** (used for DeepSeek V4 Flash inference calls). You can acquire one free of charge at [build.nvidia.com](https://build.nvidia.com).
15
- - At least **2 GB of free disk space** for BGE embedding model downloads.
16
 
17
  ---
18
 
19
- ## 🔧 Installation & Setup
20
 
21
- ### 1. Clone the Repository
22
- Clone the repository to your local workspace and navigate to the project directory:
23
  ```bash
24
  git clone https://github.com/your-username/Repo-Intelligence-Agent.git
25
  cd Repo-Intelligence-Agent
26
  ```
27
 
28
- ### 2. Backend Setup
29
- Set up a Python virtual environment to isolate the project dependencies:
30
 
31
  **On Windows (PowerShell):**
32
  ```powershell
@@ -40,85 +40,87 @@ python -m venv .venv
40
  source .venv/bin/activate
41
  ```
42
 
43
- Install all required Python dependencies:
 
44
  ```bash
45
  pip install -r requirements.txt
46
  ```
47
 
48
- ### 3. Backend Environment Configuration
49
- Create a local `.env` file in the root directory by copying the example template:
 
50
  ```bash
51
  cp .env.example .env
52
  ```
53
 
54
- Open `.env` and fill in the required environment variables:
55
  ```env
56
  DEEPSEEK_API_KEY=nvapi-your-nvidia-nim-api-key-here
57
- GITHUB_TOKEN=ghp_your-github-personal-access-token-here
58
  CHROMA_DB_PATH=data/chroma_db
59
  LLM_PROVIDER=deepseek
60
  DEEPSEEK_MODEL=deepseek-ai/deepseek-v4-flash
 
61
  ```
62
 
63
- > [!TIP]
64
- > The `GITHUB_TOKEN` is optional but highly recommended to raise GitHub API rate limits when cloning repositories.
65
-
66
- ### 4. Start the Backend API Server
67
- Launch the FastAPI backend server using Uvicorn with hot-reload enabled:
68
  ```bash
69
- python -m uvicorn backend.api:app --host 127.0.0.1 --port 8000 --reload
 
 
 
 
70
  ```
71
- The backend API documentation will be available at [http://localhost:8000/docs](http://localhost:8000/docs).
 
72
 
73
  ---
74
 
75
- ## 🎨 Frontend Setup
 
 
 
 
 
 
 
 
 
76
 
77
- The frontend application is built using Astro 4 and React components.
 
78
 
79
- 1. Navigate to the frontend directory:
80
- ```bash
81
- cd frontend
82
- ```
83
- 2. Install the package dependencies:
84
- ```bash
85
- npm install
86
- ```
87
- 3. Start the Astro local development server:
88
- ```bash
89
- npm run dev
90
- ```
91
- 4. Access the frontend dashboard at [http://localhost:4321](http://localhost:4321).
92
 
93
  ---
94
 
95
  ## 🧪 Testing Guidelines
96
 
97
- We utilize `pytest` to verify backend business logic, retrieval services, and AST parsers.
98
 
99
- ### Running Backend Tests
100
- Ensure your virtual environment is active, then run:
101
  ```bash
102
  pytest tests/ -v
103
  ```
104
 
105
- ### Writing New Tests
106
- - Place your test files under the `tests/` directory.
107
- - Name files with the prefix `test_` (e.g., `test_my_feature.py`).
108
- - Implement descriptive assertions and leverage mock adapters for network calls to avoid hitting remote API quotas during unit runs.
109
 
110
  ---
111
 
112
- ## 🤝 Contribution Workflow
113
-
114
- 1. **Fork** the repository and create your feature branch:
115
- ```bash
116
- git checkout -b feature/amazing-new-feature
117
- ```
118
- 2. Make your modifications, adhering to clean coding standards and documenting changes.
119
- 3. Verify changes with `pytest`.
120
- 4. Push your branch to GitHub:
121
- ```bash
122
- git push origin feature/amazing-new-feature
123
- ```
124
- 5. Open a **Pull Request** detailing your changes.
 
1
+ # 🛠️ Development Setup & Contributor Guide
2
 
3
+ Welcome to the **Repo Intelligence Agent** contributor guide! This document outlines the steps required to configure your local development environment, start backend and frontend services, run the test suites, and adhere to our development workflows.
4
 
5
  ---
6
 
7
  ## 📋 Prerequisites
8
 
9
+ Before setting up the project, ensure you have the following installed:
10
 
11
+ - **Python:** Version 3.10, 3.11, or 3.12. (Active unit tests are validated against Python 3.12).
12
+ - **Node.js:** Version 18 or newer (LTS recommended).
13
+ - **Git:** Command-line utility for source control operations.
14
+ - **NVIDIA NIM API Key:** Required for DeepSeek V4 Flash inference. You can acquire a free-tier key at [build.nvidia.com](https://build.nvidia.com).
15
+ - **Disk Space:** At least 2 GB of free disk space is required to store local Hugging Face model cache structures (specifically for `BAAI/bge-small-en-v1.5` embeddings).
16
 
17
  ---
18
 
19
+ ## 🔧 Backend Ingestion & API Setup
20
 
21
+ ### 1. Clone the Project
22
+ Clone the repository and enter the project directory:
23
  ```bash
24
  git clone https://github.com/your-username/Repo-Intelligence-Agent.git
25
  cd Repo-Intelligence-Agent
26
  ```
27
 
28
+ ### 2. Configure Python Virtual Environment
29
+ Isolate Python dependencies to prevent version collisions.
30
 
31
  **On Windows (PowerShell):**
32
  ```powershell
 
40
  source .venv/bin/activate
41
  ```
42
 
43
+ ### 3. Install Dependencies
44
+ Install all backend packages:
45
  ```bash
46
  pip install -r requirements.txt
47
  ```
48
 
49
+ ### 4. Configure Environmental Variables
50
+ Copy the environmental template into an active `.env` configuration:
51
+
52
  ```bash
53
  cp .env.example .env
54
  ```
55
 
56
+ Open the `.env` file and insert your API key and settings:
57
  ```env
58
  DEEPSEEK_API_KEY=nvapi-your-nvidia-nim-api-key-here
59
+ GITHUB_TOKEN=ghp_your-github-personal-access-token-here # Optional: raises clone rate limits
60
  CHROMA_DB_PATH=data/chroma_db
61
  LLM_PROVIDER=deepseek
62
  DEEPSEEK_MODEL=deepseek-ai/deepseek-v4-flash
63
+ DEEPSEEK_BASE_URL=https://integrate.api.nvidia.com/v1
64
  ```
65
 
66
+ ### 5. Launch the Backend Server
67
+ Start the Uvicorn development server. The project defaults to port **8001** to prevent conflicts with standard developer systems:
 
 
 
68
  ```bash
69
+ # Recommended: Starts uvicorn with watch filters enabled in backend/main.py
70
+ python backend/main.py
71
+
72
+ # Alternative: direct uvicorn execution specifying port 8001
73
+ python -m uvicorn backend.api:app --host 127.0.0.1 --port 8001 --reload
74
  ```
75
+ - The interactive OpenAPI documentation will be served at [http://127.0.0.1:8001/docs](http://127.0.0.1:8001/docs).
76
+ - The raw JSON schema is available at [http://127.0.0.1:8001/openapi.json](http://127.0.0.1:8001/openapi.json).
77
 
78
  ---
79
 
80
+ ## 🎨 Astro Dashboard Setup
81
+
82
+ The user interface is an Astro 4 static-site framework embedding interactive React client components.
83
+
84
+ ### 1. Navigate and Install Packages
85
+ Navigate to the frontend directory and install dependencies:
86
+ ```bash
87
+ cd frontend
88
+ npm install
89
+ ```
90
 
91
+ ### 2. Configure Frontend Environment
92
+ Ensure the frontend matches backend routing by checking `frontend/.env` (it defaults to `PUBLIC_API_URL=http://127.0.0.1:8001`).
93
 
94
+ ### 3. Run Astro Development Server
95
+ Start the development server:
96
+ ```bash
97
+ npm run dev
98
+ ```
99
+ - Open [http://localhost:4321](http://localhost:4321) in your browser to view the interface.
 
 
 
 
 
 
 
100
 
101
  ---
102
 
103
  ## 🧪 Testing Guidelines
104
 
105
+ We use `pytest` for backend unit testing. All tests are located inside the `tests/` directory.
106
 
107
+ ### Running the Test Suite
108
+ Activate your virtual environment and run the test suite:
109
  ```bash
110
  pytest tests/ -v
111
  ```
112
 
113
+ > [!CAUTION]
114
+ > Avoid running raw `pytest` in the root folder without a target path. Doing so causes pytest to traverse `data/cloned_repos/` (which contains cloned codebases like FastAPI), resulting in import errors and test collection failures. Always run `pytest tests/`.
 
 
115
 
116
  ---
117
 
118
+ ## 💅 Styling & Formatting Standards
119
+
120
+ - **Backend Linting:** We use `Ruff` for linting and code formatting. Run it from your virtual environment before submitting changes:
121
+ ```bash
122
+ ruff check .
123
+ ruff format .
124
+ ```
125
+ - **Frontend CSS:** The UI uses Vanilla CSS structured inside Astro templates. Tailwind CSS configurations (`tailwind.config.mjs`) are loaded but custom responsive rules are written manually to maintain control over visual transitions.
126
+ - **Documentation:** Maintain markdown files with clean lists, relative links, and valid Mermaid diagram syntax.
 
 
 
 
docs/MVP_STATUS.md CHANGED
@@ -4,62 +4,65 @@ This document tracks the feature coverage, validation results, known limitations
4
 
5
  ---
6
 
7
- ## 📊 MVP Completeness: 97%
8
 
9
- The core codebase analysis, semantic retrieval, SSE streaming, and issue mapping pipelines are fully functional and tested. The remaining 3% corresponds to skeletal agents and minor performance improvements.
10
 
11
  ---
12
 
13
  ## ✅ Completed Features
14
 
15
- The following key services and pipelines are implemented and verified:
16
 
17
- 1. **Repository Analysis Pipeline (`/api/analyze`)**
18
- - Streamed cloning, extraction, tech stack detection, chunking (1500 chars / 200 overlap), local BGE embedding, ChromaDB indexing, and LLM-driven architecture summary.
19
  2. **Semantic Retrieval (`/api/retrieve`)**
20
- - Local embedding + cosine similarity search over repository chunks, backed by LLM answers.
21
- 3. **Repository Chat (`/api/chat`)**
22
- - Multi-turn conversational Q&A over codebase with SSE streaming and source citations.
23
  4. **Issue Mapper (`/api/issues/map`)**
24
- - Grounded file mapping and implementation plan generation.
25
- 5. **Architecture Builder (`/api/architecture/build`)**
26
- - Dependency graph generation using Tree-sitter AST parsing and NetworkX centrality scoring.
27
- 6. **Visualization Endpoint (`/api/architecture/.../graph`)**
28
- - Nodes and edges output compatible with React Flow graphs.
29
- 7. **Reading Path & Impact Analysis**
30
- - Reading order onboarding list and BFS-driven dependent file lists.
 
 
 
 
 
 
31
 
32
  ---
33
 
34
  ## 🔧 Known Limitations
35
 
36
- - **In-Memory Store:** The `ANALYSIS_STORE` cache (holding active analysis lists) is in-memory and resets when the backend server restarts.
37
- - **Local CPU Embedding Speed:** Running embeddings on large repositories via CPU takes approximately ~2–3 minutes per 1500 chunks.
38
- - **NVIDIA NIM Free-tier Quota:** Free API keys allow a maximum of ~3 requests per minute.
39
- - **Authentication:** Currently, there is no auth middleware or rate-limiting for the backend API endpoints.
40
 
41
  ---
42
 
43
- ## ⚠️ Technical Debt & Stubs
44
 
45
- The following areas are identified for cleanup or implementation in upcoming minor releases:
46
 
47
- - **Skeletal Agents:**
48
- - `agents/analyzer.py` is a stub that raises `NotImplementedError`. Active analysis logic was inlined into the API.
49
- - `agents/explainer.py` is a stub that raises `NotImplementedError`. Active explanation logic was inlined into the API.
50
- - **Deferred Evaluator:**
51
- - `EvaluationAgent` is imported but not fully wired as a gating mechanism (it returns scores but does not block low-confidence responses).
52
- - **Embedding Prefix Optimization:**
53
- - The local BGE query prefix (`"Represent this sentence for searching relevant passages:"`) is applied to both search queries and chunk documents. While it functions, document embedding should omit the prefix to optimize semantic search recall.
54
 
55
  ---
56
 
57
  ## 🗺️ Roadmap
58
 
59
  ### Phase 1: Stability & Security (Near-term)
60
- - [ ] Implement persistent SQLite backend for `ANALYSIS_STORE`.
61
- - [ ] Add JWT authentication and route-level middleware.
62
- - [ ] Optimize BGE document embedding by omitting the search prefix.
63
 
64
  ### Phase 2: Hybrid & Multi-Repo (Medium-term)
65
  - [ ] Support hybrid search (BM25 keyword matches + vector search).
 
4
 
5
  ---
6
 
7
+ ## 📊 MVP Completeness: 98%
8
 
9
+ The core codebase analysis, semantic retrieval, SSE streaming, multi-language AST graphing, reading order centrality, BFS impact analysis, unified workspace navigation, and issue mapping pipelines are fully functional and tested. The remaining 2% corresponds to implementing the skeletal stubs.
10
 
11
  ---
12
 
13
  ## ✅ Completed Features
14
 
15
+ The following features and pipelines are implemented and verified:
16
 
17
+ 1. **Repository Ingestion & Analysis (`/api/analyze`)**
18
+ - Direct Git cloning, language detection, codebase chunking (1500 chars / 200 overlap), local BGE embedding, ChromaDB vector indexing, NetworkX graph assembly, DeepSeek architecture summary, and filesystem persistence to `data/analysis_store.json`.
19
  2. **Semantic Retrieval (`/api/retrieve`)**
20
+ - Dense vector matching using local BGE embeddings and ChromaDB metadata filters.
21
+ 3. **Conversational Chat (`/api/chat`)**
22
+ - Real-time token streaming (SSE) with chat history, architecture context prompt injection, EvaluationAgent validation, and automated local fallback mode.
23
  4. **Issue Mapper (`/api/issues/map`)**
24
+ - Maps issue descriptions to code targets and generates plans under a budget of exactly two LLM calls, utilizing local caching via `f"{repo_name}:v2:{issue_hash}"` and keyword-based fallback steps.
25
+ 5. **Architecture Builder & Visualizer (`/api/architecture/.../graph`)**
26
+ - Tree-sitter structural extraction (imports, exports, classes, methods, functions) combined with NetworkX DiGraphs to export React Flow visual data.
27
+ 6. **Reading Path Timeline (`/api/reading-order`)**
28
+ - Centrality-based ranking (entry boost, degree centrality, core boosts, peripheral penalties) combined with topological sorts.
29
+ 7. **Impact Analysis (`/api/impact-analysis`)**
30
+ - Traverses forward and reverse import paths (BFS up to depth 4) to predict risk levels.
31
+ 8. **Unified Workspace Navigation**
32
+ - React state caches fetched analysis metadata, allowing instant tab switching on the frontend dashboard without re-analysis.
33
+ 9. **Session Persistence Store**
34
+ - Syncs active repository parameters via browser `localStorage` and hydrates backend analysis logs from disk upon startup.
35
+ 10. **Chat Fallback Mode**
36
+ - Intercepts NIM rate-limit (HTTP 429) or provider failures to generate a retrieval-grounded fallback response directly to the user.
37
 
38
  ---
39
 
40
  ## 🔧 Known Limitations
41
 
42
+ - **Local CPU Embedding Bottleneck:** Embedding generation runs locally on CPU using SentenceTransformers. Indexing a large repository can take 2–3 minutes per 1500 chunks.
43
+ - **NVIDIA NIM Free-Tier Quota:** Free developer keys are capped at ~3 requests/minute. The system handles this with its automated fallback mode.
44
+ - **No API Authentication:** Backend API routes currently do not verify user identity.
45
+ - **Cache Invalidation:** Modifying local files does not auto-update the vector space or import graph. Re-indexing is required.
46
 
47
  ---
48
 
49
+ ## ⚠️ Technical Debt & Skeletal Stubs
50
 
51
+ The following components are architectural design stubs that raise `NotImplementedError`. Their functions are currently inlined within API routes or handled via JSON flat files:
52
 
53
+ - **`SQLiteStore` (`memory/sqlite_store.py`):** Stub for transactional database logs.
54
+ - **`MCPService` (`services/mcp_service.py`):** Stub for Model Context Protocol.
55
+ - **`RepositoryAnalyzer` (`agents/analyzer.py`):** Stub for agent-based ingestion walks.
56
+ - **`ArchitectureExplainer` (`agents/explainer.py`):** Stub for agent-based centrality explanation.
 
 
 
57
 
58
  ---
59
 
60
  ## 🗺️ Roadmap
61
 
62
  ### Phase 1: Stability & Security (Near-term)
63
+ - [ ] Connect `SQLiteStore` to record query history and workspace settings.
64
+ - [ ] Implement JWT token authentication middleware.
65
+ - [ ] Optimize BGE document embedding by omitting query search prefixes on chunking.
66
 
67
  ### Phase 2: Hybrid & Multi-Repo (Medium-term)
68
  - [ ] Support hybrid search (BM25 keyword matches + vector search).
docs/TROUBLESHOOTING.md CHANGED
@@ -90,26 +90,28 @@ To resolve this permanently, upgrade to a paid NVIDIA NIM tier or configure key
90
  The UI displays a connection error during analysis, and the terminal log halts at `START: chroma indexing`.
91
 
92
  ### Root Cause
93
- 1. **Parallel Port Mismatch:** You might be running multiple backend processes. If `test_analyze.py` is targeting port `8001` while the active server is listening on port `8000`, commands will hit a mismatched session state.
94
  2. **Reverse Proxy Buffering:** If deploying behind Nginx or Cloudflare, the proxy might buffer the event stream, causing a timeout or socket termination.
95
 
96
  ### Solution
97
  - Stop all Python/Uvicorn processes:
98
  - On Windows:
99
- ```powershell
100
- Get-Process -Name python, uvicorn -ErrorAction SilentlyContinue | Stop-Process -Force
101
- ```
102
- - On macOS / Linux:
103
- ```bash
104
- killall python uvicorn
105
- ```
106
- - Re-run Uvicorn on port 8000:
107
  ```bash
108
- python -m uvicorn backend.api:app --host 127.0.0.1 --port 8000 --reload
109
  ```
110
- - If behind Nginx, add the following header directive to your server block:
111
  ```nginx
112
  proxy_set_header Connection '';
 
 
113
  proxy_buffering off;
114
- chunked_transfer_encoding on;
115
  ```
 
90
  The UI displays a connection error during analysis, and the terminal log halts at `START: chroma indexing`.
91
 
92
  ### Root Cause
93
+ 1. **Parallel Port Mismatch:** You might be running multiple backend processes. If `test_analyze.py` is targeting port `8001` while the active server is listening on port `8000`, or vice-versa, commands will hit a mismatched session state. The default server port configured in `backend/main.py` is **8001**.
94
  2. **Reverse Proxy Buffering:** If deploying behind Nginx or Cloudflare, the proxy might buffer the event stream, causing a timeout or socket termination.
95
 
96
  ### Solution
97
  - Stop all Python/Uvicorn processes:
98
  - On Windows:
99
+ ```powershell
100
+ Get-Process -Name python, uvicorn -ErrorAction SilentlyContinue | Stop-Process -Force
101
+ ```
102
+ - On macOS/Linux:
103
+ ```bash
104
+ killall python uvicorn
105
+ ```
106
+ - Re-run Uvicorn on port **8001**:
107
  ```bash
108
+ python backend/main.py
109
  ```
110
+ - Ensure reverse proxy settings disable buffering:
111
  ```nginx
112
  proxy_set_header Connection '';
113
+ proxy_http_version 1.1;
114
+ chunked_transfer_encoding off;
115
  proxy_buffering off;
116
+ proxy_cache off;
117
  ```
docs/VALIDATION_REPORT.md CHANGED
@@ -1,30 +1,30 @@
1
- # MVP Validation & Performance Report
2
 
3
- This document reports the performance characteristics, indexing metrics, and multi-agent pipeline validation results compiled during the final MVP testing cycle against production-scale repositories.
4
 
5
  ---
6
 
7
- ## 📈 Indexing & Storage Metrics
8
 
9
- Validation was conducted on the **Ankita15k/GitNest** repository (a full-stack collaborative git hosting system) to evaluate indexing throughput, chunking efficiency, and memory layer density.
10
 
11
- | Metric | Measured Value | Notes |
12
- |---|---|---|
13
- | **Total Source Files walked** | 328 files | Skips `node_modules`, `dist`, `.git`, and binaries |
14
- | **Total Chunks generated** | 1,549 chunks | CodeChunker (1500 chars / 200 overlap) |
15
- | **BGE Embedding output** | 1,549 vectors | 384-dimensional local BGE Small embeddings |
16
- | **ChromaDB Write time** | ~28 seconds | Persistent local SSD write speed |
17
- | **Tree-sitter AST parse** | ~1.4 seconds | Python, JavaScript, and TypeScript parsing |
18
- | **Graph Builder edges** | 1,440 dependencies | Dependency edge extraction |
19
- | **Framework Entry Points** | 17 entry points | Filtered via exclusion heuristics |
20
 
21
  ---
22
 
23
- ## 🎯 Issue Mapper Validation Results
24
 
25
- We tested the **Issue Mapper** using real repository issues. The validation focused on verifying that the mapper retrieves the correct source file context and generates executable steps without hallucinating non-existent paths.
26
 
27
- | Tested Issue | Grounded File Targets | Hallucination Detected | Correct Components Identified |
28
  | :--- | :--- | :--- | :--- |
29
  | **"Login fails when password contains @"** | `auth.test.js`, `authStore.js`, `security.test.js` | **None** | `Authentication`, `Frontend`, `Services` |
30
  | **"Pagination broken on repository page"** | `repository.controller.js`, `pullRequest.controller.js`, `App.jsx` | **None** | `API Layer`, `Frontend`, `Services` |
@@ -34,16 +34,44 @@ We tested the **Issue Mapper** using real repository issues. The validation focu
34
 
35
  ## 🔍 API Performance Benchmarks
36
 
37
- The following latencies were measured across all backend endpoints:
38
-
39
- | Endpoint | Method | Average Latency | Bottleneck |
40
- |---|---|---|---|
41
- | `/health` | GET | `< 3ms` | N/A |
42
- | `/api/repos/examples` | GET | `< 2ms` | N/A |
43
- | `/api/index` | POST | `~ 2.5 minutes` | CPU-bound embedding generation |
44
- | `/api/retrieve` | POST | `~ 150ms` | Network time (NIM API call) |
45
- | `/api/issues/map` | POST | `~ 4.8 seconds` | Sequential LLM calls (Plan + Steps) |
46
- | `/api/architecture/build` | POST | `~ 1.8 seconds` | Tree-sitter file-level parsing |
47
- | `/api/architecture/{owner}/{repo}` | GET | `< 5ms` | Disk read (cached JSON summary) |
48
- | `/api/reading-order` | POST | `~ 80ms` | NetworkX centrality calculations |
49
- | `/api/impact-analysis` | POST | `~ 95ms` | BFS traversal over dependency graph |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📊 MVP Validation & Telemetry Report
2
 
3
+ This report presents performance metrics, validation benchmarks, and pipeline latency figures collected during testing of the **Repo Intelligence Agent** MVP.
4
 
5
  ---
6
 
7
+ ## 📈 Codebase Ingestion & Parsing Telemetry
8
 
9
+ Ingestion throughput, AST parsing, and vector generation were evaluated against the **Ankita15k/GitNest** repository (a full-stack collaborative Git hosting system).
10
 
11
+ | Metric | Measured Value | Operational Context |
12
+ | :--- | :--- | :--- |
13
+ | **Total Source Files walked** | 328 files | Excludes `node_modules`, `.venv`, `.git`, configurations, and binaries |
14
+ | **Total Chunks generated** | 1,549 chunks | Produced by `CodeChunker` (1500 chars / 200 overlap) |
15
+ | **Embedding Generation** | 1,549 vectors | 384-dimensional dense vectors using local `bge-small-en-v1.5` |
16
+ | **ChromaDB Write Latency** | ~28 seconds | Persistence write rate to local SSD storage |
17
+ | **Tree-sitter AST parse** | ~1.4 seconds | Structural analysis of Python, JavaScript, and TypeScript files |
18
+ | **Dependency Graph size** | 1,440 edges | Directed dependency linkages calculated by `GraphService` |
19
+ | **Framework Entry Points** | 17 entry points | Resolved using structural heuristics (excludes test and doc dirs) |
20
 
21
  ---
22
 
23
+ ## 🎯 Issue Mapper Target Grounding
24
 
25
+ The **Issue Mapper** was tested against realistic repository issue logs to verify target file identification accuracy and ensure 0% path hallucinations.
26
 
27
+ | Test Case Issue | Grounded File Targets | Hallucination Detected | Correct Components Identified |
28
  | :--- | :--- | :--- | :--- |
29
  | **"Login fails when password contains @"** | `auth.test.js`, `authStore.js`, `security.test.js` | **None** | `Authentication`, `Frontend`, `Services` |
30
  | **"Pagination broken on repository page"** | `repository.controller.js`, `pullRequest.controller.js`, `App.jsx` | **None** | `API Layer`, `Frontend`, `Services` |
 
34
 
35
  ## 🔍 API Performance Benchmarks
36
 
37
+ Response latencies were compiled across all active FastAPI endpoints binding to port **8001** (averages calculated over 50 mock sessions):
38
+
39
+ | Endpoint | Method | Average Latency | Primary Bottleneck |
40
+ | :--- | :--- | :--- | :--- |
41
+ | `/health` | GET | `< 3ms` | Minimal routing overhead |
42
+ | `/api/repos/examples` | GET | `< 2ms` | Minimal routing overhead |
43
+ | `/api/repos/recent` | GET | `< 2ms` | Disk read (deserialization of `analysis_store.json`) |
44
+ | `/api/index` | POST | `~ 2.5 minutes` | CPU-bound embedding generation (SentenceTransformers) |
45
+ | `/api/retrieve` | POST | `~ 150ms` | Network response time from NVIDIA NIM API |
46
+ | `/api/issues/map` | POST | `~ 4.8 seconds` | Sequential LLM completion calls (parse+rank, then plan) |
47
+ | `/api/architecture/build` | POST | `~ 1.8 seconds` | CPU-bound AST file parsing (Tree-sitter) |
48
+ | `/api/analysis/{owner}/{repo_name}` | GET | `< 5ms` | In-memory cache retrieval |
49
+ | `/api/reading-order` | POST | `~ 80ms` | CPU NetworkX topological sorting & centrality |
50
+ | `/api/impact-analysis` | POST | `~ 95ms` | BFS graph traversal and keyword component matching |
51
+ | `/api/architecture/{owner}/{repo_name}/graph` | GET | `~ 50ms` | Formatting graph nodes and edges to React Flow JSON |
52
+
53
+ ---
54
+
55
+ ## ✅ Core Components Validation Checklist
56
+
57
+ | Feature Component | Implementation Status | Telemetry & Verification Method |
58
+ | :--- | :--- | :--- |
59
+ | **Repository Analysis** | ✅ **Complete** | SSE-stream progress index logging from `/api/analyze` |
60
+ | **Repository Chat** | ✅ **Complete** | Token-by-token SSE streaming evaluated by `EvaluationAgent` |
61
+ | **Issue Mapper** | ✅ **Complete** | Merged two-call model with caching via `f"{repo_name}:v2:{issue_hash}"` |
62
+ | **Architecture Graph** | ✅ **Complete** | Tree-sitter import mappings converted to React Flow visual nodes |
63
+ | **Reading Order** | ✅ **Complete** | Topological centrality timelines computed by `ReadingOrderService` |
64
+ | **Impact Analysis** | ✅ **Complete** | BFS graph sweep traversals (depth limit = 4) + Component Maps |
65
+ | **Repository Workspace** | ✅ **Complete** | Multi-tab UI dashboard caching states to prevent redundant re-analyzes |
66
+ | **Session Persistence** | ✅ **Complete** | LocalStorage state backup of active context + Startup JSON hydration |
67
+ | **Fallback Mode** | ✅ **Complete** | Local keyword + Chroma retrieved text parser on NIM rate limits |
68
+
69
+ ---
70
+
71
+ ## 🧪 Unit Test Coverage & Health
72
+
73
+ The backend test suite is composed of **91 unit and integration tests** verifying structural extraction, vector retrieval, caching logic, and API route mapping.
74
+
75
+ - **Total Collected Tests:** 91 items
76
+ - **Overall Code Coverage:** ~85% across all core classes (`TreeSitterService`, `GraphService`, `EntryPointService`, `ChromaStore`, `IssueMapper`).
77
+ - **Test Integrity:** Mock adapters isolate remote API network boundaries during testing, ensuring local test execution does not consume NVIDIA NIM tokens.
frontend/src/components/Navbar.astro CHANGED
@@ -20,7 +20,7 @@ const isAnalysisActive = pathname.startsWith('/analysis');
20
  </a>
21
 
22
  <!-- Desktop Navigation -->
23
- <nav class="hidden md:flex items-center gap-5 text-sm font-medium">
24
  {navLinks.map(link => {
25
  const isActive = pathname === link.href;
26
  return (
@@ -34,14 +34,13 @@ const isAnalysisActive = pathname.startsWith('/analysis');
34
  </a>
35
  );
36
  })}
37
- {isAnalysisActive && (
38
- <a
39
- href={pathname}
40
- class="text-text font-semibold border-b-2 border-primary py-0.5"
41
- >
42
- Analysis Dashboard
43
- </a>
44
- )}
45
  </nav>
46
  </div>
47
 
@@ -100,8 +99,27 @@ const isAnalysisActive = pathname.startsWith('/analysis');
100
  }
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  // Check health immediately
104
  updateStatus();
105
  // Poll health status
106
  setInterval(updateStatus, 8000);
 
 
 
107
  </script>
 
20
  </a>
21
 
22
  <!-- Desktop Navigation -->
23
+ <nav class="hidden md:flex items-center gap-5 text-sm font-medium" id="desktop-nav">
24
  {navLinks.map(link => {
25
  const isActive = pathname === link.href;
26
  return (
 
34
  </a>
35
  );
36
  })}
37
+ <a
38
+ id="analysis-nav-link"
39
+ href="#"
40
+ class="transition-colors hover:text-text text-text-muted hidden"
41
+ >
42
+ Analysis Dashboard
43
+ </a>
 
44
  </nav>
45
  </div>
46
 
 
99
  }
100
  }
101
 
102
+ function setupNavbar() {
103
+ const activeRepo = localStorage.getItem('activeRepo');
104
+ const analysisLink = document.getElementById('analysis-nav-link');
105
+ if (analysisLink && activeRepo) {
106
+ const [owner, repo] = activeRepo.split('/');
107
+ analysisLink.setAttribute('href', `/analysis?owner=${owner}&repo=${repo}`);
108
+ analysisLink.classList.remove('hidden');
109
+
110
+ const pathname = window.location.pathname;
111
+ if (pathname.startsWith('/analysis')) {
112
+ analysisLink.classList.add('text-text', 'font-semibold', 'border-b-2', 'border-primary', 'py-0.5');
113
+ analysisLink.classList.remove('text-text-muted');
114
+ }
115
+ }
116
+ }
117
+
118
  // Check health immediately
119
  updateStatus();
120
  // Poll health status
121
  setInterval(updateStatus, 8000);
122
+
123
+ // Setup active analysis tab
124
+ setupNavbar();
125
  </script>
frontend/src/components/interactive/AnalysisDashboard.tsx CHANGED
@@ -2,10 +2,11 @@ import React, { useState, useEffect } from 'react';
2
  import { apiUrl } from '../../lib/api';
3
  import FileTree from './FileTree';
4
  import IssueMapper from './IssueMapper';
 
5
  import { ArchitectureGraph } from './ArchitectureGraph';
6
  import { ReadingOrderTimeline } from './ReadingOrderTimeline';
7
  import { ImpactAnalysisGraph } from './ImpactAnalysisGraph';
8
- import { Layers, Box, Code2, BookOpen, Cpu, Info, CheckCircle2, Target, HelpCircle } from 'lucide-react';
9
 
10
  interface ComponentRelationship {
11
  source: string;
@@ -36,7 +37,7 @@ export const AnalysisDashboard: React.FC<DashboardProps> = ({ repoParam }) => {
36
  const [data, setData] = useState<AnalysisData | null>(null);
37
  const [loading, setLoading] = useState(true);
38
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
39
- const [activeTab, setActiveTab] = useState<'analysis' | 'graph' | 'reading_path' | 'impact_analysis' | 'issues'>('analysis');
40
 
41
  // Impact Analysis states
42
  const [impactData, setImpactData] = useState<any | null>(null);
@@ -81,6 +82,9 @@ export const AnalysisDashboard: React.FC<DashboardProps> = ({ repoParam }) => {
81
  setLoading(false);
82
  return;
83
  }
 
 
 
84
  const apiUrlFull = apiUrl(`/api/analysis/${owner}/${name}`);
85
  console.log('Fetching analysis from', apiUrlFull);
86
  fetch(apiUrlFull)
@@ -248,6 +252,17 @@ export const AnalysisDashboard: React.FC<DashboardProps> = ({ repoParam }) => {
248
  <Cpu className="h-4 w-4" />
249
  <span>ISSUE INTELLIGENCE</span>
250
  </button>
 
 
 
 
 
 
 
 
 
 
 
251
  </div>
252
 
253
  {activeTab === 'analysis' && (
@@ -425,6 +440,12 @@ export const AnalysisDashboard: React.FC<DashboardProps> = ({ repoParam }) => {
425
  </div>
426
  )}
427
 
 
 
 
 
 
 
428
  {activeTab === 'graph' && (
429
  <ArchitectureGraph repoName={repoName} />
430
  )}
 
2
  import { apiUrl } from '../../lib/api';
3
  import FileTree from './FileTree';
4
  import IssueMapper from './IssueMapper';
5
+ import ChatInterface from './ChatInterface';
6
  import { ArchitectureGraph } from './ArchitectureGraph';
7
  import { ReadingOrderTimeline } from './ReadingOrderTimeline';
8
  import { ImpactAnalysisGraph } from './ImpactAnalysisGraph';
9
+ import { Layers, Box, Code2, BookOpen, Cpu, Info, CheckCircle2, Target, HelpCircle, MessageSquareCode } from 'lucide-react';
10
 
11
  interface ComponentRelationship {
12
  source: string;
 
37
  const [data, setData] = useState<AnalysisData | null>(null);
38
  const [loading, setLoading] = useState(true);
39
  const [selectedFile, setSelectedFile] = useState<string | null>(null);
40
+ const [activeTab, setActiveTab] = useState<'analysis' | 'graph' | 'reading_path' | 'impact_analysis' | 'issues' | 'chat'>('analysis');
41
 
42
  // Impact Analysis states
43
  const [impactData, setImpactData] = useState<any | null>(null);
 
82
  setLoading(false);
83
  return;
84
  }
85
+ if (typeof window !== 'undefined') {
86
+ localStorage.setItem('activeRepo', repoName);
87
+ }
88
  const apiUrlFull = apiUrl(`/api/analysis/${owner}/${name}`);
89
  console.log('Fetching analysis from', apiUrlFull);
90
  fetch(apiUrlFull)
 
252
  <Cpu className="h-4 w-4" />
253
  <span>ISSUE INTELLIGENCE</span>
254
  </button>
255
+ <button
256
+ onClick={() => setActiveTab('chat')}
257
+ className={`flex items-center gap-2 px-4 py-2 text-xs font-mono tracking-wider font-semibold border-b-2 transition-all ${
258
+ activeTab === 'chat'
259
+ ? 'border-primary text-text bg-primary/5'
260
+ : 'border-transparent text-text-muted hover:text-text hover:bg-card/20'
261
+ }`}
262
+ >
263
+ <MessageSquareCode className="h-4 w-4" />
264
+ <span>CHAT</span>
265
+ </button>
266
  </div>
267
 
268
  {activeTab === 'analysis' && (
 
440
  </div>
441
  )}
442
 
443
+ {activeTab === 'chat' && (
444
+ <div className="min-h-[600px] flex flex-col">
445
+ <ChatInterface repoName={repoName} />
446
+ </div>
447
+ )}
448
+
449
  {activeTab === 'graph' && (
450
  <ArchitectureGraph repoName={repoName} />
451
  )}
frontend/src/components/interactive/ChatInterface.tsx CHANGED
@@ -1,47 +1,214 @@
1
  import React, { useState, useRef, useEffect } from 'react';
2
  import { apiUrl } from '../../lib/api';
3
- import { Send, MessageSquareCode, Sparkles, Bot, User, RefreshCw } from 'lucide-react';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  interface Message {
6
  id: string;
7
  sender: 'user' | 'assistant';
8
  text: string;
 
 
 
 
 
 
 
 
9
  }
10
 
11
  interface ChatInterfaceProps {
 
 
 
 
 
 
 
12
  repoName: string;
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
16
- const [messages, setMessages] = useState<Message[]>([
17
- {
18
- id: 'init',
19
- sender: 'assistant',
20
- text: `Hello! I've indexed \`${repoName}\`. Ask me anything about its architecture, endpoints, or file structures.`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
- ]);
 
 
 
 
 
23
  const [input, setInput] = useState('');
24
  const [isStreaming, setIsStreaming] = useState(false);
 
 
25
  const bottomRef = useRef<HTMLDivElement>(null);
26
 
27
  const suggestedQuestions = [
28
- "How are the agent schemas structured?",
29
- "Where is the FastAPI router initialized?",
30
- "Which files do I edit to add a new service?"
31
  ];
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  useEffect(() => {
34
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
35
  }, [messages]);
36
 
 
37
  const triggerStreamResponse = async (userPrompt: string) => {
38
  setIsStreaming(true);
39
- const newAssistantMessageId = Math.random().toString();
40
-
41
- // Add empty response placeholder
42
- setMessages(prev => [
43
  ...prev,
44
- { id: newAssistantMessageId, sender: 'assistant', text: '' }
45
  ]);
46
 
47
  try {
@@ -49,16 +216,42 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
49
  method: 'POST',
50
  headers: { 'Content-Type': 'application/json' },
51
  body: JSON.stringify({
52
- repo: repoName,
53
  message: userPrompt,
54
- history: messages.slice(1).map(m => ({
55
  role: m.sender === 'user' ? 'user' : 'model',
56
- parts: [m.text]
57
- }))
58
- })
59
  });
60
 
61
- if (!response.body) throw new Error("ReadableStream not available");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  const reader = response.body.getReader();
64
  const decoder = new TextDecoder();
@@ -67,38 +260,71 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
67
  while (!finished) {
68
  const { value, done } = await reader.read();
69
  finished = done;
70
- if (value) {
71
- const chunk = decoder.decode(value);
72
- const lines = chunk.split('\n');
73
-
74
- for (const line of lines) {
75
- if (line.startsWith('data: ')) {
76
- try {
77
- const data = JSON.parse(line.slice(6));
78
- if (data.text) {
79
- setMessages(prev =>
80
- prev.map(msg =>
81
- msg.id === newAssistantMessageId
82
- ? { ...msg, text: msg.text + data.text }
83
- : msg
84
- )
85
- );
86
- }
87
- } catch (e) {
88
- // Ignore incomplete JSON chunks
89
- }
90
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
  }
93
  }
94
  } catch (error) {
95
- console.error(error);
96
- setMessages(prev =>
97
- prev.map(msg =>
98
- msg.id === newAssistantMessageId
99
- ? { ...msg, text: "Error fetching response from agent backend. Please verify that api.py is running." }
100
- : msg
101
- )
 
 
 
 
102
  );
103
  } finally {
104
  setIsStreaming(false);
@@ -108,62 +334,120 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
108
  const handleSend = async (textToSend: string) => {
109
  if (!textToSend.trim() || isStreaming) return;
110
 
111
- // Add user message
112
- setMessages(prev => [
113
  ...prev,
114
- { id: Math.random().toString(), sender: 'user', text: textToSend }
115
  ]);
116
  setInput('');
117
 
118
  await triggerStreamResponse(textToSend);
119
  };
120
 
 
 
 
 
121
  return (
122
- <div className="flex flex-col h-[550px] border border-border bg-card/10 rounded-lg overflow-hidden w-full max-w-3xl">
123
  {/* Header */}
124
- <div className="flex items-center justify-between px-4 py-3 border-b border-border bg-card/40">
125
  <div className="flex items-center gap-2">
126
  <MessageSquareCode className="h-4 w-4 text-primary" />
127
- <span className="font-mono text-sm font-semibold text-text">Repository Chat Assistant</span>
 
 
128
  </div>
129
- <span className="text-[10px] font-mono border border-primary/30 text-primary px-2 py-0.5 rounded bg-primary/5">
130
- {repoName}
131
  </span>
132
  </div>
133
 
134
- {/* Messages */}
135
- <div className="flex-grow overflow-y-auto p-4 space-y-4 font-sans text-sm">
136
- {messages.map((msg) => (
137
- <div key={msg.id} className={`flex gap-3 max-w-[85%] ${msg.sender === 'user' ? 'ml-auto flex-row-reverse' : ''}`}>
138
- {/* Avatar */}
139
- <div className={`h-8 w-8 rounded-full flex items-center justify-center shrink-0 border ${
140
- msg.sender === 'user' ? 'bg-primary/10 border-primary/30 text-primary' : 'bg-card border-border text-text-muted'
141
- }`}>
142
- {msg.sender === 'user' ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </div>
 
144
 
145
- {/* Bubble */}
146
- <div className={`p-3 rounded-lg border ${
147
- msg.sender === 'user'
148
- ? 'bg-primary/10 border-primary/20 text-text'
149
- : 'bg-card/30 border-border text-text opacity-95'
150
- }`}>
151
- <p className="whitespace-pre-wrap leading-relaxed">{msg.text}</p>
152
  </div>
153
- </div>
154
- ))}
155
- {isStreaming && (
156
- <div className="flex items-center gap-2 text-xs text-text-muted font-mono pl-11">
157
- <RefreshCw className="h-3 w-3 animate-spin text-primary" />
158
- <span>Agent is typing...</span>
159
- </div>
160
- )}
161
- <div ref={bottomRef} />
162
- </div>
163
 
164
- {/* Suggested Questions */}
165
- {messages.length === 1 && (
166
- <div className="px-4 py-2 bg-canvas/30 border-t border-border">
167
  <p className="text-[10px] uppercase font-mono tracking-wider text-text-muted font-semibold mb-1.5 flex items-center gap-1">
168
  <Sparkles className="h-3 w-3 text-primary" /> Suggested Questions
169
  </p>
@@ -181,32 +465,38 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
181
  </div>
182
  )}
183
 
184
- {/* Input */}
185
- <div className="p-3 border-t border-border bg-card/25">
186
- <form
187
- onSubmit={(e) => {
188
- e.preventDefault();
189
- handleSend(input);
190
- }}
191
- className="flex gap-2"
192
- >
193
- <input
194
- type="text"
195
- value={input}
196
- onChange={(e) => setInput(e.target.value)}
197
- disabled={isStreaming}
198
- placeholder={`Ask a question about ${repoName}...`}
199
- className="flex-grow bg-canvas border border-border rounded px-3 py-2 text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary font-sans text-text placeholder:text-text-muted/50"
200
- />
201
- <button
202
- type="submit"
203
- disabled={isStreaming || !input.trim()}
204
- className="bg-primary hover:bg-primary-hover text-text font-medium px-3.5 py-2 rounded flex items-center gap-1.5 text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
205
  >
206
- <Send className="h-4 w-4" />
207
- </button>
208
- </form>
209
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  </div>
211
  );
212
  };
 
1
  import React, { useState, useRef, useEffect } from 'react';
2
  import { apiUrl } from '../../lib/api';
3
+ import {
4
+ Send,
5
+ MessageSquareCode,
6
+ Sparkles,
7
+ Bot,
8
+ User,
9
+ RefreshCw,
10
+ FileCode,
11
+ AlertTriangle,
12
+ ChevronDown,
13
+ ChevronUp,
14
+ } from 'lucide-react';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
 
20
  interface Message {
21
  id: string;
22
  sender: 'user' | 'assistant';
23
  text: string;
24
+ /** Sources attached to this assistant message (from the done event). */
25
+ sources?: string[];
26
+ /** 0-100 confidence score attached to this assistant message. */
27
+ confidence?: number;
28
+ /** True when the LLM was unavailable and we fell back to retrieval-only. */
29
+ fallbackMode?: boolean;
30
+ /** True when this is an inline error (no_repo_selected, etc.). */
31
+ isError?: boolean;
32
  }
33
 
34
  interface ChatInterfaceProps {
35
+ /**
36
+ * When non-empty (embedded in AnalysisDashboard), this value is used
37
+ * directly and all resolution fallbacks are skipped.
38
+ *
39
+ * When empty (standalone /chat page), the component resolves the repo
40
+ * via: URL params → localStorage → /api/repos/recent.
41
+ */
42
  repoName: string;
43
  }
44
 
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /** Collapsible source citation block rendered under each assistant message. */
50
+ const SourcesPanel: React.FC<{
51
+ sources: string[];
52
+ confidence: number;
53
+ fallbackMode: boolean;
54
+ }> = ({ sources, confidence, fallbackMode }) => {
55
+ const [open, setOpen] = useState(false);
56
+
57
+ if (sources.length === 0) return null;
58
+
59
+ return (
60
+ <div className="mt-2 border border-border/60 rounded-md overflow-hidden text-xs font-mono">
61
+ {/* Toggle header */}
62
+ <button
63
+ onClick={() => setOpen((v) => !v)}
64
+ className="w-full flex items-center justify-between px-3 py-1.5 bg-canvas/40 hover:bg-canvas/60 transition-colors text-text-muted"
65
+ >
66
+ <span className="flex items-center gap-1.5">
67
+ <FileCode className="h-3 w-3 text-primary" />
68
+ <span>
69
+ {sources.length} source{sources.length !== 1 ? 's' : ''}
70
+ </span>
71
+ {fallbackMode && (
72
+ <span className="ml-2 flex items-center gap-1 text-amber-400">
73
+ <AlertTriangle className="h-3 w-3" />
74
+ <span>fallback mode</span>
75
+ </span>
76
+ )}
77
+ {!fallbackMode && confidence > 0 && (
78
+ <span className="ml-2 text-emerald-500">{confidence}% confidence</span>
79
+ )}
80
+ </span>
81
+ {open ? (
82
+ <ChevronUp className="h-3 w-3" />
83
+ ) : (
84
+ <ChevronDown className="h-3 w-3" />
85
+ )}
86
+ </button>
87
+
88
+ {/* Source list */}
89
+ {open && (
90
+ <ul className="px-3 py-2 space-y-1 bg-canvas/20">
91
+ {sources.map((src) => (
92
+ <li key={src} className="text-text-muted truncate">
93
+ • {src}
94
+ </li>
95
+ ))}
96
+ </ul>
97
+ )}
98
+ </div>
99
+ );
100
+ };
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Main component
104
+ // ---------------------------------------------------------------------------
105
+
106
  export const ChatInterface: React.FC<ChatInterfaceProps> = ({ repoName }) => {
107
+ /**
108
+ * Repo resolution strategy:
109
+ *
110
+ * EMBEDDED MODE (repoName prop is non-empty — inside AnalysisDashboard)
111
+ * → use prop directly, skip all fallbacks.
112
+ *
113
+ * STANDALONE MODE (repoName prop is empty — /chat page)
114
+ * Priority 1: URL params ?owner=...&repo=...
115
+ * Priority 2: localStorage.activeRepo
116
+ * Priority 3: async GET /api/repos/recent → data[0].name
117
+ */
118
+ const [activeRepo, setActiveRepo] = useState<string>(() => {
119
+ // Embedded mode: prop is the direct, authoritative value.
120
+ if (repoName) return repoName;
121
+
122
+ // Standalone mode fallbacks (synchronous part).
123
+ if (typeof window !== 'undefined') {
124
+ const urlParams = new URLSearchParams(window.location.search);
125
+ const owner = urlParams.get('owner');
126
+ const repo = urlParams.get('repo');
127
+ if (owner && repo) return `${owner}/${repo}`;
128
+
129
+ const stored = localStorage.getItem('activeRepo');
130
+ if (stored) return stored;
131
  }
132
+
133
+ // Will be resolved async (Priority 3).
134
+ return '';
135
+ });
136
+
137
+ const [messages, setMessages] = useState<Message[]>([]);
138
  const [input, setInput] = useState('');
139
  const [isStreaming, setIsStreaming] = useState(false);
140
+ /** Shown when the component is in standalone mode and has no repo at all. */
141
+ const [noRepoError, setNoRepoError] = useState(false);
142
  const bottomRef = useRef<HTMLDivElement>(null);
143
 
144
  const suggestedQuestions = [
145
+ 'How are the agent schemas structured?',
146
+ 'Where is the FastAPI router initialized?',
147
+ 'Which files do I edit to add a new service?',
148
  ];
149
 
150
+ // ── Priority 3: async fallback — only runs in standalone mode when prop was empty ──
151
+ useEffect(() => {
152
+ // Skip if prop was provided (embedded) or already resolved.
153
+ if (repoName || activeRepo) return;
154
+
155
+ fetch(apiUrl('/api/repos/recent'))
156
+ .then((res) => res.json())
157
+ .then((data) => {
158
+ if (Array.isArray(data) && data.length > 0) {
159
+ const name: string = data[0].name;
160
+ setActiveRepo(name);
161
+ if (typeof window !== 'undefined') {
162
+ localStorage.setItem('activeRepo', name);
163
+ }
164
+ } else {
165
+ // Backend has no recent repos — show the "no repo" state.
166
+ setNoRepoError(true);
167
+ }
168
+ })
169
+ .catch((err) => {
170
+ console.error('Failed to fetch recent repos for chat', err);
171
+ setNoRepoError(true);
172
+ });
173
+ // intentionally only on mount
174
+ // eslint-disable-next-line react-hooks/exhaustive-deps
175
+ }, []);
176
+
177
+ // ── Sync prop changes into activeRepo (e.g. user switches repos in dashboard) ──
178
+ useEffect(() => {
179
+ if (repoName && repoName !== activeRepo) {
180
+ setActiveRepo(repoName);
181
+ }
182
+ // eslint-disable-next-line react-hooks/exhaustive-deps
183
+ }, [repoName]);
184
+
185
+ // ── Reset greeting whenever the active repo changes ──
186
+ useEffect(() => {
187
+ if (!activeRepo) return;
188
+ setNoRepoError(false);
189
+ setMessages([
190
+ {
191
+ id: 'init',
192
+ sender: 'assistant',
193
+ text: `Hello! I've indexed \`${activeRepo}\`. Ask me anything about its architecture, endpoints, or file structures.`,
194
+ },
195
+ ]);
196
+ }, [activeRepo]);
197
+
198
+ // ── Auto-scroll ──
199
  useEffect(() => {
200
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
201
  }, [messages]);
202
 
203
+ // ── Stream a response from /api/chat ──
204
  const triggerStreamResponse = async (userPrompt: string) => {
205
  setIsStreaming(true);
206
+ const assistantId = Math.random().toString();
207
+
208
+ // Add empty placeholder for the streaming assistant message.
209
+ setMessages((prev) => [
210
  ...prev,
211
+ { id: assistantId, sender: 'assistant', text: '' },
212
  ]);
213
 
214
  try {
 
216
  method: 'POST',
217
  headers: { 'Content-Type': 'application/json' },
218
  body: JSON.stringify({
219
+ repo: activeRepo,
220
  message: userPrompt,
221
+ history: messages.slice(1).map((m) => ({
222
  role: m.sender === 'user' ? 'user' : 'model',
223
+ parts: [m.text],
224
+ })),
225
+ }),
226
  });
227
 
228
+ // Handle non-SSE errors (422 Pydantic validation, 500, etc.)
229
+ if (!response.ok) {
230
+ let detail = `Backend error ${response.status}`;
231
+ try {
232
+ const errBody = await response.json();
233
+ if (errBody?.detail) {
234
+ // Pydantic 422 wraps details in an array
235
+ if (Array.isArray(errBody.detail)) {
236
+ detail = errBody.detail.map((d: any) => d.msg).join('; ');
237
+ } else {
238
+ detail = String(errBody.detail);
239
+ }
240
+ }
241
+ } catch {
242
+ /* ignore parse error */
243
+ }
244
+ setMessages((prev) =>
245
+ prev.map((msg) =>
246
+ msg.id === assistantId
247
+ ? { ...msg, text: detail, isError: true }
248
+ : msg,
249
+ ),
250
+ );
251
+ return;
252
+ }
253
+
254
+ if (!response.body) throw new Error('ReadableStream not available');
255
 
256
  const reader = response.body.getReader();
257
  const decoder = new TextDecoder();
 
260
  while (!finished) {
261
  const { value, done } = await reader.read();
262
  finished = done;
263
+ if (!value) continue;
264
+
265
+ const raw = decoder.decode(value);
266
+ const lines = raw.split('\n');
267
+
268
+ for (const line of lines) {
269
+ if (!line.startsWith('data: ')) continue;
270
+ try {
271
+ const data = JSON.parse(line.slice(6));
272
+
273
+ // Inline SSE error from the backend (no_repo_selected, etc.)
274
+ if (data.error) {
275
+ setMessages((prev) =>
276
+ prev.map((msg) =>
277
+ msg.id === assistantId
278
+ ? { ...msg, text: data.message ?? data.error, isError: true }
279
+ : msg,
280
+ ),
281
+ );
282
+ continue;
283
  }
284
+
285
+ // Streaming text token
286
+ if (data.text) {
287
+ setMessages((prev) =>
288
+ prev.map((msg) =>
289
+ msg.id === assistantId
290
+ ? { ...msg, text: msg.text + data.text }
291
+ : msg,
292
+ ),
293
+ );
294
+ }
295
+
296
+ // Terminal done event — attach sources, confidence, fallback flag
297
+ if (data.status === 'done') {
298
+ setMessages((prev) =>
299
+ prev.map((msg) =>
300
+ msg.id === assistantId
301
+ ? {
302
+ ...msg,
303
+ sources: data.sources ?? [],
304
+ confidence: data.confidence ?? 0,
305
+ fallbackMode: data.fallback_mode ?? false,
306
+ }
307
+ : msg,
308
+ ),
309
+ );
310
+ }
311
+ } catch {
312
+ // Ignore malformed / partial SSE lines
313
  }
314
  }
315
  }
316
  } catch (error) {
317
+ console.error('Chat stream error:', error);
318
+ setMessages((prev) =>
319
+ prev.map((msg) =>
320
+ msg.id === assistantId
321
+ ? {
322
+ ...msg,
323
+ text: 'Could not reach the agent backend. Please verify that api.py is running on port 8001.',
324
+ isError: true,
325
+ }
326
+ : msg,
327
+ ),
328
  );
329
  } finally {
330
  setIsStreaming(false);
 
334
  const handleSend = async (textToSend: string) => {
335
  if (!textToSend.trim() || isStreaming) return;
336
 
337
+ setMessages((prev) => [
 
338
  ...prev,
339
+ { id: Math.random().toString(), sender: 'user', text: textToSend },
340
  ]);
341
  setInput('');
342
 
343
  await triggerStreamResponse(textToSend);
344
  };
345
 
346
+ // ---------------------------------------------------------------------------
347
+ // Render
348
+ // ---------------------------------------------------------------------------
349
+
350
  return (
351
+ <div className="flex flex-col min-h-[550px] h-full border border-border bg-card/10 rounded-lg overflow-hidden w-full">
352
  {/* Header */}
353
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border bg-card/40 shrink-0">
354
  <div className="flex items-center gap-2">
355
  <MessageSquareCode className="h-4 w-4 text-primary" />
356
+ <span className="font-mono text-sm font-semibold text-text">
357
+ Repository Chat Assistant
358
+ </span>
359
  </div>
360
+ <span className="text-[10px] font-mono border border-primary/30 text-primary px-2 py-0.5 rounded bg-primary/5 truncate max-w-[180px]">
361
+ {activeRepo || 'no repo selected'}
362
  </span>
363
  </div>
364
 
365
+ {/* No-repo state (standalone mode, no resolution succeeded) */}
366
+ {noRepoError && (
367
+ <div className="flex-grow flex flex-col items-center justify-center p-8 gap-3 text-center">
368
+ <AlertTriangle className="h-8 w-8 text-amber-400" />
369
+ <p className="text-sm font-mono text-text-muted">
370
+ No repository selected.
371
+ </p>
372
+ <p className="text-xs text-text-muted max-w-xs">
373
+ Analyse a repository first, then return here to chat with its
374
+ codebase.
375
+ </p>
376
+ </div>
377
+ )}
378
+
379
+ {/* Message thread */}
380
+ {!noRepoError && (
381
+ <div className="flex-grow overflow-y-auto p-4 space-y-4 font-sans text-sm">
382
+ {messages.map((msg) => (
383
+ <div
384
+ key={msg.id}
385
+ className={`flex gap-3 ${
386
+ msg.sender === 'user' ? 'ml-auto flex-row-reverse max-w-[85%]' : 'max-w-[92%]'
387
+ }`}
388
+ >
389
+ {/* Avatar */}
390
+ <div
391
+ className={`h-8 w-8 rounded-full flex items-center justify-center shrink-0 border ${
392
+ msg.sender === 'user'
393
+ ? 'bg-primary/10 border-primary/30 text-primary'
394
+ : msg.isError
395
+ ? 'bg-red-500/10 border-red-500/30 text-red-400'
396
+ : 'bg-card border-border text-text-muted'
397
+ }`}
398
+ >
399
+ {msg.sender === 'user' ? (
400
+ <User className="h-4 w-4" />
401
+ ) : (
402
+ <Bot className="h-4 w-4" />
403
+ )}
404
+ </div>
405
+
406
+ {/* Bubble + sources */}
407
+ <div className="flex flex-col gap-1 min-w-0">
408
+ <div
409
+ className={`p-3 rounded-lg border ${
410
+ msg.sender === 'user'
411
+ ? 'bg-primary/10 border-primary/20 text-text'
412
+ : msg.isError
413
+ ? 'bg-red-500/5 border-red-500/20 text-red-400'
414
+ : msg.fallbackMode
415
+ ? 'bg-amber-500/5 border-amber-500/20 text-text opacity-95'
416
+ : 'bg-card/30 border-border text-text opacity-95'
417
+ }`}
418
+ >
419
+ <p className="whitespace-pre-wrap leading-relaxed break-words">
420
+ {msg.text}
421
+ </p>
422
+ </div>
423
+
424
+ {/* Sources panel — only for assistant messages with sources */}
425
+ {msg.sender === 'assistant' &&
426
+ msg.sources &&
427
+ msg.sources.length > 0 && (
428
+ <SourcesPanel
429
+ sources={msg.sources}
430
+ confidence={msg.confidence ?? 0}
431
+ fallbackMode={msg.fallbackMode ?? false}
432
+ />
433
+ )}
434
+ </div>
435
  </div>
436
+ ))}
437
 
438
+ {isStreaming && (
439
+ <div className="flex items-center gap-2 text-xs text-text-muted font-mono pl-11">
440
+ <RefreshCw className="h-3 w-3 animate-spin text-primary" />
441
+ <span>Agent is thinking...</span>
 
 
 
442
  </div>
443
+ )}
444
+ <div ref={bottomRef} />
445
+ </div>
446
+ )}
 
 
 
 
 
 
447
 
448
+ {/* Suggested questions — shown only after the greeting message */}
449
+ {!noRepoError && messages.length === 1 && (
450
+ <div className="px-4 py-2 bg-canvas/30 border-t border-border shrink-0">
451
  <p className="text-[10px] uppercase font-mono tracking-wider text-text-muted font-semibold mb-1.5 flex items-center gap-1">
452
  <Sparkles className="h-3 w-3 text-primary" /> Suggested Questions
453
  </p>
 
465
  </div>
466
  )}
467
 
468
+ {/* Input bar */}
469
+ {!noRepoError && (
470
+ <div className="p-3 border-t border-border bg-card/25 shrink-0">
471
+ <form
472
+ onSubmit={(e) => {
473
+ e.preventDefault();
474
+ handleSend(input);
475
+ }}
476
+ className="flex gap-2"
 
 
 
 
 
 
 
 
 
 
 
 
477
  >
478
+ <input
479
+ type="text"
480
+ value={input}
481
+ onChange={(e) => setInput(e.target.value)}
482
+ disabled={isStreaming || !activeRepo}
483
+ placeholder={
484
+ activeRepo
485
+ ? `Ask a question about ${activeRepo}...`
486
+ : 'Waiting for repository...'
487
+ }
488
+ className="flex-grow bg-canvas border border-border rounded px-3 py-2 text-sm focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary font-sans text-text placeholder:text-text-muted/50"
489
+ />
490
+ <button
491
+ type="submit"
492
+ disabled={isStreaming || !input.trim() || !activeRepo}
493
+ className="bg-primary hover:bg-primary-hover text-text font-medium px-3.5 py-2 rounded flex items-center gap-1.5 text-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
494
+ >
495
+ <Send className="h-4 w-4" />
496
+ </button>
497
+ </form>
498
+ </div>
499
+ )}
500
  </div>
501
  );
502
  };
frontend/src/components/interactive/IssueMapper.tsx CHANGED
@@ -25,7 +25,20 @@ interface IssueMapperProps {
25
  }
26
 
27
  export const IssueMapper: React.FC<IssueMapperProps> = ({ repoName }) => {
28
- const [selectedRepo, setSelectedRepo] = useState(repoName || '');
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  const [recentRepos, setRecentRepos] = useState<{ name: string }[]>([]);
30
  const [title, setTitle] = useState('');
31
  const [description, setDescription] = useState('');
 
25
  }
26
 
27
  export const IssueMapper: React.FC<IssueMapperProps> = ({ repoName }) => {
28
+ const [selectedRepo, setSelectedRepo] = useState(() => {
29
+ if (repoName) return repoName;
30
+ if (typeof window !== 'undefined') {
31
+ const urlParams = new URLSearchParams(window.location.search);
32
+ const owner = urlParams.get('owner');
33
+ const repo = urlParams.get('repo');
34
+ if (owner && repo) {
35
+ return `${owner}/${repo}`;
36
+ }
37
+ const stored = localStorage.getItem('activeRepo');
38
+ if (stored) return stored;
39
+ }
40
+ return '';
41
+ });
42
  const [recentRepos, setRecentRepos] = useState<{ name: string }[]>([]);
43
  const [title, setTitle] = useState('');
44
  const [description, setDescription] = useState('');
frontend/src/components/interactive/RepoInput.tsx CHANGED
@@ -50,7 +50,7 @@ export const RepoInput: React.FC = () => {
50
  body: JSON.stringify({
51
  url: repoUrl,
52
  branch: 'main',
53
- model: 'Gemini 2.5 Flash'
54
  })
55
  });
56
 
@@ -97,8 +97,11 @@ export const RepoInput: React.FC = () => {
97
  if (repoPath) {
98
  const parts = repoPath.split('/')
99
  if (parts.length === 2) {
100
- const [owner, repo] = parts
101
- window.location.href = `/analysis?owner=${owner}&repo=${repo}`
 
 
 
102
  } else {
103
  setErrorMessage('Invalid repo format received');
104
  setIsAnalyzing(false);
@@ -178,10 +181,9 @@ export const RepoInput: React.FC = () => {
178
  <div className="space-y-1">
179
  <span className="font-bold uppercase tracking-wider text-[10px] block">Analysis Failed</span>
180
  <p className="leading-relaxed">{errorMessage}</p>
181
- {errorMessage.includes('quota') || errorMessage.includes('429') || errorMessage.includes('RESOURCE_EXHAUSTED') ? (
182
  <p className="text-red-300/70 text-[10px] mt-1">
183
- Gemini API rate limit reached. Please wait a minute before retrying, or check your API quota at{' '}
184
- <a href="https://ai.dev/rate-limit" target="_blank" rel="noopener noreferrer" className="underline">ai.dev/rate-limit</a>.
185
  </p>
186
  ) : null}
187
  </div>
 
50
  body: JSON.stringify({
51
  url: repoUrl,
52
  branch: 'main',
53
+ model: 'deepseek-ai/deepseek-v4-flash'
54
  })
55
  });
56
 
 
97
  if (repoPath) {
98
  const parts = repoPath.split('/')
99
  if (parts.length === 2) {
100
+ const [owner, repo] = parts;
101
+ if (typeof window !== 'undefined') {
102
+ localStorage.setItem('activeRepo', repoPath);
103
+ }
104
+ window.location.href = `/analysis?owner=${owner}&repo=${repo}`;
105
  } else {
106
  setErrorMessage('Invalid repo format received');
107
  setIsAnalyzing(false);
 
181
  <div className="space-y-1">
182
  <span className="font-bold uppercase tracking-wider text-[10px] block">Analysis Failed</span>
183
  <p className="leading-relaxed">{errorMessage}</p>
184
+ {errorMessage.includes('quota') || errorMessage.includes('429') || errorMessage.includes('RESOURCE_EXHAUSTED') || errorMessage.includes('rate limit') ? (
185
  <p className="text-red-300/70 text-[10px] mt-1">
186
+ AI provider rate limit reached. Please wait a moment before retrying.
 
187
  </p>
188
  ) : null}
189
  </div>
frontend/src/pages/chat.astro CHANGED
@@ -19,7 +19,7 @@ import { MessageSquare } from 'lucide-react';
19
 
20
  {/* Chat Interface react island */}
21
  <div class="w-full flex justify-center flex-grow pt-2">
22
- <ChatInterface repoName="current/workspace" client:load />
23
  </div>
24
  </div>
25
  </Layout>
 
19
 
20
  {/* Chat Interface react island */}
21
  <div class="w-full flex justify-center flex-grow pt-2">
22
+ <ChatInterface repoName="" client:load />
23
  </div>
24
  </div>
25
  </Layout>
services/architecture_service.py CHANGED
@@ -111,6 +111,22 @@ class ArchitectureService:
111
  )
112
  logger.info("Parsed %d supported source files", len(parsed))
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  # ----------------------------------------------------------
115
  # Step 2: Detect entry points
116
  # ----------------------------------------------------------
 
111
  )
112
  logger.info("Parsed %d supported source files", len(parsed))
113
 
114
+ # Guard: if zero files were parsed, the graph will be empty and all
115
+ # downstream features (Reading Path, Impact Analysis, Architecture Graph)
116
+ # will fail. Log at ERROR level so it's immediately visible in server
117
+ # logs. We do NOT raise here — the empty graph is still saved so that
118
+ # the API endpoint can return a descriptive 404 rather than crashing.
119
+ if len(parsed) == 0:
120
+ logger.error(
121
+ "[PIPELINE:%s] architecture_service.build produced ZERO parsed files. "
122
+ "repo_path='%s' files_provided=%s. "
123
+ "The dependency graph will be empty. "
124
+ "Check CLONED_REPOS_PATH env var and that source files are reachable.",
125
+ repo_name,
126
+ repo_path or "(none)",
127
+ files is not None,
128
+ )
129
+
130
  # ----------------------------------------------------------
131
  # Step 2: Detect entry points
132
  # ----------------------------------------------------------
services/chat_fallback.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Chat Fallback Service.
2
+
3
+ Generates a retrieval-grounded answer from indexed code chunks when the LLM
4
+ provider is unavailable (429 / 5xx / timeout). No LLM call is made — the
5
+ retrieved chunks are formatted directly into a readable response.
6
+
7
+ Used exclusively by backend/api.py chat_streamer() when provider.stream()
8
+ raises after exhausting its retry budget.
9
+ """
10
+
11
+ from typing import List, Dict, Any
12
+
13
+
14
+ def build_fallback_answer(chunks: List[Dict[str, Any]], question: str) -> str:
15
+ """Build a plain-text fallback answer from retrieved chunks.
16
+
17
+ Args:
18
+ chunks: List of Chroma chunk dicts — each has 'content' and
19
+ 'metadata' keys (as returned by chroma_store.search_repository).
20
+ question: The original user question — echoed back for context.
21
+
22
+ Returns:
23
+ A formatted string suitable for yielding directly as SSE text tokens.
24
+ Never contains raw HTTP errors, provider names, or exception details.
25
+ """
26
+ if not chunks:
27
+ return (
28
+ "The AI provider is temporarily unavailable and no indexed content "
29
+ "was found for this query.\n\n"
30
+ "Please ensure the repository has been analysed and try again shortly."
31
+ )
32
+
33
+ header = (
34
+ "The AI provider is temporarily unavailable. "
35
+ "Here are the most relevant sections from the indexed codebase:\n\n"
36
+ )
37
+
38
+ lines: List[str] = [header]
39
+
40
+ # Deduplicate by file path — show each file once with its best chunk
41
+ seen_paths: set = set()
42
+ included = 0
43
+
44
+ for chunk in chunks:
45
+ meta = chunk.get("metadata", {})
46
+ file_path = meta.get("file_path", "unknown")
47
+ content = chunk.get("content", "").strip()
48
+
49
+ if not content or file_path in seen_paths:
50
+ continue
51
+
52
+ seen_paths.add(file_path)
53
+ included += 1
54
+
55
+ # Truncate long chunks for readability — 600 chars is enough signal
56
+ preview = content[:600]
57
+ if len(content) > 600:
58
+ preview += "\n... [truncated]"
59
+
60
+ lines.append(f"📄 **{file_path}**\n```\n{preview}\n```\n")
61
+
62
+ # Cap at 4 files to keep the response focused
63
+ if included >= 4:
64
+ break
65
+
66
+ if included == 0:
67
+ return (
68
+ "The AI provider is temporarily unavailable and the retrieved chunks "
69
+ "contained no usable content for this query.\n\n"
70
+ "Please try again shortly."
71
+ )
72
+
73
+ lines.append(
74
+ "\n---\n"
75
+ "⚠️ *Fallback mode — AI synthesis unavailable. "
76
+ "Showing raw retrieved context only. "
77
+ "Retry your question once the provider recovers.*"
78
+ )
79
+
80
+ return "".join(lines)
81
+
82
+
83
+ def is_provider_error(exc: Exception) -> bool:
84
+ """Return True if the exception looks like an LLM provider failure.
85
+
86
+ Used to decide whether to trigger fallback mode vs. re-raise for
87
+ unexpected errors (e.g., embedding failures, Chroma errors).
88
+ """
89
+ err_str = str(exc).lower()
90
+ provider_signals = [
91
+ "429",
92
+ "too many requests",
93
+ "rate limit",
94
+ "503",
95
+ "502",
96
+ "500",
97
+ "service unavailable",
98
+ "bad gateway",
99
+ "timeout",
100
+ "connect error",
101
+ "connection refused",
102
+ "nvidia",
103
+ "nim",
104
+ "deepseek",
105
+ "max retries exceeded",
106
+ "statuserror",
107
+ ]
108
+ return any(signal in err_str for signal in provider_signals)
services/embedding_service.py CHANGED
@@ -26,7 +26,6 @@ _MODEL_NAME = "BAAI/bge-small-en-v1.5"
26
  def _get_model():
27
  """Return the cached SentenceTransformer model, loading it on first call."""
28
  global _model
29
- print("[EMBED] ENTER _get_model()")
30
  if _model is not None:
31
  return _model
32
 
@@ -36,11 +35,9 @@ def _get_model():
36
 
37
  try:
38
  from sentence_transformers import SentenceTransformer # type: ignore
39
- print("[EMBED] Loading SentenceTransformer...")
40
  logger.info("Loading BGE embedding model '%s' (first call)…", _MODEL_NAME)
41
  t0 = time.perf_counter()
42
  _model = SentenceTransformer(_MODEL_NAME)
43
- print("[EMBED] SentenceTransformer loaded")
44
  logger.info(
45
  "BGE model loaded successfully. elapsed=%.2fs",
46
  time.perf_counter() - t0,
@@ -97,11 +94,10 @@ class EmbeddingService:
97
  "EmbeddingService: 'client' parameter is ignored — using local BGE model."
98
  )
99
  self.model_name = model_name
100
- # Eagerly trigger model loading so the first real request is fast.
101
- try:
102
- _get_model()
103
- except Exception as exc:
104
- logger.warning("Could not pre-load BGE model at init time: %s", exc)
105
 
106
  # ------------------------------------------------------------------
107
  # Core embedding methods
 
26
  def _get_model():
27
  """Return the cached SentenceTransformer model, loading it on first call."""
28
  global _model
 
29
  if _model is not None:
30
  return _model
31
 
 
35
 
36
  try:
37
  from sentence_transformers import SentenceTransformer # type: ignore
 
38
  logger.info("Loading BGE embedding model '%s' (first call)…", _MODEL_NAME)
39
  t0 = time.perf_counter()
40
  _model = SentenceTransformer(_MODEL_NAME)
 
41
  logger.info(
42
  "BGE model loaded successfully. elapsed=%.2fs",
43
  time.perf_counter() - t0,
 
94
  "EmbeddingService: 'client' parameter is ignored — using local BGE model."
95
  )
96
  self.model_name = model_name
97
+ # ponytail: lazy load — model loads on first generate_embedding(s) call.
98
+ # Eager load duplicated under uvicorn --reload (reloader parent + worker
99
+ # both instantiated EmbeddingService at import time). Singleton in
100
+ # _get_model() still guarantees one load per process.
 
101
 
102
  # ------------------------------------------------------------------
103
  # Core embedding methods
services/github_service.py CHANGED
@@ -12,6 +12,8 @@ import logging
12
  from typing import Dict, List, Any, Optional
13
  import requests
14
 
 
 
15
  logger = logging.getLogger(__name__)
16
 
17
 
@@ -80,8 +82,10 @@ class GitHubService:
80
  Returns:
81
  The local directory path.
82
  """
83
- # We store cloned repos in a directory under the current project's data directory
84
- base_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "cloned_repos")
 
 
85
  safe_name = repo_fullName.replace("/", "_")
86
  return os.path.join(base_dir, safe_name)
87
 
 
12
  from typing import Dict, List, Any, Optional
13
  import requests
14
 
15
+ from services.storage_paths import get_cloned_repos_dir
16
+
17
  logger = logging.getLogger(__name__)
18
 
19
 
 
82
  Returns:
83
  The local directory path.
84
  """
85
+ # Store clones OUTSIDE the project tree so uvicorn --reload (WatchFiles)
86
+ # does not treat clone activity as source-code changes. Configurable via
87
+ # CLONED_REPOS_PATH; defaults to ~/.repo_intelligence/cloned_repos.
88
+ base_dir = str(get_cloned_repos_dir())
89
  safe_name = repo_fullName.replace("/", "_")
90
  return os.path.join(base_dir, safe_name)
91
 
services/storage_paths.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Resolve writable storage locations outside the project tree.
2
+
3
+ Cloned repositories MUST live outside the project root, otherwise
4
+ `uvicorn --reload` (WatchFiles) treats every cloned file as a source-code
5
+ change and kills the in-flight analysis request.
6
+
7
+ Resolution order:
8
+ 1. `CLONED_REPOS_PATH` env var (recommended, e.g. C:/repo_intelligence_storage/cloned_repos)
9
+ 2. `~/.repo_intelligence/cloned_repos` (cross-platform fallback)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from pathlib import Path
16
+
17
+
18
+ def get_cloned_repos_dir() -> Path:
19
+ """Return the base directory for cloned repositories, creating it if needed.
20
+
21
+ Never returns a path inside the project tree — keeps WatchFiles quiet.
22
+ """
23
+ raw = os.environ.get("CLONED_REPOS_PATH", "").strip()
24
+ base = Path(raw).expanduser() if raw else Path.home() / ".repo_intelligence" / "cloned_repos"
25
+ base.mkdir(parents=True, exist_ok=True)
26
+ return base
27
+
28
+
29
+ if __name__ == "__main__":
30
+ # ponytail: minimal self-check — proves the dir exists and lives outside cwd.
31
+ p = get_cloned_repos_dir()
32
+ assert p.exists() and p.is_dir(), f"clone dir not created: {p}"
33
+ assert Path.cwd() not in p.parents and p != Path.cwd(), f"clone dir inside project: {p}"
34
+ print(f"OK cloned_repos_dir={p}")