diff --git a/README.md b/README.md index 3dd33a2..4d182ec 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,9 @@ With CCE: context_search "payment flow" = 800 tokens We benchmarked CCE against [FastAPI](https://github.com/fastapi/fastapi) (53 source files, 180K tokens) with 20 real coding questions. No cherry-picking, no synthetic queries. -**Methodology:** For each query, "without CCE" means reading the full content of every file the query touches. "With CCE" means the relevant chunks after compression. This is conservative (agents often read more files than needed). +**Methodology:** For each query, "without CCE" means reading the full content of every file the query touches. "With CCE" means the relevant chunks after compression. + +**Important baseline note:** The 94% number is measured against full-file reads, not against what Claude Code actually does. In practice, Claude Code already uses grep, partial file reads, and targeted tools, so the real-world savings compared to normal Claude Code behavior will be lower than 94%. We use full-file as the baseline because it's reproducible and deterministic (no agent behavior variability). The benchmark measures CCE's retrieval efficiency, not a head-to-head comparison with Claude Code's built-in exploration. | Metric | Result | |--------|--------| diff --git a/docs/blog/cce-cursor-setup-guide.html b/docs/blog/cce-cursor-setup-guide.html new file mode 100644 index 0000000..154f8c6 --- /dev/null +++ b/docs/blog/cce-cursor-setup-guide.html @@ -0,0 +1,186 @@ + + + + + + How to Use CCE with Cursor: Complete Setup Guide (2026) + + + + + + + + + + + + + +
+
May 7, 2026 · 5 min read
+

How to Use CCE with Cursor

+

Set up Code Context Engine in Cursor. Works alongside built-in indexing. One command, no conflicts.

+ +

Why add CCE to Cursor?

+

Cursor has built-in code indexing. So why add CCE? Three reasons:

+ + +
+

CCE does not replace Cursor's indexing. They run side by side. Cursor's built-in features (tab completion, inline edits, apply) still use its own index. CCE adds MCP-based semantic search on top.

+
+ +

Setup (2 minutes)

+ +
+ 1 +
+

Install CCE

+
uv tool install code-context-engine
+

Or pipx install code-context-engine. Requires Python 3.11+.

+
+
+ +
+ 2 +
+

Initialize in your project

+
cd /path/to/your/project
+cce init
+

CCE auto-detects Cursor and writes .cursor/mcp.json plus .cursorrules with search instructions.

+
+
+ +
+ 3 +
+

Restart Cursor

+

That's it. Cursor now has access to CCE's 9 MCP tools. The AI will use context_search automatically when exploring your code.

+
+
+ +

What gets created

+ + + + + +
FilePurpose
.cursor/mcp.jsonRegisters CCE as an MCP server
.cursorrulesTells Cursor's AI to use context_search instead of reading files
.gitignoreCCE entries added (local cache, settings)
+ +

Verify it works

+
cce search "your feature name"
+

If results come back with token counts, CCE is working. In Cursor, ask the AI any question about your codebase. It should call context_search instead of reading entire files.

+ +

Check your savings

+
cce savings
+

Shows tokens saved, dollar amounts, and per-layer breakdown. Run after a few Cursor sessions to see the impact.

+ +

What CCE gives Cursor's AI

+ + + + + + + +
ToolWhat it does
context_searchSemantic search across your codebase
expand_chunkGet full source for a compressed result
related_contextFind imports, callers, dependencies
session_recallRecall past decisions
record_decisionSave decisions for next session
+ +

CCE vs Cursor indexing: what handles what

+ + + + + + + + + +
FeatureCursor built-inCCE
Tab completionYesNo
Inline editsYesNo
Semantic code searchYesYes (MCP)
Cross-session memoryNoYes
Token savings trackingNoYes
Works in other editorsNoYes (6 editors)
Code stays localNo (cloud)Yes
+ +

Troubleshooting

+ +

Cursor doesn't use context_search

+

Check that .cursor/mcp.json exists and Cursor was restarted after cce init. Cursor needs a restart to pick up new MCP servers.

+ +

Index seems stale

+

Git hooks reindex on every commit. If you haven't committed, run cce index manually. Or run cce watch in a separate terminal for real-time updates.

+ +

Want to remove CCE

+
cce uninstall
+

Removes all CCE files, config, hooks, and index data. Cursor's built-in indexing is unaffected.

+ +
+

Try it now

+

One command. Works alongside Cursor's built-in indexing. No conflicts.

+
uv tool install code-context-engine && cce init
+ View on GitHub +
+ +
+ #Cursor + #CursorAI + #MCPServer + #CodeContextEngine + #SaveTokens + #AICoding + #DeveloperTools + #CursorSetup +
+
+ + + diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 4dd298e..d6fff9a 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -24,6 +24,12 @@ monthly 0.8 + + https://elara-labs.github.io/code-context-engine/blog/cce-cursor-setup-guide.html + 2026-05-07 + monthly + 0.9 + https://elara-labs.github.io/code-context-engine/blog/cce-vs-cursor-indexing.html 2026-05-05 diff --git a/src/context_engine/indexer/embedding_cache.py b/src/context_engine/indexer/embedding_cache.py index b2e52a1..c7bb75a 100644 --- a/src/context_engine/indexer/embedding_cache.py +++ b/src/context_engine/indexer/embedding_cache.py @@ -137,7 +137,8 @@ def prune_orphans(self, known_hashes: set[str]) -> int: for i in range(0, len(orphan_list), 500): batch = orphan_list[i : i + 500] placeholders = ",".join("?" * len(batch)) - self._conn.execute( + # Safe: placeholders is only "?" chars; values are parameterized. + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM embedding_cache WHERE content_hash IN ({placeholders})", batch, ) diff --git a/src/context_engine/memory/db.py b/src/context_engine/memory/db.py index 8278a95..7425797 100644 --- a/src/context_engine/memory/db.py +++ b/src/context_engine/memory/db.py @@ -403,9 +403,10 @@ def _write_vec_row(conn, table: str, rowid: int, vec) -> None: doesn't break inserts on the source table — the failed row simply won't be semantically searchable until the vec tables are rebuilt. """ + # Safe: table name is an internal constant, never from user input. try: - conn.execute(f"DELETE FROM {table} WHERE rowid = ?", (rowid,)) - conn.execute( + conn.execute(f"DELETE FROM {table} WHERE rowid = ?", (rowid,)) # nosemgrep: sqlalchemy-execute-raw-query + conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"INSERT INTO {table}(rowid, embedding) VALUES (?, ?)", (rowid, _serialize_vec(vec)), ) @@ -786,9 +787,10 @@ def prune_old_rows( archived: dict[str, list[dict]] = {} + # Safe: table and col_list are internal constants, never from user input. def _harvest_and_delete(table: str, columns: list[str], cutoff: int) -> int: col_list = ", ".join(columns) - rows = conn.execute( + rows = conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"SELECT {col_list} FROM {table} WHERE created_at_epoch < ?", (cutoff,), ).fetchall() @@ -796,7 +798,7 @@ def _harvest_and_delete(table: str, columns: list[str], cutoff: int) -> int: return 0 if archive: archived[table] = [dict(r) for r in rows] - conn.execute( + conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM {table} WHERE created_at_epoch < ?", (cutoff,), ) diff --git a/src/context_engine/storage/fts_store.py b/src/context_engine/storage/fts_store.py index f87d6a3..2160353 100644 --- a/src/context_engine/storage/fts_store.py +++ b/src/context_engine/storage/fts_store.py @@ -84,7 +84,8 @@ def _delete_files_sync(self, file_paths: list[str]) -> None: with self._lock: for batch in batched_params(file_paths): placeholders = ",".join("?" * len(batch)) - self._conn.execute( + # Safe: placeholders is only "?" chars; values are parameterized. + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM chunks_fts WHERE file_path IN ({placeholders})", batch, ) diff --git a/src/context_engine/storage/graph_store.py b/src/context_engine/storage/graph_store.py index 03693fd..7ba6869 100644 --- a/src/context_engine/storage/graph_store.py +++ b/src/context_engine/storage/graph_store.py @@ -162,22 +162,23 @@ def _sync_delete_by_files(self, file_paths: list[str]) -> None: with self._lock: cur = self._conn.cursor() # Collect node IDs in batches to respect SQLite param limits. + # Safe: ph is only "?" chars; values are parameterized. node_ids: list[str] = [] for batch in batched_params(file_paths): ph = ",".join("?" * len(batch)) - cur.execute( + cur.execute( # nosemgrep: sqlalchemy-execute-raw-query f"SELECT id FROM nodes WHERE file_path IN ({ph})", batch ) node_ids.extend(row[0] for row in cur.fetchall()) # Delete edges and nodes in batches. for batch in batched_params(node_ids): ph = ",".join("?" * len(batch)) - cur.execute( + cur.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM edges WHERE source_id IN ({ph}) " f"OR target_id IN ({ph})", batch + batch, ) - cur.execute(f"DELETE FROM nodes WHERE id IN ({ph})", batch) + cur.execute(f"DELETE FROM nodes WHERE id IN ({ph})", batch) # nosemgrep: sqlalchemy-execute-raw-query self._conn.commit() # ------------------------------------------------------------------ diff --git a/src/context_engine/storage/vector_store.py b/src/context_engine/storage/vector_store.py index c3cedeb..b646dfe 100644 --- a/src/context_engine/storage/vector_store.py +++ b/src/context_engine/storage/vector_store.py @@ -112,10 +112,11 @@ def _ensure_vec_table(self, dim: int) -> None: self._conn.execute("DROP TABLE IF EXISTS chunks_vec") self._conn.execute("DELETE FROM chunks") self._conn.execute("DELETE FROM chunk_compressions") - self._conn.execute(f""" - CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec - USING vec0(embedding float[{dim}]) - """) + # Safe: dim is a validated integer, never from user input. + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query + f"CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec " + f"USING vec0(embedding float[{dim}])" + ) self._dim = dim self._conn.commit() @@ -242,20 +243,21 @@ async def delete_by_files(self, file_paths: list[str]) -> None: from context_engine.utils import batched_params with self._lock: + # Safe: placeholders is only "?" chars; values are parameterized. for batch in batched_params(file_paths): placeholders = ",".join("?" * len(batch)) if self._dim is not None: - self._conn.execute( + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM chunks_vec " f"WHERE rowid IN (SELECT rowid FROM chunks WHERE file_path IN ({placeholders}))", batch, ) - self._conn.execute( + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM chunk_compressions " f"WHERE chunk_id IN (SELECT id FROM chunks WHERE file_path IN ({placeholders}))", batch, ) - self._conn.execute( + self._conn.execute( # nosemgrep: sqlalchemy-execute-raw-query f"DELETE FROM chunks WHERE file_path IN ({placeholders})", batch, )