codrspot-processor-mcp 0.1.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.
- codepreproc_client/__init__.py +14 -0
- codepreproc_client/__main__.py +9 -0
- codepreproc_client/layer1_business/__init__.py +0 -0
- codepreproc_client/layer1_business/allowed_scope_cache.py +62 -0
- codepreproc_client/layer1_business/api_client/__init__.py +0 -0
- codepreproc_client/layer1_business/api_client/lease_client.py +34 -0
- codepreproc_client/layer1_business/api_client/net_guard.py +98 -0
- codepreproc_client/layer1_business/api_client/promote_client.py +59 -0
- codepreproc_client/layer1_business/api_client/reasoning_client.py +163 -0
- codepreproc_client/layer1_business/api_client/registry_client.py +119 -0
- codepreproc_client/layer1_business/api_client/superkg_client.py +327 -0
- codepreproc_client/layer1_business/cli/__init__.py +0 -0
- codepreproc_client/layer1_business/cli/init.py +578 -0
- codepreproc_client/layer1_business/cli/main.py +78 -0
- codepreproc_client/layer1_business/config.py +104 -0
- codepreproc_client/layer1_business/git_utils.py +67 -0
- codepreproc_client/layer1_business/license/__init__.py +0 -0
- codepreproc_client/layer1_business/license/fingerprint.py +61 -0
- codepreproc_client/layer1_business/license/jwt_client.py +60 -0
- codepreproc_client/layer1_business/license/project_lock.py +44 -0
- codepreproc_client/layer1_business/local_registry.py +40 -0
- codepreproc_client/layer1_business/manifest/__init__.py +0 -0
- codepreproc_client/layer1_business/manifest/credentials.py +35 -0
- codepreproc_client/layer1_business/mcp/__init__.py +0 -0
- codepreproc_client/layer1_business/mcp/action_monitor.py +246 -0
- codepreproc_client/layer1_business/mcp/lifecycle.py +649 -0
- codepreproc_client/layer1_business/mcp/server.py +656 -0
- codepreproc_client/layer1_business/mcp/tools.py +1665 -0
- codepreproc_client/layer1_business/project_registry.py +99 -0
- codepreproc_client/layer2_tooling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ast_graph.py +426 -0
- codepreproc_client/layer2_tooling/condensation/bm25_store.py +132 -0
- codepreproc_client/layer2_tooling/condensation/chunking/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/chunking/ast_chunker.py +313 -0
- codepreproc_client/layer2_tooling/condensation/chunking/languages.py +238 -0
- codepreproc_client/layer2_tooling/condensation/embed_worker.py +128 -0
- codepreproc_client/layer2_tooling/condensation/embeddings.py +452 -0
- codepreproc_client/layer2_tooling/condensation/extract/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/compactor.py +103 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/extract/providers/base.py +38 -0
- codepreproc_client/layer2_tooling/condensation/indexer.py +945 -0
- codepreproc_client/layer2_tooling/condensation/ppl/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/ppl/emitter.py +162 -0
- codepreproc_client/layer2_tooling/condensation/ppl/parser.py +352 -0
- codepreproc_client/layer2_tooling/condensation/qdrant_store.py +387 -0
- codepreproc_client/layer2_tooling/condensation/scanning/__init__.py +0 -0
- codepreproc_client/layer2_tooling/condensation/scanning/manifest_scanner.py +72 -0
- codepreproc_client/layer2_tooling/condensation/scanning/md_scanner.py +97 -0
- codepreproc_client/layer2_tooling/condensation/scanning/repo_mapper.py +495 -0
- codepreproc_client/layer2_tooling/condensation/session_vector_store.py +69 -0
- codepreproc_client/layer2_tooling/coupling/__init__.py +0 -0
- codepreproc_client/layer2_tooling/coupling/domain.py +263 -0
- codepreproc_client/layer2_tooling/coupling/graph.py +255 -0
- codepreproc_client/layer2_tooling/coupling/layer2_gate.py +189 -0
- codepreproc_client/layer2_tooling/coupling/projector.py +139 -0
- codepreproc_client/layer2_tooling/coupling/resolver.py +150 -0
- codepreproc_client/layer2_tooling/materialization/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/execute_semantic.py +1249 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/filesystem_reorg.py +575 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_generator.py +127 -0
- codepreproc_client/layer2_tooling/materialization/pipeline/patch_validator.py +101 -0
- codepreproc_client/layer2_tooling/materialization/semantic/__init__.py +19 -0
- codepreproc_client/layer2_tooling/materialization/semantic/anchors.py +61 -0
- codepreproc_client/layer2_tooling/materialization/semantic/applicability.py +75 -0
- codepreproc_client/layer2_tooling/materialization/semantic/edit_planner.py +264 -0
- codepreproc_client/layer2_tooling/materialization/semantic/materializer.py +121 -0
- codepreproc_client/layer2_tooling/materialization/semantic/merger.py +73 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/add_import.py +36 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/append_argument.py +37 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/insert_literal.py +24 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_import.py +33 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_statement_unique.py +14 -0
- codepreproc_client/layer2_tooling/materialization/semantic/ops/rename_symbol_local.py +16 -0
- codepreproc_client/layer2_tooling/materialization/semantic/region_resolver.py +229 -0
- codepreproc_client/layer2_tooling/materialization/semantic/synthesizer.py +186 -0
- codepreproc_client/layer2_tooling/materialization/semantic/target_locator.py +94 -0
- codepreproc_client/layer2_tooling/materialization/semantic/validator.py +322 -0
- codepreproc_client/layer2_tooling/materialization/snippet/__init__.py +0 -0
- codepreproc_client/layer2_tooling/materialization/snippet/assembler.py +503 -0
- codepreproc_client/layer2_tooling/materialization/snippet/loader.py +187 -0
- codepreproc_client/layer2_tooling/retrieval/__init__.py +0 -0
- codepreproc_client/layer2_tooling/retrieval/graph_walk.py +47 -0
- codepreproc_client/layer2_tooling/retrieval/hybrid.py +95 -0
- codepreproc_client/layer2_tooling/retrieval/rerank_worker.py +86 -0
- codepreproc_client/layer2_tooling/retrieval/reranker.py +213 -0
- codepreproc_client/layer2_tooling/watcher/__init__.py +0 -0
- codepreproc_client/layer2_tooling/watcher/fs_watcher.py +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/METADATA +396 -0
- codrspot_processor_mcp-0.1.0.dist-info/RECORD +100 -0
- codrspot_processor_mcp-0.1.0.dist-info/WHEEL +5 -0
- codrspot_processor_mcp-0.1.0.dist-info/entry_points.txt +3 -0
- codrspot_processor_mcp-0.1.0.dist-info/licenses/LICENSE +23 -0
- codrspot_processor_mcp-0.1.0.dist-info/top_level.txt +2 -0
- contracts/__init__.py +0 -0
- contracts/dtos.py +1239 -0
- contracts/ir.py +102 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""AST-first chunking with safe fallback."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import xxhash
|
|
9
|
+
|
|
10
|
+
from codepreproc_client.layer2_tooling.condensation.chunking.languages import detect_language, get_chunk_query, get_language, get_parser
|
|
11
|
+
from codepreproc_client.layer1_business.git_utils import normalize_content
|
|
12
|
+
from contracts.dtos import ChunkingConfig, CodeChunk, ProjectConfig
|
|
13
|
+
|
|
14
|
+
_SYMBOL_RE = re.compile(r"\b([A-Za-z_][A-Za-z0-9_\.]{2,})\b")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AstChunker:
|
|
18
|
+
"""Chunk files using tree-sitter when possible, otherwise fallback to line blocks."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, chunking: ChunkingConfig) -> None:
|
|
21
|
+
self.chunking = chunking
|
|
22
|
+
|
|
23
|
+
def chunk_file(self, file_path: Path, project: ProjectConfig, git_sha: str) -> list[CodeChunk]:
|
|
24
|
+
"""Chunk a source file."""
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
if file_path.stat().st_size > self.chunking.max_file_size_kb * 1024:
|
|
28
|
+
return []
|
|
29
|
+
raw = file_path.read_bytes()
|
|
30
|
+
except OSError:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
if b"\x00" in raw:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
text = normalize_content(raw.decode("utf-8"))
|
|
38
|
+
except UnicodeDecodeError:
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
relative_path = str(file_path.resolve().relative_to(project.root.resolve()))
|
|
42
|
+
language = detect_language(file_path) or "text"
|
|
43
|
+
parser = get_parser(language)
|
|
44
|
+
query = get_chunk_query(language)
|
|
45
|
+
if parser is not None and query is not None:
|
|
46
|
+
chunks = self._chunk_with_tree_sitter(text, relative_path, language, project, git_sha, parser, query)
|
|
47
|
+
if chunks:
|
|
48
|
+
return chunks
|
|
49
|
+
return self._chunk_fallback(text, relative_path, language, project, git_sha)
|
|
50
|
+
|
|
51
|
+
def _chunk_with_tree_sitter(
|
|
52
|
+
self,
|
|
53
|
+
text: str,
|
|
54
|
+
relative_path: str,
|
|
55
|
+
language: str,
|
|
56
|
+
project: ProjectConfig,
|
|
57
|
+
git_sha: str,
|
|
58
|
+
parser: object,
|
|
59
|
+
query: object,
|
|
60
|
+
) -> list[CodeChunk]:
|
|
61
|
+
"""Best-effort tree-sitter chunking."""
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
from tree_sitter import QueryCursor
|
|
65
|
+
|
|
66
|
+
tree = parser.parse(text.encode("utf-8"))
|
|
67
|
+
cursor = QueryCursor(query)
|
|
68
|
+
raw_matches = list(cursor.matches(getattr(tree, "root_node")))
|
|
69
|
+
except Exception:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
# matches() yields (pattern_index, {capture_name: Node | [Node]}) —
|
|
73
|
+
# each dict entry is a single matched pattern, so chunk and symbol are
|
|
74
|
+
# guaranteed to correspond to each other.
|
|
75
|
+
matches: list[tuple[object, str | None]] = []
|
|
76
|
+
for _pat_idx, cap in raw_matches:
|
|
77
|
+
chunk = cap.get("chunk")
|
|
78
|
+
sym = cap.get("symbol")
|
|
79
|
+
if chunk is None:
|
|
80
|
+
continue
|
|
81
|
+
if isinstance(chunk, list):
|
|
82
|
+
chunk = chunk[0] if chunk else None
|
|
83
|
+
if isinstance(sym, list):
|
|
84
|
+
sym = sym[0] if sym else None
|
|
85
|
+
if chunk is None:
|
|
86
|
+
continue
|
|
87
|
+
try:
|
|
88
|
+
symbol_text: str | None = sym.text.decode("utf-8") if sym is not None else None
|
|
89
|
+
except Exception:
|
|
90
|
+
symbol_text = None
|
|
91
|
+
matches.append((chunk, symbol_text))
|
|
92
|
+
|
|
93
|
+
results: list[CodeChunk] = []
|
|
94
|
+
for node, symbol in matches:
|
|
95
|
+
try:
|
|
96
|
+
body = normalize_content(node.text.decode("utf-8"))
|
|
97
|
+
start_line = node.start_point.row + 1
|
|
98
|
+
except Exception:
|
|
99
|
+
continue
|
|
100
|
+
kind = self._kind_from_node_type(getattr(node, "type", "symbol"))
|
|
101
|
+
results.extend(
|
|
102
|
+
self._build_chunks(
|
|
103
|
+
text=body,
|
|
104
|
+
relative_path=relative_path,
|
|
105
|
+
language=language,
|
|
106
|
+
project_id=project.project_id,
|
|
107
|
+
symbol=symbol or f"{Path(relative_path).stem}:{start_line}",
|
|
108
|
+
kind=kind,
|
|
109
|
+
start_line=start_line,
|
|
110
|
+
git_sha=git_sha,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
return results
|
|
114
|
+
|
|
115
|
+
def _chunk_fallback(
|
|
116
|
+
self,
|
|
117
|
+
text: str,
|
|
118
|
+
relative_path: str,
|
|
119
|
+
language: str,
|
|
120
|
+
project: ProjectConfig,
|
|
121
|
+
git_sha: str,
|
|
122
|
+
) -> list[CodeChunk]:
|
|
123
|
+
"""Split a file into bounded blocks."""
|
|
124
|
+
|
|
125
|
+
lines = text.splitlines()
|
|
126
|
+
blocks: list[list[str]] = []
|
|
127
|
+
current: list[str] = []
|
|
128
|
+
current_len = 0
|
|
129
|
+
for line in lines:
|
|
130
|
+
line_len = len(line) + 1
|
|
131
|
+
should_break = current and (
|
|
132
|
+
current_len + line_len > self.chunking.max_chunk_chars
|
|
133
|
+
or (not line.strip() and current_len >= self.chunking.max_chunk_chars // 2)
|
|
134
|
+
)
|
|
135
|
+
if should_break:
|
|
136
|
+
blocks.append(current)
|
|
137
|
+
current = []
|
|
138
|
+
current_len = 0
|
|
139
|
+
current.append(line)
|
|
140
|
+
current_len += line_len
|
|
141
|
+
if current:
|
|
142
|
+
blocks.append(current)
|
|
143
|
+
|
|
144
|
+
chunks: list[CodeChunk] = []
|
|
145
|
+
cursor_line = 1
|
|
146
|
+
for index, block in enumerate(blocks, start=1):
|
|
147
|
+
body = "\n".join(block).strip("\n")
|
|
148
|
+
if not body.strip():
|
|
149
|
+
cursor_line += len(block)
|
|
150
|
+
continue
|
|
151
|
+
symbol = self._derive_symbol(relative_path, body, index)
|
|
152
|
+
chunks.extend(
|
|
153
|
+
self._build_chunks(
|
|
154
|
+
text=body,
|
|
155
|
+
relative_path=relative_path,
|
|
156
|
+
language=language,
|
|
157
|
+
project_id=project.project_id,
|
|
158
|
+
symbol=symbol,
|
|
159
|
+
kind="module",
|
|
160
|
+
start_line=cursor_line,
|
|
161
|
+
git_sha=git_sha,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
cursor_line += len(block)
|
|
165
|
+
return chunks
|
|
166
|
+
|
|
167
|
+
def _build_chunks(
|
|
168
|
+
self,
|
|
169
|
+
text: str,
|
|
170
|
+
relative_path: str,
|
|
171
|
+
language: str,
|
|
172
|
+
project_id: str,
|
|
173
|
+
symbol: str,
|
|
174
|
+
kind: str,
|
|
175
|
+
start_line: int,
|
|
176
|
+
git_sha: str,
|
|
177
|
+
) -> list[CodeChunk]:
|
|
178
|
+
"""Build one or more CodeChunk instances for bounded text."""
|
|
179
|
+
|
|
180
|
+
normalized = normalize_content(text)
|
|
181
|
+
if len(normalized) <= self.chunking.max_chunk_chars:
|
|
182
|
+
return [self._make_chunk(normalized, relative_path, language, project_id, symbol, kind, start_line, git_sha, 0)]
|
|
183
|
+
|
|
184
|
+
chunks: list[CodeChunk] = []
|
|
185
|
+
lines = normalized.splitlines()
|
|
186
|
+
current: list[str] = []
|
|
187
|
+
current_len = 0
|
|
188
|
+
block_start = start_line
|
|
189
|
+
sub_index = 0
|
|
190
|
+
for line in lines:
|
|
191
|
+
line_len = len(line) + 1
|
|
192
|
+
if current and current_len + line_len > self.chunking.max_chunk_chars:
|
|
193
|
+
body = "\n".join(current)
|
|
194
|
+
chunks.append(
|
|
195
|
+
self._make_chunk(body, relative_path, language, project_id, f"{symbol}#{sub_index}", kind, block_start, git_sha, len(current) - 1)
|
|
196
|
+
)
|
|
197
|
+
sub_index += 1
|
|
198
|
+
block_start += len(current)
|
|
199
|
+
current = []
|
|
200
|
+
current_len = 0
|
|
201
|
+
current.append(line)
|
|
202
|
+
current_len += line_len
|
|
203
|
+
if current:
|
|
204
|
+
body = "\n".join(current)
|
|
205
|
+
chunks.append(
|
|
206
|
+
self._make_chunk(body, relative_path, language, project_id, f"{symbol}#{sub_index}", kind, block_start, git_sha, len(current) - 1)
|
|
207
|
+
)
|
|
208
|
+
return chunks
|
|
209
|
+
|
|
210
|
+
def _make_chunk(
|
|
211
|
+
self,
|
|
212
|
+
body: str,
|
|
213
|
+
relative_path: str,
|
|
214
|
+
language: str,
|
|
215
|
+
project_id: str,
|
|
216
|
+
symbol: str,
|
|
217
|
+
kind: str,
|
|
218
|
+
start_line: int,
|
|
219
|
+
git_sha: str,
|
|
220
|
+
relative_end_offset: int,
|
|
221
|
+
) -> CodeChunk:
|
|
222
|
+
"""Create a chunk model."""
|
|
223
|
+
|
|
224
|
+
signature = next((line.strip() for line in body.splitlines() if line.strip()), None)
|
|
225
|
+
content_hash = xxhash.xxh64_hexdigest(f"{relative_path}\n{symbol}\n{body}")
|
|
226
|
+
deps = sorted({token for token in _SYMBOL_RE.findall(body) if token != symbol})[:32]
|
|
227
|
+
return CodeChunk(
|
|
228
|
+
chunk_id=content_hash,
|
|
229
|
+
project_id=project_id,
|
|
230
|
+
file_path=relative_path,
|
|
231
|
+
language=language,
|
|
232
|
+
symbol=symbol,
|
|
233
|
+
kind=kind,
|
|
234
|
+
start_line=start_line,
|
|
235
|
+
end_line=start_line + relative_end_offset,
|
|
236
|
+
body=body,
|
|
237
|
+
signature=signature,
|
|
238
|
+
deps=deps,
|
|
239
|
+
git_sha=git_sha,
|
|
240
|
+
content_hash=content_hash,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _kind_from_node_type(node_type: str) -> str:
|
|
245
|
+
_EXACT: dict[str, str] = {
|
|
246
|
+
# JavaScript / TypeScript
|
|
247
|
+
"class_declaration": "class",
|
|
248
|
+
"abstract_class_declaration": "class",
|
|
249
|
+
"interface_declaration": "class",
|
|
250
|
+
"enum_declaration": "class",
|
|
251
|
+
"type_alias_declaration": "class",
|
|
252
|
+
"function_declaration": "function",
|
|
253
|
+
"arrow_function": "function",
|
|
254
|
+
"function_expression": "function",
|
|
255
|
+
"generator_function_declaration": "function",
|
|
256
|
+
"lexical_declaration": "function", # const foo = () => {}
|
|
257
|
+
"method_definition": "method",
|
|
258
|
+
# Python
|
|
259
|
+
"function_definition": "function",
|
|
260
|
+
"class_definition": "class",
|
|
261
|
+
# Go
|
|
262
|
+
"method_declaration": "method",
|
|
263
|
+
"type_declaration": "class",
|
|
264
|
+
# Rust
|
|
265
|
+
"function_item": "function",
|
|
266
|
+
"struct_item": "class",
|
|
267
|
+
"impl_item": "class",
|
|
268
|
+
# C#
|
|
269
|
+
"record_declaration": "class",
|
|
270
|
+
"constructor_declaration": "method",
|
|
271
|
+
"namespace_declaration": "module",
|
|
272
|
+
"file_scoped_namespace_declaration": "module",
|
|
273
|
+
# C
|
|
274
|
+
"struct_specifier": "class",
|
|
275
|
+
"enum_specifier": "class",
|
|
276
|
+
"type_definition": "class",
|
|
277
|
+
"preproc_function_def": "function",
|
|
278
|
+
}
|
|
279
|
+
if node_type in _EXACT:
|
|
280
|
+
return _EXACT[node_type]
|
|
281
|
+
# Fallback: substring signals
|
|
282
|
+
if "class" in node_type or "struct" in node_type or "interface" in node_type or "enum" in node_type:
|
|
283
|
+
return "class"
|
|
284
|
+
if "method" in node_type:
|
|
285
|
+
return "method"
|
|
286
|
+
if "function" in node_type:
|
|
287
|
+
return "function"
|
|
288
|
+
return "module"
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _derive_symbol(relative_path: str, body: str, index: int) -> str:
|
|
292
|
+
# Python / Go / Rust / generic: def foo, class Foo, func Foo, fn foo
|
|
293
|
+
m = re.search(
|
|
294
|
+
r"^\s*(?:export\s+(?:default\s+)?)?(?:async\s+)?(?:def|class|func|fn|function\*?)\s+([A-Za-z_$][A-Za-z0-9_$]*)",
|
|
295
|
+
body, flags=re.MULTILINE,
|
|
296
|
+
)
|
|
297
|
+
if m:
|
|
298
|
+
return m.group(1)
|
|
299
|
+
# JS/TS: const/let/var foo = ... (arrow fn, function expr, class expr)
|
|
300
|
+
m = re.search(
|
|
301
|
+
r"^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*[=:]",
|
|
302
|
+
body, flags=re.MULTILINE,
|
|
303
|
+
)
|
|
304
|
+
if m:
|
|
305
|
+
return m.group(1)
|
|
306
|
+
# TypeScript-specific declarations: interface, type, enum, abstract class
|
|
307
|
+
m = re.search(
|
|
308
|
+
r"^\s*(?:export\s+)?(?:interface|type|enum|abstract\s+class)\s+([A-Za-z_$][A-Za-z0-9_$]*)",
|
|
309
|
+
body, flags=re.MULTILINE,
|
|
310
|
+
)
|
|
311
|
+
if m:
|
|
312
|
+
return m.group(1)
|
|
313
|
+
return f"{Path(relative_path).stem}:block_{index}"
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Language detection and tree-sitter query helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
LANGUAGE_EXTENSIONS: dict[str, tuple[str, ...]] = {
|
|
10
|
+
"python": (".py",),
|
|
11
|
+
"typescript": (".ts",),
|
|
12
|
+
"tsx": (".tsx",),
|
|
13
|
+
"javascript": (".js", ".mjs", ".cjs", ".jsx"),
|
|
14
|
+
"go": (".go",),
|
|
15
|
+
"rust": (".rs",),
|
|
16
|
+
"c": (".c", ".h"),
|
|
17
|
+
"csharp": (".cs",),
|
|
18
|
+
"java": (".java",),
|
|
19
|
+
"php": (".php", ".phtml"),
|
|
20
|
+
"yaml": (".yaml", ".yml"),
|
|
21
|
+
"markdown": (".md",),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Arrow-function-in-const pattern reused across JS/TS grammars.
|
|
25
|
+
_JS_ARROW_CONST = """
|
|
26
|
+
(lexical_declaration
|
|
27
|
+
(variable_declarator name: (identifier) @symbol value: (arrow_function))
|
|
28
|
+
) @chunk
|
|
29
|
+
(lexical_declaration
|
|
30
|
+
(variable_declarator name: (identifier) @symbol value: (function_expression))
|
|
31
|
+
) @chunk
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
QUERY_STRINGS: dict[str, str] = {
|
|
35
|
+
"python": """
|
|
36
|
+
(function_definition name: (identifier) @symbol) @chunk
|
|
37
|
+
(class_definition name: (identifier) @symbol) @chunk
|
|
38
|
+
""",
|
|
39
|
+
"typescript": """
|
|
40
|
+
(function_declaration name: (identifier) @symbol) @chunk
|
|
41
|
+
(class_declaration name: (type_identifier) @symbol) @chunk
|
|
42
|
+
(method_definition name: (property_identifier) @symbol) @chunk
|
|
43
|
+
(interface_declaration name: (type_identifier) @symbol) @chunk
|
|
44
|
+
(enum_declaration name: (identifier) @symbol) @chunk
|
|
45
|
+
(type_alias_declaration name: (type_identifier) @symbol) @chunk
|
|
46
|
+
""" + _JS_ARROW_CONST,
|
|
47
|
+
"tsx": """
|
|
48
|
+
(function_declaration name: (identifier) @symbol) @chunk
|
|
49
|
+
(class_declaration name: (type_identifier) @symbol) @chunk
|
|
50
|
+
(method_definition name: (property_identifier) @symbol) @chunk
|
|
51
|
+
(interface_declaration name: (type_identifier) @symbol) @chunk
|
|
52
|
+
(enum_declaration name: (identifier) @symbol) @chunk
|
|
53
|
+
""" + _JS_ARROW_CONST,
|
|
54
|
+
"javascript": """
|
|
55
|
+
(function_declaration name: (identifier) @symbol) @chunk
|
|
56
|
+
(class_declaration name: (identifier) @symbol) @chunk
|
|
57
|
+
(method_definition name: (property_identifier) @symbol) @chunk
|
|
58
|
+
""" + _JS_ARROW_CONST,
|
|
59
|
+
"go": """
|
|
60
|
+
(function_declaration name: (identifier) @symbol) @chunk
|
|
61
|
+
(method_declaration name: (field_identifier) @symbol) @chunk
|
|
62
|
+
(type_declaration (type_spec name: (type_identifier) @symbol)) @chunk
|
|
63
|
+
""",
|
|
64
|
+
"rust": """
|
|
65
|
+
(function_item name: (identifier) @symbol) @chunk
|
|
66
|
+
(struct_item name: (type_identifier) @symbol) @chunk
|
|
67
|
+
(impl_item type: (type_identifier) @symbol) @chunk
|
|
68
|
+
""",
|
|
69
|
+
"csharp": """
|
|
70
|
+
(class_declaration name: (identifier) @symbol) @chunk
|
|
71
|
+
(interface_declaration name: (identifier) @symbol) @chunk
|
|
72
|
+
(method_declaration name: (identifier) @symbol) @chunk
|
|
73
|
+
(constructor_declaration name: (identifier) @symbol) @chunk
|
|
74
|
+
(struct_declaration name: (identifier) @symbol) @chunk
|
|
75
|
+
(enum_declaration name: (identifier) @symbol) @chunk
|
|
76
|
+
(record_declaration name: (identifier) @symbol) @chunk
|
|
77
|
+
(namespace_declaration name: (qualified_name) @symbol) @chunk
|
|
78
|
+
(namespace_declaration name: (identifier) @symbol) @chunk
|
|
79
|
+
""",
|
|
80
|
+
"java": """
|
|
81
|
+
(class_declaration name: (identifier) @symbol) @chunk
|
|
82
|
+
(interface_declaration name: (identifier) @symbol) @chunk
|
|
83
|
+
(enum_declaration name: (identifier) @symbol) @chunk
|
|
84
|
+
(method_declaration name: (identifier) @symbol) @chunk
|
|
85
|
+
(constructor_declaration name: (identifier) @symbol) @chunk
|
|
86
|
+
""",
|
|
87
|
+
"php": """
|
|
88
|
+
(function_definition name: (name) @symbol) @chunk
|
|
89
|
+
(class_declaration name: (name) @symbol) @chunk
|
|
90
|
+
(method_declaration name: (name) @symbol) @chunk
|
|
91
|
+
(interface_declaration name: (name) @symbol) @chunk
|
|
92
|
+
(trait_declaration name: (name) @symbol) @chunk
|
|
93
|
+
(enum_declaration name: (name) @symbol) @chunk
|
|
94
|
+
""",
|
|
95
|
+
"c": """
|
|
96
|
+
(function_definition
|
|
97
|
+
declarator: (function_declarator
|
|
98
|
+
declarator: (identifier) @symbol)) @chunk
|
|
99
|
+
(function_definition
|
|
100
|
+
declarator: (pointer_declarator
|
|
101
|
+
declarator: (function_declarator
|
|
102
|
+
declarator: (identifier) @symbol))) @chunk
|
|
103
|
+
(struct_specifier
|
|
104
|
+
name: (type_identifier) @symbol
|
|
105
|
+
body: (_)) @chunk
|
|
106
|
+
(enum_specifier
|
|
107
|
+
name: (type_identifier) @symbol
|
|
108
|
+
body: (_)) @chunk
|
|
109
|
+
(type_definition
|
|
110
|
+
declarator: (type_identifier) @symbol) @chunk
|
|
111
|
+
(preproc_function_def
|
|
112
|
+
name: (identifier) @symbol) @chunk
|
|
113
|
+
""",
|
|
114
|
+
"yaml": "",
|
|
115
|
+
"markdown": "",
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def detect_language(file_path: Path) -> str | None:
|
|
120
|
+
"""Return a supported language name from extension."""
|
|
121
|
+
|
|
122
|
+
suffix = file_path.suffix.lower()
|
|
123
|
+
for language, extensions in LANGUAGE_EXTENSIONS.items():
|
|
124
|
+
if suffix in extensions:
|
|
125
|
+
return language
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@lru_cache(maxsize=None)
|
|
130
|
+
def get_parser(language: str) -> Any | None:
|
|
131
|
+
"""Return a tree-sitter parser when available."""
|
|
132
|
+
|
|
133
|
+
if language not in QUERY_STRINGS or not QUERY_STRINGS[language]:
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
from tree_sitter_language_pack import get_parser as ts_get_parser
|
|
137
|
+
return ts_get_parser(language)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
try:
|
|
141
|
+
from tree_sitter_languages import get_parser as ts_get_parser
|
|
142
|
+
return ts_get_parser(language)
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
# Fallback: build a parser from the DLL-loaded language when the
|
|
146
|
+
# tree_sitter_languages package API is incompatible with the installed
|
|
147
|
+
# tree-sitter binding version.
|
|
148
|
+
lang = _get_language_via_dll(language)
|
|
149
|
+
if lang is None:
|
|
150
|
+
return None
|
|
151
|
+
try:
|
|
152
|
+
from tree_sitter import Parser
|
|
153
|
+
return Parser(lang)
|
|
154
|
+
except Exception:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@lru_cache(maxsize=None)
|
|
159
|
+
def _get_language_via_dll(language: str) -> Any | None:
|
|
160
|
+
"""Load a tree-sitter Language directly from tree_sitter_languages' bundled library.
|
|
161
|
+
|
|
162
|
+
Needed when tree_sitter_languages was compiled against an older tree-sitter ABI
|
|
163
|
+
(pre-0.22) but the installed tree-sitter Python binding is 0.22+, which changed
|
|
164
|
+
the Language() constructor. We bypass tree_sitter_languages.get_language() and
|
|
165
|
+
call the underlying C symbol ourselves.
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
import ctypes
|
|
169
|
+
import warnings
|
|
170
|
+
from pathlib import Path as _Path
|
|
171
|
+
import tree_sitter_languages as _tsl
|
|
172
|
+
pkg_dir = _Path(_tsl.__file__).parent
|
|
173
|
+
# Find the bundled native library (name varies by platform).
|
|
174
|
+
for candidate in ("languages.dll", "languages.so", "languages.dylib"):
|
|
175
|
+
lib_path = pkg_dir / candidate
|
|
176
|
+
if lib_path.exists():
|
|
177
|
+
break
|
|
178
|
+
else:
|
|
179
|
+
# Try .pyd / .so with version suffix (Linux wheels)
|
|
180
|
+
matches = list(pkg_dir.glob("languages*.so")) + list(pkg_dir.glob("languages*.pyd"))
|
|
181
|
+
if not matches:
|
|
182
|
+
return None
|
|
183
|
+
lib_path = matches[0]
|
|
184
|
+
|
|
185
|
+
lib = ctypes.CDLL(str(lib_path))
|
|
186
|
+
fn_name = f"tree_sitter_{language.replace('-', '_')}"
|
|
187
|
+
fn = getattr(lib, fn_name, None)
|
|
188
|
+
if fn is None:
|
|
189
|
+
return None
|
|
190
|
+
fn.restype = ctypes.c_void_p
|
|
191
|
+
ptr = fn()
|
|
192
|
+
if not ptr:
|
|
193
|
+
return None
|
|
194
|
+
from tree_sitter import Language
|
|
195
|
+
with warnings.catch_warnings():
|
|
196
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
197
|
+
return Language(ptr)
|
|
198
|
+
except Exception:
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@lru_cache(maxsize=None)
|
|
203
|
+
def get_language(language: str) -> Any | None:
|
|
204
|
+
"""Return the tree-sitter Language object for query compilation."""
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
from tree_sitter_language_pack import get_language as ts_get_language
|
|
208
|
+
return ts_get_language(language)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
try:
|
|
212
|
+
from tree_sitter_languages import get_language as ts_get_language
|
|
213
|
+
return ts_get_language(language)
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
return _get_language_via_dll(language)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@lru_cache(maxsize=None)
|
|
220
|
+
def get_chunk_query(language: str) -> Any | None:
|
|
221
|
+
"""Return a compiled tree-sitter Query when available."""
|
|
222
|
+
|
|
223
|
+
query_str = QUERY_STRINGS.get(language, "")
|
|
224
|
+
if not query_str:
|
|
225
|
+
return None
|
|
226
|
+
lang = get_language(language)
|
|
227
|
+
if lang is None:
|
|
228
|
+
return None
|
|
229
|
+
try:
|
|
230
|
+
from tree_sitter import Query
|
|
231
|
+
return Query(lang, query_str)
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
# Fallback for older tree-sitter where lang.query() still works
|
|
235
|
+
try:
|
|
236
|
+
return lang.query(query_str)
|
|
237
|
+
except Exception:
|
|
238
|
+
return None
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Embedding subprocess worker.
|
|
2
|
+
|
|
3
|
+
Launched as ``python -u -m codepreproc_client.layer2_tooling.condensation.embed_worker``.
|
|
4
|
+
Communicates with the parent via stdin/stdout JSON lines.
|
|
5
|
+
|
|
6
|
+
Protocol
|
|
7
|
+
--------
|
|
8
|
+
Parent → worker (stdin):
|
|
9
|
+
First line: {"model_name": "...", "device": "..."}
|
|
10
|
+
Subsequent lines: {"type": "encode", "request_id": "...", "texts": [...], "batch_size": N}
|
|
11
|
+
or {"type": "shutdown"}
|
|
12
|
+
|
|
13
|
+
Worker → parent (stdout):
|
|
14
|
+
{"type": "worker_ready"}
|
|
15
|
+
{"type": "worker_failed", "error": "..."}
|
|
16
|
+
{"type": "batch_done", "request_id": "...", "vectors": [[...]], "start": N, "end": N, "total_texts": N}
|
|
17
|
+
{"type": "batch_resized", "request_id": "...", "batch_size": N, "reason": "oom"}
|
|
18
|
+
{"type": "completed", "request_id": "...", "total_texts": N}
|
|
19
|
+
{"type": "failed", "request_id": "...", "error": "..."}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _send(event: dict) -> None:
|
|
31
|
+
sys.stdout.write(json.dumps(event) + "\n")
|
|
32
|
+
sys.stdout.flush()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _coerce_batch_vectors(vectors, *, expected_rows: int) -> list[list[float]]:
|
|
36
|
+
array = np.asarray(vectors, dtype=np.float32)
|
|
37
|
+
if expected_rows == 0:
|
|
38
|
+
return []
|
|
39
|
+
if array.ndim == 1:
|
|
40
|
+
if expected_rows != 1:
|
|
41
|
+
raise ValueError(f"embedding_batch_shape_invalid: got {array.shape} for {expected_rows} texts")
|
|
42
|
+
array = array.reshape(1, -1)
|
|
43
|
+
if array.ndim != 2:
|
|
44
|
+
raise ValueError(f"embedding_batch_shape_invalid: got {array.shape} for {expected_rows} texts")
|
|
45
|
+
if array.shape[0] != expected_rows:
|
|
46
|
+
raise ValueError(f"embedding_batch_row_mismatch: got {array.shape[0]} rows for {expected_rows} texts")
|
|
47
|
+
return array.tolist()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> None:
|
|
51
|
+
config_line = sys.stdin.readline()
|
|
52
|
+
if not config_line:
|
|
53
|
+
return
|
|
54
|
+
try:
|
|
55
|
+
config = json.loads(config_line)
|
|
56
|
+
except json.JSONDecodeError as exc:
|
|
57
|
+
_send({"type": "worker_failed", "error": f"bad config line: {exc}"})
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
model_name = config["model_name"]
|
|
61
|
+
device = config["device"]
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
from sentence_transformers import SentenceTransformer
|
|
65
|
+
|
|
66
|
+
model = SentenceTransformer(model_name, device=device)
|
|
67
|
+
_send({"type": "worker_ready"})
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
_send({"type": "worker_failed", "error": str(exc)})
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
for raw_line in sys.stdin:
|
|
73
|
+
line = raw_line.strip()
|
|
74
|
+
if not line:
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
command = json.loads(line)
|
|
78
|
+
except json.JSONDecodeError:
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if command["type"] == "shutdown":
|
|
82
|
+
return
|
|
83
|
+
if command["type"] != "encode":
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
request_id = command["request_id"]
|
|
87
|
+
texts = command["texts"]
|
|
88
|
+
current_batch = max(1, int(command["batch_size"]))
|
|
89
|
+
offset = 0
|
|
90
|
+
try:
|
|
91
|
+
while offset < len(texts):
|
|
92
|
+
upper = min(len(texts), offset + current_batch)
|
|
93
|
+
batch = texts[offset:upper]
|
|
94
|
+
try:
|
|
95
|
+
vectors = model.encode(
|
|
96
|
+
batch,
|
|
97
|
+
batch_size=current_batch,
|
|
98
|
+
show_progress_bar=False,
|
|
99
|
+
convert_to_numpy=True,
|
|
100
|
+
normalize_embeddings=True,
|
|
101
|
+
)
|
|
102
|
+
except RuntimeError as exc:
|
|
103
|
+
if "out of memory" not in str(exc).lower() or current_batch == 1:
|
|
104
|
+
raise
|
|
105
|
+
current_batch = max(1, current_batch // 2)
|
|
106
|
+
_send({
|
|
107
|
+
"type": "batch_resized",
|
|
108
|
+
"request_id": request_id,
|
|
109
|
+
"batch_size": current_batch,
|
|
110
|
+
"reason": "oom",
|
|
111
|
+
})
|
|
112
|
+
continue
|
|
113
|
+
_send({
|
|
114
|
+
"type": "batch_done",
|
|
115
|
+
"request_id": request_id,
|
|
116
|
+
"vectors": _coerce_batch_vectors(vectors, expected_rows=len(batch)),
|
|
117
|
+
"start": offset,
|
|
118
|
+
"end": upper,
|
|
119
|
+
"total_texts": len(texts),
|
|
120
|
+
})
|
|
121
|
+
offset = upper
|
|
122
|
+
_send({"type": "completed", "request_id": request_id, "total_texts": len(texts)})
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
_send({"type": "failed", "request_id": request_id, "error": str(exc)})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|