onecoder 0.0.2__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 (73) hide show
  1. onecoder/agent.py +95 -0
  2. onecoder/agentic_tool_search/__init__.py +0 -0
  3. onecoder/agentic_tool_search/dynamic_tool_search.py +64 -0
  4. onecoder/agentic_tool_search/registry.py +33 -0
  5. onecoder/agents/__init__.py +7 -0
  6. onecoder/agents/documentation_agent.py +12 -0
  7. onecoder/agents/file_reader_agent.py +19 -0
  8. onecoder/agents/file_writer_agent.py +19 -0
  9. onecoder/agents/orchestrator_agent.py +51 -0
  10. onecoder/agents/refactoring_agent.py +12 -0
  11. onecoder/agents/research_agent.py +31 -0
  12. onecoder/agents/task_suggestion_agent.py +88 -0
  13. onecoder/alignment.py +236 -0
  14. onecoder/api.py +162 -0
  15. onecoder/api_client.py +112 -0
  16. onecoder/backends/base.py +22 -0
  17. onecoder/backends/local_tui.py +65 -0
  18. onecoder/blackboard.py +102 -0
  19. onecoder/cli.py +108 -0
  20. onecoder/commands/__init__.py +1 -0
  21. onecoder/commands/auth.py +78 -0
  22. onecoder/commands/ci.py +29 -0
  23. onecoder/commands/delegate.py +557 -0
  24. onecoder/commands/doctor.py +40 -0
  25. onecoder/commands/issue.py +136 -0
  26. onecoder/commands/logs.py +45 -0
  27. onecoder/commands/project.py +270 -0
  28. onecoder/commands/server.py +170 -0
  29. onecoder/config_manager.py +87 -0
  30. onecoder/constants.py +9 -0
  31. onecoder/diagnostics/__init__.py +2 -0
  32. onecoder/diagnostics/env_scan.py +207 -0
  33. onecoder/discovery.py +101 -0
  34. onecoder/distillation.py +236 -0
  35. onecoder/evaluation/__init__.py +1 -0
  36. onecoder/evaluation/ttu.py +176 -0
  37. onecoder/governance/__init__.py +0 -0
  38. onecoder/governance/probllm.py +91 -0
  39. onecoder/hooks.py +74 -0
  40. onecoder/ipc_auth.py +200 -0
  41. onecoder/issues.py +188 -0
  42. onecoder/jules_client.py +343 -0
  43. onecoder/knowledge.py +106 -0
  44. onecoder/llm.py +61 -0
  45. onecoder/logger.py +42 -0
  46. onecoder/metrics.py +129 -0
  47. onecoder/models/delegation.py +46 -0
  48. onecoder/onboarding.py +264 -0
  49. onecoder/review.py +233 -0
  50. onecoder/services/delegation_service.py +209 -0
  51. onecoder/services/validation_service.py +104 -0
  52. onecoder/sessions.py +186 -0
  53. onecoder/sprint_collector.py +165 -0
  54. onecoder/sync.py +167 -0
  55. onecoder/tmux.py +86 -0
  56. onecoder/tools/__init__.py +10 -0
  57. onecoder/tools/executor.py +53 -0
  58. onecoder/tools/external_tools.py +106 -0
  59. onecoder/tools/file_tools.py +77 -0
  60. onecoder/tools/interface.py +25 -0
  61. onecoder/tools/jules_tools.py +122 -0
  62. onecoder/tools/kit_tools.py +122 -0
  63. onecoder/tools/registry.py +32 -0
  64. onecoder/tui/__init__.py +5 -0
  65. onecoder/tui/app.py +263 -0
  66. onecoder/tui/commands.py +150 -0
  67. onecoder/tui/widgets.py +92 -0
  68. onecoder/worktree.py +186 -0
  69. onecoder-0.0.2.dist-info/METADATA +17 -0
  70. onecoder-0.0.2.dist-info/RECORD +73 -0
  71. onecoder-0.0.2.dist-info/WHEEL +5 -0
  72. onecoder-0.0.2.dist-info/entry_points.txt +2 -0
  73. onecoder-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,106 @@
