mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-06 01:07:35 +00:00
fix: staticcheck 24 条存量全部清零(含 4 个真实缺陷)
staticcheck 门接入时把存量设为基线 24 条,本次全部清掉,基线改为 0。
## 生产代码里的真实缺陷(4 个)
### ① memory/fusion.go —— BFS 的 depth 参数形同虚设
for d := 1; d <= depth; d++ {
neighbors, _ := g.NeighbourBlocks(seed.BlockID) // ← 永远是 seed
...
break // ← 只跑一轮
}
三处叠加:循环体末尾无条件 `break`;每层传的还是 `seed.BlockID`;
于是 depth=2 与 depth=3 召回结果**完全一样**。
staticcheck SA4008("变量在循环条件里从不改变")正是从这里报出来的 ——
它看出来 `d` 除了打分没有参与寻址。
改成真正的逐层 BFS(frontier 推进到上一层新发现的节点)。实测:
depth=1 → 2 个 [节点0 节点1]
depth=2 → 3 个 [节点0 节点1 节点2] ← 修复前停在这里
depth=3 → 4 个 [节点0 节点1 节点2 节点3]
补判据 `TestBFS召回_层深真实生效`:必须用**链**(n0-n1-n2-n3),
星型在 depth=1 就能召回全部叶子、断不出有没有走深。
变异(改回单层)⇒ 实测红:`★ depth=2 召回 2 个,期望 3`。
### ② nlp/extractor.go —— subjRels/objRels 只写不读
两个切片被 append 了 4 处,但**从未被读取**。三元组的关系类型来自
`relLabel := result.Tokens[vi]`,置信度来自 `result.DepRels[si]`,
与这两个切片无关。删除。
### ③ plugins/cmd/plugin.go —— 配置解析失败会把上限写成 0
if n, err := fmt.Sscanf(s, "%d", &p.maxOutput); err == nil && n > 0 {
} // ← 空分支
直接 Sscanf 到 &p.maxOutput:解析失败时它可能已被写成 0 或部分值,
而空分支无法回滚 ⇒ 配置写错一个字符就把输出上限变成 0
(下游 `maxLen := p.maxOutput` 直接拿它算截断长度)。
改为解析到临时变量、失败时保持默认并记日志。
### ④ cmd/homed/bootstrap.go —— 空的 else 分支
`if err != nil { log } else { }` —— 删掉 else。
## 测试判据里的真实缺陷(3 个)
### ⑤ stop_semantics_test —— 短路求值让第二次调用从不执行
if !a.sched.consumeCancel() || !a.sched.consumeCancel()
第一次返回 true 时第二次**根本不会执行**,于是"两条配额都可消费"
这条断言实际只测了一次。拆成两次独立调用(两处同型)。
### ⑥ seq/store_test —— 永假断言
if maxCallDepth != maxCallDepth { t.Fatal("maxCallDepth 不稳定") }
自己跟自己比,从诞生起就不可能失败。改成显式读两次比较。
### ⑦ pluginhost/catalog_test —— typed-nil 守卫永假
var nb *kstate.SyncBus
var iface kstate.SyncState = nb
if iface == nil { t.Skip(...) } // ← 恒 false
把 typed-nil 装进接口后,接口的 (type, value) 二元组都不为 nil。
这道前置守卫**从未生效** —— 而这正是该判据要演示的陷阱本身。
改用 `reflect.ValueOf(iface).IsNil()`。
## 其余(写法/冗余,无行为变化)
agent.go / context.go / toolresult_store_test / apply_regression_test
空 if 块(注释移到真实分支上方)
codec_chunkfast_golden_test 未使用的三个返回值 + 空 if
scheduler_critical/stack_test `if _, _ = f(); true { }` → 直接调用
overflow_test / upgrade_test 变量被整体覆盖,首次赋值未使用
dense_test m[string(x)] 替代临时变量
lex_rules_test range over string 而非 []rune(string)
## 验证
staticcheck -checks 'SA4*,SA5*,SA6*,SA9*' 0 条(基线 24 → 0)
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
go test ./... -count=1 65 包全过,0 FAIL
干净检出(worktree) build + memory/nlp/core 全绿
This commit is contained in:
@ -473,9 +473,7 @@ func New(cfg AgentConfig) *Agent {
|
||||
if embedder == nil {
|
||||
embedder = memory.NewStaticEmbedder(strings.Split(cfg.EmbeddingModelPath, ",")...)
|
||||
}
|
||||
if cfg.DocStore != nil {
|
||||
// TF-IDF 内置为 fallback,无需外部注入
|
||||
}
|
||||
// DocStore 无需处理:TF-IDF 内置为 fallback,不需要外部注入向量器。
|
||||
if cfg.Knowledge != nil {
|
||||
cfg.Knowledge.SetVectorizer(embedder)
|
||||
cfg.Knowledge.ReindexWithVectorizer(embedder)
|
||||
|
||||
@ -1177,16 +1177,17 @@ func (c *RelevanceContext) PruneWithProtected(currentInput string, topK int, col
|
||||
})
|
||||
|
||||
archived := 0
|
||||
if coldcontextStore != nil && len(archive) > 0 {
|
||||
// ★ 回填事件(NoMemory)**照常归档** —— 这是数据不丢的关键。
|
||||
//
|
||||
// 曾经在这里跳过它们(理由是"防自激"),那是错的:
|
||||
// coldcontext_query 的 Consume 已经把 L2 副本删掉了,L1 是唯一副本。
|
||||
// 跳过归档 = 裁掉即丢失(L1、L2、L3 三层都不在)。
|
||||
//
|
||||
// 自激的入口不在"归档"这一侧,而在"重新计算向量"那一侧 ——
|
||||
// 那已经由 textForVector 里 `if evt.NoMemory { return "" }` 挡住。
|
||||
}
|
||||
// ★ 回填事件(NoMemory)**照常归档** —— 这是数据不丢的关键。
|
||||
//
|
||||
// 曾经在这里跳过它们(理由是"防自激"),那是错的:
|
||||
// coldcontext_query 的 Consume 已经把 L2 副本删掉了,L1 是唯一副本。
|
||||
// 跳过归档 = 裁掉即丢失(L1、L2、L3 三层都不在)。
|
||||
//
|
||||
// 自激的入口不在"归档"这一侧,而在"重新计算向量"那一侧 ——
|
||||
// 那已经由 textForVector 里 `if evt.NoMemory { return "" }` 挡住。
|
||||
//
|
||||
// (2026-11-04:这里原先是一个**空 if 块**,条件与下面的 if 完全相同。
|
||||
// 空块只有注释、没有语句 ⇒ staticcheck SA9003。注释移到真实分支上方。)
|
||||
if coldcontextStore != nil && len(archive) > 0 {
|
||||
entries := make([]coldcontext.ContextEntry, len(archive))
|
||||
for i, s := range archive {
|
||||
|
||||
@ -707,8 +707,10 @@ func TestOverflow_恢复裁剪保护本帧现场(t *testing.T) {
|
||||
//
|
||||
// 变异:恢复 handleUpstreamContextFull(直调 handleContextOverflow)⇒ 变红。
|
||||
func TestOverflow_上游与本地共用同一个处理函数(t *testing.T) {
|
||||
a := newOverflowAgent(2000, 2, 20, 400) // 总量超页,但下面要看 force 的效果
|
||||
|
||||
// ★ 2026-11-04:原先这里先 `a := newOverflowAgent(...)` 再在下方
|
||||
// `a = empty` 整体覆盖 —— 第一次构造从未被使用(staticcheck SA4006)。
|
||||
// 删掉它;本判据需要的是"水位很低但上游报满"那个空上下文 agent。
|
||||
//
|
||||
// force=true 时**必须**进入处理,即使水位判定不成立。
|
||||
// 这正是"上游说满了"的语义:不信本地估算,直接裁。
|
||||
//
|
||||
@ -720,7 +722,7 @@ func TestOverflow_上游与本地共用同一个处理函数(t *testing.T) {
|
||||
context: NewRelevanceContext("", nil),
|
||||
}
|
||||
empty.context.Append(ContextEvent{Input: "短", Response: "短"})
|
||||
a = empty
|
||||
a := empty
|
||||
if a.overflowRatioNow() >= overflowRatio {
|
||||
t.Fatal("前置:水位应低于阈值")
|
||||
}
|
||||
|
||||
@ -42,8 +42,7 @@ func TestPreempt_DeferredDuringToolExec(t *testing.T) {
|
||||
StageHost: sh,
|
||||
})
|
||||
|
||||
if _, _ = enqueueQueued(t, a, "qq", "低优先级任务"); true {
|
||||
}
|
||||
enqueueQueued(t, a, "qq", "低优先级任务")
|
||||
lt, _, _ := a.sched.nextRef()
|
||||
done := make(chan struct{})
|
||||
go func() { a.executeNewTask(lt); close(done) }()
|
||||
@ -149,8 +148,7 @@ func TestBatch_NotAbandonedWithoutPreemption(t *testing.T) {
|
||||
StageHost: sh,
|
||||
})
|
||||
|
||||
if _, _ = enqueueQueued(t, a, "cli", "跑两个工具"); true {
|
||||
}
|
||||
enqueueQueued(t, a, "cli", "跑两个工具")
|
||||
tt, _, _ := a.sched.nextRef()
|
||||
a.executeNewTask(tt)
|
||||
|
||||
|
||||
@ -74,8 +74,7 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
|
||||
a := newPreemptAgent(t, sp)
|
||||
|
||||
// A(L1)开始运行
|
||||
if _, _ = enqueueQueued(t, a, "qq", "任务A"); true {
|
||||
}
|
||||
enqueueQueued(t, a, "qq", "任务A")
|
||||
at, _, _ := a.sched.nextRef()
|
||||
doneA := make(chan struct{})
|
||||
go func() { a.executeNewTask(at); close(doneA) }()
|
||||
|
||||
@ -176,8 +176,14 @@ func TestStop_ArmTwiceTakesMaxNotSum(t *testing.T) {
|
||||
if n := a.sched.armStop(0); n != 2 {
|
||||
t.Fatalf("重复 armStop=%d,期望仍为 2(取 max 不累加)", n)
|
||||
}
|
||||
if !a.sched.consumeCancel() || !a.sched.consumeCancel() {
|
||||
t.Fatal("两次配额都应可消费")
|
||||
// ★ 拆成两次独立调用(2026-11-04 修)。
|
||||
//
|
||||
// 原写法 `if !x() || !x()` 依赖短路:第一次返回 true 时第二次**根本不执行**,
|
||||
// 于是"两次都能消费"这条断言实际只测了一次 —— 第二次是否被正确扣减从未验证。
|
||||
// staticcheck SA4000 把它当"左右相同的表达式"报出来,正是这个形态。
|
||||
first, second := a.sched.consumeCancel(), a.sched.consumeCancel()
|
||||
if !first || !second {
|
||||
t.Fatalf("两次配额都应可消费,实际 first=%v second=%v", first, second)
|
||||
}
|
||||
if a.sched.consumeCancel() {
|
||||
t.Fatal("第三次不应再有配额")
|
||||
@ -212,8 +218,10 @@ func TestStop_ArmCountsPendingChannelInputs(t *testing.T) {
|
||||
if n != 2 {
|
||||
t.Fatalf("armStop(2)=%d,期望 2(配额须包含 channel 中的待处理)", n)
|
||||
}
|
||||
if !a.sched.consumeCancel() || !a.sched.consumeCancel() {
|
||||
t.Fatal("两条配额都应可消费")
|
||||
// 同样拆开:短路求值会让第二次从不执行(见上方同型说明)。
|
||||
c1, c2 := a.sched.consumeCancel(), a.sched.consumeCancel()
|
||||
if !c1 || !c2 {
|
||||
t.Fatalf("两条配额都应可消费,实际 c1=%v c2=%v", c1, c2)
|
||||
}
|
||||
if a.sched.consumeCancel() {
|
||||
t.Fatal("不应有多余配额")
|
||||
|
||||
@ -58,10 +58,10 @@ func TestOffloadReplacesFullTextWithPreview(t *testing.T) {
|
||||
}
|
||||
|
||||
// ③ 记录挂在事件上(供 findToolResultStore 找到)
|
||||
if len(a.context.events) == 0 {
|
||||
// offloadToolResult 只是落盘并返回预览,不负责 append 事件;
|
||||
// 事件由调用方(task.go)创建。这里直接验证帧上的 EventID 已分配。
|
||||
}
|
||||
//
|
||||
// offloadToolResult 只是落盘并返回预览,**不负责 append 事件**
|
||||
// —— 事件由调用方(task.go)创建。所以这里不能断言 len(events)>0,
|
||||
// 直接验证帧上的 EventID 已分配。
|
||||
if f.EventID == "" {
|
||||
t.Fatal("落盘后帧上必须有 EventID,否则没法回读")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user