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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/SimplyLiz/CodeMCP

go 1.24.11
go 1.24.12

require (
github.com/BurntSushi/toml v1.6.0
Expand Down
116 changes: 93 additions & 23 deletions internal/query/doctor.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package query

import (
Expand All @@ -10,6 +10,9 @@
"sort"
"strings"
"time"

"github.com/SimplyLiz/CodeMCP/internal/config"
"github.com/SimplyLiz/CodeMCP/internal/project"
)

// DoctorResponse is the response for the doctor command.
Expand Down Expand Up @@ -161,14 +164,14 @@

if e.scipAdapter == nil {
check.Status = "warn"
check.Message = "SCIP backend not configured"
check.Message = "SCIP index not configured — run 'ckb init' to set up"
check.SuggestedFixes = e.getSCIPInstallSuggestions()
return check
}

if !e.scipAdapter.IsAvailable() {
check.Status = "warn"
check.Message = "SCIP index not found"
check.Message = e.scipNotFoundMessage()
check.SuggestedFixes = e.getSCIPInstallSuggestions()
return check
}
Expand Down Expand Up @@ -201,6 +204,7 @@
}

// checkLsp verifies LSP servers are configured and their commands are available.
// Only checks servers relevant to the detected project languages.
func (e *Engine) checkLsp(ctx context.Context) DoctorCheck {
check := DoctorCheck{
Name: "lsp",
Expand All @@ -219,15 +223,18 @@
return check
}

// Test each configured server command
// Detect project languages to filter relevant LSP servers
relevantServers := e.filterRelevantLspServers(servers)

// Test each relevant server command
var available, missing []string
var fixes []FixAction

for lang, cfg := range servers {
for lang, cfg := range relevantServers {
if _, err := exec.LookPath(cfg.Command); err == nil {
available = append(available, lang)
} else {
missing = append(missing, fmt.Sprintf("%s (%s)", lang, cfg.Command))
missing = append(missing, lang)
fixes = append(fixes, e.getLspInstallFix(lang, cfg.Command))
}
}
Expand All @@ -240,22 +247,72 @@
check.Status = "pass"
check.Message = fmt.Sprintf("LSP ready: %s (starts on-demand)",
strings.Join(available, ", "))
} else if len(available) > 0 {
check.Status = "warn"
check.Message = fmt.Sprintf("LSP ready: %s | missing: %s",
strings.Join(available, ", "),
strings.Join(missing, ", "))
check.SuggestedFixes = fixes
} else {
check.Status = "warn"
check.Message = fmt.Sprintf("LSP commands not found: %s",
strings.Join(missing, ", "))
var parts []string
for _, lang := range missing {
cfg := relevantServers[lang]
fix := e.getLspInstallFix(lang, cfg.Command)
if fix.Command != "" {
parts = append(parts, fmt.Sprintf("LSP not installed for %s — install %s: %s",
lang, cfg.Command, fix.Command))
} else {
parts = append(parts, fmt.Sprintf("LSP not installed for %s — install %s",
lang, cfg.Command))
}
}
check.Message = strings.Join(parts, "; ")
check.SuggestedFixes = fixes
}

return check
}

// langToLspServer maps project.Language values to LSP server config keys.
var langToLspServer = map[project.Language]string{
project.LangGo: "go",
project.LangTypeScript: "typescript",
project.LangJavaScript: "typescript", // JS uses typescript-language-server
project.LangPython: "python",
project.LangDart: "dart",
project.LangRust: "rust",
project.LangJava: "java",
project.LangKotlin: "kotlin",
project.LangCpp: "cpp",
project.LangRuby: "ruby",
project.LangCSharp: "csharp",
project.LangPHP: "php",
}

// filterRelevantLspServers returns only the LSP servers that match detected
// project languages. Falls back to all servers if no languages are detected.
func (e *Engine) filterRelevantLspServers(servers map[string]config.LspServerCfg) map[string]config.LspServerCfg {
_, _, allLangs := project.DetectAllLanguages(e.repoRoot)
if len(allLangs) == 0 {
return servers // Fall back to checking all
}

// Build set of relevant LSP server keys
relevant := make(map[string]bool)
for _, lang := range allLangs {
if serverKey, ok := langToLspServer[lang]; ok {
relevant[serverKey] = true
}
}

filtered := make(map[string]config.LspServerCfg)
for key, cfg := range servers {
if relevant[key] {
filtered[key] = cfg
}
}

if len(filtered) == 0 {
return servers // Fall back if no configured servers match
}
return filtered
}

// getLspInstallFix returns installation instructions for an LSP server command.
func (e *Engine) getLspInstallFix(lang, command string) FixAction {
switch command {
Expand Down Expand Up @@ -347,11 +404,32 @@
return check
}

// scipNotFoundMessage returns a contextual "SCIP index not found" message
// based on the detected project language.
func (e *Engine) scipNotFoundMessage() string {
lang, _, ok := project.DetectLanguage(e.repoRoot)
if !ok {
return "SCIP index not found — run 'ckb index' to generate"
}
indexer := project.GetIndexerConfig(lang)
if indexer == nil {
return "SCIP index not found — run 'ckb index' to generate"
}
return fmt.Sprintf("SCIP index not found — run 'ckb index' (uses %s)", indexer.Cmd)
}

// getSCIPInstallSuggestions returns suggestions for installing SCIP.
func (e *Engine) getSCIPInstallSuggestions() []FixAction {
suggestions := make([]FixAction, 0)
suggestions := []FixAction{
{
Type: "run-command",
Command: "ckb index",
Safe: true,
Description: "Generate SCIP index (auto-detects language)",
},
}

// Detect language and suggest appropriate indexer
// Detect language and suggest appropriate indexer install
if e.hasFile("package.json") || e.hasFile("tsconfig.json") {
suggestions = append(suggestions, FixAction{
Type: "run-command",
Expand Down Expand Up @@ -379,14 +457,6 @@
})
}

if len(suggestions) == 0 {
suggestions = append(suggestions, FixAction{
Type: "open-docs",
URL: "https://sourcegraph.com/docs/code-intelligence/references/indexers",
Description: "Find SCIP indexer for your language",
})
}

return suggestions
}

Expand Down
Loading