1
+ # onecoder/tools/external_tools.py
2
+
3
+ import subprocess
4
+ import os
5
+ from ..governance.probllm import ProbLLMGuardian
6
+
7
+ def shell_executor_tool(command: str) -> str:
8
+ """
9
+ Executes a shell command and returns its output (stdout and stderr).
10
+ Use this for running general system commands, testing, or querying tools.
11
+
12
+ Args:
13
+ command: The full shell command to execute.
14
+
15
+ Returns:
16
+ The command output or an error message.
17
+ """
18
+ try:
19
+ # ProbLLM Governance Check
20
+ # Resolve path to governance.yaml relative to repository root
21
+ # onecoder-cli/onecoder/tools/external_tools.py -> ../../../governance.yaml
22
+ gov_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "governance.yaml"))
23
+
24
+ # Instantiate Guardian on demand
25
+ local_guardian = ProbLLMGuardian(gov_path)
26
+
27
+ is_safe, message = local_guardian.validate_tool_execution("shell_execute", {"command": command})
28
+ if not is_safe:
29
+ return f"GOVERNANCE BLOCK: {message}. Please verify 'governance.yaml' policies."
30
+
31
+ # Run the command
32
+ result = subprocess.run(
33
+ command,
34
+ shell=True,
35
+ capture_output=True,
36
+ text=True,
37
+ timeout=30,
38
+ )
39
+
40
+ output = ""
41
+ if result.stdout:
42
+ output += f"STDOUT:\n{result.stdout}\n"
43
+ if result.stderr:
44
+ output += f"STDERR:\n{result.stderr}\n"
45
+
46
+ raw_response = output or f"Command exited with code {result.returncode}"
47
+
48
+ # ProbLLM Output Validation
49
+ is_safe, message = local_guardian.validate_output(raw_response)
50
+ if not is_safe:
51
+ return f"GOVERNANCE BLOCK: {message}"
52
+
53
+ if not output and result.returncode == 0:
54
+ return "Command executed successfully with no output."
55
+
56
+ return raw_response
57
+
58
+ except subprocess.TimeoutExpired:
59
+ return "Error: Command timed out after 30 seconds."
60
+ except Exception as e:
61
+ return f"An error occurred while executing the command: {e}"
62
+
63
+
64
+ def gemini_tool(query: str) -> str:
65
+ """
66
+ Executes a Gemini CLI command ('gemini ask') for general queries.
67
+ Note: For image generation (nanobanana), inform the user that it must be done
68
+ interactively within the Gemini TUI.
69
+
70
+ Args:
71
+ query: The query or instruction to pass to 'gemini ask'.
72
+
73
+ Returns:
74
+ The result from Gemini CLI or an error/guidance message.
75
+ """
76
+ # Check if the query is about image generation
77
+ image_keywords = ["image", "generate", "draw", "picture", "nanobanana"]
78
+ if any(keyword in query.lower() for keyword in image_keywords):
79
+ return (
80
+ "To generate images with 'nanobanana', you must use the Gemini TUI. "
81
+ "Run 'gemini' in your terminal and use the interactive prompt there."
82
+ )
83
+
84
+ try:
85
+ # Construct the 'gemini ask' command
86
+ # We escape single quotes in the query to avoid shell injection issues
87
+ escaped_query = query.replace("'", "'\\''")
88
+ command = f"gemini ask '{escaped_query}'"
89
+
90
+ result = subprocess.run(
91
+ command,
92
+ shell=True,
93
+ capture_output=True,
94
+ text=True,
95
+ timeout=60, # Gemini might take longer
96
+ )
97
+
98
+ if result.returncode == 0:
99
+ return result.stdout
100
+ else:
101
+ return f"Error running gemini: {result.stderr}"
102
+
103
+ except subprocess.TimeoutExpired:
104
+ return "Error: Gemini command timed out."
105
+ except Exception as e:
106
+ return f"An error occurred while running Gemini: {e}"
@@ -0,0 +1,77 @@
1
+ # onecoder/tools/file_tools.py
2
+
3
+
4
+ def write_file_tool(filepath: str, content: str) -> str:
5
+ """
6
+ Writes content to a file at the given filepath.
7
+
8
+ Args:
9
+ filepath: The path to the file.
10
+ content: The content to write to the file.
11
+
12
+ Returns:
13
+ A success message or an error message if the file cannot be written.
14
+ """
15
+ try:
16
+ with open(filepath, "w") as f:
17
+ f.write(content)
18
+
19
+ # Trigger hooks
20
+ try:
21
+ from onecoder.hooks import hooks_manager
22
+ hooks_manager.on_file_edit(filepath)
23
+ except ImportError:
24
+ pass # Ignore if hooks module is not available or has circular import
25
+ except Exception as e:
26
+ print(f"Warning: Failed to trigger hooks: {e}")
27
+
28
+ return f"Successfully wrote content to {filepath}"
29
+ except Exception as e:
30
+ return f"An error occurred while writing to the file: {e}"
31
+
32
+
33
+ def read_file_tool(filepath: str) -> str:
34
+ """
35
+ Reads the contents of a file at the given filepath.
36
+
37
+ Args:
38
+ filepath: The path to the file.
39
+
40
+ Returns:
41
+ The content of the file, or an error message if the file cannot be read.
42
+ """
43
+ try:
44
+ with open(filepath, "r") as f:
45
+ return f.read()
46
+ except FileNotFoundError:
47
+ return f"Error: File not found at {filepath}"
48
+ except Exception as e:
49
+ return f"An error occurred while reading the file: {e}"
50
+
51
+
52
+ def list_directory_tool(directory: str = ".") -> str:
53
+ """
54
+ Lists the contents of a directory.
55
+
56
+ Args:
57
+ directory: The path to the directory to list.
58
+
59
+ Returns:
60
+ A string containing the directory listing, or an error message if the directory cannot be read.
61
+ """
62
+ import os
63
+
64
+ try:
65
+ items = os.listdir(directory)
66
+ files = [
67
+ item for item in items if os.path.isfile(os.path.join(directory, item))
68
+ ]
69
+ dirs = [item for item in items if os.path.isdir(os.path.join(directory, item))]
70
+ result = f"Directory: {directory}\n"
71
+ if dirs:
72
+ result += f"Directories: {', '.join(dirs)}\n"
73
+ if files:
74
+ result += f"Files: {', '.join(files)}"
75
+ return result
76
+ except Exception as e:
77
+ return f"An error occurred while listing directory {directory}: {e}"
@@ -0,0 +1,25 @@
1
+ # onecoder/tools/interface.py
2
+
3
+ from typing import Any, Callable, Dict, List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+ class BaseTool(BaseModel):
7
+ """Base class for all tools in the system."""
8
+ name: str
9
+ description: str
10
+ func: Callable
11
+ parameters: Optional[Dict[str, Any]] = None
12
+
13
+ def execute(self, **kwargs) -> Any:
14
+ """Execute the tool function with the given arguments."""
15
+ return self.func(**kwargs)
16
+
17
+ async def execute_async(self, **kwargs) -> Any:
18
+ """Execute the tool function asynchronously if it is a coroutine."""
19
+ from ..metrics import TTUMetrics
20
+ TTUMetrics.get_instance().track_first_tool_call()
21
+
22
+ import asyncio
23
+ if asyncio.iscoroutinefunction(self.func):
24
+ return await self.func(**kwargs)
25
+ return self.func(**kwargs)
@@ -0,0 +1,122 @@
1
+ """Jules API integration tool for OneCoder.
2
+
3
+ This module provides tool functions that wrap the JulesAPIClient for use
4
+ in the ADK tool registry and backward compatibility with existing tests.
5
+ """
6
+
7
+ import os
8
+ import requests
9
+ from typing import Union
10
+ from ..jules_client import JulesAPIClient, JulesAPIError, JulesAuthError, JulesNotFoundError
11
+
12
+
13
+ def jules_delegate_tool(prompt: str, source: Union[str, None] = None) -> str:
14
+ """Delegate a coding task to Google Jules.
15
+
16
+ Args:
17
+ prompt: The coding task description
18
+ source: GitHub source (e.g., 'sources/github/owner/repo').
19
+ Defaults to JULES_SOURCE env var.
20
+
21
+ Returns:
22
+ Status message with session ID and initial response
23
+ """
24
+ try:
25
+ # Initialize client
26
+ client = JulesAPIClient()
27
+
28
+ # Create session
29
+ session = client.create_session(prompt, source=source)
30
+
31
+ # Get initial activities
32
+ try:
33
+ activities = client.list_activities(session.id, page_size=5)
34
+ except JulesNotFoundError:
35
+ # Activities might not be available yet
36
+ activities = []
37
+
38
+ # Format response
39
+ result = f"✓ Jules session created: {session.id}\n"
40
+ result += f"Task: {prompt}\n"
41
+ result += f"Source: {source or os.environ.get('JULES_SOURCE')}\n\n"
42
+
43
+ if activities:
44
+ result += "Initial Activities:\n"
45
+ for activity in activities[:3]:
46
+ if activity.activity_type == "plan_generated":
47
+ plan = activity.data.get("planGenerated", {}).get("plan", {})
48
+ steps = len(plan.get("steps", []))
49
+ result += f"- Plan generated with {steps} steps\n"
50
+ elif activity.activity_type == "progress_updated":
51
+ progress = activity.data.get("progressUpdated", {})
52
+ result += f"- {progress.get('title', 'Progress update')}\n"
53
+
54
+ result += f"\nMonitor progress at: https://jules.google.com/sessions/{session.id}"
55
+ return result
56
+
57
+ except JulesAuthError as e:
58
+ return f"Error: JULES_API_KEY environment variable not set"
59
+ except JulesAPIError as e:
60
+ return f"Error: {str(e)}"
61
+ except Exception as e:
62
+ return f"Error: {str(e)}"
63
+
64
+
65
+ def jules_status_tool(session_id: str) -> str:
66
+ """Check the status of a Jules session.
67
+
68
+ Args:
69
+ session_id: The Jules session ID
70
+
71
+ Returns:
72
+ Current status and recent activities
73
+ """
74
+ try:
75
+ # Initialize client
76
+ client = JulesAPIClient()
77
+
78
+ # Get session info
79
+ session = client.get_session(session_id)
80
+
81
+ # Get recent activities
82
+ try:
83
+ activities = client.list_activities(session_id, page_size=10)
84
+ except JulesNotFoundError:
85
+ activities = []
86
+
87
+ # Format response
88
+ result = f"Session: {session_id}\n"
89
+ result += f"Title: {session.title or 'N/A'}\n"
90
+ result += f"Prompt: {session.prompt or 'N/A'}\n\n"
91
+
92
+ # Check for PR output
93
+ pr_output = client.detect_pr_output(session_id)
94
+ if pr_output:
95
+ result += "Outputs:\n"
96
+ result += f"- PR: {pr_output['url']}\n"
97
+ result += f" Title: {pr_output['title']}\n"
98
+
99
+ # Show recent activities
100
+ if activities:
101
+ result += "\nRecent Activities:\n"
102
+ for activity in activities[:5]:
103
+ if activity.activity_type == "plan_generated":
104
+ result += f"- [{activity.originator}] Plan generated\n"
105
+ elif activity.activity_type == "progress_updated":
106
+ progress = activity.data.get("progressUpdated", {})
107
+ result += f"- [{activity.originator}] {progress.get('title', 'Progress')}\n"
108
+ elif activity.activity_type == "session_completed":
109
+ result += f"- [{activity.originator}] Session completed ✓\n"
110
+
111
+ return result
112
+
113
+ except JulesNotFoundError:
114
+ return f"Jules session with ID '{session_id}' not found."
115
+ except JulesAuthError:
116
+ return "Error: JULES_API_KEY environment variable not set"
117
+ except JulesAPIError as e:
118
+ return f"Error communicating with Jules API: {str(e)}"
119
+ except requests.exceptions.RequestException as e:
120
+ return f"Error communicating with Jules API: {str(e)}"
121
+ except Exception as e:
122
+ return f"Error: {str(e)}"
@@ -0,0 +1,122 @@
1
+ # onecoder/tools/kit_tools.py
2
+
3
+ import subprocess
4
+ import json
5
+
6
+
7
+ def kit_index_tool(directory: str = ".") -> str:
8
+ """
9
+ Uses kit to build and return a comprehensive index of the repository.
10
+
11
+ Args:
12
+ directory: The path to the repository directory to index.
13
+
14
+ Returns:
15
+ A JSON string containing the repository index, or an error message.
16
+ """
17
+ try:
18
+ # Run kit index command
19
+ result = subprocess.run(
20
+ ["kit", "index", "."],
21
+ cwd=directory,
22
+ capture_output=True,
23
+ text=True,
24
+ timeout=30,
25
+ )
26
+
27
+ if result.returncode == 0:
28
+ # Parse and return the JSON
29
+ try:
30
+ index_data = json.loads(result.stdout)
31
+ return json.dumps(index_data, indent=2)
32
+ except json.JSONDecodeError:
33
+ return (
34
+ f"Kit index completed but output is not valid JSON: {result.stdout}"
35
+ )
36
+ else:
37
+ return f"Error running kit index: {result.stderr}"
38
+
39
+ except subprocess.TimeoutExpired:
40
+ return "Error: kit index command timed out"
41
+ except FileNotFoundError:
42
+ return "Error: kit command not found. Make sure kit is installed."
43
+ except Exception as e:
44
+ return f"An error occurred while running kit index: {e}"
45
+
46
+
47
+ def kit_file_tree_tool(directory: str = ".") -> str:
48
+ """
49
+ Uses kit to get the file tree structure of a repository.
50
+
51
+ Args:
52
+ directory: The path to the repository directory.
53
+
54
+ Returns:
55
+ A string containing the file tree structure, or an error message.
56
+ """
57
+ import tempfile
58
+ import os
59
+
60
+ try:
61
+ # Create a temporary file to store the output
62
+ with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.json') as tmp:
63
+ tmp_path = tmp.name
64
+
65
+ result = subprocess.run(
66
+ ["kit", "file-tree", directory, "--output", tmp_path],
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=10
70
+ )
71
+
72
+ if result.returncode == 0:
73
+ with open(tmp_path, 'r') as f:
74
+ content = f.read()
75
+ os.remove(tmp_path)
76
+ try:
77
+ return json.loads(content)
78
+ except json.JSONDecodeError:
79
+ return f"Error: Invalid JSON from kit: {content}"
80
+ else:
81
+ if os.path.exists(tmp_path):
82
+ os.remove(tmp_path)
83
+ return f"Error running kit file-tree: {result.stderr}"
84
+
85
+ except subprocess.TimeoutExpired:
86
+ return "Error: kit file-tree command timed out"
87
+ except Exception as e:
88
+ return f"An error occurred while running kit file-tree: {e}"
89
+
90
+
91
+ def kit_symbols_tool(directory: str = ".") -> str:
92
+ """
93
+ Uses kit to extract code symbols from the repository.
94
+
95
+ Args:
96
+ directory: The path to the repository directory.
97
+
98
+ Returns:
99
+ A JSON string containing code symbols, or an error message.
100
+ """
101
+ try:
102
+ result = subprocess.run(
103
+ ["kit", "symbols", directory, "--format", "json"],
104
+ cwd=directory,
105
+ capture_output=True,
106
+ text=True,
107
+ timeout=15,
108
+ )
109
+
110
+ if result.returncode == 0:
111
+ try:
112
+ symbols_data = json.loads(result.stdout)
113
+ return json.dumps(symbols_data, indent=2)
114
+ except json.JSONDecodeError:
115
+ return f"Kit symbols completed but output is not valid JSON: {result.stdout}"
116
+ else:
117
+ return f"Error running kit symbols: {result.stderr}"
118
+
119
+ except subprocess.TimeoutExpired:
120
+ return "Error: kit symbols command timed out"
121
+ except Exception as e:
122
+ return f"An error occurred while running kit symbols: {e}"
@@ -0,0 +1,32 @@
1
+ # onecoder/tools/registry.py
2
+
3
+ from typing import Dict, List, Optional
4
+ from .interface import BaseTool
5
+
6
+ class ToolRegistry:
7
+ """A registry for managing and discovering tools."""
8
+
9
+ def __init__(self):
10
+ self._tools: Dict[str, BaseTool] = {}
11
+
12
+ def register(self, tool: BaseTool):
13
+ """Register a tool in the registry."""
14
+ self._tools[tool.name] = tool
15
+
16
+ def get_tool(self, name: str) -> Optional[BaseTool]:
17
+ """Retrieve a tool by name."""
18
+ return self._tools.get(name)
19
+
20
+ def list_tools(self) -> List[BaseTool]:
21
+ """List all registered tools."""
22
+ return list(self._tools.values())
23
+
24
+ def get_tool_descriptions(self) -> str:
25
+ """Get a formatted string of all tool names and descriptions."""
26
+ descriptions = []
27
+ for tool in self._tools.values():
28
+ descriptions.append(f"- {tool.name}: {tool.description}")
29
+ return "\n".join(descriptions)
30
+
31
+ # Global registry instance
32
+ registry = ToolRegistry()
@@ -0,0 +1,5 @@
1
+ """OneCoder Textual TUI package."""
2
+
3
+ from .app import OneCoderApp
4
+
5
+ __all__ = ["OneCoderApp"]