Implement Phase 0

This commit is contained in:
2025-10-09 18:50:51 -04:00
parent 7ef57642cc
commit f444113057
10 changed files with 1104 additions and 0 deletions

173
internal/models/oplog.go Normal file
View File

@ -0,0 +1,173 @@
package models
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"time"
)
// OplogEntry represents a single entry in the action log
type OplogEntry struct {
// ID is a monotonically increasing entry ID
ID uint64
// Timestamp when the operation was performed
Timestamp time.Time
// Operation type (e.g., "save", "switch", "new", "sync")
Operation string
// Description of the operation
Description string
// StateBefore captures the state before the operation
StateBefore *RepositoryState
// StateAfter captures the state after the operation
StateAfter *RepositoryState
// Metadata contains operation-specific data
Metadata map[string]string
}
// RepositoryState captures the state of the repository at a point in time
type RepositoryState struct {
// Refs maps reference names to their SHA-1 hashes
Refs map[string]string
// CurrentWorkstream is the active workstream name
CurrentWorkstream string
// WorkingTreeHash is the hash of the current working tree snapshot
WorkingTreeHash string
// IndexHash is the hash of the staging area
IndexHash string
}
// Serialize converts an OplogEntry to binary format
func (e *OplogEntry) Serialize() ([]byte, error) {
buf := new(bytes.Buffer)
// Write entry ID (8 bytes)
if err := binary.Write(buf, binary.LittleEndian, e.ID); err != nil {
return nil, fmt.Errorf("failed to write ID: %w", err)
}
// Write timestamp (8 bytes, Unix nano)
timestamp := e.Timestamp.UnixNano()
if err := binary.Write(buf, binary.LittleEndian, timestamp); err != nil {
return nil, fmt.Errorf("failed to write timestamp: %w", err)
}
// Serialize the rest as JSON for flexibility
payload := struct {
Operation string `json:"operation"`
Description string `json:"description"`
StateBefore *RepositoryState `json:"state_before"`
StateAfter *RepositoryState `json:"state_after"`
Metadata map[string]string `json:"metadata"`
}{
Operation: e.Operation,
Description: e.Description,
StateBefore: e.StateBefore,
StateAfter: e.StateAfter,
Metadata: e.Metadata,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
}
// Write JSON length (4 bytes)
jsonLen := uint32(len(jsonData))
if err := binary.Write(buf, binary.LittleEndian, jsonLen); err != nil {
return nil, fmt.Errorf("failed to write JSON length: %w", err)
}
// Write JSON data
if _, err := buf.Write(jsonData); err != nil {
return nil, fmt.Errorf("failed to write JSON data: %w", err)
}
return buf.Bytes(), nil
}
// Deserialize converts binary data back to an OplogEntry
func DeserializeOplogEntry(data []byte) (*OplogEntry, error) {
buf := bytes.NewReader(data)
entry := &OplogEntry{}
// Read entry ID (8 bytes)
if err := binary.Read(buf, binary.LittleEndian, &entry.ID); err != nil {
return nil, fmt.Errorf("failed to read ID: %w", err)
}
// Read timestamp (8 bytes)
var timestamp int64
if err := binary.Read(buf, binary.LittleEndian, &timestamp); err != nil {
return nil, fmt.Errorf("failed to read timestamp: %w", err)
}
entry.Timestamp = time.Unix(0, timestamp)
// Read JSON length (4 bytes)
var jsonLen uint32
if err := binary.Read(buf, binary.LittleEndian, &jsonLen); err != nil {
return nil, fmt.Errorf("failed to read JSON length: %w", err)
}
// Read JSON data
jsonData := make([]byte, jsonLen)
if _, err := io.ReadFull(buf, jsonData); err != nil {
return nil, fmt.Errorf("failed to read JSON data: %w", err)
}
// Unmarshal JSON
payload := struct {
Operation string `json:"operation"`
Description string `json:"description"`
StateBefore *RepositoryState `json:"state_before"`
StateAfter *RepositoryState `json:"state_after"`
Metadata map[string]string `json:"metadata"`
}{}
if err := json.Unmarshal(jsonData, &payload); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
entry.Operation = payload.Operation
entry.Description = payload.Description
entry.StateBefore = payload.StateBefore
entry.StateAfter = payload.StateAfter
entry.Metadata = payload.Metadata
return entry, nil
}
// NewOplogEntry creates a new oplog entry
func NewOplogEntry(id uint64, operation, description string, before, after *RepositoryState) *OplogEntry {
return &OplogEntry{
ID: id,
Timestamp: time.Now(),
Operation: operation,
Description: description,
StateBefore: before,
StateAfter: after,
Metadata: make(map[string]string),
}
}
// NewRepositoryState creates a new repository state snapshot
func NewRepositoryState(refs map[string]string, currentWorkstream, workingTreeHash, indexHash string) *RepositoryState {
return &RepositoryState{
Refs: refs,
CurrentWorkstream: currentWorkstream,
WorkingTreeHash: workingTreeHash,
IndexHash: indexHash,
}
}

View File

