@@ -114,6 +114,11 @@ func (m *Manager) CreateFeature(name string, content string) error {
114114 return fmt .Errorf ("failed to create features directory: %w" , err )
115115 }
116116
117+ // Normalize filename: replace spaces with dashes
118+ name = strings .TrimSpace (name )
119+ name = strings .ReplaceAll (name , " " , "-" )
120+ name = strings .ReplaceAll (name , "_" , "-" )
121+
117122 // Add .md extension if not present
118123 if ! strings .HasSuffix (name , ".md" ) {
119124 name = name + ".md"
@@ -126,13 +131,11 @@ func (m *Manager) CreateFeature(name string, content string) error {
126131 return fmt .Errorf ("feature file already exists: %s" , name )
127132 }
128133
129- // If content is empty, use template
130- if content == "" {
131- content = generateFeatureTemplate (name )
132- }
134+ // Generate file content from template with content parameter
135+ fileContent := generateFeatureContent (name , content )
133136
134137 // Write file
135- if err := os .WriteFile (featurePath , []byte (content ), 0644 ); err != nil {
138+ if err := os .WriteFile (featurePath , []byte (fileContent ), 0644 ); err != nil {
136139 return fmt .Errorf ("failed to create feature file: %w" , err )
137140 }
138141
@@ -146,6 +149,11 @@ func (m *Manager) CreateFeature(name string, content string) error {
146149func (m * Manager ) DeleteFeature (name string , force bool ) error {
147150 featuresDir := filepath .Join (m .TaskDir , "tasks" , "features" )
148151
152+ // Normalize filename: replace spaces with dashes
153+ name = strings .TrimSpace (name )
154+ name = strings .ReplaceAll (name , " " , "-" )
155+ name = strings .ReplaceAll (name , "_" , "-" )
156+
149157 // Add .md extension if not present
150158 if ! strings .HasSuffix (name , ".md" ) {
151159 name = name + ".md"
@@ -170,6 +178,11 @@ func (m *Manager) DeleteFeature(name string, force bool) error {
170178
171179// GetFeaturePath returns the full path of a feature file
172180func (m * Manager ) GetFeaturePath (name string ) (string , error ) {
181+ // Normalize filename: replace spaces with dashes
182+ name = strings .TrimSpace (name )
183+ name = strings .ReplaceAll (name , " " , "-" )
184+ name = strings .ReplaceAll (name , "_" , "-" )
185+
173186 // Add .md extension if not present
174187 if ! strings .HasSuffix (name , ".md" ) {
175188 name = name + ".md"
@@ -210,3 +223,101 @@ Add any additional notes here.
210223*Created: %s*
211224` , baseName , time .Now ().Format ("2006-01-02 15:04:05" ))
212225}
226+
227+ // generateFeatureContent generates feature content from template and user content
228+ func generateFeatureContent (name string , userContent string ) string {
229+ // Try to read example-feature.md template
230+ templateContent := ""
231+ examplePath := filepath .Join ("tasks" , "example-feature.md" )
232+
233+ // Try different paths to find example-feature.md
234+ possiblePaths := []string {
235+ examplePath ,
236+ filepath .Join (".." , examplePath ),
237+ filepath .Join ("." , examplePath ),
238+ }
239+
240+ for _ , path := range possiblePaths {
241+ if data , err := os .ReadFile (path ); err == nil {
242+ templateContent = string (data )
243+ break
244+ }
245+ }
246+
247+ // If template not found, use default
248+ if templateContent == "" {
249+ templateContent = DefaultExampleFeature
250+ }
251+
252+ // Replace title placeholder
253+ baseName := strings .TrimSuffix (name , ".md" )
254+ baseName = strings .ReplaceAll (baseName , "-" , " " )
255+ baseName = strings .Title (baseName )
256+
257+ content := strings .ReplaceAll (templateContent , "Example Feature" , baseName )
258+ content = strings .ReplaceAll (content , "# Task: Example Feature" , fmt .Sprintf ("# Task: %s" , baseName ))
259+
260+ // If user provided content, replace or insert into Description section
261+ if userContent != "" {
262+ content = replaceDescriptionContent (content , userContent )
263+ }
264+
265+ // Add creation timestamp
266+ content = content + fmt .Sprintf ("\n ---\n *Created: %s*\n " , time .Now ().Format ("2006-01-02 15:04:05" ))
267+
268+ return content
269+ }
270+
271+ // replaceDescriptionContent replaces the content in Description section with user content
272+ func replaceDescriptionContent (template string , userContent string ) string {
273+ // Support both English and Chinese section headers
274+ // Find ## Description or ## 描述 section
275+ descMarkers := []string {"## Description" , "## 描述" }
276+
277+ var descIndex int = - 1
278+ var foundMarker string
279+
280+ for _ , marker := range descMarkers {
281+ if idx := strings .Index (template , marker ); idx != - 1 {
282+ descIndex = idx
283+ foundMarker = marker
284+ break
285+ }
286+ }
287+
288+ if descIndex == - 1 {
289+ // No description section found, append at end
290+ return template + "\n \n ## Description\n \n " + userContent + "\n "
291+ }
292+
293+ // Find the start of content after the marker
294+ contentStart := descIndex + len (foundMarker )
295+
296+ // Find the next section (any line starting with ##)
297+ lines := strings .Split (template [contentStart :], "\n " )
298+ contentEndOffset := 0
299+
300+ for i , line := range lines {
301+ trimmed := strings .TrimSpace (line )
302+ if i > 0 && strings .HasPrefix (trimmed , "## " ) {
303+ // Found next section
304+ for j := 0 ; j < i ; j ++ {
305+ contentEndOffset += len (lines [j ]) + 1 // +1 for newline
306+ }
307+ break
308+ }
309+ }
310+
311+ if contentEndOffset == 0 {
312+ // No next section found, use rest of template
313+ contentEndOffset = len (template ) - contentStart
314+ }
315+
316+ // Build new content
317+ before := template [:contentStart ] // Include marker
318+ after := template [contentStart + contentEndOffset :]
319+
320+ return before + "\n \n " + userContent + "\n " + after
321+ }
322+
323+
0 commit comments