Skip to content

Commit 293b7b3

Browse files
authored
Merge pull request #15 from doITmagic/feature/skills-system-clean
feat: Intelligent AI Skills System & Smart Auto-Update (v2)
2 parents ee2b60b + c7250a2 commit 293b7b3

18 files changed

Lines changed: 1471 additions & 55 deletions

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ data/
33
test_data/
44
qdrant_data/
55
barou/
6-
.agent/
76

87
# Binaries
98
/cmd/agent-runner/agent-runner
@@ -59,3 +58,8 @@ temp/
5958
# Scripts
6059
scripts/
6160
.ragcode/
61+
.agent/skills/
62+
63+
# Debug logs
64+
startup_debug.txt
65+
windsurf_debug_log.txt

cmd/install/main.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,9 @@ func installBinary() {
511511
}
512512
var binDir string
513513
if runtime.GOOS == "windows" {
514-
binDir = filepath.Join(home, ".local", "share", "ragcode", "bin")
514+
binDir = filepath.Join(home, installDirName, "bin")
515515
} else {
516-
binDir = filepath.Join(home, ".local", "share", "ragcode", "bin")
516+
binDir = filepath.Join(home, installDirName, "bin")
517517
}
518518
if err := os.MkdirAll(binDir, 0755); err != nil {
519519
fail(fmt.Sprintf("Could not create bin directory: %v", err))
@@ -588,10 +588,13 @@ func copyFile(src, dst string) error {
588588
return err
589589
}
590590
}
591-
defer destFile.Close()
591+
_, copyErr := io.Copy(destFile, sourceFile)
592+
closeErr := destFile.Close()
592593

593-
_, err = io.Copy(destFile, sourceFile)
594-
return err
594+
if copyErr != nil {
595+
return copyErr
596+
}
597+
return closeErr
595598
}
596599

597600
// downloadAndExtractBinary fetches the release archive and extracts the binary.
@@ -661,11 +664,17 @@ func downloadAndExtractBinary(dest string) bool {
661664
warn(fmt.Sprintf("Could not create destination file: %v", err))
662665
return false
663666
}
664-
defer outFile.Close()
665667
cmd.Stdout = outFile
666668

667-
if err := cmd.Run(); err != nil {
668-
warn(fmt.Sprintf("Failed to extract binary: %v", err))
669+
runErr := cmd.Run()
670+
closeErr := outFile.Close()
671+
672+
if runErr != nil {
673+
warn(fmt.Sprintf("Failed to extract binary: %v", runErr))
674+
return false
675+
}
676+
if closeErr != nil {
677+
warn(fmt.Sprintf("Failed to finalise binary file: %v", closeErr))
669678
return false
670679
}
671680

@@ -707,10 +716,15 @@ func addToPath(binDir string) {
707716
warn(fmt.Sprintf("Could not update shell config: %v", err))
708717
return
709718
}
710-
defer f.Close()
711-
712719
if _, err := f.WriteString(fmt.Sprintf("\nexport PATH=\"%s:$PATH\"\n", binDir)); err != nil {
713720
warn(fmt.Sprintf("Could not write to shell config: %v", err))
721+
if cerr := f.Close(); cerr != nil {
722+
warn(fmt.Sprintf("Could not finalise shell config after write failure: %v", cerr))
723+
}
724+
return
725+
}
726+
if err := f.Close(); err != nil {
727+
warn(fmt.Sprintf("Could not finalise shell config: %v", err))
714728
} else {
715729
success(fmt.Sprintf("Added to %s (restart shell to apply)", shellConfig))
716730
}

cmd/rag-code-mcp/main.go

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/signal"
1313
"path/filepath"
1414
"strings"
15+
"sync"
1516
"syscall"
1617
"time"
1718

@@ -364,16 +365,6 @@ workspace:
364365
}
365366

