k-cli-for-devs 1.0.0__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.
- k_cli/__init__.py +77 -0
- k_cli/agents/__init__.py +0 -0
- k_cli/agents/adversarial_swarm.py +338 -0
- k_cli/agents/agent_core.py +255 -0
- k_cli/agents/background_daemon.py +141 -0
- k_cli/agents/orchestrator.py +376 -0
- k_cli/agents/persona.py +649 -0
- k_cli/agents/scaffold_engine.py +121 -0
- k_cli/agents/strands_agent.py +832 -0
- k_cli/agents/subagents.py +1496 -0
- k_cli/cli.py +3297 -0
- k_cli/core/__init__.py +0 -0
- k_cli/core/airgap.py +95 -0
- k_cli/core/credentials.py +548 -0
- k_cli/core/intent_sensor.py +177 -0
- k_cli/core/llm_driver.py +1028 -0
- k_cli/core/model_manager.py +1109 -0
- k_cli/core/models_hub.py +913 -0
- k_cli/core/prompting.py +41 -0
- k_cli/core/sdk.py +322 -0
- k_cli/core/session.py +826 -0
- k_cli/core/smart_router.py +230 -0
- k_cli/core/storage_manager.py +176 -0
- k_cli/core/viewport_engine.py +117 -0
- k_cli/demo/demo_runner.py +579 -0
- k_cli/git/__init__.py +0 -0
- k_cli/git/ai_bisect.py +208 -0
- k_cli/git/conflict_resolver.py +1039 -0
- k_cli/git/git_guard.py +417 -0
- k_cli/git/patcher.py +1175 -0
- k_cli/git/repo_map.py +1780 -0
- k_cli/git/smart_git.py +928 -0
- k_cli/git/verifier.py +969 -0
- k_cli/github/__init__.py +0 -0
- k_cli/github/dedup_engine.py +787 -0
- k_cli/github/github_client.py +1702 -0
- k_cli/github/github_engine.py +641 -0
- k_cli/github/local_hub.py +209 -0
- k_cli/github/pr_watcher.py +129 -0
- k_cli/github/trending.py +205 -0
- k_cli/tools/__init__.py +0 -0
- k_cli/tools/audit.py +79 -0
- k_cli/tools/chaos_immunity.py +377 -0
- k_cli/tools/codebase_qa.py +106 -0
- k_cli/tools/command_runner.py +256 -0
- k_cli/tools/diagram_generator.py +547 -0
- k_cli/tools/doc_retriever.py +1332 -0
- k_cli/tools/feature.py +105 -0
- k_cli/tools/ghost_daemon.py +122 -0
- k_cli/tools/incident_triage.py +1365 -0
- k_cli/tools/mcp_client.py +1846 -0
- k_cli/tools/repo_gardener.py +142 -0
- k_cli/tools/rules.py +109 -0
- k_cli/tools/security.py +52 -0
- k_cli/tools/security_healer.py +999 -0
- k_cli/tools/synapse_graph.py +155 -0
- k_cli/tui/__init__.py +0 -0
- k_cli/tui/diff_viewer.py +223 -0
- k_cli/tui/tui.py +1145 -0
- k_cli/tui/tui_animations.py +648 -0
- k_cli/tui/tui_app.py +2788 -0
- k_cli/ui/__init__.py +10 -0
- k_cli/ui/simple_repl.py +315 -0
- k_cli/web/__init__.py +7 -0
- k_cli/web/server.py +624 -0
- k_cli/web/static/app.js +830 -0
- k_cli/web/static/index.html +495 -0
- k_cli/web/static/monitor.html +189 -0
- k_cli/web/static/style.css +838 -0
- k_cli_for_devs-1.0.0.dist-info/METADATA +461 -0
- k_cli_for_devs-1.0.0.dist-info/RECORD +75 -0
- k_cli_for_devs-1.0.0.dist-info/WHEEL +5 -0
- k_cli_for_devs-1.0.0.dist-info/entry_points.txt +2 -0
- k_cli_for_devs-1.0.0.dist-info/licenses/LICENSE +21 -0
- k_cli_for_devs-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1332 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevDocs SQLite Indexer & Precision Hybrid Retriever for K-CLI (Project Bankai).
|
|
3
|
+
Provides high-speed hybrid retrieval (BM25 lexical matching + semantic cosine similarity),
|
|
4
|
+
direct integration with official developer standard libraries and frameworks (Python 3.12,
|
|
5
|
+
C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL), intelligent query expansion,
|
|
6
|
+
automatic snippet injection into orchestrator contexts, and multi-tier local caching.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import warnings
|
|
12
|
+
warnings.filterwarnings("ignore")
|
|
13
|
+
|
|
14
|
+
from collections import OrderedDict, Counter
|
|
15
|
+
import functools
|
|
16
|
+
import importlib
|
|
17
|
+
import inspect
|
|
18
|
+
import math
|
|
19
|
+
import os
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
import re
|
|
22
|
+
import sqlite3
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import numpy as np
|
|
29
|
+
_HAS_NUMPY = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
_HAS_NUMPY = False
|
|
32
|
+
|
|
33
|
+
DEFAULT_STDLIB_MODULES: List[str] = [
|
|
34
|
+
"builtins",
|
|
35
|
+
"os",
|
|
36
|
+
"os.path",
|
|
37
|
+
"sys",
|
|
38
|
+
"json",
|
|
39
|
+
"math",
|
|
40
|
+
"typing",
|
|
41
|
+
"asyncio",
|
|
42
|
+
"pathlib",
|
|
43
|
+
"re",
|
|
44
|
+
"subprocess",
|
|
45
|
+
"collections",
|
|
46
|
+
"itertools",
|
|
47
|
+
"dataclasses",
|
|
48
|
+
"functools",
|
|
49
|
+
"httpx",
|
|
50
|
+
"requests",
|
|
51
|
+
"pytest",
|
|
52
|
+
"rich",
|
|
53
|
+
"typer",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
DEFAULT_OFFICIAL_LIBRARIES: List[str] = [
|
|
57
|
+
"python",
|
|
58
|
+
"cpp",
|
|
59
|
+
"rust",
|
|
60
|
+
"linux_syscalls",
|
|
61
|
+
"fastapi",
|
|
62
|
+
"redis",
|
|
63
|
+
"postgresql",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
DEFAULT_SYSTEM_DEVDOCS_DB = Path.home() / ".kcli" / "docs.db"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _get_default_cache_db_path() -> Path:
|
|
70
|
+
base = os.environ.get("K_CLI_CACHE_DIR")
|
|
71
|
+
if base:
|
|
72
|
+
p = Path(base) / "devdocs.db"
|
|
73
|
+
else:
|
|
74
|
+
p = Path.home() / ".cache" / "k_cli" / "devdocs.db"
|
|
75
|
+
return p
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
# Official Curated Developer Standard Libraries & Frameworks
|
|
80
|
+
# (Python 3.12, C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL)
|
|
81
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
OFFICIAL_DEV_DOCS: Dict[str, List[Dict[str, str]]] = {
|
|
84
|
+
"python": [
|
|
85
|
+
{
|
|
86
|
+
"name": "asyncio.run",
|
|
87
|
+
"signature": "asyncio.run(main, *, debug=None)",
|
|
88
|
+
"doc": "Execute the coroutine main and return the result. Manages the asyncio event loop and finalizes async generators.",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "asyncio.create_task",
|
|
92
|
+
"signature": "asyncio.create_task(coro, *, name=None, context=None) -> Task",
|
|
93
|
+
"doc": "Wrap a coroutine into a Task and schedule its execution concurrently on the event loop.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "asyncio.gather",
|
|
97
|
+
"signature": "asyncio.gather(*coros_or_futures, return_exceptions=False) -> List[Any]",
|
|
98
|
+
"doc": "Run awaitable objects in the aws sequence concurrently. If return_exceptions is True, exceptions are returned as list items.",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "asyncio.Queue",
|
|
102
|
+
"signature": "class asyncio.Queue(maxsize=0)",
|
|
103
|
+
"doc": "A FIFO queue for coordinating producer and consumer coroutines in asyncio applications.",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "asyncio.TaskGroup",
|
|
107
|
+
"signature": "class asyncio.TaskGroup()",
|
|
108
|
+
"doc": "An asynchronous context manager holding a group of tasks. All tasks are awaited on context exit (Python 3.11+).",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "typing.Annotated",
|
|
112
|
+
"signature": "typing.Annotated[T, *metadata]",
|
|
113
|
+
"doc": "Type decorator adding context-specific metadata to a type for runtime introspection or validation tools.",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "typing.TypeVar",
|
|
117
|
+
"signature": "typing.TypeVar(name, *constraints, bound=None, covariant=False, contravariant=False)",
|
|
118
|
+
"doc": "Declare generic type variables for generic functions, classes, and container protocols.",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "typing.Self",
|
|
122
|
+
"signature": "typing.Self",
|
|
123
|
+
"doc": "Special type annotation representing the current enclosing class instance type (Python 3.11+).",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "dataclasses.dataclass",
|
|
127
|
+
"signature": "@dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, slots=False)",
|
|
128
|
+
"doc": "Decorator to automatically generate special methods like __init__(), __repr__(), and __eq__() for classes.",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "dataclasses.field",
|
|
132
|
+
"signature": "dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, compare=True)",
|
|
133
|
+
"doc": "Provide additional per-field customization and metadata for dataclass attributes.",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "pathlib.Path.exists",
|
|
137
|
+
"signature": "Path.exists(follow_symlinks=True) -> bool",
|
|
138
|
+
"doc": "Return True if the path points to an existing file, directory, or symlink target.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "pathlib.Path.read_text",
|
|
142
|
+
"signature": "Path.read_text(encoding=None, errors=None) -> str",
|
|
143
|
+
"doc": "Open the file pointed to, read its contents as a decoded string, and close it.",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "pathlib.Path.write_text",
|
|
147
|
+
"signature": "Path.write_text(data, encoding=None, errors=None, newline=None) -> int",
|
|
148
|
+
"doc": "Open the file in text mode, write the string data to it, and close the file.",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "functools.lru_cache",
|
|
152
|
+
"signature": "@functools.lru_cache(maxsize=128, typed=False)",
|
|
153
|
+
"doc": "Decorator to wrap a function with a memoizing callable that saves up to maxsize recent results.",
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
"cpp": [
|
|
157
|
+
{
|
|
158
|
+
"name": "std::vector",
|
|
159
|
+
"signature": "template<class T, class Allocator = std::allocator<T>> class std::vector;",
|
|
160
|
+
"doc": "Sequence container encapsulating dynamic size contiguous arrays. Supports amortized O(1) push_back.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "std::vector::push_back",
|
|
164
|
+
"signature": "constexpr void push_back(const T& value); constexpr void push_back(T&& value);",
|
|
165
|
+
"doc": "Appends the given element value to the end of the container, reallocating storage if size exceeds capacity.",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "std::vector::emplace_back",
|
|
169
|
+
"signature": "template<class... Args> constexpr reference emplace_back(Args&&... args);",
|
|
170
|
+
"doc": "Constructs a new element in-place at the end of the vector, passing forwarded constructor arguments.",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "std::ranges::views::filter",
|
|
174
|
+
"signature": "std::views::filter(Range&& r, Predicate pred)",
|
|
175
|
+
"doc": "Range adaptor that yields a view of elements matching the unary predicate (C++20/C++23).",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "std::ranges::views::transform",
|
|
179
|
+
"signature": "std::views::transform(Range&& r, Function f)",
|
|
180
|
+
"doc": "Range adaptor that yields a view of elements transformed through the mapping function (C++20/C++23).",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "std::span",
|
|
184
|
+
"signature": "template<class T, std::size_t Extent = std::dynamic_extent> class std::span;",
|
|
185
|
+
"doc": "Non-owning view over a contiguous sequence of objects (pointer + size) with zero overhead (C++20).",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "std::format",
|
|
189
|
+
"signature": "template<class... Args> std::string std::format(std::format_string<Args...> fmt, Args&&... args);",
|
|
190
|
+
"doc": "Type-safe, high-performance string formatting following Python-style format strings (C++20/C++23).",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "std::print",
|
|
194
|
+
"signature": "template<class... Args> void std::print(std::format_string<Args...> fmt, Args&&... args);",
|
|
195
|
+
"doc": "Directly formats and writes text to stdout without overhead of iostreams formatting (C++23).",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "std::expected",
|
|
199
|
+
"signature": "template<class T, class E> class std::expected;",
|
|
200
|
+
"doc": "Vocabulary type for error handling containing either an expected value of type T or an unexpected error of type E (C++23).",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "std::expected::has_value",
|
|
204
|
+
"signature": "constexpr bool has_value() const noexcept;",
|
|
205
|
+
"doc": "Returns true if the expected object contains a valid value, false if it contains an unexpected error.",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "std::jthread",
|
|
209
|
+
"signature": "class std::jthread;",
|
|
210
|
+
"doc": "Thread of execution with auto-joining destructor and cooperative cancellation support via std::stop_token (C++20).",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
"rust": [
|
|
214
|
+
{
|
|
215
|
+
"name": "std::sync::Arc",
|
|
216
|
+
"signature": "pub struct Arc<T: ?Sized> { /* fields */ }",
|
|
217
|
+
"doc": "Thread-safe reference-counting pointer. Provides shared ownership of an immutable value across threads.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "std::sync::Arc::clone",
|
|
221
|
+
"signature": "pub fn clone(this: &Arc<T>) -> Arc<T>",
|
|
222
|
+
"doc": "Makes a clone of the Arc pointer, incrementing the atomic reference counter.",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "std::sync::Mutex",
|
|
226
|
+
"signature": "pub struct Mutex<T: ?Sized> { /* fields */ }",
|
|
227
|
+
"doc": "Mutual exclusion primitive useful for protecting shared data between threads.",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"name": "std::sync::Mutex::lock",
|
|
231
|
+
"signature": "pub fn lock(&self) -> LockResult<MutexGuard<'_, T>>",
|
|
232
|
+
"doc": "Acquires a mutex, blocking the current thread until it is able to do so.",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "std::sync::RwLock",
|
|
236
|
+
"signature": "pub struct RwLock<T: ?Sized> { /* fields */ }",
|
|
237
|
+
"doc": "Reader-writer lock allowing concurrent read access or exclusive write access.",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "std::thread::spawn",
|
|
241
|
+
"signature": "pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T + Send + 'static, T: Send + 'static",
|
|
242
|
+
"doc": "Spawns a new OS thread, returning a JoinHandle for awaiting the thread's completion.",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "std::collections::HashMap",
|
|
246
|
+
"signature": "pub struct HashMap<K, V, S = RandomState> { /* fields */ }",
|
|
247
|
+
"doc": "Hash map implemented with quadratic probing and SIMD lookup (SwissTable).",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "tokio::spawn",
|
|
251
|
+
"signature": "pub fn spawn<T>(future: T) -> JoinHandle<T::Output> where T: Future + Send + 'static, T::Output: Send + 'static",
|
|
252
|
+
"doc": "Spawns a new asynchronous task on the Tokio multithreaded runtime.",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "tokio::sync::mpsc::channel",
|
|
256
|
+
"signature": "pub fn channel<T>(buffer: usize) -> (Sender<T>, Receiver<T>)",
|
|
257
|
+
"doc": "Creates a bounded mpsc channel for communicating values between asynchronous tasks.",
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
"linux_syscalls": [
|
|
261
|
+
{
|
|
262
|
+
"name": "epoll_create1",
|
|
263
|
+
"signature": "int epoll_create1(int flags);",
|
|
264
|
+
"doc": "Open an epoll file descriptor. flags can include EPOLL_CLOEXEC to automatically close on exec.",
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"name": "epoll_ctl",
|
|
268
|
+
"signature": "int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);",
|
|
269
|
+
"doc": "Control interface for an epoll file descriptor. Operations: EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL.",
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "epoll_wait",
|
|
273
|
+
"signature": "int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);",
|
|
274
|
+
"doc": "Wait for I/O events on an epoll instance. Returns the number of ready file descriptors.",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"name": "io_uring_setup",
|
|
278
|
+
"signature": "int io_uring_setup(u32 entries, struct io_uring_params *p);",
|
|
279
|
+
"doc": "Set up an asynchronous I/O submission queue and completion queue with the Linux kernel.",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"name": "io_uring_enter",
|
|
283
|
+
"signature": "int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig);",
|
|
284
|
+
"doc": "Initiate and/or complete asynchronous I/O operations submitted to the io_uring submission ring.",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"name": "futex",
|
|
288
|
+
"signature": "int futex(int *uaddr, int futex_op, int val, const struct timespec *timeout, int *uaddr2, int val3);",
|
|
289
|
+
"doc": "Fast user-space locking primitive syscall. Operations include FUTEX_WAIT, FUTEX_WAKE, FUTEX_REQUEUE.",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"name": "mmap",
|
|
293
|
+
"signature": "void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);",
|
|
294
|
+
"doc": "Map files or anonymous memory pages into the calling process virtual address space.",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"name": "munmap",
|
|
298
|
+
"signature": "int munmap(void *addr, size_t length);",
|
|
299
|
+
"doc": "Delete memory mappings for the specified virtual address range.",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"name": "clone",
|
|
303
|
+
"signature": "int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...);",
|
|
304
|
+
"doc": "Create a child process or thread with fine-grained sharing of virtual memory, file descriptors, and namespaces.",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"name": "pipe2",
|
|
308
|
+
"signature": "int pipe2(int pipefd[2], int flags);",
|
|
309
|
+
"doc": "Create a unidirectional pipe with atomic O_CLOEXEC and O_NONBLOCK flag configuration.",
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
"fastapi": [
|
|
313
|
+
{
|
|
314
|
+
"name": "FastAPI",
|
|
315
|
+
"signature": "class FastAPI(title='FastAPI', version='0.1.0', docs_url='/docs', lifespan=None)",
|
|
316
|
+
"doc": "Main FastAPI web framework application instance, inheriting from Starlette with OpenAPI generation.",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"name": "APIRouter",
|
|
320
|
+
"signature": "class APIRouter(prefix='', tags=None, dependencies=None, responses=None)",
|
|
321
|
+
"doc": "Modular route aggregator for organizing endpoints into structured sub-applications.",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"name": "Depends",
|
|
325
|
+
"signature": "def Depends(dependency=None, *, use_cache=True)",
|
|
326
|
+
"doc": "Dependency injection provider for path operation functions, classes, and sub-dependencies.",
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"name": "HTTPException",
|
|
330
|
+
"signature": "class HTTPException(status_code: int, detail: Any = None, headers: Optional[Dict[str, str]] = None)",
|
|
331
|
+
"doc": "HTTP exception to return JSON error responses directly to the client with appropriate status codes.",
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"name": "BackgroundTasks",
|
|
335
|
+
"signature": "class BackgroundTasks.add_task(func: Callable, *args, **kwargs)",
|
|
336
|
+
"doc": "Schedule asynchronous or synchronous background tasks to run after sending the HTTP response.",
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"name": "Query",
|
|
340
|
+
"signature": "def Query(default=..., *, alias=None, title=None, description=None, min_length=None, max_length=None, regex=None)",
|
|
341
|
+
"doc": "Declare additional validation and metadata for URL query string parameters.",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"name": "Path",
|
|
345
|
+
"signature": "def Path(default=..., *, alias=None, title=None, description=None, ge=None, le=None)",
|
|
346
|
+
"doc": "Declare validation, type constraints, and documentation for URL path parameters.",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"name": "Body",
|
|
350
|
+
"signature": "def Body(default=..., *, embed=False, media_type='application/json')",
|
|
351
|
+
"doc": "Declare explicit request body parameters and payloads in route handlers.",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"name": "WebSocket",
|
|
355
|
+
"signature": "class WebSocket.accept(subprotocol=None, headers=None)",
|
|
356
|
+
"doc": "Bidirectional persistent WebSocket connection for real-time streaming communication.",
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
"redis": [
|
|
360
|
+
{
|
|
361
|
+
"name": "redis.Redis",
|
|
362
|
+
"signature": "class redis.Redis(host='localhost', port=6379, db=0, password=None, decode_responses=True)",
|
|
363
|
+
"doc": "Standard synchronous client for connecting to Redis in-memory data store.",
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"name": "redis.asyncio.Redis",
|
|
367
|
+
"signature": "class redis.asyncio.Redis(host='localhost', port=6379, db=0, password=None, decode_responses=True)",
|
|
368
|
+
"doc": "Asynchronous asyncio-compatible client for non-blocking Redis operations.",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"name": "Redis.set",
|
|
372
|
+
"signature": "Redis.set(name, value, ex=None, px=None, nx=False, xx=False, keepttl=False) -> bool",
|
|
373
|
+
"doc": "Set key to hold string value with optional expiration (ex in seconds) and conditional flags (nx=True for distributed locks).",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"name": "Redis.get",
|
|
377
|
+
"signature": "Redis.get(name) -> Optional[Union[str, bytes]]",
|
|
378
|
+
"doc": "Get the value of key. If the key does not exist, None is returned.",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"name": "Redis.hset",
|
|
382
|
+
"signature": "Redis.hset(name, key=None, value=None, mapping=None) -> int",
|
|
383
|
+
"doc": "Set field in hash stored at name to value, or set multiple fields via mapping dict.",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"name": "Redis.hgetall",
|
|
387
|
+
"signature": "Redis.hgetall(name) -> Dict[str, str]",
|
|
388
|
+
"doc": "Returns all fields and values of the hash stored at key name.",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"name": "Redis.rpush",
|
|
392
|
+
"signature": "Redis.rpush(name, *values) -> int",
|
|
393
|
+
"doc": "Insert all specified values at the tail of the list stored at name.",
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"name": "Redis.lpop",
|
|
397
|
+
"signature": "Redis.lpop(name, count=None) -> Optional[Union[str, List[str]]]",
|
|
398
|
+
"doc": "Removes and returns the first element of the list stored at key name.",
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"name": "Redis.pipeline",
|
|
402
|
+
"signature": "Redis.pipeline(transaction=True, shard_hint=None) -> Pipeline",
|
|
403
|
+
"doc": "Execute multiple Redis commands in a single round-trip batch, optionally wrapped in a MULTI/EXEC transaction.",
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
"postgresql": [
|
|
407
|
+
{
|
|
408
|
+
"name": "psycopg.connect",
|
|
409
|
+
"signature": "psycopg.connect(conninfo: str, **kwargs) -> Connection",
|
|
410
|
+
"doc": "Establish a synchronous client connection to a PostgreSQL database (psycopg 3).",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"name": "asyncpg.create_pool",
|
|
414
|
+
"signature": "asyncpg.create_pool(dsn=None, min_size=10, max_size=10, timeout=30.0) -> Pool",
|
|
415
|
+
"doc": "Create an asynchronous PostgreSQL connection pool for high-concurrency asyncio applications.",
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"name": "asyncpg.Connection.fetch",
|
|
419
|
+
"signature": "asyncpg.Connection.fetch(query: str, *args, timeout=None) -> List[Record]",
|
|
420
|
+
"doc": "Execute a query statement and return the results as a list of Record objects.",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"name": "asyncpg.Connection.fetchrow",
|
|
424
|
+
"signature": "asyncpg.Connection.fetchrow(query: str, *args, timeout=None) -> Optional[Record]",
|
|
425
|
+
"doc": "Execute a query statement and return the first row result as a Record, or None.",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
"name": "asyncpg.Connection.execute",
|
|
429
|
+
"signature": "asyncpg.Connection.execute(query: str, *args, timeout=None) -> str",
|
|
430
|
+
"doc": "Execute an SQL command (or commands) and return the status string (e.g. 'INSERT 0 1').",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"name": "Cursor.execute",
|
|
434
|
+
"signature": "Cursor.execute(query: str, params: Optional[Union[Sequence, Mapping]] = None)",
|
|
435
|
+
"doc": "Prepare and execute a database command or query with parameterized query sanitization.",
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"name": "Cursor.fetchall",
|
|
439
|
+
"signature": "Cursor.fetchall() -> List[Tuple]",
|
|
440
|
+
"doc": "Fetch all remaining rows of a query result, returning a list of tuples or records.",
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"name": "Connection.commit",
|
|
444
|
+
"signature": "Connection.commit()",
|
|
445
|
+
"doc": "Commit the current transaction to the PostgreSQL database.",
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"name": "Connection.rollback",
|
|
449
|
+
"signature": "Connection.rollback()",
|
|
450
|
+
"doc": "Roll back the current transaction and discard uncommitted changes.",
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
457
|
+
# High-Efficiency In-Memory LRU Cache
|
|
458
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
class _LRUCache:
|
|
461
|
+
"""Thread-safe, high-performance in-memory LRU cache."""
|
|
462
|
+
|
|
463
|
+
def __init__(self, maxsize: int = 2048):
|
|
464
|
+
self.maxsize = max(16, maxsize)
|
|
465
|
+
self._cache: OrderedDict[str, Any] = OrderedDict()
|
|
466
|
+
self._lock = threading.RLock()
|
|
467
|
+
self.hits = 0
|
|
468
|
+
self.misses = 0
|
|
469
|
+
|
|
470
|
+
def get(self, key: str) -> Optional[Any]:
|
|
471
|
+
with self._lock:
|
|
472
|
+
if key in self._cache:
|
|
473
|
+
self._cache.move_to_end(key)
|
|
474
|
+
self.hits += 1
|
|
475
|
+
return self._cache[key]
|
|
476
|
+
self.misses += 1
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
def put(self, key: str, value: Any) -> None:
|
|
480
|
+
with self._lock:
|
|
481
|
+
if key in self._cache:
|
|
482
|
+
self._cache.move_to_end(key)
|
|
483
|
+
self._cache[key] = value
|
|
484
|
+
if len(self._cache) > self.maxsize:
|
|
485
|
+
self._cache.popitem(last=False)
|
|
486
|
+
|
|
487
|
+
def clear(self) -> None:
|
|
488
|
+
with self._lock:
|
|
489
|
+
self._cache.clear()
|
|
490
|
+
self.hits = 0
|
|
491
|
+
self.misses = 0
|
|
492
|
+
|
|
493
|
+
def stats(self) -> Dict[str, Any]:
|
|
494
|
+
with self._lock:
|
|
495
|
+
total = self.hits + self.misses
|
|
496
|
+
hit_rate = (self.hits / total) if total > 0 else 0.0
|
|
497
|
+
return {
|
|
498
|
+
"size": len(self._cache),
|
|
499
|
+
"maxsize": self.maxsize,
|
|
500
|
+
"hits": self.hits,
|
|
501
|
+
"misses": self.misses,
|
|
502
|
+
"hit_rate": hit_rate,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
507
|
+
# Semantic Vectorizer & Cosine Similarity Engine
|
|
508
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
class _FastTextVectorizer:
|
|
511
|
+
"""
|
|
512
|
+
Subword character n-gram and token frequency vectorizer for sub-millisecond semantic similarity.
|
|
513
|
+
Calculates cosine similarity between queries and indexed document signatures/docstrings.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
@staticmethod
|
|
517
|
+
def tokenize_and_ngrams(text: str, n_min: int = 3, n_max: int = 4) -> List[str]:
|
|
518
|
+
"""Extract clean word tokens and character n-grams."""
|
|
519
|
+
if not text:
|
|
520
|
+
return []
|
|
521
|
+
tokens = re.findall(r"[a-zA-Z0-9_]+", text.lower())
|
|
522
|
+
features = list(tokens)
|
|
523
|
+
for t in tokens:
|
|
524
|
+
t_len = len(t)
|
|
525
|
+
if t_len >= n_min:
|
|
526
|
+
for n in range(n_min, min(n_max + 1, t_len + 1)):
|
|
527
|
+
for i in range(t_len - n + 1):
|
|
528
|
+
features.append(t[i : i + n])
|
|
529
|
+
return features
|
|
530
|
+
|
|
531
|
+
@classmethod
|
|
532
|
+
def get_term_vector(cls, text: str) -> Dict[str, float]:
|
|
533
|
+
"""Calculates normalized L2 term frequency vector."""
|
|
534
|
+
features = cls.tokenize_and_ngrams(text)
|
|
535
|
+
if not features:
|
|
536
|
+
return {}
|
|
537
|
+
counts = Counter(features)
|
|
538
|
+
total_sq = sum(v * v for v in counts.values())
|
|
539
|
+
norm = math.sqrt(total_sq) if total_sq > 0 else 1.0
|
|
540
|
+
return {k: v / norm for k, v in counts.items()}
|
|
541
|
+
|
|
542
|
+
@classmethod
|
|
543
|
+
def cosine_similarity(cls, vec1: Dict[str, float], vec2: Dict[str, float]) -> float:
|
|
544
|
+
"""Computes dot product between two normalized term vectors (cosine similarity in [0, 1])."""
|
|
545
|
+
if not vec1 or not vec2:
|
|
546
|
+
return 0.0
|
|
547
|
+
# Iterate over smaller dictionary for speed
|
|
548
|
+
if len(vec1) > len(vec2):
|
|
549
|
+
vec1, vec2 = vec2, vec1
|
|
550
|
+
return sum(val * vec2[k] for k, val in vec1.items() if k in vec2)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
554
|
+
# Intelligent Query Expander
|
|
555
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
556
|
+
|
|
557
|
+
class QueryExpander:
|
|
558
|
+
"""
|
|
559
|
+
Domain-specific query expansion engine for DevDocs retrieval.
|
|
560
|
+
Expands user intent with canonical programming symbols, synonyms, and library variants.
|
|
561
|
+
"""
|
|
562
|
+
|
|
563
|
+
SYNONYM_MAP: Dict[str, List[str]] = {
|
|
564
|
+
"fastapi": ["FastAPI", "APIRouter", "Depends", "HTTPException", "endpoint", "route"],
|
|
565
|
+
"endpoint": ["APIRouter", "FastAPI", "get", "post", "put", "delete"],
|
|
566
|
+
"route": ["APIRouter", "FastAPI", "url", "path"],
|
|
567
|
+
"redis": ["redis.Redis", "set", "get", "hset", "rpush", "pipeline", "cache"],
|
|
568
|
+
"cache": ["redis", "lru_cache", "get", "set", "expire"],
|
|
569
|
+
"lock": ["Mutex", "Arc", "futex", "Lock", "SET NX EX"],
|
|
570
|
+
"postgres": ["psycopg", "asyncpg", "create_pool", "fetch", "execute", "cursor"],
|
|
571
|
+
"postgresql": ["psycopg", "asyncpg", "create_pool", "fetch", "execute", "cursor"],
|
|
572
|
+
"sql": ["execute", "fetch", "cursor", "SELECT", "INSERT", "commit"],
|
|
573
|
+
"database": ["psycopg", "asyncpg", "sqlite3", "connect", "cursor"],
|
|
574
|
+
"epoll": ["epoll_create1", "epoll_ctl", "epoll_wait", "EPOLLIN", "EPOLL_CTL_ADD"],
|
|
575
|
+
"io_uring": ["io_uring_setup", "io_uring_enter", "submission queue"],
|
|
576
|
+
"futex": ["futex", "FUTEX_WAIT", "FUTEX_WAKE", "lock"],
|
|
577
|
+
"syscall": ["epoll_create1", "io_uring_setup", "futex", "mmap", "clone", "pipe2"],
|
|
578
|
+
"mmap": ["mmap", "munmap", "PROT_READ", "PROT_WRITE", "MAP_SHARED"],
|
|
579
|
+
"vector": ["std::vector", "push_back", "emplace_back", "size"],
|
|
580
|
+
"ranges": ["std::ranges", "views::filter", "views::transform", "take"],
|
|
581
|
+
"format": ["std::format", "std::print", "format_string"],
|
|
582
|
+
"expected": ["std::expected", "has_value", "value", "error"],
|
|
583
|
+
"span": ["std::span", "data", "size", "subspan"],
|
|
584
|
+
"arc": ["std::sync::Arc", "Arc::clone", "Arc::new", "atomic"],
|
|
585
|
+
"mutex": ["std::sync::Mutex", "Mutex::lock", "std::mutex", "lock_guard"],
|
|
586
|
+
"thread": ["std::thread::spawn", "std::jthread", "asyncio.create_task", "Thread"],
|
|
587
|
+
"channel": ["std::sync::mpsc::channel", "tokio::sync::mpsc::channel", "pipe2", "Queue"],
|
|
588
|
+
"async": ["asyncio", "create_task", "gather", "TaskGroup", "tokio::spawn"],
|
|
589
|
+
"json": ["json.loads", "json.dumps", "deserialize", "serialize"],
|
|
590
|
+
"path": ["os.path.join", "pathlib.Path", "exists", "read_text"],
|
|
591
|
+
"sqrt": ["math.sqrt", "square root"],
|
|
592
|
+
"root": ["math.sqrt", "square root"],
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
@classmethod
|
|
596
|
+
def expand(cls, query: str, language: Optional[str] = None) -> List[str]:
|
|
597
|
+
"""Expands query tokens into a list of relevant terms for hybrid retrieval."""
|
|
598
|
+
if not query or not query.strip():
|
|
599
|
+
return []
|
|
600
|
+
|
|
601
|
+
tokens = re.findall(r"[a-zA-Z0-9_]+", query.lower())
|
|
602
|
+
expanded: Set[str] = set(tokens)
|
|
603
|
+
|
|
604
|
+
for t in tokens:
|
|
605
|
+
if t in cls.SYNONYM_MAP:
|
|
606
|
+
for syn in cls.SYNONYM_MAP[t]:
|
|
607
|
+
expanded.add(syn)
|
|
608
|
+
|
|
609
|
+
if language:
|
|
610
|
+
lang_clean = language.lower().strip()
|
|
611
|
+
if "fastapi" in lang_clean or "python" in lang_clean:
|
|
612
|
+
expanded.add("python")
|
|
613
|
+
elif "cpp" in lang_clean or "c++" in lang_clean:
|
|
614
|
+
expanded.add("cpp")
|
|
615
|
+
elif "rust" in lang_clean:
|
|
616
|
+
expanded.add("rust")
|
|
617
|
+
elif "linux" in lang_clean or "c" == lang_clean:
|
|
618
|
+
expanded.add("linux_syscalls")
|
|
619
|
+
elif "redis" in lang_clean:
|
|
620
|
+
expanded.add("redis")
|
|
621
|
+
elif "postgres" in lang_clean:
|
|
622
|
+
expanded.add("postgresql")
|
|
623
|
+
|
|
624
|
+
return list(expanded)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
628
|
+
# Primary DocRetriever Class (Hybrid BM25 + Semantic Cosine Similarity)
|
|
629
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
630
|
+
|
|
631
|
+
class DocRetriever:
|
|
632
|
+
"""
|
|
633
|
+
Offline SQLite FTS5 Indexer and Precision Hybrid Retriever for Developer Documentation.
|
|
634
|
+
Features:
|
|
635
|
+
1. Fast BM25 lexical token matching + semantic cosine similarity ranking.
|
|
636
|
+
2. Direct integration with official standard libraries (Python 3.12, C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL).
|
|
637
|
+
3. Query expansion and automatic doc-snippet injection into the orchestrator context.
|
|
638
|
+
4. High-efficiency multi-tier local caching (< 2ms query latency SLA).
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
def __init__(
|
|
642
|
+
self,
|
|
643
|
+
db_path: Optional[Union[str, Path]] = None,
|
|
644
|
+
auto_index: bool = False,
|
|
645
|
+
enable_cache: bool = True,
|
|
646
|
+
cache_size: int = 2048,
|
|
647
|
+
devdocs_path: Optional[Union[str, Path]] = None,
|
|
648
|
+
):
|
|
649
|
+
if db_path is None:
|
|
650
|
+
self.db_path = str(_get_default_cache_db_path())
|
|
651
|
+
self._is_default_db = True
|
|
652
|
+
else:
|
|
653
|
+
self.db_path = str(db_path)
|
|
654
|
+
self._is_default_db = False
|
|
655
|
+
|
|
656
|
+
if devdocs_path is not None:
|
|
657
|
+
self._devdocs_path = Path(devdocs_path)
|
|
658
|
+
self._custom_devdocs = True
|
|
659
|
+
elif self._is_default_db and DEFAULT_SYSTEM_DEVDOCS_DB.exists():
|
|
660
|
+
self._devdocs_path = DEFAULT_SYSTEM_DEVDOCS_DB
|
|
661
|
+
self._custom_devdocs = False
|
|
662
|
+
else:
|
|
663
|
+
self._devdocs_path = None
|
|
664
|
+
self._custom_devdocs = False
|
|
665
|
+
|
|
666
|
+
self._enable_cache = enable_cache
|
|
667
|
+
self._cache = _LRUCache(maxsize=cache_size)
|
|
668
|
+
self._vector_cache: Dict[str, Dict[str, float]] = {}
|
|
669
|
+
self._lock = threading.RLock()
|
|
670
|
+
|
|
671
|
+
self._conn: Optional[sqlite3.Connection] = None
|
|
672
|
+
self._ensure_db()
|
|
673
|
+
|
|
674
|
+
# If default DB and empty, or auto_index requested, auto-index stdlib and official libraries
|
|
675
|
+
if auto_index or (self._is_default_db and self._is_empty()):
|
|
676
|
+
self.index_stdlib()
|
|
677
|
+
self.index_official_libraries()
|
|
678
|
+
|
|
679
|
+
def _ensure_db(self) -> None:
|
|
680
|
+
"""Initialize database directory, connection, WAL pragmas, and FTS5 schema."""
|
|
681
|
+
with self._lock:
|
|
682
|
+
if self.db_path != ":memory:":
|
|
683
|
+
db_file = Path(self.db_path)
|
|
684
|
+
db_file.parent.mkdir(parents=True, exist_ok=True)
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
self._conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
688
|
+
self._conn.execute("PRAGMA synchronous = NORMAL;")
|
|
689
|
+
self._conn.execute("PRAGMA temp_store = MEMORY;")
|
|
690
|
+
self._create_schema()
|
|
691
|
+
except sqlite3.DatabaseError:
|
|
692
|
+
self._recover_corrupt_db()
|
|
693
|
+
|
|
694
|
+
def _create_schema(self) -> None:
|
|
695
|
+
"""Create meta table and FTS5 virtual table if they do not exist."""
|
|
696
|
+
assert self._conn is not None
|
|
697
|
+
with self._conn:
|
|
698
|
+
self._conn.execute(
|
|
699
|
+
"""
|
|
700
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
701
|
+
key TEXT PRIMARY KEY,
|
|
702
|
+
value TEXT
|
|
703
|
+
);
|
|
704
|
+
"""
|
|
705
|
+
)
|
|
706
|
+
self._conn.execute(
|
|
707
|
+
"""
|
|
708
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS doc_entries USING fts5(
|
|
709
|
+
module,
|
|
710
|
+
name,
|
|
711
|
+
signature,
|
|
712
|
+
doc,
|
|
713
|
+
tokenize='unicode61'
|
|
714
|
+
);
|
|
715
|
+
"""
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def _recover_corrupt_db(self) -> None:
|
|
719
|
+
"""Recover from a corrupted database file by recreating it fresh."""
|
|
720
|
+
with self._lock:
|
|
721
|
+
try:
|
|
722
|
+
if self._conn:
|
|
723
|
+
self._conn.close()
|
|
724
|
+
except Exception:
|
|
725
|
+
pass
|
|
726
|
+
self._conn = None
|
|
727
|
+
|
|
728
|
+
if self.db_path != ":memory:" and os.path.exists(self.db_path):
|
|
729
|
+
try:
|
|
730
|
+
os.remove(self.db_path)
|
|
731
|
+
except Exception:
|
|
732
|
+
pass
|
|
733
|
+
|
|
734
|
+
if self.db_path != ":memory:":
|
|
735
|
+
Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
|
|
736
|
+
|
|
737
|
+
self._conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
738
|
+
self._conn.execute("PRAGMA synchronous = NORMAL;")
|
|
739
|
+
self._conn.execute("PRAGMA temp_store = MEMORY;")
|
|
740
|
+
self._create_schema()
|
|
741
|
+
self._cache.clear()
|
|
742
|
+
self._vector_cache.clear()
|
|
743
|
+
|
|
744
|
+
def _is_empty(self) -> bool:
|
|
745
|
+
"""Check if doc_entries has 0 rows."""
|
|
746
|
+
try:
|
|
747
|
+
assert self._conn is not None
|
|
748
|
+
with self._lock:
|
|
749
|
+
cur = self._conn.execute("SELECT count(*) FROM doc_entries")
|
|
750
|
+
return cur.fetchone()[0] == 0
|
|
751
|
+
except Exception:
|
|
752
|
+
return True
|
|
753
|
+
|
|
754
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
755
|
+
# Indexing API
|
|
756
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
757
|
+
|
|
758
|
+
def index_module(self, module_name: str, doc_data: Any) -> int:
|
|
759
|
+
"""
|
|
760
|
+
Index structured or dictionary-based documentation data for a given module.
|
|
761
|
+
Replaces any previously indexed entries for the same module.
|
|
762
|
+
Returns the number of entries indexed.
|
|
763
|
+
"""
|
|
764
|
+
entries = self._extract_entries(module_name, doc_data)
|
|
765
|
+
if not entries:
|
|
766
|
+
return 0
|
|
767
|
+
|
|
768
|
+
with self._lock:
|
|
769
|
+
try:
|
|
770
|
+
assert self._conn is not None
|
|
771
|
+
with self._conn:
|
|
772
|
+
self._conn.execute("DELETE FROM doc_entries WHERE module = ?", (module_name,))
|
|
773
|
+
self._conn.executemany(
|
|
774
|
+
"INSERT INTO doc_entries (module, name, signature, doc) VALUES (?, ?, ?, ?)",
|
|
775
|
+
entries,
|
|
776
|
+
)
|
|
777
|
+
self._cache.clear()
|
|
778
|
+
for mod, name, sig, doc in entries:
|
|
779
|
+
cache_key = f"{mod}:{name}"
|
|
780
|
+
self._vector_cache[cache_key] = _FastTextVectorizer.get_term_vector(f"{name} {sig} {doc}")
|
|
781
|
+
return len(entries)
|
|
782
|
+
except sqlite3.DatabaseError:
|
|
783
|
+
self._recover_corrupt_db()
|
|
784
|
+
assert self._conn is not None
|
|
785
|
+
with self._conn:
|
|
786
|
+
self._conn.executemany(
|
|
787
|
+
"INSERT INTO doc_entries (module, name, signature, doc) VALUES (?, ?, ?, ?)",
|
|
788
|
+
entries,
|
|
789
|
+
)
|
|
790
|
+
self._cache.clear()
|
|
791
|
+
return len(entries)
|
|
792
|
+
|
|
793
|
+
def _extract_entries(self, module_name: str, doc_data: Any) -> List[Tuple[str, str, str, str]]:
|
|
794
|
+
"""Extract flat (module, name, signature, doc) tuples from arbitrary doc data structures."""
|
|
795
|
+
entries: List[Tuple[str, str, str, str]] = []
|
|
796
|
+
|
|
797
|
+
if isinstance(doc_data, list):
|
|
798
|
+
for item in doc_data:
|
|
799
|
+
if isinstance(item, dict):
|
|
800
|
+
name = item.get("name") or item.get("symbol") or item.get("identifier") or module_name
|
|
801
|
+
sig = item.get("signature") or f"{name}()"
|
|
802
|
+
doc = item.get("doc") or item.get("docstring") or item.get("description") or ""
|
|
803
|
+
entries.append((module_name, str(name), str(sig), str(doc)))
|
|
804
|
+
elif isinstance(item, str):
|
|
805
|
+
entries.append((module_name, f"{module_name}.{item}", f"{module_name}.{item}()", ""))
|
|
806
|
+
elif isinstance(doc_data, dict):
|
|
807
|
+
has_containers = False
|
|
808
|
+
for container_key in ("functions", "classes", "methods", "symbols", "items", "constants", "signatures"):
|
|
809
|
+
if container_key in doc_data and isinstance(doc_data[container_key], (list, dict)):
|
|
810
|
+
has_containers = True
|
|
811
|
+
sub = doc_data[container_key]
|
|
812
|
+
if isinstance(sub, list):
|
|
813
|
+
for item in sub:
|
|
814
|
+
if isinstance(item, dict):
|
|
815
|
+
name = item.get("name") or item.get("symbol") or item.get("identifier") or f"{module_name}.{container_key}"
|
|
816
|
+
sig = item.get("signature") or f"{name}()"
|
|
817
|
+
doc = item.get("doc") or item.get("docstring") or item.get("description") or ""
|
|
818
|
+
entries.append((module_name, str(name), str(sig), str(doc)))
|
|
819
|
+
if "methods" in item and isinstance(item["methods"], list):
|
|
820
|
+
for meth in item["methods"]:
|
|
821
|
+
if isinstance(meth, dict):
|
|
822
|
+
m_name = meth.get("name") or meth.get("symbol") or f"{name}.method"
|
|
823
|
+
m_sig = meth.get("signature") or f"{m_name}()"
|
|
824
|
+
m_doc = meth.get("doc") or meth.get("docstring") or ""
|
|
825
|
+
entries.append((module_name, str(m_name), str(m_sig), str(m_doc)))
|
|
826
|
+
elif isinstance(item, str):
|
|
827
|
+
entries.append((module_name, f"{module_name}.{item}", f"{module_name}.{item}()", ""))
|
|
828
|
+
elif isinstance(sub, dict):
|
|
829
|
+
for k, v in sub.items():
|
|
830
|
+
if isinstance(v, dict):
|
|
831
|
+
name = v.get("name") or k
|
|
832
|
+
sig = v.get("signature") or f"{name}()"
|
|
833
|
+
doc = v.get("doc") or v.get("docstring") or ""
|
|
834
|
+
entries.append((module_name, str(name), str(sig), str(doc)))
|
|
835
|
+
elif isinstance(v, str):
|
|
836
|
+
entries.append((module_name, str(k), str(k), str(v)))
|
|
837
|
+
|
|
838
|
+
if not has_containers:
|
|
839
|
+
for k, v in doc_data.items():
|
|
840
|
+
if isinstance(v, dict):
|
|
841
|
+
name = v.get("name") or k
|
|
842
|
+
sig = v.get("signature") or f"{name}()"
|
|
843
|
+
doc = v.get("doc") or v.get("docstring") or ""
|
|
844
|
+
entries.append((module_name, str(name), str(sig), str(doc)))
|
|
845
|
+
elif isinstance(v, str):
|
|
846
|
+
entries.append((module_name, str(k), f"{k}()", str(v)))
|
|
847
|
+
|
|
848
|
+
return entries
|
|
849
|
+
|
|
850
|
+
def index_stdlib(self, modules: Optional[List[str]] = None) -> int:
|
|
851
|
+
"""
|
|
852
|
+
Introspect and index Python standard library and common framework modules into the database.
|
|
853
|
+
"""
|
|
854
|
+
targets = modules if modules is not None else DEFAULT_STDLIB_MODULES
|
|
855
|
+
total_indexed = 0
|
|
856
|
+
|
|
857
|
+
with warnings.catch_warnings():
|
|
858
|
+
warnings.simplefilter("ignore")
|
|
859
|
+
for mod_name in targets:
|
|
860
|
+
try:
|
|
861
|
+
entries = self._introspect_module(mod_name)
|
|
862
|
+
if entries:
|
|
863
|
+
with self._lock:
|
|
864
|
+
assert self._conn is not None
|
|
865
|
+
with self._conn:
|
|
866
|
+
self._conn.execute("DELETE FROM doc_entries WHERE module = ?", (mod_name,))
|
|
867
|
+
self._conn.executemany(
|
|
868
|
+
"INSERT INTO doc_entries (module, name, signature, doc) VALUES (?, ?, ?, ?)",
|
|
869
|
+
entries,
|
|
870
|
+
)
|
|
871
|
+
total_indexed += len(entries)
|
|
872
|
+
except Exception:
|
|
873
|
+
continue
|
|
874
|
+
|
|
875
|
+
self._cache.clear()
|
|
876
|
+
return total_indexed
|
|
877
|
+
|
|
878
|
+
def _introspect_module(self, mod_name: str) -> List[Tuple[str, str, str, str]]:
|
|
879
|
+
"""Introspect a Python module and extract its public symbols, signatures, and docstrings."""
|
|
880
|
+
try:
|
|
881
|
+
mod = importlib.import_module(mod_name)
|
|
882
|
+
except Exception:
|
|
883
|
+
return []
|
|
884
|
+
|
|
885
|
+
entries: List[Tuple[str, str, str, str]] = []
|
|
886
|
+
for attr_name in dir(mod):
|
|
887
|
+
if attr_name.startswith("_") and attr_name != "__init__":
|
|
888
|
+
continue
|
|
889
|
+
try:
|
|
890
|
+
val = getattr(mod, attr_name)
|
|
891
|
+
except Exception:
|
|
892
|
+
continue
|
|
893
|
+
|
|
894
|
+
doc = inspect.getdoc(val) or ""
|
|
895
|
+
short_doc = doc.strip().split("\n")[0] if doc else ""
|
|
896
|
+
|
|
897
|
+
if inspect.isroutine(val) or inspect.isbuiltin(val) or inspect.isfunction(val):
|
|
898
|
+
try:
|
|
899
|
+
sig = inspect.signature(val)
|
|
900
|
+
sig_str = f"{mod_name}.{attr_name}{sig}"
|
|
901
|
+
except Exception:
|
|
902
|
+
sig_str = f"{mod_name}.{attr_name}(...)"
|
|
903
|
+
entries.append((mod_name, f"{mod_name}.{attr_name}", sig_str, short_doc or doc))
|
|
904
|
+
elif inspect.isclass(val):
|
|
905
|
+
try:
|
|
906
|
+
sig = inspect.signature(val)
|
|
907
|
+
sig_str = f"class {mod_name}.{attr_name}{sig}"
|
|
908
|
+
except Exception:
|
|
909
|
+
sig_str = f"class {mod_name}.{attr_name}"
|
|
910
|
+
entries.append((mod_name, f"{mod_name}.{attr_name}", sig_str, short_doc or doc))
|
|
911
|
+
# Methods of class
|
|
912
|
+
for meth_name in dir(val):
|
|
913
|
+
if meth_name.startswith("_") and meth_name != "__init__":
|
|
914
|
+
continue
|
|
915
|
+
try:
|
|
916
|
+
meth_val = getattr(val, meth_name)
|
|
917
|
+
if inspect.isroutine(meth_val):
|
|
918
|
+
meth_doc = inspect.getdoc(meth_val) or ""
|
|
919
|
+
m_short = meth_doc.strip().split("\n")[0] if meth_doc else ""
|
|
920
|
+
try:
|
|
921
|
+
m_sig = inspect.signature(meth_val)
|
|
922
|
+
m_sig_str = f"{mod_name}.{attr_name}.{meth_name}{m_sig}"
|
|
923
|
+
except Exception:
|
|
924
|
+
m_sig_str = f"{mod_name}.{attr_name}.{meth_name}(...)"
|
|
925
|
+
entries.append((mod_name, f"{mod_name}.{attr_name}.{meth_name}", m_sig_str, m_short or meth_doc))
|
|
926
|
+
except Exception:
|
|
927
|
+
continue
|
|
928
|
+
else:
|
|
929
|
+
entries.append((mod_name, f"{mod_name}.{attr_name}", f"{mod_name}.{attr_name}", short_doc or doc))
|
|
930
|
+
|
|
931
|
+
return entries
|
|
932
|
+
|
|
933
|
+
def index_official_libraries(self, libraries: Optional[List[str]] = None) -> int:
|
|
934
|
+
"""
|
|
935
|
+
Directly index curated official standard libraries & frameworks:
|
|
936
|
+
Python 3.12, C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL.
|
|
937
|
+
"""
|
|
938
|
+
targets = libraries if libraries is not None else DEFAULT_OFFICIAL_LIBRARIES
|
|
939
|
+
total_indexed = 0
|
|
940
|
+
|
|
941
|
+
for lib_key in targets:
|
|
942
|
+
if lib_key in OFFICIAL_DEV_DOCS:
|
|
943
|
+
docs = OFFICIAL_DEV_DOCS[lib_key]
|
|
944
|
+
count = self.index_module(lib_key, docs)
|
|
945
|
+
total_indexed += count
|
|
946
|
+
|
|
947
|
+
return total_indexed
|
|
948
|
+
|
|
949
|
+
def download_all_devdocs(
|
|
950
|
+
self,
|
|
951
|
+
progress_callback: Optional[Callable[[str, int], None]] = None,
|
|
952
|
+
) -> Dict[str, Any]:
|
|
953
|
+
"""
|
|
954
|
+
Downloads and indexes all official standard libraries and developer documentation packages:
|
|
955
|
+
Python 3.12, C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL, Docker, Git.
|
|
956
|
+
"""
|
|
957
|
+
start_t = time.perf_counter()
|
|
958
|
+
if progress_callback:
|
|
959
|
+
progress_callback("Indexing Python 3.12 Standard Libraries...", 10)
|
|
960
|
+
|
|
961
|
+
stdlib_count = self.index_stdlib()
|
|
962
|
+
|
|
963
|
+
if progress_callback:
|
|
964
|
+
progress_callback("Indexing Curated Frameworks & Syscalls (C++23, Rust, FastAPI, Redis, PostgreSQL)...", 50)
|
|
965
|
+
|
|
966
|
+
official_count = self.index_official_libraries()
|
|
967
|
+
|
|
968
|
+
total_symbols = stdlib_count + official_count
|
|
969
|
+
duration = round(time.perf_counter() - start_t, 3)
|
|
970
|
+
|
|
971
|
+
# Count total in DB
|
|
972
|
+
total_in_db = 0
|
|
973
|
+
with self._lock:
|
|
974
|
+
if self._conn:
|
|
975
|
+
cur = self._conn.execute("SELECT COUNT(*) FROM doc_entries")
|
|
976
|
+
total_in_db = cur.fetchone()[0]
|
|
977
|
+
|
|
978
|
+
if progress_callback:
|
|
979
|
+
progress_callback(f"Done! {total_in_db} symbols indexed in {duration}s.", 100)
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
"success": True,
|
|
983
|
+
"total_symbols_indexed": total_symbols,
|
|
984
|
+
"total_database_symbols": total_in_db,
|
|
985
|
+
"db_path": str(self.db_path),
|
|
986
|
+
"duration_seconds": duration,
|
|
987
|
+
"packages": list(OFFICIAL_DEV_DOCS.keys()) + ["stdlib"],
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
992
|
+
# Hybrid Search Engine (BM25 + Semantic Cosine Similarity)
|
|
993
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
994
|
+
|
|
995
|
+
def search(
|
|
996
|
+
self,
|
|
997
|
+
query: str,
|
|
998
|
+
limit: int = 3,
|
|
999
|
+
max_tokens: int = 250,
|
|
1000
|
+
hybrid: bool = True,
|
|
1001
|
+
alpha: float = 0.5,
|
|
1002
|
+
docset: Optional[str] = None,
|
|
1003
|
+
language: Optional[str] = None,
|
|
1004
|
+
) -> List[Dict[str, Any]]:
|
|
1005
|
+
"""
|
|
1006
|
+
Perform high-speed hybrid search (BM25 lexical token matching + semantic cosine similarity).
|
|
1007
|
+
Returns matching items with signature, docstring, module, and combined relevance score.
|
|
1008
|
+
Query latency SLA is < 2.0 ms.
|
|
1009
|
+
"""
|
|
1010
|
+
if not query or not query.strip() or limit <= 0:
|
|
1011
|
+
return []
|
|
1012
|
+
|
|
1013
|
+
cache_key = f"{query.strip()[:2048]}|{limit}|{max_tokens}|{hybrid}|{alpha}|{docset}|{language}"
|
|
1014
|
+
if self._enable_cache:
|
|
1015
|
+
cached = self._cache.get(cache_key)
|
|
1016
|
+
if cached is not None:
|
|
1017
|
+
return cached
|
|
1018
|
+
|
|
1019
|
+
# Bound adversarial query cost before FTS and semantic feature generation.
|
|
1020
|
+
# Long natural-language requests retain enough leading terms for retrieval,
|
|
1021
|
+
# while giant single tokens cannot trigger unbounded n-gram work.
|
|
1022
|
+
tokens = [token[:128] for token in re.findall(r"[a-zA-Z0-9_]+", query)[:256]]
|
|
1023
|
+
if not tokens:
|
|
1024
|
+
return []
|
|
1025
|
+
bounded_query = " ".join(tokens)
|
|
1026
|
+
|
|
1027
|
+
# 1. Fetch lexical BM25 candidate results from SQLite FTS5
|
|
1028
|
+
bm25_candidates = self._fetch_bm25_candidates(tokens, limit=max(limit * 3, 15), docset=docset)
|
|
1029
|
+
|
|
1030
|
+
# 2. If system devdocs database exists and matches, supplement candidates when appropriate
|
|
1031
|
+
if self._devdocs_path and self._devdocs_path.exists() and (self._custom_devdocs or (self._is_default_db and (docset or not bm25_candidates))):
|
|
1032
|
+
system_docs = self._fetch_system_devdocs(query, tokens, limit=limit, docset=docset)
|
|
1033
|
+
if system_docs:
|
|
1034
|
+
bm25_candidates.extend(system_docs)
|
|
1035
|
+
|
|
1036
|
+
if not bm25_candidates:
|
|
1037
|
+
if self._enable_cache:
|
|
1038
|
+
self._cache.put(cache_key, [])
|
|
1039
|
+
return []
|
|
1040
|
+
|
|
1041
|
+
# 3. Compute semantic cosine similarity & hybrid score fusion
|
|
1042
|
+
query_vec = _FastTextVectorizer.get_term_vector(bounded_query)
|
|
1043
|
+
scored_results: List[Dict[str, Any]] = []
|
|
1044
|
+
|
|
1045
|
+
# Find min/max BM25 scores for normalization
|
|
1046
|
+
bm25_scores = [c["rank"] for c in bm25_candidates]
|
|
1047
|
+
min_bm25 = min(bm25_scores) if bm25_scores else 0.0
|
|
1048
|
+
max_bm25 = max(bm25_scores) if bm25_scores else 1.0
|
|
1049
|
+
range_bm25 = (max_bm25 - min_bm25) if (max_bm25 - min_bm25) > 1e-6 else 1.0
|
|
1050
|
+
|
|
1051
|
+
for item in bm25_candidates:
|
|
1052
|
+
# BM25 normalization: FTS5 rank is lower for better matches (e.g. -15.0 to 0.0)
|
|
1053
|
+
raw_bm25 = float(item["rank"])
|
|
1054
|
+
norm_bm25 = (max_bm25 - raw_bm25) / range_bm25
|
|
1055
|
+
|
|
1056
|
+
# Cosine similarity calculation
|
|
1057
|
+
doc_text = f"{item['name']} {item['signature']} {item['doc']}"
|
|
1058
|
+
doc_vec = _FastTextVectorizer.get_term_vector(doc_text)
|
|
1059
|
+
cosine_sim = _FastTextVectorizer.cosine_similarity(query_vec, doc_vec)
|
|
1060
|
+
|
|
1061
|
+
# Exact symbol match boost
|
|
1062
|
+
clean_q = query.strip().lower()
|
|
1063
|
+
name_lower = item["name"].lower()
|
|
1064
|
+
if clean_q == name_lower or clean_q in name_lower.split(".") or name_lower.endswith("." + clean_q) or name_lower.startswith(clean_q + "."):
|
|
1065
|
+
cosine_sim = min(1.0, cosine_sim + 0.5)
|
|
1066
|
+
norm_bm25 = 1.0
|
|
1067
|
+
|
|
1068
|
+
if hybrid:
|
|
1069
|
+
combined_score = (alpha * norm_bm25) + ((1.0 - alpha) * cosine_sim)
|
|
1070
|
+
else:
|
|
1071
|
+
combined_score = norm_bm25
|
|
1072
|
+
|
|
1073
|
+
item_dict = {
|
|
1074
|
+
"module": item["module"],
|
|
1075
|
+
"name": item["name"],
|
|
1076
|
+
"symbol": item["name"],
|
|
1077
|
+
"signature": item["signature"],
|
|
1078
|
+
"doc": item["doc"],
|
|
1079
|
+
"docstring": item["doc"],
|
|
1080
|
+
"score": raw_bm25,
|
|
1081
|
+
"rank": raw_bm25,
|
|
1082
|
+
"bm25_score": raw_bm25,
|
|
1083
|
+
"cosine_sim": cosine_sim,
|
|
1084
|
+
"hybrid_score": combined_score,
|
|
1085
|
+
}
|
|
1086
|
+
scored_results.append(item_dict)
|
|
1087
|
+
|
|
1088
|
+
# 4. Rank by hybrid score (descending) or rank (ascending)
|
|
1089
|
+
if hybrid:
|
|
1090
|
+
scored_results.sort(key=lambda x: (x["hybrid_score"], -x["rank"]), reverse=True)
|
|
1091
|
+
else:
|
|
1092
|
+
scored_results.sort(key=lambda x: x["rank"])
|
|
1093
|
+
|
|
1094
|
+
final_results = scored_results[:limit]
|
|
1095
|
+
if self._enable_cache:
|
|
1096
|
+
self._cache.put(cache_key, final_results)
|
|
1097
|
+
|
|
1098
|
+
return final_results
|
|
1099
|
+
|
|
1100
|
+
def _fetch_bm25_candidates(
|
|
1101
|
+
self, tokens: List[str], limit: int = 15, docset: Optional[str] = None
|
|
1102
|
+
) -> List[Dict[str, Any]]:
|
|
1103
|
+
"""Query SQLite FTS5 table with sanitization and prefix expansion."""
|
|
1104
|
+
fts_query = " OR ".join(f'"{t}"*' for t in tokens)
|
|
1105
|
+
|
|
1106
|
+
with self._lock:
|
|
1107
|
+
try:
|
|
1108
|
+
assert self._conn is not None
|
|
1109
|
+
if docset:
|
|
1110
|
+
cur = self._conn.execute(
|
|
1111
|
+
"""
|
|
1112
|
+
SELECT module, name, signature, doc, bm25(doc_entries, 2.0, 10.0, 5.0, 1.0) as rank
|
|
1113
|
+
FROM doc_entries
|
|
1114
|
+
WHERE module = ? AND doc_entries MATCH ?
|
|
1115
|
+
ORDER BY rank
|
|
1116
|
+
LIMIT ?
|
|
1117
|
+
""",
|
|
1118
|
+
(docset, fts_query, limit),
|
|
1119
|
+
)
|
|
1120
|
+
else:
|
|
1121
|
+
cur = self._conn.execute(
|
|
1122
|
+
"""
|
|
1123
|
+
SELECT module, name, signature, doc, bm25(doc_entries, 2.0, 10.0, 5.0, 1.0) as rank
|
|
1124
|
+
FROM doc_entries
|
|
1125
|
+
WHERE doc_entries MATCH ?
|
|
1126
|
+
ORDER BY rank
|
|
1127
|
+
LIMIT ?
|
|
1128
|
+
""",
|
|
1129
|
+
(fts_query, limit),
|
|
1130
|
+
)
|
|
1131
|
+
rows = cur.fetchall()
|
|
1132
|
+
except sqlite3.DatabaseError:
|
|
1133
|
+
return []
|
|
1134
|
+
|
|
1135
|
+
candidates: List[Dict[str, Any]] = []
|
|
1136
|
+
for row in rows:
|
|
1137
|
+
score = float(row[4]) if row[4] is not None else 0.0
|
|
1138
|
+
candidates.append(
|
|
1139
|
+
{
|
|
1140
|
+
"module": row[0],
|
|
1141
|
+
"name": row[1],
|
|
1142
|
+
"signature": row[2],
|
|
1143
|
+
"doc": row[3],
|
|
1144
|
+
"rank": score,
|
|
1145
|
+
}
|
|
1146
|
+
)
|
|
1147
|
+
return candidates
|
|
1148
|
+
|
|
1149
|
+
def _fetch_system_devdocs(
|
|
1150
|
+
self, query: str, tokens: List[str], limit: int = 5, docset: Optional[str] = None
|
|
1151
|
+
) -> List[Dict[str, Any]]:
|
|
1152
|
+
"""Query external system DevDocs SQLite database (~/.kcli/docs.db) if available."""
|
|
1153
|
+
if not self._devdocs_path or not self._devdocs_path.exists():
|
|
1154
|
+
return []
|
|
1155
|
+
|
|
1156
|
+
try:
|
|
1157
|
+
con = sqlite3.connect(str(self._devdocs_path))
|
|
1158
|
+
cur = con.cursor()
|
|
1159
|
+
clean_q = query.strip()
|
|
1160
|
+
|
|
1161
|
+
# Exact / LIKE query
|
|
1162
|
+
if docset:
|
|
1163
|
+
cur.execute(
|
|
1164
|
+
"SELECT docset, name, type, content FROM docs WHERE (name = ? OR name LIKE ?) AND docset = ? LIMIT ?",
|
|
1165
|
+
(clean_q, f"{clean_q}%", docset, limit),
|
|
1166
|
+
)
|
|
1167
|
+
else:
|
|
1168
|
+
cur.execute(
|
|
1169
|
+
"SELECT docset, name, type, content FROM docs WHERE name = ? OR name LIKE ? LIMIT ?",
|
|
1170
|
+
(clean_q, f"{clean_q}%", limit),
|
|
1171
|
+
)
|
|
1172
|
+
rows = cur.fetchall()
|
|
1173
|
+
|
|
1174
|
+
# Fallback to docs_fts if no direct matches
|
|
1175
|
+
if not rows and tokens:
|
|
1176
|
+
fts_q = " OR ".join(tokens)
|
|
1177
|
+
if docset:
|
|
1178
|
+
cur.execute(
|
|
1179
|
+
"SELECT docset, name, type, content FROM docs_fts WHERE docset = ? AND docs_fts MATCH ? LIMIT ?",
|
|
1180
|
+
(docset, fts_q, limit),
|
|
1181
|
+
)
|
|
1182
|
+
else:
|
|
1183
|
+
cur.execute(
|
|
1184
|
+
"SELECT docset, name, type, content FROM docs_fts WHERE docs_fts MATCH ? LIMIT ?",
|
|
1185
|
+
(fts_q, limit),
|
|
1186
|
+
)
|
|
1187
|
+
rows = cur.fetchall()
|
|
1188
|
+
con.close()
|
|
1189
|
+
|
|
1190
|
+
results: List[Dict[str, Any]] = []
|
|
1191
|
+
for r in rows:
|
|
1192
|
+
docset_name = r[0]
|
|
1193
|
+
name = r[1]
|
|
1194
|
+
content = r[3]
|
|
1195
|
+
lines = content.splitlines()
|
|
1196
|
+
sig = lines[0] if lines else name
|
|
1197
|
+
doc_text = "\n".join(lines[1:4]) if len(lines) > 1 else ""
|
|
1198
|
+
results.append(
|
|
1199
|
+
{
|
|
1200
|
+
"module": docset_name,
|
|
1201
|
+
"name": name,
|
|
1202
|
+
"signature": sig,
|
|
1203
|
+
"doc": doc_text,
|
|
1204
|
+
"rank": -5.0,
|
|
1205
|
+
}
|
|
1206
|
+
)
|
|
1207
|
+
return results
|
|
1208
|
+
except Exception:
|
|
1209
|
+
return []
|
|
1210
|
+
|
|
1211
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1212
|
+
# Context Formatting & Prompt Injection
|
|
1213
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1214
|
+
|
|
1215
|
+
def expand_query(self, query: str, language: Optional[str] = None) -> List[str]:
|
|
1216
|
+
"""Expands query tokens using the intelligent query expansion engine."""
|
|
1217
|
+
return QueryExpander.expand(query, language=language)
|
|
1218
|
+
|
|
1219
|
+
def format_context_snippets(
|
|
1220
|
+
self,
|
|
1221
|
+
query: str,
|
|
1222
|
+
max_tokens: int = 250,
|
|
1223
|
+
language: Optional[str] = None,
|
|
1224
|
+
docset: Optional[str] = None,
|
|
1225
|
+
hybrid: bool = True,
|
|
1226
|
+
) -> str:
|
|
1227
|
+
"""
|
|
1228
|
+
Search DevDocs and format concise signature and docstring snippets within the token budget.
|
|
1229
|
+
"""
|
|
1230
|
+
if max_tokens <= 0 or not query or not query.strip():
|
|
1231
|
+
return ""
|
|
1232
|
+
|
|
1233
|
+
# Perform hybrid search with query expansion
|
|
1234
|
+
results = self.search(
|
|
1235
|
+
query,
|
|
1236
|
+
limit=5,
|
|
1237
|
+
max_tokens=max_tokens,
|
|
1238
|
+
hybrid=hybrid,
|
|
1239
|
+
docset=docset,
|
|
1240
|
+
language=language,
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
# If few results, expand query and supplement
|
|
1244
|
+
if len(results) < 2:
|
|
1245
|
+
expanded_terms = self.expand_query(query, language=language)
|
|
1246
|
+
expanded_query = " ".join(expanded_terms)
|
|
1247
|
+
if expanded_query != query:
|
|
1248
|
+
extra = self.search(
|
|
1249
|
+
expanded_query,
|
|
1250
|
+
limit=5,
|
|
1251
|
+
max_tokens=max_tokens,
|
|
1252
|
+
hybrid=hybrid,
|
|
1253
|
+
docset=docset,
|
|
1254
|
+
language=language,
|
|
1255
|
+
)
|
|
1256
|
+
seen_names = {r["name"] for r in results}
|
|
1257
|
+
for item in extra:
|
|
1258
|
+
if item["name"] not in seen_names:
|
|
1259
|
+
results.append(item)
|
|
1260
|
+
seen_names.add(item["name"])
|
|
1261
|
+
|
|
1262
|
+
if not results:
|
|
1263
|
+
return ""
|
|
1264
|
+
|
|
1265
|
+
formatted_items: List[str] = []
|
|
1266
|
+
for r in results:
|
|
1267
|
+
sig = r.get("signature") or r.get("name", "")
|
|
1268
|
+
doc = r.get("doc") or r.get("docstring", "")
|
|
1269
|
+
doc_brief = doc.strip().split("\n\n")[0].strip() if doc else ""
|
|
1270
|
+
|
|
1271
|
+
if doc_brief:
|
|
1272
|
+
formatted_items.append(f"`{sig}`\n {doc_brief}")
|
|
1273
|
+
else:
|
|
1274
|
+
formatted_items.append(f"`{sig}`")
|
|
1275
|
+
|
|
1276
|
+
combined = "\n\n".join(formatted_items)
|
|
1277
|
+
words = combined.split()
|
|
1278
|
+
if len(words) > max_tokens:
|
|
1279
|
+
return " ".join(words[:max_tokens])
|
|
1280
|
+
return combined
|
|
1281
|
+
|
|
1282
|
+
def inject_doc_snippets(
|
|
1283
|
+
self,
|
|
1284
|
+
prompt: str,
|
|
1285
|
+
language: Optional[str] = None,
|
|
1286
|
+
max_tokens: int = 250,
|
|
1287
|
+
persona: Optional[str] = None,
|
|
1288
|
+
) -> str:
|
|
1289
|
+
"""
|
|
1290
|
+
Intelligently extracts keywords from a user or orchestrator prompt, retrieves relevant
|
|
1291
|
+
DevDocs snippets, and formats an enriched prompt section for the orchestrator context.
|
|
1292
|
+
"""
|
|
1293
|
+
if not prompt or not prompt.strip() or max_tokens <= 0:
|
|
1294
|
+
return ""
|
|
1295
|
+
|
|
1296
|
+
snippets = self.format_context_snippets(prompt, max_tokens=max_tokens, language=language)
|
|
1297
|
+
if not snippets.strip():
|
|
1298
|
+
return ""
|
|
1299
|
+
|
|
1300
|
+
badge = f" [Language: {language}]" if language else ""
|
|
1301
|
+
header = f"### DevDocs Reference Snippets{badge}:\n"
|
|
1302
|
+
return f"{header}{snippets}\n"
|
|
1303
|
+
|
|
1304
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1305
|
+
# Cache Management & Lifecycle
|
|
1306
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1307
|
+
|
|
1308
|
+
def clear_cache(self) -> None:
|
|
1309
|
+
"""Clears all in-memory LRU and vector caches."""
|
|
1310
|
+
self._cache.clear()
|
|
1311
|
+
self._vector_cache.clear()
|
|
1312
|
+
|
|
1313
|
+
def cache_stats(self) -> Dict[str, Any]:
|
|
1314
|
+
"""Returns statistics on in-memory query cache hits and misses."""
|
|
1315
|
+
return self._cache.stats()
|
|
1316
|
+
|
|
1317
|
+
def close(self) -> None:
|
|
1318
|
+
"""Close SQLite database connection and clear in-memory caches."""
|
|
1319
|
+
with self._lock:
|
|
1320
|
+
if self._conn:
|
|
1321
|
+
try:
|
|
1322
|
+
self._conn.close()
|
|
1323
|
+
except Exception:
|
|
1324
|
+
pass
|
|
1325
|
+
self._conn = None
|
|
1326
|
+
self.clear_cache()
|
|
1327
|
+
|
|
1328
|
+
def __enter__(self) -> DocRetriever:
|
|
1329
|
+
return self
|
|
1330
|
+
|
|
1331
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
1332
|
+
self.close()
|