trellis/vcs

Version control model operations, branches, milestones, checkpoints, issues, diff, merge, and blob storage.

Overview

The trellis/vcs subpath exposes the operation types and domain-level building blocks for version control. Use it when you want Trellis VCS capabilities without the full platform surface.

import { BranchEntity, MilestoneEntity, VcsOp, VcsOpKind } from "trellis/vcs";

VcsOp

Every change is an immutable, content-addressed operation:

interface VcsOp {
  hash: string; // 'trellis:op:<sha256>' content address
  kind: VcsOpKind; // Operation type
  timestamp: string; // ISO 8601
  agentId: string; // Author identity (DID)
  previousHash?: string; // Causal chain link
  vcs?: VcsPayload; // Kind-specific payload (file paths, issue fields, store facts, …)
  facts?: Fact[]; // Optional top-level facts (some op kinds)
  links?: Link[]; // Optional top-level links (some op kinds)
  signature?: string; // Ed25519 signature (when signing enabled)
}

Local durability and concurrent writers

When multiple Trellis processes write to the same repository path, the local VCS storage layer uses file locks to avoid dropped operations and duplicate issue IDs.

  • ops.json appends are lock-guarded and re-read from disk before write.
  • Writes use a temporary file plus atomic rename to avoid partial writes.
  • Replayed duplicate op hashes are ignored as an idempotency guard.
  • Issue IDs (TRL-<n>) are allocated under a lock so concurrent issue create calls stay monotonic.

If Trellis cannot acquire a local lock within a short timeout, the command fails with a lock-timeout error instead of risking log corruption.

Agent Lanes

Multi-agent work needs isolated causal journals per agent. Agent Lanes add per-agent op logs under .trellis/lanes/lane-{uuid}/ops.json, forked from an integration branch head and promoted explicitly into main.

Optional git worktree bind (W5, 3.2.3+): when lanes.worktreeBind is true in .trellis/config.json, createLane provisions a git worktree at .trellis/worktrees/<shortId>/. 4.0.0+ (ADR 0038): enterLane loads lane bytes from the lane git branch (auto-commit on exit); the op-log does not materialize file state. Pre-4.0: enterLane replayed lane blobs to the worktree. Without worktree bind, lanes still isolate op journals while agents may share the repo root on disk.

ModuleRole
JsonOpLogIntegration journal (.trellis/ops.json)
LaneOpLogPer-lane journal
lane.tsLaneMeta, paths, createLaneMeta
lane-promote.tsPromote planning, conflict detection
lane-materialize.tsIntegration cache + lazy lane overlay (W4)
lane-worktree.tsGit worktree provision / cleanup (W5)
lane-disk-materialize.tsBlob replay to bound worktree on enter
import { createLaneMeta, JsonOpLog, laneDir, LaneOpLog } from "trellis/vcs";

Engine API (via TrellisVcsEngine): createLane, enterLane, leaveLane, dropLane, promoteLane, getMaterializationStats. Config: lanes.worktreeBind?: boolean.

Decisions are recorded in kernel/docs/adr/ (0001–0008, 0014, 0021–0022, 0038). Lane fork semantics: ADR 0006–0007. EAV store materialization: ADR 0008. Git byte authority: ADR 0038 (4.0.0+). 3.4.0+: VcsOp.laneId is envelope data (outside the hashed payload); read op.laneId, not op.vcs.laneId. CLI: trellis lane … (3.1.32+), worktree bind (3.2.3+), lane split (3.4.0+).

Operation Tiers

TierOperationsDescription
1dirAdd, dirDelete, branchCreate, milestoneCreate, …Structural VCS control ops
2fileAdd, fileModify, fileDelete, fileRenameFile lifecycle ops
3astPatchSemantic AST-level patches
4Signatures, governanceCryptographic layer

EAV store operations

CMS and knowledge-graph entities persist as four dedicated op kinds in the integration journal. On materialization, decompose() passes vcs.facts and vcs.links through to the in-memory EAV store (kernel ADR 0008):

Op kindVcsPayload fieldsEffect
vcs:storeAssertfacts[]Add facts
vcs:storeRetractfacts[]Remove facts
vcs:storeLinklinks[]Add links
vcs:storeUnlinklinks[]Remove links

Entity ids use typed prefixes (person:…, organization:…, …), not a generic entity: prefix.

Branch Entities

Branches are graph entities with a pointer to their current head op:

interface BranchEntity {
  id: string; // 'branch:<name>'
  name: string;
  headHash: string; // Current tip op hash
  createdAt: string;
  policy?: BranchPolicy;
}
// List branches
const branches = await engine.listBranches();

// Create a branch
await engine.createBranch("feature/auth");

// Switch branch
await engine.switchBranch("feature/auth");

// Delete a branch
await engine.deleteBranch("feature/old");

Milestone Entities

Milestones are human-curated markers over a range of ops:

interface MilestoneEntity {
  id: string; // 'milestone:<hash>'
  message: string;
  fromOpHash: string;
  toOpHash: string;
  affectedFiles: string[];
  authorId: string;
  createdAt: string;
}
// Create a milestone
await engine.createMilestone({
  message: "Add user authentication",
  // fromOpHash defaults to last milestone's toOpHash + 1
  // toOpHash defaults to current HEAD
});

// List milestones
const milestones = await engine.listMilestones();

Diff & Merge

File-level Diff

import { diff } from "trellis/vcs";

const result = diff(fromOpHash, toOpHash, { engine });
// { file: string; added: number; removed: number; hunks: DiffHunk[] }[]

Semantic Diff

import { semanticDiff } from "trellis/vcs";

const patches = semanticDiff(oldSource, newSource, "auth.ts");
// SemanticPatch[] symbolAdd | symbolRemove | symbolModify | symbolRename | …

Three-way Merge

import { merge } from "trellis/vcs";

const result = await merge("feature/auth", { engine, dryRun: false });
result.conflicts; // MergeConflict[] entity-level, with suggestions
result.clean; // true if no conflicts

Checkpoints

Auto-generated stable-state markers (created automatically when op count crosses a threshold):

interface CheckpointEntity {
  id: string;
  opHash: string; // Op at checkpoint time
  snapshotId: string; // SQLite snapshot reference
  createdAt: string;
}

Issue Tracking

Issues are first-class VCS entities, not an external system:

import { IssueEntity, IssueStatus } from "trellis/vcs";

interface IssueEntity {
  id: string; // 'issue:TRL-<n>'
  title: string;
  description?: string;
  status: IssueStatus; // 'backlog' | 'queued' | 'in_progress' | 'done' | 'closed'
  assignee?: string;
  labels: string[];
  acceptanceCriteria: string[];
  parentId?: string; // Epic via childOf link (set at create or update)
  branchId?: string; // Auto-created branch when started
  createdAt: string;
  updatedAt: string;
}

Parent links are stored as childOf edges in the graph. createIssue accepts parentId; updateIssue accepts parentId (re-parent) or parentId: null (clear). Re-parenting emits vcs:issueUpdate with oldParentIssueId / parentIssueId so the prior link is removed before the new one is added.

See the Issue Tracking guide for the full lifecycle.

Blob Storage

Large file content is stored in the blob store and referenced by hash keeping the op log lean:

import { BlobStore } from "trellis/vcs";

const store = new BlobStore({ dbPath: ".trellis/blobs.db" });
const hash = await store.put(Buffer.from(fileContent));
const content = await store.get(hash);