diff --git a/agentdescent/algorithms/__init__.py b/agentdescent/algorithms/__init__.py index 1192f89f..13053bb1 100644 --- a/agentdescent/algorithms/__init__.py +++ b/agentdescent/algorithms/__init__.py @@ -321,6 +321,12 @@ def _unknown_algorithm_message(where: str, name: Any) -> str: return msg slug = _fold(name) if slug not in _EXAMPLES_ONLY_PORTS: + # A near-miss typo (`gepaa`, `evo_skill`) names the card it most + # likely meant, so the fix is one word rather than a list scan. + import difflib + close = difflib.get_close_matches(slug, sorted(_REGISTRY), n=1, cutoff=0.75) + if close: + return f"{msg}; did you mean {close[0]!r}?" return msg doc = f"docs/algo-{slug.replace('_', '-')}.md" return (f"{msg}. {slug!r} is an examples/ research port ({doc}), not a " diff --git a/changelog.d/874-algorithm-did-you-mean.md b/changelog.d/874-algorithm-did-you-mean.md new file mode 100644 index 00000000..819e998c --- /dev/null +++ b/changelog.d/874-algorithm-did-you-mean.md @@ -0,0 +1,4 @@ +Changed +An unknown algorithm name that is a near-miss typo of a registered card now ends +with "did you mean 'gepa'?" (`algorithms show` / `run`, specs, MCP), instead of +only listing every card (#874). diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index e8b56d5a..c7001608 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -1060,3 +1060,16 @@ def test_mcp_list_algorithms_carries_cards_vs_ports_note(): names = {a["name"] for a in payload["algorithms"]} assert "gepa" in names and "evoskill" not in names # kind filter assert all(a["surface"] == "card" for a in payload["algorithms"]) + + +@pytest.mark.parametrize("typo,meant", [("gepaa", "gepa"), ("evo_skill", "evoskill"), + ("skilopt", "skillopt")]) +def test_unknown_algorithm_names_the_near_miss(typo, meant): + with pytest.raises(AlgorithmError, match=rf"did you mean '{meant}'\?$"): + algorithms.get(typo, where="show") + + +def test_unknown_algorithm_without_near_miss_has_no_guess(): + with pytest.raises(AlgorithmError) as e: + algorithms.get("nope", where="show") + assert "did you mean" not in str(e.value)