-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcommand_plan_new.go
More file actions
218 lines (179 loc) · 6.24 KB
/
command_plan_new.go
File metadata and controls
218 lines (179 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package tasked
import (
"fmt"
"os"
"strings"
"time"
"github.com/dhamidi/tasked/planner"
"github.com/spf13/cobra"
)
var planAutoSteps bool
var PlanNewCmd = &cobra.Command{
Use: "new [plan-name]",
Short: "Create a new plan (with optional auto-detected steps)",
Long: `Create a new plan with the specified name. The plan will be created
in the database and can then be populated with steps using other plan commands.
When run without a plan name, a default name is generated based on the project name and date.
The command will automatically analyze the current directory for issues (TODOs, missing tests,
uncommitted changes) and offer to auto-generate steps.`,
Args: cobra.MaximumNArgs(1),
RunE: RunPlanNew,
}
func init() {
PlanNewCmd.Flags().BoolVar(&planAutoSteps, "auto-steps", false, "Automatically generate steps from detected issues")
}
func RunPlanNew(cmd *cobra.Command, args []string) error {
// Detect context automatically
ctx, err := GlobalSettings.GetOrCreateContext()
if err != nil {
return fmt.Errorf("failed to detect context: %w", err)
}
// Determine plan name
planName := ""
if len(args) > 0 {
planName = args[0]
} else {
// Generate default plan name: {project-name}-{date}
dateStr := time.Now().Format("2006-01-02")
planName = fmt.Sprintf("%s-%s", ctx.ProjectName, dateStr)
}
fmt.Printf("Detected context: %s", ctx.CurrentPath)
if ctx.IsGitRepo {
fmt.Printf(" (git root: %s)", ctx.GitRoot)
}
fmt.Printf("\n")
// Get the database file path from settings
dbPath := GlobalSettings.GetDatabaseFile()
// Initialize the planner
p, err := planner.New(dbPath)
if err != nil {
return fmt.Errorf("failed to initialize planner: %w", err)
}
defer p.Close()
// Create the new plan
plan, err := p.Create(planName)
if err != nil {
return fmt.Errorf("failed to create plan: %w", err)
}
// Set context metadata on the plan
plan.ContextPath = ctx.CurrentPath
plan.GitRoot = ctx.GitRoot
plan.ProjectName = ctx.ProjectName
// Analyze directory for issues
fmt.Printf("Scanning directory for issues...")
issues, err := planner.AnalyzeDirectory(ctx.CurrentPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not analyze directory: %v\n", err)
issues = &planner.IssuesFound{}
}
fmt.Printf("\n")
// Summary of found issues
totalIssues := len(issues.Todos) + len(issues.MissingTests) + len(issues.Uncommitted)
if totalIssues > 0 {
fmt.Printf("Found %d issues to address:\n", totalIssues)
if len(issues.Todos) > 0 {
fmt.Printf(" - %d TODO comments in code\n", len(issues.Todos))
}
if len(issues.MissingTests) > 0 {
fmt.Printf(" - %d missing test files\n", len(issues.MissingTests))
}
if len(issues.Uncommitted) > 0 {
fmt.Printf(" - %d uncommitted changes\n", len(issues.Uncommitted))
}
fmt.Printf("\n")
}
// Auto-generate steps if requested or if no name was provided and issues were found
shouldAutoGenerate := planAutoSteps || (len(args) == 0 && totalIssues > 0)
if shouldAutoGenerate && totalIssues > 0 {
fmt.Printf("Creating plan '%s' with auto-generated steps...\n", planName)
// Generate steps from issues
generateStepsFromIssues(plan, issues)
fmt.Printf("Generated %d steps from detected issues:\n", len(plan.Steps))
for i, step := range plan.Steps {
fmt.Printf(" %d. [%s] %s\n", i+1, step.Status(), step.ID())
}
fmt.Printf("\n")
} else {
fmt.Printf("Creating empty plan '%s'\n", planName)
}
// Save the plan to the database
if err := p.Save(plan); err != nil {
return fmt.Errorf("failed to save plan: %w", err)
}
if shouldAutoGenerate && totalIssues > 0 {
fmt.Printf("Run 'tasked plan inspect %s' to see all steps\n", planName)
} else {
fmt.Printf("Use 'tasked plan add-step %s <step-id> <desc> <acceptance...>' to add steps\n", planName)
}
return nil
}
// generateStepsFromIssues creates plan steps from detected issues
func generateStepsFromIssues(plan *planner.Plan, issues *planner.IssuesFound) {
// Generate step counter
stepCounter := 1
// Add steps for TODOs
for _, todo := range issues.Todos {
stepID := fmt.Sprintf("fix-todo-%d", stepCounter)
description := fmt.Sprintf("Fix TODO from %s:%d: %s", todo.File, todo.LineNumber, todo.Description)
// Clean up file path for display
fileDisplay := cleanFilePath(todo.File)
// Generate acceptance criteria
acceptance := []string{
fmt.Sprintf("TODO comment in %s:%d removed or properly addressed", fileDisplay, todo.LineNumber),
}
// Add file reference
references := []string{todo.File}
plan.AddStep(stepID, description, acceptance, references)
stepCounter++
}
// Add steps for missing tests
for _, missingTest := range issues.MissingTests {
stepID := fmt.Sprintf("add-test-%d", stepCounter)
description := fmt.Sprintf("Add tests for %s", cleanFilePath(missingTest.SourceFile))
acceptance := []string{
fmt.Sprintf("Test file %s exists", cleanFilePath(missingTest.TestFile)),
"Tests pass and provide good coverage",
}
references := []string{missingTest.SourceFile}
plan.AddStep(stepID, description, acceptance, references)
stepCounter++
}
// Add steps for uncommitted changes
// Group files by status
changesByStatus := make(map[string][]string)
for _, change := range issues.Uncommitted {
if change.Status != "?" { // Skip untracked files
changesByStatus[change.Status] = append(changesByStatus[change.Status], cleanFilePath(change.File))
}
}
// Add step for each group of changes
for status, files := range changesByStatus {
stepID := fmt.Sprintf("commit-%s-%d", strings.ToLower(status), stepCounter)
var description string
if len(files) == 1 {
description = fmt.Sprintf("Complete work on %s", files[0])
} else {
description = fmt.Sprintf("Complete work on %d files", len(files))
}
acceptance := []string{
"Changes are ready to commit",
"All files are reviewed",
}
references := files
plan.AddStep(stepID, description, acceptance, references)
stepCounter++
}
}
// cleanFilePath makes a file path more readable for display
func cleanFilePath(path string) string {
ctx, _ := GlobalSettings.GetOrCreateContext()
if ctx == nil {
return path
}
// Try to make the path relative to context
if ctx.CurrentPath != "" && strings.HasPrefix(path, ctx.CurrentPath) {
relPath := strings.TrimPrefix(path, ctx.CurrentPath+"/")
return relPath
}
return path
}