# 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
   │   └── Svelte/Vue: HTML parse → <script> extraction → re-parse as TypeScript
   ├── 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<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
```

### ollama.ts

| 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.

### 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<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`).

### provider-ollama.ts

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.

### 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<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"` |

### embeddings.ts

| 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 |

### 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<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 |

### watcher.ts

| 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_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<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 |

### 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<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`. |

### 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<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-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_<projectId>
curl -X DELETE http://localhost:16333/collections/codegraph_<projectId>
```

**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.
