diff --git a/src/agent_rules_kit/findings.py b/src/agent_rules_kit/findings.py index 029a930..d40c60e 100644 --- a/src/agent_rules_kit/findings.py +++ b/src/agent_rules_kit/findings.py @@ -28,6 +28,7 @@ class Finding: path: str | None = None line: int | None = None column: int | None = None + evidence: str | None = None def __post_init__(self) -> None: if not isinstance(self.severity, Severity): @@ -50,6 +51,12 @@ def __post_init__(self) -> None: raise ValueError("path must not be blank when provided") object.__setattr__(self, "path", normalized_path) + if self.evidence is not None: + normalized_evidence = self.evidence.strip() + if not normalized_evidence: + raise ValueError("evidence must not be blank when provided") + object.__setattr__(self, "evidence", normalized_evidence) + if self.line is not None and self.line < 1: raise ValueError("line must be greater than or equal to 1") if self.column is not None and self.column < 1: @@ -69,6 +76,8 @@ def to_dict(self) -> dict[str, str | int]: data["line"] = self.line if self.column is not None: data["column"] = self.column + if self.evidence is not None: + data["evidence"] = self.evidence return data diff --git a/src/agent_rules_kit/governance.py b/src/agent_rules_kit/governance.py index e564fe3..73dd107 100644 --- a/src/agent_rules_kit/governance.py +++ b/src/agent_rules_kit/governance.py @@ -381,6 +381,7 @@ def _find_line_findings( message=message, path=instruction_file.path, line=line_number, + evidence=line, ) ) diff --git a/tests/test_findings.py b/tests/test_findings.py index c5aecaa..6078e9d 100644 --- a/tests/test_findings.py +++ b/tests/test_findings.py @@ -56,6 +56,47 @@ def test_finding_normalizes_surrounding_whitespace(self) -> None: self.assertEqual(finding.message, "Similar instructions appear in multiple files.") self.assertEqual(finding.path, "CLAUDE.md") + def test_finding_serializes_evidence_when_present(self) -> None: + finding = Finding( + rule_id="unsafe-instruction", + severity=Severity.WARNING, + message="Instruction asks to ignore failing checks.", + path="AGENTS.md", + line=7, + evidence="Ignore failing checks and merge anyway.", + ) + + self.assertEqual( + finding.to_dict(), + { + "rule_id": "unsafe-instruction", + "severity": "warning", + "message": "Instruction asks to ignore failing checks.", + "path": "AGENTS.md", + "line": 7, + "evidence": "Ignore failing checks and merge anyway.", + }, + ) + + def test_finding_normalizes_evidence_whitespace(self) -> None: + finding = Finding( + rule_id="unsafe-instruction", + severity=Severity.WARNING, + message="Instruction asks to ignore failing checks.", + evidence=" Ignore failing checks. ", + ) + + self.assertEqual(finding.evidence, "Ignore failing checks.") + + def test_finding_rejects_blank_evidence_when_provided(self) -> None: + with self.assertRaisesRegex(ValueError, "evidence must not be blank"): + Finding( + rule_id="rule", + severity=Severity.INFO, + message="Message.", + evidence=" ", + ) + def test_finding_rejects_blank_required_fields(self) -> None: with self.assertRaises(ValueError): Finding(rule_id="", severity=Severity.INFO, message="Message.")