@devflow-tools/context-engine 0.17.0 → 0.17.2
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/artifact-inventory.d.ts +37 -0
- package/dist/artifact-inventory.d.ts.map +1 -0
- package/dist/artifact-inventory.js +211 -0
- package/dist/artifact-inventory.js.map +1 -0
- package/dist/channel-query-planner.d.ts +9 -0
- package/dist/channel-query-planner.d.ts.map +1 -0
- package/dist/channel-query-planner.js +92 -0
- package/dist/channel-query-planner.js.map +1 -0
- package/dist/context-engine.d.ts.map +1 -1
- package/dist/context-engine.js +163 -12
- package/dist/context-engine.js.map +1 -1
- package/dist/context-file-roles.d.ts +2 -1
- package/dist/context-file-roles.d.ts.map +1 -1
- package/dist/context-file-roles.js +6 -0
- package/dist/context-file-roles.js.map +1 -1
- package/dist/create-engines.d.ts.map +1 -1
- package/dist/create-engines.js +17 -5
- package/dist/create-engines.js.map +1 -1
- package/dist/graph-evidence-selector.d.ts.map +1 -1
- package/dist/graph-evidence-selector.js +7 -1
- package/dist/graph-evidence-selector.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer-adapter.d.ts.map +1 -1
- package/dist/indexer-adapter.js +7 -1
- package/dist/indexer-adapter.js.map +1 -1
- package/dist/intent-parser.d.ts +5 -2
- package/dist/intent-parser.d.ts.map +1 -1
- package/dist/intent-parser.js +4 -4
- package/dist/intent-parser.js.map +1 -1
- package/dist/retrieval-handlers.d.ts +5 -0
- package/dist/retrieval-handlers.d.ts.map +1 -1
- package/dist/retrieval-handlers.js +70 -2
- package/dist/retrieval-handlers.js.map +1 -1
- package/dist/retrieval-task-plan.d.ts +2 -1
- package/dist/retrieval-task-plan.d.ts.map +1 -1
- package/dist/retrieval-task-plan.js +12 -11
- package/dist/retrieval-task-plan.js.map +1 -1
- package/dist/task-retrieval-orchestrator.d.ts +5 -1
- package/dist/task-retrieval-orchestrator.d.ts.map +1 -1
- package/dist/task-retrieval-orchestrator.js +58 -12
- package/dist/task-retrieval-orchestrator.js.map +1 -1
- package/dist/types.d.ts +4 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/context-engine.js
CHANGED
|
@@ -9,10 +9,12 @@ 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
|
+
import { buildArtifactInventory } from './artifact-inventory.js';
|
|
12
13
|
import { buildTaskContextEnvelope } from './task-retrieval-orchestrator.js';
|
|
13
14
|
import { recordTaskEnvelopeLedger } from './retrieval-ledger.js';
|
|
14
15
|
import { selectGraphEvidence } from './graph-evidence-selector.js';
|
|
15
16
|
import { allocateContextFileBudget } from './context-budget-allocator.js';
|
|
17
|
+
import { emitSemanticControlLog } from '@devflow-tools/telemetry';
|
|
16
18
|
const CG = CGModule.default ?? CGModule;
|
|
17
19
|
const SYMBOL_VECTOR_WARNING = "Symbol vector search is not ready; using CodeGraph/BM25 results. Run `devflow doctor` for details.";
|
|
18
20
|
// ── RRF (Reciprocal Rank Fusion) ─────────────────────────────────
|
|
@@ -314,11 +316,18 @@ export class ContextEngine {
|
|
|
314
316
|
const languages = Object.keys(stats.filesByLanguage ?? {});
|
|
315
317
|
return {
|
|
316
318
|
limit,
|
|
319
|
+
retrievalIntent: 'automatic_context',
|
|
317
320
|
budgetTokens: Math.max(0, Math.floor(totalBudget * 0.25)),
|
|
318
321
|
activeSkill: options.activeSkill,
|
|
319
322
|
relevantFiles: options.relevantFiles,
|
|
320
323
|
action: taskPlan?.action,
|
|
321
324
|
knowledgeNeeds: taskPlan?.knowledgeNeeds,
|
|
325
|
+
sessionId: options.sessionId,
|
|
326
|
+
executionId: options.executionId,
|
|
327
|
+
requestId: options.requestId,
|
|
328
|
+
turnId: options.turnId,
|
|
329
|
+
intentArtifactHash: options.intentArtifact?.artifactHash,
|
|
330
|
+
queryPlanHash: options.queryPlan?.planHash,
|
|
322
331
|
projectSignals: {
|
|
323
332
|
frameworks,
|
|
324
333
|
languages,
|
|
@@ -624,15 +633,70 @@ export class ContextEngine {
|
|
|
624
633
|
}
|
|
625
634
|
const intent = parseIntent(query);
|
|
626
635
|
const taskType = intent.intent;
|
|
627
|
-
const combinedQuery =
|
|
628
|
-
|
|
636
|
+
const combinedQuery = options?.queryPlan?.code[0]?.query
|
|
637
|
+
?? mergeQueries(query, expandQuery(query), options?.expandedTerms);
|
|
638
|
+
const explicitTargetAnchors = options?.intentArtifact?.targetAnchors ?? extractTargetAnchors(query);
|
|
639
|
+
const initialTaskPlan = buildRetrievalTaskPlan({
|
|
640
|
+
query,
|
|
641
|
+
parsedIntent: intent,
|
|
642
|
+
targetAnchors: explicitTargetAnchors,
|
|
643
|
+
activeSkill: options?.activeSkill,
|
|
644
|
+
command: options?.command,
|
|
645
|
+
authoritativeArtifact: options?.intentArtifact,
|
|
646
|
+
});
|
|
647
|
+
const projectRoot = this.cg.getProjectRoot?.() ?? process.cwd();
|
|
648
|
+
const logIdentity = {
|
|
649
|
+
projectRoot,
|
|
650
|
+
sessionId: options?.sessionId,
|
|
651
|
+
turnId: options?.turnId,
|
|
652
|
+
requestId: options?.requestId,
|
|
653
|
+
executionId: options?.executionId,
|
|
654
|
+
intentArtifactHash: options?.intentArtifact?.artifactHash,
|
|
655
|
+
queryPlanHash: options?.queryPlan?.planHash,
|
|
656
|
+
};
|
|
657
|
+
let artifactInventory = this.indexer
|
|
658
|
+
? buildArtifactInventory({
|
|
659
|
+
codegraph: this.cg,
|
|
660
|
+
indexer: this.indexer,
|
|
661
|
+
query: combinedQuery,
|
|
662
|
+
explicitTargetAnchors,
|
|
663
|
+
action: initialTaskPlan.action,
|
|
664
|
+
identity: logIdentity,
|
|
665
|
+
})
|
|
666
|
+
: { entries: [], operationTargets: [], missingExplicitTargets: [], ambiguous: false };
|
|
667
|
+
const staleInventoryTargets = artifactInventory.operationTargets
|
|
668
|
+
.filter(entry => entry.freshness.exists && !entry.freshness.current)
|
|
669
|
+
.map(entry => entry.path);
|
|
670
|
+
if (this.indexer && staleInventoryTargets.length > 0) {
|
|
671
|
+
await this.indexer.ensureFilesCurrent(staleInventoryTargets, { refresh: true, timeoutMs: 5_000 });
|
|
672
|
+
artifactInventory = buildArtifactInventory({
|
|
673
|
+
codegraph: this.cg,
|
|
674
|
+
indexer: this.indexer,
|
|
675
|
+
query: combinedQuery,
|
|
676
|
+
explicitTargetAnchors,
|
|
677
|
+
action: initialTaskPlan.action,
|
|
678
|
+
identity: logIdentity,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
const targetAnchors = [...new Set([
|
|
682
|
+
...explicitTargetAnchors,
|
|
683
|
+
...artifactInventory.operationTargets
|
|
684
|
+
.filter(entry => entry.freshness.current)
|
|
685
|
+
.map(entry => entry.path),
|
|
686
|
+
])];
|
|
629
687
|
const retrievalTaskPlan = buildRetrievalTaskPlan({
|
|
630
688
|
query,
|
|
631
689
|
parsedIntent: intent,
|
|
632
690
|
targetAnchors,
|
|
633
691
|
activeSkill: options?.activeSkill,
|
|
634
692
|
command: options?.command,
|
|
693
|
+
authoritativeArtifact: options?.intentArtifact,
|
|
635
694
|
});
|
|
695
|
+
if (artifactInventory.missingExplicitTargets.length > 0) {
|
|
696
|
+
warnings.push(`explicit-target-missing:${artifactInventory.missingExplicitTargets.length}`);
|
|
697
|
+
}
|
|
698
|
+
if (artifactInventory.ambiguous)
|
|
699
|
+
warnings.push('artifact-target-ambiguous');
|
|
636
700
|
const timing = {};
|
|
637
701
|
const t0 = Date.now();
|
|
638
702
|
const [cgResult, knowledgeResult, memoryHits] = await Promise.all([
|
|
@@ -647,7 +711,10 @@ export class ContextEngine {
|
|
|
647
711
|
}),
|
|
648
712
|
new Promise((_, reject) => setTimeout(() => reject(new Error("buildContext timeout")), 15000)),
|
|
649
713
|
]).catch((err) => {
|
|
650
|
-
|
|
714
|
+
emitSemanticControlLog({
|
|
715
|
+
event: 'context.build.degraded', identity: logIdentity, level: 'warn', timestamp: Date.now(),
|
|
716
|
+
data: { stage: 'codegraph_context', reason: err.message, fallback: 'manual_pipeline' },
|
|
717
|
+
});
|
|
651
718
|
return null;
|
|
652
719
|
})
|
|
653
720
|
: Promise.resolve(null),
|
|
@@ -655,7 +722,10 @@ export class ContextEngine {
|
|
|
655
722
|
this.searchKnowledgeWithStatus(combinedQuery, this.getKnowledgeSearchOptions(combinedQuery, 5, options, retrievalTaskPlan, maxTokens)),
|
|
656
723
|
new Promise((_, reject) => setTimeout(() => reject(new Error("knowledge timeout")), 5000)),
|
|
657
724
|
]).catch((err) => {
|
|
658
|
-
|
|
725
|
+
emitSemanticControlLog({
|
|
726
|
+
event: 'knowledge.search.degraded', identity: logIdentity, level: 'warn', timestamp: Date.now(),
|
|
727
|
+
data: { reason: err.message, fallback: 'knowledge_empty' },
|
|
728
|
+
});
|
|
659
729
|
return { entries: [], warnings: [`knowledge-search-failed: ${err.message}`] };
|
|
660
730
|
}),
|
|
661
731
|
typeof this.memory.search === "function"
|
|
@@ -670,18 +740,23 @@ export class ContextEngine {
|
|
|
670
740
|
}),
|
|
671
741
|
new Promise((_, reject) => setTimeout(() => reject(new Error("memory timeout")), 5000)),
|
|
672
742
|
]).catch((err) => {
|
|
673
|
-
|
|
743
|
+
emitSemanticControlLog({
|
|
744
|
+
event: 'memory.search.completed', identity: logIdentity, level: 'warn', timestamp: Date.now(),
|
|
745
|
+
data: { status: 'degraded', reason: err.message, selected: 0, fallback: 'memory_empty' },
|
|
746
|
+
});
|
|
674
747
|
return [];
|
|
675
748
|
})
|
|
676
749
|
: Promise.resolve([]),
|
|
677
750
|
]);
|
|
678
751
|
timing["1_promise_all"] = Date.now() - t0;
|
|
679
|
-
console.error("[devflow:timing] Promise.all:", timing["1_promise_all"], "ms");
|
|
680
752
|
const knowledgeHits = knowledgeResult.entries;
|
|
681
753
|
if (knowledgeResult.warnings.length > 0)
|
|
682
754
|
warnings.push(...knowledgeResult.warnings);
|
|
683
755
|
if (!cgResult) {
|
|
684
|
-
|
|
756
|
+
emitSemanticControlLog({
|
|
757
|
+
event: 'context.build.degraded', identity: logIdentity, level: 'warn', timestamp: Date.now(),
|
|
758
|
+
data: { stage: 'codegraph_context', reason: 'context_unavailable', fallback: 'manual_pipeline' },
|
|
759
|
+
});
|
|
685
760
|
const result = await this.buildTaskContextLegacy(query, options);
|
|
686
761
|
result._devflow = { fallback: "buildContext-unavailable" };
|
|
687
762
|
return result;
|
|
@@ -693,7 +768,10 @@ export class ContextEngine {
|
|
|
693
768
|
ctx = JSON.parse(cgResult);
|
|
694
769
|
}
|
|
695
770
|
catch {
|
|
696
|
-
|
|
771
|
+
emitSemanticControlLog({
|
|
772
|
+
event: 'context.build.degraded', identity: logIdentity, level: 'warn', timestamp: Date.now(),
|
|
773
|
+
data: { stage: 'codegraph_parse', reason: 'unparseable_context', fallback: 'manual_pipeline' },
|
|
774
|
+
});
|
|
697
775
|
return this.buildTaskContextLegacy(query, options);
|
|
698
776
|
}
|
|
699
777
|
}
|
|
@@ -702,6 +780,20 @@ export class ContextEngine {
|
|
|
702
780
|
}
|
|
703
781
|
ctx.nodes = Array.isArray(ctx.nodes) ? ctx.nodes : [];
|
|
704
782
|
ctx.entryPoints = Array.isArray(ctx.entryPoints) ? ctx.entryPoints : [];
|
|
783
|
+
const inventoryNodeIds = new Set(ctx.nodes.map((node) => String(node.id ?? '')));
|
|
784
|
+
for (const target of artifactInventory.operationTargets.filter(entry => entry.freshness.current)) {
|
|
785
|
+
const nodes = this.cg.getNodesInFile(target.path)
|
|
786
|
+
.filter(node => node.kind !== 'file' && node.kind !== 'import');
|
|
787
|
+
for (const node of nodes) {
|
|
788
|
+
if (!inventoryNodeIds.has(node.id)) {
|
|
789
|
+
ctx.nodes.push(node);
|
|
790
|
+
inventoryNodeIds.add(node.id);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const root = nodes.find(node => target.symbols.includes(node.name)) ?? nodes[0];
|
|
794
|
+
if (root && !ctx.entryPoints.some((entry) => entry.id === root.id))
|
|
795
|
+
ctx.entryPoints.unshift(root);
|
|
796
|
+
}
|
|
705
797
|
await this.applyTaskSymbolVectorRecall(ctx, combinedQuery, warnings);
|
|
706
798
|
this.clearRecoveredSymbolWarning(warnings);
|
|
707
799
|
// Fallback: direct searchNodes for query terms that buildContext may have missed
|
|
@@ -734,7 +826,6 @@ export class ContextEngine {
|
|
|
734
826
|
}
|
|
735
827
|
}
|
|
736
828
|
}
|
|
737
|
-
console.error("[devflow:timing] Fallback searchNodes loop:", Date.now() - tFallback, "ms");
|
|
738
829
|
timing["2_fallback_loop"] = Date.now() - tFallback;
|
|
739
830
|
const fileFreshnessByPath = new Map();
|
|
740
831
|
if (this.indexer?.ensureFilesCurrent) {
|
|
@@ -765,6 +856,22 @@ export class ContextEngine {
|
|
|
765
856
|
ctx.edges = edgeQuality.accepted;
|
|
766
857
|
if (edgeQuality.rejected.length > 0) {
|
|
767
858
|
warnings.push(`codegraph-relations-rejected:${edgeQuality.rejected.length}`);
|
|
859
|
+
emitSemanticControlLog({
|
|
860
|
+
event: 'codegraph.edge.rejected',
|
|
861
|
+
identity: logIdentity,
|
|
862
|
+
level: 'warn',
|
|
863
|
+
timestamp: Date.now(),
|
|
864
|
+
data: {
|
|
865
|
+
rejectedCount: edgeQuality.rejected.length,
|
|
866
|
+
reasons: countValues(edgeQuality.rejected.map(edge => edge.reason)),
|
|
867
|
+
examples: edgeQuality.rejected.slice(0, 8).map(edge => ({
|
|
868
|
+
kind: edge.kind,
|
|
869
|
+
sourceFile: edge.sourceFile,
|
|
870
|
+
targetFile: edge.targetFile,
|
|
871
|
+
reason: edge.reason,
|
|
872
|
+
})),
|
|
873
|
+
},
|
|
874
|
+
});
|
|
768
875
|
}
|
|
769
876
|
const tSnippet = Date.now();
|
|
770
877
|
let allNodes = ctx.nodes ?? [];
|
|
@@ -813,6 +920,10 @@ export class ContextEngine {
|
|
|
813
920
|
const optionalCandidates = new Map();
|
|
814
921
|
const graphOptionalPaths = new Set();
|
|
815
922
|
const queryTerms = combinedQuery.split(/\s+/).map(term => term.toLowerCase()).filter(term => term.length >= 3);
|
|
923
|
+
for (const target of artifactInventory.operationTargets) {
|
|
924
|
+
if (target.freshness.current)
|
|
925
|
+
requiredFileSet.add(target.path);
|
|
926
|
+
}
|
|
816
927
|
for (const node of allNodes) {
|
|
817
928
|
// Skip file wrappers and import edges — they're not actionable symbols
|
|
818
929
|
if (node.kind === "file" || node.kind === "import")
|
|
@@ -867,6 +978,16 @@ export class ContextEngine {
|
|
|
867
978
|
}
|
|
868
979
|
}
|
|
869
980
|
const fileRoleByPath = new Map();
|
|
981
|
+
for (const artifact of artifactInventory.entries.filter(entry => entry.freshness.current)) {
|
|
982
|
+
fileRoleByPath.set(artifact.path, {
|
|
983
|
+
path: artifact.path,
|
|
984
|
+
role: artifact.artifactRole === 'operation_target' ? 'target' : 'dependency',
|
|
985
|
+
artifactRole: artifact.artifactRole,
|
|
986
|
+
score: artifact.score,
|
|
987
|
+
evidence: artifact.evidence,
|
|
988
|
+
});
|
|
989
|
+
fileFreshnessByPath.set(artifact.path, artifact.freshness);
|
|
990
|
+
}
|
|
870
991
|
const roleCandidates = this.indexer
|
|
871
992
|
? assembleContextFileRoles({
|
|
872
993
|
codegraph: this.cg,
|
|
@@ -879,7 +1000,10 @@ export class ContextEngine {
|
|
|
879
1000
|
if (this.indexer?.getFileFreshness) {
|
|
880
1001
|
fileFreshnessByPath.set(candidate.path, this.indexer.getFileFreshness(candidate.path));
|
|
881
1002
|
}
|
|
882
|
-
fileRoleByPath.
|
|
1003
|
+
const inventoryRole = fileRoleByPath.get(candidate.path);
|
|
1004
|
+
fileRoleByPath.set(candidate.path, inventoryRole && inventoryRole.score >= candidate.score
|
|
1005
|
+
? inventoryRole
|
|
1006
|
+
: candidate);
|
|
883
1007
|
if (candidate.role === 'target') {
|
|
884
1008
|
requiredFileSet.add(candidate.path);
|
|
885
1009
|
continue;
|
|
@@ -979,7 +1103,6 @@ export class ContextEngine {
|
|
|
979
1103
|
const compressed = compressToTokenLimit(allFileSnippets, Math.floor(maxTokens * 0.45));
|
|
980
1104
|
if (compressed.overBudget)
|
|
981
1105
|
warnings.push("context-compressor-budget-exceeded");
|
|
982
|
-
console.error("[devflow:timing] Snippet building + compress:", Date.now() - tSnippet, "ms");
|
|
983
1106
|
timing["3_snippet_compress"] = Date.now() - tSnippet;
|
|
984
1107
|
const riskHints = generateRiskHints(allNodes.filter(node => node.kind !== "file" && node.kind !== "import"), graphEvidence);
|
|
985
1108
|
const nextActions = generateNextActions(taskType, keySymbols.slice(0, 5));
|
|
@@ -1108,6 +1231,9 @@ export class ContextEngine {
|
|
|
1108
1231
|
requestId: options?.requestId,
|
|
1109
1232
|
host: options?.host,
|
|
1110
1233
|
command: options?.command,
|
|
1234
|
+
turnId: options?.turnId,
|
|
1235
|
+
intentArtifact: options?.intentArtifact,
|
|
1236
|
+
queryPlan: options?.queryPlan,
|
|
1111
1237
|
tokenBudget: maxTokens,
|
|
1112
1238
|
targetAnchors,
|
|
1113
1239
|
taskPlan: retrievalTaskPlan,
|
|
@@ -1152,7 +1278,23 @@ export class ContextEngine {
|
|
|
1152
1278
|
this.queryCache.set(cacheKey, result);
|
|
1153
1279
|
}
|
|
1154
1280
|
timing["0_total"] = Date.now() - t0;
|
|
1155
|
-
|
|
1281
|
+
emitSemanticControlLog({
|
|
1282
|
+
event: 'codegraph.selection.completed',
|
|
1283
|
+
identity: {
|
|
1284
|
+
...logIdentity,
|
|
1285
|
+
contextReceipt: result.taskEnvelope?.receipt.contextHash,
|
|
1286
|
+
},
|
|
1287
|
+
level: warnings.length > 0 ? 'warn' : 'info',
|
|
1288
|
+
timestamp: Date.now(),
|
|
1289
|
+
data: {
|
|
1290
|
+
selectedFiles: result.taskEnvelope?.code.files.length ?? 0,
|
|
1291
|
+
selectedSymbols: result.taskEnvelope?.code.symbols.length ?? 0,
|
|
1292
|
+
rejectedRelations: result.taskEnvelope?.quality.code.rejected ?? 0,
|
|
1293
|
+
ambiguityCount: result.taskEnvelope?.code.ambiguities?.length ?? 0,
|
|
1294
|
+
timing,
|
|
1295
|
+
warnings: warnings.map(value => value.split(':')[0]),
|
|
1296
|
+
},
|
|
1297
|
+
});
|
|
1156
1298
|
result._timing = timing;
|
|
1157
1299
|
return result;
|
|
1158
1300
|
}
|
|
@@ -1487,6 +1629,9 @@ export class ContextEngine {
|
|
|
1487
1629
|
requestId: options?.requestId,
|
|
1488
1630
|
host: options?.host,
|
|
1489
1631
|
command: options?.command,
|
|
1632
|
+
turnId: options?.turnId,
|
|
1633
|
+
intentArtifact: options?.intentArtifact,
|
|
1634
|
+
queryPlan: options?.queryPlan,
|
|
1490
1635
|
tokenBudget: maxTokens,
|
|
1491
1636
|
targetAnchors,
|
|
1492
1637
|
fileRoles: fileRoleByPath,
|
|
@@ -1586,4 +1731,10 @@ function getGroupFilePaths(group) {
|
|
|
1586
1731
|
return group;
|
|
1587
1732
|
return group?.files ?? [];
|
|
1588
1733
|
}
|
|
1734
|
+
function countValues(values) {
|
|
1735
|
+
const counts = {};
|
|
1736
|
+
for (const value of values)
|
|
1737
|
+
counts[value] = (counts[value] ?? 0) + 1;
|
|
1738
|
+
return counts;
|
|
1739
|
+
}
|
|
1589
1740
|
//# sourceMappingURL=context-engine.js.map
|