---
name: sdd
description: Spec-Driven Development workflow for cleepi-style repos. Use when the user asks to write a spec, journal entry, changelog entry, or constitution entry; when starting non-trivial work that should be designed before coding; or when about to release a package version. Covers the three-artifact ontology (spec/journal/changelog) + optional constitution, the discriminator test for where content belongs, ticket-keyed folder layout, scope resolution (package vs repo), status lifecycles, and Keep-a-Changelog 1.1.0 conventions.
---

# Spec-Driven Development (SDD)

The cleepi workflow for designing before coding, capturing in-flight
thinking so it doesn't get lost, and remembering what shipped.

## Core idea

Three artifacts + one optional declarative file. Each has a single
responsibility — never conflate them.

| Artifact | Question it answers | When written | Lifecycle |
| --- | --- | --- | --- |
| **Spec** | *What* are we going to build, and *why this approach* over alternatives? | Before coding | `draft` → `accepted` → `shipped` (frozen) or `superseded` |
| **Journal** | *What's been tried, where are we now?* | During work, append-only | No formal status; per-ticket |
| **Changelog** | *What* shipped in version N? | At release | Append-only by SemVer (package) or date (repo) |
| **Constitution** *(optional)* | *What invariants* must always be true for this project? | When the project has real invariants worth codifying | Living document; entries cite originating spec when one exists |

ADRs are not used. Their "X over Y" function lives in a required
`## Decision` section inside specs. Their durable-rule function (for
true project invariants) lives in `CONSTITUTION.md` when a project
has one.

## Voice and length

SDD artifacts are written by tired humans for tired humans. Straight
to the point. No padding. Nobody likes reading a long spec that
says nothing.

The default failure mode — especially for AI agents — is wordy
prose that says nothing concrete. This rule is not advisory: a long
artifact when a short one would do is a defect. Reviewers (human or
AI) reject it the same way they'd reject a missing Decision section.

### Principles

- **Bullets over paragraphs.** Paragraphs connect ideas. Bullets
  list things. SDD artifacts are mostly listing.
- **Specifics over generalities.** "Refactor pricing logic" is
  generality. "Move `pricingEngine.compute()` from `billing/` to
  `pricing/` and update three call sites" is specific.
- **Show the thing, don't describe it.** A file path, a code
  snippet, a citation beats ten lines of prose.
- **No throat-clearing.** No "This document describes…", no
  "In this section we will…", no "In conclusion…".
- **No filler adjectives.** Cut "comprehensive", "robust",
  "elegant", "seamlessly", "industry-leading". They mean nothing.
- **No hedging.** "Could potentially" → "may". "It might be the
  case that" → cut.
- **No restating the title or task.** The reader just read it.

### The delete test

Read each sentence. *Can you delete it without losing information?*
If yes, delete it. Apply recursively until no sentence survives.
Reading time of an SDD artifact should equal the reading time of
its information content. Nothing else.

### Length cues per artifact

- **Spec.** As long as the work demands; no longer. 100–300 lines
  is typical for a non-trivial feature spec. Over 500 lines is
  almost always a smell — either the scope is too big (split it)
  or the prose is padded (cut it).
- **Journal entry.** 3–5 bullets per entry. One line if nothing
  surprising happened. The whole journal is 3–10 entries for a
  ticket.
- **Changelog entry.** One bullet. Cites the spec with a link.
- **Constitution entry.** One line. Cites the spec with a link.
  If you can't state the invariant in one line, it's probably not
  an invariant — it's a rule (see AGENTS.md).

If you find yourself writing "this section is more detailed than
usual because of X," delete the section and write a shorter version.

## The discriminator test — where does this belong?

When unsure where to put something, ask: *what happens if it's
violated?*

