Skip to content

Commit 56b2e0a

Browse files
authored
refactor(engine): drop 21 re-export shims (#317)
Remove all engine re-export shims except safety_reexports.go (which has 217 external references and is a separate migration). Callers now import the sub-package directly (e.g. planning.PlannedCall, cost.Cost, token.EstimateTokens) instead of going through the engine namespace. Verified: go build ./..., go vet, golangci-lint (0 issues), full go test ./..., make boundaries.
1 parent 8e8702a commit 56b2e0a

55 files changed

Lines changed: 343 additions & 1038 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎cmd/chat_config_models.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"strings"
66
"sync"
77

8+
"github.com/GrayCodeAI/rho/internal/engine/cost"
9+
810
tea "charm.land/bubbletea/v2"
911

1012
rhoconfig "github.com/GrayCodeAI/rho/internal/config"
@@ -214,7 +216,7 @@ func applyModelOptionToSession(sess *engine.Session, opt configModelOption) {
214216
sess.EnsureAutoCompactor()
215217
}
216218
if opt.PriceKnown {
217-
engine.RegisterLivePricing(opt.ID, opt.InputPricePer1M, opt.OutputPricePer1M)
219+
cost.RegisterLivePricing(opt.ID, opt.InputPricePer1M, opt.OutputPricePer1M)
218220
}
219221
}
220222

