mirror of
https://github.com/github/spec-kit.git
synced 2026-03-23 13:53:08 +00:00
fix(cli): add allow_unicode=True and encoding="utf-8" to YAML I/O (#1936)
None of the yaml.dump() calls specify allow_unicode=True, causing non-ASCII characters in extension descriptions to be escaped to \uXXXX sequences in generated .agent.md frontmatter and config files. Add allow_unicode=True to all 6 yaml.dump() call sites, and encoding="utf-8" to all corresponding write_text() and read_text() calls to ensure consistent UTF-8 handling across platforms.
This commit is contained in:
@@ -3042,7 +3042,7 @@ def preset_catalog_add(
|
|||||||
# Load existing config
|
# Load existing config
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
try:
|
try:
|
||||||
config = yaml.safe_load(config_path.read_text()) or {}
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error:[/red] Failed to read {config_path}: {e}")
|
console.print(f"[red]Error:[/red] Failed to read {config_path}: {e}")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -3070,7 +3070,7 @@ def preset_catalog_add(
|
|||||||
})
|
})
|
||||||
|
|
||||||
config["catalogs"] = catalogs
|
config["catalogs"] = catalogs
|
||||||
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
|
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
||||||
|
|
||||||
install_label = "install allowed" if install_allowed else "discovery only"
|
install_label = "install allowed" if install_allowed else "discovery only"
|
||||||
console.print(f"\n[green]✓[/green] Added catalog '[bold]{name}[/bold]' ({install_label})")
|
console.print(f"\n[green]✓[/green] Added catalog '[bold]{name}[/bold]' ({install_label})")
|
||||||
@@ -3098,7 +3098,7 @@ def preset_catalog_remove(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = yaml.safe_load(config_path.read_text()) or {}
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except Exception:
|
except Exception:
|
||||||
console.print("[red]Error:[/red] Failed to read preset catalog config.")
|
console.print("[red]Error:[/red] Failed to read preset catalog config.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -3115,7 +3115,7 @@ def preset_catalog_remove(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
config["catalogs"] = catalogs
|
config["catalogs"] = catalogs
|
||||||
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
|
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] Removed catalog '{name}'")
|
console.print(f"[green]✓[/green] Removed catalog '{name}'")
|
||||||
if not catalogs:
|
if not catalogs:
|
||||||
@@ -3384,7 +3384,7 @@ def catalog_add(
|
|||||||
# Load existing config
|
# Load existing config
|
||||||
if config_path.exists():
|
if config_path.exists():
|
||||||
try:
|
try:
|
||||||
config = yaml.safe_load(config_path.read_text()) or {}
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error:[/red] Failed to read {config_path}: {e}")
|
console.print(f"[red]Error:[/red] Failed to read {config_path}: {e}")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -3412,7 +3412,7 @@ def catalog_add(
|
|||||||
})
|
})
|
||||||
|
|
||||||
config["catalogs"] = catalogs
|
config["catalogs"] = catalogs
|
||||||
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
|
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
||||||
|
|
||||||
install_label = "install allowed" if install_allowed else "discovery only"
|
install_label = "install allowed" if install_allowed else "discovery only"
|
||||||
console.print(f"\n[green]✓[/green] Added catalog '[bold]{name}[/bold]' ({install_label})")
|
console.print(f"\n[green]✓[/green] Added catalog '[bold]{name}[/bold]' ({install_label})")
|
||||||
@@ -3440,7 +3440,7 @@ def catalog_remove(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = yaml.safe_load(config_path.read_text()) or {}
|
config = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except Exception:
|
except Exception:
|
||||||
console.print("[red]Error:[/red] Failed to read catalog config.")
|
console.print("[red]Error:[/red] Failed to read catalog config.")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
@@ -3457,7 +3457,7 @@ def catalog_remove(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
config["catalogs"] = catalogs
|
config["catalogs"] = catalogs
|
||||||
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
|
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True), encoding="utf-8")
|
||||||
|
|
||||||
console.print(f"[green]✓[/green] Removed catalog '{name}'")
|
console.print(f"[green]✓[/green] Removed catalog '{name}'")
|
||||||
if not catalogs:
|
if not catalogs:
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ class CommandRegistrar:
|
|||||||
if not fm:
|
if not fm:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
yaml_str = yaml.dump(fm, default_flow_style=False, sort_keys=False)
|
yaml_str = yaml.dump(fm, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
||||||
return f"---\n{yaml_str}---\n"
|
return f"---\n{yaml_str}---\n"
|
||||||
|
|
||||||
def _adjust_script_paths(self, frontmatter: dict) -> dict:
|
def _adjust_script_paths(self, frontmatter: dict) -> dict:
|
||||||
|
|||||||
@@ -975,8 +975,8 @@ class ExtensionCatalog:
|
|||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
data = yaml.safe_load(config_path.read_text()) or {}
|
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except (yaml.YAMLError, OSError) as e:
|
except (yaml.YAMLError, OSError, UnicodeError) as e:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
f"Failed to read catalog config {config_path}: {e}"
|
f"Failed to read catalog config {config_path}: {e}"
|
||||||
)
|
)
|
||||||
@@ -1467,8 +1467,8 @@ class ConfigManager:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return yaml.safe_load(file_path.read_text()) or {}
|
return yaml.safe_load(file_path.read_text(encoding="utf-8")) or {}
|
||||||
except (yaml.YAMLError, OSError):
|
except (yaml.YAMLError, OSError, UnicodeError):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_extension_defaults(self) -> Dict[str, Any]:
|
def _get_extension_defaults(self) -> Dict[str, Any]:
|
||||||
@@ -1659,8 +1659,8 @@ class HookExecutor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return yaml.safe_load(self.config_file.read_text()) or {}
|
return yaml.safe_load(self.config_file.read_text(encoding="utf-8")) or {}
|
||||||
except (yaml.YAMLError, OSError):
|
except (yaml.YAMLError, OSError, UnicodeError):
|
||||||
return {
|
return {
|
||||||
"installed": [],
|
"installed": [],
|
||||||
"settings": {"auto_execute_hooks": True},
|
"settings": {"auto_execute_hooks": True},
|
||||||
@@ -1675,7 +1675,8 @@ class HookExecutor:
|
|||||||
"""
|
"""
|
||||||
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
self.config_file.write_text(
|
self.config_file.write_text(
|
||||||
yaml.dump(config, default_flow_style=False, sort_keys=False)
|
yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True),
|
||||||
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_hooks(self, manifest: ExtensionManifest):
|
def register_hooks(self, manifest: ExtensionManifest):
|
||||||
|
|||||||
@@ -1062,8 +1062,8 @@ class PresetCatalog:
|
|||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
data = yaml.safe_load(config_path.read_text()) or {}
|
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except (yaml.YAMLError, OSError) as e:
|
except (yaml.YAMLError, OSError, UnicodeError) as e:
|
||||||
raise PresetValidationError(
|
raise PresetValidationError(
|
||||||
f"Failed to read catalog config {config_path}: {e}"
|
f"Failed to read catalog config {config_path}: {e}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -747,6 +747,18 @@ $ARGUMENTS
|
|||||||
assert output.endswith("---\n")
|
assert output.endswith("---\n")
|
||||||
assert "description: Test command" in output
|
assert "description: Test command" in output
|
||||||
|
|
||||||
|
def test_render_frontmatter_unicode(self):
|
||||||
|
"""Test rendering frontmatter preserves non-ASCII characters."""
|
||||||
|
frontmatter = {
|
||||||
|
"description": "Prüfe Konformität der Implementierung"
|
||||||
|
}
|
||||||
|
|
||||||
|
registrar = CommandRegistrar()
|
||||||
|
output = registrar.render_frontmatter(frontmatter)
|
||||||
|
|
||||||
|
assert "Prüfe Konformität" in output
|
||||||
|
assert "\\u" not in output
|
||||||
|
|
||||||
def test_register_commands_for_claude(self, extension_dir, project_dir):
|
def test_register_commands_for_claude(self, extension_dir, project_dir):
|
||||||
"""Test registering commands for Claude agent."""
|
"""Test registering commands for Claude agent."""
|
||||||
# Create .claude directory
|
# Create .claude directory
|
||||||
|
|||||||
Reference in New Issue
Block a user