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
145 lines
5.4 KiB
Go
145 lines
5.4 KiB
Go
// Package cline implements the Gortex init integration for Cline
|
|
// (formerly Claude Dev — extension ID saoudrizwan.claude-dev). Cline
|
|
// stores its MCP config under the host editor's globalStorage
|
|
// directory; we probe both the VS Code and Cursor variants on each
|
|
// OS. The Step 3 audit will add JetBrains support and verify whether
|
|
// Cline has renamed alwaysAllow → autoApprove.
|
|
package cline
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/zzet/gortex/internal/agents"
|
|
"github.com/zzet/gortex/internal/agents/internalutil"
|
|
)
|
|
|
|
const Name = "cline"
|
|
const DocsURL = "https://docs.cline.bot/mcp/mcp-overview"
|
|
|
|
type Adapter struct{}
|
|
|
|
func New() *Adapter { return &Adapter{} }
|
|
func (a *Adapter) Name() string { return Name }
|
|
func (a *Adapter) DocsURL() string { return DocsURL }
|
|
|
|
// settingsPaths returns candidate cline_mcp_settings.json locations
|
|
// across OS/editor combos. Order doesn't matter — we write to every
|
|
// path whose parent directory exists.
|
|
func settingsPaths(home string) []string {
|
|
var paths []string
|
|
vscodeBases := []string{
|
|
filepath.Join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
|
|
filepath.Join(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
|
|
}
|
|
cursorBases := []string{
|
|
filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
|
|
filepath.Join(home, ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
|
|
}
|
|
for _, dir := range append(vscodeBases, cursorBases...) {
|
|
paths = append(paths, filepath.Join(dir, "cline_mcp_settings.json"))
|
|
}
|
|
return paths
|
|
}
|
|
|
|
// alwaysAllow is Cline's auto-approve allow-list. Matches the
|
|
// Gortex tool surface so Cline doesn't ask the user on every query.
|
|
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_signature", "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_community", "get_processes", "get_process",
|
|
"detect_changes", "index_repository", "reindex_repository",
|
|
"verify_change", "check_guards", "prefetch_context",
|
|
"find_dead_code", "find_hotspots", "find_cycles", "would_create_cycle",
|
|
"diff_context", "index_health", "get_symbol_history",
|
|
"scaffold", "batch_edit",
|
|
"flow_between", "taint_paths",
|
|
"find_clones",
|
|
}
|
|
|
|
func (a *Adapter) Detect(env agents.Env) (bool, error) {
|
|
if env.Home == "" {
|
|
return false, nil
|
|
}
|
|
for _, p := range settingsPaths(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{}
|
|
if env.Home == "" {
|
|
return p, nil
|
|
}
|
|
for _, path := range settingsPaths(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"},
|
|
})
|
|
}
|
|
}
|
|
if env.Mode != agents.ModeGlobal && env.SkillsRouting != "" {
|
|
p.Files = append(p.Files, agents.FileAction{
|
|
Path: filepath.Join(env.Root, ".clinerules", "gortex-communities.md"),
|
|
Action: agents.ActionWouldCreate,
|
|
Keys: []string{"communities-rule"},
|
|
})
|
|
}
|
|
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 Cline setup (Cline not detected)")
|
|
return res, nil
|
|
}
|
|
internalutil.Logf(env.Stderr, "[gortex init] setting up Cline integration...")
|
|
|
|
for _, path := range settingsPaths(env.Home) {
|
|
if _, err := os.Stat(filepath.Dir(path)); err != nil {
|
|
continue
|
|
}
|
|
entry := agents.DefaultGortexMCPEntry()
|
|
entry["alwaysAllow"] = alwaysAllow
|
|
action, err := agents.MergeJSON(env.Stderr, path, func(root map[string]any, _ bool) (bool, error) {
|
|
return agents.UpsertMCPServer(root, "gortex", entry, opts), nil
|
|
}, opts)
|
|
if err != nil {
|
|
internalutil.Warnf(env.Stderr, "could not configure Cline at %s: %v", path, err)
|
|
continue
|
|
}
|
|
res.Files = append(res.Files, action)
|
|
}
|
|
// Cline reads .clinerules/*.md as project-scoped instructions
|
|
// on every chat turn. The community-routing file is ours
|
|
// end-to-end — regenerated each `gortex init` run so the
|
|
// listing tracks the current graph. Skipped in global mode
|
|
// (file is per-repo) and when --no-skills / no communities
|
|
// qualify.
|
|
if env.Mode != agents.ModeGlobal && env.SkillsRouting != "" {
|
|
rulesPath := filepath.Join(env.Root, ".clinerules", "gortex-communities.md")
|
|
body := agents.CommunitiesStartMarker + "\n" + env.SkillsRouting + "\n" + agents.CommunitiesEndMarker + "\n"
|
|
ruleAction, err := agents.WriteOwnedFile(env.Stderr, rulesPath, body, opts)
|
|
if err != nil {
|
|
internalutil.Warnf(env.Stderr, "could not write Cline community rules at %s: %v", rulesPath, err)
|
|
} else {
|
|
res.Files = append(res.Files, ruleAction)
|
|
}
|
|
}
|
|
|
|
res.Configured = len(res.Files) > 0
|
|
return res, nil
|
|
}
|