← back to jmgilman__sow

Function bodies 590 total

All specs Real LLM only Function bodies
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 := r
project.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", fro
project.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 acc
project.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("fai
project.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, &notInstalled) {
		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, &notAuthenticated) {
		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 integr
project.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("B
project.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]().
				Tit
project.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 = StateComple
project.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)
		// Stay
project.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 {
		i
project.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-lay
cmd.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 whe
cmd.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 \
    --se
refs.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
}
‹ prevpage 3 / 12next ›