domino

A high-performance Rust implementation of True Affected — semantic change detection for monorepos using the Oxc parser.

🔍
Semantic Analysis
AST-level change detection, not just file diffs
3–5× Faster
Powered by Rust and the Oxc parser
🔗
Cross-File Tracking
Follows symbol references across your entire workspace
📦
Lockfile Detection
Detects which projects are affected by dependency changes
🏗️
Workspace Support
Nx, Turborepo, npm/yarn/pnpm/bun workspaces
🟢
Node.js API
Native N-API bindings for seamless JS integration

Installation #

domino is distributed as an npm package with native binaries for all major platforms.

npx (no install required)

The easiest way to use domino — no installation needed:

npx @front-ops/domino@latest affected

Global install

# npm
npm install -g @front-ops/domino

# yarn
yarn global add @front-ops/domino

# pnpm
pnpm add -g @front-ops/domino

Binary from source

If you prefer a standalone binary built from source:

git clone https://github.com/frontops-dev/domino.git
cd domino
cargo build --release
# Binary available at ./target/release/domino

Quick Start #

Run domino from the root of any supported monorepo. It compares against origin/main by default.

terminal
# Find all projects affected by your current changes
npx @front-ops/domino affected

# Output (JSON)
["packages/ui", "apps/web", "packages/utils"]

# Show all projects in the workspace
npx @front-ops/domino affected --all

# Generate an interactive HTML report
npx @front-ops/domino affected --report report.html
💡

domino only reports projects that are truly affected — it inspects which symbols were actually modified and follows their references. Renaming an unused export won't mark every importer as affected.


CLI Reference #

  • --base <BRANCH> Base branch to compare against. Default: origin/main
  • --head <COMMIT> Head commit to compare. Enables commit-to-commit diffs instead of diffing against the working tree
  • --all List all projects in the workspace, regardless of changes
  • --json Output results as a JSON array of project names
  • --report <PATH> Write a detailed interactive HTML analysis report to the given path
  • --debug Enable verbose debug logging
  • --cwd <PATH> Set the working directory (default: current directory)
  • --lockfile-strategy <STRATEGY> Lockfile detection strategy: none | direct | full. Default: direct

Examples

# Compare against a different branch
domino affected --base origin/develop

# Compare specific commits (useful in CI patch pipelines)
domino affected --base abc123 --head def456

# Run on a different directory
domino affected --cwd /path/to/monorepo

# JSON output for CI scripts
domino affected --json

# Generate report and open it
domino affected --report report.html

# Open the report (macOS)
open report.html
# Open the report (Linux)
xdg-open report.html
# Open the report (Windows)
start report.html

Lockfile Change Detection #

domino automatically detects when your lockfile changes and identifies which projects are affected by dependency version updates. This works across all major package managers.

npm
package-lock.json
yarn
yarn.lock
pnpm
pnpm-lock.yaml
bun
bun.lock

Strategies

Three strategies control how deeply domino traces dependency changes via --lockfile-strategy:

none
Ignore lockfile changes entirely. Zero overhead.
⚡ Zero overhead
direct default
Mark projects that directly import an affected dependency. Handles transitive lockfile deps.
⚡ Fast — single pass
full
Like direct, but also traces the complete symbol reference chain to find all indirectly affected projects.
🔬 Heavier — full trace
ℹ️

Detection is transitive: if a deeply nested dependency changes, domino walks the reverse dependency graph to identify which direct dependency was updated, then traces which projects import it.

Usage

# Default: direct strategy
domino affected

# Disable lockfile detection
domino affected --lockfile-strategy none

# Full reference chain tracing
domino affected --lockfile-strategy full

Node.js API #

domino ships native N-API bindings so you can call it programmatically from Node.js without spawning a subprocess.

TypeScript
import { findAffected } from '@front-ops/domino'

const affected = await findAffected({
  cwd: '/path/to/monorepo',
  base: 'origin/main',
  head: 'abc123',              // optional — defaults to working tree
  lockfileStrategy: 'direct',  // 'none' | 'direct' | 'full'
})

console.log(affected)
// ['packages/ui', 'apps/web']
💡

The Node.js API loads the correct pre-compiled binary for your platform automatically. No native compilation required at install time.


How It Works #

domino runs a six-step pipeline to compute the true set of affected projects:

1
Git Diff Analysis
Parses the git diff to identify which files changed and, crucially, which line ranges within each file.
2
Semantic Parsing
All TypeScript/JavaScript files in the workspace are parsed using Oxc, building a full AST and semantic model with an import/export graph.
3
Symbol Resolution
Changed line ranges are mapped to specific symbols — functions, classes, constants — that were actually modified.
4
Reference Finding
Recursively follows every import of each changed symbol across the entire workspace using a reverse import index for O(1) lookups.
5
Lockfile Analysis
Parses the current and base lockfile, diffs resolved package versions, walks the reverse dependency graph, and finds projects importing affected packages.
6
Project Mapping
Maps all affected files back to their owning projects using workspace configuration (Nx, Turbo, or package.json workspaces).

Architecture #

domino is a Rust crate that also ships as an npm package via N-API bindings.

Module File Responsibility
Core Algorithm src/core.rs Orchestrates the 6-step detection pipeline
Git Integration src/git.rs Parses diffs, extracts changed files and line ranges
Semantic Analyzer src/semantic/analyzer.rs Parses all files with Oxc, builds import/export index
Reference Finder src/semantic/reference_finder.rs Follows symbol references across files via oxc_resolver
Lockfile Analyzer src/lockfile.rs Parses all lockfile formats, builds reverse dep graph
Workspace src/workspace/ Project discovery for Nx, Turbo, and generic workspaces
CLI src/cli.rs Command-line interface (clap)
N-API Bindings src/lib.rs Node.js integration via napi-rs

Key Technologies

Crate Purpose
oxc_parser Fast JavaScript/TypeScript parsing
oxc_semantic Semantic analysis and symbol tables
oxc_resolver Module resolution (same engine as Rolldown and Nova)
napi-rs Node.js native addon bindings
serde / serde_yaml JSON/YAML lockfile parsing
rustc-hash FxHashMap/FxHashSet for performance-critical lookups

Performance #

domino is significantly faster than the original TypeScript implementation, primarily because Rust and Oxc avoid the overhead of the TypeScript compiler.

Metric TypeScript (traf) Rust (domino)
Parser ts-morph (TypeScript compiler) Oxc parser
Parsing speed Baseline 3–5× faster
Memory usage Baseline ~50% less
Startup time ~1–2 s (Node.js + deps) <100 ms
Binary size Requires Node.js runtime Single binary
Lockfile detection Not available Built-in

Internal optimisations

Several data-structure choices make the hot path as fast as possible:

  • Import index — maps (source_file, symbol_name) → importers for O(1) reverse lookup instead of scanning all files.
  • Resolution cache — caches (from_file, specifier) → resolved path to avoid redundant module resolution.
  • FxHashMap / FxHashSet throughout the lockfile module for performance-critical lookups.
  • Arena allocator — Oxc uses an arena allocator for AST nodes, reducing allocator pressure.

Workspace Support #

Tool Detection Config
Nx Detects nx.json Reads project.json per project
Turborepo Detects turbo.json Reads workspace config from root package.json
npm / yarn / pnpm / bun Reads workspaces in root package.json Glob-based package discovery
⚠️

Nx tsconfig alias mismatch — In Nx monorepos the project name in project.json often differs from the npm package name or tsconfig path alias used in imports (e.g. project ui-widgets vs import @acme/shared-ui-widgets). domino accounts for this by checking both project names and tsconfig path alias keys when deciding whether a specifier refers to a workspace package.

Comparison with traf #

domino is a drop-in replacement for traf, the original TypeScript implementation, with expanded capabilities.

Feature traf (TypeScript) domino (Rust)
Semantic AST analysis
Cross-file reference tracking
Nx support
Turborepo support
Lockfile change detection ✓ (all PMs)
Node.js API ✓ (N-API)
Standalone binary
Performance Baseline 3–5× faster

Development #

Build

# Debug build
cargo build

# Release build (optimised)
cargo build --release

# Node.js bindings
yarn build

Tests

# Unit tests
cargo test --lib

# Integration tests (must be serial — modify git state)
cargo test --test integration_test -- --test-threads=1

# JavaScript tests
yarn test

Lint & Format

cargo fmt --all
cargo clippy --all-targets --all-features
yarn lint
⚠️

Integration tests modify git state and must run serially: always pass --test-threads=1.