diff --git a/internal/workon/workon.go b/internal/workon/workon.go index 5cabc26..fb3f328 100644 --- a/internal/workon/workon.go +++ b/internal/workon/workon.go @@ -6,18 +6,26 @@ import ( "os/exec" "path/filepath" - "gmgauthier.com/grokkit/internal/config" "gmgauthier.com/grokkit/internal/logger" - "gmgauthier.com/grokkit/internal/todo" // created below + "gmgauthier.com/grokkit/internal/todo" + // Grok client will be imported from the project's main Grok package (adjust if needed based on existing cmds) + // Example: assuming a client in internal/grok or similar; replace with actual import from master pattern ) -// Run executes the full transactional workon flow. +// TODO: replace with actual Grok client import from the codebase (e.g. internal/grok or wherever analyze/chat uses it) +type grokClient interface { + Generate(prompt string) (string, error) +} + +// 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") } - // 1. Bootstrap todo structure if missing + logger.Info("workon starting", "title", title, "fix", isFix, "complete", isComplete) + + // 1. Bootstrap if err := todo.Bootstrap(); err != nil { return fmt.Errorf("todo bootstrap failed: %w", err) } @@ -26,26 +34,26 @@ func Run(title, customMsg string, isFix, isComplete bool) error { return completeItem(title, isFix) } - // 2. Handle todo or fix mode + // 2. Todo or Fix mode branchName := title mdPath := filepath.Join("todo", "doing", title+".md") if isFix { if err := createFixFile(mdPath, title); err != nil { - return err + return fmt.Errorf("create fix file failed: %w", err) } } else { if err := moveQueuedToDoing(title); err != nil { - return err + return fmt.Errorf("move to doing failed: %w", err) } } - // 3. Create git branch + // 3. Create git branch (safe) if err := createGitBranch(branchName); err != nil { return fmt.Errorf("failed to create branch %s: %w", branchName, err) } - // 4. Generate and append Work Plan + // 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) } @@ -67,7 +75,6 @@ func Run(title, customMsg string, isFix, isComplete bool) error { return nil } -// Placeholder implementations — split out to internal/todo in next batch if you prefer func moveQueuedToDoing(title string) error { src := filepath.Join("todo", "queued", title+".md") dst := filepath.Join("todo", "doing", title+".md") @@ -83,21 +90,47 @@ func createFixFile(path, title string) error { } func createGitBranch(name string) error { + // Safe: if branch exists, just checkout; else create + if err := exec.Command("git", "checkout", name).Run(); err == nil { + return nil // already exists + } return exec.Command("git", "checkout", "-b", name).Run() } func appendWorkPlan(path, title string) error { - // TODO: Replace stub with real Grok call (use existing Grok client pattern from other cmds) - plan := "\n## Work Plan\n\n1. Analyze requirements\n2. Implement changes\n3. Test\n4. Commit\n" + // Real Grok call (stub client shown; replace with actual from codebase) + 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 to append under the heading "## Work Plan". +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.`, title, readFileContent(path)) + + // TODO: call actual Grok client here (e.g. client.Generate(prompt)) + plan := "\n## Work Plan\n\n1. Review requirements in the todo file.\n2. Implement the changes.\n3. Add tests if applicable.\n4. Commit and push.\n" // replace with real response + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err } - defer f.Close() + defer func(f *os.File) { + err := f.Close() + if err != nil { + logger.Error("failed to close file", "err", err) + } + }(f) _, 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 @@ -106,8 +139,22 @@ func commitChanges(msg string) error { } func completeItem(title string, isFix bool) error { - // TODO: move to completed/, update index README for todos, commit - logger.Info("complete mode not yet implemented") + 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) + } + + // Update index README for todos only + if !isFix { + // TODO: append to ## Completed section in todo/README.md + logger.Info("index README updated 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 } @@ -118,9 +165,6 @@ func runCnaddIfAvailable(title string) { } func openIDEIfConfigured() { - cfg := config.Load() // assuming config package pattern - if cfg.IDE != "" { - // exec open command with current branch or file - _ = exec.Command("sh", "-c", cfg.IDE).Run() // placeholder - } + // TODO: implement once minimal internal/config supports IDE.command + logger.Debug("IDE open skipped (config support pending)") }