claude-code-workflow 6.2.7 → 6.3.0
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.
- package/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -79,36 +79,38 @@ def get_cache_dir() -> Path:
|
|
|
79
79
|
"""Get fastembed cache directory.
|
|
80
80
|
|
|
81
81
|
Returns:
|
|
82
|
-
Path to cache directory (
|
|
82
|
+
Path to cache directory (~/.cache/huggingface or custom path)
|
|
83
83
|
"""
|
|
84
84
|
# Check HF_HOME environment variable first
|
|
85
85
|
if "HF_HOME" in os.environ:
|
|
86
86
|
return Path(os.environ["HF_HOME"])
|
|
87
87
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
else: # Unix-like
|
|
92
|
-
cache_dir = Path.home() / ".cache" / "fastembed"
|
|
93
|
-
|
|
94
|
-
return cache_dir
|
|
88
|
+
# fastembed 0.7.4+ uses HuggingFace cache when cache_dir is specified
|
|
89
|
+
# Models are stored directly under the cache directory
|
|
90
|
+
return Path.home() / ".cache" / "huggingface"
|
|
95
91
|
|
|
96
92
|
|
|
97
93
|
def _get_model_cache_path(cache_dir: Path, info: Dict) -> Path:
|
|
98
94
|
"""Get the actual cache path for a model.
|
|
99
95
|
|
|
100
|
-
fastembed uses
|
|
101
|
-
|
|
96
|
+
fastembed 0.7.4+ uses HuggingFace Hub's naming convention:
|
|
97
|
+
- Prefix: 'models--'
|
|
98
|
+
- Replace '/' with '--' in model name
|
|
99
|
+
Example: jinaai/jina-embeddings-v2-base-code
|
|
100
|
+
-> models--jinaai--jina-embeddings-v2-base-code
|
|
102
101
|
|
|
103
102
|
Args:
|
|
104
|
-
cache_dir: The fastembed cache directory
|
|
103
|
+
cache_dir: The fastembed cache directory (HuggingFace hub path)
|
|
105
104
|
info: Model profile info dictionary
|
|
106
105
|
|
|
107
106
|
Returns:
|
|
108
107
|
Path to the model cache directory
|
|
109
108
|
"""
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
# HuggingFace Hub naming: models--{org}--{model}
|
|
110
|
+
# Use cache_name if available (for mapped ONNX models), else model_name
|
|
111
|
+
target_name = info.get("cache_name", info["model_name"])
|
|
112
|
+
sanitized_name = f"models--{target_name.replace('/', '--')}"
|
|
113
|
+
return cache_dir / sanitized_name
|
|
112
114
|
|
|
113
115
|
|
|
114
116
|
def list_models() -> Dict[str, any]:
|
|
@@ -194,18 +196,29 @@ def download_model(profile: str, progress_callback: Optional[callable] = None) -
|
|
|
194
196
|
model_name = info["model_name"]
|
|
195
197
|
|
|
196
198
|
try:
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
+
# Get cache directory
|
|
200
|
+
cache_dir = get_cache_dir()
|
|
201
|
+
|
|
202
|
+
# Download model by instantiating TextEmbedding with explicit cache_dir
|
|
203
|
+
# This ensures fastembed uses the correct HuggingFace Hub cache location
|
|
199
204
|
if progress_callback:
|
|
200
205
|
progress_callback(f"Downloading {model_name}...")
|
|
201
206
|
|
|
202
|
-
|
|
207
|
+
# CRITICAL: Must specify cache_dir to use HuggingFace cache
|
|
208
|
+
# and call embed() to trigger actual download
|
|
209
|
+
embedder = TextEmbedding(model_name=model_name, cache_dir=str(cache_dir))
|
|
210
|
+
|
|
211
|
+
# Trigger actual download by calling embed
|
|
212
|
+
# TextEmbedding.__init__ alone doesn't download files
|
|
213
|
+
if progress_callback:
|
|
214
|
+
progress_callback(f"Initializing {model_name}...")
|
|
215
|
+
|
|
216
|
+
list(embedder.embed(["test"])) # Trigger download
|
|
203
217
|
|
|
204
218
|
if progress_callback:
|
|
205
219
|
progress_callback(f"Model {model_name} downloaded successfully")
|
|
206
220
|
|
|
207
|
-
# Get cache info using correct
|
|
208
|
-
cache_dir = get_cache_dir()
|
|
221
|
+
# Get cache info using correct HuggingFace Hub path
|
|
209
222
|
model_cache_path = _get_model_cache_path(cache_dir, info)
|
|
210
223
|
|
|
211
224
|
cache_size = 0
|
|
@@ -35,12 +35,23 @@ def _to_jsonable(value: Any) -> Any:
|
|
|
35
35
|
return value
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def print_json(*, success: bool, result: Any = None, error: str | None = None) -> None:
|
|
38
|
+
def print_json(*, success: bool, result: Any = None, error: str | None = None, **kwargs: Any) -> None:
|
|
39
|
+
"""Print JSON output with optional additional fields.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
success: Whether the operation succeeded
|
|
43
|
+
result: Result data (used when success=True)
|
|
44
|
+
error: Error message (used when success=False)
|
|
45
|
+
**kwargs: Additional fields to include in the payload (e.g., code, details)
|
|
46
|
+
"""
|
|
39
47
|
payload: dict[str, Any] = {"success": success}
|
|
40
48
|
if success:
|
|
41
49
|
payload["result"] = _to_jsonable(result)
|
|
42
50
|
else:
|
|
43
51
|
payload["error"] = error or "Unknown error"
|
|
52
|
+
# Include additional error details if provided
|
|
53
|
+
for key, value in kwargs.items():
|
|
54
|
+
payload[key] = _to_jsonable(value)
|
|
44
55
|
console.print_json(json.dumps(payload, ensure_ascii=False))
|
|
45
56
|
|
|
46
57
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import os
|
|
6
7
|
from dataclasses import dataclass, field
|
|
7
8
|
from functools import cached_property
|
|
@@ -14,6 +15,9 @@ from .errors import ConfigError
|
|
|
14
15
|
# Workspace-local directory name
|
|
15
16
|
WORKSPACE_DIR_NAME = ".codexlens"
|
|
16
17
|
|
|
18
|
+
# Settings file name
|
|
19
|
+
SETTINGS_FILE_NAME = "settings.json"
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
def _default_global_dir() -> Path:
|
|
19
23
|
"""Get global CodexLens data directory."""
|
|
@@ -89,6 +93,19 @@ class Config:
|
|
|
89
93
|
# Hybrid chunker configuration
|
|
90
94
|
hybrid_max_chunk_size: int = 2000 # Max characters per chunk before LLM refinement
|
|
91
95
|
hybrid_llm_refinement: bool = False # Enable LLM-based semantic boundary refinement
|
|
96
|
+
|
|
97
|
+
# Embedding configuration
|
|
98
|
+
embedding_backend: str = "fastembed" # "fastembed" (local) or "litellm" (API)
|
|
99
|
+
embedding_model: str = "code" # For fastembed: profile (fast/code/multilingual/balanced)
|
|
100
|
+
# For litellm: model name from config (e.g., "qwen3-embedding")
|
|
101
|
+
embedding_use_gpu: bool = True # For fastembed: whether to use GPU acceleration
|
|
102
|
+
|
|
103
|
+
# Multi-endpoint configuration for litellm backend
|
|
104
|
+
embedding_endpoints: List[Dict[str, Any]] = field(default_factory=list)
|
|
105
|
+
# List of endpoint configs: [{"model": "...", "api_key": "...", "api_base": "...", "weight": 1.0}]
|
|
106
|
+
embedding_strategy: str = "latency_aware" # round_robin, latency_aware, weighted_random
|
|
107
|
+
embedding_cooldown: float = 60.0 # Default cooldown seconds for rate-limited endpoints
|
|
108
|
+
|
|
92
109
|
def __post_init__(self) -> None:
|
|
93
110
|
try:
|
|
94
111
|
self.data_dir = self.data_dir.expanduser().resolve()
|
|
@@ -133,6 +150,82 @@ class Config:
|
|
|
133
150
|
"""Get parsing rules for a specific language, falling back to defaults."""
|
|
134
151
|
return {**self.parsing_rules.get("default", {}), **self.parsing_rules.get(language_id, {})}
|
|
135
152
|
|
|
153
|
+
@cached_property
|
|
154
|
+
def settings_path(self) -> Path:
|
|
155
|
+
"""Path to the settings file."""
|
|
156
|
+
return self.data_dir / SETTINGS_FILE_NAME
|
|
157
|
+
|
|
158
|
+
def save_settings(self) -> None:
|
|
159
|
+
"""Save embedding and other settings to file."""
|
|
160
|
+
embedding_config = {
|
|
161
|
+
"backend": self.embedding_backend,
|
|
162
|
+
"model": self.embedding_model,
|
|
163
|
+
"use_gpu": self.embedding_use_gpu,
|
|
164
|
+
}
|
|
165
|
+
# Include multi-endpoint config if present
|
|
166
|
+
if self.embedding_endpoints:
|
|
167
|
+
embedding_config["endpoints"] = self.embedding_endpoints
|
|
168
|
+
embedding_config["strategy"] = self.embedding_strategy
|
|
169
|
+
embedding_config["cooldown"] = self.embedding_cooldown
|
|
170
|
+
|
|
171
|
+
settings = {
|
|
172
|
+
"embedding": embedding_config,
|
|
173
|
+
"llm": {
|
|
174
|
+
"enabled": self.llm_enabled,
|
|
175
|
+
"tool": self.llm_tool,
|
|
176
|
+
"timeout_ms": self.llm_timeout_ms,
|
|
177
|
+
"batch_size": self.llm_batch_size,
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
with open(self.settings_path, "w", encoding="utf-8") as f:
|
|
181
|
+
json.dump(settings, f, indent=2)
|
|
182
|
+
|
|
183
|
+
def load_settings(self) -> None:
|
|
184
|
+
"""Load settings from file if exists."""
|
|
185
|
+
if not self.settings_path.exists():
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
with open(self.settings_path, "r", encoding="utf-8") as f:
|
|
190
|
+
settings = json.load(f)
|
|
191
|
+
|
|
192
|
+
# Load embedding settings
|
|
193
|
+
embedding = settings.get("embedding", {})
|
|
194
|
+
if "backend" in embedding:
|
|
195
|
+
self.embedding_backend = embedding["backend"]
|
|
196
|
+
if "model" in embedding:
|
|
197
|
+
self.embedding_model = embedding["model"]
|
|
198
|
+
if "use_gpu" in embedding:
|
|
199
|
+
self.embedding_use_gpu = embedding["use_gpu"]
|
|
200
|
+
|
|
201
|
+
# Load multi-endpoint configuration
|
|
202
|
+
if "endpoints" in embedding:
|
|
203
|
+
self.embedding_endpoints = embedding["endpoints"]
|
|
204
|
+
if "strategy" in embedding:
|
|
205
|
+
self.embedding_strategy = embedding["strategy"]
|
|
206
|
+
if "cooldown" in embedding:
|
|
207
|
+
self.embedding_cooldown = embedding["cooldown"]
|
|
208
|
+
|
|
209
|
+
# Load LLM settings
|
|
210
|
+
llm = settings.get("llm", {})
|
|
211
|
+
if "enabled" in llm:
|
|
212
|
+
self.llm_enabled = llm["enabled"]
|
|
213
|
+
if "tool" in llm:
|
|
214
|
+
self.llm_tool = llm["tool"]
|
|
215
|
+
if "timeout_ms" in llm:
|
|
216
|
+
self.llm_timeout_ms = llm["timeout_ms"]
|
|
217
|
+
if "batch_size" in llm:
|
|
218
|
+
self.llm_batch_size = llm["batch_size"]
|
|
219
|
+
except Exception:
|
|
220
|
+
pass # Silently ignore errors
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def load(cls) -> "Config":
|
|
224
|
+
"""Load config with settings from file."""
|
|
225
|
+
config = cls()
|
|
226
|
+
config.load_settings()
|
|
227
|
+
return config
|
|
228
|
+
|
|
136
229
|
|
|
137
230
|
@dataclass
|
|
138
231
|
class WorkspaceConfig:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -494,9 +494,13 @@ class ChainSearchEngine:
|
|
|
494
494
|
else:
|
|
495
495
|
# Use fuzzy FTS if enable_fuzzy=True (mode="fuzzy"), otherwise exact FTS
|
|
496
496
|
if enable_fuzzy:
|
|
497
|
-
fts_results = store.search_fts_fuzzy(
|
|
497
|
+
fts_results = store.search_fts_fuzzy(
|
|
498
|
+
query, limit=limit, return_full_content=True
|
|
499
|
+
)
|
|
498
500
|
else:
|
|
499
|
-
fts_results = store.
|
|
501
|
+
fts_results = store.search_fts_exact(
|
|
502
|
+
query, limit=limit, return_full_content=True
|
|
503
|
+
)
|
|
500
504
|
|
|
501
505
|
# Optionally add semantic keyword results
|
|
502
506
|
if include_semantic:
|
|
@@ -27,11 +27,11 @@ class HybridSearchEngine:
|
|
|
27
27
|
default_weights: Default RRF weights for each source
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
# Default RRF weights (
|
|
30
|
+
# Default RRF weights (vector: 60%, exact: 30%, fuzzy: 10%)
|
|
31
31
|
DEFAULT_WEIGHTS = {
|
|
32
|
-
"exact": 0.
|
|
33
|
-
"fuzzy": 0.
|
|
34
|
-
"vector": 0.
|
|
32
|
+
"exact": 0.3,
|
|
33
|
+
"fuzzy": 0.1,
|
|
34
|
+
"vector": 0.6,
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
def __init__(self, weights: Optional[Dict[str, float]] = None):
|
|
@@ -200,7 +200,9 @@ class HybridSearchEngine:
|
|
|
200
200
|
"""
|
|
201
201
|
try:
|
|
202
202
|
with DirIndexStore(index_path) as store:
|
|
203
|
-
return store.search_fts_exact(
|
|
203
|
+
return store.search_fts_exact(
|
|
204
|
+
query, limit=limit, return_full_content=True
|
|
205
|
+
)
|
|
204
206
|
except Exception as exc:
|
|
205
207
|
self.logger.debug("Exact search error: %s", exc)
|
|
206
208
|
return []
|
|
@@ -220,7 +222,9 @@ class HybridSearchEngine:
|
|
|
220
222
|
"""
|
|
221
223
|
try:
|
|
222
224
|
with DirIndexStore(index_path) as store:
|
|
223
|
-
return store.search_fts_fuzzy(
|
|
225
|
+
return store.search_fts_fuzzy(
|
|
226
|
+
query, limit=limit, return_full_content=True
|
|
227
|
+
)
|
|
224
228
|
except Exception as exc:
|
|
225
229
|
self.logger.debug("Fuzzy search error: %s", exc)
|
|
226
230
|
return []
|
|
@@ -260,7 +264,7 @@ class HybridSearchEngine:
|
|
|
260
264
|
return []
|
|
261
265
|
|
|
262
266
|
# Initialize embedder and vector store
|
|
263
|
-
from codexlens.semantic.
|
|
267
|
+
from codexlens.semantic.factory import get_embedder
|
|
264
268
|
from codexlens.semantic.vector_store import VectorStore
|
|
265
269
|
|
|
266
270
|
vector_store = VectorStore(index_path)
|
|
@@ -277,32 +281,51 @@ class HybridSearchEngine:
|
|
|
277
281
|
# Get stored model configuration (preferred) or auto-detect from dimension
|
|
278
282
|
model_config = vector_store.get_model_config()
|
|
279
283
|
if model_config:
|
|
280
|
-
|
|
284
|
+
backend = model_config.get("backend", "fastembed")
|
|
285
|
+
model_name = model_config["model_name"]
|
|
286
|
+
model_profile = model_config["model_profile"]
|
|
281
287
|
self.logger.debug(
|
|
282
|
-
"Using stored model config: %s (%s, %dd)",
|
|
283
|
-
|
|
288
|
+
"Using stored model config: %s backend, %s (%s, %dd)",
|
|
289
|
+
backend, model_profile, model_name, model_config["embedding_dim"]
|
|
284
290
|
)
|
|
291
|
+
|
|
292
|
+
# Get embedder based on backend
|
|
293
|
+
if backend == "litellm":
|
|
294
|
+
embedder = get_embedder(backend="litellm", model=model_name)
|
|
295
|
+
else:
|
|
296
|
+
embedder = get_embedder(backend="fastembed", profile=model_profile)
|
|
285
297
|
else:
|
|
286
298
|
# Fallback: auto-detect from embedding dimension
|
|
287
299
|
detected_dim = vector_store.dimension
|
|
288
300
|
if detected_dim is None:
|
|
289
301
|
self.logger.info("Vector store dimension unknown, using default profile")
|
|
290
|
-
|
|
302
|
+
embedder = get_embedder(backend="fastembed", profile="code")
|
|
291
303
|
elif detected_dim == 384:
|
|
292
|
-
|
|
304
|
+
embedder = get_embedder(backend="fastembed", profile="fast")
|
|
293
305
|
elif detected_dim == 768:
|
|
294
|
-
|
|
306
|
+
embedder = get_embedder(backend="fastembed", profile="code")
|
|
295
307
|
elif detected_dim == 1024:
|
|
296
|
-
|
|
308
|
+
embedder = get_embedder(backend="fastembed", profile="multilingual")
|
|
309
|
+
elif detected_dim == 1536:
|
|
310
|
+
# Likely OpenAI text-embedding-3-small or ada-002
|
|
311
|
+
self.logger.info(
|
|
312
|
+
"Detected 1536-dim embeddings (likely OpenAI), using litellm backend with text-embedding-3-small"
|
|
313
|
+
)
|
|
314
|
+
embedder = get_embedder(backend="litellm", model="text-embedding-3-small")
|
|
315
|
+
elif detected_dim == 3072:
|
|
316
|
+
# Likely OpenAI text-embedding-3-large
|
|
317
|
+
self.logger.info(
|
|
318
|
+
"Detected 3072-dim embeddings (likely OpenAI), using litellm backend with text-embedding-3-large"
|
|
319
|
+
)
|
|
320
|
+
embedder = get_embedder(backend="litellm", model="text-embedding-3-large")
|
|
297
321
|
else:
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
322
|
+
self.logger.debug(
|
|
323
|
+
"Unknown dimension %s, using default fastembed profile 'code'",
|
|
324
|
+
detected_dim
|
|
325
|
+
)
|
|
326
|
+
embedder = get_embedder(backend="fastembed", profile="code")
|
|
327
|
+
|
|
303
328
|
|
|
304
|
-
# Use cached embedder (singleton) for performance
|
|
305
|
-
embedder = get_embedder(profile=profile)
|
|
306
329
|
|
|
307
330
|
# Generate query embedding
|
|
308
331
|
query_embedding = embedder.embed_single(query)
|
|
@@ -25,7 +25,7 @@ def reciprocal_rank_fusion(
|
|
|
25
25
|
results_map: Dictionary mapping source name to list of SearchResult objects
|
|
26
26
|
Sources: 'exact', 'fuzzy', 'vector'
|
|
27
27
|
weights: Dictionary mapping source name to weight (default: equal weights)
|
|
28
|
-
Example: {'exact': 0.
|
|
28
|
+
Example: {'exact': 0.3, 'fuzzy': 0.1, 'vector': 0.6}
|
|
29
29
|
k: Constant to avoid division by zero and control rank influence (default 60)
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
SEMANTIC_AVAILABLE = False
|
|
15
15
|
SEMANTIC_BACKEND: str | None = None
|
|
16
16
|
GPU_AVAILABLE = False
|
|
17
|
+
LITELLM_AVAILABLE = False
|
|
17
18
|
_import_error: str | None = None
|
|
18
19
|
|
|
19
20
|
|
|
@@ -67,10 +68,51 @@ def check_gpu_available() -> tuple[bool, str]:
|
|
|
67
68
|
return False, "GPU support module not available"
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
# Export embedder components
|
|
72
|
+
# BaseEmbedder is always available (abstract base class)
|
|
73
|
+
from .base import BaseEmbedder
|
|
74
|
+
|
|
75
|
+
# Factory function for creating embedders
|
|
76
|
+
from .factory import get_embedder as get_embedder_factory
|
|
77
|
+
|
|
78
|
+
# Optional: LiteLLMEmbedderWrapper (only if ccw-litellm is installed)
|
|
79
|
+
try:
|
|
80
|
+
import ccw_litellm # noqa: F401
|
|
81
|
+
from .litellm_embedder import LiteLLMEmbedderWrapper
|
|
82
|
+
LITELLM_AVAILABLE = True
|
|
83
|
+
except ImportError:
|
|
84
|
+
LiteLLMEmbedderWrapper = None
|
|
85
|
+
LITELLM_AVAILABLE = False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def is_embedding_backend_available(backend: str) -> tuple[bool, str | None]:
|
|
89
|
+
"""Check whether a specific embedding backend can be used.
|
|
90
|
+
|
|
91
|
+
Notes:
|
|
92
|
+
- "fastembed" requires the optional semantic deps (pip install codexlens[semantic]).
|
|
93
|
+
- "litellm" requires ccw-litellm to be installed in the same environment.
|
|
94
|
+
"""
|
|
95
|
+
backend = (backend or "").strip().lower()
|
|
96
|
+
if backend == "fastembed":
|
|
97
|
+
if SEMANTIC_AVAILABLE:
|
|
98
|
+
return True, None
|
|
99
|
+
return False, _import_error or "fastembed not available. Install with: pip install codexlens[semantic]"
|
|
100
|
+
if backend == "litellm":
|
|
101
|
+
if LITELLM_AVAILABLE:
|
|
102
|
+
return True, None
|
|
103
|
+
return False, "ccw-litellm not available. Install with: pip install ccw-litellm"
|
|
104
|
+
return False, f"Invalid embedding backend: {backend}. Must be 'fastembed' or 'litellm'."
|
|
105
|
+
|
|
106
|
+
|
|
70
107
|
__all__ = [
|
|
71
108
|
"SEMANTIC_AVAILABLE",
|
|
72
109
|
"SEMANTIC_BACKEND",
|
|
73
110
|
"GPU_AVAILABLE",
|
|
111
|
+
"LITELLM_AVAILABLE",
|
|
74
112
|
"check_semantic_available",
|
|
113
|
+
"is_embedding_backend_available",
|
|
75
114
|
"check_gpu_available",
|
|
115
|
+
"BaseEmbedder",
|
|
116
|
+
"get_embedder_factory",
|
|
117
|
+
"LiteLLMEmbedderWrapper",
|
|
76
118
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Base class for embedders.
|
|
2
|
+
|
|
3
|
+
Defines the interface that all embedders must implement.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Iterable
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseEmbedder(ABC):
|
|
15
|
+
"""Base class for all embedders.
|
|
16
|
+
|
|
17
|
+
All embedder implementations must inherit from this class and implement
|
|
18
|
+
the abstract methods to ensure a consistent interface.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def embedding_dim(self) -> int:
|
|
24
|
+
"""Return embedding dimensions.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
int: Dimension of the embedding vectors.
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def model_name(self) -> str:
|
|
34
|
+
"""Return model name.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
str: Name or identifier of the underlying model.
|
|
38
|
+
"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def max_tokens(self) -> int:
|
|
43
|
+
"""Return maximum token limit for embeddings.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
int: Maximum number of tokens that can be embedded at once.
|
|
47
|
+
Default is 8192 if not overridden by implementation.
|
|
48
|
+
"""
|
|
49
|
+
return 8192
|
|
50
|
+
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def embed_to_numpy(self, texts: str | Iterable[str]) -> np.ndarray:
|
|
53
|
+
"""Embed texts to numpy array.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
texts: Single text or iterable of texts to embed.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
numpy.ndarray: Array of shape (n_texts, embedding_dim) containing embeddings.
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
@@ -39,7 +39,7 @@ from codexlens.parsers.tokenizer import get_default_tokenizer
|
|
|
39
39
|
class ChunkConfig:
|
|
40
40
|
"""Configuration for chunking strategies."""
|
|
41
41
|
max_chunk_size: int = 1000 # Max characters per chunk
|
|
42
|
-
overlap: int =
|
|
42
|
+
overlap: int = 200 # Overlap for sliding window (increased from 100 for better context)
|
|
43
43
|
strategy: str = "auto" # Chunking strategy: auto, symbol, sliding_window, hybrid
|
|
44
44
|
min_chunk_size: int = 50 # Minimum chunk size
|
|
45
45
|
skip_token_count: bool = False # Skip expensive token counting (use char/4 estimate)
|
|
@@ -80,6 +80,7 @@ class Chunker:
|
|
|
80
80
|
"""Chunk code by extracted symbols (functions, classes).
|
|
81
81
|
|
|
82
82
|
Each symbol becomes one chunk with its full content.
|
|
83
|
+
Large symbols exceeding max_chunk_size are recursively split using sliding window.
|
|
83
84
|
|
|
84
85
|
Args:
|
|
85
86
|
content: Source code content
|
|
@@ -101,27 +102,49 @@ class Chunker:
|
|
|
101
102
|
if len(chunk_content.strip()) < self.config.min_chunk_size:
|
|
102
103
|
continue
|
|
103
104
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
# Check if symbol content exceeds max_chunk_size
|
|
106
|
+
if len(chunk_content) > self.config.max_chunk_size:
|
|
107
|
+
# Create line mapping for correct line number tracking
|
|
108
|
+
line_mapping = list(range(start_line, end_line + 1))
|
|
109
|
+
|
|
110
|
+
# Use sliding window to split large symbol
|
|
111
|
+
sub_chunks = self.chunk_sliding_window(
|
|
112
|
+
chunk_content,
|
|
113
|
+
file_path=file_path,
|
|
114
|
+
language=language,
|
|
115
|
+
line_mapping=line_mapping
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Update sub_chunks with parent symbol metadata
|
|
119
|
+
for sub_chunk in sub_chunks:
|
|
120
|
+
sub_chunk.metadata["symbol_name"] = symbol.name
|
|
121
|
+
sub_chunk.metadata["symbol_kind"] = symbol.kind
|
|
122
|
+
sub_chunk.metadata["strategy"] = "symbol_split"
|
|
123
|
+
sub_chunk.metadata["parent_symbol_range"] = (start_line, end_line)
|
|
124
|
+
|
|
125
|
+
chunks.extend(sub_chunks)
|
|
108
126
|
else:
|
|
109
|
-
|
|
127
|
+
# Calculate token count if not provided
|
|
128
|
+
token_count = None
|
|
129
|
+
if symbol_token_counts and symbol.name in symbol_token_counts:
|
|
130
|
+
token_count = symbol_token_counts[symbol.name]
|
|
131
|
+
else:
|
|
132
|
+
token_count = self._estimate_token_count(chunk_content)
|
|
110
133
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
chunks.append(SemanticChunk(
|
|
135
|
+
content=chunk_content,
|
|
136
|
+
embedding=None,
|
|
137
|
+
metadata={
|
|
138
|
+
"file": str(file_path),
|
|
139
|
+
"language": language,
|
|
140
|
+
"symbol_name": symbol.name,
|
|
141
|
+
"symbol_kind": symbol.kind,
|
|
142
|
+
"start_line": start_line,
|
|
143
|
+
"end_line": end_line,
|
|
144
|
+
"strategy": "symbol",
|
|
145
|
+
"token_count": token_count,
|
|
146
|
+
}
|
|
147
|
+
))
|
|
125
148
|
|
|
126
149
|
return chunks
|
|
127
150
|
|