dif
Feature flags and A/B tests that live in your repo as Markdown files — one CLI command to install, no signup, no dashboard to rot.
Repository Health
Technical Analysis
dif is a self-hosted feature-flag and A/B-testing tool that treats every flag and experiment as a Markdown file with YAML frontmatter, checked into git next to the code it changes. Instead of a web dashboard disconnected from the codebase, flags get reviewed in pull requests and their history is the git history — when an experiment concludes, the decision and what was learned are written back into the same file.
The core is a Rust CLI (dif) that scaffolds the dif/ directory convention, validates the workspace (schema, owners, surface references, and an exclusion graph that proves two active experiments on the same surface can’t silently collide), and compiles active experiments into a typed TypeScript client plus a context.json. Assignment is a pure function — SHA-256-based deterministic bucketing implemented identically in the Rust CLI and a zero-dependency TypeScript SDK, with a shared test fixture in CI that fails the build if the two implementations drift by even a single bucket.
Companion npm packages (@dif.sh/sdk, @dif.sh/react, @dif.sh/svelte) wire the generated client into application code, and dif.sh Cloud is an optional, opt-in analytics layer — the CLI sends nothing until dif connect is run with a publishable key. dif init also installs Claude Code skills and merges instructions into CLAUDE.md, AGENTS.md, and .cursorrules, so a coding agent can draft an experiment, gate the code path, and validate its own work from a plain-English request.
What You Get
- A Rust CLI (
dif) with eight commands — init, connect, new, validate, build, qa, conclude, scaffold-audiences — covering the full experiment lifecycle - Every flag and experiment stored as a single Markdown file with YAML frontmatter, versioned in git and reviewed like code
- A validator that checks schema, weight totals, and surface/audience references, and proves two active experiments on the same surface can’t collide
- A generated, typed TypeScript client plus
context.json, consumed by zero-dependency@dif.sh/sdk,@dif.sh/react, and@dif.sh/sveltepackages - Deterministic, network-free bucketing (SHA-256 based) implemented identically in the Rust CLI and the TypeScript SDK
- Optional, opt-in dif.sh Cloud analytics — nothing is sent anywhere until you explicitly connect a publishable key
- Built-in Claude Code skills and CLAUDE.md/AGENTS.md/.cursorrules scaffolding so coding agents can author and conclude experiments themselves
Common Use Cases
- Gradually ramping a new feature (e.g. a rebuilt checkout flow) from 0% to 100% while guardrail metrics hold
- Running an A/B test on CTA copy or UI variants with a primary metric and guardrail metrics defined up front
- Preventing two overlapping experiments on the same page/surface from silently interfering via exclusion groups
- Letting an AI coding agent draft a flag, gate the code path, and validate its own work end-to-end
- Auditing why a specific user got a specific variant with
dif qa, including a forced-variant preview link for QA - Recording the outcome and learnings of a concluded experiment so the same failed idea isn’t re-tested years later
Under The Hood
Architecture
The codebase splits cleanly into dif-core (correctness-critical logic: workspace discovery, YAML/Markdown parsing, schema validation, the exclusion-graph solver, deterministic bucketing, and TypeScript codegen) and dif-cli (a thin clap-based command layer in main.rs that only translates flags into dif-core calls and pretty-prints results). Workspace::load in workspace.rs walks the dif/ convention (experiments/{active,concluded}, surfaces/, audiences/) and collects per-file parse errors into a Diagnostic list rather than failing fast, so dif validate can report every problem in one pass; exclusion.rs and validate.rs build the collision-proof graph that is dif’s signature check. On the runtime side, bucket.rs and the TypeScript SDK’s bucket.ts implement the exact same SHA-256-based assignment function independently, wired together only by a shared JSON fixture (bucket_tests.json) that both sides run in CI — a deliberate two-implementation, one-fixture design so a change to one language can’t silently desync assignment from the other.
Tech Stack
The CLI and core are a Cargo workspace (serde/serde_yaml for parsing, sha2 for bucketing, miette for diagnostics, clap for the CLI, walkdir and regex for source scanning), built and released via cargo-dist across five targets (macOS, Linux musl, Windows) with shell/PowerShell/Homebrew installers. The runtime side is an npm workspace of ESM-only TypeScript packages — @dif.sh/sdk (zero runtime dependencies), @dif.sh/react, and @dif.sh/svelte — built with tsc and tested with Node’s built-in test runner via tsx. A scripts/check-versions.mjs CI job cross-checks that the Rust workspace version, the npm packages’ own versions, and their peerDependency ranges on @dif.sh/sdk never drift apart.
Code Quality
Testing is extensive on both sides: dif-core has unit tests embedded in nearly every module (parsing, config editing, exclusion, validation, audiences, codegen) plus a dedicated cross-language fixture test, and the SDK has a *.test.ts file for each of its modules (bucket, core, sha256, track, overrides, cloud, server, ssr). CI runs cargo fmt --check and cargo clippy --workspace --all-targets -- -D warnings on three operating systems, then cargo test --workspace, alongside a matching Node 20/22 matrix for the SDK — so both style and warnings are treated as hard failures, not suggestions. Error handling on the Rust side is explicit and typed via thiserror, with a distinct error path (WorkspaceError::NotFound) that the CLI catches to print an actionable hint rather than a bare stack trace.
What Makes It Unique
dif’s central bet is that feature-flag assignment can be a pure, offline function instead of a network call to an assignment service — bucketing is computed once from a SHA-256 hash of a per-experiment salt and the user id, so a user can never flip variants across page loads, devices, or outages. It pairs that with a validator that treats flag definitions like a type system: it statically proves that concurrently active experiments on the same surface either share an exclusion group or have provably non-overlapping audiences, catching a whole class of “why did this user see two conflicting experiments at once” bugs at PR review time rather than in production. The git-native, agent-native framing (flags as reviewable Markdown, context.json exported specifically for coding agents, and bundled Claude Code skills for authoring and concluding experiments) is a deliberate alternative to the dashboard-based feature-flag SaaS model rather than a reimplementation of it.
Self-Hosting
Licensing Model
MIT licensed — the CLI, Rust core, and all SDK packages (@dif.sh/sdk, @dif.sh/react, @dif.sh/svelte) are fully open source with no restrictions or license keys required for self-hosted use.
Self-Hosting Restrictions None found — assignment, validation, and code generation all run locally with no network calls or license checks gating any feature.
Enterprise Features No enterprise or pro tier exists in the codebase; the only paid/hosted component is dif.sh Cloud, an optional analytics and metrics-tracking add-on.
Cloud vs Self-Hosted
dif.sh Cloud adds hosted analytics on top of the same local flag files — assignment and bucketing logic are identical whether or not Cloud is connected, and the CLI sends no data to Cloud until dif connect is explicitly run with a publishable key.
License Key Required No — a publishable key is only needed to opt into dif.sh Cloud analytics; it is not a license/entitlement key and is safe to commit to source control.
Related Apps
deepseek-harness
AI Agents · AI Development · Developer Tools
An open-source, plugin-based agent harness from DeepSeek AI that runs coding and automation agents across web, desktop, CLI, and SDK surfaces.
Ollama
AI Development · Developer Tools
Run Llama, Gemma, DeepSeek, and other open LLMs on your own machine with one command and an OpenAI-compatible API.
Firecrawl
AI Development · Developer Tools
Turn any website into clean, LLM-ready data with a single API call — no proxy headaches, no scraping complexity.