fix: fall back to ASCII instinct status bars

Fixes #1855
This commit is contained in:
Affaan Mustafa 2026-05-13 02:59:58 -04:00 committed by GitHub
parent 0e169fecbc
commit d4728a0d80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 56 additions and 1 deletions

View File

@ -82,6 +82,27 @@ def _normalize_remote_url(remote_url: str) -> str:
return normalized.lower() if is_network else normalized
def _stream_can_encode(text: str, stream=None) -> bool:
stream = stream or sys.stdout
encoding = getattr(stream, "encoding", None) or sys.getdefaultencoding()
try:
text.encode(encoding)
except (LookupError, UnicodeEncodeError):
return False
return True
def _confidence_bar(confidence, stream=None) -> str:
try:
filled = int(float(confidence) * 10)
except (TypeError, ValueError):
filled = 5
filled = max(0, min(10, filled))
full, empty = ("\u2588", "\u2591") if _stream_can_encode("\u2588\u2591", stream) else ("#", ".")
return full * filled + empty * (10 - filled)
def _project_hash(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()[:12]
@ -550,7 +571,7 @@ def _print_instincts_by_domain(instincts: list[dict]) -> None:
for inst in sorted(domain_instincts, key=lambda x: -x.get('confidence', 0.5)):
conf = inst.get('confidence', 0.5)
conf_bar = '\u2588' * int(conf * 10) + '\u2591' * (10 - int(conf * 10))
conf_bar = _confidence_bar(conf)
trigger = inst.get('trigger', 'unknown trigger')
scope_tag = f"[{inst.get('scope', '?')}]"

View File

@ -45,6 +45,7 @@ _find_cross_project_instincts = _mod._find_cross_project_instincts
load_registry = _mod.load_registry
_validate_instinct_id = _mod._validate_instinct_id
_update_registry = _mod._update_registry
_confidence_bar = _mod._confidence_bar
# ─────────────────────────────────────────────
@ -642,6 +643,39 @@ def test_cmd_status_with_instincts(patch_globals, monkeypatch, capsys):
assert "GLOBAL" in out
def test_confidence_bar_uses_unicode_when_supported():
"""Confidence bars should retain block glyphs on UTF-8 streams."""
stream = SimpleNamespace(encoding="utf-8")
assert _confidence_bar(0.8, stream=stream) == "\u2588" * 8 + "\u2591" * 2
def test_confidence_bar_uses_ascii_when_stream_rejects_block_glyphs():
"""Windows cp1252 streams cannot encode block glyphs."""
stream = SimpleNamespace(encoding="cp1252")
assert _confidence_bar(0.8, stream=stream) == "########.."
def test_print_instincts_by_domain_is_cp1252_safe(monkeypatch):
"""Status rendering should not crash on Windows cp1252 stdout."""
raw = io.BytesIO()
stream = io.TextIOWrapper(raw, encoding="cp1252")
monkeypatch.setattr(_mod.sys, "stdout", stream)
_mod._print_instincts_by_domain([{
"id": "windows-safe",
"trigger": "when stdout uses cp1252",
"confidence": 0.8,
"domain": "platform",
"scope": "project",
}])
stream.flush()
out = raw.getvalue().decode("cp1252")
assert "########.." in out
assert "\u2588" not in out
assert "\u2591" not in out
def test_cmd_status_returns_int(patch_globals, monkeypatch):
"""cmd_status should always return an int."""
tree = patch_globals