flowseeker 0.1.7 → 0.1.8
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/.env.example +7 -0
- package/CHANGELOG.md +131 -108
- package/README.md +288 -221
- package/dist/cli/flowCommand.js +175 -0
- package/dist/cli/main.js +1794 -0
- package/dist/cli/mcpServer.js +7 -1
- package/dist/cli/runEvaluation.js +178 -2
- package/dist/config/defaultConfig.js +11 -1
- package/dist/config/env.js +118 -0
- package/dist/config/loadConfig.js +18 -1
- package/dist/config/loadConfigFromPath.js +3 -1
- package/dist/extension.js +23 -0
- package/dist/gateway/embeddingProviders.js +852 -0
- package/dist/index/cacheStore.js +43 -0
- package/dist/index/configRouteDiscoveryProbe.js +288 -0
- package/dist/index/embeddingIndex.js +193 -0
- package/dist/index/graphIndex.js +460 -0
- package/dist/index/indexWatcher.js +86 -0
- package/dist/index/structuredExtractor.js +303 -12
- package/dist/index/treeSitterExtractor.js +264 -0
- package/dist/index/vectorStore.js +41 -0
- package/dist/index/workspaceIndex.js +591 -26
- package/dist/mcp/mcpTools.js +51 -0
- package/dist/pipeline/contextPack.js +3 -3
- package/dist/pipeline/deterministicReranker.js +358 -0
- package/dist/pipeline/evaluationMetrics.js +14 -2
- package/dist/pipeline/fileGroups.js +7 -1
- package/dist/pipeline/fileScanner.js +93 -11
- package/dist/pipeline/llmReranker.js +151 -0
- package/dist/pipeline/nodeScan.js +91 -12
- package/dist/pipeline/ranker.js +875 -16
- package/dist/pipeline/retrievalFusion.js +41 -0
- package/dist/pipeline/runHeadless.js +35 -0
- package/dist/pipeline/solvePacket.js +549 -42
- package/dist/pipeline/subsystem.js +21 -0
- package/docs/demo-screenshot-checklist.md +86 -0
- package/docs/marketplace-copy.md +92 -0
- package/docs/mcp-onboarding.md +191 -0
- package/package.json +633 -561
package/dist/index/cacheStore.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.clearFlowSeekerCache = clearFlowSeekerCache;
|
|
37
|
+
exports.writeCacheFileAtomic = writeCacheFileAtomic;
|
|
38
|
+
exports.readCacheFileOrRecover = readCacheFileOrRecover;
|
|
37
39
|
const vscode = __importStar(require("vscode"));
|
|
38
40
|
async function clearFlowSeekerCache() {
|
|
39
41
|
const folders = vscode.workspace.workspaceFolders;
|
|
@@ -48,4 +50,45 @@ async function clearFlowSeekerCache() {
|
|
|
48
50
|
// Cache may not exist yet.
|
|
49
51
|
}
|
|
50
52
|
}
|
|
53
|
+
async function writeCacheFileAtomic(uri, data) {
|
|
54
|
+
const tempUri = uri.with({ path: `${uri.path}.tmp-${Date.now()}` });
|
|
55
|
+
await vscode.workspace.fs.createDirectory(parentUri(uri));
|
|
56
|
+
await vscode.workspace.fs.writeFile(tempUri, data);
|
|
57
|
+
try {
|
|
58
|
+
await vscode.workspace.fs.rename(tempUri, uri, { overwrite: true });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
await safeDelete(tempUri);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function readCacheFileOrRecover(uri) {
|
|
66
|
+
try {
|
|
67
|
+
return await vscode.workspace.fs.readFile(uri);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
await moveCorruptCacheAside(uri);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function moveCorruptCacheAside(uri) {
|
|
75
|
+
try {
|
|
76
|
+
await vscode.workspace.fs.rename(uri, uri.with({ path: `${uri.path}.corrupt-${Date.now()}` }), { overwrite: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Missing or unreadable cache can be rebuilt.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function safeDelete(uri) {
|
|
83
|
+
try {
|
|
84
|
+
await vscode.workspace.fs.delete(uri, { recursive: false, useTrash: false });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Best effort cleanup only.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function parentUri(uri) {
|
|
91
|
+
const index = uri.path.lastIndexOf("/");
|
|
92
|
+
return uri.with({ path: index > 0 ? uri.path.slice(0, index) : uri.path });
|
|
93
|
+
}
|
|
51
94
|
//# sourceMappingURL=cacheStore.js.map
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Phase 05 Sprint 5F-P23 - Task-Aware Config Route Probe Precision Repair
|
|
3
|
+
// Execution mode: disabled_prototype_report_only
|
|
4
|
+
//
|
|
5
|
+
// Pure deterministic probe. No production imports. No evaluation-specific fields in API.
|
|
6
|
+
// Task-aware: uses taskTokens (label-blind) to suppress generic config that doesn't
|
|
7
|
+
// match the task, while preserving task-relevant config.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PROBE_RULE_DEFS = exports.DEFAULT_PROBE_OPTIONS = exports.PROBE_RULE_FAMILIES = void 0;
|
|
10
|
+
exports.probeConfigRouteCandidates = probeConfigRouteCandidates;
|
|
11
|
+
const RULES = Object.freeze([
|
|
12
|
+
{ id: "framework_config_path_probe", priority: 0, patterns: ["config"], confidence: 0.8, signals: ["config_substring"] },
|
|
13
|
+
{ id: "route_entrypoint_probe", priority: 1, patterns: ["routes", "router"], confidence: 0.7, signals: ["route_substring"] },
|
|
14
|
+
{ id: "middleware_registration_probe", priority: 2, patterns: ["middleware"], confidence: 0.6, signals: ["middleware_substring"] },
|
|
15
|
+
{ id: "env_service_config_probe", priority: 3, patterns: [".env"], confidence: 0.5, signals: ["dotenv_match"] },
|
|
16
|
+
{ id: "schema_or_type_config_probe", priority: 4, patterns: ["schema"], confidence: 0.4, signals: ["schema_substring"] },
|
|
17
|
+
]);
|
|
18
|
+
exports.PROBE_RULE_DEFS = RULES;
|
|
19
|
+
// --- Exclusion ---
|
|
20
|
+
const EXCLUDE_DIRS = new Set([
|
|
21
|
+
"test", "tests", "spec", "__test__", "__tests__", "__mocks__",
|
|
22
|
+
"example", "examples", "sample", "samples", "fixture", "fixtures",
|
|
23
|
+
"doc", "docs", "documentation",
|
|
24
|
+
"mock", "mocks", "generated",
|
|
25
|
+
"node_modules", "dist", "build", "target", "vendor",
|
|
26
|
+
".next", "__pycache__", "coverage", ".nyc_output",
|
|
27
|
+
".git", ".svn", ".hg",
|
|
28
|
+
]);
|
|
29
|
+
const EXCLUDE_FILE_PATTERNS = [
|
|
30
|
+
".test.", ".spec.", ".mock.", ".snap", ".snapshot",
|
|
31
|
+
".lock", ".generated.", ".gen.", ".min.",
|
|
32
|
+
".d.ts",
|
|
33
|
+
];
|
|
34
|
+
// --- Task-aware generic operational terms ---
|
|
35
|
+
// Candidates whose filename tokens match ONLY these terms (and not the task) get suppressed.
|
|
36
|
+
const GENERIC_OPS_TERMS = new Set([
|
|
37
|
+
"logging", "logger", "log",
|
|
38
|
+
"cors",
|
|
39
|
+
"requestlogger",
|
|
40
|
+
"contenttype",
|
|
41
|
+
"metrics", "metric",
|
|
42
|
+
"ratelimiter", "ratelimit", "ratelimiting",
|
|
43
|
+
"database", "db",
|
|
44
|
+
"request", "response",
|
|
45
|
+
"validation", "validator", "validate",
|
|
46
|
+
"health", "readiness", "liveness", "monitoring", "healthcheck",
|
|
47
|
+
"development", "production", "staging", "local", "testing",
|
|
48
|
+
"error", "exception", "handler",
|
|
49
|
+
"scheduling", "schedule", "scheduler",
|
|
50
|
+
// Generic entrypoint noise (P24) — specific high-frequency patterns
|
|
51
|
+
"tsconfig",
|
|
52
|
+
"theme",
|
|
53
|
+
"app_config",
|
|
54
|
+
"app_router",
|
|
55
|
+
]);
|
|
56
|
+
// Related term groups for task relevance matching.
|
|
57
|
+
// Terms within a group are considered equivalent.
|
|
58
|
+
const RELATED_GROUPS = [
|
|
59
|
+
["mail", "email", "smtp", "notification", "notify", "notifications"],
|
|
60
|
+
["payment", "billing", "subscription", "invoice", "checkout"],
|
|
61
|
+
["auth", "login", "session", "security", "authentication", "oauth", "token", "credentials", "password"],
|
|
62
|
+
["queue", "job", "worker", "jobs", "workers", "background"],
|
|
63
|
+
["cache", "redis", "memcache", "caching"],
|
|
64
|
+
["project", "projects"],
|
|
65
|
+
["admin", "administrator"],
|
|
66
|
+
["featureflag", "featureflags", "feature", "flags", "flag"],
|
|
67
|
+
["settings", "config", "configuration", "app", "application"],
|
|
68
|
+
["routes", "route", "router", "routing", "endpoint", "endpoints"],
|
|
69
|
+
["storage", "store"],
|
|
70
|
+
["api", "rest", "graphql"],
|
|
71
|
+
["user", "users", "account", "accounts"],
|
|
72
|
+
["order", "orders"],
|
|
73
|
+
["product", "products"],
|
|
74
|
+
["blog", "post", "posts", "article", "articles", "feed"],
|
|
75
|
+
["analytics", "report", "reports", "dashboard"],
|
|
76
|
+
["middleware"],
|
|
77
|
+
["schema", "types", "type", "model", "models"],
|
|
78
|
+
];
|
|
79
|
+
const termToGroup = new Map();
|
|
80
|
+
for (let gi = 0; gi < RELATED_GROUPS.length; gi++) {
|
|
81
|
+
for (const t of RELATED_GROUPS[gi]) {
|
|
82
|
+
if (!termToGroup.has(t))
|
|
83
|
+
termToGroup.set(t, gi);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Stop words to filter from task text tokenization
|
|
87
|
+
const STOP_WORDS = new Set([
|
|
88
|
+
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
|
|
89
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
90
|
+
"should", "may", "might", "can", "shall", "to", "of", "in", "for",
|
|
91
|
+
"on", "with", "at", "by", "from", "as", "into", "through", "during",
|
|
92
|
+
"before", "after", "above", "below", "between", "and", "but", "or",
|
|
93
|
+
"not", "no", "nor", "so", "if", "then", "else", "when", "where",
|
|
94
|
+
"how", "all", "each", "every", "both", "few", "more", "most", "other",
|
|
95
|
+
"some", "such", "only", "own", "same", "that", "this", "these", "those",
|
|
96
|
+
"it", "its", "fix", "add", "update", "implement", "create", "remove",
|
|
97
|
+
]);
|
|
98
|
+
// --- Helpers ---
|
|
99
|
+
function normPath(p) {
|
|
100
|
+
return (p || "").replace(/\\/g, "/").toLowerCase();
|
|
101
|
+
}
|
|
102
|
+
function isExcluded(np) {
|
|
103
|
+
const segments = np.split("/");
|
|
104
|
+
for (let i = 0; i < segments.length; i++) {
|
|
105
|
+
const seg = segments[i];
|
|
106
|
+
if (!seg)
|
|
107
|
+
continue;
|
|
108
|
+
if (EXCLUDE_DIRS.has(seg))
|
|
109
|
+
return true;
|
|
110
|
+
if (seg.startsWith(".") && seg !== ".env" && i < segments.length - 1)
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
const filename = segments[segments.length - 1] || "";
|
|
114
|
+
for (let i = 0; i < EXCLUDE_FILE_PATTERNS.length; i++) {
|
|
115
|
+
if (filename.indexOf(EXCLUDE_FILE_PATTERNS[i]) >= 0)
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
function pathMatchesPattern(normalizedPath, pattern) {
|
|
121
|
+
return normalizedPath.indexOf(pattern.toLowerCase()) >= 0;
|
|
122
|
+
}
|
|
123
|
+
function isValidCandidate(c) {
|
|
124
|
+
return (typeof c.relativePath === "string" && c.relativePath.length > 0 &&
|
|
125
|
+
typeof c.ruleId === "string" && typeof c.reason === "string" &&
|
|
126
|
+
Number.isFinite(c.confidence) && c.confidence >= 0 && c.confidence <= 1 &&
|
|
127
|
+
Number.isFinite(c.rankHint) && Array.isArray(c.signals));
|
|
128
|
+
}
|
|
129
|
+
// --- Task tokenization ---
|
|
130
|
+
function tokenize(text) {
|
|
131
|
+
const tokens = [];
|
|
132
|
+
const parts = text.toLowerCase().split(/[^a-z0-9]+/);
|
|
133
|
+
for (let i = 0; i < parts.length; i++) {
|
|
134
|
+
const t = parts[i];
|
|
135
|
+
if (t.length >= 2 && !STOP_WORDS.has(t))
|
|
136
|
+
tokens.push(t);
|
|
137
|
+
}
|
|
138
|
+
return tokens;
|
|
139
|
+
}
|
|
140
|
+
function extractFilenameTokens(np) {
|
|
141
|
+
const segments = np.split("/");
|
|
142
|
+
const filename = segments[segments.length - 1] || "";
|
|
143
|
+
const base = filename.replace(/\.[^.]+$/, ""); // strip extension
|
|
144
|
+
return tokenize(base);
|
|
145
|
+
}
|
|
146
|
+
const COMMON_EXTENSIONS = new Set([
|
|
147
|
+
"ts", "js", "jsx", "tsx", "php", "py", "java", "go", "dart", "rb",
|
|
148
|
+
"cs", "swift", "kt", "rs", "scala", "clj", "ex", "exs", "erl",
|
|
149
|
+
"c", "cpp", "h", "hpp", "json", "xml", "yaml", "yml", "toml",
|
|
150
|
+
"ini", "cfg", "conf", "css", "scss", "less", "html", "htm",
|
|
151
|
+
"md", "txt", "sh", "bash", "zsh", "ps1", "bat", "cmd",
|
|
152
|
+
]);
|
|
153
|
+
function isGenericOnly(fileTokens) {
|
|
154
|
+
if (fileTokens.length === 0)
|
|
155
|
+
return false;
|
|
156
|
+
for (let i = 0; i < fileTokens.length; i++) {
|
|
157
|
+
if (COMMON_EXTENSIONS.has(fileTokens[i]))
|
|
158
|
+
continue;
|
|
159
|
+
if (!GENERIC_OPS_TERMS.has(fileTokens[i]))
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
function hasTaskRelevance(fileTokens, taskTokens) {
|
|
165
|
+
// Direct token match
|
|
166
|
+
for (let fi = 0; fi < fileTokens.length; fi++) {
|
|
167
|
+
for (let ti = 0; ti < taskTokens.length; ti++) {
|
|
168
|
+
if (fileTokens[fi] === taskTokens[ti])
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Group match: any file token shares a group with any task token
|
|
173
|
+
for (let fi = 0; fi < fileTokens.length; fi++) {
|
|
174
|
+
const fg = termToGroup.get(fileTokens[fi]);
|
|
175
|
+
if (fg === undefined)
|
|
176
|
+
continue;
|
|
177
|
+
for (let ti = 0; ti < taskTokens.length; ti++) {
|
|
178
|
+
const tg = termToGroup.get(taskTokens[ti]);
|
|
179
|
+
if (tg === fg)
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
// --- Task-aware candidate filter ---
|
|
186
|
+
// Returns: { confidence, reason, signals } or null if candidate should be excluded.
|
|
187
|
+
function applyTaskAwareness(baseConfidence, baseReason, baseSignals, np, taskTokens) {
|
|
188
|
+
if (taskTokens.length === 0) {
|
|
189
|
+
return { confidence: baseConfidence, reason: baseReason, signals: baseSignals };
|
|
190
|
+
}
|
|
191
|
+
const fileTokens = extractFilenameTokens(np);
|
|
192
|
+
const relevant = hasTaskRelevance(fileTokens, taskTokens);
|
|
193
|
+
const generic = isGenericOnly(fileTokens);
|
|
194
|
+
if (relevant) {
|
|
195
|
+
return { confidence: Math.min(1, baseConfidence + 0.05), reason: baseReason + "; task_relevant", signals: [...baseSignals, "task_relevant"] };
|
|
196
|
+
}
|
|
197
|
+
if (generic) {
|
|
198
|
+
// Exclude entirely — this is a generic operational config not supported by the task
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return { confidence: baseConfidence * 0.7, reason: baseReason + "; task_unrelated", signals: [...baseSignals, "task_unrelated"] };
|
|
202
|
+
}
|
|
203
|
+
// --- Main probe function ---
|
|
204
|
+
function probeConfigRouteCandidates(workspaceFiles, options) {
|
|
205
|
+
const maxPerRule = Math.max(0, Math.floor(options.maxCandidatesPerRule || 5));
|
|
206
|
+
const maxTotal = Math.max(0, Math.floor(options.maxTotalCandidates || 30));
|
|
207
|
+
if (!Array.isArray(workspaceFiles) || workspaceFiles.length === 0) {
|
|
208
|
+
return { candidates: [], ruleCounts: {}, totalMatched: 0, totalCapped: 0 };
|
|
209
|
+
}
|
|
210
|
+
// Resolve task tokens from options
|
|
211
|
+
let taskTokens = [];
|
|
212
|
+
if (options.taskTokens && Array.isArray(options.taskTokens) && options.taskTokens.length > 0) {
|
|
213
|
+
taskTokens = options.taskTokens.map((t) => t.toLowerCase());
|
|
214
|
+
}
|
|
215
|
+
else if (options.taskText && typeof options.taskText === "string" && options.taskText.length > 0) {
|
|
216
|
+
taskTokens = tokenize(options.taskText);
|
|
217
|
+
}
|
|
218
|
+
// Phase 0: exclude noise files
|
|
219
|
+
const eligible = [];
|
|
220
|
+
for (let fi = 0; fi < workspaceFiles.length; fi++) {
|
|
221
|
+
const f = workspaceFiles[fi];
|
|
222
|
+
if (!f || typeof f.relativePath !== "string")
|
|
223
|
+
continue;
|
|
224
|
+
if (isExcluded(normPath(f.relativePath)))
|
|
225
|
+
continue;
|
|
226
|
+
eligible.push(f);
|
|
227
|
+
}
|
|
228
|
+
// Phase 1: per-rule matching
|
|
229
|
+
const seen = new Map();
|
|
230
|
+
const ruleCounts = {};
|
|
231
|
+
let totalMatched = 0;
|
|
232
|
+
for (let ri = 0; ri < RULES.length; ri++) {
|
|
233
|
+
const rule = RULES[ri];
|
|
234
|
+
let ruleCount = 0;
|
|
235
|
+
for (let fi = 0; fi < eligible.length && ruleCount < maxPerRule; fi++) {
|
|
236
|
+
const f = eligible[fi];
|
|
237
|
+
const np = normPath(f.relativePath);
|
|
238
|
+
if (!np)
|
|
239
|
+
continue;
|
|
240
|
+
if (seen.has(np))
|
|
241
|
+
continue;
|
|
242
|
+
const matched = rule.patterns.some((p) => pathMatchesPattern(np, p));
|
|
243
|
+
if (!matched)
|
|
244
|
+
continue;
|
|
245
|
+
// Task-aware filtering
|
|
246
|
+
const adj = applyTaskAwareness(rule.confidence, `${rule.id}: matched path convention`, [...rule.signals], np, taskTokens);
|
|
247
|
+
if (adj === null)
|
|
248
|
+
continue; // excluded by task awareness
|
|
249
|
+
const candidate = {
|
|
250
|
+
relativePath: f.relativePath,
|
|
251
|
+
ruleId: rule.id,
|
|
252
|
+
reason: adj.reason,
|
|
253
|
+
confidence: adj.confidence,
|
|
254
|
+
rankHint: rule.priority * 100 + ruleCount,
|
|
255
|
+
signals: adj.signals,
|
|
256
|
+
};
|
|
257
|
+
if (!isValidCandidate(candidate))
|
|
258
|
+
continue;
|
|
259
|
+
seen.set(np, candidate);
|
|
260
|
+
ruleCount++;
|
|
261
|
+
totalMatched++;
|
|
262
|
+
}
|
|
263
|
+
ruleCounts[rule.id] = ruleCount;
|
|
264
|
+
}
|
|
265
|
+
// Phase 2: sort by confidence desc, priority asc, normalized path asc
|
|
266
|
+
const sorted = Array.from(seen.values()).sort((a, b) => {
|
|
267
|
+
const confDiff = b.confidence - a.confidence;
|
|
268
|
+
if (confDiff !== 0)
|
|
269
|
+
return confDiff;
|
|
270
|
+
const aRule = RULES.find((r) => r.id === a.ruleId);
|
|
271
|
+
const bRule = RULES.find((r) => r.id === b.ruleId);
|
|
272
|
+
const priDiff = (aRule?.priority ?? 99) - (bRule?.priority ?? 99);
|
|
273
|
+
if (priDiff !== 0)
|
|
274
|
+
return priDiff;
|
|
275
|
+
return normPath(a.relativePath).localeCompare(normPath(b.relativePath));
|
|
276
|
+
});
|
|
277
|
+
// Phase 3: apply maxTotal cap
|
|
278
|
+
const totalCapped = Math.max(0, sorted.length - maxTotal);
|
|
279
|
+
const candidates = sorted.slice(0, maxTotal);
|
|
280
|
+
return { candidates, ruleCounts, totalMatched, totalCapped };
|
|
281
|
+
}
|
|
282
|
+
// --- Exported constants ---
|
|
283
|
+
exports.PROBE_RULE_FAMILIES = Object.freeze(RULES.map((r) => r.id));
|
|
284
|
+
exports.DEFAULT_PROBE_OPTIONS = Object.freeze({
|
|
285
|
+
maxCandidatesPerRule: 5,
|
|
286
|
+
maxTotalCandidates: 30,
|
|
287
|
+
});
|
|
288
|
+
//# sourceMappingURL=configRouteDiscoveryProbe.js.map
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.searchSemanticFiles = searchSemanticFiles;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const embeddingProviders_1 = require("../gateway/embeddingProviders");
|
|
40
|
+
const vectorStore_1 = require("./vectorStore");
|
|
41
|
+
const embeddingVersion = "file-level-v1";
|
|
42
|
+
async function searchSemanticFiles(rootPath, index, task, config) {
|
|
43
|
+
if (!config.index.semanticEnabled) {
|
|
44
|
+
return { hits: [], status: "disabled" };
|
|
45
|
+
}
|
|
46
|
+
const provider = (0, embeddingProviders_1.createConfiguredEmbeddingProvider)({ ...config.index, workspacePath: rootPath });
|
|
47
|
+
if (!provider) {
|
|
48
|
+
return { hits: [], status: "skipped_no_key" };
|
|
49
|
+
}
|
|
50
|
+
const baseInfo = {
|
|
51
|
+
providerName: provider.name,
|
|
52
|
+
model: provider.model
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const files = index.files.slice(0, config.index.semanticMaxFilesPerRun);
|
|
56
|
+
const { records, cacheHits, cacheMisses } = await loadOrBuildFileEmbeddings(rootPath, files, provider, config);
|
|
57
|
+
if (records.length === 0) {
|
|
58
|
+
return { hits: [], status: "error_fallback", ...baseInfo, errorType: "no_records", errorMessage: "No embedding records were loaded or built", embeddedFiles: 0, cacheHits: 0, cacheMisses: 0 };
|
|
59
|
+
}
|
|
60
|
+
const [queryVector] = await provider.embedTexts([task]);
|
|
61
|
+
const store = new vectorStore_1.InMemoryVectorStore();
|
|
62
|
+
for (const record of records) {
|
|
63
|
+
store.upsert({ id: record.key, vector: record.vector, metadata: record });
|
|
64
|
+
}
|
|
65
|
+
const hits = store.search(queryVector, 100).map((item) => ({
|
|
66
|
+
relativePath: item.record.metadata.relativePath,
|
|
67
|
+
score: item.score,
|
|
68
|
+
reason: `semantic vector similarity ${item.score.toFixed(3)}`
|
|
69
|
+
}));
|
|
70
|
+
return { hits, status: "active", ...baseInfo, embeddedFiles: records.length, cacheHits, cacheMisses };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
+
const errorType = classifyEmbeddingError(message);
|
|
75
|
+
return { hits: [], status: "error_fallback", ...baseInfo, errorType, errorMessage: message, embeddedFiles: 0, cacheHits: 0, cacheMisses: 0 };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function classifyEmbeddingError(message) {
|
|
79
|
+
const lower = message.toLowerCase();
|
|
80
|
+
if (lower.includes("429") || lower.includes("quota") || lower.includes("resource_exhausted") || lower.includes("rate limit"))
|
|
81
|
+
return "quota_exceeded";
|
|
82
|
+
if (lower.includes("timeout") || lower.includes("abort") || lower.includes("etimedout"))
|
|
83
|
+
return "timeout";
|
|
84
|
+
if (lower.includes("401") || lower.includes("403") || lower.includes("unauthenticated") || lower.includes("permission_denied"))
|
|
85
|
+
return "auth_error";
|
|
86
|
+
if (lower.includes("404") || lower.includes("not_found"))
|
|
87
|
+
return "model_not_found";
|
|
88
|
+
if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("dns"))
|
|
89
|
+
return "network";
|
|
90
|
+
return "unknown";
|
|
91
|
+
}
|
|
92
|
+
async function loadOrBuildFileEmbeddings(rootPath, files, provider, config) {
|
|
93
|
+
const cache = config.index.semanticCache ? await readEmbeddingCache(rootPath) : new Map();
|
|
94
|
+
const records = [];
|
|
95
|
+
const missing = [];
|
|
96
|
+
let cacheHits = 0;
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const key = cacheKey(file, provider);
|
|
99
|
+
const cached = cache.get(key);
|
|
100
|
+
if (cached) {
|
|
101
|
+
records.push(cached);
|
|
102
|
+
cacheHits++;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
missing.push(file);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const batchSize = 32;
|
|
109
|
+
let lastWrite = 0;
|
|
110
|
+
for (let index = 0; index < missing.length; index += batchSize) {
|
|
111
|
+
const batch = missing.slice(index, index + batchSize);
|
|
112
|
+
const embeddings = await provider.embedTexts(batch.map((f) => fileEmbeddingText(f, provider.model)));
|
|
113
|
+
embeddings.forEach((vector, offset) => {
|
|
114
|
+
const file = batch[offset];
|
|
115
|
+
const record = {
|
|
116
|
+
key: cacheKey(file, provider),
|
|
117
|
+
relativePath: file.relativePath,
|
|
118
|
+
contentHash: file.contentHash,
|
|
119
|
+
model: provider.model,
|
|
120
|
+
embeddingVersion,
|
|
121
|
+
vector
|
|
122
|
+
};
|
|
123
|
+
cache.set(record.key, record);
|
|
124
|
+
records.push(record);
|
|
125
|
+
});
|
|
126
|
+
// Write cache after every 4 batches for resilience against interruption
|
|
127
|
+
if (index - lastWrite >= batchSize * 4 && config.index.semanticCache) {
|
|
128
|
+
try {
|
|
129
|
+
await writeEmbeddingCache(rootPath, cache);
|
|
130
|
+
}
|
|
131
|
+
catch { /* non-fatal */ }
|
|
132
|
+
lastWrite = index;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (config.index.semanticCache && missing.length > 0) {
|
|
136
|
+
try {
|
|
137
|
+
await writeEmbeddingCache(rootPath, cache);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Semantic cache failures must not break deterministic retrieval.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { records, cacheHits, cacheMisses: missing.length };
|
|
144
|
+
}
|
|
145
|
+
function isCodeEmbeddingModel(model) {
|
|
146
|
+
return model === "jina-code-embeddings-0.5b";
|
|
147
|
+
}
|
|
148
|
+
function embeddingTextFormatVersion(model) {
|
|
149
|
+
return isCodeEmbeddingModel(model) ? "jina-code-compact-v1" : "general-v1";
|
|
150
|
+
}
|
|
151
|
+
function fileEmbeddingText(file, model) {
|
|
152
|
+
const isCompact = model ? isCodeEmbeddingModel(model) : false;
|
|
153
|
+
const maxTokens = isCompact ? 100 : 400;
|
|
154
|
+
const maxSymbols = isCompact ? 20 : 30;
|
|
155
|
+
const maxImports = isCompact ? 20 : 30;
|
|
156
|
+
return [
|
|
157
|
+
`path: ${file.relativePath}`,
|
|
158
|
+
file.language ? `language: ${file.language}` : "",
|
|
159
|
+
file.subsystem ? `subsystem: ${file.subsystem}` : "",
|
|
160
|
+
file.symbols.length > 0 ? `symbols: ${file.symbols.slice(0, maxSymbols).map((symbol) => symbol.name).join(", ")}` : "",
|
|
161
|
+
file.imports.length > 0 ? `imports: ${file.imports.slice(0, maxImports).join(", ")}` : "",
|
|
162
|
+
file.tokens.slice(0, maxTokens).join(" ")
|
|
163
|
+
].filter(Boolean).join("\n");
|
|
164
|
+
}
|
|
165
|
+
function cacheKey(file, provider) {
|
|
166
|
+
const fmtVersion = embeddingTextFormatVersion(provider.model);
|
|
167
|
+
return `${provider.name}:${provider.model}:${embeddingVersion}:${fmtVersion}:${file.extractorVersion}:${file.chunkerVersion}:${file.contentHash}:${file.relativePath}`;
|
|
168
|
+
}
|
|
169
|
+
async function readEmbeddingCache(rootPath) {
|
|
170
|
+
try {
|
|
171
|
+
const text = await fs.readFile(embeddingCachePath(rootPath), "utf8");
|
|
172
|
+
const records = JSON.parse(text);
|
|
173
|
+
return new Map(records.filter(isEmbeddingCacheRecord).map((record) => [record.key, record]));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return new Map();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function writeEmbeddingCache(rootPath, cache) {
|
|
180
|
+
const filePath = embeddingCachePath(rootPath);
|
|
181
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
182
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
183
|
+
await fs.writeFile(tempPath, JSON.stringify(Array.from(cache.values())), "utf8");
|
|
184
|
+
await fs.rename(tempPath, filePath);
|
|
185
|
+
}
|
|
186
|
+
function embeddingCachePath(rootPath) {
|
|
187
|
+
return path.join(path.resolve(rootPath), ".flowseeker", "cache", "embeddings", "file-embeddings.json");
|
|
188
|
+
}
|
|
189
|
+
function isEmbeddingCacheRecord(value) {
|
|
190
|
+
const record = value;
|
|
191
|
+
return Boolean(record && typeof record.key === "string" && typeof record.relativePath === "string" && Array.isArray(record.vector));
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=embeddingIndex.js.map
|