crabcode 0.1.6__py3-none-win_amd64.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.
- crabcode-0.1.6.dist-info/METADATA +63 -0
- crabcode-0.1.6.dist-info/RECORD +205 -0
- crabcode-0.1.6.dist-info/WHEEL +4 -0
- crabcode-0.1.6.dist-info/entry_points.txt +2 -0
- crabcode_cli/__init__.py +7 -0
- crabcode_cli/__main__.py +4 -0
- crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
- crabcode_cli/_native/runtime.json +6 -0
- crabcode_cli/app.py +776 -0
- crabcode_cli/banner.py +39 -0
- crabcode_cli/computer_use.py +286 -0
- crabcode_cli/model_manager.py +649 -0
- crabcode_cli/model_picker.py +168 -0
- crabcode_cli/pipe.py +148 -0
- crabcode_cli/render/__init__.py +0 -0
- crabcode_cli/render/messages.py +19 -0
- crabcode_cli/repl.py +4841 -0
- crabcode_cli/session_picker.py +239 -0
- crabcode_core/__init__.py +7 -0
- crabcode_core/_version.py +3 -0
- crabcode_core/_windows_process.py +68 -0
- crabcode_core/agent_manager.py +1731 -0
- crabcode_core/api/__init__.py +20 -0
- crabcode_core/api/anthropic_adapter.py +648 -0
- crabcode_core/api/azure_adapter.py +69 -0
- crabcode_core/api/base.py +233 -0
- crabcode_core/api/codex_adapter.py +1327 -0
- crabcode_core/api/error_logging.py +132 -0
- crabcode_core/api/gemini_adapter.py +26 -0
- crabcode_core/api/model_info.py +257 -0
- crabcode_core/api/network.py +62 -0
- crabcode_core/api/ollama_adapter.py +27 -0
- crabcode_core/api/openai_adapter.py +323 -0
- crabcode_core/api/registry.py +58 -0
- crabcode_core/command_line.py +18 -0
- crabcode_core/compact/__init__.py +17 -0
- crabcode_core/compact/compact.py +514 -0
- crabcode_core/compact/context_tokens.py +165 -0
- crabcode_core/computer_use_mode.py +38 -0
- crabcode_core/computer_use_observation.py +144 -0
- crabcode_core/config/__init__.py +3 -0
- crabcode_core/config/claudemd.py +61 -0
- crabcode_core/config/manager.py +237 -0
- crabcode_core/config/model_catalog.py +256 -0
- crabcode_core/events.py +4896 -0
- crabcode_core/file_lock.py +81 -0
- crabcode_core/filesystem.py +22 -0
- crabcode_core/goal.py +149 -0
- crabcode_core/hooks/__init__.py +5 -0
- crabcode_core/hooks/manager.py +290 -0
- crabcode_core/image_io.py +111 -0
- crabcode_core/io_worker.py +211 -0
- crabcode_core/logging_utils.py +168 -0
- crabcode_core/lsp/__init__.py +6 -0
- crabcode_core/lsp/client.py +786 -0
- crabcode_core/lsp/diagnostics.py +150 -0
- crabcode_core/lsp/manager.py +583 -0
- crabcode_core/mcp/__init__.py +4 -0
- crabcode_core/mcp/client.py +169 -0
- crabcode_core/mcp/config.py +57 -0
- crabcode_core/path_validation.py +34 -0
- crabcode_core/paths.py +24 -0
- crabcode_core/peer/__init__.py +10 -0
- crabcode_core/peer/runtime.py +518 -0
- crabcode_core/permissions/__init__.py +7 -0
- crabcode_core/permissions/ai_reviewer.py +184 -0
- crabcode_core/permissions/manager.py +187 -0
- crabcode_core/plan/__init__.py +5 -0
- crabcode_core/plan/executor.py +270 -0
- crabcode_core/plan/types.py +169 -0
- crabcode_core/prompts/__init__.py +0 -0
- crabcode_core/prompts/blocks.py +10 -0
- crabcode_core/prompts/context.py +229 -0
- crabcode_core/prompts/library.py +92 -0
- crabcode_core/prompts/profile.py +79 -0
- crabcode_core/prompts/system.py +356 -0
- crabcode_core/prompts/templates.py +104 -0
- crabcode_core/query/__init__.py +0 -0
- crabcode_core/query/computer_use_history.py +83 -0
- crabcode_core/query/loop.py +2446 -0
- crabcode_core/query/retry.py +116 -0
- crabcode_core/schedule/__init__.py +13 -0
- crabcode_core/schedule/manager.py +631 -0
- crabcode_core/schedule/models.py +204 -0
- crabcode_core/schedule/store.py +558 -0
- crabcode_core/schedule/timing.py +287 -0
- crabcode_core/session/__init__.py +12 -0
- crabcode_core/session/export.py +141 -0
- crabcode_core/session/meta_db.py +684 -0
- crabcode_core/session/storage.py +2032 -0
- crabcode_core/session/title_gen.py +52 -0
- crabcode_core/skills/__init__.py +6 -0
- crabcode_core/skills/context.py +23 -0
- crabcode_core/skills/loader.py +214 -0
- crabcode_core/skills/matcher.py +203 -0
- crabcode_core/snapshot/__init__.py +21 -0
- crabcode_core/snapshot/snapshot.py +436 -0
- crabcode_core/snapshot/tracker.py +359 -0
- crabcode_core/subprocess_utils.py +260 -0
- crabcode_core/team/__init__.py +27 -0
- crabcode_core/team/bridge.py +66 -0
- crabcode_core/team/inbox.py +237 -0
- crabcode_core/team/manager.py +1660 -0
- crabcode_core/team/message_bus.py +821 -0
- crabcode_core/team/models.py +191 -0
- crabcode_core/team/recovery.py +192 -0
- crabcode_core/text_io.py +52 -0
- crabcode_core/tools/__init__.py +112 -0
- crabcode_core/tools/_input_helpers.py +39 -0
- crabcode_core/tools/_input_schema.py +130 -0
- crabcode_core/tools/agent.py +413 -0
- crabcode_core/tools/apply_patch.py +587 -0
- crabcode_core/tools/ask_user.py +122 -0
- crabcode_core/tools/bash.py +154 -0
- crabcode_core/tools/browser.py +678 -0
- crabcode_core/tools/checklist.py +241 -0
- crabcode_core/tools/checkpoint.py +78 -0
- crabcode_core/tools/computer_use.py +599 -0
- crabcode_core/tools/diff_utils.py +123 -0
- crabcode_core/tools/file_edit.py +240 -0
- crabcode_core/tools/file_read.py +187 -0
- crabcode_core/tools/file_write.py +154 -0
- crabcode_core/tools/glob.py +123 -0
- crabcode_core/tools/goal.py +134 -0
- crabcode_core/tools/grep.py +247 -0
- crabcode_core/tools/image.py +173 -0
- crabcode_core/tools/image_generate.py +115 -0
- crabcode_core/tools/lint.py +388 -0
- crabcode_core/tools/loading.py +187 -0
- crabcode_core/tools/memory.py +456 -0
- crabcode_core/tools/monitor.py +677 -0
- crabcode_core/tools/peer.py +103 -0
- crabcode_core/tools/revert.py +90 -0
- crabcode_core/tools/schedule.py +683 -0
- crabcode_core/tools/skill.py +108 -0
- crabcode_core/tools/switch_mode.py +213 -0
- crabcode_core/tools/team.py +329 -0
- crabcode_core/tools/tool_search.py +64 -0
- crabcode_core/tools/web_search.py +444 -0
- crabcode_core/turn_details.py +64 -0
- crabcode_core/types/__init__.py +95 -0
- crabcode_core/types/config.py +434 -0
- crabcode_core/types/event.py +334 -0
- crabcode_core/types/message.py +263 -0
- crabcode_core/types/tool.py +208 -0
- crabcode_core/usage.py +289 -0
- crabcode_core/utf8_sanitize.py +29 -0
- crabcode_debugger/__init__.py +5 -0
- crabcode_debugger/adapters.py +332 -0
- crabcode_debugger/dap.py +304 -0
- crabcode_debugger/memory.py +1806 -0
- crabcode_debugger/process.py +707 -0
- crabcode_debugger/runtime.py +58 -0
- crabcode_debugger/sessions.py +387 -0
- crabcode_debugger/tool.py +731 -0
- crabcode_gateway/__init__.py +18 -0
- crabcode_gateway/acp/__init__.py +13 -0
- crabcode_gateway/acp/agent.py +1581 -0
- crabcode_gateway/acp/session.py +114 -0
- crabcode_gateway/acp/transport.py +39 -0
- crabcode_gateway/acp/types.py +90 -0
- crabcode_gateway/adapter.py +30 -0
- crabcode_gateway/auth.py +139 -0
- crabcode_gateway/computer_use.py +364 -0
- crabcode_gateway/document_engine.py +637 -0
- crabcode_gateway/document_worker.py +301 -0
- crabcode_gateway/event_bus.py +655 -0
- crabcode_gateway/grpc/__init__.py +0 -0
- crabcode_gateway/grpc/crabcode_pb2.py +70 -0
- crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
- crabcode_gateway/grpc/proto/crabcode.proto +160 -0
- crabcode_gateway/grpc/server.py +765 -0
- crabcode_gateway/middleware.py +344 -0
- crabcode_gateway/protocol.py +17 -0
- crabcode_gateway/routes/__init__.py +0 -0
- crabcode_gateway/routes/agent.py +392 -0
- crabcode_gateway/routes/auth.py +150 -0
- crabcode_gateway/routes/computer_use.py +101 -0
- crabcode_gateway/routes/config.py +1698 -0
- crabcode_gateway/routes/document.py +1810 -0
- crabcode_gateway/routes/event.py +3801 -0
- crabcode_gateway/routes/git_review.py +232 -0
- crabcode_gateway/routes/health.py +14 -0
- crabcode_gateway/routes/peer.py +78 -0
- crabcode_gateway/routes/permission.py +121 -0
- crabcode_gateway/routes/schedule.py +300 -0
- crabcode_gateway/routes/session.py +1496 -0
- crabcode_gateway/routes/snapshot.py +211 -0
- crabcode_gateway/routes/tasks.py +318 -0
- crabcode_gateway/routes/team.py +392 -0
- crabcode_gateway/routes/usage.py +49 -0
- crabcode_gateway/routes/workspace.py +299 -0
- crabcode_gateway/safe_file.py +63 -0
- crabcode_gateway/schemas.py +1820 -0
- crabcode_gateway/server.py +514 -0
- crabcode_gateway/session_registry.py +36 -0
- crabcode_gateway/task_registry.py +708 -0
- crabcode_gateway/turn_details.py +60 -0
- crabcode_search/__init__.py +11 -0
- crabcode_search/background.py +368 -0
- crabcode_search/chunker.py +517 -0
- crabcode_search/embedder.py +497 -0
- crabcode_search/indexer.py +215 -0
- crabcode_search/store.py +222 -0
- crabcode_search/tool.py +268 -0
crabcode_search/tool.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""CodebaseSearchTool — semantic code search as a CrabCode extra tool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
# Must be set before ANY native library (FAISS / PyTorch) loads libomp.
|
|
10
|
+
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "TRUE")
|
|
11
|
+
os.environ.setdefault("OMP_NUM_THREADS", "1")
|
|
12
|
+
os.environ.setdefault("MKL_NUM_THREADS", "1")
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from crabcode_core.tools._input_helpers import (
|
|
18
|
+
first_non_empty_str,
|
|
19
|
+
latest_user_text_for_agent_fallback,
|
|
20
|
+
)
|
|
21
|
+
from crabcode_core.types.tool import Tool, ToolContext, ToolResult
|
|
22
|
+
|
|
23
|
+
from crabcode_search.background import STATUS_FILE_NAME
|
|
24
|
+
from crabcode_search.embedder import create_embedder
|
|
25
|
+
from crabcode_search.indexer import CodebaseIndexer
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CodebaseSearchTool(Tool):
|
|
29
|
+
"""Semantic codebase search powered by embeddings and FAISS."""
|
|
30
|
+
|
|
31
|
+
name = "CodebaseSearch"
|
|
32
|
+
description = "Search the codebase using natural language queries. Returns semantically relevant code snippets with file paths and line numbers. Use this when you need to find code by meaning rather than exact text. For exact text/regex matching, use Grep instead."
|
|
33
|
+
is_read_only = True
|
|
34
|
+
is_concurrency_safe = True
|
|
35
|
+
input_schema = {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"query": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1,
|
|
41
|
+
"description": "Natural language search query about the codebase.",
|
|
42
|
+
},
|
|
43
|
+
"target_directory": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Optional subdirectory to limit search scope.",
|
|
46
|
+
},
|
|
47
|
+
"num_results": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"description": "Number of results to return (default 10).",
|
|
50
|
+
"default": 10,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
"required": ["query"],
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def __init__(self) -> None:
|
|
57
|
+
self._indexer: CodebaseIndexer | None = None
|
|
58
|
+
self._tool_config: dict[str, Any] = {}
|
|
59
|
+
self._init_lock = asyncio.Lock()
|
|
60
|
+
self._status_path: Path | None = None
|
|
61
|
+
|
|
62
|
+
async def setup(self, context: ToolContext) -> None:
|
|
63
|
+
await super().setup(context)
|
|
64
|
+
self._tool_config = context.tool_config
|
|
65
|
+
self._status_path = Path(context.cwd) / ".crabcode" / "search" / STATUS_FILE_NAME
|
|
66
|
+
|
|
67
|
+
status = self._read_background_status()
|
|
68
|
+
if status:
|
|
69
|
+
state = status.get("state")
|
|
70
|
+
if state == "ready":
|
|
71
|
+
chunks = status.get("chunks")
|
|
72
|
+
message = "Background index ready"
|
|
73
|
+
if chunks is not None:
|
|
74
|
+
message += f" ({chunks} chunks)"
|
|
75
|
+
self.emit_event("ready", {"message": message})
|
|
76
|
+
return
|
|
77
|
+
if state in {"starting", "preloading", "loading_index", "indexing"}:
|
|
78
|
+
done = status.get("done")
|
|
79
|
+
total = status.get("total")
|
|
80
|
+
percent = None
|
|
81
|
+
if isinstance(done, int) and isinstance(total, int) and total > 0:
|
|
82
|
+
percent = done / total
|
|
83
|
+
self.emit_event(
|
|
84
|
+
"progress",
|
|
85
|
+
{
|
|
86
|
+
"message": f"Background {state}...",
|
|
87
|
+
"percent": percent,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
await self._ensure_initialized()
|
|
93
|
+
|
|
94
|
+
async def _ensure_initialized(self) -> None:
|
|
95
|
+
if self._indexer is not None:
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
async with self._init_lock:
|
|
99
|
+
if self._indexer is not None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
context = self._setup_context
|
|
103
|
+
if context is None:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
config = self._tool_config
|
|
107
|
+
embedder = create_embedder(
|
|
108
|
+
backend=config.get("embedder", "ollama"),
|
|
109
|
+
model=config.get("model"),
|
|
110
|
+
dimension=config.get("dimension"),
|
|
111
|
+
batch_size=config.get("batch_size"),
|
|
112
|
+
device=config.get("device"),
|
|
113
|
+
)
|
|
114
|
+
await embedder.preload()
|
|
115
|
+
self._indexer = CodebaseIndexer(context.cwd, embedder=embedder)
|
|
116
|
+
|
|
117
|
+
threads = config.get("threads")
|
|
118
|
+
if threads is not None:
|
|
119
|
+
threads = int(threads)
|
|
120
|
+
os.environ["OMP_NUM_THREADS"] = str(threads)
|
|
121
|
+
os.environ["MKL_NUM_THREADS"] = str(threads)
|
|
122
|
+
try:
|
|
123
|
+
import torch
|
|
124
|
+
torch.set_num_threads(threads)
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
all_files = self._indexer._scan_files()
|
|
129
|
+
self._indexer.total_files = len(all_files)
|
|
130
|
+
if self._indexer.store.count > 0 and not self._indexer._detect_changes(
|
|
131
|
+
all_files
|
|
132
|
+
):
|
|
133
|
+
self.emit_event("ready", {
|
|
134
|
+
"message": f"Index loaded ({self._indexer.store.count} chunks)",
|
|
135
|
+
})
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
self.emit_event("progress", {"message": "Starting indexing...", "percent": 0})
|
|
139
|
+
self._background_task = asyncio.create_task(self._build_index())
|
|
140
|
+
|
|
141
|
+
def _read_background_status(self) -> dict[str, Any] | None:
|
|
142
|
+
if self._status_path is None or not self._status_path.exists():
|
|
143
|
+
return None
|
|
144
|
+
try:
|
|
145
|
+
data = json.loads(self._status_path.read_text(encoding="utf-8"))
|
|
146
|
+
except (OSError, json.JSONDecodeError):
|
|
147
|
+
return None
|
|
148
|
+
return data if isinstance(data, dict) else None
|
|
149
|
+
|
|
150
|
+
def _normalize_target_directory(self, value: Any, cwd: str) -> str | None:
|
|
151
|
+
if not isinstance(value, str) or not value.strip():
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
raw = Path(value.strip()).expanduser()
|
|
155
|
+
try:
|
|
156
|
+
if raw.is_absolute():
|
|
157
|
+
rel = raw.resolve().relative_to(Path(cwd).resolve())
|
|
158
|
+
else:
|
|
159
|
+
rel = raw
|
|
160
|
+
except (OSError, ValueError):
|
|
161
|
+
return value.strip().replace("\\\\", "/").strip("/") or None
|
|
162
|
+
|
|
163
|
+
normalized = rel.as_posix().strip("/")
|
|
164
|
+
return normalized or None
|
|
165
|
+
|
|
166
|
+
async def _build_index(self) -> None:
|
|
167
|
+
assert self._indexer is not None
|
|
168
|
+
async for progress in self._indexer.build_or_update():
|
|
169
|
+
if progress.total > 0:
|
|
170
|
+
pct = progress.done / progress.total
|
|
171
|
+
else:
|
|
172
|
+
pct = 1.0
|
|
173
|
+
self.emit_event("progress", {
|
|
174
|
+
"message": f"Indexing: {progress.done}/{progress.total} files",
|
|
175
|
+
"percent": pct,
|
|
176
|
+
})
|
|
177
|
+
self.emit_event("ready", {
|
|
178
|
+
"message": f"Index ready ({self._indexer.store.count} chunks, {self._indexer.total_files} files)",
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
async def get_prompt(self, **kwargs: Any) -> str:
|
|
182
|
+
return (
|
|
183
|
+
"Search the codebase using natural language queries. "
|
|
184
|
+
"Returns semantically relevant code snippets with file paths and line numbers. "
|
|
185
|
+
"Use this when you need to find code by meaning rather than exact text. "
|
|
186
|
+
"For example: 'where is authentication handled', 'how does the build system work', "
|
|
187
|
+
"'find the payment processing logic'. "
|
|
188
|
+
"For exact text/regex matching, use Grep instead. "
|
|
189
|
+
"For finding files by name, use Glob instead."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
async def call(
|
|
193
|
+
self,
|
|
194
|
+
tool_input: dict[str, Any],
|
|
195
|
+
context: ToolContext,
|
|
196
|
+
) -> ToolResult:
|
|
197
|
+
if self._indexer is None:
|
|
198
|
+
await self._ensure_initialized()
|
|
199
|
+
if self._indexer is None:
|
|
200
|
+
return ToolResult(
|
|
201
|
+
result_for_model="CodebaseSearch is not initialized. Use Grep or Glob instead.",
|
|
202
|
+
is_error=True,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
query = first_non_empty_str(
|
|
206
|
+
tool_input,
|
|
207
|
+
("query", "search", "q", "question", "prompt", "text", "natural_language_query"),
|
|
208
|
+
)
|
|
209
|
+
if not query:
|
|
210
|
+
query = latest_user_text_for_agent_fallback(context.messages)
|
|
211
|
+
if not query:
|
|
212
|
+
return ToolResult(
|
|
213
|
+
result_for_model=(
|
|
214
|
+
"query is required. Pass a non-empty search string (field \"query\"); "
|
|
215
|
+
"aliases: search, q, question, prompt, text, natural_language_query."
|
|
216
|
+
),
|
|
217
|
+
is_error=True,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
num_results = tool_input.get("num_results", 10)
|
|
221
|
+
target_dir = self._normalize_target_directory(tool_input.get("target_directory"), context.cwd)
|
|
222
|
+
|
|
223
|
+
building = self._background_task and not self._background_task.done()
|
|
224
|
+
|
|
225
|
+
if not building:
|
|
226
|
+
await self._indexer.incremental_update()
|
|
227
|
+
|
|
228
|
+
query_vec = await self._indexer.embedder.embed([query], task_type="CODE_RETRIEVAL_QUERY")
|
|
229
|
+
results = await self._indexer.store.search(query_vec[0], top_k=num_results * 2)
|
|
230
|
+
|
|
231
|
+
if target_dir:
|
|
232
|
+
prefix = os.path.normcase(target_dir.replace("\\", "/")).replace("\\", "/").rstrip("/") + "/"
|
|
233
|
+
results = [
|
|
234
|
+
r for r in results
|
|
235
|
+
if os.path.normcase(r.chunk.file_path.replace("\\", "/")).replace("\\", "/").startswith(prefix)
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
results = results[:num_results]
|
|
239
|
+
|
|
240
|
+
if not results:
|
|
241
|
+
msg = "No results found."
|
|
242
|
+
if building:
|
|
243
|
+
progress = self._indexer.progress
|
|
244
|
+
msg += (
|
|
245
|
+
f"\n\n[Note: Index still building ({progress.done}/{progress.total} files). "
|
|
246
|
+
"Results may be incomplete. Use Grep for precise searches.]"
|
|
247
|
+
)
|
|
248
|
+
return ToolResult(result_for_model=msg)
|
|
249
|
+
|
|
250
|
+
output_parts: list[str] = []
|
|
251
|
+
for i, r in enumerate(results, 1):
|
|
252
|
+
output_parts.append(
|
|
253
|
+
f"[{i}] {r.chunk.file_path}:{r.chunk.start_line}-{r.chunk.end_line} "
|
|
254
|
+
f"(score: {r.score:.3f})\n"
|
|
255
|
+
f" {r.chunk.signature}\n"
|
|
256
|
+
f"```\n{r.chunk.content}\n```"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
output = "\n\n".join(output_parts)
|
|
260
|
+
|
|
261
|
+
if building:
|
|
262
|
+
progress = self._indexer.progress
|
|
263
|
+
output += (
|
|
264
|
+
f"\n\n[Note: Index still building ({progress.done}/{progress.total} files). "
|
|
265
|
+
"Results may be incomplete. Use Grep for precise searches.]"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return ToolResult(result_for_model=output)
|