mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 02:36:01 -10:00
Compaction moves the folded span into <session>.outputs/fold-<hash>.md and leaves a pointer the model reads back with read_file. The summarizer path, its retries, bounds and tests are removed. Answering an ask with no selection ends the turn normally instead of reporting a failure. Find bar, overview and context cards, compact dialog, image preview, root-missing banner, toasts, side panel and prompt refine are aligned to the Chat-Tools board; the agent preset picker follows the Agent-Picker board.
103 lines
4.2 KiB
Go
103 lines
4.2 KiB
Go
package agent
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"reasonix/internal/contract/provider"
|
|
"reasonix/internal/safety/evidence"
|
|
)
|
|
|
|
// coverageTools answers from the shipped registry rather than a copy of it, so
|
|
// this test reads the contracts the agent reads.
|
|
var coverageRegistry = sync.OnceValue(builtinToolRegistry)
|
|
|
|
func coverageTools(name string) evidence.ToolFacts {
|
|
t, ok := coverageRegistry().Get(name)
|
|
if !ok {
|
|
return readOnlyFacts
|
|
}
|
|
return toolFacts(t)
|
|
}
|
|
|
|
func coverageRegion() []provider.Message {
|
|
return []provider.Message{
|
|
{Role: provider.RoleAssistant, ToolCalls: []provider.ToolCall{
|
|
{ID: "w1", Name: "write_file", Arguments: `{"path":"internal/parser/lexer.go"}`},
|
|
{ID: "w2", Name: "edit_file", Arguments: `{"path":"internal/parser/reader.go","old_string":"a","new_string":"b"}`},
|
|
{ID: "r1", Name: "read_file", Arguments: `{"path":"internal/unrelated/notes.md"}`},
|
|
{ID: "b1", Name: "bash", Arguments: `{"command":"go test ./internal/parser/ -run TestLexer"}`},
|
|
}},
|
|
{Role: provider.RoleTool, ToolCallID: "w1", Name: "write_file", Content: "wrote"},
|
|
{Role: provider.RoleTool, ToolCallID: "w2", Name: "edit_file", Content: "applied"},
|
|
{Role: provider.RoleTool, ToolCallID: "r1", Name: "read_file", Content: "contents"},
|
|
{Role: provider.RoleTool, ToolCallID: "b1", Name: "bash", Content: "error: FAIL TestLexer"},
|
|
}
|
|
}
|
|
|
|
// The facts a fold must carry are derived from what it did, not from its text:
|
|
// changes made and commands that failed. Reads stay out — the workspace still
|
|
// holds what they found, and demanding them back would push the summarizer
|
|
// toward listing paths to pass a check.
|
|
func TestFoldFactsAreChangesAndFailures(t *testing.T) {
|
|
cov := foldFacts(coverageRegion(), coverageTools)
|
|
if len(cov.Mutations) != 2 {
|
|
t.Fatalf("mutations = %v, want the two written files", cov.Mutations)
|
|
}
|
|
for _, unwanted := range cov.Mutations {
|
|
if strings.Contains(unwanted, "notes.md") {
|
|
t.Errorf("a read was counted as a change: %v", cov.Mutations)
|
|
}
|
|
}
|
|
if len(cov.Failures) != 1 || cov.Failures[0] != "go test" {
|
|
t.Fatalf("failures = %v, want the failed command reduced to its signature", cov.Failures)
|
|
}
|
|
}
|
|
|
|
// A digest that names the file it changed has carried the change, whether it
|
|
// wrote the full path or the bare name.
|
|
func TestCoverageAcceptsFullPathOrBareName(t *testing.T) {
|
|
region := coverageRegion()
|
|
full := measureFoldCoverage(region, coverageTools, "changed internal/parser/lexer.go and internal/parser/reader.go; go test failed")
|
|
if full.Missing() != 0 {
|
|
t.Fatalf("full paths not credited: %+v", full)
|
|
}
|
|
bare := measureFoldCoverage(region, coverageTools, "split lexer.go, then reader.go picked it up; go test still red")
|
|
if bare.Missing() != 0 {
|
|
t.Fatalf("bare file names not credited: %+v", bare)
|
|
}
|
|
}
|
|
|
|
// The severity line is the mechanism's own: a forgotten change makes the agent
|
|
// wrong, a forgotten failure only makes it slow.
|
|
func TestCoverageSeparatesChangesFromFailures(t *testing.T) {
|
|
region := coverageRegion()
|
|
lostFailure := measureFoldCoverage(region, coverageTools, "changed lexer.go and reader.go")
|
|
if lostFailure.LostAChange() {
|
|
t.Errorf("a digest carrying every change must not read as having lost one: %+v", lostFailure)
|
|
}
|
|
if lostFailure.Missing() != 1 {
|
|
t.Errorf("the dropped failure should still be counted: %+v", lostFailure)
|
|
}
|
|
lostChange := measureFoldCoverage(region, coverageTools, "reader.go was touched; go test failed")
|
|
if !lostChange.LostAChange() || lostChange.LostEveryChange() {
|
|
t.Errorf("one missing change should be partial, not total: %+v", lostChange)
|
|
}
|
|
empty := measureFoldCoverage(region, coverageTools, "The user asked for a refactor. Work proceeded.")
|
|
if !empty.LostEveryChange() {
|
|
t.Errorf("a digest naming no change at all must read as broken: %+v", empty)
|
|
}
|
|
}
|
|
|
|
// A fold with nothing to carry cannot fail the check.
|
|
func TestCoverageIsSatisfiedByAnEmptyFold(t *testing.T) {
|
|
cov := measureFoldCoverage([]provider.Message{
|
|
{Role: provider.RoleUser, Content: "what does this project do?"},
|
|
{Role: provider.RoleAssistant, Content: "It is a coding agent."},
|
|
}, coverageTools, "the user asked what the project does")
|
|
if cov.Required() != 0 || cov.LostAChange() || cov.LostEveryChange() {
|
|
t.Fatalf("a fold that changed nothing owes nothing: %+v", cov)
|
|
}
|
|
}
|