flowseeker 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/COMPATIBILITY.md +281 -0
  3. package/LICENSE +21 -0
  4. package/README.md +375 -0
  5. package/dist/adapters/frameworkEdges.js +586 -0
  6. package/dist/agent/approvalPolicy.js +81 -0
  7. package/dist/agent/commandRunner.js +166 -0
  8. package/dist/agent/flowCommandRunner.js +124 -0
  9. package/dist/agent/mcpToolRunner.js +167 -0
  10. package/dist/auth/githubAuth.js +71 -0
  11. package/dist/auth/modelList.js +127 -0
  12. package/dist/auth/oauthHandler.js +377 -0
  13. package/dist/chat/nativeChatParticipant.js +616 -0
  14. package/dist/cli/mcpServer.js +383 -0
  15. package/dist/cli/runEvaluation.js +789 -0
  16. package/dist/cli/runReplay.js +481 -0
  17. package/dist/commands/checkHostCompatibility.js +149 -0
  18. package/dist/commands/copyAgentPrompt.js +52 -0
  19. package/dist/commands/copyContext.js +54 -0
  20. package/dist/commands/explainSelectionRelevance.js +57 -0
  21. package/dist/commands/findRelevantContext.js +127 -0
  22. package/dist/commands/openEvidence.js +49 -0
  23. package/dist/commands/openMcpConfig.js +81 -0
  24. package/dist/commands/openRelatedTests.js +54 -0
  25. package/dist/commands/rebuildIndex.js +45 -0
  26. package/dist/commands/runEvaluationSuite.js +323 -0
  27. package/dist/commands/runReplaySuite.js +228 -0
  28. package/dist/config/defaultConfig.js +72 -0
  29. package/dist/config/loadConfig.js +84 -0
  30. package/dist/config/loadConfigFromPath.js +60 -0
  31. package/dist/extension.js +513 -0
  32. package/dist/gateway/agentPrompts.js +176 -0
  33. package/dist/gateway/aiGateway.js +1255 -0
  34. package/dist/gateway/aiProviders.js +901 -0
  35. package/dist/gateway/contextExpansion.js +331 -0
  36. package/dist/gateway/editProposalStore.js +238 -0
  37. package/dist/gateway/planProposalStore.js +28 -0
  38. package/dist/index/cacheStore.js +51 -0
  39. package/dist/index/chunker.js +45 -0
  40. package/dist/index/dependencyExtractor.js +107 -0
  41. package/dist/index/fileDiscovery.js +177 -0
  42. package/dist/index/structuredExtractor.js +256 -0
  43. package/dist/index/workspaceIndex.js +518 -0
  44. package/dist/mcp/mcpConfig.js +154 -0
  45. package/dist/mcp/mcpProvider.js +109 -0
  46. package/dist/mcp/mcpTools.js +215 -0
  47. package/dist/pipeline/agentPrompt.js +79 -0
  48. package/dist/pipeline/agentPromptHeadless.js +85 -0
  49. package/dist/pipeline/contextBlueprint.js +346 -0
  50. package/dist/pipeline/contextPack.js +80 -0
  51. package/dist/pipeline/diffPreview.js +79 -0
  52. package/dist/pipeline/evaluationMetrics.js +154 -0
  53. package/dist/pipeline/evidenceGraph.js +389 -0
  54. package/dist/pipeline/feedback.js +215 -0
  55. package/dist/pipeline/fileGroups.js +84 -0
  56. package/dist/pipeline/fileScanner.js +866 -0
  57. package/dist/pipeline/nodeScan.js +219 -0
  58. package/dist/pipeline/ranker.js +563 -0
  59. package/dist/pipeline/responseLanguage.js +39 -0
  60. package/dist/pipeline/retrievalPlan.js +163 -0
  61. package/dist/pipeline/runHeadless.js +54 -0
  62. package/dist/pipeline/runPipeline.js +114 -0
  63. package/dist/pipeline/solvePacket.js +382 -0
  64. package/dist/pipeline/subsystem.js +257 -0
  65. package/dist/pipeline/taskUnderstanding.js +453 -0
  66. package/dist/pipeline/tokenSavings.js +146 -0
  67. package/dist/pipeline/universalScan.js +216 -0
  68. package/dist/pipeline/verifyAfterApply.js +233 -0
  69. package/dist/runtime/capabilities.js +71 -0
  70. package/dist/runtime/hostDetect.js +98 -0
  71. package/dist/runtime/hostTier.js +68 -0
  72. package/dist/runtime/statusBar.js +80 -0
  73. package/dist/skills/skillRegistry.js +208 -0
  74. package/dist/types.js +3 -0
  75. package/dist/ui/chatViewProvider.js +3899 -0
  76. package/dist/ui/resultTreeProvider.js +174 -0
  77. package/dist/usage/quotaTracker.js +358 -0
  78. package/dist/utils/async.js +30 -0
  79. package/dist/utils/logger.js +64 -0
  80. package/dist/utils/text.js +364 -0
  81. package/dist/utils/updateChecker.js +140 -0
  82. package/package.json +561 -0
