import { dedupeText } from "./context-composer-text.js"; import { taskMentionsScopeIdentity } from "./context-composer-scope.js"; import { buildCaseFallbackQuery, buildContinuityFallbackPatterns, buildWorkflowFallbackQuery, } from "./context-composer-task-fallbacks.js"; import type { RetrievalResult } from "./retriever.js"; import { looksLikeCaseFallbackTask, } from "./term-registry.js"; import { countPatternCueCoverage, countTaskSpecificTextMatches, looksLikeGenericWindowHandoffTask, selectRelevantPatterns, selectTaskResults, selectWorkflowFallbackCandidates, type TaskCategory, } from "./context-composer-task-selection.js"; export async function buildTaskResultSections(params: { retrieveCandidates: (args: { category?: TaskCategory; query: string; limit: number; scope?: string; }) => Promise; patternResults: RetrievalResult[]; caseResults: RetrievalResult[]; continuityTask: boolean; hasLatestCheckpoint: boolean; taskLimit: number; taskSeed?: string; scope?: string; strongWorkflowCueTerms?: string[]; }): Promise<{ relevantPatterns: string[]; recentCases: string[] }> { const { retrieveCandidates, patternResults, caseResults, continuityTask, hasLatestCheckpoint, taskLimit, taskSeed, scope, strongWorkflowCueTerms, } = params; const retrievedPatterns = selectTaskResults("patterns", patternResults, taskLimit, { scope, taskSeed, }); const allowSparseCheckpointSupplement = Boolean( hasLatestCheckpoint && scope?.startsWith("project:") && taskMentionsScopeIdentity(taskSeed, scope) && retrievedPatterns.length <= 1, ); const shouldProvideContinuityGuidance = continuityTask || allowSparseCheckpointSupplement; const workflowFallbackResults = !shouldProvideContinuityGuidance || countPatternCueCoverage(retrievedPatterns) >= 3 ? [] : await retrieveCandidates({ query: buildWorkflowFallbackQuery(taskSeed), limit: Math.max(4, taskLimit * 2), scope, }); const fallbackPatterns = selectWorkflowFallbackCandidates(workflowFallbackResults, { scope, taskSeed, limit: taskLimit, cueTerms: strongWorkflowCueTerms, }); const combinedPatterns = [ ...retrievedPatterns, ...fallbackPatterns, ]; const combinedPatternCueCoverage = countPatternCueCoverage(combinedPatterns); const allowSingleContinuityGapSupplement = Boolean( continuityTask && retrievedPatterns.length === 1 && combinedPatterns.length === 1 && ( combinedPatternCueCoverage >= 2 || looksLikeGenericWindowHandoffTask(taskSeed) ), ); const noCueCoverageAndNoFallback = combinedPatternCueCoverage === 0 && fallbackPatterns.length === 0; const continuityFallbackPatterns = !shouldProvideContinuityGuidance ? [] : combinedPatterns.length === 0 || noCueCoverageAndNoFallback ? buildContinuityFallbackPatterns(taskLimit) : combinedPatternCueCoverage < 3 && ( combinedPatterns.length >= 2 || allowSparseCheckpointSupplement || allowSingleContinuityGapSupplement ) ? buildContinuityFallbackPatterns(taskLimit, combinedPatterns) : []; const relevantPatterns = selectRelevantPatterns([ ...retrievedPatterns.map((text) => ({ text, sourcePriority: 3 })), ...fallbackPatterns.map((text) => ({ text, sourcePriority: 2 })), ...continuityFallbackPatterns.map((text) => ({ text, sourcePriority: 1 })), ], taskLimit); const retrievedCases = selectTaskResults("cases", caseResults, taskLimit, { scope, taskSeed, }); const allowSparseCaseFallback = Boolean( retrievedCases.length <= 1 && looksLikeCaseFallbackTask(taskSeed) && Math.max(0, ...retrievedCases.map((item) => countTaskSpecificTextMatches(item, taskSeed))) === 0, ); const caseFallbackResults = (retrievedCases.length > 0 && !allowSparseCaseFallback) || (!hasLatestCheckpoint && !looksLikeCaseFallbackTask(taskSeed)) ? [] : await retrieveCandidates({ category: "cases", query: buildCaseFallbackQuery(taskSeed), // Case fallback is now competing with more durable meta-maintenance // cases. Pull a wider pool so generic RecallNest continues can still // reach the canonical cleanup cases after specificity filtering. limit: Math.max(10, taskLimit * 3), scope, }); const fallbackCases = selectTaskResults("cases", caseFallbackResults, taskLimit, { scope, taskSeed, }); const recentCases = dedupeText([ ...retrievedCases, ...fallbackCases, ], taskLimit); return { relevantPatterns, recentCases }; }