Skip to content

Promotion

Promotion is the only path from ephemeral work to durable storage.

Promotion creates clean durable commits from selected session changes instead of replaying agent history.

Current core promotion supports:

  • all touched files
  • selected touched files
  • baseline conflict detection
  • durable commits through durable.applyCommit()

It does not currently support hunk-level promotion.

type PromoteSelector =
| { mode: 'all' }
| { mode: 'files'; files: string[] }

selector: { mode: 'all' } promotes all touched files.

selector: { mode: 'files', files: [...] } promotes selected touched files only.

Empty selection fails with PROMOTE_FAILED at stage staging.

flowchart TD
  Touch["ephemeral.touchedFiles(sessionId)"] --> Select["selector: all or files"]
  Select --> Empty{"empty selection?"}
  Empty -->|yes| Fail["PROMOTE_FAILED\nstage: staging"]
  Empty -->|no| Head["read durable HEAD"]
  Head --> Drift{"durable HEAD changed\nsince baseline?"}
  Drift -->|no| Apply["durable.applyCommit(files)"]
  Drift -->|yes| Overlap{"durable changed files\noverlap selected files?"}
  Overlap -->|yes| Conflict["BASELINE_CONFLICT"]
  Overlap -->|no| Apply
  Apply --> Result["PromoteResult\n{ sha, branch, prUrl? }"]

Current diff behavior:

  • Computes diffs from ephemeral.touchedFiles(sessionId).
  • Reads baseline content from durable at baselineSha.
  • Reads current content from ephemeral if the file exists.
  • Missing ephemeral file means deletion.
  • Text files use unified patches via the diff package.
  • Binary files are reported as Binary files a/<path> and b/<path> differ.

Gittrix reads current durable HEAD before applying. If durable HEAD changed since the session baseline, Gittrix checks changed file paths.

If durable-changed files overlap selected files, Gittrix throws BASELINE_CONFLICT.

Non-overlapping durable drift does not block promotion.

class BaselineConflictError extends GittrixError {
code: 'BASELINE_CONFLICT'
conflictingFiles: string[]
durableSha: string
baselineSha: string
}

Selected files are sent to durable.applyCommit() as:

Record<string, Uint8Array | null>

null means delete the file on durable.

Default commit message is gittrix: <task>, or gittrix: promote session <session-id> if task is blank.

type PromoteStrategy = 'auto' | 'commit' | 'branch' | 'pr' | 'patch'

The type includes auto, commit, branch, pr, and patch.

Current core implementation passes selected files to the durable adapter’s applyCommit() on the durable branch. It does not yet implement separate behavior for branch, PR, or patch strategies.

GitHub PR creation exists as GitHubDurableAdapter.openPullRequest(), not as automatic UserSession.promote({ strategy: 'pr' }) behavior.