Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions pkg/agentdrain/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,6 @@ func (m *Miner) Train(line string) (*MatchResult, error) {
}, nil
}

// Match performs inference only: it finds the best matching cluster but does
// not mutate any state. Returns (result, true) when a match is found.
// It is safe to call from multiple goroutines.
func (m *Miner) Match(line string) (*MatchResult, bool, error) {
masked := m.masker.Mask(line)
tokens := Tokenize(masked)
if len(tokens) == 0 {
return nil, false, errors.New("agentdrain: Match: empty line after masking")
}

m.mu.RLock()
defer m.mu.RUnlock()

result, _ := m.match(tokens)
if result == nil {
return nil, false, nil
}
return result, true, nil
}

// match is the internal (non-locking) lookup. Must be called with mu held.
func (m *Miner) match(tokens []string) (*MatchResult, bool) {
candidates := m.tree.search(tokens, m.cfg.Depth, m.cfg.ParamToken)
Expand Down
54 changes: 0 additions & 54 deletions pkg/agentdrain/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,28 +67,6 @@ func TestTrain_ClusterMerge(t *testing.T) {
}
}

func TestMatch_InferenceOnly(t *testing.T) {
m, err := NewMiner(DefaultConfig())
if err != nil {
t.Fatalf("NewMiner: %v", err)
}
// Train once.
_, err = m.Train("stage=plan action=start")
if err != nil {
t.Fatalf("Train: %v", err)
}
before := m.ClusterCount()

// Match should not create new clusters.
_, _, err = m.Match("stage=plan action=unknown_value")
if err != nil {
t.Fatalf("Match: unexpected error: %v", err)
}
if m.ClusterCount() != before {
t.Errorf("Match: cluster count changed from %d to %d", before, m.ClusterCount())
}
}

