Milestone 3
Some checks failed
CI / Test (pull_request) Failing after 6s
CI / Build (pull_request) Failing after 6s
CI / Lint (pull_request) Failing after 12s

This commit is contained in:
2025-10-14 22:10:45 -04:00
parent 99878adefb
commit 8b1339d0cf
9 changed files with 821 additions and 133 deletions

191
internal/commands/list.go Normal file
View File

@ -0,0 +1,191 @@
package commands
import (
"fmt"
"os"
"sort"
"strings"
"git.dws.rip/DWS/onyx/internal/core"
"git.dws.rip/DWS/onyx/internal/models"
"github.com/spf13/cobra"
)
// ANSI color codes
const (
colorReset = "\033[0m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorGray = "\033[90m"
colorBold = "\033[1m"
)
// NewListCmd creates the list command
func NewListCmd() *cobra.Command {
var showAll bool
cmd := &cobra.Command{
Use: "list",
Short: "List all workstreams",
Long: `List all workstreams in the repository.
Shows the current workstream (marked with *), the number of commits in each
workstream, and the workstream status.
Status indicators:
* active - Currently being worked on (green)
* merged - Has been merged (gray)
* abandoned - No longer being worked on (gray)
* archived - Archived for historical purposes (gray)`,
Aliases: []string{"ls"},
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(showAll)
},
}
cmd.Flags().BoolVarP(&showAll, "all", "a", false, "Show all workstreams including merged and archived")
return cmd
}
// runList executes the list command
func runList(showAll bool) error {
// Get current directory
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
// Check if this is an Onyx repository
if !core.IsOnyxRepo(cwd) {
return fmt.Errorf("not an Onyx repository")
}
// Open the repository
repo, err := core.Open(cwd)
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}
defer repo.Close()
// Create workstream manager
wsManager := core.NewWorkstreamManager(repo)
// Get all workstreams
workstreams, err := wsManager.ListWorkstreams()
if err != nil {
return fmt.Errorf("failed to list workstreams: %w", err)
}
// Get current workstream name
currentName, err := wsManager.GetCurrentWorkstreamName()
if err != nil {
currentName = "" // No current workstream
}
// Filter workstreams if not showing all
var displayWorkstreams []*models.Workstream
for _, ws := range workstreams {
if showAll || ws.Status == models.WorkstreamStatusActive {
displayWorkstreams = append(displayWorkstreams, ws)
}
}
// Check if there are any workstreams
if len(displayWorkstreams) == 0 {
if showAll {
fmt.Println("No workstreams found.")
} else {
fmt.Println("No active workstreams found.")
fmt.Println("Use 'onx new <name>' to create a new workstream.")
fmt.Println("Use 'onx list --all' to see all workstreams including merged and archived.")
}
return nil
}
// Sort workstreams by name for consistent output
sort.Slice(displayWorkstreams, func(i, j int) bool {
return displayWorkstreams[i].Name < displayWorkstreams[j].Name
})
// Display workstreams
fmt.Println("Workstreams:")
for _, ws := range displayWorkstreams {
displayWorkstream(ws, ws.Name == currentName)
}
// Show helpful footer
fmt.Println()
fmt.Printf("Use 'onx switch <name>' to switch to a different workstream\n")
if !showAll {
fmt.Printf("Use 'onx list --all' to see all workstreams\n")
}
return nil
}
// displayWorkstream displays a single workstream with formatting
func displayWorkstream(ws *models.Workstream, isCurrent bool) {
// Determine the indicator
indicator := " "
if isCurrent {
indicator = "*"
}
// Determine the color based on status
color := colorReset
switch ws.Status {
case models.WorkstreamStatusActive:
color = colorGreen
case models.WorkstreamStatusMerged:
color = colorGray
case models.WorkstreamStatusAbandoned:
color = colorGray
case models.WorkstreamStatusArchived:
color = colorGray
}
// Format the output
name := ws.Name
if isCurrent {
name = colorBold + name + colorReset
}
commitCount := ws.GetCommitCount()
commitText := "commit"
if commitCount != 1 {
commitText = "commits"
}
// Build status string
statusStr := string(ws.Status)
if ws.Status != models.WorkstreamStatusActive {
statusStr = colorGray + statusStr + colorReset
}
// Build the line
line := fmt.Sprintf("%s %s%s%s", indicator, color, name, colorReset)
// Add base branch info
baseBranchInfo := fmt.Sprintf(" (based on %s)", ws.BaseBranch)
line += colorGray + baseBranchInfo + colorReset
// Add commit count
commitInfo := fmt.Sprintf(" - %d %s", commitCount, commitText)
line += commitInfo
// Add status if not active
if ws.Status != models.WorkstreamStatusActive {
line += fmt.Sprintf(" [%s]", statusStr)
}
fmt.Println(line)
// Add description if present
if ws.Description != "" {
description := strings.TrimSpace(ws.Description)
fmt.Printf(" %s%s%s\n", colorGray, description, colorReset)
}
}