- The codebase becomes a *fundamentally different project* (we suddenly
  aren't a Bun project anymore) → **CONSTITUTION**.
- A PR gets review feedback ("use repository pattern here") →
  **AGENTS.md** (or whatever the project's code-conventions file is).
- It was a choice for one specific feature ("we picked Stripe SDK
  directly for this billing work") → the spec's **`## Decision`
  section**.
- It's about how SDD itself works ("specs go draft → accepted →
  shipped") → this **sdd skill**.

### Worked examples

**Constitution** — tribal knowledge, foundational, "crossing this
changes what the project is":

- We use Bun, not Node or Deno.
- We use PostgreSQL, not MySQL.
- Deployed only to AWS eu-central-1.
- All authentication through Auth0.
- Production data never leaves AWS.

**AGENTS.md** — current code rules, evolves with the codebase:

- Don't do unnecessary `as unknown as` casts.
- Prefer `unknown` over `any`.
- Database access through the repository pattern.
- Test files live next to source: `foo.ts` + `foo.test.ts`.
- Use Zod at trust boundaries.
- Be a partner not order-taker (AI interaction style).

**Spec `## Decision` section** — one-off, per-feature:

- "Used Stripe SDK directly rather than abstracting; revisit if we
  add a second payment provider."
- "Picked nock over msw for mocking; msw doesn't intercept the SDK's
  internal HTTPS validation."

**Sdd skill** — process, not content:

- Specs go `draft` → `accepted` → `shipped`.
- Numbering is per-directory monotonic for repo-level specs; ticket-ID
  for ticket-keyed folders.
- Ticket-keyed folder layout is `docs/<ticket-or-slug>/`.

## Folder layout

Ticket-keyed, flat. The folder name is the link to the tracker
(Jira, Linear, GitHub Issues, or a project-local convention).

```
my-repo/
├── README.md
├── CHANGELOG.md                  ← repo-level events
├── CONSTITUTION.md               ← OPTIONAL; only when the project
│                                   has real invariants to codify
├── AGENTS.md                     ← code rules + interaction style
│                                   (project-specific; not shipped here)
└── docs/
    ├── AC-100-billing-revamp/
    │   ├── spec.md
    │   └── journal.md
    ├── AC-101-pricing-page/
    │   ├── spec.md
    │   └── journal.md
    └── AC-999-decide-monorepo/   ← meta-tickets for cross-cutting
        ├── spec.md                 decisions are just ticket folders too
        └── journal.md
```

In a monorepo, each package can have the same structure inside
`packages/<name>/docs/`, with the package's own `CHANGELOG.md`,
`SPEC.md` (singular, purpose-and-scope), and optional
`CONSTITUTION.md`.

**Ticket ID format is not validated.** `AC-101-slug`, `LIN-42-slug`,
`gh-7-slug`, `CLEEPI-001-slug` all work. For pre-tracker work, use
`DRAFT-NNN-slug` and rename when a ticket is cut.

## Scope resolution

When creating a spec, journal, or changelog entry, determine **scope**
first:

- **Package scope** — when working inside `packages/<n>/`, write to:
  - `packages/<n>/docs/<ticket-or-slug>/spec.md` (and `journal.md`)
  - `packages/<n>/CHANGELOG.md`
  - `packages/<n>/CONSTITUTION.md` (if applicable)
- **Repo scope** — when the change is cross-cutting (affects multiple
  packages, repo tooling, conventions, adding/removing a package),
  write to:
  - `docs/<ticket-or-slug>/spec.md` (and `journal.md`)
  - `/CHANGELOG.md`
  - `/CONSTITUTION.md` (if applicable)

When in doubt: if it only affects one package, it's package scope.

The package's own `SPEC.md` (singular, capitalized, at
`packages/<n>/SPEC.md`) is a special file — it describes the
package's *purpose and scope*, not a feature. It is not numbered,
has no formal lifecycle, and isn't replaced by ticket folders.

## Status lifecycle (specs)

- `draft` — being written, open for changes. No `## Decision` section
  required yet.
- `accepted` — design agreed; implementation can start (strict mode).
  `## Decision` section **must** be populated before flipping to this
  status.
- `shipped` — implementation complete and released; spec is now
  **frozen**. Future changes go in a new spec marked
  `supersedes: docs/<old-folder>/spec.md`.
- `superseded` — replaced by a later spec. Set
  `superseded-by: docs/<new-folder>/spec.md` in frontmatter.

## Spec format

Every spec has frontmatter plus a body. Required sections:

```yaml
---
id: AC-101                     # the ticket ID, or a project-local
                               # identifier for non-tracker specs
title: <human-readable title>
status: draft                  # → accepted → shipped/superseded
created: YYYY-MM-DD
accepted: YYYY-MM-DD           # added when flipped to accepted
shipped: YYYY-MM-DD            # added when flipped to shipped
owner: <human>
ticket: AC-101                 # tracker ticket ID (optional for
                               # repo-internal specs)
ticket-url: https://...        # optional, full URL
related:
  - docs/<other-folder>/spec.md
---
```

Body sections — minimal core, everything else opt-in.

**Required:**

- **`## What`** — what's being built. Concrete, specific. Works for
  features, refactors, and bug fixes equally. Show the thing.
- **`## Why`** — the reason. Both the motivation and the timing,
  in one section.
- **`## Decision`** — required before `status: accepted` (stub OK
  while draft). Format:
  - **Alternatives considered** (honest summary + why rejected, for each)
  - **Rationale** (why the picked option wins, anchored on What and Why)
  - **Consequences** (what becomes easier, harder, locked-in)

**Optional — include only when they add information:**

- **`## Concrete surface`** — the files this delivers/changes.
  Often filled during iteration by analysis (scout phase), not at
  initial draft. Skip when obvious.
- **`## Phased rollout`** — for multi-phase work. Each phase has
  deliverables and acceptance criteria. Skip for single-pass
  changes.
- **`## Open questions`** — unresolved choices, with default-
  proposed answers where possible. Mark which phase resolves each.
- **`## Non-goals`** — only when scope is genuinely ambiguous.
  Forced "we won't boil the ocean" filler fails the delete test.
- **`## Risks`** — real risks with mitigations. Synthetic "what if
  it breaks" risks fail the delete test.

Apply the delete test to every section. A spec with three sections
(What / Why / Decision) is better than a spec with eight where five
are filler.

A spec without a Decision section is fine as `draft`; it cannot be
promoted to `accepted` until the section is populated.

## Journal discipline

Each ticket folder may contain a `journal.md` capturing in-flight
thinking. Two sections:

```markdown
# Journal — <ticket-id> <slug>

## Current state
*(overwritten each session — kept terse)*

- <where things stand right now>
- <what's blocked, what's next>

## Log

### YYYY-MM-DD — <author or session id>
- <what was tried, what worked, what didn't, what's blocked>

### YYYY-MM-DD — earlier session
- <older entries below>
```

**Hard rules:**

- One entry per work session, not per action.
- Capture **friction** (dead ends, blockers, surprises, decisions
  that changed). If everything went smoothly, one line is enough.
- No code in the journal. Reference commits/PRs.
- Bullets over paragraphs.
- 3–10 log entries per ticket is normal; 20+ is a smell (split the
  ticket, or stop logging routine progress).
- Append-only on the log; "Current state" is overwritten.
- On ticket ship: final entry citing the changelog version, then
  the log stops appending. Not deleted.

**Journal vs Changelog (one-line litmus):** *if an entry has no dead
end, blocker, or reasoning, it's not journal material — it's a
changelog entry waiting to happen.*

## Constitution discipline (when used)

`CONSTITUTION.md` is **optional**. Use it when the project has real
invariants worth making explicit — things everyone on the team
already follows but never wrote down, and that crossing would
fundamentally change what the project is.

**Don't use it** for:

- Code style or lint rules → AGENTS.md
- "Prefer X over Y" conventions → AGENTS.md
- Per-feature design choices → spec Decision sections
- Process rules → this sdd skill

**Maintenance:** when a spec is promoted to `accepted`, the author
considers whether the spec's Decision section establishes a new
invariant (commits the project to something indefinitely, not just
a one-off). If yes, a corresponding CONSTITUTION entry is added in
the same change. Most specs do NOT establish invariants — they
establish features.

**To change an invariant:** write a new spec that supersedes the
originating one. Don't edit CONSTITUTION.md directly.

## Strict-mode workflow

For non-trivial work:

1. Cut a ticket (Jira / Linear / GitHub Issue / local `DRAFT-NNN`).
2. Create the ticket folder: `docs/<ticket-id>-<slug>/`.
3. Draft `spec.md`. Status `draft`. Use the `/spec` slash command.
4. Iterate on the spec; populate the Decision section. Flip status
   to `accepted` when ready.
5. Implement. Append to `journal.md` as you go — friction, blockers,
   surprises only (`/journal` slash command).
6. When merged/released: flip spec status to `shipped`. Add a
   `CHANGELOG.md` entry for the relevant scope (`/changelog`).
7. If the spec's Decision section established a new invariant,
   propagate it to CONSTITUTION.md in the same change.

### What's "trivial" (skip the dance)

No spec required for: typo fixes, formatting, documentation polish,
mechanical renames, dependency bumps that don't change behavior,
obvious bug fixes. Trivial changes still go in the relevant changelog
if user-visible.

### Bootstrap exception

The single commit that *introduces* SDD into a repo is allowed without
a pre-existing accepted spec, because the spec defining the process is
created in that same commit. One-off. Does not apply to any later
work.

## Changelog conventions

[Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). One
file per package at `packages/<n>/CHANGELOG.md`, plus one at the
repo root for repo-level events (tooling, new packages,
conventions).

Section names (use only the ones with content): `Added`, `Changed`,
`Deprecated`, `Removed`, `Fixed`, `Security`.

Always keep an `## [Unreleased]` section at the top. On release,
rename it to the new version with today's date, then re-add an empty
`[Unreleased]`.

```markdown
## [Unreleased]

## [0.2.0] — 2026-06-01

### Added
- Thing A (see [spec AC-101](docs/AC-101-thing-a/spec.md)).

### Fixed
- Thing B.
```

## SemVer

Each package versions independently. Pre-`1.0.0`, minor bumps may
include breaking changes (per SemVer §4). At `1.0.0`, commit to
standard SemVer.

The repo itself isn't versioned — root `CHANGELOG.md` uses
`[Unreleased]` and dated sections without version numbers.

## Templates

Live in `packages/sdd/templates/`:

- `spec.md` — the four required sections + Decision template
- `journal.md` — stack-pointer-plus-log skeleton
- `constitution.md` — invariants-only skeleton with inline
  guidance on what belongs vs doesn't
- `changelog-section.md` — Keep-a-Changelog section block

The `/spec`, `/journal`, `/changelog` prompts use these. If invoked
manually:

```bash
# Make the ticket folder, copy template:
mkdir -p docs/AC-101-pricing-page
cp <sdd-pkg>/templates/spec.md docs/AC-101-pricing-page/spec.md
# Fill frontmatter and body.
```

## Quick reference

| You want to... | Use |
| --- | --- |
| Start a non-trivial change | `/spec <title>` (asks for ticket ID interactively) |
| Log a session's friction / progress | `/journal <text>` |
| Note something shipped | `/changelog <added\|changed\|fixed\|...> <text>` |
| Browse / review specs while talking to the agent | `/specs` or `alt+s` (the **cockpit**) |
| Quote a spec passage back to the agent | In the cockpit: `v` → select lines → `enter` |
| Flip a spec's status from the TUI | In the cockpit: focus the spec, press `s`, confirm with `y` |
| Append a journal entry from the TUI | In the cockpit: `J`, type, preview, `y` |
| Append a changelog entry from the TUI | In the cockpit: `C`, pick section (`1`–`6`), type, preview, `y` |
| Add a project invariant | Edit `CONSTITUTION.md` directly (no slash command) |
| Freeze a shipped spec | Edit frontmatter `status: shipped` or use the cockpit's `s` flip |
| Replace a spec | New spec; old one's status becomes `superseded`, with `superseded-by:` set |

### The cockpit

`@cleepi/sdd` v0.3.x ships a TUI extension that surfaces the spec
corpus inside pi sessions. Open it with `/specs` or `alt+s`.

Keyboard reference (view mode):

- `j`/`k`/arrows scroll; `pgup`/`pgdn` page; `g`/`G` top/bottom
- `tab` / `shift+tab` cycle focused file
- `d` toggle diff vs session-start snapshot
- `v` enter line-selection (`j`/`k` move, `J`/`K` extend, `enter`
  quotes into chat input)
- `s` flip status (`draft → accepted → shipped`), stamps the date
- `J` append journal entry to focused spec's `journal.md`
- `C` append changelog entry under `[Unreleased]`
- `/` text filter; `1`–`4` status filters; `e` edited-only
- `esc` backs out one level (mode → view, filter → cleared,
  view → close pane)

Structured edits go through `templates/*.md` so prompts and the
cockpit produce identical output.

## Migration from sdd v0.1.x

If you're upgrading from sdd v0.1.x:

- `/adr` is gone. Move ADR content into the spec's `## Decision`
  section. Mark old ADRs as `superseded by spec <id>`.
- `docs/specs/NNNN-*.md` files don't have to move. New specs go
  in ticket-keyed folders; old ones stay as historical record
  (with `status: shipped` or `superseded`).
- `docs/adr/` becomes a read-only archive directory.
- Add an optional `CONSTITUTION.md` at the relevant scope if the
  project has invariants to codify.
- Start using `journal.md` for active work.

No automated migration tooling. The transition is intentional and
unhurried.