func TestMasking(t *testing.T) {
masker, err := NewMasker(DefaultConfig().MaskRules)
if err != nil {
Expand Down Expand Up @@ -154,38 +132,6 @@ func TestFlattenEvent(t *testing.T) {
}
}

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing TestSaveLoadJSON also removes the only round-trip coverage for Miner.SaveJSON/Miner.LoadJSON, which are still used by Coordinator.SaveSnapshots/LoadSnapshots. Consider keeping a persistence round-trip test by instantiating m2 := NewMiner(cfg) and calling m2.LoadJSON(data) (instead of relying on the deleted LoadMinerJSON helper).

Suggested change
func TestSaveLoadJSON(t *testing.T) {
cfg := DefaultConfig()
m, err := NewMiner(cfg)
if err != nil {
t.Fatalf("NewMiner: %v", err)
}
lines := []string{
"stage=tool_call tool=search query=foo",
"stage=tool_call tool=search query=bar",
"stage=tool_call tool=fetch query=foo",
}
for _, line := range lines {
if _, err := m.Train(line); err != nil {
t.Fatalf("Train(%q): %v", line, err)
}
}
data, err := m.SaveJSON()
if err != nil {
t.Fatalf("SaveJSON: %v", err)
}
m2, err := NewMiner(cfg)
if err != nil {
t.Fatalf("NewMiner (load target): %v", err)
}
if err := m2.LoadJSON(data); err != nil {
t.Fatalf("LoadJSON: %v", err)
}
if m.ClusterCount() == 0 {
t.Fatal("expected source miner to have clusters after training")
}
if got, want := m2.ClusterCount(), m.ClusterCount(); got != want {
t.Fatalf("ClusterCount after load: got %d, want %d", got, want)
}
}

Copilot uses AI. Check for mistakes.
func TestSaveLoadJSON(t *testing.T) {
cfg := DefaultConfig()
m, err := NewMiner(cfg)
if err != nil {
t.Fatalf("NewMiner: %v", err)
}
lines := []string{
"stage=plan action=start",
"stage=plan action=start",
"stage=tool_call tool=search",
}
for _, l := range lines {
if _, err := m.Train(l); err != nil {
t.Fatalf("Train(%q): %v", l, err)
}
}
originalCount := m.ClusterCount()

data, err := m.SaveJSON()
if err != nil {
t.Fatalf("SaveJSON: %v", err)
}

m2, err := LoadMinerJSON(data)
if err != nil {
t.Fatalf("LoadMinerJSON: %v", err)
}
if m2.ClusterCount() != originalCount {
t.Errorf("round-trip: expected %d clusters, got %d", originalCount, m2.ClusterCount())
}
}

func TestConcurrency(t *testing.T) {
m, err := NewMiner(DefaultConfig())
if err != nil {
Expand Down
16 changes: 0 additions & 16 deletions pkg/agentdrain/persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,3 @@ func (m *Miner) LoadJSON(data []byte) error {
persistLog.Printf("Loaded miner state: clusters=%d", len(snap.Clusters))
return nil
}

// LoadMinerJSON creates a new Miner by restoring state from JSON bytes.
func LoadMinerJSON(data []byte) (*Miner, error) {
var snap Snapshot
if err := json.Unmarshal(data, &snap); err != nil {
return nil, fmt.Errorf("agentdrain: LoadMinerJSON: %w", err)
}
m, err := NewMiner(snap.Config)
if err != nil {
return nil, err
}
if err := m.LoadJSON(data); err != nil {
return nil, err
}
return m, nil
}
4 changes: 2 additions & 2 deletions pkg/workflow/compiler_draft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ This is a test workflow for draft filtering.

if tt.shouldHaveIf {
// Check that the expected if condition is present (normalize for multiline comparison)
normalizedLockContent := NormalizeExpressionForComparison(lockContent)
normalizedExpectedIf := NormalizeExpressionForComparison(tt.expectedIf)
normalizedLockContent := strings.Join(strings.Fields(lockContent), " ")
normalizedExpectedIf := strings.Join(strings.Fields(tt.expectedIf), " ")
if !strings.Contains(normalizedLockContent, normalizedExpectedIf) {
t.Errorf("Expected lock file to contain '%s' but it didn't.\nExpected (normalized): %s\nActual (normalized): %s\nOriginal Content:\n%s",
tt.expectedIf, normalizedExpectedIf, normalizedLockContent, lockContent)
Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/compiler_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ This is a test workflow for command triggering.
}

// Check that the expected if condition is present (normalize for multiline comparison)
normalizedLockContent := NormalizeExpressionForComparison(lockContent)
normalizedExpectedIf := NormalizeExpressionForComparison(tt.expectedIf)
normalizedLockContent := strings.Join(strings.Fields(lockContent), " ")
normalizedExpectedIf := strings.Join(strings.Fields(tt.expectedIf), " ")
if !strings.Contains(normalizedLockContent, normalizedExpectedIf) {
t.Errorf("Expected lock file to contain '%s' but it didn't.\nExpected (normalized): %s\nActual (normalized): %s\nOriginal Content:\n%s",
tt.expectedIf, normalizedExpectedIf, normalizedLockContent, lockContent)
Expand Down Expand Up @@ -464,8 +464,8 @@ This is a test workflow for command merging with other events.
}

// Check that the expected if condition is present (normalize for multiline comparison)
normalizedLockContent := NormalizeExpressionForComparison(lockContent)
normalizedExpectedIf := NormalizeExpressionForComparison(tt.expectedIf)
normalizedLockContent := strings.Join(strings.Fields(lockContent), " ")
normalizedExpectedIf := strings.Join(strings.Fields(tt.expectedIf), " ")
if !strings.Contains(normalizedLockContent, normalizedExpectedIf) {
t.Errorf("Expected lock file to contain '%s' but it didn't.\nExpected (normalized): %s\nActual (normalized): %s\nOriginal Content:\n%s",
tt.expectedIf, normalizedExpectedIf, normalizedLockContent, lockContent)
Expand Down
16 changes: 0 additions & 16 deletions pkg/workflow/expression_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,19 +508,3 @@ func escapeForYAMLDoubleQuoted(s string) string {
}
return b.String()
}

// NormalizeExpressionForComparison normalizes an expression by removing extra spaces and newlines
// This is used for comparing multiline expressions with their single-line equivalents
func NormalizeExpressionForComparison(expression string) string {
// Replace newlines and tabs with spaces
normalized := strings.ReplaceAll(expression, "\n", " ")
normalized = strings.ReplaceAll(normalized, "\t", " ")

// Replace multiple spaces with single spaces
for strings.Contains(normalized, " ") {
normalized = strings.ReplaceAll(normalized, " ", " ")
}

// Trim leading and trailing spaces
return strings.TrimSpace(normalized)
}
62 changes: 10 additions & 52 deletions pkg/workflow/expressions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -946,8 +946,8 @@ func TestBreakLongExpression(t *testing.T) {

// Verify that joined lines equal normalized original (whitespace differences allowed)
joined := strings.Join(lines, " ")
originalNorm := NormalizeExpressionForComparison(tt.expression)
joinedNorm := NormalizeExpressionForComparison(joined)
originalNorm := strings.Join(strings.Fields(tt.expression), " ")
joinedNorm := strings.Join(strings.Fields(joined), " ")

if joinedNorm != originalNorm {
t.Errorf("Joined lines don't match original expression\nOriginal: %s\nJoined: %s", originalNorm, joinedNorm)
Expand Down Expand Up @@ -985,8 +985,8 @@ func TestBreakAtParentheses(t *testing.T) {

// Verify that joined lines equal normalized original
joined := strings.Join(lines, " ")
originalNorm := NormalizeExpressionForComparison(tt.expression)
joinedNorm := NormalizeExpressionForComparison(joined)
originalNorm := strings.Join(strings.Fields(tt.expression), " ")
joinedNorm := strings.Join(strings.Fields(joined), " ")

if joinedNorm != originalNorm {
t.Errorf("Joined lines don't match original expression\nOriginal: %s\nJoined: %s", originalNorm, joinedNorm)
Expand All @@ -995,48 +995,6 @@ func TestBreakAtParentheses(t *testing.T) {
}
}

// TestNormalizeExpressionForComparison tests the expression normalization function
func TestNormalizeExpressionForComparison(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "single line with extra spaces",
input: "github.event_name == 'issues' || github.event.action == 'opened'",
expected: "github.event_name == 'issues' || github.event.action == 'opened'",
},
{
name: "multiline expression",
input: `github.event_name == 'issues' ||
github.event_name == 'pull_request' ||
github.event.action == 'opened'`,
expected: "github.event_name == 'issues' || github.event_name == 'pull_request' || github.event.action == 'opened'",
},
{
name: "expression with mixed whitespace",
input: `github.event_name == 'issues' ||
github.event_name == 'pull_request'`,
expected: "github.event_name == 'issues' || github.event_name == 'pull_request'",
},
{
name: "expression with leading/trailing whitespace",
input: " github.event_name == 'issues' ",
expected: "github.event_name == 'issues'",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeExpressionForComparison(tt.input)
if result != tt.expected {
t.Errorf("NormalizeExpressionForComparison() = '%s', expected '%s'", result, tt.expected)
}
})
}
}

// TestMultilineExpressionEquivalence tests that multiline expressions are equivalent to single-line expressions
func TestMultilineExpressionEquivalence(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -1079,8 +1037,8 @@ github.event_name == 'issue_comment' ||

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
singleNorm := NormalizeExpressionForComparison(tt.singleLine)
multiNorm := NormalizeExpressionForComparison(tt.multiLine)
singleNorm := strings.Join(strings.Fields(tt.singleLine), " ")
multiNorm := strings.Join(strings.Fields(tt.multiLine), " ")

isEqual := singleNorm == multiNorm
if isEqual != tt.shouldBeEqual {
Expand Down Expand Up @@ -1134,8 +1092,8 @@ func TestLongExpressionBreakingDetailed(t *testing.T) {

// Most importantly: verify equivalence
joined := strings.Join(lines, " ")
originalNorm := NormalizeExpressionForComparison(tt.expression)
joinedNorm := NormalizeExpressionForComparison(joined)
originalNorm := strings.Join(strings.Fields(tt.expression), " ")
joinedNorm := strings.Join(strings.Fields(joined), " ")

if joinedNorm != originalNorm {
t.Errorf("Broken expression is not equivalent to original\nOriginal: %s\nBroken: %s\nJoined: %s\nOriginal normalized: %s\nJoined normalized: %s",
Expand Down Expand Up @@ -1175,8 +1133,8 @@ func TestExpressionBreakingWithQuotes(t *testing.T) {

// Verify that quotes are preserved and no breaking happens inside quoted strings
joined := strings.Join(lines, " ")
originalNorm := NormalizeExpressionForComparison(tt.expression)
joinedNorm := NormalizeExpressionForComparison(joined)
originalNorm := strings.Join(strings.Fields(tt.expression), " ")
joinedNorm := strings.Join(strings.Fields(joined), " ")

if joinedNorm != originalNorm {
t.Errorf("Expression with quotes not preserved correctly\nOriginal: %s\nJoined: %s", originalNorm, joinedNorm)
Expand Down
Loading
Loading