Skip to content

Commit a3dd0cb

Browse files
authored
Merge pull request #39 from GrayCodeAI/refactor/code-clarity
refactor: split oversized Go files for code clarity
2 parents 9bc68a7 + a248d14 commit a3dd0cb

128 files changed

Lines changed: 46001 additions & 44622 deletions

File tree

Some content is hidden

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

cli/agent/factoryaidroid/transcript_2_test.go

Lines changed: 490 additions & 0 deletions
Large diffs are not rendered by default.

cli/agent/factoryaidroid/transcript_test.go

Lines changed: 0 additions & 481 deletions
Large diffs are not rendered by default.

cli/attach_2_test.go

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
package cli
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/GrayCodeAI/trace/cli/agent"
14+
_ "github.com/GrayCodeAI/trace/cli/agent/claudecode" // register agent
15+
_ "github.com/GrayCodeAI/trace/cli/agent/codex" // register agent
16+
_ "github.com/GrayCodeAI/trace/cli/agent/cursor" // register agent
17+
_ "github.com/GrayCodeAI/trace/cli/agent/factoryaidroid" // register agent
18+
_ "github.com/GrayCodeAI/trace/cli/agent/geminicli" // register agent
19+
"github.com/GrayCodeAI/trace/cli/agent/types"
20+
"github.com/GrayCodeAI/trace/cli/session"
21+
"github.com/GrayCodeAI/trace/cli/testutil"
22+
23+
"github.com/go-git/go-git/v6"
24+
"github.com/go-git/go-git/v6/plumbing"
25+
)
26+
27+
func TestAttach_CursorSuccess(t *testing.T) {
28+
setupAttachTestRepo(t)
29+
30+
cursorDir := t.TempDir()
31+
t.Setenv("TRACE_TEST_CURSOR_PROJECT_DIR", cursorDir)
32+
33+
sessionID := "test-attach-cursor-session"
34+
// Cursor uses JSONL format, same as Claude Code
35+
transcriptContent := `{"type":"user","message":{"role":"user","content":"add dark mode"},"uuid":"u1"}
36+
{"type":"assistant","message":{"role":"assistant","content":"I'll add dark mode support."},"uuid":"a1"}
37+
`
38+
// Cursor flat layout: <dir>/<id>.jsonl
39+
if err := os.WriteFile(filepath.Join(cursorDir, sessionID+".jsonl"), []byte(transcriptContent), 0o600); err != nil {
40+
t.Fatal(err)
41+
}
42+
43+
var out bytes.Buffer
44+
err := runAttach(context.Background(), &out, sessionID, agent.AgentNameCursor, true)
45+
if err != nil {
46+
t.Fatalf("runAttach failed: %v", err)
47+
}
48+
49+
if !strings.Contains(out.String(), "Attached session") {
50+
t.Errorf("expected 'Attached session' in output, got: %s", out.String())
51+
}
52+
53+
store, err := session.NewStateStore(context.Background())
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
state, err := store.Load(context.Background(), sessionID)
58+
if err != nil {
59+
t.Fatal(err)
60+
}
61+
if state == nil {
62+
t.Fatal("expected session state to be created")
63+
return
64+
}
65+
if state.AgentType != agent.AgentTypeCursor {
66+
t.Errorf("AgentType = %q, want %q", state.AgentType, agent.AgentTypeCursor)
67+
}
68+
if state.SessionTurnCount != 1 {
69+
t.Errorf("SessionTurnCount = %d, want 1", state.SessionTurnCount)
70+
}
71+
}
72+
73+
func TestAttach_CodexSuccess(t *testing.T) {
74+
setupAttachTestRepo(t)
75+
76+
codexDir := t.TempDir()
77+
t.Setenv("TRACE_TEST_CODEX_SESSION_DIR", codexDir)
78+
79+
sessionID := "019d6c43-1537-7343-9691-1f8cee04fe59"
80+
transcriptContent := `{"timestamp":"2026-04-08T10:43:48.000Z","type":"session_meta","payload":{"id":"019d6c43-1537-7343-9691-1f8cee04fe59","timestamp":"2026-04-08T10:43:48.000Z"}}
81+
{"timestamp":"2026-04-08T10:43:49.000Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"investigate attach failure"}]}}
82+
{"timestamp":"2026-04-08T10:43:50.000Z","type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Looking into it."}]}}
83+
`
84+
sessionFile := filepath.Join(codexDir, "2026", "04", "08", "rollout-2026-04-08T10-43-48-"+sessionID+".jsonl")
85+
if err := os.MkdirAll(filepath.Dir(sessionFile), 0o750); err != nil {
86+
t.Fatal(err)
87+
}
88+
if err := os.WriteFile(sessionFile, []byte(transcriptContent), 0o600); err != nil {
89+
t.Fatal(err)
90+
}
91+
92+
var out bytes.Buffer
93+
err := runAttach(context.Background(), &out, sessionID, agent.AgentNameCodex, true)
94+
if err != nil {
95+
t.Fatalf("runAttach failed: %v", err)
96+
}
97+
98+
if !strings.Contains(out.String(), "Attached session") {
99+
t.Errorf("expected 'Attached session' in output, got: %s", out.String())
100+
}
101+
102+
store, err := session.NewStateStore(context.Background())
103+
if err != nil {
104+
t.Fatal(err)
105+
}
106+
state, err := store.Load(context.Background(), sessionID)
107+
if err != nil {
108+
t.Fatal(err)
109+
}
110+
if state == nil {
111+
t.Fatal("expected session state to be created")
112+
return
113+
}
114+
if state.AgentType != agent.AgentTypeCodex {
115+
t.Errorf("AgentType = %q, want %q", state.AgentType, agent.AgentTypeCodex)
116+
}
117+
if state.TranscriptPath != sessionFile {
118+
t.Errorf("TranscriptPath = %q, want %q", state.TranscriptPath, sessionFile)
119+
}
120+
if state.LastCheckpointID.IsEmpty() {
121+
t.Error("expected LastCheckpointID to be set after attach")
122+
}
123+
}
124+
125+
func TestAttach_FactoryAIDroidSuccess(t *testing.T) {
126+
setupAttachTestRepo(t)
127+
128+
droidDir := t.TempDir()
129+
t.Setenv("TRACE_TEST_DROID_PROJECT_DIR", droidDir)
130+
131+
sessionID := "test-attach-droid-session"
132+
// Factory AI Droid uses JSONL format
133+
transcriptContent := `{"type":"user","message":{"role":"user","content":"deploy to staging"},"uuid":"u1"}
134+
{"type":"assistant","message":{"role":"assistant","content":"Deploying to staging now."},"uuid":"a1"}
135+
`
136+
// Factory AI Droid: flat <dir>/<id>.jsonl
137+
if err := os.WriteFile(filepath.Join(droidDir, sessionID+".jsonl"), []byte(transcriptContent), 0o600); err != nil {
138+
t.Fatal(err)
139+
}
140+
141+
var out bytes.Buffer
142+
err := runAttach(context.Background(), &out, sessionID, agent.AgentNameFactoryAIDroid, true)
143+
if err != nil {
144+
t.Fatalf("runAttach failed: %v", err)
145+
}
146+
147+
if !strings.Contains(out.String(), "Attached session") {
148+
t.Errorf("expected 'Attached session' in output, got: %s", out.String())
149+
}
150+
151+
store, err := session.NewStateStore(context.Background())
152+
if err != nil {
153+
t.Fatal(err)
154+
}
155+
state, err := store.Load(context.Background(), sessionID)
156+
if err != nil {
157+
t.Fatal(err)
158+
}
159+
if state == nil {
160+
t.Fatal("expected session state to be created")
161+
return
162+
}
163+
if state.AgentType != agent.AgentTypeFactoryAIDroid {
164+
t.Errorf("AgentType = %q, want %q", state.AgentType, agent.AgentTypeFactoryAIDroid)
165+
}
166+
if state.SessionTurnCount != 1 {
167+
t.Errorf("SessionTurnCount = %d, want 1", state.SessionTurnCount)
168+
}
169+
}
170+
171+
func TestAttach_CursorNestedLayout(t *testing.T) {
172+
setupAttachTestRepo(t)
173+
174+
cursorDir := t.TempDir()
175+
t.Setenv("TRACE_TEST_CURSOR_PROJECT_DIR", cursorDir)
176+
177+
sessionID := "test-cursor-nested-layout"
178+
transcriptContent := `{"type":"user","message":{"role":"user","content":"hello"},"uuid":"u1"}
179+
`
180+
// Cursor IDE nested layout: <dir>/<id>/<id>.jsonl
181+
nestedDir := filepath.Join(cursorDir, sessionID)
182+
if err := os.MkdirAll(nestedDir, 0o750); err != nil {
183+
t.Fatal(err)
184+
}
185+
if err := os.WriteFile(filepath.Join(nestedDir, sessionID+".jsonl"), []byte(transcriptContent), 0o600); err != nil {
186+
t.Fatal(err)
187+
}
188+
189+
var out bytes.Buffer
190+
err := runAttach(context.Background(), &out, sessionID, agent.AgentNameCursor, true)
191+
if err != nil {
192+
t.Fatalf("runAttach failed: %v", err)
193+
}
194+
195+
if !strings.Contains(out.String(), "Attached session") {
196+
t.Errorf("expected 'Attached session' in output, got: %s", out.String())
197+
}
198+
}
199+
200+
// setupAttachTestRepo creates a temp git repo with one commit and enables Trace.
201+
// Returns the repo directory. Caller must not use t.Parallel() (uses t.Chdir).
202+
func setupAttachTestRepo(t *testing.T) {
203+
t.Helper()
204+
tmpDir := t.TempDir()
205+
testutil.InitRepo(t, tmpDir)
206+
testutil.WriteFile(t, tmpDir, "init.txt", "init")
207+
testutil.GitAdd(t, tmpDir, "init.txt")
208+
testutil.GitCommit(t, tmpDir, "init")
209+
t.Chdir(tmpDir)
210+
enableTrace(t, tmpDir)
211+
}
212+
213+
// setupClaudeTranscript creates a fake Claude transcript file.
214+
// The file's mtime is backdated so that waitForTranscriptFlush treats it as
215+
// stale and skips the 3-second poll loop.
216+
func setupClaudeTranscript(t *testing.T, sessionID, content string) {
217+
t.Helper()
218+
claudeDir := t.TempDir()
219+
t.Setenv("TRACE_TEST_CLAUDE_PROJECT_DIR", claudeDir)
220+
fpath := filepath.Join(claudeDir, sessionID+".jsonl")
221+
if err := os.WriteFile(fpath, []byte(content), 0o600); err != nil {
222+
t.Fatal(err)
223+
}
224+
stale := time.Now().Add(-3 * time.Minute)
225+
if err := os.Chtimes(fpath, stale, stale); err != nil {
226+
t.Fatal(err)
227+
}
228+
}
229+
230+
// enableTrace creates the .trace/settings.json file to mark Trace as enabled.
231+
func enableTrace(t *testing.T, repoDir string) {
232+
t.Helper()
233+
traceDir := filepath.Join(repoDir, ".trace")
234+
if err := os.MkdirAll(traceDir, 0o750); err != nil {
235+
t.Fatal(err)
236+
}
237+
settingsContent := `{"enabled": true}`
238+
if err := os.WriteFile(filepath.Join(traceDir, "settings.json"), []byte(settingsContent), 0o600); err != nil {
239+
t.Fatal(err)
240+
}
241+
}
242+
243+
func setAttachCheckpointsV2Enabled(t *testing.T, repoDir string) {
244+
t.Helper()
245+
traceDir := filepath.Join(repoDir, ".trace")
246+
if err := os.MkdirAll(traceDir, 0o750); err != nil {
247+
t.Fatal(err)
248+
}
249+
settingsContent := `{"enabled": true, "strategy_options": {"checkpoints_v2": true}}`
250+
if err := os.WriteFile(filepath.Join(traceDir, "settings.json"), []byte(settingsContent), 0o600); err != nil {
251+
t.Fatal(err)
252+
}
253+
}
254+
255+
func setAttachCheckpointsV2Only(t *testing.T, repoDir string) {
256+
t.Helper()
257+
traceDir := filepath.Join(repoDir, ".trace")
258+
if err := os.MkdirAll(traceDir, 0o750); err != nil {
259+
t.Fatal(err)
260+
}
261+
settingsContent := `{"enabled": true, "strategy_options": {"checkpoints_version": 2}}`
262+
if err := os.WriteFile(filepath.Join(traceDir, "settings.json"), []byte(settingsContent), 0o600); err != nil {
263+
t.Fatal(err)
264+
}
265+
}
266+
267+
func mustGetwd(t *testing.T) string {
268+
t.Helper()
269+
dir, err := os.Getwd()
270+
if err != nil {
271+
t.Fatal(err)
272+
}
273+
return dir
274+
}
275+
276+
func readFileFromRef(t *testing.T, repo *git.Repository, refName, filePath string) (string, bool) {
277+
t.Helper()
278+
279+
ref, err := repo.Reference(plumbing.ReferenceName(refName), true)
280+
if err != nil {
281+
return "", false
282+
}
283+
commit, err := repo.CommitObject(ref.Hash())
284+
if err != nil {
285+
return "", false
286+
}
287+
tree, err := commit.Tree()
288+
if err != nil {
289+
return "", false
290+
}
291+
file, err := tree.File(filePath)
292+
if err != nil {
293+
return "", false
294+
}
295+
content, err := file.Contents()
296+
if err != nil {
297+
return "", false
298+
}
299+
return content, true
300+
}
301+
302+
// TestAttach_DiscoversExternalAgents verifies that `trace attach --agent <external>`
303+
// gets past the agent registry check when external_agents is enabled and a
304+
// matching binary is on PATH. Without the DiscoverAndRegister call in the
305+
// attach command, this would fail with "unknown agent: <name>".
306+
//
307+
// This test does not verify end-to-end attach behavior — it asserts only
308+
// that discovery ran. The command is expected to fail later (transcript
309+
// resolution) because we don't stand up a real session.
310+
func TestAttach_DiscoversExternalAgents(t *testing.T) {
311+
if _, err := exec.LookPath("sh"); err != nil {
312+
t.Skip("sh not available")
313+
}
314+
315+
setupAttachTestRepo(t)
316+
317+
// Overwrite settings to enable external_agents (enableTrace writes the
318+
// file without it).
319+
cwd := mustGetwd(t)
320+
settingsPath := filepath.Join(cwd, ".trace", "settings.json")
321+
if err := os.WriteFile(settingsPath, []byte(`{"enabled":true,"external_agents":true}`), 0o600); err != nil {
322+
t.Fatal(err)
323+
}
324+
325+
// Use a unique name so concurrent test runs can't collide in the global
326+
// agent registry.
327+
agentName := types.AgentName("attachtest-discovery-agent")
328+
329+
binDir := t.TempDir()
330+
binPath := filepath.Join(binDir, "trace-agent-"+string(agentName))
331+
infoJSON := `{
332+
"protocol_version": 1,
333+
"name": "` + string(agentName) + `",
334+
"type": "Attach Test Agent",
335+
"description": "Agent for attach discovery test",
336+
"is_preview": false,
337+
"protected_dirs": [],
338+
"hook_names": [],
339+
"capabilities": {}
340+
}`
341+
script := "#!/bin/sh\nif [ \"$1\" = \"info\" ]; then\n echo '" + infoJSON + "'\nfi\n"
342+
if err := os.WriteFile(binPath, []byte(script), 0o755); err != nil {
343+
t.Fatalf("failed to write mock agent binary: %v", err)
344+
}
345+
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
346+
347+
cmd := newAttachCmd()
348+
// Pass a bogus session ID — the point is to exercise the registry check,
349+
// not full attach flow.
350+
cmd.SetArgs([]string{"--agent", string(agentName), "-f", "fake-session-id"})
351+
var out bytes.Buffer
352+
cmd.SetOut(&out)
353+
cmd.SetErr(&out)
354+
355+
err := cmd.Execute()
356+
// We expect an error (no transcript), but it must not be the
357+
// registry-lookup error. A regression (removing DiscoverAndRegister)
358+
// would produce "unknown agent: attachtest-discovery-agent".
359+
if err == nil {
360+
t.Fatalf("expected attach to fail on missing transcript, got success\noutput: %s", out.String())
361+
}
362+
if strings.Contains(err.Error(), "unknown agent") {
363+
t.Fatalf("attach did not discover external agent — got registry miss: %v", err)
364+
}
365+
366+
// Also confirm the agent actually landed in the registry, so the check
367+
// above is meaningful (not merely passing because some other error
368+
// short-circuited before the registry lookup).
369+
if _, lookupErr := agent.Get(agentName); lookupErr != nil {
370+
t.Errorf("expected external agent %q in registry after attach, got: %v", agentName, lookupErr)
371+
}
372+
}
373+
374+
func runGitInDir(t *testing.T, dir string, args ...string) {
375+
t.Helper()
376+
cmd := exec.CommandContext(context.Background(), "git", args...)
377+
cmd.Dir = dir
378+
if out, err := cmd.CombinedOutput(); err != nil {
379+
t.Fatalf("git %v in %s: %v\n%s", args, dir, err, out)
380+
}
381+
}

0 commit comments

Comments
 (0)