@functional-systems/srcx 0.1.0-dev.19 → 0.1.0-dev.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/DESIGN.md +20 -1
- package/README.md +49 -6
- package/VALIDATION.md +53 -0
- package/dist/build.d.ts +1 -1
- package/dist/build.js +1 -1
- package/dist/build.js.map +1 -1
- package/dist/chunk.d.ts +2 -1
- package/dist/chunk.js +313 -4
- package/dist/chunk.js.map +1 -1
- package/dist/rerank.js +24 -5
- package/dist/rerank.js.map +1 -1
- package/package.json +3 -2
- package/runtime/grammars/README.md +26 -0
- package/runtime/grammars/tree-sitter-sql.LICENSE +21 -0
- package/runtime/grammars/tree-sitter-sql.wasm +0 -0
- package/runtime/grammars/tree-sitter-wasm.LICENSE +21 -0
- package/runtime/qwen.py +50 -15
- package/src/build.ts +1 -1
- package/src/chunk.ts +327 -4
- package/src/rerank.ts +24 -8
package/runtime/qwen.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import math
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
MODEL = "Qwen/Qwen3-Reranker-0.6B"
|
|
9
10
|
REVISION = "e61197ed45024b0ed8a2d74b80b4d909f1255473"
|
|
@@ -14,6 +15,19 @@ PREFIX = (
|
|
|
14
15
|
)
|
|
15
16
|
SUFFIX = "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n"
|
|
16
17
|
INSTRUCTION = "Given a code search query, retrieve code passages that answer the query."
|
|
18
|
+
CACHE_FILES = ["config.json", "generation_config.json", "model.safetensors",
|
|
19
|
+
"tokenizer.json", "tokenizer_config.json", "vocab.json", "merges.txt"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkerFailure(ValueError):
|
|
23
|
+
"""Exit codes: 20 imports, 21 cache, 22 device, 23 pair tokens, 24 request bytes.
|
|
24
|
+
|
|
25
|
+
The Node caller maps codes to messages; exception text never crosses the boundary.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, code):
|
|
29
|
+
super().__init__("Qwen worker failure")
|
|
30
|
+
self.code = code
|
|
17
31
|
|
|
18
32
|
|
|
19
33
|
def require(condition, message):
|
|
@@ -25,7 +39,8 @@ def encode(tokenizer, query, text):
|
|
|
25
39
|
body = f"<Instruct>: {INSTRUCTION}\n<Query>: {query}\n<Document>: {text}"
|
|
26
40
|
ids = sum((tokenizer.encode(p, add_special_tokens=False)
|
|
27
41
|
for p in (PREFIX, body, SUFFIX)), [])
|
|
28
|
-
|
|
42
|
+
if len(ids) > 8192:
|
|
43
|
+
raise WorkerFailure(23)
|
|
29
44
|
return ids
|
|
30
45
|
|
|
31
46
|
|
|
@@ -45,22 +60,33 @@ def validate(body):
|
|
|
45
60
|
|
|
46
61
|
def score(body):
|
|
47
62
|
os.environ["HF_HUB_OFFLINE"] = "1"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
device = os.environ.get("SRCX_RERANK_DEVICE", "auto")
|
|
64
|
+
if device not in ("auto", "cpu", "mps", "cuda"):
|
|
65
|
+
raise WorkerFailure(22)
|
|
66
|
+
try:
|
|
67
|
+
from huggingface_hub import snapshot_download
|
|
68
|
+
from huggingface_hub.errors import LocalEntryNotFoundError
|
|
69
|
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
|
70
|
+
import torch
|
|
71
|
+
except ImportError:
|
|
72
|
+
raise WorkerFailure(20) from None
|
|
73
|
+
if device == "auto":
|
|
74
|
+
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
|
|
75
|
+
elif ((device == "cuda" and not torch.cuda.is_available()) or
|
|
76
|
+
(device == "mps" and not torch.backends.mps.is_available())):
|
|
77
|
+
raise WorkerFailure(22)
|
|
78
|
+
try:
|
|
79
|
+
path = snapshot_download(MODEL, revision=REVISION, local_files_only=True,
|
|
80
|
+
allow_patterns=CACHE_FILES)
|
|
81
|
+
except LocalEntryNotFoundError:
|
|
82
|
+
raise WorkerFailure(21) from None
|
|
83
|
+
if not all((Path(path) / name).is_file() for name in CACHE_FILES):
|
|
84
|
+
raise WorkerFailure(21)
|
|
55
85
|
tokenizer = AutoTokenizer.from_pretrained(path, local_files_only=True, trust_remote_code=False)
|
|
56
86
|
# Reuse exact duplicate code within this query, retaining every candidate ID.
|
|
57
87
|
encoded = {c["text"]: None for c in body["candidates"]}
|
|
58
88
|
for text in encoded:
|
|
59
89
|
encoded[text] = encode(tokenizer, body["query"], text)
|
|
60
|
-
device = os.environ.get("SRCX_RERANK_DEVICE", "auto")
|
|
61
|
-
require(device in ("auto", "cpu", "mps", "cuda"), "Invalid device")
|
|
62
|
-
if device == "auto":
|
|
63
|
-
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
|
|
64
90
|
model = AutoModelForCausalLM.from_pretrained(
|
|
65
91
|
path, local_files_only=True, trust_remote_code=False,
|
|
66
92
|
dtype=torch.float32, attn_implementation="sdpa",
|
|
@@ -83,15 +109,24 @@ def score(body):
|
|
|
83
109
|
|
|
84
110
|
def main():
|
|
85
111
|
raw = sys.stdin.buffer.read(4 * 1024 * 1024 + 1)
|
|
86
|
-
|
|
112
|
+
if len(raw) > 4 * 1024 * 1024:
|
|
113
|
+
raise WorkerFailure(24)
|
|
87
114
|
body = validate(json.loads(raw))
|
|
88
115
|
print(json.dumps(score(body), allow_nan=False))
|
|
89
116
|
|
|
90
117
|
|
|
91
|
-
|
|
118
|
+
def run():
|
|
92
119
|
try:
|
|
93
120
|
main()
|
|
121
|
+
except WorkerFailure as error:
|
|
122
|
+
# The Node caller maps these codes to fixed, actionable messages.
|
|
123
|
+
return error.code
|
|
94
124
|
except Exception:
|
|
95
125
|
# Libraries may include source text or environment values in exceptions.
|
|
96
126
|
print("Qwen worker failed; verify setup, device and input size.", file=sys.stderr)
|
|
97
|
-
|
|
127
|
+
return 1
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
sys.exit(run())
|
package/src/build.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const PRESET: Preset = {
|
|
|
46
46
|
maxFileBytes: 1024 * 1024,
|
|
47
47
|
embedding: null,
|
|
48
48
|
};
|
|
49
|
-
/**
|
|
49
|
+
/** Embedding variants share the same chunking behavior. */
|
|
50
50
|
export const MANAGED_PRESET: Preset = {
|
|
51
51
|
...PRESET,
|
|
52
52
|
embedding: {
|
package/src/chunk.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { Parser, Language, type Node } from "web-tree-sitter";
|
|
3
4
|
import { getEncoding } from "js-tiktoken";
|
|
@@ -8,9 +9,11 @@ export function tokens(text: string): number {
|
|
|
8
9
|
return encoding.encode(text, [], []).length;
|
|
9
10
|
}
|
|
10
11
|
export const CHUNKER = {
|
|
11
|
-
version:
|
|
12
|
+
version: 2,
|
|
12
13
|
parser: "web-tree-sitter@0.25.10",
|
|
13
14
|
grammars: "tree-sitter-wasms@0.1.13",
|
|
15
|
+
sqlGrammar:
|
|
16
|
+
"tree-sitter-wasm@2.0.2/sql/b77530893b1dd6d1d4814eacf34d25bcbe4a12ec9a25a8c45b320d447265f42b",
|
|
14
17
|
tokenizer: "js-tiktoken@1.0.21/cl100k_base",
|
|
15
18
|
targetTokens: 800,
|
|
16
19
|
maxTokens: 1500,
|
|
@@ -46,17 +49,42 @@ async function language(name: string): Promise<Language> {
|
|
|
46
49
|
let l = languages.get(name);
|
|
47
50
|
if (!l) {
|
|
48
51
|
l = await Language.load(
|
|
49
|
-
|
|
52
|
+
name === "sql"
|
|
53
|
+
? fileURLToPath(
|
|
54
|
+
new URL(
|
|
55
|
+
"../runtime/grammars/tree-sitter-sql.wasm",
|
|
56
|
+
import.meta.url,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
: require.resolve(
|
|
60
|
+
`tree-sitter-wasms/out/tree-sitter-${name === "shell" ? "bash" : name}.wasm`,
|
|
61
|
+
),
|
|
50
62
|
);
|
|
51
63
|
languages.set(name, l);
|
|
52
64
|
}
|
|
53
65
|
return l;
|
|
54
66
|
}
|
|
55
67
|
export function detectLanguage(path: string): string {
|
|
68
|
+
if (path.endsWith(".C")) return "cpp";
|
|
56
69
|
const ext = path.split(".").at(-1)?.toLowerCase();
|
|
57
70
|
return (
|
|
58
71
|
(
|
|
59
72
|
{
|
|
73
|
+
py: "python",
|
|
74
|
+
pyi: "python",
|
|
75
|
+
go: "go",
|
|
76
|
+
rs: "rust",
|
|
77
|
+
c: "c",
|
|
78
|
+
h: "c",
|
|
79
|
+
cc: "cpp",
|
|
80
|
+
cpp: "cpp",
|
|
81
|
+
cxx: "cpp",
|
|
82
|
+
hh: "cpp",
|
|
83
|
+
hpp: "cpp",
|
|
84
|
+
hxx: "cpp",
|
|
85
|
+
sh: "shell",
|
|
86
|
+
bash: "shell",
|
|
87
|
+
sql: "sql",
|
|
60
88
|
java: "java",
|
|
61
89
|
ts: "typescript",
|
|
62
90
|
tsx: "tsx",
|
|
@@ -102,6 +130,278 @@ function scopeName(n: Node): string | undefined {
|
|
|
102
130
|
}
|
|
103
131
|
return names.length ? names.join(".") : undefined;
|
|
104
132
|
}
|
|
133
|
+
|
|
134
|
+
/** Keep complete functions (including decorators) before token-bound splitting. */
|
|
135
|
+
function pythonUnits(root: Node): Unit[] {
|
|
136
|
+
const units: Unit[] = [];
|
|
137
|
+
function visit(n: Node, scope?: string): void {
|
|
138
|
+
const definition = n.childForFieldName("definition") ?? n;
|
|
139
|
+
const body = definition.childForFieldName("body");
|
|
140
|
+
const symbol = definition.childForFieldName("name")?.text.slice(0, 200);
|
|
141
|
+
const unit: Unit = {
|
|
142
|
+
start: n.startIndex,
|
|
143
|
+
end: n.endIndex,
|
|
144
|
+
kind: classify(definition.type),
|
|
145
|
+
scope,
|
|
146
|
+
symbol,
|
|
147
|
+
};
|
|
148
|
+
if (definition.type === "class_definition" && body) {
|
|
149
|
+
units.push({ ...unit, end: body.startIndex });
|
|
150
|
+
const nested = [scope, symbol].filter(Boolean).join(".");
|
|
151
|
+
for (const child of body.namedChildren) if (child) visit(child, nested);
|
|
152
|
+
} else {
|
|
153
|
+
if (definition.type === "function_definition" && body)
|
|
154
|
+
unit.signature = definition.text
|
|
155
|
+
.slice(0, body.startIndex - definition.startIndex)
|
|
156
|
+
.trim()
|
|
157
|
+
.slice(0, 240);
|
|
158
|
+
if (
|
|
159
|
+
definition.type === "expression_statement" &&
|
|
160
|
+
definition.firstNamedChild?.type === "string"
|
|
161
|
+
)
|
|
162
|
+
unit.kind = "documentation";
|
|
163
|
+
units.push(unit);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const n of root.namedChildren) if (n) visit(n);
|
|
167
|
+
return units;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function goUnits(root: Node): Unit[] {
|
|
171
|
+
const units: Unit[] = [];
|
|
172
|
+
for (const n of root.namedChildren) {
|
|
173
|
+
if (!n) continue;
|
|
174
|
+
if (n.type === "type_declaration") {
|
|
175
|
+
// Include the type keyword and grouped declaration delimiters in coverage.
|
|
176
|
+
const specs = n.namedChildren.filter(
|
|
177
|
+
(node): node is Node => node !== null,
|
|
178
|
+
);
|
|
179
|
+
for (const [i, spec] of specs.entries())
|
|
180
|
+
units.push({
|
|
181
|
+
start: i === 0 ? n.startIndex : spec.startIndex,
|
|
182
|
+
end: i === specs.length - 1 ? n.endIndex : spec.endIndex,
|
|
183
|
+
kind: classify(spec.type),
|
|
184
|
+
symbol: spec.childForFieldName("name")?.text.slice(0, 200),
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const unit: Unit = {
|
|
189
|
+
start: n.startIndex,
|
|
190
|
+
end: n.endIndex,
|
|
191
|
+
kind: classify(n.type),
|
|
192
|
+
symbol: n.childForFieldName("name")?.text.slice(0, 200),
|
|
193
|
+
};
|
|
194
|
+
let receiver = n
|
|
195
|
+
.childForFieldName("receiver")
|
|
196
|
+
?.firstNamedChild?.childForFieldName("type");
|
|
197
|
+
if (receiver?.type === "pointer_type") receiver = receiver.firstNamedChild;
|
|
198
|
+
if (receiver?.type === "generic_type")
|
|
199
|
+
receiver = receiver.childForFieldName("type");
|
|
200
|
+
if (receiver) unit.scope = receiver.text.slice(0, 160);
|
|
201
|
+
const body = n.childForFieldName("body");
|
|
202
|
+
if (unit.kind === "function")
|
|
203
|
+
unit.signature = n.text
|
|
204
|
+
.slice(0, body ? body.startIndex - n.startIndex : undefined)
|
|
205
|
+
.trim()
|
|
206
|
+
.slice(0, 240);
|
|
207
|
+
units.push(unit);
|
|
208
|
+
}
|
|
209
|
+
return units;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Walk declarators rather than parameter/type names (including pointer returns). */
|
|
213
|
+
function declaratorName(n: Node | null): string | undefined {
|
|
214
|
+
if (!n) return undefined;
|
|
215
|
+
if (
|
|
216
|
+
/^(identifier|field_identifier|type_identifier|qualified_identifier|destructor_name|operator_name)$/.test(
|
|
217
|
+
n.type,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
return n.text;
|
|
221
|
+
return declaratorName(
|
|
222
|
+
n.childForFieldName("declarator") ??
|
|
223
|
+
(n.type === "parenthesized_declarator" ? n.firstNamedChild : null),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cUnits(root: Node): Unit[] {
|
|
228
|
+
const units: Unit[] = [];
|
|
229
|
+
function visit(n: Node, scope?: string, start = n.startIndex): void {
|
|
230
|
+
if (n.type === "template_declaration") {
|
|
231
|
+
const declaration = n.namedChildren
|
|
232
|
+
.filter((c): c is Node => !!c && c.type !== "template_parameter_list")
|
|
233
|
+
.at(-1);
|
|
234
|
+
if (declaration) {
|
|
235
|
+
visit(declaration, scope, start);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const body = n.childForFieldName("body");
|
|
240
|
+
const symbol =
|
|
241
|
+
n.childForFieldName("name")?.text ??
|
|
242
|
+
declaratorName(n.childForFieldName("declarator"));
|
|
243
|
+
const unit: Unit = {
|
|
244
|
+
start,
|
|
245
|
+
end: n.endIndex,
|
|
246
|
+
kind: n.type === "preproc_include" ? "imports" : classify(n.type),
|
|
247
|
+
scope,
|
|
248
|
+
symbol: symbol?.slice(0, 200),
|
|
249
|
+
};
|
|
250
|
+
if (body && /^(declaration_list|field_declaration_list)$/.test(body.type)) {
|
|
251
|
+
const nested = [scope, symbol].filter(Boolean).join("::") || undefined;
|
|
252
|
+
units.push({ ...unit, end: body.startIndex + 1 });
|
|
253
|
+
for (const child of body.namedChildren) if (child) visit(child, nested);
|
|
254
|
+
units.push({
|
|
255
|
+
start: body.endIndex - 1,
|
|
256
|
+
end: n.endIndex,
|
|
257
|
+
kind: "structural",
|
|
258
|
+
scope: nested,
|
|
259
|
+
});
|
|
260
|
+
} else if (/^preproc_(if|ifdef|elif|else|elifdef)$/.test(n.type)) {
|
|
261
|
+
// Preserve directives and both branches without evaluating the build configuration.
|
|
262
|
+
const condition =
|
|
263
|
+
n.childForFieldName("condition") ?? n.childForFieldName("name");
|
|
264
|
+
const children = n.namedChildren.filter(
|
|
265
|
+
(c): c is Node => !!c && c.id !== condition?.id,
|
|
266
|
+
);
|
|
267
|
+
if (!children.length) {
|
|
268
|
+
units.push(unit);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
units.push({ ...unit, end: children[0]!.startIndex });
|
|
272
|
+
for (const child of children) visit(child, scope);
|
|
273
|
+
const end = children.at(-1)!.endIndex;
|
|
274
|
+
if (end < n.endIndex)
|
|
275
|
+
units.push({ start: end, end: n.endIndex, kind: "structural", scope });
|
|
276
|
+
} else {
|
|
277
|
+
if (n.type === "function_definition")
|
|
278
|
+
unit.signature = n.text
|
|
279
|
+
.slice(0, body ? body.startIndex - n.startIndex : undefined)
|
|
280
|
+
.trim()
|
|
281
|
+
.slice(0, 240);
|
|
282
|
+
units.push(unit);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
for (const n of root.namedChildren) if (n) visit(n);
|
|
286
|
+
return units;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function shellUnits(root: Node): Unit[] {
|
|
290
|
+
return root.namedChildren
|
|
291
|
+
.filter((n): n is Node => !!n)
|
|
292
|
+
.map((n) => {
|
|
293
|
+
const body = n.childForFieldName("body");
|
|
294
|
+
return {
|
|
295
|
+
start: n.startIndex,
|
|
296
|
+
end: n.endIndex,
|
|
297
|
+
kind: classify(n.type),
|
|
298
|
+
symbol:
|
|
299
|
+
n.type === "function_definition"
|
|
300
|
+
? n.childForFieldName("name")?.text.slice(0, 200)
|
|
301
|
+
: undefined,
|
|
302
|
+
signature:
|
|
303
|
+
n.type === "function_definition"
|
|
304
|
+
? n.text
|
|
305
|
+
.slice(0, body ? body.startIndex - n.startIndex : undefined)
|
|
306
|
+
.trim()
|
|
307
|
+
.slice(0, 240)
|
|
308
|
+
: undefined,
|
|
309
|
+
};
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function sqlUnits(root: Node): Unit[] {
|
|
314
|
+
return root.namedChildren
|
|
315
|
+
.filter((n): n is Node => !!n)
|
|
316
|
+
.map((n) => {
|
|
317
|
+
const declaration = n.firstNamedChild;
|
|
318
|
+
// Object names describe DDL declarations, not every table referenced by a query.
|
|
319
|
+
const object = declaration?.type.startsWith("create_")
|
|
320
|
+
? declaration.namedChildren.find((c) => c?.type === "object_reference")
|
|
321
|
+
: undefined;
|
|
322
|
+
return {
|
|
323
|
+
start: n.startIndex,
|
|
324
|
+
end: n.nextSibling?.type === ";" ? n.nextSibling.endIndex : n.endIndex,
|
|
325
|
+
kind: n.type === "comment" ? "documentation" : "statement",
|
|
326
|
+
symbol: object?.childForFieldName("name")?.text.slice(0, 200),
|
|
327
|
+
scope: object?.childForFieldName("schema")?.text.slice(0, 160),
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function rustUnits(root: Node): Unit[] {
|
|
333
|
+
const units: Unit[] = [];
|
|
334
|
+
function visit(container: Node, scope?: string): void {
|
|
335
|
+
let prefix: number | undefined;
|
|
336
|
+
for (const n of container.namedChildren) {
|
|
337
|
+
if (!n) continue;
|
|
338
|
+
// Outer attributes/doc comments belong to the following item, not a chunk
|
|
339
|
+
// containing only cfg/derive metadata. Inner module docs remain separate.
|
|
340
|
+
if (
|
|
341
|
+
n.type === "attribute_item" ||
|
|
342
|
+
/^(\/\/\/(?!\/)|\/\*\*(?!\*))/.test(n.text)
|
|
343
|
+
) {
|
|
344
|
+
prefix ??= n.startIndex;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (prefix !== undefined && n.type.endsWith("comment")) continue;
|
|
348
|
+
const body = n.childForFieldName("body");
|
|
349
|
+
const name = n.childForFieldName("name")?.text;
|
|
350
|
+
const type = n.childForFieldName("type")?.text;
|
|
351
|
+
const trait = n.childForFieldName("trait")?.text;
|
|
352
|
+
const unit: Unit = {
|
|
353
|
+
start: prefix ?? n.startIndex,
|
|
354
|
+
end: n.endIndex,
|
|
355
|
+
kind:
|
|
356
|
+
n.type === "use_declaration" || n.type === "extern_crate_declaration"
|
|
357
|
+
? "imports"
|
|
358
|
+
: classify(n.type),
|
|
359
|
+
scope,
|
|
360
|
+
symbol: (
|
|
361
|
+
name ??
|
|
362
|
+
(n.type === "impl_item" ? type : n.childForFieldName("macro")?.text)
|
|
363
|
+
)?.slice(0, 200),
|
|
364
|
+
};
|
|
365
|
+
prefix = undefined;
|
|
366
|
+
if (body?.type === "declaration_list") {
|
|
367
|
+
const owner =
|
|
368
|
+
n.type === "impl_item"
|
|
369
|
+
? [type, trait].filter(Boolean).join(" as ")
|
|
370
|
+
: name;
|
|
371
|
+
const nested =
|
|
372
|
+
[scope, owner].filter(Boolean).join("::").slice(0, 160) || undefined;
|
|
373
|
+
units.push({ ...unit, end: body.startIndex + 1 });
|
|
374
|
+
visit(body, nested);
|
|
375
|
+
// Keep closing delimiters with their container, including empty bodies.
|
|
376
|
+
units.push({
|
|
377
|
+
start: body.endIndex - 1,
|
|
378
|
+
end: n.endIndex,
|
|
379
|
+
kind: "structural",
|
|
380
|
+
scope: nested,
|
|
381
|
+
});
|
|
382
|
+
} else {
|
|
383
|
+
if (unit.kind === "function")
|
|
384
|
+
unit.signature = n.text
|
|
385
|
+
.slice(0, body ? body.startIndex - n.startIndex : undefined)
|
|
386
|
+
.trim()
|
|
387
|
+
.slice(0, 240);
|
|
388
|
+
units.push(unit);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (prefix !== undefined)
|
|
392
|
+
units.push({
|
|
393
|
+
start: prefix,
|
|
394
|
+
end:
|
|
395
|
+
container.type === "declaration_list"
|
|
396
|
+
? container.endIndex - 1
|
|
397
|
+
: container.endIndex,
|
|
398
|
+
kind: "documentation",
|
|
399
|
+
scope,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
visit(root);
|
|
403
|
+
return units;
|
|
404
|
+
}
|
|
105
405
|
/** Bound strings without slicing a surrogate pair; prefer line ends where possible. */
|
|
106
406
|
function splitUnit(
|
|
107
407
|
source: string,
|
|
@@ -198,7 +498,21 @@ export async function chunk(
|
|
|
198
498
|
if (mode === "window") {
|
|
199
499
|
status = "window-baseline";
|
|
200
500
|
units = [{ start: 0, end: source.length, kind: "fallback" }];
|
|
201
|
-
} else if (
|
|
501
|
+
} else if (
|
|
502
|
+
[
|
|
503
|
+
"java",
|
|
504
|
+
"typescript",
|
|
505
|
+
"tsx",
|
|
506
|
+
"javascript",
|
|
507
|
+
"python",
|
|
508
|
+
"go",
|
|
509
|
+
"rust",
|
|
510
|
+
"c",
|
|
511
|
+
"cpp",
|
|
512
|
+
"shell",
|
|
513
|
+
"sql",
|
|
514
|
+
].includes(lang)
|
|
515
|
+
) {
|
|
202
516
|
const grammar = await language(lang);
|
|
203
517
|
const parser = new Parser();
|
|
204
518
|
let tree: ReturnType<Parser["parse"]> = null;
|
|
@@ -233,7 +547,13 @@ export async function chunk(
|
|
|
233
547
|
for (const child of n.namedChildren) if (child) visit(child);
|
|
234
548
|
} else if (n.endIndex > n.startIndex) units.push(u);
|
|
235
549
|
}
|
|
236
|
-
|
|
550
|
+
if (lang === "python") units = pythonUnits(tree.rootNode);
|
|
551
|
+
else if (lang === "go") units = goUnits(tree.rootNode);
|
|
552
|
+
else if (lang === "rust") units = rustUnits(tree.rootNode);
|
|
553
|
+
else if (lang === "c" || lang === "cpp") units = cUnits(tree.rootNode);
|
|
554
|
+
else if (lang === "shell") units = shellUnits(tree.rootNode);
|
|
555
|
+
else if (lang === "sql") units = sqlUnits(tree.rootNode);
|
|
556
|
+
else visit(tree.rootNode);
|
|
237
557
|
// Attach every gap (punctuation, BOM, comments, whitespace) without altering source bytes.
|
|
238
558
|
let cursor = 0;
|
|
239
559
|
const covered: Unit[] = [];
|
|
@@ -272,6 +592,9 @@ export async function chunk(
|
|
|
272
592
|
const prev = merged.at(-1);
|
|
273
593
|
if (
|
|
274
594
|
prev &&
|
|
595
|
+
lang !== "sql" &&
|
|
596
|
+
(!["python", "go", "rust", "c", "cpp", "shell"].includes(lang) ||
|
|
597
|
+
(!prev.symbol && !u.symbol)) &&
|
|
275
598
|
prev.kind !== "function" &&
|
|
276
599
|
u.kind === prev.kind &&
|
|
277
600
|
u.scope === prev.scope &&
|
package/src/rerank.ts
CHANGED
|
@@ -4,6 +4,14 @@ import { invariant } from "./common.js";
|
|
|
4
4
|
|
|
5
5
|
export const QWEN_MODEL = "Qwen/Qwen3-Reranker-0.6B";
|
|
6
6
|
export const QWEN_REVISION = "e61197ed45024b0ed8a2d74b80b4d909f1255473";
|
|
7
|
+
// Reserved worker exit codes; never interpret or forward library stderr.
|
|
8
|
+
const WORKER_FAILURES: Record<number, string> = {
|
|
9
|
+
20: "Qwen Python dependencies are missing or incompatible. Install runtime/requirements.txt with the Python selected by SRCX_RERANK_PYTHON.",
|
|
10
|
+
21: "Qwen model cache is missing or incomplete. Download the pinned model revision from README and check HF_HUB_CACHE.",
|
|
11
|
+
22: "Qwen device is invalid or unavailable. Set SRCX_RERANK_DEVICE to auto or cpu, or a supported mps/cuda device.",
|
|
12
|
+
23: "Qwen query/chunk pair exceeds 8,192 tokens. Shorten the query or use search without --rerank; source is not truncated.",
|
|
13
|
+
24: "Qwen request exceeds 4 MiB. Reduce --candidates or shorten the query.",
|
|
14
|
+
};
|
|
7
15
|
export type Candidate = { id: string; text: string };
|
|
8
16
|
export type RerankOptions = {
|
|
9
17
|
rerank?: "qwen";
|
|
@@ -49,7 +57,7 @@ export async function qwenScores(query: string, candidates: Candidate[]) {
|
|
|
49
57
|
const input = JSON.stringify({ query, candidates });
|
|
50
58
|
invariant(
|
|
51
59
|
Buffer.byteLength(input) <= 4 * 1024 * 1024,
|
|
52
|
-
"Reranker input exceeds 4 MiB.",
|
|
60
|
+
"Reranker input exceeds 4 MiB. Reduce --candidates or shorten the query; no fallback was applied.",
|
|
53
61
|
);
|
|
54
62
|
const python = process.env.SRCX_RERANK_PYTHON;
|
|
55
63
|
invariant(
|
|
@@ -74,13 +82,21 @@ export async function qwenScores(query: string, candidates: Candidate[]) {
|
|
|
74
82
|
},
|
|
75
83
|
},
|
|
76
84
|
(error, output) => {
|
|
77
|
-
if (error)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
else
|
|
85
|
+
if (!error) return resolve(output);
|
|
86
|
+
let message =
|
|
87
|
+
"Qwen reranking failed. Check Python dependencies, cached model and SRCX_RERANK_DEVICE.";
|
|
88
|
+
if (error.code === "ENOENT")
|
|
89
|
+
message =
|
|
90
|
+
"Qwen Python executable was not found. Set SRCX_RERANK_PYTHON to an existing Python executable.";
|
|
91
|
+
else if (error.code === "EACCES")
|
|
92
|
+
message =
|
|
93
|
+
"Qwen Python executable cannot be started. Check execute permissions for SRCX_RERANK_PYTHON.";
|
|
94
|
+
else if (error.killed && error.signal === "SIGKILL" && !error.code)
|
|
95
|
+
message =
|
|
96
|
+
"Qwen reranking exceeded 120 seconds. Reduce --candidates or use an available faster device via SRCX_RERANK_DEVICE.";
|
|
97
|
+
else if (typeof error.code === "number")
|
|
98
|
+
message = WORKER_FAILURES[error.code] ?? message;
|
|
99
|
+
reject(new Error(`${message} No fallback was applied.`));
|
|
84
100
|
},
|
|
85
101
|
);
|
|
86
102
|
// An early worker exit can close stdin before Node finishes writing.
|