package workon import ( "fmt" "os" "os/exec" "path/filepath" "gmgauthier.com/grokkit/config" "gmgauthier.com/grokkit/internal/grok" "gmgauthier.com/grokkit/internal/logger" "gmgauthier.com/grokkit/internal/todo" ) // Run executes the full transactional workon flow per todo/doing/workon.md spec. func Run(title, customMsg string, isFix, isComplete bool) error { if title == "" { return fmt.Errorf("todo_item_title is required") } logger.Info("workon starting", "title", title, "fix", isFix, "complete", isComplete) // 1. Bootstrap todo structure if missing if err := todo.Bootstrap(); err != nil { return fmt.Errorf("todo bootstrap failed: %w", err) } if isComplete { return completeItem(title, isFix) } // 2. Handle todo or fix mode branchName := title mdPath := filepath.Join("todo", "doing", title+".md") if isFix { if err := createFixFile(mdPath, title); err != nil { return fmt.Errorf("create fix file failed: %w", err) } } else { if err := moveQueuedToDoing(title); err != nil { return fmt.Errorf("move to doing failed: %w", err) } } // 3. Create git branch (safe) if err := createGitBranch(branchName); err != nil { return fmt.Errorf("failed to create branch %s: %w", branchName, err) } // 4. Generate + append Work Plan via Grok if err := appendWorkPlan(mdPath, title); err != nil { return fmt.Errorf("failed to generate/append Work Plan: %w", err) } // 5. Commit commitMsg := customMsg if commitMsg == "" { commitMsg = fmt.Sprintf("Start working on %s", title) } if err := commitChanges(commitMsg); err != nil { return fmt.Errorf("commit failed: %w", err) } // 6. Optional post-steps (graceful) runCnaddIfAvailable(title) openIDEIfConfigured() logger.Info("workon transaction complete", "branch", branchName, "mode", map[bool]string{true: "fix", false: "todo"}[isFix]) return nil } func moveQueuedToDoing(title string) error { src := filepath.Join("todo", "queued", title+".md") dst := filepath.Join("todo", "doing", title+".md") if err := os.Rename(src, dst); err != nil { return fmt.Errorf("move %s -> doing failed: %w", title, err) } return nil } func createFixFile(path, title string) error { content := fmt.Sprintf("# %s\n\n## Work Plan\n\n", title) return os.WriteFile(path, []byte(content), 0644) } func createGitBranch(name string) error { // Safe: if branch already exists, just checkout it; else create new if err := exec.Command("git", "checkout", name).Run(); err == nil { return nil } return exec.Command("git", "checkout", "-b", name).Run() } func appendWorkPlan(path, title string) error { content := readFileContent(path) prompt := fmt.Sprintf(`You are helping implement a todo item titled "%s". Here is the current markdown content of the todo/fix file: %s Generate a concise, actionable **Work Plan** section. Use numbered steps. Be specific to this item. Include testing and commit notes where relevant. Output ONLY the markdown starting with "## Work Plan" — no extra text, no introduction.`, title, content) // Real Grok call using the project's standard client (StreamSilent for clean output) client := grok.NewClient() model := config.GetModel("workon", "grok-4-fast-non-reasoning") plan := client.StreamSilent([]map[string]string{ {"role": "system", "content": "You are a precise software engineering assistant."}, {"role": "user", "content": prompt}, }, model) // or pull model from config/env if available // Append the plan f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err } defer func() { if cerr := f.Close(); cerr != nil { logger.Error("failed to close todo file", "err", cerr) } }() _, err = f.WriteString(plan) return err } func readFileContent(path string) string { b, _ := os.ReadFile(path) return string(b) } func commitChanges(msg string) error { if err := exec.Command("git", "add", "todo/").Run(); err != nil { return err } return exec.Command("git", "commit", "-m", msg).Run() } func completeItem(title string, isFix bool) error { src := filepath.Join("todo", "doing", title+".md") dst := filepath.Join("todo", "completed", title+".md") if err := os.Rename(src, dst); err != nil { return fmt.Errorf("move to completed failed: %w", err) } // TODO: update todo/README.md index under ## Completed (for non-fixes) if !isFix { logger.Info("TODO: update index README for completed todo") } commitMsg := fmt.Sprintf("Complete work on %s", title) if err := commitChanges(commitMsg); err != nil { return fmt.Errorf("complete commit failed: %w", err) } return nil } func runCnaddIfAvailable(title string) { if _, err := exec.LookPath("cnadd"); err == nil { _ = exec.Command("cnadd", "log", fmt.Sprintf("started work on %s", title)).Run() } } func openIDEIfConfigured() { // TODO: implement via config once internal/config or root config supports IDE command // For now, silent graceful fallback per spec logger.Debug("IDE open skipped (config support pending)") }