Skip to main content

Abstract Class: ChangeRepository

Defined in: packages/core/src/application/ports/change-repository.ts:24

Port for reading and writing changes.

Extends Repository for interface consistency with SpecRepository and ArchiveRepository, but changes are stored globally (one changes/ directory), not per-workspace. The inherited workspace(), ownership(), and isExternal() fields carry the default workspace values and are not used by any use case. They exist solely to satisfy the shared Repository base contract.

list and get return Change objects with artifact state (status, validatedHash) but without artifact content. Content is loaded on demand via artifact(). The manifest (state, hashes, approvals) is persisted separately from artifact file content via save() and saveArtifact().

Extends​

Constructors​

Constructor​

new ChangeRepository(config): ChangeRepository

Defined in: packages/core/src/application/ports/change-repository.ts:30

Creates a new ChangeRepository instance.

Parameters​

config​

RepositoryConfig

Workspace, ownership, and locality configuration

Returns​

ChangeRepository

Overrides​

Repository.constructor

Methods​

artifact()​

abstract artifact(change, filename): Promise<SpecArtifact | null>

Defined in: packages/core/src/application/ports/change-repository.ts:128

Loads the content of a single artifact file within a change.

The returned SpecArtifact has originalHash set to the sha256 of the content read from disk, enabling conflict detection if the artifact is later saved back via saveArtifact().

Parameters​

change​

Change

The change containing the artifact

filename​

string

The artifact filename to load (e.g. "proposal.md")

Returns​

Promise<SpecArtifact | null>

The artifact with content and originalHash, or null if the file does not exist


artifactExists()​

abstract artifactExists(change, filename): Promise<boolean>

Defined in: packages/core/src/application/ports/change-repository.ts:171

Parameters​

change​

Change

filename​

string

Returns​

Promise<boolean>


changePath()​

abstract changePath(change): string

Defined in: packages/core/src/application/ports/change-repository.ts:169

Returns the absolute filesystem path to the active change directory.

Used by use cases to build the change.path template variable.

Parameters​

change​

Change

The change whose path is needed

Returns​

string

Absolute path to the change directory


configPath()​

configPath(): string

Defined in: packages/core/src/application/ports/repository.ts:88

Returns the absolute path to the config directory.

Returns​

string

The config path (e.g. /project/.specd/config)

Inherited from​

Repository.configPath


delete()​

abstract delete(change): Promise<void>

Defined in: packages/core/src/application/ports/change-repository.ts:115

Deletes the entire change directory and all its contents.

Parameters​

change​

Change

The change to delete

Returns​

Promise<void>


deltaExists()​

abstract deltaExists(change, specId, filename): Promise<boolean>

Defined in: packages/core/src/application/ports/change-repository.ts:181

Checks whether a delta file exists for a change + specId pair.

Parameters​

change​

Change

The change containing the delta

specId​

string

The spec identifier (e.g. "auth/login")

filename​

string

The delta filename to check (e.g. "spec.delta.yaml")

Returns​

Promise<boolean>

true if the file exists, false otherwise


get()​

abstract get(name): Promise<Change | null>

Defined in: packages/core/src/application/ports/change-repository.ts:49

Returns the change with the given name, or null if not found.

Loads the manifest and derives each artifact's status by comparing the current file hash against the validatedHash stored at last validation. A hash mismatch indicates drift and resets the artifact status to in-progress.

This is a snapshot read only. Callers that need a concurrency-safe read-modify-write section for an existing persisted change must use mutate instead of pairing get() with a later save().

Parameters​

name​

string

The change name (e.g. "add-oauth-login")

Returns​

Promise<Change | null>

The change with current artifact state, or null if not found


isExternal()​

isExternal(): boolean

Defined in: packages/core/src/application/ports/repository.ts:79

Returns whether this repository points to data outside the current git repository.

Returns​

boolean

true if this repository is external to the current git root

Inherited from​

Repository.isExternal


list()​

abstract list(): Promise<Change[]>

Defined in: packages/core/src/application/ports/change-repository.ts:76

Lists all active (non-drafted, non-discarded) changes, sorted by creation order.

Returns Change objects with artifact state but without content.

Returns​

Promise<Change[]>

All active changes in this workspace, oldest first


listDiscarded()​

abstract listDiscarded(): Promise<Change[]>