@@ -0,0 +1,563 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rankEvidence = rankEvidence;
4
+ const contextBlueprint_1 = require("./contextBlueprint");
5
+ const text_1 = require("../utils/text");
6
+ const subsystem_1 = require("./subsystem");
7
+ const rolePriority = {
8
+ edit_candidate: 5,
9
+ read_context: 4,
10
+ test: 4,
11
+ impact: 3,
12
+ verify: 3,
13
+ unknown: 1
14
+ };
15
+ function rankEvidence(units, config, profile, rankOptions) {
16
+ const deduped = markDuplicateUnits(dedupeUnits(units).map((unit) => applyTaskMetadata(unit, profile)));
17
+ const fileCounts = new Map();
18
+ for (const unit of deduped) {
19
+ fileCounts.set(unit.relativePath, (fileCounts.get(unit.relativePath) ?? 0) + 1);
20
+ }
21
+ const boosted = deduped.map((unit) => boostUnit(unit, fileCounts, config, profile, rankOptions?.editorContext));
22
+ const sorted = boosted.sort(compareUnits);
23
+ if (!profile) {
24
+ return selectDiverseUnits(sorted, config.pipeline.maxCandidates);
25
+ }
26
+ return selectWithBlueprintCoverage(sorted, profile, config.pipeline.maxCandidates);
27
+ }
28
+ function dedupeUnits(units) {
29
+ const byId = new Map();
30
+ for (const unit of units) {
31
+ const existing = byId.get(unit.id);
32
+ if (!existing || unit.score > existing.score) {
33
+ byId.set(unit.id, unit);
34
+ }
35
+ }
36
+ return Array.from(byId.values());
37
+ }
38
+ function markDuplicateUnits(units) {
39
+ const seen = new Set();
40
+ return units.map((unit) => {
41
+ const dedupeKey = unit.dedupeKey ?? buildDedupeKey(unit);
42
+ const isDuplicate = seen.has(dedupeKey);
43
+ seen.add(dedupeKey);
44
+ return {
45
+ ...unit,
46
+ dedupeKey,
47
+ isDuplicate
48
+ };
49
+ });
50
+ }
51
+ function applyTaskMetadata(unit, profile) {
52
+ const dedupeKey = unit.dedupeKey ?? buildDedupeKey(unit);
53
+ if (!profile || profile.negativeTerms.length === 0) {
54
+ return { ...unit, dedupeKey };
55
+ }
56
+ const matchedNegativeTerms = matchedTaskNegativeTerms(unit, profile);
57
+ if (matchedNegativeTerms.length === 0) {
58
+ return { ...unit, dedupeKey };
59
+ }
60
+ const role = unit.role === "edit_candidate" || unit.role === "impact" ? "read_context" : unit.role;
61
+ return {
62
+ ...unit,
63
+ dedupeKey,
64
+ role,
65
+ contrastContext: true,
66
+ score: unit.score - 24,
67
+ reasons: [
68
+ ...unit.reasons,
69
+ `contrast context: matches avoided/previous behavior term(s) ${matchedNegativeTerms.join(", ")}`
70
+ ]
71
+ };
72
+ }
73
+ function matchedTaskNegativeTerms(unit, profile) {
74
+ const haystack = (0, text_1.normalizeText)(`${unit.relativePath}\n${unit.snippet ?? ""}`);
75
+ return profile.negativeTerms
76
+ .map(text_1.normalizeText)
77
+ .filter((term) => term.length > 1 && haystack.includes(term))
78
+ .slice(0, 5);
79
+ }
80
+ function selectDiverseUnits(sorted, maxCandidates) {
81
+ const state = createSelectionState(maxCandidates);
82
+ for (const unit of sorted) {
83
+ pushUnit(state, unit);
84
+ }
85
+ return state.selected;
86
+ }
87
+ function selectWithBlueprintCoverage(sorted, profile, maxCandidates) {
88
+ const state = createSelectionState(maxCandidates);
89
+ const constrainedTop = buildConstrainedTopK(sorted, profile, Math.min(10, maxCandidates));
90
+ for (const unit of constrainedTop) {
91
+ pushUnit(state, unit);
92
+ }
93
+ pushFrameworkCompanions(sorted, state);
94
+ for (const slot of profile.blueprint.requiredSlots) {
95
+ pushUnit(state, bestUnitForSlot(sorted, slot, state), { allowThirdFileUnit: true });
96
+ }
97
+ pushEntityCompanions(sorted, state, profile);
98
+ for (const slot of (0, contextBlueprint_1.allContextSlots)(profile.blueprint)) {
99
+ if (profile.blueprint.requiredSlots.includes(slot)) {
100
+ continue;
101
+ }
102
+ if (shouldSkipOptionalSlot(profile, slot)) {
103
+ continue;
104
+ }
105
+ pushUnit(state, bestUnitForSlot(sorted, slot, state));
106
+ if (state.selected.length >= Math.floor(maxCandidates * 0.65)) {
107
+ break;
108
+ }
109
+ }
110
+ for (const unit of sorted) {
111
+ pushUnit(state, unit);
112
+ }
113
+ return state.selected.slice(0, maxCandidates);
114
+ }
115
+ function buildConstrainedTopK(sorted, profile, limit) {
116
+ const pool = sorted.slice(0, Math.min(80, sorted.length));
117
+ const selected = [];
118
+ const seenIds = new Set();
119
+ const fileCounts = new Map();
120
+ const primarySubsystem = inferSelectionSubsystem(pool, profile);
121
+ const add = (unit, force = false) => {
122
+ if (!unit || selected.length >= limit || seenIds.has(unit.id)) {
123
+ return false;
124
+ }
125
+ const currentFileCount = fileCounts.get(unit.relativePath) ?? 0;
126
+ if (!force && currentFileCount >= 1) {
127
+ return false;
128
+ }
129
+ seenIds.add(unit.id);
130
+ fileCounts.set(unit.relativePath, currentFileCount + 1);
131
+ selected.push(unit);
132
+ return true;
133
+ };
134
+ for (const anchor of pool.filter((unit) => !unit.contrastContext).slice(0, 3)) {
135
+ add(anchor, true);
136
+ }
137
+ for (const slot of orderedRequiredSlots(profile)) {
138
+ add(bestConstrainedSlotUnit(pool, slot, profile, primarySubsystem, seenIds, fileCounts));
139
+ }
140
+ for (const unit of pool) {
141
+ if (selected.length >= limit) {
142
+ break;
143
+ }
144
+ if (!isAllowedInConstrainedTop(unit, profile, primarySubsystem)) {
145
+ continue;
146
+ }
147
+ add(unit);
148
+ }
149
+ for (const unit of sorted) {
150
+ if (selected.length >= limit) {
151
+ break;
152
+ }
153
+ add(unit);
154
+ }
155
+ return selected;
156
+ }
157
+ function orderedRequiredSlots(profile) {
158
+ const preferred = ["entry", "handler", "validation", "ui"];
159
+ const required = profile.blueprint.requiredSlots;
160
+ return [
161
+ ...preferred.filter((slot) => required.includes(slot)),
162
+ ...required.filter((slot) => !preferred.includes(slot))
163
+ ];
164
+ }
165
+ function bestConstrainedSlotUnit(pool, slot, profile, primarySubsystem, seenIds, fileCounts) {
166
+ return pool
167
+ .filter((unit) => unit.slot === slot && !seenIds.has(unit.id))
168
+ .filter((unit) => (fileCounts.get(unit.relativePath) ?? 0) === 0)
169
+ .filter((unit) => isAllowedInConstrainedTop(unit, profile, primarySubsystem) || hasStrongTaskMatch(unit, profile))
170
+ .sort((left, right) => constrainedCandidateScore(right, profile, primarySubsystem) - constrainedCandidateScore(left, profile, primarySubsystem))[0];
171
+ }
172
+ function constrainedCandidateScore(unit, profile, primarySubsystem) {
173
+ let score = unit.score;
174
+ if (hasStrongTaskMatch(unit, profile)) {
175
+ score += 60;
176
+ }
177
+ if (primarySubsystem && unit.subsystem === primarySubsystem) {
178
+ score += 24;
179
+ }
180
+ if (unit.contrastContext) {
181
+ score -= 80;
182
+ }
183
+ if (isUnrequestedTestEvidence(unit, profile)) {
184
+ score -= 50;
185
+ }
186
+ if (unit.slot === "entry" || unit.slot === "handler") {
187
+ score += 18;
188
+ }
189
+ if (unit.slot === "validation" || unit.slot === "ui") {
190
+ score += 12;
191
+ }
192
+ return score;
193
+ }
194
+ function isAllowedInConstrainedTop(unit, profile, primarySubsystem) {
195
+ if (unit.contrastContext) {
196
+ return false;
197
+ }
198
+ if (isUnrequestedTestEvidence(unit, profile)) {
199
+ return false;
200
+ }
201
+ if (!primarySubsystem || !unit.subsystem || unit.subsystem === primarySubsystem) {
202
+ return true;
203
+ }
204
+ if (unit.slot && profile.blueprint.requiredSlots.includes(unit.slot)) {
205
+ return true;
206
+ }
207
+ return hasStrongTaskMatch(unit, profile);
208
+ }
209
+ function inferSelectionSubsystem(pool, profile) {
210
+ const counts = new Map();
211
+ for (const unit of pool.slice(0, 30)) {
212
+ if (!unit.subsystem || unit.contrastContext) {
213
+ continue;
214
+ }
215
+ const weight = 1 + (hasStrongTaskMatch(unit, profile) ? 3 : 0) + (unit.slot && profile.blueprint.requiredSlots.includes(unit.slot) ? 1 : 0);
216
+ counts.set(unit.subsystem, (counts.get(unit.subsystem) ?? 0) + weight);
217
+ }
218
+ return Array.from(counts.entries()).sort((left, right) => right[1] - left[1])[0]?.[0];
219
+ }
220
+ function hasStrongTaskMatch(unit, profile) {
221
+ const matched = new Set(unit.matchedTerms.map(text_1.normalizeText));
222
+ if (profile.literalTerms.some((term) => matched.has((0, text_1.normalizeText)(term)))) {
223
+ return true;
224
+ }
225
+ const path = normalizePath(unit.relativePath);
226
+ return [...profile.literalTerms, ...profile.keywords]
227
+ .map((term) => (0, text_1.normalizeText)(term).replace(/[^a-z0-9]+/g, ""))
228
+ .some((term) => term.length >= 5 && path.replace(/[^a-z0-9]+/g, "").includes(term));
229
+ }
230
+ function createSelectionState(maxCandidates) {
231
+ return {
232
+ maxCandidates,
233
+ selected: [],
234
+ seenIds: new Set(),
235
+ seenDedupeKeys: new Set(),
236
+ fileCounts: new Map()
237
+ };
238
+ }
239
+ function pushUnit(state, unit, options = {}) {
240
+ if (!unit || state.selected.length >= state.maxCandidates || state.seenIds.has(unit.id)) {
241
+ return false;
242
+ }
243
+ const dedupeKey = unit.dedupeKey ?? buildDedupeKey(unit);
244
+ if (state.seenDedupeKeys.has(dedupeKey)) {
245
+ return false;
246
+ }
247
+ const currentFileCount = state.fileCounts.get(unit.relativePath) ?? 0;
248
+ const earlyLimit = options.allowSecondFileUnitEarly ? 2 : 1;
249
+ const fileLimit = state.selected.length < 12 ? earlyLimit : options.allowThirdFileUnit ? 3 : 2;
250
+ if (currentFileCount >= fileLimit) {
251
+ return false;
252
+ }
253
+ state.seenIds.add(unit.id);
254
+ state.seenDedupeKeys.add(dedupeKey);
255
+ state.fileCounts.set(unit.relativePath, currentFileCount + 1);
256
+ state.selected.push({ ...unit, dedupeKey });
257
+ return true;
258
+ }
259
+ function pushFrameworkCompanions(sorted, state) {
260
+ const byPath = new Map();
261
+ for (const unit of sorted) {
262
+ const key = normalizePath(unit.relativePath);
263
+ const list = byPath.get(key) ?? [];
264
+ list.push(unit);
265
+ byPath.set(key, list);
266
+ }
267
+ const companionPaths = new Map();
268
+ for (const unit of state.selected.slice(0, 8)) {
269
+ for (const companionPath of frameworkCompanionPaths(unit.relativePath)) {
270
+ companionPaths.set(companionPath, unit.relativePath);
271
+ }
272
+ }
273
+ for (const [companionPath, sourcePath] of companionPaths.entries()) {
274
+ if (state.selected.some((unit) => normalizePath(unit.relativePath) === companionPath)) {
275
+ continue;
276
+ }
277
+ const candidate = byPath.get(companionPath)?.find((unit) => !state.seenIds.has(unit.id) && !unit.contrastContext);
278
+ pushUnit(state, candidate ? withFrameworkReason(candidate, `framework companion of ${sourcePath}`) : undefined);
279
+ }
280
+ }
281
+ function pushEntityCompanions(sorted, state, profile) {
282
+ const entities = focusedEntityTerms(profile);
283
+ if (entities.length === 0 || state.selected.length >= state.maxCandidates) {
284
+ return;
285
+ }
286
+ const selectedText = normalizePath(state.selected.map((unit) => unit.relativePath).join(" "));
287
+ const activeEntities = entities.filter((entity) => selectedText.includes(entity));
288
+ const slotsToCover = profile.blueprint.requiredSlots.filter(isEntityCompanionSlot);
289
+ let added = 0;
290
+ for (const entity of activeEntities) {
291
+ for (const slot of slotsToCover) {
292
+ const companion = bestEntityCompanion(sorted, entity, slot, state);
293
+ if (pushUnit(state, companion ? withEntityCompanionReason(companion, entity, slot) : undefined, { allowThirdFileUnit: true })) {
294
+ added += 1;
295
+ }
296
+ if (added >= 3 || state.selected.length >= Math.floor(state.maxCandidates * 0.5)) {
297
+ return;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ function isEntityCompanionSlot(slot) {
303
+ return slot === "data" || slot === "permission" || slot === "entry" || slot === "domain";
304
+ }
305
+ function shouldSkipOptionalSlot(profile, slot) {
306
+ if (slot === "tests") {
307
+ return !profileHasAny(profile, ["test", "tests", "spec", "expect", "assert", "verify", "verification", "kiem tra"]);
308
+ }
309
+ if (slot === "docs") {
310
+ return !profileHasAny(profile, ["docs", "documentation", "readme", "guide"]);
311
+ }
312
+ return false;
313
+ }
314
+ function profileHasAny(profile, values) {
315
+ const terms = new Set(profile.keywords.map(text_1.normalizeText));
316
+ return values.some((value) => terms.has((0, text_1.normalizeText)(value)));
317
+ }
318
+ function frameworkCompanionPaths(relativePath) {
319
+ const normalized = normalizePath(relativePath);
320
+ const svelteRoute = normalized.match(/^(.*\/routes\/.+\/)\+(page|layout|server)\.(svelte|ts|js)$/);
321
+ if (svelteRoute) {
322
+ const dir = svelteRoute[1];
323
+ return [
324
+ `${dir}+page.ts`,
325
+ `${dir}+page.js`,
326
+ `${dir}+page.svelte`,
327
+ `${dir}+server.ts`,
328
+ `${dir}+server.js`,
329
+ `${dir}+layout.ts`,
330
+ `${dir}+layout.svelte`
331
+ ].filter((candidate) => candidate !== normalized);
332
+ }
333
+ const nextRoute = normalized.match(/^(.*\/(?:app|pages)\/.+\/)(page|route|layout)\.(tsx|jsx|ts|js)$/);
334
+ if (nextRoute) {
335
+ const dir = nextRoute[1];
336
+ return [
337
+ `${dir}page.tsx`,
338
+ `${dir}page.jsx`,
339
+ `${dir}page.ts`,
340
+ `${dir}page.js`,
341
+ `${dir}route.ts`,
342
+ `${dir}route.js`,
343
+ `${dir}layout.tsx`,
344
+ `${dir}layout.jsx`
345
+ ].filter((candidate) => candidate !== normalized);
346
+ }
347
+ return [];
348
+ }
349
+ function bestUnitForSlot(units, slot, state) {
350
+ const candidates = units.filter((unit) => unit.slot === slot && !state.seenIds.has(unit.id));
351
+ return (candidates.find((unit) => !unit.contrastContext && (state.fileCounts.get(unit.relativePath) ?? 0) === 0) ??
352
+ candidates.find((unit) => !unit.contrastContext && (state.fileCounts.get(unit.relativePath) ?? 0) < 2) ??
353
+ candidates.find((unit) => (state.fileCounts.get(unit.relativePath) ?? 0) === 0) ??
354
+ candidates.find((unit) => (state.fileCounts.get(unit.relativePath) ?? 0) < 2));
355
+ }
356
+ function bestEntityCompanion(units, entity, slot, state) {
357
+ return units
358
+ .filter((unit) => unit.slot === slot && !unit.contrastContext && !state.seenIds.has(unit.id))
359
+ .filter((unit) => (state.fileCounts.get(unit.relativePath) ?? 0) === 0)
360
+ .filter((unit) => normalizePath(unit.relativePath).includes(entity))
361
+ .sort((left, right) => entityCompanionScore(right, entity, slot) - entityCompanionScore(left, entity, slot))[0];
362
+ }
363
+ function entityCompanionScore(unit, entity, slot) {
364
+ const normalizedPath = normalizePath(unit.relativePath);
365
+ const basename = normalizedPath.split("/").pop() ?? normalizedPath;
366
+ let score = unit.score;
367
+ if (basename.startsWith(`${entity}.`) || basename.startsWith(`${entity}_`)) {
368
+ score += 36;
369
+ }
370
+ if (normalizedPath.includes(`${entity}_${entity}`) || normalizedPath.includes(`${entity}_policy`)) {
371
+ score += 46;
372
+ }
373
+ if (slot === "data" && /\b(migration|migrations|create_|alter_|add_)\b/.test(normalizedPath)) {
374
+ score += 34;
375
+ }
376
+ if (slot === "permission" && /(^|\/)(policies?|permissions?)(\/|$)/.test(normalizedPath)) {
377
+ score += 30;
378
+ }
379
+ return score;
380
+ }
381
+ function withEntityCompanionReason(unit, entity, slot) {
382
+ const normalizedPath = normalizePath(unit.relativePath);
383
+ const migrationBoost = slot === "data" && /\b(migration|migrations|create_|alter_|add_)\b/.test(normalizedPath) ? 68 : 0;
384
+ const policyBoost = slot === "permission" && /(^|\/)(policies?|permissions?)(\/|$)/.test(normalizedPath) ? 48 : 0;
385
+ const boost = Math.max(24, migrationBoost, policyBoost);
386
+ const score = unit.score + boost;
387
+ return {
388
+ ...unit,
389
+ score,
390
+ confidence: (0, text_1.clamp)(score / 30, 0.1, 0.98),
391
+ tier: score >= 22.5 ? "high" : score >= 13.5 ? "medium" : "low",
392
+ reasons: [...unit.reasons, `entity companion for ${entity} ${slot} context`]
393
+ };
394
+ }
395
+ function boostUnit(unit, fileCounts, config, profile, editorContext) {
396
+ const sameFileCount = fileCounts.get(unit.relativePath) ?? 1;
397
+ const crossEvidenceBonus = Math.min(4, sameFileCount - 1);
398
+ const multiTermBonus = Math.min(6, unit.matchedTerms.length);
399
+ const slotBonus = profile ? profileSlotBonus(unit, profile) : 0;
400
+ const subsystemBonus = profile ? (0, subsystem_1.subsystemAlignmentScore)(unit, profile) : 0;
401
+ const symbolBonus = profile && unit.symbolMatches?.length ? Math.min(8, unit.symbolMatches.length * 4) : 0;
402
+ const literalTermBonus = profile ? matchedTaskLiteralBonus(unit, profile) : 0;
403
+ const filenamePhraseBonus = profile ? filenamePhraseMatchBonus(unit, profile) : 0;
404
+ const testPenalty = profile && isUnrequestedTestEvidence(unit, profile) ? 55 : 0;
405
+ const duplicatePenalty = unit.isDuplicate ? 8 : 0;
406
+ const contrastPenalty = unit.contrastContext ? 8 : 0;
407
+ const editorBoost = editorContext ? editorContextBoost(unit, editorContext) : 0;
408
+ const score = unit.score + crossEvidenceBonus + multiTermBonus + slotBonus + subsystemBonus + symbolBonus + literalTermBonus + filenamePhraseBonus - testPenalty - duplicatePenalty - contrastPenalty + editorBoost;
409
+ const confidence = (0, text_1.clamp)(score / 30, 0.1, 0.98);
410
+ const role = confidence < config.pipeline.minConfidence && unit.role === "edit_candidate" ? "unknown" : unit.role;
411
+ return {
412
+ ...unit,
413
+ score,
414
+ role,
415
+ confidence,
416
+ tier: confidence >= 0.75 ? "high" : confidence >= config.pipeline.minConfidence ? "medium" : "low",
417
+ reasons: [
418
+ ...unit.reasons,
419
+ crossEvidenceBonus > 0 ? "multiple related regions in same file" : "",
420
+ slotBonus > 0 ? "blueprint slot coverage" : "",
421
+ subsystemBonus > 0 ? "subsystem alignment" : "",
422
+ subsystemBonus < 0 ? "possible subsystem mismatch" : "",
423
+ symbolBonus > 0 ? "symbol-name match" : "",
424
+ literalTermBonus > 0 ? "exact literal value match" : "",
425
+ filenamePhraseBonus > 0 ? "filename phrase match" : "",
426
+ testPenalty > 0 ? "test evidence deprioritized" : "",
427
+ unit.isDuplicate ? "duplicate evidence deprioritized" : "",
428
+ editorBoost > 0 ? "active editor context boost" : ""
429
+ ].filter(Boolean)
430
+ };
431
+ }
432
+ function isUnrequestedTestEvidence(unit, profile) {
433
+ if (unit.kind !== "test" && unit.slot !== "tests") {
434
+ return false;
435
+ }
436
+ return !/\b(test|tests|spec|unit test|integration test|e2e|assert|expect|coverage)\b/.test(profile.normalizedTask);
437
+ }
438
+ function filenamePhraseMatchBonus(unit, profile) {
439
+ const normalizedPath = normalizePath(unit.relativePath);
440
+ const base = normalizedPath.split("/").pop()?.replace(/\.[^.]+$/, "") ?? normalizedPath;
441
+ const compactBase = base.replace(/[^a-z0-9]+/g, "");
442
+ const compactPath = normalizedPath.replace(/[^a-z0-9]+/g, "");
443
+ let best = 0;
444
+ for (const term of [...profile.literalTerms, ...profile.keywords]) {
445
+ const compactTerm = (0, text_1.normalizeText)(term).replace(/[^a-z0-9]+/g, "");
446
+ if (compactTerm.length < 5) {
447
+ continue;
448
+ }
449
+ if (compactBase.includes(compactTerm)) {
450
+ best = Math.max(best, 64);
451
+ }
452
+ else if (compactPath.includes(compactTerm)) {
453
+ best = Math.max(best, 28);
454
+ }
455
+ }
456
+ if (/\b(source\s+code|source|src|implementation)\b/.test(profile.normalizedTask) && normalizedPath.startsWith("src/")) {
457
+ best += 18;
458
+ }
459
+ return best;
460
+ }
461
+ function matchedTaskLiteralBonus(unit, profile) {
462
+ if (profile.literalTerms.length === 0) {
463
+ return 0;
464
+ }
465
+ const literals = new Set(profile.literalTerms);
466
+ const matchedLiteral = unit.matchedTerms.find((term) => literals.has(term));
467
+ if (!matchedLiteral) {
468
+ return 0;
469
+ }
470
+ return isValueOrPathLiteral(matchedLiteral) ? 88 : 72;
471
+ }
472
+ function isValueOrPathLiteral(term) {
473
+ return term.length >= 8 && (term.includes("/") || term.includes("@") || term.includes("://"));
474
+ }
475
+ function hasSpecificMatchedTerm(unit) {
476
+ return unit.matchedTerms.some((term) => {
477
+ if (term.length >= 14 && !term.includes(" ")) {
478
+ return true;
479
+ }
480
+ if (!term.includes("_")) {
481
+ return false;
482
+ }
483
+ return term.split("_").filter((part) => part.length > 1).length >= 3;
484
+ });
485
+ }
486
+ function profileSlotBonus(unit, profile) {
487
+ const slot = unit.slot ?? "unknown";
488
+ let bonus = 0;
489
+ if (profile.blueprint.requiredSlots.includes(slot)) {
490
+ bonus += 14;
491
+ }
492
+ else if (profile.blueprint.optionalSlots.includes(slot)) {
493
+ bonus += 5;
494
+ }
495
+ if (profile.blueprint.primarySurface === "frontend" && (slot === "entry" || slot === "ui" || slot === "domain")) {
496
+ bonus += 4;
497
+ }
498
+ if (profile.blueprint.primarySurface === "backend" && ["entry", "handler", "domain", "data", "validation", "permission", "side_effect"].includes(slot)) {
499
+ bonus += 3;
500
+ }
501
+ return bonus;
502
+ }
503
+ function compareUnits(a, b) {
504
+ const roleDelta = rolePriority[b.role] - rolePriority[a.role];
505
+ if (roleDelta !== 0) {
506
+ return roleDelta;
507
+ }
508
+ if (a.contrastContext !== b.contrastContext) {
509
+ return a.contrastContext ? 1 : -1;
510
+ }
511
+ return b.score - a.score;
512
+ }
513
+ function withFrameworkReason(unit, reason) {
514
+ return {
515
+ ...unit,
516
+ frameworkEdgeReason: unit.frameworkEdgeReason ?? reason,
517
+ reasons: [...unit.reasons, reason]
518
+ };
519
+ }
520
+ const lowSignalEntityTerms = new Set(["user", "account", "member", "admin", "file", "document", "submission", "attachment"]);
521
+ function focusedEntityTerms(profile) {
522
+ return profile.entities.map(text_1.normalizeText).filter((term) => term.length > 2 && !lowSignalEntityTerms.has(term));
523
+ }
524
+ function buildDedupeKey(unit) {
525
+ const normalizedPath = normalizePath(unit.relativePath);
526
+ const slot = unit.slot ?? "unknown";
527
+ if (unit.name) {
528
+ return `${normalizedPath}:${slot}:${unit.kind}:${(0, text_1.normalizeText)(unit.name)}`;
529
+ }
530
+ const startLine = unit.range?.startLine ?? 1;
531
+ const bucket = Math.floor(Math.max(0, startLine - 1) / 20);
532
+ return `${normalizedPath}:${slot}:${unit.kind}:${bucket}`;
533
+ }
534
+ function normalizePath(relativePath) {
535
+ return relativePath.replace(/\\/g, "/").toLowerCase();
536
+ }
537
+ function editorContextBoost(unit, ctx) {
538
+ if (!ctx) {
539
+ return 0;
540
+ }
541
+ const path = normalizePath(unit.relativePath);
542
+ let boost = 0;
543
+ if (ctx.activeFile) {
544
+ const activePath = normalizePath(ctx.activeFile);
545
+ if (path === activePath) {
546
+ boost += 15;
547
+ }
548
+ else {
549
+ const activeDir = activePath.substring(0, activePath.lastIndexOf("/"));
550
+ if (path.startsWith(activeDir + "/")) {
551
+ boost += 5;
552
+ }
553
+ }
554
+ }
555
+ if (ctx.recentFiles) {
556
+ const recent = ctx.recentFiles.map(normalizePath);
557
+ if (recent.includes(path)) {
558
+ boost += 8;
559
+ }
560
+ }
561
+ return boost;
562
+ }
563
+ //# sourceMappingURL=ranker.js.map
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildResponseLanguageInstruction = buildResponseLanguageInstruction;
4
+ const text_1 = require("../utils/text");
5
+ function buildResponseLanguageInstruction(configuredLanguage, task) {
6
+ if (configuredLanguage === "vi") {
7
+ return "Respond in Vietnamese.";
8
+ }
9
+ if (configuredLanguage === "en") {
10
+ return "Respond in English only.";
11
+ }
12
+ const detected = detectTaskLanguage(task);
13
+ if (detected === "vi") {
14
+ return "Respond in Vietnamese. The user's task is written in Vietnamese.";
15
+ }
16
+ if (detected === "en") {
17
+ return "Respond in English. The user's task is written in English.";
18
+ }
19
+ return "Respond in the same language as the user's task.";
20
+ }
21
+ function detectTaskLanguage(task) {
22
+ const normalized = (0, text_1.normalizeText)(task).replace(/[^a-z0-9]+/g, " ");
23
+ const tokens = normalized.split(/\s+/).filter(Boolean);
24
+ const hasVietnameseMarks = /[\u0300-\u036f]/.test(task.normalize("NFD")) || /[\u0111\u0110]/.test(task);
25
+ const viSignals = countSignals(tokens, ["toi", "muon", "sua", "them", "xoa", "duyet", "trinh", "khong", "bang", "cho", "sau", "truoc", "thay", "doi", "nguoi", "hoc", "vien", "du", "lieu", "loi"]);
26
+ const enSignals = countSignals(tokens, ["fix", "change", "update", "add", "remove", "delete", "user", "browser", "file", "code", "when", "after", "before", "instead", "only", "should"]);
27
+ if (hasVietnameseMarks || viSignals >= 2) {
28
+ return "vi";
29
+ }
30
+ if (enSignals >= 2) {
31
+ return "en";
32
+ }
33
+ return "unknown";
34
+ }
35
+ function countSignals(tokens, signals) {
36
+ const tokenSet = new Set(tokens);
37
+ return signals.filter((signal) => tokenSet.has(signal)).length;
38
+ }
39
+ //# sourceMappingURL=responseLanguage.js.map