code-context-engine 0.4.21__py3-none-any.whl → 0.4.22__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-engine
3
- Version: 0.4.21
3
+ Version: 0.4.22
4
4
  Summary: Save 94% on Claude Code tokens. Index your codebase locally, AI agents search instead of reading files. Reduce Claude API costs, save tokens on Cursor, VS Code, Gemini CLI. Free, open source MCP server.
5
5
  Author-email: Fazle Elahee <felahee@gmail.com>, Raj <rajkumar.sakti@gmail.com>
6
6
  License-Expression: MIT
@@ -115,15 +115,17 @@ Dynamic: license-file
115
115
 
116
116
  ---
117
117
 
118
- ## Quick start (3 lines)
118
+ ## Quick start
119
119
 
120
120
  ```bash
121
- uv tool install code-context-engine
121
+ uv tool install "code-context-engine[local]" # or: pipx install "code-context-engine[local]"
122
122
  cd /path/to/your/project
123
- cce init # or: cce init --agent all
123
+ cce init # or: cce init --agent all
124
124
  ```
125
125
 
126
- That's it. Your AI coding agent now searches your index instead of reading entire files. No config needed.
126
+ That's it. Your AI coding agent now searches your index instead of reading entire files.
127
+
128
+ > **Already have Ollama?** You can skip `[local]` and use `uv tool install code-context-engine` instead. CCE auto-detects Ollama at localhost:11434 and uses `nomic-embed-text`.
127
129
 
128
130
  ---
129
131
 