‎cmd/compact_ui.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/GrayCodeAI/rho/internal/engine/token"
11+
1012
tea "charm.land/bubbletea/v2"
1113
lipgloss "charm.land/lipgloss/v2"
1214
"github.com/mattn/go-runewidth"
@@ -174,7 +176,7 @@ func (m *chatModel) startManualCompact() (chatModel, tea.Cmd) {
174176
m.compactBarWindow = m.contextWindowTokens()
175177
m.compactBarUsed = sessionContextUsedTokens(m.session)
176178
if m.compactBarUsed <= 0 && m.session != nil {
177-
m.compactBarUsed = engine.EstimateTokens(m.session.RawMessages())
179+
m.compactBarUsed = token.EstimateTokens(m.session.RawMessages())
178180
}
179181
if m.brailleSpinner != nil {
180182
m.brailleSpinner.SetLabel("")

‎cmd/options.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"strings"
1111

12+
"github.com/GrayCodeAI/rho/internal/engine/scaffold"
13+
1214
rhoconfig "github.com/GrayCodeAI/rho/internal/config"
1315
ctxrepomap "github.com/GrayCodeAI/rho/internal/context/repomap"
1416
"github.com/GrayCodeAI/rho/internal/engine"
@@ -380,7 +382,7 @@ func configureSessionStartup(sess *engine.Session, settings rhoconfig.Settings,
380382
sess.LifecycleSvc().SetReflector(engine.NewReflector(sess, sess.Model()))
381383

382384
// Few-shot learning: collect successful patterns from sessions
383-
sess.LifecycleSvc().SetFewShotStore(engine.NewFewShotStore())
385+
sess.LifecycleSvc().SetFewShotStore(scaffold.NewFewShotStore())
384386

385387
// Adaptive prompt: learn user preferences from corrections
386388
sess.LifecycleSvc().SetAdaptivePrompt(engine.NewAdaptivePrompt())

‎cmd/statusbar.go‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sync"
88
"time"
99

10+
"github.com/GrayCodeAI/rho/internal/engine/cost"
11+
1012
tea "charm.land/bubbletea/v2"
1113
lipgloss "charm.land/lipgloss/v2"
1214
"golang.org/x/text/language"
@@ -154,7 +156,7 @@ func renderStatusBarPrimaryRight(m *chatModel) string {
154156
return strings.Join(parts, statusDimStyle.Render(" · "))
155157
}
156158

157-
func formatStatusCost(c *engine.Cost) string {
159+
func formatStatusCost(c *cost.Cost) string {
158160
if c == nil {
159161
return icons.Ruby() + " $0.00"
160162
}

‎internal/engine/agent_intelligence.go‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package engine
33
import (
44
"context"
55
"strings"
6+
7+
"github.com/GrayCodeAI/rho/internal/engine/agent"
68
)
79

810
// AgentIntelligence provides smart routing, auto-spawning, and synthesis for agents.
@@ -26,7 +28,7 @@ type SpawnDecision struct {
2628
type SubTask struct {
2729
ID string
2830
Prompt string
29-
Mode SubAgentMode
31+
Mode agent.SubAgentMode
3032
Priority int // higher = run first
3133
DependsOn []string // IDs of tasks this depends on
3234
}
@@ -78,19 +80,19 @@ func (ai *AgentIntelligence) AnalyzeForParallelism(prompt string) SpawnDecision
7880
}
7981

8082
// SelectMode picks the optimal agent mode for a subtask.
81-
func (ai *AgentIntelligence) SelectMode(subtask string) SubAgentMode {
83+
func (ai *AgentIntelligence) SelectMode(subtask string) agent.SubAgentMode {
8284
lower := strings.ToLower(subtask)
8385

8486
// Read-only tasks → explore mode (cheaper, faster)
8587
readOnlyKeywords := []string{"find", "search", "list", "check", "read", "analyze", "look", "scan", "grep", "what is", "where is", "how many"}
8688
for _, kw := range readOnlyKeywords {
8789
if strings.Contains(lower, kw) {
88-
return SubAgentExplore
90+
return agent.SubAgentExplore
8991
}
9092
}
9193

9294
// Write tasks → general mode
93-
return SubAgentGeneral
95+
return agent.SubAgentGeneral
9496
}
9597

9698
// decomposeTask splits a complex task into subtasks based on patterns.
@@ -101,17 +103,17 @@ func (ai *AgentIntelligence) decomposeTask(prompt string, scale TaskScale) []Sub
101103
if (strings.Contains(lower, "research") || strings.Contains(lower, "analyze")) &&
102104
(strings.Contains(lower, "implement") || strings.Contains(lower, "build") || strings.Contains(lower, "create")) {
103105
return []SubTask{
104-
{ID: "research", Prompt: "Research and analyze: " + prompt, Mode: SubAgentExplore},
105-
{ID: "implement", Prompt: "Based on research, implement: " + prompt, Mode: SubAgentGeneral, DependsOn: []string{"research"}},
106+
{ID: "research", Prompt: "Research and analyze: " + prompt, Mode: agent.SubAgentExplore},
107+
{ID: "implement", Prompt: "Based on research, implement: " + prompt, Mode: agent.SubAgentGeneral, DependsOn: []string{"research"}},
106108
}
107109
}
108110

109111
// Pattern: multi-file refactor — pipeline
110112
if strings.Contains(lower, "refactor") && scale >= ScaleMajor {
111113
return []SubTask{
112-
{ID: "scan", Prompt: "Scan and identify all files that need changes for: " + prompt, Mode: SubAgentExplore},
113-
{ID: "plan", Prompt: "Create a refactoring plan based on scan results: " + prompt, Mode: SubAgentExplore, DependsOn: []string{"scan"}},
114-
{ID: "execute", Prompt: "Execute the refactoring plan: " + prompt, Mode: SubAgentGeneral, DependsOn: []string{"plan"}},
114+
{ID: "scan", Prompt: "Scan and identify all files that need changes for: " + prompt, Mode: agent.SubAgentExplore},
115+
{ID: "plan", Prompt: "Create a refactoring plan based on scan results: " + prompt, Mode: agent.SubAgentExplore, DependsOn: []string{"scan"}},
116+
{ID: "execute", Prompt: "Execute the refactoring plan: " + prompt, Mode: agent.SubAgentGeneral, DependsOn: []string{"plan"}},
115117
}
116118
}
117119

@@ -185,7 +187,7 @@ func splitOnConjunctions(s string) []string {
185187
}
186188

187189
// ExecuteWithIntelligence runs a task with smart agent routing.
188-
func (ai *AgentIntelligence) ExecuteWithIntelligence(ctx context.Context, prompt string, execFn func(context.Context, string, SubAgentMode) (string, error)) (string, error) {
190+
func (ai *AgentIntelligence) ExecuteWithIntelligence(ctx context.Context, prompt string, execFn func(context.Context, string, agent.SubAgentMode) (string, error)) (string, error) {
189191
decision := ai.AnalyzeForParallelism(prompt)
190192

191193
if !decision.ShouldParallelize {

‎internal/engine/agent_intelligence_test.go‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ package engine
33
import (
44
"context"
55
"testing"
6+
7+
"github.com/GrayCodeAI/rho/internal/engine/agent"
68
)
79

810
func TestAgentIntelligence_SelectMode(t *testing.T) {
911
ai := NewAgentIntelligence()
1012

1113
tests := []struct {
1214
prompt string
13-
want SubAgentMode
15+
want agent.SubAgentMode
1416
}{
15-
{"find all files using deprecated API", SubAgentExplore},
16-
{"search for authentication code", SubAgentExplore},
17-
{"analyze the dependency graph", SubAgentExplore},
18-
{"implement the new endpoint", SubAgentGeneral},
19-
{"refactor the auth module", SubAgentGeneral},
20-
{"fix the bug in parser.go", SubAgentGeneral},
17+
{"find all files using deprecated API", agent.SubAgentExplore},
18+
{"search for authentication code", agent.SubAgentExplore},
19+
{"analyze the dependency graph", agent.SubAgentExplore},
20+
{"implement the new endpoint", agent.SubAgentGeneral},
21+
{"refactor the auth module", agent.SubAgentGeneral},
22+
{"fix the bug in parser.go", agent.SubAgentGeneral},
2123
}
2224
for _, tt := range tests {
2325
got := ai.SelectMode(tt.prompt)
@@ -67,7 +69,7 @@ func TestAgentIntelligence_ExecuteWithIntelligence_Single(t *testing.T) {
6769
ai := NewAgentIntelligence()
6870

6971
called := false
70-
result, err := ai.ExecuteWithIntelligence(context.Background(), "fix typo", func(_ context.Context, prompt string, mode SubAgentMode) (string, error) {
72+
result, err := ai.ExecuteWithIntelligence(context.Background(), "fix typo", func(_ context.Context, prompt string, mode agent.SubAgentMode) (string, error) {
7173
called = true
7274
return "fixed", nil
7375
})
@@ -95,8 +97,8 @@ func TestSelfAwareness_ShouldDelegate(t *testing.T) {
9597

9698
func TestSynthesisPrompt(t *testing.T) {
9799
subtasks := []SubTask{
98-
{ID: "a", Prompt: "find files", Mode: SubAgentExplore},
99-
{ID: "b", Prompt: "fix them", Mode: SubAgentGeneral},
100+
{ID: "a", Prompt: "find files", Mode: agent.SubAgentExplore},
101+
{ID: "b", Prompt: "fix them", Mode: agent.SubAgentGeneral},
100102
}
101103
results := map[string]string{"a": "found 3 files", "b": "fixed all"}
102104
prompt := MergeSynthesisPrompt(subtasks, results)

‎internal/engine/agent_reexports.go‎

Lines changed: 0 additions & 42 deletions
This file was deleted.

‎internal/engine/agent_session_tool.go‎

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -137,26 +137,26 @@ func (s *Session) spawnSubAgentRequest(ctx context.Context, req agentcontracts.S
137137
return res, nil
138138
}
139139

140-
func mapContractsType(t agentcontracts.SubagentType) SubAgentMode {
140+
func mapContractsType(t agentcontracts.SubagentType) engagent.SubAgentMode {
141141
switch t {
142142
case agentcontracts.TypePlan:
143-
return SubAgentPlan
143+
return engagent.SubAgentPlan
144144
case agentcontracts.TypeGeneralPurpose:
145-
return SubAgentGeneral
145+
return engagent.SubAgentGeneral
146146
default:
147-
return SubAgentExplore
147+
return engagent.SubAgentExplore
148148
}
149149
}
150150

151151
// spawnSubAgent creates a sub-agent with the given mode and depth tracking.
152152
// Returns (output, worktreePath, error).
153-
func (s *Session) spawnSubAgent(ctx context.Context, norm agentcontracts.Normalized, mode SubAgentMode, depth int) (string, string, error) {
154-
if depth >= MaxAgentDepth {
155-
return "", "", fmt.Errorf("max agent depth %d exceeded", MaxAgentDepth)
153+
func (s *Session) spawnSubAgent(ctx context.Context, norm agentcontracts.Normalized, mode engagent.SubAgentMode, depth int) (string, string, error) {
154+
if depth >= engagent.MaxAgentDepth {
155+
return "", "", fmt.Errorf("max agent depth %d exceeded", engagent.MaxAgentDepth)
156156
}
157157

158-
maxTurns := DefaultTurnsForMode(mode)
159-
if mode == SubAgentExplore && norm.Thoroughness != "" {
158+
maxTurns := engagent.DefaultTurnsForMode(mode)
159+
if mode == engagent.SubAgentExplore && norm.Thoroughness != "" {
160160
maxTurns = engagent.ThoroughnessTurns(engagent.ExploreThoroughness(norm.Thoroughness))
161161
}
162162

@@ -174,7 +174,7 @@ func (s *Session) spawnSubAgent(ctx context.Context, norm agentcontracts.Normali
174174

175175
// Capability mode can further restrict tools beyond profile defaults.
176176
if norm.CapabilityMode == agentcontracts.CapReadOnly {
177-
registry = registry.Filter(ExploreTools)
177+
registry = registry.Filter(engagent.ExploreTools)
178178
}
179179

180180
subPromptCtx := prompts.PromptContext{
@@ -185,14 +185,14 @@ func (s *Session) spawnSubAgent(ctx context.Context, norm agentcontracts.Normali
185185
if err != nil {
186186
subSystemPrompt = s.Persistence().System()
187187
}
188-
if mode == SubAgentPlan {
188+
if mode == engagent.SubAgentPlan {
189189
subSystemPrompt = planSystemPrefix + "\n\n" + subSystemPrompt
190190
}
191191

192192
sub := s.SubSession(model, subSystemPrompt, registry)
193193
sub.PermSvc().SetPermissionFn(s.PermSvc().PermissionFn())
194194
// Explore/plan: hard read-only bash allowlist (in addition to tool filter).
195-
if IsReadOnlyMode(mode) || norm.CapabilityMode == agentcontracts.CapReadOnly {
195+
if engagent.IsReadOnlyMode(mode) || norm.CapabilityMode == agentcontracts.CapReadOnly {
196196
sub.Tools().SetReadOnlyBash(true)
197197
}
198198
// A child receives an independent snapshot of the parent's policy. This
@@ -281,30 +281,30 @@ func (s *Session) spawnSubAgent(ctx context.Context, norm agentcontracts.Normali
281281
const planSystemPrefix = "You are a planning sub-agent. Produce an ordered, actionable plan. " +
282282
"Do not modify files. Prefer research tools (Read, Grep, Glob, LS) and only use Bash for read-only inspection."
283283

284-
func (s *Session) resolveSubAgentModel(mode SubAgentMode) string {
284+
func (s *Session) resolveSubAgentModel(mode engagent.SubAgentMode) string {
285285
current := s.ChatLLM().Model()
286286
if s.LifecycleSvc().Cascade() == nil {
287287
return current
288288
}
289289
switch mode {
290-
case SubAgentExplore:
290+
case engagent.SubAgentExplore:
291291
return s.LifecycleSvc().Cascade().SelectModel("summarize", current, s.LifecycleSvc().Cascade().Roles.Explorer)
292-
case SubAgentPlan:
292+
case engagent.SubAgentPlan:
293293
return s.LifecycleSvc().Cascade().SelectModel("planning", current, s.LifecycleSvc().Cascade().Roles.Planner)
294-
case SubAgentGeneral:
294+
case engagent.SubAgentGeneral:
295295
return s.LifecycleSvc().Cascade().SelectModel("implement", current, "")
296296
default:
297297
return current
298298
}
299299
}
300300

301-
func (s *Session) resolveSubAgentTools(mode SubAgentMode) *tool.Registry {
301+
func (s *Session) resolveSubAgentTools(mode engagent.SubAgentMode) *tool.Registry {
302302
registry := s.Tools().Registry()
303303
switch mode {
304-
case SubAgentExplore:
305-
return registry.Filter(ExploreTools)
306-
case SubAgentPlan:
307-
return registry.Filter(PlanTools)
304+
case engagent.SubAgentExplore:
305+
return registry.Filter(engagent.ExploreTools)
306+
case engagent.SubAgentPlan:
307+
return registry.Filter(engagent.PlanTools)
308308
default:
309309
return registry
310310
}

‎internal/engine/bmad_features_test.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"os"
55
"path/filepath"
66
"testing"
7+
8+
"github.com/GrayCodeAI/rho/internal/engine/project"
79
)
810

911
func TestClassifyScale(t *testing.T) {
@@ -78,7 +80,7 @@ func TestProjectContext_Load(t *testing.T) {
7880
dir := t.TempDir()
7981
os.WriteFile(filepath.Join(dir, "PROJECT_CONTEXT.md"), []byte("## Stack\n- Go 1.21\n- PostgreSQL"), 0o644)
8082

81-
pc := NewProjectContext(dir)
83+
pc := project.NewProjectContext(dir)
8284
content := pc.Load()
8385
if !hasSubstr(content, "Go 1.21") {
8486
t.Error("expected project context content")
@@ -89,7 +91,7 @@ func TestProjectContext_Load(t *testing.T) {
8991
}
9092

9193
func TestProjectContext_NoFiles(t *testing.T) {
92-
pc := NewProjectContext(t.TempDir())
94+
pc := project.NewProjectContext(t.TempDir())
9395
content := pc.Load()
9496
if content != "" {
9597
t.Error("expected empty content when no files exist")

0 commit comments

Comments
 (0)