gobby 0.2.5__py3-none-any.whl → 0.2.6__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 (148) hide show
  1. gobby/adapters/claude_code.py +13 -4
  2. gobby/adapters/codex.py +43 -3
  3. gobby/agents/runner.py +8 -0
  4. gobby/cli/__init__.py +6 -0
  5. gobby/cli/clones.py +419 -0
  6. gobby/cli/conductor.py +266 -0
  7. gobby/cli/installers/antigravity.py +3 -9
  8. gobby/cli/installers/claude.py +9 -9
  9. gobby/cli/installers/codex.py +2 -8
  10. gobby/cli/installers/gemini.py +2 -8
  11. gobby/cli/installers/shared.py +71 -8
  12. gobby/cli/skills.py +858 -0
  13. gobby/cli/tasks/ai.py +0 -440
  14. gobby/cli/tasks/crud.py +44 -6
  15. gobby/cli/tasks/main.py +0 -4
  16. gobby/cli/tui.py +2 -2
  17. gobby/cli/utils.py +3 -3
  18. gobby/clones/__init__.py +13 -0
  19. gobby/clones/git.py +547 -0
  20. gobby/conductor/__init__.py +16 -0
  21. gobby/conductor/alerts.py +135 -0
  22. gobby/conductor/loop.py +164 -0
  23. gobby/conductor/monitors/__init__.py +11 -0
  24. gobby/conductor/monitors/agents.py +116 -0
  25. gobby/conductor/monitors/tasks.py +155 -0
  26. gobby/conductor/pricing.py +234 -0
  27. gobby/conductor/token_tracker.py +160 -0
  28. gobby/config/app.py +63 -1
  29. gobby/config/search.py +110 -0
  30. gobby/config/servers.py +1 -1
  31. gobby/config/skills.py +43 -0
  32. gobby/config/tasks.py +6 -14
  33. gobby/hooks/event_handlers.py +145 -2
  34. gobby/hooks/hook_manager.py +48 -2
  35. gobby/hooks/skill_manager.py +130 -0
  36. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  37. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  38. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  39. gobby/llm/claude.py +22 -34
  40. gobby/llm/claude_executor.py +46 -256
  41. gobby/llm/codex_executor.py +59 -291
  42. gobby/llm/executor.py +21 -0
  43. gobby/llm/gemini.py +134 -110
  44. gobby/llm/litellm_executor.py +143 -6
  45. gobby/llm/resolver.py +95 -33
  46. gobby/mcp_proxy/instructions.py +54 -0
  47. gobby/mcp_proxy/models.py +15 -0
  48. gobby/mcp_proxy/registries.py +68 -5
  49. gobby/mcp_proxy/server.py +33 -3
  50. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  51. gobby/mcp_proxy/stdio.py +2 -1
  52. gobby/mcp_proxy/tools/__init__.py +0 -2
  53. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  54. gobby/mcp_proxy/tools/clones.py +903 -0
  55. gobby/mcp_proxy/tools/memory.py +1 -24
  56. gobby/mcp_proxy/tools/metrics.py +65 -1
  57. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  58. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  59. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  60. gobby/mcp_proxy/tools/session_messages.py +1 -2
  61. gobby/mcp_proxy/tools/skills/__init__.py +631 -0
  62. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  63. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  64. gobby/mcp_proxy/tools/task_sync.py +1 -1
  65. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  66. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  67. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  68. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  69. gobby/mcp_proxy/tools/tasks/_lifecycle.py +60 -29
  70. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  71. gobby/mcp_proxy/tools/workflows.py +1 -1
  72. gobby/mcp_proxy/tools/worktrees.py +5 -0
  73. gobby/memory/backends/__init__.py +6 -1
  74. gobby/memory/backends/mem0.py +6 -1
  75. gobby/memory/extractor.py +477 -0
  76. gobby/memory/manager.py +11 -2
  77. gobby/prompts/defaults/handoff/compact.md +63 -0
  78. gobby/prompts/defaults/handoff/session_end.md +57 -0
  79. gobby/prompts/defaults/memory/extract.md +61 -0
  80. gobby/runner.py +37 -16
  81. gobby/search/__init__.py +48 -6
  82. gobby/search/backends/__init__.py +159 -0
  83. gobby/search/backends/embedding.py +225 -0
  84. gobby/search/embeddings.py +238 -0
  85. gobby/search/models.py +148 -0
  86. gobby/search/unified.py +496 -0
  87. gobby/servers/http.py +23 -8
  88. gobby/servers/routes/admin.py +280 -0
  89. gobby/servers/routes/mcp/tools.py +241 -52
  90. gobby/servers/websocket.py +2 -2
  91. gobby/sessions/analyzer.py +2 -0
  92. gobby/sessions/transcripts/base.py +1 -0
  93. gobby/sessions/transcripts/claude.py +64 -5
  94. gobby/skills/__init__.py +91 -0
  95. gobby/skills/loader.py +685 -0
  96. gobby/skills/manager.py +384 -0
  97. gobby/skills/parser.py +258 -0
  98. gobby/skills/search.py +463 -0
  99. gobby/skills/sync.py +119 -0
  100. gobby/skills/updater.py +385 -0
  101. gobby/skills/validator.py +368 -0
  102. gobby/storage/clones.py +378 -0
  103. gobby/storage/database.py +1 -1
  104. gobby/storage/memories.py +43 -13
  105. gobby/storage/migrations.py +180 -6
  106. gobby/storage/sessions.py +73 -0
  107. gobby/storage/skills.py +749 -0
  108. gobby/storage/tasks/_crud.py +4 -4
  109. gobby/storage/tasks/_lifecycle.py +41 -6
  110. gobby/storage/tasks/_manager.py +14 -5
  111. gobby/storage/tasks/_models.py +8 -3
  112. gobby/sync/memories.py +39 -4
  113. gobby/sync/tasks.py +83 -6
  114. gobby/tasks/__init__.py +1 -2
  115. gobby/tasks/validation.py +24 -15
  116. gobby/tui/api_client.py +4 -7
  117. gobby/tui/app.py +5 -3
  118. gobby/tui/screens/orchestrator.py +1 -2
  119. gobby/tui/screens/tasks.py +2 -4
  120. gobby/tui/ws_client.py +1 -1
  121. gobby/utils/daemon_client.py +2 -2
  122. gobby/workflows/actions.py +84 -2
  123. gobby/workflows/context_actions.py +43 -0
  124. gobby/workflows/detection_helpers.py +115 -31
  125. gobby/workflows/engine.py +13 -2
  126. gobby/workflows/lifecycle_evaluator.py +29 -1
  127. gobby/workflows/loader.py +19 -6
  128. gobby/workflows/memory_actions.py +74 -0
  129. gobby/workflows/summary_actions.py +17 -0
  130. gobby/workflows/task_enforcement_actions.py +448 -6
  131. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/METADATA +82 -21
  132. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/RECORD +136 -107
  133. gobby/install/codex/prompts/forget.md +0 -7
  134. gobby/install/codex/prompts/memories.md +0 -7
  135. gobby/install/codex/prompts/recall.md +0 -7
  136. gobby/install/codex/prompts/remember.md +0 -13
  137. gobby/llm/gemini_executor.py +0 -339
  138. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  139. gobby/tasks/context.py +0 -747
  140. gobby/tasks/criteria.py +0 -342
  141. gobby/tasks/expansion.py +0 -626
  142. gobby/tasks/prompts/expand.py +0 -327
  143. gobby/tasks/research.py +0 -421
  144. gobby/tasks/tdd.py +0 -352
  145. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/WHEEL +0 -0
  146. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/entry_points.txt +0 -0
  147. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/licenses/LICENSE.md +0 -0
  148. {gobby-0.2.5.dist-info → gobby-0.2.6.dist-info}/top_level.txt +0 -0