@@ -143,16 +145,18 @@ Tested on all three platforms in CI (macOS, Linux, Windows × Python 3.11/3.12/3
143
145
 
144
146
  ## Install and see savings in 60 seconds
145
147
 
146
- ```bash
147
- uv tool install code-context-engine # or: pipx install code-context-engine
148
- cd /path/to/your/project
149
- cce init # index, install hooks, register MCP server
150
- ```
148
+ You need an embedding backend to index code. Pick one:
151
149
 
152
- **Embedding backends:** CCE auto-detects the best available backend. If you have Ollama running, it uses `nomic-embed-text` with zero extra dependencies. For offline/local embedding without Ollama, install the `[local]` extra:
150
+ | Option | Install command | Size | Requires |
151
+ |--------|----------------|------|----------|
152
+ | **Local (recommended)** | `uv tool install "code-context-engine[local]"` | +60 MB | Nothing else |
153
+ | **Ollama** | `uv tool install code-context-engine` | Core only | Ollama running + `nomic-embed-text` pulled |
154
+
155
+ Then:
153
156
 
154
157
  ```bash
155
- uv tool install "code-context-engine[local]" # includes fastembed + ONNX Runtime
158
+ cd /path/to/your/project
159
+ cce init # index, install hooks, register MCP server
156
160
  ```
157
161
 
158
162
  Restart your editor. Done. Every question now hits the index instead of re-reading files.
@@ -500,16 +504,18 @@ No. Quality stays the same or slightly improves.
500
504
 
501
505
  CCE replaces "dump the entire file" with "search for the relevant function." The model still gets the code it needs (0.90 Recall@10 in benchmarks). Less irrelevant context means less noise competing for attention, which can improve the model's focus on your actual question.
502
506
 
503
- ### How do I increase output token savings?
507
+ ### How does output token savings work?
508
+
509
+ CCE writes output compression rules directly into your agent's instruction files (`CLAUDE.md`, `AGENTS.md`, `.cursorrules`, etc.) during `cce init`. These rules apply to the **entire session**, not just CCE tool responses, so every reply from the agent follows them.
504
510
 
505
- Set the output compression level in your project config (`cce.yaml`):
511
+ Set the level in `cce.yaml`:
506
512
 
507
513
  ```yaml
508
514
  compression:
509
515
  output: max # off | lite | standard | max
510
516
  ```
511
517
 
512
- Or change it at runtime via the MCP tool:
518
+ Then re-run `cce init` to update instruction files. Or change at runtime:
513
519
 
514
520
  ```
515
521
  set_output_level output_level=max
@@ -522,7 +528,7 @@ set_output_level output_level=max
522
528
  | `standard` | ~70% | Drops articles, fragments, short synonyms + diff-only for code |
523
529
  | `max` | ~80% | Telegraphic style + diff-only for code |
524
530
 
525
- Default is `standard`. All levels include **code output rules** that instruct the model to show only changed lines (not full file rewrites), which is where most output tokens go in coding sessions. The `max` level produces very terse prose (similar to "caveman mode"). Code blocks, paths, and commands are never compressed regardless of level.
531
+ Default is `standard`. All levels include **code output rules** that tell the model to show only changed lines (not full file rewrites), which is where most output tokens go in coding sessions. The `max` level produces very terse prose (similar to "caveman mode"). Code blocks, paths, and commands are never compressed regardless of level.
526
532
 
527
533
  ### Where do the savings come from?
528
534
 
@@ -1,9 +1,9 @@
1
- code_context_engine-0.4.21.dist-info/licenses/LICENSE,sha256=vLbw0GGCVJSIRppMus7Oq0PyMDhDXz-dfvz2rPpWtjQ,1069
1
+ code_context_engine-0.4.22.dist-info/licenses/LICENSE,sha256=vLbw0GGCVJSIRppMus7Oq0PyMDhDXz-dfvz2rPpWtjQ,1069
2
2
  context_engine/__init__.py,sha256=qThGxB7xfZi5M9jDpUno0MKBp7KKrEOdH1hG4wHMuLc,193
3
- context_engine/cli.py,sha256=e0lpFLY03-3pymdNm-nLirS3jNZu-rk-jAa-6vb4Hlw,129165
3
+ context_engine/cli.py,sha256=iZbxwA0O4zFD_WRVgPnh1WdhsmZpu6Me-9lJTeT28DE,130226
4
4
  context_engine/cli_style.py,sha256=a3l3Smq1gIN2asbNalFUz0i_5x7Tmkp_wEhyGMoo8a4,2460
5
5
  context_engine/config.py,sha256=UGbVuc8_wTMflzGh80AotMZXZHzzUpLI3QjMnCxTzRo,8370
6
- context_engine/editors.py,sha256=Dicljtj7gPnXJ2wLSMqQzRZwTEq_XtUpRa1xfABOvKk,23411
6
+ context_engine/editors.py,sha256=k9jrqzU5gvYkR5kMu3VcVKHdjxEODZNmxBIEhQUOszE,23986
7
7
  context_engine/event_bus.py,sha256=7Jgw_2YvGQFrnYewXk6T6FJcvRHz0LVEMDgZym9YBCE,760
8
8
  context_engine/models.py,sha256=XBbM0CUqNDQ5MOp6F3STST2qLqy2Zk0m050ZtWdXkrk,2048
9
9
  context_engine/pricing.py,sha256=aT1bsQuZXPlCdTgtwesJLwlKc2tzh8rxL67sZlMbz4E,4684
@@ -14,7 +14,7 @@ context_engine/utils.py,sha256=rytymcEY0tjG4uknJU3DXKz1_ZGjUjJRV3PhkjXoC8A,3192
14
14
  context_engine/compression/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  context_engine/compression/compressor.py,sha256=JlNxZeM6-tXISWVOGiJAcLoixqAxwfEGcYtE0dj8FPw,6680
16
16
  context_engine/compression/ollama_client.py,sha256=MKF1gii2BXMU-wxBRPyMCjo8t72v3dZ06Kv2JNfILgQ,1265
17
- context_engine/compression/output_rules.py,sha256=BK9mOL5o7muM1Ozj800WsltRMHjvkU4UhBy8zsIsDEw,4327
17
+ context_engine/compression/output_rules.py,sha256=kpLZ6r6Ng6PyAvA22wed5ecm8YTxHwwKI57PgsnX6ls,6655
18
18
  context_engine/compression/prompts.py,sha256=jZnpqhr77uI9R3S0vm3Dj17JYy03AXq24E6HQTPXy-A,711
19
19
  context_engine/compression/quality.py,sha256=F6fyxDdWjq-Hgtw4xFIaE4BqPoJw1W1EQSn3RXDgdHc,1676
20
20
  context_engine/dashboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -22,7 +22,7 @@ context_engine/dashboard/_page.py,sha256=2LOz6GxVFHdNyd6iGV-u6sbwCnTrw2p_cVUY-Ly
22
22
  context_engine/dashboard/server.py,sha256=N-QVaDCUL1h70QUgKrIy6QhQIedasf0KYHcV5LACZ0U,17437
23
23
  context_engine/indexer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  context_engine/indexer/chunker.py,sha256=f0n7gJughdHP1fmMd1sbHAxLmVlCnIq6scHOeGFmBS8,6503
25
- context_engine/indexer/embedder.py,sha256=QSrep2Si6RgddikJMyBlO-K2p58yc2VgANKEsv5rf3g,20646
25
+ context_engine/indexer/embedder.py,sha256=xznLoW8A9KfDRZWO2MYzCk6o_Kj5YLIMuQ2J-MIbo3g,22717
26
26
  context_engine/indexer/embedding_cache.py,sha256=yp7zvjjbhDei1tEczdo25GB_a5SJt3XfO4TVGujjSA0,6454
27
27
  context_engine/indexer/git_hooks.py,sha256=GjncsmFu2TZx_3TNQNSBSp15uDwOJ3AtUJxuePQCP24,3258
28
28
  context_engine/indexer/git_indexer.py,sha256=3IbAHYKa-XzpEX4zUfdvU0EHj-qjyn8muK6yPuxy9kw,4154
@@ -38,7 +38,7 @@ context_engine/integration/mcp_server.py,sha256=hIvap8fnpbeAOjJ0oy0GZdgjnUln6b-D
38
38
  context_engine/integration/session_capture.py,sha256=azc0I2PoQQ-0gsmTFy254na_Ez3ADHJ5IdOKU5oFIEU,12440
39
39
  context_engine/memory/__init__.py,sha256=-mzH2HLbjF6mlyzlt0IZoezDPLHBTJmIXFlsn8cjeQA,299
40
40
  context_engine/memory/compressor.py,sha256=TiHxFHRPS3TQxo2_YnnXv8QaQXwxehmH2iwe-azuxpw,15763
41
- context_engine/memory/db.py,sha256=x0NaR5aKcOcqrl-GKCFIW7DPuwQ1pYreqDc0dpg9O14,34579
41
+ context_engine/memory/db.py,sha256=C700MhsdzT8NhpTz_8q-XV4kO6i-Rp4h4GTRoDa8OC4,34936
42
42
  context_engine/memory/decision_extractor.py,sha256=tAFcKVaX5Y1qax71MAR03eq6uyCBIfiEDlbsgiodHUw,3508
43
43
  context_engine/memory/extractive.py,sha256=VJFBG8P6Wku0OaKBQmOr3eTk5XRS2ed3q-TYb432GLc,3227
44
44
  context_engine/memory/grammar.py,sha256=1yrMky1MlmT9m4-_XW3Rq8ZAEE6fBp4miFiWNEcH8ao,16776
@@ -56,9 +56,9 @@ context_engine/storage/fts_store.py,sha256=GzsF-xUPInqovcK72ULgpYAtMAymx4BRrYmps
56
56
  context_engine/storage/graph_store.py,sha256=EAJaDK1OzSabm6HY4h7ZdZcykzlqtdFosNTypW5VNpc,8991
57
57
  context_engine/storage/local_backend.py,sha256=5MVoAn6Jkiltho-9BjClisLkyXMkSZZc2Z_h3N7Vfcg,4200
58
58
  context_engine/storage/remote_backend.py,sha256=6AwEI9YQnmP1w0a7S0ei3YrU2h3z7wbrwv34k7g5YOU,5483
59
- context_engine/storage/vector_store.py,sha256=FOp1fqneIQ4LQQh3f6sfZcn2jswj2SoEazW5BySGBVw,15025
60
- code_context_engine-0.4.21.dist-info/METADATA,sha256=2yz6upwhp6KQ764H6psthsFxcqX0_efZbDs8A2Gv03I,25316
61
- code_context_engine-0.4.21.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
62
- code_context_engine-0.4.21.dist-info/entry_points.txt,sha256=DQuRWUuVFM7nPcXtDmJzlem7QA0IboD_4N8AnTtDD9Q,144
63
- code_context_engine-0.4.21.dist-info/top_level.txt,sha256=X1-RUqb61WXBjy3JjsW2oXwfvqk2ydXKDNidxmw4CZ4,15
64
- code_context_engine-0.4.21.dist-info/RECORD,,
59
+ context_engine/storage/vector_store.py,sha256=GyXSTlcKpByjr2C9JUF_cUCvMbGAc1UVV8Apx5X82kw,15772
60
+ code_context_engine-0.4.22.dist-info/METADATA,sha256=UUastWJFLBpuSBE0fr-bWL857Jp06tyCq_5V1bj00CI,25756
61
+ code_context_engine-0.4.22.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
62
+ code_context_engine-0.4.22.dist-info/entry_points.txt,sha256=DQuRWUuVFM7nPcXtDmJzlem7QA0IboD_4N8AnTtDD9Q,144
63
+ code_context_engine-0.4.22.dist-info/top_level.txt,sha256=X1-RUqb61WXBjy3JjsW2oXwfvqk2ydXKDNidxmw4CZ4,15
64
+ code_context_engine-0.4.22.dist-info/RECORD,,
context_engine/cli.py CHANGED
@@ -182,12 +182,12 @@ _CCE_CLAUDE_MD_MARKER = "## Context Engine (CCE)"
182
182
  # Version stamp embedded as an HTML comment so it doesn't render in the final
183
183
  # Markdown but lets `_ensure_claude_md` detect when the installed block is
184
184
  # stale and needs replacing. Bump whenever _CCE_CLAUDE_MD_BLOCK changes.
185
- _CCE_CLAUDE_MD_VERSION = "3"
185
+ _CCE_CLAUDE_MD_VERSION = "4"
186
186
  _CCE_CLAUDE_MD_VERSION_TAG = f"<!-- cce-block-version: {_CCE_CLAUDE_MD_VERSION} -->"
187
187
  _CCE_CLAUDE_MD_VERSION_PREFIX = "<!-- cce-block-version: "
188
188
  _CCE_CLAUDE_MD_END_MARKER = "<!-- /cce-block -->"
189
189
 
190
- _CCE_CLAUDE_MD_BLOCK = f"""\
190
+ _CCE_CLAUDE_MD_BLOCK_TEMPLATE = f"""\
191
191
  {_CCE_CLAUDE_MD_VERSION_TAG}
192
192
  ## Context Engine (CCE)
193
193
 
@@ -268,18 +268,22 @@ the goal is durable signal, not an event log.
268
268
  Both are read-only and cheap. Prefer them over re-running tool calls or
269
269
  asking the user to re-paste context.
270
270
 
271
- ## Output Style
272
-
273
- Be concise. Lead with the answer or action, not reasoning. Skip filler words,
274
- preamble, and phrases like "I'll help you with that" or "Certainly!". Prefer
275
- fragments over full sentences in explanations. No trailing summaries of what
276
- you just did. One sentence if it fits.
277
-
278
- Code blocks, file paths, commands, and error messages are always written in full.
271
+ {{output_style}}
279
272
  {_CCE_CLAUDE_MD_END_MARKER}
280
273
  """
281
274
 
282
275
 
276
+ def _build_claude_md_block(output_level: str = "standard") -> str:
277
+ """Generate the CLAUDE.md CCE block with the configured output style."""
278
+ from context_engine.compression.output_rules import get_instruction_output_block
279
+ block = get_instruction_output_block(output_level)
280
+ return _CCE_CLAUDE_MD_BLOCK_TEMPLATE.replace("{output_style}", block)
281
+
282
+
283
+ # Default block for backward compat
284
+ _CCE_CLAUDE_MD_BLOCK = _build_claude_md_block("standard")
285
+
286
+
283
287
  def _resolve_cce_cmd() -> str:
284
288
  """Find the globally installed cce binary path."""
285
289
  from context_engine.utils import resolve_cce_binary
@@ -623,6 +627,22 @@ def _preflight_check(config) -> None:
623
627
  one was picked, and surfaces Ollama status for the separate compression
624
628
  path so users know what compression level they will get.
625
629
  """
630
+ # --- SQLite extension support ---
631
+ import sqlite3 as _sqlite3
632
+ _test_conn = _sqlite3.connect(":memory:")
633
+ if not hasattr(_test_conn, "enable_load_extension"):
634
+ _test_conn.close()
635
+ raise click.ClickException(
636
+ "Your Python was compiled without SQLite extension support "
637
+ "(enable_load_extension is missing).\n"
638
+ "This is common with python.org installers on macOS.\n\n"
639
+ "Fix: reinstall CCE under a Python that has extension support:\n\n"
640
+ " brew install python3\n"
641
+ " uv tool install --python /opt/homebrew/bin/python3 "
642
+ "--force code-context-engine\n"
643
+ )
644
+ _test_conn.close()
645
+
626
646
  # --- Embedding backend ---
627
647
  click.echo(_dim(" Detecting embedding backend") + "...", nl=False)
628
648
  from context_engine.config import resolve_ollama_url
@@ -646,13 +666,15 @@ def _preflight_check(config) -> None:
646
666
  fg="green",
647
667
  )
648
668
  )
649
- except Exception as exc:
669
+ except Exception:
650
670
  click.echo("")
651
- _warn(f"No embedding backend available: {exc}")
652
- _warn(
653
- "Install fastembed (`pip install code-context-engine[local]`) "
654
- f"or start an Ollama server at {ollama_url} and pull "
655
- f"{ollama_model}."
671
+ raise click.ClickException(
672
+ "No embedding backend available.\n\n"
673
+ "Fix (pick one):\n"
674
+ " 1. Install local embeddings:\n"
675
+ " uv tool install 'code-context-engine[local]'\n\n"
676
+ f" 2. Start Ollama and pull the embedding model:\n"
677
+ f" ollama pull {ollama_model}\n"
656
678
  )
657
679
 
658
680
  # --- Ollama for LLM compression (independent of the embedding path) ---
@@ -678,7 +700,7 @@ def _preflight_check(config) -> None:
678
700
  click.echo(_dim(" Tip: ollama pull phi3:mini for LLM summarization"))
679
701
 
680
702
 
681
- def _ensure_claude_md(project_dir: Path) -> None:
703
+ def _ensure_claude_md(project_dir: Path, output_level: str = "standard") -> None:
682
704
  """Add or upgrade the CCE instructions block in CLAUDE.md.
683
705
 
684
706
  Three states the file can be in:
@@ -693,9 +715,10 @@ def _ensure_claude_md(project_dir: Path) -> None:
693
715
  """
694
716
  from context_engine.utils import atomic_write_text
695
717
 
718
+ block = _build_claude_md_block(output_level)
696
719
  claude_md = project_dir / "CLAUDE.md"
697
720
  if not claude_md.exists():
698
- atomic_write_text(claude_md, _CCE_CLAUDE_MD_BLOCK)
721
+ atomic_write_text(claude_md, block)
699
722
  _ok("CLAUDE.md created with CCE instructions")
700
723
  return
701
724
 
@@ -710,13 +733,13 @@ def _ensure_claude_md(project_dir: Path) -> None:
710
733
  # survives the upgrade.
711
734
  old_block = _extract_existing_cce_block(existing)
712
735
  if old_block is not None:
713
- new_content = existing.replace(old_block, _CCE_CLAUDE_MD_BLOCK.rstrip(), 1)
736
+ new_content = existing.replace(old_block, block.rstrip(), 1)
714
737
  atomic_write_text(claude_md, new_content)
715
738
  _ok("CLAUDE.md upgraded to current CCE instructions")
716
739
  return
717
740
 
718
741
  # No CCE block detected — append.
719
- new_content = existing.rstrip() + "\n\n" + _CCE_CLAUDE_MD_BLOCK
742
+ new_content = existing.rstrip() + "\n\n" + block
720
743
  atomic_write_text(claude_md, new_content)
721
744
  _ok("CLAUDE.md updated with CCE instructions")
722
745
 
@@ -905,14 +928,15 @@ def init(ctx: click.Context, agent: str) -> None:
905
928
  for file_key, info in INSTRUCTION_FILES.items():
906
929
  if any((project_dir / marker).exists() for marker in info["detect"]):
907
930
  instruction_targets.add(file_key)
931
+ output_level = getattr(config, "output_compression", "standard")
908
932
  for file_key in sorted(instruction_targets):
909
933
  info = INSTRUCTION_FILES[file_key]
910
- if write_instruction_file(project_dir, file_key):
934
+ if write_instruction_file(project_dir, file_key, output_level=output_level):
911
935
  _ok(f"CCE instructions added to {info['name']}")
912
936
 
913
937
  # 5. CLAUDE.md + session hook + memory lifecycle hooks
914
938
  if "claude" in editor_targets:
915
- _ensure_claude_md(project_dir)
939
+ _ensure_claude_md(project_dir, output_level=output_level)
916
940
  _ensure_session_hook(project_dir)
917
941
  _install_memory_hooks(project_dir)
918
942
  _check_memory_capture_reachable(config, project_dir)
@@ -90,3 +90,57 @@ def get_level_description(level: str) -> str:
90
90
  "max": "Telegraphic style with abbreviations and symbols. Diff-only for code. ~80% savings.",
91
91
  }
92
92
  return descriptions.get(level, "Unknown level")
93
+
94
+
95
+ # ── Instruction-file blocks ──────────────────────────────────────────
96
+ # These go into CLAUDE.md, AGENTS.md, .cursorrules, etc. so they apply
97
+ # to the entire session, not just CCE tool responses.
98
+
99
+ _INSTRUCTION_OUTPUT_STYLES = {
100
+ "lite": """\
101
+ ### Output style
102
+
103
+ Respond concisely. Remove filler words (just, really, basically, actually,
104
+ simply), hedging (I think, it seems, perhaps), and pleasantries (Sure!,
105
+ Happy to help, Great question). No trailing summaries. Keep full grammar.
106
+
107
+ When suggesting code changes, show only the changed lines with 3 lines of
108
+ context. Never rewrite entire files. For new files, show the full file.
109
+ For edits, show only what changes.""",
110
+
111
+ "standard": """\
112
+ ### Output style
113
+
114
+ Respond in compressed style. Drop articles (a, an, the) in prose. Use
115
+ sentence fragments over full sentences. Use short synonyms (fix not resolve,
116
+ check not investigate). Pattern: [thing] [action] [reason]. [next step].
117
+ No filler, hedging, pleasantries, trailing summaries, or restating what
118
+ the user said. One sentence if one sentence is enough.
119
+
120
+ When suggesting code changes, show only the changed lines with 3 lines of
121
+ context. Never rewrite entire files. Multiple changes in one file: show each
122
+ change separately. Never echo back unchanged code the user already has.
123
+
124
+ Code blocks, file paths, commands, error messages: always written in full.
125
+ Security warnings and destructive action confirmations: use full clarity.""",
126
+
127
+ "max": """\
128
+ ### Output style
129
+
130
+ Respond in telegraphic style. Drop articles, pronouns, conjunctions where
131
+ meaning survives. Abbreviate common terms: DB, auth, config, fn, dep, impl,
132
+ req, resp, init. Use arrows for causality: X → Y. Use symbols: + (add),
133
+ - (remove), ~ (change), ! (warning). Max 1-2 sentences per explanation.
134
+ Pattern: [thing] → [action]. [reason].
135
+
136
+ When suggesting code changes, show only changed lines. Never rewrite files.
137
+ Never echo back unchanged code.
138
+
139
+ Code blocks, paths, commands, errors: always full.
140
+ Security warnings and destructive actions: full clarity, drop compression.""",
141
+ }
142
+
143
+
144
+ def get_instruction_output_block(level: str) -> str:
145
+ """Return the output style block for instruction files, or empty if off."""
146
+ return _INSTRUCTION_OUTPUT_STYLES.get(level, "")
context_engine/editors.py CHANGED
@@ -92,7 +92,7 @@ EDITORS: dict[str, dict] = {
92
92
  # ── Instruction file definitions ──────────────────────────────────────
93
93
 
94
94
  # Editor-agnostic CCE instructions (no "Claude Code" references)
95
- _CCE_INSTRUCTIONS = """\
95
+ _CCE_INSTRUCTIONS_BASE = """\
96
96
  ## Context Engine (CCE)
97
97
 
98
98
  This project uses Code Context Engine for intelligent code retrieval and
@@ -122,6 +122,19 @@ Call `record_decision(decision="...", reason="...")` after making choices.
122
122
  Call `record_code_area(file_path="...", description="...")` after meaningful work.
123
123
  """
124
124
 
125
+
126
+ def _build_instructions(output_level: str = "standard") -> str:
127
+ """Build CCE instructions with the configured output style."""
128
+ from context_engine.compression.output_rules import get_instruction_output_block
129
+ block = get_instruction_output_block(output_level)
130
+ if block:
131
+ return _CCE_INSTRUCTIONS_BASE + "\n" + block + "\n"
132
+ return _CCE_INSTRUCTIONS_BASE
133
+
134
+
135
+ # Default instructions (standard output compression)
136
+ _CCE_INSTRUCTIONS = _build_instructions("standard")
137
+
125
138
  INSTRUCTION_FILES: dict[str, dict] = {
126
139
  "agents": {
127
140
  "name": "AGENTS.md",
@@ -568,21 +581,24 @@ def _remove_toml(config_path: Path, display_path: str, *, section: str) -> str |
568
581
  return None
569
582
 
570
583
 
571
- def write_instruction_file(project_dir: Path, file_key: str) -> bool:
584
+ def write_instruction_file(
585
+ project_dir: Path, file_key: str, output_level: str = "standard",
586
+ ) -> bool:
572
587
  """Write CCE instructions to an editor's instruction file. Returns True if written."""
573
588
  info = INSTRUCTION_FILES[file_key]
574
589
  path = project_dir / info["path"]
575
590
  marker = "## Context Engine (CCE)"
576
591
  path.parent.mkdir(parents=True, exist_ok=True)
592
+ instructions = _build_instructions(output_level)
577
593
 
578
594
  if path.exists():
579
595
  content = path.read_text()
580
596
  if marker in content:
581
597
  return False # already has CCE block
582
598
  # Append
583
- path.write_text(content.rstrip() + "\n\n" + _CCE_INSTRUCTIONS)
599
+ path.write_text(content.rstrip() + "\n\n" + instructions)
584
600
  else:
585
- path.write_text(_CCE_INSTRUCTIONS)
601
+ path.write_text(instructions)
586
602
  return True
587
603
 
588
604
 
@@ -319,16 +319,66 @@ class OllamaBackend:
319
319
  for _ in resp.iter_lines():
320
320
  pass
321
321
 
322
+ # nomic-embed-text has an 8192-token context. Dense-tokenizing content
323
+ # (YAML with ${{ }}, Python separator comments) can hit ~1 char/token,
324
+ # so 3000 chars is a safe ceiling that works for all content types.
325
+ _MAX_EMBED_CHARS = 3000
326
+
322
327
  def _embed_batch(self, texts: list[str]) -> list[list[float]]:
323
328
  import httpx
324
- resp = httpx.post(
325
- f"{self.base_url}/api/embed",
326
- json={"model": self.model_name, "input": texts},
327
- timeout=self._timeout,
328
- )
329
- resp.raise_for_status()
330
- data = resp.json()
331
- return data.get("embeddings", [])
329
+ # Truncate oversized texts and skip empty ones
330
+ safe_texts = []
331
+ original_indices = []
332
+ for i, t in enumerate(texts):
333
+ if not t or not t.strip():
334
+ continue
335
+ safe_texts.append(t[:self._MAX_EMBED_CHARS])
336
+ original_indices.append(i)
337
+
338
+ if not safe_texts:
339
+ return [[] for _ in texts]
340
+
341
+ try:
342
+ resp = httpx.post(
343
+ f"{self.base_url}/api/embed",
344
+ json={"model": self.model_name, "input": safe_texts},
345
+ timeout=self._timeout,
346
+ )
347
+ resp.raise_for_status()
348
+ embeddings = resp.json().get("embeddings", [])
349
+ except httpx.HTTPStatusError as exc:
350
+ if exc.response.status_code != 400:
351
+ raise
352
+ # Batch failed (possibly one text still too large after truncation).
353
+ # Fall back to one-at-a-time with halving retry.
354
+ log.warning("Ollama batch embed failed, retrying one-at-a-time")
355
+ embeddings = []
356
+ for text in safe_texts:
357
+ vec = self._embed_single_with_retry(text)
358
+ embeddings.append(vec)
359
+
360
+ # Map embeddings back to original positions (empty texts get empty vecs)
361
+ result: list[list[float]] = [[] for _ in texts]
362
+ for idx, emb in zip(original_indices, embeddings):
363
+ result[idx] = emb
364
+ return result
365
+
366
+ def _embed_single_with_retry(self, text: str) -> list[float]:
367
+ """Embed a single text, halving on context-length errors."""
368
+ import httpx
369
+ while text:
370
+ resp = httpx.post(
371
+ f"{self.base_url}/api/embed",
372
+ json={"model": self.model_name, "input": [text]},
373
+ timeout=self._timeout,
374
+ )
375
+ if resp.status_code == 400 and "context length" in resp.text:
376
+ text = text[:len(text) // 2]
377
+ continue
378
+ resp.raise_for_status()
379
+ vecs = resp.json().get("embeddings", [[]])
380
+ return vecs[0] if vecs else []
381
+ return []
332
382
 
333
383
  def embed_texts(self, texts: list[str], batch_size: int = 64) -> list[list[float]]:
334
384
  out: list[list[float]] = []
@@ -281,6 +281,14 @@ def _try_load_vec(conn: sqlite3.Connection) -> bool:
281
281
  sqlite_vec.load(conn)
282
282
  conn.enable_load_extension(False)
283
283
  return True
284
+ except AttributeError:
285
+ log.warning(
286
+ "sqlite-vec load failed; semantic recall disabled. "
287
+ "Python was compiled without SQLite extension support. "
288
+ "Reinstall CCE with Homebrew Python: "
289
+ "uv tool install --python /opt/homebrew/bin/python3 --force code-context-engine"
290
+ )
291
+ return False
284
292
  except Exception as exc:
285
293
  log.warning("sqlite-vec load failed; semantic recall disabled: %s", exc)
286
294
  return False
@@ -46,9 +46,23 @@ class VectorStore:
46
46
  def _connect(self) -> sqlite3.Connection:
47
47
  import sqlite_vec
48
48
  conn = sqlite3.connect(self._db_file, check_same_thread=False)
49
- conn.enable_load_extension(True)
50
- sqlite_vec.load(conn)
51
- conn.enable_load_extension(False)
49
+ try:
50
+ conn.enable_load_extension(True)
51
+ sqlite_vec.load(conn)
52
+ conn.enable_load_extension(False)
53
+ except AttributeError:
54
+ raise RuntimeError(
55
+ "Your Python was compiled without SQLite extension support "
56
+ "(enable_load_extension is missing). This is common with "
57
+ "python.org installers on macOS.\n\n"
58
+ "Fix: reinstall CCE under a Python that has extension support:\n"
59
+ " uv tool install --python $(brew --prefix python3)/bin/python3 "
60
+ "--force code-context-engine\n\n"
61
+ "Or use Homebrew Python directly:\n"
62
+ " brew install python3\n"
63
+ " uv tool install --python /opt/homebrew/bin/python3 "
64
+ "--force code-context-engine"
65
+ ) from None
52
66
  conn.execute("PRAGMA journal_mode=WAL")
53
67
  conn.execute("PRAGMA synchronous=NORMAL")
54
68
  return conn