package tools import ( "bufio" "context" "fmt" "os" "path/filepath" "strings" ) type MemoryGetTool struct { workspace string } func NewMemoryGetTool(workspace string) *MemoryGetTool { return &MemoryGetTool{workspace: workspace} } func (t *MemoryGetTool) Name() string { return "memory_get" } func (t *MemoryGetTool) Description() string { return "Read safe snippets from MEMORY.md or memory/*.md using optional line range." } func (t *MemoryGetTool) Parameters() map[string]interface{} { return map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "path": map[string]interface{}{ "type": "string", "description": "Relative path to MEMORY.md or memory/*.md", }, "from": map[string]interface{}{ "type": "integer", "description": "Start line (1-indexed)", "default": 1, }, "lines": map[string]interface{}{ "type": "integer", "description": "Number of lines to read", "default": 80, }, }, "required": []string{"path"}, } } func (t *MemoryGetTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) { rawPath, _ := args["path"].(string) rawPath = strings.TrimSpace(rawPath) if rawPath == "" { return "", fmt.Errorf("path is required") } from := 1 if v, ok := args["from"].(float64); ok && int(v) > 0 { from = int(v) } lines := 80 if v, ok := args["lines"].(float64); ok && int(v) > 0 { lines = int(v) } if lines > 500 { lines = 500 } fullPath := filepath.Clean(filepath.Join(t.workspace, rawPath)) if !t.isAllowedMemoryPath(fullPath) { return "", fmt.Errorf("path not allowed: %s", rawPath) } f, err := os.Open(fullPath) if err != nil { return "", err } defer f.Close() scanner := bufio.NewScanner(f) lineNo := 0 end := from + lines - 1 var out strings.Builder for scanner.Scan() { lineNo++ if lineNo < from { continue } if lineNo > end { break } out.WriteString(fmt.Sprintf("%d: %s\n", lineNo, scanner.Text())) } if err := scanner.Err(); err != nil { return "", err } content := strings.TrimSpace(out.String()) if content == "" { return fmt.Sprintf("No content in range for %s (from=%d, lines=%d)", rawPath, from, lines), nil } rel, _ := filepath.Rel(t.workspace, fullPath) return fmt.Sprintf("Source: %s#L%d-L%d\n%s", rel, from, end, content), nil } func (t *MemoryGetTool) isAllowedMemoryPath(fullPath string) bool { workspaceMemory := filepath.Join(t.workspace, "MEMORY.md") if fullPath == workspaceMemory { return true } memoryDir := filepath.Join(t.workspace, "memory") rel, err := filepath.Rel(memoryDir, fullPath) if err != nil { return false } if strings.HasPrefix(rel, "..") { return false } return strings.HasSuffix(strings.ToLower(fullPath), ".md") }