This file aligns development of the MSX Bridge provider with the
upstream music-assistant/server
standards. It is rendered from wrappers/CLAUDE.md.j2 in
trudenboy/ma-provider-tools
and is kept in sync across every provider repo — do not edit it here.
Provider-specific architecture, key flows, and gotchas live in
CLAUDE.local.md. Claude Code automatically picks up both
files when working in this repository.
This provider follows two layered AI policies:
- The Open Home Foundation upstream policy —
music-assistant/.github/AI_POLICY.md— governs everything that lands inmusic-assistant/server. - The ecosystem policy —
trudenboy/ma-provider-tools/AI_POLICY.md— governs work insidetrudenboy/ma-provider-*and the upstream boundary mechanics.
A per-provider summary lives in AI_POLICY.md (rendered
from wrappers/AI_POLICY.md.j2).
Operational rules for AI assistants working in this repo:
- A human owns every PR. The contributor must be able to explain every change in their own words; PRs that look like unreviewed AI output are closed. AI may be change title or description of pr.
- Never open, push to, comment on, or close anything in
music-assistant/*directly. The only path is theupstream-pr.ymlworkflow, which opens the PR as a draft with a human-attestation checklist. - Replies to human reviewers are written by humans. AI may prepare drafts, polish grammar and clarity; This applies only in upstream PRs not in our own provider-repo PRs.
- Replies to AI review comments may be AI-drafted. GitHub Copilot, code-scanning bots, and similar tools post AI output; replying with AI-drafted text is allowed because the conversation is AI ↔ AI. The contributor must still read the AI's reply and apply judgement before posting. If a human reviewer joins the same thread, rule 3 takes over from that point on — every reply after that human comment must be human-written.
- AI co-author trailers (
Co-Authored-By: <agent> ...) are encouraged in this repo's commits as honest disclosure. Use the identity of the agent that actually did the work — e.g.Co-Authored-By: Cursor <cursoragent@cursor.com>,Co-Authored-By: Claude <model> <noreply@anthropic.com>, or the line your tool documents. Do not copy another tool's example trailer, invent a model string, or duplicate a trailer your environment already injects. Wrong attribution is worse than omitting the trailer. These trailers are stripped at the upstream boundary byupstream-pr.yml.j2so they don't appear inmusic-assistant/serverhistory.
./scripts/setup.sh— initial setup (venv viauv, dependencies, pre-commit hooks). Re-run after pulling latest code.uv run pytest— run all testsuv run pytest provider/tests/<file>.py— run a specific test fileuv run ruff check provider/— lintuv run ruff format provider/— auto-formatuv run mypy provider/— type checkpre-commit run --all-files— full pre-commit gate
Always run pre-commit run --all-files after a code change to ensure the new
code adheres to the project standards.
Only use comments to explain complex, multi-line blocks of code. Do not comment obvious operations.
Use Sphinx-style docstrings with :param: syntax. For simple functions, a
single-line docstring is fine.
Don't explain inner workings of the code in the docstrings (you can use inline comments for that if/when needed). The docstring should provide clarity to the caller of the function/method, not explain how it works technically/internally.
def my_function(param1: str, param2: int, param3: bool = False) -> str:
"""
Brief one-line description of the function.
:param param1: Description of what param1 is used for.
:param param2: Description of what param2 is used for.
:param param3: Description of what param3 is used for.
"""Do not use Google-style (Args:) or bullet-style (- param:) docstrings.
AI assistants tend to generate Google-style by default — explicitly steer them
to Sphinx, and rewrite anything that slips through.
- All work-in-progress PRs target
feat/msx-bridge-player-provider(primary development branch). - Before opening a PR: run lint + tests +
pre-commit run --all-files. CI runsruff format --check, so pushing withoutruff formatis the most common red build.
All non-trivial changes go through a pull request — never push directly to
feat/msx-bridge-player-provider. Inside a PR, follow this loop:
- Self-review. Run at least one self-review pass on the diff (e.g. the
/code-reviewskill or an equivalent reviewer) before asking for human review. - Copilot triage. Check the PR for GitHub Copilot review comments. For each comment: analyze it, apply a fix when warranted, reply with a short justification, and resolve the thread. Copilot is an AI reviewer, so AI-drafted replies are allowed here (see AI Policy Alignment, rule 4). Replies to a human reviewer chiming into the same thread must be human-written from that point on.
- Changelog (+ maintainer-owned version). After review feedback is
addressed, add a
CHANGELOG.mdentry following the rules in Changelog Discipline below — in the same PR. TheVERSIONfile is owned by the maintainer (.github/CODEOWNERS) and protected onfeat/msx-bridge-player-provider: do not bump it in a contributor PR — aVERSIONchange requires the maintainer's Code-Owner approval to merge. The maintainer sets the version (typically at merge/release time, matching the changelog entry), and the release pipeline tags and publishes automatically when the newVERSIONlands onfeat/msx-bridge-player-provider. - Ask before merging. Always request explicit maintainer approval to
merge. Do not self-merge or enable auto-merge without it. (Auto-merge is
reserved for
distribute.yml-generated wrapper-sync PRs fromma-provider-tools.)
Follow-up commits driven by review (your own pass or Copilot's) land directly on the PR branch — no separate PR needed.
Never push to or open PRs against the upstream Music Assistant repo
(music-assistant/server — the true upstream) or the integration fork
(trudenboy/ma-server) without an explicit maintainer instruction. The
provider repo is the source of truth; sync to the integration fork and
upstream PR submission run automatically through ma-provider-tools
workflows (sync-to-fork.yml, upstream-pr.yml).
Reverse-sync (incoming). Contributions made upstream against the inlined
provider (someone editing music_assistant/providers/msx_bridge/ directly in
music-assistant/server) are detected and ported back here automatically by the
ma-provider-tools reverse-sync radar. They arrive as draft PRs on a
reverse-sync/<domain>-pr<N> branch, crediting the upstream author via
Co-authored-by. A PR labelled needs-human applied with conflicts: it
carries <<<<<<< markers (and the upstream change may need adapting to this
repo's current code) — resolve them, drop the label, and treat it like any other
PR (review, changelog, maintainer approval). VERSION / translations/en.json
are never touched by reverse-sync; bump them as usual. Do not manually
re-port a change the radar already opened a PR for.
This provider is intended to be inlined into
music_assistant/providers/msx_bridge upstream eventually — that is the
target shape, not a possibility. Any code that lints / type-checks here
must lint / type-check identically upstream.
When drafting or refining text for an upstream PR body in
music-assistant/server, the body MUST follow upstream's
PULL_REQUEST_TEMPLATE.md
skeleton, in this order:
# What does this implement/fix?— narrative goes under this heading.**Related issue (if applicable):**.## Types of changes— tick ≥ 1 box; multi-tick is supported (e.g.bugfix+enhancement+dependenciesfor cross-cutting releases).## Checklist. TheI have read and complied with the project's AI Policycheckbox is the human-attestation checkpoint; do not append a separate attestation block.
Upstream's pr-labels.yaml reads the ticked ## Types of changes
checkboxes to apply labels; the release-notes generator slots by
label. A body without these sections silently breaks both.
Per AI Policy Alignment rule 2, do not edit the upstream PR
directly — produce the corrected body and the human applies it
(via gh api repos/music-assistant/server/pulls/<N> -X PATCH -F body=@file.md).
ruff.toml, [tool.mypy], and [tool.codespell].skip mirror upstream
music-assistant/server/pyproject.toml and are regenerated by
ma-provider-tools (scripts/sync_upstream_config.py, weekly cron).
Do not hand-edit these in the provider repo — the
Check config sync GitHub Action will fail any PR that drifts. To change
a rule, open a PR in trudenboy/ma-provider-tools; once merged, the
distribute workflow propagates the change here.
Provider-specific carve-outs that do not drift: python_version,
packages = ["tests", "provider"], the [[tool.mypy.overrides]]
block, and codespell.ignore-words-list.
Repo-level metadata also flows through ma-provider-tools (fields
github_description, github_topics, github_homepage,
related_providers in providers.yml). They drive the GitHub "About"
block, the auto-synced README badge / quick-links / cross-links header
(between <!-- >>> ma-provider-tools sync (readme header) >>> -->
markers), and the docs-site landing hero. Do not edit GitHub repo
settings directly — open a PR in ma-provider-tools instead.
The Music Assistant badge in the README is a dynamic shields.io
endpoint hosted at
https://trudenboy.github.io/ma-provider-tools/badges/<domain>.json. A
4-hour cron in ma-provider-tools (update-ma-version-badges.yml)
refreshes the JSON to show which MA channel (stable / beta / dev) ships
the provider and at what MA version. The provider's own version pin is
included once a VERSION file has been synced alongside manifest.json
in each channel via the existing sync-to-fork / upstream-pr
workflows. Do not edit the badge URL or the JSON manually.
Non-trivial features go through a written spec before code. The template
lives at specs/feature-spec.md (auto-synced from
ma-provider-tools/wrappers/feature-spec.md.j2 — do not edit it in
place; changes go through ma-provider-tools).
- When required. Every PR whose Conventional-Commit type is
feat:MUST add a new filespecs/<state>/<NNNN>-<kebab-slug>.mdderived from the template. Typesfix:,chore:,docs:,test:,refactor:,perf:,build:,ci:are exempt. When a borderlinefeat:legitimately needs no spec (e.g. a pure wire-up, a one-line enum addition), state "no spec required because " in the PR body so reviewers can confirm. - Enforcement. P0 (now): this is a documented rule that AI agents and reviewers uphold. P1 (next release cycle): the rule is promoted to a hard CI gate after observing false-positive patterns.
- T-shirt sizing — the
size:frontmatter declares the required artifacts:- S (≤ 10 min effort): Problem Statement + Acceptance Criteria (≥ 5 bullets) + Test Plan.
- M (10–20 min): S + Sequence Diagram.
- L (> 20 min): M + Data Model.
- WIP = 1. At most one spec lives in
specs/inprogress/at any time. Move it tospecs/done/(or revert tospecs/todo/) before starting the next. - Numbering. Zero-padded 4-digit (
0001,0002, …), monotonic per repo. Gaps are fine. - Optional
feature_id:in the frontmatter links the spec to aProviderFeatureenum member. When set, theCheck feature consistencyworkflow cross-validates againstSUPPORTED_FEATURESin this repo and against the correspondingproviders.ymlfeatures[].feature_identry inma-provider-tools. A mismatch fails CI. - Idempotency. All changes flow through the spec — no "I'll patch it in copilot mode and update the spec later". If the implementation diverges from the spec, update the spec in the same PR.
Use red / green / refactor TDD for all new features and bug fixes:
- Red. Write the test first. Confirm it fails before writing implementation.
- Green. Write the minimal code to make the test pass.
- Refactor. Clean up the implementation while keeping tests green.
- Never modify existing tests to make them pass. If a test fails, fix the implementation. If a test is genuinely wrong, explain why in the commit message before changing it.
- Never write tautological tests — tests that reimplement the logic under test locally and assert on that local copy. Always call the real function / method.
- Test real behaviour, not mocks. Avoid over-mocking: if a test only
verifies that mocks return what they were configured to return, it
tests nothing. For Music Assistant providers this means: prefer real
fixtures (
tests/fixtures/*.json) andsyrupysnapshots over hand- rolled mock graphs whenever the parser / mapper under test is pure. - Every test must be able to fail. If removing the implementation doesn't break the test, the test is useless.
- Bug fix flow. First write a test that reproduces the bug (red), then fix it (green). The reproducer becomes the regression guard.
CHANGELOG.md follows
Keep a Changelog and is the
input the release pipeline reads when it composes GitHub Release notes.
Treat the rules below as falsifiable invariants — fix the entry, do not
relax the rule.
- Bare canonical headings only. Use
### Added,### Changed,### Deprecated,### Removed,### Fixed,### Security— nothing else. No### Improved,### Refactored,### Tests, no trailing prose like### Fixed — long sentence. - Canonical order. Inside each version block, categories appear in this order: Added → Changed → Deprecated → Removed → Fixed → Security. Skip categories that are empty; never reorder.
- No private symbols, no internal paths in bullets. Forbidden:
_underscore_func,provider/foo.py,ClassName.method. Allowed: uppercase config keys (e.g.CONF_QUALITY), backticked user-facing filenames (`pyproject.toml`,`manifest.json`), markdown links. Internal paths rot through refactors — describe what the user observes instead. - No process-noise headings or bullets. Things like
Code-review polish,Round 2 fixes,Copilot review on PR #N,Tests,Internalbelong ingit log, not the changelog. If a review surfaced a real bug, file it under### Fixed. - No prose between
## [version]and the first### Category. Release-note tooling collects bullets that appear after the first category heading; intro paragraphs are silently dropped. - One entry per version. For the release that lands these changes
(
X.Y.Z→X.Y.(Z+1)), add a single## [X.Y.(Z+1)] - YYYY-MM-DDblock above the existing top-most version, populated with the categories the PR touches. Do not retroactively edit older version blocks. The matchingVERSIONbump is applied by the maintainer (see Pull Request Workflow step 3).
Music Assistant stores its data in $HOME/.musicassistant/. When debugging
locally:
- Logs:
$HOME/.musicassistant/musicassistant.log(current),musicassistant.log.1,.log.2, etc. for older rotated logs. - Database:
$HOME/.musicassistant/library.db— query viasqlite3. Only execute SELECT queries — never write to a live database.