From d9fbb2b0b2b6323d30fee372fb994365a8c761ec Mon Sep 17 00:00:00 2001 From: The Wyrd One Date: Sat, 18 Apr 2026 11:37:33 -0400 Subject: [PATCH 1/6] feat: add release-please and CI/CD workflows for PyPI publishing - Add release-please-config.json and .release-please-manifest.json to bootstrap automated releases from v0.0.0 - Add .github/workflows/release-please.yml to manage release PRs on every push to main - Add .github/workflows/release.yml to run tests and publish to PyPI via trusted publishing when a GitHub release is published - Add .github/workflows/ci.yml for continuous integration (tests, linting, type checking, install smoke test) - Reset pyproject.toml version to 0.0.0 for release-please to manage --- .github/workflows/ci.yml | 71 ++++++++++++++++++++++++++++ .github/workflows/release-please.yml | 24 ++++++++++ .github/workflows/release.yml | 54 +++++++++++++++++++++ .release-please-manifest.json | 3 ++ pyproject.toml | 4 +- release-please-config.json | 11 +++++ 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6facdf6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run tests with pytest + run: python -m pytest tests/ -v --tb=short + + code-quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Check code formatting and linting with Ruff + run: | + ruff check src/ tests/ + ruff format --check src/ tests/ + + - name: Type checking with mypy + run: mypy src/ + + install-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Test package installation + run: | + python -m pip install --upgrade pip + pip install . + + - name: Test basic import + run: python -c "import grimoire; print('Import successful')" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..637aec7 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,24 @@ +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..06f8197 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,54 @@ +name: Release + +on: + release: + types: [published] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run tests + run: python -m pytest tests/ -v + + build-and-publish: + needs: test + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # required for PyPI trusted publishing + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build package + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..46b1b67 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2a3227e..fe382f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "grimoire" -version = "0.1.0" +name = "grimoire-spec" +version = "0.0.0" description = "GRIMOIRE system definition loader and validator for tabletop RPG systems" requires-python = ">=3.11" dependencies = [ diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..39340f0 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "python", + "packages": { + ".": { + "release-type": "python", + "package-name": "grimoire-spec", + "version-file": "pyproject.toml" + } + } +} \ No newline at end of file From e646fd1408951d9ec235314fa95e427e9aa42166 Mon Sep 17 00:00:00 2001 From: The Wyrd One Date: Sat, 18 Apr 2026 11:44:25 -0400 Subject: [PATCH 2/6] fix: test failures --- tests/test_system_loader.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/test_system_loader.py b/tests/test_system_loader.py index 1010d97..265339c 100644 --- a/tests/test_system_loader.py +++ b/tests/test_system_loader.py @@ -8,7 +8,7 @@ from grimoire.models.system import System SYSTEMS_DIR = Path(__file__).parent.parent / "systems" -KNAVE_DIR = SYSTEMS_DIR / "knave_1e" +KNAVE_DIR = SYSTEMS_DIR / "knave-1e" WYRDBOUND_DIR = SYSTEMS_DIR / "wyrdbound-quickstart-1e" @@ -88,8 +88,8 @@ def test_knave_character_model_name(self, knave: System) -> None: assert knave.models["character"].name == "Knave" def test_wyrdbound_loads_all_models(self, wyrdbound: System) -> None: - # 5 model YAML files in systems/wyrdbound-quickstart-1e/models/ - assert len(wyrdbound.models) == 5 + # 7 model YAML files in systems/wyrdbound-quickstart-1e/models/ + assert len(wyrdbound.models) == 7 def test_wyrdbound_has_character_model(self, wyrdbound: System) -> None: assert "character" in wyrdbound.models @@ -114,8 +114,9 @@ def test_knave_armor_table_kind(self, knave: System) -> None: def test_knave_armor_table_has_entries(self, knave: System) -> None: assert len(knave.tables["armor"].entries) > 0 - def test_wyrdbound_has_no_tables(self, wyrdbound: System) -> None: - assert len(wyrdbound.tables) == 0 + def test_wyrdbound_loads_all_tables(self, wyrdbound: System) -> None: + # 14 table YAML files in systems/wyrdbound-quickstart-1e/tables/ (recursive) + assert len(wyrdbound.tables) == 14 # --------------------------------------------------------------------------- @@ -140,8 +141,8 @@ def test_knave_melee_compendium_entries_are_dict(self, knave: System) -> None: assert "dagger" in entries def test_wyrdbound_loads_all_compendiums(self, wyrdbound: System) -> None: - # 4 compendium YAML files in systems/wyrdbound-quickstart-1e/compendiums/ - assert len(wyrdbound.compendiums) == 4 + # 5 compendium YAML files in systems/wyrdbound-quickstart-1e/compendiums/ + assert len(wyrdbound.compendiums) == 5 def test_wyrdbound_weapons_compendium_entries_keyed_by_id( self, wyrdbound: System @@ -169,8 +170,8 @@ def test_knave_character_creation_flow_has_steps(self, knave: System) -> None: assert len(knave.flows["character_creation"].steps) > 0 def test_wyrdbound_loads_all_flows(self, wyrdbound: System) -> None: - # 3 flow YAML files in systems/wyrdbound-quickstart-1e/flows/ (recursive) - assert len(wyrdbound.flows) == 3 + # 6 flow YAML files in systems/wyrdbound-quickstart-1e/flows/ (recursive) + assert len(wyrdbound.flows) == 6 def test_wyrdbound_has_character_creation_flow(self, wyrdbound: System) -> None: assert "character_creation" in wyrdbound.flows @@ -192,8 +193,9 @@ def test_knave_has_fix_json_prompt(self, knave: System) -> None: def test_knave_fix_json_prompt_has_template(self, knave: System) -> None: assert knave.prompts["fix_json"].prompt_template - def test_wyrdbound_has_no_prompts(self, wyrdbound: System) -> None: - assert len(wyrdbound.prompts) == 0 + def test_wyrdbound_loads_all_prompts(self, wyrdbound: System) -> None: + # 1 prompt YAML file in systems/wyrdbound-quickstart-1e/prompts/ + assert len(wyrdbound.prompts) == 1 # --------------------------------------------------------------------------- From 2fba8282faa30dc09326c03823b7bba1978fd065 Mon Sep 17 00:00:00 2001 From: The Wyrd One Date: Sat, 18 Apr 2026 11:44:44 -0400 Subject: [PATCH 3/6] chore: add uv --- README.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ uv.lock | 4 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8cbdf91 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# GRIMOIRE + +**GRIMOIRE** (Generic Rule Implementation Model for Omniversal Interactive Roleplaying Engines) is a Python library for loading, validating, and working with structured tabletop RPG system definitions. + +It provides a declarative YAML-based specification format for encoding game systems — covering models, flows, tables, compendiums, prompts, and sources — and a Python loader that parses and validates those definitions into typed Python objects. + +GRIMOIRE is system-agnostic and not tied to any particular game or application. It is designed to be used as a foundation for tools that need to reason about RPG rules programmatically: AI game masters, character generators, rule validators, or any application that needs to load and execute structured game logic. + +--- + +## Installation + +To add to a uv-managed project: + +```bash +uv add grimoire-spec +``` + +Or to install directly into an environment: + +```bash +uv pip install grimoire-spec +``` + +## Quick Start + +```python +from pathlib import Path +from grimoire.loader import SystemLoader + +loader = SystemLoader() +system = loader.load(Path("systems/knave-1e")) + +print(system.name) # "Knave (1st Edition)" +print(len(system.models)) # number of loaded models +print(len(system.flows)) # number of loaded flows + +errors = system.validate() +if not errors: + print("System is valid") +``` + +See the [`examples/`](examples/) directory for more usage patterns. + +## System Definition Format + +A GRIMOIRE system is a directory containing YAML files organised by type: + +``` +my-system/ + system.yaml # root metadata, currency, attribution + models/ # data model definitions + flows/ # rule sequences and game mechanics + tables/ # random tables + compendiums/ # item/entity catalogues + prompts/ # AI prompt templates + sources/ # source material attribution +``` + +Full specification for each file type is in [`spec/`](spec/). + +## Development + +This project uses [`uv`](https://github.com/astral-sh/uv) for environment and dependency management. + +### Setup + +```bash +uv sync --extra dev +``` + +### Run tests + +```bash +uv run pytest +``` + +### Linting and formatting + +```bash +uv run ruff check src/ tests/ +uv run ruff format src/ tests/ +``` + +### Type checking + +```bash +uv run mypy src/ +``` + +### Run an example + +```bash +uv run python examples/load_system.py +``` + +## Project Structure + +``` +src/grimoire/ # library source + loader.py # SystemLoader — entry point for loading a system directory + models/ # typed Python models for each definition type +spec/ # YAML format specification documents +systems/ # bundled example systems (knave-1e, wyrdbound-quickstart-1e) +examples/ # usage examples +tests/ # test suite +``` + +## Contributing + +Contributions are welcome. Please follow the existing code style (enforced by Ruff) and ensure all tests pass before submitting a pull request. New features should be accompanied by tests and an example in `examples/`. + +## License + +See [LICENSE](LICENSE) for details. diff --git a/uv.lock b/uv.lock index 2c5f6cc..e2bc8de 100644 --- a/uv.lock +++ b/uv.lock @@ -12,8 +12,8 @@ wheels = [ ] [[package]] -name = "grimoire" -version = "0.1.0" +name = "grimoire-spec" +version = "0.0.0" source = { editable = "." } dependencies = [ { name = "pyyaml" }, From 828be247b2953c88b55758b33591facfa90bc808 Mon Sep 17 00:00:00 2001 From: The Wyrd One Date: Sat, 18 Apr 2026 11:44:55 -0400 Subject: [PATCH 4/6] chore: fix ruff check --- src/grimoire/models/system.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/grimoire/models/system.py b/src/grimoire/models/system.py index 12c9520..4cc8bba 100644 --- a/src/grimoire/models/system.py +++ b/src/grimoire/models/system.py @@ -81,7 +81,10 @@ def get_compendium(self, compendium_id: str) -> CompendiumDefinition | None: return self.compendiums.get(compendium_id) def find_entry(self, entry_id: str) -> dict | None: - """Search all compendiums for an entry by ID. Returns first match with metadata.""" + """Search all compendiums for an entry by ID. + + Returns first match with metadata. + """ for comp in self.compendiums.values(): entry = comp.get_entry(entry_id) if entry is not None: From 88b1dffb8377efe63c2ae30a1e63897195e96be7 Mon Sep 17 00:00:00 2001 From: Wyrdbound Date: Sat, 18 Apr 2026 11:46:41 -0400 Subject: [PATCH 5/6] fix: permissions issue in release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06f8197..79a5ef6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,7 @@ jobs: runs-on: ubuntu-latest environment: release permissions: + contents: read id-token: write # required for PyPI trusted publishing steps: From ba1a610ec95c16628784a68c6fbad664de7c48a9 Mon Sep 17 00:00:00 2001 From: The Wyrd One Date: Sat, 18 Apr 2026 11:48:19 -0400 Subject: [PATCH 6/6] fix: mypy failure --- src/grimoire/models/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grimoire/models/system.py b/src/grimoire/models/system.py index 4cc8bba..b00650a 100644 --- a/src/grimoire/models/system.py +++ b/src/grimoire/models/system.py @@ -80,7 +80,7 @@ def get_compendium(self, compendium_id: str) -> CompendiumDefinition | None: """Get a compendium definition by ID.""" return self.compendiums.get(compendium_id) - def find_entry(self, entry_id: str) -> dict | None: + def find_entry(self, entry_id: str) -> dict[str, object] | None: """Search all compendiums for an entry by ID. Returns first match with metadata.