mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-12 16:23:18 -10:00
With ZERO_FORMAT_ON_WRITE=1, a successful edit/write runs the language's community-standard formatter on the file (gofmt, rustfmt, ruff, prettier, zig fmt, dart format, terraform fmt, shfmt, clang-format, ktlint, swiftformat, stylua, gleam format — 27 extensions), so model output always lands in project-canonical style and never fails a format check it cannot see. Best-effort: missing binary, error, or 10s timeout leaves the unformatted write standing. Off by default per house convention — auto-reformatting changes bytes the model did not write. Ordering is load-bearing: formatting runs BEFORE the FileTracker re-baseline so the recorded content matches what the formatter left on disk; baselining pre-format content would make the very next edit trip the external-modification conflict guard (covered by test). Co-Authored-By: Claude Fable 5 <[email protected]>
98 lines
3.8 KiB
Go
98 lines
3.8 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Format-on-write for the mutating file tools. When enabled, a successful
|
|
// edit_file/write_file runs the language's standard formatter on the file it
|
|
// just wrote, so the model's output always lands in project-canonical style
|
|
// and never fails a CI format check it cannot see. Off by default (set
|
|
// ZERO_FORMAT_ON_WRITE=1): auto-reformatting changes bytes the model did not
|
|
// write, which strict workflows may not want.
|
|
//
|
|
// Ordering matters: formatting runs BEFORE the FileTracker re-baseline, and
|
|
// the caller records the POST-format content. Formatting after the baseline
|
|
// would make the very next edit look like an external modification and trip
|
|
// the conflict guard.
|
|
|
|
// formatOnWriteTimeout bounds one formatter run; a wedged formatter must never
|
|
// hang a tool call. On timeout the unformatted write stands.
|
|
const formatOnWriteTimeout = 10 * time.Second
|
|
|
|
// formatterCommands maps a file extension to the formatter argv; the file path
|
|
// is appended as the final argument. Only in-place, config-respecting,
|
|
// community-standard formatters — a missing binary silently skips formatting.
|
|
var formatterCommands = map[string][]string{
|
|
".go": {"gofmt", "-w"},
|
|
".rs": {"rustfmt"},
|
|
".py": {"ruff", "format", "--quiet"},
|
|
".ts": {"prettier", "--log-level", "silent", "--write"},
|
|
".tsx": {"prettier", "--log-level", "silent", "--write"},
|
|
".js": {"prettier", "--log-level", "silent", "--write"},
|
|
".jsx": {"prettier", "--log-level", "silent", "--write"},
|
|
".json": {"prettier", "--log-level", "silent", "--write"},
|
|
".css": {"prettier", "--log-level", "silent", "--write"},
|
|
".scss": {"prettier", "--log-level", "silent", "--write"},
|
|
".html": {"prettier", "--log-level", "silent", "--write"},
|
|
".md": {"prettier", "--log-level", "silent", "--write"},
|
|
".yaml": {"prettier", "--log-level", "silent", "--write"},
|
|
".yml": {"prettier", "--log-level", "silent", "--write"},
|
|
".zig": {"zig", "fmt"},
|
|
".dart": {"dart", "format"},
|
|
".tf": {"terraform", "fmt"},
|
|
".gleam": {"gleam", "format"},
|
|
".sh": {"shfmt", "-w"},
|
|
".bash": {"shfmt", "-w"},
|
|
".c": {"clang-format", "-i"},
|
|
".h": {"clang-format", "-i"},
|
|
".cpp": {"clang-format", "-i"},
|
|
".hpp": {"clang-format", "-i"},
|
|
".cc": {"clang-format", "-i"},
|
|
".kt": {"ktlint", "-F"},
|
|
".swift": {"swiftformat"},
|
|
".lua": {"stylua"},
|
|
}
|
|
|
|
// formatOnWriteEnabled reports whether the opt-in env toggle is set.
|
|
func formatOnWriteEnabled() bool {
|
|
value := strings.TrimSpace(os.Getenv("ZERO_FORMAT_ON_WRITE"))
|
|
return value != "" && value != "0" && !strings.EqualFold(value, "false")
|
|
}
|
|
|
|
// maybeFormatWrittenFile runs the configured formatter for absolutePath (when
|
|
// enabled and on PATH) and returns the file's content afterwards. Best-effort
|
|
// throughout: any failure — no formatter, formatter error, timeout, unreadable
|
|
// result — returns writtenContent so the caller's state matches the last write
|
|
// it performed itself.
|
|
func maybeFormatWrittenFile(ctx context.Context, absolutePath string, writtenContent string) string {
|
|
if !formatOnWriteEnabled() {
|
|
return writtenContent
|
|
}
|
|
command, ok := formatterCommands[strings.ToLower(filepath.Ext(absolutePath))]
|
|
if !ok {
|
|
return writtenContent
|
|
}
|
|
if _, err := exec.LookPath(command[0]); err != nil {
|
|
return writtenContent
|
|
}
|
|
formatCtx, cancel := context.WithTimeout(ctx, formatOnWriteTimeout)
|
|
defer cancel()
|
|
arguments := append(append([]string(nil), command[1:]...), absolutePath)
|
|
formatter := exec.CommandContext(formatCtx, command[0], arguments...)
|
|
formatter.Dir = filepath.Dir(absolutePath)
|
|
if err := formatter.Run(); err != nil {
|
|
return writtenContent
|
|
}
|
|
formatted, err := os.ReadFile(absolutePath)
|
|
if err != nil {
|
|
return writtenContent
|
|
}
|
|
return string(formatted)
|
|
}
|