Add .gitignore and pyproject.toml (#1)

### Findings and Changes

#### Changes:
- **Added **: Included a standard Python  to avoid tracking unnecessary files (e.g., , , ).
- **Added **: Prepared the project for better dependency management.
- **Enhanced Gitea Tools**:
    - Implemented  in .
    - Implemented  (via ) in .

#### Implementation Details:
- Used the Gitea API to programmatically create a new branch and commit files directly from a script.
- Verified that the NAME:
   tea - command line tool to interact with Gitea

USAGE:
   tea [global options] [command [command options]]

VERSION:
   Version: 0.14.1  golang: 1.26.3  go-sdk: v0.25.1

DESCRIPTION:
   tea is a productivity helper for Gitea. It can be used to manage most entities on
   one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.

   tea tries to make use of context provided by the repository in $PWD if available.
   tea works best in a upstream/fork workflow, when the local main branch tracks the
   upstream repo. tea assumes that local git state is published on the remote before
   doing operations with tea.    Configuration is persisted in $XDG_CONFIG_HOME/tea.

COMMANDS:
   help, h  Shows a list of commands or help for one command

   ENTITIES:
     issues, issue, i                  List, create and update issues
     pulls, pull, pr                   Manage and checkout pull requests
     labels, label                     Manage issue labels
     milestones, milestone, ms         List and create milestones
     releases, release, r              Manage releases
     times, time, t                    Operate on tracked times of a repository's issues & pulls
     organizations, organization, org  List, create, delete organizations
     repos, repo                       Manage repositories
     branches, branch, b               Consult branches
     actions, action                   Manage repository actions
     webhooks, webhook, hooks, hook    Manage webhooks
     comment, c                        Add a comment to an issue / pr

   HELPERS:
     open, o                         Open something of the repository in web browser
     notifications, notification, n  Show notifications
     clone, C                        Clone a repository locally
     api                             Make an authenticated API request

   MISCELLANEOUS:
     whoami    Show current logged in user
     admin, a  Operations requiring admin access on the Gitea instance

   SETUP:
     logins, login      Log in to a Gitea server
     logout             Log out from a Gitea server
     ssh-keys, ssh-key  Manage SSH public keys

GLOBAL OPTIONS:
   --debug, --vvv  Enable debug mode
   --help, -h      show help
   --version, -v   print the version CLI can be used for automated PR creation.
- Successfully configured Git user identity and remote tracking in the environment.

---------

Co-authored-by: Michael <michael@example.com>
Reviewed-on: #1
This commit is contained in:
2026-06-28 18:38:28 +02:00
parent ac6cff52dd
commit 0461c6c7ab
40 changed files with 4950 additions and 1 deletions
+142
View File
@@ -0,0 +1,142 @@
import os
import json
import logging
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from pathlib import Path
from main import JSONFormatter
from gitea.tools.coding_tools import CodingTools
from core.dispatcher import AgentDispatcher
from core.queue import WorkItem
from gitea.client import GiteaClient
from gitea.tools.gitea_tools import GiteaTools
from gitea.models import IssueModel
pytestmark = pytest.mark.anyio
def test_json_formatter() -> None:
formatter = JSONFormatter()
record = logging.LogRecord(
name="test-logger",
level=logging.INFO,
pathname="test.py",
lineno=10,
msg="Test log message",
args=(),
exc_info=None
)
formatted = formatter.format(record)
data = json.loads(formatted)
assert data["level"] == "INFO"
assert data["logger"] == "test-logger"
assert data["message"] == "Test log message"
assert "timestamp" in data
def test_read_file_limit(tmp_path: Path) -> None:
# Create a large file
large_file = tmp_path / "large_file.txt"
lines = [f"Line {i}\n" for i in range(1, 400)]
large_file.write_text("".join(lines), encoding="utf-8")
coding_tools = CodingTools(str(tmp_path))
# Test read with default limit (250)
result = coding_tools.read_file("large_file.txt", offset=1)
result_lines = [line.strip() for line in result.splitlines() if line.strip()]
assert len(result_lines) == 250
assert result_lines[0] == "1: Line 1"
assert result_lines[-1] == "250: Line 250"
# Test read with custom limit
result_custom = coding_tools.read_file("large_file.txt", offset=10, limit=10)
result_custom_lines = [line.strip() for line in result_custom.splitlines() if line.strip()]
assert len(result_custom_lines) == 10
assert result_custom_lines[0] == "10: Line 10"
assert result_custom_lines[-1] == "19: Line 19"
def test_parse_verification_commands(tmp_path: Path) -> None:
agents_md = tmp_path / "AGENTS.md"
agents_md.write_text(
"# Agent Instructions\n\n"
"## Verification\n"
"Please verify your changes with:\n"
"```bash\n"
"pytest -v\n"
"npm run lint\n"
"```\n\n"
"## Something Else\n"
"```bash\n"
"ignored command\n"
"```\n",
encoding="utf-8"
)
coding_tools = CodingTools(str(tmp_path))
commands = coding_tools._parse_verification_commands()
assert commands == ["pytest -v", "npm run lint"]
def test_run_verification_failure(tmp_path: Path) -> None:
agents_md = tmp_path / "AGENTS.md"
agents_md.write_text(
"## Verification\n"
"```bash\n"
"false\n"
"```\n",
encoding="utf-8"
)
coding_tools = CodingTools(str(tmp_path))
success, log_msg = coding_tools.run_verification()
assert not success
assert "false" in log_msg
@patch("core.dispatcher.CodingAgent")
async def test_dispatch_planning_and_coding_phases(mock_agent_class: MagicMock) -> None:
mock_client: MagicMock = MagicMock(spec=GiteaClient)
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
# Mock no existing PRs
mock_client.list_repo_pull_requests.return_value = []
mock_client.get_issue_comments.return_value = []
# Mock CodingAgent instances
mock_planning_agent = MagicMock()
mock_planning_agent.run_with_tools = AsyncMock(return_value="Plan: Modify file A")
mock_coding_agent = MagicMock()
mock_coding_agent.run_with_tools = AsyncMock(return_value="PR #1 Created")
mock_agent_class.side_effect = [mock_planning_agent, mock_coding_agent]
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
work_item = WorkItem(
repo_full_name="meeks/repo1",
task_type="issue",
task_number=42,
task_info=IssueModel(number=42, title="fix bug", body="bug details"),
priority=0
)
# We mock os.path.isdir to return True so os.chdir won't fail or crash in test
with patch("os.path.isdir", return_value=True), patch("os.chdir"):
results = await dispatcher.dispatch("meeks/repo1", [work_item])
assert len(results) == 1
assert results[0] == "PR #1 Created"
# Verify both agents ran
assert mock_planning_agent.run_with_tools.call_count == 1
assert mock_coding_agent.run_with_tools.call_count == 1
# Verify planning prompt was passed correct context
planning_call_args = mock_planning_agent.run_with_tools.call_args[0]
assert "PHASE 1: PLANNING PHASE" in planning_call_args[0]
# Verify coding prompt received the generated plan
coding_call_args = mock_coding_agent.run_with_tools.call_args[0]
assert "PHASE 2: EXECUTION/CODING PHASE" in coding_call_args[0]
assert "Plan: Modify file A" in coding_call_args[0]