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
89 changes: 68 additions & 21 deletions src/agent_rules_kit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from agent_rules_kit.discovery import InstructionFile, discover_instruction_files
from agent_rules_kit.redaction import redact_secret_like_values

OUTPUT_FORMATS = ("console", "json")
OUTPUT_FORMATS = ("console", "json", "markdown")


def build_parser() -> argparse.ArgumentParser:
Expand Down Expand Up @@ -69,34 +69,28 @@ def _run_check(repository_root: Path, *, output_format: str = "console") -> int:
try:
instruction_files = discover_instruction_files(repository_root)
except ValueError as error:
message = redact_secret_like_values(str(error))
payload = _build_check_error_payload(repository_root, error)

if output_format == "json":
_print_json(
{
"command": "check",
"status": "error",
"repository": redact_secret_like_values(str(repository_root)),
"instruction_files": [],
"summary": {
"supported_instruction_file_count": 0,
},
"error": {
"message": message,
},
}
)
_print_json(payload)
elif output_format == "markdown":
_print_markdown(payload)
else:
print(f"ERROR: {message}", file=sys.stderr)
print(f"ERROR: {payload['error']['message']}", file=sys.stderr)

return 2

status = "ok" if instruction_files else "no_instruction_files"
payload = _build_check_payload(repository_root, instruction_files, status=status)

if output_format == "json":
status = "ok" if instruction_files else "no_instruction_files"
_print_json(_build_check_payload(repository_root, instruction_files, status=status))
return 0 if instruction_files else 1
_print_json(payload)
elif output_format == "markdown":
_print_markdown(payload)
else:
return _print_console_check(repository_root, instruction_files)

return _print_console_check(repository_root, instruction_files)
return 0 if instruction_files else 1


def _print_console_check(
Expand Down Expand Up @@ -140,9 +134,62 @@ def _build_check_payload(
}


def _build_check_error_payload(
repository_root: Path,
error: ValueError,
) -> dict[str, object]:
return {
"command": "check",
"status": "error",
"repository": redact_secret_like_values(str(repository_root)),
"instruction_files": [],
"summary": {
"supported_instruction_file_count": 0,
},
"error": {
"message": redact_secret_like_values(str(error)),
},
}


def _print_json(payload: dict[str, object]) -> None:
print(json.dumps(payload, indent=2, sort_keys=True))


def _print_markdown(payload: dict[str, object]) -> None:
print("# agent-rules-kit check")
print()
print(f"- Repository: {_markdown_value(str(payload['repository']))}")
print(f"- Status: {_markdown_value(str(payload['status']))}")
print(
"- Supported instruction files: "
f"{payload['summary']['supported_instruction_file_count']}"
)

error = payload["error"]
if error is not None:
print()
print(f"Error: {_markdown_value(str(error['message']))}")
return

instruction_files = payload["instruction_files"]
if not instruction_files:
print()
print("No supported agent instruction files found.")
return

print()
print("| Path | Kind |")
print("| --- | --- |")
for instruction_file in instruction_files:
path = _markdown_value(str(instruction_file["path"]))
kind = _markdown_value(str(instruction_file["kind"]))
print(f"| {path} | {kind} |")


def _markdown_value(value: str) -> str:
return redact_secret_like_values(value).replace("|", "\\|").replace("\n", " ")


if __name__ == "__main__":
raise SystemExit(main())
92 changes: 92 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,98 @@ def test_check_json_redacts_secret_like_repository_values(self) -> None:
self.assertNotIn(secret_like_path.name, text)
self.assertEqual(payload["status"], "error")

def test_check_reports_discovered_instruction_files_as_markdown(self) -> None:
output = io.StringIO()

with redirect_stdout(output):
exit_code = main(
[
"check",
str(FIXTURE_ROOT / "multi-agent-overlap"),
"--format",
"markdown",
]
)

text = output.getvalue()

self.assertEqual(exit_code, 0)
self.assertIn("# agent-rules-kit check", text)
self.assertIn("- Status: ok", text)
self.assertIn("- Supported instruction files: 6", text)
self.assertIn("| Path | Kind |", text)
self.assertIn("| AGENTS.md | agents |", text)
self.assertIn("| CLAUDE.md | claude |", text)
self.assertIn("| GEMINI.md | gemini |", text)
self.assertIn("| .cursor/rules/agent-rules.mdc | cursor-rule |", text)
self.assertIn("| .github/copilot-instructions.md | copilot |", text)
self.assertIn(
"| .github/instructions/agents.instructions.md | github-instruction |",
text,
)

def test_check_markdown_returns_one_when_no_instruction_files_are_found(self) -> None:
output = io.StringIO()

with redirect_stdout(output):
exit_code = main(
[
"check",
str(FIXTURE_ROOT / "empty-repo"),
"--format",
"markdown",
]
)

text = output.getvalue()

self.assertEqual(exit_code, 1)
self.assertIn("# agent-rules-kit check", text)
self.assertIn("- Status: no_instruction_files", text)
self.assertIn("- Supported instruction files: 0", text)
self.assertIn("No supported agent instruction files found.", text)

def test_check_markdown_returns_two_for_invalid_repository_root(self) -> None:
output = io.StringIO()

with redirect_stdout(output):
exit_code = main(
[
"check",
str(FIXTURE_ROOT / "missing-repo"),
"--format",
"markdown",
]
)

text = output.getvalue()

self.assertEqual(exit_code, 2)
self.assertIn("# agent-rules-kit check", text)
self.assertIn("- Status: error", text)
self.assertIn("Error: repository root does not exist:", text)

def test_check_markdown_redacts_secret_like_repository_values(self) -> None:
output = io.StringIO()
secret_like_path = FIXTURE_ROOT / ("ghp_" + ("B" * 36))

with redirect_stdout(output):
exit_code = main(
[
"check",
str(secret_like_path),
"--format",
"markdown",
]
)

text = output.getvalue()

self.assertEqual(exit_code, 2)
self.assertIn("[REDACTED]", text)
self.assertNotIn(secret_like_path.name, text)
self.assertIn("- Status: error", text)


if __name__ == "__main__":
unittest.main()
Loading