Background
internal/cli/ 模块包含 44 个 Go 文件(~6,748 行),横跨 checkers/(诊断检查注册表)、onboard/(安装向导)、slack/(Slack 客户端辅助)和 output/(终端格式化)。模块已在 cycle 37-42 完成 12 方面分析(issues #213, #230, #248)。本次为 重新分析(增量深度),聚焦 Phase 1 SOLID + DRY,重点发现 跨包重复代码——手写 YAML 解析和 .env 文件操作分散在 3 个子包中。
Scope: solid, dry — cycle 104 (重新分析, analysis_count=7)
Key files: onboard/wizard.go, checkers/config.go, checkers/runtime.go, checkers/dependencies.go, onboard/templates.go
Finding Summary
| Category |
Critical |
High |
Medium |
Low |
| solid |
0 |
1 |
1 |
0 |
| dry |
0 |
0 |
4 |
0 |
| Total |
0 |
1 |
5 |
0 |
Findings
SOLID
wizard-god-file
Severity: High | Confidence: High | ROI: High
Location: onboard/wizard.go (1378 行, 15+ 职责)
Problem: wizard.go 是一个 1378 行的 god-file,混合了 15+ 个不同职责:环境预检、运行模式检测、向导步骤编排、显示/banner/prompt 辅助函数、YAML 配置生成、.env 文件写入、STT 依赖管理、二进制安装、服务安装、agent 配置部署、文件 I/O 工具(sameContent、fileSHA256、copyBinary)、PATH 操作和配置验证。低级文件工具(SHA256、二进制拷贝、PATH 操作)与高级向导编排混在同一文件中。
Current Pattern:
// wizard.go:1270 — cryptographic file comparison
func sameContent(a, b string) bool { ... }
// wizard.go:1293 — atomic binary copy with temp file
func copyBinary(src, dst string) error { ... }
// wizard.go:1334 — shell PATH manipulation
func addToUserPath(dir string) error { ... }
// wizard.go:170-287 — Run() mixes orchestration + step definition + display
func (w *Wizard) Run() error { ... }
Proposed Fix: 拆分为 4-5 个文件:
wizard.go — Run(), wizardContext, buildSteps() 编排(~300 行)
steps.go — 所有 stepXxx() 函数(~300 行)
prompts.go — 所有 prompt*() 和 display*() 辅助(~200 行)
binary.go — copyBinary, sameContent, fileSHA256, PATH 操作(~150 行)
envfile.go — loadEnvFile, readExistingEnv*, buildEnvContent(~150 行)
Acceptance Criteria:
global-configpath-dip-violation
Severity: Medium | Confidence: High | ROI: Medium
Location: checkers/config.go:18-23, 引用点在 config.go:32,109,148,215、security.go:87,169,217,227,327、stt.go:90
Problem: checkers 包使用包级全局 var configPath string + SetConfigPath() 模式。所有配置依赖的 checker 通过隐式全局状态获取配置路径,而非通过接口注入。这导致 configPath == "" 守卫重复 8+ 处,测试需要手动调用 SetConfigPath() 且有污染风险,无法并发运行不同配置路径的检查集。
Current Pattern:
// checkers/config.go:18-23
var configPath string
func SetConfigPath(path string) { configPath = path }
// 每个 config 依赖的 checker 中:
func (c configExistsChecker) Check(ctx context.Context) cli.Diagnostic {
if configPath == "" {
return cli.Diagnostic{ /* ... */ }
}
// ...
}
Proposed Fix: 通过 ConfigProvider 接口注入:
type ConfigProvider interface {
ConfigPath() string
EnvPath() string
}
type CheckerRegistry struct {
checkers []Checker
cfg ConfigProvider
}
Acceptance Criteria:
DRY
handrolled-yaml-parsing-3x
Severity: Medium | Confidence: High | ROI: High
Location: checkers/config.go:332-358, onboard/templates.go:227-269, onboard/wizard.go:559-590
Problem: 3 个独立函数手工逐行解析 YAML 文件,各用不同的字符串拆分逻辑。这种实现脆弱(注释、多行值、不同缩进会破坏),且修改 YAML 解析策略需同步 3 处。
Current Pattern:
// config.go:332-344 — extractPortAddr
lines := strings.Split(yaml, "\n")
prefix := key + ":"
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, prefix) { ... }
}
// templates.go:227-269 — extractBlock
// 手工缩进计数查找块边界
// wizard.go:559-590 — readExistingConfigValue
// 手工两级 YAML 嵌套状态机解析
Proposed Fix: 创建 yamlutil 共享 helper:
// yamlutil/get.go — 使用已有的 gopkg.in/yaml.v3
func GetValue(configPath, dottedKey string) (string, error)
func GetBlock(configPath, parentKey string) (string, error)
Acceptance Criteria:
envfile-parsing-7x
Severity: Medium | Confidence: High | ROI: Medium
Location: onboard/wizard.go:111,544,817,945、checkers/config.go:409-422、checkers/security.go:333-377、slack/client.go:65-92
Problem: 7 个 .env 文件读写实现在 3 个子包中分散。每个重新实现 KEY=VALUE 解析,引号/空白/注释处理各不相同。loadEnvFile 在 wizard.go 和 slack/client.go 间完全重复(已知 issue 212)。其余变体(readExistingEnvValue、readExistingEnvCredentials、hasEnvValue、fixEnvVars 内联解析)也是相同模式的不同封装。
Current Pattern:
// 7 处共享的模式:
data, _ := os.ReadFile(envPath)
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") { continue }
idx := strings.Index(line, "=")
// ... 提取 key/value ...
}
Proposed Fix: 创建共享 envfile 包:
// envfile/envfile.go
func Load(path string) map[string]string
func GetValue(path, key string) (string, bool)
func HasValue(path, key string) bool
func SetValues(path string, kv map[string]string) error
func UnsetKeys(path string, keys ...string) error
func LoadIntoEnv(path string) int
Acceptance Criteria:
writability-check-boilerplate
Severity: Medium | Confidence: High | ROI: Medium
Location: checkers/runtime.go:169-221, checkers/dependencies.go:52-101
Problem: dataDirWritableChecker 和 sqlitePathChecker 实现完全相同的可写性检测逻辑:stat 目录 → 检查是否目录 → 创建临时文件 → 关闭删除 → 报告结果。仅目录路径和诊断元数据不同。
Current Pattern:
// runtime.go:169-221 和 dependencies.go:52-101 — 相同的模式
info, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return cli.Diagnostic{Status: cli.StatusWarn, FixFunc: func() error { return os.MkdirAll(dir, 0o755) }}
}
// ...
}
testPath := filepath.Join(dir, ".hotplex_write_test")
f, err := os.Create(testPath)
// ...
Proposed Fix: 提取共享 checkDirWritable helper:
func checkDirWritable(dir, name, category string) cli.Diagnostic {
// stat → isDir → tempFile → cleanup → Diagnostic
}
Acceptance Criteria:
Implementation Priority
| Finding |
Priority |
Effort |
Risk |
Impact |
| handrolled-yaml-parsing-3x |
P0 |
Small |
Low |
消除 3 处脆弱解析,统一为 yaml.v3 |
| envfile-parsing-7x |
P0 |
Medium |
Low |
消除 7 处跨包重复 |
| wizard-god-file |
P1 |
Large |
Low |
1378 行拆分为 5 个文件 |
| global-configpath-dip-violation |
P1 |
Medium |
Medium |
消除全局可变状态 |
| writability-check-boilerplate |
P2 |
Small |
Low |
消除 2 处样板代码 |
推荐起始点: P0 两个发现(yaml 统一 + envfile 统一)可在一个 PR 中完成,彻底消除跨包解析重复。
Out of Scope
checkPythonPackages / checkOnnxPackage 重复 — Low 严重性,仅 2 实例
displayExistingConfig Slack/Feishu 状态渲染重复 — Low 严重性,仅 2 平台
loadEnvFile wizard.go vs slack/client.go 完全重复 — 已在 issue 212 中追踪
configPath == "" 守卫 8+ 处 — global-configpath-dip-violation 的症状,根因修复后消除
scanDir() agentconfig.go 样板 — 3 个一行方法,ROI 低
Verification
Background
internal/cli/模块包含 44 个 Go 文件(~6,748 行),横跨checkers/(诊断检查注册表)、onboard/(安装向导)、slack/(Slack 客户端辅助)和output/(终端格式化)。模块已在 cycle 37-42 完成 12 方面分析(issues #213, #230, #248)。本次为 重新分析(增量深度),聚焦 Phase 1 SOLID + DRY,重点发现 跨包重复代码——手写 YAML 解析和 .env 文件操作分散在 3 个子包中。Scope: solid, dry — cycle 104 (重新分析, analysis_count=7)
Key files:
onboard/wizard.go,checkers/config.go,checkers/runtime.go,checkers/dependencies.go,onboard/templates.goFinding Summary
Findings
SOLID
wizard-god-file
Severity: High | Confidence: High | ROI: High
Location:
onboard/wizard.go(1378 行, 15+ 职责)Problem:
wizard.go是一个 1378 行的 god-file,混合了 15+ 个不同职责:环境预检、运行模式检测、向导步骤编排、显示/banner/prompt 辅助函数、YAML 配置生成、.env 文件写入、STT 依赖管理、二进制安装、服务安装、agent 配置部署、文件 I/O 工具(sameContent、fileSHA256、copyBinary)、PATH 操作和配置验证。低级文件工具(SHA256、二进制拷贝、PATH 操作)与高级向导编排混在同一文件中。Current Pattern:
Proposed Fix: 拆分为 4-5 个文件:
Acceptance Criteria:
wizard.go缩减至 ~300 行(编排 + 上下文定义)binary.go或envfile.gomake test通过global-configpath-dip-violation
Severity: Medium | Confidence: High | ROI: Medium
Location:
checkers/config.go:18-23, 引用点在config.go:32,109,148,215、security.go:87,169,217,227,327、stt.go:90Problem:
checkers包使用包级全局var configPath string+SetConfigPath()模式。所有配置依赖的 checker 通过隐式全局状态获取配置路径,而非通过接口注入。这导致configPath == ""守卫重复 8+ 处,测试需要手动调用SetConfigPath()且有污染风险,无法并发运行不同配置路径的检查集。Current Pattern:
Proposed Fix: 通过
ConfigProvider接口注入:Acceptance Criteria:
ConfigProvider接口make test通过DRY
handrolled-yaml-parsing-3x
Severity: Medium | Confidence: High | ROI: High
Location:
checkers/config.go:332-358,onboard/templates.go:227-269,onboard/wizard.go:559-590Problem: 3 个独立函数手工逐行解析 YAML 文件,各用不同的字符串拆分逻辑。这种实现脆弱(注释、多行值、不同缩进会破坏),且修改 YAML 解析策略需同步 3 处。
Current Pattern:
Proposed Fix: 创建
yamlutil共享 helper:Acceptance Criteria:
yamlutil共享 helper(基于 yaml.v3)make test通过envfile-parsing-7x
Severity: Medium | Confidence: High | ROI: Medium
Location:
onboard/wizard.go:111,544,817,945、checkers/config.go:409-422、checkers/security.go:333-377、slack/client.go:65-92Problem: 7 个 .env 文件读写实现在 3 个子包中分散。每个重新实现 KEY=VALUE 解析,引号/空白/注释处理各不相同。
loadEnvFile在wizard.go和slack/client.go间完全重复(已知 issue 212)。其余变体(readExistingEnvValue、readExistingEnvCredentials、hasEnvValue、fixEnvVars内联解析)也是相同模式的不同封装。Current Pattern:
Proposed Fix: 创建共享
envfile包:Acceptance Criteria:
envfile共享包make test通过writability-check-boilerplate
Severity: Medium | Confidence: High | ROI: Medium
Location:
checkers/runtime.go:169-221,checkers/dependencies.go:52-101Problem:
dataDirWritableChecker和sqlitePathChecker实现完全相同的可写性检测逻辑:stat 目录 → 检查是否目录 → 创建临时文件 → 关闭删除 → 报告结果。仅目录路径和诊断元数据不同。Current Pattern:
Proposed Fix: 提取共享
checkDirWritablehelper:Acceptance Criteria:
checkDirWritable到checkers/helpers.gomake test通过Implementation Priority
推荐起始点: P0 两个发现(yaml 统一 + envfile 统一)可在一个 PR 中完成,彻底消除跨包解析重复。
Out of Scope
checkPythonPackages/checkOnnxPackage重复 — Low 严重性,仅 2 实例displayExistingConfigSlack/Feishu 状态渲染重复 — Low 严重性,仅 2 平台loadEnvFilewizard.go vs slack/client.go 完全重复 — 已在 issue 212 中追踪configPath == ""守卫 8+ 处 — global-configpath-dip-violation 的症状,根因修复后消除scanDir()agentconfig.go 样板 — 3 个一行方法,ROI 低Verification
make test通过,无回归make lint不产生新警告