返回 DeepSeek-Reasonix
read_novelty_test.go
根目录 / internal / evidence / read_novelty_test.go
1 package evidence
2
3 import (
4 "encoding/json"
5 "fmt"
6 "testing"
7 )
8
9 func grepReceipt(pattern, path string) Receipt {
10 args := json.RawMessage(fmt.Sprintf(`{"pattern":%q,"path":%q}`, pattern, path))
11 r := ReceiptFromToolCall("grep", args, true, true)
12 r.OutputBytes = 64
13 return r
14 }
15
16 func windowReceipt(path string, offset int) Receipt {
17 args := json.RawMessage(fmt.Sprintf(`{"path":%q,"offset":%d,"limit":300}`, path, offset))
18 r := ReceiptFromToolCall("read_file", args, true, true)
19 r.OutputBytes = 64
20 return r
21 }
22
23 // Read novelty used to be keyed on the path alone, so the two most common
24 // investigation moves — grepping one package for a second symbol, paging
25 // through a long file — scored as repeats from the second round on, and a turn
26 // doing exactly that was told it had produced no new evidence.
27 func TestProgressTrackerScoresTheQuestionNotJustThePath(t *testing.T) {
28 tr := NewProgressTracker()
29
30 if got := tr.ScoreRound([]Receipt{grepReceipt("Compose", "internal/agent")}); got != gainNewRead {
31 t.Fatalf("first grep = %d, want %d", got, gainNewRead)
32 }
33 if got := tr.ScoreRound([]Receipt{grepReceipt("Receipt", "internal/agent")}); got != gainNewRead {
34 t.Fatalf("new pattern over a read path = %d, want %d", got, gainNewRead)
35 }
36 if got := tr.ScoreRound([]Receipt{grepReceipt("Receipt", "internal/agent")}); got != 0 {
37 t.Fatalf("identical grep = %d, want 0", got)
38 }
39
40 if got := tr.ScoreRound([]Receipt{windowReceipt("agent.go", 0)}); got != gainNewRead {
41 t.Fatalf("first window = %d, want %d", got, gainNewRead)
42 }
43 if got := tr.ScoreRound([]Receipt{windowReceipt("agent.go", 300)}); got != gainNewRead {
44 t.Fatalf("next window of the same file = %d, want %d", got, gainNewRead)
45 }
46 if got := tr.ScoreRound([]Receipt{windowReceipt("agent.go", 300)}); got != 0 {
47 t.Fatalf("identical window = %d, want 0", got)
48 }
49
50 // A first read of a new path still counts once, not twice. It needs a fresh
51 // tracker: past explorationRunLimit look-only rounds the run stops counting
52 // whatever it finds, which is the separate cliff this change does not touch.
53 fresh := NewProgressTracker()
54 first := readReceipt("new.go")
55 first.OutputBytes = 64
56 if got := fresh.ScoreRound([]Receipt{first}); got != gainNewRead {
57 t.Fatalf("new path = %d, want %d", got, gainNewRead)
58 }
59 }
60
61 // The shadow scorer keys novelty the same way, so the two never disagree about
62 // what counts as a repeat.
63 func TestOutcomeTrackerScoresTheQuestionNotJustThePath(t *testing.T) {
64 tr := NewOutcomeTracker()
65
66 if s := tr.ScoreRound([]Receipt{grepReceipt("Compose", "internal/agent")}); s.Exploration != 1 {
67 t.Fatalf("first grep = %+v, want exploration 1", s)
68 }
69 if s := tr.ScoreRound([]Receipt{grepReceipt("Receipt", "internal/agent")}); s.Exploration != 1 {
70 t.Fatalf("new pattern over a read path = %+v, want exploration 1", s)
71 }
72 if s := tr.ScoreRound([]Receipt{grepReceipt("Receipt", "internal/agent")}); s.Exploration != 0 {
73 t.Fatalf("identical grep = %+v, want exploration 0", s)
74 }
75 if s := tr.ScoreRound([]Receipt{windowReceipt("agent.go", 0)}); s.Exploration != 1 {
76 t.Fatalf("first window = %+v, want exploration 1", s)
77 }
78 if s := tr.ScoreRound([]Receipt{windowReceipt("agent.go", 300)}); s.Exploration != 1 {
79 t.Fatalf("next window of the same file = %+v, want exploration 1", s)
80 }
81 }
82
82 lines GO