flowseeker 0.1.8 → 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.
@@ -0,0 +1,483 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeAccuracyV2 = computeAccuracyV2;
4
+ exports.computeFailureAnalysis = computeFailureAnalysis;
5
+ exports.computeSemanticContribution = computeSemanticContribution;
6
+ exports.aggregateSemanticContribution = aggregateSemanticContribution;
7
+ exports.aggregateAccuracyV2 = aggregateAccuracyV2;
8
+ exports.computeRoleClassifierReport = computeRoleClassifierReport;
9
+ exports.renderRoleClassifierReportMarkdown = renderRoleClassifierReportMarkdown;
10
+ exports.computeRoleConfusionMatrix = computeRoleConfusionMatrix;
11
+ exports.renderAccuracyV2Markdown = renderAccuracyV2Markdown;
12
+ const fileGroups_1 = require("../pipeline/fileGroups");
13
+ const evaluationMetrics_1 = require("../pipeline/evaluationMetrics");
14
+ function computeAccuracyV2(result, goldenTask) {
15
+ const fileGroups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units);
16
+ const topFiles = fileGroups.map((g) => g.relativePath);
17
+ const topK = (k) => topFiles.slice(0, k);
18
+ const primaryEditPaths = fileGroups
19
+ .filter((g) => g.role === "edit_candidate")
20
+ .map((g) => g.relativePath);
21
+ const allFilePaths = topFiles;
22
+ // edit_recall@K: at least one expectedEditFile in top K
23
+ const editRecallAtK = (k) => goldenTask.expectedEditFiles.length === 0
24
+ ? true
25
+ : topK(k).some((f) => goldenTask.expectedEditFiles.some((ef) => (0, evaluationMetrics_1.pathMatches)(f, ef)));
26
+ // primary_edit_precision@3: fraction of top 3 primary edit candidates in expectedEditFiles
27
+ const top3Edit = primaryEditPaths.slice(0, 3);
28
+ const primaryEditPrecision3 = top3Edit.length > 0
29
+ ? top3Edit.filter((f) => goldenTask.expectedEditFiles.some((ef) => (0, evaluationMetrics_1.pathMatches)(f, ef))).length / top3Edit.length
30
+ : 0;
31
+ // read_context_recall@10: fraction of expectedReadFiles in top 10
32
+ const readRecall10 = goldenTask.expectedReadFiles.length > 0
33
+ ? goldenTask.expectedReadFiles.filter((rf) => topK(10).some((f) => (0, evaluationMetrics_1.pathMatches)(f, rf))).length / goldenTask.expectedReadFiles.length
34
+ : 1;
35
+ // test_recall@10: fraction of expectedTestFiles in top 10
36
+ const testRecall10 = goldenTask.expectedTestFiles.length > 0
37
+ ? goldenTask.expectedTestFiles.filter((tf) => topK(10).some((f) => (0, evaluationMetrics_1.pathMatches)(f, tf))).length / goldenTask.expectedTestFiles.length
38
+ : 1;
39
+ // must_not_edit_violation: any mustNotEditFile appears as primary edit candidate
40
+ const mustNotEditViolation = goldenTask.mustNotEditFiles.length > 0 &&
41
+ primaryEditPaths.some((f) => goldenTask.mustNotEditFiles.some((mf) => (0, evaluationMetrics_1.pathMatches)(f, mf)));
42
+ // noise_promoted_as_edit: mustNotEdit/noise files promoted to edit candidates
43
+ const noisePromoted = primaryEditPaths.filter((f) => goldenTask.mustNotEditFiles.some((mf) => (0, evaluationMetrics_1.pathMatches)(f, mf)));
44
+ const noisePromotedAsEditCount = noisePromoted.length;
45
+ const noisePromotedAsEditTotal = primaryEditPaths.length;
46
+ // required_slot_coverage / missing_slot_rate
47
+ const foundSlots = new Set(result.profile.blueprint.requiredSlots);
48
+ const coveredSlots = goldenTask.requiredSlots.filter((s) => foundSlots.has(s));
49
+ const requiredSlotCoverage = goldenTask.requiredSlots.length > 0
50
+ ? coveredSlots.length / goldenTask.requiredSlots.length
51
+ : 1;
52
+ const missingSlotRate = goldenTask.requiredSlots.length > 0
53
+ ? (goldenTask.requiredSlots.length - coveredSlots.length) / goldenTask.requiredSlots.length
54
+ : 0;
55
+ // first3_hit_rate: firstThreeFiles contains an expected edit, read, or test file
56
+ const first3 = result.solvePacket?.firstThreeFiles ?? [];
57
+ const allExpected = [
58
+ ...goldenTask.expectedEditFiles,
59
+ ...goldenTask.expectedReadFiles,
60
+ ...goldenTask.expectedTestFiles,
61
+ ];
62
+ const first3Hit = allExpected.length > 0 &&
63
+ first3.some((f) => allExpected.some((ef) => (0, evaluationMetrics_1.pathMatches)(f, ef)));
64
+ return {
65
+ taskId: goldenTask.id,
66
+ editRecall1: editRecallAtK(1),
67
+ editRecall3: editRecallAtK(3),
68
+ editRecall5: editRecallAtK(5),
69
+ editRecall10: editRecallAtK(10),
70
+ primaryEditPrecision3,
71
+ readContextRecall10: readRecall10,
72
+ testRecall10,
73
+ mustNotEditViolation,
74
+ noisePromotedAsEditCount,
75
+ noisePromotedAsEditTotal,
76
+ requiredSlotCoverage,
77
+ missingSlotRate,
78
+ first3Hit,
79
+ };
80
+ }
81
+ // ── Failure analysis ──────────────────────────────────────────────────────────
82
+ function computeFailureAnalysis(result, goldenTask, v2) {
83
+ const fileGroups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units);
84
+ const topFiles = fileGroups.map((g) => g.relativePath);
85
+ const top10Files = topFiles.slice(0, 10);
86
+ const primaryEditPaths = fileGroups.filter((g) => g.role === "edit_candidate").map((g) => g.relativePath);
87
+ // Missed expected files (not in top 10)
88
+ const inTop10 = (f) => top10Files.some((tf) => (0, evaluationMetrics_1.pathMatches)(tf, f));
89
+ const missedEditFiles = goldenTask.expectedEditFiles.filter((ef) => !inTop10(ef));
90
+ const missedReadFiles = goldenTask.expectedReadFiles.filter((rf) => !inTop10(rf));
91
+ const missedTestFiles = goldenTask.expectedTestFiles.filter((tf) => !inTop10(tf));
92
+ // wrongTop1: the top-1 file if it's not in any expected list
93
+ const allGoldenFiles = new Set([
94
+ ...goldenTask.expectedEditFiles,
95
+ ...goldenTask.expectedReadFiles,
96
+ ...goldenTask.expectedTestFiles,
97
+ ]);
98
+ const top1 = fileGroups[0];
99
+ const wrongTop1 = top1 && !allGoldenFiles.has(top1.relativePath) && !Array.from(allGoldenFiles).some((gf) => (0, evaluationMetrics_1.pathMatches)(top1.relativePath, gf))
100
+ ? top1.relativePath
101
+ : null;
102
+ // noise promoted as edit
103
+ const noisePromotedAsEdit = primaryEditPaths.filter((f) => goldenTask.mustNotEditFiles.some((mf) => (0, evaluationMetrics_1.pathMatches)(f, mf)));
104
+ // mustNotEditViolations
105
+ const mustNotEditViolations = primaryEditPaths.filter((f) => goldenTask.mustNotEditFiles.some((mf) => (0, evaluationMetrics_1.pathMatches)(f, mf)));
106
+ // Missing slots
107
+ const foundSlots = new Set(result.profile.blueprint.requiredSlots);
108
+ const missingSlots = goldenTask.requiredSlots.filter((s) => !foundSlots.has(s));
109
+ // Top files list
110
+ const topFilesList = fileGroups.slice(0, 10).map((g, i) => ({
111
+ rank: i + 1,
112
+ path: g.relativePath,
113
+ role: g.role,
114
+ score: g.score,
115
+ }));
116
+ // first3Files
117
+ const first3Files = result.solvePacket?.firstThreeFiles ?? [];
118
+ // Determine likelyFailureReason
119
+ let likelyFailureReason = "no_failure_detected";
120
+ if (v2.mustNotEditViolation) {
121
+ likelyFailureReason = "must_not_promoted_as_edit";
122
+ }
123
+ else if (v2.noisePromotedAsEditCount > 0) {
124
+ likelyFailureReason = "noisy_primary_edit";
125
+ }
126
+ else if (missingSlots.length > 0) {
127
+ likelyFailureReason = "missing_required_slot";
128
+ }
129
+ else if (wrongTop1) {
130
+ likelyFailureReason = "wrong_top1";
131
+ }
132
+ else if (missedEditFiles.length > 0) {
133
+ likelyFailureReason = "missed_expected_edit";
134
+ }
135
+ else if (missedReadFiles.length > 0) {
136
+ likelyFailureReason = "missed_expected_read";
137
+ }
138
+ else if (missedTestFiles.length > 0) {
139
+ likelyFailureReason = "missed_expected_test";
140
+ }
141
+ else if (!v2.editRecall1) {
142
+ likelyFailureReason = "weak_retrieval_evidence";
143
+ }
144
+ return {
145
+ taskId: goldenTask.id,
146
+ missedEditFiles,
147
+ missedReadFiles,
148
+ missedTestFiles,
149
+ wrongTop1,
150
+ noisePromotedAsEdit,
151
+ mustNotEditViolations,
152
+ missingSlots,
153
+ likelyFailureReason,
154
+ topFiles: topFilesList,
155
+ first3Files,
156
+ };
157
+ }
158
+ // ── Semantic contribution ─────────────────────────────────────────────────────
159
+ function computeSemanticContribution(result) {
160
+ const stats = result.stats;
161
+ const status = stats.semanticRetrievalStatus ?? "disabled";
162
+ return {
163
+ semanticStatus: status,
164
+ semanticAddedCandidates: status === "active" ? (stats.semanticAddedFileCount ?? 0) : 0,
165
+ semanticSupportedCandidates: status === "active" ? (stats.semanticHitCount ?? 0) : 0,
166
+ semanticAcceptedAsTopK: 0, // requires deterministic comparison — not available in single-run mode
167
+ semanticTop3Delta: 0, // requires deterministic comparison
168
+ semanticErrorType: status === "error_fallback" ? (stats.semanticErrorType ?? "unknown") : null,
169
+ };
170
+ }
171
+ function aggregateSemanticContribution(perTask, results) {
172
+ const anyEnabled = results.some((r) => r.stats.semanticRetrievalStatus === "active");
173
+ const anyError = results.some((r) => r.stats.semanticRetrievalStatus === "error_fallback");
174
+ const status = anyEnabled ? "active" : anyError ? "error_fallback" : "disabled";
175
+ return {
176
+ semanticEnabled: anyEnabled,
177
+ semanticStatus: status,
178
+ semanticAddedCandidates: perTask.reduce((s, t) => s + t.semanticAddedCandidates, 0),
179
+ semanticSupportedCandidates: perTask.reduce((s, t) => s + t.semanticSupportedCandidates, 0),
180
+ semanticAcceptedAsTopK: 0, // requires deterministic comparison
181
+ semanticImprovedTop3: 0, // requires deterministic comparison
182
+ semanticRegressedTop3: 0, // requires deterministic comparison
183
+ semanticNoOp: anyEnabled ? perTask.filter((t) => t.semanticTop3Delta === 0).length : 0,
184
+ semanticErrorType: anyError ? (results.find((r) => r.stats.semanticErrorType)?.stats.semanticErrorType ?? null) : null,
185
+ };
186
+ }
187
+ // ── Aggregate ─────────────────────────────────────────────────────────────────
188
+ function aggregateAccuracyV2(perTask, failureAnalysis, semanticContribution, roleConfusionMatrix, roleClassifierReport, name) {
189
+ const n = perTask.length || 1;
190
+ const avg = (vals) => vals.reduce((s, v) => s + v, 0) / vals.length;
191
+ const frac = (vals) => vals.filter(Boolean).length / n;
192
+ const noiseTotal = perTask.reduce((s, t) => s + t.noisePromotedAsEditTotal, 0);
193
+ const noiseCount = perTask.reduce((s, t) => s + t.noisePromotedAsEditCount, 0);
194
+ const er1 = frac(perTask.map((t) => t.editRecall1));
195
+ const er3 = frac(perTask.map((t) => t.editRecall3));
196
+ const er5 = frac(perTask.map((t) => t.editRecall5));
197
+ const er10 = frac(perTask.map((t) => t.editRecall10));
198
+ const pep3 = avg(perTask.map((t) => t.primaryEditPrecision3));
199
+ const rcr10 = avg(perTask.map((t) => t.readContextRecall10));
200
+ const tr10 = avg(perTask.map((t) => t.testRecall10));
201
+ const mnvr = frac(perTask.map((t) => t.mustNotEditViolation));
202
+ const rsc = avg(perTask.map((t) => t.requiredSlotCoverage));
203
+ const msr = avg(perTask.map((t) => t.missingSlotRate));
204
+ const f3h = frac(perTask.map((t) => t.first3Hit));
205
+ return {
206
+ name,
207
+ taskCount: perTask.length,
208
+ perTask,
209
+ // camelCase
210
+ editRecall1: er1,
211
+ editRecall3: er3,
212
+ editRecall5: er5,
213
+ editRecall10: er10,
214
+ primaryEditPrecision3: pep3,
215
+ readContextRecall10: rcr10,
216
+ testRecall10: tr10,
217
+ mustNotEditViolationRate: mnvr,
218
+ noisePromotedAsEditRate: noiseTotal > 0 ? noiseCount / noiseTotal : 0,
219
+ requiredSlotCoverage: rsc,
220
+ missingSlotRate: msr,
221
+ first3HitRate: f3h,
222
+ // Public metric keys
223
+ "edit_recall@1": er1,
224
+ "edit_recall@3": er3,
225
+ "edit_recall@5": er5,
226
+ "edit_recall@10": er10,
227
+ "primary_edit_precision@3": pep3,
228
+ "read_context_recall@10": rcr10,
229
+ "test_recall@10": tr10,
230
+ "must_not_edit_violation_rate": mnvr,
231
+ "noise_promoted_as_edit_rate": noiseTotal > 0 ? noiseCount / noiseTotal : 0,
232
+ "required_slot_coverage": rsc,
233
+ "missing_slot_rate": msr,
234
+ "first3_hit_rate": f3h,
235
+ // V2 extended fields
236
+ failureAnalysis,
237
+ semanticContribution,
238
+ retrievalModes: {
239
+ deterministic: "active",
240
+ semantic_fused_when_configured: semanticContribution.semanticEnabled ? "active" : "disabled_no_config",
241
+ file_only: "report_only_unavailable",
242
+ chunk_only_placeholder: "report_only_unavailable",
243
+ oracle_fusion_upper_bound: "report_only_unavailable",
244
+ },
245
+ roleConfusionMatrix,
246
+ roleClassifierReport,
247
+ };
248
+ }
249
+ function computeSingleRoleMatrix(results, goldenTasks, riskMode) {
250
+ const cells = {
251
+ expected_edit: { predicted_edit: 0, predicted_read: 0, predicted_test: 0, predicted_noise: 0, missing: 0 },
252
+ expected_read: { predicted_edit: 0, predicted_read: 0, predicted_test: 0, predicted_noise: 0, missing: 0 },
253
+ expected_test: { predicted_edit: 0, predicted_read: 0, predicted_test: 0, predicted_noise: 0, missing: 0 },
254
+ must_not: { predicted_edit: 0, predicted_read: 0, predicted_test: 0, predicted_noise: 0, missing: 0 },
255
+ };
256
+ const failureExamples = [];
257
+ let allPredictedEdit = 0;
258
+ let allPredictedRead = 0;
259
+ let allPredictedTest = 0;
260
+ for (let i = 0; i < goldenTasks.length; i++) {
261
+ const gt = goldenTasks[i];
262
+ const result = results[i];
263
+ if (!result)
264
+ continue;
265
+ const groups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units);
266
+ const topPaths = new Set(groups.slice(0, 40).map((g) => g.relativePath));
267
+ const predictedEdits = new Set(groups.filter((g) => g.role === "edit_candidate").map((g) => g.relativePath));
268
+ const predictedReads = new Set(groups.filter((g) => g.role === "read_context" || g.role === "impact").map((g) => g.relativePath));
269
+ const predictedTests = new Set(groups.filter((g) => g.role === "test" || g.role === "verify").map((g) => g.relativePath));
270
+ const predictedNoise = new Set(result.solvePacket?.noiseRisk.map((f) => f.relativePath) ?? []);
271
+ const doNotEdit = new Set(result.solvePacket?.doNotEditYet ?? []);
272
+ allPredictedEdit += predictedEdits.size;
273
+ allPredictedRead += predictedReads.size;
274
+ allPredictedTest += predictedTests.size;
275
+ for (const f of topPaths) {
276
+ for (const rolePairs of [
277
+ { files: gt.expectedEditFiles, role: "expected_edit" },
278
+ { files: gt.expectedReadFiles, role: "expected_read" },
279
+ { files: gt.expectedTestFiles, role: "expected_test" },
280
+ { files: gt.mustNotEditFiles, role: "must_not" },
281
+ ]) {
282
+ if (rolePairs.files.some((ef) => (0, evaluationMetrics_1.pathMatches)(f, ef))) {
283
+ let pred = "missing";
284
+ if (predictedEdits.has(f))
285
+ pred = "predicted_edit";
286
+ else if (predictedReads.has(f))
287
+ pred = "predicted_read";
288
+ else if (predictedTests.has(f))
289
+ pred = "predicted_test";
290
+ else if (predictedNoise.has(f) || doNotEdit.has(f))
291
+ pred = "predicted_noise";
292
+ cells[rolePairs.role][pred]++;
293
+ if ((rolePairs.role === "expected_edit" && pred !== "predicted_edit") ||
294
+ (rolePairs.role === "must_not" && pred === "predicted_edit") ||
295
+ (rolePairs.role === "expected_test" && pred !== "predicted_test" && pred !== "predicted_read")) {
296
+ failureExamples.push({ taskId: gt.id, filePath: f, expectedRole: rolePairs.role, predictedRole: pred });
297
+ }
298
+ }
299
+ }
300
+ }
301
+ // Count expected files NOT in top results
302
+ for (const ef of gt.expectedEditFiles) {
303
+ if (!Array.from(topPaths).some((p) => (0, evaluationMetrics_1.pathMatches)(p, ef))) {
304
+ cells.expected_edit.missing++;
305
+ failureExamples.push({ taskId: gt.id, filePath: ef, expectedRole: "expected_edit", predictedRole: "missing" });
306
+ }
307
+ }
308
+ for (const rf of gt.expectedReadFiles) {
309
+ if (!Array.from(topPaths).some((p) => (0, evaluationMetrics_1.pathMatches)(p, rf)))
310
+ cells.expected_read.missing++;
311
+ }
312
+ for (const tf of gt.expectedTestFiles) {
313
+ if (!Array.from(topPaths).some((p) => (0, evaluationMetrics_1.pathMatches)(p, tf)))
314
+ cells.expected_test.missing++;
315
+ }
316
+ }
317
+ const rowTotals = {
318
+ expected_edit: Object.values(cells.expected_edit).reduce((s, v) => s + v, 0),
319
+ expected_read: Object.values(cells.expected_read).reduce((s, v) => s + v, 0),
320
+ expected_test: Object.values(cells.expected_test).reduce((s, v) => s + v, 0),
321
+ must_not: Object.values(cells.must_not).reduce((s, v) => s + v, 0),
322
+ };
323
+ // Fix: predictedRoleAccuracy = correct predictions / all predictions of that role
324
+ // expectedRowRecall = correct / expected total (same as before, renamed)
325
+ const pae = allPredictedEdit || 1;
326
+ const par = allPredictedRead || 1;
327
+ const pat = allPredictedTest || 1;
328
+ return {
329
+ cells,
330
+ rowTotals,
331
+ predictedRoleAccuracy: {
332
+ edit: cells.expected_edit.predicted_edit / pae,
333
+ read: cells.expected_read.predicted_read / par,
334
+ test: cells.expected_test.predicted_test / pat,
335
+ },
336
+ expectedRowRecall: {
337
+ edit: rowTotals.expected_edit > 0 ? cells.expected_edit.predicted_edit / (cells.expected_edit.predicted_edit + cells.expected_edit.missing || 1) : 0,
338
+ read: rowTotals.expected_read > 0 ? cells.expected_read.predicted_read / (cells.expected_read.predicted_read + cells.expected_read.missing || 1) : 0,
339
+ test: rowTotals.expected_test > 0 ? cells.expected_test.predicted_test / (cells.expected_test.predicted_test + cells.expected_test.missing || 1) : 0,
340
+ },
341
+ failureExamples: failureExamples.slice(0, 20),
342
+ riskMode,
343
+ };
344
+ }
345
+ function computeRoleClassifierReport(results, goldenTasks) {
346
+ // Currently same classifier for all modes; report-only
347
+ const balanced = computeSingleRoleMatrix(results, goldenTasks, "balanced");
348
+ const strict = computeSingleRoleMatrix(results, goldenTasks, "strict");
349
+ const broad = computeSingleRoleMatrix(results, goldenTasks, "broad");
350
+ return {
351
+ defaultMode: "balanced",
352
+ modeImplementation: "same_current_classifier_report_only",
353
+ modeSemantics: {
354
+ strict: "prefer fewer edit candidates; aggressively protect must_not/noise",
355
+ balanced: "current/default behavior",
356
+ broad: "wider context; lower-confidence files must be marked clearly",
357
+ },
358
+ modes: { strict, balanced, broad },
359
+ failureExamples: balanced.failureExamples,
360
+ };
361
+ }
362
+ function renderRoleClassifierReportMarkdown(report) {
363
+ const lines = ["### Role Classifier Report", "", `Default mode: ${report.defaultMode}`,
364
+ `Implementation: ${report.modeImplementation}`, ""];
365
+ for (const mode of ["strict", "balanced", "broad"]) {
366
+ const m = report.modes[mode];
367
+ lines.push(`#### ${mode} mode`, "");
368
+ lines.push("| Expected \\ Predicted | edit | read | test | noise | missing | Total |");
369
+ lines.push("|-----------------------|------|------|------|-------|---------|-------|");
370
+ for (const row of [
371
+ { label: "expected_edit", key: "expected_edit" },
372
+ { label: "expected_read", key: "expected_read" },
373
+ { label: "expected_test", key: "expected_test" },
374
+ { label: "must_not", key: "must_not" },
375
+ ]) {
376
+ const c = m.cells[row.key];
377
+ lines.push(`| ${row.label} | ${c.predicted_edit} | ${c.predicted_read} | ${c.predicted_test} | ${c.predicted_noise} | ${c.missing} | ${m.rowTotals[row.key]} |`);
378
+ }
379
+ lines.push("", `Predicted-role accuracy: edit=${(m.predictedRoleAccuracy.edit * 100).toFixed(1)}% read=${(m.predictedRoleAccuracy.read * 100).toFixed(1)}% test=${(m.predictedRoleAccuracy.test * 100).toFixed(1)}%`, `Expected-row recall: edit=${(m.expectedRowRecall.edit * 100).toFixed(1)}% read=${(m.expectedRowRecall.read * 100).toFixed(1)}% test=${(m.expectedRowRecall.test * 100).toFixed(1)}%`, "");
380
+ }
381
+ if (report.failureExamples.length > 0) {
382
+ lines.push("### Role Failure Examples", "");
383
+ for (const ex of report.failureExamples.slice(0, 10)) {
384
+ lines.push(`- **${ex.taskId}**: \`${ex.filePath}\` expected=${ex.expectedRole} predicted=${ex.predictedRole}`);
385
+ }
386
+ lines.push("");
387
+ }
388
+ return lines;
389
+ }
390
+ // Kept for backward compat
391
+ function computeRoleConfusionMatrix(results, goldenTasks, riskMode = "balanced") {
392
+ return computeSingleRoleMatrix(results, goldenTasks, riskMode);
393
+ }
394
+ function renderAccuracyV2Markdown(report) {
395
+ const lines = [
396
+ "## Accuracy V2",
397
+ "",
398
+ `Tasks: ${report.taskCount}`,
399
+ "",
400
+ "### Retrieval Metrics",
401
+ "",
402
+ `| Metric | Value |`,
403
+ `|--------|-------|`,
404
+ `| edit_recall@1 | ${(report.editRecall1 * 100).toFixed(1)}% |`,
405
+ `| edit_recall@3 | ${(report.editRecall3 * 100).toFixed(1)}% |`,
406
+ `| edit_recall@5 | ${(report.editRecall5 * 100).toFixed(1)}% |`,
407
+ `| edit_recall@10 | ${(report.editRecall10 * 100).toFixed(1)}% |`,
408
+ `| primary_edit_precision@3 | ${(report.primaryEditPrecision3 * 100).toFixed(1)}% |`,
409
+ `| read_context_recall@10 | ${(report.readContextRecall10 * 100).toFixed(1)}% |`,
410
+ `| test_recall@10 | ${(report.testRecall10 * 100).toFixed(1)}% |`,
411
+ "",
412
+ "### Safety Metrics",
413
+ "",
414
+ `| Metric | Value |`,
415
+ `|--------|-------|`,
416
+ `| must_not_edit_violation_rate | ${(report.mustNotEditViolationRate * 100).toFixed(1)}% |`,
417
+ `| noise_promoted_as_edit_rate | ${(report.noisePromotedAsEditRate * 100).toFixed(1)}% |`,
418
+ "",
419
+ "### Flow Completeness",
420
+ "",
421
+ `| Metric | Value |`,
422
+ `|--------|-------|`,
423
+ `| required_slot_coverage | ${(report.requiredSlotCoverage * 100).toFixed(1)}% |`,
424
+ `| missing_slot_rate | ${(report.missingSlotRate * 100).toFixed(1)}% |`,
425
+ `| first3_hit_rate | ${(report.first3HitRate * 100).toFixed(1)}% |`,
426
+ "",
427
+ "### Per-Task Detail",
428
+ "",
429
+ ];
430
+ for (const t of report.perTask) {
431
+ lines.push(`- **${t.taskId}**: editR@1=${t.editRecall1} editR@3=${t.editRecall3} editR@5=${t.editRecall5} editR@10=${t.editRecall10} editP@3=${t.primaryEditPrecision3.toFixed(2)} readR@10=${t.readContextRecall10.toFixed(2)} testR@10=${t.testRecall10.toFixed(2)} mustNotViolation=${t.mustNotEditViolation} noiseAsEdit=${t.noisePromotedAsEditCount}/${t.noisePromotedAsEditTotal} slotCov=${t.requiredSlotCoverage.toFixed(2)} missingSlot=${t.missingSlotRate.toFixed(2)} first3Hit=${t.first3Hit}`);
432
+ }
433
+ // Failure analysis section
434
+ lines.push("");
435
+ lines.push("### Failure Analysis", "");
436
+ for (const fa of report.failureAnalysis) {
437
+ lines.push(`#### ${fa.taskId}`);
438
+ lines.push(`- **likelyFailureReason:** ${fa.likelyFailureReason}`);
439
+ lines.push(`- **missedEditFiles:** ${fa.missedEditFiles.join(", ") || "none"}`);
440
+ lines.push(`- **missedReadFiles:** ${fa.missedReadFiles.join(", ") || "none"}`);
441
+ lines.push(`- **missedTestFiles:** ${fa.missedTestFiles.join(", ") || "none"}`);
442
+ lines.push(`- **wrongTop1:** ${fa.wrongTop1 ?? "none"}`);
443
+ lines.push(`- **noisePromotedAsEdit:** ${fa.noisePromotedAsEdit.join(", ") || "none"}`);
444
+ lines.push(`- **mustNotEditViolations:** ${fa.mustNotEditViolations.join(", ") || "none"}`);
445
+ lines.push(`- **missingSlots:** ${fa.missingSlots.join(", ") || "none"}`);
446
+ lines.push(`- **first3Files:** ${fa.first3Files.join(", ") || "none"}`);
447
+ lines.push(`- **topFiles:** ${fa.topFiles.map((f) => `${f.rank}.${f.path}[${f.role}]`).join(" ")}`);
448
+ lines.push("");
449
+ }
450
+ // Semantic contribution section
451
+ const sc = report.semanticContribution;
452
+ lines.push("### Semantic Contribution", "");
453
+ lines.push(`| Field | Value |`);
454
+ lines.push(`|-------|-------|`);
455
+ lines.push(`| semanticEnabled | ${sc.semanticEnabled} |`);
456
+ lines.push(`| semanticStatus | ${sc.semanticStatus} |`);
457
+ lines.push(`| semanticAddedCandidates | ${sc.semanticAddedCandidates} |`);
458
+ lines.push(`| semanticSupportedCandidates | ${sc.semanticSupportedCandidates} |`);
459
+ lines.push(`| semanticAcceptedAsTopK | ${sc.semanticAcceptedAsTopK} |`);
460
+ lines.push(`| semanticImprovedTop3 | ${sc.semanticImprovedTop3} |`);
461
+ lines.push(`| semanticRegressedTop3 | ${sc.semanticRegressedTop3} |`);
462
+ lines.push(`| semanticNoOp | ${sc.semanticNoOp} |`);
463
+ lines.push(`| semanticErrorType | ${sc.semanticErrorType ?? "none"} |`);
464
+ lines.push("");
465
+ // Retrieval modes section
466
+ const rm = report.retrievalModes;
467
+ lines.push("### Retrieval Modes", "");
468
+ lines.push(`| Mode | Status |`);
469
+ lines.push(`|------|--------|`);
470
+ lines.push(`| deterministic | ${rm.deterministic} |`);
471
+ lines.push(`| semantic_fused_when_configured | ${rm.semantic_fused_when_configured} |`);
472
+ lines.push(`| file_only | ${rm.file_only} |`);
473
+ lines.push(`| chunk_only_placeholder | ${rm.chunk_only_placeholder} |`);
474
+ lines.push(`| oracle_fusion_upper_bound | ${rm.oracle_fusion_upper_bound} |`);
475
+ lines.push("");
476
+ lines.push("");
477
+ if (report.roleClassifierReport) {
478
+ lines.push(...renderRoleClassifierReportMarkdown(report.roleClassifierReport));
479
+ }
480
+ lines.push("");
481
+ return lines;
482
+ }
483
+ //# sourceMappingURL=accuracyV2.js.map
@@ -0,0 +1,192 @@
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.validateGoldenTask = validateGoldenTask;
37
+ exports.validateGoldenManifest = validateGoldenManifest;
38
+ const fssync = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ function validateGoldenTask(task, index) {
41
+ const issues = [];
42
+ const prefix = `GoldenTask[${index}]`;
43
+ if (!task || typeof task !== "object") {
44
+ return [`${prefix}: not an object`];
45
+ }
46
+ const t = task;
47
+ // Required string fields — must be non-empty strings
48
+ const stringFields = ["id", "repoType", "repoName", "workspace", "task", "notes"];
49
+ for (const field of stringFields) {
50
+ const val = t[field];
51
+ if (typeof val !== "string" || !val.trim()) {
52
+ issues.push(`${prefix}: ${field} must be a non-empty string`);
53
+ }
54
+ }
55
+ // Array fields — must be arrays of non-empty strings
56
+ const stringArrayFields = [
57
+ { key: "expectedEditFiles", arr: t.expectedEditFiles },
58
+ { key: "expectedReadFiles", arr: t.expectedReadFiles },
59
+ { key: "expectedTestFiles", arr: t.expectedTestFiles },
60
+ { key: "mustNotEditFiles", arr: t.mustNotEditFiles },
61
+ { key: "requiredSlots", arr: t.requiredSlots },
62
+ { key: "tags", arr: t.tags },
63
+ ];
64
+ for (const { key, arr } of stringArrayFields) {
65
+ if (!Array.isArray(arr)) {
66
+ issues.push(`${prefix}: ${key} must be an array`);
67
+ continue;
68
+ }
69
+ for (let i = 0; i < arr.length; i++) {
70
+ if (typeof arr[i] !== "string" || !arr[i].trim()) {
71
+ issues.push(`${prefix}: ${key}[${i}] must be a non-empty string`);
72
+ }
73
+ }
74
+ }
75
+ // Path safety checks for file arrays
76
+ const fileArrays = [
77
+ { key: "expectedEditFiles", arr: t.expectedEditFiles },
78
+ { key: "expectedReadFiles", arr: t.expectedReadFiles },
79
+ { key: "expectedTestFiles", arr: t.expectedTestFiles },
80
+ { key: "mustNotEditFiles", arr: t.mustNotEditFiles },
81
+ ];
82
+ for (const { key, arr } of fileArrays) {
83
+ if (!Array.isArray(arr))
84
+ continue;
85
+ for (let i = 0; i < arr.length; i++) {
86
+ const p = arr[i];
87
+ if (!p || !p.trim())
88
+ continue;
89
+ if (p.includes("..")) {
90
+ issues.push(`${prefix}: ${key}[${i}] contains path traversal (".."): ${p}`);
91
+ }
92
+ if (/^[A-Za-z]:[\\/]/.test(p) || p.startsWith("/")) {
93
+ issues.push(`${prefix}: ${key}[${i}] is an absolute path: ${p}`);
94
+ }
95
+ }
96
+ }
97
+ // Duplicate file paths across edit/read/test/must-not roles
98
+ if (Array.isArray(t.expectedEditFiles) && Array.isArray(t.expectedReadFiles) &&
99
+ Array.isArray(t.expectedTestFiles) && Array.isArray(t.mustNotEditFiles)) {
100
+ const fileSet = new Set();
101
+ const allFiles = [];
102
+ for (const f of t.expectedEditFiles) {
103
+ allFiles.push({ key: "expectedEditFiles", path: f });
104
+ }
105
+ for (const f of t.expectedReadFiles) {
106
+ allFiles.push({ key: "expectedReadFiles", path: f });
107
+ }
108
+ for (const f of t.expectedTestFiles) {
109
+ allFiles.push({ key: "expectedTestFiles", path: f });
110
+ }
111
+ for (const f of t.mustNotEditFiles) {
112
+ allFiles.push({ key: "mustNotEditFiles", path: f });
113
+ }
114
+ for (const { key, path: fp } of allFiles) {
115
+ if (!fp || !fp.trim())
116
+ continue;
117
+ const norm = fp.replace(/\\/g, "/").toLowerCase();
118
+ if (fileSet.has(norm)) {
119
+ issues.push(`${prefix}: duplicate file path "${fp}" in ${key} (normalized: ${norm})`);
120
+ }
121
+ fileSet.add(norm);
122
+ }
123
+ }
124
+ // Empty expectedEditFiles for normal edit/bugfix/feature tasks
125
+ const editFiles = Array.isArray(t.expectedEditFiles) ? t.expectedEditFiles : [];
126
+ const taskTags = Array.isArray(t.tags) ? t.tags : [];
127
+ const isReadOnly = taskTags.includes("read_only");
128
+ const isAnalysisOnly = taskTags.includes("analysis_only");
129
+ if (editFiles.length === 0 && !isReadOnly && !isAnalysisOnly) {
130
+ issues.push(`${prefix}: expectedEditFiles is empty; tag as read_only or analysis_only if no edits expected`);
131
+ }
132
+ return issues;
133
+ }
134
+ function validateGoldenManifest(manifest, workspaceRoot) {
135
+ if (!manifest || typeof manifest !== "object") {
136
+ return ["GoldenEvalManifest: not an object"];
137
+ }
138
+ const m = manifest;
139
+ const issues = [];
140
+ if (typeof m.name !== "string" || !m.name.trim())
141
+ issues.push("GoldenEvalManifest: name must be a non-empty string");
142
+ if (typeof m.workspace !== "string" || !m.workspace.trim())
143
+ issues.push("GoldenEvalManifest: workspace must be a non-empty string");
144
+ if (!Array.isArray(m.tasks) || m.tasks.length === 0) {
145
+ issues.push("GoldenEvalManifest: tasks must be a non-empty array");
146
+ return issues;
147
+ }
148
+ // Duplicate task ids
149
+ const taskIds = new Set();
150
+ for (let i = 0; i < m.tasks.length; i++) {
151
+ const t = m.tasks[i];
152
+ if (t && typeof t.id === "string" && t.id.trim()) {
153
+ if (taskIds.has(t.id)) {
154
+ issues.push(`GoldenTask[${i}]: duplicate task id "${t.id}"`);
155
+ }
156
+ taskIds.add(t.id);
157
+ }
158
+ }
159
+ for (let i = 0; i < m.tasks.length; i++) {
160
+ issues.push(...validateGoldenTask(m.tasks[i], i));
161
+ }
162
+ // Non-existent expected file paths (only when workspaceRoot is provided)
163
+ if (workspaceRoot && issues.length === 0) {
164
+ const wsRoot = workspaceRoot;
165
+ for (let i = 0; i < m.tasks.length; i++) {
166
+ const t = m.tasks[i];
167
+ const fileArrays = [
168
+ { key: "expectedEditFiles", arr: t.expectedEditFiles },
169
+ { key: "expectedReadFiles", arr: t.expectedReadFiles },
170
+ { key: "expectedTestFiles", arr: t.expectedTestFiles },
171
+ { key: "mustNotEditFiles", arr: t.mustNotEditFiles },
172
+ ];
173
+ for (const { key, arr } of fileArrays) {
174
+ if (!Array.isArray(arr))
175
+ continue;
176
+ for (let j = 0; j < arr.length; j++) {
177
+ const fp = arr[j];
178
+ if (!fp || !fp.trim())
179
+ continue;
180
+ if (fp.includes("*"))
181
+ continue; // glob patterns not checked
182
+ const abs = path.join(wsRoot, fp);
183
+ if (!fssync.existsSync(abs)) {
184
+ issues.push(`GoldenTask[${i}]: ${key}[${j}] path does not exist: ${fp} (workspace: ${wsRoot})`);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ return issues;
191
+ }
192
+ //# sourceMappingURL=goldenTask.js.map