diff --git a/cmd/pm/tasks/codingTask.go b/cmd/pm/tasks/codingTask.go index 5b7d3e0..5df0ec3 100644 --- a/cmd/pm/tasks/codingTask.go +++ b/cmd/pm/tasks/codingTask.go @@ -2,37 +2,80 @@ package tasks import ( "context" + "fmt" "os" + "strings" + "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/utils/check" - "github.com/charmbracelet/huh" ) -const codingDescription = `You are tasked with writing a simple function. +const codingDescription = `You are tasked with doing a chore in the codebase. -This task will test your ability to write clean, working code. +This task will test your ability to read and understand instructions, change text, and save it to a file. + +The MongoDB Driver dependency in the file is outdated and needs to be updated to the latest version. +This is a common task for developers, and it requires attention to detail and the ability to follow instructions carefully. Your task: -1. Review the requirements below -2. Write a function that takes two integers and returns their sum -3. The function should be named "Add" -4. The function should be part of the "coding" package -5. Save your code to the code.txt file +1. Assign this Issue to yourself as "Me" and mark it as "In Progress" +2. Create a New Issue and give it these details: + - Title: "Upgrade MongoDB Driver Dependency" + - Description: "We need to upgrade the MongoDB Driver dependency to the latest version." + - Status: "In Progress" + - Issue Type: "Chore" +3. A file will appear in the current directory named "code.txt". + Open it and follow the instructions inside. And save the file after you are done. +4. When you are done, mark this and the issue you made as "Closed".` -Requirements: -- Function name: Add -- Parameters: two integers -- Return value: integer (sum of the two inputs) -- Package: coding` +var textFileDescription = ` +Please upgrade the MongoDB Driver dependency in the go.mod file to the latest version. +It should be v1.17.9. After you are done, save the file and mark the task as completed. +############################################################` -var textFileContent = codingDescription + ` -Please write your code below this line! -############################################################ +var code = ` +require ( + charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 + github.com/go-git/go-git/v6 v6.0.0-20260222090600-424e9964d3a3 + github.com/muesli/reflow v0.3.0 + github.com/steveyegge/beads v0.49.6 + go.mongodb.org/mongo-driver v1.17.8 + go.mongodb.org/mongo-driver/v2 v2.5.0 +) + +require ( + github.com/c-bata/go-prompt v0.2.6 + github.com/charmbracelet/bubbles v0.21.1 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/fang v0.4.4 + github.com/charmbracelet/huh v0.8.0 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 + github.com/spf13/cobra v1.10.2 + golang.org/x/term v0.40.0 +) + +require ( + github.com/NYTimes/gziphandler v1.1.1 + github.com/a-h/templ v0.3.977 + github.com/donseba/go-htmx v1.12.1 + github.com/go-chi/chi/v5 v5.2.5 + github.com/go-playground/form/v4 v4.3.0 + github.com/go-playground/validator/v10 v10.30.1 + github.com/rs/cors v1.11.1 +) + +tool ( + github.com/a-h/templ/cmd/templ + github.com/haatos/goshipit/cmd/gsi +) ` +var textFileContent = codingDescription + textFileDescription + "\n" + code + type CodingTask struct { - done bool - app *App + done bool + setupIssue *Issue + app *App } func NewCodingTask(app *App) *CodingTask { @@ -48,35 +91,11 @@ func (t *CodingTask) Details() TaskDetails { } func (t *CodingTask) Questions(interfaceType InterfaceType) (questions Questions) { - return BaseQuestions(interfaceType). - With( - ReplQuestion(interfaceType, - huh.NewConfirm().Key("repl_experience").Title("Question only for REPL interface")), - ). - With( - WebQuestion(interfaceType, - huh.NewInput().Key("web_feedback").Title("Question only for Web interface")), - ). - With( - TUIQuestion(interfaceType, - huh.NewConfirm().Key("tui_experience").Title("Question only for TUI interface")), - ). - With( - Question( - huh.NewConfirm().Key("final_confirmation").Title("One last question for all interfaces!")), - ) + return BaseQuestions(interfaceType) } func (t *CodingTask) QuestionnaireKeys(interfaceType InterfaceType) []string { - keys := []string{"task_completed", "task_difficulty", "final_confirmation"} - switch interfaceType { - case InterfaceTypeREPL: - keys = append(keys, "repl_experience") - case InterfaceTypeWeb: - keys = append(keys, "web_feedback") - case InterfaceTypeTUI: - keys = append(keys, "tui_experience") - } + keys := []string{"task_completed", "task_difficulty"} return keys } @@ -85,6 +104,17 @@ func (t *CodingTask) Setup(ctx context.Context) error { return err } + t.setupIssue = NewIssueBuilder(). + WithTitle("Coding Task - Upgrade MongoDB Driver"). + WithDescription(codingDescription). + WithStatus(models.StatusOpen). + WithIssueType(models.TypeTask). + Build() + + if err := t.app.Issues.CreateIssue(ctx, t.setupIssue, "LazyPM"); err != nil { + return err + } + if err := os.WriteFile("./code.txt", []byte(textFileContent), 0644); err != nil { return err } @@ -92,8 +122,87 @@ func (t *CodingTask) Setup(ctx context.Context) error { return nil } +var codeTaskInProgress = false + func (t *CodingTask) Validate(ctx context.Context) ValidationFeedback { expect := check.NewExpector() - expect.Assert(true, "This task is always valid") + + issues, err := FetchIssues(ctx, t.app, t.setupIssue) + if err != nil { + return expect.ValidationFeedback + } + + expect.Assert(t.setupIssue.Assignee == "Me", + "The original issue should be assigned to 'Me'.") + + expect.Assert(t.setupIssue.Status == models.StatusInProgress, + "The original issue should be marked as In Progress before starting work.") + + expect.Assert(len(issues) > 0, + "No new issues created. Please create an issue with the specified details.") + + if len(issues) == 0 { + expect.Fail("No new issues created") + return expect.ValidationFeedback + } + + issue := issues[0] + + expect.Assert(len(issues) < 2, + "Multiple issues were created instead of one. Delete the extra issues and try again.") + + expect.NotEmptyString(issue.Title, + "Issue title should not be empty") + + expect.Assert(issue.Title == "Upgrade MongoDB Driver Dependency", + fmt.Sprintf("Issue title does not match the expected value 'Upgrade MongoDB Driver Dependency', but was '%s'", issue.Title)) + + expect.NotEmptyString(issue.Description, + "Issue description should not be empty") + + expect.Assert(issue.Description == "We need to upgrade the MongoDB Driver dependency to the latest version.", + fmt.Sprintf("Issue description does not match 'We need to upgrade the MongoDB Driver dependency to the latest version.', but was '%s'", issue.Description)) + + expect.Assert(issue.IssueType == models.TypeChore, + fmt.Sprintf("Issue type should be 'Chore', but was '%s'", issue.IssueType)) + + expect.Assert(issue.Assignee == "Me", + fmt.Sprintf("Issue should be assigned to 'Me', but was assigned to '%s'", issue.Assignee)) + + if issue.Status == models.StatusInProgress || codeTaskInProgress { + codeTaskInProgress = true + } else { + expect.Fail("Issue should be marked as In Progress when work starts") + } + + if _, err := os.Stat("./code.txt"); os.IsNotExist(err) { + expect.Fail("The code.txt file should exist on the desktop.") + return expect.ValidationFeedback + } + + fileContent, err := os.ReadFile("./code.txt") + if err != nil { + expect.Fail("Error reading code.txt file: " + err.Error()) + return expect.ValidationFeedback + } + + code, ok := strings.CutPrefix(string(fileContent), codingDescription+textFileDescription+"\n") + if !ok { + expect.Fail("The content of code.txt does not match the expected format.") + return expect.ValidationFeedback + } + + expect.Assert(strings.Contains(code, "go.mongodb.org/mongo-driver v1.17.9"), + "The MongoDB Driver dependency should be updated to version v1.17.9 in the file.") + + if !codeTaskInProgress { + return expect.ValidationFeedback + } else if issue.Status != models.StatusClosed { + expect.Fail("Issue should be set to Closed once the work is completed") + } else { + expect.Assert(t.setupIssue.Status == models.StatusClosed, + "The original setup issue should be set to Closed once the work is completed") + } + return expect.Complete() } diff --git a/internal/commands/survey/status.go b/internal/commands/survey/status.go index 2168d18..0e5a954 100644 --- a/internal/commands/survey/status.go +++ b/internal/commands/survey/status.go @@ -25,6 +25,13 @@ func runStatusCmd(cmd *cobra.Command, args []string) error { return nil } - cmd.Print(app.CurrentFeedback.Message) + cmd.Println(app.CurrentFeedback.Message) + for _, check := range app.CurrentFeedback.Checks { + if check.Valid { + cmd.Printf("✅ %s\n", check.Message) + } else { + cmd.Printf("❌ %s\n", check.Message) + } + } return nil } diff --git a/pkg/repl/repl.go b/pkg/repl/repl.go index 091d0f0..607100d 100644 --- a/pkg/repl/repl.go +++ b/pkg/repl/repl.go @@ -129,6 +129,7 @@ func (r *REPL) watchValidation() { r.app.CurrentFeedback = &ValidationFeedback{ Success: feedback.Success, Message: feedback.Message, + Checks: feedback.Checks, } } if feedback.Success { diff --git a/pkg/task/taskui.go b/pkg/task/taskui.go index cfdbea0..2f7086b 100644 --- a/pkg/task/taskui.go +++ b/pkg/task/taskui.go @@ -69,7 +69,7 @@ func (m TaskModel) View() string { style.TextStyle.Render("Terminal too small.")) } - boxWidth := min(m.width-10, 80) + boxWidth := min(m.width-10, 120) detailsText := fmt.Sprintf("Time to complete: %s | Difficulty: %s", m.TimeToComplete, m.Difficulty)