Skip to content

Feature/refactor-go 使用 Go 重新实现项目功能#14

Merged
YewFence merged 34 commits intomainfrom
feature/refactor-go
Jan 18, 2026
Merged

Feature/refactor-go 使用 Go 重新实现项目功能#14
YewFence merged 34 commits intomainfrom
feature/refactor-go

Conversation

@YewFence
Copy link
Copy Markdown
Owner

@YewFence YewFence commented Jan 16, 2026

可以编译成多平台单可执行文件,错误处理更完善
顺便修改了一下 Github Action,删除了原来的 latest 版本发布,手动发布更明确点
增加了手动触发 Github Action 的功能,去除了 Push 即构建的功能

Summary by CodeRabbit

  • New Features

    • Go v2.x CLI: full backup orchestrator with managed service stop/start, Kopia snapshots, notifications, optional GitHub Gist log uploads, configurable logging, and CLI flags (config, version, dry-run, auto-confirm).
  • Build

    • Cross-platform Go Makefile and packaging producing platform-specific binaries; module and dependency added.
  • CI

    • PR artifact builds and tagged production-release pipeline added; previous dev-release workflow removed.
  • Documentation

    • README expanded for dual Shell v1.x / Go v2.x usage; new developer guidance file and Go TODO.
  • Tests

    • Extensive unit tests for config, logger, Docker manager, orchestrator, and CLI confirm logic.

✏️ Tip: You can customize this high-level summary in your review settings.

YewFence and others added 6 commits January 16, 2026 10:40
- main.go: CLI 参数解析、信号处理
- config.go: 配置加载(.env + 环境变量)
- docker.go: Docker Compose 服务发现与启停
- backup.go: Kopia 依赖检查、仓库连接、快照创建
- orchestrator.go: 主流程编排(停止→备份→恢复)
- notify.go: Apprise 通知发送
- CLAUDE.md: Claude Code 项目指引文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- 新增 logger.go: 支持同时输出到终端和日志文件,可捕获日志内容供上传
- 新增 gist.go: 实现 GitHub Gist 日志上传和旧日志自动清理
- 修改 docker.go: 添加 StopParallel/StartParallel 方法实现并行服务管理
- 修改 orchestrator.go: 普通服务并行处理,优先服务保持顺序执行
- 修改 main.go: 集成新的日志初始化流程

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- 新增 go/Makefile 支持多平台交叉编译 (linux/darwin/windows)
- 新增 go-build-artifact.yml 用于 PR 和非 main 分支构建
- 新增 go-dev-release.yml 用于开发版自动发布
- 新增 go-prod-release.yml 用于 v2* tag 正式发布
- 更新 .gitignore 忽略 dist 目录

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- 说明两个版本的区别和版本号规则 (v1.x Shell, v2.x Go)
- 添加 Go 版本的下载和使用说明
- 更新命令行参数表格,区分两版本支持的参数
- 添加 Go 版本的开发说明和构建命令

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 16, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a Go v2 implementation (CLI, orchestrator, Docker manager, Kopia backup, Gist uploader, Apprise notifier, logger), tests, Go build tooling (Makefile, go.mod), new GitHub Actions workflows for build/release, README/CLAUDE updates, and removes the prior dev-release workflow.

Changes

