mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* refactor(cli): run one-shot commands through one shared owner scripts/rea.mjs and the test CLI session worker each implemented the one-shot command sequence: argument normalization, output admission, configuration errors, version documents, the Amp scope refusal, and result delivery. The worker copy had drifted: it never rendered formatted version reports or the Amp refusal. Move the sequence into src/cliOneShot.ts. Both callers supply a host for their streams, exit and resource ownership. The launcher keeps closed-pipe status, worker shutdown and process exit; the session worker captures output per command. The cliOutput bundle entry is superseded and removed. The dispatcher test now replaces only the command runtime, so output admission runs production code. A new parity test compares the file session with a fresh process. * test(cli): bundle the one-shot entry in the runtime bundle fixture * perf(tests): run plain CLI commands on the file's shared process `cli.run` in `cliTest` files started a fresh compiled CLI for every command, about 0.45s and 260 MB each before any work. The file-scoped session already existed, but callers had to opt in through `runFileCli` or `sharedCliRun`, so 1,300 call sites still spawned. Make the fixture choose: `cli.run` uses the shared process when it reports the command exactly as a fresh process would, and starts one otherwise (stdin, explicit deadlines, MCP and MCP doctor routing, NODE_OPTIONS, or enabled logging). `freshProcess: true` keeps explicit process-level checks. The session worker now presents the launcher's entrypoint, so registration identity matches a fresh process. `runFileCli` and `sharedCliRun` are superseded and removed; their call sites use `cli.run`, and paired fresh/shared checks use `freshProcess`. * style(tests): format the managed member summary call site * test(cli): give each shared-session command an empty stdin The shared CLI process reads session frames from its own stdin. A command that reads stdin without supplied input, such as `compare-wasm-interfaces - --json`, read that pipe and hung, where a fresh process receives EOF. The worker now presents an ended, empty stdin to each command and restores its own afterwards. The parity test compared the routed path with itself after cli.run began choosing the session. It now compares a fresh process with the session directly, including the `mcp add` refusal and a command reading absent stdin. * test(cli): keep default command logging and launcher checks on fresh processes environment-serve and environment-build log at info when REA_LOG_LEVEL is unset, through a logger that writes to stderr directly. The shared CLI process captures only process.stderr writes, so routed runs lost those lines. Declare the default-logging commands beside the command names, have infoLogger read that set, and start a fresh process for them. The log-level precedence checks compared the shared process with itself after routing; their fresh side now passes freshProcess: true.
432 lines
15 KiB
TypeScript
432 lines
15 KiB
TypeScript
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 { toolContract } from "../../../src/contracts/toolContracts.js";
|
|
import { createEvidence } from "../../../src/domain/evidence.js";
|
|
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
|
|
import { modelArtifactSchema } from "../../../src/domain/model/modelArtifact.js";
|
|
import { renderCliDocument } from "../../../src/cliOutput.js";
|
|
import { safetensorsFixture } from "../../fixtures/model/model.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;
|
|
};
|
|
|
|
const tableColumnByHeader = (html: string, header: 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 descendants = (
|
|
node: DefaultTreeAdapterTypes.Node,
|
|
tagName: string,
|
|
): DefaultTreeAdapterTypes.Element[] => {
|
|
const elements: DefaultTreeAdapterTypes.Element[] = [];
|
|
const visit = (candidate: DefaultTreeAdapterTypes.Node): void => {
|
|
if ("tagName" in candidate && candidate.tagName === tagName)
|
|
elements.push(candidate as DefaultTreeAdapterTypes.Element);
|
|
if ("childNodes" in candidate) candidate.childNodes.forEach(visit);
|
|
};
|
|
visit(node);
|
|
return elements;
|
|
};
|
|
const cellText = (row: DefaultTreeAdapterTypes.Element): string[] =>
|
|
row.childNodes
|
|
.filter(
|
|
(node): node is DefaultTreeAdapterTypes.Element =>
|
|
"tagName" in node && (node.tagName === "th" || node.tagName === "td"),
|
|
)
|
|
.map(text);
|
|
|
|
for (const table of descendants(parseFragment(html), "table")) {
|
|
const rows = descendants(table, "tr");
|
|
const headerIndex = rows.findIndex((row) =>
|
|
row.childNodes.some((node) => "tagName" in node && node.tagName === "th"),
|
|
);
|
|
if (headerIndex < 0) continue;
|
|
const headerCells = cellText(rows[headerIndex]!);
|
|
const columnIndex = headerCells.indexOf(header);
|
|
if (columnIndex < 0) continue;
|
|
return rows
|
|
.slice(headerIndex + 1)
|
|
.filter((row) =>
|
|
row.childNodes.some(
|
|
(node) => "tagName" in node && node.tagName === "td",
|
|
),
|
|
)
|
|
.map((row) => cellText(row)[columnIndex] ?? "");
|
|
}
|
|
throw new Error(`No Markdown table has a ${JSON.stringify(header)} column`);
|
|
};
|
|
|
|
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 <T> ©";
|
|
const identifiers = "x__y__z _a_b_ 中_文 snake_case_";
|
|
const edgeSpaceEntries = {
|
|
" leading ": " leading ",
|
|
leading: "leading",
|
|
"trailing ": "trailing ",
|
|
"\tleading tab": "trailing tab\t",
|
|
" mixed\t ": " mixed\t ",
|
|
"\u00a0name": "\u00a0value\u00a0",
|
|
name: "value",
|
|
"\u202fname": "\u202fvalue\u202f",
|
|
"\u2003name": "\u2003value\u2003",
|
|
"\u2028name": "\u2028value\u2028",
|
|
"\u2029name": "\u2029value\u2029",
|
|
"\ufeffname": "\ufeffvalue\ufeff",
|
|
"\u00a0": "\u00a0",
|
|
"\u202f\u2003": "\u202f\u2003",
|
|
" \t\u00a0mixed\u2003\t ": " \t\u00a0mixed\u2003\t ",
|
|
"internal\u00a0name": "internal\u00a0value",
|
|
" name": " value",
|
|
" ": " ",
|
|
" ": " ",
|
|
"": "",
|
|
" 中文🙂 ": " 中文🙂 ",
|
|
"   © <T> a|b ": "   © <T> a|b ",
|
|
"{.hidden}": "literal {.hidden}",
|
|
"{name=value}": "{name=value}",
|
|
};
|
|
const row = {
|
|
[literal]: literal,
|
|
punctuation,
|
|
nested: { text: literal, values: ["__name__", "plain"] },
|
|
empty: "",
|
|
zero: 0,
|
|
disabled: false,
|
|
absent: null,
|
|
source_index: identifiers,
|
|
...edgeSpaceEntries,
|
|
};
|
|
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,
|
|
...Object.values(edgeSpaceEntries),
|
|
];
|
|
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);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"renders long internal Unicode whitespace within the CLI deadline",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-markdown-long-whitespace-");
|
|
const path = join(root, "bundle.json");
|
|
const literal = `left${"\u00a0\u202f\u2003".repeat(80_000)}right`;
|
|
const record = createEvidence(
|
|
undefined,
|
|
{ id: "fixture", name: "Fixture", version: "1" },
|
|
{
|
|
operation: "ordinary-json",
|
|
parameters: {},
|
|
result: { rows: [{ [literal]: literal }] },
|
|
},
|
|
);
|
|
const saved = JSON.stringify(createEvidenceBundle([record]));
|
|
await writeFile(path, saved);
|
|
// The parent enforces this deadline even if the CLI is stuck in a regexp.
|
|
const markdown = await cli.run({
|
|
arguments: [
|
|
"inspect-evidence-bundle",
|
|
JSON.stringify({ path, detail: "complete" }),
|
|
"--filter-output",
|
|
"records.0.normalized_result",
|
|
"--format",
|
|
"md",
|
|
"--full-output",
|
|
],
|
|
});
|
|
expect(markdown.exitCode, markdown.stderr).toBe(0);
|
|
const renderer = await createMarkdownRenderer(root, { linkify: false });
|
|
// Full output also includes metadata tables; check the Evidence column.
|
|
const column = tableColumnByHeader(
|
|
renderer.render(markdown.stdout),
|
|
literal,
|
|
);
|
|
expect(column).toHaveLength(1);
|
|
expect(column[0] === literal).toBe(true);
|
|
expect(await readFile(path, "utf8")).toBe(saved);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"preserves capability provider identities in rendered Markdown",
|
|
async ({ cli }) => {
|
|
const json = await cli.run({ arguments: ["capabilities", "--json"] });
|
|
const markdown = await cli.run({
|
|
arguments: ["capabilities", "--format", "md"],
|
|
});
|
|
expect(json.exitCode, json.stderr).toBe(0);
|
|
expect(markdown.exitCode, markdown.stderr).toBe(0);
|
|
const data = JSON.parse(json.stdout) as {
|
|
capabilities: { provider: unknown }[];
|
|
};
|
|
const renderer = await createMarkdownRenderer(process.cwd(), {
|
|
linkify: false,
|
|
});
|
|
expect(
|
|
tableColumnByHeader(renderer.render(markdown.stdout), "provider"),
|
|
).toEqual(
|
|
data.capabilities.map(({ provider }) => JSON.stringify(provider)),
|
|
);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"keeps scalar markers, edge spaces, and emoji-like names literal in rendered reports",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-markdown-literal-markers-");
|
|
const path = join(root, "bundle.json");
|
|
const result = {
|
|
container: "::: details",
|
|
trailing: "archive ",
|
|
trailingTab: "archive\t",
|
|
trailingMixed: "archive \t ",
|
|
emoji: ":)",
|
|
shortcuts: ":smile: :D :-( ;) ;-() x-) X-) 8-) B-) <3 &",
|
|
rows: [{ value: ":)", nested: { path: ";) :smile: 8-)" } }],
|
|
after: "still visible",
|
|
};
|
|
const record = createEvidence(
|
|
undefined,
|
|
{ id: "fixture", name: "Fixture", version: "1" },
|
|
{ operation: "ordinary-json", parameters: {}, result },
|
|
);
|
|
await writeFile(path, JSON.stringify(createEvidenceBundle([record])));
|
|
const base = [
|
|
"inspect-evidence-bundle",
|
|
JSON.stringify({ path, detail: "complete" }),
|
|
"--filter-output",
|
|
"records.0.normalized_result",
|
|
];
|
|
const markdown = await cli.run({
|
|
arguments: [...base, "--format", "md"],
|
|
});
|
|
const json = await cli.run({ arguments: [...base, "--json"] });
|
|
expect(markdown.exitCode, markdown.stderr).toBe(0);
|
|
expect(json.exitCode, json.stderr).toBe(0);
|
|
expect(JSON.parse(json.stdout)).toEqual({
|
|
records: { "0": { normalized_result: result } },
|
|
});
|
|
const renderer = await createMarkdownRenderer(root, { linkify: false });
|
|
const html = renderer.render(markdown.stdout);
|
|
const text = (node: DefaultTreeAdapterTypes.Node): string =>
|
|
node.nodeName === "#text" && "value" in node
|
|
? node.value
|
|
: "childNodes" in node
|
|
? node.childNodes.map(text).join("")
|
|
: "";
|
|
const children = parseFragment(html).childNodes;
|
|
for (const [key, value] of Object.entries(result).filter(
|
|
([, value]) => typeof value === "string",
|
|
)) {
|
|
const index = children.findIndex(
|
|
(node) =>
|
|
"tagName" in node &&
|
|
node.tagName === "h2" &&
|
|
text(node).replaceAll("\u200b", "").trim().endsWith(`.${key}`),
|
|
);
|
|
expect(index, key).toBeGreaterThanOrEqual(0);
|
|
const body = children.slice(index + 1).find((node) => "tagName" in node);
|
|
expect(body && "tagName" in body ? body.tagName : undefined, key).toBe(
|
|
"p",
|
|
);
|
|
expect(body ? text(body) : undefined, key).toBe(value);
|
|
}
|
|
expect(html).not.toMatch(/<(?:details|ol|li)(?:\s|>)/u);
|
|
expect(renderedCells(html)).toContain(
|
|
JSON.stringify(result.rows[0]?.nested),
|
|
);
|
|
expect(renderedCells(html)).toContain(":)");
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"preserves safetensors identifier edge spaces in rendered CLI tables",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-model-edge-spaces-");
|
|
const path = join(root, "model.safetensors");
|
|
const names = [" leading ", "leading", "trailing ", "plain"];
|
|
const header = JSON.stringify(
|
|
Object.fromEntries(
|
|
names.map((name, index) => [
|
|
name,
|
|
{
|
|
dtype: "F32",
|
|
shape: [1],
|
|
data_offsets: [index * 4, index * 4 + 4],
|
|
},
|
|
]),
|
|
),
|
|
);
|
|
await writeFile(
|
|
path,
|
|
safetensorsFixture(header, Buffer.alloc(names.length * 4)).bytes,
|
|
);
|
|
const base = ["inspect-model-artifact", path, "--detail", "complete"];
|
|
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);
|
|
|
|
const response = toolContract("inspect_model_artifact").outputSchema.parse(
|
|
json.json,
|
|
);
|
|
const report = modelArtifactSchema.parse(response.normalized_result);
|
|
const expectedNames = report.tensors.map(({ name }) => name);
|
|
expect(expectedNames).toEqual([
|
|
" leading ",
|
|
"leading",
|
|
"plain",
|
|
"trailing ",
|
|
]);
|
|
|
|
const renderer = await createMarkdownRenderer(root, { linkify: false });
|
|
expect(
|
|
tableColumnByHeader(renderer.render(markdown.stdout), "name"),
|
|
).toEqual(expectedNames);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"preserves literal scalar, array, and heading Evidence facts",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-markdown-literal-text-");
|
|
const path = join(root, "bundle.json");
|
|
const literal = "**literal** [source](wrong) <T> ©";
|
|
const heading = "**key**\n# injected";
|
|
const result = {
|
|
[heading]: literal,
|
|
list: [literal],
|
|
bullet: "- literal",
|
|
numbered: "1. literal",
|
|
numberedBare: "1.",
|
|
parenBare: "1)",
|
|
rule: "---",
|
|
indented: " literal\n- next\n# heading",
|
|
nested: { value: 1 },
|
|
};
|
|
const record = createEvidence(
|
|
undefined,
|
|
{ id: "fixture", name: "Fixture", version: "1" },
|
|
{ operation: "ordinary-json", parameters: {}, result },
|
|
);
|
|
await writeFile(path, JSON.stringify(createEvidenceBundle([record])));
|
|
const base = [
|
|
"inspect-evidence-bundle",
|
|
JSON.stringify({ path, detail: "complete" }),
|
|
"--filter-output",
|
|
"records.0.normalized_result",
|
|
];
|
|
const markdown = await cli.run({
|
|
arguments: [...base, "--format", "md"],
|
|
});
|
|
const json = await cli.run({ arguments: [...base, "--json"] });
|
|
expect(markdown.exitCode, markdown.stderr).toBe(0);
|
|
expect(json.exitCode, json.stderr).toBe(0);
|
|
expect(JSON.parse(json.stdout)).toEqual({
|
|
records: { "0": { normalized_result: result } },
|
|
});
|
|
const renderer = await createMarkdownRenderer(root, { linkify: false });
|
|
for (const document of [
|
|
markdown.stdout,
|
|
renderCliDocument(result, "md", false),
|
|
]) {
|
|
const html = renderer.render(document);
|
|
const text = (node: DefaultTreeAdapterTypes.Node): string =>
|
|
node.nodeName === "#text" && "value" in node
|
|
? node.value
|
|
: "childNodes" in node
|
|
? node.childNodes.map(text).join("")
|
|
: "";
|
|
expect(text(parseFragment(html))).toContain(literal);
|
|
expect(html).toContain("**key**<br># injected");
|
|
expect(html).toContain("literal<br>- next<br># heading");
|
|
expect(html).toContain("<p>1.</p>");
|
|
expect(html).toContain("<p>1)</p>");
|
|
expect(html).not.toMatch(/<(?:strong|em|ul|ol|li|T|pre|hr)(?:\s|>)/u);
|
|
expect(html).not.toContain('href="wrong"');
|
|
expect(html).not.toContain("<h1");
|
|
}
|
|
},
|
|
);
|