A C# tool for semantic source code search with local embeddings. Search your codebase by meaning, not just keywords.
Your code never leaves your machine. Embeddings, vector search, and the index all run locally. No telemetry, no API calls, no third-party LLMs. Everything stays on your hardware.
Most AI-powered code search tools send your source code to a cloud server for processing. SemanticSourceCode takes a different approach: the embedding model runs on your own machine via Ollama, LM Studio or any OpenAI-compatible server (llama.cpp, vLLM), the vectors are stored in a local SQLite database, and search queries never touch the internet.
| Tool | Code stays local | Local embeddings | Open source |
|---|---|---|---|
| SemanticSourceCode | ✅ | ✅ | ✅ |
| GitHub Copilot Workspace | ❌ | ❌ | ❌ |
| Sourcegraph Cody | ❌ | ||
| Cursor | ❌ | ❌ | ❌ |
If you need a hosted solution, a GUI, or multi-language support beyond C#, tools like Cursor or Sourcegraph Cody may be a better fit. SemanticSourceCode is built for the terminal and for AI agents that you run yourself.
- 🔍 Semantic Chunking — Analyzes C# classes, methods, properties, constructors and fields separately
- 🧠 Local Embeddings — Uses Ollama, LM Studio or any OpenAI-compatible server (llama.cpp, vLLM) locally, no cloud dependency, no data leakage
- 💾 SQLite Vector Database — Simple embedded database with cosine similarity search
- 🔎 Semantic Search — Find code based on meaning, not just keywords
- 👀 Watch Mode — Live incremental re-indexing on file changes (500 ms debounce, Ctrl+C to stop)
- 🔌 MCP Server — Expose the search as a Model Context Protocol tool.
- 📜 Scriptable Search — Non-interactive one-shot mode with
--queryfor pipes, scripts and agentic use - ⚡ Multiple Providers — Switch between Ollama and LM Studio via configuration
- 🚀 Enhanced Search Quality — Content boosting and query expansion for better results
- 🏷️ Framework Detection — Automatic detection of ASP.NET Controllers, Services and Middleware
- 📊 Call Graph Analysis — Track method calls and dependencies between code chunks
- 🚀 Bulk Indexing — Single-transaction WAL writes + batched embedding calls for fast re-indexing
The search engine combines semantic similarity with keyword matching:
- Semantic Score — Cosine similarity of embeddings (weight: 0.7)
- Keyword Score — Matches in class names, member names, and content (weight: 0.3)
- Combined —
hybrid_score = 0.7 * semantic + 0.3 * keyword
This ensures that exact keyword matches (e.g., class DatabaseService) are not overshadowed by semantically similar but structurally irrelevant results.
Narrow down search results with structural filters:
# Only search in controllers
./SemanticSourceCode --mode search --namespace Api.Controllers --http-method GET
# Only search in specific class
./SemanticSourceCode --mode search --class DatabaseService
# File path pattern
./SemanticSourceCode --mode search --file-pattern "*/Controllers/*"Available filters:
| Filter | CLI Flag | Description |
|---|---|---|
| Namespace | --namespace |
Match namespace name (exact or partial) |
| Class | --class |
Match class name |
| HTTP Method | --http-method |
Match HTTP method (GET, POST, etc.) |
| File Pattern | --file-pattern |
Match file path (glob pattern) |
When no strong matches are found, the engine suggests alternative queries based on Levenshtein distance to known class and member names:
> DataBase
Do you mean: DatabaseService?
Suggestions are computed from the indexed codebase and require no external dependencies.
The similarity threshold adjusts automatically based on:
- Score Distribution — Percentile-based analysis of result scores
- Gap Detection — Elbow method to find natural cutoffs
- Query Specificity — Shorter queries get lower thresholds (generic), longer queries get higher thresholds (specific)
Configure in appsettings.json:
{
"Search": {
"AdaptiveThreshold": {
"Enabled": true,
"FloorThreshold": 0.30,
"CeilingThreshold": 0.85,
"Percentile": 70
}
}
}Results are re-ranked using structural boosts:
| Signal | Boost | Description |
|---|---|---|
| ClassName Match | ×1.3 | Query matches class name |
| MemberName Match | ×1.0 | Query matches member name |
| Controller | ×1.1 | ASP.NET Controller detected |
| Service | ×1.1 | Service class detected |
| Middleware | ×1.1 | Middleware class detected |
| Documentation | ×1.05 | Has XML documentation |
| Small File | ×0.9 | Penalty for very small files (often helpers) |
All search features can be configured in appsettings.json:
{
"Search": {
"MinimumSimilarity": 0.35,
"TopK": 20,
"DisplayCount": 5,
"WeakMatchThreshold": 0.30,
"Hybrid": {
"SemanticWeight": 0.7,
"KeywordWeight": 0.3
},
"AdaptiveThreshold": {
"Enabled": true,
"FloorThreshold": 0.30,
"CeilingThreshold": 0.85,
"Percentile": 70
},
"ReRanking": {
"ClassNameBoost": 1.3,
"MemberNameBoost": 1.0,
"ControllerBoost": 1.1,
"DocumentationBoost": 1.05
}
}
}SSC supports three embedding providers, selected via "Embedding": "Provider":
| Provider | Value | Best for |
|---|---|---|
| Ollama | ollama |
Default local setup (ollama pull nomic-embed-text) |
| LM Studio | lmstudio |
LM Studio's local server |
| OpenAI-compatible | openai-compatible |
Any server exposing the OpenAI /v1/embeddings API: llama.cpp (llama-server), vLLM, a remote endpoint, etc. |
For the OpenAI-compatible provider, configure the endpoint in appsettings.json:
{
"Embedding": {
"Provider": "openai-compatible"
},
"OpenAICompatible": {
"BaseUrl": "http://localhost:8080/v1",
"EmbeddingModel": "nomic-embed-text",
"ApiKey": ""
}
}BaseUrlmay include the/v1suffix or not — both work.EmbeddingModelis optional; when empty, the first model reported by/v1/modelsis used.ApiKeyis sent as aBearertoken when set (required for remote endpoints, ignored by local llama.cpp).- Security note:
ApiKeyinappsettings.jsonis stored in plaintext. For remote endpoints prefer an environment variable (OpenAICompatible__ApiKey) ordotnet user-secretsso a real key never ends up in source control.
Note: the auto provider (default) only auto-detects LM Studio and Ollama. The OpenAI-compatible provider is only used when explicitly configured — pointing it at a remote endpoint is a deliberate choice, since embeddings leave your machine.
┌─────────────────┐ ┌──────────────────┐
│ C# Files │ ───> │ CodeAnalyzer │ (Roslyn)
└─────────────────┘ └────────┬─────────┘
│ CodeChunks
v
┌──────────────────┐
│ EmbeddingProvider│ (Ollama/LM Studio)
└────────┬─────────┘
│ float[]
v
┌──────────────────┐
│ SqliteVssDatabase│ (vec0)
└────────┬─────────┘
│
v
┌──────────────────┐
│ SearchEngine │ (Cosine Sim)
└──────────────────┘
| Components | Responsibility | File |
|---|---|---|
| CodeAnalyzer | Roslyn-based code decomposition | Services/CodeAnalyzer.cs |
| IEmbeddingService | Provider abstraction | Services/IEmbeddingService.cs |
| EmbeddingServiceFactory | Auto-detect provider | Services/EmbeddingServiceFactory.cs |
| IVectorDatabase | Vector storage with cosine similarity | Services/IVectorDatabase.cs |
| SqliteVssDatabase | SQLite + vec0 implementation | Services/SqliteVssDatabase.cs |
| HybridSearchService | Combines semantic + keyword search | Search/HybridSearchService.cs |
| ResultRanker | Re-ranking with structural signals | Search/ResultRanker.cs |
| QuerySuggester | Levenshtein-based suggestions | Search/QuerySuggester.cs |
| AdaptiveThreshold | Dynamic similarity threshold | Search/AdaptiveThreshold.cs |
| SearchFilter | Context filters (namespace, class, etc.) | Search/SearchFilter.cs |
| QueryExpander | Synonym expansion | Search/QueryExpander.cs |
| CodeChunk | Data model | Models/CodeChunk.cs |
- .NET 10.0 SDK
- Either Ollama or LM Studio (locally installed)
# Using the dotnet-install script
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 10.0 --install-dir ~/.dotnet
# Add to PATH
export PATH="$HOME/.dotnet:$PATH"
# Verify version
dotnet --version # Should print 10.0.xFor other installation methods (Windows, package managers), see the official .NET 10 documentation.
# Install Ollama (Linux/macOS)
curl -fsSL https://ollama.com/install.sh | sh
# Pull an embedding model
ollama pull nomic-embed-textDefault Ollama endpoint: http://localhost:11434
- Download and install LM Studio for your platform
- Open LM Studio and go to the Developer tab
- Start the local server (default port: 1234)
- Load an embedding model, e.g.:
nomic-ai/nomic-embed-text-v1.5sentence-transformers/all-MiniLM-L6-v2
Default LM Studio endpoint: http://localhost:1234
By default, the app uses auto-detect — you don't need to configure anything.
Just set:
{
"Embedding": {
"Provider": "auto"
}
}The app will automatically:
- Check LM Studio first (faster, local UI) — port 1234
- Fall back to Ollama — port 11434
- Pick whichever is available with a loaded embedding model
- Zero-config out-of-the-box — Install either LM Studio or Ollama, the app just works
- Respects explicit choice — Set
"ollama"or"lmstudio"to pin a provider (fallback still works if that one is down) - Transparent logging — The console tells you exactly which provider was chosen and why
| Config | First Try | Fallback |
|---|---|---|
auto |
LM Studio | Ollama |
lmstudio |
LM Studio | Ollama |
ollama |
Ollama | LM Studio |
If neither provider is reachable, you'll get a clear error with installation instructions for both.
dotnet restore
dotnet build
dotnet test # All 109 tests should pass
dotnet publish -c Release# Index C# files in a directory
./SemanticSourceCode --mode index --path ./src
# Example with absolute path
./SemanticSourceCode --mode index --path /home/user/projects/MyAppPerformance: Indexing batches embedding requests and writes to the SQLite
database inside a single transaction with WAL mode enabled. The DB write step
that used to dominate indexing time now takes ~0.3 seconds for ~140 chunks on a
Raspberry Pi 5. See scripts/benchmark-index.sh to reproduce.
# Start watch mode on a directory
./SemanticSourceCode --mode watch --path ./srcWatch mode runs an initial full index, then keeps the process running and
re-indexes the affected file automatically whenever a *.cs file is
created, changed, deleted, or renamed. The index stays fresh within
~500 ms of an edit, so searches in another shell always see the latest code.
- Debounce — Multiple rapid saves to the same file are coalesced into a single re-index (default: 500 ms).
- Excluded directories —
bin/,obj/,.git/,.vs/,.idea/,node_modules/,dist/,build/are ignored automatically. - Stop — Press
Ctrl+Cto stop watching. The watcher exits cleanly, no leftover background tasks.
Example workflow:
# Terminal 1: start watching
./SemanticSourceCode --mode watch --path ./src
# Terminal 2: edit a file
vim ./src/Services/MyService.cs # → re-indexes automatically
# Terminal 3: search while watching
./SemanticSourceCode --mode searchInteractive mode:
# Start interactive search mode
./SemanticSourceCode --mode searchExample queries:
- "How do I find all files in a directory?"
- "Database connection handling"
- "Async HTTP client"
- "User authentication"
Non-interactive (one-shot) mode:
# Default (text format) — prints human-readable results, exits
./SemanticSourceCode --mode search --query "arithmetic calculation"
# JSON output — for piping into jq, scripts, or other tools
./SemanticSourceCode --mode search --query "arithmetic calculation" --format json
# Quiet output — only the top-1 result, one line
./SemanticSourceCode --mode search --query "Add" --quiet
# Short flags
./SemanticSourceCode --mode search -q "Add" -f json -l 2
# With structural filter
./SemanticSourceCode --mode search -q "Query" --namespace MyApp.DataThe one-shot mode is perfect for scripts and agentic use:
| Flag | Description |
|---|---|
--query, -q |
The search query (triggers non-interactive mode) |
--format, -f |
text (default), json, or quiet |
--limit, -l |
Max results to display |
--quiet |
Shorthand for --format quiet |
--namespace |
Filter to chunks in this namespace |
--class |
Filter to chunks in this class |
--http-method |
Filter to controller methods with this verb |
--file-pattern |
Filter to files matching this glob |
Exit codes (non-interactive only):
0— at least one result found1— no results, validation error, or DB not initialized
# Start the MCP server over stdio
./SemanticSourceCode --mode mcpThe server speaks JSON-RPC 2.0 over stdin/stdout (MCP standard). It exposes two tools that AI agents can call directly:
| Tool | Description |
|---|---|
search_code |
Semantic search with optional namespace, class, filePattern, limit filters |
get_chunk_by_id |
Fetch a single indexed chunk by its semantic ID |
Status messages go to stderr so the JSON-RPC channel on stdout stays clean for client parsing.
Example:
project-local .mcp.json):
{
"mcpServers": {
"semantic-source-code": {
"command": "SemanticSourceCode",
"args": ["--mode", "mcp"]
}
}
}After restarting the agent can call search_code and
get_chunk_by_id directly in its tool-using workflow.
Edit appsettings.json to switch providers. Use "auto" (default) for zero-config behavior, or explicitly pin a provider.
{
"Embedding": {
"Provider": "auto"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"EmbeddingModel": "nomic-embed-text"
},
"LMStudio": {
"BaseUrl": "http://localhost:1234",
"EmbeddingModel": "text-embedding-nomic-embed-text-v1.5"
},
"Database": {
"Path": "codechunks.db"
}
}{
"Embedding": {
"Provider": "ollama"
},
"Ollama": {
"BaseUrl": "http://localhost:11434",
"EmbeddingModel": "nomic-embed-text"
},
"Database": {
"Path": "codechunks.db"
}
}{
"Embedding": {
"Provider": "lmstudio"
},
"LMStudio": {
"BaseUrl": "http://localhost:1234",
"EmbeddingModel": "text-embedding-nomic-embed-text-v1.5"
},
"Database": {
"Path": "codechunks.db"
}
}| Section | Key | Default | Description |
|---|---|---|---|
Embedding |
Provider |
auto |
Provider: auto, ollama, or lmstudio |
Ollama |
BaseUrl |
http://localhost:11434 |
Ollama API endpoint |
Ollama |
EmbeddingModel |
nomic-embed-text |
Model name in Ollama |
LMStudio |
BaseUrl |
http://localhost:1234 |
LM Studio API endpoint |
LMStudio |
EmbeddingModel |
text-embedding-nomic-embed-text-v1.5 |
Model identifier for LM Studio |
Database |
Path |
codechunks.db |
SQLite database file path |
Chunking |
MaxChunkSize |
1000 |
Maximum tokens per chunk |
Chunking |
OverlapTokens |
100 |
Overlap between chunks |
Search queries are automatically expanded with synonyms and related terms. You can customize this in appsettings.json:
{
"QueryExpansion": {
"db": "database,sql,entity framework",
"http": "web,api,rest,endpoint",
"async": "asynchronous,task,background"
}
}Each C# class is split into separate chunks:
- Methods — With signature, body and XML documentation
- Properties — Including getter/setter logic
- Constructors — Separate initialization logic
- Fields — With type and initialization
To improve search quality, the tool implements several techniques:
Each code chunk is enhanced with additional metadata to improve search relevance:
- Class Name Boosting — Class names are repeated to increase their weight
- Member Name Boosting — Member names are emphasized for better matching
- Framework Metadata — Framework-specific terms are added for ASP.NET components
Search queries are automatically expanded with synonyms and related terms:
db→database,sql,entity frameworkhttp→web,api,rest,endpointasync→asynchronous,task,backgroundsensor→ultrasonic,distance,color,gyrofile→io,read,write,stream
- Uses the Ollama HTTP API (
/api/embeddings) - Compatible with all Ollama embedding models
- Default:
nomic-embed-text(768 dimensions) - Alternatives:
mxbai-embed-large,all-minilm
- Uses the OpenAI-compatible HTTP API (
/v1/embeddings) - Works with any model loaded in LM Studio
- Default:
text-embedding-nomic-embed-text-v1.5 - Supports models from HuggingFace, GGUF, etc.
Cosine similarity implementation:
similarity = (A · B) / (||A|| × ||B||)If you see:
No embedding provider available.
Make sure at least one of these is running:
LM Studio:
- Open LM Studio and go to the Developer tab
- Start the local server (toggle should be green)
- Load an embedding model (e.g.
nomic-embed-text-v1.5) - Verify:
curl http://localhost:1234/v1/models
Ollama:
# Pull an embedding model
ollama pull nomic-embed-text
# Ensure Ollama is running
ollama serve
# Verify
curl http://localhost:11434/api/tagsThe app is set to auto by default, so it will pick whichever is available.
If you see:
LM Studio erreichbar, aber kein Modell geladen.
Go to the Developer tab in LM Studio, load an embedding model, and make sure the server is started.
# Check if Ollama is running
curl http://localhost:11434/api/tags
# Start Ollama
ollama serve- Open LM Studio and go to the Developer tab
- Ensure the server is started (toggle should be green)
- Verify the port in
appsettings.jsonmatches the displayed port - Test with:
curl http://localhost:1234/v1/models
- Make sure indexing completed successfully
- Check
codechunks.dbfile size (should be > 0 bytes) - Use more specific search terms
- Verify your embedding provider is running and the model is loaded
- Embedding generation is CPU-intensive — expect slower performance on Raspberry Pi or low-power devices
- The tool processes chunks sequentially (batch size: 1)
- Consider using a machine with GPU support for faster embedding generation
We welcome contributions! Please see CONTRIBUTING.md for details.
- Report bugs via GitHub Issues
- Request features via GitHub Discussions
- Submit pull requests following our PR template
MIT