From 61c194de3753eb5d392bf04f0563e4d3451cc68d Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Wed, 6 May 2026 16:00:31 +0800 Subject: [PATCH] fix: commit agent changes before opening PR --- src/agent_gitea/rendering.py | 25 ++++++++++++++----------- src/agent_gitea/service.py | 17 ++++++++++++----- src/agent_gitea/workspace.py | 24 ++++++++++++++++++++++++ tests/test_gitea_service.py | 20 +++++++++++++++++--- tests/test_rendering_workspace.py | 6 +++--- 5 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/agent_gitea/rendering.py b/src/agent_gitea/rendering.py index 271737e..50032e1 100644 --- a/src/agent_gitea/rendering.py +++ b/src/agent_gitea/rendering.py @@ -34,7 +34,8 @@ Issue URL: {issue.html_url} Implement the requested change in this workspace. Keep the change scoped to this issue. Run the relevant tests before finishing. -Write `AGENT_IMPLEMENTATION_REPORT.md` in the workspace root using this exact section contract: +Write `.agent-output/AGENT_IMPLEMENTATION_REPORT.md` using this exact section contract. +Keep the section headings exactly as written below, but write the section content in Chinese: - Summary - Files changed @@ -55,7 +56,9 @@ Issue: #{issue.issue_number} {issue.title} Review the implementation currently checked out in this workspace. Focus on correctness, scope control, test evidence, and human risks. Do not modify code. -Write `AGENT_REVIEW_REPORT.md` in the workspace root using this exact section contract: +Write `.agent-output/AGENT_REVIEW_REPORT.md` using this exact section contract. +Keep the section headings exactly as written below. Keep the Verdict token in English, +but write the section content and Suggested PR Comment in Chinese: - Verdict: APPROVE, REQUEST_CHANGES, or NEEDS_HUMAN_DECISION - Summary @@ -69,16 +72,16 @@ Write `AGENT_REVIEW_REPORT.md` in the workspace root using this exact section co def render_pr_body(issue: IssueRecord, implementation_report: str) -> str: - return f"""Closes #{issue.issue_number} + return f"""关联 Issue:#{issue.issue_number} -## Agent Implementation Report +## 代理实现报告 {implementation_report.strip()} -## Human Review Gate +## 人工审核 -This PR was opened by the local agent manager. It has not been auto-merged. -Human maintainers must review, decide, and merge manually. +此 PR 由本地 agent-manager 自动创建,但不会自动合并。 +请维护者人工审核、决策并手动合并。 """ @@ -102,13 +105,13 @@ def extract_section(raw: str, title: str) -> str: def render_human_review_summary(review: ReviewReport) -> str: - return f"""## Agent Review Summary + return f"""## 代理评审摘要 -Verdict: `{review.verdict}` +结论:`{review.verdict}` {review.suggested_pr_comment.strip()} -## Human Action Required +## 需要人工处理 -Please review the PR manually. The agent manager will not merge, close, or request changes automatically. +请人工审核该 PR。agent-manager 不会自动合并、关闭 PR 或提交变更请求。 """ diff --git a/src/agent_gitea/service.py b/src/agent_gitea/service.py index 7057e24..d722123 100644 --- a/src/agent_gitea/service.py +++ b/src/agent_gitea/service.py @@ -132,12 +132,15 @@ class TaskRunner: error_message="implementer produced no diff", clear_lease=True, ) + commit_message = f"agent: implement issue #{issue.issue_number} - {issue.title}" + commit_id = self.workspace_manager.commit_changes(workspace, commit_message) + self.db.add_event(task.id, TaskState.TESTING, TaskState.TESTING, f"committed implementation {commit_id}") self.workspace_manager.push_branch(workspace, branch_name) pr_body = render_pr_body(issue, implementation_report) pr = self.gitea.create_pull_request( owner=repo.owner, name=repo.name, - title=f"Agent: {issue.title}", + title=f"代理实现:{issue.title}", body=pr_body, head=branch_name, base=repo.default_branch, @@ -191,7 +194,9 @@ class TaskRunner: workspace: Path, ) -> str: prompt = render_implementer_prompt(repo, issue, branch_name) - prompt_path = workspace / "AGENT_IMPLEMENTER_PROMPT.md" + output_dir = workspace / ".agent-output" + output_dir.mkdir(exist_ok=True) + prompt_path = output_dir / "AGENT_IMPLEMENTER_PROMPT.md" write_prompt(prompt_path, prompt) command = render_command( self.config.agents.implementer.command, @@ -202,7 +207,7 @@ class TaskRunner: branch_name=branch_name, ) result = self.command_runner.run(command, workspace, stdin=prompt) - report = read_report(workspace / "AGENT_IMPLEMENTATION_REPORT.md") + report = read_report(output_dir / "AGENT_IMPLEMENTATION_REPORT.md") self.db.add_agent_run( task_id=task.id, role="implementer", @@ -226,7 +231,9 @@ class TaskRunner: workspace: Path, ) -> str: prompt = render_reviewer_prompt(repo, issue, pr_number) - prompt_path = workspace / "AGENT_REVIEWER_PROMPT.md" + output_dir = workspace / ".agent-output" + output_dir.mkdir(exist_ok=True) + prompt_path = output_dir / "AGENT_REVIEWER_PROMPT.md" write_prompt(prompt_path, prompt) command = render_command( self.config.agents.reviewer.command, @@ -237,7 +244,7 @@ class TaskRunner: pr_number=pr_number, ) result = self.command_runner.run(command, workspace, stdin=prompt) - report = read_report(workspace / "AGENT_REVIEW_REPORT.md") + report = read_report(output_dir / "AGENT_REVIEW_REPORT.md") self.db.add_agent_run( task_id=task.id, role="reviewer", diff --git a/src/agent_gitea/workspace.py b/src/agent_gitea/workspace.py index b82f8d1..6933f1d 100644 --- a/src/agent_gitea/workspace.py +++ b/src/agent_gitea/workspace.py @@ -32,8 +32,22 @@ class WorkspaceManager: self._git(["clone", repo.clone_url, str(path)], Path.cwd()) self._git(["checkout", repo.default_branch], path) self._git(["checkout", "-B", branch_name], path) + self.exclude_runtime_artifacts(path) return path + def exclude_runtime_artifacts(self, workspace: str | Path) -> None: + exclude_path = Path(workspace) / ".git" / "info" / "exclude" + entries = { + ".agent-output/", + "AGENT_IMPLEMENTER_PROMPT.md", + "AGENT_REVIEWER_PROMPT.md", + } + existing = exclude_path.read_text(encoding="utf-8") if exclude_path.exists() else "" + with exclude_path.open("a", encoding="utf-8") as handle: + for entry in sorted(entries): + if entry not in existing: + handle.write(f"\n{entry}\n") + def has_diff(self, workspace: str | Path, base_ref: str = "origin/HEAD") -> bool: result = self._git(["status", "--porcelain"], Path(workspace), check=False) if result.stdout.strip(): @@ -41,6 +55,16 @@ class WorkspaceManager: diff = self._git(["diff", "--quiet", f"{base_ref}...HEAD"], Path(workspace), check=False) return diff.returncode == 1 + def commit_changes(self, workspace: str | Path, message: str) -> str: + workspace_path = Path(workspace) + self._git(["add", "-A"], workspace_path) + result = self._git(["diff", "--cached", "--quiet"], workspace_path, check=False) + if result.returncode == 0: + raise RuntimeError("no staged implementation changes to commit") + self._git(["commit", "-m", message], workspace_path) + commit = self._git(["rev-parse", "--short", "HEAD"], workspace_path) + return commit.stdout.strip() + def push_branch(self, workspace: str | Path, branch_name: str) -> None: self._git(["push", "-u", "origin", branch_name], Path(workspace)) diff --git a/tests/test_gitea_service.py b/tests/test_gitea_service.py index 640e649..7475921 100644 --- a/tests/test_gitea_service.py +++ b/tests/test_gitea_service.py @@ -136,6 +136,10 @@ class FakeWorkspaceManager: def push_branch(self, workspace, branch_name): self.pushed.append(branch_name) + def commit_changes(self, workspace, message): + self.commit_message = message + return "abc1234" + def cleanup(self, workspace): pass @@ -150,12 +154,16 @@ class FakeRunner: if role == self.fail_role: return AgentResult(exit_code=1, stdout="", stderr="failed") if role == "implementer": - (Path(cwd) / "AGENT_IMPLEMENTATION_REPORT.md").write_text( + output_dir = Path(cwd) / ".agent-output" + output_dir.mkdir(exist_ok=True) + (output_dir / "AGENT_IMPLEMENTATION_REPORT.md").write_text( "## Summary\nImplemented\n\n## Test commands run\npytest\n", encoding="utf-8", ) if role == "reviewer": - (Path(cwd) / "AGENT_REVIEW_REPORT.md").write_text( + output_dir = Path(cwd) / ".agent-output" + output_dir.mkdir(exist_ok=True) + (output_dir / "AGENT_REVIEW_REPORT.md").write_text( "## Verdict\nAPPROVE\n\n## Suggested PR Comment\nLooks good.\n", encoding="utf-8", ) @@ -196,11 +204,12 @@ def test_run_task_success_posts_review_comments(db, tmp_path): return httpx.Response(201, json={"id": 1}) return httpx.Response(404) + workspace_manager = FakeWorkspaceManager(tmp_path / "work") task = TaskRunner( db=db, config=config, gitea=make_client(handler), - workspace_manager=FakeWorkspaceManager(tmp_path / "work"), + workspace_manager=workspace_manager, command_runner=FakeRunner(), worker_id="worker", ).run_once() @@ -208,6 +217,11 @@ def test_run_task_success_posts_review_comments(db, tmp_path): assert task is not None assert task.state == TaskState.HUMAN_REVIEW_READY assert task.pr_number == 5 + assert workspace_manager.pushed == ["agent/issue-1-ready-issue"] + assert workspace_manager.commit_message == "agent: implement issue #1 - Ready issue" + pull_requests = [payload for _, path, payload in requests if path == "/api/v1/repos/acme/service/pulls"] + assert pull_requests[0]["title"] == "代理实现:Ready issue" + assert "代理实现报告" in pull_requests[0]["body"] command = json.loads(db.list_agent_runs(task.id)[0]["command_json"]) assert command[1] == "--cd" assert Path(command[2]).is_absolute() diff --git a/tests/test_rendering_workspace.py b/tests/test_rendering_workspace.py index d7830ef..f610cbf 100644 --- a/tests/test_rendering_workspace.py +++ b/tests/test_rendering_workspace.py @@ -38,9 +38,9 @@ def test_prompt_and_pr_body_include_contract_sections(db): prompt = render_implementer_prompt(repo, issue, "agent/issue-7-add-thing") body = render_pr_body(issue, "## Summary\nDone") - assert "AGENT_IMPLEMENTATION_REPORT.md" in prompt - assert "Closes #7" in body - assert "Human Review Gate" in body + assert ".agent-output/AGENT_IMPLEMENTATION_REPORT.md" in prompt + assert "关联 Issue:#7" in body + assert "人工审核" in body def test_review_report_parsing_extracts_verdict_and_comment():