This document covers the internals of SocratiCode — architecture, data flow, configuration, and how to build, extend, and debug.
- Architecture Overview
- Prerequisites for Development
- Building and Running
- Project Structure
- Configuration & Constants
- Data Flow: Indexing
- Data Flow: Search
- Data Flow: Incremental Update
- Data Flow: Code Graph
- Testing
- Services Reference
- MCP Tools Reference
- Data Structures
- Docker & Infrastructure
- Extending the Indexer
- Troubleshooting
┌─────────────────────────────────────────────────────┐
│ MCP Host │
│ (VS Code, Claude Desktop, etc.) │
└──────────────────────┬──────────────────────────────┘
│ stdio (JSON-RPC)
┌──────────────────────▼──────────────────────────────┐
│ MCP Server (src/index.ts) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────┐ ┌──────────┐ │
│ │ Index │ │ Query │ │ Graph │ │ Manage │ │
│ │ Tools │ │ Tools │ │ Tools │ │ Tools │ │
│ └────┬─────┘ └─────┬─────┘ └───┬───┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────▼─────────────▼───────────▼───────────▼─────┐ │
│ │ Services │ │
│ │ ┌─────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Indexer │ │ Qdrant │ │ Ollama │ │ Docker │ │ │
│ │ │ │ │ Client │ │ Client │ │ Mgmt │ │ │
│ │ └────┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────┐ ┌───▼────┐ ┌──▼──────┐ │ │
│ │ │ Ignore │ │Embedder│ │ Watcher │ │ │
│ │ │ Filter │ │ │ │(@parcel) │ │ │
│ │ └─────────┘ └────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────────┐
│ Qdrant (Docker) │ │ Ollama (Docker) │
│ localhost:16333 │ │ localhost:11435 │
│ │ │ │
│ Vector storage │ │ nomic-embed-text │
│ 768-dim cosine │ │ 768-dim embeddings │
└──────────────────┘ └─────────────────────┘
| Tool | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Runtime |
| npm | 9+ | Package manager |
| TypeScript | 5.7+ | Installed as devDependency |
| Docker | Any recent | Runs Qdrant |
| Ollama | Any recent | Runs embedding model |
npm installnpm run buildThis compiles TypeScript from src/ to dist/ with source maps and declarations.
npm run devUses tsx to run TypeScript directly without a build step.
npm start
# or
node dist/index.jsThe server communicates over stdio using JSON-RPC (MCP protocol). It's designed to be launched by an MCP host, not run standalone in a terminal. For testing, you can use the MCP Inspector or pipe JSON-RPC messages.
- Target: ES2022
- Module: Node16 (ESM)
- Strict mode: Enabled
- Output:
dist/with source maps and.d.tsdeclarations
SocratiCode uses Biome for linting. Biome is fast, zero-config, and catches unused imports, style issues, and potential bugs.
# Check for lint issues
npm run lint
# Auto-fix safe issues
npm run lint:fixFor VS Code, install the Biome extension for real-time lint feedback and auto-fix on save.
SocratiCode uses Conventional Commits and release-it for automated versioning and changelog generation.
Commit message format:
feat: add fuzzy search support → Features
fix: resolve race condition → Bug Fixes
perf: optimise embedding batching → Performance
refactor: simplify provider factory → Refactors
docs: update quickstart guide → Documentation
test: add watcher edge-case tests → Tests
chore: update deps → hidden from changelog
Before committing:
npm run lint && npx tsc --noEmit && npm run test:unitCreating a release (maintainers only):
# Interactive — prompts for patch/minor/major
npm run release
# Dry run — preview what will happen without making changes
npm run release:dryThis will automatically:
- Determine the version bump from your commits
- Update
CHANGELOG.mdwith allfeat:,fix:, etc. entries - Bump the version in
package.json - Create a git commit and tag (
v1.1.0) - Push to GitHub and create a GitHub Release
src/
├── index.ts # MCP server entry point — registers all 21 tools
├── config.ts # Project ID generation (SHA-256), collection naming
├── constants.ts # All constants: ports, container names, models, chunk sizes, extensions
├── types.ts # TypeScript interfaces and types
│
├── services/
│ ├── docker.ts # Docker CLI wrapper — manage Qdrant & Ollama containers
│ ├── ollama.ts # Ollama client — model availability, embedding calls
│ ├── embeddings.ts # Embedding generation with batching and task prefixes
│ ├── qdrant.ts # Qdrant client — collections, upsert, search, metadata
│ ├── indexer.ts # Core indexing — file discovery, chunking, full/incremental
│ ├── watcher.ts # File system watcher via @parcel/watcher with debouncing
│ ├── lock.ts # Cross-process file-based locking via proper-lockfile
│ ├── ignore.ts # Ignore filter (.gitignore + .socraticodeignore + defaults)
│ ├── logger.ts # Structured JSON logging — stderr (startup/no MCP transport) or MCP notifications/message (when hosted)
│ ├── code-graph.ts # AST-based code graph building via ast-grep
│ ├── graph-analysis.ts # Graph queries: dependencies, stats, cycles, Mermaid diagrams
│ ├── graph-imports.ts # Import/require/use extraction for 18+ languages via AST
│ ├── graph-resolution.ts # Module specifier → file path resolution
│ ├── startup.ts # Startup lifecycle: auto-resume, graceful shutdown coordination
│ └── context-artifacts.ts # Context artifact loading, chunking, indexing, search
│
├── tools/
│ ├── index-tools.ts # Handlers: codebase_index, codebase_update, codebase_remove, codebase_stop, codebase_watch
│ ├── query-tools.ts # Handlers: codebase_search, codebase_status
│ ├── graph-tools.ts # Handlers: codebase_graph_build/query/stats/circular/visualize
│ ├── context-tools.ts # Handlers: codebase_context, codebase_context_search/index/remove
│ └── manage-tools.ts # Handlers: codebase_health, codebase_list_projects, codebase_about
tests/
├── helpers/
│ ├── fixtures.ts # Test fixture utilities (temp projects, Docker checks)
│ └── setup.ts # Integration test infrastructure (Qdrant client, cleanup)
├── unit/ # 460 tests — no Docker required
├── integration/ # 137 tests — requires Docker
└── e2e/ # 20 tests — full lifecycle
docker-compose.yml # Alternative way to run infrastructure
vitest.config.ts # Test framework configuration
All constants are defined in src/constants.ts:
| Constant | Value | Description |
|---|---|---|
SEARCH_DEFAULT_LIMIT |
10 |
Default search results per query (env-configurable, 1-50) |
SEARCH_MIN_SCORE |
0.10 |
Minimum RRF score threshold (env-configurable, 0-1) |
CHUNK_SIZE |
100 |
Lines per chunk |
CHUNK_OVERLAP |
10 |
Overlapping lines between adjacent chunks |
MAX_FILE_BYTES |
5 MB |
Max file size before skipping (env-configurable via MAX_FILE_SIZE_MB) |
MAX_AVG_LINE_LENGTH |
500 |
Avg line length above which character-based chunking is used (minified files) |
MAX_CHUNK_CHARS |
2000 |
Hard character limit per chunk (provider-level safety net) |
QDRANT_PORT |
16333 |
Qdrant HTTP API port (host-side) |
QDRANT_GRPC_PORT |
16334 |
Qdrant gRPC port (host-side) |
QDRANT_CONTAINER_NAME |
socraticode-qdrant |
Docker container name |
QDRANT_IMAGE |
qdrant/qdrant:v1.17.0 |
Docker image (pinned version) |
OLLAMA_PORT |
11435 |
Ollama API port (host-side) |
OLLAMA_CONTAINER_NAME |
socraticode-ollama |
Docker container name |
OLLAMA_IMAGE |
ollama/ollama:latest |
Docker image |
Note:
EMBEDDING_MODEL,EMBEDDING_DIMENSIONS, andEMBEDDING_CONTEXT_LENGTHare defined insrc/services/embedding-config.ts, not insrc/constants.ts. Defaults arenomic-embed-text/768for Ollama,text-embedding-3-small/1536for OpenAI, andgemini-embedding-001/3072for Google.
Defined in src/services/embeddings.ts: texts are sent to Ollama in batches of 32.
Defined in src/services/watcher.ts: file changes are debounced for 2000ms before triggering an index update.
Defined in src/constants.ts as MAX_FILE_BYTES: files larger than 5 MB are skipped (configurable via MAX_FILE_SIZE_MB env var).
Defined in src/services/docker.ts: after starting the container, the server polls /healthz up to 30 times with 1000ms between retries.
Defined in src/config.ts:
- Project ID: First 12 characters of SHA-256 hash of the absolute project path.
- Code collection:
codebase_{projectId} - Graph collection:
codegraph_{projectId} - Context artifacts collection:
context_{projectId}
This means the same folder path always maps to the same collection, even across restarts.
| Category | Extensions |
|---|---|
| JavaScript/TypeScript | .js, .jsx, .ts, .tsx, .mjs, .cjs |
| Python | .py, .pyw, .pyi |
| Java/Kotlin/Scala | .java, .kt, .kts, .scala |
| C/C++ | .c, .h, .cpp, .hpp, .cc, .hh, .cxx |
| C# | .cs |
| Go | .go |
| Rust | .rs |
| Ruby | .rb |
| PHP | .php |
| Swift | .swift |
| Shell | .sh, .bash, .zsh |
| Web | .html, .htm, .css, .scss, .sass, .less, .vue, .svelte |
| Config | .json, .yaml, .yml, .toml, .xml, .ini, .cfg |
| Documentation | .md, .mdx, .rst, .txt |
| SQL | .sql |
| Dart | .dart |
| Lua | .lua |
| R | .r, .R |
| Docker | .dockerfile |
Special files always indexed: Dockerfile, Makefile, Rakefile, Gemfile, Procfile, .env.example, .gitignore, .dockerignore.
The full list is in src/services/ignore.ts. Key entries: node_modules, .git, dist, build, .next, __pycache__, .venv, target, .idea, .vscode, *.min.js, *.lock, package-lock.json, yarn.lock, coverage, vendor, .DS_Store, Thumbs.db.
When codebase_index is called:
1. INFRASTRUCTURE CHECK
handleIndexTool() → ensureQdrantReady() + ensureOllamaReady()
├── Check Docker CLI: docker info
├── Check Qdrant image: docker images (qdrant/qdrant:v1.17.0)
├── Pull image if missing: docker pull qdrant/qdrant:v1.17.0
├── Check container: docker ps --filter name=socraticode-qdrant
├── Create/start container with volume mount
├── Wait for /healthz (up to 30s, 30 × 1s retries)
├── Check Ollama container: docker ps --filter name=socraticode-ollama
├── Start Ollama container if needed
├── Check for nomic-embed-text model
└── Pull model if missing
2. FILE DISCOVERY
getIndexableFiles(projectPath, extraExts?)
├── glob("**/*") to enumerate all files
├── Build ignore filter: defaults + .gitignore + .socraticodeignore
├── Filter by supported extension, special filename, or extra extensions
└── Filter out ignored paths
3. COLLECTION SETUP
ensureCollection(collectionName)
├── Check if collection exists
├── Create with: provider-dependent dimensions (768/1536/3072), cosine distance, on-disk payload
└── Create payload indexes: filePath, relativePath, language, contentHash
4. FILE SCANNING & CHUNKING (parallel batches of 50 files)
├── Read file content (skip if > 5 MB or unreadable)
├── Hash content: SHA-256 → 16-char prefix
├── Skip if hash matches existing (re-index mode)
├── Chunk using three-tier strategy:
│ ├── Minified/bundled (avg line length > 500): character-based chunking
│ │ └── Splits at safe boundaries (newline, space, semicolon, comma)
│ ├── AST-aware (supported languages): chunk at function/class boundaries
│ │ ├── ast-grep parses top-level declarations
│ │ ├── Small declarations merged, large ones sub-chunked
│ │ └── Preamble (imports) and epilogue handled separately
│ └── Line-based fallback: 100-line segments with 10-line overlap
├── Hard character cap (2000 chars) applied to all chunks
├── Generate chunk ID: SHA-256 of "filePath:startLine" formatted as UUID
└── Detect language from file extension
5. BATCHED EMBEDDING + UPSERT (50 files per batch)
For each batch of files:
├── Prepare text: "search_document: {relativePath}\n{content}"
├── Generate embeddings via configured provider (further batched internally)
├── Upsert to Qdrant with dense vector + BM25 text + payload
├── Update in-memory file hashes
├── Checkpoint: persist hashes to Qdrant (progress survives crashes)
└── Check for cancellation request before next batch
6. POST-INDEX
├── Save final metadata (status: "completed")
├── Auto-build code dependency graph (non-fatal on failure)
└── Auto-index context artifacts if config exists (non-fatal on failure)
When codebase_search is called:
1. Generate query embedding
├── Prepare text: "search_query: {query}"
└── Send to configured embedding provider → provider-dependent vector (768 / 1536 / 3072 dims)
2. HYBRID SEARCH (dense + BM25, RRF-fused)
├── Build two parallel prefetch sub-queries:
│ ├── Dense: query vector → semantic cosine similarity (client-side)
│ └── BM25: query text → server-side BM25 inference (Qdrant v1.15.2+)
├── Apply optional filters (filePath, language) as payload conditions on both sub-queries
├── Qdrant Query API runs both sub-queries then fuses results via Reciprocal Rank Fusion (RRF)
└── Return top N results with RRF-combined scores and payloads
3. Format results
└── Each result: file path, line range, language, RRF score, code content
The nomic-embed-text model uses task-specific prefixes for asymmetric retrieval:
- Documents are prefixed with
search_document:— this tells the model to encode the text as a passage to be retrieved. - Queries are prefixed with
search_query:— this tells the model to encode as a search query.
This asymmetric encoding significantly improves retrieval quality.
When codebase_update is called:
1. Check if collection exists and has points
└── If empty/missing → fall back to full indexProject()
2. Enumerate current files on disk
└── Same filtering as full index (extensions, ignore rules)
3. Compare against in-memory hash map
├── File hash matches → skip (unchanged)
├── File hash differs → delete old chunks, re-chunk, re-embed, upsert
├── File not in hash map → new file, chunk, embed, upsert
└── Hash map entry not on disk → deleted file, remove chunks
4. Return delta: { added, updated, removed, chunksCreated, cancelled }
Note: File content hashes are persisted in Qdrant after each batch. On server restart, hashes are loaded from Qdrant on first use, so incremental updates remain truly incremental across restarts.
When codebase_graph_build is called:
0. CONCURRENCY GUARD
├── If a build is already in progress for this project, return the
│ existing in-flight promise (deduplication — callers share the result)
└── Otherwise, start a new tracked build
1. BACKGROUND EXECUTION (fire-and-forget)
├── Tool returns immediately with "build started" message
├── Actual build runs asynchronously on the event loop
├── Progress tracked via GraphBuildProgress { filesTotal, filesProcessed, phase }
└── Client polls codebase_graph_status for progress %
2. FILE DISCOVERY (phase: "scanning files")
├── Get graphable files from project (same ignore filters as indexing)
└── Include files with AST-grep grammar + files with extra extensions
3. PARSE IMPORTS (phase: "analyzing imports", per file, via ast-grep)
├── Determine AST-grep language from file extension
│ ├── Built-in: TypeScript, JavaScript, Python, Java, Kotlin, etc.
│ └── Dynamic: C, C++, C#, Go, Rust, Ruby, PHP, Swift, Bash, Scala
├── Files with extra extensions (no AST grammar) → leaf nodes only
├── Parse file with ast-grep
├── Extract import/require/use/include statements using AST patterns
│ ├── JavaScript/TypeScript: import ... from, require(), dynamic import()
│ ├── Python: import, from ... import
│ ├── Java/Kotlin/Scala: import statements
│ ├── Go: import declarations
│ ├── Rust: use, mod
│ ├── Ruby: require, require_relative
│ ├── PHP: use, require, include
│ ├── C/C++: #include
│ ├── Swift: import
│ ├── Bash: source, . (dot)
│ └── Dart/Lua: regex-based extraction
├── Update progress: filesProcessed++
└── Return ImportInfo[] with module specifiers
4. RESOLVE IMPORTS
├── For each import, resolve module specifier to actual file path
├── Handle relative paths: ./foo → foo.ts, foo/index.ts, etc.
├── Try extension variations per language
├── Check against known file set for existence
└── Skip unresolvable imports (external packages, built-ins)
5. BUILD GRAPH
├── Create node for each file: { filePath, relativePath, imports[], dependencies[], dependents[] }
├── Create edges for resolved dependencies: { source, target }
└── Back-fill dependents lists from edges
6. PERSIST (phase: "persisting")
└── Save graph data to Qdrant
7. CACHE in-memory (per project) for subsequent queries
└── Cache invalidated on graph rebuild or file watcher events
Note: Graph builds are always full reconstructions, not incremental. Unlike the indexer where each file's embedding chunks are independent, the graph's edges are global — a single file change can affect edges across many files. Graph builds are fast since they only do local AST parsing (no network calls like embedding generation). The concurrency guard ensures that duplicate builds from multiple callers (tool, watcher, indexer, auto-resume) are deduplicated.
Uses depth-first search (DFS) with a recursion stack to find cycles in the directed dependency graph.
SocratiCode uses vitest as its test framework with 634 tests across three layers.
# All tests
npm test
# Unit tests only (no Docker needed)
npm run test:unit
# Integration tests only (requires Docker)
npm run test:integration
# End-to-end tests (requires Docker)
npm run test:e2e
# Watch mode
npm run test:watch
# Coverage report
npm run test:coverage| Layer | Directory | Tests | Docker | Description |
|---|---|---|---|---|
| Unit | tests/unit/ |
477 | No | Pure logic: config, constants, ignore rules, cross-process locking, logging, graph analysis, import extraction, path resolution, startup lifecycle |
| Integration | tests/integration/ |
137 | Yes | Real Docker containers: Qdrant CRUD, real Ollama embeddings, indexer, watcher, code graph, all 21 MCP tools |
| E2E | tests/e2e/ |
20 | Yes | Full lifecycle: health check → index → search → graph build/query/stats → watch → remove |
tests/helpers/fixtures.ts— Creates temporary fixture projects with TypeScript, Python files, and supporting config. ProvidesisDockerAvailable()for conditional test skipping.tests/helpers/setup.ts— Manages test Qdrant collections withcreateTestQdrantClient(),cleanupTestCollections(), cache reset helpers, and infrastructure readiness polling (waitForQdrant(),waitForOllama()).
- Sequential execution: Tests run sequentially (
fileParallelism: false) because integration tests share Docker containers. - Automatic skip: Integration/E2E tests use
describe.skipIf(!dockerAvailable)to gracefully skip when Docker is unavailable. - 120-second timeouts: Both test and hook timeouts are set to 120s to accommodate Docker image pulls and embedding model downloads on first run.
- Real embeddings: Integration tests use real Ollama embeddings (not mocks) to validate semantic search quality.
- Fixture cleanup: Each test cleans up its temporary directories and Qdrant collections.
Test configuration is in vitest.config.ts:
{
pool: "forks", // Process isolation
testTimeout: 120_000, // 2 minutes per test
hookTimeout: 120_000, // 2 minutes per hook
fileParallelism: false, // Sequential file execution
sequence: { concurrent: false },
coverage: { include: ["src/**/*.ts"], exclude: ["src/index.ts"] }
}| Function | Signature | Description |
|---|---|---|
isDockerAvailable |
() → Promise<boolean> |
Runs docker info to check |
isQdrantImagePresent |
() → Promise<boolean> |
Checks docker images |
pullQdrantImage |
(onProgress?) → Promise<void> |
Runs docker pull |
isQdrantRunning |
() → Promise<boolean> |
Checks docker ps |
startQdrant |
(onProgress?) → Promise<void> |
Creates and starts Qdrant container |
resetQdrantReadinessCache |
() → void |
Clear cached readiness state |
ensureQdrantReady |
(onProgress?) → Promise<{ started, pulled }> |
Full Qdrant setup orchestration |
isOllamaImagePresent |
() → Promise<boolean> |
Checks for Ollama Docker image |
pullOllamaImage |
(onProgress?) → Promise<void> |
Pulls Ollama Docker image |
isOllamaRunning |
() → Promise<boolean> |
Checks Ollama container status |
startOllama |
(onProgress?) → Promise<void> |
Creates and starts Ollama container |
resetOllamaContainerReadinessCache |
() → void |
Clear cached readiness state |
ensureOllamaContainerReady |
(onProgress?) → Promise<{ started, pulled }> |
Full Ollama container setup |
Qdrant container is started with:
docker run -d \
--name socraticode-qdrant \
-p 16333:6333 -p 16334:6334 \
-v socraticode_qdrant_data:/qdrant/storage \
--restart unless-stopped \
qdrant/qdrant:v1.17.0
Ollama container is started with:
docker run -d \
--name socraticode-ollama \
-p 11435:11434 \
-v socraticode_ollama_data:/root/.ollama \
--restart unless-stopped \
ollama/ollama:latest
| Function | Signature | Description |
|---|---|---|
isOllamaAvailable |
() → Promise<boolean> |
Calls Ollama API to check availability |
isModelAvailable |
() → Promise<boolean> |
Checks for the configured EMBEDDING_MODEL |
pullModel |
() → Promise<void> |
Pulls the model |
embed |
(texts: string[]) → Promise<number[][]> |
Batch embedding |
embedSingle |
(text: string) → Promise<number[]> |
Single embedding |
resetOllamaReadinessCache |
() → void |
Clear cached readiness state |
ensureOllamaReady |
() → Promise<{ modelPulled, containerStarted, imagePulled }> |
Full setup: container + model |
Ollama connectivity URL is configurable via the OLLAMA_URL environment variable (resolved via embedding-config.ts). Both the container and embedding model are managed automatically.
Shared TypeScript interfaces for all embedding providers. Extracted to avoid circular imports between the factory and the provider implementations.
| Export | Description |
|---|---|
EmbeddingProvider (interface) |
Contract all providers implement: name, ensureReady(), embed(), embedSingle(), healthCheck() |
EmbeddingReadinessResult |
Returned by ensureReady(): { modelPulled, containerStarted, imagePulled } |
EmbeddingHealthStatus |
Returned by healthCheck(): { available, modelReady, statusLines } |
Loads and caches embedding configuration from environment variables. Singleton — loaded once, cached for the process lifetime.
| Export | Description |
|---|---|
loadEmbeddingConfig |
() → EmbeddingConfig — Load and cache config from env vars. Throws on invalid values. |
getEmbeddingConfig |
() → EmbeddingConfig — Get cached config (loads if not yet loaded). |
setResolvedOllamaMode |
(mode, url) → void — Update config after OLLAMA_MODE=auto probes native Ollama. |
resetEmbeddingConfig |
() → void — Clear cache (for testing). |
Key env vars: EMBEDDING_PROVIDER, OLLAMA_MODE, OLLAMA_URL, EMBEDDING_MODEL, EMBEDDING_DIMENSIONS, EMBEDDING_CONTEXT_LENGTH, OLLAMA_API_KEY.
Factory that creates and caches the active EmbeddingProvider instance. Uses dynamic import() to avoid loading all provider SDKs at startup.
| Export | Description |
|---|---|
getEmbeddingProvider |
(onProgress?) → Promise<EmbeddingProvider> — Get (or create) the active provider singleton. Re-creates if config changed. |
resetEmbeddingProvider |
() → void — Clear cached provider (for testing). |
Provider selected via EMBEDDING_PROVIDER env var (ollama / openai / google).
Ollama embedding provider. Supports Docker-managed, external, and auto-detect modes.
| Export | Description |
|---|---|
OllamaEmbeddingProvider |
Main provider class implementing EmbeddingProvider. |
isOllamaAvailable |
() → Promise<boolean> — Calls ollama.list() to check reachability. |
isModelAvailable |
() → Promise<boolean> — Checks if the configured model is pulled. |
pullModel |
() → Promise<void> — Pull the configured model. |
resetOllamaReadinessCache |
() → void — Force re-check on next ensureReady() call. |
resetAutoDetectionCache |
() → void — Re-run OLLAMA_MODE=auto probe on next call (for testing). |
ensureReady() handles the full setup sequence: auto-detect native Ollama → start Docker container if needed → pull model. Has a 60s TTL cache to avoid re-checking on every embedding call.
OpenAI embedding provider. Requires OPENAI_API_KEY.
| Export | Description |
|---|---|
OpenAIEmbeddingProvider |
Provider class. Uses client.embeddings.create() with native batching (up to 512 inputs/request). |
resetOpenAIClient |
() → void — Clear cached client (for testing). |
ensureReady() validates the API key and calls models.list() to confirm connectivity. Pre-truncates inputs to the model's context window (default 8191 tokens, ~3 chars/token estimate for code).
Google Generative AI embedding provider. Requires GOOGLE_API_KEY.
| Export | Description |
|---|---|
GoogleEmbeddingProvider |
Provider class. Uses batchEmbedContents() (up to 100 inputs/request). |
resetGoogleClient |
() → void — Clear cached client (for testing). |
ensureReady() validates the API key and makes a minimal test embedding call. Pre-truncates to 2048 tokens (~3 chars/token). Default model is gemini-embedding-001 (3072 dims).
| Function | Signature | Description |
|---|---|---|
ensureCollection |
(name) → Promise<void> |
Create collection with dense + BM25 vectors and payload indexes |
ensurePayloadIndex |
(collName, fieldName) → Promise<void> |
Create a payload index (idempotent) |
deleteCollection |
(name) → Promise<void> |
Drop collection |
listCodebaseCollections |
() → Promise<string[]> |
List codebase_*, codegraph_*, and context_* entries |
upsertChunks |
(collection, chunks, contentHash) → Promise<void> |
Generate embeddings and upsert (batches of 100) |
upsertPreEmbeddedChunks |
(collection, points) → Promise<{ pointsSkipped }> |
Upsert pre-embedded points with per-point fallback |
deleteFileChunks |
(collection, filePath) → Promise<void> |
Remove all chunks for a file |
searchChunks |
(collection, query, limit?, fileFilter?, languageFilter?) → Promise<SearchResult[]> |
Hybrid search (dense + BM25, RRF-fused) |
searchChunksWithFilter |
(collection, query, limit, filters) → Promise<SearchResult[]> |
Hybrid search with arbitrary payload filters |
getCollectionInfo |
(name) → Promise<{ pointsCount, status } | null> |
Collection info (null if not found, throws on transient errors) |
resetMetadataCollectionCache |
() → void |
Reset cached metadata collection readiness (testing) |
saveProjectMetadata |
(collName, projectPath, filesTotal, filesIndexed, fileHashes, indexingStatus) → Promise<void> |
Persist project metadata and file hashes |
loadProjectHashes |
(collName) → Promise<Map<string, string> | null> |
Load file content hashes (throws on transient errors) |
getProjectMetadata |
(collName) → Promise<{ projectPath, filesIndexed, ... } | null> |
Read project metadata (null on error) |
deleteProjectMetadata |
(collName) → Promise<void> |
Remove project metadata (best-effort) |
saveGraphData |
(graphCollName, projectPath, graph) → Promise<void> |
Persist code graph to metadata |
loadGraphData |
(graphCollName) → Promise<CodeGraph | null> |
Load persisted code graph (null on error) |
getGraphMetadata |
(graphCollName) → Promise<{ projectPath, lastBuiltAt, ... } | null> |
Read graph metadata (null on error) |
deleteGraphData |
(graphCollName) → Promise<void> |
Remove graph data (best-effort) |
saveContextMetadata |
(contextCollName, projectPath, artifacts) → Promise<void> |
Persist context artifact metadata |
loadContextMetadata |
(contextCollName) → Promise<ArtifactIndexState[] | null> |
Load context artifact states (null on error) |
getContextMetadata |
(contextCollName) → Promise<{ projectPath, lastIndexedAt, ... } | null> |
Read context metadata (null on error) |
deleteContextMetadata |
(contextCollName) → Promise<void> |
Remove context metadata (best-effort) |
deleteArtifactChunks |
(collection, artifactName) → Promise<void> |
Remove all chunks for a specific artifact |
IndexingStatus |
type |
"in-progress" | "completed" |
| Function | Signature | Description |
|---|---|---|
generateEmbeddings |
(texts: string[]) → Promise<number[][]> |
Batch generate embeddings with search_document: prefix |
generateQueryEmbedding |
(query: string) → Promise<number[]> |
Single query embedding with search_query: prefix |
prepareDocumentText |
(content, filePath) → string |
Prepare text with relative path prefix |
| Export | Signature | Description |
|---|---|---|
IndexingProgress |
(interface) | Progress details for an in-flight indexing operation: type, phase, files/chunks/batches processed |
IndexingCompleted |
(interface) | Summary of a completed indexing operation: type, duration, files processed, chunks created, optional error |
isIndexingInProgress |
(projectPath) → boolean |
Check if a project is currently being indexed |
getIndexingProgress |
(projectPath) → IndexingProgress | null |
Get progress details for an in-flight operation |
setIndexingProgress |
(projectPath, progress) → void |
Set or clear progress (used by index-tools during infrastructure setup) |
getLastCompleted |
(projectPath) → IndexingCompleted | null |
Get the last completed indexing operation |
getIndexingInProgressProjects |
() → string[] |
List all projects currently being indexed |
getPersistedIndexingStatus |
(projectPath) → Promise<"completed" | "in-progress" | "unknown"> |
Check persisted indexing status in Qdrant metadata |
requestCancellation |
(projectPath) → boolean |
Request graceful cancellation (stops after current batch) |
hashContent |
(content) → string |
SHA-256 hash (16-char hex prefix) for change detection |
chunkId |
(filePath, startLine) → string |
Generate a stable UUID chunk ID from file path and line number |
isIndexableFile |
(fileName, extraExts?) → boolean |
Check if a file should be indexed based on extension or name |
chunkFileContent |
(filePath, relativePath, content) → FileChunk[] |
AST-aware chunking with line-based and character-based fallbacks |
getIndexableFiles |
(projectPath, extraExts?) → Promise<string[]> |
Discover files respecting ignore rules + extra extensions |
indexProject |
(projectPath, onProgress?, extraExtensions?) → Promise<{ filesIndexed, chunksCreated, cancelled }> |
Full index with batched/resumable pipeline |
updateProjectIndex |
(projectPath, onProgress?, extraExtensions?) → Promise<{ added, updated, removed, chunksCreated, cancelled }> |
Incremental update |
removeProjectIndex |
(projectPath) → Promise<void> |
Delete index, code graph, and context artifacts |
| Function | Signature | Description |
|---|---|---|
startWatching |
(projectPath, onProgress?) → Promise<boolean> |
Start @parcel/watcher native subscription. Returns true if now watching (or already was), false if another process holds the lock or subscription failed |
stopWatching |
(projectPath) → Promise<void> |
Stop watcher |
stopAllWatchers |
() → Promise<void> |
Stop all watchers |
isWatching |
(projectPath) → boolean |
Check if a project is being watched by this process |
isWatchedByAnyProcess |
(projectPath) → Promise<boolean> |
Cross-process check: local subscriptions first, then file-based lock |
getWatchedProjects |
() → string[] |
List watched paths |
ensureWatcherStarted |
(projectPath) → void |
Fire-and-forget auto-start with TTL cache: checks not watching, not externally watched (60s cache), not indexing, collection exists |
clearExternalWatchCache |
() → void |
Clear the external watch TTL cache (for testing) |
Watcher settings:
- Uses native OS file watching: FSEvents (macOS), ReadDirectoryChangesW (Windows), inotify (Linux)
- Single native subscription per directory tree — no per-file enumeration
- Error throttling: logs first 3 errors, then every 100th
- Auto-stops after 10 consecutive errors
- Cross-process lock prevents duplicate watchers
- Cross-process status awareness:
codebase_statusandcodebase_searchdetect watchers running in other MCP processes via file-based locks - Auto-starts on first tool interaction with an indexed project (search, status, update, graph), with 60-second TTL cache to avoid retrying when another process holds the lock
| Function | Signature | Description |
|---|---|---|
buildCodeGraph |
(projectPath, extraExtensions?, progress?) → Promise<CodeGraph> |
Build dependency graph via ast-grep with optional progress tracking |
getOrBuildGraph |
(projectPath, extraExtensions?) → Promise<CodeGraph> |
Get cached graph or build new one |
rebuildGraph |
(projectPath, extraExtensions?) → Promise<CodeGraph> |
Force rebuild with concurrency guard (joins existing build if in progress) |
invalidateGraphCache |
(projectPath) → void |
Clear cached graph for project |
isGraphBuildInProgress |
(projectPath) → boolean |
Check if graph build is running |
getGraphBuildProgress |
(projectPath) → GraphBuildProgress | null |
Get in-flight build progress |
getLastGraphBuildCompleted |
(projectPath) → GraphBuildCompleted | null |
Get last completed build info |
getGraphBuildInProgressProjects |
() → string[] |
List all projects currently building |
ensureDynamicLanguages |
() → void |
Register dynamic ast-grep language grammars |
getAstGrepLang |
(ext) → Lang | string | null |
Map file extension to ast-grep language |
| Function | Signature | Description |
|---|---|---|
getFileDependencies |
(graph, relativePath) → { imports, importedBy } |
Query single file |
findCircularDependencies |
(graph) → string[][] |
DFS cycle detection |
getGraphStats |
(graph) → { totalFiles, totalEdges, ... } |
Summary statistics |
generateMermaidDiagram |
(graph) → string |
Generate Mermaid dependency diagram |
| Export | Signature | Description |
|---|---|---|
SocratiCodeConfig |
(interface) | Parsed .socraticodecontextartifacts.json shape: { artifacts?: ContextArtifact[] } |
loadConfig |
(projectPath) → Promise<SocratiCodeConfig | null> |
Load and validate config file (null if missing, throws on parse/validation errors) |
readArtifactContent |
(artifactPath, projectPath) → Promise<{ content, contentHash }> |
Read file or directory content with SHA-256 hash for staleness detection |
chunkArtifactContent |
(content, artifactName, artifactPath) → ArtifactChunk[] |
Line-based chunking with overlap (same CHUNK_SIZE/CHUNK_OVERLAP as code) |
indexArtifact |
(projectPath, artifact, collection) → Promise<ArtifactIndexState> |
Index a single artifact: read → chunk → embed → upsert to Qdrant |
indexAllArtifacts |
(projectPath) → Promise<{ indexed, errors }> |
Index all artifacts from config, saving metadata. Errors per-artifact, never throws. |
ensureArtifactsIndexed |
(projectPath) → Promise<{ reindexed, upToDate, errors }> |
Staleness check: compare content hashes, re-index only changed artifacts, remove deleted ones |
searchArtifacts |
(projectPath, query, artifactName?, limit?) → Promise<SearchResult[]> |
Hybrid semantic + BM25 search across context artifacts with optional name filter |
removeAllArtifacts |
(projectPath) → Promise<void> |
Delete context collection and metadata for a project |
getArtifactStatusSummary |
(projectPath) → Promise<{ configuredCount, indexedCount, totalChunks, lines } | null> |
Compact status summary (null if no config file). Used by codebase_status and codebase_list_projects. |
| Function | Signature | Description |
|---|---|---|
extractImports |
(source, lang, ext) → ImportInfo[] |
Extract imports from source using ast-grep AST patterns |
Supports 18+ languages including TypeScript, JavaScript, Python, Java, Kotlin, Go, Rust, Ruby, PHP, C, C++, C#, Swift, Scala, Bash, Dart, and Lua.
| Function | Signature | Description |
|---|---|---|
resolveImport |
(specifier, importerPath, projectRoot, fileSet, language) → string | null |
Resolve module specifier to file path |
| Function | Signature | Description |
|---|---|---|
acquireProjectLock |
(projectPath, operation) → Promise<boolean> |
Acquire cross-process lock (returns false if held by another process) |
releaseProjectLock |
(projectPath, operation) → Promise<void> |
Release a previously acquired lock |
isProjectLocked |
(projectPath, operation) → Promise<boolean> |
Check if a lock is currently held by any process |
getLockHolderPid |
(projectPath, operation) → Promise<number | null> |
Get PID of the process holding the lock (null if none) |
terminateLockHolder |
(projectPath, operation) → Promise<{ terminated, pid }> |
Send SIGTERM to an orphan lock-holder process |
releaseAllLocks |
() → Promise<void> |
Release all locks held by this process (graceful shutdown) |
Lock settings:
- Lock files stored in
os.tmpdir()/socraticode-locks/ - Staleness threshold: 2 minutes (locks auto-reclaim if holder crashes)
- Refresh interval: 30 seconds
- Uses
proper-lockfilefor atomic, cross-platform file-based locking
| Function | Signature | Description |
|---|---|---|
createIgnoreFilter |
(projectPath) → Ignore |
Build combined filter |
shouldIgnore |
(ig, relativePath) → boolean |
Check if path is ignored |
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
extraExtensions?: string — Comma-separated extra file extensions (e.g. '.tpl,.blade,.hbs')
Returns: Infrastructure setup messages + "Indexing started" confirmation (runs in background)
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
extraExtensions?: string — Comma-separated extra file extensions (e.g. '.tpl,.blade')
Returns: Count of added/updated/removed files and new chunks
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: Cancellation confirmation with current phase, or orphan process termination status
Parameters:
projectPath: string — Absolute path (required)
Returns: Confirmation message
Behaviour:
1. Stops the file watcher (if active) to prevent updates on a deleted index.
2. Cancels any in-progress indexing or incremental update and waits for it to
drain (up to 30 s timeout — proceeds anyway if it does not stop in time).
3. Waits for any in-flight code graph build to finish.
4. Deletes the vector collection, graph, context artifacts, and metadata.
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
action: "start" | "stop" | "status"
Returns: Status message or list of watched projects
Parameters:
query: string — Natural language search query (required)
projectPath?: string — Absolute path (defaults to cwd)
limit?: number — 1-50 (default 10)
fileFilter?: string — Filter by relative file path
languageFilter?: string — Filter by language name
minScore?: number — 0-1, minimum RRF score threshold (default: 0.10, env: SEARCH_MIN_SCORE)
Returns: Ranked code chunks with file paths, line numbers, language, similarity scores, and content
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: Collection name, status, and indexed chunk count
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
extraExtensions?: string — Comma-separated extra extensions
Behavior: Starts graph build in the background (fire-and-forget).
If a build is already in progress, returns current progress instead.
Uses concurrency guard — duplicate callers share the same build.
Returns: "Build started" message with instructions to poll codebase_graph_status
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
filePath: string — Relative file path to query (required)
Returns: List of dependencies (imports) and dependents (imported by)
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: Statistics: total files, edges, avg dependencies, top 10 most connected, orphans, circular dep count
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: List of circular dependency chains (up to 20 displayed)
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: Mermaid diagram of the dependency graph, color-coded by language
Parameters:
projectPath: string — Absolute path (required)
Returns: Confirmation message
Behaviour:
Waits for any in-flight graph build to finish before removing the persisted
graph data and clearing the in-memory cache.
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns:
If build in progress: Status BUILDING with phase, progress %, elapsed time
If ready: Status READY with node/edge count, last built time, cache status, last build duration
If not found: Instructions to build
Parameters: none
Returns: Status of Docker, Qdrant image, Qdrant container, Ollama, and embedding model
Parameters: none
Returns: List of all Qdrant collections (indexed projects and their graph status)
Parameters: none
Returns: Information about SocratiCode — philosophy, features, and capabilities
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: List of all artifacts defined in .socraticodecontextartifacts.json with names, descriptions, paths, and index status
Parameters:
query: string — Natural language search query
projectPath?: string — Absolute path (defaults to cwd)
artifactName?: string — Filter to a specific artifact name
limit?: number — Max results (default: 10, range: 1-50)
minScore?: number — 0-1, minimum RRF score threshold (default: 0.10, env: SEARCH_MIN_SCORE)
Returns: Ranked context chunks matching the query. Auto-indexes on first use, auto-detects staleness.
Parameters:
projectPath?: string — Absolute path (defaults to cwd)
Returns: Number of indexed artifacts and chunks per artifact
Parameters:
projectPath: string — Absolute path (required)
Returns: Confirmation message
Behaviour:
Refuses removal if indexing/update is in progress (which includes context
artifact indexing). Returns a message suggesting codebase_stop or waiting.
interface FileChunk {
id: string; // SHA-256 of "filePath:startLine" formatted as UUID (36 chars, 8-4-4-4-12)
filePath: string; // Absolute path
relativePath: string; // Relative to project root
content: string; // Chunk text content
startLine: number; // 1-based line number
endLine: number; // Inclusive
language: string; // Detected from extension
type: "code" | "comment" | "mixed";
}interface SearchResult {
filePath: string;
relativePath: string;
content: string;
startLine: number;
endLine: number;
language: string;
score: number; // RRF (Reciprocal Rank Fusion) score from hybrid search
}interface CodeGraph {
nodes: CodeGraphNode[];
edges: CodeGraphEdge[];
}
interface CodeGraphNode {
filePath: string;
relativePath: string;
imports: string[]; // Module specifiers (e.g. "./utils")
exports: string[];
dependencies: string[]; // Resolved relative paths
dependents: string[]; // Files that import this file
}
interface CodeGraphEdge {
source: string; // Relative path of importer
target: string; // Relative path of imported
type: "import" | "re-export" | "dynamic-import";
}interface HealthStatus {
docker: boolean;
ollama: boolean;
qdrant: boolean;
ollamaModel: boolean;
qdrantImage: boolean;
ollamaImage: boolean;
}/** A context artifact defined in .socraticodecontextartifacts.json */
interface ContextArtifact {
name: string; // Unique name (e.g. "database-schema")
path: string; // File or directory path (relative or absolute)
description: string; // Describes what this artifact is and how AI should use it
}
/** Runtime state of an indexed artifact */
interface ArtifactIndexState {
name: string;
description: string;
resolvedPath: string; // Absolute path
contentHash: string; // For staleness detection
lastIndexedAt: string; // ISO timestamp
chunksIndexed: number; // Number of chunks stored in Qdrant
}interface ProjectConfig {
projectId: string; // 12-char SHA-256 prefix
projectPath: string;
collectionName: string; // "codebase_{projectId}"
graphCollectionName: string; // "codegraph_{projectId}"
lastIndexedAt?: string;
}The server manages a single Qdrant container with these settings:
Name: socraticode-qdrant
Image: qdrant/qdrant:v1.17.0
Ports: 16333:6333 (HTTP REST API), 16334:6334 (gRPC)
Volume: socraticode_qdrant_data:/qdrant/storage
Restart: unless-stopped
The server manages a single Ollama container:
Name: socraticode-ollama
Image: ollama/ollama:latest
Ports: 11435:11434 (API)
Volume: socraticode_ollama_data:/root/.ollama
Restart: unless-stopped
Data persistence: The named Docker volumes persist across container restarts and upgrades. Your indexes and models survive server and Docker restarts.
Alternative: Instead of the server auto-managing the containers, you can run them yourself via docker-compose up -d using the included docker-compose.yml. The server will detect the already-running containers and skip creation.
Each project gets a collection with:
- Vectors: Provider-dependent dimensions (768 for Ollama, 1536 for OpenAI, 3072 for Google), cosine distance
- Optimizers: 2 segments
- Payload storage: On-disk (to handle large codebases)
- Payload indexes:
filePath(keyword),relativePath(keyword),language(keyword),contentHash(keyword)
Edit SUPPORTED_EXTENSIONS and getLanguageFromExtension() in src/constants.ts.
Edit CHUNK_SIZE and CHUNK_OVERLAP in src/constants.ts. Smaller chunks give more precise search results but use more storage and embedding calls. Larger chunks give more context per result.
- Set
EMBEDDING_PROVIDERin your MCP config env block (ollama,openai, orgoogle). - Optionally override
EMBEDDING_MODELandEMBEDDING_DIMENSIONSfor the chosen provider (auto-detected defaults exist for all built-in models). - Re-index all projects (
codebase_removethencodebase_index) since existing vectors have different dimensions.
See src/services/embedding-config.ts for all supported environment variables and per-provider defaults.
- Define the handler in the appropriate file under
src/tools/ - Register the tool in
src/index.tsusingserver.tool() - Follow the existing pattern: zod schema for input, string return value
Edit DEFAULT_IGNORE_PATTERNS in src/services/ignore.ts.
Make sure Docker Desktop is installed and running. On Linux, ensure the Docker daemon is started and your user is in the docker group.
The Ollama container is managed automatically via Docker. Check that the socraticode-ollama container is running with docker ps. If it's not starting, check docker logs socraticode-ollama.
The container may be slow to start. Try:
docker logs socraticode-qdrantMake sure the project has been indexed first (codebase_index). Check the status with codebase_status.
The code graph uses ast-grep for AST-based import extraction. It works for 18+ languages. If a file has no recognized imports (or uses non-standard import patterns), it may appear as an orphan node.
- Initial indexing is CPU/IO intensive (embedding generation). Subsequent updates are incremental and much faster.
- Files over 5 MB are automatically skipped (configurable via
MAX_FILE_SIZE_MB). - Consider adding large generated or data files to
.socraticodeignore.
Check that the dist/ directory exists. Run npm run build first.
Qdrant exposes a REST API on port 16333. You can inspect and clean up directly via curl:
List all collections:
curl -s http://localhost:16333/collections | python3 -m json.toolDelete a specific collection:
curl -X DELETE http://localhost:16333/collections/codebase_<projectId>
curl -X DELETE http://localhost:16333/collections/codegraph_<projectId>Delete ALL collections (nuclear option):
curl -s http://localhost:16333/collections | python3 -c "
import sys, json
for c in json.load(sys.stdin)['result']['collections']:
print(c['name'])
" | while read name; do
echo "Deleting $name"
curl -s -X DELETE "http://localhost:16333/collections/$name" > /dev/null
doneWipe the entire Qdrant volume (most thorough):
docker stop socraticode-qdrant
docker rm socraticode-qdrant
docker volume rm socraticode_qdrant_dataThe server will recreate everything automatically on next use.