@cue-dev/retrieval-core 0.1.3
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/AGENTS.md +27 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/chunking.d.ts +64 -0
- package/dist/chunking.js +983 -0
- package/dist/index.d.ts +673 -0
- package/dist/index.js +6605 -0
- package/dist/indexing-ignore.d.ts +9 -0
- package/dist/indexing-ignore.js +151 -0
- package/dist/remote-sync.d.ts +193 -0
- package/dist/remote-sync.js +816 -0
- package/package.json +37 -0
- package/scripts/poc-node-parser-host.cjs +105 -0
- package/scripts/poc-parser-availability-benchmark.ts +338 -0
- package/src/chunking.ts +1187 -0
- package/src/index.ts +8338 -0
- package/src/indexing-ignore.ts +179 -0
- package/src/remote-sync.ts +1119 -0
- package/test/benchmark.thresholds.test.ts +815 -0
- package/test/chunking.config.test.ts +84 -0
- package/test/chunking.language-aware.test.ts +1248 -0
- package/test/chunking.parser-availability.poc.test.ts +86 -0
- package/test/claude-agent-provider.test.ts +209 -0
- package/test/embedding-context-prefix.test.ts +101 -0
- package/test/embedding-provider.test.ts +570 -0
- package/test/enhance-confidence.test.ts +752 -0
- package/test/index-prep.concurrency.regression.test.ts +142 -0
- package/test/integration.test.ts +508 -0
- package/test/local-sqlite.integration.test.ts +258 -0
- package/test/mcp-search-quality.regression.test.ts +1358 -0
- package/test/remote-sync.integration.test.ts +350 -0
- package/test/smart-cutoff.config.test.ts +86 -0
- package/test/snippet-integrity.config.test.ts +59 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { InMemoryQueryCache } from "@cue-dev/data-plane";
|
|
3
|
+
import { InMemoryIndexStore, RetrievalCore } from "../src/index.js";
|
|
4
|
+
|
|
5
|
+
const testRerankerProvider = {
|
|
6
|
+
async rerank(input: { query: string; documents: string[]; top_n: number }) {
|
|
7
|
+
return input.documents.slice(0, input.top_n).map((_, index) => ({
|
|
8
|
+
index,
|
|
9
|
+
relevance_score: input.top_n - index
|
|
10
|
+
}));
|
|
11
|
+
},
|
|
12
|
+
describe: () => ({ provider: "test-reranker", model: "stub-v1" })
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("enhance prompt confidence controls", () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
vi.unstubAllEnvs();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("filters risky refs when retrieval confidence is low", async () => {
|
|
22
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
|
|
23
|
+
vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
24
|
+
trace_id: "trc-low-confidence",
|
|
25
|
+
results: [
|
|
26
|
+
{
|
|
27
|
+
path: "docs/reference/hooks.md",
|
|
28
|
+
start_line: 1,
|
|
29
|
+
end_line: 20,
|
|
30
|
+
snippet: "addHook lifecycle ordering and examples",
|
|
31
|
+
score: 0.71,
|
|
32
|
+
reason: "semantic match"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: "test/hooks.test.js",
|
|
36
|
+
start_line: 1,
|
|
37
|
+
end_line: 25,
|
|
38
|
+
snippet: "addHook lifecycle should preserve plugin encapsulation",
|
|
39
|
+
score: 0.707,
|
|
40
|
+
reason: "path and token overlap"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
path: "lib/hooks.js",
|
|
44
|
+
start_line: 1,
|
|
45
|
+
end_line: 40,
|
|
46
|
+
snippet: "function addHook(name, fn) { return hooks.add(name, fn) }",
|
|
47
|
+
score: 0.704,
|
|
48
|
+
reason: "exact symbol match"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
path: "examples/hooks.js",
|
|
52
|
+
start_line: 1,
|
|
53
|
+
end_line: 22,
|
|
54
|
+
snippet: "example for addHook lifecycle behavior",
|
|
55
|
+
score: 0.703,
|
|
56
|
+
reason: "semantic match"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
path: "lib/error-handler.js",
|
|
60
|
+
start_line: 1,
|
|
61
|
+
end_line: 30,
|
|
62
|
+
snippet: "function setErrorHandler(fn) { return fn }",
|
|
63
|
+
score: 0.702,
|
|
64
|
+
reason: "path and token overlap"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
search_metadata: {
|
|
68
|
+
latency_ms: 15,
|
|
69
|
+
retrieval_mode: "hybrid",
|
|
70
|
+
index_version: "idx-1"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const output = await core.enhancePrompt({
|
|
75
|
+
trace_id: "trc-low-confidence",
|
|
76
|
+
tenant_id: "tenant-dev",
|
|
77
|
+
workspace_id: "ws-dev",
|
|
78
|
+
request: {
|
|
79
|
+
prompt: "Refine addHook lifecycle runtime behavior and preserve plugin compatibility.",
|
|
80
|
+
project_root_path: "/workspace/dev",
|
|
81
|
+
conversation_history: [{ role: "user", content: "Avoid docs and tests as primary context." }]
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(output.warnings).toEqual([]);
|
|
86
|
+
expect(output.questions).toEqual([]);
|
|
87
|
+
expect(output.context_refs.length).toBeGreaterThan(0);
|
|
88
|
+
expect(output.context_refs.some((ref) => ref.path === "lib/hooks.js")).toBe(true);
|
|
89
|
+
expect(output.context_refs.every((ref) => !/^docs\/|^test\/|^examples\//.test(ref.path))).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("builds enhancer retrieval query from history symbols and implementation hints", async () => {
|
|
93
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
|
|
94
|
+
const searchSpy = vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
95
|
+
trace_id: "trc-query-build",
|
|
96
|
+
results: [
|
|
97
|
+
{
|
|
98
|
+
path: "lib/error-handler.js",
|
|
99
|
+
start_line: 1,
|
|
100
|
+
end_line: 20,
|
|
101
|
+
snippet: "function setErrorHandler(fn) { return fn }",
|
|
102
|
+
score: 0.95,
|
|
103
|
+
reason: "exact symbol match"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
path: "lib/hooks.js",
|
|
107
|
+
start_line: 1,
|
|
108
|
+
end_line: 20,
|
|
109
|
+
snippet: "function addHook(name, fn) { return hooks.add(name, fn) }",
|
|
110
|
+
score: 0.3,
|
|
111
|
+
reason: "path and token overlap"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
path: "fastify.js",
|
|
115
|
+
start_line: 1,
|
|
116
|
+
end_line: 20,
|
|
117
|
+
snippet: "function setErrorHandler(fn) { return buildErrorHandler(fn) }",
|
|
118
|
+
score: 0.28,
|
|
119
|
+
reason: "path and token overlap"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
search_metadata: {
|
|
123
|
+
latency_ms: 10,
|
|
124
|
+
retrieval_mode: "hybrid",
|
|
125
|
+
index_version: "idx-2"
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await core.enhancePrompt({
|
|
130
|
+
trace_id: "trc-query-build",
|
|
131
|
+
tenant_id: "tenant-dev",
|
|
132
|
+
workspace_id: "ws-dev",
|
|
133
|
+
request: {
|
|
134
|
+
prompt: "Improve request pipeline error handling and keep behavior stable.",
|
|
135
|
+
project_root_path: "/workspace/dev",
|
|
136
|
+
conversation_history: [
|
|
137
|
+
{ role: "user", content: "Focus on `setErrorHandler` and onRequest ordering." },
|
|
138
|
+
{ role: "assistant", content: "Likely files include lib/error-handler.js and fastify.js." }
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(searchSpy).toHaveBeenCalledTimes(1);
|
|
144
|
+
const retrievalQuery = searchSpy.mock.calls[0]?.[0].request.query ?? "";
|
|
145
|
+
expect(retrievalQuery).toContain("setErrorHandler");
|
|
146
|
+
expect(retrievalQuery).toContain("lib/error-handler.js");
|
|
147
|
+
expect(retrievalQuery).toMatch(/query_intent:\s+symbol-heavy/);
|
|
148
|
+
expect(retrievalQuery).toMatch(/retrieval_hints:\s+.*setErrorHandler.*lib\/error-handler\.js/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("routes symbol-heavy prompts to tighter pre-rerank caps", async () => {
|
|
152
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
153
|
+
enhancerConfig: {
|
|
154
|
+
max_candidates_pre_rerank: 4
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const searchSpy = vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
158
|
+
trace_id: "trc-symbol-intent-cap",
|
|
159
|
+
results: [
|
|
160
|
+
{
|
|
161
|
+
path: "lib/error-handler.js",
|
|
162
|
+
start_line: 1,
|
|
163
|
+
end_line: 20,
|
|
164
|
+
snippet: "function setErrorHandler(fn) { return buildErrorHandler(fn) }",
|
|
165
|
+
score: 0.92,
|
|
166
|
+
reason: "exact symbol match"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
path: "src/server.ts",
|
|
170
|
+
start_line: 1,
|
|
171
|
+
end_line: 20,
|
|
172
|
+
snippet: "addHook onRequest preHandler onError lifecycle",
|
|
173
|
+
score: 0.9,
|
|
174
|
+
reason: "path and token overlap"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
path: "internal/router.ts",
|
|
178
|
+
start_line: 1,
|
|
179
|
+
end_line: 20,
|
|
180
|
+
snippet: "registerRoute and dispatchRequest internals",
|
|
181
|
+
score: 0.89,
|
|
182
|
+
reason: "path and token overlap"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
path: "app/runtime.ts",
|
|
186
|
+
start_line: 1,
|
|
187
|
+
end_line: 20,
|
|
188
|
+
snippet: "runtime hook orchestration",
|
|
189
|
+
score: 0.88,
|
|
190
|
+
reason: "semantic match"
|
|
191
|
+
}
|
|
192
|
+
],
|
|
193
|
+
search_metadata: {
|
|
194
|
+
latency_ms: 10,
|
|
195
|
+
retrieval_mode: "hybrid",
|
|
196
|
+
index_version: "idx-symbol-intent"
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const output = await core.enhancePrompt({
|
|
201
|
+
trace_id: "trc-symbol-intent-cap",
|
|
202
|
+
tenant_id: "tenant-dev",
|
|
203
|
+
workspace_id: "ws-dev",
|
|
204
|
+
request: {
|
|
205
|
+
prompt: "Refine `setErrorHandler` + `addHook` + `onRequest` + `preHandler` flow.",
|
|
206
|
+
project_root_path: "/workspace/dev",
|
|
207
|
+
conversation_history: [{ role: "user", content: "Target exact lifecycle symbols only." }]
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const retrievalQuery = searchSpy.mock.calls[0]?.[0].request.query ?? "";
|
|
212
|
+
const retrievalTopK = searchSpy.mock.calls[0]?.[0].request.top_k;
|
|
213
|
+
expect(retrievalQuery).toMatch(/query_intent:\s+symbol-heavy/);
|
|
214
|
+
expect(retrievalTopK).toBe(8);
|
|
215
|
+
expect(output.context_refs.length).toBeLessThanOrEqual(3);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("routes conceptual prompts without forcing implementation-only filtering", async () => {
|
|
219
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
|
|
220
|
+
const searchSpy = vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
221
|
+
trace_id: "trc-conceptual-intent",
|
|
222
|
+
results: [
|
|
223
|
+
{
|
|
224
|
+
path: "docs/architecture/retrieval.md",
|
|
225
|
+
start_line: 1,
|
|
226
|
+
end_line: 30,
|
|
227
|
+
snippet: "retrieval architecture and ranking tradeoffs",
|
|
228
|
+
score: 0.91,
|
|
229
|
+
reason: "path and token overlap"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
path: "src/retrieval/rerank.ts",
|
|
233
|
+
start_line: 1,
|
|
234
|
+
end_line: 20,
|
|
235
|
+
snippet: "rerank implementation details",
|
|
236
|
+
score: 0.89,
|
|
237
|
+
reason: "semantic match"
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
search_metadata: {
|
|
241
|
+
latency_ms: 9,
|
|
242
|
+
retrieval_mode: "hybrid",
|
|
243
|
+
index_version: "idx-conceptual-intent"
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const output = await core.enhancePrompt({
|
|
248
|
+
trace_id: "trc-conceptual-intent",
|
|
249
|
+
tenant_id: "tenant-dev",
|
|
250
|
+
workspace_id: "ws-dev",
|
|
251
|
+
request: {
|
|
252
|
+
prompt: "Explain architecture tradeoffs for retrieval ranking and docs guidance.",
|
|
253
|
+
project_root_path: "/workspace/dev",
|
|
254
|
+
conversation_history: [{ role: "user", content: "Need high-level conceptual guidance." }]
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const retrievalQuery = searchSpy.mock.calls[0]?.[0].request.query ?? "";
|
|
259
|
+
const retrievalTopK = searchSpy.mock.calls[0]?.[0].request.top_k;
|
|
260
|
+
expect(retrievalQuery).toMatch(/query_intent:\s+conceptual/);
|
|
261
|
+
expect(retrievalTopK).toBe(12);
|
|
262
|
+
expect(output.context_refs.some((ref) => ref.path.startsWith("docs/"))).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("demotes declaration-heavy top hits during low-confidence rerank", async () => {
|
|
266
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
267
|
+
rerankerProvider: testRerankerProvider
|
|
268
|
+
});
|
|
269
|
+
vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
270
|
+
trace_id: "trc-declaration-demotion",
|
|
271
|
+
results: [
|
|
272
|
+
{
|
|
273
|
+
path: "types/hooks.d.ts",
|
|
274
|
+
start_line: 1,
|
|
275
|
+
end_line: 24,
|
|
276
|
+
snippet: "export interface HookContext { requestId: string; route: string }",
|
|
277
|
+
score: 0.92,
|
|
278
|
+
reason: "path and token overlap"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
path: "lib/hooks.js",
|
|
282
|
+
start_line: 1,
|
|
283
|
+
end_line: 40,
|
|
284
|
+
snippet: "function addHook(name, fn) { return hooks.add(name, fn) }",
|
|
285
|
+
score: 0.91,
|
|
286
|
+
reason: "semantic match"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
path: "lib/handle-request.js",
|
|
290
|
+
start_line: 1,
|
|
291
|
+
end_line: 35,
|
|
292
|
+
snippet: "function preHandlerHookRunner(state) { return state }",
|
|
293
|
+
score: 0.9,
|
|
294
|
+
reason: "semantic match"
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
search_metadata: {
|
|
298
|
+
latency_ms: 10,
|
|
299
|
+
retrieval_mode: "hybrid",
|
|
300
|
+
index_version: "idx-declaration-demotion"
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const output = await core.enhancePrompt({
|
|
305
|
+
trace_id: "trc-declaration-demotion",
|
|
306
|
+
tenant_id: "tenant-dev",
|
|
307
|
+
workspace_id: "ws-dev",
|
|
308
|
+
request: {
|
|
309
|
+
prompt: "Stabilize runtime hook lifecycle behavior and avoid docs/examples context.",
|
|
310
|
+
project_root_path: "/workspace/dev",
|
|
311
|
+
conversation_history: [{ role: "user", content: "Keep implementation-focused guidance." }]
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(output.context_refs.length).toBeGreaterThan(0);
|
|
316
|
+
expect(output.context_refs[0]?.path).not.toBe("types/hooks.d.ts");
|
|
317
|
+
expect(output.context_refs[0]?.path.startsWith("lib/")).toBe(true);
|
|
318
|
+
expect(output.context_refs[0]?.path.endsWith(".d.ts")).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("skips enhancer rerank when reranker provider is not configured", async () => {
|
|
322
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
|
|
323
|
+
vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
324
|
+
trace_id: "trc-rerank-disabled",
|
|
325
|
+
results: [
|
|
326
|
+
{
|
|
327
|
+
path: "types/hooks.d.ts",
|
|
328
|
+
start_line: 1,
|
|
329
|
+
end_line: 24,
|
|
330
|
+
snippet: "export interface HookContext { requestId: string; route: string }",
|
|
331
|
+
score: 0.92,
|
|
332
|
+
reason: "path and token overlap"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
path: "lib/hooks.js",
|
|
336
|
+
start_line: 1,
|
|
337
|
+
end_line: 40,
|
|
338
|
+
snippet: "function addHook(name, fn) { return hooks.add(name, fn) }",
|
|
339
|
+
score: 0.91,
|
|
340
|
+
reason: "semantic match"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
path: "lib/handle-request.js",
|
|
344
|
+
start_line: 1,
|
|
345
|
+
end_line: 35,
|
|
346
|
+
snippet: "function preHandlerHookRunner(state) { return state }",
|
|
347
|
+
score: 0.9,
|
|
348
|
+
reason: "semantic match"
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
search_metadata: {
|
|
352
|
+
latency_ms: 10,
|
|
353
|
+
retrieval_mode: "hybrid",
|
|
354
|
+
index_version: "idx-rerank-disabled"
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const output = await core.enhancePrompt({
|
|
359
|
+
trace_id: "trc-rerank-disabled",
|
|
360
|
+
tenant_id: "tenant-dev",
|
|
361
|
+
workspace_id: "ws-dev",
|
|
362
|
+
request: {
|
|
363
|
+
prompt: "Stabilize runtime hook lifecycle behavior and avoid docs/examples context.",
|
|
364
|
+
project_root_path: "/workspace/dev",
|
|
365
|
+
conversation_history: [{ role: "user", content: "Keep implementation-focused guidance." }]
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
expect(output.context_refs.length).toBeGreaterThan(0);
|
|
370
|
+
expect(output.context_refs[0]?.path).toBe("types/hooks.d.ts");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("applies negation-aware filtering for docs/tests/examples/archive paths", async () => {
|
|
374
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache());
|
|
375
|
+
vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
376
|
+
trace_id: "trc-negation-filter",
|
|
377
|
+
results: [
|
|
378
|
+
{
|
|
379
|
+
path: "docs/reference/blueprints.md",
|
|
380
|
+
start_line: 1,
|
|
381
|
+
end_line: 20,
|
|
382
|
+
snippet: "blueprints runtime design notes",
|
|
383
|
+
score: 0.93,
|
|
384
|
+
reason: "path and token overlap"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
path: "tests/blueprints.test.py",
|
|
388
|
+
start_line: 1,
|
|
389
|
+
end_line: 20,
|
|
390
|
+
snippet: "test blueprint registration order",
|
|
391
|
+
score: 0.92,
|
|
392
|
+
reason: "path and token overlap"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: "examples/blueprint_demo.py",
|
|
396
|
+
start_line: 1,
|
|
397
|
+
end_line: 20,
|
|
398
|
+
snippet: "example blueprint usage",
|
|
399
|
+
score: 0.91,
|
|
400
|
+
reason: "semantic match"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
path: "src/flask/_archive/blueprints_legacy.py",
|
|
404
|
+
start_line: 1,
|
|
405
|
+
end_line: 30,
|
|
406
|
+
snippet: "legacy blueprint implementation",
|
|
407
|
+
score: 0.9,
|
|
408
|
+
reason: "semantic match"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
path: "src/flask/blueprints.py",
|
|
412
|
+
start_line: 1,
|
|
413
|
+
end_line: 40,
|
|
414
|
+
snippet: "def register_blueprint(app, blueprint): return app",
|
|
415
|
+
score: 0.89,
|
|
416
|
+
reason: "exact symbol match"
|
|
417
|
+
}
|
|
418
|
+
],
|
|
419
|
+
search_metadata: {
|
|
420
|
+
latency_ms: 12,
|
|
421
|
+
retrieval_mode: "hybrid",
|
|
422
|
+
index_version: "idx-negation"
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const output = await core.enhancePrompt({
|
|
427
|
+
trace_id: "trc-negation-filter",
|
|
428
|
+
tenant_id: "tenant-dev",
|
|
429
|
+
workspace_id: "ws-dev",
|
|
430
|
+
request: {
|
|
431
|
+
prompt: "Improve blueprints runtime behavior and avoid docs/tests/examples/archive context.",
|
|
432
|
+
project_root_path: "/workspace/dev",
|
|
433
|
+
conversation_history: [{ role: "user", content: "Keep implementation focused." }]
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(output.context_refs.length).toBeGreaterThan(0);
|
|
438
|
+
expect(output.context_refs[0]?.path).toBe("src/flask/blueprints.py");
|
|
439
|
+
expect(output.context_refs.every((ref) => !/docs\/|tests\/|examples\/|_archive\//.test(ref.path))).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("caps expansion hints and emits path-form hint expansions", async () => {
|
|
443
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
444
|
+
enhancerConfig: {
|
|
445
|
+
max_expansion_hints: 6
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
const searchSpy = vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
449
|
+
trace_id: "trc-hint-cap",
|
|
450
|
+
results: [],
|
|
451
|
+
search_metadata: {
|
|
452
|
+
latency_ms: 8,
|
|
453
|
+
retrieval_mode: "hybrid",
|
|
454
|
+
index_version: "idx-cap"
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
await core.enhancePrompt({
|
|
459
|
+
trace_id: "trc-hint-cap",
|
|
460
|
+
tenant_id: "tenant-dev",
|
|
461
|
+
workspace_id: "ws-dev",
|
|
462
|
+
request: {
|
|
463
|
+
prompt: "Harden JSON provider and blueprints runtime behavior without docs.",
|
|
464
|
+
project_root_path: "/workspace/dev",
|
|
465
|
+
conversation_history: [{ role: "user", content: "Focus on `render_template_string` and json provider internals." }]
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const retrievalQuery = searchSpy.mock.calls[0]?.[0].request.query ?? "";
|
|
470
|
+
expect(retrievalQuery).toContain("json/provider");
|
|
471
|
+
const hintLine = retrievalQuery
|
|
472
|
+
.split("\n")
|
|
473
|
+
.find((line) => line.startsWith("retrieval_hints:"));
|
|
474
|
+
const hintCount = (hintLine ?? "")
|
|
475
|
+
.replace(/^retrieval_hints:\s*/u, "")
|
|
476
|
+
.split(/\s+/)
|
|
477
|
+
.filter(Boolean).length;
|
|
478
|
+
expect(hintCount).toBeLessThanOrEqual(6);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("uses configured enhancer provider output", async () => {
|
|
482
|
+
const generate = vi.fn(async (_input: unknown) => ({
|
|
483
|
+
enhanced_prompt: "Provider-generated plan with explicit checklist."
|
|
484
|
+
}));
|
|
485
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
486
|
+
enhancerProvider: {
|
|
487
|
+
generate,
|
|
488
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
489
|
+
},
|
|
490
|
+
enhancerGenerationConfig: {
|
|
491
|
+
timeout_ms: 2_000,
|
|
492
|
+
max_retries: 0,
|
|
493
|
+
tool_mode: "read_only"
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const output = await core.enhancePrompt({
|
|
498
|
+
trace_id: "trc-provider-output",
|
|
499
|
+
tenant_id: "tenant-dev",
|
|
500
|
+
workspace_id: "ws-dev",
|
|
501
|
+
request: {
|
|
502
|
+
prompt: "Improve runtime guardrails and preserve API behavior.",
|
|
503
|
+
project_root_path: "/workspace/dev",
|
|
504
|
+
conversation_history: [{ role: "user", content: "Include regression tests." }]
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
expect(output.enhanced_prompt).toBe("Provider-generated plan with explicit checklist.");
|
|
509
|
+
expect(generate).toHaveBeenCalledTimes(1);
|
|
510
|
+
const providerInput = generate.mock.calls[0]?.[0] as {
|
|
511
|
+
tool_mode?: string;
|
|
512
|
+
intent?: string;
|
|
513
|
+
style_requested?: string;
|
|
514
|
+
style_resolved?: string;
|
|
515
|
+
} | undefined;
|
|
516
|
+
expect(providerInput?.tool_mode).toBe("read_only");
|
|
517
|
+
expect(providerInput?.intent).toBeDefined();
|
|
518
|
+
expect(providerInput?.style_requested).toBe("standard");
|
|
519
|
+
expect(providerInput?.style_resolved).toBe("standard");
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("resolves style=auto to deep for high-risk requests", async () => {
|
|
523
|
+
const generate = vi.fn(async (_input: unknown) => ({
|
|
524
|
+
enhanced_prompt: "Provider-generated deep plan."
|
|
525
|
+
}));
|
|
526
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
527
|
+
enhancerProvider: {
|
|
528
|
+
generate,
|
|
529
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
530
|
+
},
|
|
531
|
+
enhancerGenerationConfig: {
|
|
532
|
+
timeout_ms: 2_000,
|
|
533
|
+
max_retries: 0,
|
|
534
|
+
tool_mode: "read_only"
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
await core.enhancePrompt({
|
|
539
|
+
trace_id: "trc-provider-style-auto",
|
|
540
|
+
tenant_id: "tenant-dev",
|
|
541
|
+
workspace_id: "ws-dev",
|
|
542
|
+
request: {
|
|
543
|
+
prompt: "Harden tenant authorization boundaries and preserve compatibility",
|
|
544
|
+
style: "auto",
|
|
545
|
+
project_root_path: "/workspace/dev",
|
|
546
|
+
conversation_history: [{ role: "user", content: "Add comprehensive regression tests." }]
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const providerInput = generate.mock.calls[0]?.[0] as { style_requested?: string; style_resolved?: string } | undefined;
|
|
551
|
+
expect(providerInput?.style_requested).toBe("auto");
|
|
552
|
+
expect(providerInput?.style_resolved).toBe("deep");
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("returns provider output without synthetic fallback rewriting", async () => {
|
|
556
|
+
const generate = vi.fn(async () => ({
|
|
557
|
+
enhanced_prompt: [
|
|
558
|
+
"**Goal:**",
|
|
559
|
+
"Harden tenant authorization and keep behavior stable.",
|
|
560
|
+
"",
|
|
561
|
+
"**Deliverables:**",
|
|
562
|
+
"1. Comprehensive regression test suite with documentation.",
|
|
563
|
+
"2. Security review of changes and integration testing in staging environment.",
|
|
564
|
+
"",
|
|
565
|
+
"**Likely Files/Areas to Modify:**",
|
|
566
|
+
"- auth/, middleware/, guards/, context/, handlers/"
|
|
567
|
+
].join("\n")
|
|
568
|
+
}));
|
|
569
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
570
|
+
enhancerProvider: {
|
|
571
|
+
generate,
|
|
572
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
573
|
+
},
|
|
574
|
+
enhancerGenerationConfig: {
|
|
575
|
+
timeout_ms: 2_000,
|
|
576
|
+
max_retries: 0,
|
|
577
|
+
tool_mode: "none"
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
vi.spyOn(core, "searchContext").mockResolvedValue({
|
|
581
|
+
trace_id: "trc-provider-generic",
|
|
582
|
+
results: [
|
|
583
|
+
{
|
|
584
|
+
path: "src/auth/tenant-guard.ts",
|
|
585
|
+
start_line: 12,
|
|
586
|
+
end_line: 44,
|
|
587
|
+
snippet: "export function assertTenantAccess(ctx) { /* ... */ }",
|
|
588
|
+
score: 0.97,
|
|
589
|
+
reason: "exact symbol match"
|
|
590
|
+
}
|
|
591
|
+
],
|
|
592
|
+
search_metadata: {
|
|
593
|
+
latency_ms: 8,
|
|
594
|
+
retrieval_mode: "hybrid",
|
|
595
|
+
index_version: "idx-provider-generic"
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
const output = await core.enhancePrompt({
|
|
600
|
+
trace_id: "trc-provider-generic",
|
|
601
|
+
tenant_id: "tenant-dev",
|
|
602
|
+
workspace_id: "ws-dev",
|
|
603
|
+
request: {
|
|
604
|
+
prompt: "Harden tenant authorization and keep behavior stable",
|
|
605
|
+
project_root_path: "/workspace/dev",
|
|
606
|
+
conversation_history: [{ role: "user", content: "Add regression tests for auth boundaries" }]
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
expect(output.enhanced_prompt).toContain("Deliverables");
|
|
611
|
+
expect(output.enhanced_prompt).not.toContain("Codebase anchors");
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it("retries enhancer provider and then fails with upstream error when attempts are exhausted", async () => {
|
|
615
|
+
const generate = vi.fn(async (_input: unknown) => {
|
|
616
|
+
throw new Error("provider unavailable");
|
|
617
|
+
});
|
|
618
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
619
|
+
enhancerProvider: {
|
|
620
|
+
generate,
|
|
621
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
622
|
+
},
|
|
623
|
+
enhancerGenerationConfig: {
|
|
624
|
+
timeout_ms: 1_000,
|
|
625
|
+
max_retries: 1,
|
|
626
|
+
tool_mode: "none"
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
await expect(
|
|
631
|
+
core.enhancePrompt({
|
|
632
|
+
trace_id: "trc-provider-retry-fail",
|
|
633
|
+
tenant_id: "tenant-dev",
|
|
634
|
+
workspace_id: "ws-dev",
|
|
635
|
+
request: {
|
|
636
|
+
prompt: "Tighten workspace checks and keep behavior stable.",
|
|
637
|
+
project_root_path: "/workspace/dev",
|
|
638
|
+
conversation_history: [{ role: "user", content: "No contract changes." }]
|
|
639
|
+
}
|
|
640
|
+
})
|
|
641
|
+
).rejects.toThrow(/enhancer provider failed after retries/i);
|
|
642
|
+
expect(generate).toHaveBeenCalledTimes(2);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it("does not retry enhancer provider when attempt times out", async () => {
|
|
646
|
+
const generate = vi.fn(async (input: unknown) => {
|
|
647
|
+
const signal = (input as { abort_signal?: AbortSignal }).abort_signal;
|
|
648
|
+
await new Promise<void>((resolve, reject) => {
|
|
649
|
+
signal?.addEventListener(
|
|
650
|
+
"abort",
|
|
651
|
+
() => {
|
|
652
|
+
reject(new Error("aborted_by_timeout"));
|
|
653
|
+
},
|
|
654
|
+
{ once: true }
|
|
655
|
+
);
|
|
656
|
+
});
|
|
657
|
+
return { enhanced_prompt: "unreachable" };
|
|
658
|
+
});
|
|
659
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
660
|
+
enhancerProvider: {
|
|
661
|
+
generate,
|
|
662
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
663
|
+
},
|
|
664
|
+
enhancerGenerationConfig: {
|
|
665
|
+
timeout_ms: 30,
|
|
666
|
+
max_retries: 4,
|
|
667
|
+
tool_mode: "none"
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
await expect(
|
|
672
|
+
core.enhancePrompt({
|
|
673
|
+
trace_id: "trc-provider-timeout-no-retry",
|
|
674
|
+
tenant_id: "tenant-dev",
|
|
675
|
+
workspace_id: "ws-dev",
|
|
676
|
+
request: {
|
|
677
|
+
prompt: "Tighten workspace checks and keep behavior stable.",
|
|
678
|
+
project_root_path: "/workspace/dev",
|
|
679
|
+
conversation_history: [{ role: "user", content: "No contract changes." }]
|
|
680
|
+
}
|
|
681
|
+
})
|
|
682
|
+
).rejects.toThrow(/timeout_after_30ms/i);
|
|
683
|
+
expect(generate).toHaveBeenCalledTimes(1);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it("keeps enhancer request alive when provider emits progress heartbeats", async () => {
|
|
687
|
+
const generate = vi.fn(async (input: unknown) => {
|
|
688
|
+
const onProgress = (input as { on_progress?: () => void }).on_progress;
|
|
689
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
690
|
+
onProgress?.();
|
|
691
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
692
|
+
onProgress?.();
|
|
693
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
694
|
+
return { enhanced_prompt: "heartbeat-kept-alive" };
|
|
695
|
+
});
|
|
696
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
697
|
+
enhancerProvider: {
|
|
698
|
+
generate,
|
|
699
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
700
|
+
},
|
|
701
|
+
enhancerGenerationConfig: {
|
|
702
|
+
timeout_ms: 40,
|
|
703
|
+
max_retries: 0,
|
|
704
|
+
tool_mode: "none"
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const output = await core.enhancePrompt({
|
|
709
|
+
trace_id: "trc-provider-heartbeat",
|
|
710
|
+
tenant_id: "tenant-dev",
|
|
711
|
+
workspace_id: "ws-dev",
|
|
712
|
+
request: {
|
|
713
|
+
prompt: "Tighten workspace checks and keep behavior stable.",
|
|
714
|
+
project_root_path: "/workspace/dev",
|
|
715
|
+
conversation_history: [{ role: "user", content: "No contract changes." }]
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
expect(output.enhanced_prompt).toBe("heartbeat-kept-alive");
|
|
719
|
+
expect(generate).toHaveBeenCalledTimes(1);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("does not retry enhancer provider when response shape is invalid", async () => {
|
|
723
|
+
const generate = vi.fn(async () => ({
|
|
724
|
+
enhanced_prompt: ""
|
|
725
|
+
}));
|
|
726
|
+
const core = new RetrievalCore(new InMemoryIndexStore(), new InMemoryQueryCache(), {
|
|
727
|
+
enhancerProvider: {
|
|
728
|
+
generate,
|
|
729
|
+
describe: () => ({ provider: "test_enhancer", model: "stub-v1" })
|
|
730
|
+
},
|
|
731
|
+
enhancerGenerationConfig: {
|
|
732
|
+
timeout_ms: 500,
|
|
733
|
+
max_retries: 3,
|
|
734
|
+
tool_mode: "none"
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
await expect(
|
|
739
|
+
core.enhancePrompt({
|
|
740
|
+
trace_id: "trc-invalid-response-no-retry",
|
|
741
|
+
tenant_id: "tenant-dev",
|
|
742
|
+
workspace_id: "ws-dev",
|
|
743
|
+
request: {
|
|
744
|
+
prompt: "Harden tenant authorization and keep behavior stable",
|
|
745
|
+
project_root_path: "/workspace/dev",
|
|
746
|
+
conversation_history: [{ role: "user", content: "Add regression tests." }]
|
|
747
|
+
}
|
|
748
|
+
})
|
|
749
|
+
).rejects.toThrow(/empty enhanced_prompt/i);
|
|
750
|
+
expect(generate).toHaveBeenCalledTimes(1);
|
|
751
|
+
});
|
|
752
|
+
});
|