Skip to content

Commit fee43da

Browse files
committed
fix: spark task create command bugs
Fix the following issues in spark task create command: 1. Makefile - Add clean dependency to build target and ldflags for optimized builds 2. Task Create - Filename normalization: - Convert spaces to dashes automatically - Convert underscores to dashes - Example: 'make script issue' -> 'make-script-issue.md' 3. Task Create - Template and content handling: - Properly read example-feature.md as template - Insert --content parameter into ## Description section - Support both English and Chinese section headers 4. Task Delete/Get - Consistent filename normalization Tests: - Add tests for filename with spaces/underscores - Add tests for duplicate detection with normalized names - Add tests for Chinese section header support Refs: #5
1 parent ba7c192 commit fee43da

6 files changed

Lines changed: 333 additions & 23 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,17 @@ spark task sync my-task --work-path ./workspace
327327
|--------|------|
328328
| `init` | 初始化任务目录结构 |
329329
| `list` | 列出所有任务和特性 |
330-
| `create` | 创建新特性文件 |
330+
| `create` | 创建新特性文件(文件名空格自动转换为 `-`)|
331331
| `delete` | 删除特性文件 |
332332
| `impl` | 实现特性(使用 kimi CLI)|
333333
| `dispatch` | 分发任务到新目录 |
334334
| `sync` | 同步任务回任务目录 |
335335

336+
**特性文件创建说明**:
337+
- 文件名中的空格和下划线会自动转换为 `-`
338+
- `--content` 参数的内容会写入 `## 描述` section
339+
- 如果存在 `example-feature.md`,会将其作为模板
340+
336341
**任务目录结构**:
337342
```
338343
tasks/

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ endif
2020

2121
all: build test
2222

23-
build:
24-
$(GO) build -o $(BINARY_NAME)$(BINARY_EXT) main.go
23+
build: clean
24+
$(GO) build -ldflags="-s -w" -o $(BINARY_NAME)$(BINARY_EXT) main.go
2525
@echo "Installing $(BINARY_NAME) to $(INSTALL_DIR)..."
2626
@mkdir -p $(INSTALL_DIR)
2727
@cp $(BINARY_NAME)$(BINARY_EXT) $(INSTALL_DIR)/$(BINARY_NAME)

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ spark task init
8181
# 列出所有任务和特性
8282
spark task list
8383

84-
# 创建新特性文件
84+
# 创建新特性文件(文件名中的空格会自动转换为 -)
8585
spark task create my-feature
86+
spark task create "my feature name" # 将创建 my-feature-name.md
8687

87-
# 创建带内容的特性文件
88+
# 创建带内容的特性文件(内容将写入 ## 描述 section)
8889
spark task create my-feature --content "Custom description"
8990

9091
# 删除特性文件

internal/task/task_feature.go

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
146149
func (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
172180
func (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

Comments
 (0)