cli

Repository Pattern

Data persistence strategy using the Repository Pattern with SQLite.

Overview

The Repository Pattern abstracts data access, providing a collection-like interface for domain objects while hiding storage implementation details.

+-------------------------+
|     Application Layer   |
|                         |
|  +-------------------+  |
|  | IFeatureRepository|  |  <-- Interface (Port)
|  +---------+---------+  |
+------------+------------+
             | implements
+------------+------------+
|  Infrastructure Layer   |
|                         |
|  +-------------------+  |
|  |SqliteFeatureRepo  |  |  <-- Implementation
|  +---------+---------+  |
|            |            |
|  +---------v---------+  |
|  |   SQLite Database |  |
|  +-------------------+  |
+-------------------------+

Repository Interfaces

Defined in packages/core/src/application/ports/output/repositories/:

IFeatureRepository

export interface IFeatureRepository {
  create(feature: Feature): Promise<void>;
  findById(id: string): Promise<Feature | null>;
  findByIdPrefix(prefix: string): Promise<Feature | null>;
  findBySlug(slug: string, repositoryPath: string): Promise<Feature | null>;
  list(filters?: FeatureListFilters): Promise<Feature[]>;
  update(feature: Feature): Promise<void>;
  findByParentId(parentId: string): Promise<Feature[]>;
  delete(id: string): Promise<void>;
}

export interface FeatureListFilters {
  repositoryPath?: string;
  lifecycle?: SdlcLifecycle;
}

ISettingsRepository

export interface ISettingsRepository {
  initialize(settings: Settings): Promise<void>;
  load(): Promise<Settings | null>;
  update(settings: Settings): Promise<void>;
}

IRepositoryRepository

export interface IRepositoryRepository {
  create(repository: Repository): Promise<Repository>;
  findById(id: string): Promise<Repository | null>;
  findByPath(path: string): Promise<Repository | null>;
  findByPathIncludingDeleted(path: string): Promise<Repository | null>;
  list(): Promise<Repository[]>;
  remove(id: string): Promise<void>;
  softDelete(id: string): Promise<void>;
  restore(id: string): Promise<void>;
}

Defined in packages/core/src/application/ports/output/agents/:

SQLite Implementation

Database Locations

The repo path is base64-encoded to create valid directory names while preserving uniqueness.

Actual Schema

The schema is managed through 28 migrations in packages/core/src/infrastructure/persistence/sqlite/migrations.ts. Key tables:

settings table

Singleton row with flattened columns for nested configuration objects:

features table

Stores Feature entities with JSON columns for complex nested data (messages, plan, related_artifacts, ci_fix_history, attachments). Includes columns for lifecycle tracking, PR state, approval gates, worktree paths, and parent/child hierarchy.

agent_runs table

Tracks agent execution records with status, timing, PID for background processes, approval configuration, and model selection.

repositories table

Tracked code repositories with soft delete support via deleted_at column.

phase_timings table

Per-phase timing data for agent runs, including approval wait tracking.

Implementation Files

packages/core/src/infrastructure/
+-- persistence/
|   +-- sqlite/
|       +-- connection.ts          # Database connection
|       +-- migrations.ts          # 28 migrations (user_version pragma)
|       +-- mappers/               # Domain <-> Persistence mapping
+-- repositories/
    +-- sqlite-feature.repository.ts
    +-- sqlite-settings.repository.ts
    +-- sqlite-repository.repository.ts
    +-- agent-run.repository.ts
    +-- sqlite-phase-timing.repository.ts

Migrations

Migrations are managed via SQLite user_version pragma. All migration SQL is inlined in TypeScript (not separate .sql files) so it survives tsc compilation. The runSQLiteMigrations() function applies pending migrations transactionally.


Maintaining This Document

Update when:

Related docs: