methodology-framework
Portable process tooling for agent-driven delivery -- scripts, playbooks, templates, and specs.
Installation
pip install FlywheelIC
# or, from a source checkout:
pip install -e .
Package contents
methodology_framework.sync_stories_to_jira-- one-way repo-to-Jira story syncmethodology_framework.build_playbook-- build a concrete playbook from a parameterized body + bindingsmethodology_framework.register_playbook_with_devin-- register a rendered playbook with Devin's APImethodology_framework/playbooks/-- parameterized playbook bodies (package data)methodology_framework/templates/-- story and process templates (package data)methodology_framework/specs/-- format specs (package data)methodology_framework.bootstrap_jira-- verify a Jira project matches the canonical shape (read-only spec-compliance checker)methodology_framework/jira_shapes/-- canonical Jira shape definitions (YAML, package data)
Jira Bootstrap (Verify Mode)
The CLI checks whether a Jira project matches the methodology-framework's canonical shape and reports gaps. It does not provision — operators apply gaps via the Jira UI. This read-only design lets the tool run in CI as a gate.
v0.2.0 breaking change:
--applyand--dry-runwere removed. The CLI is now a spec-compliance verifier. Seeoperator-verify-runbook.mdfor the operator workflow.
Prerequisites
- Python 3.12+
pip install FlywheelIC(orpip install -e .from source)- For
--verifymode, Jira OAuth client credentials — the only supported Jira auth path (resolved by the samejira_auth.resolve_jira_auth_configthe story sync uses). Admin scope is not required; read access is sufficient:JIRA_CLOUD_ID,JIRA_OAUTH_CLIENT_ID, andJIRA_OAUTH_CLIENT_SECRETare exchanged for a bearer token. Requests go tohttps://api.atlassian.com/ex/jira/{cloud_id}, so--jira-hostis ignored. The connectivity probe uses/serverInforather than/myself, since client-credentials tokens have no user context.
Usage
# Verify — query Jira and report spec-compliance gaps (read-only).
# OAuth client credentials are the only supported Jira auth path; these are the
# same service-account credentials the story sync uses.
export JIRA_CLOUD_ID="<cloud-id>"
export JIRA_OAUTH_CLIENT_ID="<client-id>"
export JIRA_OAUTH_CLIENT_SECRET="<client-secret>"
python -m methodology_framework bootstrap-jira \
--project-key=MYPROJ \
--jira-host=myorg.atlassian.net \
--verify
# JSON output (stable schema — safe for CI parsing)
python -m methodology_framework bootstrap-jira \
--project-key=MYPROJ \
--jira-host=myorg.atlassian.net \
--verify --output=json
# Markdown output
python -m methodology_framework bootstrap-jira \
--project-key=MYPROJ \
--jira-host=myorg.atlassian.net \
--verify --output=markdown
# Export — emit an importable config bundle (YAML); no API calls
python -m methodology_framework bootstrap-jira \
--project-key=MYPROJ \
--jira-host=myorg.atlassian.net \
--export /tmp/jira-bundle.yaml
Exit codes
| Code | Meaning |
|---|---|
0 |
All spec items verified OK |
1 |
One or more items missing (gaps found) |
2 |
One or more items unverifiable (API limitation; operator must inspect manually) |
3 |
Error (connectivity, auth, or runtime failure) |
Flags
| Flag | Required | Description |
|---|---|---|
--project-key |
Yes | Jira project key (e.g. SCRUM). Substituted for {{PROJECT_KEY}} in shape defs. |
--jira-host |
Yes | Jira Cloud host (e.g. myorg.atlassian.net). No https:// prefix. Ignored for API calls: OAuth requests go to the api.atlassian.com gateway. |
--verify |
No | Query Jira and report spec-compliance gaps (read-only). |
--export <path> |
No | Emit importable config bundle at <path>. No API calls. |
--output |
No | Output format for --verify: text (default), json, markdown. |
Environment variables
| Variable | When needed | Description |
|---|---|---|
JIRA_CLOUD_ID |
--verify mode |
Atlassian cloud ID of the tenant; the gateway base becomes https://api.atlassian.com/ex/jira/{cloud_id}. |
JIRA_OAUTH_CLIENT_ID / JIRA_OAUTH_CLIENT_SECRET |
--verify mode |
Service-account client credentials, exchanged for a bearer token at https://api.atlassian.com/oauth/token. Both are required; never accepted as CLI flags. |
JIRA_BASE_URL |
optional | Retained for compatibility/logging only; API calls use the OAuth gateway. |
JSON output schema (v0.2.0)
{
"spec_version": "0.2.0",
"project_key": "METH",
"verified_at": "2025-05-31T00:00:00Z",
"items": [
{"resource_type": "status", "name": "To Do", "result": "ok", "details": null},
{"resource_type": "custom_field", "name": "Story File", "result": "missing", "details": "Operator must create via Settings → Custom fields → Create"}
],
"summary": {"total": 14, "ok": 12, "missing": 1, "unverifiable": 1},
"exit_code": 1
}
Any breaking change to this schema requires a MAJOR version bump.
Shape definitions
The three canonical shape files shipped as package data:
jira_shapes/workflow.yaml— workflow statuses (To Do, Ready for AI agent, In Progress, In Review, Waiting, Blocked, Done, Won't do) and transitions with actor permissionsjira_shapes/custom_fields.yaml— Story File (URL), Requirement IDs (labels), Agent Estimate (number)jira_shapes/automation_rules.yaml— PR-title-key auto-transition, dependency resolution, BLOCKED protection
For full methodology context see the methodology requirements doc § 4.13.3 ("Jira Bootstrap CLI").
Adopter Integration
Adopting projects consume the methodology framework's CI pipelines via reusable GitHub Actions workflows. Instead of copying workflow YAML into each repo, adopters write a thin caller that references the framework's workflows by SemVer tag.
Version pinning requirement: adopters MUST pin to a specific workflow tag (
@v0.X.Y), never@main. The reusable workflow at that tag owns the matching PyPI package install, so normal adopters update only theuses:ref. This keeps workflow and package updates deliberate, not silent, and matches the version-pinning model per methodology requirements § 4.13.
For adopter and intern repos, the normal contract is:
uses: invoice-cloud/Flywheel/.github/workflows/sync.yml@v0.22.0
with:
install_from_local: false
install_from_local: true is reserved for framework-internal validation where
this repository dogfoods unreleased HEAD code. Adopter and intern repos should
normally use install_from_local: false or omit it and accept the default.
The @v0.22.0 reusable workflows install FlywheelIC==0.22.0 from PyPI by
default. Reusable workflow install paths should not use unpinned
pip install FlywheelIC.
Advanced operators may override flywheel_version, but routine adopter and
intern release adoption should not need it. The expected release motion is:
update uses: invoice-cloud/Flywheel/.github/workflows/<workflow>.yml@vX.Y.Z
and let that reusable workflow install its matching FlywheelIC==X.Y.Z
default.
The same single-knob contract applies to all adopter-facing reusable workflows that execute Flywheel code:
sync.ymlpopulate-story-acus.ymlbuild-and-register.yml
Each reusable workflow exposes flywheel_version only as an advanced optional
override. Normal adopter and intern callers should pin the reusable workflow ref
and should not pass flywheel_version.
For the local wrapper model, one-command wrapper upgrades, stale-ref guard, and
intern controlled-test checklist, see
docs/flywheel-wrapper-automation.md.
Required inputs
| Input | Required | Default | Description |
|---|---|---|---|
project-key |
Yes | — | Jira project key (e.g. METH, SCRUM). No default — sync errors if absent. |
jira-auth-mode |
No | oauth |
Jira auth mode. OAuth client credentials are the standard CI path. Set basic only for legacy API-token callers. |
jira-cloud-id |
Yes for OAuth | — | Atlassian cloud ID used by OAuth gateway requests. Operators/repo factory provision this; interns should not manage it. |
jira-base-url |
Yes for Basic | — | Jira Cloud base URL (e.g. https://icpipeline.atlassian.net). Optional for OAuth gateway mode. |
jira-user-email |
Yes for Basic | — | Jira service account email for legacy HTTP Basic auth. Not required for OAuth. |
stories-dir |
No | docs/stories |
Relative path to the stories directory. |
phase-regex |
No | ^phase\d+$ |
Regex for phase directory matching. Empty string ("") disables phase labeling entirely. |
repo-url |
No | (auto) | Override repo URL. Normally derived from GITHUB_SERVER_URL + GITHUB_REPOSITORY (GHA-native). |
flywheel_version |
No | 0.22.0 |
Advanced override for the FlywheelIC PyPI package version installed when install_from_local is false. Omit in normal adopter/intern callers; the reusable workflow ref owns the default. |
install_from_local |
No | false |
Use false in adopter/intern repos. Use true only for framework-internal HEAD validation. |
Per-tenant discovery
Sync discovers all tenant-specific Jira IDs at runtime via the REST API,
using canonical names as lookup keys. The names come from the framework's
shape definitions (jira_shapes/*.yaml) — the same spec the verifier
(bootstrap-jira --verify) enforces.
Custom fields (discovered via GET /rest/api/3/field):
| Name | Purpose |
|---|---|
Requirement IDs |
Multi-value field for REQ-id traceability |
Story File |
URL to the canonical story .md file in the repo |
Agent Estimate |
Numeric estimate in agent-hours |
Statuses (discovered via GET /rest/api/3/project/{key}/statuses):
| Name | Category |
|---|---|
To Do |
TODO |
Ready for AI agent |
TODO |
In Progress |
IN_PROGRESS |
In Review |
IN_PROGRESS |
Waiting |
IN_PROGRESS |
Blocked |
IN_PROGRESS |
Done |
DONE |
Won't do |
DONE |
Transitions: Sourced from the workflow spec (jira_shapes/workflow.yaml).
Link types (discovered via GET /rest/api/3/issueLinkType):
Blocks (inward: "is blocked by").
Issue types (discovered via GET /rest/api/3/issuetype):
Story, Sub-task.
If any required name is missing from the target project, sync fails fast:
ERROR: Required custom field 'Story File' not found in Jira project METH;
run 'methodology-framework bootstrap-jira --verify --project-key=METH' to identify the gap.
Story sync workflow
Syncs story .md files from the adopter's repo to Jira. Create
.github/workflows/sync-stories.yml in the adopter repo:
name: Sync stories to Jira
on:
push:
branches: [main]
paths:
- "docs/stories/**/*.md"
workflow_dispatch:
inputs:
mode:
description: "Sync mode"
required: true
default: "since-ref"
type: choice
options:
- since-ref
- all
jobs:
sync:
permissions:
contents: write
pull-requests: read
uses: invoice-cloud/Flywheel/.github/workflows/sync.yml@v0.22.0
with:
project_key: MYPROJ
mode: ${{ github.event.inputs.mode || 'since-ref' }}
jira_cloud_id: ${{ vars.JIRA_CLOUD_ID }}
jira_base_url: ${{ vars.JIRA_BASE_URL }}
install_from_local: false
# stories_dir: "docs/stories" # default
# phase_regex: "^phase\\d+$" # default; set "" to disable
secrets:
JIRA_OAUTH_CLIENT_ID: ${{ secrets.JIRA_OAUTH_CLIENT_ID }}
JIRA_OAUTH_CLIENT_SECRET: ${{ secrets.JIRA_OAUTH_CLIENT_SECRET }}
The mode input defaults to since-ref for routine push-triggered
runs (smallest target set per push). Pass mode: all explicitly for
nightly or workflow_dispatch full-corpus syncs — an all run
bulk-reads every ticket state in one paginated JQL search, so its Jira
read cost is a function of the project's page count, not the corpus size.
Secrets forwarding: the caller's
secrets:block must explicitly map each secret the reusable workflow declares. Secrets are NOT inherited by default in reusable workflows. Usingsecrets: inheritworks but is brittle — prefer explicit mapping.
OAuth client credentials are the only supported Jira auth path (METH-61).
The jira_auth_mode / jira_user_email inputs and the JIRA_API_TOKEN /
JIRA_OAUTH_ACCESS_TOKEN secrets are gone: the sync fails fast naming
JIRA_OAUTH_CLIENT_ID and JIRA_OAUTH_CLIENT_SECRET when either is missing.
Required caller permissions
The reusable sync workflow declares permissions: contents: write
internally (for jira_key + agent_acus writeback commits). GitHub
Actions requires the caller to grant at least the same permissions
at the job level — otherwise the workflow fails at the uses:
resolution step with:
The workflow is requesting 'contents: write', but is only allowed 'contents: read'.
Add a job-level permissions: block to your caller:
jobs:
sync:
permissions:
contents: write
pull-requests: read
uses: invoice-cloud/Flywheel/.github/workflows/sync.yml@v0.22.0
# ... with: / secrets: as above
Job-scoped, not workflow-scoped. Keep
permissions:on the job, not the top-levelon:— if future jobs are added to the same file, they should not inherit write access they don't need.
Playbook build + register workflow
Builds a concrete playbook from a parameterized body + bindings file,
then registers it with Devin's API. Create
.github/workflows/build-and-register.yml in the adopter repo:
name: Build and register playbook
on:
push:
branches: [main]
paths:
- "methodology/playbooks/*.body.md"
- ".flywheel/playbook-bindings.yml"
workflow_dispatch:
jobs:
build-and-register:
uses: invoice-cloud/Flywheel/.github/workflows/build-and-register.yml@v0.22.0
with:
playbook_body_path: "methodology/playbooks/router-base.body.md"
bindings_path: ".flywheel/playbook-bindings.yml"
install_from_local: false
secrets:
DEVIN_ENTERPRISE_API_TOKEN: ${{ secrets.DEVIN_ENTERPRISE_API_TOKEN }}
Registration is enterprise-scoped only (METH-61): the devin-playbook-scope and
devin-org-id inputs and the DEVIN_API_TOKEN secret are gone, and the workflow
fails fast naming DEVIN_ENTERPRISE_API_TOKEN when it is missing.
The enterprise service user needs
an account-level custom role with ManageAccountPlaybooks; do not grant broader
admin permissions just for playbook registration. Enterprise playbook IDs are
returned by Devin with the playbook- prefix.
Dedicated Devin Enterprise customers should also set DEVIN_API_ROOT to their
deployment API host, for example https://api.<company>.devinenterprise.com.
InvoiceCloud uses DEVIN_API_ROOT=https://api.invoicecloud.devinenterprise.com.
If DEVIN_API_ROOT is unset or empty, Flywheel defaults to https://api.devin.ai.
Nesting limit: GitHub allows reusable workflow nesting up to 4 levels deep. If your repo wraps these workflows in another caller layer, verify the total nesting depth stays within limits.
How this repo evolves
The methodology-framework repo itself uses the same sync workflow it
ships to adopters. Framework-side evolution stories (new specs, CLI
features, workflow improvements) are drafted as .md files under
docs/stories/ and synced to the METH Jira project via
.github/workflows/sync-stories.yml.
Adopter-side stories (product-specific work) file in each adopter's own
Jira project (e.g. SCRUM for centralized-pipeline-ui).
PyPI Publishing
The package is published to PyPI automatically via OIDC Trusted Publishing when
pyproject.toml changes on main.
The release.yml workflow builds and publishes using
pypa/gh-action-pypi-publish in OIDC mode -- no PYPI_API_TOKEN secret
is needed. The job uses the pypi GitHub environment for protection rules.
Operator setup (one-time): bind the PyPI project FlywheelIC
to the GitHub repo invoice-cloud/Flywheel, workflow release.yml,
environment pypi at
PyPI Trusted Publishers.
Release lifecycle
Version bumps in pyproject.toml drive the entire release cycle
automatically. The operator's only decision surface is PR review.
Primary flow
-
A PR bumps the
versionfield inpyproject.toml(e.g."0.2.0"→"0.2.1"). -
The same release PR keeps each adopter-facing reusable workflow's
flywheel_versiondefault aligned to that package version.python -m methodology_framework.prepare_flywheel_release --version X.Y.Zstamps every version-carrying artifact from that one input:pyproject.toml.flywheel/versionandsrc/methodology_framework/templates/.flywheel/version.flywheel/release-policy.jsonandsrc/methodology_framework/release_policy/release-policy.json- the wrapper templates under
src/methodology_framework/templates/github_workflows/wrappers/ README.mdanddocs/flywheel-skeleton-materializer.md— the adopter-facing self-reference literals (workflow/action refs,FlywheelIC==pins, action-input examples), whichtests/test_doc_literal_policy_tie.pyties to the release policy'slatest. Historical version mentions are deliberately not stamped. The pinned legs of.github/workflows/adopter-contract-smoke.ymlare not stamped: they track the policy'sminimum_supportedfloor by design.
Tie-test anchor choice (2026-08 retro): every self-reference tie test anchors to exactly one policy field, and picking it is the first design question when adding one — adopter-facing docs and wrapper pins tie to
latest; the contract-smoke's pinned legs tie tominimum_supported. The pattern has been applied with both anchors (METH-54, METH-64); choosing the wrong one silently inverts the invariant while every test stays green. -
Reviewer approves and merges the PR to
main. -
release.ymlfires on thepyproject.tomlchange, extracts the version, validates it as stable SemVer (^[0-9]+\.[0-9]+\.[0-9]+$), builds the package, creates an annotated tag such asv0.2.1, pushes the tag, and publishes to PyPI via OIDC Trusted Publishing.
Operator surface = PR review only. No manual git tag step, no manual
publish trigger for the normal path.
Fallback (operator-pushed tag)
If the pyproject.toml push trigger does not fire, an operator can run
release.yml manually with the stable SemVer version input.
Pre-release versions
The release.yml workflow only tags stable SemVer versions. Versions
containing -rc, -beta, -alpha, .dev, or .pre suffixes (or any
string not matching ^[0-9]+\.[0-9]+\.[0-9]+$) are logged and skipped.
Pre-release publishing is out of scope; if needed, a separate workflow
would handle pre-release tag patterns.
Cost tracking via agent_acus
The methodology's post-execution notes include an agent_acus field that
records per-story compute cost. Since agents cannot self-report ACU
consumption mid-session, a post-merge populator workflow backfills the
value from the Devin API after the session completes.
Adopter wiring (3 steps):
- Copy the template caller into your repo:
cp "$(python -c "import methodology_framework; import pathlib; \ print(pathlib.Path(methodology_framework.__file__).parent / \ 'templates/github_workflows/populate-story-acus-caller.yml')")" \ .github/workflows/populate-story-acus.yml
- Set the
DEVIN_ENTERPRISE_ACU_TOKENrepo secret (Settings > Secrets and variables > Actions > New repository secret) — the only supported ACU credential. - Pin the framework version in the caller's
uses:line, for exampleuses: invoice-cloud/Flywheel/.github/workflows/populate-story-acus.yml@v0.22.0. Do not passflywheel_versionfor normal adopter/intern repos; the reusable workflow owns the matchingFlywheelIC==0.22.0install.
The caller fires on merged PRs that touch story files, invokes the
reusable populate-story-acus.yml workflow, and opens a follow-on PR
with the populated ACU values.
Contributing
Framework evolution stories — new specs, CLI features, workflow improvements —
are filed as Markdown story files under docs/stories/.
See the directory README for the filing convention
(slug-named directories, frontmatter shape, depends_on hygiene). Stories
sync to the METH Jira project via the same sync workflow the framework
ships to adopters.
For the canonical story template and format spec, see
whiteout59/centralized-pipeline-ui/methodology/templates/story.md
and
whiteout59/centralized-pipeline-ui/methodology/devin-story-format.md.
Jira Story Sync Auth
Story sync authenticates with Jira OAuth client credentials — the only
supported path. It requires JIRA_CLOUD_ID plus both JIRA_OAUTH_CLIENT_ID
and JIRA_OAUTH_CLIENT_SECRET for client-credentials token exchange via
https://api.atlassian.com/oauth/token. Jira REST calls use the Atlassian API
gateway base URL https://api.atlassian.com/ex/jira/{JIRA_CLOUD_ID}.
Operators or the repo factory provision the Jira OAuth variable and secrets; intern/adopter repos should not manage these values directly.
Basic auth, JIRA_API_TOKEN, pre-minted JIRA_OAUTH_ACCESS_TOKEN overrides,
and the JIRA_AUTH_MODE switch were removed in METH-61.
Development
The project requires Python ≥ 3.12. On machines whose default python3 is
older, pip install -e ".[dev]" can spin for ~30 minutes in resolver
backtracking (observed twice on 3.10, driven by the vcrpy range). Use uv:
uv venv --python 3.12 .venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pip install -e ".[dev]"
pytest tests/ -v
ruff check src/methodology_framework
ruff format --check src/methodology_framework
mypy --strict src/methodology_framework
Adding a reusable workflow
A new file under .github/workflows/ declaring workflow_call is a registry
edit, not just a new file: the test suite enforces a reusable-workflow
contract, so the file must join the registries in tests/test_workflows.py
(REUSABLES_WITH_PYPI_INSTALL, runner-selection coverage) and carry the exact
Validate flywheel_version + Install FlywheelIC step shape. If it ships with
a managed wrapper/caller template, those join the skeleton inventory
(flywheel_skeleton.py) — and the wrapper is then automatically covered by the
producer-derived pin tests. A structurally correct reusable that skips
registration fails CI by design. Budget for this in any story that adds one.
When building a changed-files-based CI check, dogfood it against the
implementing PR's own diff before opening the PR — that practice caught a
path-normalization defect (lstrip("./") is a character strip, not a prefix
strip) in the post-exec gate's first run.
Shape-def expansion (v0.1.2+)
workflow.yaml now includes a statusCategory field on each status
(TODO, IN_PROGRESS, or DONE). The --apply handler validates
this field at load time and exits with a clear error if any status is
missing it.
custom_fields.yaml uses canonical shorthand types (text, string,
multi-value, numeric) that are mapped to fully-qualified Jira
identifiers at apply time. Fields may also specify a fully-qualified
type directly (any value containing : is passed through unchanged).
Recording new VCR cassettes
Integration tests under tests/integration/ replay pre-recorded VCR
cassettes in CI (no network access required). To record fresh cassettes
against a real Jira instance:
export JIRA_CLOUD_ID="<cloud-id>"
export JIRA_OAUTH_CLIENT_ID="<client-id>"
export JIRA_OAUTH_CLIENT_SECRET="<client-secret>"
PYTEST_VCR_MODE=record pytest tests/integration/ -v
Cassettes are stored at tests/fixtures/vcr/*.yaml. The VCR config in
tests/integration/conftest.py automatically strips Authorization
headers from recorded interactions. Never commit a cassette with a
real token in any header.
After recording, verify no credentials leaked:
grep -i 'authorization' tests/fixtures/vcr/*.yaml
# Expected: no output
License
Apache-2.0
Metadata
Release files for FlywheelIC 0.22.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flywheelic-0.22.0.tar.gz | 470.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flywheelic-0.22.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 744.6 kB
Release files / flywheelic-0.22.0.tar.gz
| Download URL | flywheelic-0.22.0.tar.gz |
|---|---|
| Size | 470.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e7f363a4ece1f0053ae64fc25124dea7b47d6b4dda984bf938795268c23165ff
|
|
BLAKE2b-256 checksum How to use checksums |
5883a6c9e33207cd65f04f77d02277ebd557098fbfd299f1325f738ae19e554b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / flywheelic-0.22.0-py3-none-any.whl
| Download URL | flywheelic-0.22.0-py3-none-any.whl |
|---|---|
| Size | 274.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
467f5db6779d8e755ad02e3f94d9cf81f2087f5df2a54a07469be509dbe8d18f
|
|
BLAKE2b-256 checksum How to use checksums |
9d8567616f5ef16f37571bc314c96fb12ffa1c3ef79a53ff0a67bfddd3dd93e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log