2 Commits

Author SHA1 Message Date
d53f3fe207 release: version 0.9.10 🚀
All checks were successful
Upload Python Package / Create Release (push) Successful in 10s
Upload Python Package / deploy (push) Successful in 31s
2026-04-11 18:05:25 +02:00
4f1d757dd8 fix: more git integration fixes, refs NOISSUE 2026-04-11 18:05:20 +02:00
6 changed files with 69 additions and 3 deletions

View File

@@ -5,10 +5,21 @@ Changelog
(unreleased) (unreleased)
------------ ------------
Fix
~~~
- More git integration fixes, refs NOISSUE. [Simon Diesenreiter]
0.9.9 (2026-04-11)
------------------
Fix Fix
~~~ ~~~
- Add missing git binary, refs NOISSUE. [Simon Diesenreiter] - Add missing git binary, refs NOISSUE. [Simon Diesenreiter]
Other
~~~~~
0.9.8 (2026-04-11) 0.9.8 (2026-04-11)
------------------ ------------------

View File

@@ -1 +1 @@
0.9.9 0.9.10

View File

@@ -58,6 +58,18 @@ class GiteaAPI:
"""Build a Gitea API URL from a relative path.""" """Build a Gitea API URL from a relative path."""
return f"{self.base_url}/api/v1/{path.lstrip('/')}" return f"{self.base_url}/api/v1/{path.lstrip('/')}"
def _normalize_pull_request_head(self, head: str | None, owner: str | None = None) -> str | None:
"""Return a Gitea-compatible head ref for pull request creation."""
normalized = (head or '').strip()
if not normalized:
return None
if ':' in normalized:
return normalized
effective_owner = (owner or self.owner or '').strip()
if not effective_owner:
return normalized
return f"{effective_owner}:{normalized}"
def build_repo_git_url(self, owner: str | None = None, repo: str | None = None) -> str | None: def build_repo_git_url(self, owner: str | None = None, repo: str | None = None) -> str | None:
"""Build the clone URL for a repository.""" """Build the clone URL for a repository."""
_owner = owner or self.owner _owner = owner or self.owner
@@ -222,11 +234,12 @@ class GiteaAPI:
"""Create a pull request.""" """Create a pull request."""
_owner = owner or self.owner _owner = owner or self.owner
_repo = repo or self.repo _repo = repo or self.repo
normalized_head = self._normalize_pull_request_head(head, _owner)
payload = { payload = {
"title": title, "title": title,
"body": body, "body": body,
"base": base, "base": base,
"head": head or f"{_owner}-{_repo}-ai-gen-{hash(title) % 10000}", "head": normalized_head or f"{_owner}:{_owner}-{_repo}-ai-gen-{hash(title) % 10000}",
} }
return await self._request("POST", f"repos/{_owner}/{_repo}/pulls", payload) return await self._request("POST", f"repos/{_owner}/{_repo}/pulls", payload)
@@ -242,11 +255,12 @@ class GiteaAPI:
"""Synchronously create a pull request.""" """Synchronously create a pull request."""
_owner = owner or self.owner _owner = owner or self.owner
_repo = repo or self.repo _repo = repo or self.repo
normalized_head = self._normalize_pull_request_head(head, _owner)
payload = { payload = {
"title": title, "title": title,
"body": body, "body": body,
"base": base, "base": base,
"head": head or f"{_owner}-{_repo}-ai-gen-{hash(title) % 10000}", "head": normalized_head or f"{_owner}:{_owner}-{_repo}-ai-gen-{hash(title) % 10000}",
} }
return self._request_sync("POST", f"repos/{_owner}/{_repo}/pulls", payload) return self._request_sync("POST", f"repos/{_owner}/{_repo}/pulls", payload)

View File

