Skip to content

Commit e89407b

Browse files
wbrezaCopilot
andauthored
feat: add FileWriter service and refactor waza init inventory #48 (#63)
* feat: add FileWriter service and refactor waza init inventory #48 - Create internal/scaffold/writer.go with FileWriter type that encapsulates the create-if-missing + skip-if-exists pattern - FileWriter returns structured Inventory with per-entry outcomes (created/skipped) - Inventory.Fprint() renders aligned table with emoji indicators: ➕ for created, ✅ (already exists) for skipped - Refactor cmd/waza/cmd_init.go to use FileWriter instead of inline write loop - Inventory is always visible (not gated behind --verbose) - Add 8 tests in writer_test.go covering: create-if-missing, skip-if-exists, mixed outcomes, parent directory creation, inventory output, relative paths, empty content handling, and CreatedCount Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: update Linus history with FileWriter work (#48) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: refactor waza new to use shared FileWriter #58 (#66) * feat: refactor waza new to use shared FileWriter #58 Replace the inline write loop in cmd_new.go with the shared FileWriter from internal/scaffold/writer.go. Malformed SKILL.md detection still runs before FileWriter — the file is removed so FileWriter creates it fresh. Inventory now uses consistent ➕/✅ emoji indicators (always visible, not gated behind --verbose), matching the waza init behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: update squad state for #58 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove .squad/ files from PR branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: remove .squad/ files from PR branch Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: improve error handling in FileWriter stat checks #48 - Directory branch: explicitly handle IsNotExist vs other stat errors - File branch: detect directory-at-file-path type mismatch - Both branches: return errors on permission failures instead of masking - Add regression tests for type-mismatch error paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: improve error handling in FileWriter stat checks #48 - Directory branch: explicitly handle IsNotExist vs other stat errors - File branch: detect directory-at-file-path type mismatch - Both branches: return errors on permission failures instead of masking - Add regression tests for type-mismatch error paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: improve error handling in FileWriter stat checks #48 - Directory branch: explicitly handle IsNotExist vs other stat errors - Directory branch: error when path exists but is not a directory - File branch: detect directory-at-file-path type mismatch - Both branches: return errors on permission failures instead of masking - Add regression tests for type-mismatch error paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: gofmt writer_test.go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 91cade3 commit e89407b

6 files changed

Lines changed: 465 additions & 273 deletions

File tree

.squad/agents/linus/history.md

Lines changed: 0 additions & 94 deletions
This file was deleted.

cmd/waza/cmd_init.go

Lines changed: 16 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -487,88 +487,38 @@ func initCommandE(cmd *cobra.Command, args []string, noSkill bool, flagSkillsDir
487487
}
488488

489489
// --- Phase 5: Create/verify project structure ---
490-
type initItem struct {
491-
path string
492-
label string
493-
isDir bool
494-
content string
495-
}
496-
497490
wazaConfigContent := ""
498491
if needConfigPrompt {
499492
wazaConfigContent = generateWazaConfig(engine, model, skillsPath, evalsPath, resultsPath)
500493
}
501494

502495
configLabel := fmt.Sprintf("Project defaults (%s, %s)", engine, model)
503496

504-
items := []initItem{
505-
{filepath.Join(absDir, skillsPath), "Skill definitions", true, ""},
506-
{filepath.Join(absDir, evalsPath), "Evaluation suites", true, ""},
507-
{filepath.Join(absDir, ".waza.yaml"), configLabel, false, wazaConfigContent},
508-
{filepath.Join(absDir, ".github", "workflows", "eval.yml"), "CI pipeline", false, initCIWorkflow()},
509-
{filepath.Join(absDir, ".gitignore"), "Build artifacts excluded", false, initGitignore()},
510-
{filepath.Join(absDir, "README.md"), "Getting started guide", false, initReadme(projectName)},
497+
entries := []scaffold.FileEntry{
498+
{Path: filepath.Join(absDir, skillsPath), Label: "Skill definitions", IsDir: true},
499+
{Path: filepath.Join(absDir, evalsPath), Label: "Evaluation suites", IsDir: true},
500+
{Path: filepath.Join(absDir, ".waza.yaml"), Label: configLabel, Content: wazaConfigContent},
501+
{Path: filepath.Join(absDir, ".github", "workflows", "eval.yml"), Label: "CI pipeline", Content: initCIWorkflow()},
502+
{Path: filepath.Join(absDir, ".gitignore"), Label: "Build artifacts excluded", Content: initGitignore()},
503+
{Path: filepath.Join(absDir, "README.md"), Label: "Getting started guide", Content: initReadme(projectName)},
511504
}
512505

513-
fmt.Fprintf(out, "\nProject structure:\n\n") //nolint:errcheck
514-
515-
var buf2 bytes.Buffer
516-
tw2 := tabwriter.NewWriter(&buf2, 0, 0, 2, ' ', 0)
517-
created := 0
518-
for _, item := range items {
519-
var indicator string
520-
var existed bool
521-
522-
if item.isDir {
523-
if info, err := os.Stat(item.path); err == nil && info.IsDir() {
524-
existed = true
525-
} else {
526-
if err := os.MkdirAll(item.path, 0o755); err != nil {
527-
return fmt.Errorf("failed to create %s: %w", item.path, err)
528-
}
529-
}
530-
} else {
531-
if _, err := os.Stat(item.path); err == nil {
532-
existed = true
533-
} else if item.content != "" {
534-
if err := os.MkdirAll(filepath.Dir(item.path), 0o755); err != nil {
535-
return fmt.Errorf("failed to create directory for %s: %w", item.path, err)
536-
}
537-
if err := os.WriteFile(item.path, []byte(item.content), 0o644); err != nil {
538-
return fmt.Errorf("failed to write %s: %w", item.path, err)
539-
}
540-
}
541-
}
542-
543-
// Relative path for display
544-
relPath := item.path
545-
if rel, err := filepath.Rel(absDir, item.path); err == nil {
546-
relPath = rel
547-
}
548-
if item.isDir {
549-
relPath += string(filepath.Separator)
550-
}
506+
fw := scaffold.NewFileWriter(absDir)
507+
inv, err := fw.Write(entries)
508+
if err != nil {
509+
return err
510+
}
551511

552-
if existed {
553-
indicator = "{exist}"
554-
} else {
555-
indicator = "{new}"
556-
created++
557-
}
512+
fmt.Fprintf(out, "\nProject structure:\n\n") //nolint:errcheck
513+
inv.Fprint(out)
558514

559-
fmt.Fprintf(tw2, " %s\t%s\t%s\n", indicator, relPath, item.label) //nolint:errcheck
560-
}
561-
tw2.Flush() //nolint:errcheck
562-
result2 := buf2.String()
563-
result2 = strings.ReplaceAll(result2, "{exist}", "✅")
564-
result2 = strings.ReplaceAll(result2, "{new}", "➕")
565-
fmt.Fprint(out, result2) //nolint:errcheck
515+
created := inv.CreatedCount()
566516

567517
// --- Phase 5b: Summary ---
568518
fmt.Fprintln(out) //nolint:errcheck
569519
if created == 0 {
570520
fmt.Fprintf(out, "✅ Project up to date.\n") //nolint:errcheck
571-
} else if created == len(items) {
521+
} else if created == len(entries) {
572522
fmt.Fprintf(out, "✅ Project created — %d items set up.\n", created) //nolint:errcheck
573523
} else {
574524
fmt.Fprintf(out, "✅ Repaired — %d item(s) added.\n", created) //nolint:errcheck

0 commit comments

Comments
 (0)