From 11996f1a0b7cdc91a2f6978c4ca06c2dda7f9bd3 Mon Sep 17 00:00:00 2001 From: mosir Date: Tue, 24 Feb 2026 23:57:13 +0800 Subject: [PATCH] refactor(pkg): move atomic file write to dedicated fileutil package --- pkg/agent/memory.go | 6 +++--- pkg/auth/store.go | 4 ++-- pkg/config/config.go | 4 ++-- pkg/cron/service.go | 4 ++-- pkg/{utils => fileutil}/file.go | 3 ++- pkg/heartbeat/service.go | 4 ++-- pkg/skills/installer.go | 4 ++-- pkg/state/state.go | 4 ++-- pkg/tools/filesystem.go | 4 ++-- pkg/tools/skills_install.go | 3 ++- 10 files changed, 21 insertions(+), 19 deletions(-) rename pkg/{utils => fileutil}/file.go (97%) diff --git a/pkg/agent/memory.go b/pkg/agent/memory.go index 7e952be55..87a687479 100644 --- a/pkg/agent/memory.go +++ b/pkg/agent/memory.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) // MemoryStore manages persistent memory for the agent. @@ -62,7 +62,7 @@ func (ms *MemoryStore) ReadLongTerm() string { func (ms *MemoryStore) WriteLongTerm(content string) error { // Use unified atomic write utility with explicit sync for flash storage reliability. // Using 0o600 (owner read/write only) for secure default permissions. - return utils.WriteFileAtomic(ms.memoryFile, []byte(content), 0o600) + return fileutil.WriteFileAtomic(ms.memoryFile, []byte(content), 0o600) } // ReadToday reads today's daily note. @@ -102,7 +102,7 @@ func (ms *MemoryStore) AppendToday(content string) error { } // Use unified atomic write utility with explicit sync for flash storage reliability. - return utils.WriteFileAtomic(todayFile, []byte(newContent), 0o600) + return fileutil.WriteFileAtomic(todayFile, []byte(newContent), 0o600) } // GetRecentDailyNotes returns daily notes from the last N days. diff --git a/pkg/auth/store.go b/pkg/auth/store.go index 34d7d8a3f..283dc6977 100644 --- a/pkg/auth/store.go +++ b/pkg/auth/store.go @@ -6,7 +6,7 @@ import ( "path/filepath" "time" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) type AuthCredential struct { @@ -71,7 +71,7 @@ func SaveStore(store *AuthStore) error { } // Use unified atomic write utility with explicit sync for flash storage reliability. - return utils.WriteFileAtomic(path, data, 0o600) + return fileutil.WriteFileAtomic(path, data, 0o600) } func GetCredential(provider string) (*AuthCredential, error) { diff --git a/pkg/config/config.go b/pkg/config/config.go index 9d2860407..67b71cfda 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "github.com/caarlos0/env/v11" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) // rrCounter is a global counter for round-robin load balancing across models. @@ -527,7 +527,7 @@ func SaveConfig(path string, cfg *Config) error { } // Use unified atomic write utility with explicit sync for flash storage reliability. - return utils.WriteFileAtomic(path, data, 0o600) + return fileutil.WriteFileAtomic(path, data, 0o600) } func (c *Config) WorkspacePath() string { diff --git a/pkg/cron/service.go b/pkg/cron/service.go index 7501117f5..f14d86bcb 100644 --- a/pkg/cron/service.go +++ b/pkg/cron/service.go @@ -11,7 +11,7 @@ import ( "time" "github.com/adhocore/gronx" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) type CronSchedule struct { @@ -336,7 +336,7 @@ func (cs *CronService) saveStoreUnsafe() error { } // Use unified atomic write utility with explicit sync for flash storage reliability. - return utils.WriteFileAtomic(cs.storePath, data, 0o600) + return fileutil.WriteFileAtomic(cs.storePath, data, 0o600) } func (cs *CronService) AddJob( diff --git a/pkg/utils/file.go b/pkg/fileutil/file.go similarity index 97% rename from pkg/utils/file.go rename to pkg/fileutil/file.go index e7c2675e4..7ca872374 100644 --- a/pkg/utils/file.go +++ b/pkg/fileutil/file.go @@ -4,7 +4,8 @@ // // Copyright (c) 2026 PicoClaw contributors -package utils +// Package fileutil provides file manipulation utilities. +package fileutil import ( "fmt" diff --git a/pkg/heartbeat/service.go b/pkg/heartbeat/service.go index 2108fbf8c..3aeca6188 100644 --- a/pkg/heartbeat/service.go +++ b/pkg/heartbeat/service.go @@ -19,7 +19,7 @@ import ( "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/state" "github.com/sipeed/picoclaw/pkg/tools" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) const ( @@ -276,7 +276,7 @@ This file contains tasks for the heartbeat service to check periodically. Add your heartbeat tasks below this line: ` - if err := utils.WriteFileAtomic(heartbeatPath, []byte(defaultContent), 0o644); err != nil { + if err := fileutil.WriteFileAtomic(heartbeatPath, []byte(defaultContent), 0o644); err != nil { hs.logError("Failed to create default HEARTBEAT.md: %v", err) } else { hs.logInfo("Created default HEARTBEAT.md template") diff --git a/pkg/skills/installer.go b/pkg/skills/installer.go index 12ffe33e6..d6ff5f3a3 100644 --- a/pkg/skills/installer.go +++ b/pkg/skills/installer.go @@ -10,7 +10,7 @@ import ( "path/filepath" "time" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) type SkillInstaller struct { @@ -68,7 +68,7 @@ func (si *SkillInstaller) InstallFromGitHub(ctx context.Context, repo string) er skillPath := filepath.Join(skillDir, "SKILL.md") // Use unified atomic write utility with explicit sync for flash storage reliability. - if err := utils.WriteFileAtomic(skillPath, body, 0o600); err != nil { + if err := fileutil.WriteFileAtomic(skillPath, body, 0o600); err != nil { return fmt.Errorf("failed to write skill file: %w", err) } diff --git a/pkg/state/state.go b/pkg/state/state.go index efccc9332..1663faa4c 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) // State represents the persistent state for a workspace. @@ -139,7 +139,7 @@ func (sm *Manager) saveAtomic() error { return fmt.Errorf("failed to marshal state: %w", err) } - return utils.WriteFileAtomic(sm.stateFile, data, 0o600) + return fileutil.WriteFileAtomic(sm.stateFile, data, 0o600) } // load loads the state from disk. diff --git a/pkg/tools/filesystem.go b/pkg/tools/filesystem.go index e40782ab6..03d461dcc 100644 --- a/pkg/tools/filesystem.go +++ b/pkg/tools/filesystem.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/sipeed/picoclaw/pkg/utils" + "github.com/sipeed/picoclaw/pkg/fileutil" ) // validatePath ensures the given path is within the workspace if restrict is true. @@ -280,7 +280,7 @@ func (h *hostFs) ReadDir(path string) ([]os.DirEntry, error) { func (h *hostFs) WriteFile(path string, data []byte) error { // Use unified atomic write utility with explicit sync for flash storage reliability. // Using 0o600 (owner read/write only) for secure default permissions. - return utils.WriteFileAtomic(path, data, 0o600) + return fileutil.WriteFileAtomic(path, data, 0o600) } // sandboxFs is a sandboxed fileSystem that operates within a strictly defined workspace using os.Root. diff --git a/pkg/tools/skills_install.go b/pkg/tools/skills_install.go index 57d29f355..71bfe730b 100644 --- a/pkg/tools/skills_install.go +++ b/pkg/tools/skills_install.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/sipeed/picoclaw/pkg/fileutil" "github.com/sipeed/picoclaw/pkg/logger" "github.com/sipeed/picoclaw/pkg/skills" "github.com/sipeed/picoclaw/pkg/utils" @@ -198,5 +199,5 @@ func writeOriginMeta(targetDir, registryName, slug, version string) error { } // Use unified atomic write utility with explicit sync for flash storage reliability. - return utils.WriteFileAtomic(filepath.Join(targetDir, ".skill-origin.json"), data, 0o600) + return fileutil.WriteFileAtomic(filepath.Join(targetDir, ".skill-origin.json"), data, 0o600) }