This repository contains the implementation of the voyd programming language. voyd is a level between rust and typescript in terms of abstraction. It compiles to webassembly.
apps/cli:voyd/vtcommand line entrypoints.apps/smoke: End-to-end smoke tests (prefer adding public API tests here).apps/site:voyd.devdocs/playground site.apps/vscode: VSCode extension and language client wiring.packages/compiler: Parser, semantics, and Wasm codegen pipeline.packages/language-server: LSP server built on compiler + std.packages/sdk: Public Node/browser/deno APIs for compile/run/test flows.packages/lib: Shared runtime/tooling utilities (CLI helpers, binaryen helpers).packages/vx-dom: Browser/server VX DOM renderer and hydration APIs.packages/js-host: JS host runtime used for executing compiled modules.packages/std: Standard library source bundle (Voyd source).packages/reference: Language reference source/build scripts.docs/architecture: Design constraints and cross-module contracts.
- Monorepo layout: product surfaces live in
apps/*, reusable language/runtime components live inpackages/*. - Compilation flow (authoritative path): parser -> semantics (typing/binding/lowering) ->
ProgramCodegenView-> Wasm codegen. - Boundary rule:
packages/compiler/src/semantics/codegen-viewis the contract; codegen should consume this view, not typing internals. - Runtime split: compiler emits Wasm, while
@voyd-lang/js-host+@voyd-lang/libprovide JS-side execution and interop helpers. - Public integration point:
@voyd-lang/sdkcomposes compiler + host + std and is the preferred API for tests and external tooling. - Developer tooling stack: CLI (
apps/cli), language server (packages/language-server), and VSCode extension (apps/vscode) all build on shared packages.
- Always build with long term maintainability in mind. Avoid short term hacks.
- If you encounter code or an architecture that could benefit from a refactor, report on it and suggest direction in your final response.
- When diagnosing bugs, prefer implementation-level root-cause fixes over call-site/type-annotation workarounds unless a workaround is explicitly requested.
- Always be mindful about clear code boundaries and contracts. Avoid introducing any unnecessary coupling.
- When you encounter a bug or important missing feature that is likely to have a real impact on users and is unrelated to your current task, file a ticket in linear tagged with codex.
- Before contributing to
packages/compilerorpackages/std, readdocs/agent-voyd-quick-reference.md. This is required for compiler/std work because those areas often need precise, idiomatic Voyd syntax in std sources, fixtures, smoke tests, and reference examples.
A cli is available after npm link
Helpful commands:
vt --emit-parser-ast <path-to-voyd-file>vt --run <path-to-voyd-file>// runs the pub fn main of the filevt --emit-wasm-text --opt <path-to-voyd-file>// Careful, this can be large
npm test(runs vitest suite). Always confirm this passes before finishing.npm run typecheck.npx vitest <path-to-test>
You should generally add unit tests when they protect new behavior or a regression. Do not add tests just to mirror coverage that already exists at another layer.
E2E Unit tests should go in apps/smoke (unless strictly scoped to the compiler). Always prefer the public API
Before adding tests, read docs/testing/test-layer-ownership.md.
packages/compiler: parser/typing/lowering/codegen internals and compiler-only contracts.packages/sdk: public compile/run/test APIs and runtime adapter contracts.apps/cli: argument parsing, command wiring, process UX, and exit/reporting behavior.apps/smoke: end-to-end user-visible flows via public APIs (cross-package integration).- Avoid duplicating semantic assertions across CLI/SDK/smoke unless it protects a boundary; if duplicated, document the boundary and keep exactly one canonical layer.
- Prefer batching expensive compile/run setup: one Voyd fixture with several focused entrypoints is usually better than several tiny fixtures that each compile the std/runtime stack.
- Keep compile-heavy smoke tests at the highest useful boundary only. If compiler or std unit tests already own the semantics, smoke should assert a narrow public integration signal.
- Before adding a new
.test.tssmoke file or.test.voydmodule, check whether an existing fixture can host the scenario without mixing unrelated ownership. - Avoid brute-force sweeps in the default suite. Use representative edge cases in normal tests, and put benchmark/perf loops behind an explicit perf script or keep them intentionally small.
- When adding or expanding a slow test, note the boundary it protects and the expected runtime cost in the PR or final handoff.
- Keep functions small
- Prefer early returns to else ifs
- Use
constwhenever possible - Use ternary conditionals for conditional variable init
- Prefer functional control flow (
map,filter, etc) to imperative loop constructs. - Files should be ordered by importance. The main export of a file at the top.
- Use a single parameter object for functions containing more than three params to name the parameters on call.
- Avoid reaching across module boundaries.
Guide for writing voyd code and APIs. Voyd APIs should share a similar spirit to Swift API Design Guidelines
- Snake case for functions, variables, and effect ops. UpperCamelCase for types and effects
- Treat labels as required when a function has more than two non-
selfparameters, unless there is a clear readability reason not to. - For two non-
selfparameters, prefer labels when they improve clarity or prevent ambiguity. - Make use of function / method overloading when it makes semantic sense.
- Prefer positional parameters when a label does not add clarity (for example
push(value: x)should bepush(x)). - Avoid labels that only repeat the function name or obvious role (
contains_key({ key })should prefercontains(key)). - Use labels to distinguish overload families when argument types overlap (
contains(key: ...),contains(value: ...),contains(where: ...)). - Prefer semantic base function names over type-encoded names. Use labels to describe input role/source instead of suffixes like
_byteswhen practical (for exampleascii_string_from(bytes: source)). - Any public/api function that accepts
StringSliceshould also provide aStringoverload with equivalent behavior. Implement one overload as a thin forwarder to avoid duplicated logic. - Use stable effect ids with dotted capability naming:
@effect(id: "<owner>.<package>.<capability>")(for examplevoyd.std.fs). IDs are API contracts: do not change them for refactors.