From 30be39550605abd5671fb7def59405c159101381 Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 2 Apr 2026 14:45:51 -0700 Subject: [PATCH 1/5] fix: change AgentTypeUnknown from 'Agent' to 'Unknown' Co-Authored-By: Claude Sonnet 4.6 Entire-Checkpoint: f2e246b13aa1 --- cmd/entire/cli/agent/registry.go | 2 +- cmd/entire/cli/commit_message_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/entire/cli/agent/registry.go b/cmd/entire/cli/agent/registry.go index 11269e359..cc2b201e9 100644 --- a/cmd/entire/cli/agent/registry.go +++ b/cmd/entire/cli/agent/registry.go @@ -118,7 +118,7 @@ const ( AgentTypeFactoryAIDroid types.AgentType = "Factory AI Droid" AgentTypeGemini types.AgentType = "Gemini CLI" AgentTypeOpenCode types.AgentType = "OpenCode" - AgentTypeUnknown types.AgentType = "Agent" // Fallback for backwards compatibility + AgentTypeUnknown types.AgentType = "Unknown" ) // DefaultAgentName is the registry key for the default agent. diff --git a/cmd/entire/cli/commit_message_test.go b/cmd/entire/cli/commit_message_test.go index 0a86426c4..62bc05279 100644 --- a/cmd/entire/cli/commit_message_test.go +++ b/cmd/entire/cli/commit_message_test.go @@ -248,10 +248,10 @@ func TestGenerateCommitMessage(t *testing.T) { expected: "OpenCode session updates", }, { - name: "returns Agent fallback for empty agent type", + name: "returns Unknown fallback for empty agent type", prompt: "", agentType: "", - expected: "Agent session updates", + expected: "Unknown session updates", }, } From 615d7f5d77b0aa45b0128c683e6f0a1b5e393546 Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 2 Apr 2026 14:52:27 -0700 Subject: [PATCH 2/5] fix: ReadAgentTypeFromTree returns Unknown when multiple agent dirs exist Previously, .claude/ directory presence always returned AgentTypeClaudeCode, even when other agent config dirs (e.g., .codex/) coexisted from 'entire configure'. Now counts detected agent dirs and returns Unknown when ambiguous. Co-Authored-By: Claude Sonnet 4.6 Entire-Checkpoint: 45a6a07753e8 --- cmd/entire/cli/strategy/common.go | 33 ++++- .../cli/strategy/read_agent_from_tree_test.go | 121 ++++++++++++++++++ 2 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 cmd/entire/cli/strategy/read_agent_from_tree_test.go diff --git a/cmd/entire/cli/strategy/common.go b/cmd/entire/cli/strategy/common.go index b8a9b8d7d..811255b2f 100644 --- a/cmd/entire/cli/strategy/common.go +++ b/cmd/entire/cli/strategy/common.go @@ -690,21 +690,40 @@ func ReadAgentTypeFromTree(tree *object.Tree, checkpointPath string) types.Agent } // Fall back to detecting agent from config files (shadow branches don't have metadata.json). - // Order: Gemini (most specific check), Claude (established default), OpenCode (newest/preview). + // Multiple agent config dirs may coexist when users configure multiple agents via + // `entire configure`. Only return a specific agent type when exactly one agent config + // dir is present; otherwise return Unknown since we can't determine which agent + // created the checkpoint. + var detected types.AgentType + detectedCount := 0 + if _, err := tree.File(".gemini/settings.json"); err == nil { - return agent.AgentTypeGemini + detected = agent.AgentTypeGemini + detectedCount++ } if _, err := tree.Tree(".claude"); err == nil { - return agent.AgentTypeClaudeCode + detected = agent.AgentTypeClaudeCode + detectedCount++ } - // OpenCode: .opencode directory or opencode.json config if _, err := tree.Tree(".opencode"); err == nil { - return agent.AgentTypeOpenCode + detected = agent.AgentTypeOpenCode + detectedCount++ + } else if _, err := tree.File("opencode.json"); err == nil { + detected = agent.AgentTypeOpenCode + detectedCount++ + } + if _, err := tree.Tree(".codex"); err == nil { + detected = agent.AgentTypeCodex + detectedCount++ } - if _, err := tree.File("opencode.json"); err == nil { - return agent.AgentTypeOpenCode + if _, err := tree.Tree(".cursor"); err == nil { + detected = agent.AgentTypeCursor + detectedCount++ } + if detectedCount == 1 { + return detected + } return agent.AgentTypeUnknown } diff --git a/cmd/entire/cli/strategy/read_agent_from_tree_test.go b/cmd/entire/cli/strategy/read_agent_from_tree_test.go new file mode 100644 index 000000000..3e9cb5a38 --- /dev/null +++ b/cmd/entire/cli/strategy/read_agent_from_tree_test.go @@ -0,0 +1,121 @@ +package strategy + +import ( + "testing" + + "github.com/entireio/cli/cmd/entire/cli/agent" + _ "github.com/entireio/cli/cmd/entire/cli/agent/claudecode" + "github.com/entireio/cli/cmd/entire/cli/testutil" + git "github.com/go-git/go-git/v6" + "github.com/go-git/go-git/v6/plumbing/object" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// openRepoHeadTree opens the repo at dir and returns the HEAD commit tree. +func openRepoHeadTree(t *testing.T, dir string) *object.Tree { + t.Helper() + repo, err := git.PlainOpen(dir) + require.NoError(t, err) + head, err := repo.Head() + require.NoError(t, err) + commit, err := repo.CommitObject(head.Hash()) + require.NoError(t, err) + tree, err := commit.Tree() + require.NoError(t, err) + return tree +} + +func TestReadAgentTypeFromTree_OnlyClaude(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + // git doesn't track empty dirs — add a file inside .claude/ + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeClaudeCode, result) +} + +func TestReadAgentTypeFromTree_OnlyGemini(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) + testutil.GitAdd(t, dir, ".gemini/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeGemini, result) +} + +func TestReadAgentTypeFromTree_ClaudeAndCodex_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.WriteFile(t, dir, ".codex/config.json", `{}`) + testutil.GitAdd(t, dir, ".codex/config.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_ClaudeAndGemini_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) + testutil.GitAdd(t, dir, ".gemini/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_NoAgentDirs_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, "f.txt", "init") + testutil.GitAdd(t, dir, "f.txt") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_MetadataJSON_OverridesDir(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + // .claude/ dir present — without metadata.json this would return ClaudeCode + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + // metadata.json at checkpoint path declares a different agent + testutil.WriteFile(t, dir, "cp/metadata.json", `{"agent":"Cursor"}`) + testutil.GitAdd(t, dir, "cp/metadata.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + // Pass "cp" as checkpointPath — ReadAgentTypeFromTree looks for "cp/metadata.json" + result := ReadAgentTypeFromTree(tree, "cp") + assert.Equal(t, agent.AgentTypeCursor, result) +} From 1434d72bd6e25cdf351e1536eb1fe7d6462ead0f Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 2 Apr 2026 15:05:38 -0700 Subject: [PATCH 3/5] docs: clarify DefaultAgentName is for setup, not attribution Co-Authored-By: Claude Sonnet 4.6 Entire-Checkpoint: fe6c491e7122 --- cmd/entire/cli/agent/registry_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/entire/cli/agent/registry_test.go b/cmd/entire/cli/agent/registry_test.go index c456e4287..3789f08d8 100644 --- a/cmd/entire/cli/agent/registry_test.go +++ b/cmd/entire/cli/agent/registry_test.go @@ -144,6 +144,9 @@ func TestAgentNameConstants(t *testing.T) { } func TestDefaultAgentName(t *testing.T) { + // DefaultAgentName is for the `entire enable` setup flow when no agent is + // detected. It is NOT used for agent attribution fallbacks — those use + // AgentTypeUnknown ("Unknown") or "Unknown" in the DB. if DefaultAgentName != AgentNameClaudeCode { t.Errorf("expected DefaultAgentName %q, got %q", AgentNameClaudeCode, DefaultAgentName) } From cd37aa0372c26316ee4109d5cb272a92b7d0845e Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 2 Apr 2026 16:01:50 -0700 Subject: [PATCH 4/5] fix: add missing .factory detection and expand test coverage Addresses PR review feedback: - Add .factory dir detection for Factory AI Droid (Cursor Bugbot) - Add single-agent tests for Codex, Cursor, Factory (Copilot) - Update comment to say "config markers" not "config dir" (Copilot) Co-Authored-By: Claude Opus 4.6 (1M context) Entire-Checkpoint: 0372b92d6beb --- cmd/entire/cli/strategy/common.go | 12 ++++-- .../cli/strategy/read_agent_from_tree_test.go | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/cmd/entire/cli/strategy/common.go b/cmd/entire/cli/strategy/common.go index 811255b2f..72900240c 100644 --- a/cmd/entire/cli/strategy/common.go +++ b/cmd/entire/cli/strategy/common.go @@ -689,11 +689,11 @@ func ReadAgentTypeFromTree(tree *object.Tree, checkpointPath string) types.Agent } } - // Fall back to detecting agent from config files (shadow branches don't have metadata.json). - // Multiple agent config dirs may coexist when users configure multiple agents via + // Fall back to detecting agent from config markers (shadow branches don't have metadata.json). + // Multiple agent config markers may coexist when users configure multiple agents via // `entire configure`. Only return a specific agent type when exactly one agent config - // dir is present; otherwise return Unknown since we can't determine which agent - // created the checkpoint. + // marker (directory or file) is present; otherwise return Unknown since we can't + // determine which agent created the checkpoint. var detected types.AgentType detectedCount := 0 @@ -720,6 +720,10 @@ func ReadAgentTypeFromTree(tree *object.Tree, checkpointPath string) types.Agent detected = agent.AgentTypeCursor detectedCount++ } + if _, err := tree.Tree(".factory"); err == nil { + detected = agent.AgentTypeFactoryAIDroid + detectedCount++ + } if detectedCount == 1 { return detected diff --git a/cmd/entire/cli/strategy/read_agent_from_tree_test.go b/cmd/entire/cli/strategy/read_agent_from_tree_test.go index 3e9cb5a38..d45c3404d 100644 --- a/cmd/entire/cli/strategy/read_agent_from_tree_test.go +++ b/cmd/entire/cli/strategy/read_agent_from_tree_test.go @@ -55,6 +55,48 @@ func TestReadAgentTypeFromTree_OnlyGemini(t *testing.T) { assert.Equal(t, agent.AgentTypeGemini, result) } +func TestReadAgentTypeFromTree_OnlyCodex(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".codex/config.json", `{}`) + testutil.GitAdd(t, dir, ".codex/config.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeCodex, result) +} + +func TestReadAgentTypeFromTree_OnlyCursor(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".cursor/settings.json", `{}`) + testutil.GitAdd(t, dir, ".cursor/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeCursor, result) +} + +func TestReadAgentTypeFromTree_OnlyFactory(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".factory/settings.json", `{}`) + testutil.GitAdd(t, dir, ".factory/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeFactoryAIDroid, result) +} + func TestReadAgentTypeFromTree_ClaudeAndCodex_ReturnsUnknown(t *testing.T) { t.Parallel() From f168d0634bc96786d80bd9174705e27efd90132d Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 2 Apr 2026 16:05:25 -0700 Subject: [PATCH 5/5] refactor: move ReadAgentTypeFromTree tests into common_test.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need for a standalone test file — these tests belong with the function they test in common.go. Co-Authored-By: Claude Opus 4.6 (1M context) Entire-Checkpoint: 751884f8707a --- cmd/entire/cli/strategy/common_test.go | 150 ++++++++++++++++ .../cli/strategy/read_agent_from_tree_test.go | 163 ------------------ 2 files changed, 150 insertions(+), 163 deletions(-) delete mode 100644 cmd/entire/cli/strategy/read_agent_from_tree_test.go diff --git a/cmd/entire/cli/strategy/common_test.go b/cmd/entire/cli/strategy/common_test.go index 6a7c0cf90..c5aa4a2e4 100644 --- a/cmd/entire/cli/strategy/common_test.go +++ b/cmd/entire/cli/strategy/common_test.go @@ -8,6 +8,8 @@ import ( "sync" "testing" + "github.com/entireio/cli/cmd/entire/cli/agent" + _ "github.com/entireio/cli/cmd/entire/cli/agent/claudecode" "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" "github.com/entireio/cli/cmd/entire/cli/testutil" @@ -15,6 +17,8 @@ import ( "github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6/plumbing" "github.com/go-git/go-git/v6/plumbing/object" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestOpenRepository(t *testing.T) { @@ -1544,3 +1548,149 @@ func TestIsEmptyRepository(t *testing.T) { } }) } + +// openRepoHeadTree opens the repo at dir and returns the HEAD commit tree. +func openRepoHeadTree(t *testing.T, dir string) *object.Tree { + t.Helper() + repo, err := git.PlainOpen(dir) + require.NoError(t, err) + head, err := repo.Head() + require.NoError(t, err) + commit, err := repo.CommitObject(head.Hash()) + require.NoError(t, err) + tree, err := commit.Tree() + require.NoError(t, err) + return tree +} + +func TestReadAgentTypeFromTree_OnlyClaude(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeClaudeCode, result) +} + +func TestReadAgentTypeFromTree_OnlyGemini(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) + testutil.GitAdd(t, dir, ".gemini/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeGemini, result) +} + +func TestReadAgentTypeFromTree_OnlyCodex(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".codex/config.json", `{}`) + testutil.GitAdd(t, dir, ".codex/config.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeCodex, result) +} + +func TestReadAgentTypeFromTree_OnlyCursor(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".cursor/settings.json", `{}`) + testutil.GitAdd(t, dir, ".cursor/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeCursor, result) +} + +func TestReadAgentTypeFromTree_OnlyFactory(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".factory/settings.json", `{}`) + testutil.GitAdd(t, dir, ".factory/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeFactoryAIDroid, result) +} + +func TestReadAgentTypeFromTree_ClaudeAndCodex_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.WriteFile(t, dir, ".codex/config.json", `{}`) + testutil.GitAdd(t, dir, ".codex/config.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_ClaudeAndGemini_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) + testutil.GitAdd(t, dir, ".gemini/settings.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_NoAgentDirs_ReturnsUnknown(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, "f.txt", "init") + testutil.GitAdd(t, dir, "f.txt") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "nonexistent-path") + assert.Equal(t, agent.AgentTypeUnknown, result) +} + +func TestReadAgentTypeFromTree_MetadataJSON_OverridesDir(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + testutil.InitRepo(t, dir) + testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) + testutil.GitAdd(t, dir, ".claude/settings.json") + testutil.WriteFile(t, dir, "cp/metadata.json", `{"agent":"Cursor"}`) + testutil.GitAdd(t, dir, "cp/metadata.json") + testutil.GitCommit(t, dir, "init") + + tree := openRepoHeadTree(t, dir) + result := ReadAgentTypeFromTree(tree, "cp") + assert.Equal(t, agent.AgentTypeCursor, result) +} diff --git a/cmd/entire/cli/strategy/read_agent_from_tree_test.go b/cmd/entire/cli/strategy/read_agent_from_tree_test.go deleted file mode 100644 index d45c3404d..000000000 --- a/cmd/entire/cli/strategy/read_agent_from_tree_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package strategy - -import ( - "testing" - - "github.com/entireio/cli/cmd/entire/cli/agent" - _ "github.com/entireio/cli/cmd/entire/cli/agent/claudecode" - "github.com/entireio/cli/cmd/entire/cli/testutil" - git "github.com/go-git/go-git/v6" - "github.com/go-git/go-git/v6/plumbing/object" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// openRepoHeadTree opens the repo at dir and returns the HEAD commit tree. -func openRepoHeadTree(t *testing.T, dir string) *object.Tree { - t.Helper() - repo, err := git.PlainOpen(dir) - require.NoError(t, err) - head, err := repo.Head() - require.NoError(t, err) - commit, err := repo.CommitObject(head.Hash()) - require.NoError(t, err) - tree, err := commit.Tree() - require.NoError(t, err) - return tree -} - -func TestReadAgentTypeFromTree_OnlyClaude(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - // git doesn't track empty dirs — add a file inside .claude/ - testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) - testutil.GitAdd(t, dir, ".claude/settings.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeClaudeCode, result) -} - -func TestReadAgentTypeFromTree_OnlyGemini(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) - testutil.GitAdd(t, dir, ".gemini/settings.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeGemini, result) -} - -func TestReadAgentTypeFromTree_OnlyCodex(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".codex/config.json", `{}`) - testutil.GitAdd(t, dir, ".codex/config.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeCodex, result) -} - -func TestReadAgentTypeFromTree_OnlyCursor(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".cursor/settings.json", `{}`) - testutil.GitAdd(t, dir, ".cursor/settings.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeCursor, result) -} - -func TestReadAgentTypeFromTree_OnlyFactory(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".factory/settings.json", `{}`) - testutil.GitAdd(t, dir, ".factory/settings.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeFactoryAIDroid, result) -} - -func TestReadAgentTypeFromTree_ClaudeAndCodex_ReturnsUnknown(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) - testutil.GitAdd(t, dir, ".claude/settings.json") - testutil.WriteFile(t, dir, ".codex/config.json", `{}`) - testutil.GitAdd(t, dir, ".codex/config.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeUnknown, result) -} - -func TestReadAgentTypeFromTree_ClaudeAndGemini_ReturnsUnknown(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) - testutil.GitAdd(t, dir, ".claude/settings.json") - testutil.WriteFile(t, dir, ".gemini/settings.json", `{}`) - testutil.GitAdd(t, dir, ".gemini/settings.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeUnknown, result) -} - -func TestReadAgentTypeFromTree_NoAgentDirs_ReturnsUnknown(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - testutil.WriteFile(t, dir, "f.txt", "init") - testutil.GitAdd(t, dir, "f.txt") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - result := ReadAgentTypeFromTree(tree, "nonexistent-path") - assert.Equal(t, agent.AgentTypeUnknown, result) -} - -func TestReadAgentTypeFromTree_MetadataJSON_OverridesDir(t *testing.T) { - t.Parallel() - - dir := t.TempDir() - testutil.InitRepo(t, dir) - // .claude/ dir present — without metadata.json this would return ClaudeCode - testutil.WriteFile(t, dir, ".claude/settings.json", `{}`) - testutil.GitAdd(t, dir, ".claude/settings.json") - // metadata.json at checkpoint path declares a different agent - testutil.WriteFile(t, dir, "cp/metadata.json", `{"agent":"Cursor"}`) - testutil.GitAdd(t, dir, "cp/metadata.json") - testutil.GitCommit(t, dir, "init") - - tree := openRepoHeadTree(t, dir) - // Pass "cp" as checkpointPath — ReadAgentTypeFromTree looks for "cp/metadata.json" - result := ReadAgentTypeFromTree(tree, "cp") - assert.Equal(t, agent.AgentTypeCursor, result) -}