# skills v0.1

Agent skills.

dif init installs three Claude Code skills into .claude/skills/. They teach a coding agent the dif workflow: reading the surface log, drafting frontmatter, and writing a decision that becomes a learning.

The three skills map onto the daily loop: generate surfacesauthor an experimentconclude it. Each is a plain SKILL.md with a trigger description; an agent loads one when your request matches (say "draft an experiment" and dif-author-experiment picks it up). They're markdown. Read or edit them like any file in the repo.

.claude/skills/
  dif-author-experiment/     # draft + iterate on an experiment
  dif-conclude-experiment/   # write the decision, archive, learn
  dif-generate-surfaces/     # propose the surface set from the codebase
CLAUDE.md                    # managed dif block
AGENTS.md                    # managed dif block
.cursorrules                 # managed dif block

All of this is installed by dif init unless you pass --no-agent-files. The skills read from dif/context.json and the surface logs, so they stay current as your experiments change, with no separate sync step.

dif-author-experiment

Drafting, editing, fixing, or iterating on an experiment .md under dif/experiments/active/. It covers the loop from dif new through dif validate to dif build.

Triggers on: create / draft / edit / fix an experiment, or any mention of an A/B test, variant, audience, hypothesis, surface, exclusion group, or the commands dif new, dif validate, dif build.

Reads context first

The draft is downstream of three reads, so the skill does them before writing anything:

  • dif/context.json: what's active, on which surface, and how long it's been running.
  • dif/surfaces/<surface>.md: the ## Learnings log, so it builds on prior findings instead of re-testing an answered question.
  • dif/config.yaml: the declared audience_attributes it's allowed to write predicates over.

The loop

$dif new <kebab-id> --surface <surface>
# fill in hypothesis, ## Brief, ## Rationale, audience, metrics
$dif validate          # collects every error at once
$dif build             # also runs validate
$git add dif/ && git commit

It fills what dif new leaves as stubs, with opinions:

  • hypothesis: one falsifiable sentence naming the change, the expected primary-metric direction, and the audience.
  • variants: at least two, weights summing to exactly 100, control kept as the first id.
  • audience: predicates only over attributes declared in dif/config.yaml.
  • metrics.primary: exactly one metric this experiment is moving.
  • exclusion_group: added when E007 fires on a same-surface overlap.

The skill ships reference files for the details: frontmatter.md (full schema + a worked example), validation-errors.md (every E/W code with its fix), and audiences.md (predicate grammar and the resolver contract).

dif-conclude-experiment

Concluding an experiment that has run its course: writing a decision, archiving the file, and running dif conclude. The whole operation is atomic; if any step fails, every file change rolls back.

Triggers on: conclude / finish / ship / wrap up an experiment, recording a learning or writing a decision, or the command dif conclude.

The decision is load-bearing

What you write becomes the next line in the surface's ## Learnings log, and the next dif new on that surface embeds it into the draft. So the skill won't conclude until it can answer four things:

  • Is there a real analysis of the primary metric over the run, not a hunch?
  • Did the experiment run long enough, on enough of the right traffic, to answer its hypothesis?
  • Can the outcome compress to one sentence with a metric direction and magnitude?
  • Did the guardrails hold? A primary win that regresses a guardrail isn't a ship.
$dif conclude checkout-cta-v2 --decision "Shipped variant_a. +2.1% checkout conversion, p<0.01 over 14d. Guardrails clean."

That one command, atomically:

  1. moves dif/experiments/active/<id>.mddif/experiments/concluded/<YYYY-MM>-<id>.md;
  2. sets status: concluded and concluded: <today>;
  3. fills the ## Decision block;
  4. appends one dated line under ## Learnings on the surface.

A good decision is one line: did the hypothesis hold, by how much, over what period, and what's the next move (ship / kill / iterate). After concluding, the skill runs dif build and, if the decision was to ship, removes the now-dead dif("<id>", ...) call site (it would otherwise trip W001) and bakes the winning variant into the code.

// noteBad decisions to avoid: "Inconclusive." (no next move), "Looked good, shipping it." (no metric or magnitude), "See Notion doc." (the surface log is the source of truth for future drafts; links rot). Either you have an answer or you're not ready to conclude.

dif-generate-surfaces

Producing the initial dif/surfaces/<name>.md set by reading the app's structure and product context. Most useful right after dif init (which writes only the one default surface), or when dif new --surface X exits with "surface does not exist."

Triggers on: set up / scaffold / populate / propose surfaces, especially a sparse dif/surfaces/, or a dif new that failed on a missing surface.

A surface is a unit of experimentation agency, not a unit of code: the place where you'd reasonably A/B test something. A typical app has 3-8 surfaces, not one per route or component. The skill:

  1. surveys existing surfaces and dif/context.json, scoping to what's missing;
  2. reads product context (README.md, package.json, the routing files) to learn where experimentation actually happens;
  3. drafts 3-8 coarse-grained candidates, each with a one-line rationale;
  4. confirms the list with you before writing anything;
  5. writes each file in the same format dif init emits, then runs dif validate and dif build.
# Surface: checkout

(One-sentence description: where in the app is this surface? Who sees it?)

## Known landmines

(Vendor DOM, regulated regions, race conditions — one bullet each. Empty to start.)

## Learnings

(One line per concluded test, appended by dif conclude. Empty to start.)

It keeps names kebab-case and singular (checkout, search-results), leaves ## Known landmines and ## Learnings empty (both accrue from real experience; guesses pollute future drafts), and never auto-flips default_surface in your config. From here, dif-author-experiment takes over.

Agent guidance files

Alongside the skills, dif init writes a managed dif block into CLAUDE.md, AGENTS.md, and .cursorrules. dif owns only its marked block; anything else you've written in those files is preserved across re-runs.

The block points the agent at the workflow and at dif/context.json, the session-start read that lists active experiments, their surfaces and variants, and each surface's most recent learning. dif build regenerates it, and it lives outside the gitignored dif/generated/ directory so it's checked in and any agent picking up the repo sees the current state.

// notePass dif init --no-agent-files to skip the skills and the guidance blocks entirely. The CLI, SDK, and build are unaffected. The skills are an accelerant for the agent-assisted workflow, not a dependency of it.