@@ -123,7 +123,11 @@ class ClaudeTranscriptParser:
123
123
 
124
124
  # If no /clear found, just take the last max_turns
125
125
  if most_recent_clear_idx is None:
126
- return turns[-max_turns:] if len(turns) > max_turns else turns
126
+ result = turns[-max_turns:] if len(turns) > max_turns else turns
127
+ result, removed = self._validate_tool_pairing(result)
128
+ if removed:
129
+ self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
130
+ return result
127
131
 
128
132
  # Start after this /clear (which is the last in any cluster since we scanned backwards)
129
133
  start_idx = most_recent_clear_idx + 1
@@ -159,7 +163,11 @@ class ClaudeTranscriptParser:
159
163
  start_idx = max(start_idx, boundary_idx + 1)
160
164
  break
161
165
 
162
- return turns[start_idx:end_idx]
166
+ result = turns[start_idx:end_idx]
167
+ result, removed = self._validate_tool_pairing(result)
168
+ if removed:
169
+ self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
170
+ return result
163
171
 
164
172
  # Segment is > max_turns, so we need to limit it
165
173
  # Take the last max_turns from the segment
@@ -184,7 +192,11 @@ class ClaudeTranscriptParser:
184
192
  start_idx = boundary_idx + 1
185
193
  break
