flowseeker 0.1.7 → 0.1.9

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 (50) hide show
  1. package/.env.example +7 -0
  2. package/CHANGELOG.md +143 -108
  3. package/README.md +288 -221
  4. package/dist/chat/nativeChatParticipant.js +1 -1
  5. package/dist/cli/flowCommand.js +175 -0
  6. package/dist/cli/main.js +1810 -0
  7. package/dist/cli/mcpServer.js +7 -1
  8. package/dist/cli/runEvaluation.js +281 -2
  9. package/dist/config/defaultConfig.js +11 -1
  10. package/dist/config/env.js +118 -0
  11. package/dist/config/loadConfig.js +18 -1
  12. package/dist/config/loadConfigFromPath.js +3 -1
  13. package/dist/eval/accuracyV2.js +483 -0
  14. package/dist/eval/goldenTask.js +192 -0
  15. package/dist/extension.js +23 -0
  16. package/dist/framework/laravel.js +177 -0
  17. package/dist/gateway/embeddingProviders.js +852 -0
  18. package/dist/index/cacheStore.js +43 -0
  19. package/dist/index/configRouteDiscoveryProbe.js +288 -0
  20. package/dist/index/embeddingIndex.js +193 -0
  21. package/dist/index/graphIndex.js +460 -0
  22. package/dist/index/indexWatcher.js +86 -0
  23. package/dist/index/semanticChunkIndex.js +388 -0
  24. package/dist/index/structuredExtractor.js +303 -12
  25. package/dist/index/treeSitterExtractor.js +264 -0
  26. package/dist/index/vectorStore.js +41 -0
  27. package/dist/index/workspaceIndex.js +901 -26
  28. package/dist/mcp/mcpTools.js +1678 -2
  29. package/dist/pipeline/contextBlueprint.js +15 -2
  30. package/dist/pipeline/contextPack.js +3 -3
  31. package/dist/pipeline/deterministicReranker.js +358 -0
  32. package/dist/pipeline/evaluationMetrics.js +14 -2
  33. package/dist/pipeline/fileGroups.js +7 -1
  34. package/dist/pipeline/fileScanner.js +209 -12
  35. package/dist/pipeline/fusionTrace.js +149 -0
  36. package/dist/pipeline/llmReranker.js +151 -0
  37. package/dist/pipeline/nodeScan.js +102 -12
  38. package/dist/pipeline/ranker.js +875 -16
  39. package/dist/pipeline/retrievalFusion.js +41 -0
  40. package/dist/pipeline/roleRefinement.js +62 -0
  41. package/dist/pipeline/runHeadless.js +60 -5
  42. package/dist/pipeline/runPipeline.js +2 -2
  43. package/dist/pipeline/solvePacket.js +656 -43
  44. package/dist/pipeline/subsystem.js +21 -0
  45. package/dist/pipeline/taskUnderstanding.js +2 -2
  46. package/dist/ui/chatViewProvider.js +1 -1
  47. package/docs/demo-screenshot-checklist.md +86 -0
  48. package/docs/marketplace-copy.md +92 -0
  49. package/docs/mcp-onboarding.md +191 -0
  50. package/package.json +633 -561
@@ -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