@devflow-tools/context-engine 0.16.27 → 0.16.29

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.
Files changed (44) hide show
  1. package/dist/context-engine.d.ts +3 -2
  2. package/dist/context-engine.d.ts.map +1 -1
  3. package/dist/context-engine.js +137 -13
  4. package/dist/context-engine.js.map +1 -1
  5. package/dist/context-file-roles.d.ts.map +1 -1
  6. package/dist/context-file-roles.js +16 -4
  7. package/dist/context-file-roles.js.map +1 -1
  8. package/dist/create-engines.d.ts.map +1 -1
  9. package/dist/create-engines.js +10 -8
  10. package/dist/create-engines.js.map +1 -1
  11. package/dist/graph-adapter.d.ts.map +1 -1
  12. package/dist/graph-adapter.js +21 -10
  13. package/dist/graph-adapter.js.map +1 -1
  14. package/dist/graph-edge-quality.d.ts +7 -0
  15. package/dist/graph-edge-quality.d.ts.map +1 -1
  16. package/dist/graph-edge-quality.js +52 -1
  17. package/dist/graph-edge-quality.js.map +1 -1
  18. package/dist/indexer-adapter.d.ts +23 -0
  19. package/dist/indexer-adapter.d.ts.map +1 -1
  20. package/dist/indexer-adapter.js +94 -0
  21. package/dist/indexer-adapter.js.map +1 -1
  22. package/dist/query-cache.d.ts +2 -0
  23. package/dist/query-cache.d.ts.map +1 -1
  24. package/dist/query-cache.js +27 -1
  25. package/dist/query-cache.js.map +1 -1
  26. package/dist/retrieval-handlers.d.ts +5 -11
  27. package/dist/retrieval-handlers.d.ts.map +1 -1
  28. package/dist/retrieval-handlers.js +11 -1
  29. package/dist/retrieval-handlers.js.map +1 -1
  30. package/dist/retrieval-ledger.d.ts +3 -0
  31. package/dist/retrieval-ledger.d.ts.map +1 -0
  32. package/dist/retrieval-ledger.js +91 -0
  33. package/dist/retrieval-ledger.js.map +1 -0
  34. package/dist/retrieval-session-controller.js +23 -2
  35. package/dist/retrieval-session-controller.js.map +1 -1
  36. package/dist/retrieval-task-plan.d.ts +4 -7
  37. package/dist/retrieval-task-plan.d.ts.map +1 -1
  38. package/dist/retrieval-task-plan.js +98 -6
  39. package/dist/retrieval-task-plan.js.map +1 -1
  40. package/dist/task-retrieval-orchestrator.d.ts +9 -0
  41. package/dist/task-retrieval-orchestrator.d.ts.map +1 -1
  42. package/dist/task-retrieval-orchestrator.js +180 -44
  43. package/dist/task-retrieval-orchestrator.js.map +1 -1
  44. package/package.json +8 -8
@@ -1,4 +1,4 @@
1
- import type { ContextRequest, ContextResult, IndexStatus, IDataProvider } from "@devflow-tools/sdk";
1
+ import type { ContextRequest, ContextResult, IndexStatus, IDataProvider, SelectedMemoryEvidence } from "@devflow-tools/sdk";
2
2
  import type { EmbeddingProvider } from "@devflow-tools/memory-engine";
3
3
  import type { IMemoryEngine } from "@devflow-tools/sdk";
4
4
  import type { KnowledgeEngine } from "@devflow-tools/knowledge-engine";
