fix: resolve 8 bugs affecting real users

- Fix KeyError 'root' in FileGrouper when merging groups with no root-level files
- Fix changelog commit parsing breaking on multiline bodies (use %x00 separator)
- Add empty choices/content checks in OpenAI, Anthropic, OpenRouter providers
- Fix config edit crash when config is empty
- Fix validate command using wrong color for 'no staged files'
- Add type check for corrupted YAML config (non-dict returns empty dict)
This commit is contained in:
sankalp committed 2026-06-16 14:36:19 +05:45
1 parent a56fe0a6ee
commit ede489c219
8 files changed
+29 -10

No files matched your search

+1 -1
View File
@@ -120,7 +120,7 @@ def validate() -> None:
for f in staged:
typer.echo(f"- {f}")
else:
typer.secho("[i] No staged files.", fg=typer.colors.RED)
typer.secho("[i] No staged files.", fg=typer.colors.YELLOW)
@app.command("undo")
+6
View File
@@ -48,6 +48,12 @@ def edit_config(
if not key:
choices = list(config.keys())
if not choices:
typer.secho(
"No configuration keys found. Run `devgen setup config` first.",
fg=typer.colors.YELLOW,
)
raise typer.Exit()
key = questionary.select(
"Select key to update:", choices=choices, style=style
).ask()
+7 -3
View File
@@ -32,17 +32,21 @@ class ChangelogGenerator:
else:
cmd = self._resolve_range(to_ref)
try:
return run_git_command(cmd).split("\n")
output = run_git_command(cmd)
# Use %x00 as separator to avoid splitting on newlines in commit bodies
# Then filter out empty strings
return [c for c in output.split("\0") if c.strip()]
except subprocess.CalledProcessError as e:
self.logger.error(f"Git command failed: {e}")
raise RuntimeError(f"Git command failed: {e}") from e
def _log_cmd(self, range_spec: str) -> List[str]:
# Format: hash|author|date|subject|body
# Format: hash|author|date|subject|body (separated by %x00 to avoid
# splitting on newlines within commit bodies)
return [
"git",
"log",
"--format=%H|%an|%ad|%s|%b",
"--format=%H|%an|%ad|%s|%b%x00",
"--date=short",
range_spec,
]
+1 -4
View File
@@ -129,10 +129,7 @@ class FileGrouper:
parent_path = str(Path(deepest).parent)
new_key = "root" if parent_path == "." else parent_path
files = groups.pop(deepest)
if new_key in groups:
groups[new_key].extend(files)
else:
groups["root"].extend(files)
groups.setdefault(new_key, []).extend(files)
return groups
+2
View File
@@ -16,4 +16,6 @@ class AnthropicProvider(BaseProvider):
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
if not message.content:
raise RuntimeError(f"Anthropic returned no content for model {model!r}.")
return message.content[0].text or ""
+2
View File
@@ -17,4 +17,6 @@ class OpenaiProvider(BaseProvider):
messages=[{"role": "user", "content": prompt}],
**kwargs,
)
if not response.choices:
raise RuntimeError(f"OpenAI returned no choices for model {model!r}.")
return response.choices[0].message.content or ""
+3 -1
View File
@@ -22,4 +22,6 @@ class OpenrouterProvider(BaseProvider):
},
**kwargs,
)
return response.choices[0].message.content
if not response.choices:
raise RuntimeError(f"OpenRouter returned no choices for model {model!r}.")
return response.choices[0].message.content or ""
+7 -1
View File
@@ -246,7 +246,13 @@ def load_config() -> Dict[str, Any]:
try:
with config_path.open("r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
data = yaml.safe_load(f)
if not isinstance(data, dict):
print(
f"Warning: Config at {config_path} is not a dict, using defaults."
)
return {}
return data
except Exception as e:
print(f"Warning: Failed to load config from {config_path}: {e}")
return {}