@mirnoorata/codexa 0.2.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/LICENSE +21 -0
- package/README.md +634 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +375 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/autonomy.d.ts +17 -0
- package/dist/autonomy.js +124 -0
- package/dist/autonomy.js.map +1 -0
- package/dist/autoverify/policy.d.ts +5 -0
- package/dist/autoverify/policy.js +18 -0
- package/dist/autoverify/policy.js.map +1 -0
- package/dist/autoverify.d.ts +45 -0
- package/dist/autoverify.js +1041 -0
- package/dist/autoverify.js.map +1 -0
- package/dist/cache-lock.d.ts +16 -0
- package/dist/cache-lock.js +181 -0
- package/dist/cache-lock.js.map +1 -0
- package/dist/cli/hooks.d.ts +5 -0
- package/dist/cli/hooks.js +264 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1034 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-contract.d.ts +2 -0
- package/dist/codex-contract.js +78 -0
- package/dist/codex-contract.js.map +1 -0
- package/dist/command.d.ts +34 -0
- package/dist/command.js +162 -0
- package/dist/command.js.map +1 -0
- package/dist/doctor.d.ts +112 -0
- package/dist/doctor.js +518 -0
- package/dist/doctor.js.map +1 -0
- package/dist/eval/baseline.d.ts +7 -0
- package/dist/eval/baseline.js +146 -0
- package/dist/eval/baseline.js.map +1 -0
- package/dist/eval/historical.d.ts +4 -0
- package/dist/eval/historical.js +663 -0
- package/dist/eval/historical.js.map +1 -0
- package/dist/eval/render.d.ts +2 -0
- package/dist/eval/render.js +53 -0
- package/dist/eval/render.js.map +1 -0
- package/dist/eval/scoring.d.ts +21 -0
- package/dist/eval/scoring.js +618 -0
- package/dist/eval/scoring.js.map +1 -0
- package/dist/eval/synthetic.d.ts +36 -0
- package/dist/eval/synthetic.js +107 -0
- package/dist/eval/synthetic.js.map +1 -0
- package/dist/eval/types.d.ts +36 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/eval.d.ts +140 -0
- package/dist/eval.js +551 -0
- package/dist/eval.js.map +1 -0
- package/dist/git.d.ts +17 -0
- package/dist/git.js +189 -0
- package/dist/git.js.map +1 -0
- package/dist/github-release.d.ts +47 -0
- package/dist/github-release.js +610 -0
- package/dist/github-release.js.map +1 -0
- package/dist/github-sync.d.ts +68 -0
- package/dist/github-sync.js +345 -0
- package/dist/github-sync.js.map +1 -0
- package/dist/graph.d.ts +10 -0
- package/dist/graph.js +665 -0
- package/dist/graph.js.map +1 -0
- package/dist/indexer/aliases.d.ts +2 -0
- package/dist/indexer/aliases.js +190 -0
- package/dist/indexer/aliases.js.map +1 -0
- package/dist/indexer/artifact-writing.d.ts +3 -0
- package/dist/indexer/artifact-writing.js +79 -0
- package/dist/indexer/artifact-writing.js.map +1 -0
- package/dist/indexer/discovery.d.ts +2 -0
- package/dist/indexer/discovery.js +5 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/external-facts.d.ts +6 -0
- package/dist/indexer/external-facts.js +45 -0
- package/dist/indexer/external-facts.js.map +1 -0
- package/dist/indexer/freshness.d.ts +8 -0
- package/dist/indexer/freshness.js +56 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/graph-stage.d.ts +2 -0
- package/dist/indexer/graph-stage.js +21 -0
- package/dist/indexer/graph-stage.js.map +1 -0
- package/dist/indexer/parsing.d.ts +30 -0
- package/dist/indexer/parsing.js +177 -0
- package/dist/indexer/parsing.js.map +1 -0
- package/dist/indexer/pipeline.d.ts +5 -0
- package/dist/indexer/pipeline.js +8 -0
- package/dist/indexer/pipeline.js.map +1 -0
- package/dist/indexer/ranking.d.ts +4 -0
- package/dist/indexer/ranking.js +134 -0
- package/dist/indexer/ranking.js.map +1 -0
- package/dist/indexer.d.ts +13 -0
- package/dist/indexer.js +395 -0
- package/dist/indexer.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +566 -0
- package/dist/init.js.map +1 -0
- package/dist/language.d.ts +8 -0
- package/dist/language.js +123 -0
- package/dist/language.js.map +1 -0
- package/dist/live-index.d.ts +68 -0
- package/dist/live-index.js +215 -0
- package/dist/live-index.js.map +1 -0
- package/dist/lsp/assist.d.ts +44 -0
- package/dist/lsp/assist.js +331 -0
- package/dist/lsp/assist.js.map +1 -0
- package/dist/lsp/client.d.ts +59 -0
- package/dist/lsp/client.js +208 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/mcp/compaction.d.ts +15 -0
- package/dist/mcp/compaction.js +1249 -0
- package/dist/mcp/compaction.js.map +1 -0
- package/dist/mcp/envelope.d.ts +44 -0
- package/dist/mcp/envelope.js +425 -0
- package/dist/mcp/envelope.js.map +1 -0
- package/dist/mcp/prompts.d.ts +2 -0
- package/dist/mcp/prompts.js +109 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +2 -0
- package/dist/mcp/resources.js +132 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/runtime.d.ts +15 -0
- package/dist/mcp/runtime.js +122 -0
- package/dist/mcp/runtime.js.map +1 -0
- package/dist/mcp/session-memory.d.ts +3 -0
- package/dist/mcp/session-memory.js +61 -0
- package/dist/mcp/session-memory.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +269 -0
- package/dist/mcp/tool-registry.js +284 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/tools.d.ts +53 -0
- package/dist/mcp/tools.js +372 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp-repo-root.d.ts +16 -0
- package/dist/mcp-repo-root.js +322 -0
- package/dist/mcp-repo-root.js.map +1 -0
- package/dist/mcp-tool-catalog.d.ts +2 -0
- package/dist/mcp-tool-catalog.js +2 -0
- package/dist/mcp-tool-catalog.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +332 -0
- package/dist/mcp.js.map +1 -0
- package/dist/outcome-ranking.d.ts +5 -0
- package/dist/outcome-ranking.js +115 -0
- package/dist/outcome-ranking.js.map +1 -0
- package/dist/parser/context.d.ts +28 -0
- package/dist/parser/context.js +2 -0
- package/dist/parser/context.js.map +1 -0
- package/dist/parser/ecma.d.ts +5 -0
- package/dist/parser/ecma.js +388 -0
- package/dist/parser/ecma.js.map +1 -0
- package/dist/parser/facts.d.ts +12 -0
- package/dist/parser/facts.js +137 -0
- package/dist/parser/facts.js.map +1 -0
- package/dist/parser/json.d.ts +3 -0
- package/dist/parser/json.js +318 -0
- package/dist/parser/json.js.map +1 -0
- package/dist/parser/markdown.d.ts +3 -0
- package/dist/parser/markdown.js +180 -0
- package/dist/parser/markdown.js.map +1 -0
- package/dist/parser/nodes.d.ts +5 -0
- package/dist/parser/nodes.js +75 -0
- package/dist/parser/nodes.js.map +1 -0
- package/dist/parser/python.d.ts +2 -0
- package/dist/parser/python.js +307 -0
- package/dist/parser/python.js.map +1 -0
- package/dist/parser/references.d.ts +3 -0
- package/dist/parser/references.js +204 -0
- package/dist/parser/references.js.map +1 -0
- package/dist/parser/risks.d.ts +4 -0
- package/dist/parser/risks.js +62 -0
- package/dist/parser/risks.js.map +1 -0
- package/dist/parser/routes.d.ts +5 -0
- package/dist/parser/routes.js +97 -0
- package/dist/parser/routes.js.map +1 -0
- package/dist/parser/shallow.d.ts +3 -0
- package/dist/parser/shallow.js +545 -0
- package/dist/parser/shallow.js.map +1 -0
- package/dist/parser/source.d.ts +4 -0
- package/dist/parser/source.js +127 -0
- package/dist/parser/source.js.map +1 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +2 -0
- package/dist/parser.js.map +1 -0
- package/dist/placeholder-signals.d.ts +15 -0
- package/dist/placeholder-signals.js +511 -0
- package/dist/placeholder-signals.js.map +1 -0
- package/dist/post-edit-outcomes.d.ts +167 -0
- package/dist/post-edit-outcomes.js +484 -0
- package/dist/post-edit-outcomes.js.map +1 -0
- package/dist/queries.d.ts +12 -0
- package/dist/queries.js +13 -0
- package/dist/queries.js.map +1 -0
- package/dist/query/change-plan.d.ts +48 -0
- package/dist/query/change-plan.js +858 -0
- package/dist/query/change-plan.js.map +1 -0
- package/dist/query/compact-data.d.ts +25 -0
- package/dist/query/compact-data.js +74 -0
- package/dist/query/compact-data.js.map +1 -0
- package/dist/query/context.d.ts +5 -0
- package/dist/query/context.js +1162 -0
- package/dist/query/context.js.map +1 -0
- package/dist/query/diff.d.ts +5 -0
- package/dist/query/diff.js +111 -0
- package/dist/query/diff.js.map +1 -0
- package/dist/query/edge-evidence.d.ts +3 -0
- package/dist/query/edge-evidence.js +36 -0
- package/dist/query/edge-evidence.js.map +1 -0
- package/dist/query/formatting.d.ts +14 -0
- package/dist/query/formatting.js +67 -0
- package/dist/query/formatting.js.map +1 -0
- package/dist/query/graph-traversal.d.ts +22 -0
- package/dist/query/graph-traversal.js +218 -0
- package/dist/query/graph-traversal.js.map +1 -0
- package/dist/query/graph.d.ts +14 -0
- package/dist/query/graph.js +102 -0
- package/dist/query/graph.js.map +1 -0
- package/dist/query/impact.d.ts +28 -0
- package/dist/query/impact.js +568 -0
- package/dist/query/impact.js.map +1 -0
- package/dist/query/inspection.d.ts +9 -0
- package/dist/query/inspection.js +290 -0
- package/dist/query/inspection.js.map +1 -0
- package/dist/query/next-tools.d.ts +3 -0
- package/dist/query/next-tools.js +25 -0
- package/dist/query/next-tools.js.map +1 -0
- package/dist/query/placeholders.d.ts +24 -0
- package/dist/query/placeholders.js +121 -0
- package/dist/query/placeholders.js.map +1 -0
- package/dist/query/post-edit/decision.d.ts +49 -0
- package/dist/query/post-edit/decision.js +130 -0
- package/dist/query/post-edit/decision.js.map +1 -0
- package/dist/query/post-edit/dirty-scope.d.ts +16 -0
- package/dist/query/post-edit/dirty-scope.js +21 -0
- package/dist/query/post-edit/dirty-scope.js.map +1 -0
- package/dist/query/post-edit/next-actions.d.ts +22 -0
- package/dist/query/post-edit/next-actions.js +44 -0
- package/dist/query/post-edit/next-actions.js.map +1 -0
- package/dist/query/post-edit/snapshot-contract.d.ts +8 -0
- package/dist/query/post-edit/snapshot-contract.js +111 -0
- package/dist/query/post-edit/snapshot-contract.js.map +1 -0
- package/dist/query/post-edit.d.ts +5 -0
- package/dist/query/post-edit.js +1108 -0
- package/dist/query/post-edit.js.map +1 -0
- package/dist/query/quality.d.ts +43 -0
- package/dist/query/quality.js +134 -0
- package/dist/query/quality.js.map +1 -0
- package/dist/query/raw-search.d.ts +23 -0
- package/dist/query/raw-search.js +147 -0
- package/dist/query/raw-search.js.map +1 -0
- package/dist/query/runtime.d.ts +11 -0
- package/dist/query/runtime.js +79 -0
- package/dist/query/runtime.js.map +1 -0
- package/dist/query/search.d.ts +25 -0
- package/dist/query/search.js +429 -0
- package/dist/query/search.js.map +1 -0
- package/dist/query/session-memory.d.ts +3 -0
- package/dist/query/session-memory.js +108 -0
- package/dist/query/session-memory.js.map +1 -0
- package/dist/query/session.d.ts +41 -0
- package/dist/query/session.js +90 -0
- package/dist/query/session.js.map +1 -0
- package/dist/query/targets.d.ts +25 -0
- package/dist/query/targets.js +97 -0
- package/dist/query/targets.js.map +1 -0
- package/dist/query/test-commands.d.ts +10 -0
- package/dist/query/test-commands.js +110 -0
- package/dist/query/test-commands.js.map +1 -0
- package/dist/query/test-plan.d.ts +6 -0
- package/dist/query/test-plan.js +104 -0
- package/dist/query/test-plan.js.map +1 -0
- package/dist/query/tests.d.ts +48 -0
- package/dist/query/tests.js +444 -0
- package/dist/query/tests.js.map +1 -0
- package/dist/query/verification/shell.d.ts +20 -0
- package/dist/query/verification/shell.js +164 -0
- package/dist/query/verification/shell.js.map +1 -0
- package/dist/query/verification.d.ts +47 -0
- package/dist/query/verification.js +1123 -0
- package/dist/query/verification.js.map +1 -0
- package/dist/query/workflow.d.ts +17 -0
- package/dist/query/workflow.js +252 -0
- package/dist/query/workflow.js.map +1 -0
- package/dist/query/workspace-guidance.d.ts +26 -0
- package/dist/query/workspace-guidance.js +214 -0
- package/dist/query/workspace-guidance.js.map +1 -0
- package/dist/query/worktree-state.d.ts +22 -0
- package/dist/query/worktree-state.js +32 -0
- package/dist/query/worktree-state.js.map +1 -0
- package/dist/query/worktree.d.ts +16 -0
- package/dist/query/worktree.js +194 -0
- package/dist/query/worktree.js.map +1 -0
- package/dist/query-data.d.ts +4 -0
- package/dist/query-data.js +112 -0
- package/dist/query-data.js.map +1 -0
- package/dist/repo-files.d.ts +24 -0
- package/dist/repo-files.js +105 -0
- package/dist/repo-files.js.map +1 -0
- package/dist/resolver.d.ts +9 -0
- package/dist/resolver.js +555 -0
- package/dist/resolver.js.map +1 -0
- package/dist/retrieval.d.ts +46 -0
- package/dist/retrieval.js +783 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/risk-ingest.d.ts +16 -0
- package/dist/risk-ingest.js +458 -0
- package/dist/risk-ingest.js.map +1 -0
- package/dist/rules.d.ts +10 -0
- package/dist/rules.js +107 -0
- package/dist/rules.js.map +1 -0
- package/dist/semantic/python.d.ts +9 -0
- package/dist/semantic/python.js +817 -0
- package/dist/semantic/python.js.map +1 -0
- package/dist/semantic/typescript.d.ts +10 -0
- package/dist/semantic/typescript.js +714 -0
- package/dist/semantic/typescript.js.map +1 -0
- package/dist/semantic-retrieval.d.ts +53 -0
- package/dist/semantic-retrieval.js +673 -0
- package/dist/semantic-retrieval.js.map +1 -0
- package/dist/session-memory/derivation.d.ts +6 -0
- package/dist/session-memory/derivation.js +400 -0
- package/dist/session-memory/derivation.js.map +1 -0
- package/dist/session-memory/event-log.d.ts +23 -0
- package/dist/session-memory/event-log.js +126 -0
- package/dist/session-memory/event-log.js.map +1 -0
- package/dist/session-memory/formatting.d.ts +7 -0
- package/dist/session-memory/formatting.js +86 -0
- package/dist/session-memory/formatting.js.map +1 -0
- package/dist/session-memory/model.d.ts +94 -0
- package/dist/session-memory/model.js +17 -0
- package/dist/session-memory/model.js.map +1 -0
- package/dist/session-memory/runtime.d.ts +24 -0
- package/dist/session-memory/runtime.js +289 -0
- package/dist/session-memory/runtime.js.map +1 -0
- package/dist/session-memory/store.d.ts +27 -0
- package/dist/session-memory/store.js +447 -0
- package/dist/session-memory/store.js.map +1 -0
- package/dist/session-memory.d.ts +1 -0
- package/dist/session-memory.js +2 -0
- package/dist/session-memory.js.map +1 -0
- package/dist/static-analysis.d.ts +36 -0
- package/dist/static-analysis.js +505 -0
- package/dist/static-analysis.js.map +1 -0
- package/dist/symbol-report-ingest.d.ts +8 -0
- package/dist/symbol-report-ingest.js +504 -0
- package/dist/symbol-report-ingest.js.map +1 -0
- package/dist/task-snapshots.d.ts +41 -0
- package/dist/task-snapshots.js +430 -0
- package/dist/task-snapshots.js.map +1 -0
- package/dist/types.d.ts +848 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +11 -0
- package/dist/util.js +63 -0
- package/dist/util.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +81 -0
- package/plugins/codexa/.codex-plugin/plugin.json +38 -0
- package/plugins/codexa/.mcp.json +20 -0
- package/plugins/codexa/scripts/codexa-mcp.js +100 -0
- package/plugins/codexa/skills/codexa/SKILL.md +48 -0
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isTestPath, moduleNameForPath } from "../language.js";
|
|
4
|
+
import { uniqueSorted } from "../util.js";
|
|
5
|
+
import { formatDiffGroups, formatGaps, groupDiffImpact, indexGaps } from "./diff.js";
|
|
6
|
+
import { addContextPackImpactExpansion, verificationRecipes } from "./impact.js";
|
|
7
|
+
import { lspAssistForFiles, lspOptionsFromQueryOptions } from "../lsp/assist.js";
|
|
8
|
+
import { betterTier, clampInt, confidenceTier, focusTierCounts, formatReasons, formatRecipes, limitTextToTokens, tierScore } from "./formatting.js";
|
|
9
|
+
import { formatWorkflowSummary, recommendNextCodexaCall } from "./graph.js";
|
|
10
|
+
import { nextTool } from "./next-tools.js";
|
|
11
|
+
import { assessContextQuality, formatContextQuality, formatValueEstimate, valueEstimate } from "./quality.js";
|
|
12
|
+
import { baselineSearchSummary } from "./raw-search.js";
|
|
13
|
+
import { codeLikeQueryFromTask, fileStemQueryTerms, matchReason, matchScore, uniqueFiles } from "./search.js";
|
|
14
|
+
import { freshnessBanner } from "./runtime.js";
|
|
15
|
+
import { ensureQuerySession } from "./session.js";
|
|
16
|
+
import { compactWorktreeState, getWorktreeState, worktreeStateGaps, worktreeStateText } from "./worktree-state.js";
|
|
17
|
+
import { isCodexaControlPath } from "./worktree.js";
|
|
18
|
+
import { formatTestRecommendations, recommendTests } from "./tests.js";
|
|
19
|
+
import { findFile, resolveFileTarget, resolveSymbolTarget } from "./targets.js";
|
|
20
|
+
import { coverageForDisplay, formatVerificationCoverage, verificationCommandPlan, verificationCommandsForContext } from "./verification.js";
|
|
21
|
+
import { classifyTaskIntent, retrieveForTask } from "../retrieval.js";
|
|
22
|
+
import { semanticOptionsFromQueryOptions } from "../semantic-retrieval.js";
|
|
23
|
+
import { compactChangedSymbol, compactDiffGroup, compactFileFact, compactRetrievalResult, compactWorkflowTrace } from "./compact-data.js";
|
|
24
|
+
import { summarizeSessionMemory } from "../session-memory.js";
|
|
25
|
+
import { workspaceGuidancePreview } from "./workspace-guidance.js";
|
|
26
|
+
export async function contextPackQuery(input, contextInput = {}, options = {}) {
|
|
27
|
+
const session = await ensureQuerySession(input, options);
|
|
28
|
+
const { index, freshness, refresh, repoRoot } = session;
|
|
29
|
+
const tokenBudget = clampInt(contextInput.tokenBudget ?? 4000, 500, 12000);
|
|
30
|
+
const limit = clampInt(contextInput.limit ?? 12, 3, session.maxResults);
|
|
31
|
+
const includeSnippets = contextInput.includeSnippets ?? true;
|
|
32
|
+
const changeType = contextInput.changeType ?? "unknown";
|
|
33
|
+
const warnings = [...session.warnings];
|
|
34
|
+
const focusState = createContextFocusState(index, warnings);
|
|
35
|
+
const { focus, impactSeeds, addFocus } = focusState;
|
|
36
|
+
const requestedFiles = contextInput.files ?? [];
|
|
37
|
+
const requestedSymbols = contextInput.symbols ?? [];
|
|
38
|
+
const requestedResolvedPaths = addExplicitTargetsToContextFocus({
|
|
39
|
+
index,
|
|
40
|
+
repoRoot,
|
|
41
|
+
requestedFiles,
|
|
42
|
+
requestedSymbols,
|
|
43
|
+
focus: focusState,
|
|
44
|
+
warnings
|
|
45
|
+
});
|
|
46
|
+
const explicitQuery = contextInput.query?.trim() ?? "";
|
|
47
|
+
const explicitTargetProvided = requestedFiles.length > 0 || requestedSymbols.length > 0;
|
|
48
|
+
const explicitConfigTarget = requestedResolvedPaths.some(isConfigExpansionPath);
|
|
49
|
+
const taskIntents = contextInput.task ? classifyTaskIntent(contextInput.task) : [];
|
|
50
|
+
const dirtyContextHint = Boolean(contextInput.task && taskReferencesDirtyContext(contextInput.task) && !explicitTargetProvided && !explicitQuery);
|
|
51
|
+
const includeDiff = contextInput.diff ?? true;
|
|
52
|
+
const worktree = includeDiff ? await getWorktreeState(session) : undefined;
|
|
53
|
+
const changedEntries = worktree?.entries ?? [];
|
|
54
|
+
const changed = worktree?.files ?? [];
|
|
55
|
+
const changedSymbols = worktree?.symbols ?? [];
|
|
56
|
+
const dirtyContextTask = dirtyContextHint && includeDiff && changed.length > 0;
|
|
57
|
+
const derivedTaskQuery = explicitQuery || explicitTargetProvided || dirtyContextTask ? "" : codeLikeQueryFromTask(contextInput.task);
|
|
58
|
+
const queryText = explicitQuery || derivedTaskQuery;
|
|
59
|
+
const naturalRetrieval = contextInput.task && !dirtyContextTask && shouldRunNaturalRetrieval(explicitTargetProvided, explicitConfigTarget, taskIntents)
|
|
60
|
+
? await retrieveForTask(index, contextInput.task, Math.max(limit * 2, 12), semanticOptionsFromQueryOptions(repoRoot, options))
|
|
61
|
+
: undefined;
|
|
62
|
+
const naturalExpansionAllowed = Boolean(naturalRetrieval &&
|
|
63
|
+
!dirtyContextTask &&
|
|
64
|
+
!queryText.trim() &&
|
|
65
|
+
(!explicitTargetProvided ||
|
|
66
|
+
(explicitConfigTarget &&
|
|
67
|
+
(naturalRetrieval.intentConfidence.mode === "edit" ||
|
|
68
|
+
naturalRetrieval.intentConfidence.anchors.length > 0 ||
|
|
69
|
+
naturalRetrieval.intents.some((intent) => intent === "configuration" || intent === "testing")))));
|
|
70
|
+
const naturalRetrievalFocused = Boolean(naturalRetrieval && naturalRetrieval.matches.length > 0 && naturalExpansionAllowed);
|
|
71
|
+
const explicitFocusProvided = explicitTargetProvided || Boolean(queryText) || naturalRetrievalFocused;
|
|
72
|
+
if (queryText.trim()) {
|
|
73
|
+
addLexicalQueryFocus(index, queryText, addFocus);
|
|
74
|
+
}
|
|
75
|
+
if (naturalExpansionAllowed && naturalRetrieval) {
|
|
76
|
+
addNaturalRetrievalFocus({
|
|
77
|
+
naturalRetrieval,
|
|
78
|
+
explicitTargetProvided,
|
|
79
|
+
explicitConfigTarget,
|
|
80
|
+
limit,
|
|
81
|
+
focus: focusState
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const indexedPaths = new Set(index.files.map((file) => file.path));
|
|
85
|
+
const unindexedChanged = changed.filter((file) => !indexedPaths.has(file));
|
|
86
|
+
const groups = groupDiffImpact(index, changedEntries, changedSymbols, unindexedChanged).slice(0, 12);
|
|
87
|
+
const { broadDirty, dirtyDrivesFocus } = addDirtyWorktreeFocus({
|
|
88
|
+
index,
|
|
89
|
+
changed,
|
|
90
|
+
changedSymbols,
|
|
91
|
+
groups,
|
|
92
|
+
indexedPaths,
|
|
93
|
+
explicitFocusProvided,
|
|
94
|
+
dirtyContextTask,
|
|
95
|
+
limit,
|
|
96
|
+
focus: focusState,
|
|
97
|
+
warnings
|
|
98
|
+
});
|
|
99
|
+
if (!dirtyContextTask) {
|
|
100
|
+
addContextPackImpactExpansion(index, impactSeeds, changeType, limit, addFocus);
|
|
101
|
+
}
|
|
102
|
+
if (requestedFiles.length > 0 || requestedSymbols.length > 0 || queryText.trim() || naturalExpansionAllowed) {
|
|
103
|
+
const testSeedPaths = uniqueSorted([...impactSeeds.keys(), ...focus.keys()]);
|
|
104
|
+
for (const test of recommendTests(index, testSeedPaths, repoRoot).slice(0, Math.max(1, Math.min(4, Math.floor(limit / 2))))) {
|
|
105
|
+
addFocus(test.path, `likely test: ${test.reason}`, test.evidenceTier === "authoritative" ? 18 : test.evidenceTier === "derived" ? 14 : 8, test.evidenceTier ?? "derived", "test_evidence");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (focus.size === 0) {
|
|
109
|
+
for (const file of index.files.slice(0, limit)) {
|
|
110
|
+
addFocus(file.path, "top-ranked fallback", 1, "fallback", "rank_fallback");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const focusEntries = [...focus.values()].sort((a, b) => tierScore(a.tier) - tierScore(b.tier) || b.rank - a.rank || a.file.path.localeCompare(b.file.path)).slice(0, limit);
|
|
114
|
+
const focusPaths = focusEntries.map((entry) => entry.file.path);
|
|
115
|
+
const contextSeedPaths = dirtyContextTask ? focusPaths : dirtyDrivesFocus ? uniqueSorted([...focusPaths, ...changed]) : focusPaths;
|
|
116
|
+
const snippetChangedSymbols = dirtyDrivesFocus ? changedSymbols : changedSymbols.filter((entry) => impactSeeds.has(entry.symbol.path));
|
|
117
|
+
const testLimit = dirtyContextTask ? Math.max(1, Math.min(2, contextSeedPaths.length)) : 12;
|
|
118
|
+
const tests = recommendTests(index, contextSeedPaths, repoRoot).slice(0, testLimit);
|
|
119
|
+
const snippetQueryText = queryText ||
|
|
120
|
+
contextInput.task ||
|
|
121
|
+
requestedSymbols.join(" ") ||
|
|
122
|
+
requestedFiles
|
|
123
|
+
.flatMap((filePath) => fileStemQueryTerms(path.posix.basename(filePath).replace(/\.[^.]+$/, "")))
|
|
124
|
+
.join(" ");
|
|
125
|
+
const snippets = includeSnippets ? await contextSnippets(repoRoot, index, focusPaths, snippetChangedSymbols, snippetQueryText, limit) : [];
|
|
126
|
+
const nextReads = focusEntries.slice(0, Math.min(8, focusEntries.length)).map((entry) => entry.file.path);
|
|
127
|
+
const dirtyScope = dirtyContextTask
|
|
128
|
+
? dirtyScopeSummary({
|
|
129
|
+
taskIntents,
|
|
130
|
+
changed,
|
|
131
|
+
worktree,
|
|
132
|
+
broadDirty,
|
|
133
|
+
focusEntries
|
|
134
|
+
})
|
|
135
|
+
: undefined;
|
|
136
|
+
const packetIntent = naturalRetrieval
|
|
137
|
+
? packetIntentConfidence(naturalRetrieval.intentConfidence, focusEntries, {
|
|
138
|
+
explicitTargetProvided,
|
|
139
|
+
dirtyAnchorAllowed: Boolean(contextInput.task && taskReferencesDirtyContext(contextInput.task) && dirtyDrivesFocus && !broadDirty)
|
|
140
|
+
})
|
|
141
|
+
: dirtyScope
|
|
142
|
+
? dirtyWorktreeIntentConfidence({
|
|
143
|
+
taskIntents,
|
|
144
|
+
dirtyScope,
|
|
145
|
+
focusEntries,
|
|
146
|
+
worktree
|
|
147
|
+
})
|
|
148
|
+
: undefined;
|
|
149
|
+
const packetDiagnostics = packetIntent ? packetIntentDiagnostics(packetIntent, naturalRetrieval?.diagnostics ?? []) : [];
|
|
150
|
+
const actionability = packetIntent
|
|
151
|
+
? actionabilityFromPacketVerdict(packetIntent.verdict)
|
|
152
|
+
: explicitTargetProvided
|
|
153
|
+
? "inspect_first"
|
|
154
|
+
: qualityLikeFallbackActionability(focusEntries);
|
|
155
|
+
const baseline = explicitQuery ? await baselineSearchSummary(repoRoot, queryText) : undefined;
|
|
156
|
+
const gaps = [...indexGaps(index, freshness, unindexedChanged), ...(worktree ? worktreeStateGaps(worktree) : [])];
|
|
157
|
+
const quality = assessContextQuality({
|
|
158
|
+
freshness,
|
|
159
|
+
gaps,
|
|
160
|
+
tiers: focusTierCounts(focusEntries),
|
|
161
|
+
selectedCount: focusEntries.length,
|
|
162
|
+
fanoutCount: dirtyDrivesFocus ? changed.length : focusEntries.length,
|
|
163
|
+
testCount: tests.length,
|
|
164
|
+
queryBroad: naturalRetrieval?.broad,
|
|
165
|
+
centralFileCount: focusEntries.filter((entry) => entry.file.rank >= (index.files[Math.min(index.files.length - 1, 5)]?.rank ?? Number.POSITIVE_INFINITY)).length,
|
|
166
|
+
packetVerdict: explicitTargetProvided ? undefined : packetIntent?.verdict,
|
|
167
|
+
discardedAnchorCount: explicitTargetProvided ? 0 : packetIntent?.discardedAnchorCount
|
|
168
|
+
});
|
|
169
|
+
const suppressActionGuidance = quality.level === "low";
|
|
170
|
+
const displayedTests = suppressActionGuidance ? [] : tests;
|
|
171
|
+
const recipes = suppressActionGuidance ? [] : verificationRecipes(index, contextSeedPaths, changeType).slice(0, 8);
|
|
172
|
+
const verificationCommands = suppressActionGuidance ? [] : verificationCommandsForContext(index, repoRoot, contextSeedPaths, displayedTests, 16);
|
|
173
|
+
const verificationCoverage = suppressActionGuidance ? [] : coverageForDisplay(index, verificationCommands, repoRoot);
|
|
174
|
+
const commandPlan = suppressActionGuidance ? [] : verificationCommandPlan(verificationCoverage);
|
|
175
|
+
const value = valueEstimate("context_pack", {
|
|
176
|
+
rawFileCount: baseline?.lines,
|
|
177
|
+
codexaFileCount: focusEntries.length,
|
|
178
|
+
exactTargetCount: requestedFiles.length + requestedSymbols.length,
|
|
179
|
+
testCount: displayedTests.length,
|
|
180
|
+
parserErrors: index.parserErrors.length,
|
|
181
|
+
affectedCount: changed.length,
|
|
182
|
+
quality
|
|
183
|
+
});
|
|
184
|
+
const lspAssist = options.lsp || process.env.CODEXA_LSP === "1"
|
|
185
|
+
? await lspAssistForFiles(repoRoot, focusEntries.map((entry) => entry.file), lspOptionsFromQueryOptions(options))
|
|
186
|
+
: [];
|
|
187
|
+
const sessionMemory = await sessionMemoryPreview({
|
|
188
|
+
repoRoot,
|
|
189
|
+
freshness,
|
|
190
|
+
files: focusPaths,
|
|
191
|
+
symbols: requestedSymbols,
|
|
192
|
+
topics: contextInput.task ? [contextInput.task] : explicitQuery ? [explicitQuery] : [],
|
|
193
|
+
limit: 6
|
|
194
|
+
});
|
|
195
|
+
const workspaceGuidance = await workspaceGuidancePreview({
|
|
196
|
+
repoRoot,
|
|
197
|
+
task: contextInput.task,
|
|
198
|
+
query: explicitQuery,
|
|
199
|
+
files: uniqueSorted([...requestedFiles, ...focusPaths]).slice(0, 24),
|
|
200
|
+
symbols: requestedSymbols,
|
|
201
|
+
limit: 6
|
|
202
|
+
});
|
|
203
|
+
const contextSources = summarizeContextSources(focusEntries);
|
|
204
|
+
const dirtyScopeChangePlan = dirtyScope?.mode === "edit" && dirtyScope.canPlan && packetIntent?.verdict === "edit-ready";
|
|
205
|
+
const changePlanInputs = dirtyScopeChangePlan
|
|
206
|
+
? { task: contextInput.task, diff: true, changeType, saveSnapshot: true }
|
|
207
|
+
: { task: contextInput.task, files: focusPaths.slice(0, 8), changeType, saveSnapshot: true };
|
|
208
|
+
const nextTools = [
|
|
209
|
+
packetIntent?.verdict === "needs-target" || packetIntent?.verdict === "orientation-only"
|
|
210
|
+
? nextTool(packetIntent.recommendedNextTool, "context packet needs a narrower edit target", { task: contextInput.task ?? explicitQuery })
|
|
211
|
+
: undefined,
|
|
212
|
+
focusPaths.length > 0
|
|
213
|
+
? nextTool("change_plan", dirtyScopeChangePlan ? "save the full dirty-worktree edit plan and planned verification before editing" : "save the focused edit plan and planned verification before editing", changePlanInputs, true, [".codex/cache/codexa-task-snapshots"])
|
|
214
|
+
: undefined,
|
|
215
|
+
displayedTests.length > 0 ? nextTool("test_plan", "inspect targeted verification for the focused files", { files: focusPaths.slice(0, 8) }) : undefined
|
|
216
|
+
].filter((tool) => Boolean(tool));
|
|
217
|
+
const text = [
|
|
218
|
+
freshnessBanner(freshness, refresh),
|
|
219
|
+
formatContextQuality(quality),
|
|
220
|
+
formatValueEstimate(value),
|
|
221
|
+
"Codexa context pack",
|
|
222
|
+
contextInput.task ? `Task: ${contextInput.task}` : undefined,
|
|
223
|
+
packetIntent ? `Packet verdict: ${packetIntent.verdict}; edit-ready ${packetIntent.editReady ? "yes" : "no"}; confidence ${Math.round(packetIntent.confidence * 100)}%` : undefined,
|
|
224
|
+
`Actionability: ${actionability}`,
|
|
225
|
+
packetIntent ? `Intent mode: ${packetIntent.mode}; primary ${packetIntent.intent}; anchors ${packetIntent.anchors.slice(0, 4).join(", ") || "none"}` : undefined,
|
|
226
|
+
packetIntent ? `Recommended next MCP call: ${packetIntent.recommendedNextTool}` : undefined,
|
|
227
|
+
packetDiagnostics.length ? `Retrieval diagnostics: ${packetDiagnostics.join("; ")}` : undefined,
|
|
228
|
+
`Change type: ${changeType}`,
|
|
229
|
+
`Budget: ${tokenBudget} tokens approx; focus files: ${focusEntries.length}; changed files: ${changed.length}`,
|
|
230
|
+
contextSources.length > 0 ? `Context sources: ${formatContextSources(contextSources)}` : undefined,
|
|
231
|
+
baseline ? `Baseline search: ${baseline.command} returned ${baseline.lines} non-empty lines; Codexa selected ${focusEntries.length} focus files.` : undefined,
|
|
232
|
+
warnings.length + session.warnings.length > 0
|
|
233
|
+
? `Warnings: ${uniqueSorted([...session.warnings, ...warnings]).join("; ")}`
|
|
234
|
+
: undefined,
|
|
235
|
+
"",
|
|
236
|
+
"Read first:",
|
|
237
|
+
...focusEntries.map((entry) => `- ${entry.file.path}: ${entry.tier}; rank ${entry.file.rank.toFixed(2)}, risk ${entry.file.riskScore.toFixed(1)}; ${formatReasons(entry.reasons)}`),
|
|
238
|
+
groups.length > 0 ? "" : undefined,
|
|
239
|
+
groups.length > 0 ? "Change groups:" : undefined,
|
|
240
|
+
...(groups.length > 0 ? formatDiffGroups(groups) : []),
|
|
241
|
+
"",
|
|
242
|
+
"Likely tests:",
|
|
243
|
+
...(suppressActionGuidance ? ["- deferred until Codexa has an explicit file, symbol, or higher-confidence packet."] : formatTestRecommendations(displayedTests)),
|
|
244
|
+
"",
|
|
245
|
+
"Known gaps:",
|
|
246
|
+
...formatGaps(gaps),
|
|
247
|
+
...(worktree ? worktreeStateText(worktree) : []),
|
|
248
|
+
lspAssist.length > 0 ? "" : undefined,
|
|
249
|
+
lspAssist.length > 0 ? "LSP assist:" : undefined,
|
|
250
|
+
...lspAssist.flatMap((assist) => [
|
|
251
|
+
`- ${assist.file ?? "unknown"}: ${assist.status}${assist.server ? ` via ${assist.server}` : ""}; symbols ${assist.documentSymbols.length}; diagnostics ${assist.diagnostics.length}`,
|
|
252
|
+
...assist.warnings.slice(0, 3).map((warning) => ` warning: ${warning}`)
|
|
253
|
+
]),
|
|
254
|
+
sessionMemory.lines.length > 0 ? "" : undefined,
|
|
255
|
+
sessionMemory.lines.length > 0 ? "Session memory:" : undefined,
|
|
256
|
+
...sessionMemory.lines,
|
|
257
|
+
workspaceGuidance.lines.length > 0 ? "" : undefined,
|
|
258
|
+
workspaceGuidance.lines.length > 0 ? "Workspace guidance:" : undefined,
|
|
259
|
+
...workspaceGuidance.lines,
|
|
260
|
+
suppressActionGuidance ? undefined : "",
|
|
261
|
+
suppressActionGuidance ? undefined : "If run, these commands would cover:",
|
|
262
|
+
...(suppressActionGuidance ? [] : formatVerificationCoverage(verificationCoverage)),
|
|
263
|
+
suppressActionGuidance ? undefined : "",
|
|
264
|
+
suppressActionGuidance ? undefined : "Verification recipes:",
|
|
265
|
+
...(suppressActionGuidance ? [] : formatRecipes(recipes)),
|
|
266
|
+
snippets.length > 0 ? "" : undefined,
|
|
267
|
+
snippets.length > 0 ? "Evidence snippets:" : undefined,
|
|
268
|
+
...snippets,
|
|
269
|
+
"",
|
|
270
|
+
"Next inspection order:",
|
|
271
|
+
...nextReads.map((file) => `- ${file}`)
|
|
272
|
+
]
|
|
273
|
+
.filter((line) => line !== undefined)
|
|
274
|
+
.join("\n");
|
|
275
|
+
return {
|
|
276
|
+
freshness,
|
|
277
|
+
refresh,
|
|
278
|
+
text: limitTextToTokens(text, tokenBudget),
|
|
279
|
+
data: {
|
|
280
|
+
mode: "context_pack",
|
|
281
|
+
task: contextInput.task,
|
|
282
|
+
changeType,
|
|
283
|
+
tokenBudget,
|
|
284
|
+
focusFiles: focusEntries.map((entry) => ({ file: compactFileFact(entry.file), reasons: uniqueSorted(entry.reasons).slice(0, 12), rank: entry.rank, tier: entry.tier })),
|
|
285
|
+
changedFiles: changed.slice(0, 120),
|
|
286
|
+
changedEntries: changedEntries.slice(0, 120),
|
|
287
|
+
changedSymbols: changedSymbols.slice(0, 80).map(compactChangedSymbol),
|
|
288
|
+
unindexedChanged: unindexedChanged.slice(0, 80),
|
|
289
|
+
worktree: worktree ? compactWorktreeState(worktree) : undefined,
|
|
290
|
+
worktreeDegradationReasons: worktree?.degradedReasons ?? [],
|
|
291
|
+
dirtyScope,
|
|
292
|
+
groups: groups.slice(0, 20).map(compactDiffGroup),
|
|
293
|
+
tests: displayedTests.slice(0, 30),
|
|
294
|
+
snippets,
|
|
295
|
+
contextSources,
|
|
296
|
+
warnings: uniqueSorted([...session.warnings, ...warnings]),
|
|
297
|
+
nextReads,
|
|
298
|
+
baseline,
|
|
299
|
+
retrieval: naturalRetrieval ? compactRetrievalResult(naturalRetrieval) : undefined,
|
|
300
|
+
lspAssist,
|
|
301
|
+
sessionMemory: sessionMemory.data,
|
|
302
|
+
workspaceGuidance: workspaceGuidance.data,
|
|
303
|
+
intentConfidence: packetIntent,
|
|
304
|
+
packetVerdict: packetIntent?.verdict,
|
|
305
|
+
actionability,
|
|
306
|
+
diagnostics: packetDiagnostics,
|
|
307
|
+
actionGuidanceSuppressed: suppressActionGuidance,
|
|
308
|
+
recipes,
|
|
309
|
+
verificationCommands,
|
|
310
|
+
verificationCoverage,
|
|
311
|
+
verificationCommandPlan: commandPlan,
|
|
312
|
+
value,
|
|
313
|
+
quality,
|
|
314
|
+
gaps,
|
|
315
|
+
nextTools,
|
|
316
|
+
systemMessage: nextTools[0]?.reason,
|
|
317
|
+
session: { commandBudgetMs: session.commandBudgetMs, maxResultBytes: session.maxResultBytes, maxResults: session.maxResults, provenance: session.provenance }
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function createContextFocusState(index, warnings) {
|
|
322
|
+
const focus = new Map();
|
|
323
|
+
const impactSeeds = new Map();
|
|
324
|
+
const addFocus = (filePath, reason, rank = 1, tier = "derived", source) => {
|
|
325
|
+
const file = findFile(index, filePath);
|
|
326
|
+
if (!file) {
|
|
327
|
+
warnings.push(`unindexed file ${filePath}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const existing = focus.get(file.path) ?? { file, reasons: new Set(), rank: file.rank, tier, provenance: [] };
|
|
331
|
+
existing.reasons.add(reason);
|
|
332
|
+
const sources = source ? [source] : contextSourcesForReason(reason);
|
|
333
|
+
for (const provenanceSource of sources.length > 0 ? sources : ["rank_fallback"]) {
|
|
334
|
+
const provenanceTier = provenanceSource === "lexical_query" && tier === "authoritative" ? "derived" : tier;
|
|
335
|
+
if (!existing.provenance.some((entry) => entry.source === provenanceSource && entry.reason === reason && entry.tier === provenanceTier)) {
|
|
336
|
+
existing.provenance.push({ source: provenanceSource, reason, tier: provenanceTier });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
existing.rank += rank;
|
|
340
|
+
existing.tier = betterTier(existing.tier, tier);
|
|
341
|
+
focus.set(file.path, existing);
|
|
342
|
+
};
|
|
343
|
+
return { focus, impactSeeds, addFocus };
|
|
344
|
+
}
|
|
345
|
+
function addExplicitTargetsToContextFocus(input) {
|
|
346
|
+
const requestedResolvedPaths = [];
|
|
347
|
+
for (const requested of input.requestedFiles) {
|
|
348
|
+
const resolved = resolveFileTarget(input.index, requested, input.repoRoot);
|
|
349
|
+
if (resolved.ambiguous.length > 0) {
|
|
350
|
+
input.warnings.push(`ambiguous file ${requested}`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (resolved.file) {
|
|
354
|
+
requestedResolvedPaths.push(resolved.file.path);
|
|
355
|
+
input.focus.addFocus(resolved.file.path, "requested file", 100, "authoritative", "explicit_target");
|
|
356
|
+
input.focus.impactSeeds.set(resolved.file.path, "requested file");
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
input.warnings.push(`missing file ${requested}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const requested of input.requestedSymbols) {
|
|
363
|
+
const resolved = resolveSymbolTarget(input.index, requested);
|
|
364
|
+
if (resolved.ambiguous.length > 0) {
|
|
365
|
+
input.warnings.push(`ambiguous symbol ${requested}`);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (resolved.symbol) {
|
|
369
|
+
input.focus.addFocus(resolved.symbol.path, `requested symbol ${resolved.symbol.qualifiedName}`, 100, "authoritative", "explicit_target");
|
|
370
|
+
input.focus.impactSeeds.set(resolved.symbol.path, `requested symbol ${resolved.symbol.qualifiedName}`);
|
|
371
|
+
for (const usage of input.index.usageSites.filter((site) => site.targetSymbolId === resolved.symbol.id).slice(0, 12)) {
|
|
372
|
+
input.focus.addFocus(usage.path, `uses ${resolved.symbol.qualifiedName}`, 4, confidenceTier(usage.confidence), "graph_impact");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
input.warnings.push(`missing symbol ${requested}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return requestedResolvedPaths;
|
|
380
|
+
}
|
|
381
|
+
function addLexicalQueryFocus(index, queryText, addFocus) {
|
|
382
|
+
for (const file of index.files) {
|
|
383
|
+
const score = matchScore(queryText, file.path);
|
|
384
|
+
if (score > 0) {
|
|
385
|
+
addFocus(file.path, `path ${matchReason(score)} ${queryText}`, score, score >= 9 ? "derived" : "heuristic", "lexical_query");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const symbol of index.symbols) {
|
|
389
|
+
const score = Math.max(matchScore(queryText, symbol.name), matchScore(queryText, symbol.qualifiedName), matchScore(queryText, symbol.path));
|
|
390
|
+
if (score > 0) {
|
|
391
|
+
addFocus(symbol.path, `symbol ${matchReason(score)} ${symbol.qualifiedName}`, score + 1, score >= 9 ? confidenceTier(symbol.confidence) : "heuristic", "lexical_query");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
for (const usage of index.usageSites) {
|
|
395
|
+
const score = Math.max(matchScore(queryText, usage.name), matchScore(queryText, usage.text), matchScore(queryText, usage.path));
|
|
396
|
+
if (score > 0) {
|
|
397
|
+
addFocus(usage.path, `usage ${matchReason(score)} ${usage.name}`, score, confidenceTier(usage.confidence), "lexical_query");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function addNaturalRetrievalFocus(input) {
|
|
402
|
+
const scoreScale = input.explicitTargetProvided ? 0.6 : 1;
|
|
403
|
+
for (const match of input.naturalRetrieval.matches.slice(0, input.limit * 2)) {
|
|
404
|
+
if (input.explicitTargetProvided && !shouldAddExplicitNaturalMatch(match, input.explicitConfigTarget)) {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const testLaneBacked = Boolean((match.lanes.test ?? 0) > 0 && input.naturalRetrieval.intents.includes("testing") && !input.naturalRetrieval.intents.includes("implementation"));
|
|
408
|
+
const laneBacked = Boolean((match.lanes.exact ?? 0) > 0 || (match.lanes.symbol ?? 0) > 0 || (match.lanes.workflow ?? 0) > 0 || testLaneBacked);
|
|
409
|
+
input.focus.addFocus(match.file.path, `natural task retrieval ${match.matchedTerms.slice(0, 6).join(", ") || "intent"}: ${formatReasons(match.reasons, 3)}`, Math.max(2, match.score * scoreScale), laneBacked ? "derived" : "heuristic", "natural_retrieval");
|
|
410
|
+
if (laneBacked || match.score >= 4) {
|
|
411
|
+
input.focus.impactSeeds.set(match.file.path, "natural task retrieval");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!input.explicitTargetProvided) {
|
|
415
|
+
for (const workflow of input.naturalRetrieval.workflows.slice(0, 4)) {
|
|
416
|
+
for (const filePath of workflow.relatedFiles.slice(0, 6)) {
|
|
417
|
+
input.focus.addFocus(filePath, `workflow ${workflow.title}`, Math.max(2, workflow.rank / 4), confidenceTier(workflow.confidence), "workflow_trace");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function addDirtyWorktreeFocus(input) {
|
|
423
|
+
const broadDirty = input.changed.length > Math.max(8, input.limit);
|
|
424
|
+
const dirtyDrivesFocus = input.changed.length > 0 && (!input.explicitFocusProvided || !broadDirty);
|
|
425
|
+
if (input.changed.length > 0 && input.explicitFocusProvided && broadDirty) {
|
|
426
|
+
input.warnings.push(`broad dirty tree (${input.changed.length} files) kept as diff context, not read-first focus`);
|
|
427
|
+
}
|
|
428
|
+
if (!dirtyDrivesFocus) {
|
|
429
|
+
return { broadDirty, dirtyDrivesFocus };
|
|
430
|
+
}
|
|
431
|
+
const dirtyRepresentativeLimit = input.dirtyContextTask ? Math.max(1, Math.min(2, Math.ceil(input.limit / 5))) : input.limit * 2;
|
|
432
|
+
const dirtyRepresentativePaths = new Set();
|
|
433
|
+
if (input.dirtyContextTask) {
|
|
434
|
+
for (const group of input.groups.slice(0, dirtyRepresentativeLimit)) {
|
|
435
|
+
const representative = representativeFileForDiffGroup(input.index, group.files);
|
|
436
|
+
if (representative) {
|
|
437
|
+
dirtyRepresentativePaths.add(representative.path);
|
|
438
|
+
input.focus.addFocus(representative.path, `change-group representative ${group.module}`, 12, group.kind === "unknown" ? "fallback" : "authoritative", "dirty_worktree");
|
|
439
|
+
input.focus.impactSeeds.set(representative.path, `change-group representative ${group.module}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (dirtyRepresentativePaths.size === 0) {
|
|
443
|
+
for (const file of input.changed.filter((candidate) => input.indexedPaths.has(candidate)).slice(0, dirtyRepresentativeLimit)) {
|
|
444
|
+
dirtyRepresentativePaths.add(file);
|
|
445
|
+
input.focus.addFocus(file, "dirty diff", 6, "authoritative", "dirty_worktree");
|
|
446
|
+
input.focus.impactSeeds.set(file, "dirty diff");
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
for (const file of input.changed.filter((candidate) => input.indexedPaths.has(candidate)).slice(0, input.limit * 2)) {
|
|
452
|
+
input.focus.addFocus(file, "dirty diff", 6, "authoritative", "dirty_worktree");
|
|
453
|
+
input.focus.impactSeeds.set(file, "dirty diff");
|
|
454
|
+
}
|
|
455
|
+
for (const entry of input.changedSymbols.slice(0, input.limit * 2)) {
|
|
456
|
+
input.focus.addFocus(entry.symbol.path, `changed symbol ${entry.symbol.qualifiedName}`, 6, "derived", "dirty_worktree");
|
|
457
|
+
input.focus.impactSeeds.set(entry.symbol.path, `changed symbol ${entry.symbol.qualifiedName}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
for (const group of input.groups) {
|
|
461
|
+
if (input.dirtyContextTask && dirtyRepresentativePaths.size >= dirtyRepresentativeLimit) {
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
const representative = representativeFileForDiffGroup(input.index, group.files);
|
|
465
|
+
if (representative) {
|
|
466
|
+
if (input.dirtyContextTask) {
|
|
467
|
+
dirtyRepresentativePaths.add(representative.path);
|
|
468
|
+
}
|
|
469
|
+
input.focus.addFocus(representative.path, `change-group representative ${group.module}`, 5, group.kind === "unknown" ? "fallback" : "derived", "dirty_worktree");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return { broadDirty, dirtyDrivesFocus };
|
|
473
|
+
}
|
|
474
|
+
function summarizeContextSources(focusEntries) {
|
|
475
|
+
const summaries = new Map();
|
|
476
|
+
const add = (source, filePath, tier, reason) => {
|
|
477
|
+
const summary = summaries.get(source) ?? {
|
|
478
|
+
fileTiers: new Map(),
|
|
479
|
+
reasons: new Set()
|
|
480
|
+
};
|
|
481
|
+
summary.fileTiers.set(filePath, betterTier(summary.fileTiers.get(filePath) ?? "fallback", tier));
|
|
482
|
+
summary.reasons.add(reason);
|
|
483
|
+
summaries.set(source, summary);
|
|
484
|
+
};
|
|
485
|
+
for (const entry of focusEntries) {
|
|
486
|
+
if (entry.provenance.length === 0) {
|
|
487
|
+
add("rank_fallback", entry.file.path, "fallback", "ranked context selection");
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
for (const provenance of entry.provenance) {
|
|
491
|
+
add(provenance.source, entry.file.path, provenance.tier, provenance.reason);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const priority = ["explicit_target", "dirty_worktree", "natural_retrieval", "graph_impact", "workflow_trace", "test_evidence", "lexical_query", "rank_fallback"];
|
|
495
|
+
return [...summaries.entries()]
|
|
496
|
+
.map(([source, summary]) => {
|
|
497
|
+
const evidenceTierCounts = { authoritative: 0, derived: 0, heuristic: 0, fallback: 0 };
|
|
498
|
+
for (const tier of summary.fileTiers.values()) {
|
|
499
|
+
evidenceTierCounts[tier] += 1;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
source,
|
|
503
|
+
fileCount: summary.fileTiers.size,
|
|
504
|
+
evidenceTierCounts,
|
|
505
|
+
sampleFiles: [...summary.fileTiers.keys()].sort().slice(0, 6),
|
|
506
|
+
sampleReasons: [...summary.reasons].sort().slice(0, 6)
|
|
507
|
+
};
|
|
508
|
+
})
|
|
509
|
+
.sort((a, b) => priority.indexOf(a.source) - priority.indexOf(b.source) || b.fileCount - a.fileCount || a.source.localeCompare(b.source));
|
|
510
|
+
}
|
|
511
|
+
function contextSourcesForReason(reason) {
|
|
512
|
+
const lower = reason.toLowerCase();
|
|
513
|
+
if (lower.startsWith("likely test")) {
|
|
514
|
+
return ["test_evidence"];
|
|
515
|
+
}
|
|
516
|
+
const sources = [];
|
|
517
|
+
if (lower === "requested file" || lower.startsWith("requested symbol ")) {
|
|
518
|
+
sources.push("explicit_target");
|
|
519
|
+
}
|
|
520
|
+
if (lower.includes("dirty diff") || lower.includes("changed symbol") || lower.includes("change-group representative")) {
|
|
521
|
+
sources.push("dirty_worktree");
|
|
522
|
+
}
|
|
523
|
+
if (lower.includes("natural task retrieval")) {
|
|
524
|
+
sources.push("natural_retrieval");
|
|
525
|
+
}
|
|
526
|
+
if (lower.includes("impact from") || lower.includes("imports ") || lower.includes("uses ") || lower.includes("call ") || lower.includes("reference ")) {
|
|
527
|
+
sources.push("graph_impact");
|
|
528
|
+
}
|
|
529
|
+
if (lower.startsWith("workflow ") || lower.includes("workflow entry") || lower.includes("workflow test candidate")) {
|
|
530
|
+
sources.push("workflow_trace");
|
|
531
|
+
}
|
|
532
|
+
if (lower.includes("likely test") || lower.includes("tests ")) {
|
|
533
|
+
sources.push("test_evidence");
|
|
534
|
+
}
|
|
535
|
+
if (lower.startsWith("path ") || lower.startsWith("symbol ") || lower.startsWith("usage ")) {
|
|
536
|
+
sources.push("lexical_query");
|
|
537
|
+
}
|
|
538
|
+
if (lower.includes("top-ranked fallback") || lower.includes("ranked project entry point")) {
|
|
539
|
+
sources.push("rank_fallback");
|
|
540
|
+
}
|
|
541
|
+
return sources;
|
|
542
|
+
}
|
|
543
|
+
function formatContextSources(sources) {
|
|
544
|
+
return sources
|
|
545
|
+
.slice(0, 8)
|
|
546
|
+
.map((source) => {
|
|
547
|
+
const tiers = ["authoritative", "derived", "heuristic", "fallback"]
|
|
548
|
+
.map((tier) => (source.evidenceTierCounts[tier] > 0 ? `${source.evidenceTierCounts[tier]} ${tier}` : undefined))
|
|
549
|
+
.filter((entry) => Boolean(entry))
|
|
550
|
+
.join(", ");
|
|
551
|
+
return `${source.source} ${source.fileCount} file${source.fileCount === 1 ? "" : "s"}${tiers ? ` (${tiers})` : ""}`;
|
|
552
|
+
})
|
|
553
|
+
.join("; ");
|
|
554
|
+
}
|
|
555
|
+
function representativeFileForDiffGroup(index, files) {
|
|
556
|
+
return files
|
|
557
|
+
.map((filePath) => findFile(index, filePath))
|
|
558
|
+
.filter((file) => Boolean(file))
|
|
559
|
+
.sort((a, b) => b.riskScore - a.riskScore || b.rank - a.rank || a.path.localeCompare(b.path))[0];
|
|
560
|
+
}
|
|
561
|
+
function shouldRunNaturalRetrieval(explicitTargetProvided, explicitConfigTarget, taskIntents) {
|
|
562
|
+
if (!explicitTargetProvided) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
if (!explicitConfigTarget) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
return taskIntents.some((intent) => intent === "configuration" || intent === "testing" || intent === "implementation");
|
|
569
|
+
}
|
|
570
|
+
export async function taskBriefQuery(input, contextInput = {}, options = {}) {
|
|
571
|
+
const result = await contextPackQuery(input, {
|
|
572
|
+
...contextInput,
|
|
573
|
+
tokenBudget: contextInput.tokenBudget ?? 3000,
|
|
574
|
+
limit: contextInput.limit ?? 10,
|
|
575
|
+
includeSnippets: contextInput.includeSnippets ?? true
|
|
576
|
+
}, options);
|
|
577
|
+
return {
|
|
578
|
+
...result,
|
|
579
|
+
text: result.text.replace("Codexa context pack", "Codexa task brief"),
|
|
580
|
+
data: {
|
|
581
|
+
...(result.data && typeof result.data === "object" ? result.data : {}),
|
|
582
|
+
mode: "task_brief"
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
export async function focusBriefQuery(input, focusInput = {}, options = {}) {
|
|
587
|
+
const session = await ensureQuerySession(input, options);
|
|
588
|
+
const { index, freshness, refresh, repoRoot } = session;
|
|
589
|
+
const task = focusInput.task?.trim() || "Session start: identify project focus, current changes, workflows, and next Codexa call";
|
|
590
|
+
const limit = clampInt(focusInput.limit ?? 10, 3, session.maxResults);
|
|
591
|
+
const tokenBudget = clampInt(focusInput.tokenBudget ?? 2400, 600, 8000);
|
|
592
|
+
const retrieval = await retrieveForTask(index, task, limit, semanticOptionsFromQueryOptions(repoRoot, options));
|
|
593
|
+
const includeDiff = focusInput.diff ?? true;
|
|
594
|
+
const worktree = includeDiff ? await getWorktreeState(session) : undefined;
|
|
595
|
+
const changedEntries = worktree?.entries ?? [];
|
|
596
|
+
const changed = worktree?.files ?? [];
|
|
597
|
+
const indexedPaths = new Set(index.files.map((file) => file.path));
|
|
598
|
+
const unindexedChanged = changed.filter((file) => !indexedPaths.has(file));
|
|
599
|
+
const groups = includeDiff ? groupDiffImpact(index, changedEntries, worktree?.symbols ?? [], unindexedChanged).slice(0, 8) : [];
|
|
600
|
+
const exactMatches = exactFocusFileMatches(index, task).map((file) => ({
|
|
601
|
+
file,
|
|
602
|
+
score: file.rank + 100,
|
|
603
|
+
reasons: ["exact path in task"],
|
|
604
|
+
matchedTerms: [file.path],
|
|
605
|
+
tier: "derived"
|
|
606
|
+
}));
|
|
607
|
+
const workflowMatches = workflowFocusEntries(index, retrieval.workflows, task, limit);
|
|
608
|
+
const workflowTestMatches = workflowMatches.length > 0 && taskAsksForTests(task)
|
|
609
|
+
? recommendTests(index, uniqueSorted(workflowMatches.map((entry) => entry.file.path)), repoRoot)
|
|
610
|
+
.slice(0, 4)
|
|
611
|
+
.map((test) => {
|
|
612
|
+
const file = findFile(index, test.path);
|
|
613
|
+
return file
|
|
614
|
+
? {
|
|
615
|
+
file,
|
|
616
|
+
score: test.evidenceTier === "authoritative" ? 70 : test.evidenceTier === "derived" ? 62 : 48,
|
|
617
|
+
reasons: [`workflow test candidate: ${test.reason}`],
|
|
618
|
+
matchedTerms: [],
|
|
619
|
+
tier: test.evidenceTier ?? "derived"
|
|
620
|
+
}
|
|
621
|
+
: undefined;
|
|
622
|
+
})
|
|
623
|
+
.filter((entry) => Boolean(entry))
|
|
624
|
+
: [];
|
|
625
|
+
const retrievalMatches = retrieval.matches.map((match) => ({ ...match, tier: focusMatchTier(match.file, task, match) }));
|
|
626
|
+
const workflowModules = new Set(workflowMatches.map((entry) => moduleNameForPath(entry.file.path)));
|
|
627
|
+
const workflowScopedMatches = workflowMatches.length > 0 && retrieval.broad
|
|
628
|
+
? retrieval.matches
|
|
629
|
+
.filter((match) => workflowModules.has(moduleNameForPath(match.file.path)) || hasExactRetrievalLane(match))
|
|
630
|
+
.map((match) => ({ ...match, tier: focusMatchTier(match.file, task, match) }))
|
|
631
|
+
: retrievalMatches;
|
|
632
|
+
const selected = retrieval.matches.length > 0 || exactMatches.length > 0 || workflowMatches.length > 0
|
|
633
|
+
? uniqueFocusEntries([...exactMatches, ...workflowMatches, ...workflowTestMatches, ...workflowScopedMatches]).slice(0, limit)
|
|
634
|
+
: index.files.slice(0, limit).map((file) => ({ file, score: file.rank, reasons: ["ranked project entry point fallback"], matchedTerms: [], tier: "fallback" }));
|
|
635
|
+
const focusFiles = uniqueFiles(selected.map((entry) => entry.file)).slice(0, limit);
|
|
636
|
+
const tiersByPath = new Map(selected.map((entry) => [entry.file.path, entry.tier]));
|
|
637
|
+
const tests = recommendTests(index, focusFiles.map((file) => file.path), repoRoot).slice(0, 10);
|
|
638
|
+
const nextCall = recommendNextCodexaCall(retrieval.intents, retrieval.workflows, changed.length, task);
|
|
639
|
+
const actionability = actionabilityFromPacketVerdict(retrieval.intentConfidence.verdict);
|
|
640
|
+
const recommendedNextCall = retrieval.intentConfidence.recommendedNextTool === nextCall.tool
|
|
641
|
+
? `${nextCall.tool} - ${nextCall.reason}`
|
|
642
|
+
: `${retrieval.intentConfidence.recommendedNextTool} - ${nextCall.reason}`;
|
|
643
|
+
const gaps = [...indexGaps(index, freshness, unindexedChanged), ...(worktree ? worktreeStateGaps(worktree) : [])];
|
|
644
|
+
const quality = assessContextQuality({
|
|
645
|
+
freshness,
|
|
646
|
+
gaps,
|
|
647
|
+
tiers: {
|
|
648
|
+
authoritative: 0,
|
|
649
|
+
derived: focusFiles.filter((file) => tiersByPath.get(file.path) === "derived").length,
|
|
650
|
+
heuristic: focusFiles.filter((file) => tiersByPath.get(file.path) === "heuristic").length +
|
|
651
|
+
retrieval.workflows.filter((workflow) => workflow.confidence === "heuristic").length,
|
|
652
|
+
fallback: focusFiles.filter((file) => tiersByPath.get(file.path) === "fallback").length
|
|
653
|
+
},
|
|
654
|
+
selectedCount: focusFiles.length,
|
|
655
|
+
testCount: tests.length,
|
|
656
|
+
queryBroad: retrieval.broad,
|
|
657
|
+
centralFileCount: focusFiles.filter((file) => file.rank >= index.files[Math.min(index.files.length - 1, 5)]?.rank).length,
|
|
658
|
+
packetVerdict: retrieval.intentConfidence.verdict,
|
|
659
|
+
discardedAnchorCount: retrieval.intentConfidence.discardedAnchorCount
|
|
660
|
+
});
|
|
661
|
+
const sessionMemory = await sessionMemoryPreview({
|
|
662
|
+
repoRoot,
|
|
663
|
+
freshness,
|
|
664
|
+
files: focusFiles.map((file) => file.path),
|
|
665
|
+
topics: [task],
|
|
666
|
+
limit: 6
|
|
667
|
+
});
|
|
668
|
+
const workspaceGuidance = await workspaceGuidancePreview({
|
|
669
|
+
repoRoot,
|
|
670
|
+
task,
|
|
671
|
+
files: focusFiles.map((file) => file.path),
|
|
672
|
+
limit: 6
|
|
673
|
+
});
|
|
674
|
+
const text = [
|
|
675
|
+
freshnessBanner(freshness, refresh),
|
|
676
|
+
formatContextQuality(quality),
|
|
677
|
+
"Codexa focus brief",
|
|
678
|
+
`Task: ${task}`,
|
|
679
|
+
`Intent: ${retrieval.intents.join(", ")}`,
|
|
680
|
+
`Packet verdict: ${retrieval.intentConfidence.verdict}; edit-ready ${retrieval.intentConfidence.editReady ? "yes" : "no"}; confidence ${Math.round(retrieval.intentConfidence.confidence * 100)}%`,
|
|
681
|
+
`Actionability: ${actionability}`,
|
|
682
|
+
`Intent mode: ${retrieval.intentConfidence.mode}; primary ${retrieval.intentConfidence.intent}; anchors ${retrieval.intentConfidence.anchors.slice(0, 4).join(", ") || "none"}`,
|
|
683
|
+
retrieval.diagnostics.length > 0 ? `Retrieval diagnostics: ${retrieval.diagnostics.join("; ")}` : undefined,
|
|
684
|
+
`Recommended next MCP call: ${recommendedNextCall}`,
|
|
685
|
+
nextCall.arguments ? `Suggested arguments: ${JSON.stringify(nextCall.arguments)}` : undefined,
|
|
686
|
+
"",
|
|
687
|
+
"Likely subsystems:",
|
|
688
|
+
...(retrieval.modules.length > 0
|
|
689
|
+
? retrieval.modules.map((module) => `- ${module.name}: score ${module.score.toFixed(2)}; files ${module.files.slice(0, 5).join(", ")}; ${module.reasons.join("; ") || "task intent match"}`)
|
|
690
|
+
: index.modules.slice(0, 5).map((module) => `- ${module.name}: rank ${module.rank.toFixed(2)}; ${module.summary}`)),
|
|
691
|
+
"",
|
|
692
|
+
"Read first:",
|
|
693
|
+
...focusFiles.map((file) => {
|
|
694
|
+
const match = selected.find((entry) => entry.file.path === file.path);
|
|
695
|
+
const reasons = match?.reasons.length ? match.reasons.join("; ") : "ranked project entry point";
|
|
696
|
+
return `- ${file.path}: score ${(match?.score ?? file.rank).toFixed(2)}, rank ${file.rank.toFixed(2)}, risk ${file.riskScore.toFixed(1)}; ${reasons}`;
|
|
697
|
+
}),
|
|
698
|
+
retrieval.workflows.length > 0 ? "" : undefined,
|
|
699
|
+
retrieval.workflows.length > 0 ? "Likely workflows:" : undefined,
|
|
700
|
+
...retrieval.workflows.slice(0, 6).map(formatWorkflowSummary),
|
|
701
|
+
groups.length > 0 ? "" : undefined,
|
|
702
|
+
groups.length > 0 ? "Current change groups:" : undefined,
|
|
703
|
+
...(groups.length > 0 ? formatDiffGroups(groups) : []),
|
|
704
|
+
sessionMemory.lines.length > 0 ? "" : undefined,
|
|
705
|
+
sessionMemory.lines.length > 0 ? "Session memory:" : undefined,
|
|
706
|
+
...sessionMemory.lines,
|
|
707
|
+
workspaceGuidance.lines.length > 0 ? "" : undefined,
|
|
708
|
+
workspaceGuidance.lines.length > 0 ? "Workspace guidance:" : undefined,
|
|
709
|
+
...workspaceGuidance.lines,
|
|
710
|
+
"",
|
|
711
|
+
"Likely tests:",
|
|
712
|
+
...formatTestRecommendations(tests),
|
|
713
|
+
"",
|
|
714
|
+
"Known gaps:",
|
|
715
|
+
...formatGaps(gaps),
|
|
716
|
+
...(worktree ? worktreeStateText(worktree) : [])
|
|
717
|
+
]
|
|
718
|
+
.filter((line) => line !== undefined)
|
|
719
|
+
.join("\n");
|
|
720
|
+
return {
|
|
721
|
+
freshness,
|
|
722
|
+
refresh,
|
|
723
|
+
text: limitTextToTokens(text, tokenBudget),
|
|
724
|
+
data: {
|
|
725
|
+
mode: "focus_brief",
|
|
726
|
+
task,
|
|
727
|
+
retrieval: compactRetrievalResult(retrieval),
|
|
728
|
+
intentConfidence: retrieval.intentConfidence,
|
|
729
|
+
packetVerdict: retrieval.intentConfidence.verdict,
|
|
730
|
+
actionability,
|
|
731
|
+
diagnostics: retrieval.diagnostics,
|
|
732
|
+
focusFiles: focusFiles.map(compactFileFact),
|
|
733
|
+
workflows: retrieval.workflows.slice(0, 12).map(compactWorkflowTrace),
|
|
734
|
+
modules: retrieval.modules.slice(0, 12).map((module) => ({ ...module, files: module.files.slice(0, 40), reasons: module.reasons.slice(0, 12) })),
|
|
735
|
+
groups: groups.slice(0, 12).map(compactDiffGroup),
|
|
736
|
+
worktree: worktree ? compactWorktreeState(worktree) : undefined,
|
|
737
|
+
worktreeDegradationReasons: worktree?.degradedReasons ?? [],
|
|
738
|
+
tests: tests.slice(0, 30),
|
|
739
|
+
nextCall,
|
|
740
|
+
sessionMemory: sessionMemory.data,
|
|
741
|
+
workspaceGuidance: workspaceGuidance.data,
|
|
742
|
+
quality,
|
|
743
|
+
gaps
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function focusMatchTier(file, task, match) {
|
|
748
|
+
const lowerTask = task.toLowerCase();
|
|
749
|
+
const lowerPath = file.path.toLowerCase();
|
|
750
|
+
const baseName = path.posix.basename(lowerPath);
|
|
751
|
+
const stem = baseName.replace(/\.[^.]+$/u, "");
|
|
752
|
+
if (lowerTask.includes(lowerPath) || lowerTask.includes(baseName) || (stem.length >= 4 && lowerTask.includes(stem))) {
|
|
753
|
+
return "derived";
|
|
754
|
+
}
|
|
755
|
+
if (match && ((match.lanes.exact ?? 0) > 0 || (match.lanes.symbol ?? 0) > 0 || (match.lanes.workflow ?? 0) > 0 || (match.lanes.dirty ?? 0) > 0)) {
|
|
756
|
+
return "derived";
|
|
757
|
+
}
|
|
758
|
+
if (match && (match.lanes.test ?? 0) > 0 && taskAsksForTests(task)) {
|
|
759
|
+
return "derived";
|
|
760
|
+
}
|
|
761
|
+
return "heuristic";
|
|
762
|
+
}
|
|
763
|
+
function workflowFocusEntries(index, workflows, task, limit) {
|
|
764
|
+
const includeTests = taskAsksForTests(task);
|
|
765
|
+
const entries = [];
|
|
766
|
+
const scopedWorkflows = preferredWorkflowsForTask(workflows, task);
|
|
767
|
+
const add = (filePath, score, reason, tier) => {
|
|
768
|
+
if (!filePath) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const file = findFile(index, filePath);
|
|
772
|
+
if (!file) {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
if ((file.test || isTestPath(file.path)) && !includeTests) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
entries.push({
|
|
779
|
+
file,
|
|
780
|
+
score,
|
|
781
|
+
reasons: [reason],
|
|
782
|
+
matchedTerms: [],
|
|
783
|
+
tier
|
|
784
|
+
});
|
|
785
|
+
};
|
|
786
|
+
for (const workflow of scopedWorkflows.slice(0, Math.max(1, Math.min(3, limit)))) {
|
|
787
|
+
const tier = confidenceTier(workflow.confidence);
|
|
788
|
+
add(workflow.entryPath, 90 + workflow.rank, `workflow entry ${workflow.title}`, tier);
|
|
789
|
+
for (const step of workflow.steps.slice(0, 24)) {
|
|
790
|
+
const stepTier = confidenceTier(step.confidence);
|
|
791
|
+
const score = step.kind === "entry"
|
|
792
|
+
? 85
|
|
793
|
+
: step.kind === "endpoint" || step.kind === "ui"
|
|
794
|
+
? 72
|
|
795
|
+
: step.kind === "store" || step.kind === "adapter" || step.kind === "manifest"
|
|
796
|
+
? 68
|
|
797
|
+
: step.kind === "test"
|
|
798
|
+
? 62
|
|
799
|
+
: step.kind === "call" || step.kind === "reference" || step.kind === "import"
|
|
800
|
+
? 58
|
|
801
|
+
: 36;
|
|
802
|
+
add(step.path, score, `workflow ${workflow.title}: ${step.kind}`, stepTier);
|
|
803
|
+
add(step.targetPath, Math.max(20, score - 8), `workflow ${workflow.title}: target ${step.kind}`, stepTier);
|
|
804
|
+
}
|
|
805
|
+
for (const filePath of workflow.relatedFiles.slice(0, 12)) {
|
|
806
|
+
add(filePath, 45, `workflow related ${workflow.title}`, tier);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return entries;
|
|
810
|
+
}
|
|
811
|
+
function preferredWorkflowsForTask(workflows, task) {
|
|
812
|
+
const lower = task.toLowerCase();
|
|
813
|
+
const preferredKinds = new Set();
|
|
814
|
+
if (/\b(route|routes|endpoint|endpoints|api|request|handler|backend)\b/u.test(lower)) {
|
|
815
|
+
preferredKinds.add("route");
|
|
816
|
+
}
|
|
817
|
+
if (/\b(job|jobs|queue|worker|task|tasks|background|celery|polling)\b/u.test(lower)) {
|
|
818
|
+
preferredKinds.add("job");
|
|
819
|
+
}
|
|
820
|
+
if (/\b(manifest|node|nodes|adapter|package|registry)\b/u.test(lower)) {
|
|
821
|
+
preferredKinds.add("manifest");
|
|
822
|
+
}
|
|
823
|
+
if (preferredKinds.size === 0) {
|
|
824
|
+
return workflows;
|
|
825
|
+
}
|
|
826
|
+
const narrowed = workflows.filter((workflow) => preferredKinds.has(workflow.workflowKind));
|
|
827
|
+
return narrowed.length > 0 ? narrowed : workflows;
|
|
828
|
+
}
|
|
829
|
+
function hasExactRetrievalLane(match) {
|
|
830
|
+
return Boolean((match.lanes.exact ?? 0) > 0);
|
|
831
|
+
}
|
|
832
|
+
function taskAsksForTests(task) {
|
|
833
|
+
return /\b(test|tests|spec|specs|pytest|vitest|coverage|verification|verify|tested)\b/iu.test(task);
|
|
834
|
+
}
|
|
835
|
+
function exactFocusFileMatches(index, task) {
|
|
836
|
+
const lowerTask = task.toLowerCase();
|
|
837
|
+
return index.files
|
|
838
|
+
.filter((file) => {
|
|
839
|
+
const lowerPath = file.path.toLowerCase();
|
|
840
|
+
const baseName = path.posix.basename(lowerPath);
|
|
841
|
+
return lowerTask.includes(lowerPath) || lowerTask.includes(baseName);
|
|
842
|
+
})
|
|
843
|
+
.sort((a, b) => b.rank - a.rank || a.path.localeCompare(b.path));
|
|
844
|
+
}
|
|
845
|
+
function isConfigExpansionPath(filePath) {
|
|
846
|
+
return (/^manifests?\//u.test(filePath) ||
|
|
847
|
+
/(^|\/)(package|manifest|node|adapter)[^/]*\.(json|ya?ml|toml)$/iu.test(filePath) ||
|
|
848
|
+
/\.(json|ya?ml|toml)$/iu.test(filePath));
|
|
849
|
+
}
|
|
850
|
+
function shouldAddExplicitNaturalMatch(match, explicitConfigTarget) {
|
|
851
|
+
if (!explicitConfigTarget) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const laneBacked = Boolean((match.lanes.exact ?? 0) > 0 || (match.lanes.symbol ?? 0) > 0 || (match.lanes.bm25 ?? 0) > 0);
|
|
855
|
+
if (!laneBacked) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
const evidenceText = `${match.file.path} ${match.reasons.join(" ")}`.toLowerCase();
|
|
859
|
+
return /\b(adapter|manifest|node|type_id|node_type)\b/u.test(evidenceText) || /(^|\/)adapters?\//u.test(match.file.path);
|
|
860
|
+
}
|
|
861
|
+
function dirtyScopeSummary(input) {
|
|
862
|
+
const mode = dirtyScopeMode(input.taskIntents);
|
|
863
|
+
const changedPlanFiles = uniqueSorted(input.changed.filter((filePath) => !isCodexaControlPath(filePath)));
|
|
864
|
+
const degraded = input.worktree?.degraded ?? false;
|
|
865
|
+
const canPlan = changedPlanFiles.length > 0 && !degraded;
|
|
866
|
+
const reason = degraded
|
|
867
|
+
? `worktree state unavailable: ${input.worktree?.degradedReasons.join("; ") || "unknown"}`
|
|
868
|
+
: changedPlanFiles.length === 0
|
|
869
|
+
? "no dirty files available for the requested dirty-worktree scope"
|
|
870
|
+
: mode === "edit"
|
|
871
|
+
? "current dirty worktree is the explicit edit scope"
|
|
872
|
+
: "current dirty worktree is the explicit inspection scope";
|
|
873
|
+
return {
|
|
874
|
+
requested: true,
|
|
875
|
+
mode,
|
|
876
|
+
canPlan,
|
|
877
|
+
broad: input.broadDirty,
|
|
878
|
+
changedFileCount: changedPlanFiles.length,
|
|
879
|
+
representativeCount: input.focusEntries.filter((entry) => entry.provenance.some((item) => item.source === "dirty_worktree")).length,
|
|
880
|
+
plannedEditTargets: changedPlanFiles,
|
|
881
|
+
reason
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
function dirtyScopeMode(taskIntents) {
|
|
885
|
+
return taskIntents.some((intent) => intent === "implementation" || intent === "debugging") ? "edit" : "orientation";
|
|
886
|
+
}
|
|
887
|
+
function dirtyWorktreeIntentConfidence(input) {
|
|
888
|
+
const primaryIntent = input.taskIntents.find((intent) => intent !== "unknown") ?? "unknown";
|
|
889
|
+
const dirtyAnchors = uniqueSorted(input.focusEntries
|
|
890
|
+
.filter((entry) => entry.provenance.some((item) => item.source === "dirty_worktree"))
|
|
891
|
+
.map((entry) => entry.file.path)).slice(0, 8);
|
|
892
|
+
const missingAnchors = [];
|
|
893
|
+
if (input.dirtyScope.changedFileCount === 0) {
|
|
894
|
+
missingAnchors.push("no dirty files");
|
|
895
|
+
}
|
|
896
|
+
if (input.worktree?.degraded) {
|
|
897
|
+
missingAnchors.push("worktree state unavailable");
|
|
898
|
+
}
|
|
899
|
+
if (input.dirtyScope.mode === "edit" && dirtyAnchors.length === 0) {
|
|
900
|
+
missingAnchors.push("no selected dirty-worktree anchors");
|
|
901
|
+
}
|
|
902
|
+
const confidence = Math.max(0, Math.min(1, 0.46 +
|
|
903
|
+
Math.min(0.24, dirtyAnchors.length * 0.06) +
|
|
904
|
+
(input.dirtyScope.canPlan ? 0.18 : 0) -
|
|
905
|
+
(input.dirtyScope.broad ? 0.08 : 0) -
|
|
906
|
+
missingAnchors.length * 0.2));
|
|
907
|
+
const editReady = input.dirtyScope.mode === "edit" && input.dirtyScope.canPlan && dirtyAnchors.length > 0 && missingAnchors.length === 0 && confidence >= 0.48;
|
|
908
|
+
const verdict = editReady
|
|
909
|
+
? "edit-ready"
|
|
910
|
+
: input.dirtyScope.mode === "orientation" && dirtyAnchors.length > 0 && !input.worktree?.degraded
|
|
911
|
+
? "orientation-only"
|
|
912
|
+
: "needs-target";
|
|
913
|
+
const recommendedNextTool = verdict === "edit-ready" ? "change_plan" : verdict === "orientation-only" ? "diff_impact" : "search";
|
|
914
|
+
return {
|
|
915
|
+
mode: input.dirtyScope.mode,
|
|
916
|
+
intent: primaryIntent,
|
|
917
|
+
confidence,
|
|
918
|
+
anchors: dirtyAnchors,
|
|
919
|
+
selectedAnchorCount: dirtyAnchors.length,
|
|
920
|
+
discardedAnchorCount: Math.max(0, input.dirtyScope.changedFileCount - dirtyAnchors.length),
|
|
921
|
+
missingAnchors: uniqueSorted(missingAnchors),
|
|
922
|
+
recommendedNextTool,
|
|
923
|
+
editReady,
|
|
924
|
+
verdict,
|
|
925
|
+
reasons: uniqueSorted([
|
|
926
|
+
`mode ${input.dirtyScope.mode}`,
|
|
927
|
+
`primary intent ${primaryIntent}`,
|
|
928
|
+
"explicit dirty-worktree scope",
|
|
929
|
+
`${input.dirtyScope.changedFileCount} dirty file(s)`,
|
|
930
|
+
input.dirtyScope.broad ? "broad dirty tree represented by ranked change groups" : undefined,
|
|
931
|
+
dirtyAnchors.length > 0 ? `${dirtyAnchors.length} selected dirty-worktree anchor(s)` : "no selected dirty-worktree anchors",
|
|
932
|
+
...missingAnchors.map((anchor) => `missing ${anchor}`)
|
|
933
|
+
].filter((entry) => Boolean(entry)))
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function packetIntentConfidence(base, focusEntries, options) {
|
|
937
|
+
const focusPathSet = new Set(focusEntries.map((entry) => entry.file.path));
|
|
938
|
+
const allowTestAnchors = options.explicitTargetProvided || base.intent === "testing";
|
|
939
|
+
const selectedBaseAnchors = base.anchors.filter((anchor) => focusPathSet.has(anchor));
|
|
940
|
+
const evidenceEntries = focusEntries
|
|
941
|
+
.filter((entry) => entry.tier === "authoritative" || entry.tier === "derived")
|
|
942
|
+
.filter((entry) => entry.reasons.size > 0);
|
|
943
|
+
const strongEvidenceAnchors = evidenceEntries
|
|
944
|
+
.filter((entry) => isStrongPacketAnchor(entry, options.dirtyAnchorAllowed))
|
|
945
|
+
.filter((entry) => allowTestAnchors || (!entry.file.test && !isTestPath(entry.file.path)))
|
|
946
|
+
.map((entry) => entry.file.path);
|
|
947
|
+
const anchors = uniqueSorted([...selectedBaseAnchors, ...strongEvidenceAnchors]).slice(0, 8);
|
|
948
|
+
const nonTestEvidenceCount = evidenceEntries.filter((entry) => !entry.file.test && !isTestPath(entry.file.path)).length;
|
|
949
|
+
const selectedTestOnlyAnchorCount = allowTestAnchors || anchors.length > 0 || nonTestEvidenceCount > 0
|
|
950
|
+
? 0
|
|
951
|
+
: evidenceEntries.filter((entry) => entry.file.test || isTestPath(entry.file.path)).length;
|
|
952
|
+
const discardedAnchorCount = base.anchors.filter((anchor) => !focusPathSet.has(anchor)).length;
|
|
953
|
+
const missingAnchors = base.missingAnchors.filter((reason) => {
|
|
954
|
+
if (anchors.length > 0 && (reason === "no authoritative or derived edit anchor" || reason === "broad prompt matched only weak lexical evidence")) {
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
if (selectedTestOnlyAnchorCount === 0 && reason === "only test anchors for edit prompt") {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
return true;
|
|
961
|
+
});
|
|
962
|
+
if (base.mode === "edit" && anchors.length === 0 && !missingAnchors.includes("no selected packet anchors")) {
|
|
963
|
+
missingAnchors.push("no selected packet anchors");
|
|
964
|
+
}
|
|
965
|
+
if (base.mode === "edit" && selectedTestOnlyAnchorCount > 0 && !missingAnchors.includes("only test anchors for edit prompt")) {
|
|
966
|
+
missingAnchors.push("only test anchors for edit prompt");
|
|
967
|
+
}
|
|
968
|
+
const confidence = Math.max(0, Math.min(1, base.confidence +
|
|
969
|
+
Math.min(0.16, anchors.length * 0.03) +
|
|
970
|
+
(options.explicitTargetProvided && anchors.length > 0 ? 0.08 : 0) +
|
|
971
|
+
(options.dirtyAnchorAllowed && anchors.length > 0 ? 0.05 : 0) -
|
|
972
|
+
Math.min(0.3, discardedAnchorCount * 0.05) -
|
|
973
|
+
(missingAnchors.length - base.missingAnchors.length) * 0.18));
|
|
974
|
+
const editReady = base.mode === "edit" && missingAnchors.length === 0 && anchors.length > 0 && confidence >= 0.48;
|
|
975
|
+
const rawSearchBetter = missingAnchors.includes("broad prompt matched only weak lexical evidence") || missingAnchors.includes("only test anchors for edit prompt");
|
|
976
|
+
const verdict = editReady ? "edit-ready" : rawSearchBetter || confidence < 0.24 ? "raw-search-better" : base.mode === "orientation" && confidence >= 0.3 && anchors.length > 0 ? "orientation-only" : "needs-target";
|
|
977
|
+
return {
|
|
978
|
+
...base,
|
|
979
|
+
confidence,
|
|
980
|
+
anchors,
|
|
981
|
+
selectedAnchorCount: anchors.length,
|
|
982
|
+
discardedAnchorCount,
|
|
983
|
+
missingAnchors: uniqueSorted(missingAnchors),
|
|
984
|
+
editReady,
|
|
985
|
+
verdict,
|
|
986
|
+
recommendedNextTool: verdict === "edit-ready" ? (base.mode === "edit" ? "task_brief" : "find_context") : verdict === "orientation-only" ? "find_context" : "search",
|
|
987
|
+
reasons: uniqueSorted([
|
|
988
|
+
...base.reasons.filter((reason) => !/direct anchor/.test(reason)),
|
|
989
|
+
anchors.length > 0 ? `${anchors.length} selected packet anchor(s)` : "no selected packet anchors",
|
|
990
|
+
discardedAnchorCount > 0 ? `${discardedAnchorCount} discarded retrieval anchor(s) not shown in packet` : undefined
|
|
991
|
+
].filter((entry) => Boolean(entry)))
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function isStrongPacketAnchor(entry, dirtyAnchorAllowed) {
|
|
995
|
+
if (entry.provenance.some((item) => item.source === "explicit_target")) {
|
|
996
|
+
return true;
|
|
997
|
+
}
|
|
998
|
+
if (dirtyAnchorAllowed && entry.provenance.some((item) => item.source === "dirty_worktree")) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
function taskReferencesDirtyContext(task) {
|
|
1004
|
+
return /\b(current|dirty|diff|worktree|working tree|changed|changes|unstaged|staged|this change|these changes)\b/iu.test(task);
|
|
1005
|
+
}
|
|
1006
|
+
function packetIntentDiagnostics(intent, baseDiagnostics) {
|
|
1007
|
+
if (intent.editReady) {
|
|
1008
|
+
return [];
|
|
1009
|
+
}
|
|
1010
|
+
const diagnostics = baseDiagnostics.filter((diagnostic) => !/needs explicit file|raw search likely|broad packet|only test anchors/iu.test(diagnostic));
|
|
1011
|
+
if (intent.verdict === "needs-target") {
|
|
1012
|
+
diagnostics.push("needs explicit file, symbol, or narrower search before edit planning");
|
|
1013
|
+
}
|
|
1014
|
+
if (intent.verdict === "raw-search-better") {
|
|
1015
|
+
diagnostics.push("raw search likely gives a cleaner first pass than this broad packet");
|
|
1016
|
+
}
|
|
1017
|
+
for (const anchor of intent.missingAnchors) {
|
|
1018
|
+
diagnostics.push(`missing ${anchor}`);
|
|
1019
|
+
}
|
|
1020
|
+
return uniqueSorted(diagnostics);
|
|
1021
|
+
}
|
|
1022
|
+
function actionabilityFromPacketVerdict(verdict) {
|
|
1023
|
+
if (verdict === "edit-ready") {
|
|
1024
|
+
return "edit_ready";
|
|
1025
|
+
}
|
|
1026
|
+
if (verdict === "orientation-only") {
|
|
1027
|
+
return "orientation";
|
|
1028
|
+
}
|
|
1029
|
+
if (verdict === "raw-search-better") {
|
|
1030
|
+
return "raw_search_better";
|
|
1031
|
+
}
|
|
1032
|
+
if (verdict === "needs-target") {
|
|
1033
|
+
return "needs_target";
|
|
1034
|
+
}
|
|
1035
|
+
return "inspect_first";
|
|
1036
|
+
}
|
|
1037
|
+
function qualityLikeFallbackActionability(entries) {
|
|
1038
|
+
if (entries.length === 0 || entries.every((entry) => entry.tier === "fallback")) {
|
|
1039
|
+
return "needs_target";
|
|
1040
|
+
}
|
|
1041
|
+
return "inspect_first";
|
|
1042
|
+
}
|
|
1043
|
+
function uniqueFocusEntries(entries) {
|
|
1044
|
+
const byPath = new Map();
|
|
1045
|
+
for (const entry of entries.sort((a, b) => tierScore(a.tier) - tierScore(b.tier) || b.score - a.score || a.file.path.localeCompare(b.file.path))) {
|
|
1046
|
+
if (!byPath.has(entry.file.path)) {
|
|
1047
|
+
byPath.set(entry.file.path, entry);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return [...byPath.values()];
|
|
1051
|
+
}
|
|
1052
|
+
async function contextSnippets(repoRoot, index, focusPaths, changedSymbols, queryText, limit) {
|
|
1053
|
+
const snippets = [];
|
|
1054
|
+
const used = new Set();
|
|
1055
|
+
const unreadableFiles = new Set();
|
|
1056
|
+
const add = async (filePath, line, reason) => {
|
|
1057
|
+
if (snippets.length >= Math.min(10, limit)) {
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const key = `${filePath}:${line}`;
|
|
1061
|
+
if (used.has(key)) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
used.add(key);
|
|
1065
|
+
const snippet = await readSnippet(repoRoot, filePath, line, 3);
|
|
1066
|
+
if ("unreadable" in snippet) {
|
|
1067
|
+
if (!unreadableFiles.has(filePath)) {
|
|
1068
|
+
unreadableFiles.add(filePath);
|
|
1069
|
+
snippets.push(`- ${filePath}:${line} ${reason}\n <snippet unavailable: ${snippet.unreadable}>`);
|
|
1070
|
+
}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (snippet.text) {
|
|
1074
|
+
snippets.push(`- ${filePath}:${line} ${reason}\n${snippet.text}`);
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
for (const entry of changedSymbols.slice(0, limit)) {
|
|
1078
|
+
await add(entry.symbol.path, entry.symbol.range?.startLine ?? 1, `changed ${entry.symbol.qualifiedName}`);
|
|
1079
|
+
}
|
|
1080
|
+
const focusSet = new Set(focusPaths);
|
|
1081
|
+
const hasQuery = Boolean(queryText.trim());
|
|
1082
|
+
const symbols = index.symbols
|
|
1083
|
+
.filter((symbol) => focusSet.has(symbol.path))
|
|
1084
|
+
.sort((a, b) => {
|
|
1085
|
+
const fileA = findFile(index, a.path)?.rank ?? 0;
|
|
1086
|
+
const fileB = findFile(index, b.path)?.rank ?? 0;
|
|
1087
|
+
return fileB - fileA || (a.range?.startLine ?? 0) - (b.range?.startLine ?? 0) || a.qualifiedName.localeCompare(b.qualifiedName);
|
|
1088
|
+
});
|
|
1089
|
+
if (hasQuery) {
|
|
1090
|
+
const usages = index.usageSites
|
|
1091
|
+
.map((usage) => ({
|
|
1092
|
+
usage,
|
|
1093
|
+
score: focusSet.has(usage.path) ? Math.max(matchScore(queryText, usage.name), matchScore(queryText, usage.text), matchScore(queryText, usage.path)) : 0
|
|
1094
|
+
}))
|
|
1095
|
+
.filter((entry) => entry.score > 0)
|
|
1096
|
+
.sort((a, b) => b.score - a.score ||
|
|
1097
|
+
Number(a.usage.kind === "import") - Number(b.usage.kind === "import") ||
|
|
1098
|
+
a.usage.path.localeCompare(b.usage.path) ||
|
|
1099
|
+
(a.usage.range?.startLine ?? 0) - (b.usage.range?.startLine ?? 0))
|
|
1100
|
+
.slice(0, limit);
|
|
1101
|
+
for (const { usage } of usages) {
|
|
1102
|
+
await add(usage.path, usage.range?.startLine ?? 1, `usage ${usage.name} ${usage.confidence}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const symbolCandidates = hasQuery
|
|
1106
|
+
? symbols.filter((symbol) => Math.max(matchScore(queryText, symbol.name), matchScore(queryText, symbol.qualifiedName), matchScore(queryText, symbol.path)) > 0)
|
|
1107
|
+
: symbols;
|
|
1108
|
+
for (const symbol of symbolCandidates.slice(0, limit)) {
|
|
1109
|
+
await add(symbol.path, symbol.range?.startLine ?? 1, `${symbol.kind} ${symbol.qualifiedName}`);
|
|
1110
|
+
}
|
|
1111
|
+
return snippets;
|
|
1112
|
+
}
|
|
1113
|
+
async function sessionMemoryPreview(input) {
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await summarizeSessionMemory({
|
|
1116
|
+
repoRoot: input.repoRoot,
|
|
1117
|
+
taskId: input.taskId,
|
|
1118
|
+
files: input.files,
|
|
1119
|
+
symbols: input.symbols,
|
|
1120
|
+
topics: input.topics,
|
|
1121
|
+
freshness: input.freshness,
|
|
1122
|
+
limit: input.limit,
|
|
1123
|
+
includeStale: true
|
|
1124
|
+
});
|
|
1125
|
+
if (result.memory.entries.length === 0) {
|
|
1126
|
+
return { lines: [] };
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
lines: (result.memory.markdown ?? "").split(/\r?\n/u).slice(0, 12),
|
|
1130
|
+
data: {
|
|
1131
|
+
sessionId: result.sessionId,
|
|
1132
|
+
revision: result.revision,
|
|
1133
|
+
entries: result.memory.entries.slice(0, input.limit),
|
|
1134
|
+
warnings: result.warnings
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
catch (error) {
|
|
1139
|
+
return {
|
|
1140
|
+
lines: [`- unavailable: ${error instanceof Error ? error.message : String(error)}`],
|
|
1141
|
+
data: { warning: error instanceof Error ? error.message : String(error) }
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
async function readSnippet(repoRoot, filePath, centerLine, radius) {
|
|
1146
|
+
try {
|
|
1147
|
+
const source = await fs.readFile(path.join(repoRoot, filePath), "utf8");
|
|
1148
|
+
const lines = source.split(/\r?\n/);
|
|
1149
|
+
const start = Math.max(1, centerLine - radius);
|
|
1150
|
+
const end = Math.min(lines.length, centerLine + radius);
|
|
1151
|
+
const text = lines
|
|
1152
|
+
.slice(start - 1, end)
|
|
1153
|
+
.map((line, index) => ` ${String(start + index).padStart(4, " ")} | ${line.slice(0, 180)}`)
|
|
1154
|
+
.join("\n");
|
|
1155
|
+
return { text };
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
const code = error?.code;
|
|
1159
|
+
return { unreadable: typeof code === "string" ? code : "ERR" };
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
//# sourceMappingURL=context.js.map
|