@@ -220,6 +220,33 @@ def test_cli_fail_on_incomplete_exits_one_after_writing_report(
220220 assert output .exists ()
221221
222222
223+ def test_cli_fail_on_findings_exits_one_below_risk_threshold (
224+ monkeypatch : pytest .MonkeyPatch , tmp_path : Path
225+ ) -> None :
226+ """Active findings can gate automation even when aggregate risk remains low."""
227+ (tmp_path / "SKILL.md" ).write_text ("# Safe" , encoding = "utf-8" )
228+ output = tmp_path / "report.json"
229+ monkeypatch .setattr (
230+ "skillspector.cli.graph.invoke" ,
231+ lambda state , config : {
232+ "report_body" : '{"issues": []}' ,
233+ "execution_successful" : True ,
234+ "analysis_completeness" : {"is_complete" : True },
235+ "risk_score" : 0 ,
236+ "findings" : [_finding ("T1" , "active finding" )],
237+ "filtered_findings" : [_finding ("T1" , "active finding" )],
238+ },
239+ )
240+
241+ result = runner .invoke (
242+ app ,
243+ ["scan" , str (tmp_path ), "-f" , "json" , "-o" , str (output ), "--fail-on-findings" ],
244+ )
245+
246+ assert result .exit_code == 1
247+ assert output .exists ()
248+
249+
223250def test_recursive_scan_exits_two_after_writing_all_child_reports (tmp_path : Path ) -> None :
224251 """Recursive mode aggregates child execution failures after producing output."""
225252 s1 = SkillDirectory (path = tmp_path / "one" , name = "one" , relative_path = "one" )
@@ -1066,6 +1093,28 @@ def _bounded_recursive_result(label: str, *, finding_count: int = 1) -> dict[str
10661093 }
10671094
10681095
1096+ def test_recursive_fail_on_findings_exits_one_below_risk_threshold (
1097+ monkeypatch : pytest .MonkeyPatch , tmp_path : Path
1098+ ) -> None :
1099+ """Recursive scans gate on active child findings, not only aggregate score."""
1100+ skill = SkillDirectory (tmp_path / "one" , "one" , "one" )
1101+ detection = MultiSkillDetectionResult (is_multi_skill = True , skills = [skill ])
1102+ monkeypatch .setattr (
1103+ cli .graph , "invoke" , lambda * _args , ** _kwargs : _bounded_recursive_result ("one" )
1104+ )
1105+
1106+ with pytest .raises (typer .Exit ) as exit_info :
1107+ _scan_multi_skill (
1108+ detection ,
1109+ FormatChoice .json ,
1110+ None ,
1111+ no_llm = True ,
1112+ fail_on_findings = True ,
1113+ )
1114+
1115+ assert exit_info .value .exit_code == 1
1116+
1117+
10691118def test_recursive_json_uses_one_global_public_record_budget (
10701119 monkeypatch : pytest .MonkeyPatch , tmp_path : Path
10711120) -> None :
0 commit comments