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,789 @@
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
+ const fs = __importStar(require("fs/promises"));
37
+ const fssync = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const loadConfigFromPath_1 = require("../config/loadConfigFromPath");
40
+ const defaultConfig_1 = require("../config/defaultConfig");
41
+ const evaluationMetrics_1 = require("../pipeline/evaluationMetrics");
42
+ const fileGroups_1 = require("../pipeline/fileGroups");
43
+ const runHeadless_1 = require("../pipeline/runHeadless");
44
+ const tokenSavings_1 = require("../pipeline/tokenSavings");
45
+ const text_1 = require("../utils/text");
46
+ void main().catch((error) => {
47
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
48
+ console.error(message);
49
+ process.exitCode = 1;
50
+ });
51
+ async function main() {
52
+ const options = parseArgs(process.argv.slice(2));
53
+ const loaded = await loadManifest(options);
54
+ const manifest = loaded.manifest;
55
+ validateManifest(manifest, loaded.source);
56
+ if (options.validateOnly) {
57
+ const issues = await validateManifestCases(manifest);
58
+ if (issues.length > 0) {
59
+ console.error(`FlowSeeker eval validation failed: ${issues.length} issue(s).`);
60
+ for (const issue of issues.slice(0, 200)) {
61
+ console.error(`- ${issue}`);
62
+ }
63
+ if (issues.length > 200) {
64
+ console.error(`...and ${issues.length - 200} more issue(s).`);
65
+ }
66
+ process.exitCode = 1;
67
+ }
68
+ else {
69
+ console.log("FlowSeeker eval validation passed.");
70
+ }
71
+ return;
72
+ }
73
+ const results = [];
74
+ const startedAt = Date.now();
75
+ console.log(`FlowSeeker headless evaluation: ${manifest.name ?? loaded.source}`);
76
+ console.log(`${loaded.kind}: ${loaded.source}`);
77
+ for (const workspace of manifest.workspaces) {
78
+ const workspacePath = path.resolve(workspace.path);
79
+ const workspaceName = workspace.name ?? path.basename(workspacePath);
80
+ const config = (0, defaultConfig_1.mergeConfig)(await (0, loadConfigFromPath_1.loadConfigFromPath)(workspacePath), workspace.config ?? {});
81
+ const suites = filterSuitesByCaseId(await loadSuitesForWorkspace(workspacePath, workspace), options.caseId);
82
+ const totalCases = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
83
+ if (totalCases === 0) {
84
+ continue;
85
+ }
86
+ console.log(`\nWorkspace: ${workspaceName}`);
87
+ console.log(`Path: ${workspacePath}`);
88
+ console.log(`Suites: ${suites.length}; cases: ${totalCases}`);
89
+ for (const suite of suites) {
90
+ for (const caseDef of suite.cases) {
91
+ const caseStartedAt = Date.now();
92
+ console.log(`- ${suite.name}/${caseDef.id}: ${caseDef.task}`);
93
+ const result = await (0, runHeadless_1.runFlowSeekerHeadless)(workspacePath, caseDef.task, config, {
94
+ onProgress: options.verbose ? (message) => console.log(` ${message}`) : undefined
95
+ });
96
+ const evaluated = evaluateCase(workspaceName, workspacePath, suite.name, caseDef, result, Date.now() - caseStartedAt);
97
+ results.push(evaluated);
98
+ console.log(` ${formatCaseStatus(evaluated)}; ${(0, tokenSavings_1.formatTokenSavingsLine)(result.tokenSavings)}; ${formatContextCoverage(result)}; ${result.stats.scannedFiles} scanned; ${result.stats.rawEvidenceCount} raw${formatIndexConsole(result.stats)}; ${(evaluated.durationMs / 1000).toFixed(2)}s`);
99
+ }
100
+ }
101
+ }
102
+ if (options.caseId && results.length === 0) {
103
+ throw new Error(`No eval case matched --case-id '${options.caseId}'.`);
104
+ }
105
+ const reportPath = path.resolve(options.reportPath ?? manifest.reportPath ?? defaultReportPath());
106
+ await fs.mkdir(path.dirname(reportPath), { recursive: true });
107
+ await fs.writeFile(reportPath, renderReport(manifest, results, Date.now() - startedAt), "utf8");
108
+ console.log(`\nReport: ${reportPath}`);
109
+ console.log(formatSummary(results));
110
+ console.log(formatTokenSavingsSummary(results));
111
+ if (options.failOnMiss && results.some((result) => result.pass === false)) {
112
+ process.exitCode = 1;
113
+ }
114
+ }
115
+ function parseArgs(args) {
116
+ const options = {
117
+ expectedPaths: [],
118
+ avoidPaths: [],
119
+ failOnMiss: false,
120
+ verbose: false,
121
+ validateOnly: false
122
+ };
123
+ for (let index = 0; index < args.length; index += 1) {
124
+ const arg = args[index];
125
+ if (arg === "--manifest" || arg === "-m") {
126
+ options.manifestPath = requireValue(args, index, arg);
127
+ index += 1;
128
+ continue;
129
+ }
130
+ if (arg === "--workspace" || arg === "-w") {
131
+ options.workspacePath = requireValue(args, index, arg);
132
+ index += 1;
133
+ continue;
134
+ }
135
+ if (arg === "--name") {
136
+ options.workspaceName = requireValue(args, index, arg);
137
+ index += 1;
138
+ continue;
139
+ }
140
+ if (arg === "--task" || arg === "-t") {
141
+ options.task = requireValue(args, index, arg);
142
+ index += 1;
143
+ continue;
144
+ }
145
+ if (arg === "--case-id") {
146
+ options.caseId = requireValue(args, index, arg);
147
+ index += 1;
148
+ continue;
149
+ }
150
+ if (arg === "--expected") {
151
+ options.expectedPaths.push(...splitList(requireValue(args, index, arg)));
152
+ index += 1;
153
+ continue;
154
+ }
155
+ if (arg === "--avoid") {
156
+ options.avoidPaths.push(...splitList(requireValue(args, index, arg)));
157
+ index += 1;
158
+ continue;
159
+ }
160
+ if (arg === "--pass-at-k") {
161
+ options.passAtK = Number(requireValue(args, index, arg));
162
+ if (!Number.isFinite(options.passAtK) || options.passAtK < 1) {
163
+ throw new Error("--pass-at-k must be a positive number.");
164
+ }
165
+ index += 1;
166
+ continue;
167
+ }
168
+ if (arg === "--expected-mode") {
169
+ const mode = requireValue(args, index, arg);
170
+ if (mode !== "any" && mode !== "all" && mode !== "subset") {
171
+ throw new Error("--expected-mode must be either 'any', 'all', or 'subset'.");
172
+ }
173
+ options.expectedMode = mode;
174
+ index += 1;
175
+ continue;
176
+ }
177
+ if (arg === "--report" || arg === "-r") {
178
+ options.reportPath = requireValue(args, index, arg);
179
+ index += 1;
180
+ continue;
181
+ }
182
+ if (arg === "--fail-on-miss") {
183
+ options.failOnMiss = true;
184
+ continue;
185
+ }
186
+ if (arg === "--verbose" || arg === "-v") {
187
+ options.verbose = true;
188
+ continue;
189
+ }
190
+ if (arg === "--validate-only") {
191
+ options.validateOnly = true;
192
+ continue;
193
+ }
194
+ if (arg === "--help" || arg === "-h") {
195
+ printUsage();
196
+ process.exit(0);
197
+ }
198
+ throw new Error(`Unknown argument: ${arg}`);
199
+ }
200
+ return options;
201
+ }
202
+ async function loadManifest(options) {
203
+ if (options.workspacePath) {
204
+ if (!options.task?.trim()) {
205
+ throw new Error("--workspace requires --task for ad-hoc evaluation.");
206
+ }
207
+ const workspacePath = path.resolve(options.workspacePath);
208
+ const caseId = options.caseId ?? slugify(options.task);
209
+ return {
210
+ source: workspacePath,
211
+ kind: "Ad-hoc workspace",
212
+ manifest: {
213
+ name: `Ad-hoc evaluation: ${options.workspaceName ?? path.basename(workspacePath)}`,
214
+ reportPath: options.reportPath,
215
+ workspaces: [
216
+ {
217
+ name: options.workspaceName ?? path.basename(workspacePath),
218
+ path: workspacePath,
219
+ discoverSuites: false,
220
+ suiteFiles: [],
221
+ cases: [
222
+ {
223
+ id: caseId,
224
+ task: options.task.trim(),
225
+ expectedPaths: options.expectedPaths.length > 0 ? options.expectedPaths : undefined,
226
+ avoidPaths: options.avoidPaths.length > 0 ? options.avoidPaths : undefined,
227
+ passAtK: options.passAtK,
228
+ expectedMode: options.expectedMode
229
+ }
230
+ ]
231
+ }
232
+ ]
233
+ }
234
+ };
235
+ }
236
+ const manifestPath = path.resolve(options.manifestPath ?? path.join(".flowseeker", "eval-workspaces.json"));
237
+ return {
238
+ source: manifestPath,
239
+ kind: "Manifest",
240
+ manifest: await readJson(manifestPath)
241
+ };
242
+ }
243
+ function requireValue(args, index, flag) {
244
+ const value = args[index + 1];
245
+ if (!value || value.startsWith("-")) {
246
+ throw new Error(`${flag} requires a value.`);
247
+ }
248
+ return cleanArgValue(value);
249
+ }
250
+ function cleanArgValue(value) {
251
+ return value.replace(/\^/g, "");
252
+ }
253
+ function splitList(value) {
254
+ return value
255
+ .split(",")
256
+ .map((item) => item.trim())
257
+ .filter(Boolean);
258
+ }
259
+ function slugify(value) {
260
+ const slug = (0, text_1.normalizeText)(value)
261
+ .replace(/[^a-z0-9]+/g, "-")
262
+ .replace(/^-+|-+$/g, "")
263
+ .slice(0, 64);
264
+ return slug || "ad-hoc-task";
265
+ }
266
+ function printUsage() {
267
+ console.log([
268
+ "Usage:",
269
+ " node dist/cli/runEvaluation.js --manifest .flowseeker/eval-workspaces.json",
270
+ " node dist/cli/runEvaluation.js --workspace D:/repo --task \"Fix timestamp based on browser timezone\"",
271
+ "",
272
+ "Options:",
273
+ " --manifest, -m Path to a multi-workspace eval manifest.",
274
+ " --workspace, -w Run one ad-hoc task against this workspace path without a manifest.",
275
+ " --task, -t Natural-language task for ad-hoc workspace mode.",
276
+ " --name Optional workspace name for ad-hoc mode.",
277
+ " --case-id Optional case id for ad-hoc mode, or filter manifest cases by id.",
278
+ " --expected Optional expected path(s), comma-separated or repeated.",
279
+ " --avoid Optional avoid path(s), comma-separated or repeated.",
280
+ " --pass-at-k Optional pass threshold for expected paths. Default: 10.",
281
+ " --expected-mode Expected path pass mode: any, subset, or all. Default: any.",
282
+ " --report, -r Override report output path.",
283
+ " --fail-on-miss Exit with code 1 if a labeled case misses passAtK.",
284
+ " --verbose, -v Print scan progress.",
285
+ " --validate-only Validate eval labels and schema without scanning.",
286
+ ""
287
+ ].join("\n"));
288
+ }
289
+ async function loadSuitesForWorkspace(workspacePath, workspace) {
290
+ const suites = [];
291
+ if (workspace.cases?.length) {
292
+ suites.push({
293
+ name: `${workspace.name ?? path.basename(workspacePath)} direct cases`,
294
+ cases: workspace.cases
295
+ });
296
+ }
297
+ const shouldDiscoverSuites = workspace.discoverSuites !== false;
298
+ const suiteFiles = workspace.suiteFiles?.length ? workspace.suiteFiles.map((file) => path.resolve(workspacePath, file)) : shouldDiscoverSuites ? await discoverSuiteFiles(workspacePath) : [];
299
+ for (const suiteFile of suiteFiles) {
300
+ suites.push(await readSuite(suiteFile));
301
+ }
302
+ if (suites.length === 0) {
303
+ throw new Error(`No eval suites found for workspace: ${workspacePath}`);
304
+ }
305
+ return suites;
306
+ }
307
+ function filterSuitesByCaseId(suites, caseId) {
308
+ if (!caseId) {
309
+ return suites;
310
+ }
311
+ return suites
312
+ .map((suite) => ({
313
+ ...suite,
314
+ cases: suite.cases.filter((caseDef) => caseDef.id === caseId || `${suite.name}/${caseDef.id}` === caseId)
315
+ }))
316
+ .filter((suite) => suite.cases.length > 0);
317
+ }
318
+ async function discoverSuiteFiles(workspacePath) {
319
+ const files = [];
320
+ const direct = path.join(workspacePath, ".flowseeker", "eval.json");
321
+ if (await pathExists(direct)) {
322
+ files.push(direct);
323
+ }
324
+ const evalDir = path.join(workspacePath, ".flowseeker", "evals");
325
+ if (await pathExists(evalDir)) {
326
+ await collectJsonFiles(evalDir, files);
327
+ }
328
+ return files.sort((left, right) => left.localeCompare(right));
329
+ }
330
+ async function collectJsonFiles(directoryPath, output) {
331
+ const entries = await fs.readdir(directoryPath, { withFileTypes: true });
332
+ for (const entry of entries) {
333
+ const absolutePath = path.join(directoryPath, entry.name);
334
+ if (entry.isDirectory()) {
335
+ await collectJsonFiles(absolutePath, output);
336
+ }
337
+ else if (entry.isFile() && entry.name.endsWith(".json")) {
338
+ output.push(absolutePath);
339
+ }
340
+ }
341
+ }
342
+ async function readSuite(suitePath) {
343
+ const suite = await readJson(suitePath);
344
+ if (!suite.name || !Array.isArray(suite.cases)) {
345
+ throw new Error(`Invalid eval suite: ${suitePath}`);
346
+ }
347
+ suite.cases.forEach((caseDef, index) => {
348
+ if (!caseDef?.id || !caseDef?.task) {
349
+ throw new Error(`Invalid eval case at ${suitePath}#${index + 1}: id and task are required.`);
350
+ }
351
+ });
352
+ return {
353
+ name: suite.name,
354
+ description: suite.description,
355
+ cases: suite.cases
356
+ };
357
+ }
358
+ function evaluateCase(workspaceName, workspacePath, suiteName, caseDef, result, durationMs) {
359
+ const passAtK = caseDef.passAtK ?? 10;
360
+ const schemaIssues = validateCaseDefinition(workspacePath, caseDef);
361
+ const validGroups = resolveRequiredGroups(caseDef);
362
+ const expectedMetrics = (0, evaluationMetrics_1.computeExpectedPathMetrics)(result.units, caseDef.expectedPaths ?? [], passAtK, caseDef.expectedMode ?? "any");
363
+ const groupResults = validGroups.length > 0 ? (0, evaluationMetrics_1.computeGroupResults)(result.units, validGroups, passAtK) : undefined;
364
+ const allGroupsPass = groupResults ? groupResults.every((g) => g.pass) : undefined;
365
+ const hardAvoidHitsTop10 = findAvoidHits(result.units.slice(0, 10), caseDef.avoidPaths ?? [], "hard");
366
+ const contrastAvoidHitsTop10 = findAvoidHits(result.units.slice(0, 10), caseDef.avoidPaths ?? [], "contrast");
367
+ const wrongSubsystemAvoidHitsTop10 = findWrongSubsystemAvoidHits(result, caseDef.avoidPaths ?? []);
368
+ // When requiredGroups are present, pass depends on all groups passing (and expectedPaths pass if provided)
369
+ const pass = groupResults && groupResults.length > 0
370
+ ? (expectedMetrics.pass !== false && allGroupsPass === true)
371
+ : expectedMetrics.pass;
372
+ const contaminationHitsTop10 = findContaminationHits((0, fileGroups_1.aggregateEvidenceFiles)(result.units).slice(0, 10).map((group) => group.relativePath));
373
+ return {
374
+ workspaceName,
375
+ workspacePath,
376
+ suiteName,
377
+ caseDef,
378
+ result,
379
+ durationMs,
380
+ firstHitRank: expectedMetrics.firstUnitHitRank,
381
+ firstFileHitRank: expectedMetrics.firstFileHitRank,
382
+ expectedMetrics: { ...expectedMetrics, groupResults, allGroupsPass },
383
+ pass: pass,
384
+ hitAt5: expectedMetrics.coverageAt5 > 0,
385
+ hitAt10: expectedMetrics.coverageAt10 > 0,
386
+ hitAt20: expectedMetrics.coverageAt20 > 0,
387
+ hitAt40: expectedMetrics.coverageAt40 > 0,
388
+ avoidHitsTop10: uniqueStrings([...hardAvoidHitsTop10, ...contrastAvoidHitsTop10]),
389
+ hardAvoidHitsTop10,
390
+ contrastAvoidHitsTop10,
391
+ wrongSubsystemAvoidHitsTop10,
392
+ groupResults,
393
+ allGroupsPass,
394
+ schemaIssues,
395
+ contaminationHitsTop10,
396
+ rankingQuality: (0, evaluationMetrics_1.computeRankingQualityMetrics)(result)
397
+ };
398
+ }
399
+ function findWrongSubsystemAvoidHits(result, avoidPaths) {
400
+ if (avoidPaths.length === 0) {
401
+ return [];
402
+ }
403
+ const topGroups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units).slice(0, 10);
404
+ const primarySubsystem = result.units.find((unit) => unit.subsystem && !unit.contrastContext)?.subsystem;
405
+ if (!primarySubsystem) {
406
+ return [];
407
+ }
408
+ return uniqueStrings(topGroups
409
+ .filter((group) => group.subsystem && group.subsystem !== primarySubsystem)
410
+ .filter((group) => avoidPaths.some((avoidPath) => (0, evaluationMetrics_1.pathMatches)(group.relativePath, avoidPath)))
411
+ .map((group) => group.relativePath));
412
+ }
413
+ function findAvoidHits(units, avoidPaths, mode = "hard") {
414
+ if (avoidPaths.length === 0) {
415
+ return [];
416
+ }
417
+ return uniqueStrings(units
418
+ .filter((unit) => avoidPaths.some((avoidPath) => (0, evaluationMetrics_1.pathMatches)(unit.relativePath, avoidPath)))
419
+ .filter((unit) => mode === "contrast" ? unit.contrastContext : !unit.contrastContext)
420
+ .map((unit) => unit.relativePath));
421
+ }
422
+ function resolveRequiredGroups(caseDef) {
423
+ const groups = caseDef.requiredGroups;
424
+ if (!Array.isArray(groups)) {
425
+ return [];
426
+ }
427
+ const explicitGroups = groups.filter(isRequiredGroup);
428
+ if (explicitGroups.length > 0) {
429
+ return explicitGroups;
430
+ }
431
+ if (groups.some((group) => typeof group === "string")) {
432
+ return inferRequiredGroups(caseDef.expectedPaths ?? [], caseDef.expectedMode ?? "any");
433
+ }
434
+ return [];
435
+ }
436
+ function isRequiredGroup(group) {
437
+ const value = group;
438
+ return Boolean(value &&
439
+ typeof value.name === "string" &&
440
+ Array.isArray(value.paths) &&
441
+ value.paths.every((item) => typeof item === "string") &&
442
+ (value.mode === "any" || value.mode === "all"));
443
+ }
444
+ function inferRequiredGroups(expectedPaths, expectedMode) {
445
+ const byName = new Map();
446
+ for (const expectedPath of expectedPaths) {
447
+ const groupName = inferGroupName(expectedPath);
448
+ byName.set(groupName, [...(byName.get(groupName) ?? []), expectedPath]);
449
+ }
450
+ const groups = Array.from(byName.entries()).map(([name, paths]) => ({
451
+ name,
452
+ paths,
453
+ mode: "any"
454
+ }));
455
+ if (groups.length === 1 && expectedMode === "all") {
456
+ return groups.map((group) => ({ ...group, mode: "all" }));
457
+ }
458
+ return groups;
459
+ }
460
+ function inferGroupName(relativePath) {
461
+ const normalized = normalizeCasePath(relativePath);
462
+ if (/(^|\/)(__tests__|tests?|specs?)(\/|$)|\.(test|spec)\./.test(normalized)) {
463
+ return "verification";
464
+ }
465
+ if (/(^|\/)(config|configs?|settings?|environments?)(\/|$)|\.(env|toml|ya?ml|properties|ini)$/.test(normalized)) {
466
+ return "config";
467
+ }
468
+ if (/(^|\/)(migrations?|schema|schemas|models?|entities?|repositories?|prisma)(\/|$)|\.(sql|prisma)$/.test(normalized)) {
469
+ return "data";
470
+ }
471
+ if (/(^|\/)(jobs?|workers?|events?|listeners?|mailers?|mail|notifications?|webhooks?|queues?)(\/|$)/.test(normalized)) {
472
+ return "side_effect";
473
+ }
474
+ if (/(^|\/)(policies?|permissions?|guards?|middleware|auth)(\/|$)/.test(normalized)) {
475
+ return "permission";
476
+ }
477
+ if (/(^|\/)(routes?|router|controllers?|handlers?|api)(\/|$)|\+page\.(svelte|ts|tsx)$|page\.(ts|tsx|jsx|svelte)$/.test(normalized)) {
478
+ return "entry";
479
+ }
480
+ if (/(^|\/)(components?|views?|templates?|pages?|store|stores|hooks?|context)(\/|$)|\.(svelte|vue|tsx|jsx|erb|blade\.php)$/.test(normalized)) {
481
+ return "ui";
482
+ }
483
+ if (/(^|\/)(services?|usecases?|domain|lib|utils?|clients?)(\/|$)/.test(normalized)) {
484
+ return "core_logic";
485
+ }
486
+ return "context";
487
+ }
488
+ function validateCaseDefinition(workspacePath, caseDef) {
489
+ const issues = [];
490
+ const expectedPaths = caseDef.expectedPaths ?? [];
491
+ const avoidPaths = caseDef.avoidPaths ?? [];
492
+ if (caseDef.expectedMode && caseDef.expectedMode !== "any" && caseDef.expectedMode !== "all" && caseDef.expectedMode !== "subset") {
493
+ issues.push(`invalid expectedMode '${caseDef.expectedMode}'`);
494
+ }
495
+ const expectedSet = new Set(expectedPaths.map(normalizeCasePath));
496
+ for (const avoidPath of avoidPaths) {
497
+ if (expectedSet.has(normalizeCasePath(avoidPath))) {
498
+ issues.push(`expected/avoid overlap: ${avoidPath}`);
499
+ }
500
+ }
501
+ for (const expectedPath of expectedPaths) {
502
+ if (!casePathExists(workspacePath, expectedPath)) {
503
+ issues.push(`missing expected path: ${expectedPath}`);
504
+ }
505
+ }
506
+ for (const avoidPath of avoidPaths) {
507
+ if (!casePathExists(workspacePath, avoidPath)) {
508
+ issues.push(`missing avoid path: ${avoidPath}`);
509
+ }
510
+ }
511
+ if (Array.isArray(caseDef.requiredGroups)) {
512
+ for (const group of caseDef.requiredGroups) {
513
+ if (typeof group === "string" && expectedPaths.length > 0) {
514
+ continue;
515
+ }
516
+ if (!isRequiredGroup(group)) {
517
+ issues.push("invalid requiredGroups entry; use { name, paths, mode }");
518
+ continue;
519
+ }
520
+ for (const groupPath of group.paths) {
521
+ if (!casePathExists(workspacePath, groupPath)) {
522
+ issues.push(`missing group path (${group.name}): ${groupPath}`);
523
+ }
524
+ }
525
+ }
526
+ }
527
+ return Array.from(new Set(issues));
528
+ }
529
+ function casePathExists(workspacePath, relativePath) {
530
+ if (relativePath.includes("*")) {
531
+ return true;
532
+ }
533
+ return fssync.existsSync(path.join(workspacePath, relativePath));
534
+ }
535
+ function normalizeCasePath(relativePath) {
536
+ return relativePath.replace(/\\/g, "/").replace(/^\/+/, "").toLowerCase();
537
+ }
538
+ function findContaminationHits(relativePaths) {
539
+ return relativePaths.filter(isContaminationPath);
540
+ }
541
+ function isContaminationPath(relativePath) {
542
+ const normalized = normalizeCasePath(relativePath);
543
+ return /(^|\/)\.flowseeker(\/|$)|(^|\/)_gen[^/]*\.(py|sh)$|(^|\/)_bulk_write\.js$/.test(normalized);
544
+ }
545
+ function renderReport(manifest, results, durationMs) {
546
+ const lines = [
547
+ "# FlowSeeker Headless Evaluation Report",
548
+ "",
549
+ `Generated: ${new Date().toISOString()}`,
550
+ `Benchmark: ${manifest.name ?? "Unnamed benchmark"}`,
551
+ `Workspaces: ${new Set(results.map((result) => result.workspaceName)).size}`,
552
+ `Cases: ${results.length}`,
553
+ `Duration: ${(durationMs / 1000).toFixed(2)}s`,
554
+ "",
555
+ "## Summary",
556
+ "",
557
+ formatSummary(results),
558
+ formatTokenSavingsSummary(results),
559
+ "",
560
+ "## Workspace Summary",
561
+ "",
562
+ ...renderWorkspaceSummary(results),
563
+ "",
564
+ "## Cases",
565
+ ""
566
+ ];
567
+ for (const result of results) {
568
+ lines.push(...renderCase(result));
569
+ }
570
+ return `${lines.join("\n")}\n`;
571
+ }
572
+ function renderWorkspaceSummary(results) {
573
+ const byWorkspace = new Map();
574
+ for (const result of results) {
575
+ byWorkspace.set(result.workspaceName, [...(byWorkspace.get(result.workspaceName) ?? []), result]);
576
+ }
577
+ return Array.from(byWorkspace.entries()).map(([workspaceName, workspaceResults]) => `- ${workspaceName}: ${formatSummary(workspaceResults)}; ${formatTokenSavingsSummary(workspaceResults)}`);
578
+ }
579
+ function renderCase(result) {
580
+ const status = result.pass === "unlabeled" ? "UNLABELED" : result.pass ? "PASS" : "FAIL";
581
+ const stats = result.result.stats;
582
+ const topFiles = (0, fileGroups_1.aggregateEvidenceFiles)(result.result.units).slice(0, 15);
583
+ return [
584
+ `### ${status} - ${result.workspaceName}/${result.suiteName}/${result.caseDef.id}`,
585
+ "",
586
+ `Workspace: ${result.workspacePath}`,
587
+ `Task: ${result.caseDef.task}`,
588
+ ...(result.caseDef.notes ? [`Notes: ${result.caseDef.notes}`] : []),
589
+ `Intent: ${result.result.profile.intent}`,
590
+ `Keywords: ${result.result.profile.keywords.join(", ") || "none"}`,
591
+ `Strategies: ${result.result.profile.strategies.join(", ") || "none"}`,
592
+ `Expected paths: ${(result.caseDef.expectedPaths ?? []).join(", ") || "none"}`,
593
+ `Expected mode: ${result.caseDef.expectedMode ?? "any"}`,
594
+ `Avoid paths: ${(result.caseDef.avoidPaths ?? []).join(", ") || "none"}`,
595
+ `First expected unit rank: ${result.firstHitRank ?? "none"}`,
596
+ `First expected file rank: ${result.firstFileHitRank ?? "none"}`,
597
+ `Coverage@5/10/20/40: ${result.expectedMetrics.coverageAt5}/${result.expectedMetrics.expectedCount}, ${result.expectedMetrics.coverageAt10}/${result.expectedMetrics.expectedCount}, ${result.expectedMetrics.coverageAt20}/${result.expectedMetrics.expectedCount}, ${result.expectedMetrics.coverageAt40}/${result.expectedMetrics.expectedCount}`,
598
+ `Missing expected at 10: ${result.expectedMetrics.missingAt10.join(", ") || "none"}`,
599
+ `Missing expected at 20: ${result.expectedMetrics.missingAt20.join(", ") || "none"}`,
600
+ `Avoid hits in top 10: ${result.hardAvoidHitsTop10.join(", ") || "none"}`,
601
+ `Contrast avoid hits in top 10: ${result.contrastAvoidHitsTop10.join(", ") || "none"}`,
602
+ `Wrong-subsystem avoid hits in top 10: ${result.wrongSubsystemAvoidHitsTop10.join(", ") || "none"}`,
603
+ `Contamination hits in top 10: ${result.contaminationHitsTop10.join(", ") || "none"}`,
604
+ `Schema issues: ${result.schemaIssues.join("; ") || "none"}`,
605
+ ...(result.groupResults && result.groupResults.length > 0 ? [
606
+ `All groups pass: ${result.allGroupsPass ? "yes" : "no"}`,
607
+ ...result.groupResults.map((g) => `Group ${g.name} [${g.mode}]: pass@${g.passAtK}=${g.pass}, rank=${g.firstHitRank ?? "none"}, coverage@${g.passAtK}=${g.coverageAtPassK}/${g.expectedCount}, missing=${g.missingAtPassK.join(", ") || "none"}`)
608
+ ] : []),
609
+ `Token savings: ${formatCaseTokenSavings(result)}`,
610
+ `Context coverage: ${formatContextCoverage(result.result)}`,
611
+ `Ranking quality: uniqueTop10Files=${result.rankingQuality.uniqueTop10Files}, duplicateEvidenceTop10=${result.rankingQuality.duplicateEvidenceTop10}, slotCoverageTop10=${result.rankingQuality.slotCoverageTop10}/${result.rankingQuality.requiredSlotCount}${result.rankingQuality.missingRequiredSlotsTop10.length ? `, missingSlots=${result.rankingQuality.missingRequiredSlotsTop10.join("|")}` : ""}, contrastContextTop10=${result.rankingQuality.contrastContextTop10}, primarySubsystem=${result.rankingQuality.primarySubsystemTop10 ?? "none"}, subsystemPurityTop10=${result.rankingQuality.subsystemPurityTop10.toFixed(2)}, symbolEvidenceTop10=${result.rankingQuality.symbolEvidenceTop10}`,
612
+ `Duration: ${(result.durationMs / 1000).toFixed(2)}s`,
613
+ `Scan: discovered=${stats.discoveredFiles}, scanned=${stats.scannedFiles}, skipped=${stats.skippedFiles}, bytes=${formatBytes(stats.scannedBytes)}, rawEvidence=${stats.rawEvidenceCount}, ranked=${stats.rankedEvidenceCount}, capped=${stats.capped}, stoppedEarly=${stats.stoppedEarly}${stats.stopReason ? ` (${stats.stopReason})` : ""}${formatIndexStats(stats)}`,
614
+ "",
615
+ "Top files:",
616
+ "",
617
+ ...topFiles.map((group, index) => `${index + 1}. ${group.relativePath} [${group.role}/${group.slot}] score=${group.score.toFixed(1)} evidence=${group.unitCount} dup=${group.duplicateUnitCount} contrast=${group.contrastContext} subsystem=${group.subsystem ?? "none"} kinds=${group.kinds.join(",")}`),
618
+ "",
619
+ "Top evidence:",
620
+ "",
621
+ ...result.result.units.slice(0, 15).map((unit, index) => {
622
+ const range = unit.range ? `:${unit.range.startLine}-${unit.range.endLine}` : "";
623
+ const passes = unit.retrievalPasses.length > 0 ? unit.retrievalPasses.join("+") : "none";
624
+ const meta = [unit.isDuplicate ? "duplicate" : "", unit.contrastContext ? "contrast" : "", unit.frameworkEdgeReason ? "framework" : ""].filter(Boolean).join(",");
625
+ return `${index + 1}. ${unit.relativePath}${range} [${unit.role}/${unit.kind}/${unit.slot ?? "unknown"}${meta ? `/${meta}` : ""}] score=${unit.score.toFixed(1)} passes=${passes}`;
626
+ }),
627
+ ""
628
+ ];
629
+ }
630
+ function formatSummary(results) {
631
+ const labeled = results.filter((result) => result.pass !== "unlabeled");
632
+ const passed = labeled.filter((result) => result.pass === true);
633
+ const hitAt5 = count(results, (result) => result.hitAt5);
634
+ const hitAt10 = count(results, (result) => result.hitAt10);
635
+ const hitAt20 = count(results, (result) => result.hitAt20);
636
+ const hitAt40 = count(results, (result) => result.hitAt40);
637
+ const allExpectedAt10 = count(labeled, (result) => result.expectedMetrics.expectedCount > 0 && result.expectedMetrics.coverageAt10 === result.expectedMetrics.expectedCount);
638
+ const allExpectedAt20 = count(labeled, (result) => result.expectedMetrics.expectedCount > 0 && result.expectedMetrics.coverageAt20 === result.expectedMetrics.expectedCount);
639
+ const avgExpectedCoverageAt10 = average(labeled.map((result) => result.expectedMetrics.expectedCount > 0 ? result.expectedMetrics.coverageAt10 / result.expectedMetrics.expectedCount : 0));
640
+ const avgMrr = average(labeled.map((result) => (result.firstFileHitRank ? 1 / result.firstFileHitRank : 0)));
641
+ const avgDuration = average(results.map((result) => result.durationMs));
642
+ const withGroups = results.filter((r) => r.allGroupsPass !== undefined);
643
+ const groupsPassed = withGroups.filter((r) => r.allGroupsPass === true).length;
644
+ const avoidCases = results.filter((result) => (result.caseDef.avoidPaths ?? []).length > 0).length;
645
+ const avoidHitCases = results.filter((result) => result.hardAvoidHitsTop10.length > 0).length;
646
+ const contrastAvoidCases = results.filter((result) => result.contrastAvoidHitsTop10.length > 0).length;
647
+ const wrongSubsystemAvoidCases = results.filter((result) => result.wrongSubsystemAvoidHitsTop10.length > 0).length;
648
+ const contaminationCases = results.filter((result) => result.contaminationHitsTop10.length > 0).length;
649
+ const schemaIssueCases = results.filter((result) => result.schemaIssues.length > 0).length;
650
+ const avgUniqueTop10Files = average(results.map((result) => result.rankingQuality.uniqueTop10Files));
651
+ const avgDuplicateEvidenceTop10 = average(results.map((result) => result.rankingQuality.duplicateEvidenceTop10));
652
+ const avgSlotCoverageTop10 = average(results.map((result) => result.rankingQuality.requiredSlotCount > 0 ? result.rankingQuality.slotCoverageTop10 / result.rankingQuality.requiredSlotCount : 1));
653
+ const avgSubsystemPurityTop10 = average(results.map((result) => result.rankingQuality.subsystemPurityTop10));
654
+ const avgSymbolEvidenceTop10 = average(results.map((result) => result.rankingQuality.symbolEvidenceTop10));
655
+ const groupSummary = withGroups.length > 0
656
+ ? `, groupsPass=${groupsPassed}/${withGroups.length}`
657
+ : "";
658
+ const avgTokenReduction = average(results
659
+ .map((result) => result.result.tokenSavings)
660
+ .filter((item) => item !== undefined && item.status !== "unavailable")
661
+ .map((item) => item.reductionPercent / 100));
662
+ const avoidHitRate = avoidCases > 0 ? avoidHitCases / avoidCases : 0;
663
+ const qualityScore = (0, evaluationMetrics_1.computeQualityScore)({
664
+ hitAt10Norm: labeled.length > 0 ? count(labeled, (result) => result.hitAt10) / labeled.length : 0,
665
+ contextCoverageNorm: avgSlotCoverageTop10,
666
+ avoidHitRate,
667
+ mrr: avgMrr,
668
+ tokenReductionNorm: avgTokenReduction
669
+ });
670
+ return `quality=${qualityScore.toFixed(3)}, labeled=${labeled.length}, pass=${passed.length}/${labeled.length}, hit@5=${hitAt5}/${results.length}, hit@10=${hitAt10}/${results.length}, hit@20=${hitAt20}/${results.length}, hit@40=${hitAt40}/${results.length}, allExpected@10=${allExpectedAt10}/${labeled.length}, allExpected@20=${allExpectedAt20}/${labeled.length}, avgExpectedCoverage@10=${avgExpectedCoverageAt10.toFixed(3)}, MRR=${avgMrr.toFixed(3)}, avg=${(avgDuration / 1000).toFixed(2)}s${groupSummary}, avoidHitCases=${avoidHitCases}/${avoidCases}, contrastAvoidCases=${contrastAvoidCases}/${avoidCases}, wrongSubsystemAvoidCases=${wrongSubsystemAvoidCases}/${avoidCases}, contaminationCases=${contaminationCases}/${results.length}, schemaIssueCases=${schemaIssueCases}/${results.length}, uniqueTop10FilesAvg=${avgUniqueTop10Files.toFixed(1)}, duplicateEvidenceTop10Avg=${avgDuplicateEvidenceTop10.toFixed(1)}, slotCoverageTop10Avg=${avgSlotCoverageTop10.toFixed(2)}, subsystemPurityTop10Avg=${avgSubsystemPurityTop10.toFixed(2)}, symbolEvidenceTop10Avg=${avgSymbolEvidenceTop10.toFixed(1)}`;
671
+ }
672
+ function formatTokenSavingsSummary(results) {
673
+ const metrics = results.map((result) => result.result.tokenSavings).filter((item) => item !== undefined && item.status !== "unavailable");
674
+ if (metrics.length === 0) {
675
+ return "tokenSavings=unavailable";
676
+ }
677
+ const avgReduction = average(metrics.map((item) => item.reductionPercent));
678
+ const avgPacketTokens = average(metrics.map((item) => item.solvePacketTokensEstimate));
679
+ const avgWorkspaceTokens = average(metrics.map((item) => item.workspaceTokensEstimate));
680
+ const partial = metrics.filter((item) => item.status === "partial").length;
681
+ return `tokenSavings avg=${(0, tokenSavings_1.formatReductionPercent)(avgReduction)}, packetAvg=${(0, tokenSavings_1.formatTokenCount)(avgPacketTokens)}, workspaceAvg=${(0, tokenSavings_1.formatTokenCount)(avgWorkspaceTokens)}, partial=${partial}/${metrics.length}`;
682
+ }
683
+ function formatCaseTokenSavings(result) {
684
+ const metrics = result.result.tokenSavings;
685
+ if (!metrics || metrics.status === "unavailable") {
686
+ return "unavailable";
687
+ }
688
+ const note = metrics.reason ? `; note=${metrics.reason}` : "";
689
+ const warnings = metrics.proofWarnings.length > 0 ? `; warnings=${metrics.proofWarnings.join(" | ")}` : "";
690
+ return `Solve Packet ${(0, tokenSavings_1.formatTokenCount)(metrics.solvePacketTokensEstimate)} / workspace ${(0, tokenSavings_1.formatTokenCount)(metrics.workspaceTokensEstimate)} tokens, candidates=${(0, tokenSavings_1.formatTokenCount)(metrics.candidateTokensEstimate)}, reduction ${(0, tokenSavings_1.formatReductionPercent)(metrics.reductionPercent)}, method=${metrics.method}, tokenizer=${metrics.tokenizer}, basis=${metrics.basis}, status=${metrics.status}, proof=${metrics.proofStatus}, files=${metrics.rankedFileCount}/${metrics.workspaceFileCount}${note}${warnings}`;
691
+ }
692
+ function formatContextCoverage(result) {
693
+ const coverage = result.contextCoverage;
694
+ if (!coverage) {
695
+ return "coverage unavailable";
696
+ }
697
+ const missing = coverage.missingRequiredSlots.length > 0 ? `, missing=${coverage.missingRequiredSlots.join("|")}` : "";
698
+ return `coverage ${coverage.foundRequiredSlots.length}/${coverage.requiredSlots.length} required (${coverage.requiredCoveragePercent.toFixed(0)}%)${missing}`;
699
+ }
700
+ function formatCaseStatus(result) {
701
+ if (result.pass === "unlabeled") {
702
+ return "UNLABELED";
703
+ }
704
+ const base = result.pass ? `PASS fileRank=${result.firstFileHitRank ?? "n/a"}` : "FAIL missing expected coverage";
705
+ if (result.groupResults && result.groupResults.length > 0) {
706
+ const groupStatus = result.groupResults.map((g) => `${g.name}=${g.pass ? "PASS" : "FAIL"}`).join(" ");
707
+ const allG = result.allGroupsPass ? "groups=ALL_PASS" : "groups=SOME_FAIL";
708
+ return `${base}; ${allG}; ${groupStatus}`;
709
+ }
710
+ return base;
711
+ }
712
+ function formatIndexConsole(stats) {
713
+ if (stats.indexedFiles === undefined) {
714
+ return "";
715
+ }
716
+ return `; ${stats.candidateFiles ?? 0}/${stats.indexedFiles} candidates; index ${stats.indexReusedFiles ?? 0} reused/${stats.indexUpdatedFiles ?? 0} updated`;
717
+ }
718
+ function formatIndexStats(stats) {
719
+ if (stats.indexedFiles === undefined) {
720
+ return "";
721
+ }
722
+ return `, indexed=${stats.indexedFiles}, candidates=${stats.candidateFiles ?? "unknown"}, indexReused=${stats.indexReusedFiles ?? 0}, indexUpdated=${stats.indexUpdatedFiles ?? 0}`;
723
+ }
724
+ async function readJson(filePath) {
725
+ const text = await fs.readFile(filePath, "utf8");
726
+ return JSON.parse(text);
727
+ }
728
+ function validateManifest(manifest, manifestPath) {
729
+ if (!Array.isArray(manifest.workspaces) || manifest.workspaces.length === 0) {
730
+ throw new Error(`Invalid manifest ${manifestPath}: workspaces must be a non-empty array.`);
731
+ }
732
+ manifest.workspaces.forEach((workspace, index) => {
733
+ if (!workspace.path) {
734
+ throw new Error(`Invalid manifest ${manifestPath}: workspaces[${index}].path is required.`);
735
+ }
736
+ });
737
+ }
738
+ async function validateManifestCases(manifest) {
739
+ const issues = [];
740
+ for (const workspace of manifest.workspaces) {
741
+ const workspacePath = path.resolve(workspace.path);
742
+ const workspaceName = workspace.name ?? path.basename(workspacePath);
743
+ const suites = await loadSuitesForWorkspace(workspacePath, workspace);
744
+ for (const suite of suites) {
745
+ for (const caseDef of suite.cases) {
746
+ const prefix = `${workspaceName}/${suite.name}/${caseDef.id}`;
747
+ for (const issue of validateCaseDefinition(workspacePath, caseDef)) {
748
+ issues.push(`${prefix}: ${issue}`);
749
+ }
750
+ }
751
+ }
752
+ }
753
+ return issues;
754
+ }
755
+ async function pathExists(filePath) {
756
+ try {
757
+ await fs.access(filePath);
758
+ return true;
759
+ }
760
+ catch {
761
+ return false;
762
+ }
763
+ }
764
+ function defaultReportPath() {
765
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
766
+ return path.join(".flowseeker", "reports", `headless-eval-${timestamp}.md`);
767
+ }
768
+ function yesNo(value) {
769
+ return value ? "yes" : "no";
770
+ }
771
+ function uniqueStrings(values) {
772
+ return Array.from(new Set(values));
773
+ }
774
+ function count(items, predicate) {
775
+ return items.filter(predicate).length;
776
+ }
777
+ function average(values) {
778
+ if (values.length === 0) {
779
+ return 0;
780
+ }
781
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
782
+ }
783
+ function formatBytes(bytes) {
784
+ if (bytes < 1024 * 1024) {
785
+ return `${(bytes / 1024).toFixed(1)}KB`;
786
+ }
787
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
788
+ }
789
+ //# sourceMappingURL=runEvaluation.js.map