flowseeker 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/CHANGELOG.md +143 -108
- package/README.md +288 -221
- package/dist/chat/nativeChatParticipant.js +1 -1
- package/dist/cli/flowCommand.js +175 -0
- package/dist/cli/main.js +1810 -0
- package/dist/cli/mcpServer.js +7 -1
- package/dist/cli/runEvaluation.js +281 -2
- package/dist/config/defaultConfig.js +11 -1
- package/dist/config/env.js +118 -0
- package/dist/config/loadConfig.js +18 -1
- package/dist/config/loadConfigFromPath.js +3 -1
- package/dist/eval/accuracyV2.js +483 -0
- package/dist/eval/goldenTask.js +192 -0
- package/dist/extension.js +23 -0
- package/dist/framework/laravel.js +177 -0
- package/dist/gateway/embeddingProviders.js +852 -0
- package/dist/index/cacheStore.js +43 -0
- package/dist/index/configRouteDiscoveryProbe.js +288 -0
- package/dist/index/embeddingIndex.js +193 -0
- package/dist/index/graphIndex.js +460 -0
- package/dist/index/indexWatcher.js +86 -0
- package/dist/index/semanticChunkIndex.js +388 -0
- package/dist/index/structuredExtractor.js +303 -12
- package/dist/index/treeSitterExtractor.js +264 -0
- package/dist/index/vectorStore.js +41 -0
- package/dist/index/workspaceIndex.js +901 -26
- package/dist/mcp/mcpTools.js +1678 -2
- package/dist/pipeline/contextBlueprint.js +15 -2
- package/dist/pipeline/contextPack.js +3 -3
- package/dist/pipeline/deterministicReranker.js +358 -0
- package/dist/pipeline/evaluationMetrics.js +14 -2
- package/dist/pipeline/fileGroups.js +7 -1
- package/dist/pipeline/fileScanner.js +209 -12
- package/dist/pipeline/fusionTrace.js +149 -0
- package/dist/pipeline/llmReranker.js +151 -0
- package/dist/pipeline/nodeScan.js +102 -12
- package/dist/pipeline/ranker.js +875 -16
- package/dist/pipeline/retrievalFusion.js +41 -0
- package/dist/pipeline/roleRefinement.js +62 -0
- package/dist/pipeline/runHeadless.js +60 -5
- package/dist/pipeline/runPipeline.js +2 -2
- package/dist/pipeline/solvePacket.js +656 -43
- package/dist/pipeline/subsystem.js +21 -0
- package/dist/pipeline/taskUnderstanding.js +2 -2
- package/dist/ui/chatViewProvider.js +1 -1
- package/docs/demo-screenshot-checklist.md +86 -0
- package/docs/marketplace-copy.md +92 -0
- package/docs/mcp-onboarding.md +191 -0
- package/package.json +633 -561
|
@@ -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
|