@@ -593,6 +593,16 @@ class AgentOrchestrator:
f"Prompt: {self.prompt_text or self.description}\n\n" f"Prompt: {self.prompt_text or self.description}\n\n"
f"Branch: {self.branch_name}" f"Branch: {self.branch_name}"
) )
pull_request_debug = self.ui_manager.ui_data.setdefault('git', {}).setdefault('pull_request_debug', {})
pull_request_request = {
'owner': self.repo_owner,
'repo': self.repo_name,
'title': title,
'body': body,
'base': 'main',
'head': self.gitea_api._normalize_pull_request_head(self.branch_name, self.repo_owner) or self.branch_name,
}
pull_request_debug['request'] = pull_request_request
result = await self.gitea_api.create_pull_request( result = await self.gitea_api.create_pull_request(
title=title, title=title,
body=body, body=body,
@@ -601,7 +611,9 @@ class AgentOrchestrator:
base='main', base='main',
head=self.branch_name, head=self.branch_name,
) )
pull_request_debug['response'] = result
if result.get('error'): if result.get('error'):
pull_request_debug['status'] = 'error'
raise RuntimeError(f"Unable to create pull request: {result.get('error')}") raise RuntimeError(f"Unable to create pull request: {result.get('error')}")
pr_number = result.get('number') or result.get('id') or 0 pr_number = result.get('number') or result.get('id') or 0
@@ -616,6 +628,8 @@ class AgentOrchestrator:
'merged': bool(result.get('merged')), 'merged': bool(result.get('merged')),
'pr_state': result.get('state', 'open'), 'pr_state': result.get('state', 'open'),
} }
pull_request_debug['status'] = 'created'
pull_request_debug['resolved'] = pr_data
if self.db_manager and self.history: if self.db_manager and self.history:
self.db_manager.save_pr_data(self.history.id, pr_data) self.db_manager.save_pr_data(self.history.id, pr_data)
self.active_pull_request = self.db_manager.get_open_pull_request(project_id=self.project_id) if self.db_manager else pr_data self.active_pull_request = self.db_manager.get_open_pull_request(project_id=self.project_id) if self.db_manager else pr_data

View File

@@ -261,6 +261,21 @@ def _render_generation_diagnostics(ui_data: dict | None) -> None:
ui.label(f"Remote push error: {git_debug['remote_error']}").classes('factory-code') ui.label(f"Remote push error: {git_debug['remote_error']}").classes('factory-code')
if git_debug.get('error'): if git_debug.get('error'):
ui.label(f"Git error: {git_debug['error']}").classes('factory-code') ui.label(f"Git error: {git_debug['error']}").classes('factory-code')
pull_request_debug = git_debug.get('pull_request_debug') if isinstance(git_debug.get('pull_request_debug'), dict) else {}
if pull_request_debug:
ui.label('Pull request creation').style('font-weight: 700; color: #2f241d;')
if pull_request_debug.get('status'):
ui.label(str(pull_request_debug['status'])).classes('factory-chip')
if pull_request_debug.get('request'):
with ui.expansion('PR request payload').classes('w-full q-mt-sm'):
ui.label(json.dumps(pull_request_debug['request'], indent=2, sort_keys=True)).classes('factory-code')
if pull_request_debug.get('response'):
with ui.expansion('PR API response').classes('w-full q-mt-sm'):
ui.label(json.dumps(pull_request_debug['response'], indent=2, sort_keys=True)).classes('factory-code')
if pull_request_debug.get('resolved'):
resolved = pull_request_debug['resolved']
if resolved.get('pr_url'):
ui.link('Open pull request', resolved['pr_url'], new_tab=True).classes('factory-code')
def _render_timeline(events: list[dict]) -> None: def _render_timeline(events: list[dict]) -> None:

View File

@@ -241,6 +241,17 @@ def _serialize_project_log(log: ProjectLog) -> dict:
} }
def _ensure_summary_mentions_pull_request(summary_message: str, pull_request: dict | None) -> str:
"""Append the pull request URL to chat summaries when one exists."""
if not isinstance(pull_request, dict):
return summary_message
pr_url = (pull_request.get('pr_url') or '').strip()
if not pr_url or pr_url in summary_message:
return summary_message
separator = '' if summary_message.endswith(('.', '!', '?')) else '.'
return f"{summary_message}{separator} Review PR: {pr_url}"
def _serialize_system_log(log: SystemLog) -> dict: def _serialize_system_log(log: SystemLog) -> dict:
"""Serialize a system log row.""" """Serialize a system log row."""
return { return {
@@ -391,6 +402,7 @@ async def _run_generation(
'logs': [log.get('message', '') for log in response_data.get('logs', []) if isinstance(log, dict)], 'logs': [log.get('message', '') for log in response_data.get('logs', []) if isinstance(log, dict)],
} }
summary_message, summary_trace = await ChangeSummaryGenerator().summarize_with_trace(summary_context) summary_message, summary_trace = await ChangeSummaryGenerator().summarize_with_trace(summary_context)
summary_message = _ensure_summary_mentions_pull_request(summary_message, response_data.get('pull_request'))
if orchestrator.db_manager and orchestrator.history and orchestrator.prompt_audit: if orchestrator.db_manager and orchestrator.history and orchestrator.prompt_audit:
orchestrator.db_manager.log_llm_trace( orchestrator.db_manager.log_llm_trace(
project_id=project_id, project_id=project_id,