Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ jobs:
python -m pip install -e . --no-deps
python -m pip install pytest pytest-asyncio pydantic pydantic-settings click PyYAML fastapi structlog ruff mypy mcp httpx

- name: Run Milestone 0-4 tests
run: PYTHONPATH=src python -m pytest tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py
- name: Run Milestone 0-5 tests
run: PYTHONPATH=src python -m pytest tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py

- name: Run Milestone 0-4 lint baseline
run: ruff check src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/cli.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py scripts/evalfly_smoke.py
- name: Run Milestone 0-5 lint baseline
run: ruff check src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/graph src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/cli.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py

- name: Run Milestone 0-4 type baseline
run: PYTHONPATH=src mypy src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py scripts/evalfly_smoke.py
- name: Run Milestone 0-5 type baseline
run: PYTHONPATH=src mypy src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/graph src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py

- name: Compile changed runtime modules
run: python -m py_compile src/coderag/__init__.py src/coderag/config.py src/coderag/main.py src/coderag/cli.py src/coderag/indexing/embeddings.py src/coderag/indexing/vectorstore.py src/coderag/generation/*.py src/coderag/models/*.py src/coderag/retrieval/*.py src/coderag/services/*.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py scripts/evalfly_smoke.py
run: python -m py_compile src/coderag/__init__.py src/coderag/config.py src/coderag/main.py src/coderag/cli.py src/coderag/indexing/embeddings.py src/coderag/indexing/vectorstore.py src/coderag/generation/*.py src/coderag/models/*.py src/coderag/retrieval/*.py src/coderag/graph/*.py src/coderag/services/*.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py

- name: Build package
run: python -m build
Expand Down
21 changes: 20 additions & 1 deletion .pi/.specsafe-state.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"currentSlice": null,
"history": [
{
"sliceId": "CRAG-20260627-ROADMAP",
Expand Down Expand Up @@ -140,6 +139,26 @@
"turns": 0
}
}
},
{
"sliceId": "CRAG-20260630-M5-CODE-INTELLIGENCE-GRAPH",
"workspaceId": "CodeRAG",
"sessionId": "omp-session",
"beganAt": "2026-06-30T21:02:39.132Z",
"endedAt": "2026-06-30T21:32:20.419Z",
"outcome": "PASS",
"costSummary": {
"externalMemoryCalls": 0,
"externalMemoryCost": 0,
"subagentTokens": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0,
"cost": 0,
"turns": 0
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# CodeRAG Milestone 5: Code Intelligence Graph Contract

Spec-Slice: CRAG-20260630-M5-CODE-INTELLIGENCE-GRAPH

## Scope

Milestone 5 adds a deterministic, local code-intelligence graph that can answer graph questions without LLM generation.

## Acceptance

1. Extract symbol graph data for Python and TypeScript/JavaScript documents.
2. Persist graph facts in the SQLite registry, keyed by repository and file path.
3. Support incremental replacement by file path so changed files do not require a full graph rebuild.
4. Expose service, REST, MCP, and CLI paths for:
- `find_symbol`
- `find_references`
- `get_blast_radius`
5. Return explicit capability flags when a language is unsupported and falls back to text retrieval only.
6. Include graph evidence in blast radius: impacted files, symbols, direct edges, associated tests, and reason strings.

## Required tests

- Parser fixtures for Python and TS/JS symbols/imports/calls/references.
- Registry persistence and per-file replacement tests.
- Service tests for symbol lookup, references, blast radius, and unsupported-language capability fallback.
- REST/MCP/CLI adapter shape tests.
- EvalFly smoke cases for graph model, service, and adapter exposure.

## Verification

Run before closing the slice:

```bash
PYTHONPATH=src .venv/bin/python -m pytest tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py
.venv/bin/ruff check src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/cli.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_mcp_tools.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py
PYTHONPATH=src .venv/bin/mypy src/coderag/generation src/coderag/indexing/vectorstore.py src/coderag/models src/coderag/retrieval src/coderag/services src/coderag/config.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_mcp_handlers.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py
python -m py_compile src/coderag/__init__.py src/coderag/config.py src/coderag/main.py src/coderag/cli.py src/coderag/indexing/embeddings.py src/coderag/indexing/vectorstore.py src/coderag/generation/*.py src/coderag/models/*.py src/coderag/retrieval/*.py src/coderag/services/*.py src/coderag/api/schemas.py src/coderag/api/routes.py src/coderag/mcp/handlers.py src/coderag/mcp/tools.py src/coderag/ui/grounding.py src/coderag/ui/repository_metadata.py src/coderag/ui/handlers.py tests/test_m0_baseline.py tests/test_m1_services.py tests/test_m2_registry.py tests/test_citations.py tests/test_m3_citations.py tests/test_api_routes.py tests/test_ui_handlers.py tests/test_m4_hybrid_retrieval.py tests/test_m5_code_graph.py scripts/evalfly_smoke.py
python scripts/evalfly_smoke.py --suite smoke
.venv/bin/python -m build
```
132 changes: 132 additions & 0 deletions evals/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,138 @@
}
]
}
},
{
"schema_version": "evalfly.case.v1",
"case_id": "m5-code-graph-contract-exists",
"title": "Milestone 5 code graph contract exists",
"suite": "smoke",
"risk_tier": "critical",
"task_type": "delivery_governance",
"source": {
"kind": "acceptance_criteria",
"criteria": "M5-AC1 M5-AC2 M5-AC3"
},
"privacy": {
"classification": "public",
"sanitized": true
},
"expected": {
"success_criteria": [
"The Milestone 5 contract is committed for code graph intelligence and blast radius."
]
},
"judge": {
"type": "deterministic",
"assertions": [
{
"type": "file_exists",
"path": "docs/superpowers/plans/2026-06-30-m5-code-intelligence-graph-contract.md"
}
]
}
},
{
"schema_version": "evalfly.case.v1",
"case_id": "m5-code-graph-extractor-exists",
"title": "Code graph extractor exists",
"suite": "smoke",
"risk_tier": "critical",
"task_type": "code_intelligence",
"source": {
"kind": "acceptance_criteria",
"criteria": "M5-AC1"
},
"privacy": {
"classification": "public",
"sanitized": true
},
"expected": {
"success_criteria": [
"CodeGraphExtractor is implemented as the graph fact extraction seam."
]
},
"judge": {
"type": "deterministic",
"assertions": [
{
"type": "file_contains",
"path": "src/coderag/graph/extractor.py",
"text": "class CodeGraphExtractor"
}
]
}
},
{
"schema_version": "evalfly.case.v1",
"case_id": "m5-graph-adapters-exist",
"title": "Code graph adapters exist",
"suite": "smoke",
"risk_tier": "critical",
"task_type": "adapter_contract",
"source": {
"kind": "acceptance_criteria",
"criteria": "M5-AC2 M5-AC3"
},
"privacy": {
"classification": "public",
"sanitized": true
},
"expected": {
"success_criteria": [
"REST, MCP, and CLI expose symbol, reference, and blast radius graph paths."
]
},
"judge": {
"type": "deterministic",
"assertions": [
{
"type": "file_contains",
"path": "src/coderag/api/routes.py",
"text": "@router.post(\"/graph/blast-radius\""
},
{
"type": "file_contains",
"path": "src/coderag/mcp/tools.py",
"text": "async def get_blast_radius"
},
{
"type": "file_contains",
"path": "src/coderag/cli.py",
"text": "@cli.command(\"blast-radius\")"
}
]
}
},
{
"schema_version": "evalfly.case.v1",
"case_id": "m5-targeted-tests-exist",
"title": "Milestone 5 targeted tests exist",
"suite": "smoke",
"risk_tier": "critical",
"task_type": "test_coverage",
"source": {
"kind": "acceptance_criteria",
"criteria": "M5-AC1 M5-AC2 M5-AC3"
},
"privacy": {
"classification": "public",
"sanitized": true
},
"expected": {
"success_criteria": [
"Targeted code graph extraction, registry, service, and adapter tests are committed."
]
},
"judge": {
"type": "deterministic",
"assertions": [
{
"type": "file_exists",
"path": "tests/test_m5_code_graph.py"
}
]
}
}
]
}
32 changes: 32 additions & 0 deletions src/coderag/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
from fastapi import APIRouter, BackgroundTasks, HTTPException

from coderag.api.schemas import (
BlastRadiusResponse,
CitationResponse,
CitationVerificationResponse,
ContextPackRequest,
ContextPackResponse,
ContextSnippetResponse,
FindReferencesResponse,
FindSymbolResponse,
GraphQueryRequest,
GraphReferenceResponse,
GraphSymbolResponse,
IndexRepositoryRequest,
IndexRepositoryResponse,
ListRepositoriesResponse,
Expand All @@ -20,6 +26,7 @@
)
from coderag.logging import get_logger
from coderag.models.repository import Repository
from coderag.services.graph import CodeGraphService
from coderag.services.indexing import IndexingOptions, IndexingService
from coderag.services.registry import RepositoryRegistry
from coderag.services.retrieval import RetrievalService
Expand All @@ -30,6 +37,7 @@
registry = RepositoryRegistry()
indexing_service = IndexingService(registry=registry)
retrieval_service = RetrievalService(registry=registry)
graph_service = CodeGraphService(registry=registry)


def get_repo_or_404(repo_id: str) -> Repository:
Expand Down Expand Up @@ -210,6 +218,30 @@ async def context_pack(request: ContextPackRequest) -> ContextPackResponse:
)


@router.post("/graph/find-symbol", response_model=FindSymbolResponse)
def find_symbol(request: GraphQueryRequest) -> FindSymbolResponse:
"""Find graph symbols by name."""
repo = get_repo_or_404(request.repo_id)
symbols = graph_service.find_symbol(repo.id, request.symbol)
return FindSymbolResponse(symbols=[GraphSymbolResponse(**symbol.to_dict()) for symbol in symbols])


@router.post("/graph/find-references", response_model=FindReferencesResponse)
def find_references(request: GraphQueryRequest) -> FindReferencesResponse:
"""Find graph references by symbol name."""
repo = get_repo_or_404(request.repo_id)
references = graph_service.find_references(repo.id, request.symbol)
return FindReferencesResponse(references=[GraphReferenceResponse(**reference.to_dict()) for reference in references])


@router.post("/graph/blast-radius", response_model=BlastRadiusResponse)
def get_blast_radius(request: GraphQueryRequest) -> BlastRadiusResponse:
"""Return graph-backed blast radius evidence for a symbol."""
repo = get_repo_or_404(request.repo_id)
blast = graph_service.get_blast_radius(repo.id, request.symbol)
return BlastRadiusResponse(**blast.to_dict())


@router.get("/repos", response_model=ListRepositoriesResponse)
async def list_repositories() -> ListRepositoriesResponse:
"""List all repositories."""
Expand Down
47 changes: 47 additions & 0 deletions src/coderag/api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,53 @@ class ContextPackResponse(BaseModel):
budget: dict[str, int]
capabilities: dict[str, object]


class GraphQueryRequest(BaseModel):
"""Request for graph symbol queries."""

repo_id: str = Field(..., description="Repository ID to query")
symbol: str = Field(..., description="Symbol name")


class GraphSymbolResponse(BaseModel):
repo_id: str
file_path: str
name: str
kind: str
language: str | None = None
start_line: int
end_line: int
container: str | None = None


class GraphReferenceResponse(BaseModel):
repo_id: str
file_path: str
symbol_name: str
reference_kind: str
start_line: int
end_line: int
source_name: str | None = None


class FindSymbolResponse(BaseModel):
symbols: list[GraphSymbolResponse]


class FindReferencesResponse(BaseModel):
references: list[GraphReferenceResponse]


class BlastRadiusResponse(BaseModel):
repo_id: str
symbol: str
impacted_files: list[str]
impacted_symbols: list[str]
associated_tests: list[str]
edges: list[dict[str, object]]
reasons: list[str]
capabilities: dict[str, object]

class RepositoryInfo(BaseModel):
"""Repository information."""

Expand Down
Loading
Loading