Function bodies 590 total
project.isValidBranchName function · go · L272-L404 (133 LOC)cli/cmd/project/wizard_helpers.go
func isValidBranchName(name string) error {
// Trim and check empty
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("branch name cannot be empty")
}
// Check protected branches
if isProtectedBranch(name) {
return fmt.Errorf("cannot use protected branch name")
}
// Check for invalid patterns
if strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") {
return fmt.Errorf("branch name cannot start or end with /")
}
if strings.Contains(name, "..") {
return fmt.Errorf("branch name cannot contain double dots")
}
if strings.Contains(name, "//") {
return fmt.Errorf("branch name cannot contain consecutive slashes")
}
if strings.HasSuffix(name, ".lock") {
return fmt.Errorf("branch name cannot end with .lock")
}
// Check for invalid characters
invalidChars := []string{"~", "^", ":", "?", "*", "[", "\\", " "}
for _, char := range invalidChars {
if strings.Contains(name, char) {
return fmt.Errorf("branch name contains invalid character:project.validateProjectName function · go · L327-L336 (10 LOC)cli/cmd/project/wizard_helpers.go
func validateProjectName(name string, prefix string) error {
if strings.TrimSpace(name) == "" {
return fmt.Errorf("project name cannot be empty")
}
normalized := normalizeName(name)
branchName := prefix + normalized
return isValidBranchName(branchName)
}project.shouldCheckUncommittedChanges function · go · L354-L362 (9 LOC)cli/cmd/project/wizard_helpers.go
func shouldCheckUncommittedChanges(ctx *sow.Context, targetBranch string) (bool, error) {
currentBranch, err := ctx.Git().CurrentBranch()
if err != nil {
return false, fmt.Errorf("failed to get current branch: %w", err)
}
// Only check if we'll need to switch branches
return currentBranch == targetBranch, nil
}project.performUncommittedChangesCheckIfNeeded function · go · L382-L407 (26 LOC)cli/cmd/project/wizard_helpers.go
func performUncommittedChangesCheckIfNeeded(ctx *sow.Context, targetBranch string) error {
shouldCheck, err := shouldCheckUncommittedChanges(ctx, targetBranch)
if err != nil {
return err
}
if !shouldCheck {
return nil // No check needed
}
// Use existing validation
if err := git.CheckUncommittedChanges(ctx.Git()); err != nil {
// Enhance with user-friendly message
return fmt.Errorf(
"repository has uncommitted changes\n\n"+
"You are currently on branch '%s'.\n"+
"Creating a worktree requires switching to a different branch first.\n\n"+
"To fix:\n"+
" Commit: git add . && git commit -m \"message\"\n"+
" Or stash: git stash",
targetBranch,
)
}
return nil
}project.checkBranchState function · go · L422-L451 (30 LOC)cli/cmd/project/wizard_helpers.go
func checkBranchState(ctx *sow.Context, branchName string) (*BranchState, error) {
state := &BranchState{}
// Check if branch exists
branches, err := ctx.Git().Branches()
if err != nil {
return nil, fmt.Errorf("failed to list branches: %w", err)
}
for _, branch := range branches {
if branch == branchName {
state.BranchExists = true
break
}
}
// Check if worktree exists
worktreePath := git.WorktreePath(ctx.RepoRoot(), branchName)
if _, err := os.Stat(worktreePath); err == nil {
state.WorktreeExists = true
// If worktree exists, check for project
projectStatePath := filepath.Join(worktreePath, ".sow", "project", "state.yaml")
if _, err := os.Stat(projectStatePath); err == nil {
state.ProjectExists = true
}
}
return state, nil
}project.canCreateProject function · go · L459-L472 (14 LOC)cli/cmd/project/wizard_helpers.go
func canCreateProject(state *BranchState, branchName string) error {
// Check if project already exists
if state.ProjectExists {
return fmt.Errorf("branch '%s' already has a project", branchName)
}
// Check for inconsistent state (worktree without project)
if state.WorktreeExists && !state.ProjectExists {
return fmt.Errorf("worktree exists but project missing for branch '%s'", branchName)
}
// Creation is allowed
return nil
}project.validateProjectExists function · go · L481-L500 (20 LOC)cli/cmd/project/wizard_helpers.go
func validateProjectExists(ctx *sow.Context, branchName string) error {
state, err := checkBranchState(ctx, branchName)
if err != nil {
return err
}
if !state.BranchExists {
return fmt.Errorf("branch '%s' does not exist", branchName)
}
if !state.WorktreeExists {
return fmt.Errorf("worktree for branch '%s' does not exist", branchName)
}
if !state.ProjectExists {
return fmt.Errorf("project for branch '%s' does not exist", branchName)
}
return nil
}Source: Repobility analyzer · https://repobility.com
project.listExistingProjects function · go · L508-L529 (22 LOC)cli/cmd/project/wizard_helpers.go
func listExistingProjects(ctx *sow.Context) ([]string, error) {
branches, err := ctx.Git().Branches()
if err != nil {
return nil, fmt.Errorf("failed to list branches: %w", err)
}
var projects []string
for _, branch := range branches {
state, err := checkBranchState(ctx, branch)
if err != nil {
return nil, err
}
if state.ProjectExists {
projects = append(projects, branch)
}
}
// Sort alphabetically for consistent UI
sort.Strings(projects)
return projects, nil
}project.listProjects function · go · L544-L596 (53 LOC)cli/cmd/project/wizard_helpers.go
func listProjects(ctx *sow.Context) ([]ProjectInfo, error) {
// Construct worktrees directory path using MainRepoRoot
// This works whether we're in a worktree or the main repo
worktreesDir := filepath.Join(ctx.MainRepoRoot(), ".sow", "worktrees")
// Check if worktrees directory exists
if _, err := os.Stat(worktreesDir); err != nil {
if os.IsNotExist(err) {
// Missing worktrees directory is not an error - just means no projects exist
return []ProjectInfo{}, nil
}
return nil, fmt.Errorf("failed to stat worktrees directory: %w", err)
}
var projects []ProjectInfo
// Walk the worktrees directory tree to find all state.yaml files
err := filepath.Walk(worktreesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Look for state.yaml files in .sow/project/ subdirectories
if info.IsDir() || info.Name() != "state.yaml" {
return nil
}
// Check if this is a project state file (path ends with .sow/project/state.yaml)project.processProjectState function · go · L600-L645 (46 LOC)cli/cmd/project/wizard_helpers.go
func processProjectState(path, worktreesDir string, info os.FileInfo) *ProjectInfo {
// Extract branch name from path
// Path structure: worktreesDir/branchName/.sow/project/state.yaml
worktreePath := filepath.Dir(filepath.Dir(filepath.Dir(path)))
branchName, err := filepath.Rel(worktreesDir, worktreePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to extract branch name from %s: %v\n", path, err)
return nil
}
// Create a context for this worktree
worktreeCtx, err := sow.NewContext(worktreePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to create context for %s: %v\n", branchName, err)
return nil
}
// Load project state
proj, err := cmdutil.LoadProject(context.Background(), worktreeCtx)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to load project state for %s: %v\n", branchName, err)
return nil
}
// Count tasks across ALL phases
var tasksCompleted, tasksTotal int
for _, phase := range proj.Phases {
for _, task := rproject.formatProjectProgress function · go · L649-L659 (11 LOC)cli/cmd/project/wizard_helpers.go
func formatProjectProgress(proj ProjectInfo) string {
// Capitalize first letter of type
typeName := strings.ToUpper(proj.Type[:1]) + proj.Type[1:]
if proj.TasksTotal > 0 {
return fmt.Sprintf("%s: %s, %d/%d tasks completed",
typeName, proj.Phase, proj.TasksCompleted, proj.TasksTotal)
}
return fmt.Sprintf("%s: %s", typeName, proj.Phase)
}project.validateStateTransition function · go · L665-L691 (27 LOC)cli/cmd/project/wizard_helpers.go
func validateStateTransition(from, to WizardState) error {
// Define valid transitions
validTransitions := map[WizardState][]WizardState{
StateEntry: {StateCreateSource, StateProjectSelect, StateCancelled},
StateCreateSource: {StateIssueSelect, StateTypeSelect, StateCancelled},
StateIssueSelect: {StateTypeSelect, StateCreateSource, StateCancelled},
StateTypeSelect: {StateFileSelect, StateCancelled},
StateNameEntry: {StateFileSelect, StateCancelled},
StateFileSelect: {StatePromptEntry, StateCancelled},
StatePromptEntry: {StateComplete, StateCancelled},
StateProjectSelect: {StateContinuePrompt, StateCancelled},
StateContinuePrompt: {StateComplete, StateCancelled},
}
allowed, exists := validTransitions[from]
if !exists {
return fmt.Errorf("unknown source state: %s", from)
}
for _, validTo := range allowed {
if validTo == to {
return nil // Transition is valid
}
}
return fmt.Errorf("invalid transition from %s to %s", froproject.formatError function · go · L708-L722 (15 LOC)cli/cmd/project/wizard_helpers.go
func formatError(problem string, howToFix string, nextSteps string) string {
var parts []string
if problem != "" {
parts = append(parts, problem)
}
if howToFix != "" {
parts = append(parts, howToFix)
}
if nextSteps != "" {
parts = append(parts, nextSteps)
}
return strings.Join(parts, "\n\n")
}project.showErrorWithOptions function · go · L742-L778 (37 LOC)cli/cmd/project/wizard_helpers.go
func showErrorWithOptions(message string, options map[string]string) (string, error) {
// Skip interactive prompts in test mode
if os.Getenv("SOW_TEST") == "1" {
debugLog("ErrorWithOptions", "%s", message)
// Return first option key in test mode
for key := range options {
return key, nil
}
return "", nil
}
var selected string
// Convert map to huh options
var huhOptions []huh.Option[string]
for key, label := range options {
huhOptions = append(huhOptions, huh.NewOption(label, key))
}
form := huh.NewForm(
huh.NewGroup(
huh.NewNote().
Title("Error").
Description(message),
huh.NewSelect[string]().
Title("What would you like to do?").
Options(huhOptions...).
Value(&selected),
),
)
if err := form.Run(); err != nil {
return "", err
}
return selected, nil
}project.errorProtectedBranch function · go · L782-L788 (7 LOC)cli/cmd/project/wizard_helpers.go
func errorProtectedBranch(branchName string) string {
return formatError(
fmt.Sprintf("Cannot create project on protected branch '%s'", branchName),
"Projects must be created on feature branches.",
"Action: Choose a different project name",
)
}Repobility · severity-and-effort ranking · https://repobility.com
project.errorIssueAlreadyLinked function · go · L792-L798 (7 LOC)cli/cmd/project/wizard_helpers.go
func errorIssueAlreadyLinked(issueNumber int, linkedBranch string) string {
return formatError(
fmt.Sprintf("Issue #%d already has a linked branch: %s", issueNumber, linkedBranch),
"To continue working on this issue:\n Select \"Continue existing project\" from the main menu",
"",
)
}project.errorBranchHasProject function · go · L802-L808 (7 LOC)cli/cmd/project/wizard_helpers.go
func errorBranchHasProject(branchName string, projectName string) string {
return formatError(
fmt.Sprintf("Branch '%s' already has a project", branchName),
"To continue this project:\n Select \"Continue existing project\" from the main menu",
fmt.Sprintf("To create a different project:\n Choose a different project name (currently: \"%s\")", projectName),
)
}project.errorUncommittedChanges function · go · L812-L824 (13 LOC)cli/cmd/project/wizard_helpers.go
func errorUncommittedChanges(currentBranch string) string {
return formatError(
"Repository has uncommitted changes",
fmt.Sprintf(
"You are currently on branch '%s'.\n"+
"Creating a worktree requires switching to a different branch first.",
currentBranch,
),
"To fix:\n"+
" Commit: git add . && git commit -m \"message\"\n"+
" Or stash: git stash",
)
}project.errorInconsistentState function · go · L828-L846 (19 LOC)cli/cmd/project/wizard_helpers.go
func errorInconsistentState(branchName string, worktreePath string) string {
return formatError(
"Worktree exists but project missing",
fmt.Sprintf(
"Branch '%s' has a worktree at %s\n"+
"but no .sow/project/ directory.",
branchName,
worktreePath,
),
fmt.Sprintf(
"To fix:\n"+
" 1. Remove worktree: git worktree remove %s\n"+
" 2. Delete directory: rm -rf %s\n"+
" 3. Try creating project again",
branchName,
worktreePath,
),
)
}project.errorGitHubCLIMissing function · go · L850-L859 (10 LOC)cli/cmd/project/wizard_helpers.go
func errorGitHubCLIMissing() string {
return formatError(
"GitHub CLI not found",
"The 'gh' command is required for GitHub issue integration.\n\n"+
"To install:\n"+
" macOS: brew install gh\n"+
" Linux: See https://cli.github.com/",
"Or select \"From branch name\" instead.",
)
}project.wrapValidationError function · go · L864-L875 (12 LOC)cli/cmd/project/wizard_helpers.go
func wrapValidationError(err error, context string) error {
if err == nil {
return nil
}
// Wrap the error with context
if context != "" {
return fmt.Errorf("%s: %w", context, err)
}
return err
}project.formatGitHubError function · go · L888-L934 (47 LOC)cli/cmd/project/wizard_helpers.go
func formatGitHubError(err error) string {
var ghErr git.ErrGHCommand
if !errors.As(err, &ghErr) {
// Not a GitHub command error, return generic message
return fmt.Sprintf("GitHub operation failed: %v", err)
}
stderr := strings.ToLower(ghErr.Stderr)
// Check for rate limit (most specific)
if strings.Contains(stderr, "rate limit") {
return "GitHub API rate limit exceeded.\n\n" +
"To fix:\n" +
" Wait a few minutes and try again\n" +
" Or run: gh auth login (for higher limits)"
}
// Check for network errors
if strings.Contains(stderr, "network") ||
strings.Contains(stderr, "connection") ||
strings.Contains(stderr, "timeout") ||
strings.Contains(stderr, "unreachable") {
return "Cannot reach GitHub.\n\n" +
"Check your internet connection and try again."
}
// Check for not found
if strings.Contains(stderr, "not found") ||
strings.Contains(stderr, "does not exist") {
return "Resource not found.\n\n" +
"Check the issue number or repository accproject.checkIssueLinkedBranch function · go · L942-L957 (16 LOC)cli/cmd/project/wizard_helpers.go
func checkIssueLinkedBranch(github git.GitHubClient, issueNumber int) error {
branches, err := github.GetLinkedBranches(issueNumber)
if err != nil {
// Format the GitHub error for user display
return errors.New(formatGitHubError(err))
}
// If no linked branches, OK to create
if len(branches) == 0 {
return nil
}
// Issue already has linked branch - show error
branchName := branches[0].Name
return errors.New(errorIssueAlreadyLinked(issueNumber, branchName))
}Repobility · open methodology · https://repobility.com/research/
project.filterIssuesBySowLabel function · go · L964-L974 (11 LOC)cli/cmd/project/wizard_helpers.go
func filterIssuesBySowLabel(issues []git.Issue) []git.Issue {
var filtered []git.Issue
for _, issue := range issues {
if issue.HasLabel("sow") {
filtered = append(filtered, issue)
}
}
return filtered
}project.discoverKnowledgeFiles function · go · L994-L1037 (44 LOC)cli/cmd/project/wizard_helpers.go
func discoverKnowledgeFiles(knowledgeDir string) ([]string, error) {
// Check if directory exists
if _, err := os.Stat(knowledgeDir); err != nil {
if os.IsNotExist(err) {
// Directory doesn't exist - not an error, just return empty slice
return []string{}, nil
}
// Other stat errors (permission denied, etc.) should be returned
return nil, fmt.Errorf("failed to stat knowledge directory: %w", err)
}
var files []string
// Walk the directory tree
err := filepath.Walk(knowledgeDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
// Error accessing path - propagate it
return err
}
// Skip directories - we only want files
if info.IsDir() {
return nil
}
// Get relative path from knowledge directory
relPath, err := filepath.Rel(knowledgeDir, path)
if err != nil {
return fmt.Errorf("failed to get relative path: %w", err)
}
files = append(files, relPath)
return nil
})
if err != nil {
return nil, fmt.Errorf("faiproject.NewWizard function · go · L49-L60 (12 LOC)cli/cmd/project/wizard_state.go
func NewWizard(cmd *cobra.Command, ctx *sow.Context, claudeFlags []string) *Wizard {
gh, _ := git.NewGitHubClient()
return &Wizard{
state: StateEntry,
ctx: ctx,
choices: make(map[string]interface{}),
claudeFlags: claudeFlags,
cmd: cmd,
github: gh,
}
}project.Wizard.Run method · go · L63-L79 (17 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) Run() error {
debugLog("Wizard", "Starting wizard in state=%s", w.state)
for w.state != StateComplete && w.state != StateCancelled {
if err := w.handleState(); err != nil {
return err
}
}
if w.state == StateCancelled {
debugLog("Wizard", "User cancelled wizard")
return nil // User cancelled, not an error
}
debugLog("Wizard", "Wizard complete, finalizing project")
return w.finalize()
}project.Wizard.handleState method · go · L82-L105 (24 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleState() error {
switch w.state {
case StateEntry:
return w.handleEntry()
case StateCreateSource:
return w.handleCreateSource()
case StateIssueSelect:
return w.handleIssueSelect()
case StateTypeSelect:
return w.handleTypeSelect()
case StateNameEntry:
return w.handleNameEntry()
case StateFileSelect:
return w.handleFileSelect()
case StatePromptEntry:
return w.handlePromptEntry()
case StateProjectSelect:
return w.handleProjectSelect()
case StateContinuePrompt:
return w.handleContinuePrompt()
default:
return fmt.Errorf("unknown state: %s", w.state)
}
}project.Wizard.handleEntry method · go · L108-L144 (37 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleEntry() error {
var action string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("What would you like to do?").
Options(
huh.NewOption("Create new project", "create"),
huh.NewOption("Continue existing project", "continue"),
huh.NewOption("Cancel", "cancel"),
).
Value(&action),
),
)
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
w.state = StateCancelled
return nil
}
return fmt.Errorf("entry screen error: %w", err)
}
w.choices["action"] = action
switch action {
case "create":
w.state = StateCreateSource
case "continue":
w.state = StateProjectSelect
case "cancel":
w.state = StateCancelled
}
return nil
}project.Wizard.handleCreateSource method · go · L147-L183 (37 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleCreateSource() error {
var source string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("How would you like to create the project?").
Options(
huh.NewOption("From GitHub issue", "issue"),
huh.NewOption("From branch name", "branch"),
huh.NewOption("Cancel", "cancel"),
).
Value(&source),
),
)
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
w.state = StateCancelled
return nil
}
return fmt.Errorf("create source screen error: %w", err)
}
w.choices["source"] = source
switch source {
case "issue":
w.state = StateIssueSelect
case "branch":
w.state = StateTypeSelect
case "cancel":
w.state = StateCancelled
}
return nil
}project.Wizard.handleIssueSelect method · go · L186-L237 (52 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleIssueSelect() error {
debugLog("Wizard", "State=%s", w.state)
// Validate GitHub CLI is available and authenticated
if err := w.github.CheckAvailability(); err != nil {
return w.handleGitHubError(err)
}
// Fetch issues with 'sow' label using spinner
var issues []git.Issue
var fetchErr error
debugLog("GitHub", "Calling gh issue list --label sow --state open")
err := withSpinner("Fetching issues from GitHub...", func() error {
issues, fetchErr = w.github.ListIssues("sow", "open")
return fetchErr
})
if err != nil {
debugLog("GitHub", "Failed to fetch issues: %v", err)
errorMsg := fmt.Sprintf("Failed to fetch issues: %v\n\n"+
"This may be a network issue or a GitHub API problem.\n"+
"Please try again or select 'From branch name' instead.", err)
_ = showError(errorMsg)
w.state = StateCreateSource
return nil
}
debugLog("GitHub", "Fetched %d issues", len(issues))
for _, issue := range issues {
debugLog("GitHub", "Issue #%d: %s"Want this analysis on your repo? https://repobility.com/scan/
project.Wizard.handleGitHubError method · go · L241-L406 (166 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleGitHubError(err error) error {
var errorMsg string
var fallbackMsg string
// Determine error type using errors.As for wrapped error support
var notInstalled git.ErrGHNotInstalled
var notAuthenticated git.ErrGHNotAuthenticated
if errors.As(err, ¬Installed) {
errorMsg = "GitHub CLI not found\n\n" +
"The 'gh' command is required for GitHub issue integration.\n\n" +
"To install:\n" +
" macOS: brew install gh\n" +
" Linux: See https://cli.github.com/"
fallbackMsg = "Or select 'From branch name' instead."
} else if errors.As(err, ¬Authenticated) {
errorMsg = "GitHub CLI not authenticated\n\n" +
"Run the following command to authenticate:\n" +
" gh auth login\n\n" +
"Then try creating your project again."
fallbackMsg = "Or select 'From branch name' instead."
} else {
// Generic GitHub error
errorMsg = fmt.Sprintf("GitHub CLI error: %v", err)
fallbackMsg = "Select 'From branch name' to continue without GitHub integrproject.Wizard.handleTypeSelect method · go · L280-L320 (41 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleTypeSelect() error {
var selectedType string
// Check if we have issue context
_, hasIssue := w.choices["issue"].(*git.Issue)
// Build form with just type selection
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("What type of project?").
Options(getTypeOptions()...).
Value(&selectedType),
),
)
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
w.state = StateCancelled
return nil
}
return fmt.Errorf("type selection error: %w", err)
}
if selectedType == "cancel" {
w.state = StateCancelled
return nil
}
w.choices["type"] = selectedType
// Route based on context
if hasIssue {
// GitHub issue path: create branch then go to prompt entry
return w.createLinkedBranch()
}
// Branch name path: go to name entry
w.state = StateNameEntry
return nil
}project.Wizard.handleNameEntry method · go · L323-L406 (84 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleNameEntry() error {
var name string
projectType, ok := w.choices["type"].(string)
if !ok {
return fmt.Errorf("type choice not set or invalid")
}
prefix := getTypePrefix(projectType)
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Enter project name:").
Placeholder("e.g., Web Based Agents").
Value(&name).
Validate(func(s string) error {
// Validation 1: Not empty
if strings.TrimSpace(s) == "" {
return fmt.Errorf("project name cannot be empty")
}
// Validation 2: Not protected branch
normalized := normalizeName(s)
branchName := fmt.Sprintf("%s%s", prefix, normalized)
if w.ctx.Git().IsProtectedBranch(branchName) {
return fmt.Errorf("cannot use protected branch name")
}
// Validation 3: Valid git branch name
if err := isValidBranchName(branchName); err != nil {
return err
}
return nil
}),
// Real-time preview
huh.NewNote().
Title("Bproject.Wizard.handleFileSelect method · go · L411-L474 (64 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleFileSelect() error {
debugLog("Wizard", "State=%s", w.state)
// Discover knowledge files
knowledgeDir := filepath.Join(w.ctx.MainRepoRoot(), ".sow", "knowledge")
files, err := discoverKnowledgeFiles(knowledgeDir)
if err != nil {
// Log error but don't fail - just skip file selection
debugLog("FileSelect", "Failed to discover files: %v", err)
w.state = StatePromptEntry
return nil
}
// If no files exist, skip selection
if len(files) == 0 {
debugLog("FileSelect", "No knowledge files found, skipping")
w.state = StatePromptEntry
return nil
}
var selectedFiles []string
// In test mode, skip interactive form
if os.Getenv("SOW_TEST") == "1" {
debugLog("FileSelect", "Test mode: skipping interactive selection, storing empty list")
// Store empty list in test mode (files were discovered successfully)
w.choices["knowledge_files"] = selectedFiles
w.state = StatePromptEntry
return nil
}
// Build multi-select options
options := make([project.Wizard.handlePromptEntry method · go · L477-L537 (61 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handlePromptEntry() error {
var prompt string
// Build context display based on project source
var contextLines []string
// Check for issue context (GitHub issue path)
if issue, ok := w.choices["issue"].(*git.Issue); ok {
contextLines = append(contextLines,
fmt.Sprintf("Issue: #%d - %s", issue.Number, issue.Title))
}
// Show branch name
if branchName, ok := w.choices["branch"].(string); ok {
contextLines = append(contextLines, fmt.Sprintf("Branch: %s", branchName))
} else if name, ok := w.choices["name"].(string); ok {
// Branch name path - compute branch name for display
projectType, ok := w.choices["type"].(string)
if !ok {
projectType = "standard" // Default fallback
}
prefix := getTypePrefix(projectType)
normalized := normalizeName(name)
contextLines = append(contextLines,
fmt.Sprintf("Branch: %s%s", prefix, normalized))
}
// Add project type for clarity
if projectType, ok := w.choices["type"].(string); ok {
typeConfig :project.Wizard.handleProjectSelect method · go · L540-L621 (82 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleProjectSelect() error {
// 1. Discover projects with spinner
var projects []ProjectInfo
err := withSpinner("Discovering projects...", func() error {
var discoverErr error
projects, discoverErr = listProjects(w.ctx)
return discoverErr
})
if err != nil {
return fmt.Errorf("failed to discover projects: %w", err)
}
// 2. Handle empty list
if len(projects) == 0 {
fmt.Fprintln(os.Stderr, "\nNo existing projects found.")
w.state = StateCancelled
return nil
}
// 3. Build selection options
var selectedBranch string
options := make([]huh.Option[string], 0, len(projects)+1)
for _, proj := range projects {
progress := formatProjectProgress(proj)
label := fmt.Sprintf("%s - %s\n [%s]", proj.Branch, proj.Name, progress)
options = append(options, huh.NewOption(label, proj.Branch))
}
options = append(options, huh.NewOption("Cancel", "cancel"))
// 4. Show selection
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Titproject.Wizard.handleContinuePrompt method · go · L624-L666 (43 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleContinuePrompt() error {
// 1. Extract selected project
proj, ok := w.choices["project"].(ProjectInfo)
if !ok {
return fmt.Errorf("internal error: project choice not set or invalid")
}
// 2. Build context display
progress := formatProjectProgress(proj)
contextInfo := fmt.Sprintf(
"Project: %s\nBranch: %s\nState: %s",
proj.Name,
proj.Branch,
progress,
)
// 3. Show prompt entry form
var prompt string
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("What would you like to work on? (optional):").
Description(contextInfo + "\n\nPress Ctrl+E to open $EDITOR for multi-line input").
CharLimit(5000).
Value(&prompt).
EditorExtension(".md"),
),
)
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
w.state = StateCancelled
return nil
}
return fmt.Errorf("continuation prompt error: %w", err)
}
// 4. Save prompt and transition
w.choices["prompt"] = prompt
w.state = StateCompleproject.Wizard.showIssueSelectScreen method · go · L670-L744 (75 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) showIssueSelectScreen() error {
issues, ok := w.choices["issues"].([]git.Issue)
if !ok {
return fmt.Errorf("issues not found in choices")
}
var selectedIssueNumber int
// Build select options
options := make([]huh.Option[int], 0, len(issues)+1)
for _, issue := range issues {
label := fmt.Sprintf("#%d: %s", issue.Number, issue.Title)
options = append(options, huh.NewOption(label, issue.Number))
}
// Add cancel option
options = append(options, huh.NewOption("Cancel", -1))
// Create select form
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[int]().
Title("Select an issue (filtered by 'sow' label):").
Options(options...).
Value(&selectedIssueNumber),
),
)
if err := form.Run(); err != nil {
if errors.Is(err, huh.ErrUserAborted) {
w.state = StateCancelled
return nil
}
return fmt.Errorf("issue selection error: %w", err)
}
// Handle cancel
if selectedIssueNumber == -1 {
w.state = StateCancelled
return nil
}
/Source: Repobility analyzer · https://repobility.com
project.Wizard.createLinkedBranch method · go · L747-L789 (43 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) createLinkedBranch() error {
issue, ok := w.choices["issue"].(*git.Issue)
if !ok {
return fmt.Errorf("issue not found in choices")
}
projectType, ok := w.choices["type"].(string)
if !ok {
return fmt.Errorf("type not found in choices")
}
// Generate branch name: <prefix><issue-slug>-<number>
prefix := getTypePrefix(projectType)
issueSlug := normalizeName(issue.Title)
branchName := fmt.Sprintf("%s%s-%d", prefix, issueSlug, issue.Number)
// Create linked branch via gh issue develop with spinner
var createdBranch string
err := withSpinner("Creating linked branch...", func() error {
var err error
// Pass checkout=false because we use worktrees, not traditional checkout
createdBranch, err = w.github.CreateLinkedBranch(issue.Number, branchName, false)
return err
})
if err != nil {
errorMsg := fmt.Sprintf("Failed to create linked branch: %v\n\n"+
"This may be a GitHub API issue. Please try again.",
err)
_ = showError(errorMsg)
// Stayproject.Wizard.handleAlreadyLinkedError method · go · L793-L809 (17 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) handleAlreadyLinkedError(issueNumber int, branch git.LinkedBranch) error {
errorMsg := fmt.Sprintf(
"Issue #%d already has a linked branch: %s\n\n"+
"To continue working on this issue:\n"+
" Select \"Continue existing project\" from the main menu\n\n"+
"To work on a different issue:\n"+
" Select a different issue from the list",
issueNumber,
branch.Name,
)
_ = showError(errorMsg)
// Return to issue select to let user choose different issue
// Keep issues list in choices so we don't need to fetch again
return w.showIssueSelectScreen()
}project.Wizard.finalize method · go · L812-L827 (16 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) finalize() error {
// Determine which path we're on
action, ok := w.choices["action"].(string)
if !ok {
return fmt.Errorf("internal error: action choice not set")
}
switch action {
case "create":
return w.finalizeCreation()
case "continue":
return w.finalizeContinuation()
default:
return fmt.Errorf("internal error: unknown action: %s", action)
}
}project.Wizard.finalizeCreation method · go · L830-L915 (86 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) finalizeCreation() error {
// Extract wizard choices
name, ok := w.choices["name"].(string)
if !ok {
return fmt.Errorf("name choice not set or invalid")
}
branch, ok := w.choices["branch"].(string)
if !ok {
return fmt.Errorf("branch choice not set or invalid")
}
initialPrompt := ""
if prompt, ok := w.choices["prompt"].(string); ok {
initialPrompt = prompt
}
// Extract issue if present (GitHub issue path)
var issue *git.Issue
if issueData, ok := w.choices["issue"].(*git.Issue); ok {
issue = issueData
}
// Extract knowledge files if present
var knowledgeFiles []string
if files, ok := w.choices["knowledge_files"].([]string); ok {
knowledgeFiles = files
}
// Step 1: Conditional uncommitted changes check
currentBranch, err := w.ctx.Git().CurrentBranch()
if err != nil {
return fmt.Errorf("failed to get current branch: %w", err)
}
// Only check if we're on the branch we're trying to create a worktree for
if currentBranch == branch {
iproject.Wizard.finalizeContinuation method · go · L921-L974 (54 LOC)cli/cmd/project/wizard_state.go
func (w *Wizard) finalizeContinuation() error {
// 1. Extract choices
proj, ok := w.choices["project"].(ProjectInfo)
if !ok {
return fmt.Errorf("internal error: project choice not set or invalid")
}
userPrompt, ok := w.choices["prompt"].(string)
if !ok {
return fmt.Errorf("internal error: prompt choice not set or invalid")
}
// 2. Ensure worktree exists (idempotent)
worktreePath := git.WorktreePath(w.ctx.MainRepoRoot(), proj.Branch)
if err := git.EnsureWorktree(w.ctx.Git(), w.ctx.MainRepoRoot(), worktreePath, proj.Branch); err != nil {
return fmt.Errorf("failed to ensure worktree: %w", err)
}
// 3. Create worktree context
worktreeCtx, err := sow.NewContext(worktreePath)
if err != nil {
return fmt.Errorf("failed to create worktree context: %w", err)
}
// 4. Load fresh project state
projectState, err := cmdutil.LoadProject(context.Background(), worktreeCtx)
if err != nil {
return fmt.Errorf("failed to load project state: %w", err)
}
// 5. Generate 3-laycmd.NewPromptCmd function · go · L13-L58 (46 LOC)cli/cmd/prompt.go
func NewPromptCmd() *cobra.Command {
var listFlag bool
cmd := &cobra.Command{
Use: "prompt <type>",
Short: "Output guidance prompts for AI agents",
Long: `Output guidance prompts to stdout for AI agent consumption.
This command provides on-demand context and guidance for specific tasks.
AI agents can invoke this during exploration or other modes to get
detailed best practices and methodology guidance.
Examples:
sow prompt guidance/research
sow prompt guidance/design/adr
sow prompt guidance/implementer/base
sow prompt guidance/implementer/tdd
To see all available prompts:
sow prompt --list
The output is designed to be consumed by AI agents, providing focused
guidance without overwhelming the initial context window.`,
Args: func(_ *cobra.Command, args []string) error {
// If --list flag is set, no args required
if listFlag {
return nil
}
// Otherwise require exactly one arg
if len(args) != 1 {
return fmt.Errorf("requires exactly 1 arg whecmd.runPrompt function · go · L60-L76 (17 LOC)cli/cmd/prompt.go
func runPrompt(cmd *cobra.Command, promptType string) error {
// User provides the path exactly as shown in --list
// ListTemplates already strips "templates/" prefix and ".md" suffix
// So we just need to add them back
// "guidance/research" -> "templates/guidance/research.md"
templatePath := fmt.Sprintf("templates/%s.md", promptType)
// Render the template with no context
output, err := templates.Render(prompts.FS, templatePath, nil)
if err != nil {
// Provide helpful error with suggestion to list prompts
return fmt.Errorf("prompt type '%s' not found\n\nRun 'sow prompt --list' to see available prompts", promptType)
}
cmd.Println(output)
return nil
}cmd.listPrompts function · go · L78-L96 (19 LOC)cli/cmd/prompt.go
func listPrompts(cmd *cobra.Command) error {
availablePrompts, err := templates.ListTemplates(prompts.FS, "templates")
if err != nil {
return fmt.Errorf("failed to list prompts: %w", err)
}
cmd.Println("Available prompt types:")
cmd.Println()
// Only show guidance templates (not command/greet templates which require context)
for _, p := range availablePrompts {
// Filter to only guidance and modes (templates meant for on-demand fetching)
if strings.HasPrefix(p, "guidance/") || strings.HasPrefix(p, "modes/") {
cmd.Printf(" %s\n", p)
}
}
return nil
}Repobility · severity-and-effort ranking · https://repobility.com
refs.newAddCmd function · go · L12-L72 (61 LOC)cli/cmd/refs/add.go
func newAddCmd() *cobra.Command {
var (
id string
semantic string
link string
tags []string
description string
branch string
path string
local bool
)
cmd := &cobra.Command{
Use: "add <url>",
Short: "Add a new reference",
Long: `Add a new reference to external knowledge or code.
The reference type is automatically inferred from the URL scheme:
git+https://github.com/org/repo
git+ssh://[email protected]/org/repo
[email protected]:org/repo (SSH shorthand, auto-converted)
file:///absolute/path
Type-specific flags:
--branch, --path Only valid for git URLs
Examples:
# Add git ref with subpath
sow refs add git+https://github.com/acme/style-guides \
--link python-style \
--semantic knowledge \
--tags formatting,naming \
--description "Python coding standards" \
--path python/ \
--branch main
# Add local file ref
sow refs add file:///Users/josh/docs \
--link local-docs \
--serefs.runRefsAdd function · go · L74-L127 (54 LOC)cli/cmd/refs/add.go
func runRefsAdd(
c *cobra.Command,
args []string,
id string,
semantic string,
link string,
tags []string,
description string,
branch string,
path string,
local bool,
) error {
rawURL := args[0]
ctx := c.Context()
// Get context
sowCtx := cmdutil.GetContext(ctx)
// Create refs manager
mgr := refs.NewManager(sowCtx)
// Build options
opts := []refs.RefOption{
refs.WithRefLink(link),
refs.WithRefSemantic(semantic),
refs.WithRefDescription(description),
refs.WithRefLocal(local),
}
if id != "" {
opts = append(opts, refs.WithRefID(id))
}
if len(tags) > 0 {
opts = append(opts, refs.WithRefTags(tags...))
}
if branch != "" {
opts = append(opts, refs.WithRefBranch(branch))
}
if path != "" {
opts = append(opts, refs.WithRefPath(path))
}
// Add ref (handles all validation, type inference, caching, symlinking)
ref, err := mgr.Add(ctx, rawURL, opts...)
if err != nil {
return err
}
// Print confirmation
return printAddConfirmation(c, ref)
}refs.newInitCmd function · go · L10-L26 (17 LOC)cli/cmd/refs/init.go
func newInitCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize refs after cloning",
Long: `Initialize references by caching and symlinking.
Run this after cloning a repository to set up all configured refs.
Each ref is cached locally and symlinked into .sow/refs/.
Types that are not enabled on this system will be skipped with warnings.`,
RunE: func(cmd *cobra.Command, _ []string) error {
return runRefsInit(cmd)
},
}
return cmd
}