workspace

package
v1.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 25, 2025 License: MIT Imports: 21 Imported by: 0

README

Workspace Package

The workspace package provides workspace detection and identification for multi-workspace MCP support.

Features

  • Auto-detection: Automatically detect workspace root from file paths
  • Multi-marker support: Detects various project types (.git, go.mod, package.json, etc.)
  • Stable IDs: Generates consistent workspace identifiers using SHA256 hashing
  • Caching: Built-in cache to avoid redundant detection
  • Extensible: Customizable markers and exclusion patterns

Usage

Basic Detection
import "github.com/doITmagic/rag-code-mcp/internal/workspace"

// Create detector
detector := workspace.NewDetector()

// Detect from file path
info, err := detector.DetectFromPath("/home/user/projects/my-app/src/main.go")
if err != nil {
    log.Fatal(err)
}

fmt.Println("Workspace:", info.Root)
fmt.Println("ID:", info.ID)
fmt.Println("Type:", info.ProjectType)
fmt.Println("Collection:", info.CollectionName())
Detection from MCP Parameters
// MCP tool receives parameters
params := map[string]interface{}{
    "file_path": "/home/user/project/internal/handler.go",
    "query": "search term",
}

// Detect workspace from params
info, err := detector.DetectFromParams(params)
if err != nil {
    log.Fatal(err)
}

// Use workspace-specific collection
collectionName := info.CollectionName() // "ragcode-a3f4b8c9d2e1"
Using Cache
// Create cache with 5 minute TTL
cache := workspace.NewCache(5 * time.Minute)

// Check cache before detection
if cached := cache.Get(filePath); cached != nil {
    return cached
}

// Detect and cache
info, err := detector.DetectFromPath(filePath)
cache.Set(filePath, info)
Custom Configuration
detector := workspace.NewDetector()

// Customize markers
detector.SetMarkers([]string{
    ".git",
    "package.json",
    "deno.json",      // Custom marker
    "requirements.txt", // Python projects
})

// Customize exclusions
detector.SetExcludePatterns([]string{
    "/node_modules/",
    "/dist/",
    "/.next/",
    "/target/",
})

API Reference

Types
Info

Represents detected workspace information.

type Info struct {
    Root        string    // Absolute path to workspace root
    ID          string    // Unique workspace identifier (12-char hash)
    ProjectType string    // Detected project type (go, nodejs, python, etc.)
    Markers     []string  // Found workspace markers
    DetectedAt  time.Time // Detection timestamp
}

func (i *Info) CollectionName() string // Returns "ragcode-{ID}"
Metadata

Workspace metadata for storage in Qdrant.

type Metadata struct {
    WorkspaceID  string    // Workspace unique ID
    RootPath     string    // Workspace root path
    LastIndexed  time.Time // Last indexing time
    FileCount    int       // Number of indexed files
    ChunkCount   int       // Number of indexed chunks
    Status       string    // "indexed", "indexing", "failed", "pending"
    ProjectType  string    // Project type
    Markers      []string  // Workspace markers
    ErrorMessage string    // Error if status is "failed"
}
Detector
NewDetector() *Detector

Creates detector with default markers.

Default markers (in priority order):

  • .git - Git repository
  • go.mod - Go project
  • package.json - Node.js project
  • Cargo.toml - Rust project
  • pyproject.toml - Python project (modern)
  • setup.py - Python project (legacy)
  • pom.xml - Maven project
  • build.gradle - Gradle project
  • .project - Generic project
  • .vscode - VS Code workspace
DetectFromPath(filePath string) (*Info, error)

Detects workspace from a file path. Walks up directory tree looking for workspace markers.

