mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
- Go LSP cross-file type resolution engine (internal/cbm/lsp/) with C-based type registry, scope tracking, and Go stdlib data (30K+ definitions) - lsp_bridge.go: CrossFileDef struct and RunGoLSPCrossFile CGo bridge - config.go: persistent ConfigStore (SQLite) for auto_index, mem_limit settings - CLI config subcommand for get/set/list/delete operations - Store: edge batch upsert, project config columns, goleak test harness - Pipeline: prefetch parallelism, resolver improvements, memleak tests - CI: dry-run workflow improvements, vendor-grammar.sh enhancements
202 lines
5.5 KiB
Go
202 lines
5.5 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
func (s *Server) handleListProjects(_ context.Context, _ *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
projectInfos, err := s.router.ListProjects()
|
|
if err != nil {
|
|
return errResult(fmt.Sprintf("list projects: %v", err)), nil
|
|
}
|
|
|
|
type projectEntry struct {
|
|
Name string `json:"name"`
|
|
RootPath string `json:"root_path"`
|
|
IndexedAt string `json:"indexed_at"`
|
|
Nodes int `json:"nodes"`
|
|
Edges int `json:"edges"`
|
|
DBPath string `json:"db_path"`
|
|
ADRPresent bool `json:"adr_present"`
|
|
IsSessionProject bool `json:"is_session_project,omitempty"`
|
|
}
|
|
|
|
result := make([]projectEntry, 0, len(projectInfos))
|
|
for _, info := range projectInfos {
|
|
st, err := s.router.ForProject(info.Name)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
nc, _ := st.CountNodes(info.Name)
|
|
ec, _ := st.CountEdges(info.Name)
|
|
|
|
indexedAt := ""
|
|
rootPath := info.RootPath
|
|
proj, _ := st.GetProject(info.Name)
|
|
if proj != nil {
|
|
indexedAt = proj.IndexedAt
|
|
rootPath = proj.RootPath
|
|
}
|
|
|
|
adr, _ := st.GetADR(info.Name)
|
|
|
|
entry := projectEntry{
|
|
Name: info.Name,
|
|
RootPath: rootPath,
|
|
IndexedAt: indexedAt,
|
|
Nodes: nc,
|
|
Edges: ec,
|
|
DBPath: info.DBPath,
|
|
ADRPresent: adr != nil,
|
|
}
|
|
if info.Name == s.sessionProject {
|
|
entry.IsSessionProject = true
|
|
}
|
|
result = append(result, entry)
|
|
}
|
|
|
|
return jsonResult(result), nil
|
|
}
|
|
|
|
func (s *Server) handleDeleteProject(_ context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
args, err := parseArgs(req)
|
|
if err != nil {
|
|
return errResult(err.Error()), nil
|
|
}
|
|
|
|
name := getStringArg(args, "project_name")
|
|
if name == "" {
|
|
return errResult("project_name is required"), nil
|
|
}
|
|
|
|
// Verify project exists
|
|
if !s.router.HasProject(name) {
|
|
return errResult(fmt.Sprintf("project not found: %s", name)), nil
|
|
}
|
|
|
|
if err := s.router.DeleteProject(name); err != nil {
|
|
return errResult(fmt.Sprintf("delete failed: %v", err)), nil
|
|
}
|
|
|
|
return jsonResult(map[string]any{
|
|
"deleted": name,
|
|
"status": "ok",
|
|
}), nil
|
|
}
|
|
|
|
func (s *Server) handleIndexStatus(_ context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
args, err := parseArgs(req)
|
|
if err != nil {
|
|
return errResult(err.Error()), nil
|
|
}
|
|
|
|
projectName := getStringArg(args, "project")
|
|
if projectName == "" {
|
|
projectName = s.sessionProject
|
|
}
|
|
if projectName == "" {
|
|
return jsonResult(map[string]any{
|
|
"status": "no_session",
|
|
"message": "No session project detected. Pass 'project' parameter or ensure the MCP client provides roots.",
|
|
}), nil
|
|
}
|
|
|
|
// Check if DB file exists
|
|
if !s.router.HasProject(projectName) {
|
|
return jsonResult(map[string]any{
|
|
"project": projectName,
|
|
"status": "not_indexed",
|
|
"message": fmt.Sprintf("No index found for project %q. Call index_repository to create one.", projectName),
|
|
"db_path": filepath.Join(s.router.Dir(), projectName+".db"),
|
|
}), nil
|
|
}
|
|
|
|
// Get store and project metadata
|
|
st, err := s.router.ForProject(projectName)
|
|
if err != nil {
|
|
return errResult(fmt.Sprintf("open store: %v", err)), nil
|
|
}
|
|
|
|
proj, _ := st.GetProject(projectName)
|
|
if proj == nil {
|
|
// DB file exists but no project row — partially indexed or corrupted
|
|
return jsonResult(map[string]any{
|
|
"project": projectName,
|
|
"status": "partial",
|
|
"message": "Database file exists but project metadata is missing. Re-run index_repository.",
|
|
"db_path": filepath.Join(s.router.Dir(), projectName+".db"),
|
|
}), nil
|
|
}
|
|
|
|
nodeCount, _ := st.CountNodes(projectName)
|
|
edgeCount, _ := st.CountEdges(projectName)
|
|
|
|
// Determine current indexing state
|
|
currentStatus, _ := s.indexStatus.Load().(string)
|
|
isSessionProject := projectName == s.sessionProject
|
|
|
|
result := map[string]any{
|
|
"project": projectName,
|
|
"status": "ready",
|
|
"indexed_at": proj.IndexedAt,
|
|
"root_path": proj.RootPath,
|
|
"nodes": nodeCount,
|
|
"edges": edgeCount,
|
|
"is_session_project": isSessionProject,
|
|
"db_path": filepath.Join(s.router.Dir(), projectName+".db"),
|
|
}
|
|
|
|
// Determine index type (initial vs incremental)
|
|
if nodeCount == 0 && edgeCount == 0 {
|
|
result["index_type"] = "none"
|
|
} else {
|
|
result["index_type"] = "incremental"
|
|
}
|
|
|
|
// Add edge type breakdown
|
|
if edgeTypes, err := st.EdgeCountsByType(projectName); err == nil && len(edgeTypes) > 0 {
|
|
result["edges_by_type"] = edgeTypes
|
|
}
|
|
|
|
// Add LSP coverage metrics for CALLS edges
|
|
if stats, err := st.CallsResolutionStats(projectName); err == nil && len(stats) > 0 {
|
|
totalCalls := 0
|
|
lspCalls := 0
|
|
for strategy, count := range stats {
|
|
totalCalls += count
|
|
if len(strategy) >= 4 && strategy[:4] == "lsp_" {
|
|
lspCalls += count
|
|
}
|
|
}
|
|
lspPct := 0.0
|
|
if totalCalls > 0 {
|
|
lspPct = float64(lspCalls) / float64(totalCalls) * 100
|
|
}
|
|
result["calls_total"] = totalCalls
|
|
result["calls_lsp"] = lspCalls
|
|
result["calls_lsp_pct"] = math.Round(lspPct*10) / 10
|
|
result["calls_by_strategy"] = stats
|
|
}
|
|
|
|
// If this is the session project and indexing is in progress
|
|
if isSessionProject && currentStatus == "indexing" {
|
|
result["status"] = "indexing"
|
|
if startedAt, ok := s.indexStartedAt.Load().(time.Time); ok {
|
|
result["index_started_at"] = startedAt.UTC().Format(time.RFC3339)
|
|
result["index_elapsed_seconds"] = int(time.Since(startedAt).Seconds())
|
|
}
|
|
if nodeCount == 0 {
|
|
result["index_type"] = "initial"
|
|
}
|
|
}
|
|
|
|
return jsonResult(result), nil
|
|
}
|