From 9ffaa18efe85fbe4e3961d0d15aba5e0ebbf27a4 Mon Sep 17 00:00:00 2001 From: Simon Diesenreiter Date: Sat, 11 Apr 2026 20:09:31 +0200 Subject: [PATCH] fix: project association improvements, refs NOISSUE --- ai_software_factory/agents/orchestrator.py | 1 + .../agents/request_interpreter.py | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ai_software_factory/agents/orchestrator.py b/ai_software_factory/agents/orchestrator.py index c2708dc..d8db7f5 100644 --- a/ai_software_factory/agents/orchestrator.py +++ b/ai_software_factory/agents/orchestrator.py @@ -133,6 +133,7 @@ class AgentOrchestrator: features=self.features, tech_stack=self.tech_stack, actor_name=self.prompt_actor, + source=self.prompt_actor, related_issue={'number': self.related_issue_number} if self.related_issue_number is not None else None, source_context=self.prompt_source_context, routing=self.prompt_routing, diff --git a/ai_software_factory/agents/request_interpreter.py b/ai_software_factory/agents/request_interpreter.py index fe71108..078512e 100644 --- a/ai_software_factory/agents/request_interpreter.py +++ b/ai_software_factory/agents/request_interpreter.py @@ -31,6 +31,11 @@ class RequestInterpreter: PLACEHOLDER_PROJECT_NAME_WORDS = { 'generated project', 'new project', 'project', 'temporary name', 'temp name', 'placeholder', 'untitled project', } + ROUTING_STOPWORDS = REPO_NOISE_WORDS | GENERIC_PROJECT_NAME_WORDS | { + 'about', 'after', 'again', 'appropriate', 'before', 'best', 'details', 'follow', 'following', 'implement', + 'integration', 'instance', 'instances', 'later', 'make', 'now', 'primary', 'primarily', 'probably', + 'remember', 'specific', 'suite', 'tearing', 'testing', 'through', 'used', 'using', 'workflow', 'workflows', + } def __init__(self, ollama_url: str | None = None, model: str | None = None): self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/') @@ -468,6 +473,7 @@ class RequestInterpreter: projects = context.get('projects', []) last_project_id = recent_history[0].get('project_id') if recent_history else None last_issue = ((recent_history[0].get('related_issue') or {}).get('number') if recent_history else None) + last_project = next((project for project in projects if project.get('project_id') == last_project_id), None) if last_project_id else None matched_project = None for project in projects: @@ -481,8 +487,24 @@ class RequestInterpreter: break if matched_project is None and not explicit_new: follow_up_tokens = ['also', 'continue', 'for this project', 'for that project', 'work on this', 'work on that', 'fix that', 'add this'] - if any(token in lowered for token in follow_up_tokens) and last_project_id: - matched_project = next((project for project in projects if project.get('project_id') == last_project_id), None) + leading_follow_up = lowered.startswith(('also', 'now', 'continue', 'remember', 'then')) + recent_overlap = 0 + if last_project is not None: + recent_prompt_text = recent_history[0].get('prompt_text') or '' + project_reference_text = ' '.join( + part for part in [ + last_project.get('name') or '', + last_project.get('description') or '', + ((last_project.get('repository') or {}).get('name') or ''), + ] + if part + ) + recent_overlap = len( + self._routing_tokens(prompt_text) + & (self._routing_tokens(recent_prompt_text) | self._routing_tokens(project_reference_text)) + ) + if last_project_id and (leading_follow_up or any(token in lowered for token in follow_up_tokens) or recent_overlap >= 2): + matched_project = last_project issue_number = referenced_issue if issue_number is None and any(token in lowered for token in ['that issue', 'this issue', 'the issue']) and last_issue is not None: issue_number = last_issue @@ -497,6 +519,14 @@ class RequestInterpreter: 'reasoning_summary': 'Heuristic routing from chat history and project names.', } + def _routing_tokens(self, text: str) -> set[str]: + """Extract meaningful tokens for heuristic continuation matching.""" + cleaned = re.sub(r'[^a-z0-9]+', ' ', (text or '').lower()) + return { + token for token in cleaned.split() + if len(token) >= 4 and token not in self.ROUTING_STOPWORDS + } + def _extract_issue_number(self, prompt_text: str) -> int | None: match = re.search(r'(?:#|issue\s+)(\d+)', prompt_text, flags=re.IGNORECASE) return int(match.group(1)) if match else None \ No newline at end of file