Returns fallback workspace (file's directory) if no markers found.

DetectFromParams(params map[string]interface{}) (*Info, error)

Detects workspace from MCP tool parameters. Looks for file paths in common parameter names:

  • file_path, filePath
  • path, file
  • source_file, target_file
  • directory, dir

Falls back to current working directory if no paths found.

SetMarkers(markers []string)

Sets custom workspace markers.

SetExcludePatterns(patterns []string)

Sets path patterns to exclude from detection.

Cache
NewCache(ttl time.Duration) *Cache

Creates cache with specified TTL.

Get(key string) *Info

Retrieves cached workspace info. Returns nil if not found or expired.

Set(key string, info *Info)

Stores workspace info in cache.

Clear()

Removes all entries from cache.

CleanExpired() int

Removes expired entries. Returns number of entries removed.

Size() int

Returns number of cached entries.

Workspace ID Generation

Workspace IDs are generated using SHA256 hash of the absolute workspace path:

func generateWorkspaceID(rootPath string) string {
    h := sha256.Sum256([]byte(rootPath))
    return hex.EncodeToString(h[:])[:12] // First 12 chars
}

Properties:

  • Stable: Same path always generates same ID
  • Unique: Different paths generate different IDs
  • Collision-resistant: 12-char hex = 48 bits (281 trillion combinations)
  • Readable: Short enough for logs and debugging

Examples:

/home/user/projects/do-ai       → a3f4b8c9d2e1
/home/user/projects/other-app   → 5e6f7g8h9i0j
/opt/workspace/backend          → 1a2b3c4d5e6f

Project Type Detection

Automatically infers project type from markers:

Marker Project Type
go.mod go
package.json nodejs
Cargo.toml rust
pyproject.toml, setup.py python
pom.xml maven
build.gradle gradle
.git git
No markers unknown

For Laravel projects, the detector uses the combination of composer.json + artisan and normalizes the project type to a PHP/Laravel variant (e.g. laravel, php-laravel), so that the language_manager can automatically select the PHP + Laravel analyzer.

Integration Examples

MCP Tool Integration
type SearchTool struct {
    detector *workspace.Detector
    cache    *workspace.Cache
}

func (t *SearchTool) Execute(ctx context.Context, params map[string]interface{}) (string, error) {
    // Detect workspace from params
    info, err := t.detector.DetectFromParams(params)
    if err != nil {
        return "", err
    }
    
    // Get workspace-specific collection
    collection := info.CollectionName()
    
    // Use collection for search
    results, err := t.searchInCollection(ctx, collection, params)
    return results, err
}
Background Cleanup
// Periodic cache cleanup
func startCacheCleanup(cache *workspace.Cache) {
    ticker := time.NewTicker(10 * time.Minute)
    go func() {
        defer ticker.Stop()
        for range ticker.C {
            removed := cache.CleanExpired()
            log.Printf("Cleaned %d expired workspace cache entries", removed)
        }
    }()
}

Testing

Run tests:

go test ./internal/workspace/...

Run with coverage:

go test -cover ./internal/workspace/...

Run benchmarks:

go test -bench=. ./internal/workspace/...

Performance

  • Detection: ~0.1ms (with filesystem access)
  • Cache hit: ~0.001ms (memory lookup)
  • ID generation: ~0.01ms (SHA256 hash)

Cache significantly improves performance for repeated calls with same paths.

See Also

Documentation

Index

Examples

Constants

View Source
const (
	StatusIndexed  = "indexed"
	StatusIndexing = "indexing"
	StatusFailed   = "failed"
	StatusPending  = "pending"
)

IndexingStatus represents possible indexing states

Variables

This section is empty.

Functions

func LanguageFileExtensions

func LanguageFileExtensions(language string) []string

LanguageFileExtensions returns the file extensions for a given language

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache provides caching for workspace detection results

Example
package main

import (
	"fmt"
	"time"

	"github.com/doITmagic/rag-code-mcp/internal/workspace"
)

func main() {
	// Create cache with 5 minute TTL
	cache := workspace.NewCache(5 * time.Minute)

	detector := workspace.NewDetector()

	// Function to get or detect workspace
	getWorkspace := func(filePath string) (*workspace.Info, error) {
		// Try cache first
		if cached := cache.Get(filePath); cached != nil {
			return cached, nil
		}

		// Detect and cache
		info, err := detector.DetectFromPath(filePath)
		if err != nil {
			return nil, err
		}

		cache.Set(filePath, info)
		return info, nil
	}

	// First call - detects and caches
	info1, _ := getWorkspace("/home/user/project/main.go")
	fmt.Printf("First call: %s\n", info1.Root)

	// Second call - from cache (fast)
	info2, _ := getWorkspace("/home/user/project/main.go")
	fmt.Printf("Second call (cached): %s\n", info2.Root)

	// Periodic cleanup
	go func() {
		ticker := time.NewTicker(10 * time.Minute)
		defer ticker.Stop()

		for range ticker.C {
			removed := cache.CleanExpired()
			fmt.Printf("Cleaned %d expired entries\n", removed)
		}
	}()
}

func NewCache

func NewCache(ttl time.Duration) *Cache

NewCache creates a new workspace cache with specified TTL

func (*Cache) CleanExpired

func (c *Cache) CleanExpired() int

CleanExpired removes expired entries from cache

func (*Cache) Clear

func (c *Cache) Clear()

Clear removes all entries from cache

func (*Cache) Get

func (c *Cache) Get(key string) *Info

Get retrieves workspace info from cache Returns nil if not found or expired

func (*Cache) Set

func (c *Cache) Set(key string, info *Info)

Set stores workspace info in cache

func (*Cache) Size

func (c *Cache) Size() int

Size returns the number of entries in cache

type Detector

type Detector struct {
	// contains filtered or unexported fields
}

Detector detects workspace roots from file paths

func NewDetector

func NewDetector() *Detector

NewDetector creates a new workspace detector with default markers

func NewDetectorWithConfig

func NewDetectorWithConfig(markers []string, excludePatterns []string) *Detector

NewDetectorWithConfig creates a detector with configuration

func (*Detector) DetectFromParams

func (d *Detector) DetectFromParams(params map[string]interface{}) (*Info, error)

DetectFromParams detects workspace from MCP tool parameters Looks for file paths in common parameter names

Example
package main

import (
	"fmt"
	"log"

	"github.com/doITmagic/rag-code-mcp/internal/workspace"
)

func main() {
	detector := workspace.NewDetector()

	// Simulate MCP tool parameters
	params := map[string]interface{}{
		"file_path": "/home/user/projects/my-app/internal/handlers/user.go",
		"query":     "user authentication",
	}

	info, err := detector.DetectFromParams(params)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Detected workspace: %s\n", info.Root)
	fmt.Printf("Collection: %s\n", info.CollectionName())
}

func (*Detector) DetectFromPath

func (d *Detector) DetectFromPath(filePath string) (*Info, error)

DetectFromPath detects workspace from a file path

Example
package main

import (
	"fmt"
	"log"

	"github.com/doITmagic/rag-code-mcp/internal/workspace"
)

func main() {
	detector := workspace.NewDetector()

	// Detect workspace from a file path
	info, err := detector.DetectFromPath("/home/user/projects/my-app/src/main.go")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Workspace root: %s\n", info.Root)
	fmt.Printf("Workspace ID: %s\n", info.ID)
	fmt.Printf("Project type: %s\n", info.ProjectType)
	fmt.Printf("Collection name: %s\n", info.CollectionName())
}

func (*Detector) SetExcludePatterns

func (d *Detector) SetExcludePatterns(patterns []string)

SetExcludePatterns sets path patterns to exclude

func (*Detector) SetMarkers

func (d *Detector) SetMarkers(markers []string)

SetMarkers allows customizing workspace markers

Example
package main

import (
	"fmt"

	"github.com/doITmagic/rag-code-mcp/internal/workspace"
)

func main() {
	detector := workspace.NewDetector()

	// Customize workspace markers for specific use case
	detector.SetMarkers([]string{
		".git",
		"package.json",
		"deno.json", // Add custom marker
	})

	// Set exclusion patterns
	detector.SetExcludePatterns([]string{
		"/node_modules/",
		"/dist/",
		"/.next/",
	})

	info, _ := detector.DetectFromPath("/home/user/deno-project/src/main.ts")
	fmt.Printf("Custom detection: %s\n", info.Root)
}

type FileState

type FileState struct {
	// Test comment for incremental indexing
	ModTime time.Time `json:"mod_time"`
	Size    int64     `json:"size"`
}

FileState represents the state of a single file

type FileWatcher

type FileWatcher struct {
	// contains filtered or unexported fields
}

FileWatcher handles file system notifications for a workspace

func NewFileWatcher

func NewFileWatcher(root string, manager *Manager) (*FileWatcher, error)

NewFileWatcher creates a new file watcher for the given root directory

func (*FileWatcher) Start

func (fw *FileWatcher) Start()

Start begins watching the directory tree

func (*FileWatcher) Stop

func (fw *FileWatcher) Stop()

type Info

type Info struct {
	// Root is the absolute path to the workspace root directory
	Root string `json:"root"`

	// ID is a stable, unique identifier for this workspace (hash of Root)
	ID string `json:"id"`

	// ProjectType indicates the detected project type (go, node, python, etc.)
	ProjectType string `json:"project_type,omitempty"`

	// Languages is the list of programming languages detected in this workspace
	// For polyglot workspaces (e.g., Go + Python microservices)
	Languages []string `json:"languages,omitempty"`

	// Markers are the workspace markers found (e.g., ".git", "go.mod")
	Markers []string `json:"markers,omitempty"`

	// DetectedAt is when this workspace was first detected
	DetectedAt time.Time `json:"detected_at,omitempty"`

	// CollectionPrefix is the prefix used for this workspace's collection
	// Set by Manager based on config
	CollectionPrefix string `json:"collection_prefix,omitempty"`
}

Info contains information about a detected workspace

func (*Info) CollectionName

func (w *Info) CollectionName() string

CollectionName returns the Qdrant collection name for this workspace Deprecated: Use CollectionNameForLanguage instead for multi-language support

func (*Info) CollectionNameForLanguage

func (w *Info) CollectionNameForLanguage(language string) string

CollectionNameForLanguage returns the Qdrant collection name for a specific language in this workspace Format: {prefix}-{workspaceID}-{language} Example: ragcode-a1b2c3d4e5f6-go, ragcode-a1b2c3d4e5f6-python

type LanguageDetector

type LanguageDetector struct{}

LanguageDetector detects programming languages in a workspace

func NewLanguageDetector

func NewLanguageDetector() *LanguageDetector

NewLanguageDetector creates a new language detector

func (*LanguageDetector) DetectLanguages

func (ld *LanguageDetector) DetectLanguages(rootPath string) ([]string, error)

DetectLanguages scans a workspace and returns detected programming languages Returns a slice of language identifiers (e.g., "go", "python", "php")

func (*LanguageDetector) GetPrimaryLanguage

func (ld *LanguageDetector) GetPrimaryLanguage(rootPath string, markers []string) string

GetPrimaryLanguage returns the primary language based on project markers This is a heuristic-based approach for workspace-level detection

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages workspace detection, collection management, and indexing

func NewManager

func NewManager(qdrant *storage.QdrantClient, llm llm.Provider, cfg *config.Config) *Manager

NewManager creates a new workspace manager

func (*Manager) DetectWorkspace

func (m *Manager) DetectWorkspace(params map[string]interface{}) (*Info, error)

DetectWorkspace detects workspace from tool parameters

func (*Manager) EnsureWorkspaceIndexed

func (m *Manager) EnsureWorkspaceIndexed(ctx context.Context, rootPath string) error

EnsureWorkspaceIndexed triggers indexing for all detected languages in the workspace

func (*Manager) GetAllIndexedCollectionNames

func (m *Manager) GetAllIndexedCollectionNames() []string

GetAllIndexedCollectionNames returns the names of all indexed collections

func (*Manager) GetAllIndexedMemories

func (m *Manager) GetAllIndexedMemories() []memory.LongTermMemory

GetAllIndexedMemories returns all indexed workspace memories This is useful for searching across all workspaces when no specific file_path is provided

func (*Manager) GetMemoriesForAllLanguages

func (m *Manager) GetMemoriesForAllLanguages(ctx context.Context, info *Info) (map[string]memory.LongTermMemory, error)

GetMemoriesForAllLanguages returns memory instances for all detected languages in the workspace Creates collections and triggers indexing if needed

func (*Manager) GetMemoryForWorkspace

func (m *Manager) GetMemoryForWorkspace(ctx context.Context, info *Info) (memory.LongTermMemory, error)

GetMemoryForWorkspace returns a memory instance for the workspace Creates collection and triggers indexing if needed Deprecated: Use GetMemoryForWorkspaceLanguage for multi-language support

func (*Manager) GetMemoryForWorkspaceLanguage

func (m *Manager) GetMemoryForWorkspaceLanguage(ctx context.Context, info *Info, language string) (memory.LongTermMemory, error)

GetMemoryForWorkspaceLanguage returns a memory instance for a specific language in the workspace Creates collection and triggers indexing if needed

func (*Manager) IndexLanguage

func (m *Manager) IndexLanguage(ctx context.Context, info *Info, language string, collectionName string) error

IndexLanguage indexes a specific language in a workspace It runs synchronously. Use StartIndexing for background execution.

func (*Manager) IsIndexing

func (m *Manager) IsIndexing(workspaceID string) bool

IsIndexing checks if a workspace is currently being indexed

func (*Manager) NeedsReindex

func (m *Manager) NeedsReindex(info *Info, language string) (bool, error)

NeedsReindex rescans the workspace and determines if tracked files changed for the language. Returns true when changes are detected or no previous fingerprint exists.

func (*Manager) SearchAllWorkspaces

func (m *Manager) SearchAllWorkspaces(ctx context.Context, queryEmbedding []float64, limit int) ([]memory.Document, error)

SearchAllWorkspaces searches across all indexed workspace collections Returns aggregated results from all workspaces

func (*Manager) StartIndexing

func (m *Manager) StartIndexing(ctx context.Context, info *Info, language string) error

StartIndexing explicitly starts background indexing for a workspace language This is used by the index_workspace tool to manually trigger indexing

func (*Manager) StartWatcher

func (m *Manager) StartWatcher(root string)

StartWatcher starts the file watcher for a workspace if not already running

type Metadata

type Metadata struct {
	WorkspaceID  string    `json:"workspace_id"`
	RootPath     string    `json:"root_path"`
	Language     string    `json:"language"` // Programming language (go, python, php, etc.)
	LastIndexed  time.Time `json:"last_indexed"`
	FileCount    int       `json:"file_count"`
	ChunkCount   int       `json:"chunk_count"`
	Status       string    `json:"status"` // "indexed", "indexing", "failed"
	ProjectType  string    `json:"project_type,omitempty"`
	Markers      []string  `json:"markers,omitempty"`
	ErrorMessage string    `json:"error_message,omitempty"`
}

Metadata represents workspace metadata stored in Qdrant

type WorkspaceState

type WorkspaceState struct {
	Files       map[string]FileState `json:"files"`
	LastIndexed time.Time            `json:"last_indexed"`
	// contains filtered or unexported fields
}

WorkspaceState tracks the state of files in a workspace

func LoadState

func LoadState(path string) (*WorkspaceState, error)

LoadState loads workspace state from disk

func NewWorkspaceState

func NewWorkspaceState() *WorkspaceState

NewWorkspaceState creates a new workspace state

func (*WorkspaceState) GetFileState

func (s *WorkspaceState) GetFileState(path string) (FileState, bool)

GetFileState returns the state of a file

func (*WorkspaceState) RemoveFile

func (s *WorkspaceState) RemoveFile(path string)

RemoveFile removes a file from the state

func (*WorkspaceState) Save

func (s *WorkspaceState) Save(path string) error

SaveState saves workspace state to disk

func (*WorkspaceState) UpdateFile

func (s *WorkspaceState) UpdateFile(path string, info os.FileInfo)

UpdateFile updates the state for a file

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL