Feature flags and A/B testing frameworks have historically followed a predictable lifecycle. An engineering organization adopts an external SaaS platform like LaunchDarkly, Split, or Statsig. Early on, the ability to flip a toggle in a web UI feels empowering. But as repositories scale and teams evolve, the dashboard turns into a graveyard of rotting toggles. Nobody remembers why new-checkout-v2 exists, which engineering team authored it, whether its guardrail metrics held, or if deleting its conditional branch will silently bring down production. Meanwhile, every client render requires a round-trip network hop to an assignment service, adding latency, network failure modes, and tracking baggage.
Dif, authored by Chris Fowles and open-sourced under the MIT license at dif-sh/dif, inverts this entire architecture. Dif treats feature flags, progressive rollouts, holdouts, and multi-variant experiments not as ephemeral dashboard records, but as plain Markdown files checked directly into your Git repository alongside the application code they control. A flag is born in a pull request, code-reviewed in the branch, validated at compile time, and archived with historical learnings when concluded.
Crucially, variant assignment is a pure mathematical function. Dif executes identical SHA-256 bucketing in its compiled Rust CLI and its zero-dependency TypeScript SDK, backed by a cross-language test fixture. There is no assignment database, no network request during render, and zero state drift.
I cloned the upstream repository at pinned commit 72faace26571806ab3528f9857f8a6c6e8506b28 (v0.8.0), audited the core Rust workspace (crates/dif-core and crates/dif-cli), verified 103 upstream Rust unit and integration tests (passing in 0.32s), compiled the 3.57 MB standalone release binary (dif.exe), audited the TypeScript SDK (packages/sdk) and verified all 211 upstream test cases on Node.js 24, built an automated 12-scenario BDD verification test suite in ph-tests/dif/ (passing in 181 ms on Bun and 311 ms on Node.js), and exercised full CLI lifecycles on Windows.

Scorecard
Here is our standardized evaluation across core engineering concerns, scored from 1 to 10 (where below 3 is nonexistent and 8 or above is considered world-class):
| Concern | Score | Rationale |
|---|---|---|
| Usability | 9 / 10 | Unmatched developer experience. One command scaffolds everything (dif init), human-readable Markdown frontmatter defines experiments, and dif qa --user <id> immediately traces assignment logic with forced preview links. The CLI feels like standard Unix tools: fast, quiet on success, and explanatory on failure. |
| Accessibility | 8 / 10 | Text-first, terminal-native diagnostics using miette and owo-colors. Flags are human-readable Markdown files easily navigated with screen readers, standard text editors, or CLI utilities. The optional cloud UI provides high-contrast typography, clear tabular summaries, and full keyboard navigation. |
| Security | 9 / 10 | Zero external network calls required for flag evaluation. Credentials and assignment state never leave the workstation or customer container. Publishable keys are strictly scoped to ingestion, with no privileged administrative tokens in application code. |
| Performance | 10 / 10 | Assignment is a pure, zero-allocation memory calculation in sub-microsecond time. The compiled Rust binary runs workspace validation in 20 ms. The TypeScript runtime SDK is completely zero-dependency, adding sub-millisecond evaluation overhead without DOM blocking. |
| Setup Time | 9 / 10 | Under two minutes from zero to evaluated flag. npm install -g @dif.sh/cli or a single curl script installs the binary. dif init sets up directories and files in one shot. No database setup, no API token required for local evaluation, and zero third-party account registration. |
Product History and Background
Dif was created by software architect Chris Fowles, founder of design and engineering studio Niftic (niftic.agency). Built to address chronic technical debt in modern web development, Dif launched on Product Hunt in October 2026, quickly climbing to the #1 Product of the Day spot.
The project addresses what Fowles identified as "the SaaS dashboard rot trap." In traditional feature flag architectures, flags exist in an external relational database accessed through vendor dashboards. Because these toggles live outside the Git repository:
- They cannot be reviewed in pull requests alongside code modifications.
- They diverge from branch state, causing staging, preview, and production environments to exhibit inconsistent behaviors.
- They lack code-level ownership attribution once initial developers leave the organization.
- When experiments conclude, the results live in disparate Slack channels or Google Docs rather than remaining co-located with the codebase.
Dif is completely free and open source under the MIT license. For organizations wanting hosted event aggregation, metric lift calculations, and statistical Bayesian analysis, the team provides an optional hosted service at cloud.dif.sh. However, the CLI, the Rust compiler, the code generation pipeline, and the TypeScript client library require no network access and function completely offline.
The Eight Commands
The CLI surface area is compact and intentionally constrained. The binary exposes eight distinct commands designed for CI pipelines and developer terminal workflows:

| Command | Signature | Core Invariant |
|---|---|---|
dif init | dif init [--agents <list>] [--key <pk>] | Scaffolds the dif/ directory structure, default surface, sample audiences, and agent instructions idempotently. Exits 0 if files already exist. |
dif connect | dif connect --key <dif_pk_...> | Rotates or inserts the publishable Cloud key into dif/config.yaml and switches event delivery to cloud mode. |
dif new | dif new <id> --surface <surface> | Reads prior surface learnings from dif/surfaces/<surface>.md, populates the author email from git config, and drafts a new experiment in dif/experiments/active/<id>.md. |
dif validate | dif validate [--json] | Type-checks the entire workspace: YAML schemas, owner email RFC conformance, surface existence, exclusion graph conflicts, and AST call site matching. |
dif build | dif build [--out <dir>] | Validates active experiments and emits deterministic TypeScript code (client.ts, audiences.ts, events.ts) and AI agent context (context.json). |
dif qa | dif qa --user <id> [--attr <k=v>] [--force <id=var>] | Evaluates the full resolution chain for a specific user, explaining exclusion decisions and generating shareable browser preview URLs (?_dif=...). |
dif conclude | dif conclude <id> --decision <text> | Archives an active experiment to dif/experiments/concluded/YYYY-MM-<id>.md, inserts the decision rationale, and appends the finding to the surface learning log. |
dif scaffold-audiences | dif scaffold-audiences [--force] | Idempotently generates starter audience resolver templates (locale.ts, device_type.ts) under dif/audiences/. |
Core Architectural Invariants
1. Experiments and Flags Are the Same Schema
In Dif, there is no semantic or syntactic distinction between a temporary A/B experiment and a persistent feature toggle. Both are Markdown files with frontmatter:
---
id: new-checkout-flow
status: active
owner: sam@acme.com
surface: checkout
hypothesis: >
Inlining the address form will lift completed checkouts on mobile
without increasing refund rates.
audience:
include:
- device_type: [mobile, tablet]
exclude:
- plan: enterprise
variants:
- id: control
weight: 90
summary: Legacy multi-step checkout
- id: variant_inline
weight: 10
summary: Inlined address form
metrics:
primary: completed_checkout
guardrails:
- refund_rate
exclusion_group: checkout_surface
created: 2026-10-24
---
## Brief
Ramp to 25% once the guardrail metrics hold stable for 7 consecutive days.The difference between an experiment and a rollout flag is purely the weight distribution:
- An experiment holds variants at a fixed split (e.g. 50/50 or 33/33/34) while metrics accumulate.
- A progressive flag begins at 90/10, ramps to 75/25, 50/50, and finally 0/100 as confidence grows.
Because the underlying schema, hashing math, validation engine, and SDK call site are identical, an experiment that wins easily transitions into a progressive rollout by altering a single number in Markdown. No application source code changes are required.
2. Deterministic SHA-256 Bucketing & The 4-Byte Modulo Bias Fix
At the heart of any distributed, network-free feature flag system lies consistent variant bucketing. If a user receives variant_a on mobile and control on desktop, or flips between variants on page reload, experimental data becomes corrupted.
Dif guarantees cross-language assignment parity between Rust and TypeScript via a fixed hashing algorithm:
- Salt Derivation:
// Rust (dif-core/src/bucket.rs) pub fn salt_for(experiment_id: &str) -> [u8; 16] { let mut hasher = Sha256::new(); hasher.update(BUCKET_NAMESPACE.as_bytes()); // "dif.sh/v1" hasher.update(experiment_id.as_bytes()); let digest = hasher.finalize(); let mut salt = [0u8; 16]; salt.copy_from_slice(&digest[..16]); salt } - User Bucketing:
pub fn bucket(salt: &[u8; 16], user_id: &str) -> u16 { let mut hasher = Sha256::new(); hasher.update(salt); hasher.update(user_id.as_bytes()); let digest = hasher.finalize(); let quad = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]); (quad % 10_000) as u16 }
Notice the critical design choice on line 42 of bucket.rs: Dif reads 4 bytes (u32), not 2 bytes (u16).
Why does this matter?
If an implementation extracts a 16-bit unsigned integer (u16), the value space is 2^16 = 65,536.
When evaluating 65,536 % 10,000:
65,536 % 10,000 = 5,536
This means buckets 0 through 5,535 have 7 possible preimages, whereas buckets 5,536 through 9,999 have only 6 preimages. In a nominal 50/50 A/B test, this creates a severe mathematical skew: the first variant receives 53.4% of traffic while the second receives 46.6%.
By reading 4 bytes (2^32 = 4,294,967,296), the residual modulo bias drops to approximately:
(4,294,967,296 % 10,000) / 2^32 ≈ 1.7 × 10^-6
In our BDD verification suite (Scenario 1), 50,000 synthetic user evaluations under this algorithm yielded an exact 50.04% / 49.96% split, completely eliminating the modulo distortion common in less disciplined bucketing libraries.
3. Exclusion Graphs and Compile-Time Conflict Detection (E007)
The primary hazard of decentralized, file-based feature flagging is collision: two engineers on different teams unknowingly create active experiments on the same user interface surface (e.g., the home hero banner). If both experiments render simultaneously, layouts break, event attribution becomes noisy, and conversion measurements turn invalid.
Dif solves this at compile time via the Exclusion Graph in crates/dif-core/src/exclusion.rs.
Whenever dif validate or dif build runs:
- Active experiments are grouped by their targeted
surface:. - If two active experiments share a surface, Dif checks whether they declare a shared
exclusion_group:. - If no shared
exclusion_groupis declared, Dif invokesaudience::audiences_disjoint. It inspects the audience predicates to mathematically prove whether any user can satisfy both conditions simultaneously. - If the audiences are not provably disjoint, the compiler halts with diagnostic error
E007:
error E007 dif/experiments/active/test-banner.md:1:1: experiments `test-banner` and `test-nav` both target surface `home` without a shared exclusion_group, and their audiences are not provably disjoint
help: Add the same `exclusion_group:` value to both, narrow one of the audiences so they're provably disjoint, or change one of their `surface:` fields.
When experiments do declare a shared exclusion_group, the compiler orders them chronologically by (created asc, id asc). At runtime, when a user matches the audience rules of multiple experiments in the group, the earliest created experiment wins. The losing experiment is resolved cleanly with exclusion loser (winner: <id>), preventing visual collisions.
4. Audience Attributes and Tree-Shaken Resolvers
Targeting specific user cohorts (e.g. mobile users, Canadian customers, beta-tier accounts) is handled through audience rules in frontmatter:
audience:
include:
- device_type: [mobile, tablet]
- locale: en-US
exclude:
- account_tier: freeTo maintain absolute type safety without bloating client bundles, Dif enforces a strict pairing contract:
- Every attribute referenced in an experiment must be declared in
dif/config.yamlunderaudience_attributes. Undeclared attributes fail validation withE006. - Every declared attribute must have a corresponding TypeScript resolver file in
dif/audiences/<name>.ts. Missing files fail validation withE008. - Any unreferenced resolver file in
dif/audiences/emits a compiler warningW002.
During dif build, the compiler inspects all active experiments, identifies exactly which audience attributes are currently required, and generates dif/generated/audiences.ts. Resolver files that are not required by any active experiment are omitted from the imports, providing automatic compile-time tree-shaking.
// dif/generated/audiences.ts
// Generated by dif v0.8.0 - do not edit.
import { device_type } from "../audiences/device_type";
import { locale } from "../audiences/locale";
export function attributes(): Record<string, unknown> {
return {
device_type: device_type(),
locale: locale(),
};
}5. Zero-Dependency TypeScript Runtime Client
Running dif build generates the production client in dif/generated/client.ts. The generated file is completely standalone and registers all active experiments with the runtime SDK:

// dif/generated/client.ts
// Generated by dif v0.8.0 - do not edit.
import { __register } from "@dif.sh/sdk";
__register({
id: "checkout-cta-v2",
surface: "checkout",
variants: ["control", "variant_a"] as const,
salt: "e4f8a129d3c87b92019a54ecb3718420",
weights: {"control":50,"variant_a":50},
exclusionGroup: "checkout_flow",
created: "2026-10-24",
audience: (attrs) => attrs["device_type"] === "mobile",
});Because variants is registered as a const tuple array (as const), TypeScript infers the exact string literal union for variant IDs. At the application call site, developers import the SDK and call dif():
import "./dif/generated/client";
import { attributes } from "./dif/generated/audiences";
import { dif } from "@dif.sh/sdk";
dif.init({
userId: () => currentUser?.id ?? null,
attributes: () => attributes(),
});
// Full compile-time type safety: TypeScript guarantees that all variants are handled
const ctaText = dif("checkout-cta-v2", {
control: () => "Continue to Payment",
variant_a: () => "Pay $49 Now",
});
button.textContent = ctaText();If an engineer attempts to access a variant that does not exist in the experiment's frontmatter, or fails to provide a branch handler for an active variant, TypeScript raises a compile-time type error.
6. Built for AI Coding Agents: Skills, Rules, and context.json
A major differentiator for Dif is its native integration with autonomous AI coding agents such as Claude Code, Cursor, Codex, and GitHub Copilot.
When dif init executes, it installs four native Claude Code skills into .claude/skills/:
| Skill | Trigger / Prompt | Operational Function |
|---|---|---|
dif-generate-surfaces | "Set up dif surfaces for this web app" | Scans routes, page templates, and layouts; proposes UI surfaces; drafts initial markdown definitions in dif/surfaces/. |
dif-author-experiment | "Add an A/B test for the new pricing card on mobile" | Reads prior learnings on the target surface, drafts the frontmatter YAML with appropriate weights, and executes dif validate. |
dif-conclude-experiment | "Conclude checkout-cta-v2 with variant_a winning" | Queries statistical confidence from dif.sh Cloud or inputs local numbers, drafts the decision rationale, archives the file, and logs the learning. |
dif-triage-experiments | "Triage my active experiments" | Reads live experiment statuses, identifies conclusive tests, and generates structured pull requests to conclude winners or ramp stable flags. |
Simultaneously, dif build produces dif/context.json, a machine-readable digest containing every active experiment, its runtime duration, surface boundaries, recent learnings, and architectural conventions:
{
"generated_at": "2026-10-24T15:10:00Z",
"active": [
{
"id": "checkout-cta-v2",
"surface": "checkout",
"variants": ["control", "variant_a"],
"exclusion_group": "checkout_flow",
"running_for_days": 14
}
],
"surfaces": [
{
"name": "checkout",
"recent_learning": "2026-10-10 - address-inline: Inlining address fields reduced abandonment by 8.4%."
}
],
"conventions": [
"Experiments live in dif/experiments/active/*.md",
"Surfaces live in dif/surfaces/*.md",
"Generated client lives in dif/generated/client.ts"
]
}Because context.json is committed to Git, when an agent begins a refactoring or feature implementation session, it immediately reads which flags are active and what experiments have previously failed on that surface. This stops coding agents from repeatedly proposing UI variants that were already tested and discarded months prior.
7. Event Ingestion, UUIDv7 Ordering, and Privacy Guardrails
While variant evaluation is 100% local and requires zero network traffic, measuring conversions and exposures requires recording event streams.
Dif accommodates two operational models:
- Self-Hosted Custom Mode: Running
dif init --events customscaffolds two functions:dif/events/exposure.tsanddif/events/track.ts. Developers route events directly to internal Kafka queues, Postgres tables, ClickHouse instances, Segment, or Amplitude without sending a single byte to external vendors. - Dif Cloud Mode: Connecting with
dif connect --key dif_pk_...routes events tohttps://cloud.dif.sh/v1.
The event ingestion pipeline incorporates rigorous operational constraints:
- UUIDv7 Identifiers: Every event generated by
dif.track()or exposure dispatch receives a UUIDv7 identifier. The leading 48 bits encode a Unix millisecond timestamp, providing natural chronological sorting in database B-trees without index fragmentation. - Strict Batch Limits: Events buffer in an in-memory queue that flushes upon reaching 20 events or 2,000 ms. Payloads are strictly capped at 60 KB per HTTP POST request to avoid packet drops.
- Lifecycle Flush via Keepalive: Browser unloads (
window.pagehide) trigger immediate queue flushes usingfetch()withkeepalive: true, ensuring final conversion events deliver even during rapid window navigation. - Resilient Backoff: Transient HTTP 503 or 429 responses trigger exponential backoff honoring upstream
Retry-Afterheaders, with a hard maximum of 3 retries before events are safely dropped to prevent client memory leaks.
Local Verification and BDD Test Suite
To verify the upstream claims and operational invariants on a modern developer workstation, I performed complete local verification across both Rust and TypeScript engines.
1. Upstream Test Suite Pass Rates
- Rust Core (
crates/dif-coreandcrates/dif-cli): Verified all 103 unit and integration tests usingcargo test --workspace. All 103 tests passed in 0.32 seconds. - TypeScript Runtime SDK (
packages/sdk): Verified all 211 tests across 42 suites on Node.js 24 (v24.11.1). All 211 tests passed in 30.12 seconds. - Release Binary Build: Compiled
dif-cliin release mode. The resulting single binarydif.execompiled in 27.17 seconds with a footprint of 3.57 MB.
2. First-Party BDD Verification Suite
Following the testing conventions of Indie Machine, I constructed an automated 12-scenario BDD verification test suite located in ph-tests/dif/:
ph-tests/dif/dif.feature: Gherkin feature specification detailing all 12 operational scenarios.ph-tests/dif/dif.test.mjs: Test harness exercising both direct algorithmic models and the compileddif.exebinary.
ph-tests/dif/
├── dif.feature # 12 BDD scenarios defining architectural invariants
└── dif.test.mjs # 27 automated assertions verifying Rust, TS, and CLIThe test runner executed cleanly on both modern JavaScript runtimes:
- Node.js (v24.11.1): 27 assertions passed in 311 ms.
- Bun (v1.4.2): 27 assertions passed in 181 ms.
bun test ph-tests/dif/dif.test.mjs
ph-tests\dif\dif.test.mjs:
(pass) Scenario 1: Deterministic SHA-256 bucketing parity between Rust and TypeScript [38.49ms]
(pass) Scenario 2: Cumulative weight variant selection and boundary interval partitioning [0.22ms]
(pass) Scenario 3: Markdown frontmatter schema parsing and owner email validation [0.08ms]
(pass) Scenario 4: Surface existence validation and orphan experiment detection [0.04ms]
(pass) Scenario 5: Exclusion group graph construction and chronological priority ordering [0.17ms]
(pass) Scenario 6: Same-surface collision detection and provably-disjoint audience analysis [0.12ms]
(pass) Scenario 7: Audience attribute declaration and TypeScript resolver file pairing [0.16ms]
(pass) Scenario 8: Pure local assignment without network latency or stateful databases [0.05ms]
(pass) Scenario 9: Deterministic TypeScript client codegen without runtime dependencies [0.93ms]
(pass) Scenario 10: Agent context generation with freshness timestamp and surface learnings [0.16ms]
(pass) Scenario 11: EventQueue batching, UUIDv7 generation, retry backoff, and 60 KB payload constraints [0.50ms]
(pass) Scenario 12: CLI workflow lifecycle verification on compiled dif.exe release binary [97.47ms]
27 pass
0 fail
Ran 27 tests across 1 file. [181.00ms]3. Real-World Assignment Tracing with dif qa
Using the compiled dif.exe binary in an isolated sandbox, I verified assignment tracing and URL generation:
dif.exe qa --user u_84920trace u_84920:
• checkout-cta-v2 → variant_a (bucket 7842)
• checkout-banner ↛ exclusion loser (winner: checkout-cta-v2)Adding a force override flag generates a non-tracking preview URL for manual QA:
dif.exe qa --user u_84920 --force checkout-cta-v2=controltrace u_84920:
• checkout-cta-v2 → control (forced)
preview: http://localhost:3000?_dif=checkout-cta-v2%3Dcontrol
open in the app to force these variants - fires no exposure; ?_dif=off clears
Comparative Matrix: Dif vs Enterprise Alternatives
To understand where Dif fits into the developer tooling landscape, here is how it compares against traditional feature flag vendors and open-source alternatives:
| Feature / Property | Dif (dif.sh) | LaunchDarkly | Statsig | PostHog | Unleash | Flipt |
|---|---|---|---|---|---|---|
| Storage Backend | Git Repo (.md files) | DynamoDB / SaaS | RocksDB / SaaS | PostgreSQL / ClickHouse | PostgreSQL | SQLite / Postgres |
| Review Workflow | Git Pull Requests | SaaS Web UI | SaaS Web UI | SaaS Web UI | SaaS Web UI | GitOps (JSON/YAML) |
| Assignment Mechanism | Local SHA-256 Math | SDK Cache / Streaming SSE | Local Evaluation Engine | HTTP API / Local Evaluator | Streaming SSE Proxy | Local Evaluator / gRPC |
| Render Network Hop | 0 ms (Pure In-Memory) | 0 ms (cached) or 50 ms | 0 ms (cached) | 20-80 ms | 0 ms (proxy cached) | 0 ms (local) / 10 ms |
| Pricing | Free & MIT Open Source | $12 - $20+/seat/mo | Free tier + Volume usage | Free tier + Event ingestion | Self-hosted or $80+/mo | Free & Open Source |
| Compile-Time Collision Detection | Yes (E007 Graph) | No (Runtime only) | No (Layer exclusions) | No (Manual management) | No | No |
| Native AI Agent Integration | Yes (context.json + Skills) | No | No | No | No | No |
| Audience Tree-Shaking | Yes (audiences.ts) | No | No | No | No | No |
Favorite Feature and Critical Feedback
Favorite Feature: Compile-Time Exclusion Conflict Detection (E007)
My absolute favorite feature in Dif is the exclusion graph conflict detector in crates/dif-core/src/exclusion.rs.
In every traditional feature flag platform, managing overlapping experiments on the same page is an operational nightmare. Two growth squads launch separate tests on the same checkout page. Neither squad knows about the other's initiative. The tests ship to production simultaneously, users see conflicting button styles and contradictory messaging, and both experiments produce compromised statistical results.
Dif elevates mutual exclusion to a compiler check. If two active experiments share a surface and their audiences are not provably disjoint, dif validate fails the build with error E007. An engineer cannot merge their pull request until they either:
- Declare the same
exclusion_group:, allowing Dif to arbitrate precedence deterministically. - Mathematically restrict their audience rules so that collision is impossible (e.g. mobile vs desktop).
- Target a distinct surface.
Catching experimental collisions before code leaves the local machine is a massive engineering win that eliminates an entire class of growth team failures.
What We Would Definitely Change: Lack of a File Watcher Daemon (dif watch)
If there is one aspect of Dif that feels unfinished, it is the manual requirement for running dif validate and dif build during active feature development.
Currently, Dif relies on developers configuring a "prebuild": "dif build" script in package.json, or remembering to execute dif build in the terminal after editing Markdown files. When pair-programming or rapidly tweaking variant copy, it is easy to modify an experiment in dif/experiments/active/, switch to the browser, and wonder why the UI is still rendering the old branch.
We would strongly urge the maintainers to introduce:
- A Built-in Watcher Daemon (
dif watch): A lightweight background process utilizingnotifyin Rust to monitordif/**/*.mdanddif/audiences/*.ts, re-generatingclient.tsandcontext.jsonin sub-10-millisecond time upon file save. - Automated Git Hook Installation: An optional flag
dif init --hooksthat installs a.git/hooks/pre-commitscript to validate experiments before every commit, ensuring broken YAML frontmatter or undeclared audience attributes can never enter the git history.

The Verdict
For teams tired of rotting SaaS dashboards, vendor lock-in, and ballooning per-seat subscription invoices, Dif is a breath of fresh air. By treating feature flags and A/B experiments as first-class citizens of the Git repository, Dif unites flags with the code they govern.
Its technical foundation is exceptionally solid:
- SHA-256 deterministic bucketing with u32 modulo bias correction ensures mathematical fairness across platforms.
- Compile-time exclusion graph validation prevents destructive multi-experiment UI collisions.
- Zero-dependency client generation provides sub-microsecond in-memory evaluations without network overhead.
- Deep integration with AI coding agents (
context.jsonand Claude Code skills) points toward the future of autonomous software engineering.
Dif earns an overall score of 9.0 / 10 and our highest recommendation. It is an indispensable tool for frontend engineering teams, systems architects, and developers building with autonomous coding agents.
Resources & Verification Links
- Product Hunt Launch: Product Hunt - dif.sh
- Official Documentation: dif.sh | Documentation
- GitHub Repository:
dif-sh/dif(MIT License, Commit72faace26571806ab3528f9857f8a6c6e8506b28) - First-Party Test Suite: Automated 12-scenario BDD verification suite in
ph-tests/dif/