@@ -33,11 +33,12 @@ export declare class ContextEngine {
33
33
  close(): void;
34
34
  private queryCache;
35
35
  recordPrefetchedContextSelection(input: {
36
- memoryIds: string[];
36
+ entries: SelectedMemoryEvidence[];
37
37
  sessionId?: string;
38
38
  executionId?: string;
39
39
  requestId?: string;
40
40
  query: string;
41
+ contextReceipt: string;
41
42
  }): Promise<void>;
42
43
  index(_projectRoot: string): Promise<void>;
43
44
  private getKnowledgeSearchOptions;
@@ -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,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,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;AAwBD,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;YAuBnB,yBAAyB;IAiCvC,OAAO,CAAC,eAAe;IAKjB,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAiRtD,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,+BAA+B,CAAC;YAif/F,sBAAsB;IAkUpC,OAAO,CAAC,yBAAyB;YAgBnB,2BAA2B;YA4B3B,6BAA6B;IAkB3C,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,2BAA2B;IAOnC,SAAS,IAAI,WAAW;CASzB"}
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,EAOb,sBAAsB,EACvB,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;AAuSpB,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,OAAO,EAAE,sBAAsB,EAAE,CAAC;QAClC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;KACxB,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;YA4iB/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"}
@@ -4,12 +4,13 @@ import { buildRetrievalTaskPlan } from './retrieval-task-plan.js';
4
4
  import { fuzzySearch, shouldFallback } from "./fuzzy-fallback.js";
5
5
  import { compressToTokenLimit } from "./compressor.js";
6
6
  import { claudeRerank } from "./claude-rerank.js";
7
- import { QueryCache } from "./query-cache.js";
7
+ import { invalidateContextPrefetchCache, QueryCache } from "./query-cache.js";
8
8
  import { rankOptionalContextFiles, } from "./ranker.js";
9
9
  import { describeLayerEvidence, detectLanguageFromPath, } from "./layer-rules.js";
10
10
  import { reachableCodeGraphNodeIds, sanitizeCodeGraphEdges, } from './graph-edge-quality.js';
11
11
  import { assembleContextFileRoles } from './context-file-roles.js';
12
12
  import { buildTaskContextEnvelope } from './task-retrieval-orchestrator.js';
13
+ import { recordTaskEnvelopeLedger } from './retrieval-ledger.js';
13
14
  const CG = CGModule.default ?? CGModule;
14
15
  const SYMBOL_VECTOR_WARNING = "Symbol vector search is not ready; using CodeGraph/BM25 results. Run `devflow doctor` for details.";
15
16
  // ── RRF (Reciprocal Rank Fusion) ─────────────────────────────────
@@ -242,8 +243,21 @@ function extractTargetAnchors(query) {
242
243
  if (match[1])
243
244
  pathAnchors.add(match[1].replace(/^\.\//, '').replace(/\/+$/, '').toLowerCase());
244
245
  }
245
- if (pathAnchors.size > 0)
246
+ if (pathAnchors.size > 0) {
247
+ const directories = [...pathAnchors]
248
+ .filter(anchor => /\.[a-z0-9]+$/iu.test(anchor))
249
+ .map(anchor => anchor.split('/').slice(0, -1).join('/'))
250
+ .filter(Boolean);
251
+ const existingNames = new Set([...pathAnchors].map(anchor => anchor.split('/').at(-1)));
252
+ for (const match of query.matchAll(/\b([A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?|vue|svelte|json|ya?ml|md))\b/gu)) {
253
+ const fileName = match[1]?.toLowerCase();
254
+ if (!fileName || existingNames.has(fileName))
255
+ continue;
256
+ if (directories.length === 1)
257
+ pathAnchors.add(`${directories[0]}/${fileName}`);
258
+ }
246
259
  return [...pathAnchors];
260
+ }
247
261
  const anchors = new Set();
248
262
  for (const match of query.matchAll(/([a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)+)\s+(?:component|module|file|组件|模块|文件)/gi)) {
249
263
  if (match[1])
@@ -290,7 +304,7 @@ export class ContextEngine {
290
304
  async index(_projectRoot) {
291
305
  await this.indexer.reconcile();
292
306
  }
293
- getKnowledgeSearchOptions(query, limit, options = {}) {
307
+ getKnowledgeSearchOptions(query, limit, options = {}, taskPlan, totalBudget = 8_000) {
294
308
  const stats = this.cg.getStats();
295
309
  const frameworks = typeof this.cg.getDetectedFrameworks === "function"
296
310
  ? this.cg.getDetectedFrameworks()
@@ -298,8 +312,11 @@ export class ContextEngine {
298
312
  const languages = Object.keys(stats.filesByLanguage ?? {});
299
313
  return {
300
314
  limit,
315
+ budgetTokens: Math.max(0, Math.floor(totalBudget * 0.25)),
301
316
  activeSkill: options.activeSkill,
302
317
  relevantFiles: options.relevantFiles,
318
+ action: taskPlan?.action,
319
+ knowledgeNeeds: taskPlan?.knowledgeNeeds,
303
320
  projectSignals: {
304
321
  frameworks,
305
322
  languages,
@@ -425,7 +442,7 @@ export class ContextEngine {
425
442
  }
426
443
  // 2. Knowledge + Memory in parallel
427
444
  const [kResultsRaw, memResultsRaw] = await Promise.all([
428
- this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 10, { activeSkill: request.activeSkill, relevantFiles: request.relevantFiles })).catch(() => ({ entries: [], warnings: [] })),
445
+ this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 10, { activeSkill: request.activeSkill, relevantFiles: request.relevantFiles }, retrievalTaskPlan, maxTokens)).catch(() => ({ entries: [], warnings: [] })),
429
446
  this.memory.search(request.query, {
430
447
  limit: 5,
431
448
  purpose: intent.intent,
@@ -576,9 +593,27 @@ export class ContextEngine {
576
593
  const mode = options?.mode ?? "fast";
577
594
  const memoryRevision = this.memory.getRetrievalRevision?.() ?? 0;
578
595
  const cacheKey = `ctx:${query}:${mode}:${options?.activeSkill ?? ""}:${(options?.relevantFiles ?? []).join(",")}:memory:${memoryRevision}`;
579
- const cached = warnings.length === 0 && !options?.requestId
596
+ let cached = warnings.length === 0 && !options?.requestId
580
597
  ? this.queryCache.get(cacheKey)
581
598
  : undefined;
599
+ if (cached) {
600
+ const changed = typeof this.cg.getChangedFiles === 'function'
601
+ ? this.cg.getChangedFiles()
602
+ : {};
603
+ const changedPaths = [...(changed.added ?? []), ...(changed.modified ?? []), ...(changed.removed ?? [])];
604
+ const cachedPaths = [
605
+ ...getGroupFilePaths(cached.requiredFiles),
606
+ ...getGroupFilePaths(cached.optionalFiles),
607
+ ];
608
+ const freshness = this.indexer?.ensureFilesCurrent
609
+ ? await this.indexer.ensureFilesCurrent(cachedPaths, { refresh: false })
610
+ : null;
611
+ if (changedPaths.length > 0 || freshness?.files.some(file => !file.current)) {
612
+ this.queryCache.invalidatePaths(new Set([...changedPaths, ...(freshness?.stale ?? []), ...(freshness?.missing ?? [])]));
613
+ invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
614
+ cached = undefined;
615
+ }
616
+ }
582
617
  if (cached) {
583
618
  return {
584
619
  ...cached,
@@ -615,7 +650,7 @@ export class ContextEngine {
615
650
  })
616
651
  : Promise.resolve(null),
617
652
  Promise.race([
618
- this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options)),
653
+ this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options, retrievalTaskPlan, maxTokens)),
619
654
  new Promise((_, reject) => setTimeout(() => reject(new Error("knowledge timeout")), 5000)),
620
655
  ]).catch((err) => {
621
656
  console.error("[devflow] knowledge.search timeout:", err.message);
@@ -699,6 +734,31 @@ export class ContextEngine {
699
734
  }
700
735
  console.error("[devflow:timing] Fallback searchNodes loop:", Date.now() - tFallback, "ms");
701
736
  timing["2_fallback_loop"] = Date.now() - tFallback;
737
+ const fileFreshnessByPath = new Map();
738
+ if (this.indexer?.ensureFilesCurrent) {
739
+ const candidatePaths = [...new Set([
740
+ ...ctx.nodes.map((node) => String(node.filePath ?? '')).filter(Boolean),
741
+ ...ctx.entryPoints.map((entry) => String(entry.filePath ?? '')).filter(Boolean),
742
+ ])];
743
+ const freshness = await this.indexer.ensureFilesCurrent(candidatePaths, { refresh: true, timeoutMs: 5_000 });
744
+ for (const file of freshness.files)
745
+ fileFreshnessByPath.set(file.path, file);
746
+ const rejectedPaths = new Set([...freshness.missing, ...freshness.stale]);
747
+ if (rejectedPaths.size > 0 || freshness.refreshed.length > 0) {
748
+ this.queryCache.invalidatePaths(new Set([...rejectedPaths, ...freshness.refreshed]));
749
+ invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
750
+ }
751
+ if (freshness.missing.length > 0)
752
+ warnings.push(`codegraph-missing-files:${freshness.missing.length}`);
753
+ if (freshness.stale.length > 0)
754
+ warnings.push(`codegraph-stale-degraded:${freshness.stale.length}`);
755
+ ctx.nodes = ctx.nodes.filter((node) => !rejectedPaths.has(node.filePath));
756
+ ctx.entryPoints = ctx.entryPoints.filter((entry) => !rejectedPaths.has(entry.filePath));
757
+ if (Array.isArray(ctx.edges) && rejectedPaths.size > 0) {
758
+ const retainedIds = new Set(ctx.nodes.map((node) => node.id));
759
+ ctx.edges = ctx.edges.filter((edge) => retainedIds.has(edge.source) && retainedIds.has(edge.target));
760
+ }
761
+ }
702
762
  const edgeQuality = sanitizeCodeGraphEdges(this.cg, Array.isArray(ctx.edges) ? ctx.edges : []);
703
763
  ctx.edges = edgeQuality.accepted;
704
764
  if (edgeQuality.rejected.length > 0) {
@@ -795,6 +855,9 @@ export class ContextEngine {
795
855
  })
796
856
  : [];
797
857
  for (const candidate of roleCandidates) {
858
+ if (this.indexer?.getFileFreshness) {
859
+ fileFreshnessByPath.set(candidate.path, this.indexer.getFileFreshness(candidate.path));
860
+ }
798
861
  fileRoleByPath.set(candidate.path, candidate);
799
862
  if (candidate.role === 'target') {
800
863
  requiredFileSet.add(candidate.path);
@@ -812,10 +875,21 @@ export class ContextEngine {
812
875
  });
813
876
  }
814
877
  }
815
- const rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
816
- const rankedOptionalPaths = new Set(rankedOptionalFiles.map(candidate => candidate.path));
878
+ let rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
817
879
  for (const candidate of rankedOptionalFiles)
818
880
  optionalFileSet.add(candidate.path);
881
+ if (this.indexer?.getFileFreshness) {
882
+ for (const path of [...requiredFileSet, ...optionalFileSet]) {
883
+ const freshness = fileFreshnessByPath.get(path) ?? this.indexer.getFileFreshness(path);
884
+ fileFreshnessByPath.set(path, freshness);
885
+ if (freshness.current)
886
+ continue;
887
+ requiredFileSet.delete(path);
888
+ optionalFileSet.delete(path);
889
+ fileRoleByPath.delete(path);
890
+ }
891
+ rankedOptionalFiles = rankedOptionalFiles.filter(candidate => optionalFileSet.has(candidate.path));
892
+ }
819
893
  const optionalCandidateByPath = new Map(rankedOptionalFiles.map(candidate => [candidate.path, candidate]));
820
894
  const retainedFiles = new Set([...requiredFileSet, ...optionalFileSet]);
821
895
  for (let index = keySymbols.length - 1; index >= 0; index--) {
@@ -866,7 +940,7 @@ export class ContextEngine {
866
940
  score: 0.5,
867
941
  });
868
942
  }
869
- const compressed = compressToTokenLimit(allFileSnippets, maxTokens);
943
+ const compressed = compressToTokenLimit(allFileSnippets, Math.floor(maxTokens * 0.45));
870
944
  if (compressed.overBudget)
871
945
  warnings.push("context-compressor-budget-exceeded");
872
946
  console.error("[devflow:timing] Snippet building + compress:", Date.now() - tSnippet, "ms");
@@ -1002,6 +1076,7 @@ export class ContextEngine {
1002
1076
  targetAnchors,
1003
1077
  taskPlan: retrievalTaskPlan,
1004
1078
  fileRoles: fileRoleByPath,
1079
+ fileFreshness: fileFreshnessByPath,
1005
1080
  memoryHits,
1006
1081
  knowledgeHits,
1007
1082
  rejectedCodeRelations: edgeQuality.rejected.length,
@@ -1010,14 +1085,21 @@ export class ContextEngine {
1010
1085
  + Object.values((knowledgeResult
1011
1086
  .retrievalDiagnostics?.qualityRejected) ?? {}).reduce((total, count) => total + count, 0),
1012
1087
  });
1088
+ try {
1089
+ recordTaskEnvelopeLedger(result.taskEnvelope);
1090
+ }
1091
+ catch {
1092
+ result.warnings = [...(result.warnings ?? []), 'retrieval-ledger-degraded'];
1093
+ }
1013
1094
  if (options?.requestId && result.taskEnvelope.memory.entries.length > 0) {
1014
1095
  try {
1015
1096
  await this.memory.recordContextEnvelopeSelection?.({
1016
- memoryIds: result.taskEnvelope.memory.entries.map(entry => entry.id),
1097
+ entries: result.taskEnvelope.memory.entries,
1017
1098
  sessionId: options.sessionId,
1018
1099
  executionId: options.executionId,
1019
1100
  requestId: options.requestId,
1020
1101
  query,
1102
+ contextReceipt: result.taskEnvelope.receipt.contextHash,
1021
1103
  });
1022
1104
  }
1023
1105
  catch {
@@ -1051,6 +1133,27 @@ export class ContextEngine {
1051
1133
  });
1052
1134
  await this.applyLegacySymbolVectorRecall(cgResult, query, warnings);
1053
1135
  this.clearRecoveredSymbolWarning(warnings);
1136
+ const fileFreshnessByPath = new Map();
1137
+ if (this.indexer?.ensureFilesCurrent) {
1138
+ const candidatePaths = [...new Set(Array.from(cgResult.nodes.values()).map(node => node.filePath))];
1139
+ const freshness = await this.indexer.ensureFilesCurrent(candidatePaths, { refresh: true, timeoutMs: 5_000 });
1140
+ for (const file of freshness.files)
1141
+ fileFreshnessByPath.set(file.path, file);
1142
+ const rejectedPaths = new Set([...freshness.missing, ...freshness.stale]);
1143
+ for (const [id, node] of cgResult.nodes) {
1144
+ if (rejectedPaths.has(node.filePath))
1145
+ cgResult.nodes.delete(id);
1146
+ }
1147
+ cgResult.roots = cgResult.roots.filter(id => cgResult.nodes.has(id));
1148
+ if (freshness.missing.length > 0)
1149
+ warnings.push(`codegraph-missing-files:${freshness.missing.length}`);
1150
+ if (freshness.stale.length > 0)
1151
+ warnings.push(`codegraph-stale-degraded:${freshness.stale.length}`);
1152
+ if (rejectedPaths.size > 0 || freshness.refreshed.length > 0) {
1153
+ this.queryCache.invalidatePaths(new Set([...rejectedPaths, ...freshness.refreshed]));
1154
+ invalidateContextPrefetchCache(this.cg.getProjectRoot?.() ?? process.cwd());
1155
+ }
1156
+ }
1054
1157
  const edgeQuality = sanitizeCodeGraphEdges(this.cg, cgResult.edges ?? []);
1055
1158
  if (edgeQuality.rejected.length > 0) {
1056
1159
  warnings.push(`codegraph-relations-rejected:${edgeQuality.rejected.length}`);
@@ -1124,6 +1227,9 @@ export class ContextEngine {
1124
1227
  })
1125
1228
  : [];
1126
1229
  for (const candidate of roleCandidates) {
1230
+ if (this.indexer?.getFileFreshness) {
1231
+ fileFreshnessByPath.set(candidate.path, this.indexer.getFileFreshness(candidate.path));
1232
+ }
1127
1233
  fileRoleByPath.set(candidate.path, candidate);
1128
1234
  if (candidate.role === 'target') {
1129
1235
  requiredFileSet.add(candidate.path);
@@ -1141,9 +1247,21 @@ export class ContextEngine {
1141
1247
  });
1142
1248
  }
1143
1249
  }
1144
- const rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
1250
+ let rankedOptionalFiles = rankOptionalContextFiles([...optionalCandidates.values()], taskType);
1145
1251
  for (const candidate of rankedOptionalFiles)
1146
1252
  optionalFileSet.add(candidate.path);
1253
+ if (this.indexer?.getFileFreshness) {
1254
+ for (const path of [...requiredFileSet, ...optionalFileSet]) {
1255
+ const freshness = fileFreshnessByPath.get(path) ?? this.indexer.getFileFreshness(path);
1256
+ fileFreshnessByPath.set(path, freshness);
1257
+ if (freshness.current)
1258
+ continue;
1259
+ requiredFileSet.delete(path);
1260
+ optionalFileSet.delete(path);
1261
+ fileRoleByPath.delete(path);
1262
+ }
1263
+ rankedOptionalFiles = rankedOptionalFiles.filter(candidate => optionalFileSet.has(candidate.path));
1264
+ }
1147
1265
  for (const node of allNodes) {
1148
1266
  if (optionalFileSet.has(node.filePath))
1149
1267
  optionalSymbols.push(node.name);
@@ -1203,7 +1321,7 @@ export class ContextEngine {
1203
1321
  let knowledgeReasoning = [];
1204
1322
  let knowledgeHits = [];
1205
1323
  try {
1206
- const kResults = await this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options));
1324
+ const kResults = await this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options, retrievalTaskPlan, maxTokens));
1207
1325
  if (kResults.warnings.length > 0)
1208
1326
  warnings.push(...kResults.warnings);
1209
1327
  knowledgeHits = kResults.entries;
@@ -1257,7 +1375,7 @@ export class ContextEngine {
1257
1375
  score: 0.5,
1258
1376
  });
1259
1377
  }
1260
- const compressed = compressToTokenLimit(allFileSnippets, maxTokens);
1378
+ const compressed = compressToTokenLimit(allFileSnippets, Math.floor(maxTokens * 0.45));
1261
1379
  if (compressed.overBudget)
1262
1380
  warnings.push("context-compressor-budget-exceeded");
1263
1381
  const requiredPaths = new Set(compressed.files.filter(f => requiredFileSet.has(f.path)).map(f => f.path));
@@ -1330,6 +1448,7 @@ export class ContextEngine {
1330
1448
  tokenBudget: maxTokens,
1331
1449
  targetAnchors,
1332
1450
  fileRoles: fileRoleByPath,
1451
+ fileFreshness: fileFreshnessByPath,
1333
1452
  memoryHits,
1334
1453
  knowledgeHits,
1335
1454
  rejectedCodeRelations: edgeQuality.rejected.length,
@@ -1420,4 +1539,9 @@ export class ContextEngine {
1420
1539
  };
1421
1540
  }
1422
1541
  }
1542
+ function getGroupFilePaths(group) {
1543
+ if (Array.isArray(group))
1544
+ return group;
1545
+ return group?.files ?? [];
1546
+ }
1423
1547
  //# sourceMappingURL=context-engine.js.map