@ -0,0 +1,107 @@
package models
import (
"encoding/json"
"fmt"
"time"
)
// WorkspaceState represents the current state of the workspace
type WorkspaceState struct {
// CurrentCommitSHA is the SHA of the current ephemeral commit
CurrentCommitSHA string `json:"current_commit_sha"`
// WorkstreamName is the name of the active workstream
WorkstreamName string `json:"workstream_name"`
// LastSnapshot is when the last automatic snapshot was created
LastSnapshot time.Time `json:"last_snapshot"`
// IsDirty indicates if there are uncommitted changes
IsDirty bool `json:"is_dirty"`
// TreeHash is the hash of the current working tree
TreeHash string `json:"tree_hash,omitempty"`
// IndexHash is the hash of the staging area
IndexHash string `json:"index_hash,omitempty"`
// Metadata contains additional workspace-specific data
Metadata map[string]string `json:"metadata,omitempty"`
}
// NewWorkspaceState creates a new workspace state
func NewWorkspaceState(commitSHA, workstreamName string) *WorkspaceState {
return &WorkspaceState{
CurrentCommitSHA: commitSHA,
WorkstreamName: workstreamName,
LastSnapshot: time.Now(),
IsDirty: false,
Metadata: make(map[string]string),
}
}
// UpdateSnapshot updates the workspace state with a new snapshot
func (ws *WorkspaceState) UpdateSnapshot(commitSHA, treeHash, indexHash string, isDirty bool) {
ws.CurrentCommitSHA = commitSHA
ws.TreeHash = treeHash
ws.IndexHash = indexHash
ws.IsDirty = isDirty
ws.LastSnapshot = time.Now()
}
// SetWorkstream changes the active workstream
func (ws *WorkspaceState) SetWorkstream(workstreamName string) {
ws.WorkstreamName = workstreamName
}
// MarkDirty marks the workspace as having uncommitted changes
func (ws *WorkspaceState) MarkDirty() {
ws.IsDirty = true
}
// MarkClean marks the workspace as clean (no uncommitted changes)
func (ws *WorkspaceState) MarkClean() {
ws.IsDirty = false
}
// Serialize converts the workspace state to JSON
func (ws *WorkspaceState) Serialize() ([]byte, error) {
data, err := json.MarshalIndent(ws, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal workspace state: %w", err)
}
return data, nil
}
// DeserializeWorkspaceState converts JSON data to a workspace state
func DeserializeWorkspaceState(data []byte) (*WorkspaceState, error) {
ws := &WorkspaceState{}
if err := json.Unmarshal(data, ws); err != nil {
return nil, fmt.Errorf("failed to unmarshal workspace state: %w", err)
}
return ws, nil
}
// GetTimeSinceLastSnapshot returns the duration since the last snapshot
func (ws *WorkspaceState) GetTimeSinceLastSnapshot() time.Duration {
return time.Since(ws.LastSnapshot)
}
// Clone creates a deep copy of the workspace state
func (ws *WorkspaceState) Clone() *WorkspaceState {
metadata := make(map[string]string, len(ws.Metadata))
for k, v := range ws.Metadata {
metadata[k] = v
}
return &WorkspaceState{
CurrentCommitSHA: ws.CurrentCommitSHA,
WorkstreamName: ws.WorkstreamName,
LastSnapshot: ws.LastSnapshot,
IsDirty: ws.IsDirty,
TreeHash: ws.TreeHash,
IndexHash: ws.IndexHash,
Metadata: metadata,
}
}

View File