Cohort / File(s) Change Summary
CI Workflows
'.github/workflows/...'
'.github/workflows/go-build-artifact.yml', '.github/workflows/go-prod-release.yml', '.github/workflows/build-artifact.yml'
Added Go-focused PR/dispatch build workflow and a tag-based production release workflow; adjusted triggers, checkout ref handling, and artifact uploads/retention.
Removed Workflow
'.github/workflows/dev-release.yml'
Deleted the previous development release workflow.
Docs & Guides
'CLAUDE.md', 'README.md', 'go/TODO.md'
Added CLAUDE guidance, expanded README for Shell v1.x / Go v2.x usage, and added Go TODO.
Go Build System
**go/**
'go/Makefile, go/.gitignore, go/go.mod'
New cross-platform Makefile, updated go/.gitignore, and new go.mod (module path, go 1.23, godotenv).
Configuration
**go/**
'go/config.go, go/config_test.go'
New Config type with .env loading/validation, env helpers, masked Print(), plus unit tests for parsing and error cases.
Logging
**go/**
'go/logger.go, go/logger_test.go'
In-memory multi-writer LogCapture, InitLogger with optional file output, and tests validating logger paths.
Docker Compose Manager
**go/**
'go/docker.go, go/docker_test.go'
DockerManager and Service types with discovery, IsRunning, prioritized Start/Stop, parallel operations, script/compose detection, ServiceError, and tests.
Kopia Backup
**go/**
'go/backup.go'
KopiaBackup type with dependency checks, repository connect via rclone, repository validation, and CreateSnapshot (dry-run support).
Gist Uploader
**go/**
'go/gist.go'
GistManager for uploading logs to GitHub Gist and cleanup of old logs with configurable retention and keep-first-file option.
Notifier (Apprise)
**go/**
'go/notify.go'
Notifier for asynchronous Apprise POSTs with NewNotifier, Send, and Wait.
Orchestration & CLI
**go/**
'go/orchestrator.go, go/main.go, go/main_test.go, go/orchestrator_test.go'
Orchestrator coordinating lock acquisition, service classification, stop/start sequencing, snapshot creation, gist upload, notifications, cleanup, CLI flags, signal handling, and comprehensive tests with fakes.
Tests & Misc
**go/**
'go/*_test.go, go/TODO.md'
Unit tests across config, logger, docker manager, orchestrator; TODO and small misc additions.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (main.go)
    participant Orch as Orchestrator
    participant Docker as DockerManager
    participant Kopia as KopiaBackup
    participant Lock as Lock (dir)
    participant Gist as GistManager
    participant Notifier as Notifier

    CLI->>Orch: Run()
    Orch->>Kopia: CheckDependencies()
    Kopia-->>Orch: ok / err
    Orch->>Kopia: CheckRepository()
    Kopia-->>Orch: ok / err
    Orch->>Lock: acquireLock()
    Lock-->>Orch: acquired / fail
    Orch->>Docker: DiscoverServices()
    Docker-->>Orch: services
    Orch->>Docker: ClassifyServices(priority list)
    Docker-->>Orch: priority, normal
    Orch->>Docker: StopParallel(normal)
    Docker-->>Orch: results
    Orch->>Docker: Stop(priority) (serial)
    Docker-->>Orch: results
    Orch->>Kopia: CreateSnapshot()
    Kopia-->>Orch: snapshot result
    Orch->>Docker: Start(priority) (serial)
    Docker-->>Orch: results
    Orch->>Docker: StartParallel(normal)
    Docker-->>Orch: results
    Orch->>Gist: Upload(logContent)
    Gist-->>Orch: upload result
    Orch->>Notifier: Send(final status)
    Notifier-->>Orch: async ack
    Orch->>Lock: releaseLock()
    Lock-->>Orch: released
    Orch-->>CLI: completion / exit
Loading
sequenceDiagram
    participant Main as main.go
    participant Config as Config loader
    participant Logger as Logger
    participant Orch as Orchestrator

    Main->>Config: LoadConfig(path)
    Config->>Config: load .env & parse env vars
    Config-->>Main: *Config / error
    Main->>Logger: InitLogger(cfg.LogFile)
    Logger-->>Main: *os.File / nil
    Main->>Main: cfg.Print()
    Main->>Orch: NewOrchestrator(cfg, dryRun)
    Main->>Orch: Run()
    Orch-->>Main: result / error
    Main->>Orch: Cleanup() on signal or exit
    Orch-->>Main: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Poem

🐰 I hopped through code and stitched each part,
Binaries born and services start,
Gists hold tales of tasks we've run,
Snapshots saved when day is done,
A tiny hop — v2 has begun!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: a comprehensive Go reimplementation of the project with multi-platform compilation support, as evidenced by new Go workflows, Makefile, and orchestrator architecture replacing the shell-based system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @YewFence, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求引入了YewResin Docker Compose备份工具的Go语言重构版本。新版本旨在提供更强大的跨平台兼容性、通过并行处理提升性能,并改进错误处理机制。它保留了停止服务、创建Kopia快照并重启服务的核心功能,同时通过模块化的Go代码库和增强的日志与通知系统,为用户带来了更稳定和高效的备份体验。

Highlights

  • Go语言重构: 项目核心功能已使用Go语言重新实现,提供跨平台单可执行文件,并优化了性能和错误处理。
  • 并行服务管理: Go版本引入了并行停止和启动Docker Compose服务的能力,显著提高了备份流程的效率。
  • 增强的日志与通知: 新的日志系统支持文件输出和GitHub Gist上传,并集成了Apprise通知,提供更完善的备份状态反馈。
  • 模块化设计: Go代码库采用模块化结构,易于维护和扩展,并提供了详细的Makefile支持多平台交叉编译。
  • 文档更新: README.md和新增的CLAUDE.md文件已更新,详细说明了Shell和Go两个版本的特点、安装、使用和开发流程。
Ignored Files
  • Ignored by pattern: .github/workflows/** (3)
    • .github/workflows/go-build-artifact.yml
    • .github/workflows/go-dev-release.yml
    • .github/workflows/go-prod-release.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c376a780dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 引入了 Go 语言对项目的重构,这是一个巨大的进步。整体代码结构清晰,模块化合理,并且充分利用了 Go 的并发特性来提升性能。新增的 Go 版本代码质量很高,包含了单元测试、跨平台编译支持和完善的错误处理机制。我的审查主要集中在一些可以提升代码健壮性和可维护性的细节上:

  • 修正了从环境变量解析整数时的潜在 bug。
  • 提高了布尔配置的容错性。
  • 改进了交互式确认的输入处理方式。
  • 指出了一个潜在的跨平台兼容性问题。
  • 建议更新项目文档以反映 Go 版本的加入。
    此外,go.mod 中使用的 Go 版本(1.23)尚未正式发布,建议降级到稳定版本以方便其他开发者协作。总体而言,这是一次出色的重构,显著提升了项目的性能和可维护性。合并这些修改后,项目将更加完善。

return false
}
// 在 Unix 系统上检查执行权限
return info.Mode()&0111 != 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

isExecutable 函数中 info.Mode()&0111 != 0 的检查方式在非 Unix 系统(如 Windows)上无法正常工作。由于项目提供了 Windows 平台的构建,这个函数可能会导致在 Windows 环境下无法正确识别可执行的 .sh 脚本(即使通过 Git Bash 或 WSL 运行)。考虑到跨平台兼容性,可以考虑调整这里的逻辑,或者明确在文档中指出在 Windows 上自定义启停脚本的限制。一个简单的改进是在 Windows 上总是返回 false,因为 .sh 脚本本身在原生 Windows 环境中不可执行。

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@CLAUDE.md`:
- Around line 1-76: Update CLAUDE.md to add a "Go v2" section describing the new
Go rewrite: document the go/ directory layout, list Go build/test commands
(e.g., make build, make all, make test under go/), outline the Go architecture
and key files (main.go, orchestrator.go, config.go, docker.go and any package
names), and enumerate Go-specific CI workflows (go-dev-release.yml,
go-prod-release.yml, go-build-artifact.yml); place this Section alongside the
existing shell v1 docs and include brief instructions on when to use the shell
vs Go implementation and how to build/test the Go binary.

In `@go/config.go`:
- Around line 147-154: getEnvInt currently uses fmt.Sscanf and returns the zero
value 0 when parsing fails (e.g., "abc"), so update getEnvInt to detect parse
errors and fall back to defaultVal; specifically, either replace fmt.Sscanf with
strconv.Atoi and if strconv.Atoi returns an error return defaultVal, or keep
Sscanf but check its return (number of items scanned or error) and return
defaultVal on failure; ensure the function still returns the parsed int when
successful and defaultVal otherwise.

In `@go/logger.go`:
- Around line 26-35: The LogCapture.Write method currently ignores errors from
each writer; update LogCapture.Write to collect and handle write errors from
lc.writers (and from appending to lc.buffer if applicable), returning a non-nil
error when writes fail (at minimum if all writers fail) and/or attempt a
fallback log to stderr; specifically modify func (lc *LogCapture) Write(p
[]byte) to iterate writers, capture individual errors, aggregate or pick the
first meaningful error, and return it (instead of always nil), and optionally
write a fallback message to os.Stderr when a writer.Write(p) returns an error.
- Around line 12-16: LogCapture's buffer is not synchronized and can be
written/read concurrently causing data races; add a sync.Mutex (or sync.RWMutex)
field to the LogCapture struct and protect accesses to buffer in Write and
GetContent (lock around appending to buffer and when returning its contents) and
also protect any iteration over writers while writing to them; update Write to
lock, append to buffer, unlock (or use defer), then write to writers under the
same lock or copy buffer to a local var for writing outside the lock, and update
GetContent to acquire the lock and return a copy of buffer to avoid exposing
internal slice.

In `@go/orchestrator.go`:
- Around line 143-149: The loop is appending full error strings to
failedServices; update handling so only service names are added—have
o.docker.StartParallel return structured results (e.g., a slice of structs with
ServiceName and Err) or a map from service name to error, then in the
error-handling branch iterate those results and append the ServiceName (not
err.Error()) to failedServices; adjust the loop that currently iterates over
errs from o.docker.StartParallel and replace err.Error() with the struct field
(e.g., result.ServiceName) or the map key so notifications list only service
names.
- Around line 44-126: The Run method acquires a lock via acquireLock() but never
releases it on the normal success path; change Run so releaseLock() is always
called after a successful acquire — simplest fix: call defer o.releaseLock()
immediately after o.acquireLock() returns nil (or ensure releaseLock() is
invoked in all return paths after services are stopped and snapshots/upload
complete). Update references in Run (acquireLock, releaseLock, startAllServices,
Cleanup) to avoid leaving the lock held.
🧹 Nitpick comments (15)
go/.gitignore (1)

1-2: Consider adding the binary name to ignore non-Windows builds.

The dist/ directory will catch most build outputs, but if binaries are ever built outside dist/ (e.g., during local development with go build), the Linux/macOS executables (without .exe extension) could be accidentally committed. Consider adding the binary name directly.

Suggested additions
 *.exe
 dist/
+# Local build output
+yewresin
go/logger_test.go (1)

11-27: Consider test isolation for global state.

This test modifies the global LogWriter and slog default logger. If other tests in this package also call InitLogger, they may interfere with each other when run in parallel (go test -parallel). Consider using t.Cleanup() to reset the global state after each test, or restructure to avoid relying on globals in tests.

.github/workflows/go-dev-release.yml (1)

32-42: Pin the action version to avoid unexpected breakage.

Using @latest for marvinpinto/action-automatic-releases is risky for CI stability. The static analysis also indicates this action's runner may be outdated. Consider pinning to a specific version or SHA, or migrating to a maintained alternative like softprops/action-gh-release.

♻️ Suggested fix using softprops/action-gh-release
      - name: Update Latest Release
-        uses: "marvinpinto/action-automatic-releases@latest"
+        uses: softprops/action-gh-release@v2
        with:
-          repo_token: "${{ secrets.GITHUB_TOKEN }}"
-          automatic_release_tag: "latest"
+          tag_name: "latest"
          prerelease: true
-          title: "Development Build (Latest)"
+          name: "Development Build (Latest)"
          files: |
            yewresin.sh
            .env.example
            go/dist/*
go/notify.go (1)

40-40: Fire-and-forget goroutine may not complete on application exit.

The go n.sendAsync(...) launches a detached goroutine. If the application exits shortly after calling Send() (e.g., at the end of a backup run), the notification may never be delivered. Consider using a sync.WaitGroup or channel-based mechanism to allow graceful waiting for pending notifications before shutdown.

♻️ Example approach with WaitGroup
type Notifier struct {
	url        string
	notifyURL  string
	deviceName string
	wg         sync.WaitGroup
}

func (n *Notifier) Send(title, body string) {
	// ... validation ...
	n.wg.Add(1)
	go func() {
		defer n.wg.Done()
		n.sendAsync(title, body)
	}()
}

func (n *Notifier) Wait() {
	n.wg.Wait()
}

Call notifier.Wait() before application exit in the orchestrator.

go/Makefile (2)

11-12: Consider adding CGO_ENABLED=0 for fully static binaries.

For maximum cross-platform compatibility and truly static binaries (especially for Linux), consider disabling CGO:

♻️ Suggested change
 LDFLAGS := -s -w -X main.version=$(VERSION)
-GO_BUILD := go build -ldflags "$(LDFLAGS)"
+GO_BUILD := CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)"

This ensures binaries don't link against glibc and can run on any Linux distribution (e.g., Alpine, musl-based containers).


14-15: Unused PLATFORMS variable.

The PLATFORMS variable is defined but never used. You could either remove it or leverage it for a loop-based build (though the current explicit targets are clearer for maintenance).

go/config.go (1)

41-58: Consider environment variable isolation.

godotenv.Load modifies the process's global environment. This could cause issues if LoadConfig is called multiple times or concurrently (e.g., in tests). For this CLI tool's single-run lifecycle, this is acceptable, but be aware of this behavior if the function is reused elsewhere.

go/config_test.go (1)

21-31: Consider adding a test case for invalid integer input.

Given the bug in getEnvInt where invalid input returns 0 instead of the default, adding a test case like t.Setenv("TEST_INT", "invalid") would document the expected behavior (and currently fail to return the default).

💡 Suggested test addition
// Test invalid input - should return default
t.Setenv("TEST_INT", "invalid")
if got := getEnvInt("TEST_INT", 7); got != 7 {
	t.Fatalf("expected default 7 for invalid input, got %d", got)
}
go/docker.go (1)

92-105: Consider adding a timeout to prevent hanging on unresponsive Docker daemon.

exec.Command without a context or timeout could hang indefinitely if Docker is unresponsive.

💡 Suggested improvement with context timeout
+import (
+	"context"
+	"time"
+)

 func (dm *DockerManager) IsRunning(svc *Service) bool {
-	cmd := exec.Command("docker", "compose", "ps", "-q")
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "docker", "compose", "ps", "-q")
 	cmd.Dir = svc.Path
 	// ...
 }

Apply similar timeout pattern to Stop and Start methods.

go/main.go (1)

67-75: Signal handler exits immediately after cleanup without allowing main goroutine to finish.

The os.Exit(1) in the signal handler prevents any deferred cleanup in main() (like logFile.Close()) from running. This is generally acceptable for signal handlers, but worth noting.

💡 Alternative: Use context cancellation for graceful shutdown

A more sophisticated approach would use context.Context cancellation to signal shutdown to orch.Run(), allowing the main function to exit normally with deferred cleanup. However, the current approach is simpler and sufficient for this CLI tool.

README.md (2)

206-221: Add language specifier to fenced code block.

Per static analysis, this code block lacks a language specifier. Since this displays a directory structure, consider using text or plaintext.

Proposed fix
-```
+```text
 YewResin/
 ├── yewresin.sh            # 生成的脚本(由 make build 生成)

243-255: Add language specifier to fenced code block.

Same issue as above - add text or plaintext as the language specifier.

Proposed fix
-```
+```text
 go/
 ├── main.go           # 程序入口,CLI 参数解析
go/backup.go (1)

58-66: Substring matching may produce false positives.

strings.Contains(outputStr, k.expectedRemote) could match unintended paths. For example, if expectedRemote is gdrive:backup, it would also match gdrive:backup-old in the output.

Consider using a more precise match, such as parsing the status output or using a regex with word boundaries.

go/gist.go (1)

50-85: Potential timezone inconsistency in log timestamps.

Line 51 uses time.Now().UTC() for the filename timestamp, but Lines 84-85 use startTime.Format(...) and time.Now().Format(...) without explicit timezone handling. If startTime is in local time (which it is, set in orchestrator.go Line 45), the log content will mix local time with UTC filename.

Consider using consistent timezone handling throughout:

Proposed fix
-		time.Now().Format("2006-01-02 15:04:05"), logContent)
+		time.Now().UTC().Format("2006-01-02 15:04:05 UTC"), logContent)

Or convert startTime to UTC as well for consistency.

go/orchestrator.go (1)

183-195: Use os.RemoveAll() for more robust lock cleanup.

The lock directory is created empty with os.Mkdir() and os.Remove() will successfully delete it. However, use os.RemoveAll() to be defensive against any future writes to the lock directory.

Proposed fix
-	if err := os.Remove(o.cfg.LockFile); err != nil {
+	if err := os.RemoveAll(o.cfg.LockFile); err != nil {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a733774 and c376a78.

⛔ Files ignored due to path filters (1)
  • go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (19)
  • .github/workflows/go-build-artifact.yml
  • .github/workflows/go-dev-release.yml
  • .github/workflows/go-prod-release.yml
  • CLAUDE.md
  • README.md
  • go/.gitignore
  • go/Makefile
  • go/TODO.md
  • go/backup.go
  • go/config.go
  • go/config_test.go
  • go/docker.go
  • go/gist.go
  • go/go.mod
  • go/logger.go
  • go/logger_test.go
  • go/main.go
  • go/notify.go
  • go/orchestrator.go
🧰 Additional context used
🧬 Code graph analysis (3)
go/logger_test.go (1)
go/logger.go (2)
  • InitLogger (44-67)
  • LogWriter (10-10)
go/main.go (3)
go/config.go (1)
  • LoadConfig (42-103)
go/logger.go (1)
  • InitLogger (44-67)
go/orchestrator.go (1)
  • NewOrchestrator (32-41)
go/config_test.go (1)
go/config.go (1)
  • LoadConfig (42-103)
🪛 actionlint (1.7.10)
.github/workflows/go-dev-release.yml

33-33: the runner of "marvinpinto/action-automatic-releases@latest" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 checkmake (0.2.2)
go/Makefile

[warning] 26-26: Target body for "all" exceeds allowed length of 5 (8).

(maxbodylength)


[warning] 82-82: Target body for "help" exceeds allowed length of 5 (18).

(maxbodylength)

🪛 markdownlint-cli2 (0.18.1)
README.md

206-206: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


243-243: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (38)
.github/workflows/go-build-artifact.yml (1)

1-44: Well-structured CI workflow.

The workflow configuration is appropriate:

  • Path filtering ensures it only runs for Go-related changes
  • Go version (1.23) is consistent with go.mod
  • Minimal permissions (contents: read) follow the principle of least privilege
  • Latest action versions are used
.github/workflows/go-prod-release.yml (1)

1-38: Production release workflow looks solid.

The workflow correctly:

  • Triggers only on v2* tags (appropriate for Go v2)
  • Passes the tag name as VERSION for build-time embedding
  • Uses softprops/action-gh-release@v2 with auto-generated release notes
  • Has appropriate write permissions for release creation
go/TODO.md (1)

1-15: Good project tracking document.

Clear checklist of implemented and pending features. The completed items align with the new files added in this PR (Gist upload, parallel operations, logging, Makefile, CI/CD, tests).

go/go.mod (1)

1-5: Module setup is correct.

The godotenv dependency at v1.5.1 is the latest available version with no known security vulnerabilities. The Go 1.23 version and minimal dependency footprint are appropriate for the project.

go/logger_test.go (1)

29-49: LGTM!

Good use of t.TempDir() for automatic cleanup. The test correctly closes the file before reading to ensure all buffered data is flushed to disk.

.github/workflows/go-dev-release.yml (1)

39-42: Verify intentional inclusion of shell script in Go release.

The release includes yewresin.sh (shell v1) alongside Go binaries. If this is intentional for backward compatibility, consider documenting this. Otherwise, remove it to keep the Go release artifacts focused.

go/notify.go (1)

44-68: LGTM on the HTTP client implementation.

Good practices: timeout configured, response body closed via defer, appropriate status code validation, and error logging without failing the main flow.

go/Makefile (1)

19-100: LGTM on the overall Makefile structure.

The build targets are well-organized with clear help documentation. The version embedding via ldflags and multi-platform support are properly implemented. The static analysis warnings about target body length are acceptable given the clarity and maintainability of the current structure.

go/config.go (4)

12-39: LGTM!

The Config struct is well-organized with clear grouping of related fields and helpful Chinese comments explaining each field's purpose.


60-103: LGTM!

Configuration loading logic is solid with proper validation of required fields, existence check for BaseDir, and sensible defaults. Error messages are clear and actionable.


105-136: LGTM!

The Print method properly masks sensitive fields (GistToken, KopiaPassword, AppriseURL) while displaying useful configuration information. Good security practice.


167-172: LGTM!

The maskString function provides reasonable masking logic—short strings are fully masked while longer strings show partial content for identification.

go/config_test.go (5)

9-19: LGTM!

Tests both fallback behavior and value override cases for getEnvDefault.


33-57: LGTM!

Good use of table-driven tests for boolean parsing with comprehensive coverage of common boolean representations.


59-68: LGTM!

Tests both short string masking and partial masking for longer strings.


70-90: LGTM!

Comprehensive test for successful config loading with proper use of t.TempDir() and t.Setenv() for test isolation.


92-121: LGTM!

Error condition tests properly verify that missing required fields and non-existent directories produce appropriate errors with Chinese error messages.

go/logger.go (1)

44-66: LGTM!

The InitLogger function properly initializes the logging system with optional file output and configures slog as the default logger. File handle is returned for proper cleanup by the caller.

go/docker.go (7)

14-27: LGTM!

Well-defined constants for compose filenames and a clear Service struct with appropriate fields.


43-69: LGTM!

Service discovery logic is straightforward—scans directories for compose files or scripts and checks running status.


107-148: LGTM!

The Stop method has clear priority logic for stop methods (scripts over compose commands), proper dry-run support, and good logging.


150-187: LGTM!

The Start method mirrors Stop with appropriate script-first logic and dry-run support.


189-197: Note: isExecutable uses Unix-specific permission check.

The 0111 bitmask check works on Unix but may not behave as expected on Windows. Since this tool targets Docker environments (typically Linux), this is acceptable.


199-214: LGTM!

ClassifyServices correctly partitions services using case-insensitive matching for flexibility.


216-265: LGTM!

Parallel operations are correctly implemented with sync.WaitGroup for coordination and mutex-protected error collection. The loop variable capture pattern go func(s *Service) is correct.

go/main.go (4)

16-42: LGTM!

CLI flag definitions are well-structured with both long and short forms, clear help text, and proper version handling.


44-65: LGTM!

Configuration loading, logger initialization, and orchestrator creation follow a clear sequence with proper error handling and resource cleanup via defer.


77-94: LGTM!

Interactive confirmation flow is properly gated by flags, execution is timed, and errors are logged with appropriate exit codes.


97-114: LGTM!

The confirm function provides a clear warning banner and accepts common affirmative responses. Using fmt.Scanln is appropriate for this simple interactive prompt.

go/backup.go (4)

11-25: LGTM!

Clean struct definition and constructor with appropriate fields for Kopia backup management.


28-41: LGTM!

Dependency checking with exec.LookPath is the idiomatic Go approach for verifying CLI tool availability.


69-84: LGTM!

Good practice passing the password via environment variable rather than command-line argument (avoids exposure in process listings).


87-105: LGTM!

Proper dry-run handling and clear logging for snapshot creation.

go/gist.go (3)

16-35: LGTM!

Good initialization with a sensible HTTP client timeout of 30 seconds.


139-200: LGTM!

The cleanup logic correctly:

  1. Respects keepFirstFile option
  2. Filters by log prefix to avoid deleting unrelated files
  3. Sorts lexicographically (works correctly with the timestamp format)
  4. Deletes oldest files first

108-110: Verify token type before changing authentication header format.

GitHub's authentication header depends on the token type:

  • Fine-grained PATs (prefix github_pat_): Use Authorization: Bearer <token>
  • Classic PATs (prefix ghp_/gho_): Use Authorization: token <token>

Confirm which token type g.token is before refactoring, as using Bearer with a classic PAT will fail.

go/orchestrator.go (2)

31-41: LGTM!

Clean dependency injection pattern - all subsystems are initialized in the constructor.


163-181: LGTM!

Using os.Mkdir as an atomic lock is a good pattern for POSIX systems - the operation is atomic and will fail if the directory already exists.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@go/config.go`:
- Around line 43-59: LoadConfig currently ignores os.Stat errors and silently
proceeds when a caller passes an explicit configPath or when stat fails for
reasons other than "not exist" for the default path; update LoadConfig to
surface stat errors: call os.Stat(configPath) and if configPath != "" return the
stat error (wrap it) so missing/permission issues are reported, while if
configPath == "" treat only os.IsNotExist(err) as non-fatal and return other
errors (wrap and return) for the default path; only call
godotenv.Load(configPath) when stat succeeded. Reference LoadConfig and the
configPath handling logic.
♻️ Duplicate comments (1)
go/orchestrator.go (1)

144-151: Failed service list still uses error strings instead of names.
Line 150 appends err.Error(), so notifications show full error text rather than clean service names. Consider returning structured results from StartParallel (service name + error) and append the name only.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c376a78 and a83d73b.

📒 Files selected for processing (3)
  • go/config.go
  • go/logger.go
  • go/orchestrator.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • go/logger.go
🔇 Additional comments (6)
go/config.go (3)

13-40: Config struct is well‑scoped and readable.
Clear separation of required vs optional fields will make downstream usage straightforward.


106-137: Config.Print masks sensitive fields well.
Nice touch keeping tokens/passwords hidden while still showing key settings.


141-174: Env helper functions look solid.
Concise defaults and parsing keep config loading predictable.

go/orchestrator.go (3)

11-40: Orchestrator initialization is cohesive.
Dependency wiring is clear and centralized.


44-129: Run flow now cleans up locks and restores services on failure.
The defer at Line 61 plus restart on failure at Line 87 improves resilience.


159-198: Cleanup/lock handling is straightforward and idempotent.
releaseLock guards on lockAcquired and logs failures, keeping cleanup safe.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@go/config_test.go`:
- Around line 3-7: Tests currently pass a missing config path so LoadConfig
fails early with the "配置文件不存在或不可访问" error; modify the tests that call LoadConfig
in config_test.go (and the similar blocks around lines 75-123) to create an
empty temporary .env file (or call LoadConfig("") to use defaults) before
invoking LoadConfig, e.g. create a temp file via os.CreateTemp, write nothing,
close it, and pass its path into LoadConfig so the function proceeds to
environment validation; ensure the temp file is removed after the test to avoid
leaks.

In `@go/docker.go`:
- Around line 50-71: DiscoverServices currently ignores failures from IsRunning
and treats any error as "not running"; change IsRunning on DockerManager to
return (bool, error) and update DiscoverServices to call running, err :=
dm.IsRunning(svc) and if err != nil return nil, fmt.Errorf("检查服务运行状态失败 for %s:
%w", svc.Name, err) (or similar) instead of silently setting svc.Running=false;
set svc.Running = running only on success. Update all other IsRunning call sites
(e.g., Stop and any other usages) to handle the (bool, error) return and
propagate or handle errors accordingly so orchestration fails fast on docker
command failures.

In `@README.md`:
- Around line 207-208: Add explicit language identifiers to the fenced code
blocks that currently lack them: update the fences containing the literal lines
"YewResin/" and "go/" (and the similar fences at lines noted 244-245) to use
```text so markdownlint MD040 is satisfied; locate the triple-backtick fences
that immediately surround the strings "YewResin/" and "go/" in README.md and
change their opening fences to ```text.
♻️ Duplicate comments (1)
go/orchestrator.go (1)

145-152: Return service names instead of raw error strings in restore summary.

failedServices currently appends err.Error(), which produces noisy notifications. Prefer structured results from StartParallel so you can append the service name only.

♻️ Suggested shape (requires StartParallel to return service-aware results)
-if errs := o.docker.StartParallel(o.normalServices); len(errs) > 0 {
-	for _, err := range errs {
-		slog.Error("启动服务失败", "error", err)
-		// 从错误信息中提取服务名
-		failedServices = append(failedServices, err.Error())
-	}
-}
+if results := o.docker.StartParallel(o.normalServices); len(results) > 0 {
+	for _, r := range results {
+		slog.Error("启动服务失败", "service", r.Service.Name, "error", r.Err)
+		failedServices = append(failedServices, r.Service.Name)
+	}
+}
🧹 Nitpick comments (1)
go/backup.go (1)

58-63: Accept both quoted and unquoted remote paths from kopia repository status.

The current check only passes if the output includes quotes. Some Kopia versions may output the remote unquoted, which would incorrectly fail the repository check. Consider accepting both formats and verify against your target Kopia version.

♻️ Proposed robustness tweak
 outputStr := string(output)
 quotedExpected := `"` + k.expectedRemote + `"`
-if !strings.Contains(outputStr, quotedExpected) {
+if !strings.Contains(outputStr, quotedExpected) && !strings.Contains(outputStr, k.expectedRemote) {
     return fmt.Errorf("Kopia 仓库路径不匹配,期望: %s", k.expectedRemote)
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a83d73b and b3e3937.

📒 Files selected for processing (10)
  • .github/workflows/dev-release.yml
  • README.md
  • go/.gitignore
  • go/Makefile
  • go/backup.go
  • go/config.go
  • go/config_test.go
  • go/docker.go
  • go/notify.go
  • go/orchestrator.go
💤 Files with no reviewable changes (1)
  • .github/workflows/dev-release.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • go/notify.go
🧰 Additional context used
🧬 Code graph analysis (2)
go/config_test.go (1)
go/config.go (1)
  • LoadConfig (44-115)
go/orchestrator.go (6)
go/config.go (1)
  • Config (14-41)
go/docker.go (4)
  • DockerManager (32-36)
  • Service (25-29)
  • NewDockerManager (39-48)
  • ClassifyServices (219-233)
go/backup.go (2)
  • KopiaBackup (12-16)
  • NewKopiaBackup (19-25)
go/notify.go (2)
  • Notifier (14-19)
  • NewNotifier (22-28)
go/gist.go (2)
  • GistManager (16-23)
  • NewGistManager (26-35)
go/logger.go (1)
  • LogWriter (11-11)
🪛 checkmake (0.2.2)
go/Makefile

[warning] 26-26: Target body for "all" exceeds allowed length of 5 (8).

(maxbodylength)


[warning] 82-82: Target body for "help" exceeds allowed length of 5 (18).

(maxbodylength)

🪛 GitHub Actions: Go Build Artifact
go/config_test.go

[error] 83-83: TestLoadConfigSuccess failed: unexpected error: 配置文件不存在或不可访问: stat /tmp/TestLoadConfigSuccess3780344504/002/missing.env: no such file or directory. Command failed: go test -v ./... (exit code 1)


[error] 103-103: TestLoadConfigMissingBaseDir failed: expected BASE_DIR error, got 配置文件不存在或不可访问: stat /tmp/TestLoadConfigMissingBaseDir1994894384/001/missing.env: no such file or directory. Command failed: go test -v ./... (exit code 1)


[error] 114-114: TestLoadConfigMissingExpectedRemote failed: expected EXPECTED_REMOTE error, got 配置文件不存在或不可访问: stat /tmp/TestLoadConfigMissingExpectedRemote667949831/002/missing.env: no such file or directory. Command failed: go test -v ./... (exit code 1)


[error] 124-124: TestLoadConfigBaseDirNotExist failed: expected BASE_DIR not exist error, got 配置文件不存在或不可访问: stat /tmp/TestLoadConfigBaseDirNotExist4091979593/002/missing.env: no such file or directory. Command failed: go test -v ./... (exit code 1)

🪛 markdownlint-cli2 (0.18.1)
README.md

207-207: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


244-244: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (11)
go/.gitignore (1)

1-4: LGTM: build outputs are ignored correctly.

These patterns align with the binary and dist artifacts produced by the Makefile/CI.

go/Makefile (1)

7-12: No issues found. The main.version variable exists in go/main.go (line 14) as a mutable string variable, so the -X main.version=$(VERSION) ldflags injection will work correctly.

go/config.go (4)

13-41: Config schema is clear and well-grouped.


43-115: LoadConfig flow is robust for default vs. explicit paths.


117-149: Print output masks secrets while keeping useful context.

Also applies to: 177-186


153-175: Env helpers are straightforward and safe.

go/orchestrator.go (5)

31-40: Constructor wiring looks consistent.


44-129: Run flow is cohesive and ensures lock release.


160-166: Cleanup performs best‑effort recovery and releases the lock.


168-186: Directory lock acquisition logic looks solid.


188-200: Lock release resets state correctly.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@YewFence
Copy link
Copy Markdown
Owner Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 引入了 Go 语言对项目的重构,这是一个非常棒的改进,带来了跨平台和更优性能的潜力。整体代码结构清晰,模块划分合理,并且考虑了并发、超时和信号处理等,使程序更加健壮。
文档更新非常详尽,对新用户和开发者都很友好。单元测试的加入也为代码质量提供了保障。
我发现了一些可以改进的小地方,主要涉及配置一致性、代码简化和 Go 版本选择,具体请看我的评论。总体来说,这是一次高质量的重构!

for _, err := range errs {
slog.Error("启动服务失败", "error", err)
// 从错误信息中提取服务名
failedServices = append(failedServices, err.Error())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这里将完整的错误信息添加到了 failedServices 数组中,这可能导致最终的通知信息比较冗长(例如:[service-name: start failed: ...])。如注释所说,如果只想提取服务名,可以考虑从错误字符串中解析。一个更健壮的方式是使用 strings.SplitN(err.Error(), ":", 2)[0] 来获取服务名,这样可以避免当错误信息本身包含冒号时出现问题。

Suggested change
failedServices = append(failedServices, err.Error())
failedServices = append(failedServices, strings.SplitN(err.Error(), ":", 2)[0])

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@README.md`:
- Line 146: Update the environment variables table to include the same "Shell"
and "Go" support columns used by the command-line parameters table; locate the
env vars table and add two new columns labeled "Shell" and "Go", then update
each variable row (including DOCKER_COMMAND_TIMEOUT_SECONDS) to mark support —
for DOCKER_COMMAND_TIMEOUT_SECONDS set Shell to "-" and Go to "✓" — follow the
existing column alignment and formatting used by the command-line parameters
table to keep consistency.
♻️ Duplicate comments (3)
go/config.go (2)

160-167: Previous review concern has been addressed.

getEnvInt now correctly uses strconv.Atoi with proper error handling, returning defaultVal when parsing fails instead of silently returning 0.


169-175: Previous review concern has been addressed.

getEnvBool now correctly converts the value to lowercase before comparison, making it case-insensitive for values like "TRUE", "YES", "True", etc.

README.md (1)

207-208: Add language identifiers to fenced code blocks.

The code blocks at lines 207 and 244 are missing language identifiers, which causes markdownlint violations and inconsistent rendering. This issue was flagged in a previous review but only partially addressed.

♻️ Proposed fix
-```
+```text
 YewResin/
-```
+```text
 go/

Based on static analysis hints and past review comments.

Also applies to: 244-245

🧹 Nitpick comments (2)
README.md (2)

494-496: Remove excessive blank lines at end of file.

Standard practice is to have exactly one blank line at the end of a markdown file, not three.

♻️ Proposed fix
 MIT
-


+

5-9: Consider clarifying the relationship between "identical core functionality" and "better performance."

The introduction states both that the Go version offers "better performance and parallel processing" (line 7) and that "core functionality is completely identical" (line 9). While technically accurate (same outcomes, different implementation efficiency), this might be slightly confusing to readers.

Consider adding a brief clarification, such as:

两个版本的核心功能完全一致,依赖也相同,可根据使用场景自由选择。Go 版本在实现上更高效,但对用户而言功能表现一致。

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.github/workflows/build-artifact.yml:
- Around line 3-8: Update the pull_request workflow's paths filter so changes to
build-critical files trigger the job: modify the existing paths list under the
pull_request trigger (the paths: key currently containing 'src/**') to include
'Makefile' and '.env.example' as additional entries so updates to the Makefile
and the .env.example artifact will run the workflow.
♻️ Duplicate comments (1)
go/docker.go (1)

229-240: Windows .sh executability check will cause false positives.

On native Windows, ./compose-up.sh won’t run unless a shell is invoked explicitly, so marking .sh as executable can break stop/start even when a compose file exists. Consider returning false on Windows (or invoking bash explicitly / adding .cmd support) to avoid hard failures and allow the compose fallback.

💡 Suggested fix (prefer fallback to docker compose on Windows)
 func (dm *DockerManager) isExecutable(path string) bool {
 	info, err := os.Stat(path)
 	if err != nil {
 		return false
 	}
 	if runtime.GOOS == "windows" {
-		return !info.IsDir() && strings.EqualFold(filepath.Ext(path), ".sh")
+		// .sh isn't directly executable on native Windows; fall back to docker compose.
+		return false
 	}
 	// 在 Unix 系统上检查执行权限
 	return info.Mode()&0111 != 0
 }
🧹 Nitpick comments (1)
.github/workflows/go-build-artifact.yml (1)

37-41: Prefer go-version-file to keep the Go version in sync.

Hard-coding Go 1.23 can drift from the go/go.mod go directive over time. Consider using go-version-file so the workflow always matches the module version.

♻️ Proposed change
-        with:
-          go-version: '1.23'
+        with:
+          go-version-file: 'go/go.mod'

@YewFence
Copy link
Copy Markdown
Owner Author

/gemini review

@YewFence YewFence force-pushed the feature/refactor-go branch from ad2628e to 79f6c2f Compare January 18, 2026 11:00
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 Pull Request 主要是用 Go 语言重构了原有的 Shell 脚本项目,实现了跨平台二进制文件的构建。整体代码结构清晰,模块化做得很好,并且充分利用了 Go 的并发特性来提升服务启停的效率。

我的评审主要集中在以下几个方面:

  1. 健壮性: 建议在与外部工具(如 kopia)交互时使用 JSON 格式,以提高稳定性。
  2. 资源管理: 优化了网络请求中 http.Client 的使用方式,避免不必要的资源消耗。
  3. 一致性: 统一了代码中时间戳的处理方式,全部采用 UTC 时间,以避免时区问题。

此外,代码中对并发、错误处理和信号处理的实现都非常出色。文档更新也很详尽,清晰地说明了新旧两个版本的使用方式。总体来说,这是一次高质量的重构。

@YewFence
Copy link
Copy Markdown
Owner Author

@CodeRabbit summary

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 18, 2026

✅ Actions performed

Summary regeneration triggered.

@YewFence YewFence merged commit fe389ec into main Jan 18, 2026
3 checks passed
@YewFence YewFence deleted the feature/refactor-go branch March 7, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant