From 5d594e8d7b039dafac162c9f7cf41af649031c9e Mon Sep 17 00:00:00 2001 From: Munawar Shah <93397215+mwrshah@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:57:16 +0500 Subject: [PATCH] Complete plugin setup help --- README.md | 8 +++++++- pyproject.toml | 2 +- src/codedecorum/cli.py | 9 ++++++++- tests/test_contracts.py | 7 +++++-- uv.lock | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82ab397..1edabdb 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,13 @@ For a temporary rule-and-file suppression, run `codedecorum --unlocking-with-exp ## How to write a plugin -Each immediate non-private `.py` file in a configured plugin directory exports one `RULE`. Plugin directories are non-recursive. Plugins are trusted Python and execute inside the Codedecorum process. +Store repository-specific plugins in a tracked directory and register it in the repository's `.codedecorum.toml`: + +```toml +plugin-dirs = ["codedecorum-rules"] +``` + +Relative plugin directories resolve from the configuration file's directory. Each immediate non-private `.py` file in the directory exports one `RULE`. Plugin directories are non-recursive. Plugins are trusted Python and execute inside the Codedecorum process. ### Scopes and rule units diff --git a/pyproject.toml b/pyproject.toml index e212c6c..5cebc12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "codedecorum" -version = "0.1.2" +version = "0.1.3" description = "Run code-backed policy rules across Git repository changes" readme = "README.md" license = "MIT" diff --git a/src/codedecorum/cli.py b/src/codedecorum/cli.py index 6c8b618..44a2a12 100644 --- a/src/codedecorum/cli.py +++ b/src/codedecorum/cli.py @@ -20,7 +20,14 @@ def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( - prog="codedecorum", description="Lint a repository against code-backed policy rules." + prog="codedecorum", + description="Lint a repository against code-backed policy rules.", + epilog=( + "Full README:\n" + " curl -fsSL " + "https://raw.githubusercontent.com/mwrshah/codedecorum/main/README.md" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("paths", nargs="*", type=Path) parser.add_argument("--config", type=Path, metavar="PATH") diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 08569cf..e73bf70 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -1,7 +1,6 @@ import subprocess from collections.abc import Iterable, Mapping from datetime import UTC, datetime, timedelta -from importlib.metadata import metadata from pathlib import Path, PurePosixPath import pytest @@ -274,7 +273,7 @@ def test_parameterized_guidance_contract() -> None: def test_plugin_help_prints_packaged_readme_section( capsys: pytest.CaptureFixture[str], ) -> None: - readme = metadata("codedecorum").get_payload() + readme = (Path(__file__).parents[1] / "README.md").read_text(encoding="utf-8") expected = readme[readme.index("## How to write a plugin") :].split("\n## ", 1)[0].rstrip() assert main(["--plugin-help"]) == 0 @@ -289,6 +288,10 @@ def test_plugin_help_prints_packaged_readme_section( assert "--plugin-help" in help_output assert "show the complete plugin setup and rule-authoring" in help_output assert "guide, then exit" in help_output + assert ( + "curl -fsSL https://raw.githubusercontent.com/mwrshah/codedecorum/main/README.md" + in help_output + ) def test_complete_plugin_pipeline(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: diff --git a/uv.lock b/uv.lock index 7782399..4e29018 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "codedecorum" -version = "0.1.2" +version = "0.1.3" source = { editable = "." } dependencies = [ { name = "pygments" },