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.
Files changed (39) hide show
  1. package/.env.example +7 -0
  2. package/CHANGELOG.md +131 -108
  3. package/README.md +288 -221
  4. package/dist/cli/flowCommand.js +175 -0
  5. package/dist/cli/main.js +1794 -0
  6. package/dist/cli/mcpServer.js +7 -1
  7. package/dist/cli/runEvaluation.js +178 -2
  8. package/dist/config/defaultConfig.js +11 -1
  9. package/dist/config/env.js +118 -0
  10. package/dist/config/loadConfig.js +18 -1
  11. package/dist/config/loadConfigFromPath.js +3 -1
  12. package/dist/extension.js +23 -0
  13. package/dist/gateway/embeddingProviders.js +852 -0
  14. package/dist/index/cacheStore.js +43 -0
  15. package/dist/index/configRouteDiscoveryProbe.js +288 -0
  16. package/dist/index/embeddingIndex.js +193 -0
  17. package/dist/index/graphIndex.js +460 -0
  18. package/dist/index/indexWatcher.js +86 -0
  19. package/dist/index/structuredExtractor.js +303 -12
  20. package/dist/index/treeSitterExtractor.js +264 -0
  21. package/dist/index/vectorStore.js +41 -0
  22. package/dist/index/workspaceIndex.js +591 -26
  23. package/dist/mcp/mcpTools.js +51 -0
  24. package/dist/pipeline/contextPack.js +3 -3
  25. package/dist/pipeline/deterministicReranker.js +358 -0
  26. package/dist/pipeline/evaluationMetrics.js +14 -2
  27. package/dist/pipeline/fileGroups.js +7 -1
  28. package/dist/pipeline/fileScanner.js +93 -11
  29. package/dist/pipeline/llmReranker.js +151 -0
  30. package/dist/pipeline/nodeScan.js +91 -12
  31. package/dist/pipeline/ranker.js +875 -16
  32. package/dist/pipeline/retrievalFusion.js +41 -0
  33. package/dist/pipeline/runHeadless.js +35 -0
  34. package/dist/pipeline/solvePacket.js +549 -42
  35. package/dist/pipeline/subsystem.js +21 -0
  36. package/docs/demo-screenshot-checklist.md +86 -0
  37. package/docs/marketplace-copy.md +92 -0
  38. package/docs/mcp-onboarding.md +191 -0
  39. package/package.json +633 -561
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expandIndexedGraph = expandIndexedGraph;
4
+ exports.buildIndexedGraph = buildIndexedGraph;
5
+ exports.getEdgesFrom = getEdgesFrom;
6
+ exports.getEdgesTo = getEdgesTo;
7
+ exports.getEdgesByKind = getEdgesByKind;
8
+ exports.getNodesByPath = getNodesByPath;
9
+ exports.getNodeById = getNodeById;
10
+ exports.dedupeGraphEdges = dedupeGraphEdges;
11
+ exports.extractGraphFromIndex = extractGraphFromIndex;
12
+ function expandIndexedGraph(index, seedPaths, options = {}) {
13
+ const lookup = buildGraphLookup(index);
14
+ const maxDepth = options.maxDepth ?? 2;
15
+ const maxEdgesPerSeed = options.maxEdgesPerSeed ?? 12;
16
+ const maxAddedFiles = options.maxAddedFiles ?? 25;
17
+ const maxPerEdgeType = options.maxPerEdgeType ?? 20;
18
+ const seen = new Set(seedPaths);
19
+ const edgeTypeCounts = new Map();
20
+ const queue = seedPaths.map((relativePath) => ({ relativePath, depth: 0 }));
21
+ const expanded = [];
22
+ while (queue.length > 0 && expanded.length < maxAddedFiles) {
23
+ const current = queue.shift();
24
+ if (!current || current.depth >= maxDepth) {
25
+ continue;
26
+ }
27
+ const edges = edgesForPath(current.relativePath, lookup).slice(0, maxEdgesPerSeed);
28
+ for (const edge of edges) {
29
+ if ((edgeTypeCounts.get(edge.type) ?? 0) >= maxPerEdgeType) {
30
+ continue;
31
+ }
32
+ const targets = targetFiles(edge, lookup);
33
+ for (const file of targets) {
34
+ if (seen.has(file.relativePath) || expanded.length >= maxAddedFiles) {
35
+ continue;
36
+ }
37
+ seen.add(file.relativePath);
38
+ edgeTypeCounts.set(edge.type, (edgeTypeCounts.get(edge.type) ?? 0) + 1);
39
+ expanded.push({
40
+ file,
41
+ distance: current.depth + 1,
42
+ viaEdge: edge,
43
+ reason: `${edge.type} from ${edge.fromPath || current.relativePath}: ${edge.reason}`
44
+ });
45
+ queue.push({ relativePath: file.relativePath, depth: current.depth + 1 });
46
+ }
47
+ }
48
+ }
49
+ return expanded;
50
+ }
51
+ function buildGraphLookup(index) {
52
+ const byFromPath = new Map();
53
+ const byToPath = new Map();
54
+ const bySymbolOrCall = new Map();
55
+ const byPath = new Map(index.files.map((file) => [file.relativePath, file]));
56
+ for (const file of index.files) {
57
+ for (const edge of file.edges ?? []) {
58
+ addEdge(byFromPath, edge.fromPath, edge);
59
+ addEdge(byToPath, edge.toPath, edge);
60
+ }
61
+ for (const symbol of file.symbols ?? []) {
62
+ addFile(bySymbolOrCall, normalizeName(symbol.name), file);
63
+ addFile(bySymbolOrCall, normalizeName(symbol.fqn), file);
64
+ }
65
+ }
66
+ return { byFromPath, byToPath, bySymbolOrCall, byPath };
67
+ }
68
+ function edgesForPath(relativePath, lookup) {
69
+ return [...(lookup.byFromPath.get(relativePath) ?? []), ...(lookup.byToPath.get(relativePath) ?? [])];
70
+ }
71
+ function targetFiles(edge, lookup) {
72
+ const byDirectPath = [lookup.byPath.get(edge.toPath), lookup.byPath.get(edge.fromPath)].filter((file) => Boolean(file));
73
+ const bySymbol = lookup.bySymbolOrCall.get(normalizeName(edge.to)) ?? [];
74
+ return uniqueByPath([...byDirectPath, ...bySymbol]);
75
+ }
76
+ function addEdge(map, key, edge) {
77
+ if (!key) {
78
+ return;
79
+ }
80
+ map.set(key, [...(map.get(key) ?? []), edge]);
81
+ }
82
+ function addFile(map, key, file) {
83
+ if (!key) {
84
+ return;
85
+ }
86
+ map.set(key, [...(map.get(key) ?? []), file]);
87
+ }
88
+ function normalizeName(value) {
89
+ return value.toLowerCase().split(/\.|::|->/).filter(Boolean).pop() ?? value.toLowerCase();
90
+ }
91
+ function uniqueByPath(files) {
92
+ const seen = new Set();
93
+ return files.filter((file) => {
94
+ if (seen.has(file.relativePath)) {
95
+ return false;
96
+ }
97
+ seen.add(file.relativePath);
98
+ return true;
99
+ });
100
+ }
101
+ function norm(p) {
102
+ return (p || "").replace(/\\/g, "/");
103
+ }
104
+ function buildIndexedGraph(input) {
105
+ return {
106
+ nodes: input.nodes.map((n) => ({ ...n, relativePath: norm(n.relativePath) })),
107
+ edges: input.edges.map((e) => ({
108
+ ...e,
109
+ fromPath: norm(e.fromPath),
110
+ toPath: norm(e.toPath)
111
+ }))
112
+ };
113
+ }
114
+ function getEdgesFrom(index, nodeOrPath, options) {
115
+ const id = typeof nodeOrPath === "string" ? nodeOrPath : nodeOrPath.id;
116
+ const np = norm(typeof nodeOrPath === "string" ? nodeOrPath : nodeOrPath.relativePath);
117
+ let results = index.edges.filter((e) => e.fromId === id || norm(e.fromPath) === np);
118
+ if (options?.kind) {
119
+ results = results.filter((e) => e.kind === options.kind);
120
+ }
121
+ if (options?.maxResults !== undefined && results.length > options.maxResults) {
122
+ results = results.slice(0, options.maxResults);
123
+ }
124
+ return results;
125
+ }
126
+ function getEdgesTo(index, nodeOrPath, options) {
127
+ const id = typeof nodeOrPath === "string" ? nodeOrPath : nodeOrPath.id;
128
+ const np = norm(typeof nodeOrPath === "string" ? nodeOrPath : nodeOrPath.relativePath);
129
+ let results = index.edges.filter((e) => e.toId === id || norm(e.toPath) === np);
130
+ if (options?.kind) {
131
+ results = results.filter((e) => e.kind === options.kind);
132
+ }
133
+ if (options?.maxResults !== undefined && results.length > options.maxResults) {
134
+ results = results.slice(0, options.maxResults);
135
+ }
136
+ return results;
137
+ }
138
+ function getEdgesByKind(index, kind, options) {
139
+ let results = index.edges.filter((e) => e.kind === kind);
140
+ if (options?.maxResults !== undefined && results.length > options.maxResults) {
141
+ results = results.slice(0, options.maxResults);
142
+ }
143
+ return results;
144
+ }
145
+ function getNodesByPath(index, relativePath) {
146
+ const np = norm(relativePath);
147
+ return index.nodes.filter((n) => n.relativePath === np);
148
+ }
149
+ function getNodeById(index, id) {
150
+ return index.nodes.find((n) => n.id === id);
151
+ }
152
+ function structuralKey(e) {
153
+ return `${e.kind}|${e.fromId}|${e.toId}|${norm(e.fromPath)}|${norm(e.toPath)}`;
154
+ }
155
+ function dedupeGraphEdges(edges) {
156
+ const byId = new Map();
157
+ const byStructural = new Map();
158
+ for (const edge of edges) {
159
+ const sk = structuralKey(edge);
160
+ // Dedupe by stable id when present
161
+ if (edge.id) {
162
+ const existingById = byId.get(edge.id);
163
+ const existingBySk = byStructural.get(sk);
164
+ if (existingById || existingBySk) {
165
+ const best = pickHigherConfidence([existingById, existingBySk, edge].filter(Boolean));
166
+ byId.set(edge.id, best);
167
+ byStructural.set(sk, best);
168
+ continue;
169
+ }
170
+ byId.set(edge.id, edge);
171
+ byStructural.set(sk, edge);
172
+ continue;
173
+ }
174
+ // Dedupe by structural key when no id
175
+ const existing = byStructural.get(sk);
176
+ if (!existing || edge.confidence > existing.confidence) {
177
+ byStructural.set(sk, edge);
178
+ }
179
+ }
180
+ // Merge: collect unique edges from both maps, prefer higher confidence
181
+ const seen = new Map();
182
+ for (const e of [...byId.values(), ...byStructural.values()]) {
183
+ const sk = structuralKey(e);
184
+ const existing = seen.get(sk);
185
+ if (!existing || e.confidence > existing.confidence) {
186
+ seen.set(sk, e);
187
+ }
188
+ }
189
+ return Array.from(seen.values());
190
+ }
191
+ function pickHigherConfidence(edges) {
192
+ return edges.reduce((a, b) => (a.confidence >= b.confidence ? a : b));
193
+ }
194
+ const AMBIGUOUS_CALL_NAMES = new Set([
195
+ "handle", "index", "show", "create", "store", "update", "delete",
196
+ "destroy", "run", "execute", "dispatch", "complete", "process",
197
+ "render", "boot", "register"
198
+ ]);
199
+ function isAmbiguousCallName(name) {
200
+ return AMBIGUOUS_CALL_NAMES.has(name.toLowerCase());
201
+ }
202
+ // Count how many files define a symbol with this name
203
+ function countSymbolMatches(name, symbolIndex) {
204
+ return symbolIndex.get(name.toLowerCase())?.length ?? 0;
205
+ }
206
+ // Check if a call name resolves deterministically:
207
+ // - only one matching symbol exists across the workspace
208
+ // - OR the caller imports a file that contains exactly one matching symbol
209
+ function isDeterministicCallTarget(callName, fromFile, symbolIndex) {
210
+ const matches = symbolIndex.get(callName.toLowerCase()) ?? [];
211
+ if (matches.length === 1)
212
+ return true;
213
+ // Check if any import resolves to a file with exactly one match
214
+ for (const imp of fromFile.imports) {
215
+ const impBase = imp.replace(/\\/g, "/").split("/").pop()?.replace(/\.[^.]+$/, "").toLowerCase() || "";
216
+ // Check each match — if a match's file basename matches the import basename
217
+ for (const m of matches) {
218
+ const matchBase = m.np.split("/").pop()?.replace(/\.[^.]+$/, "").toLowerCase() || "";
219
+ if (matchBase === impBase)
220
+ return true;
221
+ }
222
+ }
223
+ return false;
224
+ }
225
+ function extractGraphFromIndex(index) {
226
+ const stats = {
227
+ duplicatesRemoved: 0,
228
+ unresolvedImportsSkipped: 0,
229
+ unresolvedLocalImportsSkipped: 0,
230
+ packageImportsSkipped: 0,
231
+ emptyPathEdgesSkipped: 0,
232
+ selfEdgesSkipped: 0,
233
+ ambiguousCallsSkipped: 0,
234
+ unresolvedCallsSkipped: 0,
235
+ callsKept: 0,
236
+ callsSkippedByAmbiguousName: {}
237
+ };
238
+ const pathIndex = new Map();
239
+ for (const file of index.files) {
240
+ pathIndex.set(norm(file.relativePath), file);
241
+ }
242
+ // File nodes: one per indexed file
243
+ const nodes = [];
244
+ const nodeIds = new Set();
245
+ for (const file of index.files) {
246
+ const np = norm(file.relativePath);
247
+ const id = `file:${np}`;
248
+ if (nodeIds.has(id))
249
+ continue;
250
+ nodeIds.add(id);
251
+ nodes.push({
252
+ id,
253
+ kind: "file",
254
+ relativePath: np,
255
+ label: np.split("/").pop() || np
256
+ });
257
+ }
258
+ // Symbol nodes: for files with symbol data
259
+ const symbolIndex = new Map(); // normalized name → file lookup
260
+ for (const file of index.files) {
261
+ const np = norm(file.relativePath);
262
+ for (const sym of file.symbols) {
263
+ const sid = `symbol:${np}:${sym.name}`;
264
+ if (nodeIds.has(sid))
265
+ continue;
266
+ nodeIds.add(sid);
267
+ nodes.push({
268
+ id: sid,
269
+ kind: "symbol",
270
+ relativePath: np,
271
+ label: sym.name,
272
+ symbolFqn: sym.fqn || sym.name,
273
+ range: sym.line > 0 ? { startLine: sym.line, endLine: sym.endLine || sym.line } : undefined
274
+ });
275
+ const symName = sym.name ? sym.name.toLowerCase() : "";
276
+ if (symName) {
277
+ const list = symbolIndex.get(symName) ?? [];
278
+ list.push({ file, np });
279
+ symbolIndex.set(symName, list);
280
+ }
281
+ }
282
+ }
283
+ const edges = [];
284
+ let edgeCounter = 0;
285
+ for (const file of index.files) {
286
+ const fromNp = norm(file.relativePath);
287
+ const fromFileId = `file:${fromNp}`;
288
+ // Import edges
289
+ for (const imp of file.imports) {
290
+ if (!imp || imp.trim() === "") {
291
+ stats.emptyPathEdgesSkipped++;
292
+ continue;
293
+ }
294
+ const impNorm = imp.replace(/\\/g, "/");
295
+ // Distinguish package imports from unresolved local imports
296
+ const isPackage = !impNorm.startsWith(".") && !impNorm.startsWith("/");
297
+ const resolved = resolveImportPath(fromNp, imp, pathIndex);
298
+ if (!resolved) {
299
+ stats.unresolvedImportsSkipped++;
300
+ if (isPackage) {
301
+ stats.packageImportsSkipped++;
302
+ }
303
+ else {
304
+ stats.unresolvedLocalImportsSkipped++;
305
+ }
306
+ continue;
307
+ }
308
+ const toNp = norm(resolved);
309
+ // Skip self-edges for imports of the same file
310
+ if (toNp === fromNp) {
311
+ stats.selfEdgesSkipped++;
312
+ continue;
313
+ }
314
+ const toFileId = `file:${toNp}`;
315
+ edgeCounter++;
316
+ edges.push({
317
+ id: `import:${edgeCounter}:${fromNp}->${toNp}`,
318
+ kind: "import",
319
+ fromId: fromFileId,
320
+ toId: toFileId,
321
+ fromPath: fromNp,
322
+ toPath: toNp,
323
+ confidence: 0.85,
324
+ source: "import",
325
+ reason: `imports ${imp}`
326
+ });
327
+ }
328
+ // Call edges
329
+ for (const call of file.calls || []) {
330
+ if (!call || call.trim() === "") {
331
+ stats.emptyPathEdgesSkipped++;
332
+ continue;
333
+ }
334
+ const callName = call.toLowerCase();
335
+ const targets = symbolIndex.get(callName);
336
+ if (!targets || targets.length === 0) {
337
+ stats.unresolvedCallsSkipped++;
338
+ continue;
339
+ }
340
+ // Ambiguity guard: skip ambiguous call names unless deterministic
341
+ if (isAmbiguousCallName(callName)) {
342
+ if (!isDeterministicCallTarget(callName, file, symbolIndex)) {
343
+ stats.ambiguousCallsSkipped++;
344
+ stats.callsSkippedByAmbiguousName[callName] = (stats.callsSkippedByAmbiguousName[callName] ?? 0) + 1;
345
+ continue;
346
+ }
347
+ }
348
+ // Use the first target (there should be exactly 1 for deterministic)
349
+ // For ambiguous names that passed the guard, prefer target from import
350
+ let target = targets[0];
351
+ if (isAmbiguousCallName(callName) && targets.length > 1) {
352
+ const impBase = new Set(file.imports.map(imp => imp.replace(/\\/g, "/").split("/").pop()?.replace(/\.[^.]+$/, "").toLowerCase() || ""));
353
+ const matching = targets.find(t => impBase.has(t.np.split("/").pop()?.replace(/\.[^.]+$/, "").toLowerCase() || ""));
354
+ if (matching)
355
+ target = matching;
356
+ }
357
+ const toNp = norm(target.file.relativePath);
358
+ // Skip self-calls
359
+ if (toNp === fromNp) {
360
+ stats.selfEdgesSkipped++;
361
+ continue;
362
+ }
363
+ const fromSymbolId = file.symbols.length > 0
364
+ ? `symbol:${fromNp}:${file.symbols[0].name}`
365
+ : fromFileId;
366
+ const toSymbolId = `symbol:${toNp}:${call}`;
367
+ edgeCounter++;
368
+ edges.push({
369
+ id: `call:${edgeCounter}:${fromNp}->${toNp}`,
370
+ kind: "calls",
371
+ fromId: fromSymbolId,
372
+ toId: toSymbolId,
373
+ fromPath: fromNp,
374
+ toPath: toNp,
375
+ confidence: 0.70,
376
+ source: "parser",
377
+ reason: `calls ${call}`
378
+ });
379
+ stats.callsKept++;
380
+ }
381
+ }
382
+ // Deduplicate
383
+ const deduped = dedupeGraphEdges(edges);
384
+ stats.duplicatesRemoved = edges.length - deduped.length;
385
+ return {
386
+ graph: { nodes, edges: deduped },
387
+ stats
388
+ };
389
+ }
390
+ function resolveImportPath(fromFile, importSource, pathIndex) {
391
+ // Normalize the import source
392
+ let imp = importSource.replace(/\\/g, "/");
393
+ // Helper: check if pathIndex has a file, trying original and normalized case
394
+ const lookupIndex = (candidate) => {
395
+ if (pathIndex.has(candidate))
396
+ return candidate;
397
+ const lower = candidate.toLowerCase();
398
+ if (pathIndex.has(lower))
399
+ return lower;
400
+ // Also try finding a case-insensitive match
401
+ for (const key of pathIndex.keys()) {
402
+ if (key.toLowerCase() === lower)
403
+ return key;
404
+ }
405
+ return null;
406
+ };
407
+ // Skip package imports (no leading . or /)
408
+ if (!imp.startsWith(".") && !imp.startsWith("/")) {
409
+ // Workspace-relative path (e.g., "app/Services/PaymentService")
410
+ if (imp.includes("/")) {
411
+ for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".php", ".py", ".dart", ".java", ".go"]) {
412
+ const result = lookupIndex(imp + ext);
413
+ if (result)
414
+ return result;
415
+ }
416
+ const direct = lookupIndex(imp);
417
+ if (direct)
418
+ return direct;
419
+ }
420
+ // Basename matching for simple names
421
+ const base = imp.split("/").pop()?.toLowerCase() || "";
422
+ if (base) {
423
+ for (const [filePath] of pathIndex) {
424
+ const fileBase = filePath.split("/").pop()?.replace(/\.[^.]+$/, "").toLowerCase() || "";
425
+ if (fileBase === base)
426
+ return filePath;
427
+ }
428
+ }
429
+ return null;
430
+ }
431
+ // Relative import: resolve from fromFile directory
432
+ const fromDir = fromFile.includes("/") ? fromFile.slice(0, fromFile.lastIndexOf("/")) : "";
433
+ const parts = fromDir ? fromDir.split("/") : [];
434
+ for (const seg of imp.split("/")) {
435
+ if (seg === "..") {
436
+ parts.pop();
437
+ }
438
+ else if (seg !== ".") {
439
+ parts.push(seg);
440
+ }
441
+ }
442
+ const candidate = parts.join("/");
443
+ const directHit = lookupIndex(candidate);
444
+ if (directHit)
445
+ return directHit;
446
+ // Try common extensions
447
+ for (const ext of [".ts", ".tsx", ".js", ".jsx", ".php", ".py", ".dart", ".java", ".go", ".vue", ".svelte"]) {
448
+ const result = lookupIndex(candidate + ext);
449
+ if (result)
450
+ return result;
451
+ }
452
+ // Try index files
453
+ for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx", "/index.php"]) {
454
+ const result = lookupIndex(candidate + idx);
455
+ if (result)
456
+ return result;
457
+ }
458
+ return null;
459
+ }
460
+ //# sourceMappingURL=graphIndex.js.map
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ // Phase 06 Sprint 6E-R1 — VS Code Index Watcher: Debounce, Batch, Invalidation Callback
3
+ // Tracks workspace file changes for safe index invalidation.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.createIndexWatcher = createIndexWatcher;
6
+ exports.onFileChanged = onFileChanged;
7
+ exports.onFileDeleted = onFileDeleted;
8
+ exports.onFileCreated = onFileCreated;
9
+ exports.flush = flush;
10
+ exports.dispose = dispose;
11
+ function createIndexWatcher(options) {
12
+ return {
13
+ changedPaths: new Set(),
14
+ deletedPaths: new Set(),
15
+ timer: null,
16
+ options,
17
+ };
18
+ }
19
+ function onFileChanged(state, relativePath) {
20
+ if (isIgnored(relativePath, state.options))
21
+ return;
22
+ const np = normalizePath(relativePath);
23
+ state.changedPaths.add(np);
24
+ state.deletedPaths.delete(np);
25
+ scheduleFlush(state);
26
+ }
27
+ function onFileDeleted(state, relativePath) {
28
+ if (isIgnored(relativePath, state.options))
29
+ return;
30
+ const np = normalizePath(relativePath);
31
+ state.deletedPaths.add(np);
32
+ state.changedPaths.delete(np);
33
+ scheduleFlush(state);
34
+ }
35
+ function onFileCreated(state, relativePath) {
36
+ if (isIgnored(relativePath, state.options))
37
+ return;
38
+ const np = normalizePath(relativePath);
39
+ state.changedPaths.add(np);
40
+ scheduleFlush(state);
41
+ }
42
+ function flush(state) {
43
+ if (state.timer !== null) {
44
+ clearTimeout(state.timer);
45
+ state.timer = null;
46
+ }
47
+ const changed = Array.from(state.changedPaths);
48
+ const deleted = Array.from(state.deletedPaths);
49
+ state.changedPaths.clear();
50
+ state.deletedPaths.clear();
51
+ if (state.options.onFlush) {
52
+ state.options.onFlush(changed, deleted);
53
+ }
54
+ return { changed, deleted };
55
+ }
56
+ function dispose(state) {
57
+ if (state.timer !== null) {
58
+ clearTimeout(state.timer);
59
+ state.timer = null;
60
+ }
61
+ state.changedPaths.clear();
62
+ state.deletedPaths.clear();
63
+ }
64
+ function scheduleFlush(state) {
65
+ if (state.timer !== null)
66
+ return;
67
+ state.timer = setTimeout(() => {
68
+ flush(state);
69
+ }, state.options.debounceMs);
70
+ }
71
+ function normalizePath(p) {
72
+ return (p || "").replace(/\\/g, "/").toLowerCase();
73
+ }
74
+ function isIgnored(relativePath, options) {
75
+ const np = normalizePath(relativePath);
76
+ const noiseDirs = ["node_modules", ".git", ".flowseeker", "dist", "build", "coverage", ".next", "vendor", "__pycache__"];
77
+ const segments = np.split("/");
78
+ for (const seg of segments) {
79
+ if (noiseDirs.includes(seg))
80
+ return true;
81
+ if (seg.startsWith(".") && seg !== ".env")
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ //# sourceMappingURL=indexWatcher.js.map