# Developer Guide This document covers the internals of **SocratiCode** — architecture, data flow, configuration, and how to build, extend, and debug. ## Table of Contents - [Architecture Overview](#architecture-overview) - [Prerequisites for Development](#prerequisites-for-development) - [Building and Running](#building-and-running) - [Project Structure](#project-structure) - [Configuration & Constants](#configuration--constants) - [Data Flow: Indexing](#data-flow-indexing) - [Data Flow: Search](#data-flow-search) - [Data Flow: Incremental Update](#data-flow-incremental-update) - [Data Flow: Code Graph](#data-flow-code-graph) - [Testing](#testing) - [Services Reference](#services-reference) - [MCP Tools Reference](#mcp-tools-reference) - [Data Structures](#data-structures) - [Docker & Infrastructure](#docker--infrastructure) - [Extending the Indexer](#extending-the-indexer) - [Troubleshooting](#troubleshooting) --- ## Architecture Overview ``` ┌─────────────────────────────────────────────────────┐ │ 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 │ └──────────────────┘ └─────────────────────┘ ``` --- ## Prerequisites for Development | 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 | --- ## Building and Running ### Install dependencies ```bash npm install ``` ### Build ```bash npm run build ``` This compiles TypeScript from `src/` to `dist/` with source maps and declarations. ### Run directly (development) ```bash npm run dev ``` Uses `tsx` to run TypeScript directly without a build step. ### Run built version ```bash npm start # or node dist/index.js ``` The 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. ### TypeScript Configuration - **Target**: ES2022 - **Module**: Node16 (ESM) - **Strict mode**: Enabled - **Output**: `dist/` with source maps and `.d.ts` declarations ### Linting SocratiCode uses [Biome](https://biomejs.dev/) for linting. Biome is fast, zero-config, and catches unused imports, style issues, and potential bugs. ```bash # Check for lint issues npm run lint # Auto-fix safe issues npm run lint:fix ``` For VS Code, install the [Biome extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) for real-time lint feedback and auto-fix on save. ### Versioning & Releases SocratiCode uses [Conventional Commits](https://www.conventionalcommits.org/) and [release-it](https://github.com/release-it/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:** ```bash npm run lint && npx tsc --noEmit && npm run test:unit ``` **Creating a release** (maintainers only): ```bash # Interactive — prompts for patch/minor/major npm run release # Dry run — preview what will happen without making changes npm run release:dry ``` This will automatically: 1. Determine the version bump from your commits 2. Update `CHANGELOG.md` with all `feat:`, `fix:`, etc. entries 3. Bump the version in `package.json` 4. Create a git commit and tag (`v1.1.0`) 5. Push to GitHub and create a GitHub Release --- ## Project Structure ``` 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 ``` --- ## Configuration & Constants 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`, and `EMBEDDING_CONTEXT_LENGTH` are defined in `src/services/embedding-config.ts`, not in `src/constants.ts`. Defaults are `nomic-embed-text` / `768` for Ollama, `text-embedding-3-small` / `1536` for OpenAI, and `gemini-embedding-001` / `3072` for Google. ### Embedding batch size Defined in `src/services/embeddings.ts`: texts are sent to Ollama in batches of **32**. ### File watcher debounce Defined in `src/services/watcher.ts`: file changes are debounced for **2000ms** before triggering an index update. ### Maximum file size Defined in `src/constants.ts` as `MAX_FILE_BYTES`: files larger than **5 MB** are skipped (configurable via `MAX_FILE_SIZE_MB` env var). ### Qdrant health check Defined in `src/services/docker.ts`: after starting the container, the server polls `/healthz` up to **30 times** with **1000ms** between retries. ### Project ID & Collection Naming 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. ### Supported File Extensions (54) | 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`. ### Built-in Ignore Patterns (45) 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`. --- ## Data Flow: Indexing 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) ``` --- ## Data Flow: Search 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 ``` ### nomic-embed-text Task Prefixes 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. --- ## Data Flow: Incremental Update 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. --- ## Data Flow: Code Graph 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. ### Circular Dependency Detection Uses depth-first search (DFS) with a recursion stack to find cycles in the directed dependency graph. --- ## Testing SocratiCode uses **vitest** as its test framework with **634 tests** across three layers. ### Running Tests ```bash # 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 ``` ### Test Architecture | 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 | ### Test Infrastructure - **`tests/helpers/fixtures.ts`** — Creates temporary fixture projects with TypeScript, Python files, and supporting config. Provides `isDockerAvailable()` for conditional test skipping. - **`tests/helpers/setup.ts`** — Manages test Qdrant collections with `createTestQdrantClient()`, `cleanupTestCollections()`, cache reset helpers, and infrastructure readiness polling (`waitForQdrant()`, `waitForOllama()`). ### Key Design Decisions - **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. ### Configuration Test configuration is in `vitest.config.ts`: ```typescript { 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"] } } ``` --- ## Services Reference ### docker.ts | Function | Signature | Description | |----------|-----------|-------------| | `isDockerAvailable` | `() → Promise` | Runs `docker info` to check | | `isQdrantImagePresent` | `() → Promise` | Checks `docker images` | | `pullQdrantImage` | `(onProgress?) → Promise` | Runs `docker pull` | | `isQdrantRunning` | `() → Promise` | Checks `docker ps` | | `startQdrant` | `(onProgress?) → Promise` | Creates and starts Qdrant container | | `resetQdrantReadinessCache` | `() → void` | Clear cached readiness state | | `ensureQdrantReady` | `(onProgress?) → Promise<{ started, pulled }>` | Full Qdrant setup orchestration | | `isOllamaImagePresent` | `() → Promise` | Checks for Ollama Docker image | | `pullOllamaImage` | `(onProgress?) → Promise` | Pulls Ollama Docker image | | `isOllamaRunning` | `() → Promise` | Checks Ollama container status | | `startOllama` | `(onProgress?) → Promise` | 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 ``` ### ollama.ts | Function | Signature | Description | |----------|-----------|-------------| | `isOllamaAvailable` | `() → Promise` | Calls Ollama API to check availability | | `isModelAvailable` | `() → Promise` | Checks for the configured `EMBEDDING_MODEL` | | `pullModel` | `() → Promise` | Pulls the model | | `embed` | `(texts: string[]) → Promise` | Batch embedding | | `embedSingle` | `(text: string) → Promise` | 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. ### embedding-types.ts 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 }` | ### embedding-config.ts 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`. ### embedding-provider.ts 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` — 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`). ### provider-ollama.ts Ollama embedding provider. Supports Docker-managed, external, and auto-detect modes. | Export | Description | |--------|-------------| | `OllamaEmbeddingProvider` | Main provider class implementing `EmbeddingProvider`. | | `isOllamaAvailable` | `() → Promise` — Calls `ollama.list()` to check reachability. | | `isModelAvailable` | `() → Promise` — Checks if the configured model is pulled. | | `pullModel` | `() → Promise` — 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. ### provider-openai.ts 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). ### provider-google.ts 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). ### qdrant.ts | Function | Signature | Description | |----------|-----------|-------------| | `ensureCollection` | `(name) → Promise` | Create collection with dense + BM25 vectors and payload indexes | | `ensurePayloadIndex` | `(collName, fieldName) → Promise` | Create a payload index (idempotent) | | `deleteCollection` | `(name) → Promise` | Drop collection | | `listCodebaseCollections` | `() → Promise` | List `codebase_*`, `codegraph_*`, and `context_*` entries | | `upsertChunks` | `(collection, chunks, contentHash) → Promise` | Generate embeddings and upsert (batches of 100) | | `upsertPreEmbeddedChunks` | `(collection, points) → Promise<{ pointsSkipped }>` | Upsert pre-embedded points with per-point fallback | | `deleteFileChunks` | `(collection, filePath) → Promise` | Remove all chunks for a file | | `searchChunks` | `(collection, query, limit?, fileFilter?, languageFilter?) → Promise` | Hybrid search (dense + BM25, RRF-fused) | | `searchChunksWithFilter` | `(collection, query, limit, filters) → Promise` | 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` | Persist project metadata and file hashes | | `loadProjectHashes` | `(collName) → Promise \| null>` | Load file content hashes (throws on transient errors) | | `getProjectMetadata` | `(collName) → Promise<{ projectPath, filesIndexed, ... } \| null>` | Read project metadata (null on error) | | `deleteProjectMetadata` | `(collName) → Promise` | Remove project metadata (best-effort) | | `saveGraphData` | `(graphCollName, projectPath, graph) → Promise` | Persist code graph to metadata | | `loadGraphData` | `(graphCollName) → Promise` | Load persisted code graph (null on error) | | `getGraphMetadata` | `(graphCollName) → Promise<{ projectPath, lastBuiltAt, ... } \| null>` | Read graph metadata (null on error) | | `deleteGraphData` | `(graphCollName) → Promise` | Remove graph data (best-effort) | | `saveContextMetadata` | `(contextCollName, projectPath, artifacts) → Promise` | Persist context artifact metadata | | `loadContextMetadata` | `(contextCollName) → Promise` | Load context artifact states (null on error) | | `getContextMetadata` | `(contextCollName) → Promise<{ projectPath, lastIndexedAt, ... } \| null>` | Read context metadata (null on error) | | `deleteContextMetadata` | `(contextCollName) → Promise` | Remove context metadata (best-effort) | | `deleteArtifactChunks` | `(collection, artifactName) → Promise` | Remove all chunks for a specific artifact | | `IndexingStatus` | `type` | `"in-progress" \| "completed"` | ### embeddings.ts | Function | Signature | Description | |----------|-----------|-------------| | `generateEmbeddings` | `(texts: string[]) → Promise` | Batch generate embeddings with `search_document:` prefix | | `generateQueryEmbedding` | `(query: string) → Promise` | Single query embedding with `search_query:` prefix | | `prepareDocumentText` | `(content, filePath) → string` | Prepare text with relative path prefix | ### indexer.ts | 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` | 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` | Delete index, code graph, and context artifacts | ### watcher.ts | Function | Signature | Description | |----------|-----------|-------------| | `startWatching` | `(projectPath, onProgress?) → Promise` | 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` | Stop watcher | | `stopAllWatchers` | `() → Promise` | Stop all watchers | | `isWatching` | `(projectPath) → boolean` | Check if a project is being watched **by this process** | | `isWatchedByAnyProcess` | `(projectPath) → Promise` | 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_status` and `codebase_search` detect 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 ### code-graph.ts | Function | Signature | Description | |----------|-----------|-------------| | `buildCodeGraph` | `(projectPath, extraExtensions?, progress?) → Promise` | Build dependency graph via ast-grep with optional progress tracking | | `getOrBuildGraph` | `(projectPath, extraExtensions?) → Promise` | Get cached graph or build new one | | `rebuildGraph` | `(projectPath, extraExtensions?) → Promise` | 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 | ### graph-analysis.ts | 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 | ### context-artifacts.ts | Export | Signature | Description | |--------|-----------|-------------| | `SocratiCodeConfig` | *(interface)* | Parsed `.socraticodecontextartifacts.json` shape: `{ artifacts?: ContextArtifact[] }` | | `loadConfig` | `(projectPath) → Promise` | 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` | 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` | Hybrid semantic + BM25 search across context artifacts with optional name filter | | `removeAllArtifacts` | `(projectPath) → Promise` | 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`. | ### graph-imports.ts | 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. ### graph-resolution.ts | Function | Signature | Description | |----------|-----------|-------------| | `resolveImport` | `(specifier, importerPath, projectRoot, fileSet, language) → string \| null` | Resolve module specifier to file path | ### lock.ts | Function | Signature | Description | |----------|-----------|-------------| | `acquireProjectLock` | `(projectPath, operation) → Promise` | Acquire cross-process lock (returns false if held by another process) | | `releaseProjectLock` | `(projectPath, operation) → Promise` | Release a previously acquired lock | | `isProjectLocked` | `(projectPath, operation) → Promise` | Check if a lock is currently held by any process | | `getLockHolderPid` | `(projectPath, operation) → Promise` | 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` | 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-lockfile` for atomic, cross-platform file-based locking ### ignore.ts | Function | Signature | Description | |----------|-----------|-------------| | `createIgnoreFilter` | `(projectPath) → Ignore` | Build combined filter | | `shouldIgnore` | `(ig, relativePath) → boolean` | Check if path is ignored | --- ## MCP Tools Reference ### Indexing Tools #### `codebase_index` ``` 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) ``` #### `codebase_update` ``` 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 ``` #### `codebase_stop` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: Cancellation confirmation with current phase, or orphan process termination status ``` #### `codebase_remove` ``` 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. ``` #### `codebase_watch` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) action: "start" | "stop" | "status" Returns: Status message or list of watched projects ``` ### Query Tools #### `codebase_search` ``` 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 ``` #### `codebase_status` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: Collection name, status, and indexed chunk count ``` ### Graph Tools #### `codebase_graph_build` ``` 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 ``` #### `codebase_graph_query` ``` 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) ``` #### `codebase_graph_stats` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: Statistics: total files, edges, avg dependencies, top 10 most connected, orphans, circular dep count ``` #### `codebase_graph_circular` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: List of circular dependency chains (up to 20 displayed) ``` #### `codebase_graph_visualize` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: Mermaid diagram of the dependency graph, color-coded by language ``` #### `codebase_graph_remove` ``` 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. ``` #### `codebase_graph_status` ``` 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 ``` ### Management Tools #### `codebase_health` ``` Parameters: none Returns: Status of Docker, Qdrant image, Qdrant container, Ollama, and embedding model ``` #### `codebase_list_projects` ``` Parameters: none Returns: List of all Qdrant collections (indexed projects and their graph status) ``` #### `codebase_about` ``` Parameters: none Returns: Information about SocratiCode — philosophy, features, and capabilities ``` ### Context Artifact Tools #### `codebase_context` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: List of all artifacts defined in .socraticodecontextartifacts.json with names, descriptions, paths, and index status ``` #### `codebase_context_search` ``` 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. ``` #### `codebase_context_index` ``` Parameters: projectPath?: string — Absolute path (defaults to cwd) Returns: Number of indexed artifacts and chunks per artifact ``` #### `codebase_context_remove` ``` 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. ``` --- ## Data Structures ### FileChunk ```typescript 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"; } ``` ### SearchResult ```typescript interface SearchResult { filePath: string; relativePath: string; content: string; startLine: number; endLine: number; language: string; score: number; // RRF (Reciprocal Rank Fusion) score from hybrid search } ``` ### CodeGraph / CodeGraphNode / CodeGraphEdge ```typescript 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"; } ``` ### HealthStatus ```typescript interface HealthStatus { docker: boolean; ollama: boolean; qdrant: boolean; ollamaModel: boolean; qdrantImage: boolean; ollamaImage: boolean; } ``` ### ContextArtifact / ArtifactIndexState ```typescript /** 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 } ``` ### ProjectConfig ```typescript interface ProjectConfig { projectId: string; // 12-char SHA-256 prefix projectPath: string; collectionName: string; // "codebase_{projectId}" graphCollectionName: string; // "codegraph_{projectId}" lastIndexedAt?: string; } ``` --- ## Docker & Infrastructure ### Qdrant Container 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 ``` ### Ollama Container 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. ### Qdrant Collection Schema 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) --- ## Extending the Indexer ### Adding a new file extension Edit `SUPPORTED_EXTENSIONS` and `getLanguageFromExtension()` in `src/constants.ts`. ### Changing chunk size or overlap 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. ### Switching embedding model or provider 1. Set `EMBEDDING_PROVIDER` in your MCP config env block (`ollama`, `openai`, or `google`). 2. Optionally override `EMBEDDING_MODEL` and `EMBEDDING_DIMENSIONS` for the chosen provider (auto-detected defaults exist for all built-in models). 3. Re-index all projects (`codebase_remove` then `codebase_index`) since existing vectors have different dimensions. See `src/services/embedding-config.ts` for all supported environment variables and per-provider defaults. ### Adding a new MCP tool 1. Define the handler in the appropriate file under `src/tools/` 2. Register the tool in `src/index.ts` using `server.tool()` 3. Follow the existing pattern: zod schema for input, string return value ### Adding new ignore patterns Edit `DEFAULT_IGNORE_PATTERNS` in `src/services/ignore.ts`. --- ## Troubleshooting ### "Docker is not available" Make sure Docker Desktop is installed and running. On Linux, ensure the Docker daemon is started and your user is in the `docker` group. ### "Ollama is not available" 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`. ### Qdrant health check times out The container may be slow to start. Try: ```bash docker logs socraticode-qdrant ``` ### Search returns no results Make sure the project has been indexed first (`codebase_index`). Check the status with `codebase_status`. ### Code graph returns empty 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. ### Large codebase is slow to index - 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`. ### Server crashes on startup Check that the `dist/` directory exists. Run `npm run build` first. ### Qdrant Manual Management Qdrant exposes a REST API on port 16333. You can inspect and clean up directly via `curl`: **List all collections:** ```bash curl -s http://localhost:16333/collections | python3 -m json.tool ``` **Delete a specific collection:** ```bash curl -X DELETE http://localhost:16333/collections/codebase_ curl -X DELETE http://localhost:16333/collections/codegraph_ ``` **Delete ALL collections (nuclear option):** ```bash 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 done ``` **Wipe the entire Qdrant volume (most thorough):** ```bash docker stop socraticode-qdrant docker rm socraticode-qdrant docker volume rm socraticode_qdrant_data ``` The server will recreate everything automatically on next use.