From dbf197878d3caf06afd80036b67b1825e10131e4 Mon Sep 17 00:00:00 2001 From: CoderDeltaLAN Date: Tue, 9 Jun 2026 06:34:37 +0100 Subject: [PATCH] feat: add markdown check output --- src/agent_rules_kit/cli.py | 89 +++++++++++++++++++++++++++--------- tests/test_cli.py | 92 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 21 deletions(-) diff --git a/src/agent_rules_kit/cli.py b/src/agent_rules_kit/cli.py index 22904fc..21481a7 100644 --- a/src/agent_rules_kit/cli.py +++ b/src/agent_rules_kit/cli.py @@ -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: @@ -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( @@ -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()) diff --git a/tests/test_cli.py b/tests/test_cli.py index f43b508..4e7b9de 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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()