From 41e4365cd47193756e264a6b1a4cfdefa004ebd8 Mon Sep 17 00:00:00 2001 From: Victor Kuznetsov Date: Thu, 28 May 2026 14:16:14 -0700 Subject: [PATCH] fix(identify): explain the unknown verdict inline (#22) A bare "unknown" verdict reads as the tool being broken. Print a one-line note right under the verdict explaining that no locally-readable AI signal was found, that this is not the same as clean (metadata is often stripped), and that SynthID-class pixel watermarks have no local detector. The why was previously only in the caveats section below. Co-Authored-By: Claude Opus 4.7 --- src/remove_ai_watermarks/cli.py | 8 ++++++++ tests/test_cli.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/remove_ai_watermarks/cli.py b/src/remove_ai_watermarks/cli.py index 4e85c31..4bcbf41 100644 --- a/src/remove_ai_watermarks/cli.py +++ b/src/remove_ai_watermarks/cli.py @@ -626,6 +626,14 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo console.print(f"\n Verdict: {verdict} [dim](confidence: {report.confidence})[/]") console.print(f" Platform: {report.platform or '[dim]undetermined[/]'}") + if report.is_ai_generated is None: + console.print( + " [dim]No locally-readable AI signal found. This is not the same as 'clean': " + "metadata is often stripped by re-encoding, screenshots, or upload, and SynthID-class " + "pixel watermarks (Gemini / Nano Banana / gpt-image) have no local detector. " + "See caveats below.[/]" + ) + if report.integrity_clashes: console.print("\n [bold red]⚠ Integrity clash[/] [dim](provenance signals contradict each other)[/]") for clash in report.integrity_clashes: diff --git a/tests/test_cli.py b/tests/test_cli.py index ecea89e..b47428c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -375,6 +375,14 @@ class TestIdentifyCommand: assert result.exit_code == 0 assert "unknown" in result.output + def test_identify_unknown_explains_why(self, runner, tmp_clean_png): + # An unknown verdict must explain itself inline (issue #22: users read a bare + # "unknown" as the tool being broken) rather than only in the caveats section. + result = runner.invoke(main, ["identify", str(tmp_clean_png), "--no-visible"]) + assert result.exit_code == 0 + assert "No locally-readable AI signal found" in result.output + assert "not the same as 'clean'" in result.output + def test_identify_ai_png_reports_platform(self, runner, tmp_png_with_ai_metadata): result = runner.invoke(main, ["identify", str(tmp_png_with_ai_metadata), "--no-visible"]) assert result.exit_code == 0