From 75cb56ef336e1c105b4464ce32b9522690959fda Mon Sep 17 00:00:00 2001 From: Agent G <326687097+readyagentsdev@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:05:02 +0300 Subject: [PATCH] fix(cli-called): report a zero-match max_count: 0 guard as unproven A max_count: 0 guard that matched nothing used the same detail as a guard the agent respected, so a typo in verb, tool or flags was invisible. Emit a distinct 'guard unproven' detail for that case. The score does not change. Closes #122 Signed-off-by: Agent G <326687097+readyagentsdev@users.noreply.github.com> --- src/coder_eval/criteria/cli_called.py | 7 ++++++- tests/test_cli_called_criterion.py | 13 ++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/coder_eval/criteria/cli_called.py b/src/coder_eval/criteria/cli_called.py index 539b4131a..a8f2f9f70 100644 --- a/src/coder_eval/criteria/cli_called.py +++ b/src/coder_eval/criteria/cli_called.py @@ -170,7 +170,12 @@ def _check_impl( facets.append(f"flags={sorted(criterion.flags)}") wanted = ", ".join(facets) - if score == 1.0: + if score == 1.0 and count == 0 and criterion.max_count == 0: + details = ( + f"0 invocation(s) matched ({wanted}); guard unproven: a typo in verb, tool or flags " + "would also match nothing" + ) + elif score == 1.0: details = f"{count} invocation(s) matched ({wanted}); satisfies {bound}" elif not within_lower: # A bare count sends the reader to the sandbox; this criterion exists diff --git a/tests/test_cli_called_criterion.py b/tests/test_cli_called_criterion.py index 8667b423e..4de0f96b9 100644 --- a/tests/test_cli_called_criterion.py +++ b/tests/test_cli_called_criterion.py @@ -237,7 +237,18 @@ def test_max_count_zero_is_the_negative_guard(self, sandbox_with_log): min_count=0, max_count=0, ) - assert SuccessChecker(sandbox).check(forbidden).score == 1.0 + result = SuccessChecker(sandbox).check(forbidden) + assert result.score == 1.0 + assert "guard unproven" in (result.details or "") + assert "satisfies" not in (result.details or "") + + def test_positive_match_keeps_satisfies_wording(self, sandbox_with_log): + sandbox, sandbox_dir = sandbox_with_log + _write_log(sandbox_dir, [_call(["ixp", "fields", "rename", "proj-1"])]) + criterion = CliCalledCriterion(description="renamed the field", log=LOG, verb="ixp fields rename") + result = SuccessChecker(sandbox).check(criterion) + assert result.score == 1.0 + assert result.details == "1 invocation(s) matched (verb='ixp fields rename'); satisfies min_count=1" def test_max_count_zero_fails_when_called(self, sandbox_with_log): sandbox, sandbox_dir = sandbox_with_log