@dailephd/my-dev-kit-lab 0.7.2 → 0.8.1
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/CHANGELOG.md +33 -1
- package/README.md +9 -3
- package/benchmarks/contracts/warm-index-benchmark-cases.json +330 -0
- package/dist/src/cli/help.js +32 -1
- package/dist/src/commands/runExperimentDescribeCommand.js +16 -1
- package/dist/src/commands/runExperimentRunCommand.js +181 -4
- package/dist/src/evaluation/benchmarkMetadata.js +13 -0
- package/dist/src/evaluation/retrievalQuality/buildRetrievalEvidence.js +292 -0
- package/dist/src/evaluation/retrievalQuality/corpusCompleteness.js +122 -0
- package/dist/src/evaluation/retrievalQuality/factContextTargets.js +44 -0
- package/dist/src/evaluation/retrievalQuality/index.js +5 -0
- package/dist/src/evaluation/retrievalQuality/metrics.js +258 -0
- package/dist/src/evaluation/retrievalQuality/types.js +9 -0
- package/dist/src/evaluation/retrievalQueryStrategies.js +26 -0
- package/dist/src/evaluation/retrievalQueryStrategyEvidence.js +58 -0
- package/dist/src/evaluation/runMyDevKitRetrieval.js +61 -50
- package/dist/src/evaluation/runSemanticRetrievalStrategy.js +303 -0
- package/dist/src/experiments/defaultRegistry.js +4 -0
- package/dist/src/experiments/plugins/contextWindowScaling/localSubjectPrivacy.js +2 -0
- package/dist/src/experiments/plugins/index.js +2 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/config.js +39 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/execution.js +152 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/executionArtifact.js +15 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/index.js +9 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/localSubjectExecution.js +139 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/localSubjectPrivacy.js +119 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/metadata.js +21 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/metrics.js +158 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/plugin.js +253 -0
- package/dist/src/experiments/plugins/retrievalPrecisionRecall/types.js +8 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/analysis.js +172 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/analysisArtifact.js +28 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/analysisTypes.js +2 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/config.js +39 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/execution.js +225 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/executionArtifact.js +15 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/index.js +12 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/localSubjectExecution.js +124 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/localSubjectPrivacy.js +176 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/metadata.js +51 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/metrics.js +60 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/plugin.js +292 -0
- package/dist/src/experiments/plugins/retrievalQueryStrategyComparison/types.js +1 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +44 -2
- package/dist/src/report/experiments/buildRetrievalPrecisionRecallReport.js +83 -0
- package/dist/src/report/experiments/buildRetrievalQueryStrategyComparisonReport.js +91 -0
- package/dist/src/report/experiments/index.js +8 -0
- package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +6 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +18 -0
- package/dist/src/report/experiments/renderRetrievalPrecisionRecallHtml.js +96 -0
- package/dist/src/report/experiments/renderRetrievalPrecisionRecallText.js +121 -0
- package/dist/src/report/experiments/renderRetrievalQueryStrategyComparisonHtml.js +96 -0
- package/dist/src/report/experiments/renderRetrievalQueryStrategyComparisonText.js +58 -0
- package/dist/src/report/experiments/retrievalPrecisionRecallReportModel.js +3 -0
- package/dist/src/report/experiments/retrievalQueryStrategyComparisonReportModel.js +1 -0
- package/docs/METRICS.md +152 -2
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { RETRIEVAL_QUERY_STRATEGY_IDS } from "../../../evaluation/retrievalQueryStrategies.js";
|
|
2
|
+
/** Projects already-calculated scientific values onto generic metrics. Owns no formula. */
|
|
3
|
+
export function toRetrievalQueryStrategyOutcomeMetrics(treatment, caseId) {
|
|
4
|
+
const base = { variantId: treatment.strategyId, caseId };
|
|
5
|
+
const quality = treatment.quality;
|
|
6
|
+
const ratio = (id, name, metric) => ({
|
|
7
|
+
...base,
|
|
8
|
+
id,
|
|
9
|
+
name,
|
|
10
|
+
value: metric?.value ?? null,
|
|
11
|
+
unit: "ratio"
|
|
12
|
+
});
|
|
13
|
+
const count = (id, name, value) => ({
|
|
14
|
+
...base,
|
|
15
|
+
id,
|
|
16
|
+
name,
|
|
17
|
+
value: value ?? null,
|
|
18
|
+
unit: "count"
|
|
19
|
+
});
|
|
20
|
+
return [
|
|
21
|
+
ratio("file-precision", "File precision", quality?.file.precision),
|
|
22
|
+
ratio("file-recall", "File recall", quality?.file.recall),
|
|
23
|
+
ratio("file-f1", "File F1", treatment.fileF1),
|
|
24
|
+
ratio("symbol-precision", "Symbol precision", quality?.symbol.precision),
|
|
25
|
+
ratio("symbol-recall", "Symbol recall", quality?.symbol.recall),
|
|
26
|
+
ratio("symbol-f1", "Symbol F1", treatment.symbolF1),
|
|
27
|
+
ratio("fact-coverage", "Fact coverage", quality?.fact.coverage),
|
|
28
|
+
ratio("irrelevant-context-ratio", "Irrelevant context ratio", quality?.irrelevantContextRatio),
|
|
29
|
+
{ ...base, id: "retrieved-token-count", name: "Retrieved token count", value: quality?.retrievedTokenCount ?? null, unit: "tokens" },
|
|
30
|
+
count("missed-file-count", "Missed file count", quality?.file.missedFileCount),
|
|
31
|
+
count("missed-symbol-count", "Missed symbol count", quality?.symbol.missedSymbolCount),
|
|
32
|
+
count("uncovered-fact-count", "Uncovered fact count", quality?.fact.uncoveredFactCount),
|
|
33
|
+
count("irrelevant-retrieved-file-count", "Irrelevant retrieved file count", quality?.file.irrelevantRetrievedFiles?.length),
|
|
34
|
+
count("irrelevant-retrieved-symbol-count", "Irrelevant retrieved symbol count", quality?.symbol.irrelevantRetrievedSymbols?.length)
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/** Per scope: one matched-case count, then four objective means per strategy in canonical order. No best/rank/score metric. */
|
|
38
|
+
export function toRetrievalQueryStrategyRunMetrics(analysis) {
|
|
39
|
+
const metrics = [];
|
|
40
|
+
for (const scope of analysis.scopes) {
|
|
41
|
+
const { scopeId } = scope;
|
|
42
|
+
metrics.push({
|
|
43
|
+
id: `${scopeId}-comparison-case-count`,
|
|
44
|
+
name: `${scopeId} matched comparison cases`,
|
|
45
|
+
value: scope.comparisonCaseCount,
|
|
46
|
+
unit: "count"
|
|
47
|
+
});
|
|
48
|
+
for (const strategyId of RETRIEVAL_QUERY_STRATEGY_IDS) {
|
|
49
|
+
const objectives = scope.strategySummaries.find((summary) => summary.strategyId === strategyId)?.objectives ?? null;
|
|
50
|
+
metrics.push({ id: `${scopeId}-mean-file-f1`, name: `${scopeId} mean file F1`, value: objectives?.meanFileF1 ?? null, unit: "ratio", variantId: strategyId }, { id: `${scopeId}-mean-symbol-f1`, name: `${scopeId} mean symbol F1`, value: objectives?.meanSymbolF1 ?? null, unit: "ratio", variantId: strategyId }, { id: `${scopeId}-mean-fact-coverage`, name: `${scopeId} mean fact coverage`, value: objectives?.meanFactCoverage ?? null, unit: "ratio", variantId: strategyId }, {
|
|
51
|
+
id: `${scopeId}-mean-retrieved-token-count`,
|
|
52
|
+
name: `${scopeId} mean retrieved token count`,
|
|
53
|
+
value: objectives?.meanRetrievedTokenCount ?? null,
|
|
54
|
+
unit: "tokens",
|
|
55
|
+
variantId: strategyId
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return metrics;
|
|
60
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveWithinRoot } from "../../../core/pathSafety.js";
|
|
4
|
+
import { serializeLocalRepositorySubjectManifest } from "../../../evaluation/localRepositorySubject/index.js";
|
|
5
|
+
import { summarizeExperimentRun } from "../../results.js";
|
|
6
|
+
import { LocalSubjectExecutionError } from "../contextWindowScaling/localSubjectErrors.js";
|
|
7
|
+
import { projectExternalLocalTarget } from "../contextWindowScaling/localSubjectPrivacy.js";
|
|
8
|
+
import { isSamePhysicalDirectory } from "../contextWindowScaling/localSubjectScratch.js";
|
|
9
|
+
import { selectWarmIndexCases } from "../warmIndexReuse/selection.js";
|
|
10
|
+
import { defaultRetrievalQueryStrategyComparisonConfig, retrievalQueryStrategyComparisonConfigDefinition, validateRetrievalQueryStrategyComparisonConfig } from "./config.js";
|
|
11
|
+
import { analyzeRetrievalQueryStrategyComparison } from "./analysis.js";
|
|
12
|
+
import { buildRetrievalQueryStrategyComparisonAnalysisArtifact, RETRIEVAL_QUERY_STRATEGY_COMPARISON_ANALYSIS_ARTIFACT_FILE } from "./analysisArtifact.js";
|
|
13
|
+
import { executeRetrievalQueryStrategyCaseFromPreparedIndex, executeRetrievalQueryStrategyComparison } from "./execution.js";
|
|
14
|
+
import { executeLocalRepositorySubjectRetrievalQueryStrategyComparison } from "./localSubjectExecution.js";
|
|
15
|
+
import { assertRetrievalQueryStrategyExternalProjectionIsPrivate, describeRetrievalQueryStrategyLocalSubjectFailureForPersistence, projectRetrievalQueryStrategyAnalysisForExternalLocalPersistence, projectRetrievalQueryStrategyExecutionForExternalLocalPersistence, RETRIEVAL_QUERY_STRATEGY_UNEXPECTED_EXTERNAL_FAILURE_MESSAGE } from "./localSubjectPrivacy.js";
|
|
16
|
+
import { buildRetrievalQueryStrategyComparisonExecutionArtifact, RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXECUTION_ARTIFACT_FILE } from "./executionArtifact.js";
|
|
17
|
+
import { RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID, RETRIEVAL_QUERY_STRATEGY_COMPARISON_VARIANTS, retrievalQueryStrategyComparisonMetadata } from "./metadata.js";
|
|
18
|
+
import { toRetrievalQueryStrategyOutcomeMetrics, toRetrievalQueryStrategyRunMetrics } from "./metrics.js";
|
|
19
|
+
/** Privacy-safe subject manifest persisted beside the artifacts for external-local runs (same file as the other local-subject plugins). */
|
|
20
|
+
export const RETRIEVAL_QUERY_STRATEGY_COMPARISON_LOCAL_SUBJECT_MANIFEST_FILE = "local-repository-subject-manifest.json";
|
|
21
|
+
/** An external target must always arrive with a loaded local repository subject; there is no bundled fallback against it. */
|
|
22
|
+
export const RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXTERNAL_REQUIRES_SUBJECT_MESSAGE = "External-local retrieval-query-strategy-comparison targets require a loaded local repository subject (--local-subject-config).";
|
|
23
|
+
export const retrievalQueryStrategyComparisonPlugin = {
|
|
24
|
+
metadata: retrievalQueryStrategyComparisonMetadata,
|
|
25
|
+
defaultConfig: defaultRetrievalQueryStrategyComparisonConfig,
|
|
26
|
+
configDefinition: retrievalQueryStrategyComparisonConfigDefinition,
|
|
27
|
+
supportedVariants: RETRIEVAL_QUERY_STRATEGY_COMPARISON_VARIANTS.map((variant) => variant.id),
|
|
28
|
+
validateConfig: validateRetrievalQueryStrategyComparisonConfig,
|
|
29
|
+
async run(context) {
|
|
30
|
+
const startedAt = context.startedAt.toISOString();
|
|
31
|
+
const localSubject = readLocalSubjectInput(context.inputs);
|
|
32
|
+
if (localSubject)
|
|
33
|
+
return runLocalSubject(context, localSubject, startedAt);
|
|
34
|
+
// The runner does not enforce supportedTargets; never fall back to the bundled corpus against an external repository.
|
|
35
|
+
if (context.target.kind !== "self" || context.target.isSelf !== true) {
|
|
36
|
+
throw new Error(RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXTERNAL_REQUIRES_SUBJECT_MESSAGE);
|
|
37
|
+
}
|
|
38
|
+
const cases = selectWarmIndexCases(readCasesInput(context.inputs), context.config);
|
|
39
|
+
const outDir = context.outputRoot ?? path.resolve(context.toolRoot, context.config.outDir);
|
|
40
|
+
await mkdir(outDir, { recursive: true });
|
|
41
|
+
const caseEvidence = await executeRetrievalQueryStrategyComparison({
|
|
42
|
+
cases,
|
|
43
|
+
kitCommand: context.config.kitCommand,
|
|
44
|
+
outputRoot: outDir,
|
|
45
|
+
dependencies: readDependenciesInput(context.inputs)
|
|
46
|
+
});
|
|
47
|
+
// Scientific analysis is calculated in memory before persistence; reports later consume it, never recalculate it.
|
|
48
|
+
const analysis = analyzeRetrievalQueryStrategyComparison(cases, caseEvidence);
|
|
49
|
+
const completedAt = new Date().toISOString();
|
|
50
|
+
const artifact = buildRetrievalQueryStrategyComparisonExecutionArtifact({
|
|
51
|
+
runId: context.runId,
|
|
52
|
+
pluginId: RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID,
|
|
53
|
+
pluginSchemaVersion: retrievalQueryStrategyComparisonMetadata.schemaVersion,
|
|
54
|
+
startedAt,
|
|
55
|
+
completedAt,
|
|
56
|
+
cases: caseEvidence
|
|
57
|
+
});
|
|
58
|
+
const analysisArtifact = buildRetrievalQueryStrategyComparisonAnalysisArtifact({
|
|
59
|
+
runId: context.runId,
|
|
60
|
+
pluginId: RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID,
|
|
61
|
+
pluginSchemaVersion: retrievalQueryStrategyComparisonMetadata.schemaVersion,
|
|
62
|
+
startedAt,
|
|
63
|
+
completedAt,
|
|
64
|
+
analysis
|
|
65
|
+
});
|
|
66
|
+
const executionArtifactPath = resolveWithinRoot(outDir, RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXECUTION_ARTIFACT_FILE);
|
|
67
|
+
const analysisArtifactPath = resolveWithinRoot(outDir, RETRIEVAL_QUERY_STRATEGY_COMPARISON_ANALYSIS_ARTIFACT_FILE);
|
|
68
|
+
await writeFile(executionArtifactPath, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
69
|
+
await writeFile(analysisArtifactPath, `${JSON.stringify(analysisArtifact, null, 2)}\n`, "utf8");
|
|
70
|
+
return mapRetrievalQueryStrategyComparisonToRun({
|
|
71
|
+
runId: context.runId,
|
|
72
|
+
startedAt,
|
|
73
|
+
completedAt,
|
|
74
|
+
target: context.target,
|
|
75
|
+
caseEvidence,
|
|
76
|
+
analysis,
|
|
77
|
+
executionArtifactPath,
|
|
78
|
+
analysisArtifactPath
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
summarize(result) {
|
|
82
|
+
return result.summary ?? summarizeExperimentRun(result);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function readLocalSubjectInput(inputs) {
|
|
86
|
+
const value = inputs?.localSubject;
|
|
87
|
+
return value && typeof value === "object" ? value : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Test seams for the external path derive from the same optional dependency input the bundled path reads. */
|
|
90
|
+
function readLocalSubjectDependencies(inputs) {
|
|
91
|
+
const injected = readDependenciesInput(inputs);
|
|
92
|
+
if (!injected)
|
|
93
|
+
return undefined;
|
|
94
|
+
const { buildIndex, runCoreStrategy, runSemanticStrategy } = injected;
|
|
95
|
+
return {
|
|
96
|
+
...(buildIndex ? { buildIndex } : {}),
|
|
97
|
+
...(runCoreStrategy || runSemanticStrategy
|
|
98
|
+
? {
|
|
99
|
+
executeCase: (options) => executeRetrievalQueryStrategyCaseFromPreparedIndex({
|
|
100
|
+
...options,
|
|
101
|
+
dependencies: { ...(runCoreStrategy ? { runCoreStrategy } : {}), ...(runSemanticStrategy ? { runSemanticStrategy } : {}) }
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
: {})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* External-local path. The safe seam owns execution, immutability and scratch cleanup and either returns evidence or
|
|
109
|
+
* throws. Scientific analysis is calculated from the real, unprojected identities; only then is the evidence
|
|
110
|
+
* privacy-projected and asserted private. Nothing durable is written before every gate has passed, and then in this
|
|
111
|
+
* order: execution artifact, analysis artifact, manifest.
|
|
112
|
+
*/
|
|
113
|
+
async function runLocalSubject(context, subject, startedAt) {
|
|
114
|
+
if (context.target.isSelf || context.target.kind !== "external-local") {
|
|
115
|
+
throw new Error("Local-repository subject mode requires an external-local target.");
|
|
116
|
+
}
|
|
117
|
+
if (context.config.caseIds !== undefined || context.config.benchmarkProjects !== undefined) {
|
|
118
|
+
throw new Error("Case and benchmark-project filters are not supported for an external local repository subject; the subject config owns the case set.");
|
|
119
|
+
}
|
|
120
|
+
if (!(await isSamePhysicalDirectory(context.target.targetRoot, subject.repositoryRoot))) {
|
|
121
|
+
throw new Error("The selected --target is not the repository the local subject was loaded from.");
|
|
122
|
+
}
|
|
123
|
+
const outDir = context.outputRoot;
|
|
124
|
+
if (!outDir)
|
|
125
|
+
throw new Error("Local-repository subject mode requires an experiment output directory.");
|
|
126
|
+
let localResult;
|
|
127
|
+
try {
|
|
128
|
+
localResult = await executeLocalRepositorySubjectRetrievalQueryStrategyComparison({
|
|
129
|
+
subject,
|
|
130
|
+
kitCommand: context.config.kitCommand,
|
|
131
|
+
workRoot: outDir,
|
|
132
|
+
dependencies: readLocalSubjectDependencies(context.inputs)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof LocalSubjectExecutionError)
|
|
137
|
+
throw new Error(describeRetrievalQueryStrategyLocalSubjectFailureForPersistence(error));
|
|
138
|
+
throw new Error(RETRIEVAL_QUERY_STRATEGY_UNEXPECTED_EXTERNAL_FAILURE_MESSAGE);
|
|
139
|
+
}
|
|
140
|
+
// Science first, on real identities; placeholders are never scientific identities.
|
|
141
|
+
const analysis = analyzeRetrievalQueryStrategyComparison(subject.evaluationCases, localResult.caseEvidence);
|
|
142
|
+
const projectedExecution = projectRetrievalQueryStrategyExecutionForExternalLocalPersistence(localResult.caseEvidence);
|
|
143
|
+
const projectedAnalysis = projectRetrievalQueryStrategyAnalysisForExternalLocalPersistence(analysis);
|
|
144
|
+
const privateValues = {
|
|
145
|
+
filePaths: [...subject.eligibleFiles, ...subject.runtimeSafetyExclusions.gitIgnoredFiles, ...subject.runtimeSafetyExclusions.oversizedFiles],
|
|
146
|
+
caseTitles: subject.evaluationCases.map((evaluationCase) => evaluationCase.title),
|
|
147
|
+
warnings: localResult.caseEvidence.flatMap((entry) => entry.treatments.flatMap((treatment) => treatment.retrieval?.warnings ?? [])),
|
|
148
|
+
privateRoots: [subject.repositoryRoot, outDir]
|
|
149
|
+
};
|
|
150
|
+
assertRetrievalQueryStrategyExternalProjectionIsPrivate(projectedExecution, privateValues);
|
|
151
|
+
assertRetrievalQueryStrategyExternalProjectionIsPrivate(projectedAnalysis, privateValues);
|
|
152
|
+
await mkdir(outDir, { recursive: true });
|
|
153
|
+
const completedAt = new Date().toISOString();
|
|
154
|
+
const executionArtifact = buildRetrievalQueryStrategyComparisonExecutionArtifact({
|
|
155
|
+
runId: context.runId,
|
|
156
|
+
pluginId: RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID,
|
|
157
|
+
pluginSchemaVersion: retrievalQueryStrategyComparisonMetadata.schemaVersion,
|
|
158
|
+
startedAt,
|
|
159
|
+
completedAt,
|
|
160
|
+
cases: projectedExecution
|
|
161
|
+
});
|
|
162
|
+
const analysisArtifact = buildRetrievalQueryStrategyComparisonAnalysisArtifact({
|
|
163
|
+
runId: context.runId,
|
|
164
|
+
pluginId: RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID,
|
|
165
|
+
pluginSchemaVersion: retrievalQueryStrategyComparisonMetadata.schemaVersion,
|
|
166
|
+
startedAt,
|
|
167
|
+
completedAt,
|
|
168
|
+
analysis: projectedAnalysis
|
|
169
|
+
});
|
|
170
|
+
await writeFile(resolveWithinRoot(outDir, RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXECUTION_ARTIFACT_FILE), `${JSON.stringify(executionArtifact, null, 2)}\n`, "utf8");
|
|
171
|
+
await writeFile(resolveWithinRoot(outDir, RETRIEVAL_QUERY_STRATEGY_COMPARISON_ANALYSIS_ARTIFACT_FILE), `${JSON.stringify(analysisArtifact, null, 2)}\n`, "utf8");
|
|
172
|
+
await writeFile(resolveWithinRoot(outDir, RETRIEVAL_QUERY_STRATEGY_COMPARISON_LOCAL_SUBJECT_MANIFEST_FILE), serializeLocalRepositorySubjectManifest(subject.manifest), "utf8");
|
|
173
|
+
// Durable external run: logical target and basenames only; metrics come from the unchanged numeric analysis.
|
|
174
|
+
const run = mapRetrievalQueryStrategyComparisonToRun({
|
|
175
|
+
runId: context.runId,
|
|
176
|
+
startedAt,
|
|
177
|
+
completedAt,
|
|
178
|
+
target: projectExternalLocalTarget(subject.manifest),
|
|
179
|
+
caseEvidence: projectedExecution,
|
|
180
|
+
analysis: projectedAnalysis,
|
|
181
|
+
executionArtifactPath: RETRIEVAL_QUERY_STRATEGY_COMPARISON_EXECUTION_ARTIFACT_FILE,
|
|
182
|
+
analysisArtifactPath: RETRIEVAL_QUERY_STRATEGY_COMPARISON_ANALYSIS_ARTIFACT_FILE
|
|
183
|
+
});
|
|
184
|
+
run.artifacts.push({
|
|
185
|
+
id: "local-repository-subject-manifest",
|
|
186
|
+
label: "Privacy-safe local repository subject manifest",
|
|
187
|
+
path: RETRIEVAL_QUERY_STRATEGY_COMPARISON_LOCAL_SUBJECT_MANIFEST_FILE,
|
|
188
|
+
kind: "artifact",
|
|
189
|
+
mimeType: "application/json",
|
|
190
|
+
description: "Privacy-safe logical subject, Git, safety and aggregate inventory metadata without source or private identity lists."
|
|
191
|
+
});
|
|
192
|
+
return run;
|
|
193
|
+
}
|
|
194
|
+
export function mapRetrievalQueryStrategyComparisonToRun(args) {
|
|
195
|
+
const cases = args.caseEvidence.map((entry, caseIndex) => ({
|
|
196
|
+
id: entry.caseId,
|
|
197
|
+
name: entry.caseName,
|
|
198
|
+
outcomes: entry.treatments.map((treatment, treatmentIndex) => buildOutcome(entry, treatment, args.analysis.cases[caseIndex].treatments[treatmentIndex])),
|
|
199
|
+
metadata: { benchmarkProject: entry.benchmarkProject, taskLocality: entry.taskLocality }
|
|
200
|
+
}));
|
|
201
|
+
const run = {
|
|
202
|
+
runId: args.runId,
|
|
203
|
+
pluginId: RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID,
|
|
204
|
+
startedAt: args.startedAt,
|
|
205
|
+
completedAt: args.completedAt,
|
|
206
|
+
status: aggregateTreatmentStatus(args.caseEvidence.flatMap((entry) => entry.treatments.map((treatment) => treatment.status))),
|
|
207
|
+
target: args.target,
|
|
208
|
+
variants: RETRIEVAL_QUERY_STRATEGY_COMPARISON_VARIANTS.map((variant) => ({ ...variant })),
|
|
209
|
+
cases,
|
|
210
|
+
metrics: toRetrievalQueryStrategyRunMetrics(args.analysis),
|
|
211
|
+
artifacts: [
|
|
212
|
+
{
|
|
213
|
+
id: "retrieval-query-strategy-comparison-execution",
|
|
214
|
+
label: "Retrieval query strategy comparison execution evidence",
|
|
215
|
+
path: args.executionArtifactPath,
|
|
216
|
+
kind: "artifact",
|
|
217
|
+
mimeType: "application/json",
|
|
218
|
+
description: "Matched per-case execution evidence for all seven retrieval query strategies without context text or raw command output."
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "retrieval-query-strategy-comparison-analysis",
|
|
222
|
+
label: "Retrieval query strategy comparison scientific analysis",
|
|
223
|
+
path: args.analysisArtifactPath,
|
|
224
|
+
kind: "artifact",
|
|
225
|
+
mimeType: "application/json",
|
|
226
|
+
description: "Planner-defined retrieval-quality, F1, matched task-type aggregates, and Pareto comparison for the seven retrieval query strategies."
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
warnings: [],
|
|
230
|
+
// Outcome failures are the authoritative per-treatment failure location.
|
|
231
|
+
failures: [],
|
|
232
|
+
metadata: { executionArtifactPath: args.executionArtifactPath, analysisArtifactPath: args.analysisArtifactPath },
|
|
233
|
+
caseExecutionEvidence: structuredClone(args.caseEvidence),
|
|
234
|
+
analysis: structuredClone(args.analysis)
|
|
235
|
+
};
|
|
236
|
+
run.summary = summarizeExperimentRun(run);
|
|
237
|
+
return run;
|
|
238
|
+
}
|
|
239
|
+
function buildOutcome(caseEvidence, treatment, treatmentAnalysis) {
|
|
240
|
+
const { caseId, benchmarkProject, taskLocality } = caseEvidence;
|
|
241
|
+
const { strategyId } = treatment;
|
|
242
|
+
return {
|
|
243
|
+
id: `${caseId}:${strategyId}`,
|
|
244
|
+
caseId,
|
|
245
|
+
variantId: strategyId,
|
|
246
|
+
status: treatment.status,
|
|
247
|
+
metrics: toRetrievalQueryStrategyOutcomeMetrics(treatmentAnalysis, caseId),
|
|
248
|
+
artifacts: [],
|
|
249
|
+
warnings: (treatment.retrieval?.warnings ?? []).map((message) => ({
|
|
250
|
+
code: "retrieval-warning",
|
|
251
|
+
message,
|
|
252
|
+
variantId: strategyId,
|
|
253
|
+
caseId
|
|
254
|
+
})),
|
|
255
|
+
failures: treatment.errors.map((error) => ({
|
|
256
|
+
code: error.code,
|
|
257
|
+
message: error.message,
|
|
258
|
+
variantId: strategyId,
|
|
259
|
+
caseId,
|
|
260
|
+
recoverable: true
|
|
261
|
+
})),
|
|
262
|
+
metadata: {
|
|
263
|
+
benchmarkProject,
|
|
264
|
+
taskLocality,
|
|
265
|
+
evidenceAvailability: treatment.retrieval?.evidenceAvailability ?? null
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function aggregateTreatmentStatus(statuses) {
|
|
270
|
+
if (statuses.length === 0)
|
|
271
|
+
return "skipped";
|
|
272
|
+
if (statuses.every((status) => status === "completed"))
|
|
273
|
+
return "completed";
|
|
274
|
+
if (statuses.every((status) => status === "skipped"))
|
|
275
|
+
return "skipped";
|
|
276
|
+
if (statuses.every((status) => status === "failed"))
|
|
277
|
+
return "failed";
|
|
278
|
+
return "partial";
|
|
279
|
+
}
|
|
280
|
+
function readCasesInput(inputs) {
|
|
281
|
+
const value = inputs?.cases;
|
|
282
|
+
if (!Array.isArray(value)) {
|
|
283
|
+
throw new Error("Retrieval query strategy comparison requires cases input.");
|
|
284
|
+
}
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
function readDependenciesInput(inputs) {
|
|
288
|
+
const value = inputs?.retrievalDependencies;
|
|
289
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
290
|
+
? value
|
|
291
|
+
: undefined;
|
|
292
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,9 +3,15 @@ import { buildContextStrategyComparisonV043Report } from "./buildContextStrategy
|
|
|
3
3
|
import { buildWarmIndexReuseReport } from "./buildWarmIndexReuseReport.js";
|
|
4
4
|
import { buildIncrementalChangeStalenessPluginReport } from "./buildIncrementalChangeStalenessPluginReport.js";
|
|
5
5
|
import { buildContextWindowScalingReport } from "./buildContextWindowScalingReport.js";
|
|
6
|
+
import { buildRetrievalPrecisionRecallReport } from "./buildRetrievalPrecisionRecallReport.js";
|
|
7
|
+
import { buildRetrievalQueryStrategyComparisonReport } from "./buildRetrievalQueryStrategyComparisonReport.js";
|
|
6
8
|
// Bulk context-window-scaling evidence is presented by the typed report section; the complete
|
|
7
9
|
// evidence stays in context-window-scaling-execution.json.
|
|
8
10
|
const CONTEXT_WINDOW_SCALING_BULK_KEYS = ["executionEvidence", "aggregate"];
|
|
11
|
+
// Same for retrieval-precision-recall: the typed section presents the evidence; the execution artifact keeps all of it.
|
|
12
|
+
const RETRIEVAL_PRECISION_RECALL_BULK_KEYS = ["caseExecutionEvidence", "aggregate"];
|
|
13
|
+
// Same for retrieval-query-strategy-comparison: the typed section presents execution evidence and analysis.
|
|
14
|
+
const RETRIEVAL_QUERY_STRATEGY_COMPARISON_BULK_KEYS = ["caseExecutionEvidence", "analysis"];
|
|
9
15
|
const V043_BULK_ARRAY_KEYS = [
|
|
10
16
|
"v043StageContextExecutions",
|
|
11
17
|
"v043StageContextEvaluations",
|
|
@@ -18,6 +24,8 @@ export function buildPluginExperimentReport(args) {
|
|
|
18
24
|
const warmIndexReuse = buildWarmIndexReuseReport(args.run);
|
|
19
25
|
const incrementalChangeStaleness = buildIncrementalChangeStalenessPluginReport(args.run);
|
|
20
26
|
const contextWindowScaling = buildContextWindowScalingReport(args.run);
|
|
27
|
+
const retrievalPrecisionRecall = buildRetrievalPrecisionRecallReport(args.run);
|
|
28
|
+
const retrievalQueryStrategyComparison = buildRetrievalQueryStrategyComparisonReport(args.run);
|
|
21
29
|
const rawRun = { ...args.run, artifacts: relativizeArtifacts(args.run.artifacts, outputRoot) };
|
|
22
30
|
for (const key of V043_BULK_ARRAY_KEYS) {
|
|
23
31
|
delete rawRun[key];
|
|
@@ -27,6 +35,16 @@ export function buildPluginExperimentReport(args) {
|
|
|
27
35
|
delete rawRun[key];
|
|
28
36
|
}
|
|
29
37
|
}
|
|
38
|
+
if (retrievalPrecisionRecall) {
|
|
39
|
+
for (const key of RETRIEVAL_PRECISION_RECALL_BULK_KEYS) {
|
|
40
|
+
delete rawRun[key];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (retrievalQueryStrategyComparison) {
|
|
44
|
+
for (const key of RETRIEVAL_QUERY_STRATEGY_COMPARISON_BULK_KEYS) {
|
|
45
|
+
delete rawRun[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
30
48
|
return {
|
|
31
49
|
metadata: {
|
|
32
50
|
generatedAt: args.generatedAt ?? new Date().toISOString(),
|
|
@@ -53,8 +71,10 @@ export function buildPluginExperimentReport(args) {
|
|
|
53
71
|
warmIndexReuse,
|
|
54
72
|
incrementalChangeStaleness,
|
|
55
73
|
contextWindowScaling,
|
|
74
|
+
retrievalPrecisionRecall,
|
|
75
|
+
retrievalQueryStrategyComparison,
|
|
56
76
|
contextStrategyComparisonV043,
|
|
57
|
-
interpretation: buildInterpretation(args.run, contextStrategyComparisonV043, warmIndexReuse, contextWindowScaling),
|
|
77
|
+
interpretation: buildInterpretation(args.run, contextStrategyComparisonV043, warmIndexReuse, contextWindowScaling, retrievalPrecisionRecall, retrievalQueryStrategyComparison),
|
|
58
78
|
rawRun,
|
|
59
79
|
};
|
|
60
80
|
}
|
|
@@ -133,7 +153,29 @@ function affectedNeighborhoodSentence(report) {
|
|
|
133
153
|
`reindex recommendations were recommended=${summary.recommendedReindexCount}, not-indicated=${summary.notIndicatedReindexCount}, unknown=${summary.unknownReindexRecommendationCount}. ` +
|
|
134
154
|
"Affected-neighborhood evidence uses a one-hop baseline graph around confirmed changed indexed files/symbols. It is observational and does not alter retrieval or execution status.");
|
|
135
155
|
}
|
|
136
|
-
function buildInterpretation(run, contextStrategyComparisonV043, warmIndexReuse, contextWindowScaling) {
|
|
156
|
+
function buildInterpretation(run, contextStrategyComparisonV043, warmIndexReuse, contextWindowScaling, retrievalPrecisionRecall, retrievalQueryStrategyComparison) {
|
|
157
|
+
if (retrievalQueryStrategyComparison) {
|
|
158
|
+
const overall = retrievalQueryStrategyComparison.scopes.find((scope) => scope.scopeId === "overall");
|
|
159
|
+
const taskTypes = "Task-type results are reported separately for localized, cross-module, and broad-change cases.";
|
|
160
|
+
const summary = overall?.interpretation === "unique-best"
|
|
161
|
+
? `Overall matched comparison: ${overall.bestStrategyId} is the unique nondominated strategy across ${overall.comparisonCaseCount} matched case(s). ${taskTypes}`
|
|
162
|
+
: overall?.interpretation === "tradeoff"
|
|
163
|
+
? `Overall matched comparison has no single best strategy across ${overall.comparisonCaseCount} matched case(s); the Pareto front is ${overall.paretoFrontStrategyIds.join(", ")}. ${taskTypes}`
|
|
164
|
+
: "No matched complete-case retrieval-strategy comparison was available for the overall scope. Review treatment availability and excluded cases before drawing conclusions.";
|
|
165
|
+
return {
|
|
166
|
+
summary,
|
|
167
|
+
recommendedNextStep: "Review the task-type scope table, Pareto fronts, and per-case treatment metrics; do not treat Pareto-front order as a ranking."
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (retrievalPrecisionRecall) {
|
|
171
|
+
const summary = retrievalPrecisionRecall.runSummary;
|
|
172
|
+
return {
|
|
173
|
+
summary: `Retrieval quality was measured over ${summary.caseCount} case${summary.caseCount === 1 ? "" : "s"} (${summary.completedCaseCount} completed, ${summary.partialCaseCount} partial, ${summary.failedCaseCount} failed). ` +
|
|
174
|
+
"Available case-level file, symbol and fact metrics are summarized without ranking strategies. " +
|
|
175
|
+
"Unavailable and not-applicable evidence is excluded from macro means rather than treated as zero.",
|
|
176
|
+
recommendedNextStep: "Review per-case missed and irrelevant context together with availability before drawing conclusions."
|
|
177
|
+
};
|
|
178
|
+
}
|
|
137
179
|
if (contextWindowScaling) {
|
|
138
180
|
const summary = contextWindowScaling.runSummary;
|
|
139
181
|
return {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { RETRIEVAL_PRECISION_RECALL_PLUGIN_ID } from "../../experiments/plugins/retrievalPrecisionRecall/metadata.js";
|
|
2
|
+
import { retrievalRatioOf } from "../../experiments/plugins/retrievalPrecisionRecall/metrics.js";
|
|
3
|
+
import { RETRIEVAL_PRECISION_RECALL_RATIO_KEYS } from "../../experiments/plugins/retrievalPrecisionRecall/types.js";
|
|
4
|
+
import { MAX_RETRIEVAL_REPORT_IDENTITIES, RETRIEVAL_PRECISION_RECALL_REPORT_SCHEMA_VERSION } from "./retrievalPrecisionRecallReportModel.js";
|
|
5
|
+
/** Fixed wording; the report states observed evidence only and never ranks or scores anything. */
|
|
6
|
+
export const RETRIEVAL_PRECISION_RECALL_LIMITATIONS = [
|
|
7
|
+
"File and symbol precision and recall are set-based measures, not ranked metrics.",
|
|
8
|
+
"Symbol scoring uses exact unique symbol names, because the answer key lists names rather than node identities.",
|
|
9
|
+
"Fact coverage uses explicit fact-to-context mappings in the answer key; it does not judge generated answers or interpret fact text.",
|
|
10
|
+
"Irrelevant context ratio is file-identity based and is the complement of file precision when both are available.",
|
|
11
|
+
"Retrieved token count measures the selected retrieval context payload; it is not a relevant versus irrelevant token attribution.",
|
|
12
|
+
"The current lifecycle expands only the top search candidate (lookup, slice and source).",
|
|
13
|
+
"Unavailable and not-applicable values are not zeros and are excluded from macro means.",
|
|
14
|
+
"The experiment uses no real agents.",
|
|
15
|
+
"v0.8.0 does not compare retrieval strategies."
|
|
16
|
+
];
|
|
17
|
+
function boundedList(items) {
|
|
18
|
+
if (items === null)
|
|
19
|
+
return null;
|
|
20
|
+
const displayed = items.slice(0, MAX_RETRIEVAL_REPORT_IDENTITIES);
|
|
21
|
+
return {
|
|
22
|
+
totalCount: items.length,
|
|
23
|
+
displayed: [...displayed],
|
|
24
|
+
displayedCount: displayed.length,
|
|
25
|
+
omittedCount: items.length - displayed.length
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Presents the per-case metric results and aggregate already calculated on the run. It performs no precision, recall,
|
|
30
|
+
* coverage, ratio or mean calculation of its own; identity lists are only bounded for display.
|
|
31
|
+
*/
|
|
32
|
+
export function buildRetrievalPrecisionRecallReport(run) {
|
|
33
|
+
if (run.pluginId !== RETRIEVAL_PRECISION_RECALL_PLUGIN_ID)
|
|
34
|
+
return null;
|
|
35
|
+
const candidate = run;
|
|
36
|
+
const evidence = candidate.caseExecutionEvidence;
|
|
37
|
+
const aggregate = candidate.aggregate;
|
|
38
|
+
if (!evidence || !aggregate) {
|
|
39
|
+
// A run that failed before producing evidence (for example an unknown case id or a non-self target) still gets the
|
|
40
|
+
// generic report with its failure; only a non-failed run with missing evidence is a broken report source.
|
|
41
|
+
if (run.status === "failed")
|
|
42
|
+
return null;
|
|
43
|
+
throw new Error("Invalid retrieval-precision-recall report source: case execution evidence and aggregate are required.");
|
|
44
|
+
}
|
|
45
|
+
if (aggregate.runSummary.caseCount !== evidence.length) {
|
|
46
|
+
throw new Error("Invalid retrieval-precision-recall report source: aggregate and case execution evidence are inconsistent.");
|
|
47
|
+
}
|
|
48
|
+
const cases = evidence.map((entry) => {
|
|
49
|
+
const quality = entry.quality;
|
|
50
|
+
const metrics = Object.fromEntries(RETRIEVAL_PRECISION_RECALL_RATIO_KEYS.map((key) => {
|
|
51
|
+
const ratio = retrievalRatioOf(quality, key);
|
|
52
|
+
return [key, { availability: ratio.availability, numerator: ratio.numerator, denominator: ratio.denominator, value: ratio.value, reason: ratio.reason }];
|
|
53
|
+
}));
|
|
54
|
+
return {
|
|
55
|
+
caseId: entry.caseId,
|
|
56
|
+
caseName: entry.caseName,
|
|
57
|
+
benchmarkProject: entry.benchmarkProject,
|
|
58
|
+
taskLocality: entry.taskLocality,
|
|
59
|
+
status: entry.status,
|
|
60
|
+
evidenceAvailability: entry.retrieval?.evidenceAvailability ?? null,
|
|
61
|
+
metrics,
|
|
62
|
+
retrievedTokenCount: quality?.retrievedTokenCount ?? null,
|
|
63
|
+
tokenCountMethod: quality?.tokenCountMethod ?? null,
|
|
64
|
+
missedFiles: boundedList(quality?.file.missedFiles ?? null),
|
|
65
|
+
irrelevantRetrievedFiles: boundedList(quality?.file.irrelevantRetrievedFiles ?? null),
|
|
66
|
+
missedSymbols: boundedList(quality?.symbol.missedSymbols ?? null),
|
|
67
|
+
irrelevantRetrievedSymbols: boundedList(quality?.symbol.irrelevantRetrievedSymbols ?? null),
|
|
68
|
+
uncoveredFactIds: boundedList(quality?.fact.uncoveredFactIds ?? null),
|
|
69
|
+
errors: entry.errors.map((error) => `${error.code}: ${error.message}`),
|
|
70
|
+
identityRedaction: entry.identityRedaction ? { ...entry.identityRedaction } : null
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
schemaVersion: RETRIEVAL_PRECISION_RECALL_REPORT_SCHEMA_VERSION,
|
|
75
|
+
tokenCountMethod: evidence.find((entry) => entry.quality?.tokenCountMethod != null)?.quality?.tokenCountMethod ?? null,
|
|
76
|
+
runSummary: { ...aggregate.runSummary },
|
|
77
|
+
ratioSummaries: structuredClone(aggregate.ratios),
|
|
78
|
+
tokenSummary: { ...aggregate.tokens },
|
|
79
|
+
occurrenceSummary: { ...aggregate.occurrences },
|
|
80
|
+
cases,
|
|
81
|
+
limitations: [...RETRIEVAL_PRECISION_RECALL_LIMITATIONS]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { RETRIEVAL_QUERY_STRATEGY_IDS } from "../../evaluation/retrievalQueryStrategies.js";
|
|
2
|
+
import { RETRIEVAL_QUERY_STRATEGY_COMPARISON_METHODOLOGY } from "../../experiments/plugins/retrievalQueryStrategyComparison/analysisArtifact.js";
|
|
3
|
+
import { RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID } from "../../experiments/plugins/retrievalQueryStrategyComparison/metadata.js";
|
|
4
|
+
import { RETRIEVAL_QUERY_STRATEGY_COMPARISON_REPORT_SCHEMA_VERSION } from "./retrievalQueryStrategyComparisonReportModel.js";
|
|
5
|
+
/** Fixed wording: observed evidence only. It never claims a strategy is generally superior. */
|
|
6
|
+
export const RETRIEVAL_QUERY_STRATEGY_COMPARISON_LIMITATIONS = [
|
|
7
|
+
"Retrieval treatments are deterministic my-dev-kit workflows; no coding agent or learned judge participates.",
|
|
8
|
+
"File and symbol precision and recall retain the v0.8.0 set-based identity semantics.",
|
|
9
|
+
"Balanced file and symbol F1 use 2TP / (2TP + FP + FN).",
|
|
10
|
+
"Cross-strategy scope means use matched complete cases only: a task is excluded from every strategy aggregate when any strategy lacks a primary objective.",
|
|
11
|
+
"Scope means are unweighted macro means over task instances.",
|
|
12
|
+
"Pareto dominance compares mean file F1, mean symbol F1, mean fact coverage, and mean retrieved token count without scalar weights.",
|
|
13
|
+
"A unique best strategy is reported only when the Pareto front has exactly one member; multiple nondominated strategies are reported as a tradeoff, not forcibly ranked.",
|
|
14
|
+
"Task-type scopes reuse the existing taskLocality labels: localized, cross-module, and broad-change.",
|
|
15
|
+
"Retrieved token count is a context-size estimate from the selected retrieval payload, not provider billing telemetry.",
|
|
16
|
+
"Unavailable and not-applicable metric values are not converted to zero.",
|
|
17
|
+
"External-local durable reports withhold private file, symbol, fact, semantic-node, warning, and case-title identities."
|
|
18
|
+
];
|
|
19
|
+
const NO_RETRIEVAL_MEASUREMENT = {
|
|
20
|
+
availability: "unavailable",
|
|
21
|
+
numerator: null,
|
|
22
|
+
denominator: null,
|
|
23
|
+
value: null,
|
|
24
|
+
reason: "no-retrieval-measurement"
|
|
25
|
+
};
|
|
26
|
+
const noMeasurement = () => ({ ...NO_RETRIEVAL_MEASUREMENT });
|
|
27
|
+
const copyMetric = (metric) => ({
|
|
28
|
+
availability: metric.availability,
|
|
29
|
+
numerator: metric.numerator,
|
|
30
|
+
denominator: metric.denominator,
|
|
31
|
+
value: metric.value,
|
|
32
|
+
reason: metric.reason
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Presentation-only adapter over the already-calculated `run.analysis`. It recalculates no precision, recall, F1, fact
|
|
36
|
+
* coverage, mean, Pareto front or best strategy; it only copies values and reads persisted execution status.
|
|
37
|
+
*/
|
|
38
|
+
export function buildRetrievalQueryStrategyComparisonReport(run) {
|
|
39
|
+
if (run.pluginId !== RETRIEVAL_QUERY_STRATEGY_COMPARISON_PLUGIN_ID)
|
|
40
|
+
return null;
|
|
41
|
+
const candidate = run;
|
|
42
|
+
const analysis = candidate.analysis;
|
|
43
|
+
const execution = candidate.caseExecutionEvidence;
|
|
44
|
+
if (!analysis || !execution) {
|
|
45
|
+
// A run that failed before analysis exists still gets the generic failure report.
|
|
46
|
+
if (run.status === "failed")
|
|
47
|
+
return null;
|
|
48
|
+
throw new Error("Invalid retrieval-query-strategy-comparison report source: execution evidence and analysis are required.");
|
|
49
|
+
}
|
|
50
|
+
const inconsistent = analysis.cases.length !== execution.length ||
|
|
51
|
+
analysis.cases.some((entry, index) => entry.caseId !== execution[index].caseId ||
|
|
52
|
+
entry.treatments.length !== RETRIEVAL_QUERY_STRATEGY_IDS.length ||
|
|
53
|
+
entry.treatments.some((treatment, position) => treatment.strategyId !== RETRIEVAL_QUERY_STRATEGY_IDS[position]) ||
|
|
54
|
+
execution[index].treatments.length !== RETRIEVAL_QUERY_STRATEGY_IDS.length);
|
|
55
|
+
if (inconsistent) {
|
|
56
|
+
throw new Error("Invalid retrieval-query-strategy-comparison report source: execution evidence and analysis are inconsistent.");
|
|
57
|
+
}
|
|
58
|
+
const cases = analysis.cases.map((entry, caseIndex) => ({
|
|
59
|
+
caseId: entry.caseId,
|
|
60
|
+
caseName: execution[caseIndex].caseName,
|
|
61
|
+
benchmarkProject: entry.benchmarkProject,
|
|
62
|
+
taskLocality: entry.taskLocality,
|
|
63
|
+
treatments: entry.treatments.map((treatment, position) => {
|
|
64
|
+
const quality = treatment.quality;
|
|
65
|
+
return {
|
|
66
|
+
strategyId: treatment.strategyId,
|
|
67
|
+
executionStatus: treatment.executionStatus,
|
|
68
|
+
evidenceAvailability: execution[caseIndex].treatments[position].retrieval?.evidenceAvailability ?? null,
|
|
69
|
+
filePrecision: quality ? copyMetric(quality.file.precision) : noMeasurement(),
|
|
70
|
+
fileRecall: quality ? copyMetric(quality.file.recall) : noMeasurement(),
|
|
71
|
+
fileF1: copyMetric(treatment.fileF1),
|
|
72
|
+
symbolPrecision: quality ? copyMetric(quality.symbol.precision) : noMeasurement(),
|
|
73
|
+
symbolRecall: quality ? copyMetric(quality.symbol.recall) : noMeasurement(),
|
|
74
|
+
symbolF1: copyMetric(treatment.symbolF1),
|
|
75
|
+
factCoverage: quality ? copyMetric(quality.fact.coverage) : noMeasurement(),
|
|
76
|
+
irrelevantContextRatio: quality ? copyMetric(quality.irrelevantContextRatio) : noMeasurement(),
|
|
77
|
+
retrievedTokenCount: quality?.retrievedTokenCount ?? null,
|
|
78
|
+
tokenCountMethod: quality?.tokenCountMethod ?? null
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
}));
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: RETRIEVAL_QUERY_STRATEGY_COMPARISON_REPORT_SCHEMA_VERSION,
|
|
84
|
+
methodology: structuredClone(RETRIEVAL_QUERY_STRATEGY_COMPARISON_METHODOLOGY),
|
|
85
|
+
strategyOrder: [...RETRIEVAL_QUERY_STRATEGY_IDS],
|
|
86
|
+
identityRedaction: run.target.privacyProjection === "external-local-redacted" ? "external-local-redacted" : null,
|
|
87
|
+
scopes: structuredClone(analysis.scopes),
|
|
88
|
+
cases,
|
|
89
|
+
limitations: [...RETRIEVAL_QUERY_STRATEGY_COMPARISON_LIMITATIONS]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -24,3 +24,11 @@ export * from "./contextWindowScalingReportModel.js";
|
|
|
24
24
|
export { buildContextWindowScalingReport, CONTEXT_WINDOW_SCALING_INTERPRETATION } from "./buildContextWindowScalingReport.js";
|
|
25
25
|
export { renderContextWindowScalingHtml } from "./renderContextWindowScalingHtml.js";
|
|
26
26
|
export { renderContextWindowScalingTextLines } from "./renderContextWindowScalingText.js";
|
|
27
|
+
export * from "./retrievalPrecisionRecallReportModel.js";
|
|
28
|
+
export { buildRetrievalPrecisionRecallReport, RETRIEVAL_PRECISION_RECALL_LIMITATIONS } from "./buildRetrievalPrecisionRecallReport.js";
|
|
29
|
+
export { renderRetrievalPrecisionRecallHtml } from "./renderRetrievalPrecisionRecallHtml.js";
|
|
30
|
+
export { renderRetrievalPrecisionRecallTextLines } from "./renderRetrievalPrecisionRecallText.js";
|
|
31
|
+
export * from "./retrievalQueryStrategyComparisonReportModel.js";
|
|
32
|
+
export { buildRetrievalQueryStrategyComparisonReport, RETRIEVAL_QUERY_STRATEGY_COMPARISON_LIMITATIONS } from "./buildRetrievalQueryStrategyComparisonReport.js";
|
|
33
|
+
export { renderRetrievalQueryStrategyComparisonHtml } from "./renderRetrievalQueryStrategyComparisonHtml.js";
|
|
34
|
+
export { renderRetrievalQueryStrategyComparisonTextLines } from "./renderRetrievalQueryStrategyComparisonText.js";
|
|
@@ -2,6 +2,8 @@ import { renderContextStrategyComparisonV043Html } from "./renderContextStrategy
|
|
|
2
2
|
import { renderWarmIndexReuseHtml } from "./renderWarmIndexReuseHtml.js";
|
|
3
3
|
import { renderIncrementalChangeStalenessHtml } from "./renderIncrementalChangeStalenessHtml.js";
|
|
4
4
|
import { renderContextWindowScalingHtml } from "./renderContextWindowScalingHtml.js";
|
|
5
|
+
import { renderRetrievalPrecisionRecallHtml } from "./renderRetrievalPrecisionRecallHtml.js";
|
|
6
|
+
import { renderRetrievalQueryStrategyComparisonHtml } from "./renderRetrievalQueryStrategyComparisonHtml.js";
|
|
5
7
|
export function renderPluginExperimentReportHtml(report) {
|
|
6
8
|
const redactedTarget = report.target.privacyProjection === "external-local-redacted";
|
|
7
9
|
return `<!DOCTYPE html>
|
|
@@ -106,6 +108,10 @@ export function renderPluginExperimentReportHtml(report) {
|
|
|
106
108
|
|
|
107
109
|
${renderContextWindowScalingHtml(report.contextWindowScaling)}
|
|
108
110
|
|
|
111
|
+
${renderRetrievalPrecisionRecallHtml(report.retrievalPrecisionRecall ?? null)}
|
|
112
|
+
|
|
113
|
+
${renderRetrievalQueryStrategyComparisonHtml(report.retrievalQueryStrategyComparison ?? null)}
|
|
114
|
+
|
|
109
115
|
<section>
|
|
110
116
|
<h2>Warnings, Skips, And Failures</h2>
|
|
111
117
|
${report.findings.length ? table(["Severity", "Code", "Message", "Variant", "Case"], report.findings.map((finding) => [
|