75
internal/commands/new.go Normal file
View File

@ -0,0 +1,75 @@
package commands
import (
"fmt"
"os"
"git.dws.rip/DWS/onyx/internal/core"
"github.com/spf13/cobra"
)
// NewNewCmd creates the new command
func NewNewCmd() *cobra.Command {
var baseBranch string
cmd := &cobra.Command{
Use: "new <name>",
Short: "Create a new workstream",
Long: `Create a new workstream for a feature or task.
A workstream is a logical unit of work that can contain multiple commits.
It's similar to creating a new branch in Git, but with better support for
stacked diffs and atomic operations.
The workstream will be based on the specified base branch (default: main).`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runNew(args[0], baseBranch)
},
}
cmd.Flags().StringVarP(&baseBranch, "base", "b", "main", "Base branch for the workstream")
return cmd
}
// runNew executes the new command
func runNew(name, baseBranch string) error {
// Get current directory
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
// Check if this is an Onyx repository
if !core.IsOnyxRepo(cwd) {
return fmt.Errorf("not an Onyx repository. Run 'onx init' first")
}
// Open the repository
repo, err := core.Open(cwd)
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}
defer repo.Close()
// Create workstream manager
wsManager := core.NewWorkstreamManager(repo)
// Use ExecuteWithTransaction to capture state_before and state_after
err = core.ExecuteWithTransaction(repo, "new", fmt.Sprintf("Created workstream: %s", name), func() error {
return wsManager.CreateWorkstream(name, baseBranch)
})
if err != nil {
return err
}
fmt.Printf("Created workstream '%s' based on '%s'\n", name, baseBranch)
fmt.Printf("\nYou can now:\n")
fmt.Printf(" - Make changes to your files\n")
fmt.Printf(" - Save your work with 'onx save -m \"message\"'\n")
fmt.Printf(" - Switch to another workstream with 'onx switch <name>'\n")
return nil
}

View File

