@devflow-tools/context-engine 0.9.0 → 0.11.0
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/dist/claude-rerank.d.ts +15 -0
- package/dist/claude-rerank.d.ts.map +1 -0
- package/dist/claude-rerank.js +93 -0
- package/dist/claude-rerank.js.map +1 -0
- package/dist/context-engine.d.ts +9 -1
- package/dist/context-engine.d.ts.map +1 -1
- package/dist/context-engine.js +316 -41
- package/dist/context-engine.js.map +1 -1
- package/dist/create-engines.d.ts +2 -2
- package/dist/create-engines.d.ts.map +1 -1
- package/dist/create-engines.js +32 -6
- package/dist/create-engines.js.map +1 -1
- package/dist/graph-adapter.d.ts +15 -0
- package/dist/graph-adapter.d.ts.map +1 -1
- package/dist/graph-adapter.js +140 -1
- package/dist/graph-adapter.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer-adapter.d.ts +9 -1
- package/dist/indexer-adapter.d.ts.map +1 -1
- package/dist/indexer-adapter.js +95 -4
- package/dist/indexer-adapter.js.map +1 -1
- package/dist/intent-parser.d.ts.map +1 -1
- package/dist/intent-parser.js +26 -6
- package/dist/intent-parser.js.map +1 -1
- package/dist/layer-rules.d.ts +11 -0
- package/dist/layer-rules.d.ts.map +1 -0
- package/dist/layer-rules.js +72 -0
- package/dist/layer-rules.js.map +1 -0
- package/dist/query-cache.d.ts +11 -0
- package/dist/query-cache.d.ts.map +1 -0
- package/dist/query-cache.js +33 -0
- package/dist/query-cache.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -6
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LLMClient } from '@devflow-tools/sdk';
|
|
2
|
+
export interface RerankCandidate {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
snippet: string;
|
|
6
|
+
file?: string;
|
|
7
|
+
score?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function setRerankLLMClient(client: LLMClient): void;
|
|
10
|
+
export declare function isRerankAvailable(): boolean;
|
|
11
|
+
export declare function claudeRerank(query: string, candidates: RerankCandidate[], options?: {
|
|
12
|
+
topK?: number;
|
|
13
|
+
model?: string;
|
|
14
|
+
}): Promise<RerankCandidate[]>;
|
|
15
|
+
//# sourceMappingURL=claude-rerank.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-rerank.d.ts","sourceRoot":"","sources":["../src/claude-rerank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAKpD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAE1D;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAwCD,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,eAAe,EAAE,EAC7B,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,eAAe,EAAE,CAAC,CAsC5B"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const TIMEOUT_MS = 2000;
|
|
2
|
+
const CACHE_TTL = 5 * 60 * 1000;
|
|
3
|
+
const RERANK_SYSTEM_PROMPT = `You are a code search relevance ranker. Given a user query and
|
|
4
|
+
a list of code search results, return the indices of the most relevant results
|
|
5
|
+
in order of relevance. Only include results that are genuinely relevant to the query.
|
|
6
|
+
Respond with ONLY a JSON array of indices, like [3, 0, 7, 1]. Do not explain.`;
|
|
7
|
+
let llmClient = null;
|
|
8
|
+
export function setRerankLLMClient(client) {
|
|
9
|
+
llmClient = client;
|
|
10
|
+
}
|
|
11
|
+
export function isRerankAvailable() {
|
|
12
|
+
return llmClient?.isAvailable ?? false;
|
|
13
|
+
}
|
|
14
|
+
// LRU cache
|
|
15
|
+
const cache = new Map();
|
|
16
|
+
function cacheGet(query) {
|
|
17
|
+
const key = query.toLowerCase().trim();
|
|
18
|
+
const entry = cache.get(key);
|
|
19
|
+
if (entry && Date.now() - entry.ts < CACHE_TTL)
|
|
20
|
+
return entry.indices;
|
|
21
|
+
cache.delete(key);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
// Concurrency semaphore
|
|
25
|
+
let activeCalls = 0;
|
|
26
|
+
const MAX_CONCURRENT = 2;
|
|
27
|
+
async function acquireSemaphore() {
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
while (activeCalls >= MAX_CONCURRENT) {
|
|
30
|
+
if (Date.now() - start > 5000)
|
|
31
|
+
return false;
|
|
32
|
+
await new Promise(r => setTimeout(r, 100));
|
|
33
|
+
}
|
|
34
|
+
activeCalls++;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function releaseSemaphore() { activeCalls = Math.max(0, activeCalls - 1); }
|
|
38
|
+
// Multi-pattern response parser
|
|
39
|
+
function parseIndices(text, maxIndex) {
|
|
40
|
+
const patterns = [
|
|
41
|
+
t => { try {
|
|
42
|
+
const v = JSON.parse(t);
|
|
43
|
+
if (Array.isArray(v) && v.every(x => typeof x === 'number'))
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
catch { } return null; },
|
|
47
|
+
t => { const m = t.match(/```(?:json)?\s*([\s\S]*?)```/); return m ? parseIndices(m[1], maxIndex) : null; },
|
|
48
|
+
t => { const m = t.match(/\[([0-9,\s]+)\]/); return m ? m[1].split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n >= 0 && n < maxIndex) : null; },
|
|
49
|
+
];
|
|
50
|
+
for (const p of patterns) {
|
|
51
|
+
const result = p(text);
|
|
52
|
+
if (result !== null && result.length > 0)
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
export async function claudeRerank(query, candidates, options) {
|
|
58
|
+
const topK = options?.topK ?? 10;
|
|
59
|
+
if (candidates.length <= topK)
|
|
60
|
+
return candidates;
|
|
61
|
+
const cached = cacheGet(query);
|
|
62
|
+
if (cached)
|
|
63
|
+
return cached.map(i => candidates[i]).filter(Boolean).slice(0, topK);
|
|
64
|
+
if (!llmClient?.isAvailable) {
|
|
65
|
+
return candidates.slice(0, topK);
|
|
66
|
+
}
|
|
67
|
+
if (!(await acquireSemaphore())) {
|
|
68
|
+
return candidates.slice(0, topK);
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const candidatesJson = candidates.map((c, i) => ({
|
|
72
|
+
index: i, id: c.id, name: c.name,
|
|
73
|
+
snippet: c.snippet.slice(0, 150),
|
|
74
|
+
file: c.file ?? '',
|
|
75
|
+
}));
|
|
76
|
+
const response = await llmClient.chat({
|
|
77
|
+
systemPrompt: RERANK_SYSTEM_PROMPT,
|
|
78
|
+
messages: [{ role: 'user', content: `Query: ${query}\n\nCandidates:\n${JSON.stringify(candidatesJson)}` }],
|
|
79
|
+
maxTokens: 256,
|
|
80
|
+
});
|
|
81
|
+
const indices = parseIndices(response.content.trim(), candidates.length);
|
|
82
|
+
if (indices) {
|
|
83
|
+
cache.set(query.toLowerCase().trim(), { indices, ts: Date.now() });
|
|
84
|
+
return indices.map(i => candidates[i]).filter(Boolean).slice(0, topK);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch { /* silent fallback */ }
|
|
88
|
+
finally {
|
|
89
|
+
releaseSemaphore();
|
|
90
|
+
}
|
|
91
|
+
return candidates.slice(0, topK);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=claude-rerank.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-rerank.js","sourceRoot":"","sources":["../src/claude-rerank.ts"],"names":[],"mappings":"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"}
|
package/dist/context-engine.d.ts
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import type { ContextRequest, ContextResult, IndexStatus, IDataProvider, TaskContextResult } from "@devflow-tools/sdk";
|
|
2
|
-
import type { MemoryEngine } from "@devflow-tools/memory-engine";
|
|
2
|
+
import type { MemoryEngine, EmbeddingProvider } from "@devflow-tools/memory-engine";
|
|
3
3
|
import type { KnowledgeEngine } from "@devflow-tools/knowledge-engine";
|
|
4
4
|
import type { GraphAdapter } from "./graph-adapter.js";
|
|
5
|
+
import type { IndexerAdapter } from "./indexer-adapter.js";
|
|
5
6
|
import type { CodeGraph } from "@colbymchenry/codegraph";
|
|
6
7
|
export interface ContextEngineDeps {
|
|
7
8
|
cg: CodeGraph;
|
|
8
9
|
data: IDataProvider;
|
|
9
10
|
graph: GraphAdapter;
|
|
11
|
+
indexer: IndexerAdapter;
|
|
10
12
|
memory: MemoryEngine;
|
|
11
13
|
knowledge: KnowledgeEngine;
|
|
14
|
+
embedder?: EmbeddingProvider;
|
|
12
15
|
}
|
|
13
16
|
export declare class ContextEngine {
|
|
14
17
|
private cg;
|
|
15
18
|
private data;
|
|
16
19
|
private graph;
|
|
20
|
+
private indexer;
|
|
17
21
|
private memory;
|
|
18
22
|
private knowledge;
|
|
23
|
+
private embedder?;
|
|
19
24
|
constructor(deps: ContextEngineDeps);
|
|
25
|
+
private queryCache;
|
|
20
26
|
index(_projectRoot: string): Promise<void>;
|
|
21
27
|
build(request: ContextRequest): Promise<ContextResult>;
|
|
22
28
|
private logAccuracyQuery;
|
|
23
29
|
buildTaskContext(query: string, options?: {
|
|
24
30
|
maxTokens?: number;
|
|
25
31
|
mode?: "fast" | "deep";
|
|
32
|
+
sessionId?: string;
|
|
26
33
|
}): Promise<TaskContextResult>;
|
|
34
|
+
private buildTaskContextLegacy;
|
|
27
35
|
getStatus(): IndexStatus;
|
|
28
36
|
}
|
|
29
37
|
//# sourceMappingURL=context-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-engine.d.ts","sourceRoot":"","sources":["../src/context-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEb,WAAW,EACX,aAAa,EACb,iBAAiB,EAMlB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"context-engine.d.ts","sourceRoot":"","sources":["../src/context-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEb,WAAW,EACX,aAAa,EACb,iBAAiB,EAMlB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAqLzD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAY;IACtB,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAAoB;gBAEzB,IAAI,EAAE,iBAAiB;IAUnC,OAAO,CAAC,UAAU,CAAkD;IAE9D,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1C,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;YAqP9C,gBAAgB;IAexB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAoOjI,sBAAsB;IAyLpC,SAAS,IAAI,WAAW;CASzB"}
|
package/dist/context-engine.js
CHANGED
|
@@ -2,6 +2,8 @@ import CGModule from "@colbymchenry/codegraph";
|
|
|
2
2
|
import { parseIntent, expandQuery } from "./intent-parser.js";
|
|
3
3
|
import { fuzzySearch, shouldFallback } from "./fuzzy-fallback.js";
|
|
4
4
|
import { compressToTokenLimit } from "./compressor.js";
|
|
5
|
+
import { claudeRerank } from "./claude-rerank.js";
|
|
6
|
+
import { QueryCache } from "./query-cache.js";
|
|
5
7
|
const CG = CGModule.default ?? CGModule;
|
|
6
8
|
/**
|
|
7
9
|
* Merge the original query with L1 dict expansion and Agent-provided English terms.
|
|
@@ -19,6 +21,45 @@ function mergeQueries(original, l1, agentTerms) {
|
|
|
19
21
|
}
|
|
20
22
|
return [...terms].join(" ");
|
|
21
23
|
}
|
|
24
|
+
function sigmoid(x, midpoint = 0.5, steepness = 6) {
|
|
25
|
+
return 1 / (1 + Math.exp(-steepness * (x - midpoint)));
|
|
26
|
+
}
|
|
27
|
+
function calculateConfidence(input) {
|
|
28
|
+
// CodeGraph score: based on provided confidence, node count, and structural signals
|
|
29
|
+
let cgScore = typeof input.cgConfidence === "number" ? input.cgConfidence : 0;
|
|
30
|
+
if (cgScore === 0 && input.nodeCount > 0) {
|
|
31
|
+
// Derive from node count when explicit confidence unavailable
|
|
32
|
+
cgScore = sigmoid(Math.min(input.nodeCount, 30) / 30, 0.3, 8);
|
|
33
|
+
}
|
|
34
|
+
// Bonus for structural richness
|
|
35
|
+
if (input.hasEdges)
|
|
36
|
+
cgScore = Math.min(1, cgScore + 0.1);
|
|
37
|
+
if (input.hasExactMatches)
|
|
38
|
+
cgScore = Math.min(1, cgScore + 0.15);
|
|
39
|
+
// Knowledge score: diminishing returns after 5 hits
|
|
40
|
+
const knowledgeScore = sigmoid(input.knowledgeHitCount / 10, 0.3, 5);
|
|
41
|
+
// Memory score: diminishing returns
|
|
42
|
+
const memoryScore = sigmoid(input.memoryHitCount / 10, 0.25, 5);
|
|
43
|
+
// Node count score
|
|
44
|
+
const nodeScore = sigmoid(input.nodeCount / 30, 0.3, 5);
|
|
45
|
+
// Intent confidence
|
|
46
|
+
const intentScore = input.intentConfidence;
|
|
47
|
+
const weighted = cgScore * 0.35 +
|
|
48
|
+
knowledgeScore * 0.25 +
|
|
49
|
+
memoryScore * 0.2 +
|
|
50
|
+
nodeScore * 0.1 +
|
|
51
|
+
intentScore * 0.1;
|
|
52
|
+
let confidence = weighted;
|
|
53
|
+
// Diversity bonus: all five signals present
|
|
54
|
+
const activeSignals = [cgScore > 0, input.knowledgeHitCount > 0, input.memoryHitCount > 0, input.nodeCount > 0, input.intentConfidence > 0].filter(Boolean).length;
|
|
55
|
+
if (activeSignals >= 4)
|
|
56
|
+
confidence += 0.05;
|
|
57
|
+
// Penalty: very low node count with no other signals
|
|
58
|
+
if (input.nodeCount < 3 && input.knowledgeHitCount === 0 && input.memoryHitCount === 0) {
|
|
59
|
+
confidence *= 0.5;
|
|
60
|
+
}
|
|
61
|
+
return Math.round(Math.min(1, Math.max(0, confidence)) * 100) / 100;
|
|
62
|
+
}
|
|
22
63
|
function getAdaptiveParams(fileCount) {
|
|
23
64
|
if (fileCount <= 200)
|
|
24
65
|
return { depth: 1, searchLimit: 5, maxNodes: 10 };
|
|
@@ -103,17 +144,22 @@ export class ContextEngine {
|
|
|
103
144
|
cg;
|
|
104
145
|
data;
|
|
105
146
|
graph;
|
|
147
|
+
indexer;
|
|
106
148
|
memory;
|
|
107
149
|
knowledge;
|
|
150
|
+
embedder;
|
|
108
151
|
constructor(deps) {
|
|
109
152
|
this.cg = deps.cg;
|
|
110
153
|
this.data = deps.data;
|
|
111
154
|
this.graph = deps.graph;
|
|
155
|
+
this.indexer = deps.indexer;
|
|
112
156
|
this.memory = deps.memory;
|
|
113
157
|
this.knowledge = deps.knowledge;
|
|
158
|
+
this.embedder = deps.embedder;
|
|
114
159
|
}
|
|
160
|
+
queryCache = new QueryCache(60_000, 100);
|
|
115
161
|
async index(_projectRoot) {
|
|
116
|
-
await this.
|
|
162
|
+
await this.indexer.reconcile();
|
|
117
163
|
}
|
|
118
164
|
async build(request) {
|
|
119
165
|
const maxTokens = request.maxTokens ?? 8000;
|
|
@@ -147,37 +193,66 @@ export class ContextEngine {
|
|
|
147
193
|
}
|
|
148
194
|
}
|
|
149
195
|
}
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
// Embedding reranking (if available)
|
|
197
|
+
if (this.embedder?.isReady()) {
|
|
198
|
+
try {
|
|
199
|
+
const nodesList = Array.from(cgResult.nodes.values());
|
|
200
|
+
const nodeTexts = nodesList.map(n => `${n.name} ${n.signature ?? ''} ${n.kind} ${n.filePath}`);
|
|
201
|
+
const queryEmbedding = await this.embedder.embed(combinedQuery);
|
|
202
|
+
const nodeEmbeddings = await this.embedder.embedBatch(nodeTexts);
|
|
203
|
+
const scoredNodes = nodesList.map((node, i) => {
|
|
204
|
+
const nodeEmb = nodeEmbeddings[i];
|
|
205
|
+
let dot = 0, normA = 0, normB = 0;
|
|
206
|
+
for (let j = 0; j < queryEmbedding.length; j++) {
|
|
207
|
+
dot += queryEmbedding[j] * nodeEmb[j];
|
|
208
|
+
normA += queryEmbedding[j] * queryEmbedding[j];
|
|
209
|
+
normB += nodeEmb[j] * nodeEmb[j];
|
|
210
|
+
}
|
|
211
|
+
const score = dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
212
|
+
return { node, score };
|
|
213
|
+
});
|
|
214
|
+
scoredNodes.sort((a, b) => b.score - a.score);
|
|
215
|
+
cgResult.nodes = new Map();
|
|
216
|
+
const topK = Math.min(scoredNodes.length, request.mode === "deep" ? 30 : adaptive.maxNodes);
|
|
217
|
+
for (const { node } of scoredNodes.slice(0, topK)) {
|
|
218
|
+
cgResult.nodes.set(node.id, node);
|
|
219
|
+
}
|
|
220
|
+
cgResult.roots = scoredNodes.slice(0, 3).map(s => s.node.id);
|
|
221
|
+
}
|
|
222
|
+
catch { /* fall back to BM25 ranking */ }
|
|
171
223
|
}
|
|
172
|
-
|
|
173
|
-
|
|
224
|
+
// Claude rerank (second pass, if available)
|
|
225
|
+
const allNodesList = Array.from(cgResult.nodes.values());
|
|
226
|
+
if (allNodesList.length > 30) {
|
|
227
|
+
try {
|
|
228
|
+
const reranked = await claudeRerank(combinedQuery, allNodesList.map(n => ({ id: n.id, name: n.name, snippet: n.signature ?? '', file: n.filePath })), { topK: 30 });
|
|
229
|
+
cgResult.nodes = new Map(reranked.map(r => [r.id, allNodesList.find(n => n.id === r.id)]));
|
|
230
|
+
cgResult.roots = reranked.slice(0, 3).map(r => r.id);
|
|
231
|
+
}
|
|
232
|
+
catch { /* keep embedding/bm25 ranking */ }
|
|
174
233
|
}
|
|
234
|
+
// 2. Knowledge + Memory in parallel
|
|
235
|
+
const [kResultsRaw, memResultsRaw] = await Promise.all([
|
|
236
|
+
this.knowledge.search(request.query, { limit: 10 }).catch(() => []),
|
|
237
|
+
this.memory.search(request.query, { limit: 5 }).catch(() => []),
|
|
238
|
+
]);
|
|
239
|
+
const knowledgeHits = kResultsRaw.map((k) => ({
|
|
240
|
+
title: k.title,
|
|
241
|
+
content: k.content,
|
|
242
|
+
source: k.source,
|
|
243
|
+
}));
|
|
244
|
+
const memoryContext = memResultsRaw.length > 0 ? {
|
|
245
|
+
relevant: memResultsRaw.map((r) => ({
|
|
246
|
+
content: r.entry.content,
|
|
247
|
+
category: r.entry.category,
|
|
248
|
+
score: r.score,
|
|
249
|
+
})),
|
|
250
|
+
} : undefined;
|
|
175
251
|
// 4. Build file snippets, layer by quality
|
|
176
252
|
const MIN_CORE_SCORE = 0.7;
|
|
177
253
|
const MIN_WEAK_SCORE = 0.4;
|
|
178
254
|
const L1_TOKEN_RATIO = 0.6;
|
|
179
255
|
const L2_TOKEN_RATIO = 0.3;
|
|
180
|
-
const EXTERNAL_MAX_RATIO = 0.1;
|
|
181
256
|
const ESTIMATED_TOKENS_PER_CHAR = 0.25;
|
|
182
257
|
function estimateTokens(text) {
|
|
183
258
|
return Math.ceil(text.length * ESTIMATED_TOKENS_PER_CHAR);
|
|
@@ -241,7 +316,6 @@ export class ContextEngine {
|
|
|
241
316
|
let remaining = maxTokens;
|
|
242
317
|
const l1Cap = Math.floor(maxTokens * L1_TOKEN_RATIO);
|
|
243
318
|
const l2Cap = Math.floor(maxTokens * L2_TOKEN_RATIO);
|
|
244
|
-
const l3Cap = Math.floor(maxTokens * EXTERNAL_MAX_RATIO);
|
|
245
319
|
// L1: by score descending
|
|
246
320
|
let l1Used = 0;
|
|
247
321
|
for (const f of l1Files.sort((a, b) => (b.score ?? 0) - (a.score ?? 0))) {
|
|
@@ -264,18 +338,6 @@ export class ContextEngine {
|
|
|
264
338
|
remaining -= tokens;
|
|
265
339
|
l2Used += tokens;
|
|
266
340
|
}
|
|
267
|
-
// L3: fill remaining (max 10% budget)
|
|
268
|
-
let l3Used = 0;
|
|
269
|
-
for (const f of l3Files) {
|
|
270
|
-
const tokens = Math.min(estimateTokens(f.snippet), 200);
|
|
271
|
-
if (remaining < 100)
|
|
272
|
-
break;
|
|
273
|
-
if (l3Used + tokens > l3Cap)
|
|
274
|
-
continue;
|
|
275
|
-
allFiles.push(f);
|
|
276
|
-
remaining -= tokens;
|
|
277
|
-
l3Used += tokens;
|
|
278
|
-
}
|
|
279
341
|
// fallback: if nothing made it in, push top L1
|
|
280
342
|
if (allFiles.length === 0 && l1Files.length > 0) {
|
|
281
343
|
allFiles.push(l1Files.sort((a, b) => (b.score ?? 0) - (a.score ?? 0))[0]);
|
|
@@ -310,19 +372,20 @@ export class ContextEngine {
|
|
|
310
372
|
file: n.filePath,
|
|
311
373
|
kind: n.kind,
|
|
312
374
|
}));
|
|
313
|
-
this.logAccuracyQuery(queryId, 'codegraph', request.query, topKResults).catch(() => { });
|
|
375
|
+
this.logAccuracyQuery(queryId, 'codegraph', request.query, topKResults, request.sessionId).catch(() => { });
|
|
314
376
|
return {
|
|
315
377
|
files: allFiles,
|
|
378
|
+
knowledgeFiles: l3Files.length > 0 ? l3Files : undefined,
|
|
316
379
|
graph: graphOutput,
|
|
317
380
|
memory: memoryContext,
|
|
318
381
|
confidence: intent.confidence,
|
|
319
382
|
tokenCount,
|
|
320
383
|
};
|
|
321
384
|
}
|
|
322
|
-
async logAccuracyQuery(id, engine, query, topKResults) {
|
|
385
|
+
async logAccuracyQuery(id, engine, query, topKResults, sessionId) {
|
|
323
386
|
try {
|
|
324
387
|
const http = await import('node:http');
|
|
325
|
-
const body = JSON.stringify({ id, sessionId: 'unknown', engine, query, topKResults });
|
|
388
|
+
const body = JSON.stringify({ id, sessionId: sessionId ?? 'unknown', engine, query, topKResults });
|
|
326
389
|
const req = http.request({
|
|
327
390
|
hostname: '127.0.0.1', port: 13337, path: '/api/telemetry/accuracy/query',
|
|
328
391
|
method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'dev-key-000000', 'Content-Length': Buffer.byteLength(body) },
|
|
@@ -335,6 +398,212 @@ export class ContextEngine {
|
|
|
335
398
|
catch { /* best-effort logging */ }
|
|
336
399
|
}
|
|
337
400
|
async buildTaskContext(query, options) {
|
|
401
|
+
const maxTokens = options?.maxTokens ?? 8000;
|
|
402
|
+
const mode = options?.mode ?? "fast";
|
|
403
|
+
const cacheKey = `ctx:${query}:${mode}`;
|
|
404
|
+
const cached = this.queryCache.get(cacheKey);
|
|
405
|
+
if (cached)
|
|
406
|
+
return cached;
|
|
407
|
+
const intent = parseIntent(query);
|
|
408
|
+
const taskType = intent.intent;
|
|
409
|
+
const timing = {};
|
|
410
|
+
const t0 = Date.now();
|
|
411
|
+
const [cgResult, knowledgeHits, memoryHits] = await Promise.all([
|
|
412
|
+
Promise.race([
|
|
413
|
+
this.cg.buildContext(query, {
|
|
414
|
+
searchLimit: mode === "deep" ? 10 : 5,
|
|
415
|
+
traversalDepth: mode === "deep" ? 3 : 2,
|
|
416
|
+
maxNodes: mode === "deep" ? 50 : 20,
|
|
417
|
+
includeCode: false,
|
|
418
|
+
format: "json",
|
|
419
|
+
}),
|
|
420
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("buildContext timeout")), 15000)),
|
|
421
|
+
]).catch((err) => {
|
|
422
|
+
console.error("[devflow] cg.buildContext failed, falling back:", err.message);
|
|
423
|
+
return null;
|
|
424
|
+
}),
|
|
425
|
+
Promise.race([
|
|
426
|
+
this.knowledge.search(query, { limit: 5 }),
|
|
427
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("knowledge timeout")), 5000)),
|
|
428
|
+
]).catch((err) => {
|
|
429
|
+
console.error("[devflow] knowledge.search timeout:", err.message);
|
|
430
|
+
return [];
|
|
431
|
+
}),
|
|
432
|
+
Promise.race([
|
|
433
|
+
this.memory.search(query, { limit: 5 }),
|
|
434
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("memory timeout")), 5000)),
|
|
435
|
+
]).catch((err) => {
|
|
436
|
+
console.error("[devflow] memory.search timeout:", err.message);
|
|
437
|
+
return [];
|
|
438
|
+
}),
|
|
439
|
+
]);
|
|
440
|
+
timing["1_promise_all"] = Date.now() - t0;
|
|
441
|
+
console.error("[devflow:timing] Promise.all:", timing["1_promise_all"], "ms");
|
|
442
|
+
if (!cgResult) {
|
|
443
|
+
console.error("[devflow] buildContext failed, falling back to manual pipeline");
|
|
444
|
+
const result = await this.buildTaskContextLegacy(query, options);
|
|
445
|
+
result._devflow = { fallback: "buildContext-unavailable" };
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
// buildContext returns JSON string when format="json" — parse it
|
|
449
|
+
let ctx;
|
|
450
|
+
if (typeof cgResult === "string") {
|
|
451
|
+
try {
|
|
452
|
+
ctx = JSON.parse(cgResult);
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
console.error("[devflow] buildContext returned unparseable string, falling back");
|
|
456
|
+
return this.buildTaskContextLegacy(query, options);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
ctx = cgResult;
|
|
461
|
+
}
|
|
462
|
+
// Fallback: direct searchNodes for query terms that buildContext may have missed
|
|
463
|
+
// (e.g. hyphenated terms like "post-tool-use" get tokenized and lost in FTS noise)
|
|
464
|
+
const tFallback = Date.now();
|
|
465
|
+
const existingFiles = new Set((ctx.nodes ?? []).map((n) => n.filePath));
|
|
466
|
+
const existingIds = new Set((ctx.nodes ?? []).map((n) => n.id));
|
|
467
|
+
const queryTerms = query.split(/\s+/).filter(w => w.length > 2).sort((a, b) => b.length - a.length).slice(0, 3);
|
|
468
|
+
for (const term of queryTerms) {
|
|
469
|
+
const hits = this.cg.searchNodes(term, { limit: 3 });
|
|
470
|
+
for (const hit of hits) {
|
|
471
|
+
if (!existingFiles.has(hit.node.filePath)) {
|
|
472
|
+
const fileNodes = this.cg.getNodesInFile(hit.node.filePath);
|
|
473
|
+
let firstSymbol = null;
|
|
474
|
+
for (const fn of fileNodes) {
|
|
475
|
+
if (fn.kind === "file" || fn.kind === "import" || existingIds.has(fn.id))
|
|
476
|
+
continue;
|
|
477
|
+
ctx.nodes.push(fn);
|
|
478
|
+
existingIds.add(fn.id);
|
|
479
|
+
if (!firstSymbol)
|
|
480
|
+
firstSymbol = fn;
|
|
481
|
+
}
|
|
482
|
+
existingFiles.add(hit.node.filePath);
|
|
483
|
+
// Use a real symbol as entry point, not the file wrapper
|
|
484
|
+
if (firstSymbol) {
|
|
485
|
+
ctx.entryPoints.push(firstSymbol);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
console.error("[devflow:timing] Fallback searchNodes loop:", Date.now() - tFallback, "ms");
|
|
491
|
+
timing["2_fallback_loop"] = Date.now() - tFallback;
|
|
492
|
+
const tSnippet = Date.now();
|
|
493
|
+
const allNodes = ctx.nodes ?? [];
|
|
494
|
+
const rootIds = new Set((ctx.entryPoints ?? []).map((ep) => ep.id));
|
|
495
|
+
const requiredFileSet = new Set();
|
|
496
|
+
const optionalFileSet = new Set();
|
|
497
|
+
const keySymbols = [];
|
|
498
|
+
const entryFileSet = new Set(ctx.entryPoints?.map((ep) => ep.filePath) ?? []);
|
|
499
|
+
for (const node of allNodes) {
|
|
500
|
+
const isDirect = rootIds.has(node.id) || entryFileSet.has(node.filePath);
|
|
501
|
+
(isDirect ? requiredFileSet : optionalFileSet).add(node.filePath);
|
|
502
|
+
keySymbols.push({
|
|
503
|
+
name: node.name,
|
|
504
|
+
file: node.filePath,
|
|
505
|
+
kind: node.kind,
|
|
506
|
+
relevance: isDirect ? "direct" : "indirect",
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
const cgReasoning = [{
|
|
510
|
+
source: "graph_root",
|
|
511
|
+
fileGroup: "codegraph-buildContext",
|
|
512
|
+
explanation: ctx.summary
|
|
513
|
+
? `CodeGraph: ${ctx.summary}`
|
|
514
|
+
: `CodeGraph matched ${allNodes.length} nodes across ${requiredFileSet.size + optionalFileSet.size} files`,
|
|
515
|
+
}];
|
|
516
|
+
const knowledgeReasoning = knowledgeHits.map((k) => ({
|
|
517
|
+
source: "knowledge",
|
|
518
|
+
fileGroup: k.source ?? "knowledge",
|
|
519
|
+
explanation: `Knowledge entry "${k.title}" matched query`,
|
|
520
|
+
}));
|
|
521
|
+
const memoryReasoning = memoryHits.map((m) => ({
|
|
522
|
+
source: "memory",
|
|
523
|
+
fileGroup: m.entry?.category ?? "memory",
|
|
524
|
+
explanation: `Memory entry matched: ${String(m.entry?.content ?? "").slice(0, 100)}`,
|
|
525
|
+
}));
|
|
526
|
+
const allFileSnippets = [];
|
|
527
|
+
for (const fp of [...requiredFileSet].sort()) {
|
|
528
|
+
const nodesInFile = allNodes.filter(n => n.filePath === fp);
|
|
529
|
+
allFileSnippets.push({
|
|
530
|
+
path: fp,
|
|
531
|
+
snippet: nodesInFile.map(n => `// ${n.kind}: ${n.name}${n.signature ? ` ${n.signature}` : ""}`).join("\n"),
|
|
532
|
+
symbols: nodesInFile.map(n => n.name),
|
|
533
|
+
score: 1.0,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
for (const fp of [...optionalFileSet].sort()) {
|
|
537
|
+
const nodesInFile = allNodes.filter(n => n.filePath === fp);
|
|
538
|
+
allFileSnippets.push({
|
|
539
|
+
path: fp,
|
|
540
|
+
snippet: nodesInFile.map(n => `// ${n.kind}: ${n.name}${n.signature ? ` ${n.signature}` : ""}`).join("\n"),
|
|
541
|
+
symbols: nodesInFile.map(n => n.name),
|
|
542
|
+
score: 0.5,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const compressed = compressToTokenLimit(allFileSnippets, maxTokens);
|
|
546
|
+
console.error("[devflow:timing] Snippet building + compress:", Date.now() - tSnippet, "ms");
|
|
547
|
+
timing["3_snippet_compress"] = Date.now() - tSnippet;
|
|
548
|
+
const riskHints = generateRiskHints(allNodes);
|
|
549
|
+
const nextActions = generateNextActions(taskType, keySymbols.slice(0, 5));
|
|
550
|
+
const result = {
|
|
551
|
+
taskType,
|
|
552
|
+
requiredFiles: {
|
|
553
|
+
files: [...requiredFileSet],
|
|
554
|
+
symbols: keySymbols.filter(s => s.relevance === "direct").map(s => s.name).slice(0, 30),
|
|
555
|
+
reasoning: cgReasoning,
|
|
556
|
+
},
|
|
557
|
+
optionalFiles: {
|
|
558
|
+
files: [...optionalFileSet],
|
|
559
|
+
symbols: keySymbols.filter(s => s.relevance === "indirect").map(s => s.name).slice(0, 30),
|
|
560
|
+
reasoning: [...knowledgeReasoning, ...memoryReasoning],
|
|
561
|
+
},
|
|
562
|
+
keySymbols: keySymbols
|
|
563
|
+
.sort((a, b) => {
|
|
564
|
+
// Direct before indirect
|
|
565
|
+
if (a.relevance === "direct" && b.relevance !== "direct")
|
|
566
|
+
return -1;
|
|
567
|
+
if (a.relevance !== "direct" && b.relevance === "direct")
|
|
568
|
+
return 1;
|
|
569
|
+
// Functions/classes before constants/variables
|
|
570
|
+
const kindScore = (k) => k === "function" || k === "method" || k === "class" ? 0 :
|
|
571
|
+
k === "component" || k === "interface" || k === "type" ? 1 :
|
|
572
|
+
k === "variable" || k === "constant" || k === "enum" ? 2 : 3;
|
|
573
|
+
return kindScore(a.kind) - kindScore(b.kind);
|
|
574
|
+
})
|
|
575
|
+
.slice(0, 20),
|
|
576
|
+
graphSummary: {
|
|
577
|
+
roots: [...rootIds],
|
|
578
|
+
traversalDepth: ctx.stats?.traversalDepth ?? 2,
|
|
579
|
+
relatedNodeCount: allNodes.length,
|
|
580
|
+
},
|
|
581
|
+
riskHints,
|
|
582
|
+
nextActions,
|
|
583
|
+
reasoning: [...cgReasoning, ...knowledgeReasoning, ...memoryReasoning],
|
|
584
|
+
confidence: calculateConfidence({
|
|
585
|
+
cgConfidence: ctx.stats?.confidence,
|
|
586
|
+
knowledgeHitCount: knowledgeHits.length,
|
|
587
|
+
memoryHitCount: memoryHits.length,
|
|
588
|
+
nodeCount: allNodes.length,
|
|
589
|
+
intentConfidence: intent.confidence,
|
|
590
|
+
hasEdges: Array.isArray(ctx.edges) && (ctx.edges.length > 0),
|
|
591
|
+
hasExactMatches: keySymbols.some(s => s.relevance === "direct"),
|
|
592
|
+
}),
|
|
593
|
+
tokenCount: compressed.tokenCount,
|
|
594
|
+
};
|
|
595
|
+
this.queryCache.set(cacheKey, result);
|
|
596
|
+
timing["0_total"] = Date.now() - t0;
|
|
597
|
+
console.error("[devflow:timing] buildTaskContext TOTAL:", timing["0_total"], "ms");
|
|
598
|
+
result._timing = timing;
|
|
599
|
+
const queryId = `acc:${Date.now()}:${Math.random().toString(36).slice(2, 9)}`;
|
|
600
|
+
const topKResults = keySymbols.slice(0, 10).map(s => ({
|
|
601
|
+
id: s.name, name: s.name, file: s.file, kind: s.kind,
|
|
602
|
+
}));
|
|
603
|
+
this.logAccuracyQuery(queryId, "codegraph", query, topKResults, options?.sessionId).catch(() => { });
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
async buildTaskContextLegacy(query, options) {
|
|
338
607
|
const stats = this.cg.getStats();
|
|
339
608
|
const adaptive = getAdaptiveParams(stats.fileCount ?? 500);
|
|
340
609
|
const maxTokens = options?.maxTokens ?? 8000;
|
|
@@ -418,6 +687,12 @@ export class ContextEngine {
|
|
|
418
687
|
kind: n.kind,
|
|
419
688
|
relevance: (rootIds.has(n.id) ? "direct" : "indirect"),
|
|
420
689
|
}));
|
|
690
|
+
// Accuracy logging
|
|
691
|
+
const queryId = `acc:${Date.now()}:${Math.random().toString(36).slice(2, 9)}`;
|
|
692
|
+
const topKResults = keySymbols.slice(0, 10).map(s => ({
|
|
693
|
+
id: s.name, name: s.name, file: s.file, kind: s.kind,
|
|
694
|
+
}));
|
|
695
|
+
this.logAccuracyQuery(queryId, 'codegraph', query, topKResults, options?.sessionId).catch(() => { });
|
|
421
696
|
// Graph summary
|
|
422
697
|
const graphSummary = {
|
|
423
698
|
roots: cgResult.roots,
|