@ -0,0 +1,214 @@
package models
import (
"encoding/json"
"fmt"
"time"
)
// Workstream represents a stacked-diff workflow
type Workstream struct {
// Name is the unique identifier for the workstream
Name string `json:"name"`
// Description provides context about the workstream
Description string `json:"description"`
// BaseBranch is the Git branch this workstream is based on
BaseBranch string `json:"base_branch"`
// Commits is an ordered list of commits in this workstream
Commits []WorkstreamCommit `json:"commits"`
// Created is when the workstream was created
Created time.Time `json:"created"`
// Updated is when the workstream was last modified
Updated time.Time `json:"updated"`
// Status indicates the current state (active, merged, abandoned)
Status WorkstreamStatus `json:"status"`
// Metadata contains additional workstream-specific data
Metadata map[string]string `json:"metadata,omitempty"`
}
// WorkstreamCommit represents a single commit in a workstream
type WorkstreamCommit struct {
// SHA is the Git commit hash
SHA string `json:"sha"`
// Message is the commit message
Message string `json:"message"`
// Author is the commit author
Author string `json:"author"`
// Timestamp is when the commit was created
Timestamp time.Time `json:"timestamp"`
// ParentSHA is the parent commit in the workstream (empty for first commit)
ParentSHA string `json:"parent_sha,omitempty"`
// BaseSHA is the base commit from the base branch
BaseSHA string `json:"base_sha"`
// BranchRef is the Git reference for this commit (e.g., refs/onyx/workstreams/name/commit-1)
BranchRef string `json:"branch_ref"`
}
// WorkstreamStatus represents the state of a workstream
type WorkstreamStatus string
const (
// WorkstreamStatusActive indicates the workstream is being actively developed
WorkstreamStatusActive WorkstreamStatus = "active"
// WorkstreamStatusMerged indicates the workstream has been merged
WorkstreamStatusMerged WorkstreamStatus = "merged"
// WorkstreamStatusAbandoned indicates the workstream has been abandoned
WorkstreamStatusAbandoned WorkstreamStatus = "abandoned"
// WorkstreamStatusArchived indicates the workstream has been archived
WorkstreamStatusArchived WorkstreamStatus = "archived"
)
// WorkstreamCollection represents the collection of all workstreams
type WorkstreamCollection struct {
// Workstreams is a map of workstream name to Workstream
Workstreams map[string]*Workstream `json:"workstreams"`
// CurrentWorkstream is the name of the active workstream
CurrentWorkstream string `json:"current_workstream,omitempty"`
}
// NewWorkstream creates a new workstream
func NewWorkstream(name, description, baseBranch string) *Workstream {
now := time.Now()
return &Workstream{
Name: name,
Description: description,
BaseBranch: baseBranch,
Commits: []WorkstreamCommit{},
Created: now,
Updated: now,
Status: WorkstreamStatusActive,
Metadata: make(map[string]string),
}
}
// AddCommit adds a commit to the workstream
func (w *Workstream) AddCommit(commit WorkstreamCommit) {
w.Commits = append(w.Commits, commit)
w.Updated = time.Now()
}
// GetLatestCommit returns the latest commit in the workstream
func (w *Workstream) GetLatestCommit() (*WorkstreamCommit, error) {
if len(w.Commits) == 0 {
return nil, fmt.Errorf("workstream has no commits")
}
return &w.Commits[len(w.Commits)-1], nil
}
// GetCommitCount returns the number of commits in the workstream
func (w *Workstream) GetCommitCount() int {
return len(w.Commits)
}
// IsEmpty returns true if the workstream has no commits
func (w *Workstream) IsEmpty() bool {
return len(w.Commits) == 0
}
// NewWorkstreamCommit creates a new workstream commit
func NewWorkstreamCommit(sha, message, author, parentSHA, baseSHA, branchRef string) WorkstreamCommit {
return WorkstreamCommit{
SHA: sha,
Message: message,
Author: author,
Timestamp: time.Now(),
ParentSHA: parentSHA,
BaseSHA: baseSHA,
BranchRef: branchRef,
}
}
// NewWorkstreamCollection creates a new workstream collection
func NewWorkstreamCollection() *WorkstreamCollection {
return &WorkstreamCollection{
Workstreams: make(map[string]*Workstream),
}
}
// AddWorkstream adds a workstream to the collection
func (wc *WorkstreamCollection) AddWorkstream(workstream *Workstream) error {
if _, exists := wc.Workstreams[workstream.Name]; exists {
return fmt.Errorf("workstream '%s' already exists", workstream.Name)
}
wc.Workstreams[workstream.Name] = workstream
return nil
}
// GetWorkstream retrieves a workstream by name
func (wc *WorkstreamCollection) GetWorkstream(name string) (*Workstream, error) {
workstream, exists := wc.Workstreams[name]
if !exists {
return nil, fmt.Errorf("workstream '%s' not found", name)
}
return workstream, nil
}
// RemoveWorkstream removes a workstream from the collection
func (wc *WorkstreamCollection) RemoveWorkstream(name string) error {
if _, exists := wc.Workstreams[name]; !exists {
return fmt.Errorf("workstream '%s' not found", name)
}
delete(wc.Workstreams, name)
return nil
}
// ListWorkstreams returns all workstreams
func (wc *WorkstreamCollection) ListWorkstreams() []*Workstream {
workstreams := make([]*Workstream, 0, len(wc.Workstreams))
for _, ws := range wc.Workstreams {
workstreams = append(workstreams, ws)
}
return workstreams
}
// SetCurrentWorkstream sets the active workstream
func (wc *WorkstreamCollection) SetCurrentWorkstream(name string) error {
if _, exists := wc.Workstreams[name]; !exists {
return fmt.Errorf("workstream '%s' not found", name)
}
wc.CurrentWorkstream = name
return nil
}
// GetCurrentWorkstream returns the current workstream
func (wc *WorkstreamCollection) GetCurrentWorkstream() (*Workstream, error) {
if wc.CurrentWorkstream == "" {
return nil, fmt.Errorf("no current workstream set")
}
return wc.GetWorkstream(wc.CurrentWorkstream)
}
// Serialize converts the workstream collection to JSON
func (wc *WorkstreamCollection) Serialize() ([]byte, error) {
data, err := json.MarshalIndent(wc, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal workstream collection: %w", err)
}
return data, nil
}
// DeserializeWorkstreamCollection converts JSON data to a workstream collection
func DeserializeWorkstreamCollection(data []byte) (*WorkstreamCollection, error) {
wc := &WorkstreamCollection{}
if err := json.Unmarshal(data, wc); err != nil {
return nil, fmt.Errorf("failed to unmarshal workstream collection: %w", err)
}
return wc, nil
}