incremental-commits

$npx mdskill add EpicenterHQ/epicenter/incremental-commits

Break multi-file changes into atomic commits ordered by dependency

  • Solves the problem of organizing complex changes into logical, reviewable units
  • Relies on git and standalone-commits for message conventions and validation
  • Uses dependency order to determine commit sequence and boundaries
  • Delivers clean, incremental commit history for easier code review and auditing

SKILL.md

.github/skills/incremental-commitsView on GitHub ↗
---
name: incremental-commits
description: Break multi-file changes into atomic commits ordered by dependency. Use for "split this into commits", "commit strategy", "break this up", refactors.
metadata:
  author: epicenter
  version: '1.0'
---

# Incremental Commits

When a feature touches multiple files, implement in **waves**. Each wave is one logical concern, one standalone commit, ordered by dependency. This creates a clean git history that reviewers can audit one commit at a time.

> **Related Skills**: See `standalone-commits` for making each wave reviewable and auditable. See `git` for commit message conventions and PR guidelines.

## Relationship To Standalone Commits

`incremental-commits` decides commit order. `standalone-commits` decides whether each commit boundary is good.

Use this skill to plan the sequence:

```
foundation -> implementation -> consumers -> cleanup
```

Then use `standalone-commits` to test each wave:

```
Can this commit be reviewed, verified, and reverted as one coherent unit?
```

If a planned wave fails that test, change the wave boundary before committing.

## The Pattern

```
Wave 1: Foundation (types, interfaces)
  ↓
Wave 2: Factories/Builders (functions that create instances)
  ↓
Wave 3: Contracts/APIs (public interfaces that use types)
  ↓
Wave 4: Infrastructure (utilities, converters, dependencies)
  ↓
Wave 5: Consumers (apps, UI, integrations)
```

Not every change needs all waves. A simple bugfix might be one standalone commit. A cross-cutting refactor might need five.

## Wave Characteristics

Each wave must be:

| Property      | Description                                    |
| ------------- | ---------------------------------------------- |
| **Atomic**    | One logical concern per wave                   |
| **Buildable** | Code compiles after this wave (run type-check) |
| **Focused**   | Changes relate to ONE layer/concern            |
| **Complete**  | No half-done work within a wave                |
| **Auditable** | Reviewers can inspect this wave without waiting for a later wave |

## Real Example: Schema Refactor

This feature moved metadata from workspace to tables. Five waves:

### Wave 1: Types

```
feat(schema): add IconDefinition, CoverDefinition, and FieldMetadata types

- Add IconDefinition discriminated union (emoji | external)
- Add CoverDefinition discriminated union (external)
- Add FieldMetadata with optional name/description to all field types
- Update TableDefinition to use icon/cover instead of emoji/order
```

Files: `types.ts` only. Foundation for everything else.

### Wave 2: Factories

```
feat(schema): add optional name/description to field factory functions

All factory functions (id, text, richtext, integer, real, boolean, date,
select, tags, json) now accept optional name and description parameters.
```

Files: `factories.ts` only. Uses types from Wave 1.

### Wave 3: Contracts

```
feat(schema): remove emoji and description from WorkspaceSchema

Workspace is now just a container with guid, id, name, tables, and kv.
Visual metadata (icon, cover, description) now lives on TableDefinition.
```

Files: `contract.ts` only. API change using new types.

### Wave 4: Infrastructure

```
feat(schema): use slugify for human-readable SQL column names

- Add @sindresorhus/slugify dependency
- Add toSqlIdentifier() helper using slugify with '_' separator
- SQLite columns now use field.name (or derived from key) instead of key
```

Files: `to-drizzle.ts`, `package.json`. Utility that uses field metadata.

### Wave 5: Consumers

```
feat(schema): update epicenter app to use TablesWithMetadata

- WorkspaceSchema now accepts TablesSchema | TablesWithMetadata
- Export new types from package index
- Update app to create proper TableDefinition with metadata
```

Files: App files that consume the new types.

## The Workflow

1. **Plan waves before coding**
   - List files that need changes
   - Group by layer/concern
   - Order by dependency (foundations first)
   - Define the standalone claim each wave will prove

2. **Implement one wave**
   - Make changes for that wave only
   - Resist temptation to "fix one more thing"

3. **Verify the wave**
   - Run type-check: `bun run tsc --noEmit`
   - Re-read `git diff --staged` against the wave claim
   - Ensure no errors introduced

4. **Commit the wave**
   - Use conventional commit format
   - Message describes what this wave accomplishes
   - Body can list specific changes

5. **Repeat for next wave**

## When to Use Waves