@ -3,12 +3,10 @@ package commands
import (
"fmt"
"os"
"path/filepath"
"strings"
"git.dws.rip/DWS/onyx/internal/core"
"git.dws.rip/DWS/onyx/internal/git"
"git.dws.rip/DWS/onyx/internal/models"
"github.com/spf13/cobra"
)
@ -95,14 +93,11 @@ func executeSave(repo *core.OnyxRepository, message string) error {
return fmt.Errorf("failed to get ephemeral commit object: %w", err)
}
// 3. Load workstream collection
workstreams, err := loadWorkstreams(repo)
if err != nil {
return fmt.Errorf("failed to load workstreams: %w", err)
}
// 3. Create workstream manager
wsManager := core.NewWorkstreamManager(repo)
// 4. Get the current workstream
currentWorkstream, err := workstreams.GetCurrentWorkstream()
currentWorkstream, err := wsManager.GetCurrentWorkstream()
if err != nil {
return fmt.Errorf("no active workstream. Use 'onx new' to create one: %w", err)
}
@ -116,16 +111,21 @@ func executeSave(repo *core.OnyxRepository, message string) error {
}
parentSHA = latestCommit.SHA
} else {
// For the first commit in the workstream, use the base branch HEAD
baseBranch := currentWorkstream.BaseBranch
if baseBranch == "" {
baseBranch = "main"
}
// Try to get the base branch reference
branchRef := fmt.Sprintf("refs/heads/%s", baseBranch)
sha, err := gitBackend.GetRef(branchRef)
if err == nil {
parentSHA = sha
// For the first commit in the workstream, use the base commit
baseCommitSHA := currentWorkstream.Metadata["base_commit"]
if baseCommitSHA == "" {
// Fallback to getting the base branch HEAD
baseBranch := currentWorkstream.BaseBranch
if baseBranch == "" {
baseBranch = "main"
}
branchRef := fmt.Sprintf("refs/heads/%s", baseBranch)
sha, err := gitBackend.GetRef(branchRef)
if err == nil {
parentSHA = sha
}
} else {
parentSHA = baseCommitSHA
}
}
@ -136,29 +136,9 @@ func executeSave(repo *core.OnyxRepository, message string) error {
return fmt.Errorf("failed to create commit: %w", err)
}
// 7. Determine next branch number
nextNumber := currentWorkstream.GetCommitCount() + 1
// 8. Create branch ref (e.g., refs/onyx/workstreams/feature-name/commit-1)
branchRef := fmt.Sprintf("refs/onyx/workstreams/%s/commit-%d", currentWorkstream.Name, nextNumber)
if err := gitBackend.UpdateRef(branchRef, commitSHA); err != nil {
return fmt.Errorf("failed to create branch ref: %w", err)
}
// 9. Add commit to workstream
workstreamCommit := models.NewWorkstreamCommit(
commitSHA,
message,
"User",
parentSHA,
currentWorkstream.BaseBranch,
branchRef,
)
currentWorkstream.AddCommit(workstreamCommit)
// 10. Save updated workstreams
if err := saveWorkstreams(repo, workstreams); err != nil {
return fmt.Errorf("failed to save workstreams: %w", err)
// 7. Add commit to workstream using the manager
if err := wsManager.AddCommitToWorkstream(commitSHA, message); err != nil {
return fmt.Errorf("failed to add commit to workstream: %w", err)
}
return nil
@ -173,39 +153,6 @@ func getEphemeralCommit(gitBackend *git.GitBackend) (string, error) {
return sha, nil
}
// loadWorkstreams loads the workstream collection from .onx/workstreams.json
func loadWorkstreams(repo *core.OnyxRepository) (*models.WorkstreamCollection, error) {
workstreamsPath := filepath.Join(repo.GetOnyxPath(), "workstreams.json")
data, err := os.ReadFile(workstreamsPath)
if err != nil {
return nil, fmt.Errorf("failed to read workstreams file: %w", err)
}
workstreams, err := models.DeserializeWorkstreamCollection(data)
if err != nil {
return nil, fmt.Errorf("failed to deserialize workstreams: %w", err)
}
return workstreams, nil
}
// saveWorkstreams saves the workstream collection to .onx/workstreams.json
func saveWorkstreams(repo *core.OnyxRepository, workstreams *models.WorkstreamCollection) error {
workstreamsPath := filepath.Join(repo.GetOnyxPath(), "workstreams.json")
data, err := workstreams.Serialize()
if err != nil {
return fmt.Errorf("failed to serialize workstreams: %w", err)
}
if err := os.WriteFile(workstreamsPath, data, 0644); err != nil {
return fmt.Errorf("failed to write workstreams file: %w", err)
}
return nil
}
// validateCommitMessage validates the commit message
func validateCommitMessage(message string) error {
// Check if message is empty

107
internal/commands/switch.go Normal file
View File

@ -0,0 +1,107 @@
package commands
import (
"fmt"
"os"
"git.dws.rip/DWS/onyx/internal/core"
"github.com/spf13/cobra"
)
// NewSwitchCmd creates the switch command
func NewSwitchCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "switch <name>",
Short: "Switch to a different workstream",
Long: `Switch to a different workstream.
This command will:
1. Save the current ephemeral state (handled by the daemon)
2. Load the target workstream
3. Checkout the latest commit in the target workstream
4. Update the current workstream pointer
5. Restore the workspace state
The operation is logged to the action log, so you can undo it if needed.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runSwitch(args[0])
},
}
return cmd
}
// runSwitch executes the switch command
func runSwitch(name string) error {
// Get current directory
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
// Check if this is an Onyx repository
if !core.IsOnyxRepo(cwd) {
return fmt.Errorf("not an Onyx repository")
}
// Open the repository
repo, err := core.Open(cwd)
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}
defer repo.Close()
// Create workstream manager
wsManager := core.NewWorkstreamManager(repo)
// Get current workstream name before switching
currentName, err := wsManager.GetCurrentWorkstreamName()
if err != nil {
currentName = "none"
}
// Check if we're already on the target workstream
if currentName == name {
fmt.Printf("Already on workstream '%s'\n", name)
return nil
}
// Use ExecuteWithTransaction to capture state_before and state_after
err = core.ExecuteWithTransaction(repo, "switch", fmt.Sprintf("Switched from '%s' to '%s'", currentName, name), func() error {
return wsManager.SwitchWorkstream(name)
})
if err != nil {
return err
}
// Get the workstream we just switched to
targetWorkstream, err := wsManager.GetCurrentWorkstream()
if err != nil {
return fmt.Errorf("failed to get workstream after switch: %w", err)
}
// Display success message
fmt.Printf("Switched to workstream '%s'\n", name)
// Show workstream info
commitCount := targetWorkstream.GetCommitCount()
if commitCount == 0 {
fmt.Printf("\nThis is a new workstream based on '%s' with no commits yet.\n", targetWorkstream.BaseBranch)
fmt.Printf("Make changes and save them with 'onx save -m \"message\"'\n")
} else {
commitText := "commit"
if commitCount != 1 {
commitText = "commits"
}
fmt.Printf("\nThis workstream has %d %s.\n", commitCount, commitText)
// Show the latest commit
if latestCommit, err := targetWorkstream.GetLatestCommit(); err == nil {
fmt.Printf("Latest commit: %s\n", latestCommit.Message)
}
}
return nil
}