From 4eeec5d808d8d9211ac98578f7749df319c3442a Mon Sep 17 00:00:00 2001 From: Simon Diesenreiter Date: Sat, 11 Apr 2026 13:37:49 +0200 Subject: [PATCH] fix: repo onboarding fix, refs NOISSUE --- ai_software_factory/agents/orchestrator.py | 35 +++++++++++++++++----- ai_software_factory/database.py | 16 +--------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/ai_software_factory/agents/orchestrator.py b/ai_software_factory/agents/orchestrator.py index 3658ddf..0278950 100644 --- a/ai_software_factory/agents/orchestrator.py +++ b/ai_software_factory/agents/orchestrator.py @@ -29,6 +29,9 @@ except ImportError: class AgentOrchestrator: """Orchestrates the software generation process with full audit trail.""" + REMOTE_READY_REPOSITORY_MODES = {'project', 'onboarded'} + REMOTE_READY_REPOSITORY_STATUSES = {'created', 'exists', 'ready', 'onboarded'} + def __init__( self, project_id: str, @@ -80,6 +83,7 @@ class AgentOrchestrator: self.branch_name = self._build_pr_branch_name(project_id) self.active_pull_request = None self._gitea_username: str | None = None + existing_repository: dict | None = None hinted_issue_number = (related_issue_hint or {}).get('number') if related_issue_hint else None self.related_issue_number = hinted_issue_number if hinted_issue_number is not None else self._extract_issue_number(prompt_text) self.related_issue: dict | None = DatabaseManager._normalize_issue(related_issue_hint) @@ -110,9 +114,12 @@ class AgentOrchestrator: latest_ui = self.db_manager._get_latest_ui_snapshot_data(self.history.id) repository = latest_ui.get('repository') if isinstance(latest_ui, dict) else None if isinstance(repository, dict) and repository: + existing_repository = dict(repository) self.repo_owner = repository.get('owner') or self.repo_owner self.repo_name = repository.get('name') or self.repo_name self.repo_url = repository.get('url') or self.repo_url + git_state = latest_ui.get('git') if isinstance(latest_ui.get('git'), dict) else {} + self.branch_name = git_state.get('active_branch') or self.branch_name if self.prompt_text: self.prompt_audit = self.db_manager.log_prompt_submission( history_id=self.history.id, @@ -129,18 +136,26 @@ class AgentOrchestrator: self.ui_manager.ui_data["project_root"] = str(self.project_root) self.ui_manager.ui_data["features"] = list(self.features) self.ui_manager.ui_data["tech_stack"] = list(self.tech_stack) - self.ui_manager.ui_data["repository"] = { + repository_ui = { "owner": self.repo_owner, "name": self.repo_name, "mode": "project" if settings.use_project_repositories else "shared", "status": "pending" if settings.use_project_repositories else "shared", "provider": "gitea", } + if existing_repository: + repository_ui.update(existing_repository) + self.ui_manager.ui_data["repository"] = repository_ui if self.related_issue: self.ui_manager.ui_data["related_issue"] = self.related_issue if self.active_pull_request: self.ui_manager.ui_data["pull_request"] = self.active_pull_request + def _repository_supports_remote_delivery(self, repository: dict | None = None) -> bool: + """Return whether repository metadata supports git push and PR delivery.""" + repo = repository or self.ui_manager.ui_data.get('repository') or {} + return repo.get('mode') in self.REMOTE_READY_REPOSITORY_MODES and repo.get('status') in self.REMOTE_READY_REPOSITORY_STATUSES + def _static_files(self) -> dict[str, str]: """Files that do not need prompt-specific generation.""" return { @@ -309,6 +324,14 @@ class AgentOrchestrator: self.db_manager.attach_issue_to_prompt(self.prompt_audit.id, self.related_issue) async def _ensure_remote_repository(self) -> None: + repository = self.ui_manager.ui_data.get("repository") or {} + if self._repository_supports_remote_delivery(repository): + repository.setdefault("provider", "gitea") + repository.setdefault("status", "ready") + if repository.get("url"): + self.repo_url = repository.get("url") + self.ui_manager.ui_data["repository"] = repository + return if not settings.use_project_repositories: self.ui_manager.ui_data["repository"]["status"] = "shared" if settings.gitea_repo: @@ -400,9 +423,7 @@ class AgentOrchestrator: async def _push_branch(self, branch: str) -> dict | None: """Push a branch to the configured project repository when available.""" repository = self.ui_manager.ui_data.get('repository') or {} - if repository.get('mode') != 'project': - return None - if repository.get('status') not in {'created', 'exists', 'ready'}: + if not self._repository_supports_remote_delivery(repository): return None if not settings.gitea_token or not self.repo_owner or not self.repo_name: return None @@ -449,7 +470,7 @@ class AgentOrchestrator: self.ui_manager.ui_data['pull_request'] = self.active_pull_request return self.active_pull_request repository = self.ui_manager.ui_data.get('repository') or {} - if repository.get('mode') != 'project' or repository.get('status') not in {'created', 'exists', 'ready'}: + if not self._repository_supports_remote_delivery(repository): return None title = f"AI delivery for {self.project_name}" @@ -490,9 +511,7 @@ class AgentOrchestrator: async def _push_remote_commit(self, commit_hash: str, commit_message: str, changed_files: list[str], base_commit: str | None) -> dict | None: """Push the local commit to the provisioned Gitea repository and build browser links.""" repository = self.ui_manager.ui_data.get("repository") or {} - if repository.get("mode") != "project": - return None - if repository.get("status") not in {"created", "exists", "ready"}: + if not self._repository_supports_remote_delivery(repository): return None push_result = await self._push_branch(self.branch_name) if push_result is None: diff --git a/ai_software_factory/database.py b/ai_software_factory/database.py index b408a20..2f226ee 100644 --- a/ai_software_factory/database.py +++ b/ai_software_factory/database.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse from alembic import command from alembic.config import Config -from sqlalchemy import create_engine, event, text +from sqlalchemy import create_engine, text from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker @@ -64,20 +64,6 @@ def get_engine() -> Engine: pool_timeout=settings.DB_POOL_TIMEOUT or 30 ) - # Event listener for connection checkout (PostgreSQL only) - if not settings.use_sqlite: - @event.listens_for(engine, "checkout") - def receive_checkout(dbapi_connection, connection_record, connection_proxy): - """Log connection checkout for audit purposes.""" - if settings.LOG_LEVEL in ("DEBUG", "INFO"): - print(f"DB Connection checked out from pool") - - @event.listens_for(engine, "checkin") - def receive_checkin(dbapi_connection, connection_record): - """Log connection checkin for audit purposes.""" - if settings.LOG_LEVEL == "DEBUG": - print(f"DB Connection returned to pool") - return engine