From cc146042d16bfee5b2553e9a255f659b2cf64f9a Mon Sep 17 00:00:00 2001
From: RRFZ <247457381+Plastic-time@users.noreply.github.com>
Date: Sun, 25 Oct 2026 01:55:18 -0400
Subject: [PATCH] fix(cli): preserve literal Markdown table facts (#7073)
* fix(cli): preserve literal Markdown table facts
* test(cli): retain capacity diagnostics through Markdown escaping
* fix(cli): keep intraword underscores readable in Markdown tables
Underscore runs between letters or digits cannot open or close CommonMark emphasis, so escaping them only adds noise to identifiers such as source_index and invalid_request in raw Markdown. Escape underscore runs only at word boundaries and restore the original diagnostic assertions.
---
docs/cli.md | 6 +-
src/cli/cliOutputFormatter.ts | 18 ++-
.../boundary/cli/filteredArrayOutput.test.ts | 21 ++--
.../boundary/cli/markdownLiteralCells.test.ts | 105 ++++++++++++++++++
.../boundary/cli/markdownNestedFacts.test.ts | 2 +-
5 files changed, 137 insertions(+), 15 deletions(-)
create mode 100644 tests/boundary/cli/markdownLiteralCells.test.ts
diff --git a/docs/cli.md b/docs/cli.md
index 05316de58d..ab92ba594e 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -924,8 +924,10 @@ redirected, which keeps large results smaller for agents and scripts; pipe
through `jq .` for indented files. Output selection and formatting do not change
operation status.
-`--format md` escapes backslashes and pipes in table headers and cells, and
-renders cell line breaks as `
`. These are presentation changes; retained
+`--format md` escapes backslashes, pipes, and inline Markdown syntax in table
+headers and cells so source text is displayed literally, and renders cell line
+breaks as `
`. Underscores between letters or digits, as in `source_index`,
+cannot form emphasis and stay unescaped. These are presentation changes; retained
Evidence and JSON output keep the original values. Nested objects and arrays
use JSON inside their owning Markdown cells, retaining the field names and
values instead of replacing them with an object label. Table widths are measured
diff --git a/src/cli/cliOutputFormatter.ts b/src/cli/cliOutputFormatter.ts
index 86056e100c..2854c72286 100644
--- a/src/cli/cliOutputFormatter.ts
+++ b/src/cli/cliOutputFormatter.ts
@@ -43,13 +43,27 @@ const isObjectArray = (value: unknown): value is Record[] =>
) &&
value.some((item) => Object.keys(item).length > 0);
+const isWordCharacter = (character: string | undefined): boolean =>
+ character !== undefined && /[\p{L}\p{N}]/u.test(character);
+
+/** Underscore runs between word characters cannot open or close emphasis. */
+const escapeInlineSyntax = (
+ match: string,
+ offset: number,
+ text: string,
+): string =>
+ match.startsWith("_") &&
+ isWordCharacter(text[offset - 1]) &&
+ isWordCharacter(text[offset + match.length])
+ ? match
+ : match.replace(/./g, "\\$&");
+
const escapeTableCell = (value: string): string =>
value
.replace(/&/g, "&")
.replace(//g, ">")
- .replace(/\\/g, "\\\\")
- .replace(/\|/g, "\\|")
+ .replace(/[\\|`*[\]~]|_+/g, escapeInlineSyntax)
.replace(/\r\n|\r|\n/g, "
");
const renderTable = (
diff --git a/tests/boundary/cli/filteredArrayOutput.test.ts b/tests/boundary/cli/filteredArrayOutput.test.ts
index c0dbd382ee..a345449ca4 100644
--- a/tests/boundary/cli/filteredArrayOutput.test.ts
+++ b/tests/boundary/cli/filteredArrayOutput.test.ts
@@ -4,7 +4,10 @@ import { Cli, Formatter } from "incur";
import { estimateTokenCount } from "tokenx";
import { expect, it } from "vitest";
-import { parseCliOutputArguments } from "../../../src/cliOutput.js";
+import {
+ parseCliOutputArguments,
+ renderCliDocument,
+} from "../../../src/cliOutput.js";
import {
createCliResultOutput,
streamCliCommandResults,
@@ -80,7 +83,7 @@ it("renders only each record's own fields in heterogeneous Markdown tables", asy
"name",
"constructor",
"toString",
- "__proto__",
+ "\\_\\_proto\\_\\_",
"enabled",
"count",
]);
@@ -346,10 +349,9 @@ it.each(["json", "jsonl", "yaml", "toon", "md"] as const)(
.split("\n")
.map((line) => JSON.parse(line)),
).toEqual(expected);
- else if (format === "md") {
- expect(outputs[0]).toContain(`| ${JSON.stringify(expected[0]!.rows)} |`);
- expect(outputs[0]).not.toContain("[object Object]");
- } else expect(outputs[0]).toBe(`${Formatter.format(expected, format)}\n`);
+ else if (format === "md")
+ expect(outputs[0]).toBe(renderCliDocument(expected, format, false));
+ else expect(outputs[0]).toBe(`${Formatter.format(expected, format)}\n`);
expect(projectionSource[0]?.rows[0]?.retained).toEqual([0, 10]);
},
);
@@ -406,10 +408,9 @@ it.each(["json", "jsonl", "yaml", "toon", "md"] as const)(
.split("\n")
.map((line) => JSON.parse(line)),
).toEqual(expected);
- else if (format === "md") {
- expect(outputs[0]).toContain(`| ${JSON.stringify(expected[0]!.rows)} |`);
- expect(outputs[0]).not.toContain("[object Object]");
- } else expect(outputs[0]).toBe(`${Formatter.format(expected, format)}\n`);
+ else if (format === "md")
+ expect(outputs[0]).toBe(renderCliDocument(expected, format, false));
+ else expect(outputs[0]).toBe(`${Formatter.format(expected, format)}\n`);
expect(projectionSource).toEqual(before);
},
);
diff --git a/tests/boundary/cli/markdownLiteralCells.test.ts b/tests/boundary/cli/markdownLiteralCells.test.ts
new file mode 100644
index 0000000000..44271ad030
--- /dev/null
+++ b/tests/boundary/cli/markdownLiteralCells.test.ts
@@ -0,0 +1,105 @@
+import { readFile, writeFile } from "node:fs/promises";
+import { join } from "node:path";
+
+import { parseFragment, type DefaultTreeAdapterTypes } from "parse5";
+import { createMarkdownRenderer, disposeMdItInstance } from "vitepress";
+import { afterAll, expect } from "vitest";
+
+import { createEvidence } from "../../../src/domain/evidence.js";
+import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
+import { renderCliDocument } from "../../../src/cliOutput.js";
+import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
+import { cliTest } from "../../support/cli/cliFixture.js";
+
+afterAll(() => disposeMdItInstance());
+
+const renderedCells = (html: string): string[] => {
+ const text = (node: DefaultTreeAdapterTypes.Node): string =>
+ node.nodeName === "#text" && "value" in node
+ ? node.value
+ : "childNodes" in node
+ ? node.childNodes.map(text).join("")
+ : "";
+ const cells: string[] = [];
+ const visit = (node: DefaultTreeAdapterTypes.Node): void => {
+ if ("tagName" in node && (node.tagName === "th" || node.tagName === "td"))
+ cells.push(text(node));
+ else if ("childNodes" in node) node.childNodes.forEach(visit);
+ };
+ visit(parseFragment(html));
+ return cells;
+};
+
+cliTest(
+ "preserves literal Evidence keys and values when Markdown tables are rendered",
+ async ({ cli }) => {
+ const root = await createTestTempDirectory("rea-markdown-literal-cells-");
+ const path = join(root, "bundle.json");
+ const literal =
+ "`code` **bold** _name_ ~~deleted~~ [label](target) ";
+ const punctuation = "\\`tick` \\*star* \\[label](target) a|b ©";
+ const identifiers = "x__y__z _a_b_ 中_文 snake_case_";
+ const row = {
+ [literal]: literal,
+ punctuation,
+ nested: { text: literal, values: ["__name__", "plain"] },
+ empty: "",
+ zero: 0,
+ disabled: false,
+ absent: null,
+ source_index: identifiers,
+ };
+ const result = { scalar: { [literal]: literal }, rows: [row] };
+ const record = createEvidence(
+ undefined,
+ { id: "fixture", name: "Fixture", version: "1" },
+ { operation: "ordinary-json", parameters: {}, result },
+ );
+ const saved = JSON.stringify(createEvidenceBundle([record]));
+ await writeFile(path, saved);
+ const base = [
+ "inspect-evidence-bundle",
+ JSON.stringify({ path, detail: "complete" }),
+ "--filter-output",
+ "records.0.normalized_result",
+ ];
+ const json = await cli.run({ arguments: [...base, "--json"] });
+ const markdown = await cli.run({ arguments: [...base, "--format", "md"] });
+ expect(json.exitCode, json.stderr).toBe(0);
+ expect(markdown.exitCode, markdown.stderr).toBe(0);
+ expect(JSON.parse(json.stdout)).toEqual({
+ records: { "0": { normalized_result: result } },
+ });
+ expect(await readFile(path, "utf8")).toBe(saved);
+ // Intraword underscores cannot form emphasis, so identifiers stay readable.
+ expect(markdown.stdout).toContain(" source_index ");
+ expect(markdown.stdout).toContain(
+ " x__y__z \\_a_b\\_ 中_文 snake_case\\_ ",
+ );
+
+ const renderer = await createMarkdownRenderer(root, { linkify: false });
+ const html = renderer.render(markdown.stdout);
+ const expectedCells = [
+ "Key",
+ "Value",
+ literal,
+ literal,
+ ...Object.keys(row),
+ literal,
+ punctuation,
+ JSON.stringify(row.nested),
+ "",
+ "0",
+ "false",
+ "null",
+ identifiers,
+ ];
+ expect(renderedCells(html)).toEqual(expectedCells);
+ expect(html).not.toMatch(/<(?:em|strong|s|code|img)(?:\s|>)/u);
+
+ // Standalone reports share the same table owner as command output.
+ expect(
+ renderedCells(renderer.render(renderCliDocument(result, "md", false))),
+ ).toEqual(expectedCells);
+ },
+);
diff --git a/tests/boundary/cli/markdownNestedFacts.test.ts b/tests/boundary/cli/markdownNestedFacts.test.ts
index 3a15cbe090..b82821b028 100644
--- a/tests/boundary/cli/markdownNestedFacts.test.ts
+++ b/tests/boundary/cli/markdownNestedFacts.test.ts
@@ -16,7 +16,7 @@ it("retains nested facts in standalone Markdown table cells and mixed arrays", (
];
const markdown = renderCliDocument(value, "md", false);
expect(markdown).toContain('{"path":"alpha.txt"}');
- expect(markdown).toContain('[{"path":"sub/beta.txt"}]');
+ expect(markdown).toContain('\\[{"path":"sub/beta.txt"}\\]');
expect(markdown).toContain("caller\\|value");
expect(markdown).not.toContain("[object Object]");
expect(