Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/apm_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def ensure_config_exists():
os.makedirs(CONFIG_DIR)

if not os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "w") as f:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good fix. Note that an AST scan of the codebase found five more text-mode open() calls that still lack explicit encoding='utf-8':

File Line Operation
adapters/client/codex.py 65 toml.dump() — write config
adapters/client/codex.py 80 toml.load() — read config
adapters/client/copilot.py 68 json.dump() — write config
adapters/client/copilot.py 83 json.load() — read config
deps/github_downloader.py 184 write empty gitconfig

Since the PR title says "Standardize … across the codebase," it would be great to cover these too (same pattern, same Windows risk).

json.dump({"default_client": "vscode"}, f)


Expand All @@ -32,7 +32,7 @@ def get_config():
if _config_cache is not None:
return _config_cache
ensure_config_exists()
with open(CONFIG_FILE, "r") as f:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
_config_cache = json.load(f)
return _config_cache

Expand All @@ -53,7 +53,7 @@ def update_config(updates):
config = get_config()
config.update(updates)

with open(CONFIG_FILE, "w") as f:
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
_invalidate_config_cache()

Expand Down
6 changes: 3 additions & 3 deletions src/apm_cli/core/script_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def _auto_compile_prompts(
compiled_prompt_files.append(prompt_file)

# Read the compiled content
with open(compiled_path, "r") as f:
with open(compiled_path, "r", encoding="utf-8") as f:
compiled_content = f.read().strip()

# Check if this is a runtime command (copilot, codex, llm) before transformation
Expand Down Expand Up @@ -916,7 +916,7 @@ def compile(self, prompt_file: str, params: Dict[str, str]) -> str:
# Now ensure compiled directory exists
self.compiled_dir.mkdir(parents=True, exist_ok=True)

with open(prompt_path, "r") as f:
with open(prompt_path, "r", encoding="utf-8") as f:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core fix for #604. Consider adding a small test that round-trips a non-ASCII string (e.g. CJK characters) through PromptCompiler.compile() to lock in the fix — the existing test_cli_encoding.py only covers console stream reconfiguration, not file I/O.

content = f.read()

# Parse frontmatter and content
Expand All @@ -939,7 +939,7 @@ def compile(self, prompt_file: str, params: Dict[str, str]) -> str:
output_path = self.compiled_dir / output_name

# Write compiled content
with open(output_path, "w") as f:
with open(output_path, "w", encoding="utf-8") as f:
f.write(compiled_content)

return str(output_path)
Expand Down
10 changes: 5 additions & 5 deletions src/apm_cli/marketplace/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ def _read_cache(name: str) -> Optional[Dict]:
if not os.path.exists(data_path) or not os.path.exists(meta_path):
return None
try:
with open(meta_path, "r") as f:
with open(meta_path, "r", encoding="utf-8") as f:
meta = json.load(f)
fetched_at = meta.get("fetched_at", 0)
ttl = meta.get("ttl_seconds", _CACHE_TTL_SECONDS)
if time.time() - fetched_at > ttl:
return None # Expired
with open(data_path, "r") as f:
with open(data_path, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError, KeyError) as exc:
logger.debug("Cache read failed for '%s': %s", name, exc)
Expand All @@ -90,7 +90,7 @@ def _read_stale_cache(name: str) -> Optional[Dict]:
if not os.path.exists(data_path):
return None
try:
with open(data_path, "r") as f:
with open(data_path, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError):
return None
Expand All @@ -101,9 +101,9 @@ def _write_cache(name: str, data: Dict) -> None:
data_path = _cache_data_path(name)
meta_path = _cache_meta_path(name)
try:
with open(data_path, "w") as f:
with open(data_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
with open(meta_path, "w") as f:
with open(meta_path, "w", encoding="utf-8") as f:
json.dump(
{"fetched_at": time.time(), "ttl_seconds": _CACHE_TTL_SECONDS},
f,
Expand Down
6 changes: 3 additions & 3 deletions src/apm_cli/marketplace/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _ensure_file() -> str:
ensure_config_exists()
path = _marketplaces_path()
if not os.path.exists(path):
with open(path, "w") as f:
with open(path, "w", encoding="utf-8") as f:
json.dump({"marketplaces": []}, f, indent=2)
return path

Expand All @@ -48,7 +48,7 @@ def _load() -> List[MarketplaceSource]:

path = _ensure_file()
try:
with open(path, "r") as f:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
except (json.JSONDecodeError, OSError) as exc:
logger.warning("Failed to read %s: %s", path, exc)
Expand All @@ -71,7 +71,7 @@ def _save(sources: List[MarketplaceSource]) -> None:
path = _ensure_file()
data = {"marketplaces": [s.to_dict() for s in sources]}
tmp = path + ".tmp"
with open(tmp, "w") as f:
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
os.replace(tmp, path)
_registry_cache = list(sources)
Expand Down
2 changes: 1 addition & 1 deletion src/apm_cli/models/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def from_path(cls, plugin_path: Path) -> "Plugin":
if metadata_file is None:
raise FileNotFoundError(f"Plugin metadata not found in any expected location: {plugin_path}")

with open(metadata_file, "r") as f:
with open(metadata_file, "r", encoding="utf-8") as f:
metadata_dict = json.load(f)

metadata = PluginMetadata.from_dict(metadata_dict)
Expand Down
2 changes: 1 addition & 1 deletion src/apm_cli/runtime/copilot_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def get_mcp_servers(self) -> Dict[str, Any]:
return {}

try:
with open(mcp_config_path, 'r') as f:
with open(mcp_config_path, 'r', encoding="utf-8") as f:
config = json.load(f)
return config.get('servers', {})
except Exception as e:
Expand Down
Loading