Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs-internal/registry-parity-worklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,20 @@ real e2e tests that prove Linux-parity behavior — not smoke tests.
Biome is not applicable for this package test path; it reported the file is
ignored by config in
`2026-07-08T13-29-23-0700-item12-yq-biome-check.log`. Rev: `znlmtymu`.
- **diffutils — DONE.** Added package-local VM e2e coverage for the staged
`diff` command and fixed recursive directory output to print the compared
file pair before hunk output, matching the Linux `diff -r` shape. The suite
proves identical-file exit 0, normal and unified diffs, brief output,
ignore-case/whitespace/blank-line flags, recursive directory comparisons,
and missing-input errors through the packaged WASM command. Proof:
`2026-07-08T13-32-10-0700-item12-diffutils-toolchain-cmd-build.log`;
`2026-07-08T13-34-45-0700-item12-diffutils-package-build-after-recursive-header.log`;
`2026-07-08T13-35-04-0700-item12-diffutils-package-e2e-final.log`;
`2026-07-08T13-35-04-0700-item12-diffutils-check-types-final.log`;
`2026-07-08T13-35-04-0700-item12-diffutils-cargo-fmt-check-final.log`.
Biome is not applicable for this package test path; it reported the file is
ignored by config in
`2026-07-08T13-35-04-0700-item12-diffutils-biome-check.log`.
- **jq — DONE.** Added package-local VM e2e coverage for the staged `jq`
command and fixed the jaq-backed CLI wrapper to accept Linux-style file
operands instead of only stdin. The suite now proves version output,
Expand Down
Binary file modified software/diffutils/bin/diff
Binary file not shown.
8 changes: 8 additions & 0 deletions software/diffutils/native/crates/diff/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ fn diff_files(path_a: &Path, path_b: &Path, opts: &Options) -> Result<bool, Stri
return Ok(true);
}

if opts.recursive {
print_stdout_line(format_args!(
"diff -r {} {}",
path_a.display(),
path_b.display()
))?;
}

// Diff the original text for output (all output logic inline to avoid lifetime issues)
let diff = TextDiff::from_lines(&text_a, &text_b);
let label_a = format!("{}", path_a.display());
Expand Down
145 changes: 145 additions & 0 deletions software/diffutils/test/diff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { existsSync } from "node:fs";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import {
NodeFileSystem,
createKernel,
createWasmVmRuntime,
describeIf,
} from "@agentos/test-harness";
import type { Kernel } from "@agentos/test-harness";
import { afterEach, expect, it } from "vitest";

const DIFF_COMMAND_DIR = fileURLToPath(new URL("../bin", import.meta.url));
const hasDiffPackageBinary = existsSync(join(DIFF_COMMAND_DIR, "diff"));

let tempRoot: string | undefined;

async function writeFixture(path: string, contents: string): Promise<void> {
if (!tempRoot) throw new Error("fixture root not initialized");
const hostPath = join(tempRoot, path.replace(/^\/+/, ""));
await mkdir(dirname(hostPath), { recursive: true });
await writeFile(hostPath, contents);
}

async function createTestVFS(): Promise<NodeFileSystem> {
tempRoot = await mkdtemp(join(tmpdir(), "agentos-diff-"));
await writeFixture("/project/a.txt", "alpha\nbeta\ngamma\n");
await writeFixture("/project/b.txt", "alpha\ndelta\ngamma\n");
await writeFixture("/project/same.txt", "alpha\nbeta\ngamma\n");
await writeFixture("/project/ws-a.txt", "Alpha\n\nbeta value\n");
await writeFixture("/project/ws-b.txt", "alpha\n BETA VALUE\n");
await writeFixture("/project/left/common.txt", "one\ntwo\nthree\n");
await writeFixture("/project/right/common.txt", "one\n2\nthree\n");
await writeFixture("/project/right/extra.txt", "only on right\n");
return new NodeFileSystem({ root: tempRoot });
}

describeIf(hasDiffPackageBinary, "diff command", { timeout: 10_000 }, () => {
let kernel: Kernel | undefined;

afterEach(async () => {
await kernel?.dispose();
kernel = undefined;
if (tempRoot) {
await rm(tempRoot, { recursive: true, force: true });
tempRoot = undefined;
}
});

async function mountFixture(): Promise<void> {
const vfs = await createTestVFS();
kernel = createKernel({ filesystem: vfs });
await kernel.mount(createWasmVmRuntime({ commandDirs: [DIFF_COMMAND_DIR] }));
}

async function runDiff(args: string[]) {
if (!kernel) throw new Error("kernel not mounted");
let stdout = "";
let stderr = "";
const proc = kernel.spawn("diff", args, {
onStdout: (chunk) => {
stdout += Buffer.from(chunk).toString("utf8");
},
onStderr: (chunk) => {
stderr += Buffer.from(chunk).toString("utf8");
},
});
const exitCode = await proc.wait();
await new Promise<void>((resolve) => setTimeout(resolve, 0));
return { stdout, stderr, exitCode };
}

it("returns zero for identical files", async () => {
await mountFixture();

const result = await runDiff(["/project/a.txt", "/project/same.txt"]);
expect(result.exitCode, result.stderr || result.stdout).toBe(0);
expect(result.stdout).toBe("");
});

it("prints normal diffs for changed files", async () => {
await mountFixture();

const result = await runDiff(["/project/a.txt", "/project/b.txt"]);
expect(result.exitCode).toBe(1);
expect(result.stdout).toContain("2c2");
expect(result.stdout).toContain("< beta");
expect(result.stdout).toContain("> delta");
});

it("prints unified diffs", async () => {
await mountFixture();

const result = await runDiff(["-u", "/project/a.txt", "/project/b.txt"]);
expect(result.exitCode).toBe(1);
expect(result.stdout).toContain("--- /project/a.txt");
expect(result.stdout).toContain("+++ /project/b.txt");
expect(result.stdout).toContain("-beta");
expect(result.stdout).toContain("+delta");
});

it("prints brief output with -q", async () => {
await mountFixture();

const result = await runDiff(["-q", "/project/a.txt", "/project/b.txt"]);
expect(result.exitCode).toBe(1);
expect(result.stdout.trim()).toBe(
"Files /project/a.txt and /project/b.txt differ",
);
});

it("honors ignore-case, whitespace, and blank-line flags", async () => {
await mountFixture();

const result = await runDiff([
"-i",
"-w",
"-B",
"/project/ws-a.txt",
"/project/ws-b.txt",
]);
expect(result.exitCode, result.stderr || result.stdout).toBe(0);
expect(result.stdout).toBe("");
});

it("compares directories recursively", async () => {
await mountFixture();

const result = await runDiff(["-r", "/project/left", "/project/right"]);
expect(result.exitCode).toBe(1);
expect(result.stdout).toContain("Only in /project/right: extra.txt");
expect(result.stdout).toContain("/project/left/common.txt");
expect(result.stdout).toContain("/project/right/common.txt");
});

it("returns an error for missing inputs", async () => {
await mountFixture();

const result = await runDiff(["/project/a.txt", "/project/missing.txt"]);
expect(result.exitCode).toBe(2);
expect(result.stderr).toContain("/project/missing.txt");
});
});