186
194
 
187
- return turns[start_idx:end_idx]
195
+ result = turns[start_idx:end_idx]
196
+ result, removed = self._validate_tool_pairing(result)
197
+ if removed:
198
+ self.logger.debug(f"Removed {len(removed)} orphaned tool_results: {removed}")
199
+ return result
188
200
 
189
201
  def is_session_boundary(self, turn: dict[str, Any]) -> bool:
190
202
  """
@@ -212,6 +224,51 @@ class ClaudeTranscriptParser:
212
224
  # Check for /clear command marker
213
225
  return "<command-name>/clear</command-name>" in str(content)
214
226
 
227
+ def _validate_tool_pairing(
228
+ self, turns: list[dict[str, Any]]
229
+ ) -> tuple[list[dict[str, Any]], list[str]]:
230
+ """Remove orphaned tool_results that reference missing tool_use blocks.
231
+
232
+ This prevents Claude API validation errors when truncation cuts between
233
+ a tool_use and its corresponding tool_result.
234
+
235
+ Args:
236
+ turns: List of transcript turns to validate
237
+
238
+ Returns:
239
+ Tuple of (cleaned turns, list of removed tool_use_ids)
240
+ """
241
+ # Collect valid tool_use_ids from assistant messages
242
+ valid_ids: set[str] = set()
243
+ for turn in turns:
244
+ content = turn.get("message", {}).get("content", [])
245
+ if isinstance(content, list):
246
+ for block in content:
247
+ if isinstance(block, dict) and block.get("type") == "tool_use":
248
+ if tid := block.get("id"):
249
+ valid_ids.add(tid)
250
+
251
+ # Filter orphaned tool_results from user messages
252
+ cleaned: list[dict[str, Any]] = []
253
+ removed: list[str] = []
254
+ for turn in turns:
255
+ msg = turn.get("message", {})
256
+ content = msg.get("content", [])
257
+ if isinstance(content, list):
258
+ new_content: list[Any] = []
259
+ for block in content:
260
+ if isinstance(block, dict) and block.get("type") == "tool_result":
261
+ tid = block.get("tool_use_id")
262
+ if tid and tid not in valid_ids:
263
+ removed.append(tid)
264
+ continue
265
+ new_content.append(block)
266
+ if new_content != content:
267
+ turn = {**turn, "message": {**msg, "content": new_content}}
268
+ cleaned.append(turn)
269
+
270
+ return cleaned, removed
271
+
215
272
  def parse_line(self, line: str, index: int) -> ParsedMessage | None:
216
273
  """
217
274
  Parse a single line from the transcript JSONL.
@@ -247,6 +304,7 @@ class ClaudeTranscriptParser:
247
304
  tool_name = None
248
305
  tool_input = None
249
306
  tool_result = None
307
+ tool_use_id = None
250
308
 
251
309
  if msg_type == "user":
252
310
  role = "user"
@@ -274,8 +332,7 @@ class ClaudeTranscriptParser:
274
332
  content_type = "tool_use"
275
333
  tool_name = block.get("name")
