Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/agent_rules_kit/governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@
r".{0,100}\b(check|runtime|scan|scanning|audit|analyze|analysis|validation|validate)\b",
re.IGNORECASE,
),
re.compile(
r"\b(use|call|invoke|query)\b"
r".{0,80}\b(Claude API|Anthropic API|OpenAI API|Gemini API|ChatGPT API|LLM API|remote LLM|external LLM)\b",
re.IGNORECASE,
),
re.compile(
r"\b(validator|linter|tool|CLI|command|check|runtime|execution)\b"
r".{0,120}\b(depends on|requires?|needs?|uses?|using|must use|must call)\b"
Expand All @@ -147,6 +152,12 @@
r".{0,120}\b(during execution|at runtime|runtime|for validation|to validate|for analysis|to analyze)\b",
re.IGNORECASE,
),
re.compile(
r"\b(validat(?:e|es|ing|ion)|verif(?:y|ies|ying|ication)|check(?:s|ing)?|analyz(?:e|es|ing|sis))\b"
r".{0,80}\b(via|using|through|with|by calling|by querying)\b"
r".{0,80}\b(Claude(?:\s+API)?|Anthropic(?:\s+API)?|OpenAI(?:\s+API)?|Gemini(?:\s+API)?|ChatGPT|LLM API|remote API|external API)\b",
re.IGNORECASE,
),
re.compile(
r"\b(runtime|check|scan|scanning|audit|analyze|analysis|validation|validate)\b"
r".{0,120}\b(requires?|needs?|must have|depends on)\b"
Expand All @@ -163,7 +174,7 @@
),
re.compile(
r"\b(does not|do not|don't|must not|should not|never|avoid|avoids|no)\b"
r".{0,140}\b(call|use|depend|send|upload|post|transmit|share)\b"
r".{0,140}\b(call|use|depend|requires?|needs?|rely|relies|send|upload|post|transmit|share)\b"
r".{0,140}\b(network|LLMs?|OpenAI|Anthropic|Claude|Gemini|ChatGPT|external APIs?|remote services?)\b",
re.IGNORECASE,
),
Expand Down
53 changes: 53 additions & 0 deletions tests/test_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,59 @@ def test_reports_runtime_network_llm_dependency_guidance(self) -> None:
self.assertEqual([finding.line for finding in findings], [5, 6, 7, 8, 9, 10])
self.assertEqual([finding.path for finding in findings], ["AGENTS.md"] * 6)

def test_reports_gold_runtime_api_phrase_variants(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
repository = Path(temporary_directory)
(repository / "AGENTS.md").write_text(
"\n".join(
[
"# AGENTS.md",
"",
"Rules:",
"",
"- use the Claude API for validation.",
"- call the OpenAI API to check results.",
"- This check requires the OpenAI API.",
"- analysis needs Claude API to run.",
"- validate changes via Claude API.",
"- verify output using OpenAI API.",
]
)
+ "\n",
encoding="utf-8",
)

instruction_files = discover_instruction_files(repository)
findings = find_runtime_network_llm_dependency_findings(repository, instruction_files)

self.assertEqual([finding.rule_id for finding in findings], ["AIRK-GOV005"] * 6)
self.assertEqual([finding.line for finding in findings], [5, 6, 7, 8, 9, 10])
self.assertEqual([finding.path for finding in findings], ["AGENTS.md"] * 6)

def test_ignores_adjacent_negative_runtime_api_requirement_guidance(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
repository = Path(temporary_directory)
(repository / "AGENTS.md").write_text(
"\n".join(
[
"# AGENTS.md",
"",
"Rules:",
"",
"- Do not require Claude API or external APIs at runtime.",
"- This check requires the OpenAI API.",
]
)
+ "\n",
encoding="utf-8",
)

instruction_files = discover_instruction_files(repository)
findings = find_runtime_network_llm_dependency_findings(repository, instruction_files)

self.assertEqual(findings, ())


def test_ignores_safe_or_human_reviewed_network_llm_guidance(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
repository = Path(temporary_directory)
Expand Down