# Write A Spec

> Extend or write a journey spec in evals/specs. Use only after the coverage decision says a journey spec is missing an assertion or a new user journey exists.

- Skill: `different-ai/write-a-spec` (Agent Skill)
- Install (CLI): `npx skillmds@latest add different-ai/write-a-spec`
- Raw SKILL.md: https://api.skillmd.com/api/skills/different-ai/write-a-spec/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: different-ai (https://skillmd.com/u/different-ai)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/different-ai/write-a-spec

---


# Skill: Write a Spec

## Do not write one when…

- An existing journey spec covers the behaviour; extend it.
- The change is a pure function; write a colocated unit test, not evidence.
- You would import `../../apps|packages|ee`, read source files, or spawn another
  test runner. That is a unit test in disguise; the boundary ratchet rejects it.

One spec per user journey, not per PR; bug fixes add an assertion to the journey they escaped from.

Write new tests in `evals/specs/**/*.test.ts` and import `test` from
`@openwork/testkit`. App-driving E2E tests use `.e2e.test.ts`; the PR lane excludes
them. Model setup as resources in dependency order: `needs()` → `server()` →
`app()`.

## Use the testkit primitives

- `server()` boots or reuses Den and provisions isolated organizations.
- `app()` boots a signed-in desktop. Use `profileDir` for caller-owned profile
  continuity and `localServerDelayMs` for deterministic startup races.
- `inviteMember()` adds a named member to an existing Den.
- `faultProxy()` injects `faults.status()` or `faults.latency()` and exposes the
  `requests` log for assertions about attempts and recovery.
- `eventually()` bounds polling and reports its last value or error.
- `readDenClientState()`, `readConnectState()`, and `readConnectStateFile()`
  expose client, local-server, and persisted-profile state.

## Claims and witnesses

- Make each claim machine-checkable with an observable assertion and its
  explicit negative half. Assert both the intended effect and what must not
  happen to another identity, account, request, file, or state.
- Prose is never proof. Screenshots explain an assertion but cannot replace it.
- Describe product behavior, not incidental layout. Claims such as "side by
  side" can disagree even when pixels are identical across runs.
- Match claims to what the product actually says on screen. If product and
  claim diverge, explicitly change one; never silently bend the claim.
- Never smuggle the answer into the prompt. Assert that the user-facing request
  does not contain connector or resource IDs.

## Mocks

- Use `mcpMock()` witnesses; never exercise real providers from a spec.
- Witnesses live under `evals/packages/labs/src/`, following `mock-mcp.ts` and
  the provider-specific `mock-*.ts` fixtures.
- Keep witnesses deterministic, identity-scoped, and queryable for assertions.

## Evidence contract

- Test evidence is ambient: `screenshot()` records screenshot artifacts,
  `validate()` records their visual validations whether they pass or fail, and
  `recordAssertionEvidence()` holds witness assertions.
- Never create or pass test-evidence recorder handles in test bodies.
- Bound every wait.
- Declare every external requirement in `needs()` so missing dependencies skip
  loudly instead of timing out or weakening coverage.

