12 - Tooling

Good tooling is as important as good syntax — a clean language with a poor workflow is a frustrating one to use day to day. Everything here is driven by the single verge binary.

The Verge CLI

These are the commands you’ll use most:

CommandWhat it does
verge run [path]Compile and run a file or project
verge build [path]Compile to a binary and stop
verge test [path] [filter]Run tests, optionally filtered
verge fmt [path]Format source in place
verge init [dir]Scaffold a new project
verge add <url>@<version>Add a dependency to verge.toml
verge fetchFetch all declared dependencies
verge bind <lib|header.h>Generate Verge bindings from a C header
verge lspStart the language server
verge mcpStart the MCP server

That’s the core loop: create, build, run, test, format, repeat — and generate C bindings when you need to interface with a native library.

Building and running

Reach for verge run during fast iteration and verge build when you just want the artifact. Both accept a single file or, with no argument, the project in the current directory (they read verge.toml and start from main.v):

verge run hello.v      # single file
verge run              # the project in this directory
verge build            # compile the project, leave the binary

Formatting with verge fmt

verge fmt rewrites source into the canonical layout in place, so code style stays consistent without any manual effort or team debate. It’s idempotent — running it twice in a row produces no second round of changes:

verge fmt            # format the whole project
verge fmt hello.v    # or one file

The recommended habit is: write code, run verge fmt, then commit.

Testing

Tests live inline in .v files, right next to the code they judge. A file made up entirely of test blocks is perfectly valid — no main required — and blocks can nest to group related cases:

test "addition works" {
	assert(1 + 1 == 2)
}

test "math" {
	test "subtraction" {
		assert(5 - 3 == 2)
	}
}

Run them with verge test:

ok  addition works
ok  math/subtraction

2 passed, 0 failed

Filter to a group with verge test "math", or a single nested case with verge test "math > subtraction". That’s enough to work a tight red-green-fix loop without slicing files apart by hand.

Editor support with verge lsp

verge lsp starts the language server. Point your editor’s LSP client at it and you get the usual quality-of-life features:

  • diagnostics as you type
  • hover with types and doc comments
  • go-to-definition
  • context-aware completions
  • signature help

The editor handles the JSON-RPC plumbing; the language server handles the analysis, so you can focus on the code itself.

Automation with verge mcp

verge mcp starts an MCP server so tools and AI agents can drive the toolchain in a structured way — compiling, running, type-checking, and querying LSP features over a defined protocol — rather than parsing CLI output. If you’re building agent workflows or custom editor automation around Verge, this is the interface to use.

Projects and dependencies

verge init myproject scaffolds a starter project with a verge.toml and a main.v. Dependencies are git-based for now:

verge add github.com/user/[email protected]   # record a dependency
verge fetch                             # download everything in verge.toml

It’s simple — refreshingly minimal or suspiciously early, depending on your mood.

C bindings with verge bind

Writing FFI glue by hand is tedious and error-prone, so verge bind reads a C library or header and generates the Verge declarations for you. The generated output is a link block — the same form you’d write by hand:

link "curl" extern from "curl/curl.h" {
	curl_easy_init: f() => *u8
	curl_easy_setopt: f(handle: *u8, option: i32, value: *u8) => i32
}

Treat the output as a starting point — review the types and add higher-level wrappers as needed. (See Advanced Features for a runnable link example.)

Build targets

By default Verge builds a native binary for your machine. Additional targets are declared in verge.toml under [[target]], each with a name and platform, so a project can describe more than one build output. If you omit the section entirely, a single native target is assumed.

A sensible daily routine

A normal Verge loop looks like this:

  1. verge init a project if you need one
  2. write code, with verge lsp running in your editor
  3. verge fmt
  4. verge test
  5. verge run or verge build
  6. reach for verge bind or verge mcp only when the problem calls for it

Compact and predictable — just enough tooling to let you focus on the code itself.