gobby 0.2.6__py3-none-any.whl → 0.2.8__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.
- gobby/__init__.py +1 -1
- gobby/adapters/__init__.py +2 -1
- gobby/adapters/claude_code.py +96 -35
- gobby/adapters/codex_impl/__init__.py +28 -0
- gobby/adapters/codex_impl/adapter.py +722 -0
- gobby/adapters/codex_impl/client.py +679 -0
- gobby/adapters/codex_impl/protocol.py +20 -0
- gobby/adapters/codex_impl/types.py +68 -0
- gobby/adapters/gemini.py +140 -38
- gobby/agents/definitions.py +11 -1
- gobby/agents/isolation.py +525 -0
- gobby/agents/registry.py +11 -0
- gobby/agents/sandbox.py +261 -0
- gobby/agents/session.py +1 -0
- gobby/agents/spawn.py +42 -287
- gobby/agents/spawn_executor.py +415 -0
- gobby/agents/spawners/__init__.py +24 -0
- gobby/agents/spawners/command_builder.py +189 -0
- gobby/agents/spawners/embedded.py +21 -2
- gobby/agents/spawners/headless.py +21 -2
- gobby/agents/spawners/macos.py +26 -1
- gobby/agents/spawners/prompt_manager.py +125 -0
- gobby/cli/__init__.py +0 -2
- gobby/cli/install.py +4 -4
- gobby/cli/installers/claude.py +6 -0
- gobby/cli/installers/gemini.py +6 -0
- gobby/cli/installers/shared.py +103 -4
- gobby/cli/memory.py +185 -0
- gobby/cli/sessions.py +1 -1
- gobby/cli/utils.py +9 -2
- gobby/clones/git.py +177 -0
- gobby/config/__init__.py +12 -97
- gobby/config/app.py +10 -94
- gobby/config/extensions.py +2 -2
- gobby/config/features.py +7 -130
- gobby/config/skills.py +31 -0
- gobby/config/tasks.py +4 -28
- gobby/hooks/__init__.py +0 -13
- gobby/hooks/event_handlers.py +150 -8
- gobby/hooks/hook_manager.py +21 -3
- gobby/hooks/plugins.py +1 -1
- gobby/hooks/webhooks.py +1 -1
- gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
- gobby/llm/resolver.py +3 -2
- gobby/mcp_proxy/importer.py +62 -4
- gobby/mcp_proxy/instructions.py +4 -2
- gobby/mcp_proxy/registries.py +22 -8
- gobby/mcp_proxy/services/recommendation.py +43 -11
- gobby/mcp_proxy/tools/agent_messaging.py +93 -44
- gobby/mcp_proxy/tools/agents.py +76 -740
- gobby/mcp_proxy/tools/artifacts.py +43 -9
- gobby/mcp_proxy/tools/clones.py +0 -385
- gobby/mcp_proxy/tools/memory.py +2 -2
- gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
- gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
- gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
- gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
- gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
- gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
- gobby/mcp_proxy/tools/skills/__init__.py +14 -29
- gobby/mcp_proxy/tools/spawn_agent.py +455 -0
- gobby/mcp_proxy/tools/tasks/_context.py +18 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
- gobby/mcp_proxy/tools/tasks/_session.py +22 -7
- gobby/mcp_proxy/tools/workflows.py +84 -34
- gobby/mcp_proxy/tools/worktrees.py +32 -350
- gobby/memory/extractor.py +15 -1
- gobby/memory/ingestion/__init__.py +5 -0
- gobby/memory/ingestion/multimodal.py +221 -0
- gobby/memory/manager.py +62 -283
- gobby/memory/search/__init__.py +10 -0
- gobby/memory/search/coordinator.py +248 -0
- gobby/memory/services/__init__.py +5 -0
- gobby/memory/services/crossref.py +142 -0
- gobby/prompts/loader.py +5 -2
- gobby/runner.py +13 -0
- gobby/servers/http.py +1 -4
- gobby/servers/routes/admin.py +14 -0
- gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
- gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
- gobby/servers/routes/mcp/endpoints/execution.py +568 -0
- gobby/servers/routes/mcp/endpoints/registry.py +378 -0
- gobby/servers/routes/mcp/endpoints/server.py +304 -0
- gobby/servers/routes/mcp/hooks.py +51 -4
- gobby/servers/routes/mcp/tools.py +48 -1506
- gobby/servers/websocket.py +57 -1
- gobby/sessions/analyzer.py +2 -2
- gobby/sessions/lifecycle.py +1 -1
- gobby/sessions/manager.py +9 -0
- gobby/sessions/processor.py +10 -0
- gobby/sessions/transcripts/base.py +1 -0
- gobby/sessions/transcripts/claude.py +15 -5
- gobby/sessions/transcripts/gemini.py +100 -34
- gobby/skills/parser.py +30 -2
- gobby/storage/database.py +9 -2
- gobby/storage/memories.py +32 -21
- gobby/storage/migrations.py +174 -368
- gobby/storage/sessions.py +45 -7
- gobby/storage/skills.py +80 -7
- gobby/storage/tasks/_lifecycle.py +18 -3
- gobby/sync/memories.py +1 -1
- gobby/tasks/external_validator.py +1 -1
- gobby/tasks/validation.py +22 -20
- gobby/tools/summarizer.py +91 -10
- gobby/utils/project_context.py +2 -3
- gobby/utils/status.py +13 -0
- gobby/workflows/actions.py +221 -1217
- gobby/workflows/artifact_actions.py +31 -0
- gobby/workflows/autonomous_actions.py +11 -0
- gobby/workflows/context_actions.py +50 -1
- gobby/workflows/detection_helpers.py +38 -24
- gobby/workflows/enforcement/__init__.py +47 -0
- gobby/workflows/enforcement/blocking.py +281 -0
- gobby/workflows/enforcement/commit_policy.py +283 -0
- gobby/workflows/enforcement/handlers.py +269 -0
- gobby/workflows/enforcement/task_policy.py +542 -0
- gobby/workflows/engine.py +93 -0
- gobby/workflows/evaluator.py +110 -0
- gobby/workflows/git_utils.py +106 -0
- gobby/workflows/hooks.py +41 -0
- gobby/workflows/llm_actions.py +30 -0
- gobby/workflows/mcp_actions.py +20 -1
- gobby/workflows/memory_actions.py +91 -0
- gobby/workflows/safe_evaluator.py +191 -0
- gobby/workflows/session_actions.py +44 -0
- gobby/workflows/state_actions.py +60 -1
- gobby/workflows/stop_signal_actions.py +55 -0
- gobby/workflows/summary_actions.py +217 -51
- gobby/workflows/task_sync_actions.py +347 -0
- gobby/workflows/todo_actions.py +34 -1
- gobby/workflows/webhook_actions.py +185 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
- gobby/adapters/codex.py +0 -1332
- gobby/cli/tui.py +0 -34
- gobby/install/claude/commands/gobby/bug.md +0 -51
- gobby/install/claude/commands/gobby/chore.md +0 -51
- gobby/install/claude/commands/gobby/epic.md +0 -52
- gobby/install/claude/commands/gobby/eval.md +0 -235
- gobby/install/claude/commands/gobby/feat.md +0 -49
- gobby/install/claude/commands/gobby/nit.md +0 -52
- gobby/install/claude/commands/gobby/ref.md +0 -52
- gobby/mcp_proxy/tools/session_messages.py +0 -1055
- gobby/prompts/defaults/expansion/system.md +0 -119
- gobby/prompts/defaults/expansion/user.md +0 -48
- gobby/prompts/defaults/external_validation/agent.md +0 -72
- gobby/prompts/defaults/external_validation/external.md +0 -63
- gobby/prompts/defaults/external_validation/spawn.md +0 -83
- gobby/prompts/defaults/external_validation/system.md +0 -6
- gobby/prompts/defaults/features/import_mcp.md +0 -22
- gobby/prompts/defaults/features/import_mcp_github.md +0 -17
- gobby/prompts/defaults/features/import_mcp_search.md +0 -16
- gobby/prompts/defaults/features/recommend_tools.md +0 -32
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
- gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
- gobby/prompts/defaults/features/server_description.md +0 -20
- gobby/prompts/defaults/features/server_description_system.md +0 -6
- gobby/prompts/defaults/features/task_description.md +0 -31
- gobby/prompts/defaults/features/task_description_system.md +0 -6
- gobby/prompts/defaults/features/tool_summary.md +0 -17
- gobby/prompts/defaults/features/tool_summary_system.md +0 -6
- gobby/prompts/defaults/handoff/compact.md +0 -63
- gobby/prompts/defaults/handoff/session_end.md +0 -57
- gobby/prompts/defaults/memory/extract.md +0 -61
- gobby/prompts/defaults/research/step.md +0 -58
- gobby/prompts/defaults/validation/criteria.md +0 -47
- gobby/prompts/defaults/validation/validate.md +0 -38
- gobby/storage/migrations_legacy.py +0 -1359
- gobby/tui/__init__.py +0 -5
- gobby/tui/api_client.py +0 -278
- gobby/tui/app.py +0 -329
- gobby/tui/screens/__init__.py +0 -25
- gobby/tui/screens/agents.py +0 -333
- gobby/tui/screens/chat.py +0 -450
- gobby/tui/screens/dashboard.py +0 -377
- gobby/tui/screens/memory.py +0 -305
- gobby/tui/screens/metrics.py +0 -231
- gobby/tui/screens/orchestrator.py +0 -903
- gobby/tui/screens/sessions.py +0 -412
- gobby/tui/screens/tasks.py +0 -440
- gobby/tui/screens/workflows.py +0 -289
- gobby/tui/screens/worktrees.py +0 -174
- gobby/tui/widgets/__init__.py +0 -21
- gobby/tui/widgets/chat.py +0 -210
- gobby/tui/widgets/conductor.py +0 -104
- gobby/tui/widgets/menu.py +0 -132
- gobby/tui/widgets/message_panel.py +0 -160
- gobby/tui/widgets/review_gate.py +0 -224
- gobby/tui/widgets/task_tree.py +0 -99
- gobby/tui/widgets/token_budget.py +0 -166
- gobby/tui/ws_client.py +0 -258
- gobby/workflows/task_enforcement_actions.py +0 -1343
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
- {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
gobby/cli/memory.py
CHANGED
|
@@ -282,6 +282,191 @@ def export_memories(
|
|
|
282
282
|
click.echo(markdown)
|
|
283
283
|
|
|
284
284
|
|
|
285
|
+
@memory.command("dedupe")
|
|
286
|
+
@click.option("--dry-run", is_flag=True, help="Show duplicates without deleting")
|
|
287
|
+
@click.pass_context
|
|
288
|
+
def dedupe_memories(ctx: click.Context, dry_run: bool) -> None:
|
|
289
|
+
"""Remove duplicate memories (same content, different IDs).
|
|
290
|
+
|
|
291
|
+
Identifies memories with identical content but different IDs (caused by
|
|
292
|
+
project_id variations) and removes duplicates, keeping the earliest one.
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
|
|
296
|
+
gobby memory dedupe --dry-run # Preview duplicates
|
|
297
|
+
|
|
298
|
+
gobby memory dedupe # Remove duplicates
|
|
299
|
+
"""
|
|
300
|
+
manager = get_memory_manager(ctx)
|
|
301
|
+
|
|
302
|
+
# Get all memories
|
|
303
|
+
memories = manager.list_memories(limit=10000)
|
|
304
|
+
|
|
305
|
+
if not memories:
|
|
306
|
+
click.echo("No memories found.")
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
# Group by normalized content
|
|
310
|
+
content_groups: dict[str, list[tuple[str, str, str | None]]] = {}
|
|
311
|
+
for m in memories:
|
|
312
|
+
normalized = m.content.strip()
|
|
313
|
+
if normalized not in content_groups:
|
|
314
|
+
content_groups[normalized] = []
|
|
315
|
+
content_groups[normalized].append((m.id, m.created_at, m.project_id))
|
|
316
|
+
|
|
317
|
+
# Find duplicates
|
|
318
|
+
duplicates_to_delete: list[str] = []
|
|
319
|
+
duplicate_count = 0
|
|
320
|
+
|
|
321
|
+
for content, entries in content_groups.items():
|
|
322
|
+
if len(entries) > 1:
|
|
323
|
+
duplicate_count += len(entries) - 1
|
|
324
|
+
# Sort by created_at to keep earliest
|
|
325
|
+
entries.sort(key=lambda x: x[1])
|
|
326
|
+
keeper = entries[0]
|
|
327
|
+
to_delete = entries[1:]
|
|
328
|
+
|
|
329
|
+
if dry_run:
|
|
330
|
+
click.echo(f"\nDuplicate content ({len(entries)} copies):")
|
|
331
|
+
click.echo(f" Content: {content[:80]}{'...' if len(content) > 80 else ''}")
|
|
332
|
+
click.echo(f" Keep: {keeper[0][:12]} (created: {keeper[1][:19]})")
|
|
333
|
+
for d in to_delete:
|
|
334
|
+
click.echo(f" Delete: {d[0][:12]} (created: {d[1][:19]}, project: {d[2]})")
|
|
335
|
+
else:
|
|
336
|
+
for d in to_delete:
|
|
337
|
+
duplicates_to_delete.append(d[0])
|
|
338
|
+
|
|
339
|
+
if dry_run:
|
|
340
|
+
click.echo(f"\nFound {duplicate_count} duplicate memories.")
|
|
341
|
+
click.echo("Run without --dry-run to delete them.")
|
|
342
|
+
else:
|
|
343
|
+
# Delete duplicates
|
|
344
|
+
deleted = 0
|
|
345
|
+
for memory_id in duplicates_to_delete:
|
|
346
|
+
if manager.forget(memory_id):
|
|
347
|
+
deleted += 1
|
|
348
|
+
|
|
349
|
+
click.echo(f"Deleted {deleted} duplicate memories.")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@memory.command("fix-null-project")
|
|
353
|
+
@click.option("--dry-run", is_flag=True, help="Show affected memories without updating")
|
|
354
|
+
@click.pass_context
|
|
355
|
+
def fix_null_project(ctx: click.Context, dry_run: bool) -> None:
|
|
356
|
+
"""Fix memories with NULL project_id from their source session.
|
|
357
|
+
|
|
358
|
+
Finds memories with source_type='session' and NULL project_id, then
|
|
359
|
+
looks up the source session to get the correct project_id.
|
|
360
|
+
|
|
361
|
+
Examples:
|
|
362
|
+
|
|
363
|
+
gobby memory fix-null-project --dry-run # Preview changes
|
|
364
|
+
|
|
365
|
+
gobby memory fix-null-project # Apply fixes
|
|
366
|
+
"""
|
|
367
|
+
from gobby.storage.sessions import LocalSessionManager
|
|
368
|
+
|
|
369
|
+
db = LocalDatabase()
|
|
370
|
+
session_mgr = LocalSessionManager(db)
|
|
371
|
+
|
|
372
|
+
# Find memories with NULL project_id and session source
|
|
373
|
+
rows = db.fetchall(
|
|
374
|
+
"""
|
|
375
|
+
SELECT id, content, source_session_id
|
|
376
|
+
FROM memories
|
|
377
|
+
WHERE project_id IS NULL AND source_type = 'session' AND source_session_id IS NOT NULL
|
|
378
|
+
""",
|
|
379
|
+
(),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if not rows:
|
|
383
|
+
click.echo("No memories with NULL project_id from sessions found.")
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
click.echo(f"Found {len(rows)} memories with NULL project_id from sessions.")
|
|
387
|
+
|
|
388
|
+
fixed = 0
|
|
389
|
+
for row in rows:
|
|
390
|
+
memory_id = row["id"]
|
|
391
|
+
session_id = row["source_session_id"]
|
|
392
|
+
content_preview = row["content"][:50] if row["content"] else ""
|
|
393
|
+
|
|
394
|
+
# Look up session to get project_id
|
|
395
|
+
session = session_mgr.get(session_id)
|
|
396
|
+
if session and session.project_id:
|
|
397
|
+
if dry_run:
|
|
398
|
+
click.echo(
|
|
399
|
+
f" Would fix {memory_id[:12]}: set project_id={session.project_id[:12]}"
|
|
400
|
+
)
|
|
401
|
+
click.echo(f" Content: {content_preview}...")
|
|
402
|
+
else:
|
|
403
|
+
# Update the memory's project_id
|
|
404
|
+
with db.transaction() as conn:
|
|
405
|
+
conn.execute(
|
|
406
|
+
"UPDATE memories SET project_id = ? WHERE id = ?",
|
|
407
|
+
(session.project_id, memory_id),
|
|
408
|
+
)
|
|
409
|
+
fixed += 1
|
|
410
|
+
else:
|
|
411
|
+
if dry_run:
|
|
412
|
+
click.echo(
|
|
413
|
+
f" Cannot fix {memory_id[:12]}: session {session_id} not found or has no project_id"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if dry_run:
|
|
417
|
+
click.echo(f"\nWould fix {fixed} memories. Run without --dry-run to apply.")
|
|
418
|
+
else:
|
|
419
|
+
click.echo(f"Fixed {fixed} memories with project_id from their source sessions.")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@memory.command("backup")
|
|
423
|
+
@click.option(
|
|
424
|
+
"--output",
|
|
425
|
+
"-o",
|
|
426
|
+
"output_path",
|
|
427
|
+
type=click.Path(),
|
|
428
|
+
help="Output file path (default: .gobby/memories.jsonl)",
|
|
429
|
+
)
|
|
430
|
+
@click.pass_context
|
|
431
|
+
def backup_memories(ctx: click.Context, output_path: str | None) -> None:
|
|
432
|
+
"""Backup memories to JSONL file.
|
|
433
|
+
|
|
434
|
+
Exports all memories to a JSONL file for backup/disaster recovery.
|
|
435
|
+
This runs synchronously and can be used even when the daemon is not running.
|
|
436
|
+
|
|
437
|
+
Examples:
|
|
438
|
+
|
|
439
|
+
gobby memory backup # Export to .gobby/memories.jsonl
|
|
440
|
+
|
|
441
|
+
gobby memory backup -o ~/backups/mem.jsonl # Export to custom path
|
|
442
|
+
"""
|
|
443
|
+
from pathlib import Path
|
|
444
|
+
|
|
445
|
+
from gobby.config.persistence import MemorySyncConfig
|
|
446
|
+
from gobby.sync.memories import MemoryBackupManager
|
|
447
|
+
|
|
448
|
+
manager = get_memory_manager(ctx)
|
|
449
|
+
|
|
450
|
+
# Create a backup manager with custom or default path
|
|
451
|
+
if output_path:
|
|
452
|
+
export_path = Path(output_path)
|
|
453
|
+
else:
|
|
454
|
+
export_path = Path(".gobby/memories.jsonl")
|
|
455
|
+
|
|
456
|
+
config = MemorySyncConfig(enabled=True, export_path=export_path)
|
|
457
|
+
backup_mgr = MemoryBackupManager(
|
|
458
|
+
db=manager.db,
|
|
459
|
+
memory_manager=manager,
|
|
460
|
+
config=config,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
count = backup_mgr.backup_sync()
|
|
464
|
+
if count > 0:
|
|
465
|
+
click.echo(f"Backed up {count} memories to {export_path}")
|
|
466
|
+
else:
|
|
467
|
+
click.echo("No memories to backup.")
|
|
468
|
+
|
|
469
|
+
|
|
285
470
|
def resolve_memory_id(manager: MemoryManager, memory_ref: str) -> str:
|
|
286
471
|
"""Resolve memory reference (UUID or prefix) to full ID."""
|
|
287
472
|
# Try exact match first
|
gobby/cli/sessions.py
CHANGED
|
@@ -387,7 +387,7 @@ def create_handoff(
|
|
|
387
387
|
import time
|
|
388
388
|
from pathlib import Path
|
|
389
389
|
|
|
390
|
-
from gobby.mcp_proxy.tools.
|
|
390
|
+
from gobby.mcp_proxy.tools.sessions._handoff import _format_handoff_markdown
|
|
391
391
|
from gobby.sessions.analyzer import TranscriptAnalyzer
|
|
392
392
|
|
|
393
393
|
manager = get_session_manager()
|
gobby/cli/utils.py
CHANGED
|
@@ -118,7 +118,7 @@ def get_active_session_id(db: LocalDatabase | None = None) -> str | None:
|
|
|
118
118
|
db.close()
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
def resolve_session_id(session_ref: str | None) -> str:
|
|
121
|
+
def resolve_session_id(session_ref: str | None, project_id: str | None = None) -> str:
|
|
122
122
|
"""
|
|
123
123
|
Resolve session reference to UUID.
|
|
124
124
|
|
|
@@ -126,6 +126,8 @@ def resolve_session_id(session_ref: str | None) -> str:
|
|
|
126
126
|
|
|
127
127
|
Args:
|
|
128
128
|
session_ref: User input string (UUID, #N, N, prefix) or None
|
|
129
|
+
project_id: Project ID for project-scoped #N lookup.
|
|
130
|
+
If not provided, auto-detected from current project context.
|
|
129
131
|
|
|
130
132
|
Returns:
|
|
131
133
|
Resolved UUID string
|
|
@@ -142,10 +144,15 @@ def resolve_session_id(session_ref: str | None) -> str:
|
|
|
142
144
|
raise click.ClickException("No active session found. Specify --session.")
|
|
143
145
|
return active_id
|
|
144
146
|
|
|
147
|
+
# Get project_id from context if not provided
|
|
148
|
+
if not project_id:
|
|
149
|
+
ctx = get_project_context()
|
|
150
|
+
project_id = ctx.get("id") if ctx else None
|
|
151
|
+
|
|
145
152
|
# Use SessionManager for resolution logic
|
|
146
153
|
manager = LocalSessionManager(db)
|
|
147
154
|
try:
|
|
148
|
-
return manager.resolve_session_reference(session_ref)
|
|
155
|
+
return manager.resolve_session_reference(session_ref, project_id)
|
|
149
156
|
except ValueError as e:
|
|
150
157
|
raise click.ClickException(str(e)) from None
|
|
151
158
|
finally:
|
gobby/clones/git.py
CHANGED
|
@@ -231,6 +231,89 @@ class CloneGitManager:
|
|
|
231
231
|
error=str(e),
|
|
232
232
|
)
|
|
233
233
|
|
|
234
|
+
def full_clone(
|
|
235
|
+
self,
|
|
236
|
+
remote_url: str,
|
|
237
|
+
clone_path: str | Path,
|
|
238
|
+
branch: str = "main",
|
|
239
|
+
) -> GitOperationResult:
|
|
240
|
+
"""
|
|
241
|
+
Create a full (non-shallow) clone of a repository.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
remote_url: URL of the remote repository (HTTPS or SSH)
|
|
245
|
+
clone_path: Path where clone will be created
|
|
246
|
+
branch: Branch to clone
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
GitOperationResult with success status and message
|
|
250
|
+
"""
|
|
251
|
+
clone_path = Path(clone_path)
|
|
252
|
+
|
|
253
|
+
# Check if path already exists
|
|
254
|
+
if clone_path.exists():
|
|
255
|
+
return GitOperationResult(
|
|
256
|
+
success=False,
|
|
257
|
+
message=f"Path already exists: {clone_path}",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Ensure parent directory exists
|
|
261
|
+
clone_path.parent.mkdir(parents=True, exist_ok=True)
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Build clone command without --depth (full clone)
|
|
265
|
+
cmd = [
|
|
266
|
+
"git",
|
|
267
|
+
"clone",
|
|
268
|
+
"-b",
|
|
269
|
+
branch,
|
|
270
|
+
remote_url,
|
|
271
|
+
str(clone_path),
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
# Sanitize URL in command before logging to avoid exposing credentials
|
|
275
|
+
safe_cmd = cmd.copy()
|
|
276
|
+
safe_cmd[safe_cmd.index(remote_url)] = _sanitize_url(remote_url)
|
|
277
|
+
logger.debug(f"Running: {' '.join(safe_cmd)}")
|
|
278
|
+
|
|
279
|
+
result = subprocess.run( # nosec B603 B607 - cmd built from hardcoded git arguments
|
|
280
|
+
cmd,
|
|
281
|
+
capture_output=True,
|
|
282
|
+
text=True,
|
|
283
|
+
timeout=600, # 10 minutes for full clone
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if result.returncode == 0:
|
|
287
|
+
return GitOperationResult(
|
|
288
|
+
success=True,
|
|
289
|
+
message=f"Successfully cloned to {clone_path}",
|
|
290
|
+
output=result.stdout,
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
return GitOperationResult(
|
|
294
|
+
success=False,
|
|
295
|
+
message=f"Clone failed: {result.stderr}",
|
|
296
|
+
error=result.stderr,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
except subprocess.TimeoutExpired:
|
|
300
|
+
# Clean up partial clone
|
|
301
|
+
if clone_path.exists():
|
|
302
|
+
shutil.rmtree(clone_path, ignore_errors=True)
|
|
303
|
+
return GitOperationResult(
|
|
304
|
+
success=False,
|
|
305
|
+
message="Git clone timed out",
|
|
306
|
+
)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
# Clean up partial clone
|
|
309
|
+
if clone_path.exists():
|
|
310
|
+
shutil.rmtree(clone_path, ignore_errors=True)
|
|
311
|
+
return GitOperationResult(
|
|
312
|
+
success=False,
|
|
313
|
+
message=f"Error cloning repository: {e}",
|
|
314
|
+
error=str(e),
|
|
315
|
+
)
|
|
316
|
+
|
|
234
317
|
def sync_clone(
|
|
235
318
|
self,
|
|
236
319
|
clone_path: str | Path,
|
|
@@ -422,6 +505,100 @@ class CloneGitManager:
|
|
|
422
505
|
logger.error(f"Error getting clone status: {e}")
|
|
423
506
|
return None
|
|
424
507
|
|
|
508
|
+
def create_clone(
|
|
509
|
+
self,
|
|
510
|
+
clone_path: str | Path,
|
|
511
|
+
branch_name: str,
|
|
512
|
+
base_branch: str = "main",
|
|
513
|
+
shallow: bool = True,
|
|
514
|
+
) -> GitOperationResult:
|
|
515
|
+
"""
|
|
516
|
+
Create a clone for isolated work.
|
|
517
|
+
|
|
518
|
+
This is the high-level API used by CloneIsolationHandler.
|
|
519
|
+
It gets the remote URL from the current repository and creates
|
|
520
|
+
either a shallow or full clone at the specified path.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
clone_path: Path where clone will be created
|
|
524
|
+
branch_name: Branch to create/checkout in the clone
|
|
525
|
+
base_branch: Base branch to clone from (default: main)
|
|
526
|
+
shallow: Whether to create a shallow clone (default: True)
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
GitOperationResult with success status and message
|
|
530
|
+
"""
|
|
531
|
+
# Get remote URL from current repo
|
|
532
|
+
remote_url = self.get_remote_url()
|
|
533
|
+
if not remote_url:
|
|
534
|
+
return GitOperationResult(
|
|
535
|
+
success=False,
|
|
536
|
+
message="Could not get remote URL from repository",
|
|
537
|
+
error="no_remote_url",
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# Create clone (shallow or full based on parameter)
|
|
541
|
+
if shallow:
|
|
542
|
+
result = self.shallow_clone(
|
|
543
|
+
remote_url=remote_url,
|
|
544
|
+
clone_path=clone_path,
|
|
545
|
+
branch=base_branch,
|
|
546
|
+
depth=1,
|
|
547
|
+
)
|
|
548
|
+
else:
|
|
549
|
+
result = self.full_clone(
|
|
550
|
+
remote_url=remote_url,
|
|
551
|
+
clone_path=clone_path,
|
|
552
|
+
branch=base_branch,
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
if not result.success:
|
|
556
|
+
return result
|
|
557
|
+
|
|
558
|
+
# If branch_name differs from base_branch, create and checkout the new branch
|
|
559
|
+
if branch_name != base_branch:
|
|
560
|
+
try:
|
|
561
|
+
# Create new branch from base
|
|
562
|
+
create_result = self._run_git(
|
|
563
|
+
["checkout", "-b", branch_name],
|
|
564
|
+
cwd=clone_path,
|
|
565
|
+
timeout=30,
|
|
566
|
+
)
|
|
567
|
+
if create_result.returncode != 0:
|
|
568
|
+
# Clean up the clone on branch creation failure
|
|
569
|
+
try:
|
|
570
|
+
if Path(clone_path).exists():
|
|
571
|
+
shutil.rmtree(clone_path)
|
|
572
|
+
except Exception as cleanup_err:
|
|
573
|
+
logger.warning(
|
|
574
|
+
f"Failed to clean up clone after branch creation failure: {cleanup_err}"
|
|
575
|
+
)
|
|
576
|
+
return GitOperationResult(
|
|
577
|
+
success=False,
|
|
578
|
+
message=f"Failed to create branch {branch_name}: {create_result.stderr}",
|
|
579
|
+
error=create_result.stderr,
|
|
580
|
+
)
|
|
581
|
+
except Exception as e:
|
|
582
|
+
# Clean up the clone on exception
|
|
583
|
+
try:
|
|
584
|
+
if Path(clone_path).exists():
|
|
585
|
+
shutil.rmtree(clone_path)
|
|
586
|
+
except Exception as cleanup_err:
|
|
587
|
+
logger.warning(
|
|
588
|
+
f"Failed to clean up clone after branch creation error: {cleanup_err}"
|
|
589
|
+
)
|
|
590
|
+
return GitOperationResult(
|
|
591
|
+
success=False,
|
|
592
|
+
message=f"Error creating branch: {e}",
|
|
593
|
+
error=str(e),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
return GitOperationResult(
|
|
597
|
+
success=True,
|
|
598
|
+
message=f"Successfully created clone at {clone_path} on branch {branch_name}",
|
|
599
|
+
output=result.output,
|
|
600
|
+
)
|
|
601
|
+
|
|
425
602
|
def merge_branch(
|
|
426
603
|
self,
|
|
427
604
|
source_branch: str,
|
gobby/config/__init__.py
CHANGED
|
@@ -14,116 +14,31 @@ Module structure:
|
|
|
14
14
|
- extensions.py: Hook extension configs (webhooks, plugins)
|
|
15
15
|
- sessions.py: Session lifecycle and tracking configs
|
|
16
16
|
- features.py: MCP proxy feature configs (code execution, tool recommendation)
|
|
17
|
+
|
|
18
|
+
Import from submodules directly for specific configs:
|
|
19
|
+
from gobby.config.tasks import TaskValidationConfig
|
|
20
|
+
from gobby.config.extensions import WebhooksConfig
|
|
21
|
+
|
|
22
|
+
Import from this package for app-level items:
|
|
23
|
+
from gobby.config import DaemonConfig, load_config
|
|
17
24
|
"""
|
|
18
25
|
|
|
19
26
|
# Core configuration and utilities from app.py
|
|
20
27
|
from gobby.config.app import (
|
|
21
28
|
DaemonConfig,
|
|
22
29
|
expand_env_vars,
|
|
30
|
+
generate_default_config,
|
|
23
31
|
load_config,
|
|
32
|
+
load_yaml,
|
|
24
33
|
save_config,
|
|
25
34
|
)
|
|
26
35
|
|
|
27
|
-
# Extension configs
|
|
28
|
-
from gobby.config.extensions import (
|
|
29
|
-
HookExtensionsConfig,
|
|
30
|
-
PluginItemConfig,
|
|
31
|
-
PluginsConfig,
|
|
32
|
-
WebhookEndpointConfig,
|
|
33
|
-
WebhooksConfig,
|
|
34
|
-
WebSocketBroadcastConfig,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# Feature configs
|
|
38
|
-
from gobby.config.features import (
|
|
39
|
-
ImportMCPServerConfig,
|
|
40
|
-
MetricsConfig,
|
|
41
|
-
ProjectVerificationConfig,
|
|
42
|
-
RecommendToolsConfig,
|
|
43
|
-
ToolSummarizerConfig,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# LLM provider configs
|
|
47
|
-
from gobby.config.llm_providers import (
|
|
48
|
-
LLMProviderConfig,
|
|
49
|
-
LLMProvidersConfig,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
# Logging configs
|
|
53
|
-
from gobby.config.logging import LoggingSettings
|
|
54
|
-
|
|
55
|
-
# Persistence configs
|
|
56
|
-
from gobby.config.persistence import (
|
|
57
|
-
MemoryConfig,
|
|
58
|
-
MemorySyncConfig,
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Server configs
|
|
62
|
-
from gobby.config.servers import (
|
|
63
|
-
MCPClientProxyConfig,
|
|
64
|
-
WebSocketSettings,
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# Session configs
|
|
68
|
-
from gobby.config.sessions import (
|
|
69
|
-
ContextInjectionConfig,
|
|
70
|
-
MessageTrackingConfig,
|
|
71
|
-
SessionLifecycleConfig,
|
|
72
|
-
SessionSummaryConfig,
|
|
73
|
-
TitleSynthesisConfig,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Task configs
|
|
77
|
-
from gobby.config.tasks import (
|
|
78
|
-
CompactHandoffConfig,
|
|
79
|
-
GobbyTasksConfig,
|
|
80
|
-
PatternCriteriaConfig,
|
|
81
|
-
TaskExpansionConfig,
|
|
82
|
-
TaskValidationConfig,
|
|
83
|
-
WorkflowConfig,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
36
|
__all__ = [
|
|
87
|
-
# Core
|
|
37
|
+
# Core app-level exports only
|
|
88
38
|
"DaemonConfig",
|
|
89
39
|
"expand_env_vars",
|
|
40
|
+
"generate_default_config",
|
|
90
41
|
"load_config",
|
|
42
|
+
"load_yaml",
|
|
91
43
|
"save_config",
|
|
92
|
-
# Extension configs
|
|
93
|
-
"HookExtensionsConfig",
|
|
94
|
-
"PluginItemConfig",
|
|
95
|
-
"PluginsConfig",
|
|
96
|
-
"WebhookEndpointConfig",
|
|
97
|
-
"WebhooksConfig",
|
|
98
|
-
"WebSocketBroadcastConfig",
|
|
99
|
-
# Feature configs
|
|
100
|
-
"ImportMCPServerConfig",
|
|
101
|
-
"MetricsConfig",
|
|
102
|
-
"ProjectVerificationConfig",
|
|
103
|
-
"RecommendToolsConfig",
|
|
104
|
-
"ToolSummarizerConfig",
|
|
105
|
-
# LLM provider configs
|
|
106
|
-
"LLMProviderConfig",
|
|
107
|
-
"LLMProvidersConfig",
|
|
108
|
-
# Logging configs
|
|
109
|
-
"LoggingSettings",
|
|
110
|
-
# Persistence configs
|
|
111
|
-
"MemoryConfig",
|
|
112
|
-
"MemorySyncConfig",
|
|
113
|
-
# Server configs
|
|
114
|
-
"MCPClientProxyConfig",
|
|
115
|
-
"WebSocketSettings",
|
|
116
|
-
# Session configs
|
|
117
|
-
"ContextInjectionConfig",
|
|
118
|
-
"MessageTrackingConfig",
|
|
119
|
-
"SessionLifecycleConfig",
|
|
120
|
-
"SessionSummaryConfig",
|
|
121
|
-
"TitleSynthesisConfig",
|
|
122
|
-
# Task configs
|
|
123
|
-
"CompactHandoffConfig",
|
|
124
|
-
"GobbyTasksConfig",
|
|
125
|
-
"PatternCriteriaConfig",
|
|
126
|
-
"TaskExpansionConfig",
|
|
127
|
-
"TaskValidationConfig",
|
|
128
|
-
"WorkflowConfig",
|
|
129
44
|
]
|
gobby/config/app.py
CHANGED
|
@@ -15,14 +15,8 @@ from typing import Any
|
|
|
15
15
|
import yaml
|
|
16
16
|
from pydantic import BaseModel, Field, field_validator
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
PluginItemConfig,
|
|
21
|
-
PluginsConfig,
|
|
22
|
-
WebhookEndpointConfig,
|
|
23
|
-
WebhooksConfig,
|
|
24
|
-
WebSocketBroadcastConfig,
|
|
25
|
-
)
|
|
18
|
+
# Internal imports for DaemonConfig fields - NOT re-exported
|
|
19
|
+
from gobby.config.extensions import HookExtensionsConfig
|
|
26
20
|
from gobby.config.features import (
|
|
27
21
|
ImportMCPServerConfig,
|
|
28
22
|
MetricsConfig,
|
|
@@ -31,14 +25,9 @@ from gobby.config.features import (
|
|
|
31
25
|
TaskDescriptionConfig,
|
|
32
26
|
ToolSummarizerConfig,
|
|
33
27
|
)
|
|
34
|
-
|
|
35
|
-
# Re-export from extracted modules (Strangler Fig pattern for backwards compatibility)
|
|
36
|
-
from gobby.config.llm_providers import LLMProviderConfig, LLMProvidersConfig
|
|
28
|
+
from gobby.config.llm_providers import LLMProvidersConfig
|
|
37
29
|
from gobby.config.logging import LoggingSettings
|
|
38
|
-
from gobby.config.persistence import
|
|
39
|
-
MemoryConfig,
|
|
40
|
-
MemorySyncConfig,
|
|
41
|
-
)
|
|
30
|
+
from gobby.config.persistence import MemoryConfig, MemorySyncConfig
|
|
42
31
|
from gobby.config.search import SearchConfig
|
|
43
32
|
from gobby.config.servers import MCPClientProxyConfig, WebSocketSettings
|
|
44
33
|
from gobby.config.sessions import (
|
|
@@ -50,61 +39,10 @@ from gobby.config.sessions import (
|
|
|
50
39
|
TitleSynthesisConfig,
|
|
51
40
|
)
|
|
52
41
|
from gobby.config.skills import SkillsConfig
|
|
53
|
-
from gobby.config.tasks import
|
|
54
|
-
CompactHandoffConfig,
|
|
55
|
-
GobbyTasksConfig,
|
|
56
|
-
PatternCriteriaConfig,
|
|
57
|
-
TaskExpansionConfig,
|
|
58
|
-
TaskValidationConfig,
|
|
59
|
-
WorkflowConfig,
|
|
60
|
-
)
|
|
42
|
+
from gobby.config.tasks import CompactHandoffConfig, GobbyTasksConfig, WorkflowConfig
|
|
61
43
|
|
|
62
|
-
# Explicit exports for mypy (re-exported symbols from submodules)
|
|
63
44
|
__all__ = [
|
|
64
|
-
#
|
|
65
|
-
"HookExtensionsConfig",
|
|
66
|
-
"PluginItemConfig",
|
|
67
|
-
"PluginsConfig",
|
|
68
|
-
"WebhookEndpointConfig",
|
|
69
|
-
"WebhooksConfig",
|
|
70
|
-
"WebSocketBroadcastConfig",
|
|
71
|
-
# From gobby.config.features
|
|
72
|
-
"ImportMCPServerConfig",
|
|
73
|
-
"MetricsConfig",
|
|
74
|
-
"ProjectVerificationConfig",
|
|
75
|
-
"RecommendToolsConfig",
|
|
76
|
-
"TaskDescriptionConfig",
|
|
77
|
-
"ToolSummarizerConfig",
|
|
78
|
-
# From gobby.config.llm_providers
|
|
79
|
-
"LLMProviderConfig",
|
|
80
|
-
"LLMProvidersConfig",
|
|
81
|
-
# From gobby.config.logging
|
|
82
|
-
"LoggingSettings",
|
|
83
|
-
# From gobby.config.persistence
|
|
84
|
-
"MemoryConfig",
|
|
85
|
-
"MemorySyncConfig",
|
|
86
|
-
# From gobby.config.search
|
|
87
|
-
"SearchConfig",
|
|
88
|
-
# From gobby.config.servers
|
|
89
|
-
"MCPClientProxyConfig",
|
|
90
|
-
"WebSocketSettings",
|
|
91
|
-
# From gobby.config.skills
|
|
92
|
-
"SkillsConfig",
|
|
93
|
-
# From gobby.config.sessions
|
|
94
|
-
"ArtifactHandoffConfig",
|
|
95
|
-
"ContextInjectionConfig",
|
|
96
|
-
"MessageTrackingConfig",
|
|
97
|
-
"SessionLifecycleConfig",
|
|
98
|
-
"SessionSummaryConfig",
|
|
99
|
-
"TitleSynthesisConfig",
|
|
100
|
-
# From gobby.config.tasks
|
|
101
|
-
"CompactHandoffConfig",
|
|
102
|
-
"GobbyTasksConfig",
|
|
103
|
-
"PatternCriteriaConfig",
|
|
104
|
-
"TaskExpansionConfig",
|
|
105
|
-
"TaskValidationConfig",
|
|
106
|
-
"WorkflowConfig",
|
|
107
|
-
# Local definitions
|
|
45
|
+
# Local definitions only - no re-exports
|
|
108
46
|
"ConductorConfig",
|
|
109
47
|
"DaemonConfig",
|
|
110
48
|
"expand_env_vars",
|
|
@@ -184,32 +122,6 @@ def expand_env_vars(content: str) -> str:
|
|
|
184
122
|
return ENV_VAR_PATTERN.sub(replace_match, content)
|
|
185
123
|
|
|
186
124
|
|
|
187
|
-
# WebSocketSettings moved to gobby.config.servers (re-exported above)
|
|
188
|
-
# LoggingSettings moved to gobby.config.logging (re-exported above)
|
|
189
|
-
# CompactHandoffConfig moved to gobby.config.tasks (re-exported above)
|
|
190
|
-
|
|
191
|
-
# ContextInjectionConfig, SessionSummaryConfig, TitleSynthesisConfig,
|
|
192
|
-
# MessageTrackingConfig, SessionLifecycleConfig
|
|
193
|
-
# moved to gobby.config.sessions (re-exported above)
|
|
194
|
-
|
|
195
|
-
# ToolSummarizerConfig, RecommendToolsConfig, ImportMCPServerConfig,
|
|
196
|
-
# MetricsConfig, ProjectVerificationConfig
|
|
197
|
-
# moved to gobby.config.features (re-exported above)
|
|
198
|
-
|
|
199
|
-
# WebSocketBroadcastConfig, WebhookEndpointConfig, WebhooksConfig,
|
|
200
|
-
# PluginItemConfig, PluginsConfig, HookExtensionsConfig
|
|
201
|
-
# moved to gobby.config.extensions (re-exported above)
|
|
202
|
-
|
|
203
|
-
# PatternCriteriaConfig, TaskExpansionConfig, TaskValidationConfig, WorkflowConfig,
|
|
204
|
-
# GobbyTasksConfig, CompactHandoffConfig
|
|
205
|
-
# moved to gobby.config.tasks (re-exported above)
|
|
206
|
-
|
|
207
|
-
# MCPClientProxyConfig moved to gobby.config.servers (re-exported above)
|
|
208
|
-
# LLMProviderConfig and LLMProvidersConfig moved to gobby.config.llm_providers (re-exported above)
|
|
209
|
-
# MemoryConfig, MemorySyncConfig
|
|
210
|
-
# moved to gobby.config.persistence (re-exported above)
|
|
211
|
-
|
|
212
|
-
|
|
213
125
|
class DaemonConfig(BaseModel):
|
|
214
126
|
"""
|
|
215
127
|
Main configuration for Gobby daemon.
|
|
@@ -243,6 +155,10 @@ class DaemonConfig(BaseModel):
|
|
|
243
155
|
default="~/.gobby/gobby-hub.db",
|
|
244
156
|
description="Path to hub database for cross-project queries.",
|
|
245
157
|
)
|
|
158
|
+
use_flattened_baseline: bool = Field(
|
|
159
|
+
default=True,
|
|
160
|
+
description="Use flattened V2 baseline schema (v75) for new databases instead of legacy migrations.",
|
|
161
|
+
)
|
|
246
162
|
|
|
247
163
|
# Sub-configs
|
|
248
164
|
websocket: WebSocketSettings = Field(
|