2026-03-31 19:30:05 +00:00
package workon
import (
2026-03-31 19:42:32 +00:00
"fmt"
"os"
"os/exec"
"path/filepath"
2026-03-31 19:30:05 +00:00
"gmgauthier.com/grokkit/internal/logger"
2026-03-31 19:56:45 +00:00
"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
2026-03-31 19:30:05 +00:00
)
2026-03-31 19:56:45 +00:00
// 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.
2026-03-31 19:42:32 +00:00
func Run ( title , customMsg string , isFix , isComplete bool ) error {
if title == "" {
return fmt . Errorf ( "todo_item_title is required" )
}
2026-03-31 19:56:45 +00:00
logger . Info ( "workon starting" , "title" , title , "fix" , isFix , "complete" , isComplete )
// 1. Bootstrap
2026-03-31 19:42:32 +00:00
if err := todo . Bootstrap ( ) ; err != nil {
return fmt . Errorf ( "todo bootstrap failed: %w" , err )
}
if isComplete {
return completeItem ( title , isFix )
}
2026-03-31 19:56:45 +00:00
// 2. Todo or Fix mode
2026-03-31 19:42:32 +00:00
branchName := title
mdPath := filepath . Join ( "todo" , "doing" , title + ".md" )
if isFix {
if err := createFixFile ( mdPath , title ) ; err != nil {
2026-03-31 19:56:45 +00:00
return fmt . Errorf ( "create fix file failed: %w" , err )
2026-03-31 19:42:32 +00:00
}
} else {
if err := moveQueuedToDoing ( title ) ; err != nil {
2026-03-31 19:56:45 +00:00
return fmt . Errorf ( "move to doing failed: %w" , err )
2026-03-31 19:42:32 +00:00
}
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:56:45 +00:00
// 3. Create git branch (safe)
2026-03-31 19:42:32 +00:00
if err := createGitBranch ( branchName ) ; err != nil {
return fmt . Errorf ( "failed to create branch %s: %w" , branchName , err )
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:56:45 +00:00
// 4. Generate + append Work Plan via Grok
2026-03-31 19:42:32 +00:00
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
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:42:32 +00:00
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
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:42:32 +00:00
func createFixFile ( path , title string ) error {
content := fmt . Sprintf ( "# %s\n\n## Work Plan\n\n" , title )
return os . WriteFile ( path , [ ] byte ( content ) , 0644 )
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:42:32 +00:00
func createGitBranch ( name string ) error {
2026-03-31 19:56:45 +00:00
// Safe: if branch exists, just checkout; else create
if err := exec . Command ( "git" , "checkout" , name ) . Run ( ) ; err == nil {
return nil // already exists
}
2026-03-31 19:42:32 +00:00
return exec . Command ( "git" , "checkout" , "-b" , name ) . Run ( )
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:42:32 +00:00
func appendWorkPlan ( path , title string ) error {
2026-03-31 19:56:45 +00:00
// 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
2026-03-31 19:42:32 +00:00
f , err := os . OpenFile ( path , os . O_APPEND | os . O_WRONLY , 0644 )
if err != nil {
return err
}
2026-03-31 19:56:45 +00:00
defer func ( f * os . File ) {
err := f . Close ( )
if err != nil {
logger . Error ( "failed to close file" , "err" , err )
}
} ( f )
2026-03-31 19:42:32 +00:00
_ , err = f . WriteString ( plan )
return err
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:56:45 +00:00
func readFileContent ( path string ) string {
b , _ := os . ReadFile ( path )
return string ( b )
}
2026-03-31 19:42:32 +00:00
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 ( )
}
2026-03-31 19:30:05 +00:00
2026-03-31 19:42:32 +00:00
func completeItem ( title string , isFix bool ) error {
2026-03-31 19:56:45 +00:00
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 )
}
2026-03-31 19:30:05 +00:00
return nil
}
2026-03-31 19:42:32 +00:00
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 ( ) {
2026-03-31 19:56:45 +00:00
// TODO: implement once minimal internal/config supports IDE.command
logger . Debug ( "IDE open skipped (config support pending)" )
2026-03-31 19:42:32 +00:00
}