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


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:71

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:62

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:53

Returns the workspace name this repository is bound to.

Returns

string

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

Inherited from

Repository.workspace