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 @@ -650,6 +650,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-41-05-0700-item12-file-biome-check-final-after-install.log`.
- **vim — DONE.** Built/staged the real upstream Vim command without app
source forks by keeping terminal/process compatibility in the patched C
sysroot, disabling host Wayland/dlfcn detection at configure time, and
trimming the Vim-local bridge down to package-specific gaps. Added
package-local VM e2e coverage that proves the packaged binary starts with
`-libcall` and edits/writes a file in Ex mode with the packaged runtime.
Proof:
`2026-07-08T14-03-20-0700-item12-vim-toolchain-cmd-build-final-ioctl.log`;
`2026-07-08T14-04-03-0700-item12-vim-package-build-final.log`;
`2026-07-08T14-04-03-0700-item12-vim-check-types-final.log`;
`2026-07-08T14-04-09-0700-item12-vim-package-e2e-final.log`.
Biome is not applicable for this package test path; it reported the file is
ignored by config in
`2026-07-08T14-04-51-0700-item12-vim-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
21 changes: 1 addition & 20 deletions software/vim/native/c/vim-bridge/posix_stubs.c
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
/* posix_stubs.c — stubs for full-OS libc gaps vim references but that the VM
* does not implement (group database, etc.). Safe no-op behavior. */
* does not implement yet. Keep this narrow; the patched sysroot owns libc. */
#include <grp.h>
#include <stddef.h>
struct group *getgrgid(gid_t g) { (void)g; return NULL; }
struct group *getgrnam(const char *n) { (void)n; return NULL; }
struct group *getgrent(void) { return NULL; }
void setgrent(void) {}
void endgrent(void) {}

/* --- additional full-OS libc gaps --- */
#include <sys/types.h>
mode_t umask(mode_t mask) { (void)mask; return 0; }

#include <sys/time.h>
struct itimerval;
int setitimer(int w, const struct itimerval *n, struct itimerval *o) { (void)w; (void)n; (void)o; return 0; }
int getitimer(int w, struct itimerval *o) { (void)w; (void)o; return 0; }

/* --- process/signal stubs (not used by core editing) --- */
#include <errno.h>
pid_t fork(void) { errno = ENOSYS; return -1; }
int execvp(const char *f, char *const a[]) { (void)f; (void)a; errno = ENOSYS; return -1; }
int raise(int s) { (void)s; return -1; }
/* signal sentinel symbols (this libc takes their address for SIG_IGN/SIG_ERR) */
void __SIG_IGN(int s) { (void)s; }
void __SIG_ERR(int s) { (void)s; }
void __SIG_DFL(int s) { (void)s; }
int sigprocmask(int how, const void *set, void *old) { (void)how; (void)set; (void)old; return 0; }
int sigpending(void *set) { (void)set; return 0; }
115 changes: 115 additions & 0 deletions software/vim/test/vim.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { existsSync } from "node:fs";
import { cp, 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 VIM_COMMAND_DIR = fileURLToPath(new URL("../bin", import.meta.url));
const VIM_RUNTIME_DIR = fileURLToPath(
new URL("../dist/package/share/vim/vim92", import.meta.url),
);
const hasVimPackage = existsSync(join(VIM_COMMAND_DIR, "vim")) &&
existsSync(join(VIM_RUNTIME_DIR, "defaults.vim"));

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-vim-"));
await writeFixture("/project/input.txt", "alpha\nbeta\ngamma\n");
await writeFixture(
"/project/edit.vim",
"set nomore\nedit /project/input.txt\n%s/beta/delta/\nwrite\nquitall!\n",
);
await cp(VIM_RUNTIME_DIR, join(tempRoot, "usr/local/share/vim/vim92"), {
recursive: true,
});
return new NodeFileSystem({ root: tempRoot });
}

describeIf(hasVimPackage, "vim command", { timeout: 60_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: [VIM_COMMAND_DIR] }));
}

async function runVim(args: string[]) {
if (!kernel) throw new Error("kernel not mounted");
let stdout = "";
let stderr = "";
const proc = kernel.spawn("vim", args, {
streamStdin: true,
env: {
TERM: "xterm",
VIM: "/usr/local/share/vim",
VIMRUNTIME: "/usr/local/share/vim/vim92",
},
onStdout: (chunk) => {
stdout += Buffer.from(chunk).toString("utf8");
},
onStderr: (chunk) => {
stderr += Buffer.from(chunk).toString("utf8");
},
});
proc.closeStdin();
const exitCode = await proc.wait();
await new Promise<void>((resolve) => setTimeout(resolve, 0));
return { stdout, stderr, exitCode };
}

it("starts the packaged binary and reports Vim features", async () => {
await mountFixture();

const result = await runVim(["--version"]);

expect(result.exitCode, result.stderr || result.stdout).toBe(0);
expect(result.stdout).toContain("VIM - Vi IMproved");
expect(result.stdout).toContain("-libcall");
});

it("edits and writes a file in Ex mode", async () => {
await mountFixture();

const result = await runVim([
"-Nu",
"NONE",
"-n",
"-es",
"-S",
"/project/edit.vim",
]);

expect(result.exitCode, result.stderr || result.stdout).toBe(0);
if (!kernel) throw new Error("kernel not mounted");
const edited = Buffer.from(await kernel.readFile("/project/input.txt")).toString(
"utf8",
);
expect(edited).toBe("alpha\ndelta\ngamma\n");
});
});
24 changes: 14 additions & 10 deletions toolchain/c/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -710,8 +710,8 @@ clean:
# --- vim: real vim 9.2 -> wasm32-wasip1 against the full-OS libc ---
#
# vim compiles as a normal terminal program against the PATCHED sysroot plus a
# small bridge library (vim/): termios via host_tty (raw-mode + winsize),
# a termcap stub (builtin termcaps), and POSIX stubs the full-OS libc lacks.
# small bridge library (vim/): a termcap stub (builtin termcaps), and POSIX
# stubs the full-OS libc still lacks. Termios/process spawning live in sysroot.
# Recipe recovered from the original hand build (see vim/ headers). The result
# lands in $(BUILD_DIR)/vim; `make -C .. cmd/vim` installs it into the shared
# commands dir. NOTE: run as `vim -n <file>` docs-wise is no longer needed —
Expand All @@ -720,7 +720,7 @@ VIM_VERSION ?= v9.2.0780
VIM_SRC := libs/vim
VIM_BRIDGE_SRC := ../../software/vim/native/c/vim-bridge
VIM_BRIDGE_BUILD := $(BUILD_DIR)/vim-bridge
VIM_BRIDGE_OBJS := $(VIM_BRIDGE_BUILD)/termios_bridge.o $(VIM_BRIDGE_BUILD)/termcap_stub.o $(VIM_BRIDGE_BUILD)/posix_stubs.o
VIM_BRIDGE_OBJS := $(VIM_BRIDGE_BUILD)/termcap_stub.o $(VIM_BRIDGE_BUILD)/posix_stubs.o
VIM_WCC := $(VIM_BRIDGE_BUILD)/wcc

.PHONY: fetch-vim
Expand All @@ -733,6 +733,7 @@ $(VIM_BRIDGE_BUILD)/%.o: $(VIM_BRIDGE_SRC)/%.c $(WASI_SDK_DIR)/bin/clang sysroot
$(CC) --target=wasm32-wasip1 --sysroot=$(PATCHED_SYSROOT) -O2 -I $(VIM_BRIDGE_SRC) -c -o $@ $<

$(VIM_BRIDGE_BUILD)/libtermcap.a: $(VIM_BRIDGE_OBJS)
rm -f $@
$(WASI_SDK_DIR)/bin/llvm-ar rcs $@ $(VIM_BRIDGE_OBJS)

# Wrapper compiler: every vim TU gets the sysroot, the bridge headers, and the
Expand All @@ -747,22 +748,25 @@ $(VIM_WCC): $(WASI_SDK_DIR)/bin/clang
@chmod +x $@

$(BUILD_DIR)/vim: fetch-vim $(VIM_BRIDGE_BUILD)/libtermcap.a $(VIM_WCC) wasm-opt-check
rm -f $(VIM_SRC)/src/auto/config.cache
cd $(VIM_SRC)/src && \
CC="$(abspath $(VIM_WCC))" \
LDFLAGS="$(abspath $(VIM_BRIDGE_BUILD)/termios_bridge.o) -L$(abspath $(VIM_BRIDGE_BUILD))" \
CC="$(abspath $(VIM_WCC))" \
LDFLAGS="-L$(abspath $(VIM_BRIDGE_BUILD))" \
vim_cv_toupper_broken=no \
vim_cv_terminfo=no \
vim_cv_tgetent=zero \
vim_cv_getcwd_broken=no \
vim_cv_stat_ignores_slash=no \
vim_cv_memmove_handles_overlap=yes \
vim_cv_bcopy_handles_overlap=yes \
vim_cv_memcpy_handles_overlap=yes \
vim_cv_tty_group=world \
vim_cv_tty_mode=0620 \
./configure --host=wasm32-wasip1 --build=$$(cc -dumpmachine 2>/dev/null || echo x86_64-pc-linux-gnu) \
vim_cv_bcopy_handles_overlap=yes \
vim_cv_memcpy_handles_overlap=yes \
vim_cv_tty_group=world \
vim_cv_tty_mode=0620 \
ac_cv_header_dlfcn_h=no \
./configure --host=wasm32-wasip1 --build=$$(cc -dumpmachine 2>/dev/null || echo x86_64-pc-linux-gnu) \
--with-features=normal --with-tlib=termcap \
--disable-gui --without-x --disable-nls --disable-channel \
--without-wayland \
--disable-netbeans --disable-selinux --disable-gpm --disable-sysmouse \
--disable-icon-cache-update --disable-desktop-database-update
$(MAKE) -C $(VIM_SRC)/src vim
Expand Down
4 changes: 2 additions & 2 deletions toolchain/scripts/patch-wasi-libc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ fi

# Remove libc objects that conflict with host_socket.o.
# Our socket patch replaces these entry points with host_net-backed versions.
"$WASI_AR" d "$SYSROOT_LIB/libc.a" accept-wasip1.o send.o recv.o select.o poll.o getsockopt.o 2>/dev/null || true
echo "Removed conflicting accept-wasip1.o/send.o/recv.o/select.o/poll.o/getsockopt.o from libc.a"
"$WASI_AR" d "$SYSROOT_LIB/libc.a" accept-wasip1.o send.o recv.o select.o poll.o getsockopt.o ioctl.o 2>/dev/null || true
echo "Removed conflicting accept-wasip1.o/send.o/recv.o/select.o/poll.o/getsockopt.o/ioctl.o from libc.a"

# Remove musl's original signal entry points so host_sigaction.o is the only
# resolver for sigaction()/signal() in the patched sysroot.
Expand Down
Loading