diff --git a/docs/cli.md b/docs/cli.md
index f6e936d0a6..c51ebef579 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -796,7 +796,9 @@ operation status.
`--format md` escapes backslashes and pipes in table headers and cells, and
renders cell line breaks as `
`. These are presentation changes; retained
-Evidence and JSON output keep the original values. Table widths are measured
+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
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
diff --git a/src/cli/cliOutputFormatter.ts b/src/cli/cliOutputFormatter.ts
index 51e8a0bf78..ced0f34004 100644
--- a/src/cli/cliOutputFormatter.ts
+++ b/src/cli/cliOutputFormatter.ts
@@ -77,10 +77,18 @@ const renderTable = (
return lines.join("\n");
};
+/** Keep nested JSON facts inside their owning table cell. */
+const markdownCell = (value: unknown): string =>
+ value !== null && typeof value === "object"
+ ? JSON.stringify(value)
+ : value === undefined
+ ? ""
+ : String(value);
+
const formatKeyValueTable = (value: Record): string =>
renderTable(
["Key", "Value"],
- Object.entries(value).map(([key, child]) => [key, String(child)]),
+ Object.entries(value).map(([key, child]) => [key, markdownCell(child)]),
);
const formatObjectTable = (
@@ -96,7 +104,7 @@ const formatObjectTable = (
}
return renderTable(
headers,
- items.map((item) => headers.map((key) => String(item[key] ?? ""))),
+ items.map((item) => headers.map((key) => markdownCell(item[key]))),
);
};
@@ -114,7 +122,7 @@ const formatMarkdown = (
const table = formatObjectTable(value);
return path.length === 0 ? table : `## ${path.join(".")}\n\n${table}`;
}
- return formatMarkdown(String(value), path);
+ return formatMarkdown(markdownCell(value), path);
}
const object = value as Record;
@@ -144,7 +152,7 @@ const formatMarkdown = (
? `${heading}${formatKeyValueTable(nested)}`
: formatMarkdown(nested, childPath);
}
- return `${heading}${String(child)}`;
+ return `${heading}${markdownCell(child)}`;
})
.join("\n\n");
};
diff --git a/tests/boundary/cli/markdownNestedFacts.test.ts b/tests/boundary/cli/markdownNestedFacts.test.ts
new file mode 100644
index 0000000000..3a15cbe090
--- /dev/null
+++ b/tests/boundary/cli/markdownNestedFacts.test.ts
@@ -0,0 +1,48 @@
+import { join } from "node:path";
+import { expect, it } from "vitest";
+
+import { renderCliDocument } from "../../../src/cliOutput.js";
+import { writeOrderedZip } from "../../fixtures/artifactEntryOrder.js";
+import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
+import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
+
+it("retains nested facts in standalone Markdown table cells and mixed arrays", () => {
+ const value = [
+ {
+ details: { path: "alpha.txt" },
+ entries: [{ path: "sub/beta.txt" }],
+ mixed: ["selected", { nested: "caller|value" }],
+ },
+ ];
+ const markdown = renderCliDocument(value, "md", false);
+ expect(markdown).toContain('{"path":"alpha.txt"}');
+ expect(markdown).toContain('[{"path":"sub/beta.txt"}]');
+ expect(markdown).toContain("caller\\|value");
+ expect(markdown).not.toContain("[object Object]");
+ expect(
+ renderCliDocument({ mixed: [1, { path: "retained" }] }, "md", false),
+ ).toContain('{"path":"retained"}');
+ expect(JSON.parse(renderCliDocument(value, "json", false))).toEqual(value);
+});
+
+workspaceCliTest(
+ "preserves archive inventory facts through public Markdown and JSON output",
+ async ({ cli }) => {
+ const root = await createTestTempDirectory("rea-markdown-archive-facts-");
+ const archive = join(root, "fixture.zip");
+ await writeOrderedZip(archive, ["alpha.txt", "sub/beta.txt"]);
+ const markdown = await cli.run({
+ arguments: ["inspect-artifact", archive, "--format", "md"],
+ });
+ const json = await cli.run({
+ arguments: ["inspect-artifact", archive, "--json"],
+ });
+ expect(markdown.exitCode).toBe(0);
+ expect(json.exitCode).toBe(0);
+ for (const entry of ["alpha.txt", "sub/beta.txt"]) {
+ expect(markdown.stdout).toContain(entry);
+ expect(json.stdout).toContain(entry);
+ }
+ expect(markdown.stdout).not.toContain("[object Object]");
+ },
+);