| Scenario                 | Waves? | Why                        |
| ------------------------ | ------ | -------------------------- |
| Single file bugfix       | No     | One change, one commit     |
| Add new type + factory   | Maybe  | Could be 1-2 waves         |
| Refactor across 5+ files | Yes    | Need logical grouping      |
| Breaking API change      | Yes    | Types → API → Consumers    |
| Add dependency + use it  | Yes    | Infra wave then usage wave |

## Anti-Patterns

### Giant Commit

```
refactor: update schema system

- Add new types
- Update factories
- Change contracts
- Add slugify
- Update app
```

Problem: One monolithic commit. Can't bisect, can't revert partially, no story.

### Micro Commits

```
feat: add IconDefinition type
feat: add CoverDefinition type
feat: add FieldMetadata type
feat: update IdFieldSchema
feat: update TextFieldSchema
...
```

Problem: Too granular. 20 commits for one logical change. Noise.

### Wrong Order

```
Wave 1: Update app to use new types  ❌
Wave 2: Add the types                 ❌
```

Problem: Wave 1 won't compile. Bottom-up, not top-down.

## Dependency Order Heuristic

When deciding wave order, ask: "What does this file import?"

```
types.ts         → imports nothing (foundation)
factories.ts     → imports types.ts
contract.ts      → imports types.ts
converters.ts    → imports types.ts, may add deps
app/             → imports everything above
```

Files that import nothing come first. Files that import everything come last.

## Branch Strategy

For multi-wave work:

```bash
# Create feature branch
git checkout -b feat/my-feature

# Wave 1
# ... make changes ...
git add <files> && git commit -m "feat(scope): wave 1 description"

# Wave 2
# ... make changes ...
git add <files> && git commit -m "feat(scope): wave 2 description"

# ... continue waves ...

# When done, all waves are individual commits on the branch
# PR shows clean history of how the feature evolved
```

## Quick Reference

Before starting:

- [ ] List all files that need changes
- [ ] Group by layer (types, factories, contracts, infra, consumers)
- [ ] Order by dependency

For each wave:

- [ ] Change only files in this wave
- [ ] Run type-check
- [ ] Commit with descriptive message
- [ ] Move to next wave

After all waves:

- [ ] Final type-check
- [ ] Run tests if applicable
- [ ] Create PR with clean commit history

More from EpicenterHQ/epicenter

SkillDescription
agent-goalWrite `/goal` prompts for long-running agent work in Codex or Claude Code. Use for slash goal, agent goal, durable objective, autonomous coding run.
approachability-auditReview code as a new TypeScript developer. Use when code feels indirect, clever, hard to follow, or needs a pass on abstractions, names, first-read clarity.
arktypeArktype: runtime validation, discriminated unions with .merge()/.or(), spread keys. Use when mentioning arktype, type(), union types, command/event schemas.
attach-primitiveContract and invariants for `attach*` composition primitives in `packages/workspace` (side-effectful building blocks like attachIndexedDb, attachSqlite, attachBroadcastChannel, attachEncryption, attachTable, openCollaboration), and when to use `create*` (pure construction) instead. Use when writing or reviewing an `attach*` or `create*` function, naming a new workspace primitive, composing inside a workspace builder, or deciding whether a primitive registers listeners at call time.
authEpicenter auth packages: `@epicenter/auth`, `@epicenter/auth-svelte`, OAuth sessions, identity state, auth-owned fetch/WebSocket, and workspace lifecycle binding. Use when editing Epicenter auth clients, session state, hosted sign-in, or auth/workspace integration.
autumnAutumn billing in Epicenter: `autumn.config.ts`, `autumn-js` credit checks, `atmn` CLI, plan gates, and metered AI usage. Use when changing billing, pricing, credits, plan access, refunds, or usage events.
better-auth-best-practicesBetter Auth server/client setup: `auth.ts`, generated schema, DB adapters, sessions, cookies, env vars, and plugins. Use when mentioning Better Auth, betterauth, auth handlers, OAuth, email/password, or session configuration.
better-auth-security-best-practicesBetter Auth security hardening: rate limits, secrets, CSRF, trusted origins, cookies, sessions, OAuth tokens, and audit logging. Use when reviewing auth security, brute-force protection, token handling, or deployment safety.
change-proposalPresent proposed code changes visually before implementing. Use when: "show me options", "compare approaches", "what should we do", or when changes need before/after comparison.
claude-code-consultUse this skill when the user asks to consult Claude, ask Claude Code, get another model's take, run a taste check, find cleaner options, or prepare a Claude prompt. Create a bounded second-opinion prompt or run a read-only Claude Code consult, then verify Claude's claims against local files.