feat: add FileWriter service and refactor waza init inventory #48#63
Conversation
- 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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a reusable scaffolding writer service to standardize waza init’s create-if-missing / skip-if-exists behavior and to make the “project structure” inventory consistently visible and testable.
Changes:
- Added
internal/scaffold/FileWriterwith structuredInventoryoutput (➕ created, ✅ exists). - Refactored
cmd/waza/cmd_init.goPhase 5 to useFileWriterinstead of an inline write loop. - Added unit tests covering create/skip behavior, inventory output formatting, and parent directory creation.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| internal/scaffold/writer.go | New FileWriter + Inventory implementation for safe, repeatable scaffolding. |
| internal/scaffold/writer_test.go | New tests validating writer behavior and inventory output. |
| cmd/waza/cmd_init.go | Replaces inline scaffolding loop with FileWriter and uses Inventory for summary/output. |
| .squad/agents/linus/history.md | Documents the #48 refactor work for team history. |
Comments suppressed due to low confidence (1)
internal/scaffold/writer.go:113
- In the directory branch, any os.Stat error (including permission errors) falls through to MkdirAll. This can mask the real cause and can attempt a write in a path that exists but is inaccessible. Consider handling stat errors explicitly (skip only when err==nil && info.IsDir(); create only when os.IsNotExist(err); otherwise return an error that wraps the original stat failure).
if entry.IsDir {
if info, err := os.Stat(entry.Path); err == nil && info.IsDir() {
item.Outcome = OutcomeSkipped
} else {
if err := os.MkdirAll(entry.Path, 0o755); err != nil {
return nil, fmt.Errorf("failed to create %s: %w", entry.Path, err)
}
item.Outcome = OutcomeCreated
}
* 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>
wbreza
left a comment
There was a problem hiding this comment.
Architectural review: FileWriter extraction + always-visible inventory is the right direction, and the cmd_init complexity reduction is meaningful. Blocking issue: cmd_new.go currently does '_ = os.Remove(...)' in malformed SKILL.md repair paths; if remove fails (permissions/readonly), FileWriter will mark SKILL.md as ✅ skipped and the malformed file is left unrepaired. Fix: handle remove errors explicitly (ignore only IsNotExist, return all other errors) and add a regression test for remove/write error propagation in overwrite-repair flow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 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>
- 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>
- 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>
|
Addressing review feedback (d8d7630): Rusty, Copilot — both stat-handling issues are fixed in the FileWriter: Directory branch: Now uses three-way dispatch: \�rr == nil && IsDir()\ → skip, \�rr == nil && !IsDir()\ → error (type mismatch), \os.IsNotExist\ → create, otherwise → return the stat error (catches permission errors). File branch: Now checks \info.IsDir()\ when stat succeeds (errors if a directory sits where a file should be), and returns stat errors that aren't \IsNotExist\ instead of masking them. New regression tests: \TestFileWriter_DirEntry_FileExistsAtPath\ and \TestFileWriter_FileEntry_DirExistsAtPath\ cover both type-mismatch paths. Re: SKILL.md repair in cmd_new.go — agreed that's on the #58 branch scope, not this PR. The FileWriter now properly surfaces all stat/permission errors, so downstream consumers like cmd_new.go will get proper error propagation. All scaffold tests pass (9/9). — Linus |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (3)
internal/scaffold/writer.go:117
- For file entries, if the path exists but is a directory, Write() currently marks it as OutcomeSkipped. That leaves the project in a broken state (required file missing) without surfacing an error. Consider checking the file type and returning a clear error when an unexpected directory exists at a file path.
}
item.Outcome = OutcomeCreated
} else {
return nil, fmt.Errorf("failed to stat %s: %w", entry.Path, err)
cmd/waza/cmd_new_test.go:165
- Same issue here: "✅" is also present in the summary footer, so this assertion can pass even if no inventory entry was skipped. Prefer asserting the skipped-line suffix ("already exists") and/or the specific file path/label.
require.NoError(t, err)
assert.Equal(t, validSkillMD, string(data), "existing SKILL.md should not be overwritten")
// Output should mention ✅ for skipped file
assert.Contains(t, buf.String(), "✅")
cmd/waza/cmd_new_test.go:197
- This assertion checks for "✅", but that string will always appear in the footer (e.g. "✅ Project up to date.") even if the inventory formatting regresses. Consider asserting for the per-item skipped marker plus "(already exists)" to ensure the inventory output is validated.
output := buf.String()
assert.Contains(t, output, "✅")
assert.Contains(t, output, "Project up to date")
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ft#48 (microsoft#63) * feat: add FileWriter service and refactor waza init inventory microsoft#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 (microsoft#48) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: refactor waza new to use shared FileWriter microsoft#58 (microsoft#66) * feat: refactor waza new to use shared FileWriter microsoft#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 microsoft#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 microsoft#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 microsoft#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 microsoft#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>
Closes #48
What changed
internal/scaffold/writer.gowithFileWriterservicecmd_init.goto use FileWriter (replaced ~65 lines of inline loop)Testing
go test ./...green