276
334
  tool_input = block.get("input")
277
- # We capture the tool use ID as content if needed,
278
- # but for now we append nothing to text content
335
+ tool_use_id = block.get("id")
279
336
 
280
337
  elif block_type == "tool_result":
281
338
  content_type = "tool_result"
@@ -291,6 +348,7 @@ class ClaudeTranscriptParser:
291
348
  content_type = "tool_result"
292
349
  tool_name = data.get("tool_name")
293
350
  tool_result = data.get("result")
351
+ tool_use_id = data.get("tool_use_id")
294
352
  content = str(tool_result)
295
353
 
296
354
  else:
@@ -308,6 +366,7 @@ class ClaudeTranscriptParser:
308
366
  timestamp=timestamp,
309
367
  raw_json=data,
310
368
  usage=self._extract_usage(data),
369
+ tool_use_id=tool_use_id,
311
370
  )
312
371
 
313
372
  def _extract_usage(self, data: dict[str, Any]) -> TokenUsage | None:
@@ -0,0 +1,91 @@
1
+ """Skills module for Agent Skills spec compliant skill management.
2
+
3
+ This module provides:
4
+ - YAML frontmatter parsing for SKILL.md files
5
+ - Validation against Agent Skills specification
6
+ - Search integration (TF-IDF + optional embeddings via UnifiedSearcher)
7
+ - Skill loading from filesystem, GitHub, and ZIP archives
8
+ - Skill updates from source
9
+ """
10
+
11
+ # Embedding utilities are now in gobby.search
12
+ from gobby.search import (
13
+ generate_embedding,
14
+ generate_embeddings,
15
+ is_embedding_available,
16
+ )
17
+ from gobby.skills.loader import (
18
+ GitHubRef,
19
+ SkillLoader,
20
+ SkillLoadError,
21
+ clone_skill_repo,
22
+ extract_zip,
23
+ parse_github_url,
24
+ )
25
+ from gobby.skills.manager import SkillManager
26
+ from gobby.skills.parser import (
27
+ ParsedSkill,
28
+ SkillParseError,
29
+ parse_frontmatter,
30
+ parse_skill_file,
31
+ parse_skill_text,
32
+ )
33
+ from gobby.skills.search import (
34
+ SearchFilters,
35
+ SkillSearch,
36
+ SkillSearchResult,
37
+ )
38
+ from gobby.skills.updater import (
39
+ SkillUpdateError,
40
+ SkillUpdater,
41
+ SkillUpdateResult,
42
+ )
43
+ from gobby.skills.validator import (
44
+ SkillValidator,
45
+ ValidationResult,
46
+ validate_skill_category,
47
+ validate_skill_compatibility,
48
+ validate_skill_description,
49
+ validate_skill_name,
50
+ validate_skill_tags,
51
+ validate_skill_version,
52
+ )
53
+
54
+ __all__ = [
55
+ # Embeddings (from gobby.search)
56
+ "generate_embedding",
57
+ "generate_embeddings",
58
+ "is_embedding_available",
59
+ # Loader
60
+ "GitHubRef",
61
+ "SkillLoadError",
62
+ "SkillLoader",
63
+ "clone_skill_repo",
64
+ "extract_zip",
65
+ "parse_github_url",
66
+ # Manager
67
+ "SkillManager",
68
+ # Updater
69
+ "SkillUpdateError",
70
+ "SkillUpdateResult",
71
+ "SkillUpdater",
72
+ # Parser
73
+ "ParsedSkill",
74
+ "SkillParseError",
75
+ "parse_frontmatter",
76
+ "parse_skill_file",
77
+ "parse_skill_text",
78
+ # Search
79
+ "SearchFilters",
80
+ "SkillSearch",
81
+ "SkillSearchResult",
82
+ # Validator
83
+ "SkillValidator",
84
+ "ValidationResult",
85
+ "validate_skill_category",
86
+ "validate_skill_compatibility",
87
+ "validate_skill_description",
88
+ "validate_skill_name",
89
+ "validate_skill_tags",
90
+ "validate_skill_version",
91
+ ]