gobby 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/codex_impl/__init__.py +28 -0
  4. gobby/adapters/codex_impl/adapter.py +722 -0
  5. gobby/adapters/codex_impl/client.py +679 -0
  6. gobby/adapters/codex_impl/protocol.py +20 -0
  7. gobby/adapters/codex_impl/types.py +68 -0
  8. gobby/agents/definitions.py +11 -1
  9. gobby/agents/isolation.py +395 -0
  10. gobby/agents/sandbox.py +261 -0
  11. gobby/agents/spawn.py +42 -287
  12. gobby/agents/spawn_executor.py +385 -0
  13. gobby/agents/spawners/__init__.py +24 -0
  14. gobby/agents/spawners/command_builder.py +189 -0
  15. gobby/agents/spawners/embedded.py +21 -2
  16. gobby/agents/spawners/headless.py +21 -2
  17. gobby/agents/spawners/prompt_manager.py +125 -0
  18. gobby/cli/install.py +4 -4
  19. gobby/cli/installers/claude.py +6 -0
  20. gobby/cli/installers/gemini.py +6 -0
  21. gobby/cli/installers/shared.py +103 -4
  22. gobby/cli/sessions.py +1 -1
  23. gobby/cli/utils.py +9 -2
  24. gobby/config/__init__.py +12 -97
  25. gobby/config/app.py +10 -94
  26. gobby/config/extensions.py +2 -2
  27. gobby/config/features.py +7 -130
  28. gobby/config/tasks.py +4 -28
  29. gobby/hooks/__init__.py +0 -13
  30. gobby/hooks/event_handlers.py +45 -2
  31. gobby/hooks/hook_manager.py +2 -2
  32. gobby/hooks/plugins.py +1 -1
  33. gobby/hooks/webhooks.py +1 -1
  34. gobby/llm/resolver.py +3 -2
  35. gobby/mcp_proxy/importer.py +62 -4
  36. gobby/mcp_proxy/instructions.py +2 -0
  37. gobby/mcp_proxy/registries.py +1 -4
  38. gobby/mcp_proxy/services/recommendation.py +43 -11
  39. gobby/mcp_proxy/tools/agents.py +31 -731
  40. gobby/mcp_proxy/tools/clones.py +0 -385
  41. gobby/mcp_proxy/tools/memory.py +2 -2
  42. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  43. gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
  44. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  45. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  46. gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
  47. gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
  48. gobby/mcp_proxy/tools/skills/__init__.py +14 -29
  49. gobby/mcp_proxy/tools/spawn_agent.py +417 -0
  50. gobby/mcp_proxy/tools/tasks/_lifecycle.py +52 -18
  51. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
  52. gobby/mcp_proxy/tools/worktrees.py +0 -343
  53. gobby/memory/ingestion/__init__.py +5 -0
  54. gobby/memory/ingestion/multimodal.py +221 -0
  55. gobby/memory/manager.py +62 -283
  56. gobby/memory/search/__init__.py +10 -0
  57. gobby/memory/search/coordinator.py +248 -0
  58. gobby/memory/services/__init__.py +5 -0
  59. gobby/memory/services/crossref.py +142 -0
  60. gobby/prompts/loader.py +5 -2
  61. gobby/servers/http.py +1 -4
  62. gobby/servers/routes/admin.py +14 -0
  63. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  64. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  65. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  66. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  67. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  68. gobby/servers/routes/mcp/hooks.py +1 -1
  69. gobby/servers/routes/mcp/tools.py +48 -1506
  70. gobby/sessions/lifecycle.py +1 -1
  71. gobby/sessions/processor.py +10 -0
  72. gobby/sessions/transcripts/base.py +1 -0
  73. gobby/sessions/transcripts/claude.py +15 -5
  74. gobby/skills/parser.py +30 -2
  75. gobby/storage/migrations.py +159 -372
  76. gobby/storage/sessions.py +43 -7
  77. gobby/storage/skills.py +37 -4
  78. gobby/storage/tasks/_lifecycle.py +18 -3
  79. gobby/sync/memories.py +1 -1
  80. gobby/tasks/external_validator.py +1 -1
  81. gobby/tasks/validation.py +22 -20
  82. gobby/tools/summarizer.py +91 -10
  83. gobby/utils/project_context.py +2 -3
  84. gobby/utils/status.py +13 -0
  85. gobby/workflows/actions.py +221 -1217
  86. gobby/workflows/artifact_actions.py +31 -0
  87. gobby/workflows/autonomous_actions.py +11 -0
  88. gobby/workflows/context_actions.py +50 -1
  89. gobby/workflows/enforcement/__init__.py +47 -0
  90. gobby/workflows/enforcement/blocking.py +269 -0
  91. gobby/workflows/enforcement/commit_policy.py +283 -0
  92. gobby/workflows/enforcement/handlers.py +269 -0
  93. gobby/workflows/enforcement/task_policy.py +542 -0
  94. gobby/workflows/git_utils.py +106 -0
  95. gobby/workflows/llm_actions.py +30 -0
  96. gobby/workflows/mcp_actions.py +20 -1
  97. gobby/workflows/memory_actions.py +80 -0
  98. gobby/workflows/safe_evaluator.py +183 -0
  99. gobby/workflows/session_actions.py +44 -0
  100. gobby/workflows/state_actions.py +60 -1
  101. gobby/workflows/stop_signal_actions.py +55 -0
  102. gobby/workflows/summary_actions.py +94 -1
  103. gobby/workflows/task_sync_actions.py +347 -0
  104. gobby/workflows/todo_actions.py +34 -1
  105. gobby/workflows/webhook_actions.py +185 -0
  106. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/METADATA +6 -1
  107. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/RECORD +111 -111
  108. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
  109. gobby/adapters/codex.py +0 -1332
  110. gobby/install/claude/commands/gobby/bug.md +0 -51
  111. gobby/install/claude/commands/gobby/chore.md +0 -51
  112. gobby/install/claude/commands/gobby/epic.md +0 -52
  113. gobby/install/claude/commands/gobby/eval.md +0 -235
  114. gobby/install/claude/commands/gobby/feat.md +0 -49
  115. gobby/install/claude/commands/gobby/nit.md +0 -52
  116. gobby/install/claude/commands/gobby/ref.md +0 -52
  117. gobby/mcp_proxy/tools/session_messages.py +0 -1055
  118. gobby/prompts/defaults/expansion/system.md +0 -119
  119. gobby/prompts/defaults/expansion/user.md +0 -48
  120. gobby/prompts/defaults/external_validation/agent.md +0 -72
  121. gobby/prompts/defaults/external_validation/external.md +0 -63
  122. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  123. gobby/prompts/defaults/external_validation/system.md +0 -6
  124. gobby/prompts/defaults/features/import_mcp.md +0 -22
  125. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  126. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  127. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  128. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  129. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  130. gobby/prompts/defaults/features/server_description.md +0 -20
  131. gobby/prompts/defaults/features/server_description_system.md +0 -6
  132. gobby/prompts/defaults/features/task_description.md +0 -31
  133. gobby/prompts/defaults/features/task_description_system.md +0 -6
  134. gobby/prompts/defaults/features/tool_summary.md +0 -17
  135. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  136. gobby/prompts/defaults/handoff/compact.md +0 -63
  137. gobby/prompts/defaults/handoff/session_end.md +0 -57
  138. gobby/prompts/defaults/memory/extract.md +0 -61
  139. gobby/prompts/defaults/research/step.md +0 -58
  140. gobby/prompts/defaults/validation/criteria.md +0 -47
  141. gobby/prompts/defaults/validation/validate.md +0 -38
  142. gobby/storage/migrations_legacy.py +0 -1359
  143. gobby/workflows/task_enforcement_actions.py +0 -1343
  144. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
  145. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
  146. {gobby-0.2.6.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,68 @@
1
+ """
2
+ Type definitions and data classes for Codex adapter.
3
+
4
+ Extracted from codex.py as part of Phase 3 Strangler Fig decomposition.
5
+ These types are used throughout the Codex adapter implementation.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Any
14
+
15
+
16
+ class CodexConnectionState(Enum):
17
+ """Connection state for the Codex app-server."""
18
+
19
+ DISCONNECTED = "disconnected"
20
+ CONNECTING = "connecting"
21
+ CONNECTED = "connected"
22
+ ERROR = "error"
23
+
24
+
25
+ @dataclass
26
+ class CodexThread:
27
+ """Represents a Codex conversation thread."""
28
+
29
+ id: str
30
+ preview: str = ""
31
+ model_provider: str = "openai"
32
+ created_at: int = 0
33
+
34
+
35
+ @dataclass
36
+ class CodexTurn:
37
+ """Represents a turn in a Codex conversation."""
38
+
39
+ id: str
40
+ thread_id: str
41
+ status: str = "pending"
42
+ items: list[dict[str, Any]] = field(default_factory=list)
43
+ error: str | None = None
44
+ usage: dict[str, int] | None = None
45
+
46
+
47
+ @dataclass
48
+ class CodexItem:
49
+ """Represents an item (message, tool call, etc.) in a turn."""
50
+
51
+ id: str
52
+ type: str # "reasoning", "agent_message", "command_execution", "user_message", etc.
53
+ content: str = ""
54
+ status: str = "pending"
55
+ metadata: dict[str, Any] = field(default_factory=dict)
56
+
57
+
58
+ # Type alias for notification handlers
59
+ NotificationHandler = Callable[[str, dict[str, Any]], None]
60
+
61
+
62
+ __all__ = [
63
+ "CodexConnectionState",
64
+ "CodexThread",
65
+ "CodexTurn",
66
+ "CodexItem",
67
+ "NotificationHandler",
68
+ ]
@@ -8,11 +8,12 @@ lifecycle behavior, solving recursion loops in delegation.
8
8
 
9
9
  import logging
10
10
  from pathlib import Path
11
- from typing import Any
11
+ from typing import Any, Literal
12
12
 
13
13
  import yaml
14
14
  from pydantic import BaseModel, Field
15
15
 
16
+ from gobby.agents.sandbox import SandboxConfig
16
17
  from gobby.utils.project_context import get_project_context
17
18
 
18
19
  logger = logging.getLogger(__name__)
@@ -29,6 +30,15 @@ class AgentDefinition(BaseModel):
29
30
  # Execution parameters
30
31
  model: str | None = None
31
32
  mode: str = "headless" # Default to headless for stability
33
+ provider: str = "claude" # Provider: claude, gemini, codex
34
+
35
+ # Isolation configuration
36
+ isolation: Literal["current", "worktree", "clone"] | None = None
37
+ branch_prefix: str | None = None
38
+ base_branch: str = "main"
39
+
40
+ # Sandbox configuration
41
+ sandbox: SandboxConfig | None = None
32
42
 
33
43
  # Workflow configuration
34
44
  workflow: str | None = None
@@ -0,0 +1,395 @@
1
+ """
2
+ Isolation Handlers for Unified spawn_agent API.
3
+
4
+ This module provides the abstraction layer for different isolation modes:
5
+ - current: Work in the current directory (no isolation)
6
+ - worktree: Create/reuse a git worktree for isolated work
7
+ - clone: Create a shallow clone for full isolation
8
+
9
+ Each handler implements the IsolationHandler ABC to provide:
10
+ - Environment preparation (worktree/clone creation)
11
+ - Context prompt building (adding isolation warnings)
12
+ - Branch name generation
13
+ """
14
+
15
+ import time
16
+ from abc import ABC, abstractmethod
17
+ from dataclasses import dataclass, field
18
+ from typing import Any, Literal
19
+
20
+
21
+ @dataclass
22
+ class IsolationContext:
23
+ """Result of environment preparation."""
24
+
25
+ cwd: str
26
+ branch_name: str | None = None
27
+ worktree_id: str | None = None
28
+ clone_id: str | None = None
29
+ isolation_type: Literal["current", "worktree", "clone"] = "current"
30
+ extra: dict[str, Any] = field(default_factory=dict)
31
+
32
+
33
+ @dataclass
34
+ class SpawnConfig:
35
+ """Configuration passed to isolation handlers."""
36
+
37
+ prompt: str
38
+ task_id: str | None
39
+ task_title: str | None
40
+ task_seq_num: int | None
41
+ branch_name: str | None
42
+ branch_prefix: str | None
43
+ base_branch: str
44
+ project_id: str
45
+ project_path: str
46
+ provider: str
47
+ parent_session_id: str
48
+
49
+
50
+ def generate_branch_name(config: SpawnConfig) -> str:
51
+ """
52
+ Auto-generate branch name from task or fallback to prefix+timestamp.
53
+
54
+ Priority:
55
+ 1. Explicit branch_name if provided
56
+ 2. task-{seq_num}-{slugified_title} if task info available
57
+ 3. {branch_prefix}{timestamp} as fallback (default prefix: "agent/")
58
+ """
59
+ if config.branch_name:
60
+ return config.branch_name
61
+
62
+ if config.task_seq_num and config.task_title:
63
+ # Generate slug from task title
64
+ slug = config.task_title.lower().replace(" ", "-")
65
+ # Keep only alphanumeric and hyphens
66
+ slug = "".join(c for c in slug if c.isalnum() or c == "-")
67
+ # Truncate to 40 chars
68
+ slug = slug[:40]
69
+ return f"task-{config.task_seq_num}-{slug}"
70
+
71
+ # Fallback to prefix + timestamp
72
+ prefix = config.branch_prefix or "agent/"
73
+ return f"{prefix}{int(time.time())}"
74
+
75
+
76
+ class IsolationHandler(ABC):
77
+ """Abstract base class for isolation handlers."""
78
+
79
+ @abstractmethod
80
+ async def prepare_environment(self, config: SpawnConfig) -> IsolationContext:
81
+ """
82
+ Prepare isolated environment (worktree/clone creation).
83
+
84
+ Args:
85
+ config: Spawn configuration with project and task info
86
+
87
+ Returns:
88
+ IsolationContext with cwd and isolation metadata
89
+ """
90
+
91
+ @abstractmethod
92
+ def build_context_prompt(self, original_prompt: str, ctx: IsolationContext) -> str:
93
+ """
94
+ Build prompt with isolation context warnings.
95
+
96
+ Args:
97
+ original_prompt: The original user prompt
98
+ ctx: Isolation context from prepare_environment
99
+
100
+ Returns:
101
+ Enhanced prompt with isolation context (or unchanged for current)
102
+ """
103
+
104
+
105
+ class CurrentIsolationHandler(IsolationHandler):
106
+ """
107
+ No isolation - work in current directory.
108
+
109
+ This is the simplest handler that just returns the project path
110
+ as the working directory without any git branch changes.
111
+ """
112
+
113
+ async def prepare_environment(self, config: SpawnConfig) -> IsolationContext:
114
+ """Return project path as working directory."""
115
+ return IsolationContext(
116
+ cwd=config.project_path,
117
+ isolation_type="current",
118
+ )
119
+
120
+ def build_context_prompt(self, original_prompt: str, ctx: IsolationContext) -> str:
121
+ """Return prompt unchanged - no additional context needed."""
122
+ return original_prompt
123
+
124
+
125
+ class WorktreeIsolationHandler(IsolationHandler):
126
+ """
127
+ Worktree isolation - create/reuse a git worktree for isolated work.
128
+
129
+ This handler:
130
+ - Checks for existing worktrees by branch name
131
+ - Creates new worktrees if needed
132
+ - Copies project.json and installs hooks
133
+ - Adds CRITICAL context warning to prompt
134
+ """
135
+
136
+ def __init__(
137
+ self,
138
+ git_manager: Any, # WorktreeGitManager
139
+ worktree_storage: Any, # LocalWorktreeManager
140
+ ) -> None:
141
+ """
142
+ Initialize WorktreeIsolationHandler with dependencies.
143
+
144
+ Args:
145
+ git_manager: Git manager for worktree operations
146
+ worktree_storage: Storage for worktree records
147
+ """
148
+ self._git_manager = git_manager
149
+ self._worktree_storage = worktree_storage
150
+
151
+ async def prepare_environment(self, config: SpawnConfig) -> IsolationContext:
152
+ """
153
+ Prepare worktree environment.
154
+
155
+ - Generate branch name if not provided
156
+ - Check for existing worktree for the branch
157
+ - Create new worktree if needed
158
+ - Return IsolationContext with worktree info
159
+ """
160
+ branch_name = generate_branch_name(config)
161
+
162
+ # Check if worktree already exists for this branch
163
+ existing = self._worktree_storage.get_by_branch(config.project_id, branch_name)
164
+ if existing:
165
+ # Use existing worktree
166
+ return IsolationContext(
167
+ cwd=existing.worktree_path,
168
+ branch_name=existing.branch_name,
169
+ worktree_id=existing.id,
170
+ isolation_type="worktree",
171
+ extra={"main_repo_path": self._git_manager.repo_path},
172
+ )
173
+
174
+ # Generate worktree path
175
+ from pathlib import Path
176
+
177
+ project_name = Path(self._git_manager.repo_path).name
178
+ worktree_path = self._generate_worktree_path(branch_name, project_name)
179
+
180
+ # Create git worktree
181
+ result = self._git_manager.create_worktree(
182
+ worktree_path=worktree_path,
183
+ branch_name=branch_name,
184
+ base_branch=config.base_branch,
185
+ create_branch=True,
186
+ )
187
+
188
+ if not result.success:
189
+ raise RuntimeError(f"Failed to create worktree: {result.error}")
190
+
191
+ # Record in storage
192
+ worktree = self._worktree_storage.create(
193
+ project_id=config.project_id,
194
+ branch_name=branch_name,
195
+ worktree_path=worktree_path,
196
+ base_branch=config.base_branch,
197
+ task_id=config.task_id,
198
+ )
199
+
200
+ return IsolationContext(
201
+ cwd=worktree.worktree_path,
202
+ branch_name=worktree.branch_name,
203
+ worktree_id=worktree.id,
204
+ isolation_type="worktree",
205
+ extra={"main_repo_path": self._git_manager.repo_path},
206
+ )
207
+
208
+ def build_context_prompt(self, original_prompt: str, ctx: IsolationContext) -> str:
209
+ """
210
+ Build prompt with CRITICAL worktree context warning.
211
+
212
+ Prepends isolation context to help the agent understand it's
213
+ working in a worktree, not the main repository.
214
+ """
215
+ warning = f"""CRITICAL: Worktree Context
216
+ You are working in a git worktree, NOT the main repository.
217
+ - Branch: {ctx.branch_name}
218
+ - Worktree path: {ctx.cwd}
219
+ - Main repo: {ctx.extra.get("main_repo_path", "unknown")}
220
+
221
+ Changes in this worktree are isolated from the main repository.
222
+ Commit your changes to the worktree branch when done.
223
+
224
+ ---
225
+
226
+ """
227
+ return warning + original_prompt
228
+
229
+ def _generate_worktree_path(self, branch_name: str, project_name: str) -> str:
230
+ """Generate a unique worktree path in temp directory."""
231
+ import tempfile
232
+
233
+ # Sanitize branch name for use in path
234
+ safe_branch = branch_name.replace("/", "-").replace("\\", "-")
235
+ worktree_dir = tempfile.gettempdir()
236
+ return f"{worktree_dir}/gobby-worktrees/{project_name}/{safe_branch}"
237
+
238
+
239
+ class CloneIsolationHandler(IsolationHandler):
240
+ """
241
+ Clone isolation - create a shallow clone for full isolation.
242
+
243
+ This handler:
244
+ - Checks for existing clones by branch name
245
+ - Creates new shallow clones if needed
246
+ - Adds CRITICAL context warning to prompt
247
+ """
248
+
249
+ def __init__(
250
+ self,
251
+ clone_manager: Any, # CloneGitManager
252
+ clone_storage: Any, # LocalCloneManager
253
+ ) -> None:
254
+ """
255
+ Initialize CloneIsolationHandler with dependencies.
256
+
257
+ Args:
258
+ clone_manager: Git manager for clone operations
259
+ clone_storage: Storage for clone records
260
+ """
261
+ self._clone_manager = clone_manager
262
+ self._clone_storage = clone_storage
263
+
264
+ async def prepare_environment(self, config: SpawnConfig) -> IsolationContext:
265
+ """
266
+ Prepare clone environment.
267
+
268
+ - Generate branch name if not provided
269
+ - Check for existing clone for the branch
270
+ - Create new shallow clone if needed
271
+ - Return IsolationContext with clone info
272
+ """
273
+ branch_name = generate_branch_name(config)
274
+
275
+ # Check if clone already exists for this branch
276
+ existing = self._clone_storage.get_by_branch(config.project_id, branch_name)
277
+ if existing:
278
+ # Use existing clone
279
+ return IsolationContext(
280
+ cwd=existing.clone_path,
281
+ branch_name=existing.branch_name,
282
+ clone_id=existing.id,
283
+ isolation_type="clone",
284
+ extra={"source_repo": config.project_path},
285
+ )
286
+
287
+ # Generate clone path
288
+ from pathlib import Path
289
+
290
+ project_name = Path(config.project_path).name
291
+ clone_path = self._generate_clone_path(branch_name, project_name)
292
+
293
+ # Create shallow clone
294
+ result = self._clone_manager.create_clone(
295
+ clone_path=clone_path,
296
+ branch_name=branch_name,
297
+ base_branch=config.base_branch,
298
+ shallow=True,
299
+ )
300
+
301
+ if not result.success:
302
+ raise RuntimeError(f"Failed to create clone: {result.error}")
303
+
304
+ # Record in storage
305
+ clone = self._clone_storage.create(
306
+ project_id=config.project_id,
307
+ branch_name=branch_name,
308
+ clone_path=clone_path,
309
+ base_branch=config.base_branch,
310
+ task_id=config.task_id,
311
+ )
312
+
313
+ return IsolationContext(
314
+ cwd=clone.clone_path,
315
+ branch_name=clone.branch_name,
316
+ clone_id=clone.id,
317
+ isolation_type="clone",
318
+ extra={"source_repo": config.project_path},
319
+ )
320
+
321
+ def build_context_prompt(self, original_prompt: str, ctx: IsolationContext) -> str:
322
+ """
323
+ Build prompt with CRITICAL clone context warning.
324
+
325
+ Prepends isolation context to help the agent understand it's
326
+ working in a clone, not the original repository.
327
+ """
328
+ warning = f"""CRITICAL: Clone Context
329
+ You are working in a shallow clone, NOT the original repository.
330
+ - Branch: {ctx.branch_name}
331
+ - Clone path: {ctx.cwd}
332
+ - Source repo: {ctx.extra.get("source_repo", "unknown")}
333
+
334
+ Changes in this clone are fully isolated from the original repository.
335
+ Push your changes when ready to share with the original.
336
+
337
+ ---
338
+
339
+ """
340
+ return warning + original_prompt
341
+
342
+ def _generate_clone_path(self, branch_name: str, project_name: str) -> str:
343
+ """Generate a unique clone path in temp directory."""
344
+ import tempfile
345
+
346
+ # Sanitize branch name for use in path
347
+ safe_branch = branch_name.replace("/", "-").replace("\\", "-")
348
+ clone_dir = tempfile.gettempdir()
349
+ return f"{clone_dir}/gobby-clones/{project_name}/{safe_branch}"
350
+
351
+
352
+ def get_isolation_handler(
353
+ mode: Literal["current", "worktree", "clone"],
354
+ *,
355
+ git_manager: Any | None = None,
356
+ worktree_storage: Any | None = None,
357
+ clone_manager: Any | None = None,
358
+ clone_storage: Any | None = None,
359
+ ) -> IsolationHandler:
360
+ """
361
+ Factory function to get the appropriate isolation handler.
362
+
363
+ Args:
364
+ mode: Isolation mode - 'current', 'worktree', or 'clone'
365
+ git_manager: Git manager for worktree operations (required for 'worktree')
366
+ worktree_storage: Storage for worktree records (required for 'worktree')
367
+ clone_manager: Git manager for clone operations (required for 'clone')
368
+ clone_storage: Storage for clone records (required for 'clone')
369
+
370
+ Returns:
371
+ IsolationHandler instance for the specified mode
372
+
373
+ Raises:
374
+ ValueError: If mode is unknown or required dependencies are missing
375
+ """
376
+ if mode == "current":
377
+ return CurrentIsolationHandler()
378
+
379
+ if mode == "worktree":
380
+ if git_manager is None or worktree_storage is None:
381
+ raise ValueError("git_manager and worktree_storage are required for worktree isolation")
382
+ return WorktreeIsolationHandler(
383
+ git_manager=git_manager,
384
+ worktree_storage=worktree_storage,
385
+ )
386
+
387
+ if mode == "clone":
388
+ if clone_manager is None or clone_storage is None:
389
+ raise ValueError("clone_manager and clone_storage are required for clone isolation")
390
+ return CloneIsolationHandler(
391
+ clone_manager=clone_manager,
392
+ clone_storage=clone_storage,
393
+ )
394
+
395
+ raise ValueError(f"Unknown isolation mode: {mode}")