@devflow-tools/context-engine 0.16.26 → 0.16.28
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/context-engine.d.ts.map +1 -1
- package/dist/context-engine.js +193 -23
- package/dist/context-engine.js.map +1 -1
- package/dist/context-file-roles.d.ts.map +1 -1
- package/dist/context-file-roles.js +16 -4
- package/dist/context-file-roles.js.map +1 -1
- package/dist/create-engines.d.ts.map +1 -1
- package/dist/create-engines.js +10 -8
- package/dist/create-engines.js.map +1 -1
- package/dist/graph-adapter.d.ts.map +1 -1
- package/dist/graph-adapter.js +21 -10
- package/dist/graph-adapter.js.map +1 -1
- package/dist/graph-edge-quality.d.ts +7 -0
- package/dist/graph-edge-quality.d.ts.map +1 -1
- package/dist/graph-edge-quality.js +52 -1
- package/dist/graph-edge-quality.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer-adapter.d.ts +23 -0
- package/dist/indexer-adapter.d.ts.map +1 -1
- package/dist/indexer-adapter.js +94 -0
- package/dist/indexer-adapter.js.map +1 -1
- package/dist/intent-parser.js +3 -3
- package/dist/intent-parser.js.map +1 -1
- package/dist/query-cache.d.ts +2 -0
- package/dist/query-cache.d.ts.map +1 -1
- package/dist/query-cache.js +27 -1
- package/dist/query-cache.js.map +1 -1
- package/dist/retrieval-task-plan.d.ts +19 -0
- package/dist/retrieval-task-plan.d.ts.map +1 -0
- package/dist/retrieval-task-plan.js +160 -0
- package/dist/retrieval-task-plan.js.map +1 -0
- package/dist/task-retrieval-orchestrator.d.ts +10 -0
- package/dist/task-retrieval-orchestrator.d.ts.map +1 -1
- package/dist/task-retrieval-orchestrator.js +23 -2
- package/dist/task-retrieval-orchestrator.js.map +1 -1
- package/package.json +8 -8
|
@@ -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,EAOd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAA0B,MAAM,iCAAiC,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,
|
|
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,EAOd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAA0B,MAAM,iCAAiC,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAwB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAQzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D,OAAO,KAAK,EAKV,kBAAkB,EAClB,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAsSpB,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,aAAa,CAAC;IACtB,SAAS,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAoCD,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,CAAgB;IAC9B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAAoB;IACrC,OAAO,CAAC,WAAW,CAAC,CAAoB;IACxC,OAAO,CAAC,cAAc,CAAC,CAAiB;gBAE5B,IAAI,EAAE,iBAAiB;IAYnC,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,UAAU,CAAkD;IAE9D,gCAAgC,CAAC,KAAK,EAAE;QAC5C,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC,IAAI,CAAC;IAIX,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,OAAO,CAAC,yBAAyB;YA4BnB,yBAAyB;IAiCvC,OAAO,CAAC,eAAe;IAKjB,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAmRtD,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,+BAA+B,CAAC;YAwiB/F,sBAAsB;IAmWpC,OAAO,CAAC,yBAAyB;YAgBnB,2BAA2B;YA4B3B,6BAA6B;IAkB3C,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,2BAA2B;IAOnC,SAAS,IAAI,WAAW;CASzB"}
|
package/dist/context-engine.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import CGModule from "@colbymchenry/codegraph";
|
|
2
2
|
import { parseIntent, expandQuery } from "./intent-parser.js";
|
|
3
|
+
import { buildRetrievalTaskPlan } from './retrieval-task-plan.js';
|
|
3
4
|
import { fuzzySearch, shouldFallback } from "./fuzzy-fallback.js";
|
|
4
5
|
import { compressToTokenLimit } from "./compressor.js";
|
|
5
6
|
import { claudeRerank } from "./claude-rerank.js";
|
|
6
|
-
import { QueryCache } from "./query-cache.js";
|
|
7
|
+
import { invalidateContextPrefetchCache, QueryCache } from "./query-cache.js";
|
|
7
8
|
import { rankOptionalContextFiles, } from "./ranker.js";
|
|
8
9
|
import { describeLayerEvidence, detectLanguageFromPath, } from "./layer-rules.js";
|
|
9
10
|
import { reachableCodeGraphNodeIds, sanitizeCodeGraphEdges, } from './graph-edge-quality.js';
|
|
@@ -241,8 +242,21 @@ function extractTargetAnchors(query) {
|
|
|
241
242
|
if (match[1])
|
|
242
243
|
pathAnchors.add(match[1].replace(/^\.\//, '').replace(/\/+$/, '').toLowerCase());
|
|
243
244
|
}
|
|
244
|
-
if (pathAnchors.size > 0)
|
|
245
|
+
if (pathAnchors.size > 0) {
|
|
246
|
+
const directories = [...pathAnchors]
|
|
247
|
+
.filter(anchor => /\.[a-z0-9]+$/iu.test(anchor))
|
|
248
|
+
.map(anchor => anchor.split('/').slice(0, -1).join('/'))
|
|
249
|
+
.filter(Boolean);
|
|
250
|
+
const existingNames = new Set([...pathAnchors].map(anchor => anchor.split('/').at(-1)));
|
|
251
|
+
for (const match of query.matchAll(/\b([A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?|vue|svelte|json|ya?ml|md))\b/gu)) {
|
|
252
|
+
const fileName = match[1]?.toLowerCase();
|
|
253
|
+
if (!fileName || existingNames.has(fileName))
|
|
254
|
+
continue;
|
|
255
|
+
if (directories.length === 1)
|
|
256
|
+
pathAnchors.add(`${directories[0]}/${fileName}`);
|
|
257
|
+
}
|
|
245
258
|
return [...pathAnchors];
|
|
259
|
+
}
|
|
246
260
|
const anchors = new Set();
|
|
247
261
|
for (const match of query.matchAll(/([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)+)\s+(?:component|module|file|组件|模块|文件)/gi)) {
|
|
248
262
|
if (match[1])
|
|
@@ -289,7 +303,7 @@ export class ContextEngine {
|
|
|
289
303
|
async index(_projectRoot) {
|
|
290
304
|
await this.indexer.reconcile();
|
|
291
305
|
}
|
|
292
|
-
getKnowledgeSearchOptions(query, limit, options = {}) {
|
|
306
|
+
getKnowledgeSearchOptions(query, limit, options = {}, taskPlan, totalBudget = 8_000) {
|
|
293
307
|
const stats = this.cg.getStats();
|
|
294
308
|
const frameworks = typeof this.cg.getDetectedFrameworks === "function"
|
|
295
309
|
? this.cg.getDetectedFrameworks()
|
|
@@ -297,8 +311,11 @@ export class ContextEngine {
|
|
|
297
311
|
const languages = Object.keys(stats.filesByLanguage ?? {});
|
|
298
312
|
return {
|
|
299
313
|
limit,
|
|
314
|
+
budgetTokens: Math.max(0, Math.floor(totalBudget * 0.25)),
|
|
300
315
|
activeSkill: options.activeSkill,
|
|
301
316
|
relevantFiles: options.relevantFiles,
|
|
317
|
+
action: taskPlan?.action,
|
|
318
|
+
knowledgeNeeds: taskPlan?.knowledgeNeeds,
|
|
302
319
|
projectSignals: {
|
|
303
320
|
frameworks,
|
|
304
321
|
languages,
|
|
@@ -329,6 +346,12 @@ export class ContextEngine {
|
|
|
329
346
|
const warnings = this.scheduleSymbolVectorBuild();
|
|
330
347
|
const maxTokens = request.maxTokens ?? 8000;
|
|
331
348
|
const intent = parseIntent(request.query);
|
|
349
|
+
const retrievalTaskPlan = buildRetrievalTaskPlan({
|
|
350
|
+
query: request.query,
|
|
351
|
+
parsedIntent: intent,
|
|
352
|
+
targetAnchors: request.relevantFiles,
|
|
353
|
+
activeSkill: request.activeSkill,
|
|
354
|
+
});
|
|
332
355
|
const stats = this.cg.getStats();
|
|
333
356
|
const adaptive = getAdaptiveParams(stats.fileCount ?? 500);
|
|
334
357
|
const depth = request.mode === "deep" ? 3 : adaptive.depth;
|
|
@@ -418,10 +441,13 @@ export class ContextEngine {
|
|
|
418
441
|
}
|
|
419
442
|
// 2. Knowledge + Memory in parallel
|
|
420
443
|
const [kResultsRaw, memResultsRaw] = await Promise.all([
|
|
421
|
-
this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 10, { activeSkill: request.activeSkill, relevantFiles: request.relevantFiles })).catch(() => ({ entries: [], warnings: [] })),
|
|
444
|
+
this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 10, { activeSkill: request.activeSkill, relevantFiles: request.relevantFiles }, retrievalTaskPlan, maxTokens)).catch(() => ({ entries: [], warnings: [] })),
|
|
422
445
|
this.memory.search(request.query, {
|
|
423
446
|
limit: 5,
|
|
424
447
|
purpose: intent.intent,
|
|
448
|
+
scope: this.cg.getProjectRoot?.() ?? process.cwd(),
|
|
449
|
+
action: retrievalTaskPlan.action,
|
|
450
|
+
requiredPolicyFacets: retrievalTaskPlan.requiredPolicyFacets,
|
|
425
451
|
}).catch(() => []),
|
|
426
452
|
]);
|
|
427
453
|
if (kResultsRaw.warnings.length > 0)
|
|
@@ -564,10 +590,29 @@ export class ContextEngine {
|
|
|
564
590
|
const warnings = this.scheduleSymbolVectorBuild();
|
|
565
591
|
const maxTokens = options?.maxTokens ?? 8000;
|
|
566
592
|
const mode = options?.mode ?? "fast";
|
|
567
|
-
const
|
|
568
|
-
const
|
|
593
|
+
const memoryRevision = this.memory.getRetrievalRevision?.() ?? 0;
|
|
594
|
+
const cacheKey = `ctx:${query}:${mode}:${options?.activeSkill ?? ""}:${(options?.relevantFiles ?? []).join(",")}:memory:${memoryRevision}`;
|
|
595
|
+
let cached = warnings.length === 0 && !options?.requestId
|
|
569
596
|
? this.queryCache.get(cacheKey)
|
|
570
597
|
: undefined;
|
|
598
|
+
if (cached) {
|
|
599
|
+
const changed = typeof this.cg.getChangedFiles === 'function'
|
|
600
|
+
? this.cg.getChangedFiles()
|
|
601
|
+
: {};
|
|
602
|
+
const changedPaths = [...(changed.added ?? []), ...(changed.modified ?? []), ...(changed.removed ?? [])];
|
|
603
|
+
const cachedPaths = [
|
|
604
|
+
...getGroupFilePaths(cached.requiredFiles),
|
|
605
|
+
...getGroupFilePaths(cached.optionalFiles),
|
|
606
|
+
];
|
|
607
|
+
const freshness = this.indexer?.ensureFilesCurrent
|
|
608
|
+
? await this.indexer.ensureFilesCurrent(cachedPaths, { refresh: false })
|
|
609
|
+
: null;
|
|
610
|
+
if (changedPaths.length > 0 || freshness?.files.some(file => !file.current)) {
|
|
611
|
+
this.queryCache.invalidatePaths(new Set([...changedPaths, ...(freshness?.stale ?? []), ...(freshness?.missing ?? [])]));
|
|
612
|
+
invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
|
|
613
|
+
cached = undefined;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
571
616
|
if (cached) {
|
|
572
617
|
return {
|
|
573
618
|
...cached,
|
|
@@ -578,6 +623,13 @@ export class ContextEngine {
|
|
|
578
623
|
const taskType = intent.intent;
|
|
579
624
|
const combinedQuery = mergeQueries(query, expandQuery(query), options?.expandedTerms);
|
|
580
625
|
const targetAnchors = extractTargetAnchors(query);
|
|
626
|
+
const retrievalTaskPlan = buildRetrievalTaskPlan({
|
|
627
|
+
query,
|
|
628
|
+
parsedIntent: intent,
|
|
629
|
+
targetAnchors,
|
|
630
|
+
activeSkill: options?.activeSkill,
|
|
631
|
+
command: options?.command,
|
|
632
|
+
});
|
|
581
633
|
const timing = {};
|
|
582
634
|
const t0 = Date.now();
|
|
583
635
|
const [cgResult, knowledgeResult, memoryHits] = await Promise.all([
|
|
@@ -597,7 +649,7 @@ export class ContextEngine {
|
|
|
597
649
|
})
|
|
598
650
|
: Promise.resolve(null),
|
|
599
651
|
Promise.race([
|
|
600
|
-
this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options)),
|
|
652
|
+
this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options, retrievalTaskPlan, maxTokens)),
|
|
601
653
|
new Promise((_, reject) => setTimeout(() => reject(new Error("knowledge timeout")), 5000)),
|
|
602
654
|
]).catch((err) => {
|
|
603
655
|
console.error("[devflow] knowledge.search timeout:", err.message);
|
|
@@ -609,6 +661,9 @@ export class ContextEngine {
|
|
|
609
661
|
limit: 5,
|
|
610
662
|
purpose: taskType,
|
|
611
663
|
recordAccess: false,
|
|
664
|
+
scope: this.cg.getProjectRoot?.() ?? process.cwd(),
|
|
665
|
+
action: retrievalTaskPlan.action,
|
|
666
|
+
requiredPolicyFacets: retrievalTaskPlan.requiredPolicyFacets,
|
|
612
667
|
}),
|
|
613
668
|
new Promise((_, reject) => setTimeout(() => reject(new Error("memory timeout")), 5000)),
|
|
614
669
|
]).catch((err) => {
|
|
@@ -678,6 +733,31 @@ export class ContextEngine {
|
|
|
678
733
|
}
|
|
679
734
|
console.error("[devflow:timing] Fallback searchNodes loop:", Date.now() - tFallback, "ms");
|
|
680
735
|
timing["2_fallback_loop"] = Date.now() - tFallback;
|
|
736
|
+
const fileFreshnessByPath = new Map();
|
|
737
|
+
if (this.indexer?.ensureFilesCurrent) {
|
|
738
|
+
const candidatePaths = [...new Set([
|
|
739
|
+
...ctx.nodes.map((node) => String(node.filePath ?? '')).filter(Boolean),
|
|
740
|
+
...ctx.entryPoints.map((entry) => String(entry.filePath ?? '')).filter(Boolean),
|
|
741
|
+
])];
|
|
742
|
+
const freshness = await this.indexer.ensureFilesCurrent(candidatePaths, { refresh: true, timeoutMs: 5_000 });
|
|
743
|
+
for (const file of freshness.files)
|
|
744
|
+
fileFreshnessByPath.set(file.path, file);
|
|
745
|
+
const rejectedPaths = new Set([...freshness.missing, ...freshness.stale]);
|
|
746
|
+
if (rejectedPaths.size > 0 || freshness.refreshed.length > 0) {
|
|
747
|
+
this.queryCache.invalidatePaths(new Set([...rejectedPaths, ...freshness.refreshed]));
|
|
748
|
+
invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
|
|
749
|
+
}
|
|
750
|
+
if (freshness.missing.length > 0)
|
|
751
|
+
warnings.push(`codegraph-missing-files:${freshness.missing.length}`);
|
|
752
|
+
if (freshness.stale.length > 0)
|
|
753
|
+
warnings.push(`codegraph-stale-degraded:${freshness.stale.length}`);
|
|
754
|
+
ctx.nodes = ctx.nodes.filter((node) => !rejectedPaths.has(node.filePath));
|
|
755
|
+
ctx.entryPoints = ctx.entryPoints.filter((entry) => !rejectedPaths.has(entry.filePath));
|
|
756
|
+
if (Array.isArray(ctx.edges) && rejectedPaths.size > 0) {
|
|
757
|
+
const retainedIds = new Set(ctx.nodes.map((node) => node.id));
|
|
758
|
+
ctx.edges = ctx.edges.filter((edge) => retainedIds.has(edge.source) && retainedIds.has(edge.target));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
681
761
|
const edgeQuality = sanitizeCodeGraphEdges(this.cg, Array.isArray(ctx.edges) ? ctx.edges : []);
|
|
682
762
|
ctx.edges = edgeQuality.accepted;
|
|
683
763
|
if (edgeQuality.rejected.length > 0) {
|
|
@@ -774,6 +854,9 @@ export class ContextEngine {
|
|
|
774
854
|
})
|
|
775
855
|
: [];
|
|
776
856
|
for (const candidate of roleCandidates) {
|
|
857
|
+
if (this.indexer?.getFileFreshness) {
|
|
858
|
+
fileFreshnessByPath.set(candidate.path, this.indexer.getFileFreshness(candidate.path));
|
|
859
|
+
}
|
|
777
860
|
fileRoleByPath.set(candidate.path, candidate);
|
|
778
861
|
if (candidate.role === 'target') {
|
|
779
862
|
requiredFileSet.add(candidate.path);
|
|
@@ -791,10 +874,21 @@ export class ContextEngine {
|
|
|
791
874
|
});
|
|
792
875
|
}
|
|
793
876
|
}
|
|
794
|
-
|
|
795
|
-
const rankedOptionalPaths = new Set(rankedOptionalFiles.map(candidate => candidate.path));
|
|
877
|
+
let rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
|
|
796
878
|
for (const candidate of rankedOptionalFiles)
|
|
797
879
|
optionalFileSet.add(candidate.path);
|
|
880
|
+
if (this.indexer?.getFileFreshness) {
|
|
881
|
+
for (const path of [...requiredFileSet, ...optionalFileSet]) {
|
|
882
|
+
const freshness = fileFreshnessByPath.get(path) ?? this.indexer.getFileFreshness(path);
|
|
883
|
+
fileFreshnessByPath.set(path, freshness);
|
|
884
|
+
if (freshness.current)
|
|
885
|
+
continue;
|
|
886
|
+
requiredFileSet.delete(path);
|
|
887
|
+
optionalFileSet.delete(path);
|
|
888
|
+
fileRoleByPath.delete(path);
|
|
889
|
+
}
|
|
890
|
+
rankedOptionalFiles = rankedOptionalFiles.filter(candidate => optionalFileSet.has(candidate.path));
|
|
891
|
+
}
|
|
798
892
|
const optionalCandidateByPath = new Map(rankedOptionalFiles.map(candidate => [candidate.path, candidate]));
|
|
799
893
|
const retainedFiles = new Set([...requiredFileSet, ...optionalFileSet]);
|
|
800
894
|
for (let index = keySymbols.length - 1; index >= 0; index--) {
|
|
@@ -845,7 +939,7 @@ export class ContextEngine {
|
|
|
845
939
|
score: 0.5,
|
|
846
940
|
});
|
|
847
941
|
}
|
|
848
|
-
const compressed = compressToTokenLimit(allFileSnippets, maxTokens);
|
|
942
|
+
const compressed = compressToTokenLimit(allFileSnippets, Math.floor(maxTokens * 0.45));
|
|
849
943
|
if (compressed.overBudget)
|
|
850
944
|
warnings.push("context-compressor-budget-exceeded");
|
|
851
945
|
console.error("[devflow:timing] Snippet building + compress:", Date.now() - tSnippet, "ms");
|
|
@@ -979,7 +1073,9 @@ export class ContextEngine {
|
|
|
979
1073
|
command: options?.command,
|
|
980
1074
|
tokenBudget: maxTokens,
|
|
981
1075
|
targetAnchors,
|
|
1076
|
+
taskPlan: retrievalTaskPlan,
|
|
982
1077
|
fileRoles: fileRoleByPath,
|
|
1078
|
+
fileFreshness: fileFreshnessByPath,
|
|
983
1079
|
memoryHits,
|
|
984
1080
|
knowledgeHits,
|
|
985
1081
|
rejectedCodeRelations: edgeQuality.rejected.length,
|
|
@@ -1029,6 +1125,27 @@ export class ContextEngine {
|
|
|
1029
1125
|
});
|
|
1030
1126
|
await this.applyLegacySymbolVectorRecall(cgResult, query, warnings);
|
|
1031
1127
|
this.clearRecoveredSymbolWarning(warnings);
|
|
1128
|
+
const fileFreshnessByPath = new Map();
|
|
1129
|
+
if (this.indexer?.ensureFilesCurrent) {
|
|
1130
|
+
const candidatePaths = [...new Set(Array.from(cgResult.nodes.values()).map(node => node.filePath))];
|
|
1131
|
+
const freshness = await this.indexer.ensureFilesCurrent(candidatePaths, { refresh: true, timeoutMs: 5_000 });
|
|
1132
|
+
for (const file of freshness.files)
|
|
1133
|
+
fileFreshnessByPath.set(file.path, file);
|
|
1134
|
+
const rejectedPaths = new Set([...freshness.missing, ...freshness.stale]);
|
|
1135
|
+
for (const [id, node] of cgResult.nodes) {
|
|
1136
|
+
if (rejectedPaths.has(node.filePath))
|
|
1137
|
+
cgResult.nodes.delete(id);
|
|
1138
|
+
}
|
|
1139
|
+
cgResult.roots = cgResult.roots.filter(id => cgResult.nodes.has(id));
|
|
1140
|
+
if (freshness.missing.length > 0)
|
|
1141
|
+
warnings.push(`codegraph-missing-files:${freshness.missing.length}`);
|
|
1142
|
+
if (freshness.stale.length > 0)
|
|
1143
|
+
warnings.push(`codegraph-stale-degraded:${freshness.stale.length}`);
|
|
1144
|
+
if (rejectedPaths.size > 0 || freshness.refreshed.length > 0) {
|
|
1145
|
+
this.queryCache.invalidatePaths(new Set([...rejectedPaths, ...freshness.refreshed]));
|
|
1146
|
+
invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1032
1149
|
const edgeQuality = sanitizeCodeGraphEdges(this.cg, cgResult.edges ?? []);
|
|
1033
1150
|
if (edgeQuality.rejected.length > 0) {
|
|
1034
1151
|
warnings.push(`codegraph-relations-rejected:${edgeQuality.rejected.length}`);
|
|
@@ -1082,6 +1199,13 @@ export class ContextEngine {
|
|
|
1082
1199
|
}
|
|
1083
1200
|
}
|
|
1084
1201
|
const targetAnchors = extractTargetAnchors(query);
|
|
1202
|
+
const retrievalTaskPlan = buildRetrievalTaskPlan({
|
|
1203
|
+
query,
|
|
1204
|
+
parsedIntent: intent,
|
|
1205
|
+
targetAnchors,
|
|
1206
|
+
activeSkill: options?.activeSkill,
|
|
1207
|
+
command: options?.command,
|
|
1208
|
+
});
|
|
1085
1209
|
const roleTargets = targetAnchors.length > 0
|
|
1086
1210
|
? [...requiredFileSet].filter(file => fileMatchesTargetAnchor(file, targetAnchors))
|
|
1087
1211
|
: allNodes.filter(node => rootIds.has(node.id)).map(node => node.filePath);
|
|
@@ -1095,6 +1219,9 @@ export class ContextEngine {
|
|
|
1095
1219
|
})
|
|
1096
1220
|
: [];
|
|
1097
1221
|
for (const candidate of roleCandidates) {
|
|
1222
|
+
if (this.indexer?.getFileFreshness) {
|
|
1223
|
+
fileFreshnessByPath.set(candidate.path, this.indexer.getFileFreshness(candidate.path));
|
|
1224
|
+
}
|
|
1098
1225
|
fileRoleByPath.set(candidate.path, candidate);
|
|
1099
1226
|
if (candidate.role === 'target') {
|
|
1100
1227
|
requiredFileSet.add(candidate.path);
|
|
@@ -1112,9 +1239,21 @@ export class ContextEngine {
|
|
|
1112
1239
|
});
|
|
1113
1240
|
}
|
|
1114
1241
|
}
|
|
1115
|
-
|
|
1242
|
+
let rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
|
|
1116
1243
|
for (const candidate of rankedOptionalFiles)
|
|
1117
1244
|
optionalFileSet.add(candidate.path);
|
|
1245
|
+
if (this.indexer?.getFileFreshness) {
|
|
1246
|
+
for (const path of [...requiredFileSet, ...optionalFileSet]) {
|
|
1247
|
+
const freshness = fileFreshnessByPath.get(path) ?? this.indexer.getFileFreshness(path);
|
|
1248
|
+
fileFreshnessByPath.set(path, freshness);
|
|
1249
|
+
if (freshness.current)
|
|
1250
|
+
continue;
|
|
1251
|
+
requiredFileSet.delete(path);
|
|
1252
|
+
optionalFileSet.delete(path);
|
|
1253
|
+
fileRoleByPath.delete(path);
|
|
1254
|
+
}
|
|
1255
|
+
rankedOptionalFiles = rankedOptionalFiles.filter(candidate => optionalFileSet.has(candidate.path));
|
|
1256
|
+
}
|
|
1118
1257
|
for (const node of allNodes) {
|
|
1119
1258
|
if (optionalFileSet.has(node.filePath))
|
|
1120
1259
|
optionalSymbols.push(node.name);
|
|
@@ -1172,10 +1311,12 @@ export class ContextEngine {
|
|
|
1172
1311
|
const nextActions = generateNextActions(taskType, keySymbols.slice(0, 5));
|
|
1173
1312
|
// Knowledge hits
|
|
1174
1313
|
let knowledgeReasoning = [];
|
|
1314
|
+
let knowledgeHits = [];
|
|
1175
1315
|
try {
|
|
1176
|
-
const kResults = await this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options));
|
|
1316
|
+
const kResults = await this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options, retrievalTaskPlan, maxTokens));
|
|
1177
1317
|
if (kResults.warnings.length > 0)
|
|
1178
1318
|
warnings.push(...kResults.warnings);
|
|
1319
|
+
knowledgeHits = kResults.entries;
|
|
1179
1320
|
knowledgeReasoning = kResults.entries.map(k => ({
|
|
1180
1321
|
source: "knowledge",
|
|
1181
1322
|
fileGroup: k.source ?? "knowledge",
|
|
@@ -1187,16 +1328,21 @@ export class ContextEngine {
|
|
|
1187
1328
|
}
|
|
1188
1329
|
// Memory context
|
|
1189
1330
|
let memoryReasoning = [];
|
|
1331
|
+
let memoryHits = [];
|
|
1190
1332
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1333
|
+
memoryHits = await this.memory.search(query, {
|
|
1334
|
+
limit: 5,
|
|
1335
|
+
purpose: taskType,
|
|
1336
|
+
scope: this.cg.getProjectRoot?.() ?? process.cwd(),
|
|
1337
|
+
action: retrievalTaskPlan.action,
|
|
1338
|
+
requiredPolicyFacets: retrievalTaskPlan.requiredPolicyFacets,
|
|
1339
|
+
recordAccess: false,
|
|
1340
|
+
});
|
|
1341
|
+
memoryReasoning = memoryHits.map((hit) => ({
|
|
1342
|
+
source: 'memory',
|
|
1343
|
+
fileGroup: hit.entry?.title ?? 'project memory',
|
|
1344
|
+
explanation: `Selected applicable memory ${hit.entry?.id ?? 'unknown'} with score ${hit.score ?? 0}`,
|
|
1345
|
+
}));
|
|
1200
1346
|
}
|
|
1201
1347
|
catch {
|
|
1202
1348
|
// memory may be empty
|
|
@@ -1221,12 +1367,12 @@ export class ContextEngine {
|
|
|
1221
1367
|
score: 0.5,
|
|
1222
1368
|
});
|
|
1223
1369
|
}
|
|
1224
|
-
const compressed = compressToTokenLimit(allFileSnippets, maxTokens);
|
|
1370
|
+
const compressed = compressToTokenLimit(allFileSnippets, Math.floor(maxTokens * 0.45));
|
|
1225
1371
|
if (compressed.overBudget)
|
|
1226
1372
|
warnings.push("context-compressor-budget-exceeded");
|
|
1227
1373
|
const requiredPaths = new Set(compressed.files.filter(f => requiredFileSet.has(f.path)).map(f => f.path));
|
|
1228
1374
|
const optionalPaths = new Set(compressed.files.filter(f => optionalFileSet.has(f.path)).map(f => f.path));
|
|
1229
|
-
|
|
1375
|
+
const result = {
|
|
1230
1376
|
taskType,
|
|
1231
1377
|
requiredFiles: {
|
|
1232
1378
|
files: [...requiredPaths],
|
|
@@ -1282,6 +1428,25 @@ export class ContextEngine {
|
|
|
1282
1428
|
],
|
|
1283
1429
|
},
|
|
1284
1430
|
};
|
|
1431
|
+
result.taskEnvelope = buildTaskContextEnvelope({
|
|
1432
|
+
query,
|
|
1433
|
+
result,
|
|
1434
|
+
projectRoot: this.cg.getProjectRoot?.() ?? process.cwd(),
|
|
1435
|
+
sessionId: options?.sessionId,
|
|
1436
|
+
executionId: options?.executionId,
|
|
1437
|
+
requestId: options?.requestId,
|
|
1438
|
+
host: options?.host,
|
|
1439
|
+
command: options?.command,
|
|
1440
|
+
tokenBudget: maxTokens,
|
|
1441
|
+
targetAnchors,
|
|
1442
|
+
fileRoles: fileRoleByPath,
|
|
1443
|
+
fileFreshness: fileFreshnessByPath,
|
|
1444
|
+
memoryHits,
|
|
1445
|
+
knowledgeHits,
|
|
1446
|
+
rejectedCodeRelations: edgeQuality.rejected.length,
|
|
1447
|
+
taskPlan: retrievalTaskPlan,
|
|
1448
|
+
});
|
|
1449
|
+
return result;
|
|
1285
1450
|
}
|
|
1286
1451
|
scheduleSymbolVectorBuild() {
|
|
1287
1452
|
const warnings = [];
|
|
@@ -1366,4 +1531,9 @@ export class ContextEngine {
|
|
|
1366
1531
|
};
|
|
1367
1532
|
}
|
|
1368
1533
|
}
|
|
1534
|
+
function getGroupFilePaths(group) {
|
|
1535
|
+
if (Array.isArray(group))
|
|
1536
|
+
return group;
|
|
1537
|
+
return group?.files ?? [];
|
|
1538
|
+
}
|
|
1369
1539
|
//# sourceMappingURL=context-engine.js.map
|