jyk/voice-imev0.4.0c83ebfc0e086ae0363756bacf7a3284b29037ae2 → 34cce9f1140e2bbd2ce0efe209383996f46fc2f0codescan.git_diff / codescan.issues_diff(修复 66 条、新增 99 条)+ 源码人工复核本区间是一次“识别链路 + 隐私落盘”的集中迭代,主题可归为 6 条线:
| # | 主题 | 主要落点 |
|---|---|---|
| 1 | 用户词典下线,与识别词表二合一 | 删除 cmd/voiceimed/lexicon.go、internal/ipc 的 lexicon.get/set、config.LexiconConfig;internal/lexicon 仅保留为词表更正的实现引擎;前端移除「词典」卡片 |
| 2 | 解码偏置开关 words.enable_bias(拍板 7A) | config.WordsConfig、ipc.WordsGetResult、SetWords(偏置关时词集变化不再要求重启)、main.go 派生热词按开关分支;前端新增复选框 |
| 3 | audio.max_session_sec 180 → 300 + 迁移 | config.Defaults/migrate(旧默认值 180 才升级)、audio.DefaultMaxSessionSec、看门狗切断时 HUD 写明上限 |
| 4 | 历史记录与失败语音(docs/27,新包 internal/history) | history.jsonl 落盘 + failed-audio/ 失败语音;设置界面「历史」二级视图;托盘「重试识别上一段」;purge.go 新增 history 清理类别;IPC |
日志/文档同步:AGENTS.md、README.md、docs/09,10,17,19,25,26 与新增 docs/27。
新增/重构的关键文件:
internal/history/history.go(+_test.go)、docs/27-历史记录与失败语音.md、internal/session/{history,asr_failure,hud_tick}_test.gocmd/voiceimed/lexicon.go(+_test.go)internal/session/session.go、cmd/voiceimed/{main,ipc,tray}.go、cmd/voiceime-app/{frontend/app.js,frontend/index.html,service.go,purge.go}⚠️ 重要甄别提醒:本区间删除了整包文件、且
session.go等大文件被大幅改动,导致行号漂移。以下若干条目与「新增的问题」是同一处代码因换行被同时计入两边(例:internal/session/session.go的「上帝类」在修复侧是:279、新增侧是:305;「函数过长」在两侧分别以不同行号出现)。统计的 66/99 不能直接当作“净修复/净新增”,需按代码位置人工去重。
按文件归类(描述取自工具原文):
cmd/voiceime-app/frontend/app.js(7) — 均为删除/瘦身后消失的复杂度问题
:1021、:1112、:1248:1248、:1429;函数过长 :1248:1429、:1934cmd/voiceime-app/purge.go(1)
:377cmd/voiceime-app/service.go(3)
:1290;集合未预分配 :1335;缺少默认分支 :1394cmd/voiceimed/ipc.go(5)
:1;圈复杂度过高 :160:675、:686、:748;重复代码块 :775cmd/voiceimed/lexicon_test.go(4) — 文件已随词典一起删除
:65、:127;圈复杂度过高 :189;行过长 :236cmd/voiceimed/main.go(2)、cmd/voiceimed/tray.go(7)、cmd/voiceimed/words_test.go(1)
main.go:过度日志 :623;缺少默认分支 :660tray.go:未使用参数 :335;过度日志 :335;行过长 :404;圈复杂度过高 :499;函数过长 :499;重复代码块 :811、:847words_test.go:行过长 :115internal/asr/sherpa.go(1):重复代码块 :414
internal/ipc/ipc_test.go(9):未使用参数 :216/:224/:819/:827;圈复杂度过高 :326/:483/:744;嵌套深度过高 :326
internal/session/session.go(20):上帝类、递归调用、圈复杂度、重复代码块、goroutine 泄漏风险、过度日志、缺少默认分支等(:279/:439/:495/:519/:577/:719/:1069/:1070/:1086/:1135/:1430/:1500/:1558/:1814/:1983/:2098/:2244/:2324)
结论:真正“被修掉”的语义问题极少,绝大多数是删除代码 / 拆分重构 / 行号漂移带来的指标下降。例如词典下线和 app.js 词典卡片删除,确实消掉了若干复杂度告警;而 session.go 一侧的“修复”多为假修复。
同样按文件归类。绝大多数是复杂度/耦合/重复代码类启发式告警,安全类为 0 条(关键正面信号,见第四节)。
前端 cmd/voiceime-app/frontend/app.js(12) — 历史视图新代码引入
loadWords(11)、saveHistoryPrivacy(13)、loadHistory(15)、formatHistoryTime(11)renderWords、renderWordCandidatesrenderWordCandidates、:1376、:2211historyRow → retryHistoryEntry → loadHistory → renderHistory → historyRowcmd/voiceime-app/service.go(4):uiLog 扇入 20;集合未预分配 :1400;圈复杂度过高 :1435;缺少默认分支 :1459
cmd/voiceime-app/purge.go(2):重复代码块 :170;缺少默认分支 :399
cmd/voiceimed/main.go(7):main 圈复杂度 22 / 169 行 / 6 处日志 / fan-out 94;:658 过度日志;:695 缺少默认分支
cmd/voiceimed/tray.go(11):retryLastFailed 高耦合 12;buildTrayItems 圈复杂度 12 / 84 行 / 高耦合 11;重复代码块 :237/:663/:903;goroutine 泄漏风险 :1017;过度日志 :394;未使用参数 :449;行过长 :463
cmd/voiceimed/ipc.go(3):未使用参数 :651/:772;重复代码块 :741;另 voiceprint.go:1 报 voiceprint ↔ ipc 循环导入
测试:tray_test.go:33/:563、words_test.go:182、ipc_test.go:444/:705/:754/:762/:771
internal/history/history.go(5,新包):重复代码块 :197/:238/:432;圈复杂度过高 :244/:428
internal/history/history_test.go(3)::17 圈复杂度 12;:222 圈复杂度 11;:232 循环内 IO
internal/session/session.go(25,本次改动最集中):
:305(Session 80 个方法):424/:642/:2303/:2564(皆为 go func(){...} 启发式命中)deliver 函数过长 111 行 / fan-out 29 / 高耦合 29inject 90 行 / fan-out 21;handleRetry 高耦合 16startListening 105 行、filterVoiceprint 122 行、finishAndInject 87 行switch 缺少默认分支
internal/session/*_test.go:history_test.go:59 圈复杂度 14 且高耦合 14、:125 高耦合 12;asr_failure_test.go:45 高耦合 12;hud_tick_test.go:50/:56 重复代码块;watchdog_test.go:42/:68hotkey.js、wizard.js),且在 base 即存在;本次新增的历史/词表前端代码刻意全程用 createTextNode/textContent(见 highlighted、historyRow),与仓库红线口径一致。internal/history 是安全敏感新包(路径穿越防护),工具未报安全项,且有 TestSafeAudioPath 覆盖非法路径。internal/session/session.go 的复杂度/上帝类继续膨胀:deliver(111 行, fan-out 29)、handleRetry(耦合 16) 都是本区间新引入的高耦合点。以下是逐行阅读新增/改动代码后确认的确定性发现,按重要性排序。
session.saveFailedAudio 调 audio.WriteWAV 落盘,而 WriteWAV 用 os.Create(path)(internal/audio/wav.go)→ 受 umask 影响,macOS 默认得到 0644。docs/27 §2.2 与 AGENTS.md 明确写“文件 0600、目录 0700”;index.html 的历史页也向用户承诺“文件 0600 / 目录 0700”。0700 实际上挡住了跨用户访问,所以影响被目录权限兜住;但“文件本身 0600”的承诺未兑现,属于红线文档与实现不一致。WriteWAV 显式 OpenFile(path, O_CREATE|O_WRONLY|O_TRUNC, 0o600),或落盘后对失败语音 Chmod(0o600);history.jsonl 侧已正确使用 fileMode=0o600。restartDaemonAndWait 轮询耗尽后仍报“已重启”(低-中,假成功)app.js):只在 call("DaemonAction","restart") 抛错时置 err;60 次(≈60s)轮询若始终 daemon_running=false,循环正常结束、err 仍为 null → 走成功分支 btn.hidden = true 并提示“已重启,…已生效”。err = new Error("守护进程未在 60 秒内就绪")。history.SafeAudioPath 的布尔优先级与注释承诺不一致(低,但有安全语义)if filepath.IsAbs(rel) || filepath.Base(rel) != rel && filepath.Dir(rel) != AudioDirName 实际等价于 IsAbs || (Base!=rel && Dir!=AudioDirName)。x.wav,Base==rel 使后半为 false)会被放行并解析为 failed-audio/x.wav。它仍落在音频目录内、不构成路径穿越(TestSafeAudioPath 的危险用例都被拒),但与“只接受 failed-audio/<id>.wav 这一种形状”的注释口径不符。filepath.Dir(rel) == AudioDirName(即拒绝裸文件名)。handleRetry 依次 Toggle → HotkeyUp → AudioReady;入口已判 fsm.State()==Idle,且只在 Run 协程推事件,因此正常不可达失败分支。Listening,而重试未走 startListening、未武装看门狗,会话将无法自愈。建议失败时补一次 fsm.Send(Reset)。history.Store.Append 在清理失败时返回 error,但记录其实已写入(低,语义误导)Append 成功写行后若 pruneLocked 失败,返回 fmt.Errorf("清理历史记录(本次会话已记录): %w");上层 recordHistory 会打 "写历史失败(本次会话结果不受影响)" 的 warn,与实际(已写入)相反,排障时容易误判。deliver 的失败分支会 saveFailedAudio 再写一份+wav 并追加一条新的 failed 记录。功能上可接受(新记录是新的“发生过一次失败”),但会更快消耗 KeepFailedAudio=20 的保留额度。建议在 docs/27 §4 注明或复用原音频。history.enabled 与 audio.save_failed_audio 默认改为 true;老配置里显式的 false 不做迁移(隐私开关不替用户打开),这个取舍是正确的,但 AGENTS.md 红线口径已随之从“不落盘”改为“日志不落盘、用户数据文件可落盘”,需要在评审中明确认知,避免后续把历史/失败语音误当日志清理。audio_saved、记录 id、removed 均为元信息);docs/27 §7 的 redaction 验收项目前仍是未勾选状态,建议补一条自动化断言。0600(对应 4.1),否则目录权限一旦放宽即暴露。go test -race ./internal/session/ ./internal/history/ 覆盖“失败→重试成功/重试再失败/会话忙时拒绝”三条路径(已有 history_test.go,建议再加“看门狗切断后重试”的交叉用例)。TestMigrate_LegacyMaxSessionSecUpgraded 已覆盖 180→300 且保留用户自定义值;建议补“显式 false 的历史开关不被迁移”的负向用例。restartDaemonAndWait 的超时失败用例(对应 4.2)。session.Session 上帝类(80 方法)与 deliver(111 行/fan-out 29) 建议作为下一轮拆分目标——本次改动已把“正常会话”与“失败重试”合流到同一实现(这是正确方向),但可继续把“上屏/历史落盘/阶段计时”抽为独立协作者。session.go/history.go/tray.go 的复杂度与重复代码新增项。history.retry| 5 | ASR 超时语义修正 | sherpa.Final 解码完成后 deadline 过期仍采用结果(Result.DeadlineMissed);session.transcribeBudget 改为「音频时长 + 30s」;失败路径 HUD 给出可见结论 |
| 6 | HUD 阶段等待计时 | session.startStageTick,「识别中/整理中 · 已等 Ns」每秒跳动 |