@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
|
@@ -3,6 +3,8 @@ import { isV2ReportSection } from "./buildIncrementalChangeStalenessReportV2.js"
|
|
|
3
3
|
import { renderIncrementalChangeStalenessSectionV2 } from "./renderIncrementalChangeStalenessTextV2.js";
|
|
4
4
|
import { REINDEX_RECOMMENDATION_EXPLANATIONS } from "./buildWarmIndexReuseReport.js";
|
|
5
5
|
import { renderContextWindowScalingTextLines } from "./renderContextWindowScalingText.js";
|
|
6
|
+
import { renderRetrievalPrecisionRecallTextLines } from "./renderRetrievalPrecisionRecallText.js";
|
|
7
|
+
import { renderRetrievalQueryStrategyComparisonTextLines } from "./renderRetrievalQueryStrategyComparisonText.js";
|
|
6
8
|
function sanitizeScalar(value) {
|
|
7
9
|
const text = String(value);
|
|
8
10
|
let result = "";
|
|
@@ -671,6 +673,22 @@ export function renderPluginExperimentReportText(report) {
|
|
|
671
673
|
else {
|
|
672
674
|
lines.push(...renderContextWindowScalingTextLines(report.contextWindowScaling));
|
|
673
675
|
}
|
|
676
|
+
pushSection(lines, "Retrieval Precision/Recall");
|
|
677
|
+
const retrievalPrecisionRecall = report.retrievalPrecisionRecall ?? null;
|
|
678
|
+
if (retrievalPrecisionRecall === null) {
|
|
679
|
+
lines.push("Not applicable to this plugin.");
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
lines.push(...renderRetrievalPrecisionRecallTextLines(retrievalPrecisionRecall));
|
|
683
|
+
}
|
|
684
|
+
pushSection(lines, "Retrieval Query Strategy Comparison");
|
|
685
|
+
const retrievalQueryStrategyComparison = report.retrievalQueryStrategyComparison ?? null;
|
|
686
|
+
if (retrievalQueryStrategyComparison === null) {
|
|
687
|
+
lines.push("Not applicable to this plugin.");
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
lines.push(...renderRetrievalQueryStrategyComparisonTextLines(retrievalQueryStrategyComparison));
|
|
691
|
+
}
|
|
674
692
|
pushSection(lines, "Warnings, Skips, And Failures");
|
|
675
693
|
pushDashList(lines, report.findings.map((finding) => `${finding.severity} ${finding.code} ${finding.message} variant=${finding.variantId ?? ""} case=${finding.caseId ?? ""}`));
|
|
676
694
|
pushSection(lines, "Artifacts");
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { RETRIEVAL_PRECISION_RECALL_RATIO_KEYS } from "../../experiments/plugins/retrievalPrecisionRecall/types.js";
|
|
2
|
+
import { RETRIEVAL_IDENTITY_REDACTION_EXPLANATION, formatRetrievalCount, formatRetrievalList, formatRetrievalMean, formatRetrievalMetric, RETRIEVAL_RATIO_LABELS } from "./renderRetrievalPrecisionRecallText.js";
|
|
3
|
+
export function renderRetrievalPrecisionRecallHtml(section) {
|
|
4
|
+
if (section === null) {
|
|
5
|
+
return `<section>
|
|
6
|
+
<h2>Retrieval Precision/Recall</h2>
|
|
7
|
+
<p>Not applicable to this plugin.</p>
|
|
8
|
+
</section>`;
|
|
9
|
+
}
|
|
10
|
+
const tokens = section.tokenSummary;
|
|
11
|
+
const occurrences = section.occurrenceSummary;
|
|
12
|
+
return `<section>
|
|
13
|
+
<h2>Retrieval Precision/Recall</h2>
|
|
14
|
+
<p class="muted">Measures which expected files, symbols and facts the my-dev-kit retrieval lifecycle surfaced, and which irrelevant files it surfaced, without agents. It is descriptive and does not rank anything.</p>
|
|
15
|
+
${table(["Field", "Value"], [
|
|
16
|
+
["Schema", section.schemaVersion],
|
|
17
|
+
["Token count method", section.tokenCountMethod ?? "unavailable"],
|
|
18
|
+
...Object.entries(section.runSummary).map(([label, value]) => [label, String(value)])
|
|
19
|
+
])}
|
|
20
|
+
${section.cases.some((entry) => entry.identityRedaction !== null) ? `<p><strong>Identity redaction.</strong> ${escapeHtml(RETRIEVAL_IDENTITY_REDACTION_EXPLANATION)}</p>` : ""}
|
|
21
|
+
<h3>Macro Averages</h3>
|
|
22
|
+
<p class="muted">Mean over available per-case values only. Unavailable and not-applicable cases are counted separately and are never treated as zero.</p>
|
|
23
|
+
${table(["Metric", "Available", "Unavailable", "Not applicable", "Mean"], RETRIEVAL_PRECISION_RECALL_RATIO_KEYS.map((key) => {
|
|
24
|
+
const summary = section.ratioSummaries[key];
|
|
25
|
+
return [RETRIEVAL_RATIO_LABELS[key], String(summary.availableCount), String(summary.unavailableCount), String(summary.notApplicableCount), formatRetrievalMean(summary.meanValue)];
|
|
26
|
+
}))}
|
|
27
|
+
<h3>Retrieved Token Summary</h3>
|
|
28
|
+
<p class="muted">Measured retrieval payload size; not a relevant versus irrelevant token attribution.</p>
|
|
29
|
+
${table(["Available", "Unavailable", "Total", "Mean", "Min", "Max"], [[
|
|
30
|
+
String(tokens.availableCount),
|
|
31
|
+
String(tokens.unavailableCount),
|
|
32
|
+
formatRetrievalCount(tokens.totalTokens),
|
|
33
|
+
formatRetrievalMean(tokens.meanTokens),
|
|
34
|
+
formatRetrievalCount(tokens.minTokens),
|
|
35
|
+
formatRetrievalCount(tokens.maxTokens)
|
|
36
|
+
]])}
|
|
37
|
+
<h3>Missed And Irrelevant Occurrence Totals</h3>
|
|
38
|
+
<p class="muted">Summed per case; identical paths in different cases or projects are not deduplicated.</p>
|
|
39
|
+
${table(["Missed files", "Irrelevant files", "Cases with file evidence", "Missed symbols", "Irrelevant symbols", "Cases with symbol evidence", "Uncovered facts", "Cases with fact evidence"], [[
|
|
40
|
+
String(occurrences.totalMissedFileOccurrences),
|
|
41
|
+
String(occurrences.totalIrrelevantRetrievedFileOccurrences),
|
|
42
|
+
String(occurrences.fileOccurrenceEvidenceCaseCount),
|
|
43
|
+
String(occurrences.totalMissedSymbolOccurrences),
|
|
44
|
+
String(occurrences.totalIrrelevantRetrievedSymbolOccurrences),
|
|
45
|
+
String(occurrences.symbolOccurrenceEvidenceCaseCount),
|
|
46
|
+
String(occurrences.totalUncoveredFactOccurrences),
|
|
47
|
+
String(occurrences.factOccurrenceEvidenceCaseCount)
|
|
48
|
+
]])}
|
|
49
|
+
<h3>Per-Case Results</h3>
|
|
50
|
+
${table(["Case", "Project", "Locality", "Status", "Evidence", ...RETRIEVAL_PRECISION_RECALL_RATIO_KEYS.map((key) => RETRIEVAL_RATIO_LABELS[key]), "Retrieved tokens"], section.cases.map((entry) => [
|
|
51
|
+
entry.caseId,
|
|
52
|
+
entry.benchmarkProject,
|
|
53
|
+
entry.taskLocality ?? "unavailable",
|
|
54
|
+
entry.status,
|
|
55
|
+
entry.evidenceAvailability ?? "unavailable",
|
|
56
|
+
...RETRIEVAL_PRECISION_RECALL_RATIO_KEYS.map((key) => formatRetrievalMetric(entry.metrics[key])),
|
|
57
|
+
formatRetrievalCount(entry.retrievedTokenCount)
|
|
58
|
+
]))}
|
|
59
|
+
<h3>Per-Case Missed And Irrelevant Context</h3>
|
|
60
|
+
<p class="muted">Displayed lists are bounded; totals are exact.</p>
|
|
61
|
+
${section.cases
|
|
62
|
+
.map((entry) => {
|
|
63
|
+
const lists = [
|
|
64
|
+
["Missed files", entry.missedFiles],
|
|
65
|
+
["Irrelevant retrieved files", entry.irrelevantRetrievedFiles],
|
|
66
|
+
["Missed symbols", entry.missedSymbols],
|
|
67
|
+
["Irrelevant retrieved symbols", entry.irrelevantRetrievedSymbols],
|
|
68
|
+
["Uncovered fact IDs", entry.uncoveredFactIds]
|
|
69
|
+
];
|
|
70
|
+
return `<h4><code>${escapeHtml(entry.caseId)}</code> (${escapeHtml(entry.status)})</h4>
|
|
71
|
+
${entry.identityRedaction !== null ? `<p class="muted">Identity redaction: files, symbols, facts, warnings and case title withheld.</p>` : ""}
|
|
72
|
+
${lists
|
|
73
|
+
.map(([label, list]) => `<p>${escapeHtml(label)}: ${escapeHtml(formatRetrievalList(list))}</p>${list && list.displayed.length > 0 ? listHtml(list.displayed) : ""}`)
|
|
74
|
+
.join("\n ")}${entry.errors.length > 0 ? `\n ${listHtml(entry.errors)}` : ""}`;
|
|
75
|
+
})
|
|
76
|
+
.join("\n ")}
|
|
77
|
+
<h3>Interpretation Limits</h3>
|
|
78
|
+
${listHtml(section.limitations)}
|
|
79
|
+
</section>`;
|
|
80
|
+
}
|
|
81
|
+
function listHtml(items) {
|
|
82
|
+
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
|
|
83
|
+
}
|
|
84
|
+
function table(headers, rows) {
|
|
85
|
+
return `<table><thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr></thead><tbody>${rows
|
|
86
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`)
|
|
87
|
+
.join("")}</tbody></table>`;
|
|
88
|
+
}
|
|
89
|
+
function escapeHtml(value) {
|
|
90
|
+
return String(value ?? "")
|
|
91
|
+
.replaceAll("&", "&")
|
|
92
|
+
.replaceAll("<", "<")
|
|
93
|
+
.replaceAll(">", ">")
|
|
94
|
+
.replaceAll('"', """)
|
|
95
|
+
.replaceAll("'", "'");
|
|
96
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { RETRIEVAL_PRECISION_RECALL_RATIO_KEYS } from "../../experiments/plugins/retrievalPrecisionRecall/types.js";
|
|
2
|
+
export const RETRIEVAL_RATIO_LABELS = {
|
|
3
|
+
filePrecision: "File precision",
|
|
4
|
+
fileRecall: "File recall",
|
|
5
|
+
symbolPrecision: "Symbol precision",
|
|
6
|
+
symbolRecall: "Symbol recall",
|
|
7
|
+
factCoverage: "Fact coverage",
|
|
8
|
+
irrelevantContextRatio: "Irrelevant context ratio"
|
|
9
|
+
};
|
|
10
|
+
// Render-time formatting only; the JSON report keeps exact unrounded values. Unavailable and not-applicable are
|
|
11
|
+
// always named, never shown as a number.
|
|
12
|
+
export function formatRetrievalMetric(metric) {
|
|
13
|
+
if (metric.availability === "available" && metric.value !== null) {
|
|
14
|
+
return `${metric.value.toFixed(4)} (${metric.numerator}/${metric.denominator})`;
|
|
15
|
+
}
|
|
16
|
+
return `${metric.availability}${metric.reason ? ` (${metric.reason})` : ""}`;
|
|
17
|
+
}
|
|
18
|
+
/** Explains the placeholders; shown only when at least one case was externally projected. */
|
|
19
|
+
export const RETRIEVAL_IDENTITY_REDACTION_EXPLANATION = "External-local run: file, symbol and fact identities, warning text and case titles are withheld. A placeholder such as <redacted file 1> stands for exactly one withheld identity; it is not a file name and not an absence of files. Counts and metric values are exact.";
|
|
20
|
+
export function formatRetrievalMean(value) {
|
|
21
|
+
return value === null ? "unavailable" : value.toFixed(4);
|
|
22
|
+
}
|
|
23
|
+
export function formatRetrievalCount(value) {
|
|
24
|
+
return value === null ? "unavailable" : String(value);
|
|
25
|
+
}
|
|
26
|
+
export function formatRetrievalList(list) {
|
|
27
|
+
if (list === null)
|
|
28
|
+
return "unavailable";
|
|
29
|
+
return `${list.totalCount} (displayed ${list.displayedCount}, omitted ${list.omittedCount})`;
|
|
30
|
+
}
|
|
31
|
+
function clean(value) {
|
|
32
|
+
return String(value).replace(/[\u0000-\u001f\u007f]/g, " ");
|
|
33
|
+
}
|
|
34
|
+
function row(parts) {
|
|
35
|
+
return `- ${parts.map(([label, value]) => `${label}: ${clean(value)}`).join(" | ")}`;
|
|
36
|
+
}
|
|
37
|
+
/** Lines for the "Retrieval Precision/Recall" section; every value comes from the report model. */
|
|
38
|
+
export function renderRetrievalPrecisionRecallTextLines(section) {
|
|
39
|
+
const lines = [];
|
|
40
|
+
lines.push(`Schema: ${clean(section.schemaVersion)}`);
|
|
41
|
+
lines.push(`Token Count Method: ${clean(section.tokenCountMethod ?? "unavailable")}`);
|
|
42
|
+
if (section.cases.some((entry) => entry.identityRedaction !== null)) {
|
|
43
|
+
lines.push(`Identity Redaction: ${RETRIEVAL_IDENTITY_REDACTION_EXPLANATION}`);
|
|
44
|
+
}
|
|
45
|
+
lines.push("Run Summary:");
|
|
46
|
+
for (const [label, value] of Object.entries(section.runSummary)) {
|
|
47
|
+
lines.push(`${label}: ${value}`);
|
|
48
|
+
}
|
|
49
|
+
lines.push("Macro Averages (mean over available per-case values only; unavailable and not-applicable are counted, never zero):");
|
|
50
|
+
for (const key of RETRIEVAL_PRECISION_RECALL_RATIO_KEYS) {
|
|
51
|
+
const summary = section.ratioSummaries[key];
|
|
52
|
+
lines.push(row([
|
|
53
|
+
["Metric", RETRIEVAL_RATIO_LABELS[key]],
|
|
54
|
+
["Available", summary.availableCount],
|
|
55
|
+
["Unavailable", summary.unavailableCount],
|
|
56
|
+
["Not Applicable", summary.notApplicableCount],
|
|
57
|
+
["Mean", formatRetrievalMean(summary.meanValue)]
|
|
58
|
+
]));
|
|
59
|
+
}
|
|
60
|
+
lines.push("Retrieved Token Summary (measured payload, not relevant/irrelevant attribution):");
|
|
61
|
+
const tokens = section.tokenSummary;
|
|
62
|
+
lines.push(row([
|
|
63
|
+
["Available", tokens.availableCount],
|
|
64
|
+
["Unavailable", tokens.unavailableCount],
|
|
65
|
+
["Total", formatRetrievalCount(tokens.totalTokens)],
|
|
66
|
+
["Mean", formatRetrievalMean(tokens.meanTokens)],
|
|
67
|
+
["Min", formatRetrievalCount(tokens.minTokens)],
|
|
68
|
+
["Max", formatRetrievalCount(tokens.maxTokens)]
|
|
69
|
+
]));
|
|
70
|
+
lines.push("Missed And Irrelevant Occurrence Totals (summed per case; not deduplicated across cases or projects):");
|
|
71
|
+
const occurrences = section.occurrenceSummary;
|
|
72
|
+
lines.push(row([
|
|
73
|
+
["Missed Files", occurrences.totalMissedFileOccurrences],
|
|
74
|
+
["Irrelevant Files", occurrences.totalIrrelevantRetrievedFileOccurrences],
|
|
75
|
+
["Cases With File Evidence", occurrences.fileOccurrenceEvidenceCaseCount],
|
|
76
|
+
["Missed Symbols", occurrences.totalMissedSymbolOccurrences],
|
|
77
|
+
["Irrelevant Symbols", occurrences.totalIrrelevantRetrievedSymbolOccurrences],
|
|
78
|
+
["Cases With Symbol Evidence", occurrences.symbolOccurrenceEvidenceCaseCount],
|
|
79
|
+
["Uncovered Facts", occurrences.totalUncoveredFactOccurrences],
|
|
80
|
+
["Cases With Fact Evidence", occurrences.factOccurrenceEvidenceCaseCount]
|
|
81
|
+
]));
|
|
82
|
+
lines.push("Per-Case Results (one row per case):");
|
|
83
|
+
for (const entry of section.cases) {
|
|
84
|
+
lines.push(row([
|
|
85
|
+
["Case", entry.caseId],
|
|
86
|
+
["Project", entry.benchmarkProject],
|
|
87
|
+
["Locality", entry.taskLocality ?? "unavailable"],
|
|
88
|
+
["Status", entry.status],
|
|
89
|
+
["Evidence", entry.evidenceAvailability ?? "unavailable"],
|
|
90
|
+
...RETRIEVAL_PRECISION_RECALL_RATIO_KEYS.map((key) => [RETRIEVAL_RATIO_LABELS[key], formatRetrievalMetric(entry.metrics[key])]),
|
|
91
|
+
["Retrieved Tokens", formatRetrievalCount(entry.retrievedTokenCount)]
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
lines.push("Per-Case Missed And Irrelevant Context (displayed lists are bounded; totals are exact):");
|
|
95
|
+
for (const entry of section.cases) {
|
|
96
|
+
lines.push(`Case ${clean(entry.caseId)} (${clean(entry.status)})`);
|
|
97
|
+
if (entry.identityRedaction !== null)
|
|
98
|
+
lines.push(" Identity redaction: files, symbols, facts, warnings and case title withheld");
|
|
99
|
+
const lists = [
|
|
100
|
+
["Missed Files", entry.missedFiles],
|
|
101
|
+
["Irrelevant Retrieved Files", entry.irrelevantRetrievedFiles],
|
|
102
|
+
["Missed Symbols", entry.missedSymbols],
|
|
103
|
+
["Irrelevant Retrieved Symbols", entry.irrelevantRetrievedSymbols],
|
|
104
|
+
["Uncovered Fact IDs", entry.uncoveredFactIds]
|
|
105
|
+
];
|
|
106
|
+
for (const [label, list] of lists) {
|
|
107
|
+
lines.push(` ${label}: ${formatRetrievalList(list)}`);
|
|
108
|
+
for (const item of list?.displayed ?? []) {
|
|
109
|
+
lines.push(` - ${clean(item)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const error of entry.errors) {
|
|
113
|
+
lines.push(` Error: ${clean(error)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
lines.push("Interpretation Limits:");
|
|
117
|
+
for (const limitation of section.limitations) {
|
|
118
|
+
lines.push(`- ${clean(limitation)}`);
|
|
119
|
+
}
|
|
120
|
+
return lines;
|
|
121
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { RETRIEVAL_QUERY_STRATEGY_EXTERNAL_PRIVACY_NOTE, describeRetrievalQueryStrategyScope, formatRetrievalQueryStrategyList, formatRetrievalQueryStrategyMetric, formatRetrievalQueryStrategyNumber } from "./renderRetrievalQueryStrategyComparisonText.js";
|
|
2
|
+
export function renderRetrievalQueryStrategyComparisonHtml(section) {
|
|
3
|
+
if (section === null)
|
|
4
|
+
return "";
|
|
5
|
+
const methodology = section.methodology;
|
|
6
|
+
const objective = (value, digits) => (value === undefined ? "unavailable" : digits ? value.toFixed(4) : String(value));
|
|
7
|
+
return `<section>
|
|
8
|
+
<h2>Retrieval Query Strategy Comparison</h2>
|
|
9
|
+
<h3>Methodology</h3>
|
|
10
|
+
${table(["Field", "Value"], [
|
|
11
|
+
["Schema", section.schemaVersion],
|
|
12
|
+
["File F1", methodology.fileF1],
|
|
13
|
+
["Symbol F1", methodology.symbolF1],
|
|
14
|
+
["Aggregation", methodology.aggregation],
|
|
15
|
+
["Multi-objective comparison", methodology.multiObjectiveComparison],
|
|
16
|
+
["Unique-best rule", methodology.uniqueBestRule],
|
|
17
|
+
["Strategy order", section.strategyOrder.join(", ")]
|
|
18
|
+
])}
|
|
19
|
+
${section.identityRedaction !== null ? `<p><strong>Identity redaction.</strong> ${escapeHtml(RETRIEVAL_QUERY_STRATEGY_EXTERNAL_PRIVACY_NOTE)}</p>` : ""}
|
|
20
|
+
<h3>Task-Type Comparison</h3>
|
|
21
|
+
${table(["Scope", "Cases", "Matched Cases", "Excluded Cases", "Interpretation", "Best Strategy", "Pareto Front"], section.scopes.map((scope) => [
|
|
22
|
+
scope.scopeId,
|
|
23
|
+
String(scope.caseCount),
|
|
24
|
+
String(scope.comparisonCaseCount),
|
|
25
|
+
String(scope.excludedCaseCount),
|
|
26
|
+
scope.interpretation,
|
|
27
|
+
scope.bestStrategyId ?? "none",
|
|
28
|
+
formatRetrievalQueryStrategyList(scope.paretoFrontStrategyIds)
|
|
29
|
+
]))}
|
|
30
|
+
${listHtml(section.scopes.map(describeRetrievalQueryStrategyScope))}
|
|
31
|
+
<h3>Per-Scope Strategy Objectives</h3>
|
|
32
|
+
${section.scopes
|
|
33
|
+
.map((scope) => `<h4>${escapeHtml(scope.scopeId)}</h4>
|
|
34
|
+
${table(["Strategy", "Completed", "Partial", "Failed", "Mean File F1", "Mean Symbol F1", "Mean Fact Coverage", "Mean Retrieved Tokens"], scope.strategySummaries.map((summary) => [
|
|
35
|
+
summary.strategyId,
|
|
36
|
+
String(summary.completedCaseCount),
|
|
37
|
+
String(summary.partialCaseCount),
|
|
38
|
+
String(summary.failedCaseCount),
|
|
39
|
+
objective(summary.objectives?.meanFileF1, true),
|
|
40
|
+
objective(summary.objectives?.meanSymbolF1, true),
|
|
41
|
+
objective(summary.objectives?.meanFactCoverage, true),
|
|
42
|
+
objective(summary.objectives?.meanRetrievedTokenCount, false)
|
|
43
|
+
]))}`)
|
|
44
|
+
.join("\n ")}
|
|
45
|
+
<h3>Per-Case Treatments</h3>
|
|
46
|
+
${table([
|
|
47
|
+
"Case",
|
|
48
|
+
"Task Type",
|
|
49
|
+
"Strategy",
|
|
50
|
+
"Status",
|
|
51
|
+
"Evidence",
|
|
52
|
+
"File Precision",
|
|
53
|
+
"File Recall",
|
|
54
|
+
"File F1",
|
|
55
|
+
"Symbol Precision",
|
|
56
|
+
"Symbol Recall",
|
|
57
|
+
"Symbol F1",
|
|
58
|
+
"Fact Coverage",
|
|
59
|
+
"Irrelevant Context Ratio",
|
|
60
|
+
"Retrieved Tokens"
|
|
61
|
+
], section.cases.flatMap((entry) => entry.treatments.map((treatment) => [
|
|
62
|
+
entry.caseId,
|
|
63
|
+
entry.taskLocality ?? "unavailable",
|
|
64
|
+
treatment.strategyId,
|
|
65
|
+
treatment.executionStatus,
|
|
66
|
+
treatment.evidenceAvailability ?? "unavailable",
|
|
67
|
+
formatRetrievalQueryStrategyMetric(treatment.filePrecision),
|
|
68
|
+
formatRetrievalQueryStrategyMetric(treatment.fileRecall),
|
|
69
|
+
formatRetrievalQueryStrategyMetric(treatment.fileF1),
|
|
70
|
+
formatRetrievalQueryStrategyMetric(treatment.symbolPrecision),
|
|
71
|
+
formatRetrievalQueryStrategyMetric(treatment.symbolRecall),
|
|
72
|
+
formatRetrievalQueryStrategyMetric(treatment.symbolF1),
|
|
73
|
+
formatRetrievalQueryStrategyMetric(treatment.factCoverage),
|
|
74
|
+
formatRetrievalQueryStrategyMetric(treatment.irrelevantContextRatio),
|
|
75
|
+
formatRetrievalQueryStrategyNumber(treatment.retrievedTokenCount)
|
|
76
|
+
])))}
|
|
77
|
+
<h3>Interpretation Limits</h3>
|
|
78
|
+
${listHtml(section.limitations)}
|
|
79
|
+
</section>`;
|
|
80
|
+
}
|
|
81
|
+
function listHtml(items) {
|
|
82
|
+
return `<ul>${items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`;
|
|
83
|
+
}
|
|
84
|
+
function table(headers, rows) {
|
|
85
|
+
return `<table><thead><tr>${headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("")}</tr></thead><tbody>${rows
|
|
86
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`)
|
|
87
|
+
.join("")}</tbody></table>`;
|
|
88
|
+
}
|
|
89
|
+
function escapeHtml(value) {
|
|
90
|
+
return String(value ?? "")
|
|
91
|
+
.replaceAll("&", "&")
|
|
92
|
+
.replaceAll("<", "<")
|
|
93
|
+
.replaceAll(">", ">")
|
|
94
|
+
.replaceAll('"', """)
|
|
95
|
+
.replaceAll("'", "'");
|
|
96
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const RETRIEVAL_QUERY_STRATEGY_EXTERNAL_PRIVACY_NOTE = "External-local report: private file, symbol, fact, semantic-node, warning, and case-title identities are withheld; numeric scientific results and strategy interpretations are preserved.";
|
|
2
|
+
/** Available values keep four decimals; unavailable/not-applicable are never shown as zero. */
|
|
3
|
+
export function formatRetrievalQueryStrategyMetric(metric) {
|
|
4
|
+
if (metric.availability === "available" && metric.value !== null)
|
|
5
|
+
return metric.value.toFixed(4);
|
|
6
|
+
return `${metric.availability}${metric.reason ? ` (${metric.reason})` : ""}`;
|
|
7
|
+
}
|
|
8
|
+
export const formatRetrievalQueryStrategyNumber = (value) => (value === null ? "unavailable" : String(value));
|
|
9
|
+
const formatMean = (value) => (value === undefined ? "unavailable" : value.toFixed(4));
|
|
10
|
+
const formatTokenMean = (value) => (value === undefined ? "unavailable" : String(value));
|
|
11
|
+
export const formatRetrievalQueryStrategyList = (items) => (items.length === 0 ? "none" : items.join(", "));
|
|
12
|
+
/** One human sentence per scope: a best strategy only for a single-member front; otherwise an explicit tradeoff. */
|
|
13
|
+
export function describeRetrievalQueryStrategyScope(scope) {
|
|
14
|
+
if (scope.interpretation === "unique-best") {
|
|
15
|
+
return `${scope.scopeId}: unique-best ${scope.bestStrategyId} (single-member Pareto front).`;
|
|
16
|
+
}
|
|
17
|
+
if (scope.interpretation === "tradeoff") {
|
|
18
|
+
return `${scope.scopeId}: No Single Best strategy; Pareto front (nondominated, not ranked): ${formatRetrievalQueryStrategyList(scope.paretoFrontStrategyIds)}.`;
|
|
19
|
+
}
|
|
20
|
+
return `${scope.scopeId}: unavailable; no matched complete-case comparison.`;
|
|
21
|
+
}
|
|
22
|
+
export function renderRetrievalQueryStrategyComparisonTextLines(section) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
const methodology = section.methodology;
|
|
25
|
+
lines.push(`Schema: ${section.schemaVersion}`);
|
|
26
|
+
lines.push(`File F1: ${methodology.fileF1}`);
|
|
27
|
+
lines.push(`Symbol F1: ${methodology.symbolF1}`);
|
|
28
|
+
lines.push(`Aggregation: ${methodology.aggregation}`);
|
|
29
|
+
lines.push(`Multi-objective comparison: ${methodology.multiObjectiveComparison}`);
|
|
30
|
+
lines.push(`Unique-best rule: ${methodology.uniqueBestRule}`);
|
|
31
|
+
lines.push(`Strategy order: ${section.strategyOrder.join(", ")}`);
|
|
32
|
+
if (section.identityRedaction !== null)
|
|
33
|
+
lines.push(RETRIEVAL_QUERY_STRATEGY_EXTERNAL_PRIVACY_NOTE);
|
|
34
|
+
lines.push("", "Task-Type Comparison");
|
|
35
|
+
for (const scope of section.scopes) {
|
|
36
|
+
lines.push(`- scope=${scope.scopeId} cases=${scope.caseCount} matched=${scope.comparisonCaseCount} excluded=${scope.excludedCaseCount} interpretation=${scope.interpretation} best=${scope.bestStrategyId ?? "none"} pareto=${formatRetrievalQueryStrategyList(scope.paretoFrontStrategyIds)}`);
|
|
37
|
+
}
|
|
38
|
+
for (const scope of section.scopes)
|
|
39
|
+
lines.push(describeRetrievalQueryStrategyScope(scope));
|
|
40
|
+
lines.push("", "Per-Scope Strategy Objectives");
|
|
41
|
+
for (const scope of section.scopes) {
|
|
42
|
+
lines.push(`Scope ${scope.scopeId}:`);
|
|
43
|
+
for (const summary of scope.strategySummaries) {
|
|
44
|
+
const objectives = summary.objectives;
|
|
45
|
+
lines.push(`- ${summary.strategyId} completed=${summary.completedCaseCount} partial=${summary.partialCaseCount} failed=${summary.failedCaseCount} meanFileF1=${formatMean(objectives?.meanFileF1)} meanSymbolF1=${formatMean(objectives?.meanSymbolF1)} meanFactCoverage=${formatMean(objectives?.meanFactCoverage)} meanRetrievedTokens=${formatTokenMean(objectives?.meanRetrievedTokenCount)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
lines.push("", "Per-Case Treatments");
|
|
49
|
+
for (const entry of section.cases) {
|
|
50
|
+
for (const treatment of entry.treatments) {
|
|
51
|
+
lines.push(`- case=${entry.caseId} taskType=${entry.taskLocality ?? "unavailable"} strategy=${treatment.strategyId} status=${treatment.executionStatus} evidence=${treatment.evidenceAvailability ?? "unavailable"} filePrecision=${formatRetrievalQueryStrategyMetric(treatment.filePrecision)} fileRecall=${formatRetrievalQueryStrategyMetric(treatment.fileRecall)} fileF1=${formatRetrievalQueryStrategyMetric(treatment.fileF1)} symbolPrecision=${formatRetrievalQueryStrategyMetric(treatment.symbolPrecision)} symbolRecall=${formatRetrievalQueryStrategyMetric(treatment.symbolRecall)} symbolF1=${formatRetrievalQueryStrategyMetric(treatment.symbolF1)} factCoverage=${formatRetrievalQueryStrategyMetric(treatment.factCoverage)} irrelevantContextRatio=${formatRetrievalQueryStrategyMetric(treatment.irrelevantContextRatio)} retrievedTokens=${formatRetrievalQueryStrategyNumber(treatment.retrievedTokenCount)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
lines.push("", "Interpretation Limits");
|
|
55
|
+
for (const limitation of section.limitations)
|
|
56
|
+
lines.push(`- ${limitation}`);
|
|
57
|
+
return lines;
|
|
58
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const RETRIEVAL_PRECISION_RECALL_REPORT_SCHEMA_VERSION = "my-dev-kit-lab-retrieval-precision-recall-report-v1";
|
|
2
|
+
/** Fixed human-facing bound for every identity list; the execution artifact keeps the complete lists. */
|
|
3
|
+
export const MAX_RETRIEVAL_REPORT_IDENTITIES = 20;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RETRIEVAL_QUERY_STRATEGY_COMPARISON_REPORT_SCHEMA_VERSION = "my-dev-kit-lab-retrieval-query-strategy-comparison-report-v1";
|
package/docs/METRICS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Metrics
|
|
2
2
|
|
|
3
|
-
This document is the canonical metric glossary for my-dev-kit-lab. It defines every metric that appears in benchmark profiles, prompt variants, controlled experiment artifacts, and rendered reports across the registered experiment plugins (`context-strategy-comparison`, `warm-index-reuse`,
|
|
3
|
+
This document is the canonical metric glossary for my-dev-kit-lab. It defines every metric that appears in benchmark profiles, prompt variants, controlled experiment artifacts, and rendered reports across the registered experiment plugins (`context-strategy-comparison`, `warm-index-reuse`, `incremental-change-staleness`, `context-window-scaling`, `retrieval-precision-recall`, and `retrieval-query-strategy-comparison`).
|
|
4
4
|
|
|
5
5
|
Related documentation:
|
|
6
6
|
- [ARCHITECTURE.md](ARCHITECTURE.md) — how metrics flow through the pipeline
|
|
@@ -620,7 +620,7 @@ The generic `ExperimentMetric` projection emits run-level case and budget counts
|
|
|
620
620
|
|
|
621
621
|
The report artifact schema is `my-dev-kit-lab-context-window-scaling-report-v1`. It includes run summaries, budget-treatment summaries, per-treatment context measurements, budget cells, relevant-file summary, and fixed interpretation limits. The execution artifact is `context-window-scaling-execution.json` (`my-dev-kit-lab-context-window-scaling-execution-v1`) and does not embed context text.
|
|
622
622
|
|
|
623
|
-
**Synthetic-repository runs (v0.7.1).** The synthetic input introduces no new scientific metric: fit, utilization, correctness, success, and omitted-relevant-file semantics, the execution artifact schema, the report schema, and the plot set are identical to the bundled-corpus run. Each generated repository's `synthetic-repository-manifest.json` records generation evidence (per-file byte length, character count, SHA-256, generated-content estimated tokens, aggregate totals, and `repositoryContentIdentity`); these are reproducibility measurements, not context-window outcome metrics. The raw-full-file treatment context is measured by the existing baseline, which adds file headers and separators, so its estimated tokens differ from the manifest's generated-content estimate. Retrieval precision/recall
|
|
623
|
+
**Synthetic-repository runs (v0.7.1).** The synthetic input introduces no new scientific metric: fit, utilization, correctness, success, and omitted-relevant-file semantics, the execution artifact schema, the report schema, and the plot set are identical to the bundled-corpus run. Each generated repository's `synthetic-repository-manifest.json` records generation evidence (per-file byte length, character count, SHA-256, generated-content estimated tokens, aggregate totals, and `repositoryContentIdentity`); these are reproducibility measurements, not context-window outcome metrics. The raw-full-file treatment context is measured by the existing baseline, which adds file headers and separators, so its estimated tokens differ from the manifest's generated-content estimate. Retrieval precision/recall is documented separately in the v0.8.0 section below.
|
|
624
624
|
|
|
625
625
|
**Local-repository runs (v0.7.2).** Running over an explicitly selected local Git repository introduces no new scientific metric: fit, utilization, correctness, success, and omitted-relevant-file semantics, the execution artifact and report schemas, and the plot set are the same. In this mode file identity lists are replaced by numbered placeholders (`<redacted file N>`) with the same length as the real list, and each treatment carries `fileIdentityRedaction: "redacted"`; a placeholder is a withheld identity, not an empty result, and an empty list or `null` keeps its usual meaning. Operational compatibility with a repository, the 1 MiB file-size limit, and the Git-ignore exclusions are safety policy, not quality measures: they do not imply retrieval precision, recall, ranking quality, or a retrieval winner.
|
|
626
626
|
|
|
@@ -691,3 +691,153 @@ Availability: the first three are always present for a validated incremental tre
|
|
|
691
691
|
`no-observed-regression-relative-to-full` means only that the executed scenario showed no regression under those two evidence dimensions. It does not establish equivalence or safety and does not show that full refresh is unnecessary. `not-comparable-as-partial-refresh` says the run gives no evidence about the requested partial behavior. There is no winner, ranking, composite score, safety score, safe-to-skip-refresh flag, or stale-risk percentage. Ordinary per-treatment correctness `score` remains the existing deterministic fake-agent evidence.
|
|
692
692
|
|
|
693
693
|
**Report presentation.** `report.incrementalChangeStaleness` (schema `my-dev-kit-lab-incremental-change-staleness-report-v2`) holds the summary counts (including per-treatment applied-partial, fallback, and classification counts), per-scenario lifecycle, four treatments and three comparisons identified by explicit treatment IDs, and fixed limitations text. Unavailable evidence is written as `unavailable` or `not applicable`, never zero. Historical V1 reports keep their original two-treatment presentation.
|
|
694
|
+
|
|
695
|
+
## Retrieval precision/recall evidence (v0.8.0)
|
|
696
|
+
|
|
697
|
+
Status: released (v0.8.0). The `retrieval-precision-recall` plugin measures one retrieval per case (the existing my-dev-kit search, lookup, slice, and source lifecycle) against a deterministic answer key. It invokes no agent, and every value below is calculated by `calculateRetrievalQualityMetrics` and persisted in `retrieval-precision-recall-execution.json`; reports render only the persisted values.
|
|
698
|
+
|
|
699
|
+
**Availability.** Each ratio metric is in exactly one of three states. `available` carries a numerator, denominator, and value. `not-applicable` means the denominator is zero, so the metric does not describe that case. `unavailable` means required evidence was missing, partial, or invalid, and carries a fixed reason. Unavailable and not-applicable are never converted to zero, and an `available` zero is a real measured zero.
|
|
700
|
+
|
|
701
|
+
**File precision.** `relevant retrieved files / retrieved files`. Available when the retrieval evidence is trustworthy and at least one file was retrieved. When no file was retrieved it is `not-applicable`, not zero.
|
|
702
|
+
|
|
703
|
+
**File recall.** `relevant retrieved files / expected files`. An available retrieval that returned nothing against a nonempty expected set has recall `0`. With no expected files it is `not-applicable`.
|
|
704
|
+
|
|
705
|
+
**Symbol precision and recall.** The same set formulas over exact, case-sensitive unique symbol names: precision is `relevant retrieved symbol names / retrieved symbol names`, and recall is `relevant retrieved symbol names / expected symbol names`. There is no fuzzy or semantic matching, and several retrieved identities with the same name count once.
|
|
706
|
+
|
|
707
|
+
**Fact coverage.** `covered expected facts / all expected facts`, unweighted. A fact is covered only through the answer key's explicit `factIds`-to-context mappings (`expectedContextTargets` entries that list the fact IDs they support): a target is satisfied when its file was retrieved and each of its symbols was retrieved from that file. A fact mapped to several required targets is covered only when all of them are satisfied (conjunctive). There is no fact-prose matching, no embeddings, and no model judge. If a required fact has no usable mapping, coverage is `unavailable` rather than guessed.
|
|
708
|
+
|
|
709
|
+
**Irrelevant context ratio.** `irrelevant retrieved files / retrieved files`. It is calculated over file identities only. When both are available it is the diagnostic complement of file precision, so it is not an independent ranking metric. It makes no claim about which tokens were relevant.
|
|
710
|
+
|
|
711
|
+
**Retrieved token count.** The existing selected retrieval context's `totalEstimatedTokens`, calculated with the repository's character-based estimator (`ceil(characters / 4)`). It is not provider telemetry, not billing tokens, and not a per-file relevant-token attribution.
|
|
712
|
+
|
|
713
|
+
**Missed context.** Reported as three separate lists, never combined into a mixed-unit score: missed files, missed symbols, and uncovered fact IDs (each with a count).
|
|
714
|
+
|
|
715
|
+
**Aggregation.** `aggregateRetrievalPrecisionRecall` takes macro means over available per-case values only. An available zero stays in the mean; unavailable and not-applicable cases are counted separately and excluded from it. The aggregate has no weighting, score, winner, ranking, or threshold.
|
|
716
|
+
|
|
717
|
+
**External-local runs.** Numeric values and counts are identical in external-local mode. File, symbol, and fact identity lists are replaced by numbered placeholders of the same length (`<redacted file N>`, `<redacted symbol N>`, `<redacted fact N>`), case titles and retrieval warning text are replaced by fixed placeholders, and the artifact carries a fixed identity-redaction marker. A placeholder is a withheld identity, not an empty result.
|
|
718
|
+
|
|
719
|
+
**Not implemented in v0.8.0.** Ranked retrieval metrics such as MRR, NDCG, MAP, and ranked precision; retrieval-strategy comparison and winners; and query-strategy comparison. Strategy comparison was later added by the separate `retrieval-query-strategy-comparison` plugin documented below; ranked metrics remain outside the Lab's scope.
|
|
720
|
+
|
|
721
|
+
**Sources.** Standard set-based precision and recall come from established information-retrieval evaluation: Manning, Raghavan, and Schütze, *Introduction to Information Retrieval*, "Evaluation of unranked retrieval sets" (Stanford online edition, <https://nlp.stanford.edu/IR-book/html/htmledition/evaluation-of-unranked-retrieval-sets-1.html>), and the National Institute of Standards and Technology's Text REtrieval Conference (TREC) material on relevance judgments, which treats relevance as a judged, answer-key property of a test collection (<https://trec.nist.gov/data/reljudge_eng.html>; program overview at <https://trec.nist.gov/>). Applying those formulas to file identities and exact symbol-name identities is this Lab's deterministic operationalization. Fact coverage (through the Lab's explicit `factIds` mapping contract) and irrelevant context ratio are Lab-specific deterministic diagnostics, not standardized TREC ranking metrics, and neither source defines the `factIds` contract.
|
|
722
|
+
|
|
723
|
+
See [COMMANDS.md](COMMANDS.md#retrieval-precision-recall-v080), [WORKFLOWS.md](WORKFLOWS.md#retrieval-precision-recall-experiment-v080), and [ARCHITECTURE.md](ARCHITECTURE.md#retrieval-precisionrecall-architecture-v080).
|
|
724
|
+
|
|
725
|
+
## Retrieval query strategy comparison metrics (v0.8.1)
|
|
726
|
+
|
|
727
|
+
Status: released (v0.8.1). The `retrieval-query-strategy-comparison` plugin runs seven fixed retrieval strategies for each case, with no agent, and compares them. This section is the authoritative definition of its scientific values. Every value is calculated once by the plugin's analysis and persisted in `retrieval-query-strategy-comparison-analysis.json`; reports and generic metrics present those precomputed values and never recalculate them.
|
|
728
|
+
|
|
729
|
+
The seven strategies, in canonical order, are `keyword-search`, `symbol-lookup`, `graph-neighborhood`, `source-slice`, `data-model-graph`, `model-view-lineage`, and `combined-graph-guided`.
|
|
730
|
+
|
|
731
|
+
### Inherited retrieval-quality metrics
|
|
732
|
+
|
|
733
|
+
v0.8.1 reuses, without change, the v0.8.0 definitions (see [Retrieval precision/recall evidence](#retrieval-precisionrecall-evidence-v080)):
|
|
734
|
+
|
|
735
|
+
- File precision `|R_file ∩ E_file| / |R_file|` and file recall `|R_file ∩ E_file| / |E_file|`.
|
|
736
|
+
- Symbol precision `|R_symbol ∩ E_symbol| / |R_symbol|` and symbol recall `|R_symbol ∩ E_symbol| / |E_symbol|`, over exact, case-sensitive unique symbol names.
|
|
737
|
+
- Fact coverage: covered expected facts divided by expected facts, using only the answer key's explicit `factIds`-to-context mappings.
|
|
738
|
+
- Irrelevant context ratio `|R_file - E_file| / |R_file|`.
|
|
739
|
+
- Retrieved token count: the measured retrieval payload size, `ceil(character count / 4)` through the existing estimator.
|
|
740
|
+
|
|
741
|
+
The strategy-neutral retrieval evidence is adapted to the same generic identity-evidence metric owner that scores the v0.8.0 retrieval evidence, so no command provenance changes any numerator, denominator, identity definition, fact mapping, zero-denominator rule, or availability rule. The ratio metrics keep the `available`, `unavailable`, and `not-applicable` states; unavailable and not-applicable values are never converted to zero.
|
|
742
|
+
|
|
743
|
+
### Balanced file F1
|
|
744
|
+
|
|
745
|
+
Let `TP_file` be the number of relevant retrieved files, `FP_file` the number of irrelevant retrieved files, and `FN_file` the number of missed files. Then:
|
|
746
|
+
|
|
747
|
+
```text
|
|
748
|
+
F1_file = 2 * TP_file / (2 * TP_file + FP_file + FN_file)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
Availability: if any of the three identity sets is unavailable, F1 is `unavailable` with the reason `quality-identity-sets-unavailable`. If the denominator is zero (no relevant, irrelevant, or missed files at all), F1 is `not-applicable` with the reason `no-positive-or-retrieved-identities`. Otherwise it is `available`, with the numerator `2 * TP_file` and the denominator `2 * TP_file + FP_file + FN_file`.
|
|
752
|
+
|
|
753
|
+
An important edge case: when nothing is retrieved and the expected relevant set is nonempty, `TP = 0`, `FP = 0`, and `FN > 0`, so `F1 = 0` as an available value. It is not `not-applicable`, because retrieval performance against a nonempty relevant set is measurable. The Lab computes F1 from these counts, not by averaging stored precision and recall.
|
|
754
|
+
|
|
755
|
+
### Balanced symbol F1
|
|
756
|
+
|
|
757
|
+
```text
|
|
758
|
+
F1_symbol = 2 * TP_symbol / (2 * TP_symbol + FP_symbol + FN_symbol)
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
`TP_symbol`, `FP_symbol`, and `FN_symbol` are the numbers of relevant retrieved, irrelevant retrieved, and missed symbols, with the identical availability rules and the exact unique symbol-name identity inherited from v0.8.0. There is no fuzzy name matching, semantic matching, node-ID matching, alias handling, or case folding.
|
|
762
|
+
|
|
763
|
+
**Source for F1.** Manning, Christopher D.; Raghavan, Prabhakar; Schütze, Hinrich. *Introduction to Information Retrieval*. Cambridge University Press. Chapter 8, "Evaluation in information retrieval." <https://nlp.stanford.edu/IR-book/pdf/08eval.pdf>. The source establishes that balanced F1 is the harmonic mean of precision and recall, `F1 = 2PR / (P + R)`. The count form above is the equivalent implementation form the Lab uses so that an empty retrieval against a nonempty relevant set is well defined. The source does not specify the Lab's `unavailable` and `not-applicable` policy, which is project-owned.
|
|
764
|
+
|
|
765
|
+
### Matched complete cases
|
|
766
|
+
|
|
767
|
+
A case participates in cross-strategy aggregation for a scope only when all seven strategies have all four primary objectives available: file F1, symbol F1, fact coverage, and a valid retrieved token count. If any strategy lacks any one of them, the case is excluded from every strategy's aggregate for that scope, so no strategy averages over more cases than another. A treatment's own scientific evidence is still retained when its case is excluded; the rule applies only to cross-strategy aggregation and best-strategy interpretation.
|
|
768
|
+
|
|
769
|
+
### Macro means
|
|
770
|
+
|
|
771
|
+
For each scope and strategy, over the matched comparison cases only:
|
|
772
|
+
|
|
773
|
+
```text
|
|
774
|
+
meanFileF1 = sum(case file F1) / matched comparison case count
|
|
775
|
+
meanSymbolF1 = sum(case symbol F1) / matched comparison case count
|
|
776
|
+
meanFactCoverage = sum(case fact coverage) / matched comparison case count
|
|
777
|
+
meanRetrievedTokenCount = sum(case retrieved tokens) / matched comparison case count
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
Stored values are not rounded. Every included task contributes equally: there is no denominator weighting, no micro averaging, and no weighting by expected files, expected symbols, facts, token count, or task locality.
|
|
781
|
+
|
|
782
|
+
### Task-type scopes
|
|
783
|
+
|
|
784
|
+
The scopes, in report order, are `overall`, `localized`, `cross-module`, and `broad-change`. `overall` contains every selected case; the other three reuse the existing `taskLocality` metadata (`localized`, `cross-module`, `broad-change`). A case with null or unknown `taskLocality` participates only in `overall`. The Lab does not infer task type from query text.
|
|
785
|
+
|
|
786
|
+
### Primary objective vector
|
|
787
|
+
|
|
788
|
+
For a strategy in a scope the objective vector is:
|
|
789
|
+
|
|
790
|
+
```text
|
|
791
|
+
O(strategy, scope) = {
|
|
792
|
+
meanFileF1: maximize,
|
|
793
|
+
meanSymbolF1: maximize,
|
|
794
|
+
meanFactCoverage: maximize,
|
|
795
|
+
meanRetrievedTokenCount: minimize
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Only these four objectives determine dominance. File and symbol precision, recall, irrelevant context ratio, duration, and missed counts remain visible diagnostic metrics but are not additional dominance dimensions: F1 already combines precision and recall, and the irrelevant context ratio is algebraically coupled to file precision when both are defined.
|
|
800
|
+
|
|
801
|
+
### Pareto dominance
|
|
802
|
+
|
|
803
|
+
Strategy A dominates strategy B if and only if all of the following hold:
|
|
804
|
+
|
|
805
|
+
```text
|
|
806
|
+
A.meanFileF1 >= B.meanFileF1
|
|
807
|
+
AND A.meanSymbolF1 >= B.meanSymbolF1
|
|
808
|
+
AND A.meanFactCoverage >= B.meanFactCoverage
|
|
809
|
+
AND A.meanRetrievedTokenCount <= B.meanRetrievedTokenCount
|
|
810
|
+
AND at least one of those comparisons is strict
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
There is no epsilon, no tolerance band, no rounding, and no weighting. Two strategies with identical vectors do not dominate each other. The Pareto front of a scope is every strategy that no other strategy dominates.
|
|
814
|
+
|
|
815
|
+
**Source for Pareto dominance.** Emmerich, Michael T. M.; Deutz, André H. "A tutorial on multiobjective optimization: fundamentals and evolutionary methods." *Natural Computing* 17, 585–609 (2018). DOI 10.1007/s11047-018-9685-y. <https://link.springer.com/article/10.1007/s11047-018-9685-y>. It is cited only for the general multiobjective and Pareto framework. The Lab's exact four objectives and its unique-best interpretation are project-owned design choices.
|
|
816
|
+
|
|
817
|
+
### Best-strategy interpretation
|
|
818
|
+
|
|
819
|
+
This rule is project-owned:
|
|
820
|
+
|
|
821
|
+
- `comparisonCaseCount = 0`: the interpretation is `unavailable`, the Pareto front is empty, and `bestStrategyId` is `null`.
|
|
822
|
+
- The Pareto front has exactly one strategy: the interpretation is `unique-best` and `bestStrategyId` is that strategy.
|
|
823
|
+
- The Pareto front has more than one strategy: the interpretation is `tradeoff` and `bestStrategyId` is `null`.
|
|
824
|
+
|
|
825
|
+
No hidden tie breaker exists: token count, F1, strategy order, alphabetical order, and task locality never select a winner. "No single best strategy" is a valid result.
|
|
826
|
+
|
|
827
|
+
### No composite score or ranking
|
|
828
|
+
|
|
829
|
+
The plugin does not calculate a weighted composite score, a scalar winner score, a total ordinal ranking, MRR, NDCG, MAP, a statistical significance test, or a confidence interval. No scientifically justified weights exist for combining the four objectives, so the Lab exposes tradeoffs instead of hiding them in an arbitrary scalar. The Pareto-front array is emitted in canonical strategy order for deterministic serialization; that order is not a performance order, and the first entry is not the best strategy.
|
|
830
|
+
|
|
831
|
+
### External-local runs: science before redaction
|
|
832
|
+
|
|
833
|
+
In external-local mode the scientific analysis runs first, on the real in-memory file, symbol, and fact identities. Only afterward are file, symbol, fact, semantic-node, warning, and case-title identities replaced for persistence. Privacy placeholders are therefore never used to calculate precision, recall, F1, fact coverage, means, Pareto fronts, or `bestStrategyId`, and every numeric value and strategy interpretation is identical to the unredacted analysis.
|
|
834
|
+
|
|
835
|
+
### Token caveat
|
|
836
|
+
|
|
837
|
+
Retrieved token count is `ceil(character count / 4)` through the existing estimator. It is context-size evidence. It is not provider billing telemetry, not tokenizer-exact model usage, and not a relevant-token attribution.
|
|
838
|
+
|
|
839
|
+
### Where each value lives
|
|
840
|
+
|
|
841
|
+
`retrieval-query-strategy-comparison-execution.json` holds execution evidence only. `retrieval-query-strategy-comparison-analysis.json` is the scientific truth owner for the metrics, objective vectors, matched scopes, Pareto fronts, and best-strategy interpretation. `report.json`, `report.html`, and `report.txt` present the precomputed analysis and do not recalculate it. The generic experiment metrics (per-treatment values and per-scope objective means) are projections of the same precomputed values.
|
|
842
|
+
|
|
843
|
+
See [COMMANDS.md](COMMANDS.md#retrieval-query-strategy-comparison-v081), [WORKFLOWS.md](WORKFLOWS.md#retrieval-query-strategy-comparison-experiment-v081), and [ARCHITECTURE.md](ARCHITECTURE.md#retrieval-query-strategy-comparison-architecture-v081).
|