2 Commits
0.9.0 ... 0.9.1

Author SHA1 Message Date
3175c53504 release: version 0.9.1 🚀
All checks were successful
Upload Python Package / Create Release (push) Successful in 10s
Upload Python Package / deploy (push) Successful in 55s
2026-04-11 11:37:22 +02:00
29cf2aa6bd fix: better repo name generation, refs NOISSUE 2026-04-11 11:37:19 +02:00
4 changed files with 60 additions and 3 deletions

View File

@@ -4,6 +4,14 @@ Changelog
(unreleased)
------------
Fix
~~~
- Better repo name generation, refs NOISSUE. [Simon Diesenreiter]
0.9.0 (2026-04-11)
------------------
- Feat: editable guardrails, refs NOISSUE. [Simon Diesenreiter]

View File

@@ -1 +1 @@
0.9.0
0.9.1

View File

@@ -18,6 +18,14 @@ except ImportError:
class RequestInterpreter:
"""Use Ollama to turn free-form text into a structured software request."""
REQUEST_PREFIX_WORDS = {
'a', 'an', 'app', 'application', 'build', 'create', 'dashboard', 'develop', 'design', 'for', 'generate',
'internal', 'make', 'me', 'modern', 'need', 'new', 'our', 'platform', 'please', 'project', 'service',
'simple', 'site', 'start', 'system', 'the', 'tool', 'us', 'want', 'web', 'website', 'with',
}
REPO_NOISE_WORDS = REQUEST_PREFIX_WORDS | {'and', 'from', 'into', 'on', 'that', 'this', 'to'}
def __init__(self, ollama_url: str | None = None, model: str | None = None):
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
self.model = model or settings.OLLAMA_MODEL
@@ -301,6 +309,7 @@ class RequestInterpreter:
"""Normalize a candidate project name into a readable title."""
cleaned = re.sub(r'[^A-Za-z0-9\s-]+', ' ', raw_name).strip(' -')
cleaned = re.sub(r'\s+', ' ', cleaned)
cleaned = self._trim_request_prefix(cleaned)
special_upper = {'api', 'crm', 'erp', 'cms', 'hr', 'it', 'ui', 'qa'}
words = []
for word in cleaned.split()[:6]:
@@ -308,14 +317,49 @@ class RequestInterpreter:
words.append(lowered.upper() if lowered in special_upper else lowered.capitalize())
return ' '.join(words) or 'Generated Project'
def _trim_request_prefix(self, candidate: str) -> str:
"""Remove leading request phrasing from model-produced names and slugs."""
tokens = [token for token in re.split(r'[-\s]+', candidate or '') if token]
while tokens and tokens[0].lower() in self.REQUEST_PREFIX_WORDS:
tokens.pop(0)
trimmed = ' '.join(tokens).strip()
return trimmed or candidate.strip()
def _derive_repo_name(self, project_name: str) -> str:
"""Derive a repository slug from a human-readable project name."""
preferred = (project_name or 'project').strip().lower().replace(' ', '-')
preferred_name = self._trim_request_prefix((project_name or 'project').strip())
preferred = preferred_name.lower().replace(' ', '-')
sanitized = ''.join(ch if ch.isalnum() or ch in {'-', '_'} else '-' for ch in preferred)
while '--' in sanitized:
sanitized = sanitized.replace('--', '-')
return sanitized.strip('-') or 'project'
def _should_use_repo_name_candidate(self, candidate: str, project_name: str) -> bool:
"""Return whether a model-proposed repo slug is concise enough to trust directly."""
cleaned = self._trim_request_prefix(re.sub(r'[^A-Za-z0-9\s_-]+', ' ', candidate or '').strip())
if not cleaned:
return False
candidate_tokens = [token.lower() for token in re.split(r'[-\s_]+', cleaned) if token]
if not candidate_tokens:
return False
if len(candidate_tokens) > 6:
return False
noise_count = sum(1 for token in candidate_tokens if token in self.REPO_NOISE_WORDS)
if noise_count >= 2:
return False
if len('-'.join(candidate_tokens)) > 40:
return False
project_tokens = {
token.lower()
for token in re.split(r'[-\s_]+', project_name or '')
if token and token.lower() not in self.REPO_NOISE_WORDS
}
if project_tokens:
overlap = sum(1 for token in candidate_tokens if token in project_tokens)
if overlap == 0:
return False
return True
def _ensure_unique_repo_name(self, repo_name: str, reserved_names: set[str]) -> str:
"""Choose a repository slug that does not collide with tracked or remote repositories."""
base_name = self._derive_repo_name(repo_name)
@@ -329,7 +373,10 @@ class RequestInterpreter:
def _normalize_project_identity(self, payload: dict, fallback_name: str) -> tuple[str, str]:
"""Normalize model-proposed project and repository naming."""
project_name = self._humanize_name(str(payload.get('project_name') or payload.get('name') or fallback_name))
repo_name = self._derive_repo_name(str(payload.get('repo_name') or project_name))
repo_candidate = str(payload.get('repo_name') or '').strip()
repo_name = self._derive_repo_name(project_name)
if repo_candidate and self._should_use_repo_name_candidate(repo_candidate, project_name):
repo_name = self._derive_repo_name(repo_candidate)
return project_name, repo_name
def _heuristic_fallback(self, prompt_text: str, context: dict | None = None) -> tuple[dict, dict]:

View File

@@ -798,6 +798,7 @@ def get_llm_prompt_settings(db: DbSession):
@app.put('/llm/prompts/{prompt_key}')
def update_llm_prompt_setting(prompt_key: str, request: LLMPromptSettingUpdateRequest, db: DbSession):
"""Persist one editable LLM prompt override into the database."""
database_module.init_db()
result = DatabaseManager(db).save_llm_prompt_setting(prompt_key, request.value, actor='api')
if result.get('status') == 'error':
raise HTTPException(status_code=400, detail=result.get('message', 'Prompt save failed'))
@@ -807,6 +808,7 @@ def update_llm_prompt_setting(prompt_key: str, request: LLMPromptSettingUpdateRe
@app.delete('/llm/prompts/{prompt_key}')
def reset_llm_prompt_setting(prompt_key: str, db: DbSession):
"""Reset one editable LLM prompt override back to the environment/default value."""
database_module.init_db()
result = DatabaseManager(db).reset_llm_prompt_setting(prompt_key, actor='api')
if result.get('status') == 'error':
raise HTTPException(status_code=400, detail=result.get('message', 'Prompt reset failed'))