From 7b63e20c2d9153fb00f8bf96ac99f19cbe802398 Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Sun, 19 Jul 2026 21:24:11 -0700 Subject: [PATCH 01/12] Post-merge docs, trusted publishing, and archive define-league-data-contract (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add badges, CONTRIBUTING, CODE_OF_CONDUCT, and RELEASING Adds crates.io/docs.rs/CI/license badges to README, a CONTRIBUTING.md covering dev setup, branching, commit conventions, and the WIT-contract change process, the org's standard Citizen Code of Conduct, and a RELEASING.md documenting the prepare-release/tag-release/release pipeline built earlier (including the CRATES_API_KEY-to-trusted-publishing follow-up). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0117XhibDRjfkbNdmmxzqhHn * chore: switch to crates.io trusted publishing Trusted publishing is now configured for fulltime-plugin-api on crates.io (bootstrapped by the v0.1.0 token-based publish). Flips release.yaml to trusted-publishing: true and updates RELEASING.md; CRATES_API_KEY is no longer used by the workflow. Marks task 5.2 complete - v0.1.0 is published. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0117XhibDRjfkbNdmmxzqhHn * chore: archive define-league-data-contract, sync baseline specs All 16 tasks complete: canonical schema, WIT interface, manifest format implemented and v0.1.0 published to crates.io. Archives the change to openspec/changes/archive/2026-07-19-define-league-data-contract and syncs its three delta specs into openspec/specs/ as the initial baseline. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0117XhibDRjfkbNdmmxzqhHn * fix: drop static CHANGELOG title to match git-cliff --prepend behavior git-cliff --prepend inserts at byte offset 0, ahead of any static content already in the file — with a hand-written "# Changelog" title at the top, the v0.1.0 release landed above that title instead of under it, and the release also lacked a version heading since cliff.toml's body template didn't render one. Adds the version heading to the body template and drops the static title (README's CHANGELOG link doesn't need one), matching dtrpg-sdk.rs's title-less convention, which doesn't hit this because it never had competing static content at the top to displace. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0117XhibDRjfkbNdmmxzqhHn --------- Co-authored-by: Claude Sonnet 5 --- .github/workflows/release.yaml | 2 +- CHANGELOG.md | 30 +---- CODE_OF_CONDUCT.md | 118 ++++++++++++++++++ CONTRIBUTING.md | 48 +++++++ README.md | 55 ++++---- RELEASING.md | 40 ++++++ cliff.toml | 5 + .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/data-provider-plugin-api/spec.md | 0 .../specs/league-data-schema/spec.md | 0 .../specs/plugin-manifest-format/spec.md | 0 .../tasks.md | 2 +- .../specs/data-provider-plugin-api/spec.md | 58 +++++++++ openspec/specs/league-data-schema/spec.md | 60 +++++++++ openspec/specs/plugin-manifest-format/spec.md | 30 +++++ 17 files changed, 394 insertions(+), 54 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 RELEASING.md rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/.openspec.yaml (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/design.md (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/proposal.md (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/specs/data-provider-plugin-api/spec.md (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/specs/league-data-schema/spec.md (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/specs/plugin-manifest-format/spec.md (100%) rename openspec/changes/{define-league-data-contract => archive/2026-07-19-define-league-data-contract}/tasks.md (96%) create mode 100644 openspec/specs/data-provider-plugin-api/spec.md create mode 100644 openspec/specs/league-data-schema/spec.md create mode 100644 openspec/specs/plugin-manifest-format/spec.md diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a2138cd..56d4c96 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,6 +17,6 @@ jobs: with: tag: ${{ inputs.tag }} test-args: "--all-features --workspace" - trusted-publishing: false + trusted-publishing: true merge-back-branch: develop secrets: inherit diff --git a/CHANGELOG.md b/CHANGELOG.md index a02b279..96de104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,16 @@ +## [Unreleased] + +### Documentation + +- Add badges, CONTRIBUTING, CODE_OF_CONDUCT, and RELEASING +## [0.1.0] - 2026-07-20 ### Added - Canonical league-data schema, data-provider WIT interface, and manifest format - ### Documentation - Propose define-league-data-contract change - - Add security policy - - -# Changelog - -All notable changes to this project are documented here. - -## [Unreleased] - -### Added - -- Canonical `league-data-schema` types: competitions, teams, fixtures/results, and standings, - covering both single-table and group-based competition formats. -- `data-provider-plugin-api` WIT interface (`wit/data-provider.wit`) for plugins to implement, - plus generated Rust bindings. -- `plugin-manifest-format`: plugin manifest schema and parser. -- Independent schema and interface version identifiers, each with major/minor - consumer-compatibility semantics. - -### Tooling - -- Pinned `wit-bindgen` to `0.59.0` (MSRV `1.85.0`), matched by `wasmtime` `46.0.1` on the host - side (`Apps/rust`). Bump both together when either changes. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4ab8c09 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,118 @@ +# Citizen Code of Conduct + +## 1. Purpose + +We are committed to providing a friendly, safe and welcoming environment for all. We're here to write code and share +development ideas. + +This code of conduct outlines our expectations for all those who participate in our community, as well as the +consequences for unacceptable behavior. + +We invite all those who participate in our community to help us create safe and positive experiences for everyone. + +## 2. Open [Source/Culture/Tech] Citizenship + +A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging +participants to recognize and strengthen the relationships between our actions and their effects on our community. + +Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of +inequality and abuses of power that exist in society. + +If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all +participants to contribute to the fullest extent, we want to know. + +## 3. Expected Behavior + +The following behaviors are expected and requested of all community members: + + * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this + community. + * Exercise consideration and respect in your speech and actions. + * Attempt collaboration before conflict. + * Refrain from demeaning, discriminatory, or harassing behavior and speech. + * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous + situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. + * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of + these locations. + +## 4. Unacceptable Behavior + +The following behaviors are considered harassment and are unacceptable within our community: + + * Violence, threats of violence or violent language directed against another person. + * Inappropriate jokes and language of any kind. + * Posting or displaying sexually explicit or violent material. + * Posting or threatening to post other people's personally identifying information ("doxing"). + * Personal insults. + * Inappropriate photography or recording. + * Inappropriate physical contact. You should have someone's consent before touching them. + * Unwelcome sexual attention. This includes sexualized comments or jokes; inappropriate touching, groping, and + unwelcomed sexual advances. + * Deliberate intimidation, stalking or following (online or in person). + * Advocating for, or encouraging, any of the above behavior. + * Sustained disruption of community events, including talks and presentations. + +## 5. Weapons Policy + +No weapons will be allowed at events, community spaces, or in other spaces covered by the scope of this Code of Conduct. +Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for +hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in +possession of one of these items will be asked to leave immediately, and will only be allowed to return without the +weapon. Community members are further expected to comply with all state and local laws on this matter. + +## 6. Consequences of Unacceptable Behavior + +Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not +be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the community organizers may take any action they deem +appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without +refund in the case of a paid event). + +## 7. Reporting Guidelines + +If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer +as soon as possible. apps@pilgrimagesoftware.com. + +Additionally, community organizers are available to help community members engage with local law enforcement or to +otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will +also provide escorts as desired by the person experiencing distress. + +## 8. Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify sweetrpg with +a concise description of your grievance. Your grievance will be handled in accordance with our existing governing +policies. + +## 9. Scope + +We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code +of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to +community business. + +This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of +community activities when such behavior has the potential to adversely affect the safety and well-being of community +members. + +## 10. Contact info + +apps@pilgrimagesoftware.com + +## 11. License and attribution + +The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative +Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). + +Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek +Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). + +_Revision 2.3. Posted 6 March 2017._ + +_Revision 2.2. Posted 4 February 2016._ + +_Revision 2.1. Posted 23 June 2014._ + +_Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 +March 2013._ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ae57e07 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +## Development setup + +This is a Rust crate with no non-Rust dependencies. Clone the repo and build: + +```bash +git clone https://github.com/pilgrimagesoftware/fulltime-plugin-api.git +cd fulltime-plugin-api +cargo build +``` + +The toolchain is pinned in `rust-toolchain.toml`; `rustup` picks it up automatically. + +Before opening a PR, run what CI runs: + +```bash +cargo fmt --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-features --workspace +cargo test --no-default-features +cargo test --doc +RUSTDOCFLAGS="-D warnings" cargo doc --no-deps +``` + +## Branching and PRs + +This repo follows the standard `master`/`develop` git-flow: + +- `master` reflects the latest released version; nothing is committed here directly. +- `develop` is the integration branch. + Branch `feature/*` or `fix/*` off `develop`, and open your PR back into `develop`. +- `release/*` branches are cut automatically by the release workflow (see [RELEASING.md](RELEASING.md)). + You shouldn't need to create one by hand. + +CI (`ci.yaml`) runs `fmt`, `clippy`, `test`, and `doc` on every PR; all must pass before merging. + +## Commit messages + +Commits follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) (`feat:`, `fix:`, `chore:`, `docs:`, etc.). +This isn't enforced by CI today, but it's what `git-cliff` reads to generate the changelog and compute the next version on release. +A commit that doesn't follow the convention won't show up in the changelog and won't contribute to the version bump. + +## Changing the WIT contract + +`wit/data-provider.wit` is the source of truth for the canonical schema and the `data-provider` interface. +The Rust types in `src/bindings.rs` are generated from it via `wit-bindgen`, not hand-written. +If you change a record or function signature there, update the matching requirement/scenario in `openspec/changes/define-league-data-contract/specs/` (or a new OpenSpec change, if the original change has already been archived), and bump `SCHEMA_VERSION`/`INTERFACE_VERSION` in `src/lib.rs` per the versioning policy in [`docs/plugin-authoring.md`](docs/plugin-authoring.md#versioning). diff --git a/README.md b/README.md index 055b2a4..551dce8 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,29 @@ # plugin-api -Canonical league data schema and WIT plugin interface shared by the FullTime plugin host -(`Apps/rust`) and data-provider plugins (`Plugins/Bundesliga`, and future league plugins). +[![Crates.io](https://img.shields.io/crates/v/fulltime-plugin-api.svg)](https://crates.io/crates/fulltime-plugin-api) +[![docs.rs](https://img.shields.io/docsrs/fulltime-plugin-api)](https://docs.rs/fulltime-plugin-api) +[![CI](https://github.com/pilgrimagesoftware/fulltime-plugin-api/actions/workflows/ci.yaml/badge.svg)](https://github.com/pilgrimagesoftware/fulltime-plugin-api/actions/workflows/ci.yaml) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) -Neither the host nor any plugin owns this contract - it is versioned and published -independently so plugins and the host can evolve without a lockstep release. +Canonical league data schema and WIT plugin interface shared by the FullTime plugin host (`Apps/rust`) and data-provider plugins (`Plugins/Bundesliga`, and future league plugins). + +Neither the host nor any plugin owns this contract - it is versioned and published independently so plugins and the host can evolve without a lockstep release. ## What's in this crate -- **Canonical `league-data-schema`** (`Competition`, `Team`, `Fixture`, `Standings`, ...): - the source-agnostic data shape every plugin maps its provider's response into, and every - host UI consumes. Covers single-table league formats and group-based tournament formats - with the same types. -- **`data-provider` WIT interface** (`wit/data-provider.wit`): the contract a plugin - implements - `list-competitions`, `fetch-fixtures`, `fetch-results`, `fetch-standings`, - `fetch-metadata` - plus structured error variants for network failure, rate limiting, and - schema-mapping failure. Rust bindings are generated from this file via `wit-bindgen`, not - hand-written, so the WIT source is the single source of truth. -- **Plugin manifest format** (`Manifest`): the static TOML file every plugin ships - declaring its ID, release version, targeted schema/interface versions, and required - network hosts. This crate validates structure and field format only - network - reachability and capability enforcement belong to the host runtime (`Apps/rust`). +- **Canonical `league-data-schema`** (`Competition`, `Team`, `Fixture`, `Standings`, ...): the source-agnostic data shape every plugin maps its provider's response into, and every host UI consumes. + Covers single-table league formats and group-based tournament formats with the same types. +- **`data-provider` WIT interface** (`wit/data-provider.wit`): the contract a plugin implements - `list-competitions`, `fetch-fixtures`, `fetch-results`, `fetch-standings`, `fetch-metadata` - plus structured error variants for network failure, rate limiting, and schema-mapping failure. + Rust bindings are generated from this file via `wit-bindgen`, not hand-written, so the WIT source is the single source of truth. +- **Plugin manifest format** (`Manifest`): the static TOML file every plugin ships declaring its ID, release version, targeted schema/interface versions, and required network hosts. + This crate validates structure and field format only - network reachability and capability enforcement belong to the host runtime (`Apps/rust`). ## Versioning -The schema and the interface each carry an independent `major.minor` [`Version`] -identifier (`SCHEMA_VERSION`, `INTERFACE_VERSION`), because a schema field addition and an -interface function signature change are unrelated concerns and shouldn't force a shared -breaking version. +The schema and the interface each carry an independent `major.minor` [`Version`] identifier (`SCHEMA_VERSION`, `INTERFACE_VERSION`), because a schema field addition and an interface function signature change are unrelated concerns and shouldn't force a shared breaking version. -Compatibility is major-version equality, minor-version-or-lower on the plugin side: a -plugin declaring `1.2` loads against a host on `1.3`+ (the host is a superset) but not -`1.1` (missing fields) or `2.x` (major mismatch). See [`Version::accepts`]. +Compatibility is major-version equality, minor-version-or-lower on the plugin side: a plugin declaring `1.2` loads against a host on `1.3`+ (the host is a superset) but not `1.1` (missing fields) or `2.x` (major mismatch). +See [`Version::accepts`]. ## Usage @@ -50,13 +42,21 @@ assert!(SCHEMA_VERSION.accepts(manifest.schema_version)); ## Building a plugin -See [`docs/plugin-authoring.md`](docs/plugin-authoring.md) for a walkthrough of the WIT -interface, manifest format, and versioning policy. +See [`docs/plugin-authoring.md`](docs/plugin-authoring.md) for a walkthrough of the WIT interface, manifest format, and versioning policy. ## Change log [CHANGELOG](CHANGELOG.md) +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and commit conventions, and [RELEASING.md](RELEASING.md) for how versions get cut. +This project follows the [Code of Conduct](CODE_OF_CONDUCT.md). + +## Security + +See [SECURITY.md](SECURITY.md) to report a vulnerability. + ## License Licensed under: @@ -65,5 +65,4 @@ Licensed under: ## Contribution -Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in the work -by you shall be licensed as above. +Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed as above. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..9d1342e --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,40 @@ +# Releasing + +Releases are driven by [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) on `develop` and [`git-cliff`](https://git-cliff.org), via three workflows in `.github/workflows/` (thin wrappers around the `rust-*` reusable workflows in [`pilgrimagesoftware/github-actions`](https://github.com/pilgrimagesoftware/github-actions)). +There's no manual version bumping or changelog editing. + +## 1. Prepare the release + +Trigger **Prepare Release** manually (Actions tab → Prepare Release → Run workflow). It: + +1. Runs `git-cliff --bumped-version` against `develop` to compute the next version from commits since the last tag (`fix:` → patch, `feat:` → minor, a `!` or `BREAKING CHANGE:` footer → major). +2. Bumps `Cargo.toml` to that version and prepends the generated section to `CHANGELOG.md`. +3. Pushes a `release/` branch and opens (or updates, if one is already open) a PR from it into `master`, labeled `release` and requesting review. + +If it fails with "nothing to release," either no `feat:`/`fix:`/etc. commits have landed on `develop` since the last tag, or no tag exists yet for `git-cliff` to compute a version relative to — see the workflow's error output for which. + +## 2. Review and merge + +Check the generated `CHANGELOG.md` section and the version bump on the PR. +Fix anything wrong by pushing to the `release/` branch directly (or re-running Prepare Release, which force-pushes and updates the same PR). +Merge into `master` once CI passes. + +## 3. Tag and publish + +Merging the release PR triggers **Tag Release**, which tags `master` as `v`. +The tag push triggers **Release**, which: + +1. Re-runs the test suite against the tagged commit. +2. Publishes to crates.io. +3. Cuts a GitHub Release with that tag's changelog section as the release notes. +4. Merges `master` back into `develop` so the version bump and changelog aren't lost on the next release cycle. + +## crates.io authentication + +Publishing uses crates.io OIDC trusted publishing (`rust-lang/crates-io-auth-action`, via `release.yaml`'s `trusted-publishing: true` input) — no long-lived token in CI. +This only became possible after the crate existed on crates.io (bootstrapped by a one-time publish with the `CRATES_API_KEY` token for `v0.1.0`), then configuring `fulltime-plugin-api` as a trusted publisher on crates.io. +The `CRATES_API_KEY` repo secret is no longer used by `release.yaml` and can be removed. + +## Manually re-running a release + +If **Release** needs to be re-run for an existing tag (e.g. crates.io publish failed after the GitHub Release was already cut), trigger it manually with the `tag` input set to the existing `vX.Y.Z` tag rather than pushing a new tag. diff --git a/cliff.toml b/cliff.toml index 7cbe0da..c9170cc 100644 --- a/cliff.toml +++ b/cliff.toml @@ -1,6 +1,11 @@ [changelog] header = "" body = """ +{% if version %}\ +## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ +## [Unreleased] +{% endif %} {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} {% for commit in commits %} diff --git a/openspec/changes/define-league-data-contract/.openspec.yaml b/openspec/changes/archive/2026-07-19-define-league-data-contract/.openspec.yaml similarity index 100% rename from openspec/changes/define-league-data-contract/.openspec.yaml rename to openspec/changes/archive/2026-07-19-define-league-data-contract/.openspec.yaml diff --git a/openspec/changes/define-league-data-contract/design.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/design.md similarity index 100% rename from openspec/changes/define-league-data-contract/design.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/design.md diff --git a/openspec/changes/define-league-data-contract/proposal.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/proposal.md similarity index 100% rename from openspec/changes/define-league-data-contract/proposal.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/proposal.md diff --git a/openspec/changes/define-league-data-contract/specs/data-provider-plugin-api/spec.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/specs/data-provider-plugin-api/spec.md similarity index 100% rename from openspec/changes/define-league-data-contract/specs/data-provider-plugin-api/spec.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/specs/data-provider-plugin-api/spec.md diff --git a/openspec/changes/define-league-data-contract/specs/league-data-schema/spec.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/specs/league-data-schema/spec.md similarity index 100% rename from openspec/changes/define-league-data-contract/specs/league-data-schema/spec.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/specs/league-data-schema/spec.md diff --git a/openspec/changes/define-league-data-contract/specs/plugin-manifest-format/spec.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/specs/plugin-manifest-format/spec.md similarity index 100% rename from openspec/changes/define-league-data-contract/specs/plugin-manifest-format/spec.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/specs/plugin-manifest-format/spec.md diff --git a/openspec/changes/define-league-data-contract/tasks.md b/openspec/changes/archive/2026-07-19-define-league-data-contract/tasks.md similarity index 96% rename from openspec/changes/define-league-data-contract/tasks.md rename to openspec/changes/archive/2026-07-19-define-league-data-contract/tasks.md index 832b84f..2d278e6 100644 --- a/openspec/changes/define-league-data-contract/tasks.md +++ b/openspec/changes/archive/2026-07-19-define-league-data-contract/tasks.md @@ -39,5 +39,5 @@ - [x] 5.1 Write crate-level documentation covering the schema, WIT interface, manifest format, and versioning policy for plugin authors -- [ ] 5.2 Cut an initial versioned release for `Apps/rust` and `Plugins/Bundesliga` to +- [x] 5.2 Cut an initial versioned release for `Apps/rust` and `Plugins/Bundesliga` to depend on diff --git a/openspec/specs/data-provider-plugin-api/spec.md b/openspec/specs/data-provider-plugin-api/spec.md new file mode 100644 index 0000000..7ba6ed7 --- /dev/null +++ b/openspec/specs/data-provider-plugin-api/spec.md @@ -0,0 +1,58 @@ +### Requirement: Standard Data-Provider Interface +The WIT package SHALL define a common interface exposing operations to list competitions, +fetch fixtures, fetch results, fetch standings, and fetch team/competition metadata, for +every data-provider plugin to implement. + +#### Scenario: Host queries a plugin's supported operations +- **WHEN** the host inspects a loaded plugin's exported interface +- **THEN** the plugin reports which of the standard data-provider operations it implements + +#### Scenario: Host calls an operation the plugin implements +- **WHEN** the host invokes `fetch-fixtures` on a plugin that implements it +- **THEN** the plugin returns fixture data conforming to the canonical `league-data-schema` + +### Requirement: Canonical Schema Output +Every operation in the interface SHALL return data typed against the canonical +`league-data-schema`; the interface SHALL NOT define plugin-specific or provider-specific +return types. + +#### Scenario: Plugin returns data in canonical schema +- **WHEN** a plugin successfully fetches fixtures from its upstream source +- **THEN** the WIT function signature constrains the return type to the canonical fixture + schema, so the host never receives an untyped or provider-specific shape + +### Requirement: Structured Error Types +The interface SHALL define structured error variants a plugin returns for upstream +failures — network failure, rate limit, and schema-mapping failure — instead of letting +failures surface as unhandled traps. + +#### Scenario: Upstream source is unreachable +- **WHEN** a plugin's upstream HTTP call fails due to a network error +- **THEN** the plugin returns the `network-failure` error variant, and the host can + distinguish it from a successful empty result + +#### Scenario: Upstream source rate-limits the plugin +- **WHEN** a plugin's upstream source responds with a rate-limit error +- **THEN** the plugin returns the `rate-limited` error variant, which the host can use to + back off and retry later + +#### Scenario: Plugin cannot map upstream data to the schema +- **WHEN** a plugin receives upstream data it cannot represent in the canonical schema +- **THEN** the plugin returns the `schema-mapping-failure` error variant rather than + partial or malformed schema data + +### Requirement: Interface Versioning +The data-provider interface SHALL carry an explicit version identifier, independent of the +schema version, so the host can detect and reject plugins built against an incompatible +interface version before invoking them. + +#### Scenario: Plugin built against a newer interface than the host supports +- **WHEN** the host loads a plugin declaring an interface version newer (major) than any + version the host implements +- **THEN** the host refuses to load the plugin and reports a version-incompatibility error + +#### Scenario: Plugin built against an older, compatible interface version +- **WHEN** the host loads a plugin declaring an interface minor version lower than the + host's supported version, with the same major version +- **THEN** the host loads the plugin, since the host's interface is a superset of the + functions the plugin was built against diff --git a/openspec/specs/league-data-schema/spec.md b/openspec/specs/league-data-schema/spec.md new file mode 100644 index 0000000..452b6de --- /dev/null +++ b/openspec/specs/league-data-schema/spec.md @@ -0,0 +1,60 @@ +### Requirement: Canonical Competition and Team Model +The schema SHALL define a source-agnostic representation of a competition (league, cup, or +national-team tournament) and its participating teams, independent of any single +provider's identifiers. + +#### Scenario: Two plugins describe the same team consistently +- **WHEN** the Bundesliga plugin and a national-team plugin both supply data for teams + that play in both contexts +- **THEN** each team's canonical representation uses the same schema fields (name, short + name, canonical ID) regardless of which plugin produced it + +### Requirement: Canonical Fixture and Result Model +The schema SHALL define a single fixture/result shape covering scheduled kickoff time, +participating teams, venue, status, and score, sufficient to represent both league +fixtures and knockout/group-stage fixtures. + +#### Scenario: League fixture is represented +- **WHEN** the Bundesliga plugin returns a scheduled league match +- **THEN** the fixture conforms to the canonical fixture schema with status `scheduled` + and no score + +#### Scenario: Group-stage fixture is represented +- **WHEN** a national-team competition plugin returns a group-stage match that has + finished +- **THEN** the fixture conforms to the same canonical fixture schema with status + `finished` and a final score, using the same fields as a league fixture + +### Requirement: Canonical Standings Model +The schema SHALL define a standings/table representation supporting both single-table +league formats and group-based tournament formats using the same row shape. + +#### Scenario: League table standings +- **WHEN** the Bundesliga plugin returns a standings table +- **THEN** the standings conform to the canonical schema as a single ranked table + +#### Scenario: Group-stage standings +- **WHEN** a national-team competition plugin returns group-stage standings +- **THEN** the standings conform to the canonical schema as multiple named groups, each a + ranked table using the same row shape as the league table + +### Requirement: Schema Version Identification +The canonical schema SHALL carry an explicit version identifier, independent of the +data-provider interface version, that both the host and plugins reference to negotiate +compatibility. + +#### Scenario: Host checks plugin compatibility against schema version +- **WHEN** the host loads a plugin declaring a target schema version +- **THEN** the host compares that version against the schema version(s) it supports + before allowing the plugin to be invoked + +#### Scenario: Plugin targets an older but compatible schema minor version +- **WHEN** a plugin declares schema version `1.2` and the host implements schema version + `1.3` +- **THEN** the host accepts the plugin, since a higher host minor version is a superset of + the fields the plugin expects + +#### Scenario: Plugin targets an incompatible major schema version +- **WHEN** a plugin declares schema version `2.0` and the host implements only schema + version `1.x` +- **THEN** the host refuses to load the plugin and reports a version-incompatibility error diff --git a/openspec/specs/plugin-manifest-format/spec.md b/openspec/specs/plugin-manifest-format/spec.md new file mode 100644 index 0000000..c8a8143 --- /dev/null +++ b/openspec/specs/plugin-manifest-format/spec.md @@ -0,0 +1,30 @@ +### Requirement: Plugin Manifest Schema +This crate SHALL define the static manifest schema every plugin ships: plugin ID, version, +target schema version, target interface version, and the set of network hosts it requires +access to. + +#### Scenario: Manifest declares required fields +- **WHEN** a manifest is parsed against this crate's schema +- **THEN** parsing requires plugin ID, version, target schema version, target interface + version, and declared network hosts to be present, and fails if any is missing + +#### Scenario: Manifest declares network capabilities +- **WHEN** a plugin's manifest lists the hostnames it needs to call +- **THEN** the parsed manifest exposes exactly that list, in a form the host runtime can + use to scope the plugin's HTTP fetch capability at load time + +### Requirement: Manifest Format Validation Only +This crate SHALL validate manifest structure and field presence/format; it SHALL NOT +perform host-side enforcement decisions (network reachability, capability granting, +enable/disable state) — those belong to the plugin host runtime. + +#### Scenario: Malformed manifest is rejected at parse time +- **WHEN** a manifest file has an invalid version string or a malformed hostname entry +- **THEN** parsing returns a structured error identifying the invalid field, without + attempting to contact any declared host + +#### Scenario: Well-formed manifest is accepted regardless of runtime policy +- **WHEN** a manifest is structurally valid but declares a network host the host runtime + will later reject for policy reasons +- **THEN** this crate parses the manifest successfully; the runtime enforcement decision + happens outside this crate From ad7258e141e510b7c8f4ecd1ded4503c0bab41e9 Mon Sep 17 00:00:00 2001 From: Paul Schifferer Date: Sun, 19 Jul 2026 22:24:17 -0700 Subject: [PATCH 02/12] chore: remove local copies of skills/commands available globally in ~/.claude/ Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0117XhibDRjfkbNdmmxzqhHn --- .claude/commands/opsx/apply.md | 156 ---------- .claude/commands/opsx/archive.md | 161 ---------- .claude/commands/opsx/explore.md | 175 ----------- .claude/commands/opsx/propose.md | 110 ------- .claude/skills/openspec-apply-change/SKILL.md | 160 ---------- .../skills/openspec-archive-change/SKILL.md | 118 ------- .claude/skills/openspec-explore/SKILL.md | 290 ------------------ .claude/skills/openspec-propose/SKILL.md | 114 ------- 8 files changed, 1284 deletions(-) delete mode 100644 .claude/commands/opsx/apply.md delete mode 100644 .claude/commands/opsx/archive.md delete mode 100644 .claude/commands/opsx/explore.md delete mode 100644 .claude/commands/opsx/propose.md delete mode 100644 .claude/skills/openspec-apply-change/SKILL.md delete mode 100644 .claude/skills/openspec-archive-change/SKILL.md delete mode 100644 .claude/skills/openspec-explore/SKILL.md delete mode 100644 .claude/skills/openspec-propose/SKILL.md diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md deleted file mode 100644 index c6cb9b6..0000000 --- a/.claude/commands/opsx/apply.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -name: "OPSX: Apply" -description: Implement tasks from an OpenSpec change (Experimental) -allowed-tools: Bash(openspec:*) -category: Workflow -tags: [workflow, artifacts, experimental] ---- - -Implement tasks from an OpenSpec change. - -**Store selection:** If the user names a store (a store is a standalone OpenSpec repo registered on this machine) or the work lives in one, run `openspec store list --json` to discover registered store ids, then pass `--store ` on the commands that read or write specs and changes (`new change`, `status`, `instructions`, `list`, `show`, `validate`, `archive`, `doctor`, `context`). Other commands do not take the flag. Hints printed by commands already carry the flag; keep it on follow-ups. Without a store, commands act on the nearest local `openspec/` root. - -**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. - -**Steps** - -1. **Select the change** - - If a name is provided, use it. Otherwise: - - Infer from conversation context if the user mentioned a change - - Auto-select if only one active change exists - - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select - - Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). - -2. **Check status to understand the schema** - ```bash - openspec status --change "" --json - ``` - Parse the JSON to understand: - - `schemaName`: The workflow being used (e.g., "spec-driven") - - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints - - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) - -3. **Get apply instructions** - - ```bash - openspec instructions apply --change "" --json - ``` - - This returns: - - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema) - - Progress (total, complete, remaining) - - Task list with status - - Dynamic instruction based on current state - - **Handle states:** - - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` - - If `state: "all_done"`: congratulate, suggest archive - - Otherwise: proceed to implementation - -4. **Read context files** - - Read every file path listed under `contextFiles` from the apply instructions output. - The files depend on the schema being used: - - **spec-driven**: proposal, specs, design, tasks - - Other schemas: follow the contextFiles from CLI output - -5. **Show current progress** - - Display: - - Schema being used - - Progress: "N/M tasks complete" - - Remaining tasks overview - - Dynamic instruction from CLI - -6. **Implement tasks (loop until done or blocked)** - - For each pending task: - - Show which task is being worked on - - Make the code changes required - - Keep changes minimal and focused - - Mark task complete in the tasks file: `- [ ]` → `- [x]` - - Continue to next task - - **Pause if:** - - Task is unclear → ask for clarification - - Implementation reveals a design issue → suggest updating artifacts - - Error or blocker encountered → report and wait for guidance - - User interrupts - -7. **On completion or pause, show status** - - Display: - - Tasks completed this session - - Overall progress: "N/M tasks complete" - - If all done: suggest archive - - If paused: explain why and wait for guidance - -**Output During Implementation** - -``` -## Implementing: (schema: ) - -Working on task 3/7: -[...implementation happening...] -✓ Task complete - -Working on task 4/7: -[...implementation happening...] -✓ Task complete -``` - -**Output On Completion** - -``` -## Implementation Complete - -**Change:** -**Schema:** -**Progress:** 7/7 tasks complete ✓ - -### Completed This Session -- [x] Task 1 -- [x] Task 2 -... - -All tasks complete! You can archive this change with `/opsx:archive`. -``` - -**Output On Pause (Issue Encountered)** - -``` -## Implementation Paused - -**Change:** -**Schema:** -**Progress:** 4/7 tasks complete - -### Issue Encountered - - -**Options:** -1.