Skip to content

Commit c85113e

Browse files
committed
fix(qa): escape tool coverage markdown cells
1 parent bdf81a8 commit c85113e

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

extensions/qa-lab/src/tool-coverage-report.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,35 @@ describe("qa tool coverage report", () => {
9494
);
9595
});
9696

97+
it("escapes freeform metadata in the markdown table", () => {
98+
const report = buildQaToolCoverageReport({
99+
scenarios: [
100+
makeScenario("tool-read", "read|file", {
101+
toolCoverage: {
102+
bucket: "codex-native-workspace",
103+
expectedLayer: "codex-native-workspace",
104+
capabilityLayer: "codex-native-workspace",
105+
required: true,
106+
tracking: "#80236",
107+
reason: "tracked | runtime drift",
108+
codexDefaultImpact: "P2 | default",
109+
qaImpact: "P1 | confidence",
110+
action: "fix | backfill",
111+
},
112+
}),
113+
],
114+
generatedAt: "2026-05-10T00:00:00.000Z",
115+
});
116+
117+
const markdown = renderQaToolCoverageMarkdownReport(report);
118+
119+
expect(markdown).toContain("read\\|file");
120+
expect(markdown).toContain("P2 \\| default");
121+
expect(markdown).toContain("P1 \\| confidence");
122+
expect(markdown).toContain("fix \\| backfill");
123+
expect(markdown).toContain("#80236 tracked \\| runtime drift");
124+
});
125+
97126
it("uses runtime parity summary rows and allows tracked known-broken drift", () => {
98127
const report = buildQaToolCoverageReport({
99128
scenarios: [

extensions/qa-lab/src/tool-coverage-report.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,22 @@ export function renderQaToolCoverageMarkdownReport(report: QaToolCoverageReport)
325325
];
326326

327327
for (const row of report.rows) {
328-
lines.push(
329-
`| ${row.tool} | ${row.bucket} | ${row.expectedLayer} | ${row.capabilityLayer} | ${row.required ? "yes" : "no"} | ${row.fixtureCount} | ${row.openclaw} | ${row.codex} | ${row.drift} | ${row.codexDefaultImpact ?? ""} | ${row.qaImpact ?? ""} | ${row.action ?? ""} | ${row.tracking ?? ""} |`,
330-
);
328+
const cells = [
329+
row.tool,
330+
row.bucket,
331+
row.expectedLayer,
332+
row.capabilityLayer,
333+
row.required ? "yes" : "no",
334+
row.fixtureCount.toString(),
335+
row.openclaw,
336+
row.codex,
337+
row.drift,
338+
row.codexDefaultImpact ?? "",
339+
row.qaImpact ?? "",
340+
row.action ?? "",
341+
row.tracking ?? "",
342+
].map(escapeTableCell);
343+
lines.push(`| ${cells.join(" | ")} |`);
331344
}
332345

333346
if (report.failures.length > 0) {
@@ -344,3 +357,7 @@ export function renderQaToolCoverageMarkdownReport(report: QaToolCoverageReport)
344357

345358
return `${lines.join("\n").trimEnd()}\n`;
346359
}
360+
361+
function escapeTableCell(value: string): string {
362+
return value.replace(/\|/gu, "\\|").replace(/\s+/gu, " ").trim();
363+
}

0 commit comments

Comments
 (0)