Defined in: packages/core/src/application/ports/change-repository.ts:94

Lists all discarded changes in this workspace, sorted by creation order.

Returns Change objects with artifact state but without content.

Returns​

Promise<Change[]>

All discarded changes in this workspace, oldest first


listDrafts()​

abstract listDrafts(): Promise<Change[]>

Defined in: packages/core/src/application/ports/change-repository.ts:85

Lists all drafted (shelved) changes in this workspace, sorted by creation order.

Returns Change objects with artifact state but without content.

Returns​

Promise<Change[]>

All drafted changes in this workspace, oldest first


mutate()​

abstract mutate<T>(name, fn): Promise<T>

Defined in: packages/core/src/application/ports/change-repository.ts:67

Runs a serialized persisted mutation for one existing change.

The repository acquires exclusive mutation access for name, reloads the freshest persisted Change, invokes fn(change), persists the updated manifest if fn succeeds, and then releases the exclusive access.

This is the only concurrency-safe read-modify-write API for an existing change. Exclusive access is scoped per change name; unrelated change names may mutate concurrently.

Type Parameters​

T​

T

Parameters​

name​

string

The change name to mutate

fn​

(change) => T | Promise<T>

Callback that applies the mutation on the fresh persisted change

Returns​

Promise<T>

The callback result after the manifest has been persisted

Throws​

If no change with the given name exists


ownership()​

ownership(): "owned" | "shared" | "readOnly"

Defined in: packages/core/src/application/ports/repository.ts:70

Returns the ownership level of this repository, as declared in specd.yaml.

Returns​

"owned" | "shared" | "readOnly"

The ownership level

Inherited from​

Repository.ownership


save()​

abstract save(change): Promise<void>

Defined in: packages/core/src/application/ports/change-repository.ts:108

Persists the change manifest — state, artifact statuses, validated hashes, and approvals.

Does not write artifact file content. Use saveArtifact() for that.

This is a low-level manifest persistence primitive. Atomic writing prevents partial-file corruption, but save() alone does not serialize a caller's earlier snapshot read.

Parameters​

change​

Change

The change whose manifest should be persisted

Returns​

Promise<void>


saveArtifact()​

abstract saveArtifact(change, artifact, options?): Promise<void>

Defined in: packages/core/src/application/ports/change-repository.ts:148

Writes an artifact file within a change directory.

If artifact.originalHash is set and does not match the current file on disk, the save is rejected with ArtifactConflictError to prevent silently overwriting concurrent changes (e.g. those made by an LLM agent). Pass { force: true } to overwrite regardless.

After a successful write the corresponding ChangeArtifact status in the change manifest is reset to in-progress — call save(change) to persist that state change.

Parameters​

change​

Change

The change to write the artifact into

artifact​

SpecArtifact

The artifact to save (filename + content)

options?​

Save options

force?​

boolean

When true, skip conflict detection and overwrite unconditionally

Returns​

Promise<void>

Throws​

When a concurrent modification is detected and force is not set


scaffold()​

abstract scaffold(change, specExists): Promise<void>

Defined in: packages/core/src/application/ports/change-repository.ts:193

Ensures artifact directories exist for all files tracked by the change.

For scope: spec artifacts, creates specs/<ws>/<capPath>/ or deltas/<ws>/<capPath>/ directories under the change directory. For scope: change artifacts, the root directory already exists.

Parameters​

change​

Change

The change whose artifact directories to scaffold

specExists​

(specId) => Promise<boolean>

A function that returns whether a spec already exists in the repository

Returns​

Promise<void>


unscaffold()​

abstract unscaffold(change, specIds): Promise<void>

Defined in: packages/core/src/application/ports/change-repository.ts:205

Removes the scaffolded directories for the given spec IDs from the change directory.

For each spec ID, removes both specs/<workspace>/<capability-path>/ and deltas/<workspace>/<capability-path>/ directories. The operation is idempotent — if a directory does not exist, it is silently skipped.

Parameters​

change​

Change

The change whose spec directories to remove

specIds​

readonly string[]

The spec IDs whose directories to remove

Returns​

Promise<void>


workspace()​

workspace(): string

Defined in: packages/core/src/application/ports/repository.ts:61

Returns the workspace name this repository is bound to.

Returns​

string

The workspace name (e.g. "billing", "default")

Inherited from​

Repository.workspace