Detailed changes
@@ -7,6 +7,7 @@ package g
import (
"fmt"
+ "git.secluded.site/np/cmd/shared"
"github.com/spf13/cobra"
)
@@ -14,9 +15,31 @@ var GCmd = &cobra.Command{
Use: "g",
Short: "Goal commands",
Long: `Manage the session goal`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("[STUB] Display goal and description")
- fmt.Println("Goal: Example goal title")
- fmt.Println("Description: Example goal description")
- },
+ RunE: runShowGoal,
+}
+
+func runShowGoal(cmd *cobra.Command, _ []string) error {
+ env, err := shared.Environment(cmd)
+ if err != nil {
+ return err
+ }
+
+ sessionDoc, found, err := shared.ActiveSession(cmd, env)
+ if err != nil {
+ return err
+ }
+ if !found {
+ return nil
+ }
+
+ state, err := shared.PrintPlan(cmd, env, sessionDoc.SID)
+ if err != nil {
+ return err
+ }
+
+ if state.Goal == nil {
+ fmt.Fprintln(cmd.OutOrStdout(), "")
+ fmt.Fprintln(cmd.OutOrStdout(), "Set the goal with `np g s -t \"goal title\" -d \"goal description\"` to begin.")
+ }
+ return nil
}
@@ -6,7 +6,12 @@ package g
import (
"fmt"
+ "strings"
+ "git.secluded.site/np/cmd/shared"
+ "git.secluded.site/np/internal/db"
+ "git.secluded.site/np/internal/event"
+ "git.secluded.site/np/internal/goal"
"github.com/spf13/cobra"
)
@@ -14,9 +19,7 @@ var sCmd = &cobra.Command{
Use: "s",
Short: "Set goal",
Long: `Set the session goal with title and description`,
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("[STUB] Set session goal with title and description")
- },
+ RunE: runSetGoal,
}
func init() {
@@ -27,3 +30,82 @@ func init() {
_ = sCmd.MarkFlagRequired("title")
_ = sCmd.MarkFlagRequired("description")
}
+
+func runSetGoal(cmd *cobra.Command, _ []string) error {
+ env, err := shared.Environment(cmd)
+ if err != nil {
+ return err
+ }
+
+ sessionDoc, found, err := shared.ActiveSession(cmd, env)
+ if err != nil {
+ return err
+ }
+ if !found {
+ return nil
+ }
+
+ title, err := cmd.Flags().GetString("title")
+ if err != nil {
+ return err
+ }
+ description, err := cmd.Flags().GetString("description")
+ if err != nil {
+ return err
+ }
+
+ title = strings.TrimSpace(title)
+ description = strings.TrimSpace(description)
+
+ if title == "" {
+ fmt.Fprintln(cmd.OutOrStdout(), "Goal title is required.")
+ return nil
+ }
+ if description == "" {
+ fmt.Fprintln(cmd.OutOrStdout(), "Goal description is required.")
+ return nil
+ }
+
+ exists, err := env.GoalStore.Exists(cmd.Context(), sessionDoc.SID)
+ if err != nil {
+ return err
+ }
+ if exists {
+ fmt.Fprintln(cmd.OutOrStdout(), "Goal already set. Use 'np g u' to update it (requires -r/--reason flag).")
+ return nil
+ }
+
+ var saved goal.Document
+ err = env.DB.Update(cmd.Context(), func(txn *db.Txn) error {
+ goalTxn := env.GoalStore.WithTxn(txn)
+ var err error
+ saved, err = goalTxn.Set(sessionDoc.SID, title, description)
+ if err != nil {
+ return err
+ }
+
+ sessionTxn := env.SessionStore.WithTxn(txn)
+ if _, err := sessionTxn.TouchAt(sessionDoc.SID, saved.UpdatedAt); err != nil {
+ return err
+ }
+
+ eventTxn := env.EventStore.WithTxn(txn)
+ _, err = eventTxn.Append(sessionDoc.SID, event.BuildGoalSet(shared.CommandString(), "", saved))
+ return err
+ })
+ if err != nil {
+ return err
+ }
+
+ if _, err := shared.PrintPlan(cmd, env, sessionDoc.SID); err != nil {
+ return err
+ }
+
+ out := cmd.OutOrStdout()
+ fmt.Fprintln(out, "")
+ fmt.Fprintln(out, "Study the goal and its description carefully.")
+ fmt.Fprintln(out, "Review referenced tickets and files to gather context before planning changes.")
+ fmt.Fprintln(out, "Add tasks with `np t a -t \"task title\" -d \"details\"` once you understand the approach.")
+
+ return nil
+}
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package g
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "git.secluded.site/np/cmd/shared"
+ "git.secluded.site/np/internal/db"
+ "git.secluded.site/np/internal/event"
+ "git.secluded.site/np/internal/goal"
+ "github.com/spf13/cobra"
+)
+
+var uCmd = &cobra.Command{
+ Use: "u",
+ Short: "Update goal",
+ Long: `Update the goal title or description (requires a reason)`,
+ RunE: runUpdateGoal,
+}
+
+func init() {
+ GCmd.AddCommand(uCmd)
+
+ uCmd.Flags().StringP("title", "t", "", "New goal title")
+ uCmd.Flags().StringP("description", "d", "", "New goal description")
+ uCmd.Flags().StringP("reason", "r", "", "Reason for updating the goal (required)")
+ _ = uCmd.MarkFlagRequired("reason")
+}
+
+func runUpdateGoal(cmd *cobra.Command, _ []string) error {
+ env, err := shared.Environment(cmd)
+ if err != nil {
+ return err
+ }
+
+ sessionDoc, found, err := shared.ActiveSession(cmd, env)
+ if err != nil {
+ return err
+ }
+ if !found {
+ return nil
+ }
+
+ current, err := env.GoalStore.Get(cmd.Context(), sessionDoc.SID)
+ if err != nil {
+ if errors.Is(err, goal.ErrNotFound) {
+ fmt.Fprintln(cmd.OutOrStdout(), "No goal set yet. Use 'np g s' first.")
+ return nil
+ }
+ return err
+ }
+
+ titleInput, err := cmd.Flags().GetString("title")
+ if err != nil {
+ return err
+ }
+ descInput, err := cmd.Flags().GetString("description")
+ if err != nil {
+ return err
+ }
+ reason, err := cmd.Flags().GetString("reason")
+ if err != nil {
+ return err
+ }
+
+ reason = strings.TrimSpace(reason)
+ if reason == "" {
+ fmt.Fprintln(cmd.OutOrStdout(), "Reason is required for goal updates.")
+ return nil
+ }
+
+ newTitle := current.Title
+ if cmd.Flags().Changed("title") {
+ newTitle = strings.TrimSpace(titleInput)
+ if newTitle == "" {
+ fmt.Fprintln(cmd.OutOrStdout(), "Goal title cannot be empty.")
+ return nil
+ }
+ }
+
+ newDescription := current.Description
+ if cmd.Flags().Changed("description") {
+ newDescription = strings.TrimSpace(descInput)
+ }
+
+ if !cmd.Flags().Changed("title") && !cmd.Flags().Changed("description") {
+ fmt.Fprintln(cmd.OutOrStdout(), "Provide at least one of --title or --description to update the goal.")
+ return nil
+ }
+
+ if newTitle == current.Title && newDescription == current.Description {
+ fmt.Fprintln(cmd.OutOrStdout(), "Goal already matches the provided values; no changes made.")
+ return nil
+ }
+
+ var updated goal.Document
+ err = env.DB.Update(cmd.Context(), func(txn *db.Txn) error {
+ goalTxn := env.GoalStore.WithTxn(txn)
+
+ var err error
+ updated, err = goalTxn.Set(sessionDoc.SID, newTitle, newDescription)
+ if err != nil {
+ return err
+ }
+
+ sessionTxn := env.SessionStore.WithTxn(txn)
+ if _, err := sessionTxn.TouchAt(sessionDoc.SID, updated.UpdatedAt); err != nil {
+ return err
+ }
+
+ eventTxn := env.EventStore.WithTxn(txn)
+ _, err = eventTxn.Append(sessionDoc.SID, event.BuildGoalUpdated(shared.CommandString(), reason, current, updated))
+ return err
+ })
+ if err != nil {
+ return err
+ }
+
+ if _, err := shared.PrintPlan(cmd, env, sessionDoc.SID); err != nil {
+ return err
+ }
+
+ out := cmd.OutOrStdout()
+ fmt.Fprintln(out, "")
+ fmt.Fprintln(out, "Goal updated. Ensure pending tasks still align and adjust them if necessary.")
+ fmt.Fprintln(out, "Add or update tasks with `np t a` / `np t u` so the plan reflects the new direction.")
+
+ return nil
+}