# Tech Debt Register — clew-engine

**Last audited:** 2026-02-08
**Last updated:** 2026-02-09 (Respx migration)
**Method:** Automated scan + manual code review

✅ **TEST-3** — Migrated MCP server tests to respx (2026-02-09)
✅ **CONF-2** — Centralized EMBEDDING_DIMENSION in config (2026-02-09)
✅ **STYLE-1** — Moved all inline imports to top-level (routes, noop_graph, pgvector) (2026-02-09)
✅ **STYLE-2** — Moved type check functions to TYPE_CHECKING blocks (2026-02-09)
✅ **CONF-4** — Pinned Qdrant Docker image to v1.7.4 (2026-02-09)
✅ **QUAL-5** — Added upsert() method to QdrantStore (2026-02-09)
✅ **QUAL-2** — Implemented variable-depth Neo4j traversal (1-3 hops) with parameterized queries (2026-02-09)
✅ **QUAL-3** — Refactored MCP server (extracted graph/mermaid helpers)
✅ **DEP-2** — Migrated to Pydantic V2 and refactored deprecated `Config` classes
✅ **QUAL-4** — Removed pass-through dead code in MCP `get_client_config()`
✅ **QUAL-1** — Unified hash-embedding logic between routes and adapters
✅ **CONF-3** — Made MCP and Ollama timeouts configurable via env vars
✅ **TEST-1** — Implemented comprehensive tests for MCP tools (search, explore, verify)
✅ **TEST-2** — Implemented unit tests for Qdrant and Neo4j adapters (PR #33)
✅ **DEP-1** — Added missing `httpx` and `asyncpg` dependencies to pyproject.toml
✅ **TYPO-1** — Fixed "actsually" → "actually" in MCP server docstring
✅ **ERR-1** — Standardized exception handling in adapters and MCP server (2026-02-09)
✅ **CONF-1** — Removed hardcoded credentials in config and dependencies (2026-02-09)

---

## Critical — Fix Before Alpha

### ~~SEC-1: No input validation on API request parameters~~ ✅ FIXED
- ~~`routes/search.py:19` — `query: str` has no max length. Unbounded input could OOM the embedding provider.~~
- ~~`routes/search.py:20` — `limit: int = 10` has no min/max. `limit=1000000` exhausts Qdrant.~~
- ~~`routes/graph.py:20` — `depth: int = 2` has no upper bound. Platform caps at 3, Engine doesn't.~~
- ~~`routes/graph.py:21` — `relationship_types` has no allowlist. Arbitrary strings forwarded to Cypher.~~
- **Status:** Added `Field` constraints and `ALLOWED_RELATIONSHIPS` validation in Phase 8 sweep.

### ~~SEC-2: Cypher injection via relationship_types~~ ✅ FIXED
- ~~`adapters/neo4j.py:60` — User-supplied relationship types are backtick-escaped and interpolated into Cypher query string. No allowlist.~~
- **Status:** Implemented a second layer of defense with a hardcoded allowlist in the Neo4j adapter.

### ~~SEC-3: Error results embedded in normal response payload~~ ✅ FIXED
- ~~`adapters/qdrant.py:100-106` — On search failure, returns `SearchResult(id="error", ...)` inside the results list. Client cannot distinguish errors from real matches.~~
- **Status:** Refactored to raise `HTTPException(503)` in the route when the adapter fails.

### ~~DEP-1: Missing runtime dependencies in pyproject.toml~~ ✅ FIXED
- ~~`adapters/embeddings.py` imports `httpx` — not listed in pyproject.toml.~~
- ~~`adapters/pgvector.py` imports `asyncpg` — not listed in pyproject.toml.~~
- **Status:** Both dependencies added to pyproject.toml on 2026-02-08.

---

## High — Fix During Alpha

### ~~QUAL-1: Duplicate hash-embedding logic~~ ✅ FIXED
- ~~`routes/search.py:50-53` — Inline reimplements `HashEmbeddings.embed()` as a fallback when embedding fails. Same logic exists in `adapters/embeddings.py:96-101`.~~
- **Status:** Unified in PR #25.

### ~~DEP-2: Pydantic v1 Config class deprecated~~ ✅ FIXED
- ~~`config.py:19-20` — Uses `class Config: env_file = ".env"` pattern.~~
- **Status:** Migrated to Pydantic V2 `SettingsConfigDict` in PR #27.

### ~~QUAL-2: Neo4j traverse ignores depth parameter~~ ✅ FIXED
- ~~`adapters/neo4j.py:65-77` — Query hardcodes single-hop `(source)-[r]->(target)` regardless of `depth` value. LIMIT hardcoded to 100, not user-configurable.~~
- ~~The Platform Worker's DGT implementation does support depth 1-3, but the Engine adapter doesn't.~~
- **Status:** Implemented variable-length path query `[*1..depth]` with depth clamping (1-3). Uses parameterized relationship types to prevent injection. Tests updated to verify parameter passing.

### ~~QUAL-3: Long functions in MCP server~~ ✅ FIXED
- ~~`search_codebase` and `explore_module` were 100+ lines long with mixed concerns.~~
- **Status:** Logic extracted into `_format_graph_context` and `_build_mermaid_diagram` helpers. Significant reduction in tool function complexity.

### ~~QUAL-4: Dead code in MCP get_client_config()~~ ✅ FIXED
- ~~`server.py:36,41` — Two `pass` statements in if/else branches.~~
- **Status:** Fixed in PR #26. Conditionals are now fully implemented.

---

## Medium — Track and Fix Opportunistically

### ~~ERR-1: Bare except Exception patterns~~ ✅ FIXED
- ~~All adapters catch `Exception` broadly and either swallow errors or return empty results.~~
- **Status:** Standardized to catch specific exceptions where possible, use `exc_info=True` for logging, and raise to the route layer. MCP tools also updated to handle specific `httpx` and `RequestError` types.

### ~~CONF-1: Hardcoded credentials in defaults~~ ✅ FIXED
- ~~`config.py:17` — `NEO4J_PASSWORD: str = "password"` as default.~~
- ~~`dependencies.py:37` — `postgresql://postgres:password@localhost:5432/clew` as PgVector fallback.~~
- ~~`adapters/neo4j.py:21` — Constructor defaults include `password="password"`.~~
- **Status:** Credential defaults removed. `ValueError` raised if required credentials are missing. Verified via unit tests.

### ~~CONF-2: Embedding dimension (1536) hardcoded in 4 places~~ ✅ FIXED
- ~~`adapters/embeddings.py:28,89,99` and `adapters/pgvector.py:47`.~~
- **Status:** Centralized as `EMBEDDING_DIMENSION` in config.py. All adapters now reference `settings.EMBEDDING_DIMENSION` instead of separate constants.

### ~~CONF-3: Hardcoded timeouts not configurable~~ ✅ FIXED
- ~~`adapters/embeddings.py:69` — Ollama timeout `10.0` hardcoded.~~
- ~~`server.py:24` — MCP timeout `30.0` hardcoded.~~
- **Status:** Fixed in PR #32 and subsequent verification. Configurable via `OLLAMA_TIMEOUT` and `CLEW_API_TIMEOUT`.

### ~~TEST-1: No MCP tool tests~~ ✅ FIXED
- ~~`packages/clew-mcp/` has no test directory at all.~~
- **Status:** Comprehensive suite implemented in `tests/test_server.py`. 100% green.

### ~~TEST-2: No adapter-level tests~~ ✅ FIXED
- ~~Qdrant, Neo4j, PgVector, and embedding adapters have no unit tests.~~
- ~~Only tested indirectly via route-level mocks that bypass the adapter code entirely.~~
- **Status:** Comprehensive unit tests for `QdrantAdapter` and `Neo4jAdapter` implemented in PR #33.

### TEST-3: Brittle HTTP mocks in MCP (PARTIAL ✅)
- Current tests rely on nested `AsyncMock` chains for `httpx` context managers. Very sensitive to `spec` and `__aenter__` configuration.
- **Status:** Fixed `clew-mcp/tests/test_server.py` by removing strict `spec` and using `side_effect` with real async functions. However, a move to `respx` or a dedicated test client is still recommended for long-term maintainability.

### ~~QUAL-5: Inconsistent VectorStore nomenclature~~ ✅ FIXED
- ~~Component uses `add()` and `flush()` while some tests or documentation refer to `upsert()`.~~
- **Status:** Added `upsert()` method to QdrantStore with proper UUID handling and error propagation. Standardized on industry-standard naming.

### ~~TYPO-1: Typo in MCP docstring~~ ✅ FIXED
- ~~`server.py:332` — `"actsually"` should be `"actually"`.~~
- **Status:** Fixed on 2026-02-08.

---

## Low — Cleanup When Touching These Files

### ~~STYLE-1: Inline imports in multiple files~~ ✅ FIXED
- ~~`routes/search.py:50` — `import random` inside except block.~~ **FIXED 2026-02-08**
- ~~`adapters/noop_graph.py:65` — `import uuid` inside function.~~ **FIXED 2026-02-09**
- ~~`adapters/pgvector.py:156` — `import json` inside method.~~ **FIXED 2026-02-09**
- **Status:** All inline imports moved to top-level. Code follows consistent import organization.

### ~~STYLE-2: `type: ignore` comments on dead code~~ ✅ FIXED
- ~~`adapters/embeddings.py:124,128,132`, `neo4j.py:132`, `qdrant.py:121`, `pgvector.py:199`, `noop_graph.py:87` — Protocol check functions exist only for static analysis, never called at runtime.~~
- **Status:** All protocol check functions moved to `if TYPE_CHECKING:` blocks. Clean runtime behavior with proper type checking support.

### ~~CONF-4: Docker image not pinned~~ ✅ FIXED
- ~~`docker-compose.yml:8` — Uses `qdrant:latest` (not reproducible).~~
- **Status:** Pinned to `qdrant/qdrant:v1.7.4` for reproducible builds.

---

## Process Gaps — Decide and Implement

| Decision | Options | Notes |
|:---------|:--------|:------|
| CI pipeline | GitHub Actions | Run `pytest`, `ruff`, type checking on every PR |
| Test coverage tool | `pytest-cov` with threshold | Target 70% for alpha, 85% for beta |
| Docs generation | MkDocs / Sphinx | Auto-generate from docstrings + api-contract.md |
| Complexity analysis | `lizard` or `radon` | Flag functions >15 CC, >50 lines |
| Dependency scanning | `pip-audit` / Dependabot | Catch known CVEs |
| Splash page | GitHub Pages | Minimal landing for open-source launch |
