a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
177 lines
6.3 KiB
Go
177 lines
6.3 KiB
Go
// Package kilocode implements the Gortex init integration for the
|
|
// Kilo Code VS Code extension. Kilo Code stores MCP servers at the
|
|
// VS Code extension's globalStorage directory:
|
|
//
|
|
// macOS/Linux: ~/.config/Code/User/globalStorage/kilocode.kilo/mcp_settings.json
|
|
// (variants exist under Library/Application Support on macOS)
|
|
//
|
|
// The schema is the canonical {"mcpServers": {<name>: {...}}}
|
|
// plus an `alwaysAllow` list that mirrors the Cline pattern.
|
|
// Project-level config is also supported at .kilocode/mcp.json.
|
|
//
|
|
// Docs: https://kilo.ai/docs/features/mcp/using-mcp-in-kilo-code
|
|
package kilocode
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/zzet/gortex/internal/agents"
|
|
"github.com/zzet/gortex/internal/agents/internalutil"
|
|
)
|
|
|
|
const Name = "kilocode"
|
|
const DocsURL = "https://kilo.ai/docs/features/mcp/using-mcp-in-kilo-code"
|
|
|
|
type Adapter struct{}
|
|
|
|
func New() *Adapter { return &Adapter{} }
|
|
func (a *Adapter) Name() string { return Name }
|
|
func (a *Adapter) DocsURL() string { return DocsURL }
|
|
|
|
// alwaysAllow is Kilo Code's auto-approve list, matching the Cline
|
|
// equivalent — Kilo Code is a Cline fork and uses the same field.
|
|
var alwaysAllow = []string{
|
|
"graph_stats", "search_symbols", "winnow_symbols", "get_symbol", "get_file_summary",
|
|
"get_editing_context", "get_dependencies", "get_dependents",
|
|
"get_call_chain", "get_callers", "find_implementations", "find_usages",
|
|
"get_cluster", "get_symbol_source", "batch_symbols",
|
|
"find_import_path", "explain_change_impact", "get_recent_changes",
|
|
"smart_context", "get_edit_plan", "get_test_targets", "suggest_pattern",
|
|
"get_communities", "get_processes",
|
|
"detect_changes", "index_repository", "reindex_repository",
|
|
"verify_change", "check_guards", "prefetch_context",
|
|
"analyze", "diff_context", "index_health", "get_symbol_history",
|
|
"scaffold", "batch_edit", "contracts", "feedback",
|
|
"flow_between", "taint_paths",
|
|
}
|
|
|
|
// globalStoragePaths returns candidate paths for Kilo Code's MCP
|
|
// settings across VS Code install flavours (VS Code stable,
|
|
// VS Code Insiders, VSCodium, Cursor). We write to every candidate
|
|
// whose parent globalStorage directory exists.
|
|
func globalStoragePaths(home string) []string {
|
|
bases := []string{
|
|
filepath.Join(home, "Library", "Application Support", "Code", "User", "globalStorage", "kilocode.kilo", "settings"),
|
|
filepath.Join(home, ".config", "Code", "User", "globalStorage", "kilocode.kilo", "settings"),
|
|
filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "kilocode.kilo", "settings"),
|
|
filepath.Join(home, ".config", "Cursor", "User", "globalStorage", "kilocode.kilo", "settings"),
|
|
}
|
|
paths := make([]string, 0, len(bases)+1)
|
|
for _, b := range bases {
|
|
paths = append(paths, filepath.Join(b, "mcp_settings.json"))
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func (a *Adapter) Detect(env agents.Env) (bool, error) {
|
|
// Project-level hint: .kilocode/ in the repo.
|
|
if _, err := os.Stat(filepath.Join(env.Root, ".kilocode")); err == nil {
|
|
return true, nil
|
|
}
|
|
if env.Home == "" {
|
|
return false, nil
|
|
}
|
|
for _, p := range globalStoragePaths(env.Home) {
|
|
if _, err := os.Stat(filepath.Dir(p)); err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (a *Adapter) Plan(env agents.Env) (*agents.Plan, error) {
|
|
p := &agents.Plan{}
|
|
// Project-level .kilocode/mcp.json when we're in project mode.
|
|
if env.Mode != agents.ModeGlobal {
|
|
p.Files = append(p.Files, agents.FileAction{
|
|
Path: filepath.Join(env.Root, ".kilocode", "mcp.json"),
|
|
Action: agents.ActionWouldMerge,
|
|
Keys: []string{"mcpServers"},
|
|
})
|
|
if env.SkillsRouting != "" {
|
|
p.Files = append(p.Files, agents.FileAction{
|
|
Path: filepath.Join(env.Root, ".kilocoderules"),
|
|
Action: agents.ActionWouldMerge,
|
|
Keys: []string{"communities-block"},
|
|
})
|
|
}
|
|
}
|
|
if env.Home != "" {
|
|
for _, path := range globalStoragePaths(env.Home) {
|
|
if _, err := os.Stat(filepath.Dir(path)); err == nil {
|
|
p.Files = append(p.Files, agents.FileAction{
|
|
Path: path,
|
|
Action: agents.ActionWouldMerge,
|
|
Keys: []string{"mcpServers"},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (a *Adapter) Apply(env agents.Env, opts agents.ApplyOpts) (*agents.Result, error) {
|
|
res := &agents.Result{Name: Name, DocsURL: DocsURL}
|
|
detected, _ := a.Detect(env)
|
|
res.Detected = detected
|
|
if !detected && !opts.ForceDetect {
|
|
internalutil.Logf(env.Stderr, "[gortex init] skip Kilo Code setup (kilo-code not detected)")
|
|
return res, nil
|
|
}
|
|
internalutil.Logf(env.Stderr, "[gortex init] setting up Kilo Code integration...")
|
|
|
|
entry := agents.DefaultGortexMCPEntry()
|
|
entry["alwaysAllow"] = alwaysAllow
|
|
|
|
merge := func(root map[string]any, _ bool) (bool, error) {
|
|
return agents.UpsertMCPServer(root, "gortex", entry, opts), nil
|
|
}
|
|
|
|
// Project-level first, if a .kilocode/ dir already exists.
|
|
if env.Mode != agents.ModeGlobal {
|
|
projectPath := filepath.Join(env.Root, ".kilocode", "mcp.json")
|
|
if _, err := os.Stat(filepath.Join(env.Root, ".kilocode")); err == nil {
|
|
action, err := agents.MergeJSON(env.Stderr, projectPath, merge, opts)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
res.Files = append(res.Files, action)
|
|
}
|
|
}
|
|
|
|
// Global-storage paths — write to every one whose parent exists.
|
|
if env.Home != "" {
|
|
for _, path := range globalStoragePaths(env.Home) {
|
|
if _, err := os.Stat(filepath.Dir(path)); err != nil {
|
|
continue
|
|
}
|
|
action, err := agents.MergeJSON(env.Stderr, path, merge, opts)
|
|
if err != nil {
|
|
internalutil.Warnf(env.Stderr, "could not configure Kilo Code at %s: %v", path, err)
|
|
continue
|
|
}
|
|
res.Files = append(res.Files, action)
|
|
}
|
|
}
|
|
|
|
// .kilocoderules is the project-scoped instructions file Kilo
|
|
// Code reads on every task (inherited from the Cline lineage).
|
|
// Write a marker-guarded community-routing block when skills
|
|
// were generated. Skipped in global mode and when --no-skills /
|
|
// no communities qualify.
|
|
if env.Mode != agents.ModeGlobal && env.SkillsRouting != "" {
|
|
rulesPath := filepath.Join(env.Root, ".kilocoderules")
|
|
routingAction, err := agents.UpsertMarkedBlock(env.Stderr, rulesPath, env.SkillsRouting,
|
|
agents.CommunitiesStartMarker, agents.CommunitiesEndMarker, opts)
|
|
if err != nil {
|
|
internalutil.Warnf(env.Stderr, "could not update %s: %v", rulesPath, err)
|
|
} else {
|
|
res.Files = append(res.Files, routingAction)
|
|
}
|
|
}
|
|
|
|
res.Configured = len(res.Files) > 0
|
|
return res, nil
|
|
}
|