366367
func main() {
367-
// AGGRESSIVE STARTUP DEBUG
368-
f, _ := os.Create("/tmp/ragcode-startup.txt")
369-
cwd, _ := os.Getwd()
370-
exe, _ := os.Executable()
371-
fmt.Fprintf(f, "Time: %s\n", time.Now())
372-
fmt.Fprintf(f, "Exe: %s\n", exe)
373-
fmt.Fprintf(f, "CWD: %s\n", cwd)
374-
fmt.Fprintf(f, "Args: %v\n", os.Args)
375-
f.Close()
376-
377368
// Define flags
378369
configPath := flag.String("config", "config.yaml", "Path to configuration file")
379370
ollamaBaseURLFlag := flag.String("ollama-base-url", "", "Ollama base URL (overrides config/env)")
@@ -437,7 +428,7 @@ func main() {
437428
// Handle update flag
438429
if *updateFlag {
439430
fmt.Println("Checking for updates...")
440-
info, err := updater.CheckForUpdates(Version)
431+
info, err := updater.CheckForUpdates(context.Background(), Version, true)
441432
if err != nil {
442433
log.Fatalf("Failed to check for updates: %v", err)
443434
}
@@ -447,8 +438,26 @@ func main() {
447438
}
448439

449440
fmt.Printf("Found new version: %s\nDownloading...\n", info.LatestVersion)
450-
tempFile := filepath.Join(os.TempDir(), "ragcode_update.tar.gz")
451-
if err := info.DownloadAndVerify(tempFile); err != nil {
441+
442+
// Determine extension from asset URL
443+
ext := ".tar.gz"
444+
if strings.HasSuffix(info.AssetURL, ".zip") {
445+
ext = ".zip"
446+
}
447+
// Create a unique temporary file securely
448+
tmp, err := os.CreateTemp("", "ragcode_update_*"+ext)
449+
if err != nil {
450+
log.Fatalf("Failed to create temporary file for update: %v", err)
451+
}
452+
tempFile := tmp.Name()
453+
// We only need the path; close the file descriptor
454+
if err := tmp.Close(); err != nil {
455+
log.Fatalf("Failed to close temporary file for update: %v", err)
456+
}
457+
// Ensure the temporary file is removed after applying the update
458+
defer os.Remove(tempFile)
459+
460+
if err := info.DownloadAndVerify(context.Background(), tempFile); err != nil {
452461
log.Fatalf("Update failed: %v", err)
453462
}
454463

@@ -477,12 +486,7 @@ func main() {
477486
}
478487

479488
// Background update check
480-
go func() {
481-
info, err := updater.CheckForUpdates(Version)
482-
if err == nil && info != nil {
483-
logger.Info("🌟 New version available: %s. Run 'rag-code-mcp --update' to upgrade.", info.LatestVersion)
484-
}
485-
}()
489+
triggerBackgroundUpdateCheck()
486490

487491
// Apply logging settings from config unless env vars already override them
488492
applyLoggingConfig(cfg.Logging)
@@ -643,6 +647,11 @@ func main() {
643647

644648
indexWorkspaceTool := tools.NewIndexWorkspaceTool(workspaceManager)
645649

650+
listSkillsTool := tools.NewListSkillsTool()
651+
installSkillTool := tools.NewInstallSkillTool(workspaceManager)
652+
checkUpdateTool := tools.NewCheckUpdateTool(Version)
653+
applyUpdateTool := tools.NewApplyUpdateTool(Version)
654+
646655
// Example: use typed ToolHandlerFor for search_code
647656
registerSearchCodeToolTyped(server, searchTool, cfg)
648657

@@ -655,6 +664,10 @@ func main() {
655664
registerAgentTool(server, searchDocsTool, cfg)
656665
registerAgentTool(server, hybridTool, cfg)
657666
registerAgentTool(server, indexWorkspaceTool, cfg)
667+
registerAgentTool(server, listSkillsTool, cfg)
668+
registerAgentTool(server, installSkillTool, cfg)
669+
registerAgentTool(server, checkUpdateTool, cfg)
670+
registerAgentTool(server, applyUpdateTool, cfg)
658671

659672
if err := registerFileResources(server); err != nil {
660673
log.Fatalf("Failed to register resources: %v", err)
@@ -708,6 +721,9 @@ func registerSearchCodeToolTyped(server *mcp.Server, tool *tools.SearchLocalInde
708721

709722
logger.Info("✅ Tool '%s' completed in %v", tool.Name(), duration)
710723

724+
// Trigger background update check (non-blocking)
725+
triggerBackgroundUpdateCheck()
726+
711727
return nil, SearchCodeOutput{Results: result}, nil
712728
})
713729
}
@@ -751,6 +767,9 @@ func registerAgentTool(server *mcp.Server, tool MCPTool, cfg *config.Config) {
751767

752768
logger.Info("✅ Tool '%s' completed in %v", tool.Name(), duration)
753769

770+
// Trigger background update check (non-blocking)
771+
triggerBackgroundUpdateCheck()
772+
754773
return &mcp.CallToolResult{
755774
Content: []mcp.Content{
756775
&mcp.TextContent{Text: result},
@@ -1097,6 +1116,54 @@ func getToolSchema(toolName string) map[string]interface{} {
10971116
"required": []string{"query"},
10981117
}
10991118

1119+
case "list_skills":
1120+
return map[string]interface{}{
1121+
"type": "object",
1122+
"properties": map[string]interface{}{},
1123+
}
1124+
1125+
case "install_skill":
1126+
return map[string]interface{}{
1127+
"type": "object",
1128+
"properties": map[string]interface{}{
1129+
"skill_id": map[string]interface{}{
1130+
"type": "string",
1131+
"description": "The ID of the skill to install or uninstall",
1132+
},
1133+
"active": map[string]interface{}{
1134+
"type": "boolean",
1135+
"description": "True to install the skill, false to uninstall it",
1136+
},
1137+
"file_path": map[string]interface{}{
1138+
"type": "string",
1139+
"description": "Optional: file path to help detect workspace context",
1140+
},
1141+
},
1142+
"required": []string{"skill_id", "active"},
1143+
}
1144+
1145+
case "check_update":
1146+
return map[string]interface{}{
1147+
"type": "object",
1148+
"properties": map[string]interface{}{
1149+
"force": map[string]interface{}{
1150+
"type": "boolean",
1151+
"description": "Force check ignoring cache (default: false)",
1152+
},
1153+
},
1154+
}
1155+
1156+
case "apply_update":
1157+
return map[string]interface{}{
1158+
"type": "object",
1159+
"properties": map[string]interface{}{
1160+
"force": map[string]interface{}{
1161+
"type": "boolean",
1162+
"description": "Force update even if version matches (default: false)",
1163+
},
1164+
},
1165+
}
1166+
11001167
default:
11011168
return map[string]interface{}{
11021169
"type": "object",
@@ -1252,6 +1319,7 @@ func ensureIDERules(cfg *config.Config, filePath string) {
12521319
- Always provide 'file_path' to tools to ensure they detect the correct project context.
12531320
- Use 'hybrid_search' if looking for exact variable names or error messages.
12541321
- If the tool says "workspace not indexed", use 'index_workspace' once.
1322+
- **Skills System**: Use 'list_skills' to see available AI behaviors and 'install_skill' to enable them in this workspace (e.g., 'ragcode-priority', 'ragcode-update').
12551323
`
12561324

12571325
// 3. Define target rule files
@@ -1287,3 +1355,27 @@ func ensureIDERules(cfg *config.Config, filePath string) {
12871355
}
12881356
}
12891357
}
1358+
1359+
var (
1360+
lastUpdateCheck time.Time
1361+
lastUpdateCheckMutex sync.Mutex
1362+
)
1363+
1364+
func triggerBackgroundUpdateCheck() {
1365+
lastUpdateCheckMutex.Lock()
1366+
defer lastUpdateCheckMutex.Unlock()
1367+
1368+
// Only check if more than 1 hour passed since last check in THIS session
1369+
// to avoid spamming go-routines, while updater.CheckForUpdates handles the 24h logic
1370+
if time.Since(lastUpdateCheck) < 1*time.Hour {
1371+
return
1372+
}
1373+
lastUpdateCheck = time.Now()
1374+
1375+
go func() {
1376+
info, err := updater.CheckForUpdates(context.Background(), Version, false)
1377+
if err == nil && info != nil {
1378+
logger.Info("🌟 New version available: %s. Run 'rag-code-mcp --update' or use the 'apply_update' tool to upgrade.", info.LatestVersion)
1379+
}
1380+
}()
1381+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
name: debugging-guide
3+
description: How to use ragcode tools for fast root-cause analysis and debugging
4+
---
5+
6+
# 🐞 Skill: Debugging with Ragcode
7+
8+
This skill defines a workflow for using ragcode tools to fix bugs efficiently.
9+
10+
---
11+
12+
## 🔍 The 3-Step Debug Workflow
13+
14+
### Step 1: Locate the Symptom
15+
- Search for the exact error message using `hybrid_search`.
16+
- If no error message, search semantically for the failing feature using `search_code`.
17+
18+
### Step 2: Contextualize the Flow
19+
- Find where the failing function is called using `find_implementations`.
20+
- Look at the surrounding logic with `get_code_context` (fetch at least 10 lines before and after).
21+
22+
### Step 3: Analyze Data Models
23+
- Use `find_type_definition` to check if a struct field might be missing or wrongly typed.
24+
- Use `list_package_exports` to see if there are utility functions already solving the problem.
25+
26+
---
27+
28+
## 💡 Troubleshooting Pro-Tip:
29+
If you are lost, search for "logger" or "Error" in the package to see how other parts of the system handle failures.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
name: go-best-practices
3+
description: Go development patterns, project structure, and idiomatic practices
4+
---
5+
6+
# 🐹 Skill: Go Best Practices
7+
8+
This skill guides the AI in writing high-quality, idiomatic Go code and understanding Go project structures.
9+
10+
---
11+
12+
## 🔗 External References (Source of Truth)
13+
- [Google Go Style Guide](https://google.github.io/styleguide/go/)
14+
- [Effective Go](https://go.dev/doc/effective_go)
15+
- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)
16+
17+
---
18+
19+
## 🏗️ Project Structure
20+
Most Go projects in this ecosystem follow the Standard Go Project Layout:
21+
- `/cmd`: Main applications/binaries.
22+
- `/internal`: Private packages (not importable by other projects).
23+
- `/pkg`: Public packages (rarely used here, prefer `internal`).
24+
- `/api`: API definitions.
25+
26+
## ✅ Implementation Patterns
27+
28+
### 1. Error Handling
29+
- **Check immediately**: Never ignore errors.
30+
- **Wrap errors**: Use `fmt.Errorf("context: %w", err)` to provide trace details.
31+
- **Sentinel Errors**: Define custom errors in the package for specific conditions.
32+
33+
### 2. Interfaces
34+
- **Small is better**: Prefer `io.Reader` over large monolithic interfaces.
35+
- **Consumer defines**: Define interfaces where they are USED, not where they are implemented.
36+
37+
### 3. Concurrency
38+
- **Use Channels**: For communication/orchestration.
39+
- **Mutex**: For simple state protection.
40+
- **Context**: Always propagate `context.Context` for cancellation and timeouts.
41+
42+
### 4. Testing
43+
- **Table-Driven Tests**: Use anonymous structs and `t.Run` for multiple test cases.
44+
- **Package name**: Use `package_test` for external testing (test only the public API).
45+
- **Mocking**: Use interfaces to mock dependencies. Avoid complex mocking frameworks if simple stubs work.
46+
- **Helpers**: Use `t.Helper()` for repetitive assertions.
47+
48+
---
49+
50+
## 🔧 Tools for Go Project Analysis:
51+
- `mcp_ragcode_find_type_definition`: Essential for understanding structs and interfaces.
52+
- `mcp_ragcode_list_package_exports`: Quick overview of a package's public API.

0 commit comments

Comments
 (0)