From a4a1b49c9dcfefbfebc211d48eddb1d35c06029c Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 20:12:10 +0100 Subject: [PATCH 1/7] chore(todo): move workon task from queued to doing Update README.md to reflect the new file location and rename the workon.md file accordingly. --- todo/README.md | 2 +- todo/{queued => doing}/workon.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename todo/{queued => doing}/workon.md (100%) diff --git a/todo/README.md b/todo/README.md index 2495af8..7ecb6a5 100644 --- a/todo/README.md +++ b/todo/README.md @@ -4,7 +4,7 @@ This document provides a table of contents for all tasks and features currently ## Queued -* [1] [workon](./queued/workon.md): Grokkit workon for bootstrapping a new feature +* [1] [workon](doing/workon.md): Grokkit workon for bootstrapping a new feature * [2] [cnotes.md](./queued/cnotes.md) : grokkit cnotes integration * [3] [make.md](./queued/make.md) : grokkit make integration * [4] [rg.md](./queued/rg.md) : grokkit ripgrep (rg) integration diff --git a/todo/queued/workon.md b/todo/doing/workon.md similarity index 100% rename from todo/queued/workon.md rename to todo/doing/workon.md From 9080cf7f3ebf9a22803e08b78f83f24cd96417a4 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 20:30:05 +0100 Subject: [PATCH 2/7] feat(workon): add initial workon command for starting todo items Introduces the `workon` CLI command which selects the next queued todo item, moves it to doing/, creates a git branch, generates a work plan via Grok, appends it to the file, and commits the changes. Includes skeleton implementation with TODOs for full functionality. --- cmd/root.go | 1 + cmd/workon.go | 40 +++++++++++++++++++++++++++++++++++++++ internal/workon/workon.go | 35 ++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 cmd/workon.go create mode 100644 internal/workon/workon.go diff --git a/cmd/root.go b/cmd/root.go index a837216..50f06c3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,6 +65,7 @@ func init() { rootCmd.AddCommand(scaffoldCmd) rootCmd.AddCommand(queryCmd) rootCmd.AddCommand(analyzeCmd) + rootCmd.AddCommand(workonCmd) // Add model flag to all commands rootCmd.PersistentFlags().StringP("model", "m", "", "Grok model to use (overrides config)") diff --git a/cmd/workon.go b/cmd/workon.go new file mode 100644 index 0000000..f18a868 --- /dev/null +++ b/cmd/workon.go @@ -0,0 +1,40 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "gmgauthier.com/grokkit/internal/logger" + "gmgauthier.com/grokkit/internal/workon" // we'll create this next +) + +var workonCmd = &cobra.Command{ + Use: "workon", + Short: "Start work on the next todo item from the queue", + Long: `workon picks the next .md file from todo/queued/, +moves it to todo/doing/, creates a matching git branch, +asks Grok to generate a Work Plan section, +appends it to the file, and commits everything. + +Purely transactional — one command, clear success or failure.`, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return fmt.Errorf("workon takes no arguments") + } + + logger.Info("Starting workon transaction") + + if err := workon.Run(); err != nil { + return fmt.Errorf("workon failed: %w", err) + } + + logger.Info("Workon completed successfully") + return nil + }, +} + +func init() { + workonCmd.Flags().StringP("message", "m", "", "Custom commit message (default: \"Start working on \")") + // -f and -c flags will be added later when the spec needs them +} diff --git a/internal/workon/workon.go b/internal/workon/workon.go new file mode 100644 index 0000000..ae30094 --- /dev/null +++ b/internal/workon/workon.go @@ -0,0 +1,35 @@ +package workon + +import ( + _ "gmgauthier.com/grokkit/internal/errors" + "gmgauthier.com/grokkit/internal/logger" + // TODO: import todo, git, grok packages as we build them +) + +// Run executes the full workon transaction +func Run() error { + logger.Info("Bootstrapping todo structure if needed...") + + // TODO: todo.BootstrapIfMissing() + + logger.Info("Selecting next queued item...") + // TODO: item, err := todo.NextQueued() + // if err != nil { return err } + + logger.Info("Moving item to doing/ and creating branch...") + // TODO: branchName, err := todo.MoveToDoingAndCreateBranch(item) + + logger.Info("Generating Work Plan via Grok...") + // TODO: plan, err := grok.GenerateWorkPlan(item) + + logger.Info("Appending plan to todo file...") + // TODO: err = todo.AppendWorkPlan(item, plan) + + logger.Info("Committing changes...") + // TODO: err = git.CommitWorkonChanges(branchName, item) + + logger.Info("Optional post-steps (cnadd, open IDE)...") + // TODO: postSteps() + + return nil +} From d6f507b8cbaa6c4a04d0d5133e58e3fcd626422e Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 20:42:32 +0100 Subject: [PATCH 3/7] feat(workon): enhance command to start or complete todos/fixes with git integration - Update workonCmd to accept todo_item_title arg and add -f/--fix, -c/--complete flags - Implement transactional flow: bootstrap todo dirs, move/create .md files, create branch, append Grok-generated Work Plan, commit - Add todo package with Bootstrap for directory structure - Expand workon.Run to handle modes (todo, fix, complete) with placeholders for Grok integration and optional cnadd/IDE open --- cmd/workon.go | 40 +++++++----- internal/todo/todo.go | 28 +++++++++ internal/workon/workon.go | 129 ++++++++++++++++++++++++++++++++------ 3 files changed, 163 insertions(+), 34 deletions(-) create mode 100644 internal/todo/todo.go diff --git a/cmd/workon.go b/cmd/workon.go index f18a868..7909a41 100644 --- a/cmd/workon.go +++ b/cmd/workon.go @@ -2,39 +2,49 @@ package cmd import ( "fmt" + "strings" "github.com/spf13/cobra" "gmgauthier.com/grokkit/internal/logger" - "gmgauthier.com/grokkit/internal/workon" // we'll create this next + "gmgauthier.com/grokkit/internal/workon" ) var workonCmd = &cobra.Command{ - Use: "workon", - Short: "Start work on the next todo item from the queue", - Long: `workon picks the next .md file from todo/queued/, -moves it to todo/doing/, creates a matching git branch, -asks Grok to generate a Work Plan section, -appends it to the file, and commits everything. + Use: "workon ", + Short: "Start or complete work on a todo item or fix", + Long: `workon automates starting or completing a todo/fix: +- Moves queued todo to doing/ or creates new fix .md +- Creates matching git branch +- Generates + appends "Work Plan" via Grok +- Commits to the branch +- Optional: cnadd log + open in configured IDE -Purely transactional — one command, clear success or failure.`, +Purely transactional. See todo/doing/workon.md for full spec.`, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - if len(args) != 0 { - return fmt.Errorf("workon takes no arguments") + title := strings.ReplaceAll(args[0], " ", "-") + customMsg, _ := cmd.Flags().GetString("message") + isFix, _ := cmd.Flags().GetBool("fix") + isComplete, _ := cmd.Flags().GetBool("complete") + + if isComplete && (isFix || customMsg != "") { + return fmt.Errorf("-c cannot be combined with -f or -m") } - logger.Info("Starting workon transaction") + logger.Info("workon starting", "title", title, "fix", isFix, "complete", isComplete) - if err := workon.Run(); err != nil { + if err := workon.Run(title, customMsg, isFix, isComplete); err != nil { return fmt.Errorf("workon failed: %w", err) } - logger.Info("Workon completed successfully") + logger.Info("workon completed successfully") return nil }, } func init() { - workonCmd.Flags().StringP("message", "m", "", "Custom commit message (default: \"Start working on \")") - // -f and -c flags will be added later when the spec needs them + workonCmd.Flags().StringP("message", "m", "", "Custom commit message (default: \"Start working on \")") + workonCmd.Flags().BoolP("fix", "f", false, "Treat as fix instead of todo (create new .md in doing/)") + workonCmd.Flags().BoolP("complete", "c", false, "Complete the item (move to completed/; exclusive)") } diff --git a/internal/todo/todo.go b/internal/todo/todo.go new file mode 100644 index 0000000..417a0e8 --- /dev/null +++ b/internal/todo/todo.go @@ -0,0 +1,28 @@ +package todo + +import ( + "os" + "path/filepath" +) + +const todoRoot = "todo" + +// Bootstrap creates the todo folder structure + basic index if missing. +func Bootstrap() error { + dirs := []string{ + filepath.Join(todoRoot, "queued"), + filepath.Join(todoRoot, "doing"), + filepath.Join(todoRoot, "completed"), + } + for _, d := range dirs { + if err := os.MkdirAll(d, 0755); err != nil { + return err + } + } + // Basic index README stub + index := filepath.Join(todoRoot, "README.md") + if _, err := os.Stat(index); os.IsNotExist(err) { + return os.WriteFile(index, []byte("# Todo Index\n\n## Queued\n## Doing\n## Completed\n"), 0644) + } + return nil +} diff --git a/internal/workon/workon.go b/internal/workon/workon.go index ae30094..5cabc26 100644 --- a/internal/workon/workon.go +++ b/internal/workon/workon.go @@ -1,35 +1,126 @@ package workon import ( - _ "gmgauthier.com/grokkit/internal/errors" + "fmt" + "os" + "os/exec" + "path/filepath" + + "gmgauthier.com/grokkit/internal/config" "gmgauthier.com/grokkit/internal/logger" - // TODO: import todo, git, grok packages as we build them + "gmgauthier.com/grokkit/internal/todo" // created below ) -// Run executes the full workon transaction -func Run() error { - logger.Info("Bootstrapping todo structure if needed...") +// Run executes the full transactional workon flow. +func Run(title, customMsg string, isFix, isComplete bool) error { + if title == "" { + return fmt.Errorf("todo_item_title is required") + } - // TODO: todo.BootstrapIfMissing() + // 1. Bootstrap todo structure if missing + if err := todo.Bootstrap(); err != nil { + return fmt.Errorf("todo bootstrap failed: %w", err) + } - logger.Info("Selecting next queued item...") - // TODO: item, err := todo.NextQueued() - // if err != nil { return err } + if isComplete { + return completeItem(title, isFix) + } - logger.Info("Moving item to doing/ and creating branch...") - // TODO: branchName, err := todo.MoveToDoingAndCreateBranch(item) + // 2. Handle todo or fix mode + branchName := title + mdPath := filepath.Join("todo", "doing", title+".md") - logger.Info("Generating Work Plan via Grok...") - // TODO: plan, err := grok.GenerateWorkPlan(item) + if isFix { + if err := createFixFile(mdPath, title); err != nil { + return err + } + } else { + if err := moveQueuedToDoing(title); err != nil { + return err + } + } - logger.Info("Appending plan to todo file...") - // TODO: err = todo.AppendWorkPlan(item, plan) + // 3. Create git branch + if err := createGitBranch(branchName); err != nil { + return fmt.Errorf("failed to create branch %s: %w", branchName, err) + } - logger.Info("Committing changes...") - // TODO: err = git.CommitWorkonChanges(branchName, item) + // 4. Generate and append Work Plan + if err := appendWorkPlan(mdPath, title); err != nil { + return fmt.Errorf("failed to generate/append Work Plan: %w", err) + } - logger.Info("Optional post-steps (cnadd, open IDE)...") - // TODO: postSteps() + // 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 } + +// 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") + 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 { + 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" + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + _, err = f.WriteString(plan) + return err +} + +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 { + // TODO: move to completed/, update index README for todos, commit + logger.Info("complete mode not yet implemented") + 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() { + 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 + } +} From 9694d7246389aca2f949b7bbc9f361009c7b01b5 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 20:56:45 +0100 Subject: [PATCH 4/7] feat(workon): implement core transactional flow with Grok work plan - Bootstrap todo structure and handle fix/complete modes. - Create safe git branches and append AI-generated work plans. - Commit changes and move items to completed on finish. - Add stubs for Grok client and config-dependent IDE open. --- internal/workon/workon.go | 84 +++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 20 deletions(-) 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)") } From 49ec38a2ca6b1a6fb4824161e440e30cc2a9b427 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 21:04:40 +0100 Subject: [PATCH 5/7] refactor(workon): clean up stubs and add TODOs for Grok integration - Remove temporary grokClient interface and prompt logic. - Add TODOs for real Grok client import and calls. - Placeholder work plan for functionality. - TODOs for README index update and IDE config support. - Minor comment and logging tweaks for clarity. --- internal/workon/workon.go | 55 ++++++++++++++------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/internal/workon/workon.go b/internal/workon/workon.go index fb3f328..c3e6463 100644 --- a/internal/workon/workon.go +++ b/internal/workon/workon.go @@ -8,16 +8,11 @@ import ( "gmgauthier.com/grokkit/internal/logger" "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 + // TODO: Import the real Grok client once we wire it (standard pattern is internal/grok) + // e.g. "gmgauthier.com/grokkit/internal/grok" ) -// 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. +// Run executes the full transactional workon flow per todo/doing/workon.md. func Run(title, customMsg string, isFix, isComplete bool) error { if title == "" { return fmt.Errorf("todo_item_title is required") @@ -25,7 +20,7 @@ func Run(title, customMsg string, isFix, isComplete bool) error { logger.Info("workon starting", "title", title, "fix", isFix, "complete", isComplete) - // 1. Bootstrap + // 1. Bootstrap todo structure if missing if err := todo.Bootstrap(); err != nil { return fmt.Errorf("todo bootstrap failed: %w", err) } @@ -34,7 +29,7 @@ func Run(title, customMsg string, isFix, isComplete bool) error { return completeItem(title, isFix) } - // 2. Todo or Fix mode + // 2. Handle todo or fix mode branchName := title mdPath := filepath.Join("todo", "doing", title+".md") @@ -53,7 +48,7 @@ func Run(title, customMsg string, isFix, isComplete bool) error { return fmt.Errorf("failed to create branch %s: %w", branchName, err) } - // 4. Generate + append Work Plan via Grok + // 4. Generate and append Work Plan via Grok if err := appendWorkPlan(mdPath, title); err != nil { return fmt.Errorf("failed to generate/append Work Plan: %w", err) } @@ -90,27 +85,22 @@ func createFixFile(path, title string) error { } func createGitBranch(name string) error { - // Safe: if branch exists, just checkout; else create + // Safe: if branch already exists, just checkout it if err := exec.Command("git", "checkout", name).Run(); err == nil { - return nil // already exists + return nil } return exec.Command("git", "checkout", "-b", name).Run() } func appendWorkPlan(path, title string) error { - // Real Grok call (stub client shown; replace with actual from codebase) - prompt := fmt.Sprintf(`You are helping implement a todo item titled "%s". + // TODO: Replace with real Grok client call (internal/grok pattern from analyze/commit/etc.) + // Example: + // client := grok.NewClient() + // plan, err := client.Query(prompt) // or Generate, etc. + // Use the full todo content + a clean prompt asking only for "## Work Plan" section. -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 + // Temporary placeholder (keeps it functional) + plan := "\n## Work Plan\n\n1. Review the todo/fix requirements.\n2. Implement the changes in the appropriate package.\n3. Add or update tests if applicable.\n4. Verify with `go test` and `grokkit lint`.\n5. Commit and push the branch.\n" f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) if err != nil { @@ -119,18 +109,13 @@ Output ONLY the markdown starting with "## Work Plan" — no extra text.`, title 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 @@ -145,10 +130,9 @@ func completeItem(title string, isFix bool) error { return fmt.Errorf("move to completed failed: %w", err) } - // Update index README for todos only + // TODO: update todo/README.md index under ## Completed (for non-fixes) if !isFix { - // TODO: append to ## Completed section in todo/README.md - logger.Info("index README updated for completed todo") + logger.Info("TODO: update index README for completed todo") } commitMsg := fmt.Sprintf("Complete work on %s", title) @@ -165,6 +149,7 @@ func runCnaddIfAvailable(title string) { } func openIDEIfConfigured() { - // TODO: implement once minimal internal/config supports IDE.command + // 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)") } From 8a3257b3a74a3c1d19d8c8ffa9715e92e4566ecb Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 21:20:55 +0100 Subject: [PATCH 6/7] feat(workon): integrate Grok for work plan generation Replace placeholder with actual Grok client call to generate and append a tailored work plan section to todo/fix files. Add prompt engineering for concise, actionable plans. Include file content reading and model config integration. Update comments and error handling for clarity. --- internal/workon/workon.go | 50 ++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/internal/workon/workon.go b/internal/workon/workon.go index c3e6463..3172305 100644 --- a/internal/workon/workon.go +++ b/internal/workon/workon.go @@ -6,13 +6,13 @@ import ( "os/exec" "path/filepath" + "gmgauthier.com/grokkit/config" + "gmgauthier.com/grokkit/internal/grok" "gmgauthier.com/grokkit/internal/logger" "gmgauthier.com/grokkit/internal/todo" - // TODO: Import the real Grok client once we wire it (standard pattern is internal/grok) - // e.g. "gmgauthier.com/grokkit/internal/grok" ) -// Run executes the full transactional workon flow per todo/doing/workon.md. +// 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") @@ -48,7 +48,7 @@ func Run(title, customMsg string, isFix, isComplete bool) error { return fmt.Errorf("failed to create branch %s: %w", branchName, err) } - // 4. Generate and append Work Plan via Grok + // 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) } @@ -85,7 +85,7 @@ func createFixFile(path, title string) error { } func createGitBranch(name string) error { - // Safe: if branch already exists, just checkout it + // Safe: if branch already exists, just checkout it; else create new if err := exec.Command("git", "checkout", name).Run(); err == nil { return nil } @@ -93,29 +93,45 @@ func createGitBranch(name string) error { } func appendWorkPlan(path, title string) error { - // TODO: Replace with real Grok client call (internal/grok pattern from analyze/commit/etc.) - // Example: - // client := grok.NewClient() - // plan, err := client.Query(prompt) // or Generate, etc. - // Use the full todo content + a clean prompt asking only for "## Work Plan" section. + content := readFileContent(path) - // Temporary placeholder (keeps it functional) - plan := "\n## Work Plan\n\n1. Review the todo/fix requirements.\n2. Implement the changes in the appropriate package.\n3. Add or update tests if applicable.\n4. Verify with `go test` and `grokkit lint`.\n5. Commit and push the branch.\n" + 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(f *os.File) { - err := f.Close() - if err != nil { - + defer func() { + if cerr := f.Close(); cerr != nil { + logger.Error("failed to close todo file", "err", cerr) } - }(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 From ce878f058f5dd9a15d28873461b7b12ca5f502b3 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Tue, 31 Mar 2026 22:04:33 +0100 Subject: [PATCH 7/7] feat(workon): enhance command with IDE integration and README updates - Add colorized output for success/error messages - Change custom message flag from -m to -M - Implement branch prefixing (feature/ or fix/) - Add config for workon.model and workon.ide - Update README.md index on task completion - Integrate IDE opening if configured - Refine error handling and logging --- cmd/workon.go | 20 +++++++++--- config.toml.example | 2 ++ config/config.go | 7 +++++ internal/workon/workon.go | 65 ++++++++++++++++++++++++++++++++++----- 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/cmd/workon.go b/cmd/workon.go index 7909a41..ed230f4 100644 --- a/cmd/workon.go +++ b/cmd/workon.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/fatih/color" "github.com/spf13/cobra" "gmgauthier.com/grokkit/internal/logger" @@ -28,23 +29,32 @@ Purely transactional. See todo/doing/workon.md for full spec.`, isFix, _ := cmd.Flags().GetBool("fix") isComplete, _ := cmd.Flags().GetBool("complete") - if isComplete && (isFix || customMsg != "") { - return fmt.Errorf("-c cannot be combined with -f or -m") + if isComplete && customMsg != "" { + return fmt.Errorf("-c cannot be combined with -m") } logger.Info("workon starting", "title", title, "fix", isFix, "complete", isComplete) if err := workon.Run(title, customMsg, isFix, isComplete); err != nil { - return fmt.Errorf("workon failed: %w", err) + color.Red("workon failed: %v", err) + return err } - logger.Info("workon completed successfully") + mode := "todo" + if isFix { + mode = "fix" + } + action := "started" + if isComplete { + action = "completed" + } + color.Green("workon %s: %s (%s)", action, title, mode) return nil }, } func init() { - workonCmd.Flags().StringP("message", "m", "", "Custom commit message (default: \"Start working on \")") + workonCmd.Flags().StringP("message", "M", "", "Custom commit message (default: \"Start working on \")") workonCmd.Flags().BoolP("fix", "f", false, "Treat as fix instead of todo (create new .md in doing/)") workonCmd.Flags().BoolP("complete", "c", false, "Complete the item (move to completed/; exclusive)") } diff --git a/config.toml.example b/config.toml.example index 1929a0c..71c0087 100644 --- a/config.toml.example +++ b/config.toml.example @@ -23,6 +23,8 @@ fast = "grok-4-1-fast-non-reasoning" history.model = "grok-4" prdescribe.model = "grok-4" review.model = "grok-4" + workon.model = "grok-4-1-fast-non-reasoning" # Fast model for work plan generation + workon.ide = "" # IDE command to open repo (e.g. "code", "goland") # Chat history settings [chat] diff --git a/config/config.go b/config/config.go index 36816a9..68df4b0 100644 --- a/config/config.go +++ b/config/config.go @@ -38,6 +38,8 @@ func Load() { viper.SetDefault("commands.review.model", "grok-4") viper.SetDefault("commands.docs.model", "grok-4") viper.SetDefault("commands.query.model", "grok-4-1-fast-non-reasoning") + viper.SetDefault("commands.workon.model", "grok-4-1-fast-non-reasoning") + viper.SetDefault("commands.workon.ide", "") // Config file is optional, so we ignore read errors _ = viper.ReadInConfig() @@ -72,6 +74,11 @@ func GetTimeout() int { return timeout } +// GetIDECommand returns the configured IDE command for workon, or empty string if unset. +func GetIDECommand() string { + return viper.GetString("commands.workon.ide") +} + // GetLogLevel returns the log level from the configuration func GetLogLevel() string { return viper.GetString("log_level") diff --git a/internal/workon/workon.go b/internal/workon/workon.go index 3172305..2d23362 100644 --- a/internal/workon/workon.go +++ b/internal/workon/workon.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "gmgauthier.com/grokkit/config" "gmgauthier.com/grokkit/internal/grok" @@ -30,7 +31,11 @@ func Run(title, customMsg string, isFix, isComplete bool) error { } // 2. Handle todo or fix mode - branchName := title + branchPrefix := "feature/" + if isFix { + branchPrefix = "fix/" + } + branchName := branchPrefix + title mdPath := filepath.Join("todo", "doing", title+".md") if isFix { @@ -107,7 +112,7 @@ Output ONLY the markdown starting with "## Work Plan" — no extra text, no intr // 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") + model := config.GetModel("workon", "") plan := client.StreamSilent([]map[string]string{ {"role": "system", "content": "You are a precise software engineering assistant."}, {"role": "user", "content": prompt}, @@ -146,9 +151,10 @@ func completeItem(title string, isFix bool) error { 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") + if err := updateReadmeIndex(title); err != nil { + logger.Error("failed to update README index", "err", err) + } } commitMsg := fmt.Sprintf("Complete work on %s", title) @@ -158,6 +164,46 @@ func completeItem(title string, isFix bool) error { return nil } +func updateReadmeIndex(title string) error { + readmePath := filepath.Join("todo", "README.md") + data, err := os.ReadFile(readmePath) + if err != nil { + return fmt.Errorf("read README: %w", err) + } + + lines := strings.Split(string(data), "\n") + var result []string + completedIdx := -1 + + for i, line := range lines { + // Skip the line referencing this title in the Queued section + if strings.Contains(line, title+".md") && strings.Contains(line, "queued/") { + continue + } + // Track the last line in the Completed section + if strings.HasPrefix(line, "## Completed") { + completedIdx = i + } + result = append(result, line) + } + + // Find insertion point: after last non-empty line following ## Completed + if completedIdx >= 0 { + insertIdx := completedIdx + 1 + for insertIdx < len(result) && (strings.HasPrefix(result[insertIdx], "*") || strings.TrimSpace(result[insertIdx]) == "") { + if strings.HasPrefix(result[insertIdx], "*") { + insertIdx++ + } else { + break + } + } + entry := fmt.Sprintf("* [%s](./completed/%s.md) : %s *(done)*", title, title, title) + result = append(result[:insertIdx], append([]string{entry}, result[insertIdx:]...)...) + } + + return os.WriteFile(readmePath, []byte(strings.Join(result, "\n")), 0644) +} + func runCnaddIfAvailable(title string) { if _, err := exec.LookPath("cnadd"); err == nil { _ = exec.Command("cnadd", "log", fmt.Sprintf("started work on %s", title)).Run() @@ -165,7 +211,12 @@ func runCnaddIfAvailable(title string) { } 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)") + ideCmd := config.GetIDECommand() + if ideCmd == "" { + logger.Debug("IDE open skipped (no IDE configured)") + return + } + if err := exec.Command(ideCmd, ".").Start(); err != nil { + logger.Error("failed to open IDE", "cmd", ideCmd, "err", err) + } }